summaryrefslogtreecommitdiffstats
path: root/sc/source/core/data
diff options
context:
space:
mode:
Diffstat (limited to 'sc/source/core/data')
-rw-r--r--sc/source/core/data/attarray.cxx2690
-rw-r--r--sc/source/core/data/attrib.cxx889
-rw-r--r--sc/source/core/data/autonamecache.cxx90
-rw-r--r--sc/source/core/data/bcaslot.cxx1300
-rw-r--r--sc/source/core/data/bigrange.cxx25
-rw-r--r--sc/source/core/data/celltextattr.cxx21
-rw-r--r--sc/source/core/data/cellvalue.cxx691
-rw-r--r--sc/source/core/data/cellvalues.cxx357
-rw-r--r--sc/source/core/data/clipcontext.cxx440
-rw-r--r--sc/source/core/data/clipparam.cxx189
-rw-r--r--sc/source/core/data/colcontainer.cxx55
-rw-r--r--sc/source/core/data/colorscale.cxx1445
-rw-r--r--sc/source/core/data/column.cxx3391
-rw-r--r--sc/source/core/data/column2.cxx3793
-rw-r--r--sc/source/core/data/column3.cxx3726
-rw-r--r--sc/source/core/data/column4.cxx2225
-rw-r--r--sc/source/core/data/columniterator.cxx209
-rw-r--r--sc/source/core/data/columnset.cxx67
-rw-r--r--sc/source/core/data/columnspanset.cxx369
-rw-r--r--sc/source/core/data/compressedarray.cxx418
-rw-r--r--sc/source/core/data/conditio.cxx2335
-rw-r--r--sc/source/core/data/dbdocutl.cxx201
-rw-r--r--sc/source/core/data/dociter.cxx1779
-rw-r--r--sc/source/core/data/docparam.cxx25
-rw-r--r--sc/source/core/data/docpool.cxx619
-rw-r--r--sc/source/core/data/documen2.cxx1471
-rw-r--r--sc/source/core/data/documen3.cxx2155
-rw-r--r--sc/source/core/data/documen4.cxx1367
-rw-r--r--sc/source/core/data/documen5.cxx657
-rw-r--r--sc/source/core/data/documen6.cxx210
-rw-r--r--sc/source/core/data/documen7.cxx621
-rw-r--r--sc/source/core/data/documen8.cxx1329
-rw-r--r--sc/source/core/data/documen9.cxx681
-rw-r--r--sc/source/core/data/document.cxx7089
-rw-r--r--sc/source/core/data/document10.cxx1106
-rw-r--r--sc/source/core/data/documentimport.cxx859
-rw-r--r--sc/source/core/data/documentstreamaccess.cxx147
-rw-r--r--sc/source/core/data/dpcache.cxx1522
-rw-r--r--sc/source/core/data/dpdimsave.cxx791
-rw-r--r--sc/source/core/data/dpfilteredcache.cxx433
-rw-r--r--sc/source/core/data/dpglobal.cxx20
-rw-r--r--sc/source/core/data/dpgroup.cxx1030
-rw-r--r--sc/source/core/data/dpitemdata.cxx371
-rw-r--r--sc/source/core/data/dpnumgroupinfo.cxx35
-rw-r--r--sc/source/core/data/dpobject.cxx3952
-rw-r--r--sc/source/core/data/dpoutput.cxx1781
-rw-r--r--sc/source/core/data/dpoutputgeometry.cxx255
-rw-r--r--sc/source/core/data/dpresfilter.cxx267
-rw-r--r--sc/source/core/data/dpsave.cxx1383
-rw-r--r--sc/source/core/data/dpsdbtab.cxx169
-rw-r--r--sc/source/core/data/dpshttab.cxx323
-rw-r--r--sc/source/core/data/dptabdat.cxx292
-rw-r--r--sc/source/core/data/dptabres.cxx4117
-rw-r--r--sc/source/core/data/dptabsrc.cxx2613
-rw-r--r--sc/source/core/data/dputil.cxx420
-rw-r--r--sc/source/core/data/drawpage.cxx51
-rw-r--r--sc/source/core/data/drwlayer.cxx2676
-rw-r--r--sc/source/core/data/edittextiterator.cxx97
-rw-r--r--sc/source/core/data/fillinfo.cxx1064
-rw-r--r--sc/source/core/data/formulacell.cxx5571
-rw-r--r--sc/source/core/data/formulaiter.cxx77
-rw-r--r--sc/source/core/data/funcdesc.cxx1267
-rw-r--r--sc/source/core/data/global.cxx1160
-rw-r--r--sc/source/core/data/global2.cxx598
-rw-r--r--sc/source/core/data/globalx.cxx142
-rw-r--r--sc/source/core/data/grouptokenconverter.cxx316
-rw-r--r--sc/source/core/data/listenercontext.cxx95
-rw-r--r--sc/source/core/data/markarr.cxx462
-rw-r--r--sc/source/core/data/markdata.cxx950
-rw-r--r--sc/source/core/data/markmulti.cxx485
-rw-r--r--sc/source/core/data/mtvcellfunc.cxx31
-rw-r--r--sc/source/core/data/mtvelements.cxx198
-rw-r--r--sc/source/core/data/olinetab.cxx873
-rw-r--r--sc/source/core/data/pagepar.cxx56
-rw-r--r--sc/source/core/data/patattr.cxx1422
-rw-r--r--sc/source/core/data/pivot2.cxx161
-rw-r--r--sc/source/core/data/poolhelp.cxx118
-rw-r--r--sc/source/core/data/postit.cxx1306
-rw-r--r--sc/source/core/data/queryevaluator.cxx961
-rw-r--r--sc/source/core/data/queryiter.cxx1476
-rw-r--r--sc/source/core/data/refupdatecontext.cxx139
-rw-r--r--sc/source/core/data/rowheightcontext.cxx38
-rw-r--r--sc/source/core/data/segmenttree.cxx711
-rw-r--r--sc/source/core/data/sheetevents.cxx123
-rw-r--r--sc/source/core/data/simpleformulacalc.cxx155
-rw-r--r--sc/source/core/data/sortparam.cxx306
-rw-r--r--sc/source/core/data/stlpool.cxx447
-rw-r--r--sc/source/core/data/stlsheet.cxx303
-rw-r--r--sc/source/core/data/subtotalparam.cxx199
-rw-r--r--sc/source/core/data/tabbgcolor.cxx36
-rw-r--r--sc/source/core/data/table1.cxx2729
-rw-r--r--sc/source/core/data/table2.cxx4372
-rw-r--r--sc/source/core/data/table3.cxx3099
-rw-r--r--sc/source/core/data/table4.cxx2990
-rw-r--r--sc/source/core/data/table5.cxx1295
-rw-r--r--sc/source/core/data/table6.cxx1155
-rw-r--r--sc/source/core/data/table7.cxx652
-rw-r--r--sc/source/core/data/tabprotection.cxx724
-rw-r--r--sc/source/core/data/types.cxx32
-rw-r--r--sc/source/core/data/userdat.cxx51
-rw-r--r--sc/source/core/data/validat.cxx1087
101 files changed, 107071 insertions, 0 deletions
diff --git a/sc/source/core/data/attarray.cxx b/sc/source/core/data/attarray.cxx
new file mode 100644
index 000000000..d5eaa1906
--- /dev/null
+++ b/sc/source/core/data/attarray.cxx
@@ -0,0 +1,2690 @@
+/* -*- 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 <attarray.hxx>
+#include <scitems.hxx>
+#include <editeng/borderline.hxx>
+#include <editeng/boxitem.hxx>
+#include <editeng/lineitem.hxx>
+#include <editeng/shaditem.hxx>
+#include <editeng/editobj.hxx>
+#include <editeng/justifyitem.hxx>
+#include <osl/diagnose.h>
+#include <svl/poolcach.hxx>
+#include <sfx2/objsh.hxx>
+
+#include <global.hxx>
+#include <document.hxx>
+#include <docpool.hxx>
+#include <patattr.hxx>
+#include <stlsheet.hxx>
+#include <stlpool.hxx>
+#include <markarr.hxx>
+#include <globstr.hrc>
+#include <scresid.hxx>
+#include <segmenttree.hxx>
+#include <editdataarray.hxx>
+#include <cellvalue.hxx>
+#include <editutil.hxx>
+#include <mtvelements.hxx>
+#include <memory>
+
+using ::editeng::SvxBorderLine;
+
+ScAttrArray::ScAttrArray( SCCOL nNewCol, SCTAB nNewTab, ScDocument& rDoc, ScAttrArray* pDefaultColAttrArray ) :
+ nCol( nNewCol ),
+ nTab( nNewTab ),
+ rDocument( rDoc )
+{
+ if ( nCol == -1 || !pDefaultColAttrArray || pDefaultColAttrArray->mvData.empty() )
+ return;
+
+ ScAddress aAdrStart( nCol, 0, nTab );
+ ScAddress aAdrEnd( nCol, 0, nTab );
+ mvData.resize( pDefaultColAttrArray->mvData.size() );
+ for ( size_t nIdx = 0; nIdx < pDefaultColAttrArray->mvData.size(); ++nIdx )
+ {
+ mvData[nIdx].nEndRow = pDefaultColAttrArray->mvData[nIdx].nEndRow;
+ ScPatternAttr aNewPattern( *(pDefaultColAttrArray->mvData[nIdx].pPattern) );
+ mvData[nIdx].pPattern = &rDocument.GetPool()->Put( aNewPattern );
+ bool bNumFormatChanged = false;
+ if ( ScGlobal::CheckWidthInvalidate( bNumFormatChanged,
+ mvData[nIdx].pPattern->GetItemSet(), rDocument.GetDefPattern()->GetItemSet() ) )
+ {
+ aAdrStart.SetRow( nIdx ? mvData[nIdx-1].nEndRow+1 : 0 );
+ aAdrEnd.SetRow( mvData[nIdx].nEndRow );
+ rDocument.InvalidateTextWidth( &aAdrStart, &aAdrEnd, bNumFormatChanged );
+ }
+ }
+}
+
+ScAttrArray::~ScAttrArray()
+{
+#if DEBUG_SC_TESTATTRARRAY
+ TestData();
+#endif
+
+ ScDocumentPool* pDocPool = rDocument.GetPool();
+ for (auto const & rEntry : mvData)
+ pDocPool->Remove(*rEntry.pPattern);
+}
+
+#if DEBUG_SC_TESTATTRARRAY
+void ScAttrArray::TestData() const
+{
+
+ sal_uInt16 nErr = 0;
+ SCSIZE nPos;
+ for (nPos=0; nPos<nCount; nPos++)
+ {
+ if (nPos > 0)
+ if (mvData[nPos].pPattern == mvData[nPos-1].pPattern || mvData[nPos].nRow <= mvData[nPos-1].nRow)
+ ++nErr;
+ if (mvData[nPos].pPattern->Which() != ATTR_PATTERN)
+ ++nErr;
+ }
+ if ( nPos && mvData[nPos-1].nRow != rDocument.MaxRow() )
+ ++nErr;
+
+ SAL_WARN_IF( nErr, "sc", nErr << " errors in attribute array, column " << nCol );
+}
+#endif
+
+void ScAttrArray::SetDefaultIfNotInit( SCSIZE nNeeded )
+{
+ if ( !mvData.empty() )
+ return;
+
+ SCSIZE nNewLimit = std::max<SCSIZE>( SC_ATTRARRAY_DELTA, nNeeded );
+ mvData.reserve( nNewLimit );
+ mvData.emplace_back();
+ mvData[0].nEndRow = rDocument.MaxRow();
+ mvData[0].pPattern = rDocument.GetDefPattern(); // no put
+}
+
+void ScAttrArray::Reset( const ScPatternAttr* pPattern )
+{
+ ScDocumentPool* pDocPool = rDocument.GetPool();
+ ScAddress aAdrStart( nCol, 0, nTab );
+ ScAddress aAdrEnd ( nCol, 0, nTab );
+
+ for (SCSIZE i=0; i<mvData.size(); i++)
+ {
+ // ensure that attributing changes text width of cell
+ const ScPatternAttr* pOldPattern = mvData[i].pPattern;
+ if ( nCol != -1 )
+ {
+ bool bNumFormatChanged;
+ if ( ScGlobal::CheckWidthInvalidate( bNumFormatChanged,
+ pPattern->GetItemSet(), pOldPattern->GetItemSet() ) )
+ {
+ aAdrStart.SetRow( i ? mvData[i-1].nEndRow+1 : 0 );
+ aAdrEnd .SetRow( mvData[i].nEndRow );
+ rDocument.InvalidateTextWidth( &aAdrStart, &aAdrEnd, bNumFormatChanged );
+ }
+ }
+ pDocPool->Remove(*pOldPattern);
+ }
+ mvData.resize(0);
+
+ rDocument.SetStreamValid(nTab, false);
+
+ mvData.resize(1);
+ const ScPatternAttr* pNewPattern = &pDocPool->Put(*pPattern);
+ mvData[0].nEndRow = rDocument.MaxRow();
+ mvData[0].pPattern = pNewPattern;
+}
+
+bool ScAttrArray::Concat(SCSIZE nPos)
+{
+ bool bRet = false;
+ if (nPos < mvData.size())
+ {
+ if (nPos > 0)
+ {
+ if (mvData[nPos - 1].pPattern == mvData[nPos].pPattern)
+ {
+ mvData[nPos - 1].nEndRow = mvData[nPos].nEndRow;
+ rDocument.GetPool()->Remove(*mvData[nPos].pPattern);
+ mvData.erase(mvData.begin() + nPos);
+ nPos--;
+ bRet = true;
+ }
+ }
+ if (nPos + 1 < mvData.size())
+ {
+ if (mvData[nPos + 1].pPattern == mvData[nPos].pPattern)
+ {
+ mvData[nPos].nEndRow = mvData[nPos + 1].nEndRow;
+ rDocument.GetPool()->Remove(*mvData[nPos].pPattern);
+ mvData.erase(mvData.begin() + nPos + 1);
+ bRet = true;
+ }
+ }
+ }
+ return bRet;
+}
+
+/*
+ * nCount is the number of runs of different attribute combinations;
+ * no attribute in a column => nCount==1, one attribute somewhere => nCount == 3
+ * (ie. one run with no attribute + one attribute + another run with no attribute)
+ * so a range of identical attributes is only one entry in ScAttrArray.
+ *
+ * Iterative implementation of Binary Search
+ * The same implementation was used inside ScMarkArray::Search().
+ */
+
+bool ScAttrArray::Search( SCROW nRow, SCSIZE& nIndex ) const
+{
+/* auto it = std::lower_bound(mvData.begin(), mvData.end(), nRow,
+ [] (const ScAttrEntry &r1, SCROW nRow)
+ { return r1.nEndRow < nRow; } );
+ if (it != mvData.end())
+ nIndex = it - mvData.begin();
+ return it != mvData.end(); */
+
+ if (mvData.size() == 1)
+ {
+ nIndex = 0;
+ return true;
+ }
+
+ tools::Long nHi = static_cast<tools::Long>(mvData.size()) - 1;
+ tools::Long i = 0;
+ tools::Long nLo = 0;
+
+ while ( nLo <= nHi )
+ {
+ i = (nLo + nHi) / 2;
+
+ if (mvData[i].nEndRow < nRow)
+ {
+ // If [nRow] greater, ignore left half
+ nLo = i + 1;
+ }
+ else if ((i > 0) && (mvData[i - 1].nEndRow >= nRow))
+ {
+ // If [nRow] is smaller, ignore right half
+ nHi = i - 1;
+ }
+ else
+ {
+ // found
+ nIndex=static_cast<SCSIZE>(i);
+ return true;
+ }
+ }
+
+ nIndex=0;
+ return false;
+}
+
+const ScPatternAttr* ScAttrArray::GetPattern( SCROW nRow ) const
+{
+ if ( mvData.empty() )
+ {
+ if ( !rDocument.ValidRow(nRow) )
+ return nullptr;
+ return rDocument.GetDefPattern();
+ }
+ SCSIZE i;
+ if (Search( nRow, i ))
+ return mvData[i].pPattern;
+ else
+ return nullptr;
+}
+
+const ScPatternAttr* ScAttrArray::GetPatternRange( SCROW& rStartRow,
+ SCROW& rEndRow, SCROW nRow ) const
+{
+ if ( mvData.empty() )
+ {
+ if ( !rDocument.ValidRow( nRow ) )
+ return nullptr;
+ rStartRow = 0;
+ rEndRow = rDocument.MaxRow();
+ return rDocument.GetDefPattern();
+ }
+ SCSIZE nIndex;
+ if ( Search( nRow, nIndex ) )
+ {
+ if ( nIndex > 0 )
+ rStartRow = mvData[nIndex-1].nEndRow + 1;
+ else
+ rStartRow = 0;
+ rEndRow = mvData[nIndex].nEndRow;
+ return mvData[nIndex].pPattern;
+ }
+ return nullptr;
+}
+
+void ScAttrArray::AddCondFormat( SCROW nStartRow, SCROW nEndRow, sal_uInt32 nIndex )
+{
+ if(!rDocument.ValidRow(nStartRow) || !rDocument.ValidRow(nEndRow))
+ return;
+
+ if(nEndRow < nStartRow)
+ return;
+
+ SCROW nTempStartRow = nStartRow;
+ SCROW nTempEndRow = nEndRow;
+
+ do
+ {
+ const ScPatternAttr* pPattern = GetPattern(nTempStartRow);
+
+ std::unique_ptr<ScPatternAttr> pNewPattern;
+ if(pPattern)
+ {
+ pNewPattern.reset( new ScPatternAttr(*pPattern) );
+ SCROW nPatternStartRow;
+ SCROW nPatternEndRow;
+ GetPatternRange( nPatternStartRow, nPatternEndRow, nTempStartRow );
+
+ nTempEndRow = std::min<SCROW>( nPatternEndRow, nEndRow );
+ if (const ScCondFormatItem* pItem = pPattern->GetItemSet().GetItemIfSet( ATTR_CONDITIONAL ))
+ {
+ ScCondFormatIndexes const & rCondFormatData = pItem->GetCondFormatData();
+ if (rCondFormatData.find(nIndex) == rCondFormatData.end())
+ {
+ ScCondFormatIndexes aNewCondFormatData;
+ aNewCondFormatData.reserve(rCondFormatData.size()+1);
+ aNewCondFormatData = rCondFormatData;
+ aNewCondFormatData.insert(nIndex);
+ ScCondFormatItem aItem( std::move(aNewCondFormatData) );
+ pNewPattern->GetItemSet().Put( aItem );
+ }
+ }
+ else
+ {
+ ScCondFormatItem aItem(nIndex);
+ pNewPattern->GetItemSet().Put( aItem );
+ }
+ }
+ else
+ {
+ pNewPattern.reset( new ScPatternAttr( rDocument.GetPool() ) );
+ ScCondFormatItem aItem(nIndex);
+ pNewPattern->GetItemSet().Put( aItem );
+ nTempEndRow = nEndRow;
+ }
+
+ SetPatternArea( nTempStartRow, nTempEndRow, std::move(pNewPattern), true );
+ nTempStartRow = nTempEndRow + 1;
+ }
+ while(nTempEndRow < nEndRow);
+
+}
+
+void ScAttrArray::RemoveCondFormat( SCROW nStartRow, SCROW nEndRow, sal_uInt32 nIndex )
+{
+ if(!rDocument.ValidRow(nStartRow) || !rDocument.ValidRow(nEndRow))
+ return;
+
+ if(nEndRow < nStartRow)
+ return;
+
+ SCROW nTempStartRow = nStartRow;
+ SCROW nTempEndRow = nEndRow;
+
+ do
+ {
+ const ScPatternAttr* pPattern = GetPattern(nTempStartRow);
+
+ if(pPattern)
+ {
+ SCROW nPatternStartRow;
+ SCROW nPatternEndRow;
+ GetPatternRange( nPatternStartRow, nPatternEndRow, nTempStartRow );
+
+ nTempEndRow = std::min<SCROW>( nPatternEndRow, nEndRow );
+ if (const ScCondFormatItem* pItem = pPattern->GetItemSet().GetItemIfSet( ATTR_CONDITIONAL ))
+ {
+ auto pPatternAttr = std::make_unique<ScPatternAttr>( *pPattern );
+ if (nIndex == 0)
+ {
+ ScCondFormatItem aItem;
+ pPatternAttr->GetItemSet().Put( aItem );
+ SetPatternArea( nTempStartRow, nTempEndRow, std::move(pPatternAttr), true );
+ }
+ else
+ {
+ ScCondFormatIndexes const & rCondFormatData = pItem->GetCondFormatData();
+ auto itr = rCondFormatData.find(nIndex);
+ if(itr != rCondFormatData.end())
+ {
+ ScCondFormatIndexes aNewCondFormatData(rCondFormatData);
+ aNewCondFormatData.erase_at(std::distance(rCondFormatData.begin(), itr));
+ ScCondFormatItem aItem( std::move(aNewCondFormatData) );
+ pPatternAttr->GetItemSet().Put( aItem );
+ SetPatternArea( nTempStartRow, nTempEndRow, std::move(pPatternAttr), true );
+ }
+ }
+ }
+ }
+ else
+ {
+ return;
+ }
+
+ nTempStartRow = nTempEndRow + 1;
+ }
+ while(nTempEndRow < nEndRow);
+
+}
+
+void ScAttrArray::RemoveCellCharAttribs( SCROW nStartRow, SCROW nEndRow,
+ const ScPatternAttr* pPattern, ScEditDataArray* pDataArray )
+{
+ assert( nCol != -1 );
+ // cache mdds position, this doesn't modify the mdds container, just EditTextObject's
+ sc::ColumnBlockPosition blockPos;
+ rDocument.InitColumnBlockPosition( blockPos, nTab, nCol );
+ for (SCROW nRow = nStartRow; nRow <= nEndRow; ++nRow)
+ {
+ ScAddress aPos(nCol, nRow, nTab);
+ ScRefCellValue aCell(rDocument, aPos, blockPos);
+ if (aCell.meType != CELLTYPE_EDIT || !aCell.mpEditText)
+ continue;
+
+ std::unique_ptr<EditTextObject> pOldData;
+ if (pDataArray)
+ pOldData = aCell.mpEditText->Clone();
+
+ // Direct modification of cell content - something to watch out for if
+ // we decide to share edit text instances in the future.
+ ScEditUtil::RemoveCharAttribs(const_cast<EditTextObject&>(*aCell.mpEditText), *pPattern);
+
+ if (pDataArray)
+ {
+ std::unique_ptr<EditTextObject> pNewData = aCell.mpEditText->Clone();
+ pDataArray->AddItem(nTab, nCol, nRow, std::move(pOldData), std::move(pNewData));
+ }
+ }
+}
+
+bool ScAttrArray::Reserve( SCSIZE nReserve )
+{
+ if ( mvData.empty() && nReserve )
+ {
+ try {
+ mvData.reserve(nReserve);
+ mvData.emplace_back();
+ mvData[0].nEndRow = rDocument.MaxRow();
+ mvData[0].pPattern = rDocument.GetDefPattern(); // no put
+ return true;
+ } catch (std::bad_alloc const &) {
+ return false;
+ }
+ }
+ else if ( mvData.capacity() < nReserve )
+ {
+ try {
+ mvData.reserve(nReserve);
+ return true;
+ } catch (std::bad_alloc const &) {
+ return false;
+ }
+ }
+ else
+ return false;
+}
+
+const ScPatternAttr* ScAttrArray::SetPatternAreaImpl(SCROW nStartRow, SCROW nEndRow, const ScPatternAttr* pPattern,
+ bool bPutToPool, ScEditDataArray* pDataArray, bool bPassingOwnership )
+{
+ if (rDocument.ValidRow(nStartRow) && rDocument.ValidRow(nEndRow))
+ {
+ if (bPutToPool)
+ {
+ if (bPassingOwnership)
+ pPattern = &rDocument.GetPool()->Put(std::unique_ptr<ScPatternAttr>(const_cast<ScPatternAttr*>(pPattern)));
+ else
+ pPattern = &rDocument.GetPool()->Put(*pPattern);
+ }
+ if ((nStartRow == 0) && (nEndRow == rDocument.MaxRow()))
+ Reset(pPattern);
+ else
+ {
+ SCSIZE nNeeded = mvData.size() + 2;
+ SetDefaultIfNotInit( nNeeded );
+
+ ScAddress aAdrStart( nCol, 0, nTab );
+ ScAddress aAdrEnd ( nCol, 0, nTab );
+
+ SCSIZE ni = 0; // number of entries in beginning
+ SCSIZE nx = 0; // track position
+ SCROW ns = 0; // start row of track position
+ if ( nStartRow > 0 )
+ {
+ // skip beginning
+ SCSIZE nIndex;
+ Search( nStartRow, nIndex );
+ ni = nIndex;
+
+ if ( ni > 0 )
+ {
+ nx = ni;
+ ns = mvData[ni-1].nEndRow+1;
+ }
+ }
+
+ // ensure that attributing changes text width of cell
+ // otherwise, conditional formats need to be reset or deleted
+ bool bIsLoading = !rDocument.GetDocumentShell() || rDocument.GetDocumentShell()->IsLoading();
+ while ( ns <= nEndRow )
+ {
+ if ( nCol != -1 && !bIsLoading )
+ {
+ const SfxItemSet& rNewSet = pPattern->GetItemSet();
+ const SfxItemSet& rOldSet = mvData[nx].pPattern->GetItemSet();
+ bool bNumFormatChanged;
+ if ( ScGlobal::CheckWidthInvalidate( bNumFormatChanged,
+ rNewSet, rOldSet ) )
+ {
+ aAdrStart.SetRow( std::max(nStartRow,ns) );
+ aAdrEnd .SetRow( std::min(nEndRow,mvData[nx].nEndRow) );
+ rDocument.InvalidateTextWidth( &aAdrStart, &aAdrEnd, bNumFormatChanged );
+ }
+ }
+ ns = mvData[nx].nEndRow + 1;
+ nx++;
+ }
+
+ // continue modifying data array
+
+ SCSIZE nInsert; // insert position (MAXROWCOUNT := no insert)
+ bool bCombined = false;
+ bool bSplit = false;
+ if ( nStartRow > 0 )
+ {
+ nInsert = rDocument.MaxRow() + 1;
+ if ( mvData[ni].pPattern != pPattern )
+ {
+ if ( ni == 0 || (mvData[ni-1].nEndRow < nStartRow - 1) )
+ { // may be a split or a simple insert or just a shrink,
+ // row adjustment is done further down
+ if ( mvData[ni].nEndRow > nEndRow )
+ bSplit = true;
+ ni++;
+ nInsert = ni;
+ }
+ else if (mvData[ni - 1].nEndRow == nStartRow - 1)
+ nInsert = ni;
+ }
+ if ( ni > 0 && mvData[ni-1].pPattern == pPattern )
+ { // combine
+ mvData[ni-1].nEndRow = nEndRow;
+ nInsert = rDocument.MaxRow() + 1;
+ bCombined = true;
+ }
+ }
+ else
+ nInsert = 0;
+
+ SCSIZE nj = ni; // stop position of range to replace
+ while ( nj < mvData.size() && mvData[nj].nEndRow <= nEndRow )
+ nj++;
+ if ( !bSplit )
+ {
+ if ( nj < mvData.size() && mvData[nj].pPattern == pPattern )
+ { // combine
+ if ( ni > 0 )
+ {
+ if ( mvData[ni-1].pPattern == pPattern )
+ { // adjacent entries
+ mvData[ni-1].nEndRow = mvData[nj].nEndRow;
+ nj++;
+ }
+ else if ( ni == nInsert )
+ mvData[ni-1].nEndRow = nStartRow - 1; // shrink
+ }
+ nInsert = rDocument.MaxRow() + 1;
+ bCombined = true;
+ }
+ else if ( ni > 0 && ni == nInsert )
+ mvData[ni-1].nEndRow = nStartRow - 1; // shrink
+ }
+ ScDocumentPool* pDocPool = rDocument.GetPool();
+ if ( bSplit )
+ { // duplicate split entry in pool
+ pDocPool->Put( *mvData[ni-1].pPattern );
+ }
+ if ( ni < nj )
+ { // remove middle entries
+ for ( SCSIZE nk=ni; nk<nj; nk++)
+ { // remove entries from pool
+ pDocPool->Remove( *mvData[nk].pPattern );
+ }
+ if ( !bCombined )
+ { // replace one entry
+ mvData[ni].nEndRow = nEndRow;
+ mvData[ni].pPattern = pPattern;
+ ni++;
+ nInsert = rDocument.MaxRow() + 1;
+ }
+ if ( ni < nj )
+ { // remove entries
+ mvData.erase( mvData.begin() + ni, mvData.begin() + nj);
+ }
+ }
+
+ if ( nInsert < sal::static_int_cast<SCSIZE>(rDocument.MaxRow() + 1) )
+ { // insert or append new entry
+ if ( nInsert <= mvData.size() )
+ {
+ if ( !bSplit )
+ mvData.emplace(mvData.begin() + nInsert);
+ else
+ {
+ mvData.insert(mvData.begin() + nInsert, 2, ScAttrEntry());
+ mvData[nInsert+1] = mvData[nInsert-1];
+ }
+ }
+ if ( nInsert )
+ mvData[nInsert-1].nEndRow = nStartRow - 1;
+ mvData[nInsert].nEndRow = nEndRow;
+ mvData[nInsert].pPattern = pPattern;
+
+ // Remove character attributes from these cells if the pattern
+ // is applied during normal session.
+ if (pDataArray && nCol != -1)
+ RemoveCellCharAttribs(nStartRow, nEndRow, pPattern, pDataArray);
+ }
+
+ rDocument.SetStreamValid(nTab, false);
+ }
+ }
+
+#if DEBUG_SC_TESTATTRARRAY
+ TestData();
+#endif
+ return pPattern;
+}
+
+void ScAttrArray::ApplyStyleArea( SCROW nStartRow, SCROW nEndRow, const ScStyleSheet& rStyle )
+{
+ if (!(rDocument.ValidRow(nStartRow) && rDocument.ValidRow(nEndRow)))
+ return;
+
+ SetDefaultIfNotInit();
+ SCSIZE nPos;
+ SCROW nStart=0;
+ if (!Search( nStartRow, nPos ))
+ {
+ OSL_FAIL("Search Failure");
+ return;
+ }
+
+ ScAddress aAdrStart( nCol, 0, nTab );
+ ScAddress aAdrEnd ( nCol, 0, nTab );
+
+ do
+ {
+ const ScPatternAttr* pOldPattern = mvData[nPos].pPattern;
+ std::unique_ptr<ScPatternAttr> pNewPattern(new ScPatternAttr(*pOldPattern));
+ pNewPattern->SetStyleSheet(const_cast<ScStyleSheet*>(&rStyle));
+ SCROW nY1 = nStart;
+ SCROW nY2 = mvData[nPos].nEndRow;
+ nStart = mvData[nPos].nEndRow + 1;
+
+ if ( *pNewPattern == *pOldPattern )
+ {
+ // keep the original pattern (might be default)
+ // pNewPattern is deleted below
+ nPos++;
+ }
+ else if ( nY1 < nStartRow || nY2 > nEndRow )
+ {
+ if (nY1 < nStartRow) nY1=nStartRow;
+ if (nY2 > nEndRow) nY2=nEndRow;
+ SetPatternArea( nY1, nY2, std::move(pNewPattern), true );
+ Search( nStart, nPos );
+ }
+ else
+ {
+ if ( nCol != -1 )
+ {
+ // ensure attributing changes text width of cell; otherwise
+ // there aren't (yet) template format changes
+ const SfxItemSet& rNewSet = pNewPattern->GetItemSet();
+ const SfxItemSet& rOldSet = pOldPattern->GetItemSet();
+
+ bool bNumFormatChanged;
+ if ( ScGlobal::CheckWidthInvalidate( bNumFormatChanged,
+ rNewSet, rOldSet ) )
+ {
+ aAdrStart.SetRow( nPos ? mvData[nPos-1].nEndRow+1 : 0 );
+ aAdrEnd .SetRow( mvData[nPos].nEndRow );
+ rDocument.InvalidateTextWidth( &aAdrStart, &aAdrEnd, bNumFormatChanged );
+ }
+ }
+
+ rDocument.GetPool()->Remove(*mvData[nPos].pPattern);
+ mvData[nPos].pPattern = &rDocument.GetPool()->Put(*pNewPattern);
+ if (Concat(nPos))
+ Search(nStart, nPos);
+ else
+ nPos++;
+ }
+ }
+ while ((nStart <= nEndRow) && (nPos < mvData.size()));
+
+ rDocument.SetStreamValid(nTab, false);
+
+#if DEBUG_SC_TESTATTRARRAY
+ TestData();
+#endif
+}
+
+ // const cast, otherwise it will be too inefficient/complicated
+static void SetLineColor(SvxBorderLine const * dest, Color c)
+{
+ if (dest)
+ {
+ const_cast<SvxBorderLine*>(dest)->SetColor(c);
+ }
+}
+
+static void SetLine(const SvxBorderLine* dest, const SvxBorderLine* src)
+{
+ if (dest)
+ {
+ SvxBorderLine* pCast = const_cast<SvxBorderLine*>(dest);
+ pCast->SetBorderLineStyle( src->GetBorderLineStyle() );
+ pCast->SetWidth( src->GetWidth() );
+ }
+}
+
+void ScAttrArray::ApplyLineStyleArea( SCROW nStartRow, SCROW nEndRow,
+ const SvxBorderLine* pLine, bool bColorOnly )
+{
+ if ( bColorOnly && !pLine )
+ return;
+
+ if (!(rDocument.ValidRow(nStartRow) && rDocument.ValidRow(nEndRow)))
+ return;
+
+ SCSIZE nPos;
+ SCROW nStart=0;
+ SetDefaultIfNotInit();
+ if (!Search( nStartRow, nPos ))
+ {
+ OSL_FAIL("Search failure");
+ return;
+ }
+
+ do
+ {
+ const ScPatternAttr* pOldPattern = mvData[nPos].pPattern;
+ const SfxItemSet& rOldSet = pOldPattern->GetItemSet();
+ const SvxBoxItem* pBoxItem = rOldSet.GetItemIfSet( ATTR_BORDER );
+ const SvxLineItem* pTLBRItem = rOldSet.GetItemIfSet( ATTR_BORDER_TLBR );
+ const SvxLineItem* pBLTRItem = rOldSet.GetItemIfSet( ATTR_BORDER_BLTR );
+
+ if ( pBoxItem || pTLBRItem || pBLTRItem )
+ {
+ std::unique_ptr<ScPatternAttr> pNewPattern(new ScPatternAttr(*pOldPattern));
+ SfxItemSet& rNewSet = pNewPattern->GetItemSet();
+ SCROW nY1 = nStart;
+ SCROW nY2 = mvData[nPos].nEndRow;
+
+ std::unique_ptr<SvxBoxItem> pNewBoxItem( pBoxItem ? pBoxItem->Clone() : nullptr);
+ std::unique_ptr<SvxLineItem> pNewTLBRItem( pTLBRItem ? pTLBRItem->Clone() : nullptr);
+ std::unique_ptr<SvxLineItem> pNewBLTRItem(pBLTRItem ? pBLTRItem->Clone() : nullptr);
+
+ // fetch line and update attributes with parameters
+
+ if ( !pLine )
+ {
+ if( pNewBoxItem )
+ {
+ if ( pNewBoxItem->GetTop() ) pNewBoxItem->SetLine( nullptr, SvxBoxItemLine::TOP );
+ if ( pNewBoxItem->GetBottom() ) pNewBoxItem->SetLine( nullptr, SvxBoxItemLine::BOTTOM );
+ if ( pNewBoxItem->GetLeft() ) pNewBoxItem->SetLine( nullptr, SvxBoxItemLine::LEFT );
+ if ( pNewBoxItem->GetRight() ) pNewBoxItem->SetLine( nullptr, SvxBoxItemLine::RIGHT );
+ }
+ if( pNewTLBRItem && pNewTLBRItem->GetLine() )
+ pNewTLBRItem->SetLine( nullptr );
+ if( pNewBLTRItem && pNewBLTRItem->GetLine() )
+ pNewBLTRItem->SetLine( nullptr );
+ }
+ else
+ {
+ if ( bColorOnly )
+ {
+ Color aColor( pLine->GetColor() );
+ if( pNewBoxItem )
+ {
+ SetLineColor( pNewBoxItem->GetTop(), aColor );
+ SetLineColor( pNewBoxItem->GetBottom(), aColor );
+ SetLineColor( pNewBoxItem->GetLeft(), aColor );
+ SetLineColor( pNewBoxItem->GetRight(), aColor );
+ }
+ if( pNewTLBRItem )
+ SetLineColor( pNewTLBRItem->GetLine(), aColor );
+ if( pNewBLTRItem )
+ SetLineColor( pNewBLTRItem->GetLine(), aColor );
+ }
+ else
+ {
+ if( pNewBoxItem )
+ {
+ SetLine( pNewBoxItem->GetTop(), pLine );
+ SetLine( pNewBoxItem->GetBottom(), pLine );
+ SetLine( pNewBoxItem->GetLeft(), pLine );
+ SetLine( pNewBoxItem->GetRight(), pLine );
+ }
+ if( pNewTLBRItem )
+ SetLine( pNewTLBRItem->GetLine(), pLine );
+ if( pNewBLTRItem )
+ SetLine( pNewBLTRItem->GetLine(), pLine );
+ }
+ }
+ if( pNewBoxItem ) rNewSet.Put( std::move(pNewBoxItem) );
+ if( pNewTLBRItem ) rNewSet.Put( std::move(pNewTLBRItem) );
+ if( pNewBLTRItem ) rNewSet.Put( std::move(pNewBLTRItem) );
+
+ nStart = mvData[nPos].nEndRow + 1;
+
+ if ( nY1 < nStartRow || nY2 > nEndRow )
+ {
+ if (nY1 < nStartRow) nY1=nStartRow;
+ if (nY2 > nEndRow) nY2=nEndRow;
+ SetPatternArea( nY1, nY2, std::move(pNewPattern), true );
+ Search( nStart, nPos );
+ }
+ else
+ {
+ // remove from pool ?
+ rDocument.GetPool()->Remove(*mvData[nPos].pPattern);
+ mvData[nPos].pPattern =
+ &rDocument.GetPool()->Put(std::move(pNewPattern));
+
+ if (Concat(nPos))
+ Search(nStart, nPos);
+ else
+ nPos++;
+ }
+ }
+ else
+ {
+ nStart = mvData[nPos].nEndRow + 1;
+ nPos++;
+ }
+ }
+ while ((nStart <= nEndRow) && (nPos < mvData.size()));
+}
+
+void ScAttrArray::ApplyCacheArea( SCROW nStartRow, SCROW nEndRow, SfxItemPoolCache* pCache, ScEditDataArray* pDataArray, bool* const pIsChanged )
+{
+#if DEBUG_SC_TESTATTRARRAY
+ TestData();
+#endif
+
+ if (!(rDocument.ValidRow(nStartRow) && rDocument.ValidRow(nEndRow)))
+ return;
+
+ SCSIZE nPos;
+ SCROW nStart=0;
+ SetDefaultIfNotInit();
+ if (!Search( nStartRow, nPos ))
+ {
+ OSL_FAIL("Search Failure");
+ return;
+ }
+
+ ScAddress aAdrStart( nCol, 0, nTab );
+ ScAddress aAdrEnd ( nCol, 0, nTab );
+
+ do
+ {
+ const ScPatternAttr* pOldPattern = mvData[nPos].pPattern;
+ const ScPatternAttr* pNewPattern = static_cast<const ScPatternAttr*>( &pCache->ApplyTo( *pOldPattern ) );
+ if (pNewPattern != pOldPattern)
+ {
+ SCROW nY1 = nStart;
+ SCROW nY2 = mvData[nPos].nEndRow;
+ nStart = mvData[nPos].nEndRow + 1;
+
+ if(pIsChanged)
+ *pIsChanged = true;
+
+ if ( nY1 < nStartRow || nY2 > nEndRow )
+ {
+ if (nY1 < nStartRow) nY1=nStartRow;
+ if (nY2 > nEndRow) nY2=nEndRow;
+ SetPatternArea( nY1, nY2, pNewPattern, false, pDataArray );
+ Search( nStart, nPos );
+ }
+ else
+ {
+ if ( nCol != -1 )
+ {
+ // ensure attributing changes text-width of cell
+
+ const SfxItemSet& rNewSet = pNewPattern->GetItemSet();
+ const SfxItemSet& rOldSet = pOldPattern->GetItemSet();
+
+ bool bNumFormatChanged;
+ if ( ScGlobal::CheckWidthInvalidate( bNumFormatChanged,
+ rNewSet, rOldSet ) )
+ {
+ aAdrStart.SetRow( nPos ? mvData[nPos-1].nEndRow+1 : 0 );
+ aAdrEnd .SetRow( mvData[nPos].nEndRow );
+ rDocument.InvalidateTextWidth( &aAdrStart, &aAdrEnd, bNumFormatChanged );
+ }
+ }
+
+ rDocument.GetPool()->Remove(*mvData[nPos].pPattern);
+ mvData[nPos].pPattern = pNewPattern;
+ if (Concat(nPos))
+ Search(nStart, nPos);
+ else
+ ++nPos;
+ }
+ }
+ else
+ {
+ nStart = mvData[nPos].nEndRow + 1;
+ ++nPos;
+ }
+ }
+ while (nStart <= nEndRow);
+
+ rDocument.SetStreamValid(nTab, false);
+
+#if DEBUG_SC_TESTATTRARRAY
+ TestData();
+#endif
+}
+
+void ScAttrArray::SetAttrEntries(std::vector<ScAttrEntry> && vNewData)
+{
+ ScDocumentPool* pDocPool = rDocument.GetPool();
+ for (auto const & rEntry : mvData)
+ pDocPool->Remove(*rEntry.pPattern);
+
+ mvData = std::move(vNewData);
+
+#ifdef DBG_UTIL
+ SCROW lastEndRow = -1;
+ for(const auto& entry : mvData)
+ { // Verify that the data is not corrupted.
+ assert(entry.nEndRow > lastEndRow);
+ lastEndRow = entry.nEndRow;
+ }
+#endif
+}
+
+static void lcl_MergeDeep( SfxItemSet& rMergeSet, const SfxItemSet& rSource )
+{
+ const SfxPoolItem* pNewItem;
+ const SfxPoolItem* pOldItem;
+ for (sal_uInt16 nId=ATTR_PATTERN_START; nId<=ATTR_PATTERN_END; nId++)
+ {
+ // pMergeSet has no parent
+ SfxItemState eOldState = rMergeSet.GetItemState( nId, false, &pOldItem );
+
+ if ( eOldState == SfxItemState::DEFAULT )
+ {
+ SfxItemState eNewState = rSource.GetItemState( nId, true, &pNewItem );
+ if ( eNewState == SfxItemState::SET )
+ {
+ if ( *pNewItem != rMergeSet.GetPool()->GetDefaultItem(nId) )
+ rMergeSet.InvalidateItem( nId );
+ }
+ }
+ else if ( eOldState == SfxItemState::SET ) // Item set
+ {
+ SfxItemState eNewState = rSource.GetItemState( nId, true, &pNewItem );
+ if ( eNewState == SfxItemState::SET )
+ {
+ if ( pNewItem != pOldItem ) // Both pulled
+ rMergeSet.InvalidateItem( nId );
+ }
+ else // Default
+ {
+ if ( *pOldItem != rSource.GetPool()->GetDefaultItem(nId) )
+ rMergeSet.InvalidateItem( nId );
+ }
+ }
+ // Dontcare remains Dontcare
+ }
+}
+
+void ScAttrArray::MergePatternArea( SCROW nStartRow, SCROW nEndRow,
+ ScMergePatternState& rState, bool bDeep ) const
+{
+ if (!(rDocument.ValidRow(nStartRow) && rDocument.ValidRow(nEndRow)))
+ return;
+
+ SCSIZE nPos = 0;
+ SCROW nStart=0;
+ if ( !mvData.empty() && !Search( nStartRow, nPos ) )
+ {
+ OSL_FAIL("Search failure");
+ return;
+ }
+
+ do
+ {
+ // similar patterns must not be repeated
+ const ScPatternAttr* pPattern = nullptr;
+ if ( !mvData.empty() )
+ pPattern = mvData[nPos].pPattern;
+ else
+ pPattern = rDocument.GetDefPattern();
+ if ( pPattern != rState.pOld1 && pPattern != rState.pOld2 )
+ {
+ const SfxItemSet& rThisSet = pPattern->GetItemSet();
+ if (rState.pItemSet)
+ {
+ rState.mbValidPatternId = false;
+ if (bDeep)
+ lcl_MergeDeep( *rState.pItemSet, rThisSet );
+ else
+ rState.pItemSet->MergeValues( rThisSet );
+ }
+ else
+ {
+ // first pattern - copied from parent
+ rState.pItemSet.emplace( *rThisSet.GetPool(), rThisSet.GetRanges() );
+ rState.pItemSet->Set( rThisSet, bDeep );
+ rState.mnPatternId = pPattern->GetKey();
+ }
+
+ rState.pOld2 = rState.pOld1;
+ rState.pOld1 = pPattern;
+ }
+
+ if ( !mvData.empty() )
+ nStart = mvData[nPos].nEndRow + 1;
+ else
+ nStart = rDocument.MaxRow() + 1;
+ ++nPos;
+ }
+ while (nStart <= nEndRow);
+}
+
+// assemble border
+
+static bool lcl_TestAttr( const SvxBorderLine* pOldLine, const SvxBorderLine* pNewLine,
+ sal_uInt8& rModified, const SvxBorderLine*& rpNew )
+{
+ if (rModified == SC_LINE_DONTCARE)
+ return false; // don't go again
+
+ if (rModified == SC_LINE_EMPTY)
+ {
+ rModified = SC_LINE_SET;
+ rpNew = pNewLine;
+ return true; // initial value
+ }
+
+ if (pOldLine == pNewLine)
+ {
+ rpNew = pOldLine;
+ return false;
+ }
+
+ if (pOldLine && pNewLine)
+ if (*pOldLine == *pNewLine)
+ {
+ rpNew = pOldLine;
+ return false;
+ }
+
+ rModified = SC_LINE_DONTCARE;
+ rpNew = nullptr;
+ return true; // another line -> don't care
+}
+
+static void lcl_MergeToFrame( SvxBoxItem* pLineOuter, SvxBoxInfoItem* pLineInner,
+ ScLineFlags& rFlags, const ScPatternAttr* pPattern,
+ bool bLeft, SCCOL nDistRight, bool bTop, SCROW nDistBottom )
+{
+ // right/bottom border set when connected together
+ const ScMergeAttr& rMerge = pPattern->GetItem(ATTR_MERGE);
+ if ( rMerge.GetColMerge() == nDistRight + 1 )
+ nDistRight = 0;
+ if ( rMerge.GetRowMerge() == nDistBottom + 1 )
+ nDistBottom = 0;
+
+ const SvxBoxItem* pCellFrame = &pPattern->GetItemSet().Get( ATTR_BORDER );
+ const SvxBorderLine* pLeftAttr = pCellFrame->GetLeft();
+ const SvxBorderLine* pRightAttr = pCellFrame->GetRight();
+ const SvxBorderLine* pTopAttr = pCellFrame->GetTop();
+ const SvxBorderLine* pBottomAttr = pCellFrame->GetBottom();
+ const SvxBorderLine* pNew;
+
+ if (bTop)
+ {
+ if (lcl_TestAttr( pLineOuter->GetTop(), pTopAttr, rFlags.nTop, pNew ))
+ pLineOuter->SetLine( pNew, SvxBoxItemLine::TOP );
+ }
+ else
+ {
+ if (lcl_TestAttr( pLineInner->GetHori(), pTopAttr, rFlags.nHori, pNew ))
+ pLineInner->SetLine( pNew, SvxBoxInfoItemLine::HORI );
+ }
+
+ if (nDistBottom == 0)
+ {
+ if (lcl_TestAttr( pLineOuter->GetBottom(), pBottomAttr, rFlags.nBottom, pNew ))
+ pLineOuter->SetLine( pNew, SvxBoxItemLine::BOTTOM );
+ }
+ else
+ {
+ if (lcl_TestAttr( pLineInner->GetHori(), pBottomAttr, rFlags.nHori, pNew ))
+ pLineInner->SetLine( pNew, SvxBoxInfoItemLine::HORI );
+ }
+
+ if (bLeft)
+ {
+ if (lcl_TestAttr( pLineOuter->GetLeft(), pLeftAttr, rFlags.nLeft, pNew ))
+ pLineOuter->SetLine( pNew, SvxBoxItemLine::LEFT );
+ }
+ else
+ {
+ if (lcl_TestAttr( pLineInner->GetVert(), pLeftAttr, rFlags.nVert, pNew ))
+ pLineInner->SetLine( pNew, SvxBoxInfoItemLine::VERT );
+ }
+
+ if (nDistRight == 0)
+ {
+ if (lcl_TestAttr( pLineOuter->GetRight(), pRightAttr, rFlags.nRight, pNew ))
+ pLineOuter->SetLine( pNew, SvxBoxItemLine::RIGHT );
+ }
+ else
+ {
+ if (lcl_TestAttr( pLineInner->GetVert(), pRightAttr, rFlags.nVert, pNew ))
+ pLineInner->SetLine( pNew, SvxBoxInfoItemLine::VERT );
+ }
+}
+
+void ScAttrArray::MergeBlockFrame( SvxBoxItem* pLineOuter, SvxBoxInfoItem* pLineInner,
+ ScLineFlags& rFlags,
+ SCROW nStartRow, SCROW nEndRow, bool bLeft, SCCOL nDistRight ) const
+{
+ const ScPatternAttr* pPattern;
+
+ if (nStartRow == nEndRow)
+ {
+ pPattern = GetPattern( nStartRow );
+ lcl_MergeToFrame( pLineOuter, pLineInner, rFlags, pPattern, bLeft, nDistRight, true, 0 );
+ }
+ else if ( !mvData.empty() ) // non-default pattern
+ {
+ pPattern = GetPattern( nStartRow );
+ lcl_MergeToFrame( pLineOuter, pLineInner, rFlags, pPattern, bLeft, nDistRight, true,
+ nEndRow-nStartRow );
+
+ SCSIZE nStartIndex;
+ SCSIZE nEndIndex;
+ Search( nStartRow+1, nStartIndex );
+ Search( nEndRow-1, nEndIndex );
+ for (SCSIZE i=nStartIndex; i<=nEndIndex; i++)
+ {
+ pPattern = mvData[i].pPattern;
+ lcl_MergeToFrame( pLineOuter, pLineInner, rFlags, pPattern, bLeft, nDistRight, false,
+ nEndRow - std::min( mvData[i].nEndRow, static_cast<SCROW>(nEndRow-1) ) );
+ // nDistBottom here always > 0
+ }
+
+ pPattern = GetPattern( nEndRow );
+ lcl_MergeToFrame( pLineOuter, pLineInner, rFlags, pPattern, bLeft, nDistRight, false, 0 );
+ }
+ else
+ {
+ lcl_MergeToFrame( pLineOuter, pLineInner, rFlags, rDocument.GetDefPattern(), bLeft, nDistRight, true, 0 );
+ }
+}
+
+// apply border
+
+// ApplyFrame - on an entry into the array
+
+bool ScAttrArray::ApplyFrame( const SvxBoxItem& rBoxItem,
+ const SvxBoxInfoItem* pBoxInfoItem,
+ SCROW nStartRow, SCROW nEndRow,
+ bool bLeft, SCCOL nDistRight, bool bTop, SCROW nDistBottom )
+{
+ OSL_ENSURE( pBoxInfoItem, "Missing line attributes!" );
+
+ const ScPatternAttr* pPattern = GetPattern( nStartRow );
+ const SvxBoxItem* pOldFrame = &pPattern->GetItemSet().Get( ATTR_BORDER );
+
+ // right/bottom border set when connected together
+ const ScMergeAttr& rMerge = pPattern->GetItem(ATTR_MERGE);
+ if ( rMerge.GetColMerge() == nDistRight + 1 )
+ nDistRight = 0;
+ if ( rMerge.GetRowMerge() == nDistBottom + 1 )
+ nDistBottom = 0;
+
+ SvxBoxItem aNewFrame( *pOldFrame );
+ bool bRTL=rDocument.IsLayoutRTL(nTab);
+ // fdo#37464 check if the sheet are RTL then replace right <=> left
+ if (bRTL)
+ {
+ if( bLeft && nDistRight==0)
+ {
+ if ( pBoxInfoItem->IsValid(SvxBoxInfoItemValidFlags::LEFT) )
+ aNewFrame.SetLine( rBoxItem.GetLeft(), SvxBoxItemLine::RIGHT );
+ if ( pBoxInfoItem->IsValid(SvxBoxInfoItemValidFlags::RIGHT) )
+ aNewFrame.SetLine( rBoxItem.GetRight(), SvxBoxItemLine::LEFT );
+ }
+ else
+ {
+ if ( (nDistRight==0) ? pBoxInfoItem->IsValid(SvxBoxInfoItemValidFlags::LEFT) : pBoxInfoItem->IsValid(SvxBoxInfoItemValidFlags::VERT) )
+ aNewFrame.SetLine( (nDistRight==0) ? rBoxItem.GetLeft() : pBoxInfoItem->GetVert(),
+ SvxBoxItemLine::RIGHT );
+ if ( bLeft ? pBoxInfoItem->IsValid(SvxBoxInfoItemValidFlags::RIGHT) : pBoxInfoItem->IsValid(SvxBoxInfoItemValidFlags::VERT) )
+ aNewFrame.SetLine( bLeft ? rBoxItem.GetRight() : pBoxInfoItem->GetVert(),
+ SvxBoxItemLine::LEFT );
+ }
+ }
+ else
+ {
+ if ( bLeft ? pBoxInfoItem->IsValid(SvxBoxInfoItemValidFlags::LEFT) : pBoxInfoItem->IsValid(SvxBoxInfoItemValidFlags::VERT) )
+ aNewFrame.SetLine( bLeft ? rBoxItem.GetLeft() : pBoxInfoItem->GetVert(),
+ SvxBoxItemLine::LEFT );
+ if ( (nDistRight==0) ? pBoxInfoItem->IsValid(SvxBoxInfoItemValidFlags::RIGHT) : pBoxInfoItem->IsValid(SvxBoxInfoItemValidFlags::VERT) )
+ aNewFrame.SetLine( (nDistRight==0) ? rBoxItem.GetRight() : pBoxInfoItem->GetVert(),
+ SvxBoxItemLine::RIGHT );
+ }
+ if ( bTop ? pBoxInfoItem->IsValid(SvxBoxInfoItemValidFlags::TOP) : pBoxInfoItem->IsValid(SvxBoxInfoItemValidFlags::HORI) )
+ aNewFrame.SetLine( bTop ? rBoxItem.GetTop() : pBoxInfoItem->GetHori(),
+ SvxBoxItemLine::TOP );
+ if ( (nDistBottom==0) ? pBoxInfoItem->IsValid(SvxBoxInfoItemValidFlags::BOTTOM) : pBoxInfoItem->IsValid(SvxBoxInfoItemValidFlags::HORI) )
+ aNewFrame.SetLine( (nDistBottom==0) ? rBoxItem.GetBottom() : pBoxInfoItem->GetHori(),
+ SvxBoxItemLine::BOTTOM );
+
+ if (aNewFrame == *pOldFrame)
+ {
+ // nothing to do
+ return false;
+ }
+ else
+ {
+ SfxItemPoolCache aCache( rDocument.GetPool(), &aNewFrame );
+ ApplyCacheArea( nStartRow, nEndRow, &aCache );
+
+ return true;
+ }
+}
+
+void ScAttrArray::ApplyBlockFrame(const SvxBoxItem& rLineOuter, const SvxBoxInfoItem* pLineInner,
+ SCROW nStartRow, SCROW nEndRow, bool bLeft, SCCOL nDistRight)
+{
+ SetDefaultIfNotInit();
+ if (nStartRow == nEndRow)
+ ApplyFrame(rLineOuter, pLineInner, nStartRow, nEndRow, bLeft, nDistRight, true, 0);
+ else
+ {
+ ApplyFrame(rLineOuter, pLineInner, nStartRow, nStartRow, bLeft, nDistRight,
+ true, nEndRow-nStartRow);
+
+ if ( nEndRow > nStartRow+1 ) // inner part available?
+ {
+ SCSIZE nStartIndex;
+ SCSIZE nEndIndex;
+ Search( nStartRow+1, nStartIndex );
+ Search( nEndRow-1, nEndIndex );
+ SCROW nTmpStart = nStartRow+1;
+ SCROW nTmpEnd;
+ for (SCSIZE i=nStartIndex; i<=nEndIndex;)
+ {
+ nTmpEnd = std::min( static_cast<SCROW>(nEndRow-1), mvData[i].nEndRow );
+ bool bChanged = ApplyFrame(rLineOuter, pLineInner, nTmpStart, nTmpEnd,
+ bLeft, nDistRight, false, nEndRow - nTmpEnd);
+ nTmpStart = nTmpEnd+1;
+ if (bChanged)
+ {
+ Search(nTmpStart, i);
+ Search(nEndRow-1, nEndIndex);
+ }
+ else
+ i++;
+ }
+ }
+
+ ApplyFrame(rLineOuter, pLineInner, nEndRow, nEndRow, bLeft, nDistRight, false, 0);
+ }
+}
+
+bool ScAttrArray::HasAttrib_Impl(const ScPatternAttr* pPattern, HasAttrFlags nMask, SCROW nRow1, SCROW nRow2, SCSIZE i) const
+{
+ bool bFound = false;
+ if ( nMask & HasAttrFlags::Merged )
+ {
+ const ScMergeAttr* pMerge = &pPattern->GetItem( ATTR_MERGE );
+ if ( pMerge->GetColMerge() > 1 || pMerge->GetRowMerge() > 1 )
+ bFound = true;
+ }
+ if ( nMask & ( HasAttrFlags::Overlapped | HasAttrFlags::NotOverlapped | HasAttrFlags::AutoFilter ) )
+ {
+ const ScMergeFlagAttr* pMergeFlag = &pPattern->GetItem( ATTR_MERGE_FLAG );
+ if ( (nMask & HasAttrFlags::Overlapped) && pMergeFlag->IsOverlapped() )
+ bFound = true;
+ if ( (nMask & HasAttrFlags::NotOverlapped) && !pMergeFlag->IsOverlapped() )
+ bFound = true;
+ if ( (nMask & HasAttrFlags::AutoFilter) && pMergeFlag->HasAutoFilter() )
+ bFound = true;
+ }
+ if ( nMask & HasAttrFlags::Lines )
+ {
+ const SvxBoxItem* pBox = &pPattern->GetItem( ATTR_BORDER );
+ if ( pBox->GetLeft() || pBox->GetRight() || pBox->GetTop() || pBox->GetBottom() )
+ bFound = true;
+ }
+ if ( nMask & HasAttrFlags::Shadow )
+ {
+ const SvxShadowItem* pShadow = &pPattern->GetItem( ATTR_SHADOW );
+ if ( pShadow->GetLocation() != SvxShadowLocation::NONE )
+ bFound = true;
+ }
+ if ( nMask & HasAttrFlags::Conditional )
+ {
+ if ( !pPattern->GetItem( ATTR_CONDITIONAL ).GetCondFormatData().empty())
+ bFound = true;
+ }
+ if ( nMask & HasAttrFlags::Protected )
+ {
+ const ScProtectionAttr* pProtect = &pPattern->GetItem( ATTR_PROTECTION );
+ bool bFoundTemp = false;
+ if ( pProtect->GetProtection() || pProtect->GetHideCell() )
+ bFoundTemp = true;
+
+ bool bContainsCondFormat = !mvData.empty() &&
+ !pPattern->GetItem( ATTR_CONDITIONAL ).GetCondFormatData().empty();
+ if ( bContainsCondFormat && nCol != -1 ) // rDocument.GetCondResult() is valid only for real columns.
+ {
+ SCROW nRowStartCond = std::max<SCROW>( nRow1, i ? mvData[i-1].nEndRow + 1: 0 );
+ SCROW nRowEndCond = std::min<SCROW>( nRow2, mvData[i].nEndRow );
+ bool bFoundCond = false;
+ for(SCROW nRowCond = nRowStartCond; nRowCond <= nRowEndCond && !bFoundCond; ++nRowCond)
+ {
+ const SfxItemSet* pSet = rDocument.GetCondResult( nCol, nRowCond, nTab );
+
+ const ScProtectionAttr* pCondProtect;
+ if( pSet && (pCondProtect = pSet->GetItemIfSet( ATTR_PROTECTION )) )
+ {
+ if( pCondProtect->GetProtection() || pCondProtect->GetHideCell() )
+ bFoundCond = true;
+ else
+ break;
+ }
+ else
+ {
+ // well it is not true that we found one
+ // but existing one + cell where conditional
+ // formatting does not remove it
+ // => we should use the existing protection setting
+ bFoundCond = bFoundTemp;
+ }
+ }
+ bFoundTemp = bFoundCond;
+ }
+
+ if(bFoundTemp)
+ bFound = true;
+ }
+ if ( nMask & HasAttrFlags::Rotate )
+ {
+ const ScRotateValueItem* pRotate = &pPattern->GetItem( ATTR_ROTATE_VALUE );
+ // 90 or 270 degrees is former SvxOrientationItem - only look for other values
+ // (see ScPatternAttr::GetCellOrientation)
+ Degree100 nAngle = pRotate->GetValue();
+ if ( nAngle && nAngle != 9000_deg100 && nAngle != 27000_deg100 )
+ bFound = true;
+ }
+ if ( nMask & HasAttrFlags::NeedHeight )
+ {
+ if (pPattern->GetCellOrientation() != SvxCellOrientation::Standard)
+ bFound = true;
+ else if (pPattern->GetItem( ATTR_LINEBREAK ).GetValue())
+ bFound = true;
+ else if (pPattern->GetItem( ATTR_HOR_JUSTIFY ).GetValue() == SvxCellHorJustify::Block)
+ bFound = true;
+
+ else if (!pPattern->GetItem(ATTR_CONDITIONAL).GetCondFormatData().empty())
+ bFound = true;
+ else if (pPattern->GetItem( ATTR_ROTATE_VALUE ).GetValue())
+ bFound = true;
+ }
+ if ( nMask & ( HasAttrFlags::ShadowRight | HasAttrFlags::ShadowDown ) )
+ {
+ const SvxShadowItem* pShadow = &pPattern->GetItem( ATTR_SHADOW );
+ SvxShadowLocation eLoc = pShadow->GetLocation();
+ if ( nMask & HasAttrFlags::ShadowRight )
+ if ( eLoc == SvxShadowLocation::TopRight || eLoc == SvxShadowLocation::BottomRight )
+ bFound = true;
+ if ( nMask & HasAttrFlags::ShadowDown )
+ if ( eLoc == SvxShadowLocation::BottomLeft || eLoc == SvxShadowLocation::BottomRight )
+ bFound = true;
+ }
+ if ( nMask & HasAttrFlags::RightOrCenter )
+ {
+ // called only if the sheet is LTR, so physical=logical alignment can be assumed
+ SvxCellHorJustify eHorJust = pPattern->GetItem( ATTR_HOR_JUSTIFY ).GetValue();
+ if ( eHorJust == SvxCellHorJustify::Right || eHorJust == SvxCellHorJustify::Center )
+ bFound = true;
+ }
+
+ return bFound;
+}
+
+// Test if field contains specific attribute
+bool ScAttrArray::HasAttrib( SCROW nRow1, SCROW nRow2, HasAttrFlags nMask ) const
+{
+ if (mvData.empty())
+ {
+ return HasAttrib_Impl(rDocument.GetDefPattern(), nMask, 0, rDocument.MaxRow(), 0);
+ }
+
+ SCSIZE nStartIndex;
+ SCSIZE nEndIndex;
+ Search( nRow1, nStartIndex );
+ if (nRow1 != nRow2)
+ Search( nRow2, nEndIndex );
+ else
+ nEndIndex = nStartIndex;
+ bool bFound = false;
+
+ for (SCSIZE i=nStartIndex; i<=nEndIndex && !bFound; i++)
+ {
+ const ScPatternAttr* pPattern = mvData[i].pPattern;
+ bFound = HasAttrib_Impl(pPattern, nMask, nRow1, nRow2, i);
+ }
+
+ return bFound;
+}
+
+bool ScAttrArray::HasAttrib( SCROW nRow, HasAttrFlags nMask, SCROW* nStartRow, SCROW* nEndRow ) const
+{
+ if (mvData.empty())
+ {
+ if( nStartRow )
+ *nStartRow = 0;
+ if( nEndRow )
+ *nEndRow = rDocument.MaxRow();
+ return HasAttrib_Impl(rDocument.GetDefPattern(), nMask, 0, rDocument.MaxRow(), 0);
+ }
+
+ SCSIZE nIndex;
+ Search( nRow, nIndex );
+ if( nStartRow )
+ *nStartRow = nIndex > 0 ? mvData[nIndex-1].nEndRow+1 : 0;
+ if( nEndRow )
+ *nEndRow = mvData[nIndex].nEndRow;
+ const ScPatternAttr* pPattern = mvData[nIndex].pPattern;
+ return HasAttrib_Impl(pPattern, nMask, nRow, nRow, nIndex);
+}
+
+bool ScAttrArray::IsMerged( SCROW nRow ) const
+{
+ if ( !mvData.empty() )
+ {
+ SCSIZE nIndex;
+ Search(nRow, nIndex);
+ const ScMergeAttr& rItem = mvData[nIndex].pPattern->GetItem(ATTR_MERGE);
+
+ return rItem.IsMerged();
+ }
+
+ return rDocument.GetDefPattern()->GetItem(ATTR_MERGE).IsMerged();
+}
+
+/**
+ * Area around any given summaries expand and adapt any MergeFlag (bRefresh)
+ */
+bool ScAttrArray::ExtendMerge( SCCOL nThisCol, SCROW nStartRow, SCROW nEndRow,
+ SCCOL& rPaintCol, SCROW& rPaintRow,
+ bool bRefresh )
+{
+ assert( nCol != -1 );
+ SetDefaultIfNotInit();
+ const ScPatternAttr* pPattern;
+ const ScMergeAttr* pItem;
+ SCSIZE nStartIndex;
+ SCSIZE nEndIndex;
+ Search( nStartRow, nStartIndex );
+ Search( nEndRow, nEndIndex );
+ bool bFound = false;
+
+ for (SCSIZE i=nStartIndex; i<=nEndIndex; i++)
+ {
+ pPattern = mvData[i].pPattern;
+ pItem = &pPattern->GetItem( ATTR_MERGE );
+ SCCOL nCountX = pItem->GetColMerge();
+ SCROW nCountY = pItem->GetRowMerge();
+ if (nCountX>1 || nCountY>1)
+ {
+ SCROW nThisRow = (i>0) ? mvData[i-1].nEndRow+1 : 0;
+ SCCOL nMergeEndCol = nThisCol + nCountX - 1;
+ SCROW nMergeEndRow = nThisRow + nCountY - 1;
+ if (nMergeEndCol > rPaintCol && nMergeEndCol <= rDocument.MaxCol())
+ rPaintCol = nMergeEndCol;
+ if (nMergeEndRow > rPaintRow && nMergeEndRow <= rDocument.MaxRow())
+ rPaintRow = nMergeEndRow;
+ bFound = true;
+
+ if (bRefresh)
+ {
+ if ( nMergeEndCol > nThisCol )
+ rDocument.ApplyFlagsTab( nThisCol+1, nThisRow, nMergeEndCol, mvData[i].nEndRow,
+ nTab, ScMF::Hor );
+ if ( nMergeEndRow > nThisRow )
+ rDocument.ApplyFlagsTab( nThisCol, nThisRow+1, nThisCol, nMergeEndRow,
+ nTab, ScMF::Ver );
+ if ( nMergeEndCol > nThisCol && nMergeEndRow > nThisRow )
+ rDocument.ApplyFlagsTab( nThisCol+1, nThisRow+1, nMergeEndCol, nMergeEndRow,
+ nTab, ScMF::Hor | ScMF::Ver );
+
+ Search( nThisRow, i ); // Data changed
+ Search( nStartRow, nStartIndex );
+ Search( nEndRow, nEndIndex );
+ }
+ }
+ }
+
+ return bFound;
+}
+
+void ScAttrArray::RemoveAreaMerge(SCROW nStartRow, SCROW nEndRow)
+{
+ assert( nCol != -1 );
+ SetDefaultIfNotInit();
+ const ScPatternAttr* pPattern;
+ const ScMergeAttr* pItem;
+ SCSIZE nIndex;
+
+ Search( nStartRow, nIndex );
+ SCROW nThisStart = (nIndex>0) ? mvData[nIndex-1].nEndRow+1 : 0;
+ if (nThisStart < nStartRow)
+ nThisStart = nStartRow;
+
+ while ( nThisStart <= nEndRow )
+ {
+ SCROW nThisEnd = mvData[nIndex].nEndRow;
+ if (nThisEnd > nEndRow)
+ nThisEnd = nEndRow;
+
+ pPattern = mvData[nIndex].pPattern;
+ pItem = &pPattern->GetItem( ATTR_MERGE );
+ SCCOL nCountX = pItem->GetColMerge();
+ SCROW nCountY = pItem->GetRowMerge();
+ if (nCountX>1 || nCountY>1)
+ {
+ const ScMergeAttr* pAttr = &rDocument.GetPool()->GetDefaultItem( ATTR_MERGE );
+ const ScMergeFlagAttr* pFlagAttr = &rDocument.GetPool()->GetDefaultItem( ATTR_MERGE_FLAG );
+
+ OSL_ENSURE( nCountY==1 || nThisStart==nThisEnd, "What's up?" );
+
+ SCCOL nThisCol = nCol;
+ SCCOL nMergeEndCol = nThisCol + nCountX - 1;
+ SCROW nMergeEndRow = nThisEnd + nCountY - 1;
+
+ // ApplyAttr for areas
+ for (SCROW nThisRow = nThisStart; nThisRow <= nThisEnd; nThisRow++)
+ rDocument.ApplyAttr( nThisCol, nThisRow, nTab, *pAttr );
+
+ ScPatternAttr aNewPattern( rDocument.GetPool() );
+ SfxItemSet* pSet = &aNewPattern.GetItemSet();
+ pSet->Put( *pFlagAttr );
+ rDocument.ApplyPatternAreaTab( nThisCol, nThisStart, nMergeEndCol, nMergeEndRow,
+ nTab, aNewPattern );
+
+ Search( nThisEnd, nIndex ); // data changed
+ }
+
+ ++nIndex;
+ if ( nIndex < mvData.size() )
+ nThisStart = mvData[nIndex-1].nEndRow+1;
+ else
+ nThisStart = rDocument.MaxRow()+1; // End
+ }
+}
+
+void ScAttrArray::SetPatternAreaSafe( SCROW nStartRow, SCROW nEndRow,
+ const ScPatternAttr* pWantedPattern, bool bDefault )
+{
+ SetDefaultIfNotInit();
+ const ScPatternAttr* pOldPattern;
+ const ScMergeFlagAttr* pItem;
+
+ SCSIZE nIndex;
+ SCROW nRow;
+ SCROW nThisRow;
+ bool bFirstUse = true;
+
+ Search( nStartRow, nIndex );
+ nThisRow = (nIndex>0) ? mvData[nIndex-1].nEndRow+1 : 0;
+ while ( nThisRow <= nEndRow )
+ {
+ pOldPattern = mvData[nIndex].pPattern;
+ if (pOldPattern != pWantedPattern) // FIXME: else-branch?
+ {
+ if (nThisRow < nStartRow) nThisRow = nStartRow;
+ nRow = mvData[nIndex].nEndRow;
+ SCROW nAttrRow = std::min( nRow, nEndRow );
+ pItem = &pOldPattern->GetItem( ATTR_MERGE_FLAG );
+
+ if (pItem->IsOverlapped() || pItem->HasAutoFilter())
+ {
+ // default-constructing a ScPatternAttr for DeleteArea doesn't work
+ // because it would have no cell style information.
+ // Instead, the document's GetDefPattern is copied. Since it is passed as
+ // pWantedPattern, no special treatment of default is needed here anymore.
+ std::unique_ptr<ScPatternAttr> pNewPattern(new ScPatternAttr( *pWantedPattern ));
+ pNewPattern->GetItemSet().Put( *pItem );
+ SetPatternArea( nThisRow, nAttrRow, std::move(pNewPattern), true );
+ }
+ else
+ {
+ if ( !bDefault )
+ {
+ if (bFirstUse)
+ bFirstUse = false;
+ else
+ // it's in the pool
+ rDocument.GetPool()->Put( *pWantedPattern );
+ }
+ SetPatternArea( nThisRow, nAttrRow, pWantedPattern );
+ }
+
+ Search( nThisRow, nIndex ); // data changed
+ }
+
+ ++nIndex;
+ nThisRow = mvData[nIndex-1].nEndRow+1;
+ }
+}
+
+bool ScAttrArray::ApplyFlags( SCROW nStartRow, SCROW nEndRow, ScMF nFlags )
+{
+ SetDefaultIfNotInit();
+ const ScPatternAttr* pOldPattern;
+
+ ScMF nOldValue;
+ SCSIZE nIndex;
+ SCROW nRow;
+ SCROW nThisRow;
+ bool bChanged = false;
+
+ Search( nStartRow, nIndex );
+ nThisRow = (nIndex>0) ? mvData[nIndex-1].nEndRow+1 : 0;
+ if (nThisRow < nStartRow) nThisRow = nStartRow;
+
+ while ( nThisRow <= nEndRow )
+ {
+ pOldPattern = mvData[nIndex].pPattern;
+ nOldValue = pOldPattern->GetItem( ATTR_MERGE_FLAG ).GetValue();
+ if ( (nOldValue | nFlags) != nOldValue )
+ {
+ nRow = mvData[nIndex].nEndRow;
+ SCROW nAttrRow = std::min( nRow, nEndRow );
+ auto pNewPattern = std::make_unique<ScPatternAttr>(*pOldPattern);
+ pNewPattern->GetItemSet().Put( ScMergeFlagAttr( nOldValue | nFlags ) );
+ SetPatternArea( nThisRow, nAttrRow, std::move(pNewPattern), true );
+ Search( nThisRow, nIndex ); // data changed
+ bChanged = true;
+ }
+
+ ++nIndex;
+ nThisRow = mvData[nIndex-1].nEndRow+1;
+ }
+
+ return bChanged;
+}
+
+bool ScAttrArray::RemoveFlags( SCROW nStartRow, SCROW nEndRow, ScMF nFlags )
+{
+ SetDefaultIfNotInit();
+ const ScPatternAttr* pOldPattern;
+
+ ScMF nOldValue;
+ SCSIZE nIndex;
+ SCROW nRow;
+ SCROW nThisRow;
+ bool bChanged = false;
+
+ Search( nStartRow, nIndex );
+ nThisRow = (nIndex>0) ? mvData[nIndex-1].nEndRow+1 : 0;
+ if (nThisRow < nStartRow) nThisRow = nStartRow;
+
+ while ( nThisRow <= nEndRow )
+ {
+ pOldPattern = mvData[nIndex].pPattern;
+ nOldValue = pOldPattern->GetItem( ATTR_MERGE_FLAG ).GetValue();
+ if ( (nOldValue & ~nFlags) != nOldValue )
+ {
+ nRow = mvData[nIndex].nEndRow;
+ SCROW nAttrRow = std::min( nRow, nEndRow );
+ auto pNewPattern = std::make_unique<ScPatternAttr>(*pOldPattern);
+ pNewPattern->GetItemSet().Put( ScMergeFlagAttr( nOldValue & ~nFlags ) );
+ SetPatternArea( nThisRow, nAttrRow, std::move(pNewPattern), true );
+ Search( nThisRow, nIndex ); // data changed
+ bChanged = true;
+ }
+
+ ++nIndex;
+ nThisRow = mvData[nIndex-1].nEndRow+1;
+ }
+
+ return bChanged;
+}
+
+void ScAttrArray::ClearItems( SCROW nStartRow, SCROW nEndRow, const sal_uInt16* pWhich )
+{
+ SetDefaultIfNotInit();
+ SCSIZE nIndex;
+ SCROW nRow;
+ SCROW nThisRow;
+
+ Search( nStartRow, nIndex );
+ nThisRow = (nIndex>0) ? mvData[nIndex-1].nEndRow+1 : 0;
+ if (nThisRow < nStartRow) nThisRow = nStartRow;
+
+ while ( nThisRow <= nEndRow )
+ {
+ const ScPatternAttr* pOldPattern = mvData[nIndex].pPattern;
+ if ( pOldPattern->HasItemsSet( pWhich ) )
+ {
+ auto pNewPattern = std::make_unique<ScPatternAttr>(*pOldPattern);
+ pNewPattern->ClearItems( pWhich );
+
+ nRow = mvData[nIndex].nEndRow;
+ SCROW nAttrRow = std::min( nRow, nEndRow );
+ SetPatternArea( nThisRow, nAttrRow, std::move(pNewPattern), true );
+ Search( nThisRow, nIndex ); // data changed
+ }
+
+ ++nIndex;
+ nThisRow = mvData[nIndex-1].nEndRow+1;
+ }
+}
+
+void ScAttrArray::ChangeIndent( SCROW nStartRow, SCROW nEndRow, bool bIncrement )
+{
+ SetDefaultIfNotInit();
+ SCSIZE nIndex;
+ Search( nStartRow, nIndex );
+ SCROW nThisStart = (nIndex>0) ? mvData[nIndex-1].nEndRow+1 : 0;
+ if (nThisStart < nStartRow) nThisStart = nStartRow;
+
+ while ( nThisStart <= nEndRow )
+ {
+ const ScPatternAttr* pOldPattern = mvData[nIndex].pPattern;
+ const SfxItemSet& rOldSet = pOldPattern->GetItemSet();
+ const SvxHorJustifyItem* pItem;
+
+ bool bNeedJust = !( pItem = rOldSet.GetItemIfSet( ATTR_HOR_JUSTIFY, false ) )
+ || (pItem->GetValue() != SvxCellHorJustify::Left &&
+ pItem->GetValue() != SvxCellHorJustify::Right );
+ sal_uInt16 nOldValue = rOldSet.Get( ATTR_INDENT ).GetValue();
+ sal_uInt16 nNewValue = nOldValue;
+ // To keep Increment indent from running outside the cell1659
+ tools::Long nColWidth = static_cast<tools::Long>(
+ rDocument.GetColWidth(nCol == -1 ? rDocument.MaxCol() : nCol,nTab));
+ if ( bIncrement )
+ {
+ if ( nNewValue < nColWidth-SC_INDENT_STEP )
+ {
+ nNewValue += SC_INDENT_STEP;
+ if ( nNewValue > nColWidth-SC_INDENT_STEP )
+ nNewValue = nColWidth-SC_INDENT_STEP;
+ }
+ }
+ else
+ {
+ if ( nNewValue > 0 )
+ {
+ if ( nNewValue > SC_INDENT_STEP )
+ nNewValue -= SC_INDENT_STEP;
+ else
+ nNewValue = 0;
+ }
+ }
+
+ if ( bNeedJust || nNewValue != nOldValue )
+ {
+ SCROW nThisEnd = mvData[nIndex].nEndRow;
+ SCROW nAttrRow = std::min( nThisEnd, nEndRow );
+ auto pNewPattern = std::make_unique<ScPatternAttr>(*pOldPattern);
+ pNewPattern->GetItemSet().Put( ScIndentItem( nNewValue ) );
+ if ( bNeedJust )
+ pNewPattern->GetItemSet().Put(
+ SvxHorJustifyItem( SvxCellHorJustify::Left, ATTR_HOR_JUSTIFY ) );
+ SetPatternArea( nThisStart, nAttrRow, std::move(pNewPattern), true );
+
+ nThisStart = nThisEnd + 1;
+ Search( nThisStart, nIndex ); // data changed
+ }
+ else
+ {
+ nThisStart = mvData[nIndex].nEndRow + 1;
+ ++nIndex;
+ }
+ }
+}
+
+SCROW ScAttrArray::GetNextUnprotected( SCROW nRow, bool bUp ) const
+{
+ tools::Long nRet = nRow;
+ if (rDocument.ValidRow(nRow))
+ {
+ if ( mvData.empty() )
+ {
+ if ( bUp )
+ return -1;
+ else
+ return rDocument.MaxRow()+1;
+ }
+
+ SCSIZE nIndex;
+ Search(nRow, nIndex);
+ while (mvData[nIndex].pPattern->
+ GetItem(ATTR_PROTECTION).GetProtection())
+ {
+ if (bUp)
+ {
+ if (nIndex==0)
+ return -1; // not found
+ --nIndex;
+ nRet = mvData[nIndex].nEndRow;
+ }
+ else
+ {
+ nRet = mvData[nIndex].nEndRow+1;
+ ++nIndex;
+ if (nIndex >= mvData.size())
+ return rDocument.MaxRow()+1; // not found
+ }
+ }
+ }
+ return nRet;
+}
+
+void ScAttrArray::FindStyleSheet( const SfxStyleSheetBase* pStyleSheet, ScFlatBoolRowSegments& rUsedRows, bool bReset )
+{
+ SetDefaultIfNotInit();
+ SCROW nStart = 0;
+ SCSIZE nPos = 0;
+ while (nPos < mvData.size())
+ {
+ SCROW nEnd = mvData[nPos].nEndRow;
+ if (mvData[nPos].pPattern->GetStyleSheet() == pStyleSheet)
+ {
+ rUsedRows.setTrue(nStart, nEnd);
+
+ if (bReset)
+ {
+ ScPatternAttr aNewPattern(*mvData[nPos].pPattern);
+ rDocument.GetPool()->Remove(*mvData[nPos].pPattern);
+ aNewPattern.SetStyleSheet( static_cast<ScStyleSheet*>(
+ rDocument.GetStyleSheetPool()->
+ Find( ScResId(STR_STYLENAME_STANDARD),
+ SfxStyleFamily::Para,
+ SfxStyleSearchBits::Auto | SfxStyleSearchBits::ScStandard ) ) );
+ mvData[nPos].pPattern = &rDocument.GetPool()->Put(aNewPattern);
+
+ if (Concat(nPos))
+ {
+ Search(nStart, nPos);
+ --nPos; // because ++ at end
+ }
+ }
+ }
+ nStart = nEnd + 1;
+ ++nPos;
+ }
+}
+
+bool ScAttrArray::IsStyleSheetUsed( const ScStyleSheet& rStyle ) const
+{
+ if ( mvData.empty() )
+ {
+ const ScStyleSheet* pStyle = rDocument.GetDefPattern()->GetStyleSheet();
+ if ( pStyle )
+ {
+ pStyle->SetUsage( ScStyleSheet::Usage::USED );
+ if ( pStyle == &rStyle )
+ return true;
+ }
+ return false;
+ }
+
+ bool bIsUsed = false;
+ SCSIZE nPos = 0;
+
+ while ( nPos < mvData.size() )
+ {
+ const ScStyleSheet* pStyle = mvData[nPos].pPattern->GetStyleSheet();
+ if ( pStyle )
+ {
+ pStyle->SetUsage( ScStyleSheet::Usage::USED );
+ if ( pStyle == &rStyle )
+ {
+ bIsUsed = true;
+ }
+ }
+ nPos++;
+ }
+
+ return bIsUsed;
+}
+
+bool ScAttrArray::IsEmpty() const
+{
+ if ( mvData.empty() )
+ return true;
+
+ if (mvData.size() == 1)
+ {
+ return mvData[0].pPattern == rDocument.GetDefPattern();
+ }
+ else
+ return false;
+}
+
+bool ScAttrArray::GetFirstVisibleAttr( SCROW& rFirstRow ) const
+{
+ if ( mvData.empty() )
+ return false;
+
+ bool bFound = false;
+ SCSIZE nStart = 0;
+
+ // Skip first entry if more than 1 row.
+ // Entries at the end are not skipped, GetFirstVisibleAttr may be larger than GetLastVisibleAttr.
+
+ SCSIZE nVisStart = 1;
+ while ( nVisStart < mvData.size() && mvData[nVisStart].pPattern->IsVisibleEqual(*mvData[nVisStart-1].pPattern) )
+ ++nVisStart;
+ if ( nVisStart >= mvData.size() || mvData[nVisStart-1].nEndRow > 0 ) // more than 1 row?
+ nStart = nVisStart;
+
+ while ( nStart < mvData.size() && !bFound )
+ {
+ if ( mvData[nStart].pPattern->IsVisible() )
+ {
+ rFirstRow = nStart ? ( mvData[nStart-1].nEndRow + 1 ) : 0;
+ bFound = true;
+ }
+ else
+ ++nStart;
+ }
+
+ return bFound;
+}
+
+// size (rows) of a range of attributes after cell content where the search is stopped
+// (more than a default page size, 2*42 because it's as good as any number)
+
+const SCROW SC_VISATTR_STOP = 84;
+
+bool ScAttrArray::GetLastVisibleAttr( SCROW& rLastRow, SCROW nLastData ) const
+{
+ if ( mvData.empty() )
+ {
+ rLastRow = nLastData;
+ return false;
+ }
+
+ // #i30830# changed behavior:
+ // ignore all attributes starting with the first run of SC_VISATTR_STOP equal rows
+ // below the last content cell
+
+ if ( nLastData == rDocument.MaxRow() )
+ {
+ rLastRow = rDocument.MaxRow(); // can't look for attributes below rDocument.MaxRow()
+ return true;
+ }
+
+ // Quick check: last data row in or immediately preceding a run that is the
+ // last attribution down to the end, e.g. default style or column style.
+ SCSIZE nPos = mvData.size() - 1;
+ SCROW nStartRow = (nPos ? mvData[nPos-1].nEndRow + 1 : 0);
+ if (nStartRow <= nLastData + 1)
+ {
+ // Ignore here a few rows if data happens to end within
+ // SC_VISATTR_STOP rows before rDocument.MaxRow().
+ rLastRow = nLastData;
+ return false;
+ }
+
+ // Find a run below last data row.
+ bool bFound = false;
+ Search( nLastData, nPos );
+ while ( nPos < mvData.size() )
+ {
+ // find range of visually equal formats
+ SCSIZE nEndPos = nPos;
+ while ( nEndPos < mvData.size()-1 &&
+ mvData[nEndPos].pPattern->IsVisibleEqual( *mvData[nEndPos+1].pPattern))
+ ++nEndPos;
+ SCROW nAttrStartRow = ( nPos > 0 ) ? ( mvData[nPos-1].nEndRow + 1 ) : 0;
+ if ( nAttrStartRow <= nLastData )
+ nAttrStartRow = nLastData + 1;
+ SCROW nAttrSize = mvData[nEndPos].nEndRow + 1 - nAttrStartRow;
+ if ( nAttrSize >= SC_VISATTR_STOP )
+ break; // while, ignore this range and below
+ else if ( mvData[nEndPos].pPattern->IsVisible() )
+ {
+ rLastRow = mvData[nEndPos].nEndRow;
+ bFound = true;
+ }
+ nPos = nEndPos + 1;
+ }
+
+ return bFound;
+}
+
+bool ScAttrArray::HasVisibleAttrIn( SCROW nStartRow, SCROW nEndRow ) const
+{
+ if ( mvData.empty() )
+ return rDocument.GetDefPattern()->IsVisible();
+
+ SCSIZE nIndex;
+ Search( nStartRow, nIndex );
+ SCROW nThisStart = nStartRow;
+ bool bFound = false;
+ while ( nIndex < mvData.size() && nThisStart <= nEndRow && !bFound )
+ {
+ if ( mvData[nIndex].pPattern->IsVisible() )
+ bFound = true;
+
+ nThisStart = mvData[nIndex].nEndRow + 1;
+ ++nIndex;
+ }
+
+ return bFound;
+}
+
+bool ScAttrArray::IsVisibleEqual( const ScAttrArray& rOther,
+ SCROW nStartRow, SCROW nEndRow ) const
+{
+ if ( mvData.empty() && rOther.mvData.empty() )
+ {
+ const ScPatternAttr* pDefPattern1 = rDocument.GetDefPattern();
+ const ScPatternAttr* pDefPattern2 = rOther.rDocument.GetDefPattern();
+ return ( pDefPattern1 == pDefPattern2 || pDefPattern1->IsVisibleEqual( *pDefPattern2 ) );
+ }
+
+ {
+ const ScAttrArray* pNonDefault = nullptr;
+ const ScPatternAttr* pDefPattern = nullptr;
+ bool bDefNonDefCase = false;
+ if ( mvData.empty() && !rOther.mvData.empty() )
+ {
+ pNonDefault = &rOther;
+ pDefPattern = rDocument.GetDefPattern();
+ bDefNonDefCase = true;
+ }
+ else if ( !mvData.empty() && rOther.mvData.empty() )
+ {
+ pNonDefault = this;
+ pDefPattern = rOther.rDocument.GetDefPattern();
+ bDefNonDefCase = true;
+ }
+
+ if ( bDefNonDefCase )
+ {
+ bool bEqual = true;
+ SCSIZE nPos = 0;
+ if ( nStartRow > 0 )
+ pNonDefault->Search( nStartRow, nPos );
+
+ while ( nPos < pNonDefault->Count() && bEqual )
+ {
+ const ScPatternAttr* pNonDefPattern = pNonDefault->mvData[nPos].pPattern;
+ bEqual = ( pNonDefPattern == pDefPattern ||
+ pNonDefPattern->IsVisibleEqual( *pDefPattern ) );
+
+ if ( pNonDefault->mvData[nPos].nEndRow >= nEndRow ) break;
+ ++nPos;
+ }
+ return bEqual;
+ }
+ }
+
+ bool bEqual = true;
+ SCSIZE nThisPos = 0;
+ SCSIZE nOtherPos = 0;
+ if ( nStartRow > 0 )
+ {
+ Search( nStartRow, nThisPos );
+ rOther.Search( nStartRow, nOtherPos );
+ }
+
+ while ( nThisPos<mvData.size() && nOtherPos<rOther.Count() && bEqual )
+ {
+ SCROW nThisRow = mvData[nThisPos].nEndRow;
+ SCROW nOtherRow = rOther.mvData[nOtherPos].nEndRow;
+ const ScPatternAttr* pThisPattern = mvData[nThisPos].pPattern;
+ const ScPatternAttr* pOtherPattern = rOther.mvData[nOtherPos].pPattern;
+ bEqual = ( pThisPattern == pOtherPattern ||
+ pThisPattern->IsVisibleEqual(*pOtherPattern) );
+
+ if ( nThisRow >= nOtherRow )
+ {
+ if ( nOtherRow >= nEndRow ) break;
+ ++nOtherPos;
+ }
+ if ( nThisRow <= nOtherRow )
+ {
+ if ( nThisRow >= nEndRow ) break;
+ ++nThisPos;
+ }
+ }
+
+ return bEqual;
+}
+
+bool ScAttrArray::IsAllEqual( const ScAttrArray& rOther, SCROW nStartRow, SCROW nEndRow ) const
+{
+ // summarised with IsVisibleEqual
+ if ( mvData.empty() && rOther.mvData.empty() )
+ {
+ const ScPatternAttr* pDefPattern1 = rDocument.GetDefPattern();
+ const ScPatternAttr* pDefPattern2 = rOther.rDocument.GetDefPattern();
+ return ( pDefPattern1 == pDefPattern2 );
+ }
+
+ {
+ const ScAttrArray* pNonDefault = nullptr;
+ const ScPatternAttr* pDefPattern = nullptr;
+ bool bDefNonDefCase = false;
+ if ( mvData.empty() && !rOther.mvData.empty() )
+ {
+ pNonDefault = &rOther;
+ pDefPattern = rDocument.GetDefPattern();
+ bDefNonDefCase = true;
+ }
+ else if ( !mvData.empty() && rOther.mvData.empty() )
+ {
+ pNonDefault = this;
+ pDefPattern = rOther.rDocument.GetDefPattern();
+ bDefNonDefCase = true;
+ }
+
+ if ( bDefNonDefCase )
+ {
+ bool bEqual = true;
+ SCSIZE nPos = 0;
+ if ( nStartRow > 0 )
+ pNonDefault->Search( nStartRow, nPos );
+
+ while ( nPos < pNonDefault->Count() && bEqual )
+ {
+ const ScPatternAttr* pNonDefPattern = pNonDefault->mvData[nPos].pPattern;
+ bEqual = ( pNonDefPattern == pDefPattern );
+
+ if ( pNonDefault->mvData[nPos].nEndRow >= nEndRow ) break;
+ ++nPos;
+ }
+ return bEqual;
+ }
+ }
+
+ bool bEqual = true;
+ SCSIZE nThisPos = 0;
+ SCSIZE nOtherPos = 0;
+ if ( nStartRow > 0 )
+ {
+ Search( nStartRow, nThisPos );
+ rOther.Search( nStartRow, nOtherPos );
+ }
+
+ while ( nThisPos<mvData.size() && nOtherPos<rOther.Count() && bEqual )
+ {
+ SCROW nThisRow = mvData[nThisPos].nEndRow;
+ SCROW nOtherRow = rOther.mvData[nOtherPos].nEndRow;
+ const ScPatternAttr* pThisPattern = mvData[nThisPos].pPattern;
+ const ScPatternAttr* pOtherPattern = rOther.mvData[nOtherPos].pPattern;
+ bEqual = ( pThisPattern == pOtherPattern );
+
+ if ( nThisRow >= nOtherRow )
+ {
+ if ( nOtherRow >= nEndRow ) break;
+ ++nOtherPos;
+ }
+ if ( nThisRow <= nOtherRow )
+ {
+ if ( nThisRow >= nEndRow ) break;
+ ++nThisPos;
+ }
+ }
+
+ return bEqual;
+}
+
+bool ScAttrArray::TestInsertCol( SCROW nStartRow, SCROW nEndRow) const
+{
+ // Horizontal aggregate are not allowed to be moved out; if whole summary,
+ // here is not recognized
+
+ bool bTest = true;
+ if (!IsEmpty())
+ {
+ SCSIZE nIndex = 0;
+ if ( nStartRow > 0 )
+ Search( nStartRow, nIndex );
+
+ for ( ; nIndex < mvData.size(); nIndex++ )
+ {
+ if ( mvData[nIndex].pPattern->
+ GetItem(ATTR_MERGE_FLAG).IsHorOverlapped() )
+ {
+ bTest = false; // may not be pushed out
+ break;
+ }
+ if ( mvData[nIndex].nEndRow >= nEndRow ) // end of range
+ break;
+ }
+ }
+ return bTest;
+}
+
+bool ScAttrArray::TestInsertRow( SCSIZE nSize ) const
+{
+ // if 1st row pushed out is vertically overlapped, summary would be broken
+
+ // rDocument.MaxRow() + 1 - nSize = 1st row pushed out
+
+ if ( mvData.empty() )
+ return !rDocument.GetDefPattern()->
+ GetItem(ATTR_MERGE_FLAG).IsVerOverlapped();
+
+ SCSIZE nFirstLost = mvData.size()-1;
+ while ( nFirstLost && mvData[nFirstLost-1].nEndRow >= sal::static_int_cast<SCROW>(rDocument.MaxRow() + 1 - nSize) )
+ --nFirstLost;
+
+ return !mvData[nFirstLost].pPattern->
+ GetItem(ATTR_MERGE_FLAG).IsVerOverlapped();
+}
+
+void ScAttrArray::InsertRow( SCROW nStartRow, SCSIZE nSize )
+{
+ SetDefaultIfNotInit();
+
+ SCROW nSearch = nStartRow > 0 ? nStartRow - 1 : 0; // expand predecessor
+ SCSIZE nIndex;
+ Search( nSearch, nIndex );
+
+ // set ScMergeAttr may not be extended (so behind delete again)
+
+ bool bDoMerge = mvData[nIndex].pPattern->GetItem(ATTR_MERGE).IsMerged();
+
+ assert( !bDoMerge || nCol != -1 );
+
+ SCSIZE nRemove = 0;
+ SCSIZE i;
+ for (i = nIndex; i < mvData.size()-1; i++)
+ {
+ SCROW nNew = mvData[i].nEndRow + nSize;
+ if ( nNew >= rDocument.MaxRow() ) // at end?
+ {
+ nNew = rDocument.MaxRow();
+ if (!nRemove)
+ nRemove = i+1; // remove the following?
+ }
+ mvData[i].nEndRow = nNew;
+ }
+
+ // Remove entries at end ?
+
+ if (nRemove && nRemove < mvData.size())
+ DeleteRange( nRemove, mvData.size()-1 );
+
+ if (bDoMerge) // extensively repair (again) ScMergeAttr
+ {
+ // ApplyAttr for areas
+
+ const SfxPoolItem& rDef = rDocument.GetPool()->GetDefaultItem( ATTR_MERGE );
+ for (SCSIZE nAdd=0; nAdd<nSize; nAdd++)
+ rDocument.ApplyAttr( nCol, nStartRow+nAdd, nTab, rDef );
+
+ // reply inserts in this area not summarized
+ }
+
+ // Don't duplicate the merge flags in the inserted row.
+ // #i108488# ScMF::Scenario has to be allowed.
+ RemoveFlags( nStartRow, nStartRow+nSize-1, ScMF::Hor | ScMF::Ver | ScMF::Auto | ScMF::Button );
+}
+
+void ScAttrArray::DeleteRow( SCROW nStartRow, SCSIZE nSize )
+{
+ SetDefaultIfNotInit();
+ bool bFirst=true;
+ SCSIZE nStartIndex = 0;
+ SCSIZE nEndIndex = 0;
+ SCSIZE i;
+
+ for ( i = 0; i < mvData.size()-1; i++)
+ if (mvData[i].nEndRow >= nStartRow && mvData[i].nEndRow <= sal::static_int_cast<SCROW>(nStartRow+nSize-1))
+ {
+ if (bFirst)
+ {
+ nStartIndex = i;
+ bFirst = false;
+ }
+ nEndIndex = i;
+ }
+ if (!bFirst)
+ {
+ SCROW nStart;
+ if (nStartIndex==0)
+ nStart = 0;
+ else
+ nStart = mvData[nStartIndex-1].nEndRow + 1;
+
+ if (nStart < nStartRow)
+ {
+ mvData[nStartIndex].nEndRow = nStartRow - 1;
+ ++nStartIndex;
+ }
+ if (nEndIndex >= nStartIndex)
+ {
+ DeleteRange( nStartIndex, nEndIndex );
+ if (nStartIndex > 0)
+ if ( mvData[nStartIndex-1].pPattern == mvData[nStartIndex].pPattern )
+ DeleteRange( nStartIndex-1, nStartIndex-1 );
+ }
+ }
+ for (i = 0; i < mvData.size()-1; i++)
+ if (mvData[i].nEndRow >= nStartRow)
+ mvData[i].nEndRow -= nSize;
+
+ // Below does not follow the pattern to detect pressure ranges;
+ // instead, only remove merge flags.
+ RemoveFlags( rDocument.MaxRow()-nSize+1, rDocument.MaxRow(), ScMF::Hor | ScMF::Ver | ScMF::Auto );
+}
+
+void ScAttrArray::DeleteRange( SCSIZE nStartIndex, SCSIZE nEndIndex )
+{
+ SetDefaultIfNotInit();
+ ScDocumentPool* pDocPool = rDocument.GetPool();
+ for (SCSIZE i = nStartIndex; i <= nEndIndex; i++)
+ pDocPool->Remove(*mvData[i].pPattern);
+
+ mvData.erase(mvData.begin() + nStartIndex, mvData.begin() + nEndIndex + 1);
+}
+
+void ScAttrArray::DeleteArea(SCROW nStartRow, SCROW nEndRow)
+{
+ SetDefaultIfNotInit();
+ if ( nCol != -1 )
+ RemoveAreaMerge( nStartRow, nEndRow ); // remove from combined flags
+
+ if ( !HasAttrib( nStartRow, nEndRow, HasAttrFlags::Overlapped | HasAttrFlags::AutoFilter) )
+ SetPatternArea( nStartRow, nEndRow, rDocument.GetDefPattern() );
+ else
+ SetPatternAreaSafe( nStartRow, nEndRow, rDocument.GetDefPattern(), true ); // leave merge flags
+}
+
+void ScAttrArray::DeleteHardAttr(SCROW nStartRow, SCROW nEndRow)
+{
+ SetDefaultIfNotInit();
+ const ScPatternAttr* pDefPattern = rDocument.GetDefPattern();
+
+ SCSIZE nIndex;
+ SCROW nRow;
+ SCROW nThisRow;
+
+ Search( nStartRow, nIndex );
+ nThisRow = (nIndex>0) ? mvData[nIndex-1].nEndRow+1 : 0;
+ if (nThisRow < nStartRow) nThisRow = nStartRow;
+
+ while ( nThisRow <= nEndRow )
+ {
+ const ScPatternAttr* pOldPattern = mvData[nIndex].pPattern;
+
+ if ( pOldPattern->GetItemSet().Count() ) // hard attributes ?
+ {
+ nRow = mvData[nIndex].nEndRow;
+ SCROW nAttrRow = std::min( nRow, nEndRow );
+
+ auto pNewPattern = std::make_unique<ScPatternAttr>(*pOldPattern);
+ SfxItemSet& rSet = pNewPattern->GetItemSet();
+ for (sal_uInt16 nId = ATTR_PATTERN_START; nId <= ATTR_PATTERN_END; nId++)
+ if (nId != ATTR_MERGE && nId != ATTR_MERGE_FLAG)
+ rSet.ClearItem(nId);
+
+ if ( *pNewPattern == *pDefPattern )
+ SetPatternArea( nThisRow, nAttrRow, pDefPattern );
+ else
+ SetPatternArea( nThisRow, nAttrRow, std::move(pNewPattern), true );
+
+ Search( nThisRow, nIndex ); // data changed
+ }
+
+ ++nIndex;
+ nThisRow = mvData[nIndex-1].nEndRow+1;
+ }
+}
+
+/**
+ * Move within a document
+ */
+void ScAttrArray::MoveTo(SCROW nStartRow, SCROW nEndRow, ScAttrArray& rAttrArray)
+{
+ SetDefaultIfNotInit();
+ SCROW nStart = nStartRow;
+ for (SCSIZE i = 0; i < mvData.size(); i++)
+ {
+ if ((mvData[i].nEndRow >= nStartRow) && (i == 0 || mvData[i-1].nEndRow < nEndRow))
+ {
+ // copy (bPutToPool=TRUE)
+ rAttrArray.SetPatternArea( nStart, std::min( mvData[i].nEndRow, nEndRow ),
+ mvData[i].pPattern, true );
+ }
+ nStart = std::max( nStart, mvData[i].nEndRow + 1 );
+ }
+ DeleteArea(nStartRow, nEndRow);
+}
+
+/**
+ * Copy between documents (Clipboard)
+ */
+void ScAttrArray::CopyArea(
+ SCROW nStartRow, SCROW nEndRow, tools::Long nDy, ScAttrArray& rAttrArray, ScMF nStripFlags) const
+{
+ nStartRow -= nDy; // Source
+ nEndRow -= nDy;
+
+ SCROW nDestStart = std::max(static_cast<tools::Long>(static_cast<tools::Long>(nStartRow) + nDy), tools::Long(0));
+ SCROW nDestEnd = std::min(static_cast<tools::Long>(static_cast<tools::Long>(nEndRow) + nDy), tools::Long(rDocument.MaxRow()));
+
+ ScDocumentPool* pSourceDocPool = rDocument.GetPool();
+ ScDocumentPool* pDestDocPool = rAttrArray.rDocument.GetPool();
+ bool bSamePool = (pSourceDocPool==pDestDocPool);
+
+ if ( mvData.empty() )
+ {
+ const ScPatternAttr* pNewPattern = &pDestDocPool->GetDefaultItem( ATTR_PATTERN );
+ rAttrArray.SetPatternArea(nDestStart, nDestEnd, pNewPattern);
+ return;
+ }
+
+ for (SCSIZE i = 0; (i < mvData.size()) && (nDestStart <= nDestEnd); i++)
+ {
+ if (mvData[i].nEndRow >= nStartRow)
+ {
+ const ScPatternAttr* pOldPattern = mvData[i].pPattern;
+ const ScPatternAttr* pNewPattern;
+
+ if (IsDefaultItem( pOldPattern ))
+ {
+ // default: nothing changed
+
+ pNewPattern = &pDestDocPool->GetDefaultItem( ATTR_PATTERN );
+ }
+ else if ( nStripFlags != ScMF::NONE )
+ {
+ ScPatternAttr aTmpPattern( *pOldPattern );
+ ScMF nNewFlags = ScMF::NONE;
+ if ( nStripFlags != ScMF::All )
+ nNewFlags = aTmpPattern.GetItem(ATTR_MERGE_FLAG).GetValue() & ~nStripFlags;
+
+ if ( nNewFlags != ScMF::NONE )
+ aTmpPattern.GetItemSet().Put( ScMergeFlagAttr( nNewFlags ) );
+ else
+ aTmpPattern.GetItemSet().ClearItem( ATTR_MERGE_FLAG );
+
+ if (bSamePool)
+ pNewPattern = &pDestDocPool->Put(aTmpPattern);
+ else
+ pNewPattern = aTmpPattern.PutInPool( &rAttrArray.rDocument, &rDocument );
+ }
+ else
+ {
+ if (bSamePool)
+ pNewPattern = &pDestDocPool->Put(*pOldPattern);
+ else
+ pNewPattern = pOldPattern->PutInPool( &rAttrArray.rDocument, &rDocument );
+ }
+
+ rAttrArray.SetPatternArea(nDestStart,
+ std::min(static_cast<SCROW>(mvData[i].nEndRow + nDy), nDestEnd), pNewPattern);
+ }
+
+ // when pasting from clipboard and skipping filtered rows, the adjusted
+ // end position can be negative
+ nDestStart = std::max(static_cast<tools::Long>(nDestStart), static_cast<tools::Long>(mvData[i].nEndRow + nDy + 1));
+ }
+}
+
+/**
+ * Leave flags
+ * summarized with CopyArea
+ */
+void ScAttrArray::CopyAreaSafe( SCROW nStartRow, SCROW nEndRow, tools::Long nDy, ScAttrArray& rAttrArray )
+{
+ nStartRow -= nDy; // Source
+ nEndRow -= nDy;
+
+ SCROW nDestStart = std::max(static_cast<tools::Long>(static_cast<tools::Long>(nStartRow) + nDy), tools::Long(0));
+ SCROW nDestEnd = std::min(static_cast<tools::Long>(static_cast<tools::Long>(nEndRow) + nDy), tools::Long(rDocument.MaxRow()));
+
+ if ( !rAttrArray.HasAttrib( nDestStart, nDestEnd, HasAttrFlags::Overlapped ) )
+ {
+ CopyArea( nStartRow+nDy, nEndRow+nDy, nDy, rAttrArray );
+ return;
+ }
+
+ ScDocumentPool* pSourceDocPool = rDocument.GetPool();
+ ScDocumentPool* pDestDocPool = rAttrArray.rDocument.GetPool();
+ bool bSamePool = (pSourceDocPool==pDestDocPool);
+
+ if ( mvData.empty() )
+ {
+ const ScPatternAttr* pNewPattern;
+ if (bSamePool)
+ pNewPattern = &pDestDocPool->Put(*rDocument.GetDefPattern());
+ else
+ pNewPattern = rDocument.GetDefPattern()->PutInPool( &rAttrArray.rDocument, &rDocument );
+
+ rAttrArray.SetPatternAreaSafe(nDestStart, nDestEnd, pNewPattern, false);
+ return;
+ }
+
+
+ for (SCSIZE i = 0; (i < mvData.size()) && (nDestStart <= nDestEnd); i++)
+ {
+ if (mvData[i].nEndRow >= nStartRow)
+ {
+ const ScPatternAttr* pOldPattern = mvData[i].pPattern;
+ const ScPatternAttr* pNewPattern;
+
+ if (bSamePool)
+ pNewPattern = &pDestDocPool->Put(*pOldPattern);
+ else
+ pNewPattern = pOldPattern->PutInPool( &rAttrArray.rDocument, &rDocument );
+
+ rAttrArray.SetPatternAreaSafe(nDestStart,
+ std::min(static_cast<SCROW>(mvData[i].nEndRow + nDy), nDestEnd), pNewPattern, false);
+ }
+
+ // when pasting from clipboard and skipping filtered rows, the adjusted
+ // end position can be negative
+ nDestStart = std::max(static_cast<tools::Long>(nDestStart), static_cast<tools::Long>(mvData[i].nEndRow + nDy + 1));
+ }
+}
+
+SCROW ScAttrArray::SearchStyle(
+ SCROW nRow, const ScStyleSheet* pSearchStyle, bool bUp,
+ const ScMarkArray* pMarkArray) const
+{
+ bool bFound = false;
+
+ if (pMarkArray)
+ {
+ nRow = pMarkArray->GetNextMarked( nRow, bUp );
+ if (!rDocument.ValidRow(nRow))
+ return nRow;
+ }
+
+ if ( mvData.empty() )
+ {
+ if (rDocument.GetDefPattern()->GetStyleSheet() == pSearchStyle)
+ return nRow;
+
+ nRow = bUp ? -1 : rDocument.MaxRow() + 1;
+ return nRow;
+ }
+
+ SCSIZE nIndex;
+ Search(nRow, nIndex);
+ const ScPatternAttr* pPattern = mvData[nIndex].pPattern;
+
+ while (nIndex < mvData.size() && !bFound)
+ {
+ if (pPattern->GetStyleSheet() == pSearchStyle)
+ {
+ if (pMarkArray)
+ {
+ nRow = pMarkArray->GetNextMarked( nRow, bUp );
+ SCROW nStart = nIndex ? mvData[nIndex-1].nEndRow+1 : 0;
+ if (nRow >= nStart && nRow <= mvData[nIndex].nEndRow)
+ bFound = true;
+ }
+ else
+ bFound = true;
+ }
+
+ if (!bFound)
+ {
+ if (bUp)
+ {
+ if (nIndex==0)
+ {
+ nIndex = mvData.size();
+ nRow = -1;
+ }
+ else
+ {
+ --nIndex;
+ nRow = mvData[nIndex].nEndRow;
+ pPattern = mvData[nIndex].pPattern;
+ }
+ }
+ else
+ {
+ nRow = mvData[nIndex].nEndRow+1;
+ ++nIndex;
+ if (nIndex<mvData.size())
+ pPattern = mvData[nIndex].pPattern;
+ }
+ }
+ }
+
+ OSL_ENSURE( bFound || !rDocument.ValidRow(nRow), "Internal failure in ScAttrArray::SearchStyle" );
+
+ return nRow;
+}
+
+bool ScAttrArray::SearchStyleRange(
+ SCROW& rRow, SCROW& rEndRow, const ScStyleSheet* pSearchStyle, bool bUp,
+ const ScMarkArray* pMarkArray) const
+{
+ SCROW nStartRow = SearchStyle( rRow, pSearchStyle, bUp, pMarkArray );
+ if (rDocument.ValidRow(nStartRow))
+ {
+ if ( mvData.empty() )
+ {
+ rRow = nStartRow;
+ if (bUp)
+ {
+ rEndRow = 0;
+ if (pMarkArray)
+ {
+ SCROW nMarkEnd = pMarkArray->GetMarkEnd( nStartRow, true );
+ if (nMarkEnd>rEndRow)
+ rEndRow = nMarkEnd;
+ }
+ }
+ else
+ {
+ rEndRow = rDocument.MaxRow();
+ if (pMarkArray)
+ {
+ SCROW nMarkEnd = pMarkArray->GetMarkEnd( nStartRow, false );
+ if (nMarkEnd<rEndRow)
+ rEndRow = nMarkEnd;
+ }
+ }
+
+ return true;
+ }
+
+ SCSIZE nIndex;
+ Search(nStartRow,nIndex);
+
+ rRow = nStartRow;
+ if (bUp)
+ {
+ if (nIndex>0)
+ rEndRow = mvData[nIndex-1].nEndRow + 1;
+ else
+ rEndRow = 0;
+ if (pMarkArray)
+ {
+ SCROW nMarkEnd = pMarkArray->GetMarkEnd( nStartRow, true );
+ if (nMarkEnd>rEndRow)
+ rEndRow = nMarkEnd;
+ }
+ }
+ else
+ {
+ rEndRow = mvData[nIndex].nEndRow;
+ if (pMarkArray)
+ {
+ SCROW nMarkEnd = pMarkArray->GetMarkEnd( nStartRow, false );
+ if (nMarkEnd<rEndRow)
+ rEndRow = nMarkEnd;
+ }
+ }
+
+ return true;
+ }
+ else
+ return false;
+}
+
+SCSIZE ScAttrArray::Count( SCROW nStartRow, SCROW nEndRow ) const
+{
+ if ( mvData.empty() )
+ return 1;
+
+ SCSIZE nIndex1, nIndex2;
+
+ if( !Search( nStartRow, nIndex1 ) )
+ return 0;
+
+ if( !Search( nEndRow, nIndex2 ) )
+ nIndex2 = mvData.size() - 1;
+
+ return nIndex2 - nIndex1 + 1;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sc/source/core/data/attrib.cxx b/sc/source/core/data/attrib.cxx
new file mode 100644
index 000000000..7173bcb61
--- /dev/null
+++ b/sc/source/core/data/attrib.cxx
@@ -0,0 +1,889 @@
+/* -*- 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 <com/sun/star/util/CellProtection.hpp>
+
+#include <scitems.hxx>
+
+#include <editeng/editeng.hxx>
+#include <editeng/editobj.hxx>
+#include <editeng/eerdll.hxx>
+#include <editeng/borderline.hxx>
+#include <editeng/itemtype.hxx>
+#include <svl/itempool.hxx>
+
+#include <libxml/xmlwriter.h>
+
+#include <attrib.hxx>
+#include <global.hxx>
+#include <editutil.hxx>
+#include <mid.h>
+#include <globstr.hrc>
+#include <scresid.hxx>
+#include <textuno.hxx>
+
+using namespace com::sun::star;
+
+
+SfxPoolItem* ScProtectionAttr::CreateDefault() { return new ScProtectionAttr; }
+
+/**
+ * General Help Function
+ */
+bool ScHasPriority( const ::editeng::SvxBorderLine* pThis, const ::editeng::SvxBorderLine* pOther )
+{
+
+ if (!pThis)
+ return false;
+ if (!pOther)
+ return true;
+
+ sal_uInt16 nThisSize = pThis->GetScaledWidth();
+ sal_uInt16 nOtherSize = pOther->GetScaledWidth();
+
+ if (nThisSize > nOtherSize)
+ return true;
+ else if (nThisSize < nOtherSize)
+ return false;
+ else
+ {
+ if ( pOther->GetInWidth() && !pThis->GetInWidth() )
+ return true;
+ else if ( pThis->GetInWidth() && !pOther->GetInWidth() )
+ return false;
+ else
+ {
+ return true; // FIXME: What is this?
+ }
+ }
+}
+
+/** Item - Implementations */
+
+/**
+ * Merge
+ */
+ScMergeAttr::ScMergeAttr():
+ SfxPoolItem(ATTR_MERGE),
+ nColMerge(0),
+ nRowMerge(0)
+{}
+
+ScMergeAttr::ScMergeAttr( SCCOL nCol, SCROW nRow):
+ SfxPoolItem(ATTR_MERGE),
+ nColMerge(nCol),
+ nRowMerge(nRow)
+{}
+
+ScMergeAttr::ScMergeAttr(const ScMergeAttr& rItem):
+ SfxPoolItem(ATTR_MERGE)
+{
+ nColMerge = rItem.nColMerge;
+ nRowMerge = rItem.nRowMerge;
+}
+
+ScMergeAttr::~ScMergeAttr()
+{
+}
+
+bool ScMergeAttr::operator==( const SfxPoolItem& rItem ) const
+{
+ return SfxPoolItem::operator==(rItem)
+ && (nColMerge == static_cast<const ScMergeAttr&>(rItem).nColMerge)
+ && (nRowMerge == static_cast<const ScMergeAttr&>(rItem).nRowMerge);
+}
+
+ScMergeAttr* ScMergeAttr::Clone( SfxItemPool * ) const
+{
+ return new ScMergeAttr(*this);
+}
+
+void ScMergeAttr::dumpAsXml(xmlTextWriterPtr pWriter) const
+{
+ (void)xmlTextWriterStartElement(pWriter, BAD_CAST("ScMergeAttr"));
+ (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("col-merge"), BAD_CAST(OString::number(GetColMerge()).getStr()));
+ (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("row-merge"), BAD_CAST(OString::number(GetRowMerge()).getStr()));
+ (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("merged"), BAD_CAST(OString::boolean(IsMerged()).getStr()));
+ (void)xmlTextWriterEndElement(pWriter);
+}
+
+/**
+ * MergeFlag
+ */
+ScMergeFlagAttr::ScMergeFlagAttr():
+ SfxInt16Item(ATTR_MERGE_FLAG, 0)
+{
+}
+
+ScMergeFlagAttr::ScMergeFlagAttr(ScMF nFlags):
+ SfxInt16Item(ATTR_MERGE_FLAG, static_cast<sal_Int16>(nFlags))
+{
+}
+
+ScMergeFlagAttr::~ScMergeFlagAttr()
+{
+}
+
+ScMergeFlagAttr* ScMergeFlagAttr::Clone(SfxItemPool *) const
+{
+ return new ScMergeFlagAttr(*this);
+}
+
+bool ScMergeFlagAttr::HasPivotButton() const
+{
+ return bool(GetValue() & ScMF::Button);
+}
+
+bool ScMergeFlagAttr::HasPivotPopupButton() const
+{
+ return bool(GetValue() & ScMF::ButtonPopup);
+}
+
+void ScMergeFlagAttr::dumpAsXml(xmlTextWriterPtr pWriter) const
+{
+ (void)xmlTextWriterStartElement(pWriter, BAD_CAST("ScMergeFlagAttr"));
+ (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("overlapped"), BAD_CAST(OString::boolean(IsOverlapped()).getStr()));
+ (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("hor_overlapped"), BAD_CAST(OString::boolean(IsHorOverlapped()).getStr()));
+ (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("ver_overlapped"), BAD_CAST(OString::boolean(IsVerOverlapped()).getStr()));
+ (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("autofilter"), BAD_CAST(OString::boolean(HasAutoFilter()).getStr()));
+ (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("scenario"), BAD_CAST(OString::boolean(IsScenario()).getStr()));
+ (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("pivot-button"), BAD_CAST(OString::boolean(HasPivotButton()).getStr()));
+ (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("pivot-popup-button"), BAD_CAST(OString::boolean(HasPivotPopupButton()).getStr()));
+ (void)xmlTextWriterEndElement(pWriter);
+}
+
+/**
+ * Protection
+ */
+ScProtectionAttr::ScProtectionAttr():
+ SfxPoolItem(ATTR_PROTECTION),
+ bProtection(true),
+ bHideFormula(false),
+ bHideCell(false),
+ bHidePrint(false)
+{
+}
+
+ScProtectionAttr::ScProtectionAttr( bool bProtect, bool bHFormula,
+ bool bHCell, bool bHPrint):
+ SfxPoolItem(ATTR_PROTECTION),
+ bProtection(bProtect),
+ bHideFormula(bHFormula),
+ bHideCell(bHCell),
+ bHidePrint(bHPrint)
+{
+}
+
+ScProtectionAttr::ScProtectionAttr(const ScProtectionAttr& rItem):
+ SfxPoolItem(ATTR_PROTECTION)
+{
+ bProtection = rItem.bProtection;
+ bHideFormula = rItem.bHideFormula;
+ bHideCell = rItem.bHideCell;
+ bHidePrint = rItem.bHidePrint;
+}
+
+ScProtectionAttr::~ScProtectionAttr()
+{
+}
+
+bool ScProtectionAttr::QueryValue( uno::Any& rVal, sal_uInt8 nMemberId ) const
+{
+ nMemberId &= ~CONVERT_TWIPS;
+ switch ( nMemberId )
+ {
+ case 0 :
+ {
+ util::CellProtection aProtection;
+ aProtection.IsLocked = bProtection;
+ aProtection.IsFormulaHidden = bHideFormula;
+ aProtection.IsHidden = bHideCell;
+ aProtection.IsPrintHidden = bHidePrint;
+ rVal <<= aProtection;
+ break;
+ }
+ case MID_1 :
+ rVal <<= bProtection; break;
+ case MID_2 :
+ rVal <<= bHideFormula; break;
+ case MID_3 :
+ rVal <<= bHideCell; break;
+ case MID_4 :
+ rVal <<= bHidePrint; break;
+ default:
+ OSL_FAIL("Wrong MemberID!");
+ return false;
+ }
+
+ return true;
+}
+
+bool ScProtectionAttr::PutValue( const uno::Any& rVal, sal_uInt8 nMemberId )
+{
+ bool bRet = false;
+ bool bVal = false;
+ nMemberId &= ~CONVERT_TWIPS;
+ switch ( nMemberId )
+ {
+ case 0 :
+ {
+ util::CellProtection aProtection;
+ if ( rVal >>= aProtection )
+ {
+ bProtection = aProtection.IsLocked;
+ bHideFormula = aProtection.IsFormulaHidden;
+ bHideCell = aProtection.IsHidden;
+ bHidePrint = aProtection.IsPrintHidden;
+ bRet = true;
+ }
+ else
+ {
+ OSL_FAIL("exception - wrong argument");
+ }
+ break;
+ }
+ case MID_1 :
+ bRet = (rVal >>= bVal); if (bRet) bProtection=bVal; break;
+ case MID_2 :
+ bRet = (rVal >>= bVal); if (bRet) bHideFormula=bVal; break;
+ case MID_3 :
+ bRet = (rVal >>= bVal); if (bRet) bHideCell=bVal; break;
+ case MID_4 :
+ bRet = (rVal >>= bVal); if (bRet) bHidePrint=bVal; break;
+ default:
+ OSL_FAIL("Wrong MemberID!");
+ }
+
+ return bRet;
+}
+
+OUString ScProtectionAttr::GetValueText() const
+{
+ const OUString aStrYes ( ScResId(STR_YES) );
+ const OUString aStrNo ( ScResId(STR_NO) );
+
+ const OUString aValue = "("
+ + (bProtection ? aStrYes : aStrNo)
+ + ","
+ + (bHideFormula ? aStrYes : aStrNo)
+ + ","
+ + (bHideCell ? aStrYes : aStrNo)
+ + ","
+ + (bHidePrint ? aStrYes : aStrNo)
+ + ")";
+
+ return aValue;
+}
+
+bool ScProtectionAttr::GetPresentation
+ (
+ SfxItemPresentation ePres,
+ MapUnit /* eCoreMetric */,
+ MapUnit /* ePresMetric */,
+ OUString& rText,
+ const IntlWrapper& /* rIntl */
+ ) const
+{
+ const OUString aStrYes ( ScResId(STR_YES) );
+ const OUString aStrNo ( ScResId(STR_NO) );
+
+ switch ( ePres )
+ {
+ case SfxItemPresentation::Nameless:
+ rText = GetValueText();
+ break;
+
+ case SfxItemPresentation::Complete:
+ rText = ScResId(STR_PROTECTION)
+ + ": "
+ + (bProtection ? aStrYes : aStrNo)
+ + ", "
+ + ScResId(STR_FORMULAS)
+ + ": "
+ + (!bHideFormula ? aStrYes : aStrNo)
+ + ", "
+ + ScResId(STR_HIDE)
+ + ": "
+ + (bHideCell ? aStrYes : aStrNo)
+ + ", "
+ + ScResId(STR_PRINT)
+ + ": "
+ + (!bHidePrint ? aStrYes : aStrNo);
+ break;
+
+ default: break;
+ }
+
+ return true;
+}
+
+bool ScProtectionAttr::operator==( const SfxPoolItem& rItem ) const
+{
+ return SfxPoolItem::operator==(rItem)
+ && (bProtection == static_cast<const ScProtectionAttr&>(rItem).bProtection)
+ && (bHideFormula == static_cast<const ScProtectionAttr&>(rItem).bHideFormula)
+ && (bHideCell == static_cast<const ScProtectionAttr&>(rItem).bHideCell)
+ && (bHidePrint == static_cast<const ScProtectionAttr&>(rItem).bHidePrint);
+}
+
+ScProtectionAttr* ScProtectionAttr::Clone( SfxItemPool * ) const
+{
+ return new ScProtectionAttr(*this);
+}
+
+void ScProtectionAttr::SetProtection( bool bProtect)
+{
+ bProtection = bProtect;
+}
+
+void ScProtectionAttr::SetHideFormula( bool bHFormula)
+{
+ bHideFormula = bHFormula;
+}
+
+void ScProtectionAttr::SetHideCell( bool bHCell)
+{
+ bHideCell = bHCell;
+}
+
+void ScProtectionAttr::SetHidePrint( bool bHPrint)
+{
+ bHidePrint = bHPrint;
+}
+
+void ScProtectionAttr::dumpAsXml(xmlTextWriterPtr pWriter) const
+{
+ (void)xmlTextWriterStartElement(pWriter, BAD_CAST("ScProtectionAttr"));
+ (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("protection"), BAD_CAST(OString::boolean(GetProtection()).getStr()));
+ (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("hide-formula"), BAD_CAST(OString::boolean(GetHideFormula()).getStr()));
+ (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("hide-cell"), BAD_CAST(OString::boolean(GetHideCell()).getStr()));
+ (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("hide-print"), BAD_CAST(OString::boolean(GetHidePrint()).getStr()));
+ (void)xmlTextWriterEndElement(pWriter);
+}
+
+/**
+ * ScPageHFItem - Dates from the Head and Foot lines
+ */
+ScPageHFItem::ScPageHFItem( sal_uInt16 nWhichP )
+ : SfxPoolItem ( nWhichP )
+{
+}
+
+ScPageHFItem::ScPageHFItem( const ScPageHFItem& rItem )
+ : SfxPoolItem ( rItem )
+{
+ if ( rItem.pLeftArea )
+ pLeftArea = rItem.pLeftArea->Clone();
+ if ( rItem.pCenterArea )
+ pCenterArea = rItem.pCenterArea->Clone();
+ if ( rItem.pRightArea )
+ pRightArea = rItem.pRightArea->Clone();
+}
+
+ScPageHFItem::~ScPageHFItem()
+{
+}
+
+bool ScPageHFItem::QueryValue( uno::Any& rVal, sal_uInt8 /* nMemberId */ ) const
+{
+ rtl::Reference<ScHeaderFooterContentObj> xContent =
+ new ScHeaderFooterContentObj();
+ xContent->Init(pLeftArea.get(), pCenterArea.get(), pRightArea.get());
+
+ uno::Reference<sheet::XHeaderFooterContent> xCont(xContent);
+
+ rVal <<= xCont;
+ return true;
+}
+
+bool ScPageHFItem::PutValue( const uno::Any& rVal, sal_uInt8 /* nMemberId */ )
+{
+ bool bRet = false;
+ uno::Reference<sheet::XHeaderFooterContent> xContent;
+ if ( rVal >>= xContent )
+ {
+ if ( xContent.is() )
+ {
+ rtl::Reference<ScHeaderFooterContentObj> pImp =
+ ScHeaderFooterContentObj::getImplementation( xContent );
+ if (pImp.is())
+ {
+ const EditTextObject* pImpLeft = pImp->GetLeftEditObject();
+ pLeftArea.reset();
+ if (pImpLeft)
+ pLeftArea = pImpLeft->Clone();
+
+ const EditTextObject* pImpCenter = pImp->GetCenterEditObject();
+ pCenterArea.reset();
+ if (pImpCenter)
+ pCenterArea = pImpCenter->Clone();
+
+ const EditTextObject* pImpRight = pImp->GetRightEditObject();
+ pRightArea.reset();
+ if (pImpRight)
+ pRightArea = pImpRight->Clone();
+
+ if ( !pLeftArea || !pCenterArea || !pRightArea )
+ {
+ // no Text with Null are left
+ ScEditEngineDefaulter aEngine( EditEngine::CreatePool().get(), true );
+ if (!pLeftArea)
+ pLeftArea = aEngine.CreateTextObject();
+ if (!pCenterArea)
+ pCenterArea = aEngine.CreateTextObject();
+ if (!pRightArea)
+ pRightArea = aEngine.CreateTextObject();
+ }
+
+ bRet = true;
+ }
+ }
+ }
+
+ if (!bRet)
+ {
+ OSL_FAIL("exception - wrong argument");
+ }
+
+ return true;
+}
+
+bool ScPageHFItem::operator==( const SfxPoolItem& rItem ) const
+{
+ assert(SfxPoolItem::operator==(rItem));
+
+ const ScPageHFItem& r = static_cast<const ScPageHFItem&>(rItem);
+
+ return ScGlobal::EETextObjEqual(pLeftArea.get(), r.pLeftArea.get())
+ && ScGlobal::EETextObjEqual(pCenterArea.get(), r.pCenterArea.get())
+ && ScGlobal::EETextObjEqual(pRightArea.get(), r.pRightArea.get());
+}
+
+ScPageHFItem* ScPageHFItem::Clone( SfxItemPool* ) const
+{
+ return new ScPageHFItem( *this );
+}
+
+void ScPageHFItem::SetLeftArea( const EditTextObject& rNew )
+{
+ pLeftArea = rNew.Clone();
+}
+
+void ScPageHFItem::SetCenterArea( const EditTextObject& rNew )
+{
+ pCenterArea = rNew.Clone();
+}
+
+void ScPageHFItem::SetRightArea( const EditTextObject& rNew )
+{
+ pRightArea = rNew.Clone();
+}
+void ScPageHFItem::dumpAsXml(xmlTextWriterPtr pWriter) const
+{
+ (void)xmlTextWriterStartElement(pWriter, BAD_CAST("ScPageHFItem"));
+ GetLeftArea()->dumpAsXml(pWriter);
+ GetCenterArea()->dumpAsXml(pWriter);
+ GetRightArea()->dumpAsXml(pWriter);
+ (void)xmlTextWriterEndElement(pWriter);
+}
+
+/**
+ * ScViewObjectModeItem - Display Mode of View Objects
+ */
+ScViewObjectModeItem::ScViewObjectModeItem( sal_uInt16 nWhichP )
+ : SfxEnumItem( nWhichP, VOBJ_MODE_SHOW )
+{
+}
+
+ScViewObjectModeItem::ScViewObjectModeItem( sal_uInt16 nWhichP, ScVObjMode eMode )
+ : SfxEnumItem( nWhichP, eMode )
+{
+}
+
+ScViewObjectModeItem::~ScViewObjectModeItem()
+{
+}
+
+bool ScViewObjectModeItem::GetPresentation
+(
+ SfxItemPresentation ePres,
+ MapUnit /* eCoreUnit */,
+ MapUnit /* ePresUnit */,
+ OUString& rText,
+ const IntlWrapper& /* rIntl */
+) const
+{
+ OUString aDel(": ");
+ rText.clear();
+
+ switch ( ePres )
+ {
+ case SfxItemPresentation::Complete:
+ switch( Which() )
+ {
+ case SID_SCATTR_PAGE_CHARTS:
+ rText = ScResId(STR_VOBJ_CHART) + aDel;
+ break;
+
+ case SID_SCATTR_PAGE_OBJECTS:
+ rText = ScResId(STR_VOBJ_OBJECT) + aDel;
+ break;
+
+ case SID_SCATTR_PAGE_DRAWINGS:
+ rText = ScResId(STR_VOBJ_DRAWINGS) + aDel;
+ break;
+
+ default: break;
+ }
+ [[fallthrough]];
+ case SfxItemPresentation::Nameless:
+ if (GetValue() == VOBJ_MODE_SHOW)
+ rText += ScResId(STR_VOBJ_MODE_SHOW);
+ else
+ rText += ScResId(STR_VOBJ_MODE_HIDE);
+ return true;
+
+ default: break;
+ // added to avoid warnings
+ }
+
+ return false;
+}
+
+sal_uInt16 ScViewObjectModeItem::GetValueCount() const
+{
+ return 2;
+}
+
+ScViewObjectModeItem* ScViewObjectModeItem::Clone( SfxItemPool* ) const
+{
+ return new ScViewObjectModeItem( *this );
+}
+
+ScPageScaleToItem::ScPageScaleToItem() :
+ SfxPoolItem( ATTR_PAGE_SCALETO ),
+ mnWidth( 0 ),
+ mnHeight( 0 )
+{
+}
+
+ScPageScaleToItem::ScPageScaleToItem( sal_uInt16 nWidth, sal_uInt16 nHeight ) :
+ SfxPoolItem( ATTR_PAGE_SCALETO ),
+ mnWidth( nWidth ),
+ mnHeight( nHeight )
+{
+}
+
+ScPageScaleToItem::~ScPageScaleToItem()
+{
+}
+
+ScPageScaleToItem* ScPageScaleToItem::Clone( SfxItemPool* ) const
+{
+ return new ScPageScaleToItem( *this );
+}
+
+bool ScPageScaleToItem::operator==( const SfxPoolItem& rCmp ) const
+{
+ assert(SfxPoolItem::operator==(rCmp));
+ const ScPageScaleToItem& rPageCmp = static_cast< const ScPageScaleToItem& >( rCmp );
+ return (mnWidth == rPageCmp.mnWidth) && (mnHeight == rPageCmp.mnHeight);
+}
+
+namespace {
+void lclAppendScalePageCount( OUString& rText, sal_uInt16 nPages )
+{
+ rText += ": ";
+ if( nPages )
+ {
+ OUString aPages(ScResId(STR_SCATTR_PAGE_SCALE_PAGES, nPages));
+ rText += aPages.replaceFirst( "%1", OUString::number( nPages ) );
+ }
+ else
+ rText += ScResId( STR_SCATTR_PAGE_SCALE_AUTO );
+}
+} // namespace
+
+bool ScPageScaleToItem::GetPresentation(
+ SfxItemPresentation ePres, MapUnit, MapUnit, OUString& rText, const IntlWrapper& ) const
+{
+ rText.clear();
+ if( !IsValid())
+ return false;
+
+ OUString aName( ScResId( STR_SCATTR_PAGE_SCALETO ) );
+ OUString aValue( ScResId( STR_SCATTR_PAGE_SCALE_WIDTH ) );
+ lclAppendScalePageCount( aValue, mnWidth );
+ aValue += ", " + ScResId( STR_SCATTR_PAGE_SCALE_HEIGHT );
+ lclAppendScalePageCount( aValue, mnHeight );
+
+ switch( ePres )
+ {
+ case SfxItemPresentation::Nameless:
+ rText = aValue;
+ return true;
+
+ case SfxItemPresentation::Complete:
+ rText = aName + " (" + aValue + ")";
+ return true;
+
+ default:
+ OSL_FAIL( "ScPageScaleToItem::GetPresentation - unknown presentation mode" );
+ }
+ return false;
+}
+
+bool ScPageScaleToItem::QueryValue( uno::Any& rAny, sal_uInt8 nMemberId ) const
+{
+ bool bRet = true;
+ switch( nMemberId )
+ {
+ case SC_MID_PAGE_SCALETO_WIDTH: rAny <<= mnWidth; break;
+ case SC_MID_PAGE_SCALETO_HEIGHT: rAny <<= mnHeight; break;
+ default:
+ OSL_FAIL( "ScPageScaleToItem::QueryValue - unknown member ID" );
+ bRet = false;
+ }
+ return bRet;
+}
+
+bool ScPageScaleToItem::PutValue( const uno::Any& rAny, sal_uInt8 nMemberId )
+{
+ bool bRet = false;
+ switch( nMemberId )
+ {
+ case SC_MID_PAGE_SCALETO_WIDTH: bRet = rAny >>= mnWidth; break;
+ case SC_MID_PAGE_SCALETO_HEIGHT: bRet = rAny >>= mnHeight; break;
+ default:
+ OSL_FAIL( "ScPageScaleToItem::PutValue - unknown member ID" );
+ }
+ return bRet;
+}
+void ScPageScaleToItem::dumpAsXml(xmlTextWriterPtr pWriter) const
+{
+ (void)xmlTextWriterStartElement(pWriter, BAD_CAST("ScPageScaleToItem"));
+ (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("width"), BAD_CAST(OString::number(GetWidth()).getStr()));
+ (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("height"), BAD_CAST(OString::number(GetHeight()).getStr()));
+ (void)xmlTextWriterEndElement(pWriter);
+}
+
+ScCondFormatItem::ScCondFormatItem():
+ SfxPoolItem( ATTR_CONDITIONAL )
+{
+}
+
+ScCondFormatItem::ScCondFormatItem( sal_uInt32 nIndex ):
+ SfxPoolItem( ATTR_CONDITIONAL )
+{
+ maIndex.insert(nIndex);
+}
+
+ScCondFormatItem::ScCondFormatItem( const ScCondFormatIndexes& rIndex ):
+ SfxPoolItem( ATTR_CONDITIONAL ),
+ maIndex( rIndex )
+{
+}
+
+ScCondFormatItem::ScCondFormatItem( ScCondFormatIndexes&& aIndex ) noexcept:
+ SfxPoolItem( ATTR_CONDITIONAL ),
+ maIndex( std::move(aIndex) )
+{
+}
+
+ScCondFormatItem::~ScCondFormatItem()
+{
+}
+
+bool ScCondFormatItem::operator==( const SfxPoolItem& rCmp ) const
+{
+ if (!SfxPoolItem::operator==(rCmp))
+ return false;
+ auto const & other = static_cast<const ScCondFormatItem&>(rCmp);
+ if (maIndex.empty() && other.maIndex.empty())
+ return true;
+ // memcmp is faster than operator== on std::vector
+ return maIndex.size() == other.maIndex.size()
+ && memcmp(&maIndex.front(), &other.maIndex.front(), maIndex.size() * sizeof(sal_uInt32)) == 0;
+}
+
+bool ScCondFormatItem::operator<( const SfxPoolItem& rCmp ) const
+{
+ auto const & other = static_cast<const ScCondFormatItem&>(rCmp);
+ if ( maIndex.size() < other.maIndex.size() )
+ return true;
+ if ( maIndex.size() > other.maIndex.size() )
+ return false;
+ if (maIndex.empty() && other.maIndex.empty())
+ return false;
+ // memcmp is faster than operator< on std::vector
+ // Note that on little-endian this results in a confusing ordering (256 < 1),
+ // which technically doesn't matter as the ordering may be arbitrary.
+ return memcmp(&maIndex.front(), &other.maIndex.front(), maIndex.size() * sizeof(sal_uInt32)) < 0;
+}
+
+ScCondFormatItem* ScCondFormatItem::Clone(SfxItemPool*) const
+{
+ return new ScCondFormatItem(maIndex);
+}
+
+void ScCondFormatItem::dumpAsXml(xmlTextWriterPtr pWriter) const
+{
+ (void)xmlTextWriterStartElement(pWriter, BAD_CAST("ScCondFormatItem"));
+ for (const auto& nItem : maIndex)
+ {
+ std::string aStrVal = std::to_string(nItem);
+ (void)xmlTextWriterStartElement(pWriter, BAD_CAST(aStrVal.c_str()));
+ (void)xmlTextWriterEndElement(pWriter);
+ }
+ (void)xmlTextWriterEndElement(pWriter);
+}
+
+ScRotateValueItem::ScRotateValueItem(Degree100 nAngle)
+ : SdrAngleItem(ATTR_ROTATE_VALUE, nAngle)
+{
+}
+
+ScRotateValueItem* ScRotateValueItem::Clone(SfxItemPool*) const
+{
+ return new ScRotateValueItem(GetValue());
+}
+
+bool ScRotateValueItem::GetPresentation(SfxItemPresentation ePresentation,
+ MapUnit eCoreMetric, MapUnit ePresMetric,
+ OUString& rText,
+ const IntlWrapper& rWrapper) const
+{
+ bool bRet = SdrAngleItem::GetPresentation(SfxItemPresentation::Nameless, eCoreMetric, ePresMetric, rText, rWrapper);
+ if (bRet && ePresentation == SfxItemPresentation::Complete)
+ rText = ScResId(STR_TEXTORIENTANGLE) + " " + rText;
+ return bRet;
+}
+
+ScShrinkToFitCell::ScShrinkToFitCell(bool bShrink)
+ : SfxBoolItem(ATTR_SHRINKTOFIT, bShrink)
+{
+}
+
+ScShrinkToFitCell* ScShrinkToFitCell::Clone(SfxItemPool*) const
+{
+ return new ScShrinkToFitCell(GetValue());
+}
+
+bool ScShrinkToFitCell::GetPresentation(SfxItemPresentation,
+ MapUnit, MapUnit,
+ OUString& rText,
+ const IntlWrapper&) const
+{
+ TranslateId pId = GetValue() ? STR_SHRINKTOFITCELL_ON : STR_SHRINKTOFITCELL_OFF;
+ rText = ScResId(pId);
+ return true;
+}
+
+ScVerticalStackCell::ScVerticalStackCell(bool bStack)
+ : SfxBoolItem(ATTR_STACKED, bStack)
+{
+}
+
+ScVerticalStackCell* ScVerticalStackCell::Clone(SfxItemPool*) const
+{
+ return new ScVerticalStackCell(GetValue());
+}
+
+bool ScVerticalStackCell::GetPresentation(SfxItemPresentation,
+ MapUnit, MapUnit,
+ OUString& rText,
+ const IntlWrapper&) const
+{
+ TranslateId pId = GetValue() ? STR_VERTICALSTACKCELL_ON : STR_VERTICALSTACKCELL_OFF;
+ rText = ScResId(pId);
+ return true;
+}
+
+ScLineBreakCell::ScLineBreakCell(bool bStack)
+ : SfxBoolItem(ATTR_LINEBREAK, bStack)
+{
+}
+
+ScLineBreakCell* ScLineBreakCell::Clone(SfxItemPool*) const
+{
+ return new ScLineBreakCell(GetValue());
+}
+
+bool ScLineBreakCell::GetPresentation(SfxItemPresentation,
+ MapUnit, MapUnit,
+ OUString& rText,
+ const IntlWrapper&) const
+{
+ TranslateId pId = GetValue() ? STR_LINEBREAKCELL_ON : STR_LINEBREAKCELL_OFF;
+ rText = ScResId(pId);
+ return true;
+}
+
+ScHyphenateCell::ScHyphenateCell(bool bHyphenate)
+ : SfxBoolItem(ATTR_HYPHENATE, bHyphenate)
+{
+}
+
+ScHyphenateCell* ScHyphenateCell::Clone(SfxItemPool*) const
+{
+ return new ScHyphenateCell(GetValue());
+}
+
+bool ScHyphenateCell::GetPresentation(SfxItemPresentation,
+ MapUnit, MapUnit,
+ OUString& rText,
+ const IntlWrapper&) const
+{
+ TranslateId pId = GetValue() ? STR_HYPHENATECELL_ON : STR_HYPHENATECELL_OFF;
+ rText = ScResId(pId);
+ return true;
+}
+
+ScIndentItem::ScIndentItem(sal_uInt16 nIndent)
+ : SfxUInt16Item(ATTR_INDENT, nIndent)
+{
+}
+
+ScIndentItem* ScIndentItem::Clone(SfxItemPool*) const
+{
+ return new ScIndentItem(GetValue());
+}
+
+bool ScIndentItem::GetPresentation(SfxItemPresentation ePres,
+ MapUnit eCoreUnit, MapUnit,
+ OUString& rText,
+ const IntlWrapper& rIntl) const
+{
+ auto nValue = GetValue();
+
+ switch (ePres)
+ {
+ case SfxItemPresentation::Complete:
+ rText = ScResId(STR_INDENTCELL);
+ [[fallthrough]];
+ case SfxItemPresentation::Nameless:
+ rText += GetMetricText( nValue, eCoreUnit, MapUnit::MapPoint, &rIntl ) +
+ " " + EditResId(GetMetricId(MapUnit::MapPoint));
+ return true;
+ default: ; //prevent warning
+ }
+ return false;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sc/source/core/data/autonamecache.cxx b/sc/source/core/data/autonamecache.cxx
new file mode 100644
index 000000000..f74219baf
--- /dev/null
+++ b/sc/source/core/data/autonamecache.cxx
@@ -0,0 +1,90 @@
+/* -*- 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 <unotools/transliterationwrapper.hxx>
+
+#include <autonamecache.hxx>
+#include <dociter.hxx>
+#include <formulacell.hxx>
+#include <editutil.hxx>
+
+ScAutoNameCache::ScAutoNameCache( ScDocument& rD ) :
+ rDoc( rD ),
+ nCurrentTab( 0 ) // doesn't matter - aNames is empty
+{
+}
+
+ScAutoNameCache::~ScAutoNameCache()
+{
+}
+
+const ScAutoNameAddresses& ScAutoNameCache::GetNameOccurrences( const OUString& rName, SCTAB nTab )
+{
+ if ( nTab != nCurrentTab )
+ {
+ // the lists are valid only for one sheet, so they are cleared when another sheet is used
+ aNames.clear();
+ nCurrentTab = nTab;
+ }
+
+ ScAutoNameHashMap::const_iterator aFound = aNames.find( rName );
+ if ( aFound != aNames.end() )
+ return aFound->second; // already initialized
+
+ ScAutoNameAddresses& rAddresses = aNames[rName];
+
+ ScCellIterator aIter( rDoc, ScRange( 0, 0, nCurrentTab, rDoc.MaxCol(), rDoc.MaxRow(), nCurrentTab ) );
+ for (bool bHasCell = aIter.first(); bHasCell; bHasCell = aIter.next())
+ {
+ // don't check code length here, always use the stored result
+ // (AutoCalc is disabled during CompileXML)
+ if (aIter.hasString())
+ {
+ OUString aStr;
+ switch (aIter.getType())
+ {
+ case CELLTYPE_STRING:
+ aStr = aIter.getString();
+ break;
+ case CELLTYPE_FORMULA:
+ aStr = aIter.getFormulaCell()->GetString().getString();
+ break;
+ case CELLTYPE_EDIT:
+ {
+ const EditTextObject* p = aIter.getEditText();
+ if (p)
+ aStr = ScEditUtil::GetMultilineString(*p); // string with line separators between paragraphs
+ }
+ break;
+ case CELLTYPE_NONE:
+ case CELLTYPE_VALUE:
+ ; // nothing, prevent compiler warning
+ break;
+ }
+ if ( ScGlobal::GetTransliteration().isEqual( aStr, rName ) )
+ {
+ rAddresses.push_back(aIter.GetPos());
+ }
+ }
+ }
+
+ return rAddresses;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sc/source/core/data/bcaslot.cxx b/sc/source/core/data/bcaslot.cxx
new file mode 100644
index 000000000..094939634
--- /dev/null
+++ b/sc/source/core/data/bcaslot.cxx
@@ -0,0 +1,1300 @@
+/* -*- 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 <sfx2/objsh.hxx>
+#include <svl/listener.hxx>
+#include <sal/log.hxx>
+#include <osl/diagnose.h>
+
+#include <document.hxx>
+#include <brdcst.hxx>
+#include <bcaslot.hxx>
+#include <scerrors.hxx>
+#include <refupdat.hxx>
+#include <bulkdatahint.hxx>
+#include <columnspanset.hxx>
+
+#if DEBUG_AREA_BROADCASTER
+#include <formulacell.hxx>
+#include <grouparealistener.hxx>
+#endif
+
+ScBroadcastArea::ScBroadcastArea( const ScRange& rRange ) :
+ pUpdateChainNext(nullptr),
+ aRange(rRange),
+ nRefCount(0),
+ mbInUpdateChain(false),
+ mbGroupListening(false) {}
+
+ScBroadcastAreaSlot::ScBroadcastAreaSlot( ScDocument* pDocument,
+ ScBroadcastAreaSlotMachine* pBASMa ) :
+ aTmpSeekBroadcastArea( ScRange()),
+ pDoc( pDocument ),
+ pBASM( pBASMa ),
+ mbInBroadcastIteration( false),
+ mbHasErasedArea(false)
+{
+}
+
+ScBroadcastAreaSlot::~ScBroadcastAreaSlot()
+{
+ for ( ScBroadcastAreas::iterator aIter( aBroadcastAreaTbl.begin());
+ aIter != aBroadcastAreaTbl.end(); /* none */)
+ {
+ // Prevent hash from accessing dangling pointer in case area is
+ // deleted.
+ ScBroadcastArea* pArea = (*aIter).mpArea;
+ // Erase all so no hash will be accessed upon destruction of the
+ // unordered_map.
+ aIter = aBroadcastAreaTbl.erase(aIter);
+ if (!pArea->DecRef())
+ delete pArea;
+ }
+}
+
+ScDocument::HardRecalcState ScBroadcastAreaSlot::CheckHardRecalcStateCondition() const
+{
+ ScDocument::HardRecalcState eState = pDoc->GetHardRecalcState();
+ if (eState == ScDocument::HardRecalcState::OFF)
+ {
+ if (aBroadcastAreaTbl.size() >= aBroadcastAreaTbl.max_size())
+ { // this is more hypothetical now, check existed for old SV_PTRARR_SORT
+ SfxObjectShell* pShell = pDoc->GetDocumentShell();
+ OSL_ENSURE( pShell, "Missing DocShell :-/" );
+
+ if ( pShell )
+ pShell->SetError(SCWARN_CORE_HARD_RECALC);
+
+ pDoc->SetAutoCalc( false );
+ eState = ScDocument::HardRecalcState::ETERNAL;
+ pDoc->SetHardRecalcState( eState );
+ }
+ }
+ return eState;
+}
+
+bool ScBroadcastAreaSlot::StartListeningArea(
+ const ScRange& rRange, bool bGroupListening, SvtListener* pListener, ScBroadcastArea*& rpArea )
+{
+ bool bNewArea = false;
+ OSL_ENSURE(pListener, "StartListeningArea: pListener Null");
+ assert(!pDoc->IsDelayedFormulaGrouping()); // otherwise the group size might be incorrect
+ if (CheckHardRecalcStateCondition() == ScDocument::HardRecalcState::ETERNAL)
+ return false;
+ if ( !rpArea )
+ {
+ // Even if most times the area doesn't exist yet and immediately trying
+ // to new and insert it would save an attempt to find it, on massive
+ // operations like identical large [HV]LOOKUP() areas the new/delete
+ // would add quite some penalty for all but the first formula cell.
+ ScBroadcastAreas::const_iterator aIter( FindBroadcastArea( rRange, bGroupListening));
+ if (aIter != aBroadcastAreaTbl.end())
+ rpArea = (*aIter).mpArea;
+ else
+ {
+ rpArea = new ScBroadcastArea( rRange);
+ rpArea->SetGroupListening(bGroupListening);
+ if (aBroadcastAreaTbl.insert( rpArea).second)
+ {
+ rpArea->IncRef();
+ bNewArea = true;
+ }
+ else
+ {
+ OSL_FAIL("StartListeningArea: area not found and not inserted in slot?!?");
+ delete rpArea;
+ rpArea = nullptr;
+ }
+ }
+ if (rpArea)
+ pListener->StartListening( rpArea->GetBroadcaster());
+ }
+ else
+ {
+ if (aBroadcastAreaTbl.insert( rpArea).second)
+ rpArea->IncRef();
+ }
+ return bNewArea;
+}
+
+void ScBroadcastAreaSlot::InsertListeningArea( ScBroadcastArea* pArea )
+{
+ OSL_ENSURE( pArea, "InsertListeningArea: pArea NULL");
+ if (CheckHardRecalcStateCondition() == ScDocument::HardRecalcState::ETERNAL)
+ return;
+ if (aBroadcastAreaTbl.insert( pArea).second)
+ pArea->IncRef();
+}
+
+// If rpArea != NULL then no listeners are stopped, only the area is removed
+// and the reference count decremented.
+void ScBroadcastAreaSlot::EndListeningArea(
+ const ScRange& rRange, bool bGroupListening, SvtListener* pListener, ScBroadcastArea*& rpArea )
+{
+ OSL_ENSURE(pListener, "EndListeningArea: pListener Null");
+ if ( !rpArea )
+ {
+ ScBroadcastAreas::iterator aIter( FindBroadcastArea( rRange, bGroupListening));
+ if (aIter == aBroadcastAreaTbl.end() || isMarkedErased( aIter))
+ return;
+ rpArea = (*aIter).mpArea;
+ pListener->EndListening( rpArea->GetBroadcaster() );
+ if ( !rpArea->GetBroadcaster().HasListeners() )
+ { // if nobody is listening we can dispose it
+ if (rpArea->GetRef() == 1)
+ rpArea = nullptr; // will be deleted by erase
+ EraseArea( aIter);
+ }
+ }
+ else
+ {
+ if (rpArea && !rpArea->GetBroadcaster().HasListeners())
+ {
+ ScBroadcastAreas::iterator aIter( FindBroadcastArea( rRange, bGroupListening));
+ if (aIter == aBroadcastAreaTbl.end() || isMarkedErased( aIter))
+ return;
+ OSL_ENSURE( (*aIter).mpArea == rpArea, "EndListeningArea: area pointer mismatch");
+ if (rpArea->GetRef() == 1)
+ rpArea = nullptr; // will be deleted by erase
+ EraseArea( aIter);
+ }
+ }
+}
+
+ScBroadcastAreas::iterator ScBroadcastAreaSlot::FindBroadcastArea(
+ const ScRange& rRange, bool bGroupListening )
+{
+ aTmpSeekBroadcastArea.UpdateRange( rRange);
+ aTmpSeekBroadcastArea.SetGroupListening(bGroupListening);
+ return aBroadcastAreaTbl.find( &aTmpSeekBroadcastArea);
+}
+
+namespace {
+
+void broadcastRangeByCell( SvtBroadcaster& rBC, const ScRange& rRange, SfxHintId nHint )
+{
+ ScHint aHint(nHint, ScAddress());
+ for (SCTAB nTab = rRange.aStart.Tab(); nTab <= rRange.aEnd.Tab(); ++nTab)
+ {
+ aHint.SetAddressTab(nTab);
+ for (SCCOL nCol = rRange.aStart.Col(); nCol <= rRange.aEnd.Col(); ++nCol)
+ {
+ aHint.SetAddressCol(nCol);
+ for (SCROW nRow = rRange.aStart.Row(); nRow <= rRange.aEnd.Row(); ++nRow)
+ {
+ aHint.SetAddressRow(nRow);
+ rBC.Broadcast(aHint);
+ }
+ }
+ }
+}
+
+}
+
+bool ScBroadcastAreaSlot::AreaBroadcast( const ScRange& rRange, SfxHintId nHint )
+{
+ if (aBroadcastAreaTbl.empty())
+ return false;
+
+ bool bInBroadcast = mbInBroadcastIteration;
+ mbInBroadcastIteration = true;
+ bool bIsBroadcasted = false;
+
+ mbHasErasedArea = false;
+
+ for (ScBroadcastAreas::const_iterator aIter( aBroadcastAreaTbl.begin()),
+ aIterEnd( aBroadcastAreaTbl.end()); aIter != aIterEnd; ++aIter )
+ {
+ if (mbHasErasedArea && isMarkedErased( aIter))
+ continue;
+
+ ScBroadcastArea* pArea = (*aIter).mpArea;
+ const ScRange& rAreaRange = pArea->GetRange();
+
+ // Take the intersection of the area range and the broadcast range.
+ ScRange aIntersection = rAreaRange.Intersection(rRange);
+ if (!aIntersection.IsValid())
+ continue;
+
+ if (pArea->IsGroupListening())
+ {
+ if (pBASM->IsInBulkBroadcast())
+ {
+ pBASM->InsertBulkGroupArea(pArea, aIntersection);
+ }
+ else
+ {
+ broadcastRangeByCell(pArea->GetBroadcaster(), aIntersection, nHint);
+ bIsBroadcasted = true;
+ }
+ }
+ else if (!pBASM->IsInBulkBroadcast() || pBASM->InsertBulkArea( pArea))
+ {
+ broadcastRangeByCell(pArea->GetBroadcaster(), aIntersection, nHint);
+ bIsBroadcasted = true;
+ }
+ }
+
+ mbInBroadcastIteration = bInBroadcast;
+
+ // A Notify() during broadcast may call EndListeningArea() and thus dispose
+ // an area if it was the last listener, which would invalidate an iterator
+ // pointing to it, hence the real erase is done afterwards.
+ FinallyEraseAreas();
+
+ return bIsBroadcasted;
+}
+
+bool ScBroadcastAreaSlot::AreaBroadcast( const ScHint& rHint)
+{
+ if (aBroadcastAreaTbl.empty())
+ return false;
+
+ bool bInBroadcast = mbInBroadcastIteration;
+ mbInBroadcastIteration = true;
+ bool bIsBroadcasted = false;
+
+ mbHasErasedArea = false;
+
+ const ScRange& rRange = rHint.GetRange();
+ for (ScBroadcastAreas::const_iterator aIter( aBroadcastAreaTbl.begin()),
+ aIterEnd( aBroadcastAreaTbl.end()); aIter != aIterEnd; ++aIter )
+ {
+ if (mbHasErasedArea && isMarkedErased( aIter))
+ continue;
+
+ ScBroadcastArea* pArea = (*aIter).mpArea;
+ const ScRange& rAreaRange = pArea->GetRange();
+ if (rAreaRange.Intersects( rRange))
+ {
+ if (pArea->IsGroupListening())
+ {
+ if (pBASM->IsInBulkBroadcast())
+ {
+ pBASM->InsertBulkGroupArea(pArea, rRange);
+ }
+ else
+ {
+ pArea->GetBroadcaster().Broadcast( rHint);
+ bIsBroadcasted = true;
+ }
+ }
+ else if (!pBASM->IsInBulkBroadcast() || pBASM->InsertBulkArea( pArea))
+ {
+ pArea->GetBroadcaster().Broadcast( rHint);
+ bIsBroadcasted = true;
+ }
+ }
+ }
+
+ mbInBroadcastIteration = bInBroadcast;
+
+ // A Notify() during broadcast may call EndListeningArea() and thus dispose
+ // an area if it was the last listener, which would invalidate an iterator
+ // pointing to it, hence the real erase is done afterwards.
+ FinallyEraseAreas();
+
+ return bIsBroadcasted;
+}
+
+void ScBroadcastAreaSlot::DelBroadcastAreasInRange( const ScRange& rRange )
+{
+ if (aBroadcastAreaTbl.empty())
+ return;
+ for (ScBroadcastAreas::iterator aIter( aBroadcastAreaTbl.begin());
+ aIter != aBroadcastAreaTbl.end(); /* increment in body */ )
+ {
+ const ScRange& rAreaRange = (*aIter).mpArea->GetRange();
+ if (rRange.Contains( rAreaRange))
+ {
+ ScBroadcastArea* pArea = (*aIter).mpArea;
+ aIter = aBroadcastAreaTbl.erase(aIter); // erase before modifying
+ if (!pArea->DecRef())
+ {
+ if (pBASM->IsInBulkBroadcast())
+ pBASM->RemoveBulkArea( pArea);
+ delete pArea;
+ }
+ }
+ else
+ ++aIter;
+ }
+}
+
+void ScBroadcastAreaSlot::UpdateRemove( UpdateRefMode eUpdateRefMode,
+ const ScRange& rRange, SCCOL nDx, SCROW nDy, SCTAB nDz )
+{
+ if (aBroadcastAreaTbl.empty())
+ return;
+
+ SCCOL nCol1, nCol2, theCol1, theCol2;
+ SCROW nRow1, nRow2, theRow1, theRow2;
+ SCTAB nTab1, nTab2, theTab1, theTab2;
+ rRange.GetVars( nCol1, nRow1, nTab1, nCol2, nRow2, nTab2);
+ for ( ScBroadcastAreas::iterator aIter( aBroadcastAreaTbl.begin());
+ aIter != aBroadcastAreaTbl.end(); /* increment in body */ )
+ {
+ ScBroadcastArea* pArea = (*aIter).mpArea;
+ if ( pArea->IsInUpdateChain() )
+ {
+ aIter = aBroadcastAreaTbl.erase(aIter);
+ pArea->DecRef();
+ }
+ else
+ {
+ pArea->GetRange().GetVars( theCol1, theRow1, theTab1, theCol2, theRow2, theTab2);
+ if ( ScRefUpdate::Update( pDoc, eUpdateRefMode,
+ nCol1,nRow1,nTab1, nCol2,nRow2,nTab2, nDx,nDy,nDz,
+ theCol1,theRow1,theTab1, theCol2,theRow2,theTab2 ))
+ {
+ aIter = aBroadcastAreaTbl.erase(aIter);
+ pArea->DecRef();
+ if (pBASM->IsInBulkBroadcast())
+ pBASM->RemoveBulkArea( pArea);
+ pArea->SetInUpdateChain( true );
+ ScBroadcastArea* pUC = pBASM->GetEOUpdateChain();
+ if ( pUC )
+ pUC->SetUpdateChainNext( pArea );
+ else // no tail => no head
+ pBASM->SetUpdateChain( pArea );
+ pBASM->SetEOUpdateChain( pArea );
+ }
+ else
+ ++aIter;
+ }
+ }
+}
+
+void ScBroadcastAreaSlot::UpdateRemoveArea( ScBroadcastArea* pArea )
+{
+ ScBroadcastAreas::iterator aIter( aBroadcastAreaTbl.find( pArea));
+ if (aIter == aBroadcastAreaTbl.end())
+ return;
+ if ((*aIter).mpArea != pArea)
+ OSL_FAIL( "UpdateRemoveArea: area pointer mismatch");
+ else
+ {
+ aBroadcastAreaTbl.erase( aIter);
+ pArea->DecRef();
+ }
+}
+
+void ScBroadcastAreaSlot::UpdateInsert( ScBroadcastArea* pArea )
+{
+ ::std::pair< ScBroadcastAreas::iterator, bool > aPair =
+ aBroadcastAreaTbl.insert( pArea);
+ if (aPair.second)
+ pArea->IncRef();
+ else
+ {
+ // Identical area already exists, add listeners.
+ ScBroadcastArea* pTarget = (*(aPair.first)).mpArea;
+ if (pArea != pTarget)
+ {
+ SvtBroadcaster& rTarget = pTarget->GetBroadcaster();
+ SvtBroadcaster::ListenersType& rListeners = pArea->GetBroadcaster().GetAllListeners();
+ for (auto& pListener : rListeners)
+ {
+ SvtListener& rListener = *pListener;
+ rListener.StartListening(rTarget);
+ }
+ }
+ }
+}
+
+void ScBroadcastAreaSlot::EraseArea( ScBroadcastAreas::iterator& rIter )
+{
+ if (mbInBroadcastIteration)
+ {
+ (*rIter).mbErasure = true; // mark for erasure
+ mbHasErasedArea = true; // at least one area is marked for erasure.
+ pBASM->PushAreaToBeErased( this, rIter);
+ }
+ else
+ {
+ ScBroadcastArea* pArea = (*rIter).mpArea;
+ aBroadcastAreaTbl.erase( rIter);
+ if (!pArea->DecRef())
+ {
+ if (pBASM->IsInBulkBroadcast())
+ pBASM->RemoveBulkGroupArea(pArea);
+ delete pArea;
+ }
+ }
+}
+
+void ScBroadcastAreaSlot::GetAllListeners(
+ const ScRange& rRange, std::vector<sc::AreaListener>& rListeners,
+ sc::AreaOverlapType eType, sc::ListenerGroupType eGroup )
+{
+ for (ScBroadcastAreas::const_iterator aIter( aBroadcastAreaTbl.begin()),
+ aIterEnd( aBroadcastAreaTbl.end()); aIter != aIterEnd; ++aIter )
+ {
+ if (isMarkedErased( aIter))
+ continue;
+
+ ScBroadcastArea* pArea = (*aIter).mpArea;
+ const ScRange& rAreaRange = pArea->GetRange();
+ switch (eGroup)
+ {
+ case sc::ListenerGroupType::Group:
+ if (!pArea->IsGroupListening())
+ continue;
+ break;
+ case sc::ListenerGroupType::Both:
+ default:
+ ;
+ }
+
+ switch (eType)
+ {
+ case sc::AreaOverlapType::Inside:
+ if (!rRange.Contains(rAreaRange))
+ // The range needs to be fully inside specified range.
+ continue;
+ break;
+ case sc::AreaOverlapType::InsideOrOverlap:
+ if (!rRange.Intersects(rAreaRange))
+ // The range needs to be partially overlapping or fully inside.
+ continue;
+ break;
+ case sc::AreaOverlapType::OneRowInside:
+ if (rAreaRange.aStart.Row() != rAreaRange.aEnd.Row() || !rRange.Contains(rAreaRange))
+ // The range needs to be one single row and fully inside
+ // specified range.
+ continue;
+ break;
+ case sc::AreaOverlapType::OneColumnInside:
+ if (rAreaRange.aStart.Col() != rAreaRange.aEnd.Col() || !rRange.Contains(rAreaRange))
+ // The range needs to be one single column and fully inside
+ // specified range.
+ continue;
+ break;
+ }
+
+ SvtBroadcaster::ListenersType& rLst = pArea->GetBroadcaster().GetAllListeners();
+ for (const auto& pListener : rLst)
+ {
+ sc::AreaListener aEntry;
+ aEntry.maArea = rAreaRange;
+ aEntry.mbGroupListening = pArea->IsGroupListening();
+ aEntry.mpListener = pListener;
+ rListeners.push_back(aEntry);
+ }
+ }
+}
+
+#if DEBUG_AREA_BROADCASTER
+void ScBroadcastAreaSlot::Dump() const
+{
+ for (const ScBroadcastAreaEntry& rEntry : aBroadcastAreaTbl)
+ {
+ const ScBroadcastArea* pArea = rEntry.mpArea;
+ const SvtBroadcaster& rBC = pArea->GetBroadcaster();
+ const SvtBroadcaster::ListenersType& rListeners = rBC.GetAllListeners();
+ size_t n = rListeners.size();
+
+ cout << " * range: " << OUStringToOString(pArea->GetRange().Format(ScRefFlags::VALID|ScRefFlags::TAB_3D, pDoc), RTL_TEXTENCODING_UTF8).getStr()
+ << ", group: " << pArea->IsGroupListening()
+ << ", listener count: " << n << endl;
+
+ for (size_t i = 0; i < n; ++i)
+ {
+ const ScFormulaCell* pFC = dynamic_cast<const ScFormulaCell*>(rListeners[i]);
+ if (pFC)
+ {
+ cout << " * listener: formula cell: "
+ << OUStringToOString(pFC->aPos.Format(ScRefFlags::VALID|ScRefFlags::TAB_3D, pDoc), RTL_TEXTENCODING_UTF8).getStr()
+ << endl;
+ continue;
+ }
+
+ const sc::FormulaGroupAreaListener* pFGListener = dynamic_cast<const sc::FormulaGroupAreaListener*>(rListeners[i]);
+ if (pFGListener)
+ {
+ cout << " * listener: formula group: (pos: "
+ << OUStringToOString(pFGListener->getTopCellPos().Format(ScRefFlags::VALID | ScRefFlags::TAB_3D, pDoc), RTL_TEXTENCODING_UTF8).getStr()
+ << ", length: " << pFGListener->getGroupLength()
+ << ")" << endl;
+ continue;
+ }
+
+ cout << " * listener: unknown" << endl;
+ }
+ }
+}
+#endif
+
+void ScBroadcastAreaSlot::FinallyEraseAreas()
+{
+ pBASM->FinallyEraseAreas( this);
+}
+
+// --- ScBroadcastAreaSlotMachine -------------------------------------
+
+ScBroadcastAreaSlotMachine::TableSlots::TableSlots(SCSIZE nBcaSlots)
+ : mnBcaSlots(nBcaSlots)
+{
+ ppSlots.reset( new ScBroadcastAreaSlot* [ nBcaSlots ] );
+ memset( ppSlots.get(), 0 , sizeof( ScBroadcastAreaSlot* ) * nBcaSlots );
+}
+
+ScBroadcastAreaSlotMachine::TableSlots::~TableSlots()
+{
+ for ( ScBroadcastAreaSlot** pp = ppSlots.get() + mnBcaSlots; --pp >= ppSlots.get(); /* nothing */ )
+ delete *pp;
+}
+
+ScBroadcastAreaSlotMachine::ScBroadcastAreaSlotMachine(
+ ScDocument* pDocument ) :
+ pDoc( pDocument ),
+ pUpdateChain( nullptr ),
+ pEOUpdateChain( nullptr ),
+ nInBulkBroadcast( 0 )
+{
+ // initSlotDistribution ---------
+ // Logarithmic or any other distribution.
+ // Upper and leftmost sheet part usually is more populated and referenced and gets fine
+ // grained resolution, larger data in larger hunks.
+ // Just like with cells, slots are organized in columns. Slot 0 is for first nSliceRow x nSliceCol
+ // cells, slot 1 is for next nSliceRow x nSliceCel cells below, etc. After a while the size of row
+ // slice doubles (making more cells share the same slot), this distribution data is stored
+ // in ScSlotData including ranges of cells. This is repeated for another column of nSliceCol cells,
+ // again with the column slice doubling after some time.
+ // Functions ComputeSlotOffset(), ComputeArePoints() and ComputeNextSlot() do the necessary
+ // calculations.
+ SCSIZE nSlots = 0;
+ // This should be SCCOL, but that's only 16bit and would overflow when doubling 16k columns.
+ sal_Int32 nCol1 = 0;
+ sal_Int32 nCol2 = 1024;
+ SCSIZE nSliceCol = 16;
+ while (nCol2 <= pDoc->GetMaxColCount())
+ {
+ SCROW nRow1 = 0;
+ SCROW nRow2 = 32*1024;
+ SCSIZE nSliceRow = 128;
+ SCSIZE nSlotsCol = 0;
+ SCSIZE nSlotsStartCol = nSlots;
+ // Must be sorted by row1,row2!
+ while (nRow2 <= pDoc->GetMaxRowCount())
+ {
+ maSlotDistribution.emplace_back(nRow1, nRow2, nSliceRow, nSlotsCol, nCol1, nCol2, nSliceCol, nSlotsStartCol);
+ nSlotsCol += (nRow2 - nRow1) / nSliceRow;
+ nRow1 = nRow2;
+ nRow2 *= 2;
+ nSliceRow *= 2;
+ }
+ // Store the number of slots in a column in mnBcaSlotsCol, so that finding a slot
+ // to the right can be computed quickly in ComputeNextSlot().
+ if(nCol1 == 0)
+ mnBcaSlotsCol = nSlotsCol;
+ assert(nSlotsCol == mnBcaSlotsCol);
+ nSlots += (nCol2 - nCol1) / nSliceCol * nSlotsCol;
+ nCol1 = nCol2;
+ nCol2 *= 2;
+ nSliceCol *= 2;
+ }
+ mnBcaSlots = nSlots;
+#ifdef DBG_UTIL
+ DoChecks();
+#endif
+}
+
+ScBroadcastAreaSlotMachine::~ScBroadcastAreaSlotMachine()
+{
+ aTableSlotsMap.clear();
+ pBCAlways.reset();
+ // Areas to-be-erased still present is a serious error in handling, but at
+ // this stage there's nothing we can do anymore.
+ SAL_WARN_IF( !maAreasToBeErased.empty(), "sc.core", "ScBroadcastAreaSlotMachine::dtor: maAreasToBeErased not empty");
+}
+
+inline SCSIZE ScBroadcastAreaSlotMachine::ComputeSlotOffset(
+ const ScAddress& rAddress ) const
+{
+ SCROW nRow = rAddress.Row();
+ SCCOL nCol = rAddress.Col();
+ if ( !pDoc->ValidRow(nRow) || !pDoc->ValidCol(nCol) )
+ {
+ OSL_FAIL( "Row/Col invalid, using first slot!" );
+ return 0;
+ }
+ for (const ScSlotData& rSD : maSlotDistribution)
+ {
+ if (nRow < rSD.nStopRow && nCol < rSD.nStopCol)
+ {
+ assert(nRow >= rSD.nStartRow);
+ assert(nCol >= rSD.nStartCol);
+ SCSIZE slot = rSD.nCumulatedRow
+ + static_cast<SCSIZE>(nRow - rSD.nStartRow) / rSD.nSliceRow
+ + rSD.nCumulatedCol
+ + static_cast<SCSIZE>(nCol - rSD.nStartCol) / rSD.nSliceCol * mnBcaSlotsCol;
+ assert(slot < mnBcaSlots);
+ return slot;
+ }
+ }
+ OSL_FAIL( "No slot found, using last!" );
+ return mnBcaSlots - 1;
+}
+
+void ScBroadcastAreaSlotMachine::ComputeAreaPoints( const ScRange& rRange,
+ SCSIZE& rStart, SCSIZE& rEnd, SCSIZE& rRowBreak ) const
+{
+ rStart = ComputeSlotOffset( rRange.aStart );
+ rEnd = ComputeSlotOffset( rRange.aEnd );
+ // count of row slots per column minus one
+ rRowBreak = ComputeSlotOffset(
+ ScAddress( rRange.aStart.Col(), rRange.aEnd.Row(), 0 ) ) - rStart;
+}
+
+static void ComputeNextSlot( SCSIZE & nOff, SCSIZE & nBreak, ScBroadcastAreaSlot** & pp,
+ SCSIZE & nStart, ScBroadcastAreaSlot** const & ppSlots, SCSIZE nRowBreak, SCSIZE nBcaSlotsCol )
+{
+ if ( nOff < nBreak )
+ {
+ ++nOff;
+ ++pp;
+ }
+ else
+ {
+ nStart += nBcaSlotsCol;
+ nOff = nStart;
+ pp = ppSlots + nOff;
+ nBreak = nOff + nRowBreak;
+ }
+}
+
+#ifdef DBG_UTIL
+static void compare(SCSIZE value1, SCSIZE value2, int line)
+{
+ if(value1!=value2)
+ SAL_WARN("sc", "V1:" << value1 << " V2:" << value2 << " (" << line << ")");
+ assert(value1 == value2);
+}
+
+// Basic checks that the calculations work correctly.
+void ScBroadcastAreaSlotMachine::DoChecks()
+{
+ // Copy&paste from the ctor.
+ constexpr SCSIZE nSliceRow = 128;
+ constexpr SCSIZE nSliceCol = 16;
+ // First and second column are in the same slice and so get the same slot.
+ compare( ComputeSlotOffset( ScAddress( 0, 0, 0 )), ComputeSlotOffset( ScAddress( 1, 0, 0 )), __LINE__);
+ // Each nSliceRow rows are offset by one slot (at the start of the logarithmic distribution).
+ compare( ComputeSlotOffset( ScAddress( 0, 0, 0 )),
+ ComputeSlotOffset( ScAddress( 0, nSliceRow, 0 )) - 1, __LINE__ );
+ compare( ComputeSlotOffset( ScAddress( nSliceCol - 1, 0, 0 )),
+ ComputeSlotOffset( ScAddress( nSliceCol, 0, 0 )) - mnBcaSlotsCol, __LINE__ );
+ // Check that last cell is the last slot.
+ compare( ComputeSlotOffset( ScAddress( pDoc->GetMaxColCount() - 1, pDoc->GetMaxRowCount() - 1, 0 )),
+ mnBcaSlots - 1, __LINE__ );
+ // Check that adjacent rows in the same column but in different distribution areas differ by one slot.
+ for( size_t i = 0; i < maSlotDistribution.size() - 1; ++i )
+ {
+ const ScSlotData& s1 = maSlotDistribution[ i ];
+ const ScSlotData& s2 = maSlotDistribution[ i + 1 ];
+ if( s1.nStartCol == s2.nStartCol )
+ {
+ assert( s1.nStopRow == s2.nStartRow );
+ compare( ComputeSlotOffset( ScAddress( s1.nStartCol, s1.nStopRow - 1, 0 )),
+ ComputeSlotOffset( ScAddress( s1.nStartCol, s1.nStopRow, 0 )) - 1, __LINE__ );
+ }
+ }
+ // Check that adjacent columns in the same row but in different distribution areas differ by mnBcaSlotsCol.
+ for( size_t i = 0; i < maSlotDistribution.size() - 1; ++i )
+ {
+ const ScSlotData& s1 = maSlotDistribution[ i ];
+ for( size_t j = i + 1; j < maSlotDistribution.size(); ++j )
+ {
+ const ScSlotData& s2 = maSlotDistribution[ i + 1 ];
+ if( s1.nStartRow == s2.nStartRow && s1.nStopCol == s2.nStartCol )
+ {
+ assert( s1.nStopRow == s2.nStartRow );
+ compare( ComputeSlotOffset( ScAddress( s1.nStopCol - 1, s1.nStartRow, 0 )),
+ ComputeSlotOffset( ScAddress( s1.nStopCol, s1.nStartRow, 0 )) - mnBcaSlotsCol, __LINE__ );
+ }
+ }
+ }
+ // Iterate all slots.
+ ScRange range( ScAddress( 0, 0, 0 ), ScAddress( pDoc->MaxCol(), pDoc->MaxRow(), 0 ));
+ SCSIZE nStart, nEnd, nRowBreak;
+ ComputeAreaPoints( range, nStart, nEnd, nRowBreak );
+ assert( nStart == 0 );
+ assert( nEnd == mnBcaSlots - 1 );
+ SCSIZE nOff = nStart;
+ SCSIZE nBreak = nOff + nRowBreak;
+ std::unique_ptr<ScBroadcastAreaSlot*[]> slots( new ScBroadcastAreaSlot*[ mnBcaSlots ] ); // dummy, not accessed
+ ScBroadcastAreaSlot** ppSlots = slots.get();
+ ScBroadcastAreaSlot** pp = ppSlots;
+ while ( nOff <= nEnd )
+ {
+ SCSIZE previous = nOff;
+ ComputeNextSlot( nOff, nBreak, pp, nStart, ppSlots, nRowBreak, mnBcaSlotsCol);
+ compare( nOff, previous + 1, __LINE__ );
+ }
+ // Iterate slots in the last row (each will differ by mnBcaSlotsCol).
+ range = ScRange( ScAddress( 0, pDoc->MaxRow(), 0 ),
+ ScAddress( pDoc->MaxCol(), pDoc->MaxRow() - 1, 0 ));
+ ComputeAreaPoints( range, nStart, nEnd, nRowBreak );
+ assert( nStart == mnBcaSlotsCol - 1 );
+ assert( nEnd == mnBcaSlots - 1 );
+ nOff = nStart;
+ nBreak = nOff + nRowBreak;
+ ppSlots = slots.get();
+ pp = ppSlots;
+ while ( nOff <= nEnd )
+ {
+ SCSIZE previous = nOff;
+ ComputeNextSlot( nOff, nBreak, pp, nStart, ppSlots, nRowBreak, mnBcaSlotsCol);
+ compare( nOff, previous + mnBcaSlotsCol, __LINE__ );
+ }
+}
+#endif
+
+void ScBroadcastAreaSlotMachine::StartListeningArea(
+ const ScRange& rRange, bool bGroupListening, SvtListener* pListener )
+{
+ if ( rRange == BCA_LISTEN_ALWAYS )
+ {
+ if ( !pBCAlways )
+ pBCAlways.reset( new SvtBroadcaster );
+ pListener->StartListening( *pBCAlways );
+ }
+ else
+ {
+ // A new area needs to be inserted to the corresponding slots, for 3D
+ // ranges for all sheets, do not slice into per sheet areas or the
+ // !bDone will break too early (i.e. after the first sheet) if
+ // subsequent listeners are to be added.
+ ScBroadcastArea* pArea = nullptr;
+ bool bDone = false;
+ for (SCTAB nTab = rRange.aStart.Tab();
+ !bDone && nTab <= rRange.aEnd.Tab(); ++nTab)
+ {
+ TableSlotsMap::iterator iTab( aTableSlotsMap.find( nTab));
+ if (iTab == aTableSlotsMap.end())
+ iTab = aTableSlotsMap.emplace(nTab, std::make_unique<TableSlots>(mnBcaSlots)).first;
+ ScBroadcastAreaSlot** ppSlots = (*iTab).second->getSlots();
+ SCSIZE nStart, nEnd, nRowBreak;
+ ComputeAreaPoints( rRange, nStart, nEnd, nRowBreak );
+ SCSIZE nOff = nStart;
+ SCSIZE nBreak = nOff + nRowBreak;
+ ScBroadcastAreaSlot** pp = ppSlots + nOff;
+ while ( !bDone && nOff <= nEnd )
+ {
+ if ( !*pp )
+ *pp = new ScBroadcastAreaSlot( pDoc, this );
+ if (!pArea)
+ {
+ // If the call to StartListeningArea didn't create the
+ // ScBroadcastArea, listeners were added to an already
+ // existing identical area that doesn't need to be inserted
+ // to slots again.
+ if (!(*pp)->StartListeningArea( rRange, bGroupListening, pListener, pArea))
+ bDone = true;
+ }
+ else
+ (*pp)->InsertListeningArea( pArea);
+ ComputeNextSlot( nOff, nBreak, pp, nStart, ppSlots, nRowBreak, mnBcaSlotsCol);
+ }
+ }
+ }
+}
+
+void ScBroadcastAreaSlotMachine::EndListeningArea(
+ const ScRange& rRange, bool bGroupListening, SvtListener* pListener )
+{
+ if ( rRange == BCA_LISTEN_ALWAYS )
+ {
+ if ( pBCAlways )
+ {
+ pListener->EndListening( *pBCAlways);
+ if (!pBCAlways->HasListeners())
+ {
+ pBCAlways.reset();
+ }
+ }
+ }
+ else
+ {
+ SCTAB nEndTab = rRange.aEnd.Tab();
+ for (TableSlotsMap::iterator iTab( aTableSlotsMap.lower_bound( rRange.aStart.Tab()));
+ iTab != aTableSlotsMap.end() && (*iTab).first <= nEndTab; ++iTab)
+ {
+ ScBroadcastAreaSlot** ppSlots = (*iTab).second->getSlots();
+ SCSIZE nStart, nEnd, nRowBreak;
+ ComputeAreaPoints( rRange, nStart, nEnd, nRowBreak );
+ SCSIZE nOff = nStart;
+ SCSIZE nBreak = nOff + nRowBreak;
+ ScBroadcastAreaSlot** pp = ppSlots + nOff;
+ ScBroadcastArea* pArea = nullptr;
+ if (nOff == 0 && nEnd == mnBcaSlots-1)
+ {
+ // Slightly optimized for 0,0,MAXCOL,MAXROW calls as they
+ // happen for insertion and deletion of sheets.
+ ScBroadcastAreaSlot** const pStop = ppSlots + nEnd;
+ do
+ {
+ if ( *pp )
+ (*pp)->EndListeningArea( rRange, bGroupListening, pListener, pArea);
+ } while (++pp < pStop);
+ }
+ else
+ {
+ while ( nOff <= nEnd )
+ {
+ if ( *pp )
+ (*pp)->EndListeningArea( rRange, bGroupListening, pListener, pArea);
+ ComputeNextSlot( nOff, nBreak, pp, nStart, ppSlots, nRowBreak, mnBcaSlotsCol);
+ }
+ }
+ }
+ }
+}
+
+bool ScBroadcastAreaSlotMachine::AreaBroadcast( const ScRange& rRange, SfxHintId nHint )
+{
+ bool bBroadcasted = false;
+ SCTAB nEndTab = rRange.aEnd.Tab();
+ for (TableSlotsMap::iterator iTab( aTableSlotsMap.lower_bound( rRange.aStart.Tab()));
+ iTab != aTableSlotsMap.end() && (*iTab).first <= nEndTab; ++iTab)
+ {
+ ScBroadcastAreaSlot** ppSlots = (*iTab).second->getSlots();
+ SCSIZE nStart, nEnd, nRowBreak;
+ ComputeAreaPoints( rRange, nStart, nEnd, nRowBreak );
+ SCSIZE nOff = nStart;
+ SCSIZE nBreak = nOff + nRowBreak;
+ ScBroadcastAreaSlot** pp = ppSlots + nOff;
+ while ( nOff <= nEnd )
+ {
+ if ( *pp )
+ bBroadcasted |= (*pp)->AreaBroadcast( rRange, nHint );
+ ComputeNextSlot( nOff, nBreak, pp, nStart, ppSlots, nRowBreak, mnBcaSlotsCol);
+ }
+ }
+ return bBroadcasted;
+}
+
+bool ScBroadcastAreaSlotMachine::AreaBroadcast( const ScHint& rHint ) const
+{
+ const ScAddress& rAddress = rHint.GetStartAddress();
+ if ( rAddress == BCA_BRDCST_ALWAYS )
+ {
+ if ( pBCAlways )
+ {
+ pBCAlways->Broadcast( rHint );
+ return true;
+ }
+ else
+ return false;
+ }
+ else
+ {
+ TableSlotsMap::const_iterator iTab( aTableSlotsMap.find( rAddress.Tab()));
+ if (iTab == aTableSlotsMap.end())
+ return false;
+ // Process all slots for the given row range.
+ ScRange broadcastRange( rAddress,
+ ScAddress( rAddress.Col(), rAddress.Row() + rHint.GetRowCount() - 1, rAddress.Tab()));
+ bool bBroadcasted = false;
+ ScBroadcastAreaSlot** ppSlots = (*iTab).second->getSlots();
+ SCSIZE nStart, nEnd, nRowBreak;
+ ComputeAreaPoints( broadcastRange, nStart, nEnd, nRowBreak );
+ SCSIZE nOff = nStart;
+ SCSIZE nBreak = nOff + nRowBreak;
+ ScBroadcastAreaSlot** pp = ppSlots + nOff;
+ while ( nOff <= nEnd )
+ {
+ if ( *pp )
+ bBroadcasted |= (*pp)->AreaBroadcast( rHint );
+ ComputeNextSlot( nOff, nBreak, pp, nStart, ppSlots, nRowBreak, mnBcaSlotsCol);
+ }
+ return bBroadcasted;
+ }
+}
+
+void ScBroadcastAreaSlotMachine::DelBroadcastAreasInRange(
+ const ScRange& rRange )
+{
+ SCTAB nEndTab = rRange.aEnd.Tab();
+ for (TableSlotsMap::iterator iTab( aTableSlotsMap.lower_bound( rRange.aStart.Tab()));
+ iTab != aTableSlotsMap.end() && (*iTab).first <= nEndTab; ++iTab)
+ {
+ ScBroadcastAreaSlot** ppSlots = (*iTab).second->getSlots();
+ SCSIZE nStart, nEnd, nRowBreak;
+ ComputeAreaPoints( rRange, nStart, nEnd, nRowBreak );
+ SCSIZE nOff = nStart;
+ SCSIZE nBreak = nOff + nRowBreak;
+ ScBroadcastAreaSlot** pp = ppSlots + nOff;
+ if (nOff == 0 && nEnd == mnBcaSlots-1)
+ {
+ // Slightly optimized for 0,0,MAXCOL,MAXROW calls as they
+ // happen for insertion and deletion of sheets.
+ ScBroadcastAreaSlot** const pStop = ppSlots + nEnd;
+ do
+ {
+ if ( *pp )
+ (*pp)->DelBroadcastAreasInRange( rRange );
+ } while (++pp < pStop);
+ }
+ else
+ {
+ while ( nOff <= nEnd )
+ {
+ if ( *pp )
+ (*pp)->DelBroadcastAreasInRange( rRange );
+ ComputeNextSlot( nOff, nBreak, pp, nStart, ppSlots, nRowBreak, mnBcaSlotsCol);
+ }
+ }
+ }
+}
+
+// for all affected: remove, chain, update range, insert, and maybe delete
+void ScBroadcastAreaSlotMachine::UpdateBroadcastAreas(
+ UpdateRefMode eUpdateRefMode,
+ const ScRange& rRange, SCCOL nDx, SCROW nDy, SCTAB nDz )
+{
+ // remove affected and put in chain
+ SCTAB nEndTab = rRange.aEnd.Tab();
+ for (TableSlotsMap::iterator iTab( aTableSlotsMap.lower_bound( rRange.aStart.Tab()));
+ iTab != aTableSlotsMap.end() && (*iTab).first <= nEndTab; ++iTab)
+ {
+ ScBroadcastAreaSlot** ppSlots = (*iTab).second->getSlots();
+ SCSIZE nStart, nEnd, nRowBreak;
+ ComputeAreaPoints( rRange, nStart, nEnd, nRowBreak );
+ SCSIZE nOff = nStart;
+ SCSIZE nBreak = nOff + nRowBreak;
+ ScBroadcastAreaSlot** pp = ppSlots + nOff;
+ if (nOff == 0 && nEnd == mnBcaSlots-1)
+ {
+ // Slightly optimized for 0,0,MAXCOL,MAXROW calls as they
+ // happen for insertion and deletion of sheets.
+ ScBroadcastAreaSlot** const pStop = ppSlots + nEnd;
+ do
+ {
+ if ( *pp )
+ (*pp)->UpdateRemove( eUpdateRefMode, rRange, nDx, nDy, nDz );
+ } while (++pp < pStop);
+ }
+ else
+ {
+ while ( nOff <= nEnd )
+ {
+ if ( *pp )
+ (*pp)->UpdateRemove( eUpdateRefMode, rRange, nDx, nDy, nDz );
+ ComputeNextSlot( nOff, nBreak, pp, nStart, ppSlots, nRowBreak, mnBcaSlotsCol);
+ }
+ }
+ }
+
+ // Updating an area's range will modify the hash key, remove areas from all
+ // affected slots. Will be reinserted later with the updated range.
+ ScBroadcastArea* pChain = pUpdateChain;
+ while (pChain)
+ {
+ ScBroadcastArea* pArea = pChain;
+ pChain = pArea->GetUpdateChainNext();
+ ScRange aRange( pArea->GetRange());
+ // remove from slots
+ for (SCTAB nTab = aRange.aStart.Tab(); nTab <= aRange.aEnd.Tab() && pArea->GetRef(); ++nTab)
+ {
+ TableSlotsMap::iterator iTab( aTableSlotsMap.find( nTab));
+ if (iTab == aTableSlotsMap.end())
+ {
+ OSL_FAIL( "UpdateBroadcastAreas: Where's the TableSlot?!?");
+ continue; // for
+ }
+ ScBroadcastAreaSlot** ppSlots = (*iTab).second->getSlots();
+ SCSIZE nStart, nEnd, nRowBreak;
+ ComputeAreaPoints( aRange, nStart, nEnd, nRowBreak );
+ SCSIZE nOff = nStart;
+ SCSIZE nBreak = nOff + nRowBreak;
+ ScBroadcastAreaSlot** pp = ppSlots + nOff;
+ while ( nOff <= nEnd && pArea->GetRef() )
+ {
+ if (*pp)
+ (*pp)->UpdateRemoveArea( pArea);
+ ComputeNextSlot( nOff, nBreak, pp, nStart, ppSlots, nRowBreak, mnBcaSlotsCol);
+ }
+ }
+
+ }
+
+ // shift sheets
+ if (nDz)
+ {
+ if (nDz < 0)
+ {
+ TableSlotsMap::iterator iDel( aTableSlotsMap.lower_bound( rRange.aStart.Tab()));
+ TableSlotsMap::iterator iTab( aTableSlotsMap.lower_bound( rRange.aStart.Tab() - nDz));
+ // Remove sheets, if any, iDel or/and iTab may as well point to end().
+ while (iDel != iTab)
+ {
+ iDel = aTableSlotsMap.erase(iDel);
+ }
+ // shift remaining down
+ while (iTab != aTableSlotsMap.end())
+ {
+ SCTAB nTab = (*iTab).first + nDz;
+ aTableSlotsMap[nTab] = std::move((*iTab).second);
+ iTab = aTableSlotsMap.erase(iTab);
+ }
+ }
+ else
+ {
+ TableSlotsMap::iterator iStop( aTableSlotsMap.lower_bound( rRange.aStart.Tab()));
+ if (iStop != aTableSlotsMap.end())
+ {
+ bool bStopIsBegin = (iStop == aTableSlotsMap.begin());
+ if (!bStopIsBegin)
+ --iStop;
+ TableSlotsMap::iterator iTab( aTableSlotsMap.end());
+ --iTab;
+ while (iTab != iStop)
+ {
+ SCTAB nTab = (*iTab).first + nDz;
+ aTableSlotsMap[nTab] = std::move((*iTab).second);
+ aTableSlotsMap.erase( iTab--);
+ }
+ // Shift the very first, iTab==iStop in this case.
+ if (bStopIsBegin)
+ {
+ SCTAB nTab = (*iTab).first + nDz;
+ aTableSlotsMap[nTab] = std::move((*iTab).second);
+ aTableSlotsMap.erase( iStop);
+ }
+ }
+ }
+ }
+
+ // work off chain
+ SCCOL nCol1, nCol2, theCol1, theCol2;
+ SCROW nRow1, nRow2, theRow1, theRow2;
+ SCTAB nTab1, nTab2, theTab1, theTab2;
+ rRange.GetVars( nCol1, nRow1, nTab1, nCol2, nRow2, nTab2);
+ while ( pUpdateChain )
+ {
+ ScBroadcastArea* pArea = pUpdateChain;
+ ScRange aRange( pArea->GetRange());
+ pUpdateChain = pArea->GetUpdateChainNext();
+
+ // update range
+ aRange.GetVars( theCol1, theRow1, theTab1, theCol2, theRow2, theTab2);
+ if ( ScRefUpdate::Update( pDoc, eUpdateRefMode,
+ nCol1,nRow1,nTab1, nCol2,nRow2,nTab2, nDx,nDy,nDz,
+ theCol1,theRow1,theTab1, theCol2,theRow2,theTab2 ))
+ {
+ aRange = ScRange( theCol1,theRow1,theTab1, theCol2,theRow2,theTab2 );
+ pArea->UpdateRange( aRange );
+ // For DDE and ScLookupCache
+ pArea->GetBroadcaster().Broadcast( ScAreaChangedHint( aRange ) );
+ }
+
+ // insert to slots
+ for (SCTAB nTab = aRange.aStart.Tab(); nTab <= aRange.aEnd.Tab(); ++nTab)
+ {
+ TableSlotsMap::iterator iTab( aTableSlotsMap.find( nTab));
+ if (iTab == aTableSlotsMap.end())
+ iTab = aTableSlotsMap.emplace(nTab, std::make_unique<TableSlots>(mnBcaSlots)).first;
+ ScBroadcastAreaSlot** ppSlots = (*iTab).second->getSlots();
+ SCSIZE nStart, nEnd, nRowBreak;
+ ComputeAreaPoints( aRange, nStart, nEnd, nRowBreak );
+ SCSIZE nOff = nStart;
+ SCSIZE nBreak = nOff + nRowBreak;
+ ScBroadcastAreaSlot** pp = ppSlots + nOff;
+ while ( nOff <= nEnd )
+ {
+ if (!*pp)
+ *pp = new ScBroadcastAreaSlot( pDoc, this );
+ (*pp)->UpdateInsert( pArea );
+ ComputeNextSlot( nOff, nBreak, pp, nStart, ppSlots, nRowBreak, mnBcaSlotsCol);
+ }
+ }
+
+ // unchain
+ pArea->SetUpdateChainNext( nullptr );
+ pArea->SetInUpdateChain( false );
+
+ // Delete if not inserted to any slot. RemoveBulkArea(pArea) was
+ // already executed in UpdateRemove().
+ if (!pArea->GetRef())
+ delete pArea;
+ }
+ pEOUpdateChain = nullptr;
+}
+
+void ScBroadcastAreaSlotMachine::EnterBulkBroadcast()
+{
+ ++nInBulkBroadcast;
+}
+
+void ScBroadcastAreaSlotMachine::LeaveBulkBroadcast( SfxHintId nHintId )
+{
+ if (nInBulkBroadcast <= 0)
+ return;
+
+ if (--nInBulkBroadcast == 0)
+ {
+ ScBroadcastAreasBulk().swap( aBulkBroadcastAreas);
+ bool bBroadcasted = BulkBroadcastGroupAreas( nHintId );
+ // Trigger the "final" tracking.
+ if (pDoc->IsTrackFormulasPending())
+ pDoc->FinalTrackFormulas( nHintId );
+ else if (bBroadcasted)
+ pDoc->TrackFormulas( nHintId );
+ }
+}
+
+bool ScBroadcastAreaSlotMachine::InsertBulkArea( const ScBroadcastArea* pArea )
+{
+ return aBulkBroadcastAreas.insert( pArea ).second;
+}
+
+void ScBroadcastAreaSlotMachine::InsertBulkGroupArea( ScBroadcastArea* pArea, const ScRange& rRange )
+{
+ BulkGroupAreasType::iterator it = m_BulkGroupAreas.lower_bound(pArea);
+ if (it == m_BulkGroupAreas.end() || m_BulkGroupAreas.key_comp()(pArea, it->first))
+ {
+ // Insert a new one.
+ it = m_BulkGroupAreas.insert(it, std::make_pair(pArea, sc::ColumnSpanSet()));
+ }
+
+ sc::ColumnSpanSet& rSet = it->second;
+ rSet.set(*pDoc, rRange, true);
+}
+
+bool ScBroadcastAreaSlotMachine::BulkBroadcastGroupAreas( SfxHintId nHintId )
+{
+ if (m_BulkGroupAreas.empty())
+ return false;
+
+ sc::BulkDataHint aHint( *pDoc, nHintId);
+
+ bool bBroadcasted = false;
+ for (const auto& [pArea, rSpans] : m_BulkGroupAreas)
+ {
+ assert(pArea);
+ SvtBroadcaster& rBC = pArea->GetBroadcaster();
+ if (!rBC.HasListeners())
+ {
+ /* FIXME: find the cause where the last listener is removed and
+ * this area is still listed here. */
+ SAL_WARN("sc.core","ScBroadcastAreaSlotMachine::BulkBroadcastGroupAreas - pArea has no listeners and should had been removed already");
+ }
+ else
+ {
+ aHint.setSpans(&rSpans);
+ rBC.Broadcast(aHint);
+ bBroadcasted = true;
+ }
+ }
+
+ m_BulkGroupAreas.clear();
+
+ return bBroadcasted;
+}
+
+size_t ScBroadcastAreaSlotMachine::RemoveBulkArea( const ScBroadcastArea* pArea )
+{
+ return aBulkBroadcastAreas.erase( pArea );
+}
+
+void ScBroadcastAreaSlotMachine::RemoveBulkGroupArea( ScBroadcastArea* pArea )
+{
+ m_BulkGroupAreas.erase(pArea);
+}
+
+void ScBroadcastAreaSlotMachine::PushAreaToBeErased( ScBroadcastAreaSlot* pSlot,
+ ScBroadcastAreas::iterator& rIter )
+{
+ maAreasToBeErased.emplace_back( pSlot, rIter);
+}
+
+void ScBroadcastAreaSlotMachine::FinallyEraseAreas( ScBroadcastAreaSlot* pSlot )
+{
+ SAL_WARN_IF( pSlot->IsInBroadcastIteration(), "sc.core",
+ "ScBroadcastAreaSlotMachine::FinallyEraseAreas: during iteration? NO!");
+ if (pSlot->IsInBroadcastIteration())
+ return;
+
+ // maAreasToBeErased is a simple vector so erasing an element may
+ // invalidate iterators and would be inefficient anyway. Instead, copy
+ // elements to be preserved (usually none!) to temporary vector and swap.
+ AreasToBeErased aCopy;
+ for (auto& rArea : maAreasToBeErased)
+ {
+ if (rArea.first == pSlot)
+ pSlot->EraseArea( rArea.second);
+ else
+ aCopy.push_back( rArea);
+ }
+ maAreasToBeErased.swap( aCopy);
+}
+
+std::vector<sc::AreaListener> ScBroadcastAreaSlotMachine::GetAllListeners(
+ const ScRange& rRange, sc::AreaOverlapType eType, sc::ListenerGroupType eGroup )
+{
+ std::vector<sc::AreaListener> aRet;
+
+ SCTAB nEndTab = rRange.aEnd.Tab();
+ for (TableSlotsMap::const_iterator iTab( aTableSlotsMap.lower_bound( rRange.aStart.Tab()));
+ iTab != aTableSlotsMap.end() && (*iTab).first <= nEndTab; ++iTab)
+ {
+ ScBroadcastAreaSlot** ppSlots = (*iTab).second->getSlots();
+ SCSIZE nStart, nEnd, nRowBreak;
+ ComputeAreaPoints( rRange, nStart, nEnd, nRowBreak );
+ SCSIZE nOff = nStart;
+ SCSIZE nBreak = nOff + nRowBreak;
+ ScBroadcastAreaSlot** pp = ppSlots + nOff;
+ while ( nOff <= nEnd )
+ {
+ ScBroadcastAreaSlot* p = *pp;
+ if (p)
+ p->GetAllListeners(rRange, aRet, eType, eGroup);
+ ComputeNextSlot( nOff, nBreak, pp, nStart, ppSlots, nRowBreak, mnBcaSlotsCol);
+ }
+ }
+
+ return aRet;
+}
+
+#if DEBUG_AREA_BROADCASTER
+void ScBroadcastAreaSlotMachine::Dump() const
+{
+ cout << "slot distribution count: " << nBcaSlots << endl;
+ for (const auto& [rIndex, pTabSlots] : aTableSlotsMap)
+ {
+ cout << "-- sheet (index: " << rIndex << ")" << endl;
+
+ assert(pTabSlots);
+ ScBroadcastAreaSlot** ppSlots = pTabSlots->getSlots();
+ for (SCSIZE i = 0; i < nBcaSlots; ++i)
+ {
+ const ScBroadcastAreaSlot* pSlot = ppSlots[i];
+ if (pSlot)
+ {
+ cout << "* slot " << i << endl;
+ pSlot->Dump();
+ }
+ }
+ }
+}
+#endif
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sc/source/core/data/bigrange.cxx b/sc/source/core/data/bigrange.cxx
new file mode 100644
index 000000000..192765743
--- /dev/null
+++ b/sc/source/core/data/bigrange.cxx
@@ -0,0 +1,25 @@
+/* -*- 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/.
+ */
+
+#include <bigrange.hxx>
+#include <document.hxx>
+
+bool ScBigAddress::IsValid( const ScDocument& rDoc ) const
+{ // min/max interval bounds define whole col/row/tab
+ return
+ ((0 <= nCol && nCol <= rDoc.MaxCol())
+ || nCol == ScBigRange::nRangeMin || nCol == ScBigRange::nRangeMax) &&
+ ((0 <= nRow && nRow <= rDoc.MaxRow())
+ || nRow == ScBigRange::nRangeMin || nRow == ScBigRange::nRangeMax) &&
+ ((0 <= nTab && nTab < rDoc.GetTableCount())
+ || nTab == ScBigRange::nRangeMin || nTab == ScBigRange::nRangeMax)
+ ;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sc/source/core/data/celltextattr.cxx b/sc/source/core/data/celltextattr.cxx
new file mode 100644
index 000000000..09bfb0829
--- /dev/null
+++ b/sc/source/core/data/celltextattr.cxx
@@ -0,0 +1,21 @@
+/* -*- 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/.
+ */
+
+#include <celltextattr.hxx>
+#include <globalnames.hxx>
+
+namespace sc {
+
+CellTextAttr::CellTextAttr() :
+ mnTextWidth(TEXTWIDTH_DIRTY),
+ mnScriptType(SvtScriptType::UNKNOWN) {}
+
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sc/source/core/data/cellvalue.cxx b/sc/source/core/data/cellvalue.cxx
new file mode 100644
index 000000000..64cd34e32
--- /dev/null
+++ b/sc/source/core/data/cellvalue.cxx
@@ -0,0 +1,691 @@
+/* -*- 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/.
+ */
+
+#include <cellvalue.hxx>
+#include <document.hxx>
+#include <column.hxx>
+#include <formulacell.hxx>
+#include <editeng/editobj.hxx>
+#include <editeng/editstat.hxx>
+#include <stringutil.hxx>
+#include <editutil.hxx>
+#include <tokenarray.hxx>
+#include <formula/token.hxx>
+#include <formula/errorcodes.hxx>
+#include <svl/sharedstring.hxx>
+
+namespace {
+
+CellType adjustCellType( CellType eOrig )
+{
+ switch (eOrig)
+ {
+ case CELLTYPE_EDIT:
+ return CELLTYPE_STRING;
+ default:
+ ;
+ }
+ return eOrig;
+}
+
+template<typename T>
+OUString getString( const T& rVal )
+{
+ if (rVal.meType == CELLTYPE_STRING)
+ return rVal.mpString->getString();
+
+ if (rVal.meType == CELLTYPE_EDIT)
+ {
+ OUStringBuffer aRet;
+ sal_Int32 n = rVal.mpEditText->GetParagraphCount();
+ for (sal_Int32 i = 0; i < n; ++i)
+ {
+ if (i > 0)
+ aRet.append('\n');
+ aRet.append(rVal.mpEditText->GetText(i));
+ }
+ return aRet.makeStringAndClear();
+ }
+
+ return OUString();
+}
+
+bool equalsFormulaCells( const ScFormulaCell* p1, const ScFormulaCell* p2 )
+{
+ const ScTokenArray* pCode1 = p1->GetCode();
+ const ScTokenArray* pCode2 = p2->GetCode();
+
+ if (pCode1->GetLen() != pCode2->GetLen())
+ return false;
+
+ if (pCode1->GetCodeError() != pCode2->GetCodeError())
+ return false;
+
+ sal_uInt16 n = pCode1->GetLen();
+ formula::FormulaToken** ppToken1 = pCode1->GetArray();
+ formula::FormulaToken** ppToken2 = pCode2->GetArray();
+ for (sal_uInt16 i = 0; i < n; ++i)
+ {
+ if (!ppToken1[i]->TextEqual(*(ppToken2[i])))
+ return false;
+ }
+
+ return true;
+}
+
+template<typename T>
+bool equalsWithoutFormatImpl( const T& left, const T& right )
+{
+ CellType eType1 = adjustCellType(left.meType);
+ CellType eType2 = adjustCellType(right.meType);
+ if (eType1 != eType2)
+ return false;
+
+ switch (eType1)
+ {
+ case CELLTYPE_NONE:
+ return true;
+ case CELLTYPE_VALUE:
+ return left.mfValue == right.mfValue;
+ case CELLTYPE_STRING:
+ {
+ OUString aStr1 = getString(left);
+ OUString aStr2 = getString(right);
+ return aStr1 == aStr2;
+ }
+ case CELLTYPE_FORMULA:
+ return equalsFormulaCells(left.mpFormula, right.mpFormula);
+ default:
+ ;
+ }
+ return false;
+}
+
+void commitToColumn( const ScCellValue& rCell, ScColumn& rColumn, SCROW nRow )
+{
+ switch (rCell.meType)
+ {
+ case CELLTYPE_STRING:
+ rColumn.SetRawString(nRow, *rCell.mpString);
+ break;
+ case CELLTYPE_EDIT:
+ rColumn.SetEditText(nRow, ScEditUtil::Clone(*rCell.mpEditText, rColumn.GetDoc()));
+ break;
+ case CELLTYPE_VALUE:
+ rColumn.SetValue(nRow, rCell.mfValue);
+ break;
+ case CELLTYPE_FORMULA:
+ {
+ ScAddress aDestPos(rColumn.GetCol(), nRow, rColumn.GetTab());
+ rColumn.SetFormulaCell(nRow, new ScFormulaCell(*rCell.mpFormula, rColumn.GetDoc(), aDestPos));
+ }
+ break;
+ default:
+ rColumn.DeleteContent(nRow);
+ }
+}
+
+bool hasStringImpl( CellType eType, ScFormulaCell* pFormula )
+{
+ switch (eType)
+ {
+ case CELLTYPE_STRING:
+ case CELLTYPE_EDIT:
+ return true;
+ case CELLTYPE_FORMULA:
+ return !pFormula->IsValue();
+ default:
+ return false;
+ }
+}
+
+bool hasNumericImpl( CellType eType, ScFormulaCell* pFormula )
+{
+ switch (eType)
+ {
+ case CELLTYPE_VALUE:
+ return true;
+ case CELLTYPE_FORMULA:
+ return pFormula->IsValue();
+ default:
+ return false;
+ }
+}
+
+template<typename CellT>
+OUString getStringImpl( const CellT& rCell, const ScDocument* pDoc )
+{
+ switch (rCell.meType)
+ {
+ case CELLTYPE_VALUE:
+ return OUString::number(rCell.mfValue);
+ case CELLTYPE_STRING:
+ return rCell.mpString->getString();
+ case CELLTYPE_EDIT:
+ if (rCell.mpEditText)
+ return ScEditUtil::GetString(*rCell.mpEditText, pDoc);
+ break;
+ case CELLTYPE_FORMULA:
+ return rCell.mpFormula->GetString().getString();
+ default:
+ ;
+ }
+ return OUString();
+}
+
+template<typename CellT>
+OUString getRawStringImpl( const CellT& rCell, const ScDocument& rDoc )
+{
+ switch (rCell.meType)
+ {
+ case CELLTYPE_VALUE:
+ return OUString::number(rCell.mfValue);
+ case CELLTYPE_STRING:
+ return rCell.mpString->getString();
+ case CELLTYPE_EDIT:
+ if (rCell.mpEditText)
+ return ScEditUtil::GetString(*rCell.mpEditText, &rDoc);
+ break;
+ case CELLTYPE_FORMULA:
+ return rCell.mpFormula->GetRawString().getString();
+ default:
+ ;
+ }
+ return OUString();
+}
+
+}
+
+ScCellValue::ScCellValue() : meType(CELLTYPE_NONE), mfValue(0.0) {}
+
+ScCellValue::ScCellValue( const ScRefCellValue& rCell ) : meType(rCell.meType), mfValue(rCell.mfValue)
+{
+ switch (rCell.meType)
+ {
+ case CELLTYPE_STRING:
+ mpString = new svl::SharedString(*rCell.mpString);
+ break;
+ case CELLTYPE_EDIT:
+ mpEditText = rCell.mpEditText->Clone().release();
+ break;
+ case CELLTYPE_FORMULA:
+ mpFormula = rCell.mpFormula->Clone();
+ break;
+ default:
+ ;
+ }
+}
+
+ScCellValue::ScCellValue( double fValue ) : meType(CELLTYPE_VALUE), mfValue(fValue) {}
+
+ScCellValue::ScCellValue( const svl::SharedString& rString ) : meType(CELLTYPE_STRING), mpString(new svl::SharedString(rString)) {}
+
+ScCellValue::ScCellValue( const ScCellValue& r ) : meType(r.meType), mfValue(r.mfValue)
+{
+ switch (r.meType)
+ {
+ case CELLTYPE_STRING:
+ mpString = new svl::SharedString(*r.mpString);
+ break;
+ case CELLTYPE_EDIT:
+ mpEditText = r.mpEditText->Clone().release();
+ break;
+ case CELLTYPE_FORMULA:
+ mpFormula = r.mpFormula->Clone();
+ break;
+ default:
+ ;
+ }
+}
+
+ScCellValue::ScCellValue(ScCellValue&& r) noexcept
+ : meType(r.meType)
+ , mfValue(r.mfValue)
+{
+ switch (r.meType)
+ {
+ case CELLTYPE_STRING:
+ mpString = r.mpString;
+ break;
+ case CELLTYPE_EDIT:
+ mpEditText = r.mpEditText;
+ break;
+ case CELLTYPE_FORMULA:
+ mpFormula = r.mpFormula;
+ break;
+ default:
+ ;
+ }
+ r.meType = CELLTYPE_NONE;
+}
+
+ScCellValue::~ScCellValue()
+{
+ clear();
+}
+
+void ScCellValue::clear() noexcept
+{
+ switch (meType)
+ {
+ case CELLTYPE_STRING:
+ delete mpString;
+ break;
+ case CELLTYPE_EDIT:
+ delete mpEditText;
+ break;
+ case CELLTYPE_FORMULA:
+ delete mpFormula;
+ break;
+ default:
+ ;
+ }
+
+ // Reset to empty value.
+ meType = CELLTYPE_NONE;
+ mfValue = 0.0;
+}
+
+void ScCellValue::set( double fValue )
+{
+ clear();
+ meType = CELLTYPE_VALUE;
+ mfValue = fValue;
+}
+
+void ScCellValue::set( const svl::SharedString& rStr )
+{
+ clear();
+ meType = CELLTYPE_STRING;
+ mpString = new svl::SharedString(rStr);
+}
+
+void ScCellValue::set( const EditTextObject& rEditText )
+{
+ clear();
+ meType = CELLTYPE_EDIT;
+ mpEditText = rEditText.Clone().release();
+}
+
+void ScCellValue::set( EditTextObject* pEditText )
+{
+ clear();
+ meType = CELLTYPE_EDIT;
+ mpEditText = pEditText;
+}
+
+void ScCellValue::set( ScFormulaCell* pFormula )
+{
+ clear();
+ meType = CELLTYPE_FORMULA;
+ mpFormula = pFormula;
+}
+
+void ScCellValue::assign( const ScDocument& rDoc, const ScAddress& rPos )
+{
+ clear();
+
+ ScRefCellValue aRefVal(const_cast<ScDocument&>(rDoc), rPos);
+
+ meType = aRefVal.meType;
+ switch (meType)
+ {
+ case CELLTYPE_STRING:
+ mpString = new svl::SharedString(*aRefVal.mpString);
+ break;
+ case CELLTYPE_EDIT:
+ if (aRefVal.mpEditText)
+ mpEditText = aRefVal.mpEditText->Clone().release();
+ break;
+ case CELLTYPE_VALUE:
+ mfValue = aRefVal.mfValue;
+ break;
+ case CELLTYPE_FORMULA:
+ mpFormula = aRefVal.mpFormula->Clone();
+ break;
+ default:
+ meType = CELLTYPE_NONE; // reset to empty.
+ }
+}
+
+void ScCellValue::assign(const ScCellValue& rOther, ScDocument& rDestDoc, ScCloneFlags nCloneFlags)
+{
+ clear();
+
+ meType = rOther.meType;
+ switch (meType)
+ {
+ case CELLTYPE_STRING:
+ mpString = new svl::SharedString(*rOther.mpString);
+ break;
+ case CELLTYPE_EDIT:
+ {
+ // Switch to the pool of the destination document.
+ ScFieldEditEngine& rEngine = rDestDoc.GetEditEngine();
+ if (rOther.mpEditText->HasOnlineSpellErrors())
+ {
+ EEControlBits nControl = rEngine.GetControlWord();
+ const EEControlBits nSpellControl = EEControlBits::ONLINESPELLING | EEControlBits::ALLOWBIGOBJS;
+ bool bNewControl = ((nControl & nSpellControl) != nSpellControl);
+ if (bNewControl)
+ rEngine.SetControlWord(nControl | nSpellControl);
+ rEngine.SetTextCurrentDefaults(*rOther.mpEditText);
+ mpEditText = rEngine.CreateTextObject().release();
+ if (bNewControl)
+ rEngine.SetControlWord(nControl);
+ }
+ else
+ {
+ rEngine.SetTextCurrentDefaults(*rOther.mpEditText);
+ mpEditText = rEngine.CreateTextObject().release();
+ }
+ }
+ break;
+ case CELLTYPE_VALUE:
+ mfValue = rOther.mfValue;
+ break;
+ case CELLTYPE_FORMULA:
+ // Switch to the destination document.
+ mpFormula = new ScFormulaCell(*rOther.mpFormula, rDestDoc, rOther.mpFormula->aPos, nCloneFlags);
+ break;
+ default:
+ meType = CELLTYPE_NONE; // reset to empty.
+ }
+}
+
+void ScCellValue::commit( ScDocument& rDoc, const ScAddress& rPos ) const
+{
+ switch (meType)
+ {
+ case CELLTYPE_STRING:
+ {
+ ScSetStringParam aParam;
+ aParam.setTextInput();
+ rDoc.SetString(rPos, mpString->getString(), &aParam);
+ }
+ break;
+ case CELLTYPE_EDIT:
+ rDoc.SetEditText(rPos, mpEditText->Clone());
+ break;
+ case CELLTYPE_VALUE:
+ rDoc.SetValue(rPos, mfValue);
+ break;
+ case CELLTYPE_FORMULA:
+ rDoc.SetFormulaCell(rPos, mpFormula->Clone());
+ break;
+ default:
+ rDoc.SetEmptyCell(rPos);
+ }
+}
+
+void ScCellValue::commit( ScColumn& rColumn, SCROW nRow ) const
+{
+ commitToColumn(*this, rColumn, nRow);
+}
+
+void ScCellValue::release( ScDocument& rDoc, const ScAddress& rPos )
+{
+ switch (meType)
+ {
+ case CELLTYPE_STRING:
+ {
+ // Currently, string cannot be placed without copying.
+ ScSetStringParam aParam;
+ aParam.setTextInput();
+ rDoc.SetString(rPos, mpString->getString(), &aParam);
+ delete mpString;
+ }
+ break;
+ case CELLTYPE_EDIT:
+ // Cell takes the ownership of the text object.
+ rDoc.SetEditText(rPos, std::unique_ptr<EditTextObject>(mpEditText));
+ break;
+ case CELLTYPE_VALUE:
+ rDoc.SetValue(rPos, mfValue);
+ break;
+ case CELLTYPE_FORMULA:
+ // This formula cell instance is directly placed in the document without copying.
+ rDoc.SetFormulaCell(rPos, mpFormula);
+ break;
+ default:
+ rDoc.SetEmptyCell(rPos);
+ }
+
+ meType = CELLTYPE_NONE;
+ mfValue = 0.0;
+}
+
+void ScCellValue::release( ScColumn& rColumn, SCROW nRow, sc::StartListeningType eListenType )
+{
+ switch (meType)
+ {
+ case CELLTYPE_STRING:
+ {
+ // Currently, string cannot be placed without copying.
+ rColumn.SetRawString(nRow, *mpString);
+ delete mpString;
+ }
+ break;
+ case CELLTYPE_EDIT:
+ // Cell takes the ownership of the text object.
+ rColumn.SetEditText(nRow, std::unique_ptr<EditTextObject>(mpEditText));
+ break;
+ case CELLTYPE_VALUE:
+ rColumn.SetValue(nRow, mfValue);
+ break;
+ case CELLTYPE_FORMULA:
+ // This formula cell instance is directly placed in the document without copying.
+ rColumn.SetFormulaCell(nRow, mpFormula, eListenType);
+ break;
+ default:
+ rColumn.DeleteContent(nRow);
+ }
+
+ meType = CELLTYPE_NONE;
+ mfValue = 0.0;
+}
+
+OUString ScCellValue::getString( const ScDocument& rDoc ) const
+{
+ return getStringImpl(*this, &rDoc);
+}
+
+bool ScCellValue::isEmpty() const
+{
+ return meType == CELLTYPE_NONE;
+}
+
+bool ScCellValue::equalsWithoutFormat( const ScCellValue& r ) const
+{
+ return equalsWithoutFormatImpl(*this, r);
+}
+
+ScCellValue& ScCellValue::operator= ( const ScCellValue& r )
+{
+ ScCellValue aTmp(r);
+ swap(aTmp);
+ return *this;
+}
+
+ScCellValue& ScCellValue::operator=(ScCellValue&& rCell) noexcept
+{
+ clear();
+
+ meType = rCell.meType;
+ mfValue = rCell.mfValue;
+ switch (rCell.meType)
+ {
+ case CELLTYPE_STRING:
+ mpString = rCell.mpString;
+ break;
+ case CELLTYPE_EDIT:
+ mpEditText = rCell.mpEditText;
+ break;
+ case CELLTYPE_FORMULA:
+ mpFormula = rCell.mpFormula;
+ break;
+ default:
+ ;
+ }
+ //we don't need to reset mpString/mpEditText/mpFormula if we
+ //set meType to NONE as the ScCellValue dtor keys off the meType
+ rCell.meType = CELLTYPE_NONE;
+
+ return *this;
+}
+
+ScCellValue& ScCellValue::operator= ( const ScRefCellValue& r )
+{
+ ScCellValue aTmp(r);
+ swap(aTmp);
+ return *this;
+}
+
+void ScCellValue::swap( ScCellValue& r )
+{
+ std::swap(meType, r.meType);
+
+ // double is 8 bytes, whereas a pointer may be 4 or 8 bytes depending on
+ // the platform. Swap by double values.
+ std::swap(mfValue, r.mfValue);
+}
+
+ScRefCellValue::ScRefCellValue() : meType(CELLTYPE_NONE), mfValue(0.0) {}
+ScRefCellValue::ScRefCellValue( double fValue ) : meType(CELLTYPE_VALUE), mfValue(fValue) {}
+ScRefCellValue::ScRefCellValue( const svl::SharedString* pString ) : meType(CELLTYPE_STRING), mpString(pString) {}
+ScRefCellValue::ScRefCellValue( const EditTextObject* pEditText ) : meType(CELLTYPE_EDIT), mpEditText(pEditText) {}
+ScRefCellValue::ScRefCellValue( ScFormulaCell* pFormula ) : meType(CELLTYPE_FORMULA), mpFormula(pFormula) {}
+
+ScRefCellValue::ScRefCellValue( ScDocument& rDoc, const ScAddress& rPos )
+{
+ assign( rDoc, rPos);
+}
+
+ScRefCellValue::ScRefCellValue( ScDocument& rDoc, const ScAddress& rPos, sc::ColumnBlockPosition& rBlockPos )
+{
+ assign( rDoc, rPos, rBlockPos );
+}
+
+void ScRefCellValue::clear()
+{
+ // Reset to empty value.
+ meType = CELLTYPE_NONE;
+ mfValue = 0.0;
+}
+
+void ScRefCellValue::assign( ScDocument& rDoc, const ScAddress& rPos )
+{
+ *this = rDoc.GetRefCellValue(rPos);
+}
+
+void ScRefCellValue::assign( ScDocument& rDoc, const ScAddress& rPos, sc::ColumnBlockPosition& rBlockPos )
+{
+ *this = rDoc.GetRefCellValue(rPos, rBlockPos);
+}
+
+void ScRefCellValue::commit( ScDocument& rDoc, const ScAddress& rPos ) const
+{
+ switch (meType)
+ {
+ case CELLTYPE_STRING:
+ {
+ ScSetStringParam aParam;
+ aParam.setTextInput();
+ rDoc.SetString(rPos, mpString->getString(), &aParam);
+ }
+ break;
+ case CELLTYPE_EDIT:
+ rDoc.SetEditText(rPos, ScEditUtil::Clone(*mpEditText, rDoc));
+ break;
+ case CELLTYPE_VALUE:
+ rDoc.SetValue(rPos, mfValue);
+ break;
+ case CELLTYPE_FORMULA:
+ rDoc.SetFormulaCell(rPos, new ScFormulaCell(*mpFormula, rDoc, rPos));
+ break;
+ default:
+ rDoc.SetEmptyCell(rPos);
+ }
+}
+
+bool ScRefCellValue::hasString() const
+{
+ return hasStringImpl(meType, mpFormula);
+}
+
+bool ScRefCellValue::hasNumeric() const
+{
+ return hasNumericImpl(meType, mpFormula);
+}
+
+bool ScRefCellValue::hasError() const
+{
+ return meType == CELLTYPE_FORMULA && mpFormula->GetErrCode() != FormulaError::NONE;
+}
+
+double ScRefCellValue::getValue()
+{
+ switch (meType)
+ {
+ case CELLTYPE_VALUE:
+ return mfValue;
+ case CELLTYPE_FORMULA:
+ return mpFormula->GetValue();
+ default:
+ ;
+ }
+ return 0.0;
+}
+
+double ScRefCellValue::getRawValue() const
+{
+ switch (meType)
+ {
+ case CELLTYPE_VALUE:
+ return mfValue;
+ case CELLTYPE_FORMULA:
+ return mpFormula->GetRawValue();
+ default:
+ ;
+ }
+ return 0.0;
+}
+
+OUString ScRefCellValue::getString( const ScDocument* pDoc ) const
+{
+ return getStringImpl(*this, pDoc);
+}
+
+OUString ScRefCellValue::getRawString( const ScDocument& rDoc ) const
+{
+ return getRawStringImpl(*this, rDoc);
+}
+
+bool ScRefCellValue::isEmpty() const
+{
+ return meType == CELLTYPE_NONE;
+}
+
+bool ScRefCellValue::hasEmptyValue()
+{
+ if (isEmpty())
+ return true;
+
+ if (meType == CELLTYPE_FORMULA)
+ return mpFormula->IsEmpty();
+
+ return false;
+}
+
+bool ScRefCellValue::equalsWithoutFormat( const ScRefCellValue& r ) const
+{
+ return equalsWithoutFormatImpl(*this, r);
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sc/source/core/data/cellvalues.cxx b/sc/source/core/data/cellvalues.cxx
new file mode 100644
index 000000000..290dc0d09
--- /dev/null
+++ b/sc/source/core/data/cellvalues.cxx
@@ -0,0 +1,357 @@
+/* -*- 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/.
+ */
+
+#include <memory>
+#include <cellvalues.hxx>
+#include <column.hxx>
+#include <formulacell.hxx>
+
+#include <cassert>
+
+namespace sc {
+
+namespace {
+
+struct BlockPos
+{
+ size_t mnStart;
+ size_t mnEnd;
+};
+
+}
+
+CellValueSpan::CellValueSpan( SCROW nRow1, SCROW nRow2 ) :
+ mnRow1(nRow1), mnRow2(nRow2) {}
+
+struct CellValuesImpl
+{
+ CellStoreType maCells;
+ CellTextAttrStoreType maCellTextAttrs;
+ CellStoreType::iterator miCellPos;
+ CellTextAttrStoreType::iterator miAttrPos;
+
+ CellValuesImpl() = default;
+
+ CellValuesImpl(const CellValuesImpl&) = delete;
+ const CellValuesImpl& operator=(const CellValuesImpl&) = delete;
+};
+
+CellValues::CellValues() :
+ mpImpl(new CellValuesImpl) {}
+
+CellValues::~CellValues()
+{
+}
+
+void CellValues::transferFrom( ScColumn& rCol, SCROW nRow, size_t nLen )
+{
+ mpImpl->maCells.resize(nLen);
+ mpImpl->maCellTextAttrs.resize(nLen);
+ rCol.maCells.transfer(nRow, nRow+nLen-1, mpImpl->maCells, 0);
+ rCol.maCellTextAttrs.transfer(nRow, nRow+nLen-1, mpImpl->maCellTextAttrs, 0);
+}
+
+
+void CellValues::copyTo( ScColumn& rCol, SCROW nRow ) const
+{
+ copyCellsTo(rCol, nRow);
+ copyCellTextAttrsTo(rCol, nRow);
+}
+
+void CellValues::swapNonEmpty( ScColumn& rCol )
+{
+ std::vector<BlockPos> aBlocksToSwap;
+
+ // Go through static value blocks and record their positions and sizes.
+ for (const auto& rCell : mpImpl->maCells)
+ {
+ if (rCell.type == sc::element_type_empty)
+ continue;
+
+ BlockPos aPos;
+ aPos.mnStart = rCell.position;
+ aPos.mnEnd = aPos.mnStart + rCell.size - 1;
+ aBlocksToSwap.push_back(aPos);
+ }
+
+ // Do the swapping. The undo storage will store the replaced formula cells after this.
+ for (const auto& rBlock : aBlocksToSwap)
+ {
+ rCol.maCells.swap(rBlock.mnStart, rBlock.mnEnd, mpImpl->maCells, rBlock.mnStart);
+ rCol.maCellTextAttrs.swap(rBlock.mnStart, rBlock.mnEnd, mpImpl->maCellTextAttrs, rBlock.mnStart);
+ }
+}
+
+void CellValues::assign( const std::vector<double>& rVals )
+{
+ mpImpl->maCells.resize(rVals.size());
+ mpImpl->maCells.set(0, rVals.begin(), rVals.end());
+
+ // Set default text attributes.
+ std::vector<CellTextAttr> aDefaults(rVals.size(), CellTextAttr());
+ mpImpl->maCellTextAttrs.resize(rVals.size());
+ mpImpl->maCellTextAttrs.set(0, aDefaults.begin(), aDefaults.end());
+}
+
+void CellValues::assign( const std::vector<ScFormulaCell*>& rVals )
+{
+ std::vector<ScFormulaCell*> aCopyVals(rVals.size());
+ size_t nIdx = 0;
+ for (const auto* pCell : rVals)
+ {
+ aCopyVals[nIdx] = pCell->Clone();
+ ++nIdx;
+ }
+
+ mpImpl->maCells.resize(aCopyVals.size());
+ mpImpl->maCells.set(0, aCopyVals.begin(), aCopyVals.end());
+
+ // Set default text attributes.
+ std::vector<CellTextAttr> aDefaults(rVals.size(), CellTextAttr());
+ mpImpl->maCellTextAttrs.resize(rVals.size());
+ mpImpl->maCellTextAttrs.set(0, aDefaults.begin(), aDefaults.end());
+}
+
+size_t CellValues::size() const
+{
+ assert(mpImpl->maCells.size() == mpImpl->maCellTextAttrs.size());
+ return mpImpl->maCells.size();
+}
+
+void CellValues::reset( size_t nSize )
+{
+ mpImpl->maCells.clear();
+ mpImpl->maCells.resize(nSize);
+ mpImpl->maCellTextAttrs.clear();
+ mpImpl->maCellTextAttrs.resize(nSize);
+
+ mpImpl->miCellPos = mpImpl->maCells.begin();
+ mpImpl->miAttrPos = mpImpl->maCellTextAttrs.begin();
+}
+
+void CellValues::setValue( size_t nRow, double fVal )
+{
+ mpImpl->miCellPos = mpImpl->maCells.set(mpImpl->miCellPos, nRow, fVal);
+ mpImpl->miAttrPos = mpImpl->maCellTextAttrs.set(mpImpl->miAttrPos, nRow, sc::CellTextAttr());
+}
+
+void CellValues::setValue( size_t nRow, const svl::SharedString& rStr )
+{
+ mpImpl->miCellPos = mpImpl->maCells.set(mpImpl->miCellPos, nRow, rStr);
+ mpImpl->miAttrPos = mpImpl->maCellTextAttrs.set(mpImpl->miAttrPos, nRow, sc::CellTextAttr());
+}
+
+void CellValues::swap( CellValues& r )
+{
+ std::swap(mpImpl, r.mpImpl);
+}
+
+std::vector<CellValueSpan> CellValues::getNonEmptySpans() const
+{
+ std::vector<CellValueSpan> aRet;
+ for (const auto& rCell : mpImpl->maCells)
+ {
+ if (rCell.type != element_type_empty)
+ {
+ // Record this span.
+ size_t nRow1 = rCell.position;
+ size_t nRow2 = nRow1 + rCell.size - 1;
+ aRet.emplace_back(nRow1, nRow2);
+ }
+ }
+ return aRet;
+}
+
+void CellValues::copyCellsTo( ScColumn& rCol, SCROW nRow ) const
+{
+ CellStoreType& rDest = rCol.maCells;
+ const CellStoreType& rSrc = mpImpl->maCells;
+
+ // Caller must ensure the destination is long enough.
+ assert(rSrc.size() + static_cast<size_t>(nRow) <= rDest.size());
+
+ SCROW nCurRow = nRow;
+ CellStoreType::iterator itPos = rDest.begin();
+
+ for (const auto& rBlk : rSrc)
+ {
+ switch (rBlk.type)
+ {
+ case element_type_numeric:
+ {
+ numeric_block::const_iterator it = numeric_block::begin(*rBlk.data);
+ numeric_block::const_iterator itEnd = numeric_block::end(*rBlk.data);
+ itPos = rDest.set(itPos, nCurRow, it, itEnd);
+ }
+ break;
+ case element_type_string:
+ {
+ string_block::const_iterator it = string_block::begin(*rBlk.data);
+ string_block::const_iterator itEnd = string_block::end(*rBlk.data);
+ itPos = rDest.set(itPos, nCurRow, it, itEnd);
+ }
+ break;
+ case element_type_edittext:
+ {
+ edittext_block::const_iterator it = edittext_block::begin(*rBlk.data);
+ edittext_block::const_iterator itEnd = edittext_block::end(*rBlk.data);
+ std::vector<EditTextObject*> aVals;
+ aVals.reserve(rBlk.size);
+ for (; it != itEnd; ++it)
+ {
+ const EditTextObject* p = *it;
+ aVals.push_back(p->Clone().release());
+ }
+ itPos = rDest.set(itPos, nCurRow, aVals.begin(), aVals.end());
+ }
+ break;
+ case element_type_formula:
+ {
+ formula_block::const_iterator it = formula_block::begin(*rBlk.data);
+ formula_block::const_iterator itEnd = formula_block::end(*rBlk.data);
+ std::vector<ScFormulaCell*> aVals;
+ aVals.reserve(rBlk.size);
+ for (; it != itEnd; ++it)
+ {
+ const ScFormulaCell* p = *it;
+ aVals.push_back(p->Clone());
+ }
+ itPos = rDest.set(itPos, nCurRow, aVals.begin(), aVals.end());
+ }
+ break;
+ default:
+ itPos = rDest.set_empty(itPos, nCurRow, nCurRow+rBlk.size-1);
+ }
+
+ nCurRow += rBlk.size;
+ }
+}
+
+void CellValues::copyCellTextAttrsTo( ScColumn& rCol, SCROW nRow ) const
+{
+ CellTextAttrStoreType& rDest = rCol.maCellTextAttrs;
+ const CellTextAttrStoreType& rSrc = mpImpl->maCellTextAttrs;
+
+ // Caller must ensure the destination is long enough.
+ assert(rSrc.size() + static_cast<size_t>(nRow) <= rDest.size());
+
+ SCROW nCurRow = nRow;
+ CellTextAttrStoreType::iterator itPos = rDest.begin();
+
+ for (const auto& rBlk : rSrc)
+ {
+ switch (rBlk.type)
+ {
+ case element_type_celltextattr:
+ {
+ celltextattr_block::const_iterator it = celltextattr_block::begin(*rBlk.data);
+ celltextattr_block::const_iterator itEnd = celltextattr_block::end(*rBlk.data);
+ itPos = rDest.set(itPos, nCurRow, it, itEnd);
+ }
+ break;
+ default:
+ itPos = rDest.set_empty(itPos, nCurRow, nCurRow+rBlk.size-1);
+ }
+
+ nCurRow += rBlk.size;
+ }
+}
+
+typedef std::vector<std::unique_ptr<CellValues>> TableType;
+typedef std::vector<std::unique_ptr<TableType>> TablesType;
+
+struct TableValues::Impl
+{
+ ScRange maRange;
+ TablesType m_Tables;
+
+ explicit Impl( const ScRange& rRange ) : maRange(rRange)
+ {
+ size_t nTabs = rRange.aEnd.Tab() - rRange.aStart.Tab() + 1;
+ size_t nCols = rRange.aEnd.Col() - rRange.aStart.Col() + 1;
+
+ for (size_t nTab = 0; nTab < nTabs; ++nTab)
+ {
+ m_Tables.push_back(std::make_unique<TableType>());
+ std::unique_ptr<TableType>& rTab2 = m_Tables.back();
+ for (size_t nCol = 0; nCol < nCols; ++nCol)
+ rTab2->push_back(std::make_unique<CellValues>());
+ }
+ }
+
+ CellValues* getCellValues( SCTAB nTab, SCCOL nCol )
+ {
+ if (nTab < maRange.aStart.Tab() || maRange.aEnd.Tab() < nTab)
+ // sheet index out of bound.
+ return nullptr;
+ if (nCol < maRange.aStart.Col() || maRange.aEnd.Col() < nCol)
+ // column index out of bound.
+ return nullptr;
+ size_t nTabOffset = nTab - maRange.aStart.Tab();
+ if (nTabOffset >= m_Tables.size())
+ return nullptr;
+ std::unique_ptr<TableType>& rTab2 = m_Tables[nTab-maRange.aStart.Tab()];
+ size_t nColOffset = nCol - maRange.aStart.Col();
+ if (nColOffset >= rTab2->size())
+ return nullptr;
+ return &rTab2.get()[0][nColOffset].get()[0];
+ }
+};
+
+TableValues::TableValues() :
+ mpImpl(new Impl(ScRange(ScAddress::INITIALIZE_INVALID))) {}
+
+TableValues::TableValues( const ScRange& rRange ) :
+ mpImpl(new Impl(rRange)) {}
+
+TableValues::~TableValues()
+{
+}
+
+const ScRange& TableValues::getRange() const
+{
+ return mpImpl->maRange;
+}
+
+void TableValues::swap( SCTAB nTab, SCCOL nCol, CellValues& rColValue )
+{
+ CellValues* pCol = mpImpl->getCellValues(nTab, nCol);
+ if (!pCol)
+ return;
+
+ pCol->swap(rColValue);
+}
+
+void TableValues::swapNonEmpty( SCTAB nTab, SCCOL nCol, ScColumn& rCol )
+{
+ CellValues* pCol = mpImpl->getCellValues(nTab, nCol);
+ if (!pCol)
+ return;
+
+ pCol->swapNonEmpty(rCol);
+}
+
+std::vector<CellValueSpan> TableValues::getNonEmptySpans( SCTAB nTab, SCCOL nCol ) const
+{
+ std::vector<CellValueSpan> aRet;
+ CellValues* pCol = mpImpl->getCellValues(nTab, nCol);
+ if (pCol)
+ aRet = pCol->getNonEmptySpans();
+
+ return aRet;
+}
+
+void TableValues::swap( TableValues& rOther )
+{
+ std::swap(mpImpl, rOther.mpImpl);
+}
+
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sc/source/core/data/clipcontext.cxx b/sc/source/core/data/clipcontext.cxx
new file mode 100644
index 000000000..f83ee2c9a
--- /dev/null
+++ b/sc/source/core/data/clipcontext.cxx
@@ -0,0 +1,440 @@
+/* -*- 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/.
+ */
+
+#include <memory>
+#include <clipcontext.hxx>
+#include <document.hxx>
+#include <mtvelements.hxx>
+#include <column.hxx>
+#include <scitems.hxx>
+#include <tokenarray.hxx>
+#include <editutil.hxx>
+#include <clipparam.hxx>
+
+#include <svl/intitem.hxx>
+#include <svl/numformat.hxx>
+#include <formula/errorcodes.hxx>
+#include <refdata.hxx>
+#include <listenercontext.hxx>
+
+namespace sc {
+
+ClipContextBase::ClipContextBase(ScDocument& rDoc) :
+ mpSet(new ColumnBlockPositionSet(rDoc)) {}
+
+ClipContextBase::~ClipContextBase() {}
+
+ColumnBlockPosition* ClipContextBase::getBlockPosition(SCTAB nTab, SCCOL nCol)
+{
+ return mpSet->getBlockPosition(nTab, nCol);
+}
+
+CopyFromClipContext::CopyFromClipContext(ScDocument& rDoc,
+ ScDocument* pRefUndoDoc, ScDocument* pClipDoc, InsertDeleteFlags nInsertFlag,
+ bool bAsLink, bool bSkipEmptyCells) :
+ ClipContextBase(rDoc),
+ mnDestCol1(-1), mnDestCol2(-1),
+ mnDestRow1(-1), mnDestRow2(-1),
+ mnTabStart(-1), mnTabEnd(-1),
+ mrDestDoc(rDoc),
+ mpRefUndoDoc(pRefUndoDoc), mpClipDoc(pClipDoc),
+ mnInsertFlag(nInsertFlag), mnDeleteFlag(InsertDeleteFlags::NONE),
+ mpCondFormatList(nullptr),
+ mbAsLink(bAsLink), mbSkipEmptyCells(bSkipEmptyCells),
+ mbTableProtected(false)
+{
+}
+
+CopyFromClipContext::~CopyFromClipContext()
+{
+}
+
+void CopyFromClipContext::setTabRange(SCTAB nStart, SCTAB nEnd)
+{
+ mnTabStart = nStart;
+ mnTabEnd = nEnd;
+}
+
+SCTAB CopyFromClipContext::getTabStart() const
+{
+ return mnTabStart;
+}
+
+SCTAB CopyFromClipContext::getTabEnd() const
+{
+ return mnTabEnd;
+}
+
+void CopyFromClipContext::setDestRange( SCCOL nCol1, SCROW nRow1, SCCOL nCol2, SCROW nRow2 )
+{
+ mnDestCol1 = nCol1;
+ mnDestRow1 = nRow1;
+ mnDestCol2 = nCol2;
+ mnDestRow2 = nRow2;
+}
+
+CopyFromClipContext::Range CopyFromClipContext::getDestRange() const
+{
+ Range aRet;
+ aRet.mnCol1 = mnDestCol1;
+ aRet.mnCol2 = mnDestCol2;
+ aRet.mnRow1 = mnDestRow1;
+ aRet.mnRow2 = mnDestRow2;
+ return aRet;
+}
+
+ScDocument* CopyFromClipContext::getUndoDoc()
+{
+ return mpRefUndoDoc;
+}
+
+ScDocument* CopyFromClipContext::getClipDoc()
+{
+ return mpClipDoc;
+}
+
+InsertDeleteFlags CopyFromClipContext::getInsertFlag() const
+{
+ return mnInsertFlag;
+}
+
+void CopyFromClipContext::setDeleteFlag( InsertDeleteFlags nFlag )
+{
+ mnDeleteFlag = nFlag;
+}
+
+InsertDeleteFlags CopyFromClipContext::getDeleteFlag() const
+{
+ return mnDeleteFlag;
+}
+
+void CopyFromClipContext::setListeningFormulaSpans(
+ SCTAB nTab, SCCOL nCol1, SCROW nRow1, SCCOL nCol2, SCROW nRow2 )
+{
+ for (SCCOL nCol = nCol1; nCol <= nCol2; ++nCol)
+ maListeningFormulaSpans.set(mrDestDoc, nTab, nCol, nRow1, nRow2, true);
+}
+
+namespace {
+
+class StartListeningAction : public sc::ColumnSpanSet::Action
+{
+ ScDocument& mrDestDoc;
+ sc::StartListeningContext& mrStartCxt;
+ sc::EndListeningContext& mrEndCxt;
+
+public:
+ StartListeningAction( ScDocument& rDestDoc, sc::StartListeningContext& rStartCxt, sc::EndListeningContext& rEndCxt ) :
+ mrDestDoc(rDestDoc), mrStartCxt(rStartCxt), mrEndCxt(rEndCxt)
+ {
+ }
+
+ virtual void execute( const ScAddress& rPos, SCROW nLength, bool bVal ) override
+ {
+ if (!bVal)
+ return;
+
+ SCROW nRow1 = rPos.Row();
+ SCROW nRow2 = nRow1 + nLength - 1;
+
+ mrDestDoc.StartListeningFromClip(
+ mrStartCxt, mrEndCxt, rPos.Tab(), rPos.Col(), nRow1, rPos.Col(), nRow2);
+ }
+};
+
+}
+
+void CopyFromClipContext::startListeningFormulas()
+{
+ auto pSet = std::make_shared<sc::ColumnBlockPositionSet>(mrDestDoc);
+ sc::StartListeningContext aStartCxt(mrDestDoc, pSet);
+ sc::EndListeningContext aEndCxt(mrDestDoc, pSet, nullptr);
+
+ StartListeningAction aAction(mrDestDoc, aStartCxt, aEndCxt);
+ maListeningFormulaSpans.executeAction(mrDestDoc, aAction);
+}
+
+void CopyFromClipContext::setSingleCellColumnSize( size_t nSize )
+{
+ maSingleCells.resize(nSize);
+ maSingleCellAttrs.resize(nSize);
+ maSinglePatterns.resize(nSize, nullptr);
+ maSingleNotes.resize(nSize, nullptr);
+ maSingleSparkline.resize(nSize);
+}
+
+ScCellValue& CopyFromClipContext::getSingleCell( size_t nColOffset )
+{
+ assert(nColOffset < maSingleCells.size());
+ return maSingleCells[nColOffset];
+}
+
+sc::CellTextAttr& CopyFromClipContext::getSingleCellAttr( size_t nColOffset )
+{
+ assert(nColOffset < maSingleCellAttrs.size());
+ return maSingleCellAttrs[nColOffset];
+}
+
+void CopyFromClipContext::setSingleCell( const ScAddress& rSrcPos, const ScColumn& rSrcCol )
+{
+ SCCOL nColOffset = rSrcPos.Col() - mpClipDoc->GetClipParam().getWholeRange().aStart.Col();
+ ScCellValue& rSrcCell = getSingleCell(nColOffset);
+
+ const sc::CellTextAttr* pAttr = rSrcCol.GetCellTextAttr(rSrcPos.Row());
+
+ if (pAttr)
+ {
+ sc::CellTextAttr& rAttr = getSingleCellAttr(nColOffset);
+ rAttr = *pAttr;
+ }
+
+ if (mbAsLink)
+ {
+ ScSingleRefData aRef;
+ aRef.InitAddress(rSrcPos);
+ aRef.SetFlag3D(true);
+
+ ScTokenArray aArr(*mpClipDoc);
+ aArr.AddSingleReference(aRef);
+ rSrcCell.set(new ScFormulaCell(*mpClipDoc, rSrcPos, aArr));
+ return;
+ }
+
+ rSrcCell.assign(*mpClipDoc, rSrcPos);
+
+ // Check the paste flag to see whether we want to paste this cell. If the
+ // flag says we don't want to paste this cell, we'll return with true.
+ InsertDeleteFlags nFlags = getInsertFlag();
+ bool bNumeric = (nFlags & InsertDeleteFlags::VALUE) != InsertDeleteFlags::NONE;
+ bool bDateTime = (nFlags & InsertDeleteFlags::DATETIME) != InsertDeleteFlags::NONE;
+ bool bString = (nFlags & InsertDeleteFlags::STRING) != InsertDeleteFlags::NONE;
+ bool bBoolean = (nFlags & InsertDeleteFlags::SPECIAL_BOOLEAN) != InsertDeleteFlags::NONE;
+ bool bFormula = (nFlags & InsertDeleteFlags::FORMULA) != InsertDeleteFlags::NONE;
+
+ switch (rSrcCell.meType)
+ {
+ case CELLTYPE_VALUE:
+ {
+ bool bPaste = isDateCell(rSrcCol, rSrcPos.Row()) ? bDateTime : bNumeric;
+ if (!bPaste)
+ // Don't paste this.
+ rSrcCell.clear();
+ }
+ break;
+ case CELLTYPE_STRING:
+ case CELLTYPE_EDIT:
+ {
+ if (!bString)
+ // Skip pasting.
+ rSrcCell.clear();
+ }
+ break;
+ case CELLTYPE_FORMULA:
+ {
+ if (bBoolean)
+ {
+ // Check if this formula cell is a boolean cell, and if so, go ahead and paste it.
+ const ScTokenArray* pCode = rSrcCell.mpFormula->GetCode();
+ if (pCode && pCode->GetLen() == 1)
+ {
+ const formula::FormulaToken* p = pCode->FirstToken();
+ if (p->GetOpCode() == ocTrue || p->GetOpCode() == ocFalse)
+ // This is a boolean formula. Good.
+ break;
+ }
+ }
+
+ if (bFormula)
+ // Good.
+ break;
+
+ FormulaError nErr = rSrcCell.mpFormula->GetErrCode();
+ if (nErr != FormulaError::NONE)
+ {
+ // error codes are cloned with values
+ if (!bNumeric)
+ // Error code is treated as numeric value. Don't paste it.
+ rSrcCell.clear();
+ else
+ {
+ // Turn this into a formula cell with just the error code.
+ ScFormulaCell* pErrCell = new ScFormulaCell(*mpClipDoc, rSrcPos);
+ pErrCell->SetErrCode(nErr);
+ rSrcCell.set(pErrCell);
+ }
+ }
+ else if (rSrcCell.mpFormula->IsEmptyDisplayedAsString())
+ {
+ // Empty stays empty and doesn't become 0.
+ rSrcCell.clear();
+ }
+ else if (rSrcCell.mpFormula->IsValue())
+ {
+ bool bPaste = isDateCell(rSrcCol, rSrcPos.Row()) ? bDateTime : bNumeric;
+ if (!bPaste)
+ {
+ // Don't paste this.
+ rSrcCell.clear();
+ break;
+ }
+
+ // Turn this into a numeric cell.
+ rSrcCell.set(rSrcCell.mpFormula->GetValue());
+ }
+ else if (bString)
+ {
+ svl::SharedString aStr = rSrcCell.mpFormula->GetString();
+ if (aStr.isEmpty())
+ {
+ // do not clone empty string
+ rSrcCell.clear();
+ break;
+ }
+
+ // Turn this into a string or edit cell.
+ if (rSrcCell.mpFormula->IsMultilineResult())
+ {
+ // TODO : Add shared string support to the edit engine to
+ // make this process simpler.
+ ScFieldEditEngine& rEngine = mrDestDoc.GetEditEngine();
+ rEngine.SetTextCurrentDefaults(rSrcCell.mpFormula->GetString().getString());
+ std::unique_ptr<EditTextObject> pObj(rEngine.CreateTextObject());
+ pObj->NormalizeString(mrDestDoc.GetSharedStringPool());
+ rSrcCell.set(*pObj);
+ }
+ else
+ rSrcCell.set(rSrcCell.mpFormula->GetString());
+ }
+ else
+ // We don't want to paste this.
+ rSrcCell.clear();
+ }
+ break;
+ case CELLTYPE_NONE:
+ default:
+ // There is nothing to paste.
+ rSrcCell.clear();
+ }
+}
+
+const ScPatternAttr* CopyFromClipContext::getSingleCellPattern( size_t nColOffset ) const
+{
+ assert(nColOffset < maSinglePatterns.size());
+ return maSinglePatterns[nColOffset];
+}
+
+void CopyFromClipContext::setSingleCellPattern( size_t nColOffset, const ScPatternAttr* pAttr )
+{
+ assert(nColOffset < maSinglePatterns.size());
+ maSinglePatterns[nColOffset] = pAttr;
+}
+
+const ScPostIt* CopyFromClipContext::getSingleCellNote( size_t nColOffset ) const
+{
+ assert(nColOffset < maSingleNotes.size());
+ return maSingleNotes[nColOffset];
+}
+
+void CopyFromClipContext::setSingleCellNote( size_t nColOffset, const ScPostIt* pNote )
+{
+ assert(nColOffset < maSingleNotes.size());
+ maSingleNotes[nColOffset] = pNote;
+}
+
+std::shared_ptr<sc::Sparkline> const& CopyFromClipContext::getSingleSparkline(size_t nColOffset) const
+{
+ assert(nColOffset < maSingleSparkline.size());
+ return maSingleSparkline[nColOffset];
+}
+
+void CopyFromClipContext::setSingleSparkline(size_t nColOffset, std::shared_ptr<sc::Sparkline> const& pSparkline)
+{
+ assert(nColOffset < maSingleSparkline.size());
+ maSingleSparkline[nColOffset] = pSparkline;
+}
+
+void CopyFromClipContext::setCondFormatList( ScConditionalFormatList* pCondFormatList )
+{
+ mpCondFormatList = pCondFormatList;
+}
+
+ScConditionalFormatList* CopyFromClipContext::getCondFormatList()
+{
+ return mpCondFormatList;
+}
+
+void CopyFromClipContext::setTableProtected( bool b )
+{
+ mbTableProtected = b;
+}
+
+bool CopyFromClipContext::isTableProtected() const
+{
+ return mbTableProtected;
+}
+
+bool CopyFromClipContext::isAsLink() const
+{
+ return mbAsLink;
+}
+
+bool CopyFromClipContext::isSkipEmptyCells() const
+{
+ return mbSkipEmptyCells;
+}
+
+bool CopyFromClipContext::isCloneNotes() const
+{
+ return bool(mnInsertFlag & (InsertDeleteFlags::NOTE | InsertDeleteFlags::ADDNOTES));
+}
+
+bool CopyFromClipContext::isCloneSparklines() const
+{
+ return bool(mnInsertFlag & InsertDeleteFlags::SPARKLINES);
+}
+
+bool CopyFromClipContext::isDateCell( const ScColumn& rCol, SCROW nRow ) const
+{
+ sal_uLong nNumIndex = rCol.GetAttr(nRow, ATTR_VALUE_FORMAT).GetValue();
+ SvNumFormatType nType = mpClipDoc->GetFormatTable()->GetType(nNumIndex);
+ return (nType == SvNumFormatType::DATE) || (nType == SvNumFormatType::TIME) || (nType == SvNumFormatType::DATETIME);
+}
+
+CopyToClipContext::CopyToClipContext(
+ ScDocument& rDoc, bool bKeepScenarioFlags) :
+ ClipContextBase(rDoc), mbKeepScenarioFlags(bKeepScenarioFlags) {}
+
+CopyToClipContext::~CopyToClipContext() {}
+
+bool CopyToClipContext::isKeepScenarioFlags() const
+{
+ return mbKeepScenarioFlags;
+}
+
+CopyToDocContext::CopyToDocContext(ScDocument& rDoc) :
+ ClipContextBase(rDoc), mbStartListening(true) {}
+
+CopyToDocContext::~CopyToDocContext() {}
+
+void CopyToDocContext::setStartListening( bool b )
+{
+ mbStartListening = b;
+}
+
+bool CopyToDocContext::isStartListening() const
+{
+ return mbStartListening;
+}
+
+MixDocContext::MixDocContext(ScDocument& rDoc) : ClipContextBase(rDoc) {}
+MixDocContext::~MixDocContext() {}
+
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sc/source/core/data/clipparam.cxx b/sc/source/core/data/clipparam.cxx
new file mode 100644
index 000000000..c2255adee
--- /dev/null
+++ b/sc/source/core/data/clipparam.cxx
@@ -0,0 +1,189 @@
+/* -*- 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 <clipparam.hxx>
+
+
+ScClipParam::ScClipParam() :
+ meDirection(Unspecified),
+ mbCutMode(false),
+ mnSourceDocID(0)
+{
+}
+
+ScClipParam::ScClipParam(const ScRange& rRange, bool bCutMode) :
+ meDirection(Unspecified),
+ mbCutMode(bCutMode),
+ mnSourceDocID(0)
+{
+ maRanges.push_back(rRange);
+}
+
+bool ScClipParam::isMultiRange() const
+{
+ return maRanges.size() > 1;
+}
+
+SCCOL ScClipParam::getPasteColSize()
+{
+ if (maRanges.empty())
+ return 0;
+
+ switch (meDirection)
+ {
+ case ScClipParam::Column:
+ {
+ SCCOL nColSize = 0;
+ for ( size_t i = 0, nListSize = maRanges.size(); i < nListSize; ++i )
+ {
+ const ScRange& rRange = maRanges[ i ];
+ nColSize += rRange.aEnd.Col() - rRange.aStart.Col() + 1;
+ }
+ return nColSize;
+ }
+ case ScClipParam::Row:
+ {
+ // We assume that all ranges have identical column size.
+ const ScRange& rRange = maRanges.front();
+ return rRange.aEnd.Col() - rRange.aStart.Col() + 1;
+ }
+ case ScClipParam::Unspecified:
+ default:
+ ;
+ }
+ return 0;
+}
+
+SCROW ScClipParam::getPasteRowSize(const ScDocument& rSrcDoc, bool bIncludeFiltered)
+{
+ if (maRanges.empty())
+ return 0;
+
+ switch (meDirection)
+ {
+ case ScClipParam::Column:
+ {
+ // We assume that all ranges have identical row size.
+ const ScRange& rRange = maRanges.front();
+ return bIncludeFiltered
+ ? rRange.aEnd.Row() - rRange.aStart.Row() + 1
+ : rSrcDoc.CountNonFilteredRows(rRange.aStart.Row(), rRange.aEnd.Row(),
+ rRange.aStart.Tab());
+ }
+ case ScClipParam::Row:
+ {
+ SCROW nRowSize = 0;
+ for ( size_t i = 0, nListSize = maRanges.size(); i < nListSize; ++i )
+ {
+ const ScRange& rRange = maRanges[ i ];
+ nRowSize += bIncludeFiltered ? rRange.aEnd.Row() - rRange.aStart.Row() + 1
+ : rSrcDoc.CountNonFilteredRows(rRange.aStart.Row(),
+ rRange.aEnd.Row(),
+ rRange.aStart.Tab());
+ }
+ return nRowSize;
+ }
+ case ScClipParam::Unspecified:
+ default:
+ ;
+ }
+ return 0;
+}
+
+ScRange ScClipParam::getWholeRange() const
+{
+ return maRanges.Combine();
+}
+
+void ScClipParam::transpose(const ScDocument& rSrcDoc, bool bIncludeFiltered,
+ bool bIsMultiRangeRowFilteredTranspose)
+{
+ mbTransposed = true;
+
+ switch (meDirection)
+ {
+ case Column:
+ meDirection = ScClipParam::Row;
+ break;
+ case Row:
+ meDirection = ScClipParam::Column;
+ break;
+ case Unspecified:
+ default:
+ ;
+ }
+
+ ScRangeList aNewRanges;
+ if (!maRanges.empty())
+ {
+ const ScRange & rRange1 = maRanges.front();
+ SCCOL nColOrigin = rRange1.aStart.Col();
+ SCROW nRowOrigin = rRange1.aStart.Row();
+ SCROW nRowCount = 0;
+ for ( size_t i = 0, n = maRanges.size(); i < n; ++i )
+ {
+ const ScRange & rRange = maRanges[ i ];
+ SCCOL nColDelta = rRange.aStart.Col() - nColOrigin;
+ SCROW nRowDelta = rRange.aStart.Row() - nRowOrigin;
+ SCROW nNonFilteredRows = rSrcDoc.CountNonFilteredRows(
+ rRange.aStart.Row(), rRange.aEnd.Row(), rRange.aStart.Tab());
+ if (!bIsMultiRangeRowFilteredTranspose)
+ {
+ SCCOL nCol1 = 0;
+ SCCOL nCol2 = bIncludeFiltered
+ ? static_cast<SCCOL>(rRange.aEnd.Row() - rRange.aStart.Row())
+ : nNonFilteredRows - 1;
+ SCROW nRow1 = 0;
+ SCROW nRow2 = static_cast<SCROW>(rRange.aEnd.Col() - rRange.aStart.Col());
+ nCol1 += static_cast<SCCOL>(nRowDelta);
+ nCol2 += static_cast<SCCOL>(nRowDelta);
+ nRow1 += static_cast<SCROW>(nColDelta);
+ nRow2 += static_cast<SCROW>(nColDelta);
+ aNewRanges.push_back(ScRange(nColOrigin + nCol1, nRowOrigin + nRow1,
+ rRange.aStart.Tab(), nColOrigin + nCol2,
+ nRowOrigin + nRow2, rRange.aStart.Tab()));
+ }
+ else
+ nRowCount += nNonFilteredRows;
+ }
+
+ // Transpose of filtered multi range row selection is a special case since filtering
+ // and selection are in the same dimension (i.e. row), see ScDocument::TransposeClip()
+ if (bIsMultiRangeRowFilteredTranspose)
+ {
+ assert(!bIncludeFiltered && "bIsMultiRangeRowFilteredTranspose can only be true if bIncludeFiltered is false");
+ SCCOL nColDelta = rRange1.aStart.Col() - nColOrigin;
+ SCROW nRowDelta = rRange1.aStart.Row() - nRowOrigin;
+ SCCOL nCol1 = 0;
+ SCCOL nCol2 = nRowCount - 1;
+ SCROW nRow1 = 0;
+ SCROW nRow2 = static_cast<SCROW>(rRange1.aEnd.Col() - rRange1.aStart.Col());
+ nCol1 += static_cast<SCCOL>(nRowDelta);
+ nCol2 += static_cast<SCCOL>(nRowDelta);
+ nRow1 += static_cast<SCROW>(nColDelta);
+ nRow2 += static_cast<SCROW>(nColDelta);
+ aNewRanges.push_back(ScRange(nColOrigin + nCol1, nRowOrigin + nRow1,
+ rRange1.aStart.Tab(), nColOrigin + nCol2,
+ nRowOrigin + nRow2, rRange1.aStart.Tab()));
+ }
+ }
+ maRanges = aNewRanges;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sc/source/core/data/colcontainer.cxx b/sc/source/core/data/colcontainer.cxx
new file mode 100644
index 000000000..a0a9d8457
--- /dev/null
+++ b/sc/source/core/data/colcontainer.cxx
@@ -0,0 +1,55 @@
+/* -*- 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 <colcontainer.hxx>
+#include <column.hxx>
+
+ScColContainer::ScColContainer( ScSheetLimits const & rSheetLimits, const size_t nSize )
+{
+ aCols.resize( nSize );
+ for ( size_t nCol = 0; nCol < nSize; ++nCol )
+ aCols[nCol].reset( new ScColumn(rSheetLimits) );
+}
+
+ScColContainer::~ScColContainer() COVERITY_NOEXCEPT_FALSE
+{
+ Clear();
+}
+
+void ScColContainer::Clear()
+{
+ SCCOL nSize = size();
+ for ( SCCOL nIdx = 0; nIdx < nSize; ++nIdx )
+ {
+ aCols[nIdx]->PrepareBroadcastersForDestruction();
+ aCols[nIdx].reset();
+ }
+ aCols.clear();
+}
+
+void ScColContainer::resize( ScSheetLimits const & rSheetLimits, const size_t aNewColSize )
+{
+ size_t aOldColSize = aCols.size();
+ aCols.resize( aNewColSize );
+ for ( size_t nCol = aOldColSize; nCol < aNewColSize; ++nCol )
+ aCols[nCol].reset(new ScColumn(rSheetLimits));
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sc/source/core/data/colorscale.cxx b/sc/source/core/data/colorscale.cxx
new file mode 100644
index 000000000..0a357828c
--- /dev/null
+++ b/sc/source/core/data/colorscale.cxx
@@ -0,0 +1,1445 @@
+/* -*- 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/.
+ */
+
+#include <memory>
+#include <colorscale.hxx>
+#include <document.hxx>
+#include <formulacell.hxx>
+#include <fillinfo.hxx>
+#include <bitmaps.hlst>
+#include <tokenarray.hxx>
+#include <refupdatecontext.hxx>
+#include <refdata.hxx>
+#include <editeng/fhgtitem.hxx>
+#include <scitems.hxx>
+
+#include <formula/token.hxx>
+#include <vcl/bitmapex.hxx>
+
+#include <algorithm>
+#include <cassert>
+
+ScFormulaListener::ScFormulaListener(ScFormulaCell* pCell):
+ mbDirty(false),
+ mrDoc(pCell->GetDocument())
+{
+ startListening( pCell->GetCode(), pCell->aPos );
+}
+
+ScFormulaListener::ScFormulaListener(ScDocument& rDoc):
+ mbDirty(false),
+ mrDoc(rDoc)
+{
+}
+
+ScFormulaListener::ScFormulaListener(ScDocument& rDoc, const ScRangeList& rRange):
+ mbDirty(false),
+ mrDoc(rDoc)
+{
+ startListening(rRange);
+}
+
+void ScFormulaListener::startListening(const ScTokenArray* pArr, const ScRange& rRange)
+{
+ if (!pArr || mrDoc.IsClipOrUndo())
+ return;
+
+ for ( auto t: pArr->References() )
+ {
+ switch (t->GetType())
+ {
+ case formula::svSingleRef:
+ {
+ ScAddress aCell = t->GetSingleRef()->toAbs(mrDoc, rRange.aStart);
+ ScAddress aCell2 = t->GetSingleRef()->toAbs(mrDoc, rRange.aEnd);
+ ScRange aRange(aCell, aCell2);
+ if (aRange.IsValid())
+ mrDoc.StartListeningArea(aRange, false, this);
+ }
+ break;
+ case formula::svDoubleRef:
+ {
+ const ScSingleRefData& rRef1 = *t->GetSingleRef();
+ const ScSingleRefData& rRef2 = *t->GetSingleRef2();
+ ScAddress aCell1 = rRef1.toAbs(mrDoc, rRange.aStart);
+ ScAddress aCell2 = rRef2.toAbs(mrDoc, rRange.aStart);
+ ScAddress aCell3 = rRef1.toAbs(mrDoc, rRange.aEnd);
+ ScAddress aCell4 = rRef2.toAbs(mrDoc, rRange.aEnd);
+ ScRange aRange1(aCell1, aCell3);
+ ScRange aRange2(aCell2, aCell4);
+ aRange1.ExtendTo(aRange2);
+ if (aRange1.IsValid())
+ {
+ if (t->GetOpCode() == ocColRowNameAuto)
+ { // automagically
+ if ( rRef1.IsColRel() )
+ { // ColName
+ aRange1.aEnd.SetRow(mrDoc.MaxRow());
+ }
+ else
+ { // RowName
+ aRange1.aEnd.SetCol(mrDoc.MaxCol());
+ }
+ }
+ mrDoc.StartListeningArea(aRange1, false, this);
+ }
+ }
+ break;
+ default:
+ ; // nothing
+ }
+ }
+}
+
+void ScFormulaListener::startListening(const ScRangeList& rRange)
+{
+ if (mrDoc.IsClipOrUndo())
+ return;
+
+ size_t nLength = rRange.size();
+ for (size_t i = 0; i < nLength; ++i)
+ {
+ const ScRange& aRange = rRange[i];
+ mrDoc.StartListeningArea(aRange, false, this);
+ }
+}
+
+void ScFormulaListener::addTokenArray(const ScTokenArray* pArray, const ScRange& rRange)
+{
+ startListening(pArray, rRange);
+}
+
+void ScFormulaListener::setCallback(const std::function<void()>& aCallback)
+{
+ maCallbackFunction = aCallback;
+}
+
+void ScFormulaListener::stopListening()
+{
+ if (mrDoc.IsClipOrUndo())
+ return;
+
+ EndListeningAll();
+}
+
+ScFormulaListener::~ScFormulaListener()
+{
+ stopListening();
+}
+
+void ScFormulaListener::Notify(const SfxHint& rHint)
+{
+ mbDirty = true;
+
+ if (rHint.GetId() == SfxHintId::Dying)
+ return;
+
+ if (maCallbackFunction)
+ maCallbackFunction();
+}
+
+bool ScFormulaListener::NeedsRepaint() const
+{
+ bool bRet = mbDirty;
+ mbDirty = false;
+ return bRet;
+}
+
+ScColorScaleEntry::ScColorScaleEntry():
+ mnVal(0),
+ mpFormat(nullptr),
+ meType(COLORSCALE_VALUE)
+{
+}
+
+ScColorScaleEntry::ScColorScaleEntry(double nVal, const Color& rCol, ScColorScaleEntryType eType):
+ mnVal(nVal),
+ mpFormat(nullptr),
+ maColor(rCol),
+ meType(eType)
+{
+}
+
+ScColorScaleEntry::ScColorScaleEntry(const ScColorScaleEntry& rEntry):
+ mnVal(rEntry.mnVal),
+ mpFormat(rEntry.mpFormat),
+ maColor(rEntry.maColor),
+ meType(rEntry.meType)
+{
+ setListener();
+ if(rEntry.mpCell)
+ {
+ mpCell.reset(new ScFormulaCell(*rEntry.mpCell, rEntry.mpCell->GetDocument(), rEntry.mpCell->aPos, ScCloneFlags::NoMakeAbsExternal));
+ mpCell->StartListeningTo(mpCell->GetDocument());
+ mpListener.reset(new ScFormulaListener(mpCell.get()));
+ }
+}
+
+ScColorScaleEntry::ScColorScaleEntry(ScDocument* pDoc, const ScColorScaleEntry& rEntry):
+ mnVal(rEntry.mnVal),
+ mpFormat(rEntry.mpFormat),
+ maColor(rEntry.maColor),
+ meType(rEntry.meType)
+{
+ setListener();
+ if(rEntry.mpCell)
+ {
+ mpCell.reset(new ScFormulaCell(*rEntry.mpCell, *pDoc, rEntry.mpCell->aPos, ScCloneFlags::NoMakeAbsExternal));
+ mpCell->StartListeningTo( *pDoc );
+ mpListener.reset(new ScFormulaListener(mpCell.get()));
+ if (mpFormat)
+ mpListener->setCallback([&]() { mpFormat->DoRepaint();});
+ }
+}
+
+ScColorScaleEntry::~ScColorScaleEntry() COVERITY_NOEXCEPT_FALSE
+{
+ if(mpCell)
+ mpCell->EndListeningTo(mpCell->GetDocument());
+}
+
+void ScColorScaleEntry::SetFormula( const OUString& rFormula, ScDocument& rDoc, const ScAddress& rAddr, formula::FormulaGrammar::Grammar eGrammar )
+{
+ mpCell.reset(new ScFormulaCell( rDoc, rAddr, rFormula, eGrammar ));
+ mpCell->StartListeningTo( rDoc );
+ mpListener.reset(new ScFormulaListener(mpCell.get()));
+ if (mpFormat)
+ mpListener->setCallback([&]() { mpFormat->DoRepaint();});
+}
+
+const ScTokenArray* ScColorScaleEntry::GetFormula() const
+{
+ if(mpCell)
+ {
+ return mpCell->GetCode();
+ }
+
+ return nullptr;
+}
+
+OUString ScColorScaleEntry::GetFormula( formula::FormulaGrammar::Grammar eGrammar ) const
+{
+ if(mpCell)
+ {
+ return mpCell->GetFormula(eGrammar);
+ }
+
+ return OUString();
+}
+
+double ScColorScaleEntry::GetValue() const
+{
+ if(mpCell)
+ {
+ mpCell->Interpret();
+ if(mpCell->IsValue())
+ return mpCell->GetValue();
+
+ return std::numeric_limits<double>::max();
+ }
+
+ return mnVal;
+}
+
+void ScColorScaleEntry::SetValue(double nValue)
+{
+ mnVal = nValue;
+ mpCell.reset();
+ setListener();
+}
+
+void ScColorScaleEntry::UpdateReference( const sc::RefUpdateContext& rCxt )
+{
+ if (!mpCell)
+ {
+ setListener();
+ return;
+ }
+
+ mpCell->UpdateReference(rCxt);
+ mpListener.reset(new ScFormulaListener(mpCell.get()));
+ SetRepaintCallback(mpFormat);
+}
+
+void ScColorScaleEntry::UpdateInsertTab( const sc::RefUpdateInsertTabContext& rCxt )
+{
+ if (!mpCell)
+ {
+ setListener();
+ return;
+ }
+
+ mpCell->UpdateInsertTab(rCxt);
+ mpListener.reset(new ScFormulaListener(mpCell.get()));
+ SetRepaintCallback(mpFormat);
+}
+
+void ScColorScaleEntry::UpdateDeleteTab( const sc::RefUpdateDeleteTabContext& rCxt )
+{
+ if (!mpCell)
+ {
+ setListener();
+ return;
+ }
+
+ mpCell->UpdateDeleteTab(rCxt);
+ mpListener.reset(new ScFormulaListener(mpCell.get()));
+ SetRepaintCallback(mpFormat);
+}
+
+void ScColorScaleEntry::UpdateMoveTab( const sc::RefUpdateMoveTabContext& rCxt )
+{
+ if (!mpCell)
+ {
+ setListener();
+ return;
+ }
+
+ SCTAB nTabNo = rCxt.getNewTab(mpCell->aPos.Tab());
+ mpCell->UpdateMoveTab(rCxt, nTabNo);
+ mpListener.reset(new ScFormulaListener(mpCell.get()));
+ SetRepaintCallback(mpFormat);
+}
+
+void ScColorScaleEntry::SetColor(const Color& rColor)
+{
+ maColor = rColor;
+}
+
+void ScColorScaleEntry::SetRepaintCallback(ScConditionalFormat* pFormat)
+{
+ mpFormat = pFormat;
+ setListener();
+ if (mpFormat && mpListener)
+ mpListener->setCallback([&]() { mpFormat->DoRepaint();});
+}
+
+void ScColorScaleEntry::SetType( ScColorScaleEntryType eType )
+{
+ meType = eType;
+ if(eType != COLORSCALE_FORMULA)
+ {
+ mpCell.reset();
+ mpListener.reset();
+ }
+
+ setListener();
+}
+
+void ScColorScaleEntry::setListener()
+{
+ if (!mpFormat)
+ return;
+
+ if (meType == COLORSCALE_PERCENT || meType == COLORSCALE_PERCENTILE
+ || meType == COLORSCALE_MIN || meType == COLORSCALE_MAX
+ || meType == COLORSCALE_AUTO)
+ {
+ mpListener.reset(new ScFormulaListener(*mpFormat->GetDocument(), mpFormat->GetRange()));
+ mpListener->setCallback([&]() { mpFormat->DoRepaint();});
+ }
+}
+
+void ScColorScaleEntry::SetRepaintCallback(const std::function<void()>& func)
+{
+ mpListener->setCallback(func);
+}
+
+ScColorFormat::ScColorFormat(ScDocument* pDoc)
+ : ScFormatEntry(pDoc)
+ , mpParent(nullptr)
+{
+}
+
+ScColorFormat::~ScColorFormat()
+{
+}
+
+void ScColorFormat::SetParent( ScConditionalFormat* pParent )
+{
+ mpParent = pParent;
+}
+
+ScColorScaleFormat::ScColorScaleFormat(ScDocument* pDoc):
+ ScColorFormat(pDoc)
+{
+}
+
+ScColorScaleFormat::ScColorScaleFormat(ScDocument* pDoc, const ScColorScaleFormat& rFormat):
+ ScColorFormat(pDoc)
+{
+ for(const auto& rxEntry : rFormat)
+ {
+ maColorScales.emplace_back(new ScColorScaleEntry(pDoc, *rxEntry));
+ }
+}
+
+ScColorFormat* ScColorScaleFormat::Clone(ScDocument* pDoc) const
+{
+ return new ScColorScaleFormat(pDoc, *this);
+}
+
+ScColorScaleFormat::~ScColorScaleFormat()
+{
+}
+
+void ScColorScaleFormat::SetParent(ScConditionalFormat* pFormat)
+{
+ for (auto itr = begin(), itrEnd = end(); itr != itrEnd; ++itr)
+ {
+ (*itr)->SetRepaintCallback(pFormat);
+ }
+ ScColorFormat::SetParent(pFormat);
+}
+
+void ScColorScaleFormat::AddEntry( ScColorScaleEntry* pEntry )
+{
+ maColorScales.push_back(std::unique_ptr<ScColorScaleEntry, o3tl::default_delete<ScColorScaleEntry>>(pEntry));
+ maColorScales.back()->SetRepaintCallback(mpParent);
+}
+
+double ScColorScaleFormat::GetMinValue() const
+{
+ ScColorScaleEntries::const_iterator itr = maColorScales.begin();
+
+ if((*itr)->GetType() == COLORSCALE_VALUE || (*itr)->GetType() == COLORSCALE_FORMULA)
+ return (*itr)->GetValue();
+ else
+ {
+ return getMinValue();
+ }
+}
+
+double ScColorScaleFormat::GetMaxValue() const
+{
+ ScColorScaleEntries::const_reverse_iterator itr = maColorScales.rbegin();
+
+ if((*itr)->GetType() == COLORSCALE_VALUE || (*itr)->GetType() == COLORSCALE_FORMULA)
+ return (*itr)->GetValue();
+ else
+ {
+ return getMaxValue();
+ }
+}
+
+void ScColorScaleFormat::calcMinMax(double& rMin, double& rMax) const
+{
+ rMin = GetMinValue();
+ rMax = GetMaxValue();
+}
+
+const ScRangeList& ScColorFormat::GetRange() const
+{
+ return mpParent->GetRange();
+}
+
+std::vector<double>& ScColorFormat::getValues() const
+{
+ if(!mpCache)
+ {
+ mpCache.reset(new ScColorFormatCache);
+ std::vector<double>& rValues = mpCache->maValues;
+
+ size_t n = GetRange().size();
+ const ScRangeList& aRanges = GetRange();
+ for(size_t i = 0; i < n; ++i)
+ {
+ const ScRange & rRange = aRanges[i];
+ SCTAB nTab = rRange.aStart.Tab();
+
+ SCCOL nColStart = rRange.aStart.Col();
+ SCROW nRowStart = rRange.aStart.Row();
+ SCCOL nColEnd = rRange.aEnd.Col();
+ SCROW nRowEnd = rRange.aEnd.Row();
+
+ if(nRowEnd == mpDoc->MaxRow())
+ {
+ bool bShrunk = false;
+ mpDoc->ShrinkToUsedDataArea(bShrunk, nTab, nColStart, nRowStart,
+ nColEnd, nRowEnd, false);
+ }
+ for(SCCOL nCol = nColStart; nCol <= nColEnd; ++nCol)
+ {
+ for(SCROW nRow = nRowStart; nRow <= nRowEnd; ++nRow)
+ {
+ ScAddress aAddr(nCol, nRow, nTab);
+ ScRefCellValue rCell(*mpDoc, aAddr);
+ if(rCell.hasNumeric())
+ {
+ double aVal = rCell.getValue();
+ rValues.push_back(aVal);
+ }
+ }
+ }
+ }
+
+ std::sort(rValues.begin(), rValues.end());
+ }
+
+ return mpCache->maValues;
+}
+
+double ScColorFormat::getMinValue() const
+{
+ std::vector<double>& rValues = getValues();
+ if(rValues.empty())
+ return 0;
+ return rValues[0];
+}
+
+double ScColorFormat::getMaxValue() const
+{
+ std::vector<double>& rValues = getValues();
+ if(rValues.empty())
+ return 0;
+ return rValues[rValues.size()-1];
+}
+
+void ScColorFormat::startRendering()
+{
+ mpCache.reset();
+}
+
+void ScColorFormat::endRendering()
+{
+ mpCache.reset();
+}
+
+namespace {
+
+sal_uInt8 GetColorValue( double nVal, double nVal1, sal_uInt8 nColVal1, double nVal2, sal_uInt8 nColVal2 )
+{
+ if (nVal <= nVal1)
+ return nColVal1;
+
+ if (nVal >= nVal2)
+ return nColVal2;
+
+ sal_uInt8 nColVal = static_cast<int>((nVal - nVal1)/(nVal2-nVal1)*(nColVal2-nColVal1))+nColVal1;
+ return nColVal;
+}
+
+Color CalcColor( double nVal, double nVal1, const Color& rCol1, double nVal2, const Color& rCol2)
+{
+ sal_uInt8 nColRed = GetColorValue(nVal, nVal1, rCol1.GetRed(), nVal2, rCol2.GetRed());
+ sal_uInt8 nColBlue = GetColorValue(nVal, nVal1, rCol1.GetBlue(), nVal2, rCol2.GetBlue());
+ sal_uInt8 nColGreen = GetColorValue(nVal, nVal1, rCol1.GetGreen(), nVal2, rCol2.GetGreen());
+
+ return Color(nColRed, nColGreen, nColBlue);
+}
+
+/**
+ * @param rVector sorted vector of the array
+ * @param fPercentile percentile
+ */
+double GetPercentile( const std::vector<double>& rArray, double fPercentile )
+{
+ assert(!rArray.empty());
+ SAL_WARN_IF(fPercentile < 0, "sc", "negative percentile");
+ if (fPercentile < 0)
+ return rArray.front();
+ assert(fPercentile <= 1);
+ size_t nSize = rArray.size();
+ double fFloor = ::rtl::math::approxFloor(fPercentile * (nSize-1));
+ size_t nIndex = static_cast<size_t>(fFloor);
+ double fDiff = fPercentile * (nSize-1) - fFloor;
+ std::vector<double>::const_iterator iter = rArray.begin() + nIndex;
+ if (fDiff == 0.0)
+ return *iter;
+ else
+ {
+ double fVal = *iter;
+ iter = rArray.begin() + nIndex+1;
+ return fVal + fDiff * (*iter - fVal);
+ }
+}
+
+}
+
+double ScColorScaleFormat::CalcValue(double nMin, double nMax, const ScColorScaleEntries::const_iterator& itr) const
+{
+ switch((*itr)->GetType())
+ {
+ case COLORSCALE_PERCENT:
+ return nMin + (nMax-nMin)*((*itr)->GetValue()/100);
+ case COLORSCALE_MIN:
+ return nMin;
+ case COLORSCALE_MAX:
+ return nMax;
+ case COLORSCALE_PERCENTILE:
+ {
+ std::vector<double>& rValues = getValues();
+ if(rValues.size() == 1)
+ return rValues[0];
+ else
+ {
+ double fPercentile = (*itr)->GetValue()/100.0;
+ return GetPercentile(rValues, fPercentile);
+ }
+ }
+
+ default:
+ break;
+ }
+
+ return (*itr)->GetValue();
+}
+
+std::optional<Color> ScColorScaleFormat::GetColor( const ScAddress& rAddr ) const
+{
+ ScRefCellValue rCell(*mpDoc, rAddr);
+ if(!rCell.hasNumeric())
+ return std::optional<Color>();
+
+ // now we have for sure a value
+ double nVal = rCell.getValue();
+
+ if (maColorScales.size() < 2)
+ return std::optional<Color>();
+
+ double nMin = std::numeric_limits<double>::max();
+ double nMax = std::numeric_limits<double>::min();
+ calcMinMax(nMin, nMax);
+
+ // this check is for safety
+ if(nMin >= nMax)
+ return std::optional<Color>();
+
+ ScColorScaleEntries::const_iterator itr = begin();
+ double nValMin = CalcValue(nMin, nMax, itr);
+ Color rColMin = (*itr)->GetColor();
+ ++itr;
+ double nValMax = CalcValue(nMin, nMax, itr);
+ Color rColMax = (*itr)->GetColor();
+
+ ++itr;
+ while(itr != end() && nVal > nValMax)
+ {
+ rColMin = rColMax;
+ nValMin = nValMax;
+ rColMax = (*itr)->GetColor();
+ nValMax = CalcValue(nMin, nMax, itr);
+ ++itr;
+ }
+
+ Color aColor = CalcColor(nVal, nValMin, rColMin, nValMax, rColMax);
+
+ return aColor;
+}
+
+void ScColorScaleFormat::UpdateReference( sc::RefUpdateContext& rCxt )
+{
+ for(ScColorScaleEntries::iterator itr = begin(); itr != end(); ++itr)
+ (*itr)->UpdateReference(rCxt);
+}
+
+void ScColorScaleFormat::UpdateInsertTab( sc::RefUpdateInsertTabContext& rCxt )
+{
+ for (ScColorScaleEntries::iterator it = begin(); it != end(); ++it)
+ (*it)->UpdateInsertTab(rCxt);
+}
+
+void ScColorScaleFormat::UpdateDeleteTab( sc::RefUpdateDeleteTabContext& rCxt )
+{
+ for (ScColorScaleEntries::iterator it = begin(); it != end(); ++it)
+ (*it)->UpdateDeleteTab(rCxt);
+}
+
+void ScColorScaleFormat::UpdateMoveTab( sc::RefUpdateMoveTabContext& rCxt )
+{
+ for (ScColorScaleEntries::iterator it = begin(); it != end(); ++it)
+ (*it)->UpdateMoveTab(rCxt);
+}
+
+ScFormatEntry::Type ScColorScaleFormat::GetType() const
+{
+ return Type::Colorscale;
+}
+
+ScColorScaleEntries::iterator ScColorScaleFormat::begin()
+{
+ return maColorScales.begin();
+}
+
+ScColorScaleEntries::const_iterator ScColorScaleFormat::begin() const
+{
+ return maColorScales.begin();
+}
+
+ScColorScaleEntries::iterator ScColorScaleFormat::end()
+{
+ return maColorScales.end();
+}
+
+ScColorScaleEntries::const_iterator ScColorScaleFormat::end() const
+{
+ return maColorScales.end();
+}
+
+ScColorScaleEntry* ScColorScaleFormat::GetEntry(size_t nPos)
+{
+ if (maColorScales.size() <= nPos)
+ return nullptr;
+
+ return maColorScales[nPos].get();
+}
+
+const ScColorScaleEntry* ScColorScaleFormat::GetEntry(size_t nPos) const
+{
+ if (maColorScales.size() <= nPos)
+ return nullptr;
+
+ return maColorScales[nPos].get();
+}
+
+size_t ScColorScaleFormat::size() const
+{
+ return maColorScales.size();
+}
+
+void ScColorScaleFormat::EnsureSize()
+{
+ if (maColorScales.size() < 2)
+ {
+ // TODO: create 2 valid entries
+ }
+}
+
+ScDataBarFormat::ScDataBarFormat(ScDocument* pDoc):
+ ScColorFormat(pDoc),
+ mpFormatData(new ScDataBarFormatData())
+{
+}
+
+ScDataBarFormat::ScDataBarFormat(ScDocument* pDoc, const ScDataBarFormat& rFormat):
+ ScColorFormat(pDoc),
+ mpFormatData(new ScDataBarFormatData(*rFormat.mpFormatData))
+{
+}
+
+void ScDataBarFormat::SetDataBarData( ScDataBarFormatData* pData )
+{
+ mpFormatData.reset(pData);
+ if (mpParent)
+ {
+ mpFormatData->mpUpperLimit->SetRepaintCallback(mpParent);
+ mpFormatData->mpLowerLimit->SetRepaintCallback(mpParent);
+ }
+}
+
+ScDataBarFormatData* ScDataBarFormat::GetDataBarData()
+{
+ return mpFormatData.get();
+}
+
+const ScDataBarFormatData* ScDataBarFormat::GetDataBarData() const
+{
+ return mpFormatData.get();
+}
+
+ScColorFormat* ScDataBarFormat::Clone(ScDocument* pDoc) const
+{
+ return new ScDataBarFormat(pDoc, *this);
+}
+
+void ScDataBarFormat::SetParent(ScConditionalFormat* pFormat)
+{
+ if (mpFormatData)
+ {
+ mpFormatData->mpUpperLimit->SetRepaintCallback(pFormat);
+ mpFormatData->mpLowerLimit->SetRepaintCallback(pFormat);
+ }
+ ScColorFormat::SetParent(pFormat);
+}
+
+ScFormatEntry::Type ScDataBarFormat::GetType() const
+{
+ return Type::Databar;
+}
+
+void ScDataBarFormat::UpdateReference( sc::RefUpdateContext& rCxt )
+{
+ mpFormatData->mpUpperLimit->UpdateReference(rCxt);
+ mpFormatData->mpLowerLimit->UpdateReference(rCxt);
+}
+
+void ScDataBarFormat::UpdateInsertTab( sc::RefUpdateInsertTabContext& rCxt )
+{
+ mpFormatData->mpUpperLimit->UpdateInsertTab(rCxt);
+ mpFormatData->mpLowerLimit->UpdateInsertTab(rCxt);
+}
+
+void ScDataBarFormat::UpdateDeleteTab( sc::RefUpdateDeleteTabContext& rCxt )
+{
+ mpFormatData->mpUpperLimit->UpdateDeleteTab(rCxt);
+ mpFormatData->mpLowerLimit->UpdateDeleteTab(rCxt);
+}
+
+void ScDataBarFormat::UpdateMoveTab( sc::RefUpdateMoveTabContext& rCxt )
+{
+ mpFormatData->mpUpperLimit->UpdateMoveTab(rCxt);
+ mpFormatData->mpLowerLimit->UpdateMoveTab(rCxt);
+}
+
+double ScDataBarFormat::getMin(double nMin, double nMax) const
+{
+ switch(mpFormatData->mpLowerLimit->GetType())
+ {
+ case COLORSCALE_MIN:
+ return nMin;
+
+ case COLORSCALE_AUTO:
+ return std::min<double>(0, nMin);
+
+ case COLORSCALE_PERCENT:
+ return nMin + (nMax-nMin)/100*mpFormatData->mpLowerLimit->GetValue();
+
+ case COLORSCALE_PERCENTILE:
+ {
+ double fPercentile = mpFormatData->mpLowerLimit->GetValue()/100.0;
+ std::vector<double>& rValues = getValues();
+ return GetPercentile(rValues, fPercentile);
+ }
+
+ default:
+ break;
+ }
+
+ return mpFormatData->mpLowerLimit->GetValue();
+}
+
+double ScDataBarFormat::getMax(double nMin, double nMax) const
+{
+ switch(mpFormatData->mpUpperLimit->GetType())
+ {
+ case COLORSCALE_MAX:
+ return nMax;
+ case COLORSCALE_AUTO:
+ return std::max<double>(0, nMax);
+ case COLORSCALE_PERCENT:
+ return nMin + (nMax-nMin)/100*mpFormatData->mpUpperLimit->GetValue();
+ case COLORSCALE_PERCENTILE:
+ {
+ double fPercentile = mpFormatData->mpUpperLimit->GetValue()/100.0;
+ std::vector<double>& rValues = getValues();
+ return GetPercentile(rValues, fPercentile);
+ }
+
+ default:
+ break;
+ }
+
+ return mpFormatData->mpUpperLimit->GetValue();
+}
+
+std::unique_ptr<ScDataBarInfo> ScDataBarFormat::GetDataBarInfo(const ScAddress& rAddr) const
+{
+ ScRefCellValue rCell(*mpDoc, rAddr);
+ if(!rCell.hasNumeric())
+ return nullptr;
+
+ // now we have for sure a value
+
+ double nValMin = getMinValue();
+ double nValMax = getMaxValue();
+ double nMin = getMin(nValMin, nValMax);
+ double nMax = getMax(nValMin, nValMax);
+ double nMinLength = mpFormatData->mnMinLength;
+ double nMaxLength = mpFormatData->mnMaxLength;
+
+ double nValue = rCell.getValue();
+
+ std::unique_ptr<ScDataBarInfo> pInfo(new ScDataBarInfo);
+ if(mpFormatData->meAxisPosition == databar::NONE)
+ {
+ if(nValue <= nMin)
+ {
+ pInfo->mnLength = nMinLength;
+ }
+ else if(nValue >= nMax)
+ {
+ pInfo->mnLength = nMaxLength;
+ }
+ else
+ {
+ double nDiff = nMax - nMin;
+ pInfo->mnLength = nMinLength + (nValue - nMin)/nDiff * (nMaxLength-nMinLength);
+ }
+ pInfo->mnZero = 0;
+ }
+ else if (mpFormatData->meAxisPosition == databar::AUTOMATIC)
+ {
+ // if auto is used we may need to adjust it
+ // for the length calculation
+ if (mpFormatData->mpLowerLimit->GetType() == COLORSCALE_AUTO && nMin > 0)
+ nMin = 0;
+ if (mpFormatData->mpUpperLimit->GetType() == COLORSCALE_MAX && nMax < 0)
+ nMax = 0;
+
+ //calculate the zero position first
+ if(nMin < 0)
+ {
+ if(nMax < 0)
+ pInfo->mnZero = 100;
+ else
+ {
+ pInfo->mnZero = -100*nMin/(nMax-nMin);
+ }
+ }
+ else
+ pInfo->mnZero = 0;
+
+ double nMinNonNegative = std::max(0.0, nMin);
+ double nMaxNonPositive = std::min(0.0, nMax);
+ //calculate the length
+ if(nValue < 0 && nMin < 0)
+ {
+ if (nValue < nMin)
+ pInfo->mnLength = -100;
+ else
+ pInfo->mnLength = -100 * (nValue-nMaxNonPositive)/(nMin-nMaxNonPositive);
+ }
+ else
+ {
+ if ( nValue > nMax )
+ pInfo->mnLength = 100;
+ else if (nValue <= nMin)
+ pInfo->mnLength = 0;
+ else
+ pInfo->mnLength = 100 * (nValue-nMinNonNegative)/(nMax-nMinNonNegative);
+ }
+ }
+ else if( mpFormatData->meAxisPosition == databar::MIDDLE)
+ {
+ pInfo->mnZero = 50;
+ double nAbsMax = std::max(std::abs(nMin), std::abs(nMax));
+ if (nValue < 0 && nMin < 0)
+ {
+ if (nValue < nMin)
+ pInfo->mnLength = nMaxLength * (nMin/nAbsMax);
+ else
+ pInfo->mnLength = nMaxLength * (nValue/nAbsMax);
+ }
+ else
+ {
+ if (nValue > nMax)
+ pInfo->mnLength = nMaxLength * (nMax/nAbsMax);
+ else
+ pInfo->mnLength = nMaxLength * (std::max(nValue, nMin)/nAbsMax);
+ }
+ }
+ else
+ assert(false);
+
+ // set color
+ if(mpFormatData->mbNeg && nValue < 0)
+ {
+ if(mpFormatData->mxNegativeColor)
+ {
+ pInfo->maColor = *mpFormatData->mxNegativeColor;
+ }
+ else
+ {
+ // default negative color is red
+ pInfo->maColor = COL_LIGHTRED;
+ }
+
+ }
+ else
+ pInfo->maColor = mpFormatData->maPositiveColor;
+
+ pInfo->mbGradient = mpFormatData->mbGradient;
+ pInfo->mbShowValue = !mpFormatData->mbOnlyBar;
+ pInfo->maAxisColor = mpFormatData->maAxisColor;
+
+ return pInfo;
+}
+
+void ScDataBarFormat::EnsureSize()
+{
+ if (!mpFormatData->mpLowerLimit)
+ {
+ // TODO: implement
+ }
+ if (!mpFormatData->mpUpperLimit)
+ {
+ // TODO: implement
+ }
+}
+
+ScIconSetFormatData::ScIconSetFormatData(ScIconSetFormatData const& rOther)
+ : eIconSetType(rOther.eIconSetType)
+ , mbShowValue(rOther.mbShowValue)
+ , mbReverse(rOther.mbReverse)
+ , mbCustom(rOther.mbCustom)
+ , maCustomVector(rOther.maCustomVector)
+{
+ m_Entries.reserve(rOther.m_Entries.size());
+ for (auto const& it : rOther.m_Entries)
+ {
+ m_Entries.emplace_back(new ScColorScaleEntry(*it));
+ }
+}
+
+ScIconSetFormat::ScIconSetFormat(ScDocument* pDoc):
+ ScColorFormat(pDoc),
+ mpFormatData(new ScIconSetFormatData)
+{
+}
+
+ScIconSetFormat::ScIconSetFormat(ScDocument* pDoc, const ScIconSetFormat& rFormat):
+ ScColorFormat(pDoc),
+ mpFormatData(new ScIconSetFormatData(*rFormat.mpFormatData))
+{
+}
+
+ScColorFormat* ScIconSetFormat::Clone( ScDocument* pDoc ) const
+{
+ return new ScIconSetFormat(pDoc, *this);
+}
+
+void ScIconSetFormat::SetParent(ScConditionalFormat* pFormat)
+{
+ for(iterator itr = begin(); itr != end(); ++itr)
+ {
+ (*itr)->SetRepaintCallback(pFormat);
+ }
+ ScColorFormat::SetParent(pFormat);
+}
+
+void ScIconSetFormat::SetIconSetData( ScIconSetFormatData* pFormatData )
+{
+ mpFormatData.reset( pFormatData );
+ SetParent(mpParent);
+}
+
+ScIconSetFormatData* ScIconSetFormat::GetIconSetData()
+{
+ return mpFormatData.get();
+}
+
+const ScIconSetFormatData* ScIconSetFormat::GetIconSetData() const
+{
+ return mpFormatData.get();
+}
+
+std::unique_ptr<ScIconSetInfo> ScIconSetFormat::GetIconSetInfo(const ScAddress& rAddr) const
+{
+ ScRefCellValue rCell(*mpDoc, rAddr);
+ if(!rCell.hasNumeric())
+ return nullptr;
+
+ // now we have for sure a value
+ double nVal = rCell.getValue();
+
+ if (mpFormatData->m_Entries.size() < 2)
+ return nullptr;
+
+ double nMin = GetMinValue();
+ double nMax = GetMaxValue();
+
+ sal_Int32 nIndex = 0;
+ const_iterator itr = begin();
+ ++itr;
+ double nValMax = CalcValue(nMin, nMax, itr);
+
+ ++itr;
+ while(itr != end() && nVal >= nValMax)
+ {
+ ++nIndex;
+ nValMax = CalcValue(nMin, nMax, itr);
+ ++itr;
+ }
+
+ if(nVal >= nValMax)
+ ++nIndex;
+
+ std::unique_ptr<ScIconSetInfo> pInfo(new ScIconSetInfo);
+
+ const SfxPoolItem& rPoolItem = mpDoc->GetPattern(rAddr)->GetItem(ATTR_FONT_HEIGHT);
+ tools::Long aFontHeight = static_cast<const SvxFontHeightItem&>(rPoolItem).GetHeight();
+ pInfo->mnHeight = aFontHeight;
+
+ if(mpFormatData->mbReverse)
+ {
+ sal_Int32 nMaxIndex = mpFormatData->m_Entries.size() - 1;
+ nIndex = nMaxIndex - nIndex;
+ }
+
+ if (mpFormatData->mbCustom && sal_Int32(mpFormatData->maCustomVector.size()) > nIndex)
+ {
+ ScIconSetType eCustomType = mpFormatData->maCustomVector[nIndex].first;
+ sal_Int32 nCustomIndex = mpFormatData->maCustomVector[nIndex].second;
+ if (nCustomIndex == -1)
+ {
+ return nullptr;
+ }
+
+ pInfo->eIconSetType = eCustomType;
+ pInfo->nIconIndex = nCustomIndex;
+ }
+ else
+ {
+ pInfo->nIconIndex = nIndex;
+ pInfo->eIconSetType = mpFormatData->eIconSetType;
+ }
+
+ pInfo->mbShowValue = mpFormatData->mbShowValue;
+ return pInfo;
+}
+
+ScFormatEntry::Type ScIconSetFormat::GetType() const
+{
+ return Type::Iconset;
+}
+
+void ScIconSetFormat::UpdateReference( sc::RefUpdateContext& rCxt )
+{
+ for(iterator itr = begin(); itr != end(); ++itr)
+ {
+ (*itr)->UpdateReference(rCxt);
+ }
+}
+
+void ScIconSetFormat::UpdateInsertTab( sc::RefUpdateInsertTabContext& rCxt )
+{
+ for(iterator itr = begin(); itr != end(); ++itr)
+ {
+ (*itr)->UpdateInsertTab(rCxt);
+ }
+}
+
+void ScIconSetFormat::UpdateDeleteTab( sc::RefUpdateDeleteTabContext& rCxt )
+{
+ for(iterator itr = begin(); itr != end(); ++itr)
+ {
+ (*itr)->UpdateDeleteTab(rCxt);
+ }
+}
+
+void ScIconSetFormat::UpdateMoveTab( sc::RefUpdateMoveTabContext& rCxt )
+{
+ for(iterator itr = begin(); itr != end(); ++itr)
+ {
+ (*itr)->UpdateMoveTab(rCxt);
+ }
+}
+
+ScIconSetFormat::iterator ScIconSetFormat::begin()
+{
+ return mpFormatData->m_Entries.begin();
+}
+
+ScIconSetFormat::const_iterator ScIconSetFormat::begin() const
+{
+ return mpFormatData->m_Entries.begin();
+}
+
+ScIconSetFormat::iterator ScIconSetFormat::end()
+{
+ return mpFormatData->m_Entries.end();
+}
+
+ScIconSetFormat::const_iterator ScIconSetFormat::end() const
+{
+ return mpFormatData->m_Entries.end();
+}
+
+double ScIconSetFormat::GetMinValue() const
+{
+ const_iterator itr = begin();
+
+ if ((*itr)->GetType() == COLORSCALE_VALUE || (*itr)->GetType() == COLORSCALE_FORMULA)
+ return (*itr)->GetValue();
+ else
+ {
+ return getMinValue();
+ }
+}
+
+double ScIconSetFormat::GetMaxValue() const
+{
+ auto const itr = mpFormatData->m_Entries.rbegin();
+
+ if ((*itr)->GetType() == COLORSCALE_VALUE || (*itr)->GetType() == COLORSCALE_FORMULA)
+ return (*itr)->GetValue();
+ else
+ {
+ return getMaxValue();
+ }
+}
+
+double ScIconSetFormat::CalcValue(double nMin, double nMax, const ScIconSetFormat::const_iterator& itr) const
+{
+ switch ((*itr)->GetType())
+ {
+ case COLORSCALE_PERCENT:
+ return nMin + (nMax-nMin)*((*itr)->GetValue()/100);
+ case COLORSCALE_MIN:
+ return nMin;
+ case COLORSCALE_MAX:
+ return nMax;
+ case COLORSCALE_PERCENTILE:
+ {
+ std::vector<double>& rValues = getValues();
+ if(rValues.size() == 1)
+ return rValues[0];
+ else
+ {
+ double fPercentile = (*itr)->GetValue()/100.0;
+ return GetPercentile(rValues, fPercentile);
+ }
+ }
+
+ default:
+ break;
+ }
+
+ return (*itr)->GetValue();
+}
+
+const ScIconSetMap ScIconSetFormat::g_IconSetMap[] = {
+ { "3Arrows", IconSet_3Arrows, 3 },
+ { "3ArrowsGray", IconSet_3ArrowsGray, 3 },
+ { "3Flags", IconSet_3Flags, 3 },
+ { "3TrafficLights1", IconSet_3TrafficLights1, 3 },
+ { "3TrafficLights2", IconSet_3TrafficLights2, 3 },
+ { "3Signs", IconSet_3Signs, 3 },
+ { "3Symbols", IconSet_3Symbols, 3 },
+ { "3Symbols2", IconSet_3Symbols2, 3 },
+ { "3Smilies", IconSet_3Smilies, 3 },
+ { "3ColorSmilies", IconSet_3ColorSmilies, 3 },
+ { "3Stars", IconSet_3Stars, 3 },
+ { "3Triangles", IconSet_3Triangles, 3 },
+ { "4Arrows", IconSet_4Arrows, 4 },
+ { "4ArrowsGray", IconSet_4ArrowsGray, 4 },
+ { "4RedToBlack", IconSet_4RedToBlack, 4 },
+ { "4Rating", IconSet_4Rating, 4 },
+ { "4TrafficLights", IconSet_4TrafficLights, 4 },
+ { "5Arrows", IconSet_5Arrows, 5 },
+ { "5ArrowsGray", IconSet_5ArrowsGray, 5 },
+ { "5Rating", IconSet_5Ratings, 5 },
+ { "5Quarters", IconSet_5Quarters, 5 },
+ { "5Boxes", IconSet_5Boxes, 5 },
+ { nullptr, IconSet_3Arrows, 0 }
+};
+
+size_t ScIconSetFormat::size() const
+{
+ return mpFormatData->m_Entries.size();
+}
+
+
+namespace {
+
+constexpr rtl::OUStringConstExpr a3TrafficLights1[] = {
+ BMP_ICON_SET_CIRCLES1_RED, BMP_ICON_SET_CIRCLES1_YELLOW, BMP_ICON_SET_CIRCLES1_GREEN
+};
+
+constexpr rtl::OUStringConstExpr a3TrafficLights2[] = {
+ BMP_ICON_SET_TRAFFICLIGHTS_RED, BMP_ICON_SET_TRAFFICLIGHTS_YELLOW, BMP_ICON_SET_TRAFFICLIGHTS_GREEN
+};
+
+constexpr rtl::OUStringConstExpr a3Arrows[] = {
+ BMP_ICON_SET_COLORARROWS_DOWN, BMP_ICON_SET_COLORARROWS_SAME, BMP_ICON_SET_COLORARROWS_UP
+};
+
+constexpr rtl::OUStringConstExpr a3ArrowsGray[] = {
+ BMP_ICON_SET_GRAYARROWS_DOWN, BMP_ICON_SET_GRAYARROWS_SAME, BMP_ICON_SET_GRAYARROWS_UP
+};
+
+constexpr rtl::OUStringConstExpr a3Flags[] = {
+ BMP_ICON_SET_FLAGS_RED, BMP_ICON_SET_FLAGS_YELLOW, BMP_ICON_SET_FLAGS_GREEN
+};
+
+constexpr rtl::OUStringConstExpr a3Smilies[] = {
+ BMP_ICON_SET_POSITIVE_YELLOW_SMILIE, BMP_ICON_SET_NEUTRAL_YELLOW_SMILIE, BMP_ICON_SET_NEGATIVE_YELLOW_SMILIE
+};
+
+constexpr rtl::OUStringConstExpr a3ColorSmilies[] = {
+ BMP_ICON_SET_POSITIVE_GREEN_SMILIE, BMP_ICON_SET_NEUTRAL_YELLOW_SMILIE, BMP_ICON_SET_NEGATIVE_RED_SMILIE
+};
+
+constexpr rtl::OUStringConstExpr a3Stars[] = {
+ BMP_ICON_SET_STARS_EMPTY, BMP_ICON_SET_STARS_HALF, BMP_ICON_SET_STARS_FULL
+};
+
+constexpr rtl::OUStringConstExpr a3Triangles[] = {
+ BMP_ICON_SET_TRIANGLES_DOWN, BMP_ICON_SET_TRIANGLES_SAME, BMP_ICON_SET_TRIANGLES_UP
+};
+
+constexpr rtl::OUStringConstExpr a4Arrows[] = {
+ BMP_ICON_SET_COLORARROWS_DOWN, BMP_ICON_SET_COLORARROWS_SLIGHTLY_DOWN, BMP_ICON_SET_COLORARROWS_SLIGHTLY_UP, BMP_ICON_SET_COLORARROWS_UP
+};
+
+constexpr rtl::OUStringConstExpr a4ArrowsGray[] = {
+ BMP_ICON_SET_GRAYARROWS_DOWN, BMP_ICON_SET_GRAYARROWS_SLIGHTLY_DOWN, BMP_ICON_SET_GRAYARROWS_SLIGHTLY_UP, BMP_ICON_SET_GRAYARROWS_UP
+};
+
+constexpr rtl::OUStringConstExpr a5Arrows[] = {
+ BMP_ICON_SET_COLORARROWS_DOWN, BMP_ICON_SET_COLORARROWS_SLIGHTLY_DOWN,
+ BMP_ICON_SET_COLORARROWS_SAME, BMP_ICON_SET_COLORARROWS_SLIGHTLY_UP, BMP_ICON_SET_COLORARROWS_UP
+};
+
+constexpr rtl::OUStringConstExpr a5ArrowsGray[] = {
+ BMP_ICON_SET_GRAYARROWS_DOWN, BMP_ICON_SET_GRAYARROWS_SLIGHTLY_DOWN,
+ BMP_ICON_SET_GRAYARROWS_SAME, BMP_ICON_SET_GRAYARROWS_SLIGHTLY_UP, BMP_ICON_SET_GRAYARROWS_UP
+};
+
+constexpr rtl::OUStringConstExpr a4TrafficLights[] = {
+ BMP_ICON_SET_CIRCLES1_GRAY, BMP_ICON_SET_CIRCLES1_RED,
+ BMP_ICON_SET_CIRCLES1_YELLOW, BMP_ICON_SET_CIRCLES1_GREEN
+};
+
+constexpr rtl::OUStringConstExpr a5Quarters[] = {
+ BMP_ICON_SET_PIES_EMPTY, BMP_ICON_SET_PIES_ONE_QUARTER, BMP_ICON_SET_PIES_HALF,
+ BMP_ICON_SET_PIES_THREE_QUARTER, BMP_ICON_SET_PIES_FULL,
+};
+
+constexpr rtl::OUStringConstExpr a5Boxes[] = {
+ BMP_ICON_SET_SQUARES_EMPTY, BMP_ICON_SET_SQUARES_ONE_QUARTER,
+ BMP_ICON_SET_SQUARES_HALF, BMP_ICON_SET_SQUARES_THREE_QUARTER,
+ BMP_ICON_SET_SQUARES_FULL
+};
+
+constexpr rtl::OUStringConstExpr a3Symbols1[] = {
+ BMP_ICON_SET_SYMBOLS1_CROSS, BMP_ICON_SET_SYMBOLS1_EXCLAMATION_MARK, BMP_ICON_SET_SYMBOLS1_CHECK
+};
+
+constexpr rtl::OUStringConstExpr a3Signs[] = {
+ BMP_ICON_SET_SHAPES_DIAMOND, BMP_ICON_SET_SHAPES_TRIANGLE, BMP_ICON_SET_SHAPES_CIRCLE
+};
+
+constexpr rtl::OUStringConstExpr a4RedToBlack[] = {
+ BMP_ICON_SET_CIRCLES2_DARK_GRAY, BMP_ICON_SET_CIRCLES2_LIGHT_GRAY,
+ BMP_ICON_SET_CIRCLES2_LIGHT_RED, BMP_ICON_SET_CIRCLES2_DARK_RED
+};
+
+constexpr rtl::OUStringConstExpr a4Ratings[] = {
+ BMP_ICON_SET_BARS_ONE_QUARTER, BMP_ICON_SET_BARS_HALF,
+ BMP_ICON_SET_BARS_THREE_QUARTER, BMP_ICON_SET_BARS_FULL
+};
+
+constexpr rtl::OUStringConstExpr a5Ratings[] = {
+ BMP_ICON_SET_BARS_EMPTY, BMP_ICON_SET_BARS_ONE_QUARTER, BMP_ICON_SET_BARS_HALF,
+ BMP_ICON_SET_BARS_THREE_QUARTER, BMP_ICON_SET_BARS_FULL
+};
+
+struct ScIconSetBitmapMap {
+ ScIconSetType eType;
+ const rtl::OUStringConstExpr* pBitmaps;
+};
+
+const ScIconSetBitmapMap aBitmapMap[] = {
+ { IconSet_3Arrows, a3Arrows },
+ { IconSet_3ArrowsGray, a3ArrowsGray },
+ { IconSet_3Flags, a3Flags },
+ { IconSet_3Signs, a3Signs },
+ { IconSet_3Symbols, a3Symbols1 },
+ { IconSet_3Symbols2, a3Symbols1 },
+ { IconSet_3TrafficLights1, a3TrafficLights1 },
+ { IconSet_3TrafficLights2, a3TrafficLights2 },
+ { IconSet_3Smilies, a3Smilies },
+ { IconSet_3ColorSmilies, a3ColorSmilies },
+ { IconSet_3Triangles, a3Triangles },
+ { IconSet_3Stars, a3Stars },
+ { IconSet_4Arrows, a4Arrows },
+ { IconSet_4ArrowsGray, a4ArrowsGray },
+ { IconSet_4Rating, a4Ratings },
+ { IconSet_4RedToBlack, a4RedToBlack },
+ { IconSet_4TrafficLights, a4TrafficLights },
+ { IconSet_5Arrows, a5Arrows },
+ { IconSet_5ArrowsGray, a5ArrowsGray },
+ { IconSet_5Quarters, a5Quarters },
+ { IconSet_5Ratings, a5Ratings },
+ { IconSet_5Boxes, a5Boxes }
+};
+
+const ScIconSetMap* findIconSetType(ScIconSetType eType)
+{
+ const ScIconSetMap* pMap = ScIconSetFormat::g_IconSetMap;
+ for (; pMap->pName; ++pMap)
+ {
+ if (pMap->eType == eType)
+ return pMap;
+ }
+
+ return nullptr;
+}
+
+}
+
+const char* ScIconSetFormat::getIconSetName( ScIconSetType eType )
+{
+ const ScIconSetMap* pMap = findIconSetType(eType);
+ if (pMap)
+ return pMap->pName;
+
+ return "";
+}
+
+sal_Int32 ScIconSetFormat::getIconSetElements( ScIconSetType eType )
+{
+ const ScIconSetMap* pMap = findIconSetType(eType);
+ if (pMap)
+ return pMap->nElements;
+
+ return 0;
+}
+
+OUString ScIconSetFormat::getIconName(ScIconSetType const eType, sal_Int32 const nIndex)
+{
+ OUString sBitmap;
+
+ for(const ScIconSetBitmapMap & i : aBitmapMap)
+ {
+ if(i.eType == eType)
+ {
+ sBitmap = *(i.pBitmaps + nIndex);
+ break;
+ }
+ }
+
+ assert(!sBitmap.isEmpty());
+
+ return sBitmap;
+}
+
+BitmapEx& ScIconSetFormat::getBitmap(sc::IconSetBitmapMap & rIconSetBitmapMap,
+ ScIconSetType const eType, sal_Int32 const nIndex)
+{
+ OUString sBitmap(ScIconSetFormat::getIconName(eType, nIndex));
+
+ std::map<OUString, BitmapEx>::iterator itr = rIconSetBitmapMap.find(sBitmap);
+ if (itr != rIconSetBitmapMap.end())
+ return itr->second;
+
+ BitmapEx aBitmap(sBitmap);
+ std::pair<OUString, BitmapEx> aPair(sBitmap, aBitmap);
+ std::pair<std::map<OUString, BitmapEx>::iterator, bool> itrNew = rIconSetBitmapMap.insert(aPair);
+ assert(itrNew.second);
+
+ return itrNew.first->second;
+}
+
+void ScIconSetFormat::EnsureSize()
+{
+ ScIconSetType eType = mpFormatData->eIconSetType;
+ for (const ScIconSetMap & i : g_IconSetMap)
+ {
+ if (i.eType == eType)
+ {
+ // size_t nElements = aIconSetMap[i].nElements;
+ // TODO: implement
+ break;
+ }
+ }
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sc/source/core/data/column.cxx b/sc/source/core/data/column.cxx
new file mode 100644
index 000000000..06bbe0cf3
--- /dev/null
+++ b/sc/source/core/data/column.cxx
@@ -0,0 +1,3391 @@
+/* -*- 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 <column.hxx>
+#include <scitems.hxx>
+#include <formulacell.hxx>
+#include <document.hxx>
+#include <table.hxx>
+#include <docpool.hxx>
+#include <attarray.hxx>
+#include <patattr.hxx>
+#include <compiler.hxx>
+#include <brdcst.hxx>
+#include <markdata.hxx>
+#include <postit.hxx>
+#include <cellvalue.hxx>
+#include <tokenarray.hxx>
+#include <clipcontext.hxx>
+#include <types.hxx>
+#include <editutil.hxx>
+#include <mtvcellfunc.hxx>
+#include <columnspanset.hxx>
+#include <scopetools.hxx>
+#include <sharedformula.hxx>
+#include <refupdatecontext.hxx>
+#include <listenercontext.hxx>
+#include <formulagroup.hxx>
+#include <drwlayer.hxx>
+#include <mtvelements.hxx>
+#include <bcaslot.hxx>
+
+#include <svl/numformat.hxx>
+#include <svl/poolcach.hxx>
+#include <svl/zforlist.hxx>
+#include <svl/sharedstringpool.hxx>
+#include <editeng/fieldupdater.hxx>
+#include <formula/errorcodes.hxx>
+#include <o3tl/safeint.hxx>
+#include <osl/diagnose.h>
+
+#include <map>
+#include <cstdio>
+#include <memory>
+
+using ::editeng::SvxBorderLine;
+using namespace formula;
+
+namespace {
+
+bool IsAmbiguousScriptNonZero( SvtScriptType nScript )
+{
+ //TODO: move to a header file
+ return ( nScript != SvtScriptType::LATIN &&
+ nScript != SvtScriptType::ASIAN &&
+ nScript != SvtScriptType::COMPLEX &&
+ nScript != SvtScriptType::NONE );
+}
+
+}
+
+ScNeededSizeOptions::ScNeededSizeOptions() :
+ pPattern(nullptr), bFormula(false), bSkipMerged(true), bGetFont(true), bTotalSize(false)
+{
+}
+
+ScColumn::ScColumn(ScSheetLimits const & rSheetLimits) :
+ maCellTextAttrs(rSheetLimits.GetMaxRowCount()),
+ maCellNotes(rSheetLimits.GetMaxRowCount()),
+ maBroadcasters(rSheetLimits.GetMaxRowCount()),
+ maCells(sc::CellStoreEvent(this)),
+ maSparklines(rSheetLimits.GetMaxRowCount()),
+ mnBlkCountFormula(0),
+ nCol( 0 ),
+ nTab( 0 ),
+ mbFiltering( false ),
+ mbEmptyBroadcastersPending( false )
+{
+ maCells.resize(rSheetLimits.GetMaxRowCount());
+}
+
+ScColumn::~ScColumn() COVERITY_NOEXCEPT_FALSE
+{
+ FreeAll();
+}
+
+void ScColumn::Init(SCCOL nNewCol, SCTAB nNewTab, ScDocument& rDoc, bool bEmptyAttrArray)
+{
+ nCol = nNewCol;
+ nTab = nNewTab;
+ if ( bEmptyAttrArray )
+ InitAttrArray(new ScAttrArray( nCol, nTab, rDoc, nullptr ));
+ else
+ InitAttrArray(new ScAttrArray( nCol, nTab, rDoc, &rDoc.maTabs[nTab]->aDefaultColData.AttrArray()));
+}
+
+sc::MatrixEdge ScColumn::GetBlockMatrixEdges( SCROW nRow1, SCROW nRow2, sc::MatrixEdge nMask,
+ bool bNoMatrixAtAll ) const
+{
+ using namespace sc;
+
+ if (!GetDoc().ValidRow(nRow1) || !GetDoc().ValidRow(nRow2) || nRow1 > nRow2)
+ return MatrixEdge::Nothing;
+
+ ScAddress aOrigin(ScAddress::INITIALIZE_INVALID);
+
+ if (nRow1 == nRow2)
+ {
+ std::pair<sc::CellStoreType::const_iterator,size_t> aPos = maCells.position(nRow1);
+ if (aPos.first->type != sc::element_type_formula)
+ return MatrixEdge::Nothing;
+
+ const ScFormulaCell* pCell = sc::formula_block::at(*aPos.first->data, aPos.second);
+ if (pCell->GetMatrixFlag() == ScMatrixMode::NONE)
+ return MatrixEdge::Nothing;
+
+ return pCell->GetMatrixEdge(GetDoc(), aOrigin);
+ }
+
+ bool bOpen = false;
+ MatrixEdge nEdges = MatrixEdge::Nothing;
+
+ std::pair<sc::CellStoreType::const_iterator,size_t> aPos = maCells.position(nRow1);
+ sc::CellStoreType::const_iterator it = aPos.first;
+ size_t nOffset = aPos.second;
+ SCROW nRow = nRow1;
+ for (;it != maCells.end() && nRow <= nRow2; ++it, nOffset = 0)
+ {
+ if (it->type != sc::element_type_formula)
+ {
+ // Skip this block.
+ nRow += it->size - nOffset;
+ continue;
+ }
+
+ size_t nRowsToRead = nRow2 - nRow + 1;
+ size_t nEnd = std::min(it->size, nOffset+nRowsToRead); // last row + 1
+ sc::formula_block::const_iterator itCell = sc::formula_block::begin(*it->data);
+ std::advance(itCell, nOffset);
+ for (size_t i = nOffset; i < nEnd; ++itCell, ++i)
+ {
+ // Loop inside the formula block.
+ const ScFormulaCell* pCell = *itCell;
+ if (pCell->GetMatrixFlag() == ScMatrixMode::NONE)
+ continue;
+
+ nEdges = pCell->GetMatrixEdge(GetDoc(), aOrigin);
+ if (nEdges == MatrixEdge::Nothing)
+ continue;
+
+ // A 1x1 matrix array formula is OK even for no matrix at all.
+ if (bNoMatrixAtAll
+ && (nEdges != (MatrixEdge::Top | MatrixEdge::Left | MatrixEdge::Bottom | MatrixEdge::Right)))
+ return MatrixEdge::Inside; // per convention Inside
+
+ if (nEdges & MatrixEdge::Top)
+ bOpen = true; // top edge opens, keep on looking
+ else if (!bOpen)
+ return nEdges | MatrixEdge::Open; // there's something that wasn't opened
+ else if (nEdges & MatrixEdge::Inside)
+ return nEdges; // inside
+ if (((nMask & MatrixEdge::Right) && (nEdges & MatrixEdge::Left) && !(nEdges & MatrixEdge::Right)) ||
+ ((nMask & MatrixEdge::Left) && (nEdges & MatrixEdge::Right) && !(nEdges & MatrixEdge::Left)))
+ return nEdges; // only left/right edge
+
+ if (nEdges & MatrixEdge::Bottom)
+ bOpen = false; // bottom edge closes
+ }
+
+ nRow += nEnd - nOffset;
+ }
+ if (bOpen)
+ nEdges |= MatrixEdge::Open; // not closed, matrix continues
+
+ return nEdges;
+}
+
+bool ScColumn::HasSelectionMatrixFragment(const ScMarkData& rMark, const ScRangeList& rRangeList) const
+{
+ using namespace sc;
+
+ if (!rMark.IsMultiMarked())
+ return false;
+
+ ScAddress aOrigin(ScAddress::INITIALIZE_INVALID);
+ ScAddress aCurOrigin = aOrigin;
+
+ bool bOpen = false;
+ ScRangeList aRanges = rRangeList; // cached rMark.GetMarkedRanges(), for performance reasons (tdf#148147)
+ for (size_t i = 0, n = aRanges.size(); i < n; ++i)
+ {
+ const ScRange& r = aRanges[i];
+ if (nTab < r.aStart.Tab() || r.aEnd.Tab() < nTab)
+ continue;
+
+ if (nCol < r.aStart.Col() || r.aEnd.Col() < nCol)
+ continue;
+
+ SCROW nTop = r.aStart.Row(), nBottom = r.aEnd.Row();
+ SCROW nRow = nTop;
+ std::pair<sc::CellStoreType::const_iterator,size_t> aPos = maCells.position(nRow);
+ sc::CellStoreType::const_iterator it = aPos.first;
+ size_t nOffset = aPos.second;
+
+ for (;it != maCells.end() && nRow <= nBottom; ++it, nOffset = 0)
+ {
+ if (it->type != sc::element_type_formula)
+ {
+ // Skip this block.
+ nRow += it->size - nOffset;
+ continue;
+ }
+
+ // This is a formula cell block.
+ size_t nRowsToRead = nBottom - nRow + 1;
+ size_t nEnd = std::min(it->size, nRowsToRead);
+ sc::formula_block::const_iterator itCell = sc::formula_block::begin(*it->data);
+ std::advance(itCell, nOffset);
+ for (size_t j = nOffset; j < nEnd; ++itCell, ++j)
+ {
+ // Loop inside the formula block.
+ const ScFormulaCell* pCell = *itCell;
+ if (pCell->GetMatrixFlag() == ScMatrixMode::NONE)
+ // cell is not a part of a matrix.
+ continue;
+
+ MatrixEdge nEdges = pCell->GetMatrixEdge(GetDoc(), aOrigin);
+ if (nEdges == MatrixEdge::Nothing)
+ continue;
+
+ bool bFound = false;
+
+ if (nEdges & MatrixEdge::Top)
+ bOpen = true; // top edge opens, keep on looking
+ else if (!bOpen)
+ return true; // there's something that wasn't opened
+ else if (nEdges & MatrixEdge::Inside)
+ bFound = true; // inside, all selected?
+
+ if (((nEdges & MatrixEdge::Left) | MatrixEdge::Right) ^ ((nEdges & MatrixEdge::Right) | MatrixEdge::Left))
+ // either left or right, but not both.
+ bFound = true; // only left/right edge, all selected?
+
+ if (nEdges & MatrixEdge::Bottom)
+ bOpen = false; // bottom edge closes
+
+ if (bFound)
+ {
+ // Check if the matrix is inside the selection in its entirety.
+ //
+ // TODO: It's more efficient to skip the matrix range if
+ // it's within selection, to avoid checking it again and
+ // again.
+
+ if (aCurOrigin != aOrigin)
+ { // new matrix to check?
+ aCurOrigin = aOrigin;
+ const ScFormulaCell* pFCell;
+ if (pCell->GetMatrixFlag() == ScMatrixMode::Reference)
+ pFCell = GetDoc().GetFormulaCell(aOrigin);
+ else
+ pFCell = pCell;
+
+ SCCOL nC;
+ SCROW nR;
+ pFCell->GetMatColsRows(nC, nR);
+ ScRange aRange(aOrigin, ScAddress(aOrigin.Col()+nC-1, aOrigin.Row()+nR-1, aOrigin.Tab()));
+ if (rMark.IsAllMarked(aRange))
+ bFound = false;
+ }
+ else
+ bFound = false; // done already
+ }
+
+ if (bFound)
+ return true;
+ }
+
+ nRow += nEnd;
+ }
+ }
+
+ return bOpen;
+}
+
+bool ScColumn::HasAttribSelection( const ScMarkData& rMark, HasAttrFlags nMask ) const
+{
+ bool bFound = false;
+
+ SCROW nTop;
+ SCROW nBottom;
+
+ if (rMark.IsMultiMarked())
+ {
+ ScMultiSelIter aMultiIter( rMark.GetMultiSelData(), nCol );
+ while (aMultiIter.Next( nTop, nBottom ) && !bFound)
+ {
+ if (pAttrArray->HasAttrib( nTop, nBottom, nMask ))
+ bFound = true;
+ }
+ }
+
+ return bFound;
+}
+
+void ScColumn::MergeSelectionPattern( ScMergePatternState& rState, const ScMarkData& rMark, bool bDeep ) const
+{
+ SCROW nTop;
+ SCROW nBottom;
+
+ if ( rMark.IsMultiMarked() )
+ {
+ const ScMultiSel& rMultiSel = rMark.GetMultiSelData();
+ if ( rMultiSel.HasMarks( nCol ) )
+ {
+ ScMultiSelIter aMultiIter( rMultiSel, nCol );
+ while (aMultiIter.Next( nTop, nBottom ))
+ pAttrArray->MergePatternArea( nTop, nBottom, rState, bDeep );
+ }
+ }
+}
+
+const ScPatternAttr* ScColumnData::GetMostUsedPattern( SCROW nStartRow, SCROW nEndRow ) const
+{
+ ::std::map< const ScPatternAttr*, size_t > aAttrMap;
+ const ScPatternAttr* pMaxPattern = nullptr;
+ size_t nMaxCount = 0;
+
+ ScAttrIterator aAttrIter( pAttrArray.get(), nStartRow, nEndRow, GetDoc().GetDefPattern() );
+ const ScPatternAttr* pPattern;
+ SCROW nAttrRow1 = 0, nAttrRow2 = 0;
+
+ while( (pPattern = aAttrIter.Next( nAttrRow1, nAttrRow2 )) != nullptr )
+ {
+ size_t& rnCount = aAttrMap[ pPattern ];
+ rnCount += (nAttrRow2 - nAttrRow1 + 1);
+ if( rnCount > nMaxCount )
+ {
+ pMaxPattern = pPattern;
+ nMaxCount = rnCount;
+ }
+ }
+
+ return pMaxPattern;
+}
+
+sal_uInt32 ScColumnData::GetNumberFormat( SCROW nStartRow, SCROW nEndRow ) const
+{
+ SCROW nPatStartRow, nPatEndRow;
+ const ScPatternAttr* pPattern = pAttrArray->GetPatternRange(nPatStartRow, nPatEndRow, nStartRow);
+ sal_uInt32 nFormat = pPattern->GetNumberFormat(GetDoc().GetFormatTable());
+ while (nEndRow > nPatEndRow)
+ {
+ nStartRow = nPatEndRow + 1;
+ pPattern = pAttrArray->GetPatternRange(nPatStartRow, nPatEndRow, nStartRow);
+ sal_uInt32 nTmpFormat = pPattern->GetNumberFormat(GetDoc().GetFormatTable());
+ if (nFormat != nTmpFormat)
+ return 0;
+ }
+ return nFormat;
+}
+
+SCROW ScColumn::ApplySelectionCache( SfxItemPoolCache* pCache, const ScMarkData& rMark, ScEditDataArray* pDataArray,
+ bool* const pIsChanged )
+{
+ return ScColumnData::ApplySelectionCache( pCache, rMark, pDataArray, pIsChanged, nCol );
+}
+
+SCROW ScColumnData::ApplySelectionCache( SfxItemPoolCache* pCache, const ScMarkData& rMark, ScEditDataArray* pDataArray,
+ bool* const pIsChanged, SCCOL nCol )
+{
+ SCROW nTop = 0;
+ SCROW nBottom = 0;
+ bool bFound = false;
+
+ if ( rMark.IsMultiMarked() )
+ {
+ ScMultiSelIter aMultiIter( rMark.GetMultiSelData(), nCol );
+ while (aMultiIter.Next( nTop, nBottom ))
+ {
+ pAttrArray->ApplyCacheArea( nTop, nBottom, pCache, pDataArray, pIsChanged );
+ bFound = true;
+ }
+ }
+
+ if (!bFound)
+ return -1;
+ else if (nTop==0 && nBottom==GetDoc().MaxRow())
+ return 0;
+ else
+ return nBottom;
+}
+
+void ScColumnData::ChangeSelectionIndent( bool bIncrement, const ScMarkData& rMark, SCCOL nCol )
+{
+ assert(rMark.IsMultiMarked());
+ if ( pAttrArray && rMark.IsMultiMarked() )
+ {
+ ScMultiSelIter aMultiIter( rMark.GetMultiSelData(), nCol );
+ SCROW nTop;
+ SCROW nBottom;
+ while (aMultiIter.Next( nTop, nBottom ))
+ pAttrArray->ChangeIndent(nTop, nBottom, bIncrement);
+ }
+}
+
+void ScColumn::ChangeSelectionIndent( bool bIncrement, const ScMarkData& rMark )
+{
+ return ScColumnData::ChangeSelectionIndent( bIncrement, rMark, nCol );
+}
+
+void ScColumnData::ClearSelectionItems( const sal_uInt16* pWhich,const ScMarkData& rMark, SCCOL nCol )
+{
+ if (!pAttrArray)
+ return;
+
+ if (rMark.IsMultiMarked() )
+ {
+ ScMultiSelIter aMultiIter( rMark.GetMultiSelData(), nCol );
+ SCROW nTop;
+ SCROW nBottom;
+ while (aMultiIter.Next( nTop, nBottom ))
+ pAttrArray->ClearItems(nTop, nBottom, pWhich);
+ }
+ else if (rMark.IsMarked())
+ {
+ const ScRange& aRange = rMark.GetMarkArea();
+ if (aRange.aStart.Col() <= nCol && nCol <= aRange.aEnd.Col())
+ {
+ pAttrArray->ClearItems(aRange.aStart.Row(), aRange.aEnd.Row(), pWhich);
+ }
+ }
+}
+
+void ScColumn::ClearSelectionItems( const sal_uInt16* pWhich,const ScMarkData& rMark )
+{
+ ScColumnData::ClearSelectionItems( pWhich, rMark, nCol );
+}
+
+void ScColumn::DeleteSelection( InsertDeleteFlags nDelFlag, const ScMarkData& rMark, bool bBroadcast )
+{
+ SCROW nTop;
+ SCROW nBottom;
+
+ if ( rMark.IsMultiMarked() )
+ {
+ ScMultiSelIter aMultiIter( rMark.GetMultiSelData(), nCol );
+ while (aMultiIter.Next( nTop, nBottom ))
+ DeleteArea(nTop, nBottom, nDelFlag, bBroadcast);
+ }
+}
+
+void ScColumn::ApplyPattern( SCROW nRow, const ScPatternAttr& rPatAttr )
+{
+ const SfxItemSet* pSet = &rPatAttr.GetItemSet();
+ SfxItemPoolCache aCache( GetDoc().GetPool(), pSet );
+
+ const ScPatternAttr* pPattern = pAttrArray->GetPattern( nRow );
+
+ // true = keep old content
+
+ const ScPatternAttr* pNewPattern = static_cast<const ScPatternAttr*>( &aCache.ApplyTo( *pPattern ) );
+
+ if (pNewPattern != pPattern)
+ pAttrArray->SetPattern( nRow, pNewPattern );
+}
+
+void ScColumnData::ApplyPatternArea( SCROW nStartRow, SCROW nEndRow, const ScPatternAttr& rPatAttr,
+ ScEditDataArray* pDataArray, bool* const pIsChanged )
+{
+ const SfxItemSet* pSet = &rPatAttr.GetItemSet();
+ SfxItemPoolCache aCache( GetDoc().GetPool(), pSet );
+ pAttrArray->ApplyCacheArea( nStartRow, nEndRow, &aCache, pDataArray, pIsChanged );
+}
+
+void ScColumn::ApplyPatternIfNumberformatIncompatible( const ScRange& rRange,
+ const ScPatternAttr& rPattern, SvNumFormatType nNewType )
+{
+ const SfxItemSet* pSet = &rPattern.GetItemSet();
+ SfxItemPoolCache aCache( GetDoc().GetPool(), pSet );
+ SvNumberFormatter* pFormatter = GetDoc().GetFormatTable();
+ SCROW nEndRow = rRange.aEnd.Row();
+ for ( SCROW nRow = rRange.aStart.Row(); nRow <= nEndRow; nRow++ )
+ {
+ SCROW nRow1, nRow2;
+ const ScPatternAttr* pPattern = pAttrArray->GetPatternRange(
+ nRow1, nRow2, nRow );
+ sal_uInt32 nFormat = pPattern->GetNumberFormat( pFormatter );
+ SvNumFormatType nOldType = pFormatter->GetType( nFormat );
+ if ( nOldType == nNewType || SvNumberFormatter::IsCompatible( nOldType, nNewType ) )
+ nRow = nRow2;
+ else
+ {
+ SCROW nNewRow1 = std::max( nRow1, nRow );
+ SCROW nNewRow2 = std::min( nRow2, nEndRow );
+ pAttrArray->ApplyCacheArea( nNewRow1, nNewRow2, &aCache );
+ nRow = nNewRow2;
+ }
+ }
+}
+
+void ScColumn::ApplyStyle( SCROW nRow, const ScStyleSheet* rStyle )
+{
+ const ScPatternAttr* pPattern = pAttrArray->GetPattern(nRow);
+ std::unique_ptr<ScPatternAttr> pNewPattern(new ScPatternAttr(*pPattern));
+ pNewPattern->SetStyleSheet(const_cast<ScStyleSheet*>(rStyle));
+ pAttrArray->SetPattern(nRow, std::move(pNewPattern), true);
+}
+
+void ScColumn::ApplySelectionStyle(const ScStyleSheet& rStyle, const ScMarkData& rMark)
+{
+ SCROW nTop;
+ SCROW nBottom;
+
+ if ( rMark.IsMultiMarked() )
+ {
+ ScMultiSelIter aMultiIter( rMark.GetMultiSelData(), nCol );
+ while (aMultiIter.Next( nTop, nBottom ))
+ pAttrArray->ApplyStyleArea(nTop, nBottom, rStyle);
+ }
+}
+
+void ScColumn::ApplySelectionLineStyle( const ScMarkData& rMark,
+ const SvxBorderLine* pLine, bool bColorOnly )
+{
+ if ( bColorOnly && !pLine )
+ return;
+
+ SCROW nTop;
+ SCROW nBottom;
+
+ if (rMark.IsMultiMarked())
+ {
+ ScMultiSelIter aMultiIter( rMark.GetMultiSelData(), nCol );
+ while (aMultiIter.Next( nTop, nBottom ))
+ pAttrArray->ApplyLineStyleArea(nTop, nBottom, pLine, bColorOnly );
+ }
+}
+
+const ScStyleSheet* ScColumn::GetSelectionStyle( const ScMarkData& rMark, bool& rFound ) const
+{
+ rFound = false;
+ if (!rMark.IsMultiMarked())
+ {
+ OSL_FAIL("No selection in ScColumn::GetSelectionStyle");
+ return nullptr;
+ }
+
+ bool bEqual = true;
+
+ const ScStyleSheet* pStyle = nullptr;
+ const ScStyleSheet* pNewStyle;
+
+ ScDocument& rDocument = GetDoc();
+ ScMultiSelIter aMultiIter( rMark.GetMultiSelData(), nCol );
+ SCROW nTop;
+ SCROW nBottom;
+ while (bEqual && aMultiIter.Next( nTop, nBottom ))
+ {
+ ScAttrIterator aAttrIter( pAttrArray.get(), nTop, nBottom, rDocument.GetDefPattern() );
+ SCROW nRow;
+ SCROW nDummy;
+ while (bEqual)
+ {
+ const ScPatternAttr* pPattern = aAttrIter.Next( nRow, nDummy );
+ if (!pPattern)
+ break;
+ pNewStyle = pPattern->GetStyleSheet();
+ rFound = true;
+ if ( !pNewStyle || ( pStyle && pNewStyle != pStyle ) )
+ bEqual = false; // difference
+ pStyle = pNewStyle;
+ }
+ }
+
+ return bEqual ? pStyle : nullptr;
+}
+
+const ScStyleSheet* ScColumn::GetAreaStyle( bool& rFound, SCROW nRow1, SCROW nRow2 ) const
+{
+ rFound = false;
+
+ bool bEqual = true;
+
+ const ScStyleSheet* pStyle = nullptr;
+ const ScStyleSheet* pNewStyle;
+
+ ScAttrIterator aAttrIter( pAttrArray.get(), nRow1, nRow2, GetDoc().GetDefPattern() );
+ SCROW nRow;
+ SCROW nDummy;
+ while (bEqual)
+ {
+ const ScPatternAttr* pPattern = aAttrIter.Next( nRow, nDummy );
+ if (!pPattern)
+ break;
+ pNewStyle = pPattern->GetStyleSheet();
+ rFound = true;
+ if ( !pNewStyle || ( pStyle && pNewStyle != pStyle ) )
+ bEqual = false; // difference
+ pStyle = pNewStyle;
+ }
+
+ return bEqual ? pStyle : nullptr;
+}
+
+void ScColumn::ApplyAttr( SCROW nRow, const SfxPoolItem& rAttr )
+{
+ // in order to only create a new SetItem, we don't need SfxItemPoolCache.
+ //TODO: Warning: SfxItemPoolCache seems to create too many Refs for the new SetItem ??
+
+ ScDocumentPool* pDocPool = GetDoc().GetPool();
+
+ const ScPatternAttr* pOldPattern = pAttrArray->GetPattern( nRow );
+ ScPatternAttr aTemp(*pOldPattern);
+ aTemp.GetItemSet().Put(rAttr);
+ const ScPatternAttr* pNewPattern = &pDocPool->Put( aTemp );
+
+ if ( pNewPattern != pOldPattern )
+ pAttrArray->SetPattern( nRow, pNewPattern );
+ else
+ pDocPool->Remove( *pNewPattern ); // free up resources
+}
+
+ScRefCellValue ScColumn::GetCellValue( SCROW nRow ) const
+{
+ std::pair<sc::CellStoreType::const_iterator,size_t> aPos = maCells.position(nRow);
+ if (aPos.first == maCells.end())
+ return ScRefCellValue();
+
+ return GetCellValue(aPos.first, aPos.second);
+}
+
+ScRefCellValue ScColumn::GetCellValue( sc::ColumnBlockPosition& rBlockPos, SCROW nRow )
+{
+ std::pair<sc::CellStoreType::iterator,size_t> aPos = maCells.position(rBlockPos.miCellPos, nRow);
+ if (aPos.first == maCells.end())
+ return ScRefCellValue();
+
+ rBlockPos.miCellPos = aPos.first; // Store this for next call.
+ return GetCellValue(aPos.first, aPos.second);
+}
+
+ScRefCellValue ScColumn::GetCellValue( sc::ColumnBlockConstPosition& rBlockPos, SCROW nRow ) const
+{
+ std::pair<sc::CellStoreType::const_iterator,size_t> aPos = maCells.position(rBlockPos.miCellPos, nRow);
+ if (aPos.first == maCells.end())
+ return ScRefCellValue();
+
+ rBlockPos.miCellPos = aPos.first; // Store this for next call.
+ return GetCellValue(aPos.first, aPos.second);
+}
+
+ScRefCellValue ScColumn::GetCellValue( const sc::CellStoreType::const_iterator& itPos, size_t nOffset )
+{
+ ScRefCellValue aVal; // Defaults to empty cell.
+ switch (itPos->type)
+ {
+ case sc::element_type_numeric:
+ // Numeric cell
+ aVal.mfValue = sc::numeric_block::at(*itPos->data, nOffset);
+ aVal.meType = CELLTYPE_VALUE;
+ break;
+ case sc::element_type_string:
+ // String cell
+ aVal.mpString = &sc::string_block::at(*itPos->data, nOffset);
+ aVal.meType = CELLTYPE_STRING;
+ break;
+ case sc::element_type_edittext:
+ // Edit cell
+ aVal.mpEditText = sc::edittext_block::at(*itPos->data, nOffset);
+ aVal.meType = CELLTYPE_EDIT;
+ break;
+ case sc::element_type_formula:
+ // Formula cell
+ aVal.mpFormula = sc::formula_block::at(*itPos->data, nOffset);
+ aVal.meType = CELLTYPE_FORMULA;
+ break;
+ default:
+ ;
+ }
+
+ return aVal;
+}
+
+const sc::CellTextAttr* ScColumn::GetCellTextAttr( SCROW nRow ) const
+{
+ sc::ColumnBlockConstPosition aBlockPos;
+ aBlockPos.miCellTextAttrPos = maCellTextAttrs.begin();
+ return GetCellTextAttr(aBlockPos, nRow);
+}
+
+const sc::CellTextAttr* ScColumn::GetCellTextAttr( sc::ColumnBlockConstPosition& rBlockPos, SCROW nRow ) const
+{
+ sc::CellTextAttrStoreType::const_position_type aPos = maCellTextAttrs.position(rBlockPos.miCellTextAttrPos, nRow);
+ if (aPos.first == maCellTextAttrs.end())
+ return nullptr;
+
+ rBlockPos.miCellTextAttrPos = aPos.first;
+
+ if (aPos.first->type != sc::element_type_celltextattr)
+ return nullptr;
+
+ return &sc::celltextattr_block::at(*aPos.first->data, aPos.second);
+}
+
+bool ScColumn::TestInsertCol( SCROW nStartRow, SCROW nEndRow) const
+{
+ if (IsEmptyData() && IsEmptyAttr())
+ return true;
+
+ // Return false if we have any non-empty cells between nStartRow and nEndRow inclusive.
+ std::pair<sc::CellStoreType::const_iterator,size_t> aPos = maCells.position(nStartRow);
+ sc::CellStoreType::const_iterator it = aPos.first;
+ if (it->type != sc::element_type_empty)
+ return false;
+
+ // Get the length of the remaining empty segment.
+ size_t nLen = it->size - aPos.second;
+ SCROW nNextNonEmptyRow = nStartRow + nLen;
+ if (nNextNonEmptyRow <= nEndRow)
+ return false;
+
+ // AttrArray only looks for merged cells
+
+ return pAttrArray == nullptr || pAttrArray->TestInsertCol(nStartRow, nEndRow);
+}
+
+bool ScColumn::TestInsertRow( SCROW nStartRow, SCSIZE nSize ) const
+{
+ // AttrArray only looks for merged cells
+ {
+ std::pair<sc::CellStoreType::const_iterator,size_t> aPos = maCells.position(nStartRow);
+ sc::CellStoreType::const_iterator it = aPos.first;
+ if (it->type == sc::element_type_empty && maCells.block_size() == 1)
+ // The entire cell array is empty.
+ return pAttrArray->TestInsertRow(nSize);
+ }
+
+ // See if there would be any non-empty cell that gets pushed out.
+
+ // Find the position of the last non-empty cell below nStartRow.
+ size_t nLastNonEmptyRow = GetDoc().MaxRow();
+ sc::CellStoreType::const_reverse_iterator it = maCells.rbegin();
+ if (it->type == sc::element_type_empty)
+ nLastNonEmptyRow -= it->size;
+
+ if (nLastNonEmptyRow < o3tl::make_unsigned(nStartRow))
+ // No cells would get pushed out.
+ return pAttrArray->TestInsertRow(nSize);
+
+ if (nLastNonEmptyRow + nSize > o3tl::make_unsigned(GetDoc().MaxRow()))
+ // At least one cell would get pushed out. Not good.
+ return false;
+
+ return pAttrArray->TestInsertRow(nSize);
+}
+
+void ScColumn::InsertRow( SCROW nStartRow, SCSIZE nSize )
+{
+ pAttrArray->InsertRow( nStartRow, nSize );
+
+ maCellNotes.insert_empty(nStartRow, nSize);
+ maCellNotes.resize(GetDoc().GetMaxRowCount());
+
+ maSparklines.insert_empty(nStartRow, nSize);
+ maSparklines.resize(GetDoc().GetSheetLimits().GetMaxRowCount());
+
+ maBroadcasters.insert_empty(nStartRow, nSize);
+ maBroadcasters.resize(GetDoc().GetMaxRowCount());
+
+ maCellTextAttrs.insert_empty(nStartRow, nSize);
+ maCellTextAttrs.resize(GetDoc().GetMaxRowCount());
+
+ maCells.insert_empty(nStartRow, nSize);
+ maCells.resize(GetDoc().GetMaxRowCount());
+
+ CellStorageModified();
+
+ // We *probably* don't need to broadcast here since the parent call seems
+ // to take care of it.
+}
+
+namespace {
+
+class CopyToClipHandler
+{
+ const ScDocument& mrSrcDoc;
+ const ScColumn& mrSrcCol;
+ ScColumn& mrDestCol;
+ sc::ColumnBlockPosition maDestPos;
+ sc::ColumnBlockPosition* mpDestPos;
+
+ void setDefaultAttrsToDest(size_t nRow, size_t nSize)
+ {
+ std::vector<sc::CellTextAttr> aAttrs(nSize); // default values
+ maDestPos.miCellTextAttrPos = mrDestCol.GetCellAttrStore().set(
+ maDestPos.miCellTextAttrPos, nRow, aAttrs.begin(), aAttrs.end());
+ }
+
+public:
+ CopyToClipHandler(const ScDocument& rSrcDoc, const ScColumn& rSrcCol, ScColumn& rDestCol, sc::ColumnBlockPosition* pDestPos) :
+ mrSrcDoc(rSrcDoc), mrSrcCol(rSrcCol), mrDestCol(rDestCol), mpDestPos(pDestPos)
+ {
+ if (mpDestPos)
+ maDestPos = *mpDestPos;
+ else
+ mrDestCol.InitBlockPosition(maDestPos);
+ }
+
+ ~CopyToClipHandler()
+ {
+ if (mpDestPos)
+ *mpDestPos = maDestPos;
+ }
+
+ void operator() (const sc::CellStoreType::value_type& aNode, size_t nOffset, size_t nDataSize)
+ {
+ size_t nTopRow = aNode.position + nOffset;
+
+ bool bSet = true;
+
+ switch (aNode.type)
+ {
+ case sc::element_type_numeric:
+ {
+ sc::numeric_block::const_iterator it = sc::numeric_block::begin(*aNode.data);
+ std::advance(it, nOffset);
+ sc::numeric_block::const_iterator itEnd = it;
+ std::advance(itEnd, nDataSize);
+ maDestPos.miCellPos = mrDestCol.GetCellStore().set(maDestPos.miCellPos, nTopRow, it, itEnd);
+ }
+ break;
+ case sc::element_type_string:
+ {
+ sc::string_block::const_iterator it = sc::string_block::begin(*aNode.data);
+ std::advance(it, nOffset);
+ sc::string_block::const_iterator itEnd = it;
+ std::advance(itEnd, nDataSize);
+ maDestPos.miCellPos = mrDestCol.GetCellStore().set(maDestPos.miCellPos, nTopRow, it, itEnd);
+
+ }
+ break;
+ case sc::element_type_edittext:
+ {
+ sc::edittext_block::const_iterator it = sc::edittext_block::begin(*aNode.data);
+ std::advance(it, nOffset);
+ sc::edittext_block::const_iterator itEnd = it;
+ std::advance(itEnd, nDataSize);
+
+ std::vector<EditTextObject*> aCloned;
+ aCloned.reserve(nDataSize);
+ for (; it != itEnd; ++it)
+ aCloned.push_back(ScEditUtil::Clone(**it, mrDestCol.GetDoc()).release());
+
+ maDestPos.miCellPos = mrDestCol.GetCellStore().set(
+ maDestPos.miCellPos, nTopRow, aCloned.begin(), aCloned.end());
+ }
+ break;
+ case sc::element_type_formula:
+ {
+ sc::formula_block::const_iterator it = sc::formula_block::begin(*aNode.data);
+ std::advance(it, nOffset);
+ sc::formula_block::const_iterator itEnd = it;
+ std::advance(itEnd, nDataSize);
+
+ std::vector<ScFormulaCell*> aCloned;
+ aCloned.reserve(nDataSize);
+ ScAddress aDestPos(mrDestCol.GetCol(), nTopRow, mrDestCol.GetTab());
+ for (; it != itEnd; ++it, aDestPos.IncRow())
+ {
+ const ScFormulaCell& rOld = **it;
+ if (rOld.GetDirty() && mrSrcCol.GetDoc().GetAutoCalc())
+ const_cast<ScFormulaCell&>(rOld).Interpret();
+
+ aCloned.push_back(new ScFormulaCell(rOld, mrDestCol.GetDoc(), aDestPos));
+ }
+
+ // Group the cloned formula cells.
+ if (!aCloned.empty())
+ sc::SharedFormulaUtil::groupFormulaCells(aCloned.begin(), aCloned.end());
+
+ sc::CellStoreType& rDestCells = mrDestCol.GetCellStore();
+ maDestPos.miCellPos = rDestCells.set(
+ maDestPos.miCellPos, nTopRow, aCloned.begin(), aCloned.end());
+
+ // Merge adjacent formula cell groups (if applicable).
+ sc::CellStoreType::position_type aPos =
+ rDestCells.position(maDestPos.miCellPos, nTopRow);
+ maDestPos.miCellPos = aPos.first;
+ sc::SharedFormulaUtil::joinFormulaCellAbove(aPos);
+ size_t nLastRow = nTopRow + nDataSize;
+ if (nLastRow < o3tl::make_unsigned(mrSrcDoc.MaxRow()))
+ {
+ aPos = rDestCells.position(maDestPos.miCellPos, nLastRow+1);
+ sc::SharedFormulaUtil::joinFormulaCellAbove(aPos);
+ }
+ }
+ break;
+ default:
+ bSet = false;
+ }
+
+ if (bSet)
+ setDefaultAttrsToDest(nTopRow, nDataSize);
+
+ mrSrcCol.DuplicateNotes(nTopRow, nDataSize, mrDestCol, maDestPos, false);
+ mrSrcCol.DuplicateSparklines(nTopRow, nDataSize, mrDestCol, maDestPos);
+ }
+};
+
+class CopyTextAttrToClipHandler
+{
+ sc::CellTextAttrStoreType& mrDestAttrs;
+ sc::CellTextAttrStoreType::iterator miPos;
+
+public:
+ explicit CopyTextAttrToClipHandler( sc::CellTextAttrStoreType& rAttrs ) :
+ mrDestAttrs(rAttrs), miPos(mrDestAttrs.begin()) {}
+
+ void operator() ( const sc::CellTextAttrStoreType::value_type& aNode, size_t nOffset, size_t nDataSize )
+ {
+ if (aNode.type != sc::element_type_celltextattr)
+ return;
+
+ sc::celltextattr_block::const_iterator it = sc::celltextattr_block::begin(*aNode.data);
+ std::advance(it, nOffset);
+ sc::celltextattr_block::const_iterator itEnd = it;
+ std::advance(itEnd, nDataSize);
+
+ size_t nPos = aNode.position + nOffset;
+ miPos = mrDestAttrs.set(miPos, nPos, it, itEnd);
+ }
+};
+
+
+}
+
+void ScColumn::CopyToClip(
+ sc::CopyToClipContext& rCxt, SCROW nRow1, SCROW nRow2, ScColumn& rColumn ) const
+{
+ pAttrArray->CopyArea( nRow1, nRow2, 0, *rColumn.pAttrArray,
+ rCxt.isKeepScenarioFlags() ? (ScMF::All & ~ScMF::Scenario) : ScMF::All );
+
+ {
+ CopyToClipHandler aFunc(GetDoc(), *this, rColumn, rCxt.getBlockPosition(rColumn.nTab, rColumn.nCol));
+ sc::ParseBlock(maCells.begin(), maCells, aFunc, nRow1, nRow2);
+ }
+
+ {
+ CopyTextAttrToClipHandler aFunc(rColumn.maCellTextAttrs);
+ sc::ParseBlock(maCellTextAttrs.begin(), maCellTextAttrs, aFunc, nRow1, nRow2);
+ }
+
+ rColumn.CellStorageModified();
+}
+
+void ScColumn::CopyStaticToDocument(
+ SCROW nRow1, SCROW nRow2, const SvNumberFormatterMergeMap& rMap, ScColumn& rDestCol )
+{
+ if (nRow1 > nRow2)
+ return;
+
+ sc::ColumnBlockPosition aDestPos;
+ CopyCellTextAttrsToDocument(nRow1, nRow2, rDestCol);
+ CopyCellNotesToDocument(nRow1, nRow2, rDestCol);
+
+ // First, clear the destination column for the specified row range.
+ rDestCol.maCells.set_empty(nRow1, nRow2);
+
+ aDestPos.miCellPos = rDestCol.maCells.begin();
+
+ ScDocument& rDocument = GetDoc();
+ std::pair<sc::CellStoreType::const_iterator,size_t> aPos = maCells.position(nRow1);
+ sc::CellStoreType::const_iterator it = aPos.first;
+ size_t nOffset = aPos.second;
+ size_t nDataSize = 0;
+ size_t nCurRow = nRow1;
+
+ for (; it != maCells.end() && nCurRow <= o3tl::make_unsigned(nRow2); ++it, nOffset = 0, nCurRow += nDataSize)
+ {
+ bool bLastBlock = false;
+ nDataSize = it->size - nOffset;
+ if (nCurRow + nDataSize - 1 > o3tl::make_unsigned(nRow2))
+ {
+ // Truncate the block to copy to clipboard.
+ nDataSize = nRow2 - nCurRow + 1;
+ bLastBlock = true;
+ }
+
+ switch (it->type)
+ {
+ case sc::element_type_numeric:
+ {
+ sc::numeric_block::const_iterator itData = sc::numeric_block::begin(*it->data);
+ std::advance(itData, nOffset);
+ sc::numeric_block::const_iterator itDataEnd = itData;
+ std::advance(itDataEnd, nDataSize);
+ aDestPos.miCellPos = rDestCol.maCells.set(aDestPos.miCellPos, nCurRow, itData, itDataEnd);
+ }
+ break;
+ case sc::element_type_string:
+ {
+ sc::string_block::const_iterator itData = sc::string_block::begin(*it->data);
+ std::advance(itData, nOffset);
+ sc::string_block::const_iterator itDataEnd = itData;
+ std::advance(itDataEnd, nDataSize);
+ aDestPos.miCellPos = rDestCol.maCells.set(aDestPos.miCellPos, nCurRow, itData, itDataEnd);
+ }
+ break;
+ case sc::element_type_edittext:
+ {
+ sc::edittext_block::const_iterator itData = sc::edittext_block::begin(*it->data);
+ std::advance(itData, nOffset);
+ sc::edittext_block::const_iterator itDataEnd = itData;
+ std::advance(itDataEnd, nDataSize);
+
+ // Convert to simple strings.
+ std::vector<svl::SharedString> aConverted;
+ aConverted.reserve(nDataSize);
+ for (; itData != itDataEnd; ++itData)
+ {
+ const EditTextObject& rObj = **itData;
+ svl::SharedString aSS = rDocument.GetSharedStringPool().intern(ScEditUtil::GetString(rObj, &rDocument));
+ aConverted.push_back(aSS);
+ }
+ aDestPos.miCellPos = rDestCol.maCells.set(aDestPos.miCellPos, nCurRow, aConverted.begin(), aConverted.end());
+ }
+ break;
+ case sc::element_type_formula:
+ {
+ sc::formula_block::const_iterator itData = sc::formula_block::begin(*it->data);
+ std::advance(itData, nOffset);
+ sc::formula_block::const_iterator itDataEnd = itData;
+ std::advance(itDataEnd, nDataSize);
+
+ // Interpret and convert to raw values.
+ for (SCROW i = 0; itData != itDataEnd; ++itData, ++i)
+ {
+ SCROW nRow = nCurRow + i;
+
+ ScFormulaCell& rFC = **itData;
+ if (rFC.GetDirty() && rDocument.GetAutoCalc())
+ rFC.Interpret();
+
+ if (rFC.GetErrCode() != FormulaError::NONE)
+ // Skip cells with error.
+ continue;
+
+ if (rFC.IsValue())
+ aDestPos.miCellPos = rDestCol.maCells.set(aDestPos.miCellPos, nRow, rFC.GetValue());
+ else
+ {
+ svl::SharedString aSS = rFC.GetString();
+ if (aSS.isValid())
+ aDestPos.miCellPos = rDestCol.maCells.set(aDestPos.miCellPos, nRow, aSS);
+ }
+ }
+ }
+ break;
+ default:
+ ;
+ }
+
+ if (bLastBlock)
+ break;
+ }
+
+ // Don't forget to copy the number formats over. Charts may reference them.
+ for (SCROW nRow = nRow1; nRow <= nRow2; ++nRow)
+ {
+ sal_uInt32 nNumFmt = GetNumberFormat(rDocument.GetNonThreadedContext(), nRow);
+ SvNumberFormatterMergeMap::const_iterator itNum = rMap.find(nNumFmt);
+ if (itNum != rMap.end())
+ nNumFmt = itNum->second;
+
+ rDestCol.SetNumberFormat(nRow, nNumFmt);
+ }
+
+ rDestCol.CellStorageModified();
+}
+
+void ScColumn::CopyCellToDocument( SCROW nSrcRow, SCROW nDestRow, ScColumn& rDestCol )
+{
+ ScDocument& rDocument = GetDoc();
+ std::pair<sc::CellStoreType::const_iterator,size_t> aPos = maCells.position(nSrcRow);
+ sc::CellStoreType::const_iterator it = aPos.first;
+ bool bSet = true;
+ switch (it->type)
+ {
+ case sc::element_type_numeric:
+ rDestCol.maCells.set(nDestRow, sc::numeric_block::at(*it->data, aPos.second));
+ break;
+ case sc::element_type_string:
+ rDestCol.maCells.set(nDestRow, sc::string_block::at(*it->data, aPos.second));
+ break;
+ case sc::element_type_edittext:
+ {
+ EditTextObject* p = sc::edittext_block::at(*it->data, aPos.second);
+ if (&rDocument == &rDestCol.GetDoc())
+ rDestCol.maCells.set(nDestRow, p->Clone().release());
+ else
+ rDestCol.maCells.set(nDestRow, ScEditUtil::Clone(*p, rDestCol.GetDoc()).release());
+ }
+ break;
+ case sc::element_type_formula:
+ {
+ ScFormulaCell* p = sc::formula_block::at(*it->data, aPos.second);
+ if (p->GetDirty() && rDocument.GetAutoCalc())
+ p->Interpret();
+
+ ScAddress aDestPos = p->aPos;
+ aDestPos.SetRow(nDestRow);
+ ScFormulaCell* pNew = new ScFormulaCell(*p, rDestCol.GetDoc(), aDestPos);
+ rDestCol.SetFormulaCell(nDestRow, pNew);
+ }
+ break;
+ case sc::element_type_empty:
+ default:
+ // empty
+ rDestCol.maCells.set_empty(nDestRow, nDestRow);
+ bSet = false;
+ }
+
+ if (bSet)
+ {
+ rDestCol.maCellTextAttrs.set(nDestRow, maCellTextAttrs.get<sc::CellTextAttr>(nSrcRow));
+ ScPostIt* pNote = maCellNotes.get<ScPostIt*>(nSrcRow);
+ if (pNote)
+ {
+ pNote = pNote->Clone(ScAddress(nCol, nSrcRow, nTab),
+ rDestCol.GetDoc(),
+ ScAddress(rDestCol.nCol, nDestRow, rDestCol.nTab),
+ false).release();
+ rDestCol.maCellNotes.set(nDestRow, pNote);
+ pNote->UpdateCaptionPos(ScAddress(rDestCol.nCol, nDestRow, rDestCol.nTab));
+ }
+ else
+ rDestCol.maCellNotes.set_empty(nDestRow, nDestRow);
+ }
+ else
+ {
+ rDestCol.maCellTextAttrs.set_empty(nDestRow, nDestRow);
+ rDestCol.maCellNotes.set_empty(nDestRow, nDestRow);
+ }
+
+ rDestCol.CellStorageModified();
+}
+
+namespace {
+
+bool canCopyValue(const ScDocument& rDoc, const ScAddress& rPos, InsertDeleteFlags nFlags)
+{
+ sal_uInt32 nNumIndex = rDoc.GetAttr(rPos, ATTR_VALUE_FORMAT)->GetValue();
+ SvNumFormatType nType = rDoc.GetFormatTable()->GetType(nNumIndex);
+ if ((nType == SvNumFormatType::DATE) || (nType == SvNumFormatType::TIME) || (nType == SvNumFormatType::DATETIME))
+ return ((nFlags & InsertDeleteFlags::DATETIME) != InsertDeleteFlags::NONE);
+
+ return (nFlags & InsertDeleteFlags::VALUE) != InsertDeleteFlags::NONE;
+}
+
+class CopyAsLinkHandler
+{
+ const ScColumn& mrSrcCol;
+ ScColumn& mrDestCol;
+ sc::ColumnBlockPosition maDestPos;
+ sc::ColumnBlockPosition* mpDestPos;
+ InsertDeleteFlags mnCopyFlags;
+
+ sc::StartListeningType meListenType;
+
+ void setDefaultAttrToDest(size_t nRow)
+ {
+ maDestPos.miCellTextAttrPos = mrDestCol.GetCellAttrStore().set(
+ maDestPos.miCellTextAttrPos, nRow, sc::CellTextAttr());
+ }
+
+ void setDefaultAttrsToDest(size_t nRow, size_t nSize)
+ {
+ std::vector<sc::CellTextAttr> aAttrs(nSize); // default values
+ maDestPos.miCellTextAttrPos = mrDestCol.GetCellAttrStore().set(
+ maDestPos.miCellTextAttrPos, nRow, aAttrs.begin(), aAttrs.end());
+ }
+
+ ScFormulaCell* createRefCell(size_t nRow)
+ {
+ ScSingleRefData aRef;
+ aRef.InitAddress(ScAddress(mrSrcCol.GetCol(), nRow, mrSrcCol.GetTab())); // Absolute reference.
+ aRef.SetFlag3D(true);
+
+ ScTokenArray aArr(mrDestCol.GetDoc());
+ aArr.AddSingleReference(aRef);
+ return new ScFormulaCell(mrDestCol.GetDoc(), ScAddress(mrDestCol.GetCol(), nRow, mrDestCol.GetTab()), aArr);
+ }
+
+ void createRefBlock(const sc::CellStoreType::value_type& aNode, size_t nOffset, size_t nDataSize)
+ {
+ size_t nTopRow = aNode.position + nOffset;
+
+ for (size_t i = 0; i < nDataSize; ++i)
+ {
+ SCROW nRow = nTopRow + i;
+ mrDestCol.SetFormulaCell(maDestPos, nRow, createRefCell(nRow), meListenType);
+ }
+
+ setDefaultAttrsToDest(nTopRow, nDataSize);
+ }
+
+public:
+ CopyAsLinkHandler(const ScColumn& rSrcCol, ScColumn& rDestCol, sc::ColumnBlockPosition* pDestPos, InsertDeleteFlags nCopyFlags) :
+ mrSrcCol(rSrcCol),
+ mrDestCol(rDestCol),
+ mpDestPos(pDestPos),
+ mnCopyFlags(nCopyFlags),
+ meListenType(sc::SingleCellListening)
+ {
+ if (mpDestPos)
+ maDestPos = *mpDestPos;
+ }
+
+ ~CopyAsLinkHandler()
+ {
+ if (mpDestPos)
+ {
+ // Similar to CopyByCloneHandler, don't copy a singular iterator.
+ {
+ sc::ColumnBlockPosition aTempBlock;
+ mrDestCol.InitBlockPosition(aTempBlock);
+ maDestPos.miBroadcasterPos = aTempBlock.miBroadcasterPos;
+ }
+
+ *mpDestPos = maDestPos;
+ }
+ }
+
+ void setStartListening( bool b )
+ {
+ meListenType = b ? sc::SingleCellListening : sc::NoListening;
+ }
+
+ void operator() (const sc::CellStoreType::value_type& aNode, size_t nOffset, size_t nDataSize)
+ {
+ size_t nRow = aNode.position + nOffset;
+
+ if (mnCopyFlags & (InsertDeleteFlags::NOTE|InsertDeleteFlags::ADDNOTES))
+ {
+ bool bCloneCaption = (mnCopyFlags & InsertDeleteFlags::NOCAPTIONS) == InsertDeleteFlags::NONE;
+ mrSrcCol.DuplicateNotes(nRow, nDataSize, mrDestCol, maDestPos, bCloneCaption);
+ }
+
+ switch (aNode.type)
+ {
+ case sc::element_type_numeric:
+ {
+ if ((mnCopyFlags & (InsertDeleteFlags::DATETIME|InsertDeleteFlags::VALUE)) == InsertDeleteFlags::NONE)
+ return;
+
+ sc::numeric_block::const_iterator it = sc::numeric_block::begin(*aNode.data);
+ std::advance(it, nOffset);
+ sc::numeric_block::const_iterator itEnd = it;
+ std::advance(itEnd, nDataSize);
+
+ ScAddress aSrcPos(mrSrcCol.GetCol(), nRow, mrSrcCol.GetTab());
+ for (; it != itEnd; ++it, aSrcPos.IncRow(), ++nRow)
+ {
+ if (!canCopyValue(mrSrcCol.GetDoc(), aSrcPos, mnCopyFlags))
+ continue;
+
+ maDestPos.miCellPos = mrDestCol.GetCellStore().set(maDestPos.miCellPos, nRow, createRefCell(nRow));
+ setDefaultAttrToDest(nRow);
+ }
+ }
+ break;
+ case sc::element_type_string:
+ case sc::element_type_edittext:
+ {
+ if (!(mnCopyFlags & InsertDeleteFlags::STRING))
+ return;
+
+ createRefBlock(aNode, nOffset, nDataSize);
+ }
+ break;
+ case sc::element_type_formula:
+ {
+ if (!(mnCopyFlags & InsertDeleteFlags::FORMULA))
+ return;
+
+ createRefBlock(aNode, nOffset, nDataSize);
+ }
+ break;
+ default:
+ ;
+ }
+ }
+};
+
+class CopyByCloneHandler
+{
+ const ScColumn& mrSrcCol;
+ ScColumn& mrDestCol;
+ sc::ColumnBlockPosition maDestPos;
+ sc::ColumnBlockPosition* mpDestPos;
+ svl::SharedStringPool* mpSharedStringPool;
+ InsertDeleteFlags mnCopyFlags;
+
+ sc::StartListeningType meListenType;
+ ScCloneFlags mnFormulaCellCloneFlags;
+
+ void setDefaultAttrToDest(size_t nRow)
+ {
+ maDestPos.miCellTextAttrPos = mrDestCol.GetCellAttrStore().set(
+ maDestPos.miCellTextAttrPos, nRow, sc::CellTextAttr());
+ }
+
+ void setDefaultAttrsToDest(size_t nRow, size_t nSize)
+ {
+ std::vector<sc::CellTextAttr> aAttrs(nSize); // default values
+ maDestPos.miCellTextAttrPos = mrDestCol.GetCellAttrStore().set(
+ maDestPos.miCellTextAttrPos, nRow, aAttrs.begin(), aAttrs.end());
+ }
+
+ void cloneFormulaCell(size_t nRow, ScFormulaCell& rSrcCell)
+ {
+ ScAddress aDestPos(mrDestCol.GetCol(), nRow, mrDestCol.GetTab());
+
+ bool bCloneValue = (mnCopyFlags & InsertDeleteFlags::VALUE) != InsertDeleteFlags::NONE;
+ bool bCloneDateTime = (mnCopyFlags & InsertDeleteFlags::DATETIME) != InsertDeleteFlags::NONE;
+ bool bCloneString = (mnCopyFlags & InsertDeleteFlags::STRING) != InsertDeleteFlags::NONE;
+ bool bCloneSpecialBoolean = (mnCopyFlags & InsertDeleteFlags::SPECIAL_BOOLEAN) != InsertDeleteFlags::NONE;
+ bool bCloneFormula = (mnCopyFlags & InsertDeleteFlags::FORMULA) != InsertDeleteFlags::NONE;
+
+ bool bForceFormula = false;
+
+ if (bCloneSpecialBoolean)
+ {
+ // See if the formula consists of =TRUE() or =FALSE().
+ const ScTokenArray* pCode = rSrcCell.GetCode();
+ if (pCode && pCode->GetLen() == 1)
+ {
+ const formula::FormulaToken* p = pCode->FirstToken();
+ if (p->GetOpCode() == ocTrue || p->GetOpCode() == ocFalse)
+ // This is a boolean formula.
+ bForceFormula = true;
+ }
+ }
+
+ if (bForceFormula || bCloneFormula)
+ {
+ // Clone as formula cell.
+ ScFormulaCell* pCell = new ScFormulaCell(rSrcCell, mrDestCol.GetDoc(), aDestPos, mnFormulaCellCloneFlags);
+ pCell->SetDirtyVar();
+ mrDestCol.SetFormulaCell(maDestPos, nRow, pCell, meListenType, rSrcCell.NeedsNumberFormat());
+ setDefaultAttrToDest(nRow);
+ return;
+ }
+
+ if (mrDestCol.GetDoc().IsUndo())
+ return;
+
+ if (bCloneValue)
+ {
+ FormulaError nErr = rSrcCell.GetErrCode();
+ if (nErr != FormulaError::NONE)
+ {
+ // error codes are cloned with values
+ ScFormulaCell* pErrCell = new ScFormulaCell(mrDestCol.GetDoc(), aDestPos);
+ pErrCell->SetErrCode(nErr);
+ mrDestCol.SetFormulaCell(maDestPos, nRow, pErrCell, meListenType);
+ setDefaultAttrToDest(nRow);
+ return;
+ }
+ }
+
+ if (bCloneValue || bCloneDateTime)
+ {
+ if (rSrcCell.IsValue())
+ {
+ if (canCopyValue(mrSrcCol.GetDoc(), ScAddress(mrSrcCol.GetCol(), nRow, mrSrcCol.GetTab()), mnCopyFlags))
+ {
+ maDestPos.miCellPos = mrDestCol.GetCellStore().set(
+ maDestPos.miCellPos, nRow, rSrcCell.GetValue());
+ setDefaultAttrToDest(nRow);
+ }
+
+ return;
+ }
+ }
+
+ if (!bCloneString)
+ return;
+
+ svl::SharedString aStr = rSrcCell.GetString();
+ if (aStr.isEmpty())
+ // Don't create empty string cells.
+ return;
+
+ if (rSrcCell.IsMultilineResult())
+ {
+ // Clone as an edit text object.
+ EditEngine& rEngine = mrDestCol.GetDoc().GetEditEngine();
+ rEngine.SetText(aStr.getString());
+ maDestPos.miCellPos =
+ mrDestCol.GetCellStore().set(maDestPos.miCellPos, nRow, rEngine.CreateTextObject().release());
+ }
+ else
+ {
+ maDestPos.miCellPos =
+ mrDestCol.GetCellStore().set(maDestPos.miCellPos, nRow, aStr);
+ }
+
+ setDefaultAttrToDest(nRow);
+ }
+
+public:
+ CopyByCloneHandler(const ScColumn& rSrcCol, ScColumn& rDestCol, sc::ColumnBlockPosition* pDestPos,
+ InsertDeleteFlags nCopyFlags, svl::SharedStringPool* pSharedStringPool, bool bGlobalNamesToLocal) :
+ mrSrcCol(rSrcCol),
+ mrDestCol(rDestCol),
+ mpDestPos(pDestPos),
+ mpSharedStringPool(pSharedStringPool),
+ mnCopyFlags(nCopyFlags),
+ meListenType(sc::SingleCellListening),
+ mnFormulaCellCloneFlags(bGlobalNamesToLocal ? ScCloneFlags::NamesToLocal : ScCloneFlags::Default)
+ {
+ if (mpDestPos)
+ maDestPos = *mpDestPos;
+ }
+
+ ~CopyByCloneHandler()
+ {
+ if (!mpDestPos)
+ return;
+
+ // If broadcasters were setup in the same column,
+ // maDestPos.miBroadcasterPos doesn't match
+ // mrDestCol.maBroadcasters because it is never passed anywhere.
+ // Assign a corresponding iterator before copying all over.
+ // Otherwise this may result in wrongly copying a singular
+ // iterator.
+
+ {
+ /* XXX Using a temporary ColumnBlockPosition just for
+ * initializing from ScColumn::maBroadcasters.begin() is ugly,
+ * on the other hand we don't want to expose
+ * ScColumn::maBroadcasters to the outer world and have a
+ * getter. */
+ sc::ColumnBlockPosition aTempBlock;
+ mrDestCol.InitBlockPosition(aTempBlock);
+ maDestPos.miBroadcasterPos = aTempBlock.miBroadcasterPos;
+ }
+
+ *mpDestPos = maDestPos;
+ }
+
+ void setStartListening( bool b )
+ {
+ meListenType = b ? sc::SingleCellListening : sc::NoListening;
+ }
+
+ void operator() (const sc::CellStoreType::value_type& aNode, size_t nOffset, size_t nDataSize)
+ {
+ size_t nRow = aNode.position + nOffset;
+
+ if (mnCopyFlags & (InsertDeleteFlags::NOTE|InsertDeleteFlags::ADDNOTES))
+ {
+ bool bCloneCaption = (mnCopyFlags & InsertDeleteFlags::NOCAPTIONS) == InsertDeleteFlags::NONE;
+ mrSrcCol.DuplicateNotes(nRow, nDataSize, mrDestCol, maDestPos, bCloneCaption);
+ }
+
+ switch (aNode.type)
+ {
+ case sc::element_type_numeric:
+ {
+ if ((mnCopyFlags & (InsertDeleteFlags::DATETIME|InsertDeleteFlags::VALUE)) == InsertDeleteFlags::NONE)
+ return;
+
+ sc::numeric_block::const_iterator it = sc::numeric_block::begin(*aNode.data);
+ std::advance(it, nOffset);
+ sc::numeric_block::const_iterator itEnd = it;
+ std::advance(itEnd, nDataSize);
+
+ ScAddress aSrcPos(mrSrcCol.GetCol(), nRow, mrSrcCol.GetTab());
+ for (; it != itEnd; ++it, aSrcPos.IncRow(), ++nRow)
+ {
+ if (!canCopyValue(mrSrcCol.GetDoc(), aSrcPos, mnCopyFlags))
+ continue;
+
+ maDestPos.miCellPos = mrDestCol.GetCellStore().set(maDestPos.miCellPos, nRow, *it);
+ setDefaultAttrToDest(nRow);
+ }
+ }
+ break;
+ case sc::element_type_string:
+ {
+ if (!(mnCopyFlags & InsertDeleteFlags::STRING))
+ return;
+
+ sc::string_block::const_iterator it = sc::string_block::begin(*aNode.data);
+ std::advance(it, nOffset);
+ sc::string_block::const_iterator itEnd = it;
+ std::advance(itEnd, nDataSize);
+
+ for (; it != itEnd; ++it, ++nRow)
+ {
+ const svl::SharedString& rStr = *it;
+ if (rStr.isEmpty())
+ {
+ // String cell with empty value is used to special-case cell value removal.
+ maDestPos.miCellPos = mrDestCol.GetCellStore().set_empty(
+ maDestPos.miCellPos, nRow, nRow);
+ maDestPos.miCellTextAttrPos = mrDestCol.GetCellAttrStore().set_empty(
+ maDestPos.miCellTextAttrPos, nRow, nRow);
+ }
+ else
+ {
+ if (mpSharedStringPool)
+ {
+ // Re-intern the string if source is a different document.
+ svl::SharedString aInterned = mpSharedStringPool->intern( rStr.getString());
+ maDestPos.miCellPos =
+ mrDestCol.GetCellStore().set(maDestPos.miCellPos, nRow, aInterned);
+ }
+ else
+ {
+ maDestPos.miCellPos =
+ mrDestCol.GetCellStore().set(maDestPos.miCellPos, nRow, rStr);
+ }
+ setDefaultAttrToDest(nRow);
+ }
+ }
+ }
+ break;
+ case sc::element_type_edittext:
+ {
+ if (!(mnCopyFlags & InsertDeleteFlags::STRING))
+ return;
+
+ sc::edittext_block::const_iterator it = sc::edittext_block::begin(*aNode.data);
+ std::advance(it, nOffset);
+ sc::edittext_block::const_iterator itEnd = it;
+ std::advance(itEnd, nDataSize);
+
+ std::vector<EditTextObject*> aCloned;
+ aCloned.reserve(nDataSize);
+ for (; it != itEnd; ++it)
+ aCloned.push_back(ScEditUtil::Clone(**it, mrDestCol.GetDoc()).release());
+
+ maDestPos.miCellPos = mrDestCol.GetCellStore().set(
+ maDestPos.miCellPos, nRow, aCloned.begin(), aCloned.end());
+
+ setDefaultAttrsToDest(nRow, nDataSize);
+ }
+ break;
+ case sc::element_type_formula:
+ {
+ sc::formula_block::const_iterator it = sc::formula_block::begin(*aNode.data);
+ std::advance(it, nOffset);
+ sc::formula_block::const_iterator itEnd = it;
+ std::advance(itEnd, nDataSize);
+
+ sc::DelayStartListeningFormulaCells startDelay(mrDestCol); // disabled
+ if(nDataSize > 1024 && (mnCopyFlags & InsertDeleteFlags::FORMULA) != InsertDeleteFlags::NONE)
+ {
+ // If the column to be replaced contains a long formula group (tdf#102364), there can
+ // be so many listeners in a single vector that the quadratic cost of repeatedly removing
+ // the first element becomes very high. Optimize this by removing them in one go.
+ sc::EndListeningContext context(mrDestCol.GetDoc());
+ mrDestCol.EndListeningFormulaCells( context, nRow, nRow + nDataSize - 1, nullptr, nullptr );
+ // There can be a similar problem with starting to listen to cells repeatedly (tdf#133302).
+ // Delay it.
+ startDelay.set();
+ }
+
+ for (; it != itEnd; ++it, ++nRow)
+ cloneFormulaCell(nRow, **it);
+ }
+ break;
+ default:
+ ;
+ }
+ }
+};
+
+}
+
+void ScColumn::CopyToColumn(
+ sc::CopyToDocContext& rCxt,
+ SCROW nRow1, SCROW nRow2, InsertDeleteFlags nFlags, bool bMarked, ScColumn& rColumn,
+ const ScMarkData* pMarkData, bool bAsLink, bool bGlobalNamesToLocal) const
+{
+ if (bMarked)
+ {
+ SCROW nStart, nEnd;
+ if (pMarkData && pMarkData->IsMultiMarked())
+ {
+ ScMultiSelIter aIter( pMarkData->GetMultiSelData(), nCol );
+
+ while ( aIter.Next( nStart, nEnd ) && nStart <= nRow2 )
+ {
+ if ( nEnd >= nRow1 )
+ CopyToColumn(rCxt, std::max(nRow1,nStart), std::min(nRow2,nEnd),
+ nFlags, false, rColumn, pMarkData, bAsLink );
+ }
+ }
+ else
+ {
+ OSL_FAIL("CopyToColumn: bMarked, but no mark");
+ }
+ return;
+ }
+
+ if ( (nFlags & InsertDeleteFlags::ATTRIB) != InsertDeleteFlags::NONE )
+ {
+ if ( (nFlags & InsertDeleteFlags::STYLES) != InsertDeleteFlags::STYLES )
+ { // keep the StyleSheets in the target document
+ // e.g. DIF and RTF Clipboard-Import
+ for ( SCROW nRow = nRow1; nRow <= nRow2; nRow++ )
+ {
+ const ScStyleSheet* pStyle =
+ rColumn.pAttrArray->GetPattern( nRow )->GetStyleSheet();
+ const ScPatternAttr* pPattern = pAttrArray->GetPattern( nRow );
+ std::unique_ptr<ScPatternAttr> pNewPattern(new ScPatternAttr( *pPattern ));
+ pNewPattern->SetStyleSheet( const_cast<ScStyleSheet*>(pStyle) );
+ rColumn.pAttrArray->SetPattern( nRow, std::move(pNewPattern), true );
+ }
+ }
+ else
+ pAttrArray->CopyArea( nRow1, nRow2, 0, *rColumn.pAttrArray);
+ }
+
+ if ((nFlags & InsertDeleteFlags::CONTENTS) == InsertDeleteFlags::NONE)
+ return;
+
+ if (bAsLink)
+ {
+ CopyAsLinkHandler aFunc(*this, rColumn, rCxt.getBlockPosition(rColumn.nTab, rColumn.nCol), nFlags);
+ aFunc.setStartListening(rCxt.isStartListening());
+ sc::ParseBlock(maCells.begin(), maCells, aFunc, nRow1, nRow2);
+ }
+ else
+ {
+ // Compare the ScDocumentPool* to determine if we are copying
+ // within the same document. If not, re-intern shared strings.
+ svl::SharedStringPool* pSharedStringPool =
+ (GetDoc().GetPool() != rColumn.GetDoc().GetPool()) ?
+ &rColumn.GetDoc().GetSharedStringPool() : nullptr;
+ CopyByCloneHandler aFunc(*this, rColumn, rCxt.getBlockPosition(rColumn.nTab, rColumn.nCol), nFlags,
+ pSharedStringPool, bGlobalNamesToLocal);
+ aFunc.setStartListening(rCxt.isStartListening());
+ sc::ParseBlock(maCells.begin(), maCells, aFunc, nRow1, nRow2);
+ }
+
+ rColumn.CellStorageModified();
+}
+
+void ScColumn::UndoToColumn(
+ sc::CopyToDocContext& rCxt, SCROW nRow1, SCROW nRow2, InsertDeleteFlags nFlags, bool bMarked,
+ ScColumn& rColumn ) const
+{
+ if (nRow1 > 0)
+ CopyToColumn(rCxt, 0, nRow1-1, InsertDeleteFlags::FORMULA, false, rColumn);
+
+ CopyToColumn(rCxt, nRow1, nRow2, nFlags, bMarked, rColumn); //TODO: bMarked ????
+
+ if (nRow2 < GetDoc().MaxRow())
+ CopyToColumn(rCxt, nRow2+1, GetDoc().MaxRow(), InsertDeleteFlags::FORMULA, false, rColumn);
+}
+
+void ScColumn::CopyUpdated( const ScColumn* pPosCol, ScColumn& rDestCol ) const
+{
+ // Copy cells from this column to the destination column only for those
+ // rows that are present in the position column (pPosCol).
+
+ // First, mark all the non-empty cell ranges from the position column.
+ sc::SingleColumnSpanSet aRangeSet(GetDoc().GetSheetLimits());
+ if(pPosCol)
+ aRangeSet.scan(*pPosCol);
+
+ // Now, copy cells from this column to the destination column for those
+ // marked row ranges.
+ sc::SingleColumnSpanSet::SpansType aRanges;
+ aRangeSet.getSpans(aRanges);
+
+ CopyToClipHandler aFunc(GetDoc(), *this, rDestCol, nullptr);
+ sc::CellStoreType::const_iterator itPos = maCells.begin();
+ for (const auto& rRange : aRanges)
+ itPos = sc::ParseBlock(itPos, maCells, aFunc, rRange.mnRow1, rRange.mnRow2);
+
+ rDestCol.CellStorageModified();
+}
+
+void ScColumn::CopyScenarioFrom( const ScColumn& rSrcCol )
+{
+ // This is the scenario table, the data is copied into it
+ ScDocument& rDocument = GetDoc();
+ ScAttrIterator aAttrIter( pAttrArray.get(), 0, GetDoc().MaxRow(), rDocument.GetDefPattern() );
+ SCROW nStart = -1, nEnd = -1;
+ const ScPatternAttr* pPattern = aAttrIter.Next( nStart, nEnd );
+ while (pPattern)
+ {
+ if ( pPattern->GetItem( ATTR_MERGE_FLAG ).IsScenario() )
+ {
+ DeleteArea( nStart, nEnd, InsertDeleteFlags::CONTENTS );
+ sc::CopyToDocContext aCxt(rDocument);
+ rSrcCol.
+ CopyToColumn(aCxt, nStart, nEnd, InsertDeleteFlags::CONTENTS, false, *this);
+
+ // UpdateUsed not needed, already done in TestCopyScenario (obsolete comment ?)
+
+ sc::RefUpdateContext aRefCxt(rDocument);
+ aRefCxt.meMode = URM_COPY;
+ aRefCxt.maRange = ScRange(nCol, nStart, nTab, nCol, nEnd, nTab);
+ aRefCxt.mnTabDelta = nTab - rSrcCol.nTab;
+ UpdateReferenceOnCopy(aRefCxt);
+ UpdateCompile();
+ }
+ pPattern = aAttrIter.Next( nStart, nEnd );
+ }
+}
+
+void ScColumn::CopyScenarioTo( ScColumn& rDestCol ) const
+{
+ // This is the scenario table, the data is copied to the other
+ ScDocument& rDocument = GetDoc();
+ ScAttrIterator aAttrIter( pAttrArray.get(), 0, GetDoc().MaxRow(), rDocument.GetDefPattern() );
+ SCROW nStart = -1, nEnd = -1;
+ const ScPatternAttr* pPattern = aAttrIter.Next( nStart, nEnd );
+ while (pPattern)
+ {
+ if ( pPattern->GetItem( ATTR_MERGE_FLAG ).IsScenario() )
+ {
+ rDestCol.DeleteArea( nStart, nEnd, InsertDeleteFlags::CONTENTS );
+ sc::CopyToDocContext aCxt(rDestCol.GetDoc());
+ CopyToColumn(aCxt, nStart, nEnd, InsertDeleteFlags::CONTENTS, false, rDestCol);
+
+ sc::RefUpdateContext aRefCxt(rDocument);
+ aRefCxt.meMode = URM_COPY;
+ aRefCxt.maRange = ScRange(rDestCol.nCol, nStart, rDestCol.nTab, rDestCol.nCol, nEnd, rDestCol.nTab);
+ aRefCxt.mnTabDelta = rDestCol.nTab - nTab;
+ rDestCol.UpdateReferenceOnCopy(aRefCxt);
+ rDestCol.UpdateCompile();
+ }
+ pPattern = aAttrIter.Next( nStart, nEnd );
+ }
+}
+
+bool ScColumn::TestCopyScenarioTo( const ScColumn& rDestCol ) const
+{
+ bool bOk = true;
+ ScAttrIterator aAttrIter( pAttrArray.get(), 0, GetDoc().MaxRow(), GetDoc().GetDefPattern() );
+ SCROW nStart = 0, nEnd = 0;
+ const ScPatternAttr* pPattern = aAttrIter.Next( nStart, nEnd );
+ while (pPattern && bOk)
+ {
+ if ( pPattern->GetItem( ATTR_MERGE_FLAG ).IsScenario() )
+ if ( rDestCol.pAttrArray->HasAttrib( nStart, nEnd, HasAttrFlags::Protected ) )
+ bOk = false;
+
+ pPattern = aAttrIter.Next( nStart, nEnd );
+ }
+ return bOk;
+}
+
+void ScColumn::MarkScenarioIn( ScMarkData& rDestMark ) const
+{
+ ScRange aRange( nCol, 0, nTab );
+
+ ScAttrIterator aAttrIter( pAttrArray.get(), 0, GetDoc().MaxRow(), GetDoc().GetDefPattern() );
+ SCROW nStart = -1, nEnd = -1;
+ const ScPatternAttr* pPattern = aAttrIter.Next( nStart, nEnd );
+ while (pPattern)
+ {
+ if ( pPattern->GetItem( ATTR_MERGE_FLAG ).IsScenario() )
+ {
+ aRange.aStart.SetRow( nStart );
+ aRange.aEnd.SetRow( nEnd );
+ rDestMark.SetMultiMarkArea( aRange );
+ }
+
+ pPattern = aAttrIter.Next( nStart, nEnd );
+ }
+}
+
+namespace {
+
+void resetColumnPosition(sc::CellStoreType& rCells, SCCOL nCol)
+{
+ for (auto& rCellItem : rCells)
+ {
+ if (rCellItem.type != sc::element_type_formula)
+ continue;
+
+ sc::formula_block::iterator itCell = sc::formula_block::begin(*rCellItem.data);
+ sc::formula_block::iterator itCellEnd = sc::formula_block::end(*rCellItem.data);
+ for (; itCell != itCellEnd; ++itCell)
+ {
+ ScFormulaCell& rCell = **itCell;
+ rCell.aPos.SetCol(nCol);
+ }
+ }
+}
+
+class NoteCaptionUpdater
+{
+ SCCOL mnCol;
+ SCTAB mnTab;
+public:
+ NoteCaptionUpdater( SCCOL nCol, SCTAB nTab ) : mnCol(nCol), mnTab(nTab) {}
+
+ void operator() ( size_t nRow, ScPostIt* p )
+ {
+ p->UpdateCaptionPos(ScAddress(mnCol,nRow,mnTab));
+ }
+};
+
+}
+
+void ScColumn::UpdateNoteCaptions( SCROW nRow1, SCROW nRow2 )
+{
+ NoteCaptionUpdater aFunc(nCol, nTab);
+ sc::ProcessNote(maCellNotes.begin(), maCellNotes, nRow1, nRow2, aFunc);
+}
+
+void ScColumn::UpdateDrawObjects(std::vector<std::vector<SdrObject*>>& pObjects, SCROW nRowStart, SCROW nRowEnd)
+{
+ assert(static_cast<int>(pObjects.size()) >= nRowEnd - nRowStart + 1);
+
+ int nObj = 0;
+ for (SCROW nCurrentRow = nRowStart; nCurrentRow <= nRowEnd; nCurrentRow++, nObj++)
+ {
+ if (pObjects[nObj].empty())
+ continue; // No draw objects in this row
+
+ UpdateDrawObjectsForRow(pObjects[nObj], nCol, nCurrentRow);
+ }
+}
+
+void ScColumn::UpdateDrawObjectsForRow( std::vector<SdrObject*>& pObjects, SCCOL nTargetCol, SCROW nTargetRow )
+{
+ for (auto &pObject : pObjects)
+ {
+ ScAddress aNewAddress(nTargetCol, nTargetRow, nTab);
+
+ // Update draw object according to new anchor
+ ScDrawLayer* pDrawLayer = GetDoc().GetDrawLayer();
+ if (pDrawLayer)
+ pDrawLayer->MoveObject(pObject, aNewAddress);
+ }
+}
+
+bool ScColumn::IsDrawObjectsEmptyBlock(SCROW nStartRow, SCROW nEndRow) const
+{
+ ScDrawLayer* pDrawLayer = GetDoc().GetDrawLayer();
+ if (!pDrawLayer)
+ return true;
+
+ ScRange aRange(nCol, nStartRow, nTab, nCol, nEndRow, nTab);
+ return !pDrawLayer->HasObjectsAnchoredInRange(aRange);
+}
+
+void ScColumn::SwapCol(ScColumn& rCol)
+{
+ maBroadcasters.swap(rCol.maBroadcasters);
+ maCells.swap(rCol.maCells);
+ maCellTextAttrs.swap(rCol.maCellTextAttrs);
+ maCellNotes.swap(rCol.maCellNotes);
+ maSparklines.swap(rCol.maSparklines);
+
+ // Swap all CellStoreEvent mdds event_func related.
+ maCells.event_handler().swap(rCol.maCells.event_handler());
+ std::swap( mnBlkCountFormula, rCol.mnBlkCountFormula);
+
+ // notes update caption
+ UpdateNoteCaptions(0, GetDoc().MaxRow());
+ rCol.UpdateNoteCaptions(0, GetDoc().MaxRow());
+
+ std::swap(pAttrArray, rCol.pAttrArray);
+
+ // AttrArray needs to have the right column number
+ pAttrArray->SetCol(nCol);
+ rCol.pAttrArray->SetCol(rCol.nCol);
+
+ // Reset column positions in formula cells.
+ resetColumnPosition(maCells, nCol);
+ resetColumnPosition(rCol.maCells, rCol.nCol);
+
+ CellStorageModified();
+ rCol.CellStorageModified();
+}
+
+void ScColumn::MoveTo(SCROW nStartRow, SCROW nEndRow, ScColumn& rCol)
+{
+ pAttrArray->MoveTo(nStartRow, nEndRow, *rCol.pAttrArray);
+
+ // Mark the non-empty cells within the specified range, for later broadcasting.
+ sc::SingleColumnSpanSet aNonEmpties(GetDoc().GetSheetLimits());
+ aNonEmpties.scan(*this, nStartRow, nEndRow);
+ sc::SingleColumnSpanSet::SpansType aRanges;
+ aNonEmpties.getSpans(aRanges);
+
+ // Split the formula grouping at the top and bottom boundaries.
+ sc::CellStoreType::position_type aPos = maCells.position(nStartRow);
+ sc::SharedFormulaUtil::splitFormulaCellGroup(aPos, nullptr);
+ if (GetDoc().ValidRow(nEndRow+1))
+ {
+ aPos = maCells.position(aPos.first, nEndRow+1);
+ sc::SharedFormulaUtil::splitFormulaCellGroup(aPos, nullptr);
+ }
+
+ // Do the same with the destination column.
+ aPos = rCol.maCells.position(nStartRow);
+ sc::SharedFormulaUtil::splitFormulaCellGroup(aPos, nullptr);
+ if (GetDoc().ValidRow(nEndRow+1))
+ {
+ aPos = rCol.maCells.position(aPos.first, nEndRow+1);
+ sc::SharedFormulaUtil::splitFormulaCellGroup(aPos, nullptr);
+ }
+
+ // Move the broadcasters to the destination column.
+ maBroadcasters.transfer(nStartRow, nEndRow, rCol.maBroadcasters, nStartRow);
+ maCells.transfer(nStartRow, nEndRow, rCol.maCells, nStartRow);
+ maCellTextAttrs.transfer(nStartRow, nEndRow, rCol.maCellTextAttrs, nStartRow);
+
+ // move the notes to the destination column
+ maCellNotes.transfer(nStartRow, nEndRow, rCol.maCellNotes, nStartRow);
+ UpdateNoteCaptions(0, GetDoc().MaxRow());
+
+ // Re-group transferred formula cells.
+ aPos = rCol.maCells.position(nStartRow);
+ sc::SharedFormulaUtil::joinFormulaCellAbove(aPos);
+ if (GetDoc().ValidRow(nEndRow+1))
+ {
+ aPos = rCol.maCells.position(aPos.first, nEndRow+1);
+ sc::SharedFormulaUtil::joinFormulaCellAbove(aPos);
+ }
+
+ CellStorageModified();
+ rCol.CellStorageModified();
+
+ // Broadcast on moved ranges. Area-broadcast only.
+ ScDocument& rDocument = GetDoc();
+ ScHint aHint(SfxHintId::ScDataChanged, ScAddress(nCol, 0, nTab));
+ for (const auto& rRange : aRanges)
+ {
+ for (SCROW nRow = rRange.mnRow1; nRow <= rRange.mnRow2; ++nRow)
+ {
+ aHint.SetAddressRow(nRow);
+ rDocument.AreaBroadcast(aHint);
+ }
+ }
+}
+
+namespace {
+
+class SharedTopFormulaCellPicker
+{
+public:
+ SharedTopFormulaCellPicker() = default;
+ SharedTopFormulaCellPicker(SharedTopFormulaCellPicker const &) = default;
+ SharedTopFormulaCellPicker(SharedTopFormulaCellPicker &&) = default;
+ SharedTopFormulaCellPicker & operator =(SharedTopFormulaCellPicker const &) = default;
+ SharedTopFormulaCellPicker & operator =(SharedTopFormulaCellPicker &&) = default;
+
+ virtual ~SharedTopFormulaCellPicker() {}
+
+ void operator() ( sc::CellStoreType::value_type& node )
+ {
+ if (node.type != sc::element_type_formula)
+ return;
+
+ size_t nTopRow = node.position;
+
+ sc::formula_block::iterator itBeg = sc::formula_block::begin(*node.data);
+ sc::formula_block::iterator itEnd = sc::formula_block::end(*node.data);
+
+ // Only pick shared formula cells that are the top cells of their
+ // respective shared ranges.
+ for (sc::formula_block::iterator it = itBeg; it != itEnd; ++it)
+ {
+ ScFormulaCell* pCell = *it;
+ size_t nRow = nTopRow + std::distance(itBeg, it);
+ if (!pCell->IsShared())
+ {
+ processNonShared(pCell, nRow);
+ continue;
+ }
+
+ if (pCell->IsSharedTop())
+ {
+ ScFormulaCell** pp = &(*it);
+ processSharedTop(pp, nRow, pCell->GetSharedLength());
+
+ // Move to the last cell in the group, to get incremented to
+ // the next cell in the next iteration.
+ size_t nOffsetToLast = pCell->GetSharedLength() - 1;
+ std::advance(it, nOffsetToLast);
+ }
+ }
+ }
+
+ virtual void processNonShared( ScFormulaCell* /*pCell*/, size_t /*nRow*/ ) {}
+ virtual void processSharedTop( ScFormulaCell** /*ppCells*/, size_t /*nRow*/, size_t /*nLength*/ ) {}
+};
+
+class UpdateRefOnCopy
+{
+ const sc::RefUpdateContext& mrCxt;
+ ScDocument* mpUndoDoc;
+ bool mbUpdated;
+
+public:
+ UpdateRefOnCopy(const sc::RefUpdateContext& rCxt, ScDocument* pUndoDoc) :
+ mrCxt(rCxt), mpUndoDoc(pUndoDoc), mbUpdated(false) {}
+
+ bool isUpdated() const { return mbUpdated; }
+
+ void operator() (sc::CellStoreType::value_type& node, size_t nOffset, size_t nDataSize)
+ {
+ if (node.type != sc::element_type_formula)
+ return;
+
+ sc::formula_block::iterator it = sc::formula_block::begin(*node.data);
+ std::advance(it, nOffset);
+ sc::formula_block::iterator itEnd = it;
+ std::advance(itEnd, nDataSize);
+
+ for (; it != itEnd; ++it)
+ {
+ ScFormulaCell& rCell = **it;
+ mbUpdated |= rCell.UpdateReference(mrCxt, mpUndoDoc);
+ }
+ }
+};
+
+class UpdateRefOnNonCopy
+{
+ SCCOL mnCol;
+ SCROW mnTab;
+ const sc::RefUpdateContext* mpCxt;
+ ScDocument* mpUndoDoc;
+ bool mbUpdated;
+ bool mbClipboardSource;
+
+ void recompileTokenArray( ScFormulaCell& rTopCell )
+ {
+ // We need to re-compile the token array when a range name is
+ // modified, to correctly reflect the new references in the
+ // name.
+ ScCompiler aComp(mpCxt->mrDoc, rTopCell.aPos, *rTopCell.GetCode(), mpCxt->mrDoc.GetGrammar(),
+ true, rTopCell.GetMatrixFlag() != ScMatrixMode::NONE);
+ aComp.CompileTokenArray();
+ }
+
+ void updateRefOnShift( sc::FormulaGroupEntry& rGroup )
+ {
+ if (!rGroup.mbShared)
+ {
+ ScAddress aUndoPos(mnCol, rGroup.mnRow, mnTab);
+ mbUpdated |= rGroup.mpCell->UpdateReferenceOnShift(*mpCxt, mpUndoDoc, &aUndoPos);
+ return;
+ }
+
+ // Update references of a formula group.
+ ScFormulaCell** pp = rGroup.mpCells;
+ ScFormulaCell** ppEnd = pp + rGroup.mnLength;
+ ScFormulaCell* pTop = *pp;
+ ScTokenArray* pCode = pTop->GetCode();
+ ScTokenArray aOldCode(pCode->CloneValue());
+ ScAddress aOldPos = pTop->aPos;
+
+ // Run this before the position gets updated.
+ sc::RefUpdateResult aRes = pCode->AdjustReferenceOnShift(*mpCxt, aOldPos);
+
+ bool bGroupShifted = false;
+ if (pTop->UpdatePosOnShift(*mpCxt))
+ {
+ ScAddress aErrorPos( ScAddress::UNINITIALIZED );
+ // Update the positions of all formula cells.
+ for (++pp; pp != ppEnd; ++pp) // skip the top cell.
+ {
+ ScFormulaCell* pFC = *pp;
+ if (!pFC->aPos.Move(mpCxt->mnColDelta, mpCxt->mnRowDelta, mpCxt->mnTabDelta,
+ aErrorPos, mpCxt->mrDoc))
+ {
+ assert(!"can't move formula cell");
+ }
+ }
+
+ if (pCode->IsRecalcModeOnRefMove())
+ aRes.mbValueChanged = true;
+
+ // FormulaGroupAreaListener (contrary to ScBroadcastArea) is not
+ // updated but needs to be re-setup, else at least its mpColumn
+ // would indicate the old column to collect cells from. tdf#129396
+ /* TODO: investigate if that could be short-cut to avoid all the
+ * EndListeningTo() / StartListeningTo() overhead and is really
+ * only necessary when shifting the column, not also when shifting
+ * rows. */
+ bGroupShifted = true;
+ }
+ else if (aRes.mbReferenceModified && pCode->IsRecalcModeOnRefMove())
+ {
+ // The cell itself hasn't shifted. But it may have ROW or COLUMN
+ // referencing another cell that has.
+ aRes.mbValueChanged = true;
+ }
+
+ if (aRes.mbNameModified)
+ recompileTokenArray(*pTop);
+
+ if (aRes.mbReferenceModified || aRes.mbNameModified || bGroupShifted)
+ {
+ sc::EndListeningContext aEndCxt(mpCxt->mrDoc, &aOldCode);
+ aEndCxt.setPositionDelta(
+ ScAddress(-mpCxt->mnColDelta, -mpCxt->mnRowDelta, -mpCxt->mnTabDelta));
+
+ for (pp = rGroup.mpCells; pp != ppEnd; ++pp)
+ {
+ ScFormulaCell* p = *pp;
+ p->EndListeningTo(aEndCxt);
+ p->SetNeedsListening(true);
+ }
+
+ mbUpdated = true;
+
+ fillUndoDoc(aOldPos, rGroup.mnLength, aOldCode);
+ }
+
+ if (aRes.mbValueChanged)
+ {
+ for (pp = rGroup.mpCells; pp != ppEnd; ++pp)
+ {
+ ScFormulaCell* p = *pp;
+ p->SetNeedsDirty(true);
+ }
+ }
+ }
+
+ void updateRefOnMove( sc::FormulaGroupEntry& rGroup )
+ {
+ if (!rGroup.mbShared)
+ {
+ ScAddress aUndoPos(mnCol, rGroup.mnRow, mnTab);
+ mbUpdated |= rGroup.mpCell->UpdateReferenceOnMove(*mpCxt, mpUndoDoc, &aUndoPos);
+ return;
+ }
+
+ // Update references of a formula group.
+ ScFormulaCell** pp = rGroup.mpCells;
+ ScFormulaCell** ppEnd = pp + rGroup.mnLength;
+ ScFormulaCell* pTop = *pp;
+ ScTokenArray* pCode = pTop->GetCode();
+ ScTokenArray aOldCode(pCode->CloneValue());
+
+ ScAddress aPos = pTop->aPos;
+ ScAddress aOldPos = aPos;
+
+ bool bCellMoved;
+ if (mpCxt->maRange.Contains(aPos))
+ {
+ bCellMoved = true;
+
+ // The cell is being moved or copied to a new position. The
+ // position has already been updated prior to this call.
+ // Determine its original position before the move which will be
+ // used to adjust relative references later.
+
+ aOldPos.Set(
+ aPos.Col() - mpCxt->mnColDelta,
+ aPos.Row() - mpCxt->mnRowDelta,
+ aPos.Tab() - mpCxt->mnTabDelta);
+ }
+ else
+ {
+ bCellMoved = false;
+ }
+
+ bool bRecalcOnMove = pCode->IsRecalcModeOnRefMove();
+ if (bRecalcOnMove)
+ bRecalcOnMove = aPos != aOldPos;
+
+ sc::RefUpdateResult aRes = pCode->AdjustReferenceOnMove(*mpCxt, aOldPos, aPos);
+
+ if (!(aRes.mbReferenceModified || aRes.mbNameModified || bRecalcOnMove))
+ return;
+
+ sc::AutoCalcSwitch aACSwitch(mpCxt->mrDoc, false);
+
+ if (aRes.mbNameModified)
+ recompileTokenArray(*pTop);
+
+ // Perform end-listening, start-listening, and dirtying on all
+ // formula cells in the group.
+
+ // Make sure that the start and end listening contexts share the
+ // same block position set, else an invalid iterator may ensue.
+ auto pPosSet = std::make_shared<sc::ColumnBlockPositionSet>(mpCxt->mrDoc);
+
+ sc::StartListeningContext aStartCxt(mpCxt->mrDoc, pPosSet);
+ sc::EndListeningContext aEndCxt(mpCxt->mrDoc, pPosSet, &aOldCode);
+
+ aEndCxt.setPositionDelta(
+ ScAddress(-mpCxt->mnColDelta, -mpCxt->mnRowDelta, -mpCxt->mnTabDelta));
+
+ for (; pp != ppEnd; ++pp)
+ {
+ ScFormulaCell* p = *pp;
+ p->EndListeningTo(aEndCxt);
+ p->StartListeningTo(aStartCxt);
+ p->SetDirty();
+ }
+
+ mbUpdated = true;
+
+ // Move from clipboard is Cut&Paste, then do not copy the original
+ // positions' formula cells to the Undo document.
+ if (!mbClipboardSource || !bCellMoved)
+ fillUndoDoc(aOldPos, rGroup.mnLength, aOldCode);
+ }
+
+ void fillUndoDoc( const ScAddress& rOldPos, SCROW nLength, const ScTokenArray& rOldCode )
+ {
+ if (!mpUndoDoc || nLength <= 0)
+ return;
+
+ // Insert the old formula group into the undo document.
+ ScAddress aUndoPos = rOldPos;
+ ScFormulaCell* pFC = new ScFormulaCell(*mpUndoDoc, aUndoPos, rOldCode.Clone());
+
+ if (nLength == 1)
+ {
+ mpUndoDoc->SetFormulaCell(aUndoPos, pFC);
+ return;
+ }
+
+ std::vector<ScFormulaCell*> aCells;
+ aCells.reserve(nLength);
+ ScFormulaCellGroupRef xGroup = pFC->CreateCellGroup(nLength, false);
+ aCells.push_back(pFC);
+ aUndoPos.IncRow();
+ for (SCROW i = 1; i < nLength; ++i, aUndoPos.IncRow())
+ {
+ pFC = new ScFormulaCell(*mpUndoDoc, aUndoPos, xGroup);
+ aCells.push_back(pFC);
+ }
+
+ if (!mpUndoDoc->SetFormulaCells(rOldPos, aCells))
+ // Insertion failed. Delete all formula cells.
+ std::for_each(aCells.begin(), aCells.end(), std::default_delete<ScFormulaCell>());
+ }
+
+public:
+ UpdateRefOnNonCopy(
+ SCCOL nCol, SCTAB nTab, const sc::RefUpdateContext* pCxt,
+ ScDocument* pUndoDoc) :
+ mnCol(nCol), mnTab(nTab), mpCxt(pCxt),
+ mpUndoDoc(pUndoDoc), mbUpdated(false),
+ mbClipboardSource(pCxt->mrDoc.IsClipboardSource()){}
+
+ void operator() ( sc::FormulaGroupEntry& rGroup )
+ {
+ switch (mpCxt->meMode)
+ {
+ case URM_INSDEL:
+ updateRefOnShift(rGroup);
+ return;
+ case URM_MOVE:
+ updateRefOnMove(rGroup);
+ return;
+ default:
+ ;
+ }
+
+ if (rGroup.mbShared)
+ {
+ ScAddress aUndoPos(mnCol, rGroup.mnRow, mnTab);
+ ScFormulaCell** pp = rGroup.mpCells;
+ ScFormulaCell** ppEnd = pp + rGroup.mnLength;
+ for (; pp != ppEnd; ++pp, aUndoPos.IncRow())
+ {
+ ScFormulaCell* p = *pp;
+ mbUpdated |= p->UpdateReference(*mpCxt, mpUndoDoc, &aUndoPos);
+ }
+ }
+ else
+ {
+ ScAddress aUndoPos(mnCol, rGroup.mnRow, mnTab);
+ mbUpdated |= rGroup.mpCell->UpdateReference(*mpCxt, mpUndoDoc, &aUndoPos);
+ }
+ }
+
+ bool isUpdated() const { return mbUpdated; }
+};
+
+class UpdateRefGroupBoundChecker : public SharedTopFormulaCellPicker
+{
+ const sc::RefUpdateContext& mrCxt;
+ std::vector<SCROW>& mrBounds;
+
+public:
+ UpdateRefGroupBoundChecker(const sc::RefUpdateContext& rCxt, std::vector<SCROW>& rBounds) :
+ mrCxt(rCxt), mrBounds(rBounds) {}
+
+ virtual void processSharedTop( ScFormulaCell** ppCells, size_t /*nRow*/, size_t /*nLength*/ ) override
+ {
+ // Check its tokens and record its reference boundaries.
+ ScFormulaCell& rCell = **ppCells;
+ const ScTokenArray& rCode = *rCell.GetCode();
+ rCode.CheckRelativeReferenceBounds(
+ mrCxt, rCell.aPos, rCell.GetSharedLength(), mrBounds);
+ }
+};
+
+class UpdateRefExpandGroupBoundChecker : public SharedTopFormulaCellPicker
+{
+ const sc::RefUpdateContext& mrCxt;
+ std::vector<SCROW>& mrBounds;
+
+public:
+ UpdateRefExpandGroupBoundChecker(const sc::RefUpdateContext& rCxt, std::vector<SCROW>& rBounds) :
+ mrCxt(rCxt), mrBounds(rBounds) {}
+
+ virtual void processSharedTop( ScFormulaCell** ppCells, size_t /*nRow*/, size_t /*nLength*/ ) override
+ {
+ // Check its tokens and record its reference boundaries.
+ ScFormulaCell& rCell = **ppCells;
+ const ScTokenArray& rCode = *rCell.GetCode();
+ rCode.CheckExpandReferenceBounds(
+ mrCxt, rCell.aPos, rCell.GetSharedLength(), mrBounds);
+ }
+};
+
+class FormulaGroupPicker : public SharedTopFormulaCellPicker
+{
+ std::vector<sc::FormulaGroupEntry>& mrGroups;
+
+public:
+ explicit FormulaGroupPicker( std::vector<sc::FormulaGroupEntry>& rGroups ) : mrGroups(rGroups) {}
+
+ virtual void processNonShared( ScFormulaCell* pCell, size_t nRow ) override
+ {
+ mrGroups.emplace_back(pCell, nRow);
+ }
+
+ virtual void processSharedTop( ScFormulaCell** ppCells, size_t nRow, size_t nLength ) override
+ {
+ mrGroups.emplace_back(ppCells, nRow, nLength);
+ }
+};
+
+}
+
+bool ScColumn::UpdateReferenceOnCopy( sc::RefUpdateContext& rCxt, ScDocument* pUndoDoc )
+{
+ // When copying, the range equals the destination range where cells
+ // are pasted, and the dx, dy, dz refer to the distance from the
+ // source range.
+
+ UpdateRefOnCopy aHandler(rCxt, pUndoDoc);
+ sc::ColumnBlockPosition* blockPos = rCxt.getBlockPosition(nTab, nCol);
+ sc::CellStoreType::position_type aPos = blockPos
+ ? maCells.position(blockPos->miCellPos, rCxt.maRange.aStart.Row())
+ : maCells.position(rCxt.maRange.aStart.Row());
+ sc::ProcessBlock(aPos.first, maCells, aHandler, rCxt.maRange.aStart.Row(), rCxt.maRange.aEnd.Row());
+
+ // The formula groups at the top and bottom boundaries are expected to
+ // have been split prior to this call. Here, we only do the joining.
+ sc::SharedFormulaUtil::joinFormulaCellAbove(aPos);
+ if (rCxt.maRange.aEnd.Row() < GetDoc().MaxRow())
+ {
+ aPos = maCells.position(aPos.first, rCxt.maRange.aEnd.Row()+1);
+ sc::SharedFormulaUtil::joinFormulaCellAbove(aPos);
+ }
+
+ return aHandler.isUpdated();
+}
+
+bool ScColumn::UpdateReference( sc::RefUpdateContext& rCxt, ScDocument* pUndoDoc )
+{
+ if (IsEmptyData() || GetDoc().IsClipOrUndo())
+ // Cells in this column are all empty, or clip or undo doc. No update needed.
+ return false;
+
+ if (rCxt.meMode == URM_COPY)
+ return UpdateReferenceOnCopy(rCxt, pUndoDoc);
+
+ std::vector<SCROW> aBounds;
+
+ bool bThisColShifted = (rCxt.maRange.aStart.Tab() <= nTab && nTab <= rCxt.maRange.aEnd.Tab() &&
+ rCxt.maRange.aStart.Col() <= nCol && nCol <= rCxt.maRange.aEnd.Col());
+ if (bThisColShifted)
+ {
+ // Cells in this column is being shifted. Split formula grouping at
+ // the top and bottom boundaries before they get shifted.
+ // Also, for deleted rows split at the top of the deleted area to adapt
+ // the affected group length.
+ SCROW nSplitPos;
+ if (rCxt.mnRowDelta < 0)
+ {
+ nSplitPos = rCxt.maRange.aStart.Row() + rCxt.mnRowDelta;
+ if (GetDoc().ValidRow(nSplitPos))
+ aBounds.push_back(nSplitPos);
+ }
+ nSplitPos = rCxt.maRange.aStart.Row();
+ if (GetDoc().ValidRow(nSplitPos))
+ {
+ aBounds.push_back(nSplitPos);
+ nSplitPos = rCxt.maRange.aEnd.Row() + 1;
+ if (GetDoc().ValidRow(nSplitPos))
+ aBounds.push_back(nSplitPos);
+ }
+ }
+
+ // Check the row positions at which the group must be split per relative
+ // references.
+ UpdateRefGroupBoundChecker aBoundChecker(rCxt, aBounds);
+ std::for_each(maCells.begin(), maCells.end(), aBoundChecker);
+
+ // If expand reference edges is on, splitting groups may happen anywhere
+ // where a reference points to an adjacent row of the insertion.
+ if (rCxt.mnRowDelta > 0 && rCxt.mrDoc.IsExpandRefs())
+ {
+ UpdateRefExpandGroupBoundChecker aExpandChecker(rCxt, aBounds);
+ std::for_each(maCells.begin(), maCells.end(), aExpandChecker);
+ }
+
+ // Do the actual splitting.
+ const bool bSplit = sc::SharedFormulaUtil::splitFormulaCellGroups(GetDoc(), maCells, aBounds);
+
+ // Collect all formula groups.
+ std::vector<sc::FormulaGroupEntry> aGroups = GetFormulaGroupEntries();
+
+ // Process all collected formula groups.
+ UpdateRefOnNonCopy aHandler(nCol, nTab, &rCxt, pUndoDoc);
+ aHandler = std::for_each(aGroups.begin(), aGroups.end(), aHandler);
+ if (bSplit || aHandler.isUpdated())
+ rCxt.maRegroupCols.set(nTab, nCol);
+
+ return aHandler.isUpdated();
+}
+
+std::vector<sc::FormulaGroupEntry> ScColumn::GetFormulaGroupEntries()
+{
+ std::vector<sc::FormulaGroupEntry> aGroups;
+ std::for_each(maCells.begin(), maCells.end(), FormulaGroupPicker(aGroups));
+ return aGroups;
+}
+
+namespace {
+
+class UpdateTransHandler
+{
+ ScColumn& mrColumn;
+ sc::CellStoreType::iterator miPos;
+ ScRange maSource;
+ ScAddress maDest;
+ ScDocument* mpUndoDoc;
+public:
+ UpdateTransHandler(ScColumn& rColumn, const ScRange& rSource, const ScAddress& rDest, ScDocument* pUndoDoc) :
+ mrColumn(rColumn),
+ miPos(rColumn.GetCellStore().begin()),
+ maSource(rSource), maDest(rDest), mpUndoDoc(pUndoDoc) {}
+
+ void operator() (size_t nRow, ScFormulaCell* pCell)
+ {
+ sc::CellStoreType::position_type aPos = mrColumn.GetCellStore().position(miPos, nRow);
+ miPos = aPos.first;
+ sc::SharedFormulaUtil::unshareFormulaCell(aPos, *pCell);
+ pCell->UpdateTranspose(maSource, maDest, mpUndoDoc);
+ ScColumn::JoinNewFormulaCell(aPos, *pCell);
+ }
+};
+
+class UpdateGrowHandler
+{
+ ScColumn& mrColumn;
+ sc::CellStoreType::iterator miPos;
+ ScRange maArea;
+ SCCOL mnGrowX;
+ SCROW mnGrowY;
+public:
+ UpdateGrowHandler(ScColumn& rColumn, const ScRange& rArea, SCCOL nGrowX, SCROW nGrowY) :
+ mrColumn(rColumn),
+ miPos(rColumn.GetCellStore().begin()),
+ maArea(rArea), mnGrowX(nGrowX), mnGrowY(nGrowY) {}
+
+ void operator() (size_t nRow, ScFormulaCell* pCell)
+ {
+ sc::CellStoreType::position_type aPos = mrColumn.GetCellStore().position(miPos, nRow);
+ miPos = aPos.first;
+ sc::SharedFormulaUtil::unshareFormulaCell(aPos, *pCell);
+ pCell->UpdateGrow(maArea, mnGrowX, mnGrowY);
+ ScColumn::JoinNewFormulaCell(aPos, *pCell);
+ }
+};
+
+class InsertTabUpdater
+{
+ sc::RefUpdateInsertTabContext& mrCxt;
+ sc::CellTextAttrStoreType& mrTextAttrs;
+ sc::CellTextAttrStoreType::iterator miAttrPos;
+ SCTAB mnTab;
+ bool mbModified;
+
+public:
+ InsertTabUpdater(sc::RefUpdateInsertTabContext& rCxt, sc::CellTextAttrStoreType& rTextAttrs, SCTAB nTab) :
+ mrCxt(rCxt),
+ mrTextAttrs(rTextAttrs),
+ miAttrPos(rTextAttrs.begin()),
+ mnTab(nTab),
+ mbModified(false) {}
+
+ void operator() (size_t /*nRow*/, ScFormulaCell* pCell)
+ {
+ pCell->UpdateInsertTab(mrCxt);
+ mbModified = true;
+ }
+
+ void operator() (size_t nRow, EditTextObject* pCell)
+ {
+ editeng::FieldUpdater aUpdater = pCell->GetFieldUpdater();
+ aUpdater.updateTableFields(mnTab);
+ miAttrPos = mrTextAttrs.set(miAttrPos, nRow, sc::CellTextAttr());
+ mbModified = true;
+ }
+
+ bool isModified() const { return mbModified; }
+};
+
+class DeleteTabUpdater
+{
+ sc::RefUpdateDeleteTabContext& mrCxt;
+ sc::CellTextAttrStoreType& mrTextAttrs;
+ sc::CellTextAttrStoreType::iterator miAttrPos;
+ SCTAB mnTab;
+ bool mbModified;
+public:
+ DeleteTabUpdater(sc::RefUpdateDeleteTabContext& rCxt, sc::CellTextAttrStoreType& rTextAttrs, SCTAB nTab) :
+ mrCxt(rCxt),
+ mrTextAttrs(rTextAttrs),
+ miAttrPos(rTextAttrs.begin()),
+ mnTab(nTab),
+ mbModified(false) {}
+
+ void operator() (size_t, ScFormulaCell* pCell)
+ {
+ pCell->UpdateDeleteTab(mrCxt);
+ mbModified = true;
+ }
+
+ void operator() (size_t nRow, EditTextObject* pCell)
+ {
+ editeng::FieldUpdater aUpdater = pCell->GetFieldUpdater();
+ aUpdater.updateTableFields(mnTab);
+ miAttrPos = mrTextAttrs.set(miAttrPos, nRow, sc::CellTextAttr());
+ mbModified = true;
+ }
+
+ bool isModified() const { return mbModified; }
+};
+
+class InsertAbsTabUpdater
+{
+ sc::CellTextAttrStoreType& mrTextAttrs;
+ sc::CellTextAttrStoreType::iterator miAttrPos;
+ SCTAB mnTab;
+ SCTAB mnNewPos;
+ bool mbModified;
+public:
+ InsertAbsTabUpdater(sc::CellTextAttrStoreType& rTextAttrs, SCTAB nTab, SCTAB nNewPos) :
+ mrTextAttrs(rTextAttrs),
+ miAttrPos(rTextAttrs.begin()),
+ mnTab(nTab),
+ mnNewPos(nNewPos),
+ mbModified(false) {}
+
+ void operator() (size_t /*nRow*/, ScFormulaCell* pCell)
+ {
+ pCell->UpdateInsertTabAbs(mnNewPos);
+ mbModified = true;
+ }
+
+ void operator() (size_t nRow, EditTextObject* pCell)
+ {
+ editeng::FieldUpdater aUpdater = pCell->GetFieldUpdater();
+ aUpdater.updateTableFields(mnTab);
+ miAttrPos = mrTextAttrs.set(miAttrPos, nRow, sc::CellTextAttr());
+ mbModified = true;
+ }
+
+ bool isModified() const { return mbModified; }
+};
+
+class MoveTabUpdater
+{
+ sc::RefUpdateMoveTabContext& mrCxt;
+ sc::CellTextAttrStoreType& mrTextAttrs;
+ sc::CellTextAttrStoreType::iterator miAttrPos;
+ SCTAB mnTab;
+ bool mbModified;
+public:
+ MoveTabUpdater(sc::RefUpdateMoveTabContext& rCxt, sc::CellTextAttrStoreType& rTextAttrs, SCTAB nTab) :
+ mrCxt(rCxt),
+ mrTextAttrs(rTextAttrs),
+ miAttrPos(rTextAttrs.begin()),
+ mnTab(nTab),
+ mbModified(false) {}
+
+ void operator() (size_t /*nRow*/, ScFormulaCell* pCell)
+ {
+ pCell->UpdateMoveTab(mrCxt, mnTab);
+ mbModified = true;
+ }
+
+ void operator() (size_t nRow, EditTextObject* pCell)
+ {
+ editeng::FieldUpdater aUpdater = pCell->GetFieldUpdater();
+ aUpdater.updateTableFields(mnTab);
+ miAttrPos = mrTextAttrs.set(miAttrPos, nRow, sc::CellTextAttr());
+ mbModified = true;
+ }
+
+ bool isModified() const { return mbModified; }
+};
+
+class UpdateCompileHandler
+{
+ bool mbForceIfNameInUse:1;
+public:
+ explicit UpdateCompileHandler(bool bForceIfNameInUse) :
+ mbForceIfNameInUse(bForceIfNameInUse) {}
+
+ void operator() (size_t /*nRow*/, ScFormulaCell* pCell)
+ {
+ pCell->UpdateCompile(mbForceIfNameInUse);
+ }
+};
+
+class TabNoSetter
+{
+ SCTAB mnTab;
+public:
+ explicit TabNoSetter(SCTAB nTab) : mnTab(nTab) {}
+
+ void operator() (size_t /*nRow*/, ScFormulaCell* pCell)
+ {
+ pCell->aPos.SetTab(mnTab);
+ }
+};
+
+class UsedRangeNameFinder
+{
+ sc::UpdatedRangeNames& mrIndexes;
+public:
+ explicit UsedRangeNameFinder(sc::UpdatedRangeNames& rIndexes) : mrIndexes(rIndexes) {}
+
+ void operator() (size_t /*nRow*/, const ScFormulaCell* pCell)
+ {
+ pCell->FindRangeNamesInUse(mrIndexes);
+ }
+};
+
+class CheckVectorizationHandler
+{
+public:
+ CheckVectorizationHandler()
+ {}
+
+ void operator() (size_t /*nRow*/, ScFormulaCell* p)
+ {
+ ScTokenArray* pCode = p->GetCode();
+ if (pCode && pCode->IsFormulaVectorDisabled())
+ {
+ pCode->ResetVectorState();
+ FormulaTokenArrayPlainIterator aIter(*pCode);
+ FormulaToken* pFT = aIter.First();
+ while (pFT)
+ {
+ pCode->CheckToken(*pFT);
+ pFT = aIter.Next();
+ }
+ }
+ }
+};
+
+struct SetDirtyVarHandler
+{
+ void operator() (size_t /*nRow*/, ScFormulaCell* p)
+ {
+ p->SetDirtyVar();
+ }
+};
+
+class SetDirtyHandler
+{
+ ScDocument& mrDoc;
+ const sc::SetFormulaDirtyContext& mrCxt;
+public:
+ SetDirtyHandler( ScDocument& rDoc, const sc::SetFormulaDirtyContext& rCxt ) :
+ mrDoc(rDoc), mrCxt(rCxt) {}
+
+ void operator() (size_t /*nRow*/, ScFormulaCell* p)
+ {
+ if (mrCxt.mbClearTabDeletedFlag)
+ {
+ if (!p->IsShared() || p->IsSharedTop())
+ {
+ ScTokenArray* pCode = p->GetCode();
+ pCode->ClearTabDeleted(
+ p->aPos, mrCxt.mnTabDeletedStart, mrCxt.mnTabDeletedEnd);
+ }
+ }
+
+ p->SetDirtyVar();
+ if (!mrDoc.IsInFormulaTree(p))
+ mrDoc.PutInFormulaTree(p);
+ }
+};
+
+class SetDirtyOnRangeHandler
+{
+ sc::SingleColumnSpanSet maValueRanges;
+ ScColumn& mrColumn;
+public:
+ explicit SetDirtyOnRangeHandler(ScColumn& rColumn)
+ : maValueRanges(rColumn.GetDoc().GetSheetLimits()),
+ mrColumn(rColumn) {}
+
+ void operator() (size_t /*nRow*/, ScFormulaCell* p)
+ {
+ p->SetDirty();
+ }
+
+ void operator() (mdds::mtv::element_t type, size_t nTopRow, size_t nDataSize)
+ {
+ if (type == sc::element_type_empty)
+ // Ignore empty blocks.
+ return;
+
+ // Non-formula cells.
+ SCROW nRow1 = nTopRow;
+ SCROW nRow2 = nTopRow + nDataSize - 1;
+ maValueRanges.set(nRow1, nRow2, true);
+ }
+
+ void broadcast()
+ {
+ std::vector<SCROW> aRows;
+ maValueRanges.getRows(aRows);
+ mrColumn.BroadcastCells(aRows, SfxHintId::ScDataChanged);
+ }
+
+ void fillBroadcastSpans( sc::ColumnSpanSet& rBroadcastSpans ) const
+ {
+ SCCOL nCol = mrColumn.GetCol();
+ SCTAB nTab = mrColumn.GetTab();
+ sc::SingleColumnSpanSet::SpansType aSpans;
+ maValueRanges.getSpans(aSpans);
+
+ for (const auto& rSpan : aSpans)
+ rBroadcastSpans.set(mrColumn.GetDoc(), nTab, nCol, rSpan.mnRow1, rSpan.mnRow2, true);
+ }
+};
+
+class SetTableOpDirtyOnRangeHandler
+{
+ sc::SingleColumnSpanSet maValueRanges;
+ ScColumn& mrColumn;
+public:
+ explicit SetTableOpDirtyOnRangeHandler(ScColumn& rColumn)
+ : maValueRanges(rColumn.GetDoc().GetSheetLimits()),
+ mrColumn(rColumn) {}
+
+ void operator() (size_t /*nRow*/, ScFormulaCell* p)
+ {
+ p->SetTableOpDirty();
+ }
+
+ void operator() (mdds::mtv::element_t type, size_t nTopRow, size_t nDataSize)
+ {
+ if (type == sc::element_type_empty)
+ // Ignore empty blocks.
+ return;
+
+ // Non-formula cells.
+ SCROW nRow1 = nTopRow;
+ SCROW nRow2 = nTopRow + nDataSize - 1;
+ maValueRanges.set(nRow1, nRow2, true);
+ }
+
+ void broadcast()
+ {
+ std::vector<SCROW> aRows;
+ maValueRanges.getRows(aRows);
+ mrColumn.BroadcastCells(aRows, SfxHintId::ScTableOpDirty);
+ }
+};
+
+struct SetDirtyAfterLoadHandler
+{
+ void operator() (size_t /*nRow*/, ScFormulaCell* pCell)
+ {
+#if 1
+ // Simply set dirty and append to FormulaTree, without broadcasting,
+ // which is a magnitude faster. This is used to calculate the entire
+ // document, e.g. when loading alien file formats.
+ pCell->SetDirtyAfterLoad();
+#else
+/* This was used with the binary file format that stored results, where only
+ * newly compiled and volatile functions and their dependents had to be
+ * recalculated, which was faster then. Since that was moved to 'binfilter' to
+ * convert to an XML file this isn't needed anymore, and not used for other
+ * file formats. Kept for reference in case mechanism needs to be reactivated
+ * for some file formats, we'd have to introduce a controlling parameter to
+ * this method here then.
+*/
+
+ // If the cell was already dirty because of CalcAfterLoad,
+ // FormulaTracking has to take place.
+ if (pCell->GetDirty())
+ pCell->SetDirty();
+#endif
+ }
+};
+
+struct SetDirtyIfPostponedHandler
+{
+ void operator() (size_t /*nRow*/, ScFormulaCell* pCell)
+ {
+ if (pCell->IsPostponedDirty() || (pCell->HasRelNameReference() != ScFormulaCell::RelNameRef::NONE))
+ pCell->SetDirty();
+ }
+};
+
+struct CalcAllHandler
+{
+#define DEBUG_SC_CHECK_FORMULATREE_CALCULATION 0
+ void operator() (size_t /*nRow*/, ScFormulaCell* pCell)
+ {
+#if DEBUG_SC_CHECK_FORMULATREE_CALCULATION
+ // after F9 ctrl-F9: check the calculation for each FormulaTree
+ double nOldVal, nNewVal;
+ nOldVal = pCell->GetValue();
+#endif
+ pCell->Interpret();
+#if DEBUG_SC_CHECK_FORMULATREE_CALCULATION
+ if (pCell->GetCode()->IsRecalcModeNormal())
+ nNewVal = pCell->GetValue();
+ else
+ nNewVal = nOldVal; // random(), jetzt() etc.
+
+ assert(nOldVal == nNewVal);
+#endif
+ }
+#undef DEBUG_SC_CHECK_FORMULATREE_CALCULATION
+};
+
+class CompileAllHandler
+{
+ sc::CompileFormulaContext& mrCxt;
+public:
+ explicit CompileAllHandler( sc::CompileFormulaContext& rCxt ) : mrCxt(rCxt) {}
+
+ void operator() (size_t /*nRow*/, ScFormulaCell* pCell)
+ {
+ // for unconditional compilation
+ // bCompile=true and pCode->nError=0
+ pCell->GetCode()->SetCodeError(FormulaError::NONE);
+ pCell->SetCompile(true);
+ pCell->CompileTokenArray(mrCxt);
+ }
+};
+
+class CompileXMLHandler
+{
+ sc::CompileFormulaContext& mrCxt;
+ ScProgress& mrProgress;
+ const ScColumn& mrCol;
+public:
+ CompileXMLHandler( sc::CompileFormulaContext& rCxt, ScProgress& rProgress, const ScColumn& rCol) :
+ mrCxt(rCxt),
+ mrProgress(rProgress),
+ mrCol(rCol) {}
+
+ void operator() (size_t nRow, ScFormulaCell* pCell)
+ {
+ sal_uInt32 nFormat = mrCol.GetNumberFormat(mrCol.GetDoc().GetNonThreadedContext(), nRow);
+ if( (nFormat % SV_COUNTRY_LANGUAGE_OFFSET) != 0)
+ // Non-default number format is set.
+ pCell->SetNeedNumberFormat(false);
+ else if (pCell->NeedsNumberFormat())
+ pCell->SetDirtyVar();
+
+ if (pCell->GetMatrixFlag() != ScMatrixMode::NONE)
+ pCell->SetDirtyVar();
+
+ pCell->CompileXML(mrCxt, mrProgress);
+ }
+};
+
+class CompileErrorCellsHandler
+{
+ sc::CompileFormulaContext& mrCxt;
+ ScColumn& mrColumn;
+ sc::CellStoreType::iterator miPos;
+ FormulaError mnErrCode;
+ bool mbCompiled;
+public:
+ CompileErrorCellsHandler( sc::CompileFormulaContext& rCxt, ScColumn& rColumn, FormulaError nErrCode ) :
+ mrCxt(rCxt),
+ mrColumn(rColumn),
+ miPos(mrColumn.GetCellStore().begin()),
+ mnErrCode(nErrCode),
+ mbCompiled(false)
+ {
+ }
+
+ void operator() (size_t nRow, ScFormulaCell* pCell)
+ {
+ FormulaError nCurError = pCell->GetRawError();
+ if (nCurError == FormulaError::NONE)
+ // It's not an error cell. Skip it.
+ return;
+
+ if (mnErrCode != FormulaError::NONE && nCurError != mnErrCode)
+ // Error code is specified, and it doesn't match. Skip it.
+ return;
+
+ sc::CellStoreType::position_type aPos = mrColumn.GetCellStore().position(miPos, nRow);
+ miPos = aPos.first;
+ sc::SharedFormulaUtil::unshareFormulaCell(aPos, *pCell);
+ pCell->GetCode()->SetCodeError(FormulaError::NONE);
+ OUString aFormula = pCell->GetFormula(mrCxt);
+ pCell->Compile(mrCxt, aFormula);
+ ScColumn::JoinNewFormulaCell(aPos, *pCell);
+
+ mbCompiled = true;
+ }
+
+ bool isCompiled() const { return mbCompiled; }
+};
+
+class CalcAfterLoadHandler
+{
+ sc::CompileFormulaContext& mrCxt;
+ bool mbStartListening;
+
+public:
+ CalcAfterLoadHandler( sc::CompileFormulaContext& rCxt, bool bStartListening ) :
+ mrCxt(rCxt), mbStartListening(bStartListening) {}
+
+ void operator() (size_t /*nRow*/, ScFormulaCell* pCell)
+ {
+ pCell->CalcAfterLoad(mrCxt, mbStartListening);
+ }
+};
+
+struct ResetChangedHandler
+{
+ void operator() (size_t /*nRow*/, ScFormulaCell* pCell)
+ {
+ pCell->SetChanged(false);
+ }
+};
+
+/**
+ * Ambiguous script type counts as edit cell.
+ */
+class FindEditCellsHandler
+{
+ ScColumn& mrColumn;
+ sc::CellTextAttrStoreType::iterator miAttrPos;
+ sc::CellStoreType::iterator miCellPos;
+
+public:
+ explicit FindEditCellsHandler(ScColumn& rCol) :
+ mrColumn(rCol),
+ miAttrPos(rCol.GetCellAttrStore().begin()),
+ miCellPos(rCol.GetCellStore().begin()) {}
+
+ bool operator() (size_t, const EditTextObject*)
+ {
+ // This is definitely an edit text cell.
+ return true;
+ }
+
+ bool operator() (size_t nRow, const ScFormulaCell* p)
+ {
+ // With a formula cell, it's considered an edit text cell when either
+ // the result is multi-line or it has more than one script types.
+ SvtScriptType nScriptType = mrColumn.GetRangeScriptType(miAttrPos, nRow, nRow, miCellPos);
+ if (IsAmbiguousScriptNonZero(nScriptType))
+ return true;
+
+ return const_cast<ScFormulaCell*>(p)->IsMultilineResult();
+ }
+
+ /**
+ * Callback for a block of other types.
+ */
+ std::pair<size_t,bool> operator() (const sc::CellStoreType::value_type& node, size_t nOffset, size_t nDataSize)
+ {
+ typedef std::pair<size_t,bool> RetType;
+
+ if (node.type == sc::element_type_empty)
+ // Ignore empty blocks.
+ return RetType(0, false);
+
+ // Check the script type of a non-empty element and see if it has
+ // multiple script types.
+ for (size_t i = 0; i < nDataSize; ++i)
+ {
+ SCROW nRow = node.position + i + nOffset;
+ SvtScriptType nScriptType = mrColumn.GetRangeScriptType(miAttrPos, nRow, nRow, miCellPos);
+ if (IsAmbiguousScriptNonZero(nScriptType))
+ // Return the offset from the first row.
+ return RetType(i+nOffset, true);
+ }
+
+ // No edit text cell found.
+ return RetType(0, false);
+ }
+};
+
+}
+
+void ScColumn::UpdateTranspose( const ScRange& rSource, const ScAddress& rDest,
+ ScDocument* pUndoDoc )
+{
+ UpdateTransHandler aFunc(*this, rSource, rDest, pUndoDoc);
+ sc::ProcessFormula(maCells, aFunc);
+}
+
+void ScColumn::UpdateGrow( const ScRange& rArea, SCCOL nGrowX, SCROW nGrowY )
+{
+ UpdateGrowHandler aFunc(*this, rArea, nGrowX, nGrowY);
+ sc::ProcessFormula(maCells, aFunc);
+}
+
+void ScColumn::UpdateInsertTab( sc::RefUpdateInsertTabContext& rCxt )
+{
+ if (nTab >= rCxt.mnInsertPos)
+ {
+ nTab += rCxt.mnSheets;
+ pAttrArray->SetTab(nTab);
+ }
+
+ UpdateInsertTabOnlyCells(rCxt);
+}
+
+void ScColumn::UpdateInsertTabOnlyCells( sc::RefUpdateInsertTabContext& rCxt )
+{
+ InsertTabUpdater aFunc(rCxt, maCellTextAttrs, nTab);
+ sc::ProcessFormulaEditText(maCells, aFunc);
+ if (aFunc.isModified())
+ CellStorageModified();
+}
+
+void ScColumn::UpdateDeleteTab( sc::RefUpdateDeleteTabContext& rCxt )
+{
+ if (nTab > rCxt.mnDeletePos)
+ {
+ nTab -= rCxt.mnSheets;
+ pAttrArray->SetTab(nTab);
+ }
+
+ DeleteTabUpdater aFunc(rCxt, maCellTextAttrs, nTab);
+ sc::ProcessFormulaEditText(maCells, aFunc);
+ if (aFunc.isModified())
+ CellStorageModified();
+}
+
+void ScColumn::UpdateInsertTabAbs(SCTAB nNewPos)
+{
+ InsertAbsTabUpdater aFunc(maCellTextAttrs, nTab, nNewPos);
+ sc::ProcessFormulaEditText(maCells, aFunc);
+ if (aFunc.isModified())
+ CellStorageModified();
+}
+
+void ScColumn::UpdateMoveTab( sc::RefUpdateMoveTabContext& rCxt, SCTAB nTabNo )
+{
+ nTab = nTabNo;
+ pAttrArray->SetTab( nTabNo );
+
+ MoveTabUpdater aFunc(rCxt, maCellTextAttrs, nTab);
+ sc::ProcessFormulaEditText(maCells, aFunc);
+ if (aFunc.isModified())
+ CellStorageModified();
+}
+
+void ScColumn::UpdateCompile( bool bForceIfNameInUse )
+{
+ UpdateCompileHandler aFunc(bForceIfNameInUse);
+ sc::ProcessFormula(maCells, aFunc);
+}
+
+void ScColumn::SetTabNo(SCTAB nNewTab)
+{
+ nTab = nNewTab;
+ pAttrArray->SetTab( nNewTab );
+
+ TabNoSetter aFunc(nTab);
+ sc::ProcessFormula(maCells, aFunc);
+}
+
+void ScColumn::FindRangeNamesInUse(SCROW nRow1, SCROW nRow2, sc::UpdatedRangeNames& rIndexes) const
+{
+ UsedRangeNameFinder aFunc(rIndexes);
+ sc::ParseFormula(maCells.begin(), maCells, nRow1, nRow2, aFunc);
+}
+
+void ScColumn::SetDirtyVar()
+{
+ SetDirtyVarHandler aFunc;
+ sc::ProcessFormula(maCells, aFunc);
+}
+
+bool ScColumn::IsFormulaDirty( SCROW nRow ) const
+{
+ if (!GetDoc().ValidRow(nRow))
+ return false;
+
+ std::pair<sc::CellStoreType::const_iterator,size_t> aPos = maCells.position(nRow);
+ sc::CellStoreType::const_iterator it = aPos.first;
+ if (it->type != sc::element_type_formula)
+ // This is not a formula cell block.
+ return false;
+
+ const ScFormulaCell* p = sc::formula_block::at(*it->data, aPos.second);
+ return p->GetDirty();
+}
+
+void ScColumn::CheckVectorizationState()
+{
+ sc::AutoCalcSwitch aSwitch(GetDoc(), false);
+ CheckVectorizationHandler aFunc;
+ sc::ProcessFormula(maCells, aFunc);
+}
+
+void ScColumn::SetAllFormulasDirty( const sc::SetFormulaDirtyContext& rCxt )
+{
+ // is only done documentwide, no FormulaTracking
+ sc::AutoCalcSwitch aSwitch(GetDoc(), false);
+ SetDirtyHandler aFunc(GetDoc(), rCxt);
+ sc::ProcessFormula(maCells, aFunc);
+}
+
+void ScColumn::SetDirtyFromClip( SCROW nRow1, SCROW nRow2, sc::ColumnSpanSet& rBroadcastSpans )
+{
+ // Set all formula cells in the range dirty, and pick up all non-formula
+ // cells for later broadcasting. We don't broadcast here.
+ sc::AutoCalcSwitch aSwitch(GetDoc(), false);
+
+ SetDirtyOnRangeHandler aHdl(*this);
+ sc::ProcessFormula(maCells.begin(), maCells, nRow1, nRow2, aHdl, aHdl);
+ aHdl.fillBroadcastSpans(rBroadcastSpans);
+}
+
+namespace {
+
+class BroadcastBroadcastersHandler
+{
+ ScHint maHint;
+ bool mbBroadcasted;
+
+public:
+ explicit BroadcastBroadcastersHandler( SfxHintId nHint, SCTAB nTab, SCCOL nCol )
+ : maHint(nHint, ScAddress(nCol, 0, nTab))
+ , mbBroadcasted(false)
+ {
+ }
+
+ void operator() ( size_t nRow, SvtBroadcaster* pBroadcaster )
+ {
+ maHint.SetAddressRow(nRow);
+ pBroadcaster->Broadcast(maHint);
+ mbBroadcasted = true;
+ }
+
+ bool wasBroadcasted() { return mbBroadcasted; }
+};
+
+}
+
+bool ScColumn::BroadcastBroadcasters( SCROW nRow1, SCROW nRow2, SfxHintId nHint )
+{
+ BroadcastBroadcastersHandler aBroadcasterHdl(nHint, nTab, nCol);
+ sc::ProcessBroadcaster(maBroadcasters.begin(), maBroadcasters, nRow1, nRow2, aBroadcasterHdl);
+ return aBroadcasterHdl.wasBroadcasted();
+}
+
+void ScColumn::SetDirty( SCROW nRow1, SCROW nRow2, BroadcastMode eMode )
+{
+ // broadcasts everything within the range, with FormulaTracking
+ sc::AutoCalcSwitch aSwitch(GetDoc(), false);
+
+ switch (eMode)
+ {
+ case BROADCAST_NONE:
+ {
+ // Handler only used with formula cells.
+ SetDirtyOnRangeHandler aHdl(*this);
+ sc::ProcessFormula(maCells.begin(), maCells, nRow1, nRow2, aHdl);
+ }
+ break;
+ case BROADCAST_DATA_POSITIONS:
+ {
+ // Handler used with both, formula and non-formula cells.
+ SetDirtyOnRangeHandler aHdl(*this);
+ sc::ProcessFormula(maCells.begin(), maCells, nRow1, nRow2, aHdl, aHdl);
+ aHdl.broadcast();
+ }
+ break;
+ case BROADCAST_BROADCASTERS:
+ {
+ // Handler only used with formula cells.
+ SetDirtyOnRangeHandler aHdl(*this);
+ sc::ProcessFormula(maCells.begin(), maCells, nRow1, nRow2, aHdl);
+ // Broadcast all broadcasters in range.
+ if (BroadcastBroadcasters( nRow1, nRow2, SfxHintId::ScDataChanged))
+ {
+ // SetDirtyOnRangeHandler implicitly tracks notified
+ // formulas via ScDocument::Broadcast(), which
+ // BroadcastBroadcastersHandler doesn't, so explicitly
+ // track them here.
+ GetDoc().TrackFormulas();
+ }
+ }
+ break;
+ }
+}
+
+void ScColumn::SetTableOpDirty( const ScRange& rRange )
+{
+ sc::AutoCalcSwitch aSwitch(GetDoc(), false);
+
+ SCROW nRow1 = rRange.aStart.Row(), nRow2 = rRange.aEnd.Row();
+ SetTableOpDirtyOnRangeHandler aHdl(*this);
+ sc::ProcessFormula(maCells.begin(), maCells, nRow1, nRow2, aHdl, aHdl);
+ aHdl.broadcast();
+}
+
+void ScColumn::SetDirtyAfterLoad()
+{
+ sc::AutoCalcSwitch aSwitch(GetDoc(), false);
+ SetDirtyAfterLoadHandler aFunc;
+ sc::ProcessFormula(maCells, aFunc);
+}
+
+namespace {
+
+class RecalcOnRefMoveCollector
+{
+ std::vector<SCROW> maDirtyRows;
+public:
+ void operator() (size_t nRow, ScFormulaCell* pCell)
+ {
+ if (pCell->GetDirty() && pCell->GetCode()->IsRecalcModeOnRefMove())
+ maDirtyRows.push_back(nRow);
+ }
+
+ const std::vector<SCROW>& getDirtyRows() const
+ {
+ return maDirtyRows;
+ }
+};
+
+}
+
+void ScColumn::SetDirtyIfPostponed()
+{
+ sc::AutoCalcSwitch aSwitch(GetDoc(), false);
+ SetDirtyIfPostponedHandler aFunc;
+ ScBulkBroadcast aBulkBroadcast( GetDoc().GetBASM(), SfxHintId::ScDataChanged);
+ sc::ProcessFormula(maCells, aFunc);
+}
+
+void ScColumn::BroadcastRecalcOnRefMove()
+{
+ sc::AutoCalcSwitch aSwitch(GetDoc(), false);
+ RecalcOnRefMoveCollector aFunc;
+ sc::ProcessFormula(maCells, aFunc);
+ BroadcastCells(aFunc.getDirtyRows(), SfxHintId::ScDataChanged);
+}
+
+void ScColumn::CalcAll()
+{
+ CalcAllHandler aFunc;
+ sc::ProcessFormula(maCells, aFunc);
+}
+
+void ScColumn::CompileAll( sc::CompileFormulaContext& rCxt )
+{
+ CompileAllHandler aFunc(rCxt);
+ sc::ProcessFormula(maCells, aFunc);
+}
+
+void ScColumn::CompileXML( sc::CompileFormulaContext& rCxt, ScProgress& rProgress )
+{
+ CompileXMLHandler aFunc(rCxt, rProgress, *this);
+ sc::ProcessFormula(maCells, aFunc);
+ RegroupFormulaCells();
+}
+
+bool ScColumn::CompileErrorCells( sc::CompileFormulaContext& rCxt, FormulaError nErrCode )
+{
+ CompileErrorCellsHandler aHdl(rCxt, *this, nErrCode);
+ sc::ProcessFormula(maCells, aHdl);
+ return aHdl.isCompiled();
+}
+
+void ScColumn::CalcAfterLoad( sc::CompileFormulaContext& rCxt, bool bStartListening )
+{
+ CalcAfterLoadHandler aFunc(rCxt, bStartListening);
+ sc::ProcessFormula(maCells, aFunc);
+}
+
+void ScColumn::ResetChanged( SCROW nStartRow, SCROW nEndRow )
+{
+ ResetChangedHandler aFunc;
+ sc::ProcessFormula(maCells.begin(), maCells, nStartRow, nEndRow, aFunc);
+}
+
+bool ScColumn::HasEditCells(SCROW nStartRow, SCROW nEndRow, SCROW& rFirst)
+{
+ // used in GetOptimalHeight - ambiguous script type counts as edit cell
+
+ FindEditCellsHandler aFunc(*this);
+ std::pair<sc::CellStoreType::const_iterator,size_t> aPos =
+ sc::FindFormulaEditText(maCells, nStartRow, nEndRow, aFunc);
+
+ if (aPos.first == maCells.end())
+ return false;
+
+ rFirst = aPos.first->position + aPos.second;
+ return true;
+}
+
+SCROW ScColumn::SearchStyle(
+ SCROW nRow, const ScStyleSheet* pSearchStyle, bool bUp, bool bInSelection,
+ const ScMarkData& rMark) const
+{
+ if (bInSelection)
+ {
+ if (rMark.IsMultiMarked())
+ {
+ ScMarkArray aArray(rMark.GetMarkArray(nCol));
+ return pAttrArray->SearchStyle(nRow, pSearchStyle, bUp, &aArray);
+ }
+ else
+ return -1;
+ }
+ else
+ return pAttrArray->SearchStyle( nRow, pSearchStyle, bUp );
+}
+
+bool ScColumn::SearchStyleRange(
+ SCROW& rRow, SCROW& rEndRow, const ScStyleSheet* pSearchStyle, bool bUp,
+ bool bInSelection, const ScMarkData& rMark) const
+{
+ if (bInSelection)
+ {
+ if (rMark.IsMultiMarked())
+ {
+ ScMarkArray aArray(rMark.GetMarkArray(nCol));
+ return pAttrArray->SearchStyleRange(
+ rRow, rEndRow, pSearchStyle, bUp, &aArray);
+ }
+ else
+ return false;
+ }
+ else
+ return pAttrArray->SearchStyleRange( rRow, rEndRow, pSearchStyle, bUp );
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sc/source/core/data/column2.cxx b/sc/source/core/data/column2.cxx
new file mode 100644
index 000000000..b7922d740
--- /dev/null
+++ b/sc/source/core/data/column2.cxx
@@ -0,0 +1,3793 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4; fill-column: 100 -*- */
+/*
+ * 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 <column.hxx>
+#include <docsh.hxx>
+#include <scitems.hxx>
+#include <formulacell.hxx>
+#include <document.hxx>
+#include <drwlayer.hxx>
+#include <attarray.hxx>
+#include <patattr.hxx>
+#include <cellform.hxx>
+#include <editutil.hxx>
+#include <subtotal.hxx>
+#include <markdata.hxx>
+#include <fillinfo.hxx>
+#include <segmenttree.hxx>
+#include <docparam.hxx>
+#include <cellvalue.hxx>
+#include <tokenarray.hxx>
+#include <formulagroup.hxx>
+#include <listenercontext.hxx>
+#include <mtvcellfunc.hxx>
+#include <progress.hxx>
+#include <scmatrix.hxx>
+#include <rowheightcontext.hxx>
+#include <tokenstringcontext.hxx>
+#include <sortparam.hxx>
+#include <SparklineGroup.hxx>
+#include <SparklineList.hxx>
+
+#include <editeng/eeitem.hxx>
+#include <o3tl/safeint.hxx>
+#include <o3tl/unit_conversion.hxx>
+#include <svx/algitem.hxx>
+#include <editeng/editobj.hxx>
+#include <editeng/editstat.hxx>
+#include <editeng/emphasismarkitem.hxx>
+#include <editeng/fhgtitem.hxx>
+#include <svx/rotmodit.hxx>
+#include <editeng/unolingu.hxx>
+#include <editeng/justifyitem.hxx>
+#include <svl/numformat.hxx>
+#include <svl/zforlist.hxx>
+#include <svl/broadcast.hxx>
+#include <vcl/outdev.hxx>
+#include <formula/errorcodes.hxx>
+#include <formula/vectortoken.hxx>
+
+#include <algorithm>
+#include <limits>
+#include <memory>
+#include <numeric>
+
+// factor from font size to optimal cell height (text width)
+#define SC_ROT_BREAK_FACTOR 6
+
+static bool IsAmbiguousScript( SvtScriptType nScript )
+{
+ //TODO: move to a header file
+ return ( nScript != SvtScriptType::LATIN &&
+ nScript != SvtScriptType::ASIAN &&
+ nScript != SvtScriptType::COMPLEX );
+}
+
+// Data operations
+
+tools::Long ScColumn::GetNeededSize(
+ SCROW nRow, OutputDevice* pDev, double nPPTX, double nPPTY,
+ const Fraction& rZoomX, const Fraction& rZoomY,
+ bool bWidth, const ScNeededSizeOptions& rOptions,
+ const ScPatternAttr** ppPatternChange, bool bInPrintTwips ) const
+{
+ // If bInPrintTwips is set, the size calculated should be in print twips,
+ // else it should be in pixels.
+
+ // Switch unit to MapTwip instead ? (temporarily and then revert before exit).
+ if (bInPrintTwips)
+ assert(pDev->GetMapMode().GetMapUnit() == MapUnit::MapTwip);
+
+ std::pair<sc::CellStoreType::const_iterator,size_t> aPos = maCells.position(nRow);
+ sc::CellStoreType::const_iterator it = aPos.first;
+ if (it == maCells.end() || it->type == sc::element_type_empty)
+ // Empty cell, or invalid row.
+ return 0;
+
+ tools::Long nValue = 0;
+ ScRefCellValue aCell = GetCellValue(it, aPos.second);
+ double nPPT = bWidth ? nPPTX : nPPTY;
+
+ auto conditionalScaleFunc = [bInPrintTwips](tools::Long nMeasure, double fScale) {
+ return bInPrintTwips ? nMeasure : static_cast<tools::Long>(nMeasure * fScale);
+ };
+
+ const ScPatternAttr* pPattern = rOptions.pPattern;
+ if (!pPattern)
+ pPattern = pAttrArray->GetPattern( nRow );
+
+ // merged?
+ // Do not merge in conditional formatting
+
+ const ScMergeAttr* pMerge = &pPattern->GetItem(ATTR_MERGE);
+ const ScMergeFlagAttr* pFlag = &pPattern->GetItem(ATTR_MERGE_FLAG);
+
+ if ( bWidth )
+ {
+ if ( pFlag->IsHorOverlapped() )
+ return 0;
+ if ( rOptions.bSkipMerged && pMerge->GetColMerge() > 1 )
+ return 0;
+ }
+ else
+ {
+ if ( pFlag->IsVerOverlapped() )
+ return 0;
+ if ( rOptions.bSkipMerged && pMerge->GetRowMerge() > 1 )
+ return 0;
+ }
+
+ // conditional formatting
+ ScDocument& rDocument = GetDoc();
+ const SfxItemSet* pCondSet = rDocument.GetCondResult( nCol, nRow, nTab );
+
+ //The pPattern may change in GetCondResult
+ if (aCell.meType == CELLTYPE_FORMULA)
+ {
+ pPattern = pAttrArray->GetPattern( nRow );
+ if (ppPatternChange)
+ *ppPatternChange = pPattern;
+ }
+ // line break?
+
+ const SvxHorJustifyItem* pCondItem;
+ SvxCellHorJustify eHorJust;
+ if (pCondSet && (pCondItem = pCondSet->GetItemIfSet(ATTR_HOR_JUSTIFY)) )
+ eHorJust = pCondItem->GetValue();
+ else
+ eHorJust = pPattern->GetItem( ATTR_HOR_JUSTIFY ).GetValue();
+ bool bBreak;
+ const ScLineBreakCell* pLineBreakCell;
+ if ( eHorJust == SvxCellHorJustify::Block )
+ bBreak = true;
+ else if ( pCondSet && (pLineBreakCell = pCondSet->GetItemIfSet(ATTR_LINEBREAK)) )
+ bBreak = pLineBreakCell->GetValue();
+ else
+ bBreak = pPattern->GetItem(ATTR_LINEBREAK).GetValue();
+
+ SvNumberFormatter* pFormatter = rDocument.GetFormatTable();
+ sal_uInt32 nFormat = pPattern->GetNumberFormat( pFormatter, pCondSet );
+
+ // get "cell is value" flag
+ // Must be synchronized with ScOutputData::LayoutStrings()
+ bool bCellIsValue = (aCell.meType == CELLTYPE_VALUE);
+ if (aCell.meType == CELLTYPE_FORMULA)
+ {
+ ScFormulaCell* pFCell = aCell.mpFormula;
+ bCellIsValue = pFCell->IsRunning() || pFCell->IsValue();
+ }
+
+ // #i111387#, tdf#121040: disable automatic line breaks for all number formats
+ if (bBreak && bCellIsValue && (pFormatter->GetType(nFormat) == SvNumFormatType::NUMBER))
+ {
+ // If a formula cell needs to be interpreted during aCell.hasNumeric()
+ // to determine the type, the pattern may get invalidated because the
+ // result may set a number format. In which case there's also the
+ // General format not set anymore...
+ bool bMayInvalidatePattern = (aCell.meType == CELLTYPE_FORMULA);
+ const ScPatternAttr* pOldPattern = pPattern;
+ bool bNumeric = aCell.hasNumeric();
+ if (bMayInvalidatePattern)
+ {
+ pPattern = pAttrArray->GetPattern( nRow );
+ if (ppPatternChange)
+ *ppPatternChange = pPattern; // XXX caller may have to check for change!
+ }
+ if (bNumeric)
+ {
+ if (!bMayInvalidatePattern || pPattern == pOldPattern)
+ bBreak = false;
+ else
+ {
+ nFormat = pPattern->GetNumberFormat( pFormatter, pCondSet );
+ if (pFormatter->GetType(nFormat) == SvNumFormatType::NUMBER)
+ bBreak = false;
+ }
+ }
+ }
+
+ // get other attributes from pattern and conditional formatting
+
+ SvxCellOrientation eOrient = pPattern->GetCellOrientation( pCondSet );
+ bool bAsianVertical = ( eOrient == SvxCellOrientation::Stacked &&
+ pPattern->GetItem( ATTR_VERTICAL_ASIAN, pCondSet ).GetValue() );
+ if ( bAsianVertical )
+ bBreak = false;
+
+ if ( bWidth && bBreak ) // after determining bAsianVertical (bBreak may be reset)
+ return 0;
+
+ Degree100 nRotate(0);
+ SvxRotateMode eRotMode = SVX_ROTATE_MODE_STANDARD;
+ if ( eOrient == SvxCellOrientation::Standard )
+ {
+ const ScRotateValueItem* pRotateValueItem;
+ if (pCondSet &&
+ (pRotateValueItem = pCondSet->GetItemIfSet(ATTR_ROTATE_VALUE)) )
+ nRotate = pRotateValueItem->GetValue();
+ else
+ nRotate = pPattern->GetItem(ATTR_ROTATE_VALUE).GetValue();
+ if ( nRotate )
+ {
+ const SvxRotateModeItem* pRotateModeItem;
+ if (pCondSet &&
+ (pRotateModeItem = pCondSet->GetItemIfSet(ATTR_ROTATE_MODE)) )
+ eRotMode = pRotateModeItem->GetValue();
+ else
+ eRotMode = pPattern->GetItem(ATTR_ROTATE_MODE).GetValue();
+
+ if ( nRotate == 18000_deg100 )
+ eRotMode = SVX_ROTATE_MODE_STANDARD; // no overflow
+ }
+ }
+
+ if ( eHorJust == SvxCellHorJustify::Repeat )
+ {
+ // ignore orientation/rotation if "repeat" is active
+ eOrient = SvxCellOrientation::Standard;
+ nRotate = 0_deg100;
+ bAsianVertical = false;
+ }
+
+ const SvxMarginItem* pMargin;
+ if (pCondSet &&
+ (pMargin = pCondSet->GetItemIfSet(ATTR_MARGIN)) )
+ ;
+ else
+ pMargin = &pPattern->GetItem(ATTR_MARGIN);
+ sal_uInt16 nIndent = 0;
+ if ( eHorJust == SvxCellHorJustify::Left )
+ {
+ const ScIndentItem* pIndentItem;
+ if (pCondSet &&
+ (pIndentItem = pCondSet->GetItemIfSet(ATTR_INDENT)) )
+ nIndent = pIndentItem->GetValue();
+ else
+ nIndent = pPattern->GetItem(ATTR_INDENT).GetValue();
+ }
+
+ SvtScriptType nScript = rDocument.GetScriptType(nCol, nRow, nTab);
+ if (nScript == SvtScriptType::NONE) nScript = ScGlobal::GetDefaultScriptType();
+
+ // also call SetFont for edit cells, because bGetFont may be set only once
+ // bGetFont is set also if script type changes
+ if (rOptions.bGetFont)
+ {
+ Fraction aFontZoom = ( eOrient == SvxCellOrientation::Standard ) ? rZoomX : rZoomY;
+ vcl::Font aFont;
+ // font color doesn't matter here
+ pPattern->GetFont( aFont, SC_AUTOCOL_BLACK, pDev, &aFontZoom, pCondSet, nScript );
+ pDev->SetFont(aFont);
+ }
+
+ bool bAddMargin = true;
+ CellType eCellType = aCell.meType;
+
+ bool bEditEngine = (eCellType == CELLTYPE_EDIT ||
+ eOrient == SvxCellOrientation::Stacked ||
+ IsAmbiguousScript(nScript) ||
+ ((eCellType == CELLTYPE_FORMULA) && aCell.mpFormula->IsMultilineResult()));
+
+ if (!bEditEngine) // direct output
+ {
+ const Color* pColor;
+ OUString aValStr = ScCellFormat::GetString(
+ aCell, nFormat, &pColor, *pFormatter, rDocument, true, rOptions.bFormula);
+
+ if (!aValStr.isEmpty())
+ {
+ // SetFont is moved up
+
+ Size aSize( pDev->GetTextWidth( aValStr ), pDev->GetTextHeight() );
+ if ( eOrient != SvxCellOrientation::Standard )
+ {
+ tools::Long nTemp = aSize.Width();
+ aSize.setWidth( aSize.Height() );
+ aSize.setHeight( nTemp );
+ }
+ else if ( nRotate )
+ {
+ //TODO: take different X/Y scaling into consideration
+
+ double nRealOrient = toRadians(nRotate);
+ double nCosAbs = fabs( cos( nRealOrient ) );
+ double nSinAbs = fabs( sin( nRealOrient ) );
+ tools::Long nHeight = static_cast<tools::Long>( aSize.Height() * nCosAbs + aSize.Width() * nSinAbs );
+ tools::Long nWidth;
+ if ( eRotMode == SVX_ROTATE_MODE_STANDARD )
+ nWidth = static_cast<tools::Long>( aSize.Width() * nCosAbs + aSize.Height() * nSinAbs );
+ else if ( rOptions.bTotalSize )
+ {
+ nWidth = conditionalScaleFunc(rDocument.GetColWidth( nCol,nTab ), nPPT);
+ bAddMargin = false;
+ // only to the right:
+ //TODO: differ on direction up/down (only Text/whole height)
+ if ( pPattern->GetRotateDir( pCondSet ) == ScRotateDir::Right )
+ nWidth += static_cast<tools::Long>( rDocument.GetRowHeight( nRow,nTab ) *
+ (bInPrintTwips ? 1.0 : nPPT) * nCosAbs / nSinAbs );
+ }
+ else
+ nWidth = static_cast<tools::Long>( aSize.Height() / nSinAbs ); //TODO: limit?
+
+ if ( bBreak && !rOptions.bTotalSize )
+ {
+ // limit size for line break
+ tools::Long nCmp = pDev->GetFont().GetFontSize().Height() * SC_ROT_BREAK_FACTOR;
+ if ( nHeight > nCmp )
+ nHeight = nCmp;
+ }
+
+ aSize = Size( nWidth, nHeight );
+ }
+ nValue = bWidth ? aSize.Width() : aSize.Height();
+
+ if ( bAddMargin )
+ {
+ if (bWidth)
+ {
+ nValue += conditionalScaleFunc(pMargin->GetLeftMargin(), nPPT) +
+ conditionalScaleFunc(pMargin->GetRightMargin(), nPPT);
+ if ( nIndent )
+ nValue += conditionalScaleFunc(nIndent, nPPT);
+ }
+ else
+ nValue += conditionalScaleFunc(pMargin->GetTopMargin(), nPPT) +
+ conditionalScaleFunc(pMargin->GetBottomMargin(), nPPT);
+ }
+
+ // linebreak done ?
+
+ if ( bBreak && !bWidth )
+ {
+ // test with EditEngine the safety at 90%
+ // (due to rounding errors and because EditEngine formats partially differently)
+
+ tools::Long nDocSize = conditionalScaleFunc((rDocument.GetColWidth( nCol,nTab ) -
+ pMargin->GetLeftMargin() - pMargin->GetRightMargin() -
+ nIndent), nPPTX);
+ nDocSize = (nDocSize * 9) / 10; // for safety
+ if ( aSize.Width() > nDocSize )
+ bEditEngine = true;
+ }
+ }
+ }
+
+ if (bEditEngine)
+ {
+ // the font is not reset each time with !bEditEngine
+ vcl::Font aOldFont = pDev->GetFont();
+
+ MapMode aHMMMode( MapUnit::Map100thMM, Point(), rZoomX, rZoomY );
+
+ // save in document ?
+ std::unique_ptr<ScFieldEditEngine> pEngine = rDocument.CreateFieldEditEngine();
+
+ const bool bPrevUpdateLayout = pEngine->SetUpdateLayout( false );
+ bool bTextWysiwyg = ( pDev->GetOutDevType() == OUTDEV_PRINTER );
+ EEControlBits nCtrl = pEngine->GetControlWord();
+ if ( bTextWysiwyg )
+ nCtrl |= EEControlBits::FORMAT100;
+ else
+ nCtrl &= ~EEControlBits::FORMAT100;
+ pEngine->SetControlWord( nCtrl );
+ MapMode aOld = pDev->GetMapMode();
+ pDev->SetMapMode( aHMMMode );
+ pEngine->SetRefDevice( pDev );
+ rDocument.ApplyAsianEditSettings( *pEngine );
+ SfxItemSet aSet( pEngine->GetEmptyItemSet() );
+ if ( ScStyleSheet* pPreviewStyle = rDocument.GetPreviewCellStyle( nCol, nRow, nTab ) )
+ {
+ ScPatternAttr aPreviewPattern( *pPattern );
+ aPreviewPattern.SetStyleSheet(pPreviewStyle);
+ aPreviewPattern.FillEditItemSet( &aSet, pCondSet );
+ }
+ else
+ {
+ SfxItemSet* pFontSet = rDocument.GetPreviewFont( nCol, nRow, nTab );
+ pPattern->FillEditItemSet( &aSet, pFontSet ? pFontSet : pCondSet );
+ }
+// no longer needed, are set with the text (is faster)
+// pEngine->SetDefaults( pSet );
+
+ if ( aSet.Get(EE_PARA_HYPHENATE).GetValue() ) {
+
+ css::uno::Reference<css::linguistic2::XHyphenator> xXHyphenator( LinguMgr::GetHyphenator() );
+ pEngine->SetHyphenator( xXHyphenator );
+ }
+
+ Size aPaper( 1000000, 1000000 );
+ if ( eOrient==SvxCellOrientation::Stacked && !bAsianVertical )
+ aPaper.setWidth( 1 );
+ else if (bBreak)
+ {
+ double fWidthFactor = bInPrintTwips ? 1.0 : nPPTX;
+ if ( bTextWysiwyg )
+ {
+ // if text is formatted for printer, don't use PixelToLogic,
+ // to ensure the exact same paper width (and same line breaks) as in
+ // ScEditUtil::GetEditArea, used for output.
+
+ fWidthFactor = o3tl::convert(1.0, o3tl::Length::twip, o3tl::Length::mm100);
+ }
+
+ // use original width for hidden columns:
+ tools::Long nDocWidth = static_cast<tools::Long>( rDocument.GetOriginalWidth(nCol,nTab) * fWidthFactor );
+ SCCOL nColMerge = pMerge->GetColMerge();
+ if (nColMerge > 1)
+ for (SCCOL nColAdd=1; nColAdd<nColMerge; nColAdd++)
+ nDocWidth += static_cast<tools::Long>( rDocument.GetColWidth(nCol+nColAdd,nTab) * fWidthFactor );
+ nDocWidth -= static_cast<tools::Long>( pMargin->GetLeftMargin() * fWidthFactor )
+ + static_cast<tools::Long>( pMargin->GetRightMargin() * fWidthFactor )
+ + 1; // output size is width-1 pixel (due to gridline)
+ if ( nIndent )
+ nDocWidth -= static_cast<tools::Long>( nIndent * fWidthFactor );
+
+ // space for AutoFilter button: 20 * nZoom/100
+ constexpr tools::Long nFilterButtonWidthPix = 20; // Autofilter pixel width at 100% zoom.
+ if ( pFlag->HasAutoFilter() && !bTextWysiwyg )
+ nDocWidth -= bInPrintTwips ? o3tl::convert(nFilterButtonWidthPix, o3tl::Length::px,
+ o3tl::Length::twip)
+ : tools::Long(rZoomX * nFilterButtonWidthPix);
+
+ aPaper.setWidth( nDocWidth );
+
+ if ( !bTextWysiwyg )
+ {
+ aPaper = bInPrintTwips ?
+ o3tl::convert(aPaper, o3tl::Length::twip, o3tl::Length::mm100) :
+ pDev->PixelToLogic(aPaper, aHMMMode);
+ }
+ }
+ pEngine->SetPaperSize(aPaper);
+
+ if (aCell.meType == CELLTYPE_EDIT)
+ {
+ pEngine->SetTextNewDefaults(*aCell.mpEditText, std::move(aSet));
+ }
+ else
+ {
+ const Color* pColor;
+ OUString aString = ScCellFormat::GetString(
+ aCell, nFormat, &pColor, *pFormatter, rDocument, true,
+ rOptions.bFormula);
+
+ if (!aString.isEmpty())
+ pEngine->SetTextNewDefaults(aString, std::move(aSet));
+ else
+ pEngine->SetDefaults(std::move(aSet));
+ }
+
+ bool bEngineVertical = pEngine->IsEffectivelyVertical();
+ pEngine->SetVertical( bAsianVertical );
+ pEngine->SetUpdateLayout( bPrevUpdateLayout );
+
+ bool bEdWidth = bWidth;
+ if ( eOrient != SvxCellOrientation::Standard && eOrient != SvxCellOrientation::Stacked )
+ bEdWidth = !bEdWidth;
+ if ( nRotate )
+ {
+ //TODO: take different X/Y scaling into consideration
+
+ Size aSize( pEngine->CalcTextWidth(), pEngine->GetTextHeight() );
+ double nRealOrient = toRadians(nRotate);
+ double nCosAbs = fabs( cos( nRealOrient ) );
+ double nSinAbs = fabs( sin( nRealOrient ) );
+ tools::Long nHeight = static_cast<tools::Long>( aSize.Height() * nCosAbs + aSize.Width() * nSinAbs );
+ tools::Long nWidth;
+ if ( eRotMode == SVX_ROTATE_MODE_STANDARD )
+ nWidth = static_cast<tools::Long>( aSize.Width() * nCosAbs + aSize.Height() * nSinAbs );
+ else if ( rOptions.bTotalSize )
+ {
+ nWidth = conditionalScaleFunc(rDocument.GetColWidth( nCol,nTab ), nPPT);
+ bAddMargin = false;
+ if ( pPattern->GetRotateDir( pCondSet ) == ScRotateDir::Right )
+ nWidth += static_cast<tools::Long>( rDocument.GetRowHeight( nRow,nTab ) *
+ (bInPrintTwips ? 1.0 : nPPT) * nCosAbs / nSinAbs );
+ }
+ else
+ nWidth = static_cast<tools::Long>( aSize.Height() / nSinAbs ); //TODO: limit?
+ aSize = Size( nWidth, nHeight );
+
+ Size aTextSize = bInPrintTwips ?
+ o3tl::toTwips(aSize, o3tl::Length::mm100) :
+ pDev->LogicToPixel(aSize, aHMMMode);
+
+ if ( bEdWidth )
+ nValue = aTextSize.Width();
+ else
+ {
+ nValue = aTextSize.Height();
+
+ if ( bBreak && !rOptions.bTotalSize )
+ {
+ // limit size for line break
+ tools::Long nCmp = aOldFont.GetFontSize().Height() * SC_ROT_BREAK_FACTOR;
+ if ( nValue > nCmp )
+ nValue = nCmp;
+ }
+ }
+ }
+ else if ( bEdWidth )
+ {
+ if (bBreak)
+ nValue = 0;
+ else
+ {
+ sal_uInt32 aTextSize(pEngine->CalcTextWidth());
+ nValue = bInPrintTwips ?
+ o3tl::toTwips(aTextSize, o3tl::Length::mm100) :
+ pDev->LogicToPixel(Size(aTextSize, 0), aHMMMode).Width();
+ }
+ }
+ else // height
+ {
+ sal_uInt32 aTextSize(pEngine->GetTextHeight());
+ nValue = bInPrintTwips ?
+ o3tl::toTwips(aTextSize, o3tl::Length::mm100) :
+ pDev->LogicToPixel(Size(0, aTextSize), aHMMMode).Height();
+
+ // With non-100% zoom and several lines or paragraphs, don't shrink below the result with FORMAT100 set
+ if ( !bTextWysiwyg && ( rZoomY.GetNumerator() != 1 || rZoomY.GetDenominator() != 1 ) &&
+ ( pEngine->GetParagraphCount() > 1 || ( bBreak && pEngine->GetLineCount(0) > 1 ) ) )
+ {
+ pEngine->SetControlWord( nCtrl | EEControlBits::FORMAT100 );
+ pEngine->QuickFormatDoc( true );
+ aTextSize = pEngine->GetTextHeight();
+ tools::Long nSecondValue = bInPrintTwips ?
+ o3tl::toTwips(aTextSize, o3tl::Length::mm100) :
+ pDev->LogicToPixel(Size(0, aTextSize), aHMMMode).Height();
+ if ( nSecondValue > nValue )
+ nValue = nSecondValue;
+ }
+ }
+
+ if ( nValue && bAddMargin )
+ {
+ if (bWidth)
+ {
+ nValue += conditionalScaleFunc(pMargin->GetLeftMargin(), nPPT) +
+ conditionalScaleFunc(pMargin->GetRightMargin(), nPPT);
+ if (nIndent)
+ nValue += conditionalScaleFunc(nIndent, nPPT);
+ }
+ else
+ {
+ nValue += conditionalScaleFunc(pMargin->GetTopMargin(), nPPT) +
+ conditionalScaleFunc(pMargin->GetBottomMargin(), nPPT);
+
+ if ( bAsianVertical && pDev->GetOutDevType() != OUTDEV_PRINTER )
+ {
+ // add 1pt extra (default margin value) for line breaks with SetVertical
+ constexpr tools::Long nDefaultMarginInPoints = 1;
+ nValue += conditionalScaleFunc(
+ o3tl::convert(nDefaultMarginInPoints, o3tl::Length::pt, o3tl::Length::twip),
+ nPPT);
+ }
+ }
+ }
+
+ // EditEngine is cached and re-used, so the old vertical flag must be restored
+ pEngine->SetVertical( bEngineVertical );
+
+ rDocument.DisposeFieldEditEngine(pEngine);
+
+ pDev->SetMapMode( aOld );
+ pDev->SetFont( aOldFont );
+ }
+
+ if (bWidth)
+ {
+ // place for Autofilter Button
+ // 20 * nZoom/100
+ // Conditional formatting is not interesting here
+ constexpr tools::Long nFilterButtonWidthPix = 20; // Autofilter pixel width at 100% zoom.
+ ScMF nFlags = pPattern->GetItem(ATTR_MERGE_FLAG).GetValue();
+ if (nFlags & ScMF::Auto)
+ nValue += bInPrintTwips ? o3tl::convert(nFilterButtonWidthPix, o3tl::Length::px,
+ o3tl::Length::twip)
+ : tools::Long(rZoomX * nFilterButtonWidthPix);
+ }
+
+ return nValue;
+}
+
+namespace {
+
+class MaxStrLenFinder
+{
+ ScDocument& mrDoc;
+ sal_uInt32 mnFormat;
+ OUString maMaxLenStr;
+ sal_Int32 mnMaxLen;
+
+ // tdf#59820 - search for the longest substring in a multiline string
+ void checkLineBreak(const OUString& aStrVal)
+ {
+ sal_Int32 nFromIndex = 0;
+ sal_Int32 nToIndex = aStrVal.indexOf('\n', nFromIndex);
+ // if there is no line break, just take the length of the entire string
+ if (nToIndex == -1)
+ {
+ mnMaxLen = aStrVal.getLength();
+ maMaxLenStr = aStrVal;
+ }
+ else
+ {
+ sal_Int32 nMaxLen = 0;
+ // search for the longest substring in the multiline string
+ while (nToIndex != -1)
+ {
+ if (nMaxLen < nToIndex - nFromIndex)
+ {
+ nMaxLen = nToIndex - nFromIndex;
+ }
+ nFromIndex = nToIndex + 1;
+ nToIndex = aStrVal.indexOf('\n', nFromIndex);
+ }
+ // take into consideration the last part of multiline string
+ nToIndex = aStrVal.getLength() - nFromIndex;
+ if (nMaxLen < nToIndex)
+ {
+ nMaxLen = nToIndex;
+ }
+ // assign new maximum including its substring
+ if (mnMaxLen < nMaxLen)
+ {
+ mnMaxLen = nMaxLen;
+ maMaxLenStr = aStrVal.subView(nFromIndex);
+ }
+ }
+ }
+
+ void checkLength(const ScRefCellValue& rCell)
+ {
+ const Color* pColor;
+ OUString aValStr = ScCellFormat::GetString(
+ rCell, mnFormat, &pColor, *mrDoc.GetFormatTable(), mrDoc);
+
+ if (aValStr.getLength() <= mnMaxLen)
+ return;
+
+ switch (rCell.meType)
+ {
+ case CELLTYPE_NONE:
+ case CELLTYPE_VALUE:
+ mnMaxLen = aValStr.getLength();
+ maMaxLenStr = aValStr;
+ break;
+ case CELLTYPE_EDIT:
+ case CELLTYPE_STRING:
+ case CELLTYPE_FORMULA:
+ default:
+ checkLineBreak(aValStr);
+ }
+ }
+
+public:
+ MaxStrLenFinder(ScDocument& rDoc, sal_uInt32 nFormat) :
+ mrDoc(rDoc), mnFormat(nFormat), mnMaxLen(0) {}
+
+ void operator() (size_t /*nRow*/, double f)
+ {
+ ScRefCellValue aCell(f);
+ checkLength(aCell);
+ }
+
+ void operator() (size_t /*nRow*/, const svl::SharedString& rSS)
+ {
+ if (rSS.getLength() > mnMaxLen)
+ {
+ checkLineBreak(rSS.getString());
+ }
+ }
+
+ void operator() (size_t /*nRow*/, const EditTextObject* p)
+ {
+ ScRefCellValue aCell(p);
+ checkLength(aCell);
+ }
+
+ void operator() (size_t /*nRow*/, const ScFormulaCell* p)
+ {
+ ScRefCellValue aCell(const_cast<ScFormulaCell*>(p));
+ checkLength(aCell);
+ }
+
+ const OUString& getMaxLenStr() const { return maMaxLenStr; }
+};
+
+}
+
+sal_uInt16 ScColumn::GetOptimalColWidth(
+ OutputDevice* pDev, double nPPTX, double nPPTY, const Fraction& rZoomX, const Fraction& rZoomY,
+ bool bFormula, sal_uInt16 nOldWidth, const ScMarkData* pMarkData, const ScColWidthParam* pParam) const
+{
+ if (maCells.block_size() == 1 && maCells.begin()->type == sc::element_type_empty)
+ // All cells are empty.
+ return nOldWidth;
+
+ sc::SingleColumnSpanSet aSpanSet(GetDoc().GetSheetLimits());
+ sc::SingleColumnSpanSet::SpansType aMarkedSpans;
+ if (pMarkData && (pMarkData->IsMarked() || pMarkData->IsMultiMarked()))
+ {
+ aSpanSet.scan(*pMarkData, nTab, nCol);
+ aSpanSet.getSpans(aMarkedSpans);
+ }
+ else
+ // "Select" the entire column if no selection exists.
+ aMarkedSpans.emplace_back(0, GetDoc().MaxRow());
+
+ sal_uInt16 nWidth = static_cast<sal_uInt16>(nOldWidth*nPPTX);
+ bool bFound = false;
+ ScDocument& rDocument = GetDoc();
+
+ if ( pParam && pParam->mbSimpleText )
+ { // all the same except for number format
+ SCROW nRow = 0;
+ const ScPatternAttr* pPattern = GetPattern( nRow );
+ vcl::Font aFont;
+ // font color doesn't matter here
+ pPattern->GetFont( aFont, SC_AUTOCOL_BLACK, pDev, &rZoomX );
+ pDev->SetFont( aFont );
+ const SvxMarginItem* pMargin = &pPattern->GetItem(ATTR_MARGIN);
+ tools::Long nMargin = static_cast<tools::Long>( pMargin->GetLeftMargin() * nPPTX ) +
+ static_cast<tools::Long>( pMargin->GetRightMargin() * nPPTX );
+
+ // Try to find the row that has the longest string, and measure the width of that string.
+ SvNumberFormatter* pFormatter = rDocument.GetFormatTable();
+ sal_uInt32 nFormat = pPattern->GetNumberFormat( pFormatter );
+ while ((nFormat % SV_COUNTRY_LANGUAGE_OFFSET) == 0 && nRow <= 2)
+ {
+ // This is often used with CSV import or other data having a header
+ // row; if there is no specific format set try next row for actual
+ // data format.
+ // Or again in case there was a leading sep=";" row or two header
+ // rows..
+ const ScPatternAttr* pNextPattern = GetPattern( ++nRow );
+ if (pNextPattern != pPattern)
+ nFormat = pNextPattern->GetNumberFormat( pFormatter );
+ }
+ OUString aLongStr;
+ const Color* pColor;
+ if (pParam->mnMaxTextRow >= 0)
+ {
+ ScRefCellValue aCell = GetCellValue(pParam->mnMaxTextRow);
+ aLongStr = ScCellFormat::GetString(
+ aCell, nFormat, &pColor, *pFormatter, rDocument);
+ }
+ else
+ {
+ // Go though all non-empty cells within selection.
+ MaxStrLenFinder aFunc(rDocument, nFormat);
+ sc::CellStoreType::const_iterator itPos = maCells.begin();
+ for (const auto& rMarkedSpan : aMarkedSpans)
+ itPos = sc::ParseAllNonEmpty(itPos, maCells, rMarkedSpan.mnRow1, rMarkedSpan.mnRow2, aFunc);
+
+ aLongStr = aFunc.getMaxLenStr();
+ }
+
+ if (!aLongStr.isEmpty())
+ {
+ nWidth = pDev->GetTextWidth(aLongStr) + static_cast<sal_uInt16>(nMargin);
+ bFound = true;
+ }
+ }
+ else
+ {
+ ScNeededSizeOptions aOptions;
+ aOptions.bFormula = bFormula;
+ const ScPatternAttr* pOldPattern = nullptr;
+
+ // Go though all non-empty cells within selection.
+ sc::CellStoreType::const_iterator itPos = maCells.begin();
+ for (const auto& rMarkedSpan : aMarkedSpans)
+ {
+ SCROW nRow1 = rMarkedSpan.mnRow1, nRow2 = rMarkedSpan.mnRow2;
+ SCROW nRow = nRow1;
+ while (nRow <= nRow2)
+ {
+ std::pair<sc::CellStoreType::const_iterator,size_t> aPos = maCells.position(itPos, nRow);
+ itPos = aPos.first;
+ if (itPos->type == sc::element_type_empty)
+ {
+ // Skip empty cells.
+ nRow += itPos->size - aPos.second;
+ continue;
+ }
+
+ for (size_t nOffset = aPos.second; nOffset < itPos->size; ++nOffset, ++nRow)
+ {
+ SvtScriptType nScript = rDocument.GetScriptType(nCol, nRow, nTab);
+ if (nScript == SvtScriptType::NONE)
+ nScript = ScGlobal::GetDefaultScriptType();
+
+ const ScPatternAttr* pPattern = GetPattern(nRow);
+ aOptions.pPattern = pPattern;
+ aOptions.bGetFont = (pPattern != pOldPattern || nScript != SvtScriptType::NONE);
+ pOldPattern = pPattern;
+ sal_uInt16 nThis = static_cast<sal_uInt16>(GetNeededSize(
+ nRow, pDev, nPPTX, nPPTY, rZoomX, rZoomY, true, aOptions, &pOldPattern));
+ if (nThis && (nThis > nWidth || !bFound))
+ {
+ nWidth = nThis;
+ bFound = true;
+ }
+ }
+ }
+ }
+ }
+
+ if (bFound)
+ {
+ nWidth += 2;
+ sal_uInt16 nTwips = static_cast<sal_uInt16>(
+ std::min(nWidth / nPPTX, std::numeric_limits<sal_uInt16>::max() / 2.0));
+ return nTwips;
+ }
+ else
+ return nOldWidth;
+}
+
+static sal_uInt16 lcl_GetAttribHeight( const ScPatternAttr& rPattern, sal_uInt16 nFontHeightId )
+{
+ const SvxFontHeightItem& rFontHeight =
+ static_cast<const SvxFontHeightItem&>(rPattern.GetItem(nFontHeightId));
+
+ sal_uInt16 nHeight = rFontHeight.GetHeight();
+ nHeight *= 1.18;
+
+ if ( rPattern.GetItem(ATTR_FONT_EMPHASISMARK).GetEmphasisMark() != FontEmphasisMark::NONE )
+ {
+ // add height for emphasis marks
+ //TODO: font metrics should be used instead
+ nHeight += nHeight / 4;
+ }
+
+ const SvxMarginItem& rMargin = rPattern.GetItem(ATTR_MARGIN);
+
+ nHeight += rMargin.GetTopMargin() + rMargin.GetBottomMargin();
+
+ if (nHeight > STD_ROWHEIGHT_DIFF)
+ nHeight -= STD_ROWHEIGHT_DIFF;
+
+ if (nHeight < ScGlobal::nStdRowHeight)
+ nHeight = ScGlobal::nStdRowHeight;
+
+ return nHeight;
+}
+
+// pHeight in Twips
+// optimize nMinHeight, nMinStart : with nRow >= nMinStart is at least nMinHeight
+// (is only evaluated with bStdAllowed)
+
+void ScColumn::GetOptimalHeight(
+ sc::RowHeightContext& rCxt, SCROW nStartRow, SCROW nEndRow, sal_uInt16 nMinHeight, SCROW nMinStart )
+{
+ ScDocument& rDocument = GetDoc();
+ RowHeightsArray& rHeights = rCxt.getHeightArray();
+ ScAttrIterator aIter( pAttrArray.get(), nStartRow, nEndRow, rDocument.GetDefPattern() );
+
+ SCROW nStart = -1;
+ SCROW nEnd = -1;
+ SCROW nEditPos = 0;
+ SCROW nNextEnd = 0;
+
+ // with conditional formatting, always consider the individual cells
+
+ const ScPatternAttr* pPattern = aIter.Next(nStart,nEnd);
+ while ( pPattern )
+ {
+ const ScMergeAttr* pMerge = &pPattern->GetItem(ATTR_MERGE);
+ const ScMergeFlagAttr* pFlag = &pPattern->GetItem(ATTR_MERGE_FLAG);
+ if ( pMerge->GetRowMerge() > 1 || pFlag->IsOverlapped() )
+ {
+ // do nothing - vertically with merged and overlapping,
+ // horizontally only with overlapped (invisible) -
+ // only one horizontal merged is always considered
+ }
+ else
+ {
+ bool bStdAllowed = (pPattern->GetCellOrientation() == SvxCellOrientation::Standard);
+ bool bStdOnly = false;
+ if (bStdAllowed)
+ {
+ bool bBreak = pPattern->GetItem(ATTR_LINEBREAK).GetValue() ||
+ (pPattern->GetItem( ATTR_HOR_JUSTIFY ).GetValue() ==
+ SvxCellHorJustify::Block);
+ bStdOnly = !bBreak;
+
+ // conditional formatting: loop all cells
+ if (bStdOnly &&
+ !pPattern->GetItem(ATTR_CONDITIONAL).GetCondFormatData().empty())
+ {
+ bStdOnly = false;
+ }
+
+ // rotated text: loop all cells
+ if ( bStdOnly && pPattern->GetItem(ATTR_ROTATE_VALUE).GetValue() )
+ bStdOnly = false;
+ }
+
+ if (bStdOnly)
+ {
+ bool bHasEditCells = HasEditCells(nStart,nEnd,nEditPos);
+ // Call to HasEditCells() may change pattern due to
+ // calculation, => sync always.
+ // We don't know which row changed first, but as pPattern
+ // covered nStart to nEnd we can pick nStart. Worst case we
+ // have to repeat that for every row in range if every row
+ // changed.
+ pPattern = aIter.Resync( nStart, nStart, nEnd);
+ if (bHasEditCells && nEnd < nEditPos)
+ bHasEditCells = false; // run into that again
+ if (bHasEditCells) // includes mixed script types
+ {
+ if (nEditPos == nStart)
+ {
+ bStdOnly = false;
+ if (nEnd > nEditPos)
+ nNextEnd = nEnd;
+ nEnd = nEditPos; // calculate single
+ bStdAllowed = false; // will be computed in any case per cell
+ }
+ else
+ {
+ nNextEnd = nEnd;
+ nEnd = nEditPos - 1; // standard - part
+ }
+ }
+ }
+
+ sc::SingleColumnSpanSet aSpanSet(GetDoc().GetSheetLimits());
+ aSpanSet.scan(*this, nStart, nEnd);
+ sc::SingleColumnSpanSet::SpansType aSpans;
+ aSpanSet.getSpans(aSpans);
+
+ if (bStdAllowed)
+ {
+ sal_uInt16 nLatHeight = 0;
+ sal_uInt16 nCjkHeight = 0;
+ sal_uInt16 nCtlHeight = 0;
+ sal_uInt16 nDefHeight;
+ SvtScriptType nDefScript = ScGlobal::GetDefaultScriptType();
+ if ( nDefScript == SvtScriptType::ASIAN )
+ nDefHeight = nCjkHeight = lcl_GetAttribHeight( *pPattern, ATTR_CJK_FONT_HEIGHT );
+ else if ( nDefScript == SvtScriptType::COMPLEX )
+ nDefHeight = nCtlHeight = lcl_GetAttribHeight( *pPattern, ATTR_CTL_FONT_HEIGHT );
+ else
+ nDefHeight = nLatHeight = lcl_GetAttribHeight( *pPattern, ATTR_FONT_HEIGHT );
+
+ // if everything below is already larger, the loop doesn't have to
+ // be run again
+ SCROW nStdEnd = nEnd;
+ if ( nDefHeight <= nMinHeight && nStdEnd >= nMinStart )
+ nStdEnd = (nMinStart>0) ? nMinStart-1 : 0;
+
+ if (nStart <= nStdEnd)
+ {
+ SCROW nRow = nStart;
+ for (;;)
+ {
+ size_t nIndex;
+ SCROW nRangeEnd;
+ sal_uInt16 nRangeHeight = rHeights.GetValue(nRow, nIndex, nRangeEnd);
+ if (nRangeHeight < nDefHeight)
+ rHeights.SetValue(nRow, std::min(nRangeEnd, nStdEnd), nDefHeight);
+ nRow = nRangeEnd + 1;
+ if (nRow > nStdEnd)
+ break;
+ }
+ }
+
+ if ( bStdOnly )
+ {
+ // if cells are not handled individually below,
+ // check for cells with different script type
+ sc::CellTextAttrStoreType::iterator itAttr = maCellTextAttrs.begin();
+ sc::CellStoreType::iterator itCells = maCells.begin();
+ for (const auto& rSpan : aSpans)
+ {
+ for (SCROW nRow = rSpan.mnRow1; nRow <= rSpan.mnRow2; ++nRow)
+ {
+ SvtScriptType nScript = GetRangeScriptType(itAttr, nRow, nRow, itCells);
+ if (nScript == nDefScript)
+ continue;
+
+ if ( nScript == SvtScriptType::ASIAN )
+ {
+ if ( nCjkHeight == 0 )
+ nCjkHeight = lcl_GetAttribHeight( *pPattern, ATTR_CJK_FONT_HEIGHT );
+ if (nCjkHeight > rHeights.GetValue(nRow))
+ rHeights.SetValue(nRow, nRow, nCjkHeight);
+ }
+ else if ( nScript == SvtScriptType::COMPLEX )
+ {
+ if ( nCtlHeight == 0 )
+ nCtlHeight = lcl_GetAttribHeight( *pPattern, ATTR_CTL_FONT_HEIGHT );
+ if (nCtlHeight > rHeights.GetValue(nRow))
+ rHeights.SetValue(nRow, nRow, nCtlHeight);
+ }
+ else
+ {
+ if ( nLatHeight == 0 )
+ nLatHeight = lcl_GetAttribHeight( *pPattern, ATTR_FONT_HEIGHT );
+ if (nLatHeight > rHeights.GetValue(nRow))
+ rHeights.SetValue(nRow, nRow, nLatHeight);
+ }
+ }
+ }
+ }
+ }
+
+ if (!bStdOnly) // search covered cells
+ {
+ ScNeededSizeOptions aOptions;
+
+ for (const auto& rSpan : aSpans)
+ {
+ for (SCROW nRow = rSpan.mnRow1; nRow <= rSpan.mnRow2; ++nRow)
+ {
+ // only calculate the cell height when it's used later (#37928#)
+
+ if (rCxt.isForceAutoSize() || !(rDocument.GetRowFlags(nRow, nTab) & CRFlags::ManualSize) )
+ {
+ aOptions.pPattern = pPattern;
+ const ScPatternAttr* pOldPattern = pPattern;
+ sal_uInt16 nHeight = static_cast<sal_uInt16>(
+ std::min(
+ GetNeededSize( nRow, rCxt.getOutputDevice(), rCxt.getPPTX(), rCxt.getPPTY(),
+ rCxt.getZoomX(), rCxt.getZoomY(), false, aOptions,
+ &pPattern) / rCxt.getPPTY(),
+ double(std::numeric_limits<sal_uInt16>::max())));
+ if (nHeight > rHeights.GetValue(nRow))
+ rHeights.SetValue(nRow, nRow, nHeight);
+ // Pattern changed due to calculation? => sync.
+ if (pPattern != pOldPattern)
+ {
+ pPattern = aIter.Resync( nRow, nStart, nEnd);
+ nNextEnd = 0;
+ }
+ }
+ }
+ }
+ }
+ }
+
+ if (nNextEnd > 0)
+ {
+ nStart = nEnd + 1;
+ nEnd = nNextEnd;
+ nNextEnd = 0;
+ }
+ else
+ pPattern = aIter.Next(nStart,nEnd);
+ }
+}
+
+bool ScColumn::GetNextSpellingCell(SCROW& nRow, bool bInSel, const ScMarkData& rData) const
+{
+ ScDocument& rDocument = GetDoc();
+ sc::CellStoreType::const_iterator it = maCells.position(nRow).first;
+ mdds::mtv::element_t eType = it->type;
+ if (!bInSel && it != maCells.end() && eType != sc::element_type_empty)
+ {
+ if ( (eType == sc::element_type_string || eType == sc::element_type_edittext) &&
+ !(HasAttrib( nRow, nRow, HasAttrFlags::Protected) &&
+ rDocument.IsTabProtected(nTab)) )
+ return true;
+ }
+ if (bInSel)
+ {
+ SCROW lastDataPos = GetLastDataPos();
+ for (;;)
+ {
+ nRow = rData.GetNextMarked(nCol, nRow, false);
+ if (!rDocument.ValidRow(nRow) || nRow > lastDataPos )
+ {
+ nRow = GetDoc().MaxRow()+1;
+ return false;
+ }
+ else
+ {
+ it = maCells.position(it, nRow).first;
+ eType = it->type;
+ if ( (eType == sc::element_type_string || eType == sc::element_type_edittext) &&
+ !(HasAttrib( nRow, nRow, HasAttrFlags::Protected) &&
+ rDocument.IsTabProtected(nTab)) )
+ return true;
+ else
+ nRow++;
+ }
+ }
+ }
+ else
+ {
+ while (GetNextDataPos(nRow))
+ {
+ it = maCells.position(it, nRow).first;
+ eType = it->type;
+ if ( (eType == sc::element_type_string || eType == sc::element_type_edittext) &&
+ !(HasAttrib( nRow, nRow, HasAttrFlags::Protected) &&
+ rDocument.IsTabProtected(nTab)) )
+ return true;
+ else
+ nRow++;
+ }
+ nRow = GetDoc().MaxRow()+1;
+ return false;
+ }
+}
+
+namespace {
+
+class StrEntries
+{
+ sc::CellStoreType& mrCells;
+
+protected:
+ struct StrEntry
+ {
+ SCROW mnRow;
+ OUString maStr;
+
+ StrEntry(SCROW nRow, const OUString& rStr) : mnRow(nRow), maStr(rStr) {}
+ };
+
+ std::vector<StrEntry> maStrEntries;
+ ScDocument* mpDoc;
+
+ StrEntries(sc::CellStoreType& rCells, ScDocument* pDoc) : mrCells(rCells), mpDoc(pDoc) {}
+
+public:
+ void commitStrings()
+ {
+ svl::SharedStringPool& rPool = mpDoc->GetSharedStringPool();
+ sc::CellStoreType::iterator it = mrCells.begin();
+ for (const auto& rStrEntry : maStrEntries)
+ it = mrCells.set(it, rStrEntry.mnRow, rPool.intern(rStrEntry.maStr));
+ }
+};
+
+class RemoveEditAttribsHandler : public StrEntries
+{
+ std::unique_ptr<ScFieldEditEngine> mpEngine;
+
+public:
+ RemoveEditAttribsHandler(sc::CellStoreType& rCells, ScDocument* pDoc) : StrEntries(rCells, pDoc) {}
+
+ void operator() (size_t nRow, EditTextObject*& pObj)
+ {
+ // For the test on hard formatting (ScEditAttrTester), are the defaults in the
+ // EditEngine of no importance. When the tester would later recognise the same
+ // attributes in default and hard formatting and has to remove them, the correct
+ // defaults must be set in the EditEngine for each cell.
+
+ // test for attributes
+ if (!mpEngine)
+ {
+ mpEngine.reset(new ScFieldEditEngine(mpDoc, mpDoc->GetEditPool()));
+ // EEControlBits::ONLINESPELLING if there are errors already
+ mpEngine->SetControlWord(mpEngine->GetControlWord() | EEControlBits::ONLINESPELLING);
+ mpDoc->ApplyAsianEditSettings(*mpEngine);
+ }
+ mpEngine->SetTextCurrentDefaults(*pObj);
+ sal_Int32 nParCount = mpEngine->GetParagraphCount();
+ for (sal_Int32 nPar=0; nPar<nParCount; nPar++)
+ {
+ mpEngine->RemoveCharAttribs(nPar);
+ const SfxItemSet& rOld = mpEngine->GetParaAttribs(nPar);
+ if ( rOld.Count() )
+ {
+ SfxItemSet aNew( *rOld.GetPool(), rOld.GetRanges() ); // empty
+ mpEngine->SetParaAttribs( nPar, aNew );
+ }
+ }
+ // change URL field to text (not possible otherwise, thus pType=0)
+ mpEngine->RemoveFields();
+
+ bool bSpellErrors = mpEngine->HasOnlineSpellErrors();
+ bool bNeedObject = bSpellErrors || nParCount>1; // keep errors/paragraphs
+ // ScEditAttrTester is not needed anymore, arrays are gone
+
+ if (bNeedObject) // remains edit cell
+ {
+ EEControlBits nCtrl = mpEngine->GetControlWord();
+ EEControlBits nWantBig = bSpellErrors ? EEControlBits::ALLOWBIGOBJS : EEControlBits::NONE;
+ if ( ( nCtrl & EEControlBits::ALLOWBIGOBJS ) != nWantBig )
+ mpEngine->SetControlWord( (nCtrl & ~EEControlBits::ALLOWBIGOBJS) | nWantBig );
+
+ // Overwrite the existing object.
+ delete pObj;
+ pObj = mpEngine->CreateTextObject().release();
+ }
+ else // create String
+ {
+ // Store the string replacement for later commits.
+ OUString aText = ScEditUtil::GetSpaceDelimitedString(*mpEngine);
+ maStrEntries.emplace_back(nRow, aText);
+ }
+ }
+};
+
+class TestTabRefAbsHandler
+{
+ SCTAB mnTab;
+ bool mbTestResult;
+public:
+ explicit TestTabRefAbsHandler(SCTAB nTab) : mnTab(nTab), mbTestResult(false) {}
+
+ void operator() (size_t /*nRow*/, const ScFormulaCell* pCell)
+ {
+ if (const_cast<ScFormulaCell*>(pCell)->TestTabRefAbs(mnTab))
+ mbTestResult = true;
+ }
+
+ bool getTestResult() const { return mbTestResult; }
+};
+
+}
+
+void ScColumn::RemoveEditAttribs( sc::ColumnBlockPosition& rBlockPos, SCROW nStartRow, SCROW nEndRow )
+{
+ RemoveEditAttribsHandler aFunc(maCells, &GetDoc());
+
+ rBlockPos.miCellPos = sc::ProcessEditText(
+ rBlockPos.miCellPos, maCells, nStartRow, nEndRow, aFunc);
+
+ aFunc.commitStrings();
+}
+
+bool ScColumn::TestTabRefAbs(SCTAB nTable) const
+{
+ TestTabRefAbsHandler aFunc(nTable);
+ sc::ParseFormula(maCells, aFunc);
+ return aFunc.getTestResult();
+}
+
+bool ScColumn::IsEmptyData() const
+{
+ return maCells.block_size() == 1 && maCells.begin()->type == sc::element_type_empty;
+}
+
+namespace {
+
+class CellCounter
+{
+ size_t mnCount;
+public:
+ CellCounter() : mnCount(0) {}
+
+ void operator() (
+ const sc::CellStoreType::value_type& node, size_t /*nOffset*/, size_t nDataSize)
+ {
+ if (node.type == sc::element_type_empty)
+ return;
+
+ mnCount += nDataSize;
+ }
+
+ size_t getCount() const { return mnCount; }
+};
+
+}
+
+SCSIZE ScColumn::VisibleCount( SCROW nStartRow, SCROW nEndRow ) const
+{
+ CellCounter aFunc;
+ sc::ParseBlock(maCells.begin(), maCells, aFunc, nStartRow, nEndRow);
+ return aFunc.getCount();
+}
+
+bool ScColumn::HasVisibleDataAt(SCROW nRow) const
+{
+ std::pair<sc::CellStoreType::const_iterator,size_t> aPos = maCells.position(nRow);
+ sc::CellStoreType::const_iterator it = aPos.first;
+ if (it == maCells.end())
+ // Likely invalid row number.
+ return false;
+
+ return it->type != sc::element_type_empty;
+}
+
+bool ScColumn::IsEmptyData(SCROW nStartRow, SCROW nEndRow) const
+{
+ std::pair<sc::CellStoreType::const_iterator,size_t> aPos = maCells.position(nStartRow);
+ sc::CellStoreType::const_iterator it = aPos.first;
+ if (it == maCells.end())
+ // Invalid row number.
+ return false;
+
+ if (it->type != sc::element_type_empty)
+ // Non-empty cell at the start position.
+ return false;
+
+ // start position of next block which is not empty.
+ SCROW nNextRow = nStartRow + it->size - aPos.second;
+ return nEndRow < nNextRow;
+}
+
+bool ScColumn::IsNotesEmptyBlock(SCROW nStartRow, SCROW nEndRow) const
+{
+ std::pair<sc::CellNoteStoreType::const_iterator,size_t> aPos = maCellNotes.position(nStartRow);
+ sc::CellNoteStoreType::const_iterator it = aPos.first;
+ if (it == maCellNotes.end())
+ // Invalid row number.
+ return false;
+
+ if (it->type != sc::element_type_empty)
+ // Non-empty cell at the start position.
+ return false;
+
+ // start position of next block which is not empty.
+ SCROW nNextRow = nStartRow + it->size - aPos.second;
+ return nEndRow < nNextRow;
+}
+
+SCSIZE ScColumn::GetEmptyLinesInBlock( SCROW nStartRow, SCROW nEndRow, ScDirection eDir ) const
+{
+ // Given a range of rows, find a top or bottom empty segment.
+ switch (eDir)
+ {
+ case DIR_TOP:
+ {
+ // Determine the length of empty head segment.
+ size_t nLength = nEndRow - nStartRow + 1;
+ std::pair<sc::CellStoreType::const_iterator,size_t> aPos = maCells.position(nStartRow);
+ sc::CellStoreType::const_iterator it = aPos.first;
+ if (it->type != sc::element_type_empty)
+ // First row is already not empty.
+ return 0;
+
+ // length of this empty block minus the offset.
+ size_t nThisLen = it->size - aPos.second;
+ return std::min(nThisLen, nLength);
+ }
+ break;
+ case DIR_BOTTOM:
+ {
+ // Determine the length of empty tail segment.
+ size_t nLength = nEndRow - nStartRow + 1;
+ std::pair<sc::CellStoreType::const_iterator,size_t> aPos = maCells.position(nEndRow);
+ sc::CellStoreType::const_iterator it = aPos.first;
+ if (it->type != sc::element_type_empty)
+ // end row is already not empty.
+ return 0;
+
+ // length of this empty block from the tip to the end row position.
+ size_t nThisLen = aPos.second + 1;
+ return std::min(nThisLen, nLength);
+ }
+ break;
+ default:
+ ;
+ }
+
+ return 0;
+}
+
+SCROW ScColumn::GetFirstDataPos() const
+{
+ if (IsEmptyData())
+ return 0;
+
+ sc::CellStoreType::const_iterator it = maCells.begin();
+ if (it->type != sc::element_type_empty)
+ return 0;
+
+ return it->size;
+}
+
+SCROW ScColumn::GetLastDataPos() const
+{
+ if (IsEmptyData())
+ return 0;
+
+ sc::CellStoreType::const_reverse_iterator it = maCells.rbegin();
+ if (it->type != sc::element_type_empty)
+ return GetDoc().MaxRow();
+
+ return GetDoc().MaxRow() - static_cast<SCROW>(it->size);
+}
+
+SCROW ScColumn::GetLastDataPos( SCROW nLastRow, ScDataAreaExtras* pDataAreaExtras ) const
+{
+ nLastRow = std::min( nLastRow, GetDoc().MaxRow());
+
+ if (pDataAreaExtras && pDataAreaExtras->mnEndRow < nLastRow)
+ {
+ // Check in order of likeliness.
+ if ( (pDataAreaExtras->mbCellFormats && HasVisibleAttrIn(nLastRow, nLastRow)) ||
+ (pDataAreaExtras->mbCellNotes && !IsNotesEmptyBlock(nLastRow, nLastRow)) ||
+ (pDataAreaExtras->mbCellDrawObjects && !IsDrawObjectsEmptyBlock(nLastRow, nLastRow)))
+ pDataAreaExtras->mnEndRow = nLastRow;
+ }
+
+ sc::CellStoreType::const_position_type aPos = maCells.position(nLastRow);
+
+ if (aPos.first->type != sc::element_type_empty)
+ return nLastRow;
+
+ if (aPos.first == maCells.begin())
+ // This is the first block, and is empty.
+ return 0;
+
+ return static_cast<SCROW>(aPos.first->position - 1);
+}
+
+bool ScColumn::GetPrevDataPos(SCROW& rRow) const
+{
+ std::pair<sc::CellStoreType::const_iterator,size_t> aPos = maCells.position(rRow);
+ sc::CellStoreType::const_iterator it = aPos.first;
+ if (it == maCells.end())
+ return false;
+
+ if (it->type == sc::element_type_empty)
+ {
+ if (it == maCells.begin())
+ // No more previous non-empty cell.
+ return false;
+
+ rRow -= aPos.second + 1; // Last row position of the previous block.
+ return true;
+ }
+
+ // This block is not empty.
+ if (aPos.second)
+ {
+ // There are preceding cells in this block. Simply move back one cell.
+ --rRow;
+ return true;
+ }
+
+ // This is the first cell in a non-empty block. Move back to the previous block.
+ if (it == maCells.begin())
+ // No more preceding block.
+ return false;
+
+ --rRow; // Move to the last cell of the previous block.
+ --it;
+ if (it->type == sc::element_type_empty)
+ {
+ // This block is empty.
+ if (it == maCells.begin())
+ // No more preceding blocks.
+ return false;
+
+ // Skip the whole empty block segment.
+ rRow -= it->size;
+ }
+
+ return true;
+}
+
+bool ScColumn::GetNextDataPos(SCROW& rRow) const // greater than rRow
+{
+ std::pair<sc::CellStoreType::const_iterator,size_t> aPos = maCells.position(rRow);
+ sc::CellStoreType::const_iterator it = aPos.first;
+ if (it == maCells.end())
+ return false;
+
+ if (it->type == sc::element_type_empty)
+ {
+ // This block is empty. Skip ahead to the next block (if exists).
+ rRow += it->size - aPos.second;
+ ++it;
+ if (it == maCells.end())
+ // No more next block.
+ return false;
+
+ // Next block exists, and is non-empty.
+ return true;
+ }
+
+ if (aPos.second < it->size - 1)
+ {
+ // There are still cells following the current position.
+ ++rRow;
+ return true;
+ }
+
+ // This is the last cell in the block. Move ahead to the next block.
+ rRow += it->size - aPos.second; // First cell in the next block.
+ ++it;
+ if (it == maCells.end())
+ // No more next block.
+ return false;
+
+ if (it->type == sc::element_type_empty)
+ {
+ // Next block is empty. Move to the next block.
+ rRow += it->size;
+ ++it;
+ if (it == maCells.end())
+ return false;
+ }
+
+ return true;
+}
+
+bool ScColumn::TrimEmptyBlocks(SCROW& rRowStart, SCROW& rRowEnd) const
+{
+ assert(rRowStart <= rRowEnd);
+ SCROW nRowStartNew = rRowStart, nRowEndNew = rRowEnd;
+
+ // Trim down rRowStart first
+ std::pair<sc::CellStoreType::const_iterator,size_t> aPos = maCells.position(rRowStart);
+ sc::CellStoreType::const_iterator it = aPos.first;
+ if (it == maCells.end())
+ return false;
+
+ if (it->type == sc::element_type_empty)
+ {
+ // This block is empty. Skip ahead to the next block (if exists).
+ nRowStartNew += it->size - aPos.second;
+ if (nRowStartNew > rRowEnd)
+ return false;
+ ++it;
+ if (it == maCells.end())
+ // No more next block.
+ return false;
+ }
+
+ // Trim up rRowEnd next
+ aPos = maCells.position(rRowEnd);
+ it = aPos.first;
+ if (it == maCells.end())
+ {
+ rRowStart = nRowStartNew;
+ return true; // Because trimming of rRowStart is ok
+ }
+
+ if (it->type == sc::element_type_empty)
+ {
+ // rRowEnd cannot be in the first block which is empty !
+ assert(it != maCells.begin());
+ // This block is empty. Skip to the previous block (it exists).
+ nRowEndNew -= aPos.second + 1; // Last row position of the previous block.
+ assert(nRowStartNew <= nRowEndNew);
+ }
+
+ rRowStart = nRowStartNew;
+ rRowEnd = nRowEndNew;
+ return true;
+}
+
+SCROW ScColumn::FindNextVisibleRow(SCROW nRow, bool bForward) const
+{
+ if(bForward)
+ {
+ nRow++;
+ SCROW nEndRow = 0;
+ bool bHidden = GetDoc().RowHidden(nRow, nTab, nullptr, &nEndRow);
+ if(bHidden)
+ return std::min<SCROW>(GetDoc().MaxRow(), nEndRow + 1);
+ else
+ return nRow;
+ }
+ else
+ {
+ nRow--;
+ SCROW nStartRow = GetDoc().MaxRow();
+ bool bHidden = GetDoc().RowHidden(nRow, nTab, &nStartRow);
+ if(bHidden)
+ return std::max<SCROW>(0, nStartRow - 1);
+ else
+ return nRow;
+ }
+}
+
+SCROW ScColumn::FindNextVisibleRowWithContent(
+ sc::CellStoreType::const_iterator& itPos, SCROW nRow, bool bForward) const
+{
+ ScDocument& rDocument = GetDoc();
+ if (bForward)
+ {
+ do
+ {
+ nRow++;
+ SCROW nEndRow = 0;
+ bool bHidden = rDocument.RowHidden(nRow, nTab, nullptr, &nEndRow);
+ if (bHidden)
+ {
+ nRow = nEndRow + 1;
+ if(nRow >= GetDoc().MaxRow())
+ return GetDoc().MaxRow();
+ }
+
+ std::pair<sc::CellStoreType::const_iterator,size_t> aPos = maCells.position(itPos, nRow);
+ itPos = aPos.first;
+ if (itPos == maCells.end())
+ // Invalid row.
+ return GetDoc().MaxRow();
+
+ if (itPos->type != sc::element_type_empty)
+ return nRow;
+
+ // Move to the last cell of the current empty block.
+ nRow += itPos->size - aPos.second - 1;
+ }
+ while (nRow < GetDoc().MaxRow());
+
+ return GetDoc().MaxRow();
+ }
+
+ do
+ {
+ nRow--;
+ SCROW nStartRow = GetDoc().MaxRow();
+ bool bHidden = rDocument.RowHidden(nRow, nTab, &nStartRow);
+ if (bHidden)
+ {
+ nRow = nStartRow - 1;
+ if(nRow <= 0)
+ return 0;
+ }
+
+ std::pair<sc::CellStoreType::const_iterator,size_t> aPos = maCells.position(itPos, nRow);
+ itPos = aPos.first;
+ if (itPos == maCells.end())
+ // Invalid row.
+ return 0;
+
+ if (itPos->type != sc::element_type_empty)
+ return nRow;
+
+ // Move to the first cell of the current empty block.
+ nRow -= aPos.second;
+ }
+ while (nRow > 0);
+
+ return 0;
+}
+
+void ScColumn::CellStorageModified()
+{
+ // Remove cached values. Given how often this function is called and how (not that) often
+ // the cached values are used, it should be more efficient to just discard everything
+ // instead of trying to figure out each time exactly what to discard.
+ GetDoc().DiscardFormulaGroupContext();
+
+ // TODO: Update column's "last updated" timestamp here.
+
+ assert(sal::static_int_cast<SCROW>(maCells.size()) == GetDoc().GetMaxRowCount()
+ && "Size of the cell array is incorrect." );
+
+ assert(sal::static_int_cast<SCROW>(maCellTextAttrs.size()) == GetDoc().GetMaxRowCount()
+ && "Size of the cell text attribute array is incorrect.");
+
+ assert(sal::static_int_cast<SCROW>(maBroadcasters.size()) == GetDoc().GetMaxRowCount()
+ && "Size of the broadcaster array is incorrect.");
+
+#if DEBUG_COLUMN_STORAGE
+ // Make sure that these two containers are synchronized wrt empty segments.
+ auto lIsEmptyType = [](const auto& rElement) { return rElement.type == sc::element_type_empty; };
+ // Move to the first empty blocks.
+ auto itCell = std::find_if(maCells.begin(), maCells.end(), lIsEmptyType);
+ auto itAttr = std::find_if(maCellTextAttrs.begin(), maCellTextAttrs.end(), lIsEmptyType);
+
+ while (itCell != maCells.end())
+ {
+ if (itCell->position != itAttr->position || itCell->size != itAttr->size)
+ {
+ cout << "ScColumn::CellStorageModified: Cell array and cell text attribute array are out of sync." << endl;
+ cout << "-- cell array" << endl;
+ maCells.dump_blocks(cout);
+ cout << "-- attribute array" << endl;
+ maCellTextAttrs.dump_blocks(cout);
+ cout.flush();
+ abort();
+ }
+
+ // Move to the next empty blocks.
+ ++itCell;
+ itCell = std::find_if(itCell, maCells.end(), lIsEmptyType);
+
+ ++itAttr;
+ itAttr = std::find_if(itAttr, maCellTextAttrs.end(), lIsEmptyType);
+ }
+#endif
+}
+
+#if DUMP_COLUMN_STORAGE
+
+namespace {
+
+#define DUMP_FORMULA_RESULTS 0
+
+struct ColumnStorageDumper
+{
+ const ScDocument& mrDoc;
+
+ ColumnStorageDumper( const ScDocument& rDoc ) : mrDoc(rDoc) {}
+
+ void operator() (const sc::CellStoreType::value_type& rNode) const
+ {
+ switch (rNode.type)
+ {
+ case sc::element_type_numeric:
+ cout << " * numeric block (pos=" << rNode.position << ", length=" << rNode.size << ")" << endl;
+ break;
+ case sc::element_type_string:
+ cout << " * string block (pos=" << rNode.position << ", length=" << rNode.size << ")" << endl;
+ break;
+ case sc::element_type_edittext:
+ cout << " * edit-text block (pos=" << rNode.position << ", length=" << rNode.size << ")" << endl;
+ break;
+ case sc::element_type_formula:
+ dumpFormulaBlock(rNode);
+ break;
+ case sc::element_type_empty:
+ cout << " * empty block (pos=" << rNode.position << ", length=" << rNode.size << ")" << endl;
+ break;
+ default:
+ cout << " * unknown block" << endl;
+ }
+ }
+
+ void dumpFormulaBlock(const sc::CellStoreType::value_type& rNode) const
+ {
+ cout << " * formula block (pos=" << rNode.position << ", length=" << rNode.size << ")" << endl;
+ sc::formula_block::const_iterator it = sc::formula_block::begin(*rNode.data);
+ sc::formula_block::const_iterator itEnd = sc::formula_block::end(*rNode.data);
+
+ for (; it != itEnd; ++it)
+ {
+ const ScFormulaCell* pCell = *it;
+ if (!pCell->IsShared())
+ {
+ cout << " * row " << pCell->aPos.Row() << " not shared" << endl;
+ printFormula(pCell);
+ printResult(pCell);
+ continue;
+ }
+
+ if (pCell->GetSharedTopRow() != pCell->aPos.Row())
+ {
+ cout << " * row " << pCell->aPos.Row() << " shared with top row "
+ << pCell->GetSharedTopRow() << " with length " << pCell->GetSharedLength()
+ << endl;
+ continue;
+ }
+
+ SCROW nLen = pCell->GetSharedLength();
+ cout << " * group: start=" << pCell->aPos.Row() << ", length=" << nLen << endl;
+ printFormula(pCell);
+ printResult(pCell);
+
+ if (nLen > 1)
+ {
+ for (SCROW i = 0; i < nLen-1; ++i, ++it)
+ {
+ pCell = *it;
+ printResult(pCell);
+ }
+ }
+ }
+ }
+
+ void printFormula(const ScFormulaCell* pCell) const
+ {
+ sc::TokenStringContext aCxt(mrDoc, mrDoc.GetGrammar());
+ OUString aFormula = pCell->GetCode()->CreateString(aCxt, pCell->aPos);
+ cout << " * formula: " << aFormula << endl;
+ }
+
+#if DUMP_FORMULA_RESULTS
+ void printResult(const ScFormulaCell* pCell) const
+ {
+ sc::FormulaResultValue aRes = pCell->GetResult();
+ cout << " * result: ";
+ switch (aRes.meType)
+ {
+ case sc::FormulaResultValue::Value:
+ cout << aRes.mfValue << " (type: value)";
+ break;
+ case sc::FormulaResultValue::String:
+ cout << "'" << aRes.maString.getString() << "' (type: string)";
+ break;
+ case sc::FormulaResultValue::Error:
+ cout << "error (" << static_cast<int>(aRes.mnError) << ")";
+ break;
+ case sc::FormulaResultValue::Invalid:
+ cout << "invalid";
+ break;
+ }
+
+ cout << endl;
+ }
+#else
+ void printResult(const ScFormulaCell*) const
+ {
+ (void) this; /* loplugin:staticmethods */
+ }
+#endif
+};
+
+}
+
+void ScColumn::DumpColumnStorage() const
+{
+ cout << "-- table: " << nTab << "; column: " << nCol << endl;
+ std::for_each(maCells.begin(), maCells.end(), ColumnStorageDumper(GetDoc()));
+ cout << "--" << endl;
+}
+#endif
+
+void ScColumn::CopyCellTextAttrsToDocument(SCROW nRow1, SCROW nRow2, ScColumn& rDestCol) const
+{
+ rDestCol.maCellTextAttrs.set_empty(nRow1, nRow2); // Empty the destination range first.
+
+ sc::CellTextAttrStoreType::const_iterator itBlk = maCellTextAttrs.begin(), itBlkEnd = maCellTextAttrs.end();
+
+ // Locate the top row position.
+ size_t nBlockStart = 0, nRowPos = static_cast<size_t>(nRow1);
+ itBlk = std::find_if(itBlk, itBlkEnd, [&nRowPos, &nBlockStart](const auto& rAttr) {
+ return nBlockStart <= nRowPos && nRowPos < nBlockStart + rAttr.size; });
+
+ if (itBlk == itBlkEnd)
+ // Specified range not found. Bail out.
+ return;
+
+ size_t nBlockEnd;
+ size_t nOffsetInBlock = nRowPos - nBlockStart;
+
+ nRowPos = static_cast<size_t>(nRow2); // End row position.
+
+ // Keep copying until we hit the end row position.
+ sc::celltextattr_block::const_iterator itData, itDataEnd;
+ for (; itBlk != itBlkEnd; ++itBlk, nBlockStart = nBlockEnd, nOffsetInBlock = 0)
+ {
+ nBlockEnd = nBlockStart + itBlk->size;
+ if (!itBlk->data)
+ {
+ // Empty block.
+ if (nBlockStart <= nRowPos && nRowPos < nBlockEnd)
+ // This block contains the end row.
+ rDestCol.maCellTextAttrs.set_empty(nBlockStart + nOffsetInBlock, nRowPos);
+ else
+ rDestCol.maCellTextAttrs.set_empty(nBlockStart + nOffsetInBlock, nBlockEnd-1);
+
+ continue;
+ }
+
+ // Non-empty block.
+ itData = sc::celltextattr_block::begin(*itBlk->data);
+ itDataEnd = sc::celltextattr_block::end(*itBlk->data);
+ std::advance(itData, nOffsetInBlock);
+
+ if (nBlockStart <= nRowPos && nRowPos < nBlockEnd)
+ {
+ // This block contains the end row. Only copy partially.
+ size_t nOffset = nRowPos - nBlockStart + 1;
+ itDataEnd = sc::celltextattr_block::begin(*itBlk->data);
+ std::advance(itDataEnd, nOffset);
+
+ rDestCol.maCellTextAttrs.set(nBlockStart + nOffsetInBlock, itData, itDataEnd);
+ break;
+ }
+
+ rDestCol.maCellTextAttrs.set(nBlockStart + nOffsetInBlock, itData, itDataEnd);
+ }
+}
+
+namespace {
+
+class CopyCellNotesHandler
+{
+ ScColumn& mrDestCol;
+ sc::CellNoteStoreType& mrDestNotes;
+ sc::CellNoteStoreType::iterator miPos;
+ SCTAB mnSrcTab;
+ SCCOL mnSrcCol;
+ SCTAB mnDestTab;
+ SCCOL mnDestCol;
+ SCROW mnDestOffset; /// Add this to the source row position to get the destination row.
+ bool mbCloneCaption;
+
+public:
+ CopyCellNotesHandler( const ScColumn& rSrcCol, ScColumn& rDestCol, SCROW nDestOffset, bool bCloneCaption ) :
+ mrDestCol(rDestCol),
+ mrDestNotes(rDestCol.GetCellNoteStore()),
+ miPos(mrDestNotes.begin()),
+ mnSrcTab(rSrcCol.GetTab()),
+ mnSrcCol(rSrcCol.GetCol()),
+ mnDestTab(rDestCol.GetTab()),
+ mnDestCol(rDestCol.GetCol()),
+ mnDestOffset(nDestOffset),
+ mbCloneCaption(bCloneCaption) {}
+
+ void operator() ( size_t nRow, const ScPostIt* p )
+ {
+ SCROW nDestRow = nRow + mnDestOffset;
+ ScAddress aSrcPos(mnSrcCol, nRow, mnSrcTab);
+ ScAddress aDestPos(mnDestCol, nDestRow, mnDestTab);
+ ScPostIt* pNew = p->Clone(aSrcPos, mrDestCol.GetDoc(), aDestPos, mbCloneCaption).release();
+ miPos = mrDestNotes.set(miPos, nDestRow, pNew);
+ // Notify our LOK clients also
+ ScDocShell::LOKCommentNotify(LOKCommentNotificationType::Add, &mrDestCol.GetDoc(), aDestPos, pNew);
+ }
+};
+
+}
+
+void ScColumn::CopyCellNotesToDocument(
+ SCROW nRow1, SCROW nRow2, ScColumn& rDestCol, bool bCloneCaption, SCROW nRowOffsetDest ) const
+{
+ if (IsNotesEmptyBlock(nRow1, nRow2))
+ // The column has no cell notes to copy between specified rows.
+ return;
+
+ ScDrawLayer *pDrawLayer = rDestCol.GetDoc().GetDrawLayer();
+ bool bWasLocked = bool();
+ if (pDrawLayer)
+ {
+ // Avoid O(n^2) by temporary locking SdrModel which disables broadcasting.
+ // Each cell note adds undo listener, and all of them would be woken up in ScPostIt::CreateCaption.
+ bWasLocked = pDrawLayer->isLocked();
+ pDrawLayer->setLock(true);
+ }
+ CopyCellNotesHandler aFunc(*this, rDestCol, nRowOffsetDest, bCloneCaption);
+ sc::ParseNote(maCellNotes.begin(), maCellNotes, nRow1, nRow2, aFunc);
+ if (pDrawLayer)
+ pDrawLayer->setLock(bWasLocked);
+}
+
+void ScColumn::DuplicateNotes(SCROW nStartRow, size_t nDataSize, ScColumn& rDestCol, sc::ColumnBlockPosition& maDestBlockPos,
+ bool bCloneCaption, SCROW nRowOffsetDest ) const
+{
+ CopyCellNotesToDocument(nStartRow, nStartRow + nDataSize -1, rDestCol, bCloneCaption, nRowOffsetDest);
+ maDestBlockPos.miCellNotePos = rDestCol.maCellNotes.begin();
+}
+
+SvtBroadcaster* ScColumn::GetBroadcaster(SCROW nRow)
+{
+ return maBroadcasters.get<SvtBroadcaster*>(nRow);
+}
+
+const SvtBroadcaster* ScColumn::GetBroadcaster(SCROW nRow) const
+{
+ return maBroadcasters.get<SvtBroadcaster*>(nRow);
+}
+
+void ScColumn::DeleteBroadcasters( sc::ColumnBlockPosition& rBlockPos, SCROW nRow1, SCROW nRow2 )
+{
+ rBlockPos.miBroadcasterPos =
+ maBroadcasters.set_empty(rBlockPos.miBroadcasterPos, nRow1, nRow2);
+}
+
+void ScColumn::PrepareBroadcastersForDestruction()
+{
+ for (auto& rBroadcaster : maBroadcasters)
+ {
+ if (rBroadcaster.type == sc::element_type_broadcaster)
+ {
+ sc::broadcaster_block::iterator it = sc::broadcaster_block::begin(*rBroadcaster.data);
+ sc::broadcaster_block::iterator itEnd = sc::broadcaster_block::end(*rBroadcaster.data);
+ for (; it != itEnd; ++it)
+ (*it)->PrepareForDestruction();
+ }
+ }
+}
+
+namespace
+{
+struct BroadcasterNoListenersPredicate
+{
+ bool operator()( size_t, const SvtBroadcaster* broadcaster )
+ {
+ return !broadcaster->HasListeners();
+ }
+};
+
+}
+
+void ScColumn::DeleteEmptyBroadcasters()
+{
+ if(!mbEmptyBroadcastersPending)
+ return;
+ // Clean up after ScDocument::EnableDelayDeletingBroadcasters().
+ BroadcasterNoListenersPredicate predicate;
+ sc::SetElementsToEmpty1<sc::broadcaster_block>( maBroadcasters, predicate );
+ mbEmptyBroadcastersPending = false;
+}
+
+// Sparklines
+
+namespace
+{
+
+class DeletingSparklinesHandler
+{
+ ScDocument& m_rDocument;
+ SCTAB m_nTab;
+
+public:
+ DeletingSparklinesHandler(ScDocument& rDocument, SCTAB nTab)
+ : m_rDocument(rDocument)
+ , m_nTab(nTab)
+ {}
+
+ void operator() (size_t /*nRow*/, const sc::SparklineCell* pCell)
+ {
+ auto* pList = m_rDocument.GetSparklineList(m_nTab);
+ pList->removeSparkline(pCell->getSparkline());
+ }
+};
+
+} // end anonymous ns
+
+sc::SparklineCell* ScColumn::GetSparklineCell(SCROW nRow)
+{
+ return maSparklines.get<sc::SparklineCell*>(nRow);
+}
+
+void ScColumn::CreateSparklineCell(SCROW nRow, std::shared_ptr<sc::Sparkline> const& pSparkline)
+{
+ auto* pList = GetDoc().GetSparklineList(GetTab());
+ pList->addSparkline(pSparkline);
+ maSparklines.set(nRow, new sc::SparklineCell(pSparkline));
+}
+
+void ScColumn::DeleteSparklineCells(sc::ColumnBlockPosition& rBlockPos, SCROW nRow1, SCROW nRow2)
+{
+ DeletingSparklinesHandler aFunction(GetDoc(), nTab);
+ sc::ParseSparkline(maSparklines.begin(), maSparklines, nRow1, nRow2, aFunction);
+
+ rBlockPos.miSparklinePos = maSparklines.set_empty(rBlockPos.miSparklinePos, nRow1, nRow2);
+}
+
+bool ScColumn::DeleteSparkline(SCROW nRow)
+{
+ if (!GetDoc().ValidRow(nRow))
+ return false;
+
+ DeletingSparklinesHandler aFunction(GetDoc(), nTab);
+ sc::ParseSparkline(maSparklines.begin(), maSparklines, nRow, nRow, aFunction);
+
+ maSparklines.set_empty(nRow, nRow);
+ return true;
+}
+
+bool ScColumn::IsSparklinesEmptyBlock(SCROW nStartRow, SCROW nEndRow) const
+{
+ std::pair<sc::SparklineStoreType::const_iterator,size_t> aPos = maSparklines.position(nStartRow);
+ sc::SparklineStoreType::const_iterator it = aPos.first;
+ if (it == maSparklines.end())
+ return false;
+
+ if (it->type != sc::element_type_empty)
+ return false;
+
+ // start position of next block which is not empty.
+ SCROW nNextRow = nStartRow + it->size - aPos.second;
+ return nEndRow < nNextRow;
+}
+
+namespace
+{
+
+class CopySparklinesHandler
+{
+ ScColumn& mrDestColumn;
+ sc::SparklineStoreType& mrDestSparkline;
+ sc::SparklineStoreType::iterator miDestPosition;
+ SCROW mnDestOffset;
+
+public:
+ CopySparklinesHandler(ScColumn& rDestColumn, SCROW nDestOffset)
+ : mrDestColumn(rDestColumn)
+ , mrDestSparkline(mrDestColumn.GetSparklineStore())
+ , miDestPosition(mrDestSparkline.begin())
+ , mnDestOffset(nDestOffset)
+ {}
+
+ void operator() (size_t nRow, const sc::SparklineCell* pCell)
+ {
+ SCROW nDestRow = nRow + mnDestOffset;
+
+ auto const& pSparkline = pCell->getSparkline();
+ auto const& pGroup = pCell->getSparklineGroup();
+
+ auto& rDestDoc = mrDestColumn.GetDoc();
+ auto pDestinationGroup = rDestDoc.SearchSparklineGroup(pGroup->getID());
+ if (!pDestinationGroup)
+ pDestinationGroup = std::make_shared<sc::SparklineGroup>(*pGroup); // Copy the group
+ auto pNewSparkline = std::make_shared<sc::Sparkline>(mrDestColumn.GetCol(), nDestRow, pDestinationGroup);
+ pNewSparkline->setInputRange(pSparkline->getInputRange());
+
+ auto* pList = rDestDoc.GetSparklineList(mrDestColumn.GetTab());
+ pList->addSparkline(pNewSparkline);
+
+ miDestPosition = mrDestSparkline.set(miDestPosition, nDestRow, new sc::SparklineCell(pNewSparkline));
+ }
+};
+
+}
+
+void ScColumn::CopyCellSparklinesToDocument(SCROW nRow1, SCROW nRow2, ScColumn& rDestCol, SCROW nRowOffsetDest) const
+{
+ if (IsSparklinesEmptyBlock(nRow1, nRow2))
+ // The column has no cell sparklines to copy between specified rows.
+ return;
+
+ CopySparklinesHandler aFunctor(rDestCol, nRowOffsetDest);
+ sc::ParseSparkline(maSparklines.begin(), maSparklines, nRow1, nRow2, aFunctor);
+}
+
+void ScColumn::DuplicateSparklines(SCROW nStartRow, size_t nDataSize, ScColumn& rDestCol,
+ sc::ColumnBlockPosition& rDestBlockPos, SCROW nRowOffsetDest) const
+{
+ CopyCellSparklinesToDocument(nStartRow, nStartRow + nDataSize - 1, rDestCol, nRowOffsetDest);
+ rDestBlockPos.miSparklinePos = rDestCol.maSparklines.begin();
+}
+
+bool ScColumn::HasSparklines() const
+{
+ if (maSparklines.block_size() == 1 && maSparklines.begin()->type == sc::element_type_empty)
+ return false; // all elements are empty
+ return true; // otherwise some must be sparklines
+}
+
+SCROW ScColumn::GetSparklinesMaxRow() const
+{
+ SCROW maxRow = 0;
+ for (const auto& rSparkline : maSparklines)
+ {
+ if (rSparkline.type == sc::element_type_sparkline)
+ maxRow = rSparkline.position + rSparkline.size - 1;
+ }
+ return maxRow;
+}
+
+SCROW ScColumn::GetSparklinesMinRow() const
+{
+ SCROW minRow = 0;
+ sc::SparklineStoreType::const_iterator it = std::find_if(maSparklines.begin(), maSparklines.end(),
+ [](const auto& rSparkline)
+ {
+ return rSparkline.type == sc::element_type_sparkline;
+ });
+ if (it != maSparklines.end())
+ minRow = it->position;
+ return minRow;
+}
+
+// Notes
+
+ScPostIt* ScColumn::GetCellNote(SCROW nRow)
+{
+ return maCellNotes.get<ScPostIt*>(nRow);
+}
+
+const ScPostIt* ScColumn::GetCellNote(SCROW nRow) const
+{
+ return maCellNotes.get<ScPostIt*>(nRow);
+}
+
+const ScPostIt* ScColumn::GetCellNote( sc::ColumnBlockConstPosition& rBlockPos, SCROW nRow ) const
+{
+ sc::CellNoteStoreType::const_position_type aPos = maCellNotes.position(rBlockPos.miCellNotePos, nRow);
+ rBlockPos.miCellNotePos = aPos.first;
+
+ if (aPos.first->type != sc::element_type_cellnote)
+ return nullptr;
+
+ return sc::cellnote_block::at(*aPos.first->data, aPos.second);
+}
+
+ScPostIt* ScColumn::GetCellNote( sc::ColumnBlockConstPosition& rBlockPos, SCROW nRow )
+{
+ return const_cast<ScPostIt*>(const_cast<const ScColumn*>(this)->GetCellNote( rBlockPos, nRow ));
+}
+
+void ScColumn::SetCellNote(SCROW nRow, std::unique_ptr<ScPostIt> pNote)
+{
+ //pNote->UpdateCaptionPos(ScAddress(nCol, nRow, nTab)); // TODO notes useful ? slow import with many notes
+ maCellNotes.set(nRow, pNote.release());
+}
+
+namespace {
+ class CellNoteHandler
+ {
+ const ScDocument* m_pDocument;
+ const ScAddress m_aAddress; // 'incomplete' address consisting of tab, column
+ const bool m_bForgetCaptionOwnership;
+
+ public:
+ CellNoteHandler(const ScDocument* pDocument, const ScAddress& rPos, bool bForgetCaptionOwnership) :
+ m_pDocument(pDocument),
+ m_aAddress(rPos),
+ m_bForgetCaptionOwnership(bForgetCaptionOwnership) {}
+
+ void operator() ( size_t nRow, ScPostIt* p )
+ {
+ if (m_bForgetCaptionOwnership)
+ p->ForgetCaption();
+
+ // Create a 'complete' address object
+ ScAddress aAddr(m_aAddress);
+ aAddr.SetRow(nRow);
+ // Notify our LOK clients
+ ScDocShell::LOKCommentNotify(LOKCommentNotificationType::Remove, m_pDocument, aAddr, p);
+ }
+ };
+} // anonymous namespace
+
+void ScColumn::CellNotesDeleting(SCROW nRow1, SCROW nRow2, bool bForgetCaptionOwnership)
+{
+ ScAddress aAddr(nCol, 0, nTab);
+ CellNoteHandler aFunc(&GetDoc(), aAddr, bForgetCaptionOwnership);
+ sc::ParseNote(maCellNotes.begin(), maCellNotes, nRow1, nRow2, aFunc);
+}
+
+void ScColumn::DeleteCellNotes( sc::ColumnBlockPosition& rBlockPos, SCROW nRow1, SCROW nRow2, bool bForgetCaptionOwnership )
+{
+ CellNotesDeleting(nRow1, nRow2, bForgetCaptionOwnership);
+
+ rBlockPos.miCellNotePos =
+ maCellNotes.set_empty(rBlockPos.miCellNotePos, nRow1, nRow2);
+}
+
+bool ScColumn::HasCellNotes() const
+{
+ if (maCellNotes.block_size() == 1 && maCellNotes.begin()->type == sc::element_type_empty)
+ return false; // all elements are empty
+ return true; // otherwise some must be notes
+}
+
+SCROW ScColumn::GetCellNotesMaxRow() const
+{
+ // hypothesis : the column has cell notes (should be checked before)
+ SCROW maxRow = 0;
+ for (const auto& rCellNote : maCellNotes)
+ {
+ if (rCellNote.type == sc::element_type_cellnote)
+ maxRow = rCellNote.position + rCellNote.size -1;
+ }
+ return maxRow;
+}
+SCROW ScColumn::GetCellNotesMinRow() const
+{
+ // hypothesis : the column has cell notes (should be checked before)
+ SCROW minRow = 0;
+ sc::CellNoteStoreType::const_iterator it = std::find_if(maCellNotes.begin(), maCellNotes.end(),
+ [](const auto& rCellNote) { return rCellNote.type == sc::element_type_cellnote; });
+ if (it != maCellNotes.end())
+ minRow = it->position;
+ return minRow;
+}
+
+sal_uInt16 ScColumn::GetTextWidth(SCROW nRow) const
+{
+ return maCellTextAttrs.get<sc::CellTextAttr>(nRow).mnTextWidth;
+}
+
+void ScColumn::SetTextWidth(SCROW nRow, sal_uInt16 nWidth)
+{
+ sc::CellTextAttrStoreType::position_type aPos = maCellTextAttrs.position(nRow);
+ if (aPos.first->type != sc::element_type_celltextattr)
+ return;
+
+ // Set new value only when the slot is not empty.
+ sc::celltextattr_block::at(*aPos.first->data, aPos.second).mnTextWidth = nWidth;
+ CellStorageModified();
+}
+
+SvtScriptType ScColumn::GetScriptType( SCROW nRow ) const
+{
+ if (!GetDoc().ValidRow(nRow) || maCellTextAttrs.is_empty(nRow))
+ return SvtScriptType::NONE;
+
+ return maCellTextAttrs.get<sc::CellTextAttr>(nRow).mnScriptType;
+}
+
+SvtScriptType ScColumn::GetRangeScriptType(
+ sc::CellTextAttrStoreType::iterator& itPos, SCROW nRow1, SCROW nRow2, const sc::CellStoreType::iterator& itrCells_ )
+{
+ if (!GetDoc().ValidRow(nRow1) || !GetDoc().ValidRow(nRow2) || nRow1 > nRow2)
+ return SvtScriptType::NONE;
+
+ SCROW nRow = nRow1;
+ std::pair<sc::CellTextAttrStoreType::iterator,size_t> aRet =
+ maCellTextAttrs.position(itPos, nRow1);
+
+ itPos = aRet.first; // Track the position of cell text attribute array.
+ sc::CellStoreType::iterator itrCells = itrCells_;
+
+ SvtScriptType nScriptType = SvtScriptType::NONE;
+ bool bUpdated = false;
+ if (itPos->type == sc::element_type_celltextattr)
+ {
+ sc::celltextattr_block::iterator it = sc::celltextattr_block::begin(*itPos->data);
+ sc::celltextattr_block::iterator itEnd = sc::celltextattr_block::end(*itPos->data);
+ std::advance(it, aRet.second);
+ for (; it != itEnd; ++it, ++nRow)
+ {
+ if (nRow > nRow2)
+ return nScriptType;
+
+ sc::CellTextAttr& rVal = *it;
+ if (UpdateScriptType(rVal, nRow, itrCells))
+ bUpdated = true;
+ nScriptType |= rVal.mnScriptType;
+ }
+ }
+ else
+ {
+ // Skip this whole block.
+ nRow += itPos->size - aRet.second;
+ }
+
+ while (nRow <= nRow2)
+ {
+ ++itPos;
+ if (itPos == maCellTextAttrs.end())
+ return nScriptType;
+
+ if (itPos->type != sc::element_type_celltextattr)
+ {
+ // Skip this whole block.
+ nRow += itPos->size;
+ continue;
+ }
+
+ sc::celltextattr_block::iterator it = sc::celltextattr_block::begin(*itPos->data);
+ sc::celltextattr_block::iterator itEnd = sc::celltextattr_block::end(*itPos->data);
+ for (; it != itEnd; ++it, ++nRow)
+ {
+ if (nRow > nRow2)
+ return nScriptType;
+
+ sc::CellTextAttr& rVal = *it;
+ if (UpdateScriptType(rVal, nRow, itrCells))
+ bUpdated = true;
+
+ nScriptType |= rVal.mnScriptType;
+ }
+ }
+
+ if (bUpdated)
+ CellStorageModified();
+
+ return nScriptType;
+}
+
+void ScColumn::SetScriptType( SCROW nRow, SvtScriptType nType )
+{
+ if (!GetDoc().ValidRow(nRow))
+ return;
+
+ sc::CellTextAttrStoreType::position_type aPos = maCellTextAttrs.position(nRow);
+ if (aPos.first->type != sc::element_type_celltextattr)
+ // Set new value only when the slot is already set.
+ return;
+
+ sc::celltextattr_block::at(*aPos.first->data, aPos.second).mnScriptType = nType;
+ CellStorageModified();
+}
+
+formula::FormulaTokenRef ScColumn::ResolveStaticReference( SCROW nRow )
+{
+ std::pair<sc::CellStoreType::iterator,size_t> aPos = maCells.position(nRow);
+ sc::CellStoreType::iterator it = aPos.first;
+ if (it == maCells.end())
+ // Invalid row. Return a null token.
+ return formula::FormulaTokenRef();
+
+ switch (it->type)
+ {
+ case sc::element_type_numeric:
+ {
+ double fVal = sc::numeric_block::at(*it->data, aPos.second);
+ return formula::FormulaTokenRef(new formula::FormulaDoubleToken(fVal));
+ }
+ case sc::element_type_formula:
+ {
+ ScFormulaCell* p = sc::formula_block::at(*it->data, aPos.second);
+ if (p->IsValue())
+ return formula::FormulaTokenRef(new formula::FormulaDoubleToken(p->GetValue()));
+
+ return formula::FormulaTokenRef(new formula::FormulaStringToken(p->GetString()));
+ }
+ case sc::element_type_string:
+ {
+ const svl::SharedString& rSS = sc::string_block::at(*it->data, aPos.second);
+ return formula::FormulaTokenRef(new formula::FormulaStringToken(rSS));
+ }
+ case sc::element_type_edittext:
+ {
+ const EditTextObject* pText = sc::edittext_block::at(*it->data, aPos.second);
+ OUString aStr = ScEditUtil::GetString(*pText, &GetDoc());
+ svl::SharedString aSS( GetDoc().GetSharedStringPool().intern(aStr));
+ return formula::FormulaTokenRef(new formula::FormulaStringToken(aSS));
+ }
+ case sc::element_type_empty:
+ default:
+ // Return a value of 0.0 in all the other cases.
+ return formula::FormulaTokenRef(new formula::FormulaDoubleToken(0.0));
+ }
+}
+
+namespace {
+
+class ToMatrixHandler
+{
+ ScMatrix& mrMat;
+ SCCOL mnMatCol;
+ SCROW mnTopRow;
+ ScDocument* mpDoc;
+ svl::SharedStringPool& mrStrPool;
+public:
+ ToMatrixHandler(ScMatrix& rMat, SCCOL nMatCol, SCROW nTopRow, ScDocument* pDoc) :
+ mrMat(rMat), mnMatCol(nMatCol), mnTopRow(nTopRow),
+ mpDoc(pDoc), mrStrPool(pDoc->GetSharedStringPool()) {}
+
+ void operator() (size_t nRow, double fVal)
+ {
+ mrMat.PutDouble(fVal, mnMatCol, nRow - mnTopRow);
+ }
+
+ void operator() (size_t nRow, const ScFormulaCell* p)
+ {
+ // Formula cell may need to re-calculate.
+ ScFormulaCell& rCell = const_cast<ScFormulaCell&>(*p);
+ if (rCell.IsValue())
+ mrMat.PutDouble(rCell.GetValue(), mnMatCol, nRow - mnTopRow);
+ else
+ mrMat.PutString(rCell.GetString(), mnMatCol, nRow - mnTopRow);
+ }
+
+ void operator() (size_t nRow, const svl::SharedString& rSS)
+ {
+ mrMat.PutString(rSS, mnMatCol, nRow - mnTopRow);
+ }
+
+ void operator() (size_t nRow, const EditTextObject* pStr)
+ {
+ mrMat.PutString(mrStrPool.intern(ScEditUtil::GetString(*pStr, mpDoc)), mnMatCol, nRow - mnTopRow);
+ }
+};
+
+}
+
+bool ScColumn::ResolveStaticReference( ScMatrix& rMat, SCCOL nMatCol, SCROW nRow1, SCROW nRow2 )
+{
+ if (nRow1 > nRow2)
+ return false;
+
+ ToMatrixHandler aFunc(rMat, nMatCol, nRow1, &GetDoc());
+ sc::ParseAllNonEmpty(maCells.begin(), maCells, nRow1, nRow2, aFunc);
+ return true;
+}
+
+namespace {
+
+struct CellBucket
+{
+ SCSIZE mnEmpValStart;
+ SCSIZE mnNumValStart;
+ SCSIZE mnStrValStart;
+ SCSIZE mnEmpValCount;
+ std::vector<double> maNumVals;
+ std::vector<svl::SharedString> maStrVals;
+
+ CellBucket() : mnEmpValStart(0), mnNumValStart(0), mnStrValStart(0), mnEmpValCount(0) {}
+
+ void flush(ScMatrix& rMat, SCSIZE nCol)
+ {
+ if (mnEmpValCount)
+ {
+ rMat.PutEmptyResultVector(mnEmpValCount, nCol, mnEmpValStart);
+ reset();
+ }
+ else if (!maNumVals.empty())
+ {
+ const double* p = maNumVals.data();
+ rMat.PutDouble(p, maNumVals.size(), nCol, mnNumValStart);
+ reset();
+ }
+ else if (!maStrVals.empty())
+ {
+ const svl::SharedString* p = maStrVals.data();
+ rMat.PutString(p, maStrVals.size(), nCol, mnStrValStart);
+ reset();
+ }
+ }
+
+ void reset()
+ {
+ mnEmpValStart = mnNumValStart = mnStrValStart = 0;
+ mnEmpValCount = 0;
+ maNumVals.clear();
+ maStrVals.clear();
+ }
+};
+
+class FillMatrixHandler
+{
+ ScMatrix& mrMat;
+ size_t mnMatCol;
+ size_t mnTopRow;
+
+ ScDocument* mpDoc;
+ svl::SharedStringPool& mrPool;
+ svl::SharedStringPool* mpPool; // if matrix is not in the same document
+
+public:
+ FillMatrixHandler(ScMatrix& rMat, size_t nMatCol, size_t nTopRow, ScDocument* pDoc, svl::SharedStringPool* pPool) :
+ mrMat(rMat), mnMatCol(nMatCol), mnTopRow(nTopRow),
+ mpDoc(pDoc), mrPool(pDoc->GetSharedStringPool()), mpPool(pPool) {}
+
+ void operator() (const sc::CellStoreType::value_type& node, size_t nOffset, size_t nDataSize)
+ {
+ size_t nMatRow = node.position + nOffset - mnTopRow;
+
+ switch (node.type)
+ {
+ case sc::element_type_numeric:
+ {
+ const double* p = &sc::numeric_block::at(*node.data, nOffset);
+ mrMat.PutDouble(p, nDataSize, mnMatCol, nMatRow);
+ }
+ break;
+ case sc::element_type_string:
+ {
+ if (!mpPool)
+ {
+ const svl::SharedString* p = &sc::string_block::at(*node.data, nOffset);
+ mrMat.PutString(p, nDataSize, mnMatCol, nMatRow);
+ }
+ else
+ {
+ std::vector<svl::SharedString> aStrings;
+ aStrings.reserve(nDataSize);
+ const svl::SharedString* p = &sc::string_block::at(*node.data, nOffset);
+ for (size_t i = 0; i < nDataSize; ++i)
+ {
+ aStrings.push_back(mpPool->intern(p[i].getString()));
+ }
+ mrMat.PutString(aStrings.data(), aStrings.size(), mnMatCol, nMatRow);
+ }
+ }
+ break;
+ case sc::element_type_edittext:
+ {
+ std::vector<svl::SharedString> aSSs;
+ aSSs.reserve(nDataSize);
+ sc::edittext_block::const_iterator it = sc::edittext_block::begin(*node.data);
+ std::advance(it, nOffset);
+ sc::edittext_block::const_iterator itEnd = it;
+ std::advance(itEnd, nDataSize);
+ for (; it != itEnd; ++it)
+ {
+ OUString aStr = ScEditUtil::GetString(**it, mpDoc);
+ if (!mpPool)
+ aSSs.push_back(mrPool.intern(aStr));
+ else
+ aSSs.push_back(mpPool->intern(aStr));
+ }
+
+ const svl::SharedString* p = aSSs.data();
+ mrMat.PutString(p, nDataSize, mnMatCol, nMatRow);
+ }
+ break;
+ case sc::element_type_formula:
+ {
+ CellBucket aBucket;
+ sc::formula_block::const_iterator it = sc::formula_block::begin(*node.data);
+ std::advance(it, nOffset);
+ sc::formula_block::const_iterator itEnd = it;
+ std::advance(itEnd, nDataSize);
+
+ size_t nPrevRow = 0, nThisRow = node.position + nOffset;
+ for (; it != itEnd; ++it, nPrevRow = nThisRow, ++nThisRow)
+ {
+ ScFormulaCell& rCell = **it;
+
+ if (rCell.IsEmpty())
+ {
+ if (aBucket.mnEmpValCount && nThisRow == nPrevRow + 1)
+ {
+ // Secondary empty results.
+ ++aBucket.mnEmpValCount;
+ }
+ else
+ {
+ // First empty result.
+ aBucket.flush(mrMat, mnMatCol);
+ aBucket.mnEmpValStart = nThisRow - mnTopRow;
+ ++aBucket.mnEmpValCount;
+ }
+ continue;
+ }
+
+ FormulaError nErr;
+ double fVal;
+ if (rCell.GetErrorOrValue(nErr, fVal))
+ {
+ if (nErr != FormulaError::NONE)
+ fVal = CreateDoubleError(nErr);
+
+ if (!aBucket.maNumVals.empty() && nThisRow == nPrevRow + 1)
+ {
+ // Secondary numbers.
+ aBucket.maNumVals.push_back(fVal);
+ }
+ else
+ {
+ // First number.
+ aBucket.flush(mrMat, mnMatCol);
+ aBucket.mnNumValStart = nThisRow - mnTopRow;
+ aBucket.maNumVals.push_back(fVal);
+ }
+ continue;
+ }
+
+ svl::SharedString aStr = rCell.GetString();
+ if (mpPool)
+ aStr = mpPool->intern(aStr.getString());
+ if (!aBucket.maStrVals.empty() && nThisRow == nPrevRow + 1)
+ {
+ // Secondary strings.
+ aBucket.maStrVals.push_back(aStr);
+ }
+ else
+ {
+ // First string.
+ aBucket.flush(mrMat, mnMatCol);
+ aBucket.mnStrValStart = nThisRow - mnTopRow;
+ aBucket.maStrVals.push_back(aStr);
+ }
+ }
+
+ aBucket.flush(mrMat, mnMatCol);
+ }
+ break;
+ default:
+ ;
+ }
+ }
+};
+
+}
+
+void ScColumn::FillMatrix( ScMatrix& rMat, size_t nMatCol, SCROW nRow1, SCROW nRow2, svl::SharedStringPool* pPool ) const
+{
+ FillMatrixHandler aFunc(rMat, nMatCol, nRow1, &GetDoc(), pPool);
+ sc::ParseBlock(maCells.begin(), maCells, aFunc, nRow1, nRow2);
+}
+
+namespace {
+
+template<typename Blk>
+void getBlockIterators(
+ const sc::CellStoreType::iterator& it, size_t& rLenRemain,
+ typename Blk::iterator& rData, typename Blk::iterator& rDataEnd )
+{
+ rData = Blk::begin(*it->data);
+ if (rLenRemain >= it->size)
+ {
+ // Block is shorter than the remaining requested length.
+ rDataEnd = Blk::end(*it->data);
+ rLenRemain -= it->size;
+ }
+ else
+ {
+ rDataEnd = rData;
+ std::advance(rDataEnd, rLenRemain);
+ rLenRemain = 0;
+ }
+}
+
+bool appendToBlock(
+ ScDocument* pDoc, sc::FormulaGroupContext& rCxt, sc::FormulaGroupContext::ColArray& rColArray,
+ size_t nPos, size_t nArrayLen, const sc::CellStoreType::iterator& _it, const sc::CellStoreType::iterator& itEnd )
+{
+ svl::SharedStringPool& rPool = pDoc->GetSharedStringPool();
+ size_t nLenRemain = nArrayLen - nPos;
+
+ for (sc::CellStoreType::iterator it = _it; it != itEnd; ++it)
+ {
+ switch (it->type)
+ {
+ case sc::element_type_string:
+ {
+ sc::string_block::iterator itData, itDataEnd;
+ getBlockIterators<sc::string_block>(it, nLenRemain, itData, itDataEnd);
+ rCxt.ensureStrArray(rColArray, nArrayLen);
+
+ for (; itData != itDataEnd; ++itData, ++nPos)
+ (*rColArray.mpStrArray)[nPos] = itData->getData();
+ }
+ break;
+ case sc::element_type_edittext:
+ {
+ sc::edittext_block::iterator itData, itDataEnd;
+ getBlockIterators<sc::edittext_block>(it, nLenRemain, itData, itDataEnd);
+ rCxt.ensureStrArray(rColArray, nArrayLen);
+
+ for (; itData != itDataEnd; ++itData, ++nPos)
+ {
+ OUString aStr = ScEditUtil::GetString(**itData, pDoc);
+ (*rColArray.mpStrArray)[nPos] = rPool.intern(aStr).getData();
+ }
+ }
+ break;
+ case sc::element_type_formula:
+ {
+ sc::formula_block::iterator itData, itDataEnd;
+ getBlockIterators<sc::formula_block>(it, nLenRemain, itData, itDataEnd);
+
+ /* tdf#91416 setting progress in triggers a resize of the window
+ and so ScTabView::DoResize and an InterpretVisible and
+ InterpretDirtyCells which resets the mpFormulaGroupCxt that
+ the current rCxt points to, which is bad, so disable progress
+ during GetResult
+ */
+ ScProgress *pProgress = ScProgress::GetInterpretProgress();
+ bool bTempDisableProgress = pProgress && pProgress->Enabled();
+ if (bTempDisableProgress)
+ pProgress->Disable();
+
+ for (; itData != itDataEnd; ++itData, ++nPos)
+ {
+ ScFormulaCell& rFC = **itData;
+
+ sc::FormulaResultValue aRes = rFC.GetResult();
+
+ if (aRes.meType == sc::FormulaResultValue::Invalid || aRes.mnError != FormulaError::NONE)
+ {
+ if (aRes.mnError == FormulaError::CircularReference)
+ {
+ // This cell needs to be recalculated on next visit.
+ rFC.SetErrCode(FormulaError::NONE);
+ rFC.SetDirtyVar();
+ }
+ return false;
+ }
+
+ if (aRes.meType == sc::FormulaResultValue::String)
+ {
+ rCxt.ensureStrArray(rColArray, nArrayLen);
+ (*rColArray.mpStrArray)[nPos] = aRes.maString.getData();
+ }
+ else
+ {
+ rCxt.ensureNumArray(rColArray, nArrayLen);
+ (*rColArray.mpNumArray)[nPos] = aRes.mfValue;
+ }
+ }
+
+ if (bTempDisableProgress)
+ pProgress->Enable();
+ }
+ break;
+ case sc::element_type_empty:
+ {
+ if (nLenRemain > it->size)
+ {
+ nPos += it->size;
+ nLenRemain -= it->size;
+ }
+ else
+ {
+ nPos = nArrayLen;
+ nLenRemain = 0;
+ }
+ }
+ break;
+ case sc::element_type_numeric:
+ {
+ sc::numeric_block::iterator itData, itDataEnd;
+ getBlockIterators<sc::numeric_block>(it, nLenRemain, itData, itDataEnd);
+ rCxt.ensureNumArray(rColArray, nArrayLen);
+
+ for (; itData != itDataEnd; ++itData, ++nPos)
+ (*rColArray.mpNumArray)[nPos] = *itData;
+ }
+ break;
+ default:
+ return false;
+ }
+
+ if (!nLenRemain)
+ return true;
+ }
+
+ return false;
+}
+
+void copyFirstStringBlock(
+ ScDocument& rDoc, sc::FormulaGroupContext::StrArrayType& rArray, size_t nLen, const sc::CellStoreType::iterator& itBlk )
+{
+ sc::FormulaGroupContext::StrArrayType::iterator itArray = rArray.begin();
+
+ switch (itBlk->type)
+ {
+ case sc::element_type_string:
+ {
+ sc::string_block::iterator it = sc::string_block::begin(*itBlk->data);
+ sc::string_block::iterator itEnd = it;
+ std::advance(itEnd, nLen);
+ for (; it != itEnd; ++it, ++itArray)
+ *itArray = it->getData();
+ }
+ break;
+ case sc::element_type_edittext:
+ {
+ sc::edittext_block::iterator it = sc::edittext_block::begin(*itBlk->data);
+ sc::edittext_block::iterator itEnd = it;
+ std::advance(itEnd, nLen);
+
+ svl::SharedStringPool& rPool = rDoc.GetSharedStringPool();
+ for (; it != itEnd; ++it, ++itArray)
+ {
+ EditTextObject* pText = *it;
+ OUString aStr = ScEditUtil::GetString(*pText, &rDoc);
+ *itArray = rPool.intern(aStr).getData();
+ }
+ }
+ break;
+ default:
+ ;
+ }
+}
+
+sc::FormulaGroupContext::ColArray*
+copyFirstFormulaBlock(
+ sc::FormulaGroupContext& rCxt, const sc::CellStoreType::iterator& itBlk, size_t nArrayLen,
+ SCTAB nTab, SCCOL nCol )
+{
+ size_t nLen = std::min(itBlk->size, nArrayLen);
+
+ sc::formula_block::iterator it = sc::formula_block::begin(*itBlk->data);
+ sc::formula_block::iterator itEnd;
+
+ sc::FormulaGroupContext::NumArrayType* pNumArray = nullptr;
+ sc::FormulaGroupContext::StrArrayType* pStrArray = nullptr;
+
+ itEnd = it;
+ std::advance(itEnd, nLen);
+ size_t nPos = 0;
+ for (; it != itEnd; ++it, ++nPos)
+ {
+ ScFormulaCell& rFC = **it;
+ sc::FormulaResultValue aRes = rFC.GetResult();
+ if (aRes.meType == sc::FormulaResultValue::Invalid || aRes.mnError != FormulaError::NONE)
+ {
+ if (aRes.mnError == FormulaError::CircularReference)
+ {
+ // This cell needs to be recalculated on next visit.
+ rFC.SetErrCode(FormulaError::NONE);
+ rFC.SetDirtyVar();
+ }
+ return nullptr;
+ }
+
+ if (aRes.meType == sc::FormulaResultValue::Value)
+ {
+ if (!pNumArray)
+ {
+ rCxt.m_NumArrays.push_back(
+ std::make_unique<sc::FormulaGroupContext::NumArrayType>(nArrayLen,
+ std::numeric_limits<double>::quiet_NaN()));
+ pNumArray = rCxt.m_NumArrays.back().get();
+ }
+
+ (*pNumArray)[nPos] = aRes.mfValue;
+ }
+ else
+ {
+ if (!pStrArray)
+ {
+ rCxt.m_StrArrays.push_back(
+ std::make_unique<sc::FormulaGroupContext::StrArrayType>(nArrayLen, nullptr));
+ pStrArray = rCxt.m_StrArrays.back().get();
+ }
+
+ (*pStrArray)[nPos] = aRes.maString.getData();
+ }
+ }
+
+ if (!pNumArray && !pStrArray)
+ // At least one of these arrays should be allocated.
+ return nullptr;
+
+ return rCxt.setCachedColArray(nTab, nCol, pNumArray, pStrArray);
+}
+
+struct NonNullStringFinder
+{
+ bool operator() (const rtl_uString* p) const { return p != nullptr; }
+};
+
+bool hasNonEmpty( const sc::FormulaGroupContext::StrArrayType& rArray, SCROW nRow1, SCROW nRow2 )
+{
+ // The caller has to make sure the array is at least nRow2+1 long.
+ sc::FormulaGroupContext::StrArrayType::const_iterator it = rArray.begin();
+ std::advance(it, nRow1);
+ sc::FormulaGroupContext::StrArrayType::const_iterator itEnd = it;
+ std::advance(itEnd, nRow2-nRow1+1);
+ return std::any_of(it, itEnd, NonNullStringFinder());
+}
+
+struct ProtectFormulaGroupContext
+{
+ ProtectFormulaGroupContext( ScDocument* d )
+ : doc( d ) { doc->BlockFormulaGroupContextDiscard( true ); }
+ ~ProtectFormulaGroupContext()
+ { doc->BlockFormulaGroupContextDiscard( false ); }
+ ScDocument* doc;
+};
+
+}
+
+formula::VectorRefArray ScColumn::FetchVectorRefArray( SCROW nRow1, SCROW nRow2 )
+{
+ if (nRow1 > nRow2)
+ return formula::VectorRefArray(formula::VectorRefArray::Invalid);
+
+ // See if the requested range is already cached.
+ ScDocument& rDocument = GetDoc();
+ sc::FormulaGroupContext& rCxt = *(rDocument.GetFormulaGroupContext());
+ sc::FormulaGroupContext::ColArray* pColArray = rCxt.getCachedColArray(nTab, nCol, nRow2+1);
+ if (pColArray)
+ {
+ const double* pNum = nullptr;
+ if (pColArray->mpNumArray)
+ pNum = &(*pColArray->mpNumArray)[nRow1];
+
+ rtl_uString** pStr = nullptr;
+ if (pColArray->mpStrArray && hasNonEmpty(*pColArray->mpStrArray, nRow1, nRow2))
+ pStr = &(*pColArray->mpStrArray)[nRow1];
+
+ return formula::VectorRefArray(pNum, pStr);
+ }
+
+ // ScColumn::CellStorageModified() simply discards the entire cache (FormulaGroupContext)
+ // on any modification. However getting cell values may cause this to be called
+ // if interpreting a cell results in a change to it (not just its result though).
+ // So temporarily block the discarding.
+ ProtectFormulaGroupContext protectContext(&GetDoc());
+
+ // We need to fetch all cell values from row 0 to nRow2 for caching purposes.
+ sc::CellStoreType::iterator itBlk = maCells.begin();
+ switch (itBlk->type)
+ {
+ case sc::element_type_numeric:
+ {
+ if (o3tl::make_unsigned(nRow2) < itBlk->size)
+ {
+ // Requested range falls within the first block. No need to cache.
+ const double* p = &sc::numeric_block::at(*itBlk->data, nRow1);
+ return formula::VectorRefArray(p);
+ }
+
+ // Allocate a new array and copy the values to it.
+ sc::numeric_block::const_iterator it = sc::numeric_block::begin(*itBlk->data);
+ sc::numeric_block::const_iterator itEnd = sc::numeric_block::end(*itBlk->data);
+ rCxt.m_NumArrays.push_back(
+ std::make_unique<sc::FormulaGroupContext::NumArrayType>(it, itEnd));
+ sc::FormulaGroupContext::NumArrayType& rArray = *rCxt.m_NumArrays.back();
+ // allocate to the requested length.
+ rArray.resize(nRow2+1, std::numeric_limits<double>::quiet_NaN());
+
+ pColArray = rCxt.setCachedColArray(nTab, nCol, &rArray, nullptr);
+ if (!pColArray)
+ // Failed to insert a new cached column array.
+ return formula::VectorRefArray(formula::VectorRefArray::Invalid);
+
+ // Fill the remaining array with values from the following blocks.
+ size_t nPos = itBlk->size;
+ ++itBlk;
+ if (!appendToBlock(&rDocument, rCxt, *pColArray, nPos, nRow2+1, itBlk, maCells.end()))
+ {
+ rCxt.discardCachedColArray(nTab, nCol);
+ return formula::VectorRefArray(formula::VectorRefArray::Invalid);
+ }
+
+ rtl_uString** pStr = nullptr;
+ if (pColArray->mpStrArray && hasNonEmpty(*pColArray->mpStrArray, nRow1, nRow2))
+ pStr = &(*pColArray->mpStrArray)[nRow1];
+
+ return formula::VectorRefArray(&(*pColArray->mpNumArray)[nRow1], pStr);
+ }
+ break;
+ case sc::element_type_string:
+ case sc::element_type_edittext:
+ {
+ rCxt.m_StrArrays.push_back(
+ std::make_unique<sc::FormulaGroupContext::StrArrayType>(nRow2+1, nullptr));
+ sc::FormulaGroupContext::StrArrayType& rArray = *rCxt.m_StrArrays.back();
+ pColArray = rCxt.setCachedColArray(nTab, nCol, nullptr, &rArray);
+ if (!pColArray)
+ // Failed to insert a new cached column array.
+ return formula::VectorRefArray();
+
+ if (o3tl::make_unsigned(nRow2) < itBlk->size)
+ {
+ // Requested range falls within the first block.
+ copyFirstStringBlock(rDocument, rArray, nRow2+1, itBlk);
+ return formula::VectorRefArray(&rArray[nRow1]);
+ }
+
+ copyFirstStringBlock(rDocument, rArray, itBlk->size, itBlk);
+
+ // Fill the remaining array with values from the following blocks.
+ size_t nPos = itBlk->size;
+ ++itBlk;
+ if (!appendToBlock(&rDocument, rCxt, *pColArray, nPos, nRow2+1, itBlk, maCells.end()))
+ {
+ rCxt.discardCachedColArray(nTab, nCol);
+ return formula::VectorRefArray(formula::VectorRefArray::Invalid);
+ }
+
+ assert(pColArray->mpStrArray);
+
+ rtl_uString** pStr = nullptr;
+ if (hasNonEmpty(*pColArray->mpStrArray, nRow1, nRow2))
+ pStr = &(*pColArray->mpStrArray)[nRow1];
+
+ if (pColArray->mpNumArray)
+ return formula::VectorRefArray(&(*pColArray->mpNumArray)[nRow1], pStr);
+ else
+ return formula::VectorRefArray(pStr);
+ }
+ break;
+ case sc::element_type_formula:
+ {
+ if (o3tl::make_unsigned(nRow2) < itBlk->size)
+ {
+ // Requested length is within a single block, and the data is
+ // not cached.
+ pColArray = copyFirstFormulaBlock(rCxt, itBlk, nRow2+1, nTab, nCol);
+ if (!pColArray)
+ // Failed to insert a new cached column array.
+ return formula::VectorRefArray(formula::VectorRefArray::Invalid);
+
+ const double* pNum = nullptr;
+ rtl_uString** pStr = nullptr;
+ if (pColArray->mpNumArray)
+ pNum = &(*pColArray->mpNumArray)[nRow1];
+ if (pColArray->mpStrArray)
+ pStr = &(*pColArray->mpStrArray)[nRow1];
+
+ return formula::VectorRefArray(pNum, pStr);
+ }
+
+ pColArray = copyFirstFormulaBlock(rCxt, itBlk, nRow2+1, nTab, nCol);
+ if (!pColArray)
+ {
+ // Failed to insert a new cached column array.
+ return formula::VectorRefArray(formula::VectorRefArray::Invalid);
+ }
+
+ size_t nPos = itBlk->size;
+ ++itBlk;
+ if (!appendToBlock(&rDocument, rCxt, *pColArray, nPos, nRow2+1, itBlk, maCells.end()))
+ {
+ rCxt.discardCachedColArray(nTab, nCol);
+ return formula::VectorRefArray(formula::VectorRefArray::Invalid);
+ }
+
+ const double* pNum = nullptr;
+ rtl_uString** pStr = nullptr;
+ if (pColArray->mpNumArray)
+ pNum = &(*pColArray->mpNumArray)[nRow1];
+ if (pColArray->mpStrArray && hasNonEmpty(*pColArray->mpStrArray, nRow1, nRow2))
+ pStr = &(*pColArray->mpStrArray)[nRow1];
+
+ return formula::VectorRefArray(pNum, pStr);
+ }
+ break;
+ case sc::element_type_empty:
+ {
+ // Fill the whole length with NaN's.
+ rCxt.m_NumArrays.push_back(
+ std::make_unique<sc::FormulaGroupContext::NumArrayType>(nRow2+1,
+ std::numeric_limits<double>::quiet_NaN()));
+ sc::FormulaGroupContext::NumArrayType& rArray = *rCxt.m_NumArrays.back();
+ pColArray = rCxt.setCachedColArray(nTab, nCol, &rArray, nullptr);
+ if (!pColArray)
+ // Failed to insert a new cached column array.
+ return formula::VectorRefArray(formula::VectorRefArray::Invalid);
+
+ if (o3tl::make_unsigned(nRow2) < itBlk->size)
+ return formula::VectorRefArray(&(*pColArray->mpNumArray)[nRow1]);
+
+ // Fill the remaining array with values from the following blocks.
+ size_t nPos = itBlk->size;
+ ++itBlk;
+ if (!appendToBlock(&rDocument, rCxt, *pColArray, nPos, nRow2+1, itBlk, maCells.end()))
+ {
+ rCxt.discardCachedColArray(nTab, nCol);
+ return formula::VectorRefArray(formula::VectorRefArray::Invalid);
+ }
+
+ if (pColArray->mpStrArray && hasNonEmpty(*pColArray->mpStrArray, nRow1, nRow2))
+ return formula::VectorRefArray(&(*pColArray->mpNumArray)[nRow1], &(*pColArray->mpStrArray)[nRow1]);
+ else
+ return formula::VectorRefArray(&(*pColArray->mpNumArray)[nRow1]);
+ }
+ break;
+ default:
+ ;
+ }
+
+ return formula::VectorRefArray(formula::VectorRefArray::Invalid);
+}
+
+#ifdef DBG_UTIL
+static void assertNoInterpretNeededHelper( const sc::CellStoreType::value_type& node,
+ size_t nOffset, size_t nDataSize )
+{
+ switch (node.type)
+ {
+ case sc::element_type_formula:
+ {
+ sc::formula_block::const_iterator it = sc::formula_block::begin(*node.data);
+ std::advance(it, nOffset);
+ sc::formula_block::const_iterator itEnd = it;
+ std::advance(itEnd, nDataSize);
+ for (; it != itEnd; ++it)
+ {
+ const ScFormulaCell* pCell = *it;
+ assert( !pCell->NeedsInterpret());
+ }
+ break;
+ }
+ }
+}
+void ScColumn::AssertNoInterpretNeeded( SCROW nRow1, SCROW nRow2 )
+{
+ assert(nRow2 >= nRow1);
+ sc::ParseBlock( maCells.begin(), maCells, assertNoInterpretNeededHelper, 0, nRow2 );
+}
+#endif
+
+void ScColumn::SetFormulaResults( SCROW nRow, const double* pResults, size_t nLen )
+{
+ sc::CellStoreType::position_type aPos = maCells.position(nRow);
+ sc::CellStoreType::iterator it = aPos.first;
+ if (it->type != sc::element_type_formula)
+ {
+ // This is not a formula block.
+ assert( false );
+ return;
+ }
+
+ size_t nBlockLen = it->size - aPos.second;
+ if (nBlockLen < nLen)
+ // Result array is longer than the length of formula cells. Not good.
+ return;
+
+ sc::formula_block::iterator itCell = sc::formula_block::begin(*it->data);
+ std::advance(itCell, aPos.second);
+
+ const double* pResEnd = pResults + nLen;
+ for (; pResults != pResEnd; ++pResults, ++itCell)
+ {
+ ScFormulaCell& rCell = **itCell;
+ FormulaError nErr = GetDoubleErrorValue(*pResults);
+ if (nErr != FormulaError::NONE)
+ rCell.SetResultError(nErr);
+ else
+ rCell.SetResultDouble(*pResults);
+ rCell.ResetDirty();
+ rCell.SetChanged(true);
+ }
+}
+
+void ScColumn::CalculateInThread( ScInterpreterContext& rContext, SCROW nRow, size_t nLen, size_t nOffset,
+ unsigned nThisThread, unsigned nThreadsTotal)
+{
+ assert(GetDoc().IsThreadedGroupCalcInProgress());
+
+ sc::CellStoreType::position_type aPos = maCells.position(nRow);
+ sc::CellStoreType::iterator it = aPos.first;
+ if (it->type != sc::element_type_formula)
+ {
+ // This is not a formula block.
+ assert( false );
+ return;
+ }
+
+ size_t nBlockLen = it->size - aPos.second;
+ if (nBlockLen < nLen)
+ // Length is longer than the length of formula cells. Not good.
+ return;
+
+ sc::formula_block::iterator itCell = sc::formula_block::begin(*it->data);
+ std::advance(itCell, aPos.second);
+
+ for (size_t i = 0; i < nLen; ++i, ++itCell)
+ {
+ if (nThreadsTotal > 0 && ((i + nOffset) % nThreadsTotal) != nThisThread)
+ continue;
+
+ ScFormulaCell& rCell = **itCell;
+ if (!rCell.NeedsInterpret())
+ continue;
+ // Here we don't call IncInterpretLevel() and DecInterpretLevel() as this call site is
+ // always in a threaded calculation.
+ rCell.InterpretTail(rContext, ScFormulaCell::SCITP_NORMAL);
+ }
+}
+
+void ScColumn::HandleStuffAfterParallelCalculation( SCROW nRow, size_t nLen, ScInterpreter* pInterpreter )
+{
+ sc::CellStoreType::position_type aPos = maCells.position(nRow);
+ sc::CellStoreType::iterator it = aPos.first;
+ if (it->type != sc::element_type_formula)
+ {
+ // This is not a formula block.
+ assert( false );
+ return;
+ }
+
+ size_t nBlockLen = it->size - aPos.second;
+ if (nBlockLen < nLen)
+ // Length is longer than the length of formula cells. Not good.
+ return;
+
+ sc::formula_block::iterator itCell = sc::formula_block::begin(*it->data);
+ std::advance(itCell, aPos.second);
+
+ for (size_t i = 0; i < nLen; ++i, ++itCell)
+ {
+ ScFormulaCell& rCell = **itCell;
+ rCell.HandleStuffAfterParallelCalculation(pInterpreter);
+ }
+}
+
+void ScColumn::SetNumberFormat( SCROW nRow, sal_uInt32 nNumberFormat )
+{
+ ApplyAttr(nRow, SfxUInt32Item(ATTR_VALUE_FORMAT, nNumberFormat));
+}
+
+ScFormulaCell * const * ScColumn::GetFormulaCellBlockAddress( SCROW nRow, size_t& rBlockSize ) const
+{
+ if (!GetDoc().ValidRow(nRow))
+ {
+ rBlockSize = 0;
+ return nullptr;
+ }
+
+ std::pair<sc::CellStoreType::const_iterator,size_t> aPos = maCells.position(nRow);
+ sc::CellStoreType::const_iterator it = aPos.first;
+ if (it == maCells.end())
+ {
+ rBlockSize = 0;
+ return nullptr;
+ }
+
+ if (it->type != sc::element_type_formula)
+ {
+ // Not a formula cell.
+ rBlockSize = 0;
+ return nullptr;
+ }
+
+ rBlockSize = it->size;
+ return &sc::formula_block::at(*it->data, aPos.second);
+}
+
+const ScFormulaCell* ScColumn::FetchFormulaCell( SCROW nRow ) const
+{
+ size_t nBlockSize = 0;
+ ScFormulaCell const * const * pp = GetFormulaCellBlockAddress( nRow, nBlockSize );
+ return pp ? *pp : nullptr;
+}
+
+void ScColumn::FindDataAreaPos(SCROW& rRow, bool bDown) const
+{
+ // If the cell is empty, find the next non-empty cell position. If the
+ // cell is not empty, find the last non-empty cell position in the current
+ // contiguous cell block.
+
+ std::pair<sc::CellStoreType::const_iterator,size_t> aPos = maCells.position(rRow);
+ sc::CellStoreType::const_iterator it = aPos.first;
+ if (it == maCells.end())
+ // Invalid row.
+ return;
+
+ if (it->type == sc::element_type_empty)
+ {
+ // Current cell is empty. Find the next non-empty cell.
+ rRow = FindNextVisibleRowWithContent(it, rRow, bDown);
+ return;
+ }
+
+ // Current cell is not empty.
+ SCROW nNextRow = FindNextVisibleRow(rRow, bDown);
+ aPos = maCells.position(it, nNextRow);
+ it = aPos.first;
+ if (it->type == sc::element_type_empty)
+ {
+ // Next visible cell is empty. Find the next non-empty cell.
+ rRow = FindNextVisibleRowWithContent(it, nNextRow, bDown);
+ return;
+ }
+
+ // Next visible cell is non-empty. Find the edge that's still visible.
+ SCROW nLastRow = nNextRow;
+ do
+ {
+ nNextRow = FindNextVisibleRow(nLastRow, bDown);
+ if (nNextRow == nLastRow)
+ break;
+
+ aPos = maCells.position(it, nNextRow);
+ it = aPos.first;
+ if (it->type != sc::element_type_empty)
+ nLastRow = nNextRow;
+ }
+ while (it->type != sc::element_type_empty);
+
+ rRow = nLastRow;
+}
+
+bool ScColumn::HasDataAt(SCROW nRow, ScDataAreaExtras* pDataAreaExtras ) const
+{
+ if (pDataAreaExtras)
+ GetDataExtrasAt( nRow, *pDataAreaExtras);
+
+ return maCells.get_type(nRow) != sc::element_type_empty;
+}
+
+bool ScColumn::HasDataAt( sc::ColumnBlockConstPosition& rBlockPos, SCROW nRow,
+ ScDataAreaExtras* pDataAreaExtras ) const
+{
+ if (pDataAreaExtras)
+ GetDataExtrasAt( nRow, *pDataAreaExtras);
+
+ std::pair<sc::CellStoreType::const_iterator,size_t> aPos = maCells.position(rBlockPos.miCellPos, nRow);
+ if (aPos.first == maCells.end())
+ return false;
+ rBlockPos.miCellPos = aPos.first; // Store this for next call.
+ return aPos.first->type != sc::element_type_empty;
+}
+
+bool ScColumn::HasDataAt( sc::ColumnBlockPosition& rBlockPos, SCROW nRow,
+ ScDataAreaExtras* pDataAreaExtras )
+{
+ if (pDataAreaExtras)
+ GetDataExtrasAt( nRow, *pDataAreaExtras);
+
+ std::pair<sc::CellStoreType::iterator,size_t> aPos = maCells.position(rBlockPos.miCellPos, nRow);
+ if (aPos.first == maCells.end())
+ return false;
+ rBlockPos.miCellPos = aPos.first; // Store this for next call.
+ return aPos.first->type != sc::element_type_empty;
+}
+
+void ScColumn::GetDataExtrasAt( SCROW nRow, ScDataAreaExtras& rDataAreaExtras ) const
+{
+ if (rDataAreaExtras.mnStartRow <= nRow && nRow <= rDataAreaExtras.mnEndRow)
+ return;
+
+ // Check in order of likeliness.
+ if ( (rDataAreaExtras.mbCellFormats && HasVisibleAttrIn(nRow, nRow)) ||
+ (rDataAreaExtras.mbCellNotes && !IsNotesEmptyBlock(nRow, nRow)) ||
+ (rDataAreaExtras.mbCellDrawObjects && !IsDrawObjectsEmptyBlock(nRow, nRow)))
+ {
+ if (rDataAreaExtras.mnStartRow > nRow)
+ rDataAreaExtras.mnStartRow = nRow;
+ if (rDataAreaExtras.mnEndRow < nRow)
+ rDataAreaExtras.mnEndRow = nRow;
+ }
+}
+
+namespace {
+
+class FindUsedRowsHandler
+{
+ typedef mdds::flat_segment_tree<SCROW,bool> UsedRowsType;
+ UsedRowsType& mrUsed;
+ UsedRowsType::const_iterator miUsed;
+public:
+ explicit FindUsedRowsHandler(UsedRowsType& rUsed) : mrUsed(rUsed), miUsed(rUsed.begin()) {}
+
+ void operator() (const sc::CellStoreType::value_type& node, size_t nOffset, size_t nDataSize)
+ {
+ if (node.type == sc::element_type_empty)
+ return;
+
+ SCROW nRow1 = node.position + nOffset;
+ SCROW nRow2 = nRow1 + nDataSize - 1;
+ miUsed = mrUsed.insert(miUsed, nRow1, nRow2+1, true).first;
+ }
+};
+
+}
+
+void ScColumn::FindUsed( SCROW nStartRow, SCROW nEndRow, mdds::flat_segment_tree<SCROW,bool>& rUsed ) const
+{
+ FindUsedRowsHandler aFunc(rUsed);
+ sc::ParseBlock(maCells.begin(), maCells, aFunc, nStartRow, nEndRow);
+}
+
+namespace {
+
+void startListening(
+ sc::BroadcasterStoreType& rStore, sc::BroadcasterStoreType::iterator& itBlockPos, size_t nElemPos,
+ SCROW nRow, SvtListener& rLst)
+{
+ switch (itBlockPos->type)
+ {
+ case sc::element_type_broadcaster:
+ {
+ // Broadcaster already exists here.
+ SvtBroadcaster* pBC = sc::broadcaster_block::at(*itBlockPos->data, nElemPos);
+ rLst.StartListening(*pBC);
+ }
+ break;
+ case mdds::mtv::element_type_empty:
+ {
+ // No broadcaster exists at this position yet.
+ SvtBroadcaster* pBC = new SvtBroadcaster;
+ rLst.StartListening(*pBC);
+ itBlockPos = rStore.set(itBlockPos, nRow, pBC); // Store the block position for next iteration.
+ }
+ break;
+ default:
+ assert(false && "wrong block type encountered in the broadcaster storage.");
+ }
+}
+
+}
+
+void ScColumn::StartListening( SvtListener& rLst, SCROW nRow )
+{
+ std::pair<sc::BroadcasterStoreType::iterator,size_t> aPos = maBroadcasters.position(nRow);
+ startListening(maBroadcasters, aPos.first, aPos.second, nRow, rLst);
+}
+
+void ScColumn::EndListening( SvtListener& rLst, SCROW nRow )
+{
+ SvtBroadcaster* pBC = GetBroadcaster(nRow);
+ if (!pBC)
+ return;
+
+ rLst.EndListening(*pBC);
+ if (!pBC->HasListeners())
+ { // There is no more listeners for this cell. Remove the broadcaster.
+ if(GetDoc().IsDelayedDeletingBroadcasters())
+ mbEmptyBroadcastersPending = true;
+ else
+ maBroadcasters.set_empty(nRow, nRow);
+ }
+}
+
+void ScColumn::StartListening( sc::StartListeningContext& rCxt, const ScAddress& rAddress, SvtListener& rLst )
+{
+ if (!GetDoc().ValidRow(rAddress.Row()))
+ return;
+
+ sc::ColumnBlockPosition* p = rCxt.getBlockPosition(rAddress.Tab(), rAddress.Col());
+ if (!p)
+ return;
+
+ sc::BroadcasterStoreType::iterator& it = p->miBroadcasterPos;
+ std::pair<sc::BroadcasterStoreType::iterator,size_t> aPos = maBroadcasters.position(it, rAddress.Row());
+ it = aPos.first; // store the block position for next iteration.
+ startListening(maBroadcasters, it, aPos.second, rAddress.Row(), rLst);
+}
+
+void ScColumn::EndListening( sc::EndListeningContext& rCxt, const ScAddress& rAddress, SvtListener& rListener )
+{
+ sc::ColumnBlockPosition* p = rCxt.getBlockPosition(rAddress.Tab(), rAddress.Col());
+ if (!p)
+ return;
+
+ sc::BroadcasterStoreType::iterator& it = p->miBroadcasterPos;
+ std::pair<sc::BroadcasterStoreType::iterator,size_t> aPos = maBroadcasters.position(it, rAddress.Row());
+ it = aPos.first; // store the block position for next iteration.
+ if (it->type != sc::element_type_broadcaster)
+ return;
+
+ SvtBroadcaster* pBC = sc::broadcaster_block::at(*it->data, aPos.second);
+ assert(pBC);
+
+ rListener.EndListening(*pBC);
+ if (!pBC->HasListeners())
+ // There is no more listeners for this cell. Add it to the purge list for later purging.
+ rCxt.addEmptyBroadcasterPosition(rAddress.Tab(), rAddress.Col(), rAddress.Row());
+}
+
+namespace {
+
+class CompileDBFormulaHandler
+{
+ sc::CompileFormulaContext& mrCxt;
+
+public:
+ explicit CompileDBFormulaHandler( sc::CompileFormulaContext& rCxt ) :
+ mrCxt(rCxt) {}
+
+ void operator() (size_t, ScFormulaCell* p)
+ {
+ p->CompileDBFormula(mrCxt);
+ }
+};
+
+struct CompileColRowNameFormulaHandler
+{
+ sc::CompileFormulaContext& mrCxt;
+public:
+ explicit CompileColRowNameFormulaHandler( sc::CompileFormulaContext& rCxt ) : mrCxt(rCxt) {}
+
+ void operator() (size_t, ScFormulaCell* p)
+ {
+ p->CompileColRowNameFormula(mrCxt);
+ }
+};
+
+}
+
+void ScColumn::CompileDBFormula( sc::CompileFormulaContext& rCxt )
+{
+ CompileDBFormulaHandler aFunc(rCxt);
+ sc::ProcessFormula(maCells, aFunc);
+ RegroupFormulaCells();
+}
+
+void ScColumn::CompileColRowNameFormula( sc::CompileFormulaContext& rCxt )
+{
+ CompileColRowNameFormulaHandler aFunc(rCxt);
+ sc::ProcessFormula(maCells, aFunc);
+ RegroupFormulaCells();
+}
+
+namespace {
+
+class UpdateSubTotalHandler
+{
+ ScFunctionData& mrData;
+
+ void update(double fVal, bool bVal)
+ {
+ if (mrData.getError())
+ return;
+
+ switch (mrData.getFunc())
+ {
+ case SUBTOTAL_FUNC_CNT2: // everything
+ mrData.update( fVal);
+ break;
+ default: // only numeric values
+ if (bVal)
+ mrData.update( fVal);
+ }
+ }
+
+public:
+ explicit UpdateSubTotalHandler(ScFunctionData& rData) : mrData(rData) {}
+
+ void operator() (size_t /*nRow*/, double fVal)
+ {
+ update(fVal, true);
+ }
+
+ void operator() (size_t /*nRow*/, const svl::SharedString&)
+ {
+ update(0.0, false);
+ }
+
+ void operator() (size_t /*nRow*/, const EditTextObject*)
+ {
+ update(0.0, false);
+ }
+
+ void operator() (size_t /*nRow*/, ScFormulaCell* pCell)
+ {
+ double fVal = 0.0;
+ bool bVal = false;
+ if (mrData.getFunc() != SUBTOTAL_FUNC_CNT2) // it doesn't interest us
+ {
+
+ if (pCell->GetErrCode() != FormulaError::NONE)
+ {
+ if (mrData.getFunc() != SUBTOTAL_FUNC_CNT) // simply remove from count
+ mrData.setError();
+ }
+ else if (pCell->IsValue())
+ {
+ fVal = pCell->GetValue();
+ bVal = true;
+ }
+ // otherwise text
+ }
+
+ update(fVal, bVal);
+ }
+};
+
+}
+
+// multiple selections:
+void ScColumn::UpdateSelectionFunction(
+ const ScRangeList& rRanges, ScFunctionData& rData, const ScFlatBoolRowSegments& rHiddenRows )
+{
+ sc::SingleColumnSpanSet aSpanSet(GetDoc().GetSheetLimits());
+ aSpanSet.scan(rRanges, nTab, nCol); // mark all selected rows.
+
+ if (aSpanSet.empty())
+ return; // nothing to do, bail out
+
+ // Exclude all hidden rows.
+ ScFlatBoolRowSegments::RangeData aRange;
+ SCROW nRow = 0;
+ while (nRow <= GetDoc().MaxRow())
+ {
+ if (!rHiddenRows.getRangeData(nRow, aRange))
+ break;
+
+ if (aRange.mbValue)
+ // Hidden range detected.
+ aSpanSet.set(nRow, aRange.mnRow2, false);
+
+ nRow = aRange.mnRow2 + 1;
+ }
+
+ sc::SingleColumnSpanSet::SpansType aSpans;
+ aSpanSet.getSpans(aSpans);
+
+ switch (rData.getFunc())
+ {
+ case SUBTOTAL_FUNC_SELECTION_COUNT:
+ {
+ // Simply count selected rows regardless of cell contents.
+ for (const auto& rSpan : aSpans)
+ rData.update( rSpan.mnRow2 - rSpan.mnRow1 + 1);
+ }
+ break;
+ case SUBTOTAL_FUNC_CNT2:
+ {
+ // We need to parse all non-empty cells.
+ sc::CellStoreType::const_iterator itCellPos = maCells.begin();
+ UpdateSubTotalHandler aFunc(rData);
+ for (const auto& rSpan : aSpans)
+ {
+ itCellPos = sc::ParseAllNonEmpty(
+ itCellPos, maCells, rSpan.mnRow1, rSpan.mnRow2, aFunc);
+ }
+ }
+ break;
+ default:
+ {
+ // We need to parse only numeric values.
+ sc::CellStoreType::const_iterator itCellPos = maCells.begin();
+ UpdateSubTotalHandler aFunc(rData);
+ for (const auto& rSpan : aSpans)
+ {
+ itCellPos = sc::ParseFormulaNumeric(
+ itCellPos, maCells, rSpan.mnRow1, rSpan.mnRow2, aFunc);
+ }
+ }
+ }
+}
+
+namespace {
+
+class WeightedCounter
+{
+ sal_uLong mnCount;
+public:
+ WeightedCounter() : mnCount(0) {}
+
+ void operator() (const sc::CellStoreType::value_type& node)
+ {
+ mnCount += getWeight(node);
+ }
+
+ static sal_uLong getWeight(const sc::CellStoreType::value_type& node)
+ {
+ switch (node.type)
+ {
+ case sc::element_type_numeric:
+ case sc::element_type_string:
+ return node.size;
+ case sc::element_type_formula:
+ {
+ // Each formula cell is worth its code length plus 5.
+ return std::accumulate(sc::formula_block::begin(*node.data), sc::formula_block::end(*node.data), size_t(0),
+ [](const size_t& rCount, const ScFormulaCell* p) { return rCount + 5 + p->GetCode()->GetCodeLen(); });
+ }
+ case sc::element_type_edittext:
+ // each edit-text cell is worth 50.
+ return node.size * 50;
+ default:
+ return 0;
+ }
+ }
+
+ sal_uLong getCount() const { return mnCount; }
+};
+
+class WeightedCounterWithRows
+{
+ const SCROW mnStartRow;
+ const SCROW mnEndRow;
+ sal_uLong mnCount;
+
+public:
+ WeightedCounterWithRows(SCROW nStartRow, SCROW nEndRow)
+ : mnStartRow(nStartRow)
+ , mnEndRow(nEndRow)
+ , mnCount(0)
+ {
+ }
+
+ void operator() (const sc::CellStoreType::value_type& node)
+ {
+ const SCROW nRow1 = node.position;
+ const SCROW nRow2 = nRow1 + 1;
+
+ if ((nRow2 >= mnStartRow) && (nRow1 <= mnEndRow))
+ {
+ mnCount += WeightedCounter::getWeight(node);
+ }
+ }
+
+ sal_uLong getCount() const { return mnCount; }
+};
+
+}
+
+sal_uInt64 ScColumn::GetWeightedCount() const
+{
+ const WeightedCounter aFunc = std::for_each(maCells.begin(), maCells.end(),
+ WeightedCounter());
+ return aFunc.getCount();
+}
+
+sal_uInt64 ScColumn::GetWeightedCount(SCROW nStartRow, SCROW nEndRow) const
+{
+ const WeightedCounterWithRows aFunc = std::for_each(maCells.begin(), maCells.end(),
+ WeightedCounterWithRows(nStartRow, nEndRow));
+ return aFunc.getCount();
+}
+
+namespace {
+
+class CodeCounter
+{
+ sal_uInt64 mnCount;
+public:
+ CodeCounter() : mnCount(0) {}
+
+ void operator() (size_t, const ScFormulaCell* p)
+ {
+ mnCount += p->GetCode()->GetCodeLen();
+ }
+
+ sal_uInt64 getCount() const { return mnCount; }
+};
+
+}
+
+sal_uInt64 ScColumn::GetCodeCount() const
+{
+ CodeCounter aFunc;
+ sc::ParseFormula(maCells, aFunc);
+ return aFunc.getCount();
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sc/source/core/data/column3.cxx b/sc/source/core/data/column3.cxx
new file mode 100644
index 000000000..ead02920a
--- /dev/null
+++ b/sc/source/core/data/column3.cxx
@@ -0,0 +1,3726 @@
+/* -*- 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 <column.hxx>
+
+#include <scitems.hxx>
+#include <formulacell.hxx>
+#include <document.hxx>
+#include <attarray.hxx>
+#include <patattr.hxx>
+#include <cellform.hxx>
+#include <typedstrdata.hxx>
+#include <formula/errorcodes.hxx>
+#include <formula/token.hxx>
+#include <brdcst.hxx>
+#include <docoptio.hxx>
+#include <subtotal.hxx>
+#include <markdata.hxx>
+#include <stringutil.hxx>
+#include <docpool.hxx>
+#include <cellvalue.hxx>
+#include <tokenarray.hxx>
+#include <clipcontext.hxx>
+#include <columnspanset.hxx>
+#include <mtvcellfunc.hxx>
+#include <scopetools.hxx>
+#include <editutil.hxx>
+#include <sharedformula.hxx>
+#include <listenercontext.hxx>
+#include <filterentries.hxx>
+#include <conditio.hxx>
+#include <colorscale.hxx>
+#include <editeng/brushitem.hxx>
+#include <editeng/colritem.hxx>
+
+#include <com/sun/star/i18n/LocaleDataItem2.hpp>
+#include <com/sun/star/lang/IllegalArgumentException.hpp>
+
+#include <memory>
+
+#include <o3tl/deleter.hxx>
+
+#include <rtl/tencinfo.h>
+
+#include <svl/numformat.hxx>
+#include <svl/zforlist.hxx>
+#include <svl/zformat.hxx>
+#include <svl/sharedstringpool.hxx>
+#include <osl/diagnose.h>
+
+#include <cstdio>
+#include <refdata.hxx>
+
+using ::com::sun::star::i18n::LocaleDataItem2;
+
+using namespace formula;
+
+void ScColumn::Broadcast( SCROW nRow )
+{
+ ScHint aHint(SfxHintId::ScDataChanged, ScAddress(nCol, nRow, nTab));
+ GetDoc().Broadcast(aHint);
+}
+
+void ScColumn::BroadcastCells( const std::vector<SCROW>& rRows, SfxHintId nHint )
+{
+ if (rRows.empty())
+ return;
+
+ // Broadcast the changes.
+ ScDocument& rDocument = GetDoc();
+ ScHint aHint(nHint, ScAddress(nCol, 0, nTab));
+ for (const auto& rRow : rRows)
+ {
+ aHint.SetAddressRow(rRow);
+ rDocument.Broadcast(aHint);
+ }
+}
+
+void ScColumn::BroadcastRows( SCROW nStartRow, SCROW nEndRow, SfxHintId nHint )
+{
+ if( nStartRow > GetLastDataPos())
+ return;
+ sc::SingleColumnSpanSet aSpanSet(GetDoc().GetSheetLimits());
+ aSpanSet.scan(*this, nStartRow, nEndRow);
+ std::vector<SCROW> aRows;
+ aSpanSet.getRows(aRows);
+ BroadcastCells(aRows, nHint);
+}
+
+namespace {
+
+class CellInterpreterBase
+{
+protected:
+ void Interpret(ScFormulaCell* p)
+ {
+ // Interpret() takes a range in a formula group, so group those together.
+ if( !groupCells.empty() && p->GetCellGroup() == groupCells.back()->GetCellGroup()
+ && p->aPos.Row() == groupCells.back()->aPos.Row() + 1 )
+ {
+ assert( p->aPos.Tab() == groupCells.back()->aPos.Tab()
+ && p->aPos.Col() == groupCells.back()->aPos.Col());
+ groupCells.push_back(p); // Extend range.
+ return;
+ }
+ flushPending();
+ if( !p->GetCellGroup())
+ {
+ p->Interpret();
+ return;
+ }
+ groupCells.push_back(p);
+
+ }
+ ~CellInterpreterBase()
+ {
+ suppress_fun_call_w_exception(flushPending());
+ }
+private:
+ void flushPending()
+ {
+ if(groupCells.empty())
+ return;
+ SCROW firstRow = groupCells.front()->GetCellGroup()->mpTopCell->aPos.Row();
+ if(!groupCells.front()->Interpret(
+ groupCells.front()->aPos.Row() - firstRow, groupCells.back()->aPos.Row() - firstRow))
+ {
+ // Interpret() will try to group-interpret the given cell range if possible, but if that
+ // is not possible, it will interpret just the given cell. So if group-interpreting
+ // wasn't possible, interpret them one by one.
+ for(ScFormulaCell* cell : groupCells)
+ cell->Interpret();
+ }
+ groupCells.clear();
+ }
+ std::vector<ScFormulaCell*> groupCells;
+};
+
+class DirtyCellInterpreter : public CellInterpreterBase
+{
+public:
+ void operator() (size_t, ScFormulaCell* p)
+ {
+ if(p->GetDirty())
+ Interpret(p);
+ }
+};
+
+class NeedsInterpretCellInterpreter : public CellInterpreterBase
+{
+public:
+ void operator() (size_t, ScFormulaCell* p)
+ {
+ if(p->NeedsInterpret())
+ {
+ Interpret(p);
+ // In some cases such as circular dependencies Interpret()
+ // will not reset the dirty flag, check that in order to tell
+ // the caller that the cell range may trigger Interpret() again.
+ if(p->NeedsInterpret())
+ allInterpreted = false;
+ }
+ }
+ bool allInterpreted = true;
+};
+
+}
+
+void ScColumn::InterpretDirtyCells( SCROW nRow1, SCROW nRow2 )
+{
+ if (!GetDoc().ValidRow(nRow1) || !GetDoc().ValidRow(nRow2) || nRow1 > nRow2)
+ return;
+
+ DirtyCellInterpreter aFunc;
+ sc::ProcessFormula(maCells.begin(), maCells, nRow1, nRow2, aFunc);
+}
+
+bool ScColumn::InterpretCellsIfNeeded( SCROW nRow1, SCROW nRow2 )
+{
+ if (!GetDoc().ValidRow(nRow1) || !GetDoc().ValidRow(nRow2) || nRow1 > nRow2)
+ return false;
+
+ NeedsInterpretCellInterpreter aFunc;
+ sc::ProcessFormula(maCells.begin(), maCells, nRow1, nRow2, aFunc);
+ return aFunc.allInterpreted;
+}
+
+void ScColumn::DeleteContent( SCROW nRow, bool bBroadcast )
+{
+ sc::CellStoreType::position_type aPos = maCells.position(nRow);
+ sc::CellStoreType::iterator it = aPos.first;
+ if (it == maCells.end())
+ return;
+
+ if (it->type == sc::element_type_formula)
+ {
+ ScFormulaCell* p = sc::formula_block::at(*it->data, aPos.second);
+ p->EndListeningTo(GetDoc());
+ sc::SharedFormulaUtil::unshareFormulaCell(aPos, *p);
+ }
+ maCells.set_empty(nRow, nRow);
+
+ if (bBroadcast)
+ {
+ Broadcast(nRow);
+ CellStorageModified();
+ }
+}
+
+void ScColumn::Delete( SCROW nRow )
+{
+ DeleteContent(nRow, false);
+ maCellTextAttrs.set_empty(nRow, nRow);
+ maCellNotes.set_empty(nRow, nRow);
+ maSparklines.set_empty(nRow, nRow);
+
+ Broadcast(nRow);
+ CellStorageModified();
+}
+
+void ScColumn::FreeAll()
+{
+ maCells.event_handler().stop();
+
+ auto maxRowCount = GetDoc().GetMaxRowCount();
+ // Keep a logical empty range of 0-rDoc.MaxRow() at all times.
+ maCells.clear();
+ maCells.resize(maxRowCount);
+ maCellTextAttrs.clear();
+ maCellTextAttrs.resize(maxRowCount);
+ maCellNotes.clear();
+ maCellNotes.resize(maxRowCount);
+ maSparklines.clear();
+ maSparklines.resize(maxRowCount);
+ CellStorageModified();
+}
+
+void ScColumn::FreeNotes()
+{
+ maCellNotes.clear();
+ maCellNotes.resize(GetDoc().GetMaxRowCount());
+}
+
+namespace {
+
+class ShiftFormulaPosHandler
+{
+public:
+
+ void operator() (size_t nRow, ScFormulaCell* pCell)
+ {
+ pCell->aPos.SetRow(nRow);
+ }
+};
+
+}
+
+void ScColumn::DeleteRow( SCROW nStartRow, SCSIZE nSize, std::vector<ScAddress>* pGroupPos )
+{
+ pAttrArray->DeleteRow( nStartRow, nSize );
+
+ SCROW nEndRow = nStartRow + nSize - 1;
+
+ maBroadcasters.erase(nStartRow, nEndRow);
+ maBroadcasters.resize(GetDoc().GetMaxRowCount());
+
+ CellNotesDeleting(nStartRow, nEndRow, false);
+ maCellNotes.erase(nStartRow, nEndRow);
+ maCellNotes.resize(GetDoc().GetMaxRowCount());
+
+ // See if we have any cells that would get deleted or shifted by deletion.
+ sc::CellStoreType::position_type aPos = maCells.position(nStartRow);
+ sc::CellStoreType::iterator itCell = aPos.first;
+ if (itCell->type == sc::element_type_empty)
+ {
+ // This is an empty block. If this is the last block, then there is no cells to delete or shift.
+ sc::CellStoreType::iterator itTest = itCell;
+ ++itTest;
+ if (itTest == maCells.end())
+ {
+ // No cells are affected by this deletion. Bail out.
+ CellStorageModified(); // broadcast array has been modified.
+ return;
+ }
+ }
+
+ // Check if there are any cells below the end row that will get shifted.
+ bool bShiftCells = false;
+ if (nEndRow < GetDoc().MaxRow()) //only makes sense to do this if there *is* a row after the end row
+ {
+ aPos = maCells.position(itCell, nEndRow+1);
+ itCell = aPos.first;
+ if (itCell->type == sc::element_type_empty)
+ {
+ // This block is empty. See if there is any block that follows.
+ sc::CellStoreType::iterator itTest = itCell;
+ ++itTest;
+ if (itTest != maCells.end())
+ // Non-empty block follows -> cells that will get shifted.
+ bShiftCells = true;
+ }
+ else
+ bShiftCells = true;
+ }
+
+ sc::SingleColumnSpanSet aNonEmptySpans(GetDoc().GetSheetLimits());
+ if (bShiftCells)
+ {
+ // Mark all non-empty cell positions below the end row.
+ sc::ColumnBlockConstPosition aBlockPos;
+ aBlockPos.miCellPos = itCell;
+ aNonEmptySpans.scan(aBlockPos, *this, nEndRow+1, GetDoc().MaxRow());
+ }
+
+ sc::AutoCalcSwitch aACSwitch(GetDoc(), false);
+
+ // Remove the cells.
+ maCells.erase(nStartRow, nEndRow);
+ maCells.resize(GetDoc().GetMaxRowCount());
+
+ // Get the position again after the container change.
+ aPos = maCells.position(nStartRow);
+
+ // Shift the formula cell positions below the start row.
+ ShiftFormulaPosHandler aShiftFormulaFunc;
+ sc::ProcessFormula(aPos.first, maCells, nStartRow, GetDoc().MaxRow(), aShiftFormulaFunc);
+
+ bool bJoined = sc::SharedFormulaUtil::joinFormulaCellAbove(aPos);
+ if (bJoined && pGroupPos)
+ pGroupPos->push_back(ScAddress(nCol, nStartRow, nTab));
+
+ // Shift the text attribute array too (before the broadcast).
+ maCellTextAttrs.erase(nStartRow, nEndRow);
+ maCellTextAttrs.resize(GetDoc().GetMaxRowCount());
+
+ CellStorageModified();
+}
+
+sc::CellStoreType::iterator ScColumn::GetPositionToInsert( SCROW nRow, std::vector<SCROW>& rNewSharedRows,
+ bool bInsertFormula )
+{
+ return GetPositionToInsert(maCells.begin(), nRow, rNewSharedRows, bInsertFormula);
+}
+
+void ScColumn::JoinNewFormulaCell(
+ const sc::CellStoreType::position_type& aPos, ScFormulaCell& rCell )
+{
+ // Check the previous row position for possible grouping.
+ if (aPos.first->type == sc::element_type_formula && aPos.second > 0)
+ {
+ ScFormulaCell& rPrev = *sc::formula_block::at(*aPos.first->data, aPos.second-1);
+ sc::CellStoreType::position_type aPosPrev = aPos;
+ --aPosPrev.second;
+ sc::SharedFormulaUtil::joinFormulaCells(aPosPrev, rPrev, rCell);
+ }
+
+ // Check the next row position for possible grouping.
+ if (aPos.first->type == sc::element_type_formula && aPos.second+1 < aPos.first->size)
+ {
+ ScFormulaCell& rNext = *sc::formula_block::at(*aPos.first->data, aPos.second+1);
+ sc::SharedFormulaUtil::joinFormulaCells(aPos, rCell, rNext);
+ }
+}
+
+void ScColumn::DetachFormulaCell(
+ const sc::CellStoreType::position_type& aPos, ScFormulaCell& rCell, std::vector<SCROW>& rNewSharedRows )
+{
+ if (!GetDoc().IsClipOrUndo())
+ {
+#if USE_FORMULA_GROUP_LISTENER
+ if (rCell.IsShared() && rCell.GetSharedLength() > 1)
+ {
+ // Record new spans (shared or remaining single) that will result
+ // from unsharing to reestablish listeners.
+ // Same cases as in unshareFormulaCell().
+ // XXX NOTE: this is not part of unshareFormulaCell() because that
+ // is called in other contexts as well, for which passing and
+ // determining the rows vector would be superfluous. If that was
+ // needed, move it there.
+ const SCROW nSharedTopRow = rCell.GetSharedTopRow();
+ const SCROW nSharedLength = rCell.GetSharedLength();
+ if (rCell.aPos.Row() == nSharedTopRow)
+ {
+ // Top cell.
+ // Next row will be new shared top or single cell.
+ rNewSharedRows.push_back( nSharedTopRow + 1);
+ rNewSharedRows.push_back( nSharedTopRow + nSharedLength - 1);
+ }
+ else if (rCell.aPos.Row() == nSharedTopRow + nSharedLength - 1)
+ {
+ // Bottom cell.
+ // Current shared top row will be new shared top again or
+ // single cell.
+ rNewSharedRows.push_back( nSharedTopRow);
+ rNewSharedRows.push_back( rCell.aPos.Row() - 1);
+ }
+ else
+ {
+ // Some mid cell.
+ // Current shared top row will be new shared top again or
+ // single cell, plus a new shared top below or single cell.
+ rNewSharedRows.push_back( nSharedTopRow);
+ rNewSharedRows.push_back( rCell.aPos.Row() - 1);
+ rNewSharedRows.push_back( rCell.aPos.Row() + 1);
+ rNewSharedRows.push_back( nSharedTopRow + nSharedLength - 1);
+ }
+ }
+#endif
+
+ // Have the dying formula cell stop listening.
+ // If in a shared formula group this ends the group listening.
+ rCell.EndListeningTo(GetDoc());
+ }
+
+ sc::SharedFormulaUtil::unshareFormulaCell(aPos, rCell);
+}
+
+void ScColumn::StartListeningUnshared( const std::vector<SCROW>& rNewSharedRows )
+{
+ assert(rNewSharedRows.empty() || rNewSharedRows.size() == 2 || rNewSharedRows.size() == 4);
+ ScDocument& rDoc = GetDoc();
+ if (rNewSharedRows.empty() || rDoc.IsDelayedFormulaGrouping())
+ return;
+
+ auto pPosSet = std::make_shared<sc::ColumnBlockPositionSet>(rDoc);
+ sc::StartListeningContext aStartCxt(rDoc, pPosSet);
+ sc::EndListeningContext aEndCxt(rDoc, pPosSet);
+ if (rNewSharedRows.size() >= 2)
+ {
+ if(!rDoc.CanDelayStartListeningFormulaCells( this, rNewSharedRows[0], rNewSharedRows[1]))
+ StartListeningFormulaCells(aStartCxt, aEndCxt, rNewSharedRows[0], rNewSharedRows[1]);
+ }
+ if (rNewSharedRows.size() >= 4)
+ {
+ if(!rDoc.CanDelayStartListeningFormulaCells( this, rNewSharedRows[2], rNewSharedRows[3]))
+ StartListeningFormulaCells(aStartCxt, aEndCxt, rNewSharedRows[2], rNewSharedRows[3]);
+ }
+}
+
+namespace {
+
+class AttachFormulaCellsHandler
+{
+ sc::StartListeningContext& mrCxt;
+
+public:
+ explicit AttachFormulaCellsHandler(sc::StartListeningContext& rCxt)
+ : mrCxt(rCxt) {}
+
+ void operator() (size_t /*nRow*/, ScFormulaCell* pCell)
+ {
+ pCell->StartListeningTo(mrCxt);
+ }
+};
+
+class DetachFormulaCellsHandler
+{
+ ScDocument& mrDoc;
+ sc::EndListeningContext* mpCxt;
+
+public:
+ DetachFormulaCellsHandler( ScDocument& rDoc, sc::EndListeningContext* pCxt ) :
+ mrDoc(rDoc), mpCxt(pCxt) {}
+
+ void operator() (size_t /*nRow*/, ScFormulaCell* pCell)
+ {
+ if (mpCxt)
+ pCell->EndListeningTo(*mpCxt);
+ else
+ pCell->EndListeningTo(mrDoc);
+ }
+};
+
+}
+
+void ScColumn::DetachFormulaCells(
+ const sc::CellStoreType::position_type& aPos, size_t nLength, std::vector<SCROW>* pNewSharedRows )
+{
+ const size_t nRow = aPos.first->position + aPos.second;
+ const size_t nNextTopRow = nRow + nLength; // start row of next formula group.
+
+ bool bLowerSplitOff = false;
+ if (pNewSharedRows && !GetDoc().IsClipOrUndo())
+ {
+ const ScFormulaCell* pFC = sc::SharedFormulaUtil::getSharedTopFormulaCell(aPos);
+ if (pFC)
+ {
+ const SCROW nTopRow = pFC->GetSharedTopRow();
+ const SCROW nBotRow = nTopRow + pFC->GetSharedLength() - 1;
+ // nTopRow <= nRow <= nBotRow, because otherwise pFC would not exist.
+ if (nTopRow < static_cast<SCROW>(nRow))
+ {
+ // Upper part will be split off.
+ pNewSharedRows->push_back(nTopRow);
+ pNewSharedRows->push_back(nRow - 1);
+ }
+ if (static_cast<SCROW>(nNextTopRow) <= nBotRow)
+ {
+ // Lower part will be split off.
+ pNewSharedRows->push_back(nNextTopRow);
+ pNewSharedRows->push_back(nBotRow);
+ bLowerSplitOff = true;
+ }
+ }
+ }
+
+ // Split formula grouping at the top and bottom boundaries.
+ sc::SharedFormulaUtil::splitFormulaCellGroup(aPos, nullptr);
+
+ if (nLength > 0 && GetDoc().ValidRow(nNextTopRow))
+ {
+ if (pNewSharedRows && !bLowerSplitOff && !GetDoc().IsClipOrUndo())
+ {
+ sc::CellStoreType::position_type aPos2 = maCells.position(aPos.first, nNextTopRow-1);
+ const ScFormulaCell* pFC = sc::SharedFormulaUtil::getSharedTopFormulaCell(aPos2);
+ if (pFC)
+ {
+ const SCROW nTopRow = pFC->GetSharedTopRow();
+ const SCROW nBotRow = nTopRow + pFC->GetSharedLength() - 1;
+ // nRow < nTopRow < nNextTopRow <= nBotRow
+ if (static_cast<SCROW>(nNextTopRow) <= nBotRow)
+ {
+ // Lower part will be split off.
+ pNewSharedRows->push_back(nNextTopRow);
+ pNewSharedRows->push_back(nBotRow);
+ }
+ }
+ }
+
+ sc::CellStoreType::position_type aPos2 = maCells.position(aPos.first, nNextTopRow);
+ sc::SharedFormulaUtil::splitFormulaCellGroup(aPos2, nullptr);
+ }
+
+ if (GetDoc().IsClipOrUndo())
+ return;
+
+ DetachFormulaCellsHandler aFunc(GetDoc(), nullptr);
+ sc::ProcessFormula(aPos.first, maCells, nRow, nNextTopRow-1, aFunc);
+}
+
+void ScColumn::AttachFormulaCells( sc::StartListeningContext& rCxt, SCROW nRow1, SCROW nRow2 )
+{
+ sc::CellStoreType::position_type aPos = maCells.position(nRow1);
+ sc::CellStoreType::iterator it = aPos.first;
+
+ sc::SharedFormulaUtil::joinFormulaCellAbove(aPos);
+ if (GetDoc().ValidRow(nRow2+1))
+ {
+ aPos = maCells.position(it, nRow2+1);
+ sc::SharedFormulaUtil::joinFormulaCellAbove(aPos);
+ }
+
+ if (GetDoc().IsClipOrUndo())
+ return;
+
+ // Need to process (start listening) entire shared formula groups, not just
+ // a slice thereof.
+ bool bEnlargedDown = false;
+ aPos = maCells.position(nRow1);
+ it = aPos.first;
+ if (it->type == sc::element_type_formula)
+ {
+ ScFormulaCell& rCell = *sc::formula_block::at(*it->data, aPos.second);
+ if (rCell.IsShared())
+ {
+ nRow1 = std::min( nRow1, rCell.GetSharedTopRow());
+ if (nRow2 < rCell.GetSharedTopRow() + rCell.GetSharedLength())
+ {
+ nRow2 = rCell.GetSharedTopRow() + rCell.GetSharedLength() - 1;
+ bEnlargedDown = true;
+ // Same end row is also enlarged, i.e. doesn't need to be
+ // checked for another group.
+ }
+ }
+ }
+ if (!bEnlargedDown)
+ {
+ aPos = maCells.position(it, nRow2);
+ it = aPos.first;
+ if (it->type == sc::element_type_formula)
+ {
+ ScFormulaCell& rCell = *sc::formula_block::at(*it->data, aPos.second);
+ if (rCell.IsShared())
+ {
+ nRow2 = std::max( nRow2, rCell.GetSharedTopRow() + rCell.GetSharedLength() - 1);
+ }
+ }
+ }
+
+ AttachFormulaCellsHandler aFunc(rCxt);
+ sc::ProcessFormula(it, maCells, nRow1, nRow2, aFunc);
+}
+
+void ScColumn::DetachFormulaCells( sc::EndListeningContext& rCxt, SCROW nRow1, SCROW nRow2 )
+{
+ sc::CellStoreType::position_type aPos = maCells.position(nRow1);
+ sc::CellStoreType::iterator it = aPos.first;
+
+ // Split formula grouping at the top and bottom boundaries.
+ sc::SharedFormulaUtil::splitFormulaCellGroup(aPos, &rCxt);
+ if (GetDoc().ValidRow(nRow2+1))
+ {
+ aPos = maCells.position(it, nRow2+1);
+ sc::SharedFormulaUtil::splitFormulaCellGroup(aPos, &rCxt);
+ }
+
+ if (GetDoc().IsClipOrUndo())
+ return;
+
+ DetachFormulaCellsHandler aFunc(GetDoc(), &rCxt);
+ sc::ProcessFormula(it, maCells, nRow1, nRow2, aFunc);
+}
+
+static void lcl_AddFormulaGroupBoundaries(const sc::CellStoreType::position_type& rPos,
+ std::vector<SCROW>& rNewSharedRows )
+{
+ sc::CellStoreType::iterator itRet = rPos.first;
+ if (itRet->type != sc::element_type_formula)
+ return;
+
+ ScFormulaCell& rFC = *sc::formula_block::at(*itRet->data, rPos.second);
+ if ( rFC.IsShared() )
+ {
+ const SCROW nSharedTopRow = rFC.GetSharedTopRow();
+ const SCROW nSharedLength = rFC.GetSharedLength();
+ rNewSharedRows.push_back( nSharedTopRow);
+ rNewSharedRows.push_back( nSharedTopRow + nSharedLength - 1);
+ }
+ else
+ {
+ const SCROW nRow = rFC.aPos.Row();
+ rNewSharedRows.push_back( nRow);
+ rNewSharedRows.push_back( nRow);
+ }
+}
+
+sc::CellStoreType::iterator ScColumn::GetPositionToInsert( const sc::CellStoreType::iterator& it, SCROW nRow,
+ std::vector<SCROW>& rNewSharedRows, bool bInsertFormula )
+{
+ // See if we are overwriting an existing formula cell.
+ sc::CellStoreType::position_type aPos = maCells.position(it, nRow);
+ sc::CellStoreType::iterator itRet = aPos.first;
+
+ if (itRet->type == sc::element_type_formula)
+ {
+ ScFormulaCell& rCell = *sc::formula_block::at(*itRet->data, aPos.second);
+ DetachFormulaCell(aPos, rCell, rNewSharedRows);
+ }
+ else if (bInsertFormula && !GetDoc().IsClipOrUndo())
+ {
+ if (nRow > 0)
+ {
+ sc::CellStoreType::position_type aPosBefore = maCells.position(maCells.begin(), nRow-1);
+ lcl_AddFormulaGroupBoundaries(aPosBefore, rNewSharedRows);
+ }
+ if (nRow < GetDoc().MaxRow())
+ {
+ sc::CellStoreType::position_type aPosAfter = maCells.position(maCells.begin(), nRow+1);
+ lcl_AddFormulaGroupBoundaries(aPosAfter, rNewSharedRows);
+ }
+ }
+
+ return itRet;
+}
+
+void ScColumn::AttachNewFormulaCell(
+ const sc::CellStoreType::iterator& itPos, SCROW nRow, ScFormulaCell& rCell,
+ const std::vector<SCROW>& rNewSharedRows,
+ bool bJoin, sc::StartListeningType eListenType )
+{
+ AttachNewFormulaCell(maCells.position(itPos, nRow), rCell, rNewSharedRows, bJoin, eListenType);
+}
+
+void ScColumn::AttachNewFormulaCell(
+ const sc::CellStoreType::position_type& aPos, ScFormulaCell& rCell,
+ const std::vector<SCROW>& rNewSharedRows,
+ bool bJoin, sc::StartListeningType eListenType )
+{
+ if (bJoin)
+ // See if this new formula cell can join an existing shared formula group.
+ JoinNewFormulaCell(aPos, rCell);
+
+ // When we insert from the Clipboard we still have wrong (old) References!
+ // First they are rewired in CopyBlockFromClip via UpdateReference and the
+ // we call StartListeningFromClip and BroadcastFromClip.
+ // If we insert into the Clipboard/andoDoc, we do not use a Broadcast.
+ // After Import we call CalcAfterLoad and in there Listening.
+ ScDocument& rDocument = GetDoc();
+ if (rDocument.IsClipOrUndo() || rDocument.IsInsertingFromOtherDoc())
+ return;
+
+ switch (eListenType)
+ {
+ case sc::ConvertToGroupListening:
+ {
+ auto pPosSet = std::make_shared<sc::ColumnBlockPositionSet>(rDocument);
+ sc::StartListeningContext aStartCxt(rDocument, pPosSet);
+ sc::EndListeningContext aEndCxt(rDocument, pPosSet);
+ SCROW nStartRow, nEndRow;
+ nStartRow = nEndRow = aPos.first->position + aPos.second;
+ for (const SCROW nR : rNewSharedRows)
+ {
+ if (nStartRow > nR)
+ nStartRow = nR;
+ if (nEndRow < nR)
+ nEndRow = nR;
+ }
+ StartListeningFormulaCells(aStartCxt, aEndCxt, nStartRow, nEndRow);
+ }
+ break;
+ case sc::SingleCellListening:
+ rCell.StartListeningTo(rDocument);
+ StartListeningUnshared( rNewSharedRows);
+ break;
+ case sc::NoListening:
+ default:
+ if (!rNewSharedRows.empty())
+ {
+ assert(rNewSharedRows.size() == 2 || rNewSharedRows.size() == 4);
+ // Calling SetNeedsListeningGroup() with a top row sets it to
+ // all affected formula cells of that group.
+ const ScFormulaCell* pFC = GetFormulaCell( rNewSharedRows[0]);
+ assert(pFC); // that *is* supposed to be a top row
+ if (pFC && !pFC->NeedsListening())
+ SetNeedsListeningGroup( rNewSharedRows[0]);
+ if (rNewSharedRows.size() > 2)
+ {
+ pFC = GetFormulaCell( rNewSharedRows[2]);
+ assert(pFC); // that *is* supposed to be a top row
+ if (pFC && !pFC->NeedsListening())
+ SetNeedsListeningGroup( rNewSharedRows[2]);
+ }
+ }
+ break;
+ }
+
+ if (!rDocument.IsCalcingAfterLoad())
+ rCell.SetDirty();
+}
+
+void ScColumn::AttachNewFormulaCells( const sc::CellStoreType::position_type& aPos, size_t nLength,
+ std::vector<SCROW>& rNewSharedRows )
+{
+ // Make sure the whole length consists of formula cells.
+ if (aPos.first->type != sc::element_type_formula)
+ return;
+
+ if (aPos.first->size < aPos.second + nLength)
+ // Block is shorter than specified length.
+ return;
+
+ // Join the top and bottom cells only.
+ ScFormulaCell* pCell1 = sc::formula_block::at(*aPos.first->data, aPos.second);
+ JoinNewFormulaCell(aPos, *pCell1);
+
+ sc::CellStoreType::position_type aPosLast = aPos;
+ aPosLast.second += nLength - 1;
+ ScFormulaCell* pCell2 = sc::formula_block::at(*aPosLast.first->data, aPosLast.second);
+ JoinNewFormulaCell(aPosLast, *pCell2);
+
+ ScDocument& rDocument = GetDoc();
+ if (rDocument.IsClipOrUndo() || rDocument.IsInsertingFromOtherDoc())
+ return;
+
+ const bool bShared = pCell1->IsShared() || pCell2->IsShared();
+ if (bShared)
+ {
+ const SCROW nTopRow = (pCell1->IsShared() ? pCell1->GetSharedTopRow() : pCell1->aPos.Row());
+ const SCROW nBotRow = (pCell2->IsShared() ?
+ pCell2->GetSharedTopRow() + pCell2->GetSharedLength() - 1 : pCell2->aPos.Row());
+ if (rNewSharedRows.empty())
+ {
+ rNewSharedRows.push_back( nTopRow);
+ rNewSharedRows.push_back( nBotRow);
+ }
+ else if (rNewSharedRows.size() == 2)
+ {
+ // Combine into one span.
+ if (rNewSharedRows[0] > nTopRow)
+ rNewSharedRows[0] = nTopRow;
+ if (rNewSharedRows[1] < nBotRow)
+ rNewSharedRows[1] = nBotRow;
+ }
+ else if (rNewSharedRows.size() == 4)
+ {
+ // Merge into one span.
+ // The original two spans are ordered from top to bottom.
+ std::vector<SCROW> aRows { std::min( rNewSharedRows[0], nTopRow), std::max( rNewSharedRows[3], nBotRow) };
+ rNewSharedRows.swap( aRows);
+ }
+ else
+ {
+ assert(!"rNewSharedRows?");
+ }
+ }
+ StartListeningUnshared( rNewSharedRows);
+
+ sc::StartListeningContext aCxt(rDocument);
+ ScFormulaCell** pp = &sc::formula_block::at(*aPos.first->data, aPos.second);
+ ScFormulaCell** ppEnd = pp + nLength;
+ for (; pp != ppEnd; ++pp)
+ {
+ if (!bShared)
+ (*pp)->StartListeningTo(aCxt);
+ if (!rDocument.IsCalcingAfterLoad())
+ (*pp)->SetDirty();
+ }
+}
+
+void ScColumn::BroadcastNewCell( SCROW nRow )
+{
+ // When we insert from the Clipboard we still have wrong (old) References!
+ // First they are rewired in CopyBlockFromClip via UpdateReference and the
+ // we call StartListeningFromClip and BroadcastFromClip.
+ // If we insert into the Clipboard/andoDoc, we do not use a Broadcast.
+ // After Import we call CalcAfterLoad and in there Listening.
+ if (GetDoc().IsClipOrUndo() || GetDoc().IsInsertingFromOtherDoc() || GetDoc().IsCalcingAfterLoad())
+ return;
+
+ Broadcast(nRow);
+}
+
+bool ScColumn::UpdateScriptType( sc::CellTextAttr& rAttr, SCROW nRow, sc::CellStoreType::iterator& itr )
+{
+ if (rAttr.mnScriptType != SvtScriptType::UNKNOWN)
+ // Already updated. Nothing to do.
+ return false;
+
+ // Script type not yet determined. Determine the real script
+ // type, and store it.
+ const ScPatternAttr* pPattern = GetPattern(nRow);
+ if (!pPattern)
+ return false;
+
+ sc::CellStoreType::position_type pos = maCells.position(itr, nRow);
+ itr = pos.first;
+ size_t nOffset = pos.second;
+ ScRefCellValue aCell = GetCellValue( itr, nOffset );
+ ScAddress aPos(nCol, nRow, nTab);
+
+ ScDocument& rDocument = GetDoc();
+ const SfxItemSet* pCondSet = nullptr;
+ ScConditionalFormatList* pCFList = rDocument.GetCondFormList(nTab);
+ if (pCFList)
+ {
+ const ScCondFormatItem& rItem =
+ pPattern->GetItem(ATTR_CONDITIONAL);
+ const ScCondFormatIndexes& rData = rItem.GetCondFormatData();
+ pCondSet = rDocument.GetCondResult(aCell, aPos, *pCFList, rData);
+ }
+
+ SvNumberFormatter* pFormatter = rDocument.GetFormatTable();
+
+ const Color* pColor;
+ sal_uInt32 nFormat = pPattern->GetNumberFormat(pFormatter, pCondSet);
+ OUString aStr = ScCellFormat::GetString(aCell, nFormat, &pColor, *pFormatter, rDocument);
+
+ // Store the real script type to the array.
+ rAttr.mnScriptType = rDocument.GetStringScriptType(aStr);
+ return true;
+}
+
+namespace {
+
+class DeleteAreaHandler
+{
+ ScDocument& mrDoc;
+ std::vector<ScFormulaCell*> maFormulaCells;
+ sc::SingleColumnSpanSet maDeleteRanges;
+
+ bool mbNumeric:1;
+ bool mbString:1;
+ bool mbFormula:1;
+ bool mbDateTime:1;
+ ScColumn& mrCol;
+
+public:
+ DeleteAreaHandler(ScDocument& rDoc, InsertDeleteFlags nDelFlag, ScColumn& rCol) :
+ mrDoc(rDoc),
+ maDeleteRanges(rDoc.GetSheetLimits()),
+ mbNumeric(nDelFlag & InsertDeleteFlags::VALUE),
+ mbString(nDelFlag & InsertDeleteFlags::STRING),
+ mbFormula(nDelFlag & InsertDeleteFlags::FORMULA),
+ mbDateTime(nDelFlag & InsertDeleteFlags::DATETIME),
+ mrCol(rCol) {}
+
+ void operator() (const sc::CellStoreType::value_type& node, size_t nOffset, size_t nDataSize)
+ {
+ switch (node.type)
+ {
+ case sc::element_type_numeric:
+ // Numeric type target datetime and number, thus we have a dedicated function
+ if (!mbNumeric && !mbDateTime)
+ return;
+
+ // If numeric and datetime selected, delete full range
+ if (mbNumeric && mbDateTime)
+ break;
+
+ deleteNumeric(node, nOffset, nDataSize);
+ return;
+ case sc::element_type_string:
+ case sc::element_type_edittext:
+ if (!mbString)
+ return;
+ break;
+ case sc::element_type_formula:
+ {
+ if (!mbFormula)
+ return;
+
+ sc::formula_block::iterator it = sc::formula_block::begin(*node.data) + nOffset;
+ sc::formula_block::iterator itEnd = it + nDataSize;
+ maFormulaCells.insert(maFormulaCells.end(), it, itEnd);
+ }
+ break;
+ case sc::element_type_empty:
+ default:
+ return;
+ }
+
+ // Tag these cells for deletion.
+ SCROW nRow1 = node.position + nOffset;
+ SCROW nRow2 = nRow1 + nDataSize - 1;
+ maDeleteRanges.set(nRow1, nRow2, true);
+ }
+
+ void deleteNumeric(const sc::CellStoreType::value_type& node, size_t nOffset, size_t nDataSize)
+ {
+ size_t nStart = node.position + nOffset;
+ size_t nElements = 1;
+ bool bLastTypeDateTime = isDateTime(nStart); // true = datetime, false = numeric
+ size_t nCount = nStart + nDataSize;
+
+ for (size_t i = nStart + 1; i < nCount; i++)
+ {
+ bool bIsDateTime = isDateTime(i);
+
+ // same type as previous
+ if (bIsDateTime == bLastTypeDateTime)
+ {
+ nElements++;
+ }
+ // type switching
+ else
+ {
+ deleteNumberOrDateTime(nStart, nStart + nElements - 1, bLastTypeDateTime);
+ nStart += nElements;
+ nElements = 1;
+ }
+
+ bLastTypeDateTime = bIsDateTime;
+ }
+
+ // delete last cells
+ deleteNumberOrDateTime(nStart, nStart + nElements - 1, bLastTypeDateTime);
+ }
+
+ void deleteNumberOrDateTime(SCROW nRow1, SCROW nRow2, bool dateTime)
+ {
+ if (!dateTime && !mbNumeric) // numeric flag must be selected
+ return;
+ if (dateTime && !mbDateTime) // datetime flag must be selected
+ return;
+ maDeleteRanges.set(nRow1, nRow2, true);
+ }
+
+ bool isDateTime(size_t position)
+ {
+ SvNumFormatType nType = mrDoc.GetFormatTable()->GetType(
+ mrCol.GetAttr(position, ATTR_VALUE_FORMAT).GetValue());
+
+ return (nType == SvNumFormatType::DATE) || (nType == SvNumFormatType::TIME) ||
+ (nType == SvNumFormatType::DATETIME);
+ }
+
+ /**
+ * Query the formula ranges that may have stopped listening, accounting for
+ * the formula groups.
+ */
+ std::vector<std::pair<SCROW, SCROW>> getFormulaRanges()
+ {
+ std::vector<std::pair<SCROW, SCROW>> aRet;
+
+ for (const ScFormulaCell* pFC : maFormulaCells)
+ {
+ SCROW nTopRow = pFC->aPos.Row();
+ SCROW nBottomRow = pFC->aPos.Row();
+
+ auto xGroup = pFC->GetCellGroup();
+ if (xGroup)
+ {
+ pFC = xGroup->mpTopCell;
+ nTopRow = pFC->aPos.Row();
+ nBottomRow = nTopRow + xGroup->mnLength - 1;
+ }
+
+ aRet.emplace_back(nTopRow, nBottomRow);
+ }
+
+ return aRet;
+ }
+
+ void endFormulas()
+ {
+ mrDoc.EndListeningFormulaCells(maFormulaCells);
+ }
+
+ sc::SingleColumnSpanSet& getSpans()
+ {
+ return maDeleteRanges;
+ }
+};
+
+class EmptyCells
+{
+ ScColumn& mrColumn;
+ sc::ColumnBlockPosition& mrPos;
+
+ static void splitFormulaGrouping(const sc::CellStoreType::position_type& rPos)
+ {
+ if (rPos.first->type == sc::element_type_formula)
+ {
+ ScFormulaCell& rCell = *sc::formula_block::at(*rPos.first->data, rPos.second);
+ sc::SharedFormulaUtil::unshareFormulaCell(rPos, rCell);
+ }
+ }
+
+public:
+ EmptyCells( sc::ColumnBlockPosition& rPos, ScColumn& rColumn ) :
+ mrColumn(rColumn), mrPos(rPos) {}
+
+ void operator() (const sc::RowSpan& rSpan)
+ {
+ sc::CellStoreType& rCells = mrColumn.GetCellStore();
+
+ // First, split formula grouping at the top and bottom boundaries
+ // before emptying the cells.
+ sc::CellStoreType::position_type aPos = rCells.position(mrPos.miCellPos, rSpan.mnRow1);
+ splitFormulaGrouping(aPos);
+ aPos = rCells.position(aPos.first, rSpan.mnRow2);
+ splitFormulaGrouping(aPos);
+
+ mrPos.miCellPos = rCells.set_empty(mrPos.miCellPos, rSpan.mnRow1, rSpan.mnRow2);
+ mrPos.miCellTextAttrPos = mrColumn.GetCellAttrStore().set_empty(mrPos.miCellTextAttrPos, rSpan.mnRow1, rSpan.mnRow2);
+ }
+};
+
+}
+
+ScColumn::DeleteCellsResult::DeleteCellsResult( const ScDocument& rDoc ) :
+ aDeletedRows( rDoc.GetSheetLimits() )
+{
+}
+
+std::unique_ptr<ScColumn::DeleteCellsResult> ScColumn::DeleteCells(
+ sc::ColumnBlockPosition& rBlockPos, SCROW nRow1, SCROW nRow2, InsertDeleteFlags nDelFlag )
+{
+ std::unique_ptr<DeleteCellsResult> xResult = std::make_unique<DeleteCellsResult>(GetDoc());
+
+ // Determine which cells to delete based on the deletion flags.
+ DeleteAreaHandler aFunc(GetDoc(), nDelFlag, *this);
+ sc::CellStoreType::iterator itPos = maCells.position(rBlockPos.miCellPos, nRow1).first;
+ sc::ProcessBlock(itPos, maCells, aFunc, nRow1, nRow2);
+ xResult->aFormulaRanges = aFunc.getFormulaRanges();
+ aFunc.endFormulas(); // Have the formula cells stop listening.
+
+ // Get the deletion spans.
+ sc::SingleColumnSpanSet::SpansType aSpans;
+ aFunc.getSpans().getSpans(aSpans);
+
+ // Delete the cells for real.
+ // tdf#139820: Deleting in reverse order is more efficient.
+ std::for_each(aSpans.rbegin(), aSpans.rend(), EmptyCells(rBlockPos, *this));
+ CellStorageModified();
+
+ aFunc.getSpans().swap(xResult->aDeletedRows);
+
+ return xResult;
+}
+
+void ScColumn::DeleteArea(
+ SCROW nStartRow, SCROW nEndRow, InsertDeleteFlags nDelFlag, bool bBroadcast,
+ sc::ColumnSpanSet* pBroadcastSpans )
+{
+ InsertDeleteFlags nContMask = InsertDeleteFlags::CONTENTS;
+ // InsertDeleteFlags::NOCAPTIONS needs to be passed too, if InsertDeleteFlags::NOTE is set
+ if( nDelFlag & InsertDeleteFlags::NOTE )
+ nContMask |= InsertDeleteFlags::NOCAPTIONS;
+ InsertDeleteFlags nContFlag = nDelFlag & nContMask;
+
+ sc::ColumnBlockPosition aBlockPos;
+ InitBlockPosition(aBlockPos);
+ std::unique_ptr<DeleteCellsResult> xResult;
+
+ if (!IsEmptyData() && nContFlag != InsertDeleteFlags::NONE)
+ {
+ xResult = DeleteCells(aBlockPos, nStartRow, nEndRow, nDelFlag);
+ if (pBroadcastSpans)
+ {
+ sc::SingleColumnSpanSet::SpansType aSpans;
+ xResult->aDeletedRows.getSpans(aSpans);
+ for (const auto& rSpan : aSpans)
+ pBroadcastSpans->set(GetDoc(), nTab, nCol, rSpan.mnRow1, rSpan.mnRow2, true);
+ }
+ }
+
+ if (nDelFlag & InsertDeleteFlags::NOTE)
+ {
+ bool bForgetCaptionOwnership = ((nDelFlag & InsertDeleteFlags::FORGETCAPTIONS) != InsertDeleteFlags::NONE);
+ DeleteCellNotes(aBlockPos, nStartRow, nEndRow, bForgetCaptionOwnership);
+ }
+
+ if (nDelFlag & InsertDeleteFlags::SPARKLINES)
+ {
+ DeleteSparklineCells(aBlockPos, nStartRow, nEndRow);
+ }
+
+ if ( nDelFlag & InsertDeleteFlags::EDITATTR )
+ {
+ OSL_ENSURE( nContFlag == InsertDeleteFlags::NONE, "DeleteArea: Wrong Flags" );
+ RemoveEditAttribs(aBlockPos, nStartRow, nEndRow);
+ }
+
+ // Delete attributes just now
+ if ((nDelFlag & InsertDeleteFlags::ATTRIB) == InsertDeleteFlags::ATTRIB)
+ pAttrArray->DeleteArea( nStartRow, nEndRow );
+ else if ((nDelFlag & InsertDeleteFlags::HARDATTR) == InsertDeleteFlags::HARDATTR)
+ pAttrArray->DeleteHardAttr( nStartRow, nEndRow );
+
+ if (xResult && bBroadcast)
+ {
+ // Broadcast on only cells that were deleted; no point broadcasting on
+ // cells that were already empty before the deletion.
+ std::vector<SCROW> aRows;
+ xResult->aDeletedRows.getRows(aRows);
+ BroadcastCells(aRows, SfxHintId::ScDataChanged);
+ }
+}
+
+void ScColumn::InitBlockPosition( sc::ColumnBlockPosition& rBlockPos )
+{
+ rBlockPos.miBroadcasterPos = maBroadcasters.begin();
+ rBlockPos.miCellNotePos = maCellNotes.begin();
+ rBlockPos.miCellTextAttrPos = maCellTextAttrs.begin();
+ rBlockPos.miCellPos = maCells.begin();
+ rBlockPos.miSparklinePos = maSparklines.begin();
+}
+
+void ScColumn::InitBlockPosition( sc::ColumnBlockConstPosition& rBlockPos ) const
+{
+ rBlockPos.miCellNotePos = maCellNotes.begin();
+ rBlockPos.miCellTextAttrPos = maCellTextAttrs.begin();
+ rBlockPos.miCellPos = maCells.begin();
+}
+
+namespace {
+
+class CopyAttrArrayByRange
+{
+ ScAttrArray& mrDestAttrArray;
+ ScAttrArray& mrSrcAttrArray;
+ tools::Long mnRowOffset;
+public:
+ CopyAttrArrayByRange(ScAttrArray& rDestAttrArray, ScAttrArray& rSrcAttrArray, tools::Long nRowOffset) :
+ mrDestAttrArray(rDestAttrArray), mrSrcAttrArray(rSrcAttrArray), mnRowOffset(nRowOffset) {}
+
+ void operator() (const sc::RowSpan& rSpan)
+ {
+ mrDestAttrArray.CopyAreaSafe(
+ rSpan.mnRow1+mnRowOffset, rSpan.mnRow2+mnRowOffset, mnRowOffset, mrSrcAttrArray);
+ }
+};
+
+class CopyCellsFromClipHandler
+{
+ sc::CopyFromClipContext& mrCxt;
+ ScColumn& mrSrcCol;
+ ScColumn& mrDestCol;
+ SCTAB mnTab;
+ SCCOL mnCol;
+ SCTAB mnSrcTab;
+ SCCOL mnSrcCol;
+ tools::Long mnRowOffset;
+ sc::ColumnBlockPosition maDestBlockPos;
+ sc::ColumnBlockPosition* mpDestBlockPos; // to save it for next iteration.
+ svl::SharedStringPool* mpSharedStringPool;
+
+ void insertRefCell(SCROW nSrcRow, SCROW nDestRow)
+ {
+ ScAddress aSrcPos(mnSrcCol, nSrcRow, mnSrcTab);
+ ScAddress aDestPos(mnCol, nDestRow, mnTab);
+ ScSingleRefData aRef;
+ aRef.InitAddress(aSrcPos);
+ aRef.SetFlag3D(true);
+
+ ScTokenArray aArr(*mrCxt.getDestDoc());
+ aArr.AddSingleReference(aRef);
+
+ mrDestCol.SetFormulaCell(
+ maDestBlockPos, nDestRow, new ScFormulaCell(mrDestCol.GetDoc(), aDestPos, aArr));
+ }
+
+ void duplicateNotes(SCROW nStartRow, size_t nDataSize, bool bCloneCaption )
+ {
+ mrSrcCol.DuplicateNotes(nStartRow, nDataSize, mrDestCol, maDestBlockPos, bCloneCaption, mnRowOffset);
+ }
+
+ void duplicateSparklines(SCROW nStartRow, size_t nDataSize)
+ {
+ mrSrcCol.DuplicateSparklines(nStartRow, nDataSize, mrDestCol, maDestBlockPos, mnRowOffset);
+ }
+
+public:
+ CopyCellsFromClipHandler(sc::CopyFromClipContext& rCxt, ScColumn& rSrcCol, ScColumn& rDestCol, SCTAB nDestTab, SCCOL nDestCol, tools::Long nRowOffset, svl::SharedStringPool* pSharedStringPool) :
+ mrCxt(rCxt),
+ mrSrcCol(rSrcCol),
+ mrDestCol(rDestCol),
+ mnTab(nDestTab),
+ mnCol(nDestCol),
+ mnSrcTab(rSrcCol.GetTab()),
+ mnSrcCol(rSrcCol.GetCol()),
+ mnRowOffset(nRowOffset),
+ mpDestBlockPos(mrCxt.getBlockPosition(nDestTab, nDestCol)),
+ mpSharedStringPool(pSharedStringPool)
+ {
+ if (mpDestBlockPos)
+ {
+ {
+ // Re-initialize the broadcaster position hint, which may have
+ // become invalid by the time it gets here...
+ sc::ColumnBlockPosition aTempPos;
+ mrDestCol.InitBlockPosition(aTempPos);
+ mpDestBlockPos->miBroadcasterPos = aTempPos.miBroadcasterPos;
+ }
+ maDestBlockPos = *mpDestBlockPos;
+ }
+ else
+ mrDestCol.InitBlockPosition(maDestBlockPos);
+ }
+
+ ~CopyCellsFromClipHandler()
+ {
+ if (mpDestBlockPos)
+ // Don't forget to save this to the context!
+ *mpDestBlockPos = maDestBlockPos;
+ }
+
+ void operator() (const sc::CellStoreType::value_type& node, size_t nOffset, size_t nDataSize)
+ {
+ SCROW nSrcRow1 = node.position + nOffset;
+ bool bCopyCellNotes = mrCxt.isCloneNotes();
+ bool bCopySparklines = mrCxt.isCloneSparklines();
+
+ InsertDeleteFlags nFlags = mrCxt.getInsertFlag();
+
+ if (node.type == sc::element_type_empty)
+ {
+ if (bCopyCellNotes && !mrCxt.isSkipEmptyCells())
+ {
+ bool bCloneCaption = (nFlags & InsertDeleteFlags::NOCAPTIONS) == InsertDeleteFlags::NONE;
+ duplicateNotes(nSrcRow1, nDataSize, bCloneCaption );
+ }
+ if (bCopySparklines) // If there is a sparkline is it empty?
+ {
+ duplicateSparklines(nSrcRow1, nDataSize);
+ }
+ return;
+ }
+
+ bool bNumeric = (nFlags & InsertDeleteFlags::VALUE) != InsertDeleteFlags::NONE;
+ bool bDateTime = (nFlags & InsertDeleteFlags::DATETIME) != InsertDeleteFlags::NONE;
+ bool bString = (nFlags & InsertDeleteFlags::STRING) != InsertDeleteFlags::NONE;
+ bool bBoolean = (nFlags & InsertDeleteFlags::SPECIAL_BOOLEAN) != InsertDeleteFlags::NONE;
+ bool bFormula = (nFlags & InsertDeleteFlags::FORMULA) != InsertDeleteFlags::NONE;
+
+ bool bAsLink = mrCxt.isAsLink();
+
+ switch (node.type)
+ {
+ case sc::element_type_numeric:
+ {
+ // We need to copy numeric cells individually because of date type check.
+ sc::numeric_block::const_iterator it = sc::numeric_block::begin(*node.data);
+ std::advance(it, nOffset);
+ sc::numeric_block::const_iterator itEnd = it;
+ std::advance(itEnd, nDataSize);
+ for (SCROW nSrcRow = nSrcRow1; it != itEnd; ++it, ++nSrcRow)
+ {
+ bool bCopy = mrCxt.isDateCell(mrSrcCol, nSrcRow) ? bDateTime : bNumeric;
+ if (!bCopy)
+ continue;
+
+ if (bAsLink)
+ insertRefCell(nSrcRow, nSrcRow + mnRowOffset);
+ else
+ mrDestCol.SetValue(maDestBlockPos, nSrcRow + mnRowOffset, *it);
+ }
+ }
+ break;
+ case sc::element_type_string:
+ {
+ if (!bString)
+ break;
+
+ sc::string_block::const_iterator it = sc::string_block::begin(*node.data);
+ std::advance(it, nOffset);
+ sc::string_block::const_iterator itEnd = it;
+ std::advance(itEnd, nDataSize);
+ for (SCROW nSrcRow = nSrcRow1; it != itEnd; ++it, ++nSrcRow)
+ {
+ if (bAsLink)
+ insertRefCell(nSrcRow, nSrcRow + mnRowOffset);
+ else if (mpSharedStringPool)
+ {
+ // Re-intern the string if source is a different document.
+ svl::SharedString aInterned = mpSharedStringPool->intern( (*it).getString());
+ mrDestCol.SetRawString(maDestBlockPos, nSrcRow + mnRowOffset, aInterned);
+ }
+ else
+ mrDestCol.SetRawString(maDestBlockPos, nSrcRow + mnRowOffset, *it);
+ }
+ }
+ break;
+ case sc::element_type_edittext:
+ {
+ if (!bString)
+ break;
+
+ sc::edittext_block::const_iterator it = sc::edittext_block::begin(*node.data);
+ std::advance(it, nOffset);
+ sc::edittext_block::const_iterator itEnd = it;
+ std::advance(itEnd, nDataSize);
+ for (SCROW nSrcRow = nSrcRow1; it != itEnd; ++it, ++nSrcRow)
+ {
+
+ if (bAsLink)
+ insertRefCell(nSrcRow, nSrcRow + mnRowOffset);
+ else
+ mrDestCol.SetEditText(maDestBlockPos, nSrcRow + mnRowOffset, **it);
+ }
+ }
+ break;
+ case sc::element_type_formula:
+ {
+ sc::formula_block::const_iterator it = sc::formula_block::begin(*node.data);
+ std::advance(it, nOffset);
+ sc::formula_block::const_iterator itEnd = it;
+ std::advance(itEnd, nDataSize);
+ for (SCROW nSrcRow = nSrcRow1; it != itEnd; ++it, ++nSrcRow)
+ {
+ ScFormulaCell& rSrcCell = **it;
+ bool bForceFormula = false;
+ if (bBoolean)
+ {
+ // See if the formula consists of =TRUE() or =FALSE().
+ const ScTokenArray* pCode = rSrcCell.GetCode();
+ if (pCode && pCode->GetLen() == 1)
+ {
+ const formula::FormulaToken* p = pCode->FirstToken();
+ if (p->GetOpCode() == ocTrue || p->GetOpCode() == ocFalse)
+ // This is a boolean formula.
+ bForceFormula = true;
+ }
+ }
+
+ ScAddress aDestPos(mnCol, nSrcRow + mnRowOffset, mnTab);
+ if (bFormula || bForceFormula)
+ {
+ if (bAsLink)
+ insertRefCell(nSrcRow, nSrcRow + mnRowOffset);
+ else
+ {
+ mrDestCol.SetFormulaCell(
+ maDestBlockPos, nSrcRow + mnRowOffset,
+ new ScFormulaCell(rSrcCell, mrDestCol.GetDoc(), aDestPos),
+ sc::SingleCellListening,
+ rSrcCell.NeedsNumberFormat());
+ }
+ }
+ else if (bNumeric || bDateTime || bString)
+ {
+ // Always just copy the original row to the Undo Document;
+ // do not create Value/string cells from formulas
+
+ FormulaError nErr = rSrcCell.GetErrCode();
+ if (nErr != FormulaError::NONE)
+ {
+ // error codes are cloned with values
+ if (bNumeric)
+ {
+ if (bAsLink)
+ insertRefCell(nSrcRow, nSrcRow + mnRowOffset);
+ else
+ {
+ ScFormulaCell* pErrCell = new ScFormulaCell(mrDestCol.GetDoc(), aDestPos);
+ pErrCell->SetErrCode(nErr);
+ mrDestCol.SetFormulaCell(
+ maDestBlockPos, nSrcRow + mnRowOffset, pErrCell);
+ }
+ }
+ }
+ else if (rSrcCell.IsEmptyDisplayedAsString())
+ {
+ // Empty stays empty and doesn't become 0.
+ continue;
+ }
+ else if (rSrcCell.IsValue())
+ {
+ bool bCopy = mrCxt.isDateCell(mrSrcCol, nSrcRow) ? bDateTime : bNumeric;
+ if (!bCopy)
+ continue;
+
+ if (bAsLink)
+ insertRefCell(nSrcRow, nSrcRow + mnRowOffset);
+ else
+ mrDestCol.SetValue(maDestBlockPos, nSrcRow + mnRowOffset, rSrcCell.GetValue());
+ }
+ else if (bString)
+ {
+ svl::SharedString aStr = rSrcCell.GetString();
+ if (aStr.isEmpty())
+ // do not clone empty string
+ continue;
+
+ if (bAsLink)
+ insertRefCell(nSrcRow, nSrcRow + mnRowOffset);
+ else if (rSrcCell.IsMultilineResult())
+ {
+ // Clone as an edit text object.
+ ScFieldEditEngine& rEngine = mrDestCol.GetDoc().GetEditEngine();
+ rEngine.SetTextCurrentDefaults(aStr.getString());
+ mrDestCol.SetEditText(maDestBlockPos, nSrcRow + mnRowOffset, rEngine.CreateTextObject());
+ }
+ else if (mpSharedStringPool)
+ {
+ // Re-intern the string if source is a different document.
+ svl::SharedString aInterned = mpSharedStringPool->intern( aStr.getString());
+ mrDestCol.SetRawString(maDestBlockPos, nSrcRow + mnRowOffset, aInterned);
+ }
+ else
+ {
+ mrDestCol.SetRawString(maDestBlockPos, nSrcRow + mnRowOffset, aStr);
+ }
+ }
+ }
+ }
+ }
+ break;
+ default:
+ ;
+ }
+ if (bCopyCellNotes)
+ {
+ bool bCloneCaption = (nFlags & InsertDeleteFlags::NOCAPTIONS) == InsertDeleteFlags::NONE;
+ duplicateNotes(nSrcRow1, nDataSize, bCloneCaption );
+ }
+ if (bCopySparklines)
+ {
+ duplicateSparklines(nSrcRow1, nDataSize);
+ }
+ }
+};
+
+class CopyTextAttrsFromClipHandler
+{
+ sc::CellTextAttrStoreType& mrAttrs;
+ size_t mnDelta;
+ sc::ColumnBlockPosition maDestBlockPos;
+ sc::ColumnBlockPosition* mpDestBlockPos; // to save it for next iteration.
+
+public:
+ CopyTextAttrsFromClipHandler( sc::CopyFromClipContext& rCxt, sc::CellTextAttrStoreType& rAttrs,
+ ScColumn& rDestCol, SCTAB nDestTab, SCCOL nDestCol, size_t nDelta ) :
+ mrAttrs(rAttrs),
+ mnDelta(nDelta),
+ mpDestBlockPos(rCxt.getBlockPosition(nDestTab, nDestCol))
+ {
+ if (mpDestBlockPos)
+ maDestBlockPos.miCellTextAttrPos = mpDestBlockPos->miCellTextAttrPos;
+ else
+ rDestCol.InitBlockPosition(maDestBlockPos);
+ }
+
+ ~CopyTextAttrsFromClipHandler()
+ {
+ if (mpDestBlockPos)
+ // Don't forget to save this to the context!
+ mpDestBlockPos->miCellTextAttrPos = maDestBlockPos.miCellTextAttrPos;
+ }
+
+ void operator() ( const sc::CellTextAttrStoreType::value_type& aNode, size_t nOffset, size_t nDataSize )
+ {
+ if (aNode.type != sc::element_type_celltextattr)
+ return;
+
+ sc::celltextattr_block::const_iterator it = sc::celltextattr_block::begin(*aNode.data);
+ std::advance(it, nOffset);
+ sc::celltextattr_block::const_iterator itEnd = it;
+ std::advance(itEnd, nDataSize);
+
+ size_t nPos = aNode.position + nOffset + mnDelta;
+ maDestBlockPos.miCellTextAttrPos = mrAttrs.set(maDestBlockPos.miCellTextAttrPos, nPos, it, itEnd);
+ }
+};
+
+}
+
+// rColumn = source
+// nRow1, nRow2 = target position
+
+void ScColumn::CopyFromClip(
+ sc::CopyFromClipContext& rCxt, SCROW nRow1, SCROW nRow2, tools::Long nDy, ScColumn& rColumn )
+{
+ sc::ColumnBlockPosition* pBlockPos = rCxt.getBlockPosition(nTab, nCol);
+ if (!pBlockPos)
+ return;
+
+ if ((rCxt.getInsertFlag() & InsertDeleteFlags::ATTRIB) != InsertDeleteFlags::NONE)
+ {
+ if (rCxt.isSkipEmptyCells())
+ {
+ // copy only attributes for non-empty cells between nRow1-nDy and nRow2-nDy.
+ sc::SingleColumnSpanSet aSpanSet(GetDoc().GetSheetLimits());
+ aSpanSet.scan(rColumn, nRow1-nDy, nRow2-nDy);
+ sc::SingleColumnSpanSet::SpansType aSpans;
+ aSpanSet.getSpans(aSpans);
+ std::for_each(
+ aSpans.begin(), aSpans.end(), CopyAttrArrayByRange(*rColumn.pAttrArray, *pAttrArray, nDy));
+ }
+ else
+ rColumn.pAttrArray->CopyAreaSafe( nRow1, nRow2, nDy, *pAttrArray );
+ }
+ if ((rCxt.getInsertFlag() & InsertDeleteFlags::CONTENTS) == InsertDeleteFlags::NONE)
+ return;
+
+ ScDocument& rDocument = GetDoc();
+ if (rCxt.isAsLink() && rCxt.getInsertFlag() == InsertDeleteFlags::ALL)
+ {
+ // We also reference empty cells for "ALL"
+ // InsertDeleteFlags::ALL must always contain more flags when compared to "Insert contents" as
+ // contents can be selected one by one!
+
+ ScAddress aDestPos( nCol, 0, nTab ); // Adapt Row
+
+ // Create reference (Source Position)
+ ScSingleRefData aRef;
+ aRef.InitFlags(); // -> All absolute
+ aRef.SetAbsCol(rColumn.nCol);
+ aRef.SetAbsTab(rColumn.nTab);
+ aRef.SetFlag3D(true);
+
+ for (SCROW nDestRow = nRow1; nDestRow <= nRow2; nDestRow++)
+ {
+ aRef.SetAbsRow(nDestRow - nDy); // Source row
+ aDestPos.SetRow( nDestRow );
+
+ ScTokenArray aArr(GetDoc());
+ aArr.AddSingleReference( aRef );
+ SetFormulaCell(*pBlockPos, nDestRow, new ScFormulaCell(rDocument, aDestPos, aArr));
+ }
+
+ // Don't forget to copy the cell text attributes.
+ CopyTextAttrsFromClipHandler aFunc(rCxt, maCellTextAttrs, *this, nTab, nCol, nDy);
+ sc::ParseBlock(rColumn.maCellTextAttrs.begin(), rColumn.maCellTextAttrs, aFunc, nRow1-nDy, nRow2-nDy);
+
+ return;
+ }
+
+ // Compare the ScDocumentPool* to determine if we are copying within the
+ // same document. If not, re-intern shared strings.
+ svl::SharedStringPool* pSharedStringPool = (rColumn.GetDoc().GetPool() != rDocument.GetPool()) ?
+ &rDocument.GetSharedStringPool() : nullptr;
+
+ // nRow1 to nRow2 is for destination (this) column. Subtract nDy to get the source range.
+ // Copy all cells in the source column (rColumn) from nRow1-nDy to nRow2-nDy to this column.
+ {
+ CopyCellsFromClipHandler aFunc(rCxt, rColumn, *this, nTab, nCol, nDy, pSharedStringPool);
+ sc::ParseBlock(rColumn.maCells.begin(), rColumn.maCells, aFunc, nRow1-nDy, nRow2-nDy);
+ }
+
+ {
+ // Don't forget to copy the cell text attributes.
+ CopyTextAttrsFromClipHandler aFunc(rCxt, maCellTextAttrs, *this, nTab, nCol, nDy);
+ sc::ParseBlock(rColumn.maCellTextAttrs.begin(), rColumn.maCellTextAttrs, aFunc, nRow1-nDy, nRow2-nDy);
+ }
+}
+
+void ScColumn::MixMarked(
+ sc::MixDocContext& rCxt, const ScMarkData& rMark, ScPasteFunc nFunction,
+ bool bSkipEmpty, const ScColumn& rSrcCol )
+{
+ SCROW nRow1, nRow2;
+
+ if (rMark.IsMultiMarked())
+ {
+ ScMultiSelIter aIter( rMark.GetMultiSelData(), nCol );
+ while (aIter.Next( nRow1, nRow2 ))
+ MixData(rCxt, nRow1, nRow2, nFunction, bSkipEmpty, rSrcCol);
+ }
+}
+
+namespace {
+
+// Result in rVal1
+bool lcl_DoFunction( double& rVal1, double nVal2, ScPasteFunc nFunction )
+{
+ bool bOk = false;
+ switch (nFunction)
+ {
+ case ScPasteFunc::ADD:
+ bOk = SubTotal::SafePlus( rVal1, nVal2 );
+ break;
+ case ScPasteFunc::SUB:
+ nVal2 = -nVal2; // FIXME: Can we do this always without error?
+ bOk = SubTotal::SafePlus( rVal1, nVal2 );
+ break;
+ case ScPasteFunc::MUL:
+ bOk = SubTotal::SafeMult( rVal1, nVal2 );
+ break;
+ case ScPasteFunc::DIV:
+ bOk = SubTotal::SafeDiv( rVal1, nVal2 );
+ break;
+ default: break;
+ }
+ return bOk;
+}
+
+void lcl_AddCode( ScTokenArray& rArr, const ScFormulaCell* pCell )
+{
+ rArr.AddOpCode(ocOpen);
+
+ const ScTokenArray* pCode = pCell->GetCode();
+ if (pCode)
+ {
+ FormulaTokenArrayPlainIterator aIter(*pCode);
+ const formula::FormulaToken* pToken = aIter.First();
+ while (pToken)
+ {
+ rArr.AddToken( *pToken );
+ pToken = aIter.Next();
+ }
+ }
+
+ rArr.AddOpCode(ocClose);
+}
+
+class MixDataHandler
+{
+ ScColumn& mrDestColumn;
+ sc::ColumnBlockPosition& mrBlockPos;
+
+ sc::CellStoreType maNewCells;
+ sc::CellStoreType::iterator miNewCellsPos;
+
+ size_t mnRowOffset;
+ ScPasteFunc mnFunction;
+
+ bool mbSkipEmpty;
+
+ void doFunction( size_t nDestRow, double fVal1, double fVal2 )
+ {
+ bool bOk = lcl_DoFunction(fVal1, fVal2, mnFunction);
+
+ if (bOk)
+ miNewCellsPos = maNewCells.set(miNewCellsPos, nDestRow-mnRowOffset, fVal1);
+ else
+ {
+ ScAddress aPos(mrDestColumn.GetCol(), nDestRow, mrDestColumn.GetTab());
+
+ ScFormulaCell* pFC = new ScFormulaCell(mrDestColumn.GetDoc(), aPos);
+ pFC->SetErrCode(FormulaError::NoValue);
+
+ miNewCellsPos = maNewCells.set(miNewCellsPos, nDestRow-mnRowOffset, pFC);
+ }
+ }
+
+public:
+ MixDataHandler(
+ sc::ColumnBlockPosition& rBlockPos,
+ ScColumn& rDestColumn,
+ SCROW nRow1, SCROW nRow2,
+ ScPasteFunc nFunction, bool bSkipEmpty) :
+ mrDestColumn(rDestColumn),
+ mrBlockPos(rBlockPos),
+ maNewCells(nRow2 - nRow1 + 1),
+ miNewCellsPos(maNewCells.begin()),
+ mnRowOffset(nRow1),
+ mnFunction(nFunction),
+ mbSkipEmpty(bSkipEmpty)
+ {
+ }
+
+ void operator() (size_t nRow, double f)
+ {
+ sc::CellStoreType::position_type aPos = mrDestColumn.GetCellStore().position(mrBlockPos.miCellPos, nRow);
+ mrBlockPos.miCellPos = aPos.first;
+ switch (aPos.first->type)
+ {
+ case sc::element_type_empty:
+ case sc::element_type_numeric:
+ {
+ double fSrcVal = 0.0;
+ if (aPos.first->type == sc::element_type_numeric)
+ fSrcVal = sc::numeric_block::at(*aPos.first->data, aPos.second);
+
+ // Both src and dest are of numeric type.
+ doFunction(nRow, f, fSrcVal);
+ }
+ break;
+ case sc::element_type_formula:
+ {
+ // Combination of value and at least one formula -> Create formula
+ ScTokenArray aArr(mrDestColumn.GetDoc());
+
+ // First row
+ aArr.AddDouble(f);
+
+ // Operator
+ OpCode eOp = ocAdd;
+ switch (mnFunction)
+ {
+ case ScPasteFunc::ADD: eOp = ocAdd; break;
+ case ScPasteFunc::SUB: eOp = ocSub; break;
+ case ScPasteFunc::MUL: eOp = ocMul; break;
+ case ScPasteFunc::DIV: eOp = ocDiv; break;
+ default: break;
+ }
+ aArr.AddOpCode(eOp); // Function
+
+ // Second row
+ ScFormulaCell* pDest = sc::formula_block::at(*aPos.first->data, aPos.second);
+ lcl_AddCode(aArr, pDest);
+
+ miNewCellsPos = maNewCells.set(
+ miNewCellsPos, nRow-mnRowOffset,
+ new ScFormulaCell(
+ mrDestColumn.GetDoc(), ScAddress(mrDestColumn.GetCol(), nRow, mrDestColumn.GetTab()), aArr));
+ }
+ break;
+ case sc::element_type_string:
+ case sc::element_type_edittext:
+ {
+ // Destination cell is not a number. Just take the source cell.
+ miNewCellsPos = maNewCells.set(miNewCellsPos, nRow-mnRowOffset, f);
+ }
+ break;
+ default:
+ ;
+ }
+ }
+
+ void operator() (size_t nRow, const svl::SharedString& rStr)
+ {
+ miNewCellsPos = maNewCells.set(miNewCellsPos, nRow-mnRowOffset, rStr);
+ }
+
+ void operator() (size_t nRow, const EditTextObject* p)
+ {
+ miNewCellsPos = maNewCells.set(miNewCellsPos, nRow-mnRowOffset, p->Clone().release());
+ }
+
+ void operator() (size_t nRow, const ScFormulaCell* p)
+ {
+ sc::CellStoreType::position_type aPos = mrDestColumn.GetCellStore().position(mrBlockPos.miCellPos, nRow);
+ mrBlockPos.miCellPos = aPos.first;
+ switch (aPos.first->type)
+ {
+ case sc::element_type_numeric:
+ {
+ // Source is formula, and dest is value.
+ ScTokenArray aArr(mrDestColumn.GetDoc());
+
+ // First row
+ lcl_AddCode(aArr, p);
+
+ // Operator
+ OpCode eOp = ocAdd;
+ switch (mnFunction)
+ {
+ case ScPasteFunc::ADD: eOp = ocAdd; break;
+ case ScPasteFunc::SUB: eOp = ocSub; break;
+ case ScPasteFunc::MUL: eOp = ocMul; break;
+ case ScPasteFunc::DIV: eOp = ocDiv; break;
+ default: break;
+ }
+ aArr.AddOpCode(eOp); // Function
+
+ // Second row
+ aArr.AddDouble(sc::numeric_block::at(*aPos.first->data, aPos.second));
+
+ miNewCellsPos = maNewCells.set(
+ miNewCellsPos, nRow-mnRowOffset,
+ new ScFormulaCell(
+ mrDestColumn.GetDoc(), ScAddress(mrDestColumn.GetCol(), nRow, mrDestColumn.GetTab()), aArr));
+ }
+ break;
+ case sc::element_type_formula:
+ {
+ // Both are formulas.
+ ScTokenArray aArr(mrDestColumn.GetDoc());
+
+ // First row
+ lcl_AddCode(aArr, p);
+
+ // Operator
+ OpCode eOp = ocAdd;
+ switch (mnFunction)
+ {
+ case ScPasteFunc::ADD: eOp = ocAdd; break;
+ case ScPasteFunc::SUB: eOp = ocSub; break;
+ case ScPasteFunc::MUL: eOp = ocMul; break;
+ case ScPasteFunc::DIV: eOp = ocDiv; break;
+ default: break;
+ }
+ aArr.AddOpCode(eOp); // Function
+
+ // Second row
+ ScFormulaCell* pDest = sc::formula_block::at(*aPos.first->data, aPos.second);
+ lcl_AddCode(aArr, pDest);
+
+ miNewCellsPos = maNewCells.set(
+ miNewCellsPos, nRow-mnRowOffset,
+ new ScFormulaCell(
+ mrDestColumn.GetDoc(), ScAddress(mrDestColumn.GetCol(), nRow, mrDestColumn.GetTab()), aArr));
+ }
+ break;
+ case sc::element_type_string:
+ case sc::element_type_edittext:
+ case sc::element_type_empty:
+ {
+ // Destination cell is not a number. Just take the source cell.
+ ScAddress aDestPos(mrDestColumn.GetCol(), nRow, mrDestColumn.GetTab());
+ miNewCellsPos = maNewCells.set(
+ miNewCellsPos, nRow-mnRowOffset, new ScFormulaCell(*p, mrDestColumn.GetDoc(), aDestPos));
+ }
+ break;
+ default:
+ ;
+ }
+ }
+
+ /**
+ * Empty cell series in the source (clip) document.
+ */
+ void operator() (mdds::mtv::element_t, size_t nTopRow, size_t nDataSize)
+ {
+ if (mbSkipEmpty)
+ return;
+
+ // Source cells are empty. Treat them as if they have a value of 0.0.
+ for (size_t i = 0; i < nDataSize; ++i)
+ {
+ size_t nDestRow = nTopRow + i;
+ sc::CellStoreType::position_type aPos = mrDestColumn.GetCellStore().position(mrBlockPos.miCellPos, nDestRow);
+ mrBlockPos.miCellPos = aPos.first;
+ switch (aPos.first->type)
+ {
+ case sc::element_type_numeric:
+ {
+ double fVal2 = sc::numeric_block::at(*aPos.first->data, aPos.second);
+ doFunction(nDestRow, 0.0, fVal2);
+ }
+ break;
+ case sc::element_type_string:
+ {
+ const svl::SharedString& aVal = sc::string_block::at(*aPos.first->data, aPos.second);
+ miNewCellsPos = maNewCells.set(
+ miNewCellsPos, nDestRow-mnRowOffset, aVal);
+ }
+ break;
+ case sc::element_type_edittext:
+ {
+ EditTextObject* pObj = sc::edittext_block::at(*aPos.first->data, aPos.second);
+ miNewCellsPos = maNewCells.set(
+ miNewCellsPos, nDestRow-mnRowOffset, pObj->Clone().release());
+ }
+ break;
+ case sc::element_type_formula:
+ {
+ ScTokenArray aArr(mrDestColumn.GetDoc());
+
+ // First row
+ ScFormulaCell* pSrc = sc::formula_block::at(*aPos.first->data, aPos.second);
+ lcl_AddCode( aArr, pSrc);
+
+ // Operator
+ OpCode eOp = ocAdd;
+ switch (mnFunction)
+ {
+ case ScPasteFunc::ADD: eOp = ocAdd; break;
+ case ScPasteFunc::SUB: eOp = ocSub; break;
+ case ScPasteFunc::MUL: eOp = ocMul; break;
+ case ScPasteFunc::DIV: eOp = ocDiv; break;
+ default: break;
+ }
+
+ aArr.AddOpCode(eOp); // Function
+ aArr.AddDouble(0.0);
+
+ miNewCellsPos = maNewCells.set(
+ miNewCellsPos, nDestRow-mnRowOffset,
+ new ScFormulaCell(
+ mrDestColumn.GetDoc(), ScAddress(mrDestColumn.GetCol(), nDestRow, mrDestColumn.GetTab()), aArr));
+ }
+ break;
+ default:
+ ;
+ }
+ }
+ }
+
+ /**
+ * Set the new cells to the destination (this) column.
+ */
+ void commit()
+ {
+ sc::CellStoreType& rDestCells = mrDestColumn.GetCellStore();
+
+ // Stop all formula cells in the destination range first.
+ sc::CellStoreType::position_type aPos = rDestCells.position(mrBlockPos.miCellPos, mnRowOffset);
+ mrDestColumn.DetachFormulaCells(aPos, maNewCells.size(), nullptr);
+
+ // Move the new cells to the destination range.
+ sc::CellStoreType::iterator& itDestPos = mrBlockPos.miCellPos;
+ sc::CellTextAttrStoreType::iterator& itDestAttrPos = mrBlockPos.miCellTextAttrPos;
+
+ for (const auto& rNewCell : maNewCells)
+ {
+ bool bHasContent = true;
+ size_t nDestRow = mnRowOffset + rNewCell.position;
+
+ switch (rNewCell.type)
+ {
+ case sc::element_type_numeric:
+ {
+ sc::numeric_block::iterator itData = sc::numeric_block::begin(*rNewCell.data);
+ sc::numeric_block::iterator itDataEnd = sc::numeric_block::end(*rNewCell.data);
+ itDestPos = mrDestColumn.GetCellStore().set(itDestPos, nDestRow, itData, itDataEnd);
+ }
+ break;
+ case sc::element_type_string:
+ {
+ sc::string_block::iterator itData = sc::string_block::begin(*rNewCell.data);
+ sc::string_block::iterator itDataEnd = sc::string_block::end(*rNewCell.data);
+ itDestPos = rDestCells.set(itDestPos, nDestRow, itData, itDataEnd);
+ }
+ break;
+ case sc::element_type_edittext:
+ {
+ sc::edittext_block::iterator itData = sc::edittext_block::begin(*rNewCell.data);
+ sc::edittext_block::iterator itDataEnd = sc::edittext_block::end(*rNewCell.data);
+ itDestPos = rDestCells.set(itDestPos, nDestRow, itData, itDataEnd);
+ }
+ break;
+ case sc::element_type_formula:
+ {
+ sc::formula_block::iterator itData = sc::formula_block::begin(*rNewCell.data);
+ sc::formula_block::iterator itDataEnd = sc::formula_block::end(*rNewCell.data);
+
+ // Group new formula cells before inserting them.
+ sc::SharedFormulaUtil::groupFormulaCells(itData, itDataEnd);
+
+ // Insert the formula cells to the column.
+ itDestPos = rDestCells.set(itDestPos, nDestRow, itData, itDataEnd);
+
+ // Merge with the previous formula group (if any).
+ aPos = rDestCells.position(itDestPos, nDestRow);
+ sc::SharedFormulaUtil::joinFormulaCellAbove(aPos);
+
+ // Merge with the next formula group (if any).
+ size_t nNextRow = nDestRow + rNewCell.size;
+ if (mrDestColumn.GetDoc().ValidRow(nNextRow))
+ {
+ aPos = rDestCells.position(aPos.first, nNextRow);
+ sc::SharedFormulaUtil::joinFormulaCellAbove(aPos);
+ }
+
+ // Start listening on cells to get them updated by changes of referenced cells
+ std::vector<SCROW> aNewSharedRows;
+ aPos = rDestCells.position(itDestPos, nDestRow);
+ size_t nFormulaCells = std::distance(itData, itDataEnd);
+ mrDestColumn.AttachNewFormulaCells(aPos, nFormulaCells, aNewSharedRows);
+ }
+ break;
+ case sc::element_type_empty:
+ {
+ itDestPos = rDestCells.set_empty(itDestPos, nDestRow, nDestRow+rNewCell.size-1);
+ bHasContent = false;
+ }
+ break;
+ default:
+ ;
+ }
+
+ sc::CellTextAttrStoreType& rDestAttrs = mrDestColumn.GetCellAttrStore();
+ if (bHasContent)
+ {
+ std::vector<sc::CellTextAttr> aAttrs(rNewCell.size, sc::CellTextAttr());
+ itDestAttrPos = rDestAttrs.set(itDestAttrPos, nDestRow, aAttrs.begin(), aAttrs.end());
+ }
+ else
+ itDestAttrPos = rDestAttrs.set_empty(itDestAttrPos, nDestRow, nDestRow+rNewCell.size-1);
+ }
+
+ maNewCells.release();
+ }
+};
+
+}
+
+void ScColumn::MixData(
+ sc::MixDocContext& rCxt, SCROW nRow1, SCROW nRow2, ScPasteFunc nFunction,
+ bool bSkipEmpty, const ScColumn& rSrcCol )
+{
+ // destination (this column) block position.
+
+ sc::ColumnBlockPosition* p = rCxt.getBlockPosition(nTab, nCol);
+ if (!p)
+ return;
+
+ MixDataHandler aFunc(*p, *this, nRow1, nRow2, nFunction, bSkipEmpty);
+ sc::ParseAll(rSrcCol.maCells.begin(), rSrcCol.maCells, nRow1, nRow2, aFunc, aFunc);
+
+ aFunc.commit();
+ CellStorageModified();
+}
+
+std::unique_ptr<ScAttrIterator> ScColumnData::CreateAttrIterator( SCROW nStartRow, SCROW nEndRow ) const
+{
+ return std::make_unique<ScAttrIterator>( pAttrArray.get(), nStartRow, nEndRow, GetDoc().GetDefPattern() );
+}
+
+namespace {
+
+class StartListenersHandler
+{
+ sc::StartListeningContext* mpCxt;
+ bool mbAllListeners;
+
+public:
+ StartListenersHandler( sc::StartListeningContext& rCxt, bool bAllListeners ) :
+ mpCxt(&rCxt), mbAllListeners(bAllListeners) {}
+
+ void operator() ( sc::CellStoreType::value_type& aBlk )
+ {
+ if (aBlk.type != sc::element_type_formula)
+ return;
+
+ ScFormulaCell** pp = &sc::formula_block::at(*aBlk.data, 0);
+ ScFormulaCell** ppEnd = pp + aBlk.size;
+
+ for (; pp != ppEnd; ++pp)
+ {
+ ScFormulaCell& rFC = **pp;
+ if (!mbAllListeners && !rFC.NeedsListening())
+ continue;
+
+ if (rFC.IsSharedTop())
+ {
+ sc::SharedFormulaUtil::startListeningAsGroup(*mpCxt, pp);
+ pp += rFC.GetSharedLength() - 1; // Move to the last cell in the group.
+ }
+ else
+ rFC.StartListeningTo(*mpCxt);
+ }
+ }
+};
+
+}
+
+void ScColumn::StartListeners( sc::StartListeningContext& rCxt, bool bAll )
+{
+ std::for_each(maCells.begin(), maCells.end(), StartListenersHandler(rCxt, bAll));
+}
+
+namespace {
+
+void applyTextNumFormat( ScColumn& rCol, SCROW nRow, SvNumberFormatter* pFormatter )
+{
+ sal_uInt32 nFormat = pFormatter->GetStandardFormat(SvNumFormatType::TEXT);
+ ScPatternAttr aNewAttrs(rCol.GetDoc().GetPool());
+ SfxItemSet& rSet = aNewAttrs.GetItemSet();
+ rSet.Put(SfxUInt32Item(ATTR_VALUE_FORMAT, nFormat));
+ rCol.ApplyPattern(nRow, aNewAttrs);
+}
+
+}
+
+bool ScColumn::ParseString(
+ ScCellValue& rCell, SCROW nRow, SCTAB nTabP, const OUString& rString,
+ formula::FormulaGrammar::AddressConvention eConv,
+ const ScSetStringParam* pParam )
+{
+ if (rString.isEmpty())
+ return false;
+
+ bool bNumFmtSet = false;
+
+ ScSetStringParam aParam;
+
+ if (pParam)
+ aParam = *pParam;
+
+ sal_uInt32 nIndex = 0;
+ sal_uInt32 nOldIndex = 0;
+ SvNumFormatType eNumFormatType = SvNumFormatType::ALL;
+ if (!aParam.mpNumFormatter)
+ aParam.mpNumFormatter = GetDoc().GetFormatTable();
+
+ sal_Unicode cFirstChar = 0; // Text
+ nIndex = nOldIndex = GetNumberFormat( GetDoc().GetNonThreadedContext(), nRow );
+ if ( rString.getLength() > 1 )
+ {
+ eNumFormatType = aParam.mpNumFormatter->GetType(nIndex);
+ if ( eNumFormatType != SvNumFormatType::TEXT )
+ cFirstChar = rString[0];
+ }
+
+ svl::SharedStringPool& rPool = GetDoc().GetSharedStringPool();
+
+ if ( cFirstChar == '=' )
+ {
+ if ( rString.getLength() == 1 ) // = Text
+ {
+ rCell.set(rPool.intern(rString));
+ }
+ else if (aParam.meSetTextNumFormat == ScSetStringParam::Always)
+ {
+ // Set the cell format type to Text.
+ applyTextNumFormat(*this, nRow, aParam.mpNumFormatter);
+ rCell.set(rPool.intern(rString));
+ }
+ else // = Formula
+ {
+ ScFormulaCell* pFormulaCell = new ScFormulaCell(
+ GetDoc(), ScAddress(nCol, nRow, nTabP), rString,
+ formula::FormulaGrammar::mergeToGrammar(formula::FormulaGrammar::GRAM_DEFAULT, eConv),
+ ScMatrixMode::NONE);
+ if (aParam.mbCheckLinkFormula)
+ GetDoc().CheckLinkFormulaNeedingCheck( *pFormulaCell->GetCode());
+ rCell.set( pFormulaCell);
+ }
+ }
+ else if ( cFirstChar == '\'') // 'Text
+ {
+ bool bNumeric = false;
+ if (aParam.mbHandleApostrophe)
+ {
+ // Cell format is not 'Text', and the first char is an apostrophe.
+ // Check if the input is considered a number with all leading
+ // apostrophes removed. All because ''1 should produce '1 not ''1,
+ // thus '''1 be ''1 and so on.
+ // NOTE: this corresponds with sc/source/ui/view/tabvwsha.cxx
+ // ScTabViewShell::UpdateInputHandler() prepending an apostrophe if
+ // necessary.
+ sal_Int32 i = 1;
+ while (i < rString.getLength() && rString[i] == '\'')
+ ++i;
+ if (i < rString.getLength())
+ {
+ OUString aTest = rString.copy(i);
+ double fTest;
+ bNumeric = aParam.mpNumFormatter->IsNumberFormat(aTest, nIndex, fTest);
+ if (bNumeric)
+ // This is a number. Strip out the first apostrophe.
+ rCell.set(rPool.intern(rString.copy(1)));
+ }
+ }
+ if (!bNumeric)
+ // This is normal text. Take it as-is.
+ rCell.set(rPool.intern(rString));
+ }
+ else
+ {
+ double nVal;
+
+ do
+ {
+ if (aParam.mbDetectNumberFormat)
+ {
+ // Editing a date prefers the format's locale's edit date
+ // format's date acceptance patterns and YMD order.
+ /* TODO: this could be determined already far above when
+ * starting to edit a date "cell" and passed down. A problem
+ * could also be if a new date was typed over or written by a
+ * macro assuming the current locale if that conflicts somehow.
+ * You can't have everything. See tdf#116579 and tdf#125109. */
+ if (eNumFormatType == SvNumFormatType::ALL)
+ eNumFormatType = aParam.mpNumFormatter->GetType(nIndex);
+ bool bForceFormatDate = (eNumFormatType == SvNumFormatType::DATE
+ || eNumFormatType == SvNumFormatType::DATETIME);
+ const SvNumberformat* pOldFormat = nullptr;
+ NfEvalDateFormat eEvalDateFormat = NF_EVALDATEFORMAT_INTL_FORMAT;
+ if (bForceFormatDate)
+ {
+ ScRefCellValue aCell = GetCellValue(nRow);
+ if (aCell.meType == CELLTYPE_VALUE)
+ {
+ // Only for an actual date (serial number), not an
+ // arbitrary string or formula or empty cell.
+ // Also ensure the edit date format's pattern is used,
+ // not the display format's.
+ pOldFormat = aParam.mpNumFormatter->GetEntry( nOldIndex);
+ if (!pOldFormat)
+ bForceFormatDate = false;
+ else
+ {
+ nIndex = aParam.mpNumFormatter->GetEditFormat(
+ aCell.getValue(), nOldIndex, eNumFormatType, pOldFormat);
+ eEvalDateFormat = aParam.mpNumFormatter->GetEvalDateFormat();
+ aParam.mpNumFormatter->SetEvalDateFormat( NF_EVALDATEFORMAT_FORMAT_INTL);
+ }
+ }
+ else
+ {
+ bForceFormatDate = false;
+ }
+ }
+
+ const bool bIsNumberFormat = aParam.mpNumFormatter->IsNumberFormat(rString, nIndex, nVal);
+
+ if (bForceFormatDate)
+ aParam.mpNumFormatter->SetEvalDateFormat( eEvalDateFormat);
+
+ if (!bIsNumberFormat)
+ break;
+
+ // If we have bForceFormatDate, the pOldFormat was/is of
+ // nOldIndex date(+time) type already, if detected type is
+ // compatible keep the original format.
+ if (bForceFormatDate && SvNumberFormatter::IsCompatible(
+ eNumFormatType, aParam.mpNumFormatter->GetType( nIndex)))
+ {
+ nIndex = nOldIndex;
+ }
+ else
+ {
+ // convert back to the original language if a built-in format was detected
+ if (!pOldFormat)
+ pOldFormat = aParam.mpNumFormatter->GetEntry( nOldIndex );
+ if (pOldFormat)
+ nIndex = aParam.mpNumFormatter->GetFormatForLanguageIfBuiltIn(
+ nIndex, pOldFormat->GetLanguage());
+ }
+
+ rCell.set(nVal);
+ if ( nIndex != nOldIndex)
+ {
+ // #i22345# New behavior: Apply the detected number format only if
+ // the old one was the default number, date, time or boolean format.
+ // Exception: If the new format is boolean, always apply it.
+
+ bool bOverwrite = false;
+ if ( pOldFormat )
+ {
+ SvNumFormatType nOldType = pOldFormat->GetMaskedType();
+ if ( nOldType == SvNumFormatType::NUMBER || nOldType == SvNumFormatType::DATE ||
+ nOldType == SvNumFormatType::TIME || nOldType == SvNumFormatType::LOGICAL )
+ {
+ if ( nOldIndex == aParam.mpNumFormatter->GetStandardFormat(
+ nOldType, pOldFormat->GetLanguage() ) )
+ {
+ bOverwrite = true; // default of these types can be overwritten
+ }
+ }
+ }
+ if ( !bOverwrite && aParam.mpNumFormatter->GetType( nIndex ) == SvNumFormatType::LOGICAL )
+ {
+ bOverwrite = true; // overwrite anything if boolean was detected
+ }
+
+ if ( bOverwrite )
+ {
+ ApplyAttr( nRow, SfxUInt32Item( ATTR_VALUE_FORMAT,
+ nIndex) );
+ bNumFmtSet = true;
+ }
+ }
+ }
+ else if (aParam.meSetTextNumFormat == ScSetStringParam::Never ||
+ aParam.meSetTextNumFormat == ScSetStringParam::SpecialNumberOnly)
+ {
+ // Only check if the string is a regular number.
+ const LocaleDataWrapper* pLocale = aParam.mpNumFormatter->GetLocaleData();
+ if (!pLocale)
+ break;
+
+ const LocaleDataItem2& aLocaleItem = pLocale->getLocaleItem();
+ const OUString& rDecSep = aLocaleItem.decimalSeparator;
+ const OUString& rGroupSep = aLocaleItem.thousandSeparator;
+ const OUString& rDecSepAlt = aLocaleItem.decimalSeparatorAlternative;
+ if (rDecSep.getLength() != 1 || rGroupSep.getLength() != 1 || rDecSepAlt.getLength() > 1)
+ break;
+
+ sal_Unicode dsep = rDecSep[0];
+ sal_Unicode gsep = rGroupSep[0];
+ sal_Unicode dsepa = rDecSepAlt.toChar();
+
+ if (!ScStringUtil::parseSimpleNumber(rString, dsep, gsep, dsepa, nVal))
+ break;
+
+ rCell.set(nVal);
+ }
+ }
+ while (false);
+
+ if (rCell.meType == CELLTYPE_NONE)
+ {
+ // If we reach here with ScSetStringParam::SpecialNumberOnly it
+ // means a simple number was not detected above, so test for
+ // special numbers. In any case ScSetStringParam::Always does not
+ // mean always, but only always for content that could be any
+ // numeric.
+ if ((aParam.meSetTextNumFormat == ScSetStringParam::Always ||
+ aParam.meSetTextNumFormat == ScSetStringParam::SpecialNumberOnly) &&
+ aParam.mpNumFormatter->IsNumberFormat(rString, nIndex, nVal))
+ {
+ // Set the cell format type to Text.
+ applyTextNumFormat(*this, nRow, aParam.mpNumFormatter);
+ }
+
+ rCell.set(rPool.intern(rString));
+ }
+ }
+
+ return bNumFmtSet;
+}
+
+/**
+ * Returns true if the cell format was set as well
+ */
+bool ScColumn::SetString( SCROW nRow, SCTAB nTabP, const OUString& rString,
+ formula::FormulaGrammar::AddressConvention eConv,
+ const ScSetStringParam* pParam )
+{
+ if (!GetDoc().ValidRow(nRow))
+ return false;
+
+ ScCellValue aNewCell;
+ bool bNumFmtSet = ParseString(aNewCell, nRow, nTabP, rString, eConv, pParam);
+ if (pParam)
+ aNewCell.release(*this, nRow, pParam->meStartListening);
+ else
+ aNewCell.release(*this, nRow);
+
+ // Do not set Formats and Formulas here anymore!
+ // These are queried during output
+
+ return bNumFmtSet;
+}
+
+void ScColumn::SetEditText( SCROW nRow, std::unique_ptr<EditTextObject> pEditText )
+{
+ pEditText->NormalizeString(GetDoc().GetSharedStringPool());
+ std::vector<SCROW> aNewSharedRows;
+ sc::CellStoreType::iterator it = GetPositionToInsert(nRow, aNewSharedRows, false);
+ maCells.set(it, nRow, pEditText.release());
+ maCellTextAttrs.set(nRow, sc::CellTextAttr());
+ CellStorageModified();
+
+ StartListeningUnshared( aNewSharedRows);
+
+ BroadcastNewCell(nRow);
+}
+
+void ScColumn::SetEditText( sc::ColumnBlockPosition& rBlockPos, SCROW nRow, std::unique_ptr<EditTextObject> pEditText )
+{
+ pEditText->NormalizeString(GetDoc().GetSharedStringPool());
+ std::vector<SCROW> aNewSharedRows;
+ rBlockPos.miCellPos = GetPositionToInsert(rBlockPos.miCellPos, nRow, aNewSharedRows, false);
+ rBlockPos.miCellPos = maCells.set(rBlockPos.miCellPos, nRow, pEditText.release());
+ rBlockPos.miCellTextAttrPos = maCellTextAttrs.set(
+ rBlockPos.miCellTextAttrPos, nRow, sc::CellTextAttr());
+
+ CellStorageModified();
+
+ StartListeningUnshared( aNewSharedRows);
+
+ BroadcastNewCell(nRow);
+}
+
+void ScColumn::SetEditText( sc::ColumnBlockPosition& rBlockPos, SCROW nRow, const EditTextObject& rEditText )
+{
+ if (GetDoc().GetEditPool() == rEditText.GetPool())
+ {
+ SetEditText(rBlockPos, nRow, rEditText.Clone());
+ return;
+ }
+
+ // rats, yet another "spool"
+ // Sadly there is no other way to change the Pool than to
+ // "spool" the Object through a corresponding Engine
+ EditEngine& rEngine = GetDoc().GetEditEngine();
+ rEngine.SetText(rEditText);
+ SetEditText(rBlockPos, nRow, rEngine.CreateTextObject());
+}
+
+void ScColumn::SetEditText( SCROW nRow, const EditTextObject& rEditText, const SfxItemPool* pEditPool )
+{
+ if (pEditPool && GetDoc().GetEditPool() == pEditPool)
+ {
+ SetEditText(nRow, rEditText.Clone());
+ return;
+ }
+
+ // rats, yet another "spool"
+ // Sadly there is no other way to change the Pool than to
+ // "spool" the Object through a corresponding Engine
+ EditEngine& rEngine = GetDoc().GetEditEngine();
+ rEngine.SetText(rEditText);
+ SetEditText(nRow, rEngine.CreateTextObject());
+}
+
+void ScColumn::SetFormula( SCROW nRow, const ScTokenArray& rArray, formula::FormulaGrammar::Grammar eGram )
+{
+ ScAddress aPos(nCol, nRow, nTab);
+
+ std::vector<SCROW> aNewSharedRows;
+ sc::CellStoreType::iterator it = GetPositionToInsert(nRow, aNewSharedRows, true);
+ ScFormulaCell* pCell = new ScFormulaCell(GetDoc(), aPos, rArray, eGram);
+ sal_uInt32 nCellFormat = GetNumberFormat(GetDoc().GetNonThreadedContext(), nRow);
+ if( (nCellFormat % SV_COUNTRY_LANGUAGE_OFFSET) == 0)
+ pCell->SetNeedNumberFormat(true);
+ it = maCells.set(it, nRow, pCell);
+ maCellTextAttrs.set(nRow, sc::CellTextAttr());
+
+ CellStorageModified();
+
+ AttachNewFormulaCell(it, nRow, *pCell, aNewSharedRows);
+}
+
+void ScColumn::SetFormula( SCROW nRow, const OUString& rFormula, formula::FormulaGrammar::Grammar eGram )
+{
+ ScAddress aPos(nCol, nRow, nTab);
+
+ std::vector<SCROW> aNewSharedRows;
+ sc::CellStoreType::iterator it = GetPositionToInsert(nRow, aNewSharedRows, true);
+ ScFormulaCell* pCell = new ScFormulaCell(GetDoc(), aPos, rFormula, eGram);
+ sal_uInt32 nCellFormat = GetNumberFormat(GetDoc().GetNonThreadedContext(), nRow);
+ if( (nCellFormat % SV_COUNTRY_LANGUAGE_OFFSET) == 0)
+ pCell->SetNeedNumberFormat(true);
+ it = maCells.set(it, nRow, pCell);
+ maCellTextAttrs.set(nRow, sc::CellTextAttr());
+
+ CellStorageModified();
+
+ AttachNewFormulaCell(it, nRow, *pCell, aNewSharedRows);
+}
+
+ScFormulaCell* ScColumn::SetFormulaCell(
+ SCROW nRow, ScFormulaCell* pCell, sc::StartListeningType eListenType,
+ bool bInheritNumFormatIfNeeded )
+{
+ std::vector<SCROW> aNewSharedRows;
+ sc::CellStoreType::iterator it = GetPositionToInsert(nRow, aNewSharedRows, true);
+ sal_uInt32 nCellFormat = GetNumberFormat(GetDoc().GetNonThreadedContext(), nRow);
+ if( (nCellFormat % SV_COUNTRY_LANGUAGE_OFFSET) == 0 && bInheritNumFormatIfNeeded )
+ pCell->SetNeedNumberFormat(true);
+ it = maCells.set(it, nRow, pCell);
+ maCellTextAttrs.set(nRow, sc::CellTextAttr());
+
+ CellStorageModified();
+
+ AttachNewFormulaCell(it, nRow, *pCell, aNewSharedRows, true, eListenType);
+
+ return pCell;
+}
+
+void ScColumn::SetFormulaCell(
+ sc::ColumnBlockPosition& rBlockPos, SCROW nRow, ScFormulaCell* pCell,
+ sc::StartListeningType eListenType,
+ bool bInheritNumFormatIfNeeded )
+{
+ std::vector<SCROW> aNewSharedRows;
+ rBlockPos.miCellPos = GetPositionToInsert(rBlockPos.miCellPos, nRow, aNewSharedRows, true);
+ sal_uInt32 nCellFormat = GetNumberFormat(GetDoc().GetNonThreadedContext(), nRow);
+ if( (nCellFormat % SV_COUNTRY_LANGUAGE_OFFSET) == 0 && bInheritNumFormatIfNeeded )
+ pCell->SetNeedNumberFormat(true);
+ rBlockPos.miCellPos = maCells.set(rBlockPos.miCellPos, nRow, pCell);
+ rBlockPos.miCellTextAttrPos = maCellTextAttrs.set(
+ rBlockPos.miCellTextAttrPos, nRow, sc::CellTextAttr());
+
+ CellStorageModified();
+
+ AttachNewFormulaCell(rBlockPos.miCellPos, nRow, *pCell, aNewSharedRows, true, eListenType);
+}
+
+bool ScColumn::SetFormulaCells( SCROW nRow, std::vector<ScFormulaCell*>& rCells )
+{
+ if (!GetDoc().ValidRow(nRow))
+ return false;
+
+ SCROW nEndRow = nRow + rCells.size() - 1;
+ if (!GetDoc().ValidRow(nEndRow))
+ return false;
+
+ sc::CellStoreType::position_type aPos = maCells.position(nRow);
+
+ // Detach all formula cells that will be overwritten.
+ std::vector<SCROW> aNewSharedRows;
+ DetachFormulaCells(aPos, rCells.size(), &aNewSharedRows);
+
+ if (!GetDoc().IsClipOrUndo())
+ {
+ for (size_t i = 0, n = rCells.size(); i < n; ++i)
+ {
+ SCROW nThisRow = nRow + i;
+ sal_uInt32 nFmt = GetNumberFormat(GetDoc().GetNonThreadedContext(), nThisRow);
+ if ((nFmt % SV_COUNTRY_LANGUAGE_OFFSET) == 0)
+ rCells[i]->SetNeedNumberFormat(true);
+ }
+ }
+
+ std::vector<sc::CellTextAttr> aDefaults(rCells.size(), sc::CellTextAttr());
+ maCellTextAttrs.set(nRow, aDefaults.begin(), aDefaults.end());
+
+ maCells.set(aPos.first, nRow, rCells.begin(), rCells.end());
+
+ CellStorageModified();
+
+ // Reget position_type as the type may have changed to formula, block and
+ // block size changed, ...
+ aPos = maCells.position(nRow);
+ AttachNewFormulaCells(aPos, rCells.size(), aNewSharedRows);
+
+ return true;
+}
+
+svl::SharedString ScColumn::GetSharedString( SCROW nRow ) const
+{
+ sc::CellStoreType::const_position_type aPos = maCells.position(nRow);
+ switch (aPos.first->type)
+ {
+ case sc::element_type_string:
+ return sc::string_block::at(*aPos.first->data, aPos.second);
+ case sc::element_type_edittext:
+ {
+ const EditTextObject* pObj = sc::edittext_block::at(*aPos.first->data, aPos.second);
+ std::vector<svl::SharedString> aSSs = pObj->GetSharedStrings();
+ if (aSSs.size() != 1)
+ // We don't handle multiline content for now.
+ return svl::SharedString();
+
+ return aSSs[0];
+ }
+ break;
+ default:
+ ;
+ }
+ return svl::SharedString();
+}
+
+namespace {
+
+class FilterEntriesHandler
+{
+ ScColumn& mrColumn;
+ ScFilterEntries& mrFilterEntries;
+
+ void processCell(const ScColumn& rColumn, SCROW nRow, ScRefCellValue& rCell)
+ {
+ SvNumberFormatter* pFormatter = mrColumn.GetDoc().GetFormatTable();
+ sal_uLong nFormat = mrColumn.GetNumberFormat(mrColumn.GetDoc().GetNonThreadedContext(), nRow);
+ OUString aStr = ScCellFormat::GetInputString(rCell, nFormat, *pFormatter, mrColumn.GetDoc(), mrColumn.HasFiltering());
+
+ // Colors
+ ScAddress aPos(rColumn.GetCol(), nRow, rColumn.GetTab());
+
+ Color backgroundColor;
+ bool bHasConditionalBackgroundColor = false;
+
+ Color textColor;
+ bool bHasConditionalTextColor = false;
+ // Check text & background color from cond. formatting
+ const ScPatternAttr* pPattern
+ = mrColumn.GetDoc().GetPattern(aPos.Col(), aPos.Row(), aPos.Tab());
+ if (pPattern)
+ {
+ if (!pPattern->GetItem(ATTR_CONDITIONAL).GetCondFormatData().empty())
+ {
+ const SfxItemSet* pCondSet
+ = mrColumn.GetDoc().GetCondResult(aPos.Col(), aPos.Row(), aPos.Tab());
+ const SvxColorItem* pColor = &pPattern->GetItem(ATTR_FONT_COLOR, pCondSet);
+ textColor = pColor->GetValue();
+ bHasConditionalTextColor = true;
+
+ const SvxBrushItem* pBackgroundColor = &pPattern->GetItem(ATTR_BACKGROUND, pCondSet);
+ backgroundColor = pBackgroundColor->GetColor();
+ bHasConditionalBackgroundColor = true;
+ }
+ }
+
+ if (!bHasConditionalTextColor)
+ {
+ const SvxColorItem* pColor = rColumn.GetDoc().GetAttr(aPos, ATTR_FONT_COLOR);
+ textColor = pColor->GetValue();
+ }
+ mrFilterEntries.addTextColor(textColor);
+
+ // Color scale needs a different handling
+ ScConditionalFormat* pCondFormat
+ = rColumn.GetDoc().GetCondFormat(aPos.Col(), aPos.Row(), aPos.Tab());
+ if (pCondFormat)
+ {
+ for (size_t i = 0; i < pCondFormat->size(); i++)
+ {
+ auto aEntry = pCondFormat->GetEntry(i);
+ if (aEntry->GetType() == ScFormatEntry::Type::Colorscale)
+ {
+ const ScColorScaleFormat* pColFormat
+ = static_cast<const ScColorScaleFormat*>(aEntry);
+ std::optional<Color> oColor = pColFormat->GetColor(aPos);
+ if (oColor)
+ {
+ backgroundColor = *oColor;
+ bHasConditionalBackgroundColor = true;
+ }
+ }
+ }
+ }
+ if (!bHasConditionalBackgroundColor)
+ {
+ const SvxBrushItem* pBrush = rColumn.GetDoc().GetAttr(aPos, ATTR_BACKGROUND);
+ backgroundColor = pBrush->GetColor();
+ }
+ mrFilterEntries.addBackgroundColor(backgroundColor);
+
+ if (rCell.hasString())
+ {
+ mrFilterEntries.push_back(ScTypedStrData(std::move(aStr)));
+ return;
+ }
+
+ double fVal = 0.0;
+
+ switch (rCell.meType)
+ {
+ case CELLTYPE_VALUE:
+ fVal = rCell.mfValue;
+ break;
+
+ case CELLTYPE_FORMULA:
+ {
+ ScFormulaCell* pFC = rCell.mpFormula;
+ FormulaError nErr = pFC->GetErrCode();
+ if (nErr != FormulaError::NONE)
+ {
+ // Error cell is evaluated as string (for now).
+ OUString aErr = ScGlobal::GetErrorString(nErr);
+ if (!aErr.isEmpty())
+ {
+ mrFilterEntries.push_back(ScTypedStrData(std::move(aErr)));
+ return;
+ }
+ }
+ else
+ fVal = pFC->GetValue();
+ }
+ break;
+ default:
+ ;
+ }
+
+ SvNumFormatType nType = pFormatter->GetType(nFormat);
+ bool bDate = false;
+ if ((nType & SvNumFormatType::DATE) && !(nType & SvNumFormatType::TIME))
+ {
+ // special case for date values. Disregard the time
+ // element if the number format is of date type.
+ fVal = rtl::math::approxFloor(fVal);
+ mrFilterEntries.mbHasDates = true;
+ bDate = true;
+ // Convert string representation to ISO 8601 date to eliminate
+ // locale dependent behaviour later when filtering for dates.
+ sal_uInt32 nIndex = pFormatter->GetFormatIndex( NF_DATE_DIN_YYYYMMDD);
+ pFormatter->GetInputLineString( fVal, nIndex, aStr);
+ }
+ else if (nType == SvNumFormatType::DATETIME)
+ {
+ // special case for datetime values.
+ // Convert string representation to ISO 8601 (with blank instead of T) datetime
+ // to eliminate locale dependent behaviour later when filtering for datetimes.
+ sal_uInt32 nIndex = pFormatter->GetFormatIndex(NF_DATETIME_ISO_YYYYMMDD_HHMMSS);
+ pFormatter->GetInputLineString(fVal, nIndex, aStr);
+ }
+ // store the formatted/rounded value for filtering
+ if ((nFormat % SV_COUNTRY_LANGUAGE_OFFSET) != 0 && !bDate)
+ mrFilterEntries.push_back(ScTypedStrData(std::move(aStr), fVal, rColumn.GetDoc().RoundValueAsShown(fVal, nFormat), ScTypedStrData::Value, bDate));
+ else
+ mrFilterEntries.push_back(ScTypedStrData(std::move(aStr), fVal, fVal, ScTypedStrData::Value, bDate));
+ }
+
+public:
+ FilterEntriesHandler(ScColumn& rColumn, ScFilterEntries& rFilterEntries) :
+ mrColumn(rColumn), mrFilterEntries(rFilterEntries) {}
+
+ void operator() (size_t nRow, double fVal)
+ {
+ ScRefCellValue aCell(fVal);
+ processCell(mrColumn, nRow, aCell);
+ }
+
+ void operator() (size_t nRow, const svl::SharedString& rStr)
+ {
+ ScRefCellValue aCell(&rStr);
+ processCell(mrColumn, nRow, aCell);
+ }
+
+ void operator() (size_t nRow, const EditTextObject* p)
+ {
+ ScRefCellValue aCell(p);
+ processCell(mrColumn, nRow, aCell);
+ }
+
+ void operator() (size_t nRow, const ScFormulaCell* p)
+ {
+ ScRefCellValue aCell(const_cast<ScFormulaCell*>(p));
+ processCell(mrColumn, nRow, aCell);
+ }
+
+ void operator() (const int nElemType, size_t nRow, size_t /* nDataSize */)
+ {
+ if ( nElemType == sc::element_type_empty )
+ {
+ if (!mrFilterEntries.mbHasEmpties)
+ {
+ mrFilterEntries.push_back(ScTypedStrData(OUString()));
+ mrFilterEntries.mbHasEmpties = true;
+ }
+ return;
+ }
+ ScRefCellValue aCell = mrColumn.GetCellValue(nRow);
+ processCell(mrColumn, nRow, aCell);
+ }
+};
+
+}
+
+void ScColumn::GetFilterEntries(
+ sc::ColumnBlockConstPosition& rBlockPos, SCROW nStartRow, SCROW nEndRow,
+ ScFilterEntries& rFilterEntries, bool bFiltering )
+{
+ mbFiltering = bFiltering;
+ FilterEntriesHandler aFunc(*this, rFilterEntries);
+ rBlockPos.miCellPos =
+ sc::ParseAll(rBlockPos.miCellPos, maCells, nStartRow, nEndRow, aFunc, aFunc);
+}
+
+namespace {
+
+/**
+ * Iterate over only string and edit-text cells.
+ */
+class StrCellIterator
+{
+ typedef std::pair<sc::CellStoreType::const_iterator,size_t> PosType;
+ PosType maPos;
+ sc::CellStoreType::const_iterator miBeg;
+ sc::CellStoreType::const_iterator miEnd;
+ const ScDocument* mpDoc;
+public:
+ StrCellIterator(const sc::CellStoreType& rCells, SCROW nStart, const ScDocument* pDoc) :
+ miBeg(rCells.begin()), miEnd(rCells.end()), mpDoc(pDoc)
+ {
+ if (pDoc->ValidRow(nStart))
+ maPos = rCells.position(nStart);
+ else
+ // Make this iterator invalid.
+ maPos.first = miEnd;
+ }
+
+ bool valid() const { return (maPos.first != miEnd); }
+
+ bool has() const
+ {
+ return (maPos.first->type == sc::element_type_string || maPos.first->type == sc::element_type_edittext);
+ }
+
+ bool prev()
+ {
+ if (!has())
+ {
+ // Not in a string block. Move back until we hit a string block.
+ while (!has())
+ {
+ if (maPos.first == miBeg)
+ return false;
+
+ --maPos.first; // move to the preceding block.
+ maPos.second = maPos.first->size - 1; // last cell in the block.
+ }
+ return true;
+ }
+
+ // We are in a string block.
+ if (maPos.second > 0)
+ {
+ // Move back one cell in the same block.
+ --maPos.second;
+ }
+ else
+ {
+ // Move back to the preceding string block.
+ while (true)
+ {
+ if (maPos.first == miBeg)
+ return false;
+
+ // Move to the last cell of the previous block.
+ --maPos.first;
+ maPos.second = maPos.first->size - 1;
+ if (has())
+ break;
+ }
+ }
+ return true;
+ }
+
+ bool next()
+ {
+ if (!has())
+ {
+ // Not in a string block. Move forward until we hit a string block.
+ while (!has())
+ {
+ ++maPos.first;
+ if (maPos.first == miEnd)
+ return false;
+
+ maPos.second = 0; // First cell in this block.
+ }
+ return true;
+ }
+
+ // We are in a string block.
+ ++maPos.second;
+ if (maPos.second >= maPos.first->size)
+ {
+ // Move to the next string block.
+ while (true)
+ {
+ ++maPos.first;
+ if (maPos.first == miEnd)
+ return false;
+
+ maPos.second = 0;
+ if (has())
+ break;
+ }
+ }
+ return true;
+ }
+
+ OUString get() const
+ {
+ switch (maPos.first->type)
+ {
+ case sc::element_type_string:
+ return sc::string_block::at(*maPos.first->data, maPos.second).getString();
+ case sc::element_type_edittext:
+ {
+ const EditTextObject* p = sc::edittext_block::at(*maPos.first->data, maPos.second);
+ return ScEditUtil::GetString(*p, mpDoc);
+ }
+ default:
+ ;
+ }
+ return OUString();
+ }
+};
+
+}
+
+// GetDataEntries - Strings from continuous Section around nRow
+bool ScColumn::GetDataEntries(
+ SCROW nStartRow, std::set<ScTypedStrData>& rStrings) const
+{
+ // Start at the specified row position, and collect all string values
+ // going upward and downward directions in parallel. The start position
+ // cell must be skipped.
+
+ StrCellIterator aItrUp(maCells, nStartRow, &GetDoc());
+ StrCellIterator aItrDown(maCells, nStartRow+1, &GetDoc());
+
+ bool bMoveUp = aItrUp.valid();
+ if (!bMoveUp)
+ // Current cell is invalid.
+ return false;
+
+ // Skip the start position cell.
+ bMoveUp = aItrUp.prev(); // Find the previous string cell position.
+
+ bool bMoveDown = aItrDown.valid();
+ if (bMoveDown && !aItrDown.has())
+ bMoveDown = aItrDown.next(); // Find the next string cell position.
+
+ bool bFound = false;
+ while (bMoveUp)
+ {
+ // Get the current string and move up.
+ OUString aStr = aItrUp.get();
+ if (!aStr.isEmpty())
+ {
+ if (rStrings.insert(ScTypedStrData(std::move(aStr))).second)
+ bFound = true;
+ }
+
+ bMoveUp = aItrUp.prev();
+ }
+
+ while (bMoveDown)
+ {
+ // Get the current string and move down.
+ OUString aStr = aItrDown.get();
+ if (!aStr.isEmpty())
+ {
+ if (rStrings.insert(ScTypedStrData(std::move(aStr))).second)
+ bFound = true;
+ }
+
+ bMoveDown = aItrDown.next();
+ }
+
+ return bFound;
+}
+
+namespace {
+
+class FormulaToValueHandler
+{
+ struct Entry
+ {
+ SCROW mnRow;
+ ScCellValue maValue;
+
+ Entry(SCROW nRow, double f) : mnRow(nRow), maValue(f) {}
+ Entry(SCROW nRow, const svl::SharedString& rStr) : mnRow(nRow), maValue(rStr) {}
+ };
+
+ typedef std::vector<Entry> EntriesType;
+ EntriesType maEntries;
+
+public:
+
+ void operator() (size_t nRow, const ScFormulaCell* p)
+ {
+ ScFormulaCell* p2 = const_cast<ScFormulaCell*>(p);
+ if (p2->IsValue())
+ maEntries.emplace_back(nRow, p2->GetValue());
+ else
+ maEntries.emplace_back(nRow, p2->GetString());
+ }
+
+ void commitCells(ScColumn& rColumn)
+ {
+ sc::ColumnBlockPosition aBlockPos;
+ rColumn.InitBlockPosition(aBlockPos);
+
+ for (const Entry& r : maEntries)
+ {
+ switch (r.maValue.meType)
+ {
+ case CELLTYPE_VALUE:
+ rColumn.SetValue(aBlockPos, r.mnRow, r.maValue.mfValue, false);
+ break;
+ case CELLTYPE_STRING:
+ rColumn.SetRawString(aBlockPos, r.mnRow, *r.maValue.mpString, false);
+ break;
+ default:
+ ;
+ }
+ }
+ }
+};
+
+}
+
+void ScColumn::RemoveProtected( SCROW nStartRow, SCROW nEndRow )
+{
+ FormulaToValueHandler aFunc;
+ sc::CellStoreType::const_iterator itPos = maCells.begin();
+
+ ScAttrIterator aAttrIter( pAttrArray.get(), nStartRow, nEndRow, GetDoc().GetDefPattern() );
+ SCROW nTop = -1;
+ SCROW nBottom = -1;
+ const ScPatternAttr* pPattern = aAttrIter.Next( nTop, nBottom );
+ while (pPattern)
+ {
+ const ScProtectionAttr* pAttr = &pPattern->GetItem(ATTR_PROTECTION);
+ if ( pAttr->GetHideCell() )
+ DeleteArea( nTop, nBottom, InsertDeleteFlags::CONTENTS );
+ else if ( pAttr->GetHideFormula() )
+ {
+ // Replace all formula cells between nTop and nBottom with raw value cells.
+ itPos = sc::ParseFormula(itPos, maCells, nTop, nBottom, aFunc);
+ }
+
+ pPattern = aAttrIter.Next( nTop, nBottom );
+ }
+
+ aFunc.commitCells(*this);
+}
+
+void ScColumn::SetError( SCROW nRow, const FormulaError nError)
+{
+ if (!GetDoc().ValidRow(nRow))
+ return;
+
+ ScFormulaCell* pCell = new ScFormulaCell(GetDoc(), ScAddress(nCol, nRow, nTab));
+ pCell->SetErrCode(nError);
+
+ std::vector<SCROW> aNewSharedRows;
+ sc::CellStoreType::iterator it = GetPositionToInsert(nRow, aNewSharedRows, true);
+ it = maCells.set(it, nRow, pCell);
+ maCellTextAttrs.set(nRow, sc::CellTextAttr());
+
+ CellStorageModified();
+
+ AttachNewFormulaCell(it, nRow, *pCell, aNewSharedRows);
+}
+
+void ScColumn::SetRawString( SCROW nRow, const OUString& rStr )
+{
+ if (!GetDoc().ValidRow(nRow))
+ return;
+
+ svl::SharedString aSS = GetDoc().GetSharedStringPool().intern(rStr);
+ if (!aSS.getData())
+ return;
+
+ SetRawString(nRow, aSS);
+}
+
+void ScColumn::SetRawString( SCROW nRow, const svl::SharedString& rStr )
+{
+ if (!GetDoc().ValidRow(nRow))
+ return;
+
+ std::vector<SCROW> aNewSharedRows;
+ sc::CellStoreType::iterator it = GetPositionToInsert(nRow, aNewSharedRows, false);
+ maCells.set(it, nRow, rStr);
+ maCellTextAttrs.set(nRow, sc::CellTextAttr());
+
+ CellStorageModified();
+
+ StartListeningUnshared( aNewSharedRows);
+
+ BroadcastNewCell(nRow);
+}
+
+void ScColumn::SetRawString(
+ sc::ColumnBlockPosition& rBlockPos, SCROW nRow, const svl::SharedString& rStr, bool bBroadcast )
+{
+ if (!GetDoc().ValidRow(nRow))
+ return;
+
+ std::vector<SCROW> aNewSharedRows;
+ rBlockPos.miCellPos = GetPositionToInsert(rBlockPos.miCellPos, nRow, aNewSharedRows, false);
+ rBlockPos.miCellPos = maCells.set(rBlockPos.miCellPos, nRow, rStr);
+ rBlockPos.miCellTextAttrPos = maCellTextAttrs.set(
+ rBlockPos.miCellTextAttrPos, nRow, sc::CellTextAttr());
+
+ CellStorageModified();
+
+ StartListeningUnshared( aNewSharedRows);
+
+ if (bBroadcast)
+ BroadcastNewCell(nRow);
+}
+
+void ScColumn::SetValue( SCROW nRow, double fVal )
+{
+ if (!GetDoc().ValidRow(nRow))
+ return;
+
+ std::vector<SCROW> aNewSharedRows;
+ sc::CellStoreType::iterator it = GetPositionToInsert(nRow, aNewSharedRows, false);
+ maCells.set(it, nRow, fVal);
+ maCellTextAttrs.set(nRow, sc::CellTextAttr());
+
+ CellStorageModified();
+
+ StartListeningUnshared( aNewSharedRows);
+
+ BroadcastNewCell(nRow);
+}
+
+void ScColumn::SetValue(
+ sc::ColumnBlockPosition& rBlockPos, SCROW nRow, double fVal, bool bBroadcast )
+{
+ if (!GetDoc().ValidRow(nRow))
+ return;
+
+ std::vector<SCROW> aNewSharedRows;
+ rBlockPos.miCellPos = GetPositionToInsert(rBlockPos.miCellPos, nRow, aNewSharedRows, false);
+ rBlockPos.miCellPos = maCells.set(rBlockPos.miCellPos, nRow, fVal);
+ rBlockPos.miCellTextAttrPos = maCellTextAttrs.set(
+ rBlockPos.miCellTextAttrPos, nRow, sc::CellTextAttr());
+
+ CellStorageModified();
+
+ StartListeningUnshared( aNewSharedRows);
+
+ if (bBroadcast)
+ BroadcastNewCell(nRow);
+}
+
+OUString ScColumn::GetString( const ScRefCellValue& aCell, SCROW nRow, const ScInterpreterContext* pContext ) const
+{
+ // ugly hack for ordering problem with GetNumberFormat and missing inherited formats
+ if (aCell.meType == CELLTYPE_FORMULA)
+ aCell.mpFormula->MaybeInterpret();
+
+ sal_uInt32 nFormat = GetNumberFormat( pContext ? *pContext : GetDoc().GetNonThreadedContext(), nRow);
+ const Color* pColor = nullptr;
+ return ScCellFormat::GetString(aCell, nFormat, &pColor,
+ pContext ? *(pContext->GetFormatTable()) : *(GetDoc().GetFormatTable()), GetDoc());
+}
+
+double* ScColumn::GetValueCell( SCROW nRow )
+{
+ std::pair<sc::CellStoreType::iterator,size_t> aPos = maCells.position(nRow);
+ sc::CellStoreType::iterator it = aPos.first;
+ if (it == maCells.end())
+ return nullptr;
+
+ if (it->type != sc::element_type_numeric)
+ return nullptr;
+
+ return &sc::numeric_block::at(*it->data, aPos.second);
+}
+
+OUString ScColumn::GetInputString( const ScRefCellValue& aCell, SCROW nRow, const svl::SharedString** pShared, bool bForceSystemLocale ) const
+{
+ sal_uLong nFormat = GetNumberFormat(GetDoc().GetNonThreadedContext(), nRow);
+ return ScCellFormat::GetInputString(aCell, nFormat, *(GetDoc().GetFormatTable()), GetDoc(), pShared, false, bForceSystemLocale);
+}
+
+double ScColumn::GetValue( SCROW nRow ) const
+{
+ std::pair<sc::CellStoreType::const_iterator,size_t> aPos = maCells.position(nRow);
+ sc::CellStoreType::const_iterator it = aPos.first;
+ switch (it->type)
+ {
+ case sc::element_type_numeric:
+ return sc::numeric_block::at(*it->data, aPos.second);
+ case sc::element_type_formula:
+ {
+ const ScFormulaCell* p = sc::formula_block::at(*it->data, aPos.second);
+ ScFormulaCell* p2 = const_cast<ScFormulaCell*>(p);
+ return p2->IsValue() ? p2->GetValue() : 0.0;
+ }
+ default:
+ ;
+ }
+
+ return 0.0;
+}
+
+const EditTextObject* ScColumn::GetEditText( SCROW nRow ) const
+{
+ std::pair<sc::CellStoreType::const_iterator,size_t> aPos = maCells.position(nRow);
+ sc::CellStoreType::const_iterator it = aPos.first;
+ if (it == maCells.end())
+ return nullptr;
+
+ if (it->type != sc::element_type_edittext)
+ return nullptr;
+
+ return sc::edittext_block::at(*it->data, aPos.second);
+}
+
+void ScColumn::RemoveEditTextCharAttribs( SCROW nRow, const ScPatternAttr& rAttr )
+{
+ std::pair<sc::CellStoreType::iterator,size_t> aPos = maCells.position(nRow);
+ sc::CellStoreType::iterator it = aPos.first;
+ if (it == maCells.end())
+ return;
+
+ if (it->type != sc::element_type_edittext)
+ return;
+
+ EditTextObject* p = sc::edittext_block::at(*it->data, aPos.second);
+ ScEditUtil::RemoveCharAttribs(*p, rAttr);
+}
+
+OUString ScColumn::GetFormula( SCROW nRow ) const
+{
+ const ScFormulaCell* p = FetchFormulaCell(nRow);
+ if (p)
+ return p->GetFormula();
+ return OUString();
+}
+
+const ScFormulaCell* ScColumn::GetFormulaCell( SCROW nRow ) const
+{
+ return FetchFormulaCell(nRow);
+}
+
+ScFormulaCell* ScColumn::GetFormulaCell( SCROW nRow )
+{
+ return const_cast<ScFormulaCell*>(FetchFormulaCell(nRow));
+}
+
+CellType ScColumn::GetCellType( SCROW nRow ) const
+{
+ switch (maCells.get_type(nRow))
+ {
+ case sc::element_type_numeric:
+ return CELLTYPE_VALUE;
+ case sc::element_type_string:
+ return CELLTYPE_STRING;
+ case sc::element_type_edittext:
+ return CELLTYPE_EDIT;
+ case sc::element_type_formula:
+ return CELLTYPE_FORMULA;
+ default:
+ ;
+ }
+ return CELLTYPE_NONE;
+}
+
+namespace {
+
+/**
+ * Count the number of all non-empty cells.
+ */
+class CellCounter
+{
+ size_t mnCount;
+public:
+ CellCounter() : mnCount(0) {}
+
+ void operator() (const sc::CellStoreType::value_type& node)
+ {
+ if (node.type == sc::element_type_empty)
+ return;
+
+ mnCount += node.size;
+ }
+
+ size_t getCount() const { return mnCount; }
+};
+
+}
+
+SCSIZE ScColumn::GetCellCount() const
+{
+ CellCounter aFunc;
+ aFunc = std::for_each(maCells.begin(), maCells.end(), aFunc);
+ return aFunc.getCount();
+}
+
+FormulaError ScColumn::GetErrCode( SCROW nRow ) const
+{
+ std::pair<sc::CellStoreType::const_iterator,size_t> aPos = maCells.position(nRow);
+ sc::CellStoreType::const_iterator it = aPos.first;
+ if (it == maCells.end())
+ return FormulaError::NONE;
+
+ if (it->type != sc::element_type_formula)
+ return FormulaError::NONE;
+
+ const ScFormulaCell* p = sc::formula_block::at(*it->data, aPos.second);
+ return const_cast<ScFormulaCell*>(p)->GetErrCode();
+}
+
+bool ScColumn::HasStringData( SCROW nRow ) const
+{
+ std::pair<sc::CellStoreType::const_iterator,size_t> aPos = maCells.position(nRow);
+ switch (aPos.first->type)
+ {
+ case sc::element_type_string:
+ case sc::element_type_edittext:
+ return true;
+ case sc::element_type_formula:
+ {
+ const ScFormulaCell* p = sc::formula_block::at(*aPos.first->data, aPos.second);
+ return !const_cast<ScFormulaCell*>(p)->IsValue();
+ }
+ default:
+ ;
+ }
+
+ return false;
+}
+
+bool ScColumn::HasValueData( SCROW nRow ) const
+{
+ std::pair<sc::CellStoreType::const_iterator,size_t> aPos = maCells.position(nRow);
+ switch (aPos.first->type)
+ {
+ case sc::element_type_numeric:
+ return true;
+ case sc::element_type_formula:
+ {
+ const ScFormulaCell* p = sc::formula_block::at(*aPos.first->data, aPos.second);
+ return const_cast<ScFormulaCell*>(p)->IsValue();
+ }
+ default:
+ ;
+ }
+
+ return false;
+}
+
+/**
+ * Return true if there is a string or editcell in the range
+ */
+bool ScColumn::HasStringCells( SCROW nStartRow, SCROW nEndRow ) const
+{
+ std::pair<sc::CellStoreType::const_iterator,size_t> aPos = maCells.position(nStartRow);
+ sc::CellStoreType::const_iterator it = aPos.first;
+ size_t nOffset = aPos.second;
+ SCROW nRow = nStartRow;
+ for (; it != maCells.end() && nRow <= nEndRow; ++it)
+ {
+ if (it->type == sc::element_type_string || it->type == sc::element_type_edittext)
+ return true;
+
+ nRow += it->size - nOffset;
+ nOffset = 0;
+ }
+
+ return false;
+}
+
+namespace {
+
+class MaxStringLenHandler
+{
+ sal_Int32 mnMaxLen;
+ const ScColumn& mrColumn;
+ SvNumberFormatter* mpFormatter;
+ rtl_TextEncoding meCharSet;
+ bool mbOctetEncoding;
+
+ void processCell(size_t nRow, const ScRefCellValue& rCell)
+ {
+ const Color* pColor;
+ sal_uInt32 nFormat = mrColumn.GetAttr(nRow, ATTR_VALUE_FORMAT).GetValue();
+ OUString aString = ScCellFormat::GetString(rCell, nFormat, &pColor, *mpFormatter, mrColumn.GetDoc());
+ sal_Int32 nLen = 0;
+ if (mbOctetEncoding)
+ {
+ OString aOString;
+ if (!aString.convertToString(&aOString, meCharSet,
+ RTL_UNICODETOTEXT_FLAGS_UNDEFINED_ERROR |
+ RTL_UNICODETOTEXT_FLAGS_INVALID_ERROR))
+ {
+ // TODO: anything? this is used by the dBase export filter
+ // that throws an error anyway, but in case of another
+ // context we might want to indicate a conversion error
+ // early.
+ }
+ nLen = aOString.getLength();
+ }
+ else
+ nLen = aString.getLength() * sizeof(sal_Unicode);
+
+ if (mnMaxLen < nLen)
+ mnMaxLen = nLen;
+ }
+
+public:
+ MaxStringLenHandler(const ScColumn& rColumn, rtl_TextEncoding eCharSet) :
+ mnMaxLen(0),
+ mrColumn(rColumn),
+ mpFormatter(rColumn.GetDoc().GetFormatTable()),
+ meCharSet(eCharSet),
+ mbOctetEncoding(rtl_isOctetTextEncoding(eCharSet))
+ {
+ }
+
+ void operator() (size_t nRow, double fVal)
+ {
+ ScRefCellValue aCell(fVal);
+ processCell(nRow, aCell);
+ }
+
+ void operator() (size_t nRow, const svl::SharedString& rStr)
+ {
+ ScRefCellValue aCell(&rStr);
+ processCell(nRow, aCell);
+ }
+
+ void operator() (size_t nRow, const EditTextObject* p)
+ {
+ ScRefCellValue aCell(p);
+ processCell(nRow, aCell);
+ }
+
+ void operator() (size_t nRow, const ScFormulaCell* p)
+ {
+ ScRefCellValue aCell(const_cast<ScFormulaCell*>(p));
+ processCell(nRow, aCell);
+ }
+
+ sal_Int32 getMaxLen() const { return mnMaxLen; }
+};
+
+}
+
+sal_Int32 ScColumn::GetMaxStringLen( SCROW nRowStart, SCROW nRowEnd, rtl_TextEncoding eCharSet ) const
+{
+ MaxStringLenHandler aFunc(*this, eCharSet);
+ sc::ParseAllNonEmpty(maCells.begin(), maCells, nRowStart, nRowEnd, aFunc);
+ return aFunc.getMaxLen();
+}
+
+namespace {
+
+class MaxNumStringLenHandler
+{
+ const ScColumn& mrColumn;
+ SvNumberFormatter* mpFormatter;
+ sal_Int32 mnMaxLen;
+ sal_uInt16 mnPrecision;
+ sal_uInt16 mnMaxGeneralPrecision;
+ bool mbHaveSigned;
+
+ void processCell(size_t nRow, ScRefCellValue& rCell)
+ {
+ sal_uInt16 nCellPrecision = mnMaxGeneralPrecision;
+ if (rCell.meType == CELLTYPE_FORMULA)
+ {
+ if (!rCell.mpFormula->IsValue())
+ return;
+
+ // Limit unformatted formula cell precision to precision
+ // encountered so far, if any, otherwise we'd end up with 15 just
+ // because of =1/3 ... If no precision yet then arbitrarily limit
+ // to a maximum of 4 unless a maximum general precision is set.
+ if (mnPrecision)
+ nCellPrecision = mnPrecision;
+ else
+ nCellPrecision = (mnMaxGeneralPrecision >= 15) ? 4 : mnMaxGeneralPrecision;
+ }
+
+ double fVal = rCell.getValue();
+ if (!mbHaveSigned && fVal < 0.0)
+ mbHaveSigned = true;
+
+ OUString aString;
+ OUString aSep;
+ sal_uInt16 nPrec;
+ sal_uInt32 nFormat =
+ mrColumn.GetAttr(nRow, ATTR_VALUE_FORMAT).GetValue();
+ if (nFormat % SV_COUNTRY_LANGUAGE_OFFSET)
+ {
+ aSep = mpFormatter->GetFormatDecimalSep(nFormat);
+ aString = ScCellFormat::GetInputString(rCell, nFormat, *mpFormatter, mrColumn.GetDoc());
+ const SvNumberformat* pEntry = mpFormatter->GetEntry(nFormat);
+ if (pEntry)
+ {
+ bool bThousand, bNegRed;
+ sal_uInt16 nLeading;
+ pEntry->GetFormatSpecialInfo(bThousand, bNegRed, nPrec, nLeading);
+ }
+ else
+ nPrec = mpFormatter->GetFormatPrecision(nFormat);
+ }
+ else
+ {
+ if (mnPrecision >= mnMaxGeneralPrecision)
+ return; // early bail out for nothing changes here
+
+ if (!fVal)
+ {
+ // 0 doesn't change precision, but set a maximum length if none yet.
+ if (!mnMaxLen)
+ mnMaxLen = 1;
+ return;
+ }
+
+ // Simple number string with at most 15 decimals and trailing
+ // decimal zeros eliminated.
+ aSep = ".";
+ aString = rtl::math::doubleToUString( fVal, rtl_math_StringFormat_F, nCellPrecision, '.', true);
+ nPrec = SvNumberFormatter::UNLIMITED_PRECISION;
+ }
+
+ sal_Int32 nLen = aString.getLength();
+ if (nLen <= 0)
+ // Ignore empty string.
+ return;
+
+ if (nPrec == SvNumberFormatter::UNLIMITED_PRECISION && mnPrecision < mnMaxGeneralPrecision)
+ {
+ if (nFormat % SV_COUNTRY_LANGUAGE_OFFSET)
+ {
+ // For some reason we couldn't obtain a precision from the
+ // format, retry with simple number string.
+ aSep = ".";
+ aString = rtl::math::doubleToUString( fVal, rtl_math_StringFormat_F, nCellPrecision, '.', true);
+ nLen = aString.getLength();
+ }
+ sal_Int32 nSep = aString.indexOf( aSep);
+ if (nSep != -1)
+ nPrec = aString.getLength() - nSep - 1;
+
+ }
+
+ if (nPrec != SvNumberFormatter::UNLIMITED_PRECISION && nPrec > mnPrecision)
+ mnPrecision = nPrec;
+
+ if (mnPrecision)
+ { // less than mnPrecision in string => widen it
+ // more => shorten it
+ sal_Int32 nTmp = aString.indexOf(aSep);
+ if ( nTmp == -1 )
+ nLen += mnPrecision + aSep.getLength();
+ else
+ {
+ nTmp = aString.getLength() - (nTmp + aSep.getLength());
+ if (nTmp != mnPrecision)
+ nLen += mnPrecision - nTmp;
+ // nPrecision > nTmp : nLen + Diff
+ // nPrecision < nTmp : nLen - Diff
+ }
+ }
+
+ // Enlarge for sign if necessary. Bear in mind that
+ // GetMaxNumberStringLen() is for determining dBase decimal field width
+ // and precision where the overall field width must include the sign.
+ // Fitting -1 into "#.##" (width 4, 2 decimals) does not work.
+ if (mbHaveSigned && fVal >= 0.0)
+ ++nLen;
+
+ if (mnMaxLen < nLen)
+ mnMaxLen = nLen;
+ }
+
+public:
+ MaxNumStringLenHandler(const ScColumn& rColumn, sal_uInt16 nMaxGeneralPrecision) :
+ mrColumn(rColumn), mpFormatter(rColumn.GetDoc().GetFormatTable()),
+ mnMaxLen(0), mnPrecision(0), mnMaxGeneralPrecision(nMaxGeneralPrecision),
+ mbHaveSigned(false)
+ {
+ // Limit the decimals passed to doubleToUString().
+ // Also, the dBaseIII maximum precision is 15.
+ if (mnMaxGeneralPrecision > 15)
+ mnMaxGeneralPrecision = 15;
+ }
+
+ void operator() (size_t nRow, double fVal)
+ {
+ ScRefCellValue aCell(fVal);
+ processCell(nRow, aCell);
+ }
+
+ void operator() (size_t nRow, const ScFormulaCell* p)
+ {
+ ScRefCellValue aCell(const_cast<ScFormulaCell*>(p));
+ processCell(nRow, aCell);
+ }
+
+ sal_Int32 getMaxLen() const { return mnMaxLen; }
+
+ sal_uInt16 getPrecision() const { return mnPrecision; }
+};
+
+}
+
+sal_Int32 ScColumn::GetMaxNumberStringLen(
+ sal_uInt16& nPrecision, SCROW nRowStart, SCROW nRowEnd ) const
+{
+ sal_uInt16 nMaxGeneralPrecision = GetDoc().GetDocOptions().GetStdPrecision();
+ MaxNumStringLenHandler aFunc(*this, nMaxGeneralPrecision);
+ sc::ParseFormulaNumeric(maCells.begin(), maCells, nRowStart, nRowEnd, aFunc);
+ nPrecision = aFunc.getPrecision();
+ return aFunc.getMaxLen();
+}
+
+namespace {
+
+class GroupFormulaCells
+{
+ std::vector<ScAddress>* mpGroupPos;
+
+public:
+ explicit GroupFormulaCells(std::vector<ScAddress>* pGroupPos)
+ : mpGroupPos(pGroupPos) {}
+
+ void operator() (sc::CellStoreType::value_type& node)
+ {
+ if (node.type != sc::element_type_formula)
+ // We are only interested in formula cells.
+ return;
+
+ size_t nRow = node.position; // start row position.
+
+ sc::formula_block::iterator it = sc::formula_block::begin(*node.data);
+ sc::formula_block::iterator itEnd = sc::formula_block::end(*node.data);
+
+ // This block should never be empty.
+
+ ScFormulaCell* pPrev = *it;
+ ScFormulaCellGroupRef xPrevGrp = pPrev->GetCellGroup();
+ if (xPrevGrp)
+ {
+ // Move to the cell after the last cell of the current group.
+ std::advance(it, xPrevGrp->mnLength);
+ nRow += xPrevGrp->mnLength;
+ }
+ else
+ {
+ ++it;
+ ++nRow;
+ }
+
+ ScFormulaCell* pCur = nullptr;
+ ScFormulaCellGroupRef xCurGrp;
+ for (; it != itEnd; pPrev = pCur, xPrevGrp = xCurGrp)
+ {
+ pCur = *it;
+ xCurGrp = pCur->GetCellGroup();
+
+ ScFormulaCell::CompareState eCompState = pPrev->CompareByTokenArray(*pCur);
+ if (eCompState == ScFormulaCell::NotEqual)
+ {
+ // different formula tokens.
+ if (xCurGrp)
+ {
+ // Move to the cell after the last cell of the current group.
+ if (xCurGrp->mnLength > std::distance(it, itEnd))
+ throw css::lang::IllegalArgumentException();
+ std::advance(it, xCurGrp->mnLength);
+ nRow += xCurGrp->mnLength;
+ }
+ else
+ {
+ ++it;
+ ++nRow;
+ }
+
+ continue;
+ }
+
+ // Formula tokens equal those of the previous formula cell or cell group.
+ if (xPrevGrp)
+ {
+ // Previous cell is a group.
+ if (xCurGrp)
+ {
+ // The current cell is a group. Merge these two groups.
+ xPrevGrp->mnLength += xCurGrp->mnLength;
+ pCur->SetCellGroup(xPrevGrp);
+ sc::formula_block::iterator itGrpEnd = it;
+ if (xCurGrp->mnLength > std::distance(itGrpEnd, itEnd))
+ throw css::lang::IllegalArgumentException();
+ std::advance(itGrpEnd, xCurGrp->mnLength);
+ for (++it; it != itGrpEnd; ++it)
+ {
+ ScFormulaCell* pCell = *it;
+ pCell->SetCellGroup(xPrevGrp);
+ }
+ nRow += xCurGrp->mnLength;
+ }
+ else
+ {
+ // Add this cell to the previous group.
+ pCur->SetCellGroup(xPrevGrp);
+ ++xPrevGrp->mnLength;
+ ++nRow;
+ ++it;
+ }
+
+ }
+ else if (xCurGrp)
+ {
+ // Previous cell is a regular cell and current cell is a group.
+ nRow += xCurGrp->mnLength;
+ if (xCurGrp->mnLength > std::distance(it, itEnd))
+ throw css::lang::IllegalArgumentException();
+ std::advance(it, xCurGrp->mnLength);
+ pPrev->SetCellGroup(xCurGrp);
+ xCurGrp->mpTopCell = pPrev;
+ ++xCurGrp->mnLength;
+ xPrevGrp = xCurGrp;
+ }
+ else
+ {
+ // Both previous and current cells are regular cells.
+ assert(pPrev->aPos.Row() == static_cast<SCROW>(nRow - 1));
+ xPrevGrp = pPrev->CreateCellGroup(2, eCompState == ScFormulaCell::EqualInvariant);
+ pCur->SetCellGroup(xPrevGrp);
+ ++nRow;
+ ++it;
+ }
+
+ if (mpGroupPos)
+ mpGroupPos->push_back(pCur->aPos);
+
+ pCur = pPrev;
+ xCurGrp = xPrevGrp;
+ }
+ }
+};
+
+}
+
+void ScColumn::RegroupFormulaCells( std::vector<ScAddress>* pGroupPos )
+{
+ // re-build formula groups.
+ std::for_each(maCells.begin(), maCells.end(), GroupFormulaCells(pGroupPos));
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sc/source/core/data/column4.cxx b/sc/source/core/data/column4.cxx
new file mode 100644
index 000000000..731812485
--- /dev/null
+++ b/sc/source/core/data/column4.cxx
@@ -0,0 +1,2225 @@
+/* -*- 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/.
+ */
+
+#include <column.hxx>
+#include <clipparam.hxx>
+#include <cellvalue.hxx>
+#include <attarray.hxx>
+#include <document.hxx>
+#include <cellvalues.hxx>
+#include <columnspanset.hxx>
+#include <columniterator.hxx>
+#include <mtvcellfunc.hxx>
+#include <clipcontext.hxx>
+#include <attrib.hxx>
+#include <patattr.hxx>
+#include <docpool.hxx>
+#include <conditio.hxx>
+#include <formulagroup.hxx>
+#include <tokenarray.hxx>
+#include <scitems.hxx>
+#include <cellform.hxx>
+#include <sharedformula.hxx>
+#include <drwlayer.hxx>
+#include <compiler.hxx>
+#include <recursionhelper.hxx>
+#include <docsh.hxx>
+
+#include <SparklineGroup.hxx>
+
+#include <o3tl/safeint.hxx>
+#include <svl/sharedstringpool.hxx>
+#include <sal/log.hxx>
+#include <tools/stream.hxx>
+
+#include <numeric>
+#include <vector>
+#include <cassert>
+
+sc::MultiDataCellState::StateType ScColumn::HasDataCellsInRange(
+ SCROW nRow1, SCROW nRow2, SCROW* pRow1 ) const
+{
+ sc::CellStoreType::const_position_type aPos = maCells.position(nRow1);
+ sc::CellStoreType::const_iterator it = aPos.first;
+ size_t nOffset = aPos.second;
+ SCROW nRow = nRow1;
+ bool bHasOne = false; // whether or not we have found a non-empty block of size one.
+
+ for (; it != maCells.end() && nRow <= nRow2; ++it)
+ {
+ if (it->type != sc::element_type_empty)
+ {
+ // non-empty block found.
+ assert(it->size > 0); // mtv should never contain a block of zero length.
+ size_t nSize = it->size - nOffset;
+
+ SCROW nLastRow = nRow + nSize - 1;
+ if (nLastRow > nRow2)
+ // shrink the size to avoid exceeding the specified last row position.
+ nSize -= nLastRow - nRow2;
+
+ if (nSize == 1)
+ {
+ // this block is of size one.
+ if (bHasOne)
+ return sc::MultiDataCellState::HasMultipleCells;
+
+ bHasOne = true;
+ if (pRow1)
+ *pRow1 = nRow;
+ }
+ else
+ {
+ // size of this block is greater than one.
+ if (pRow1)
+ *pRow1 = nRow;
+ return sc::MultiDataCellState::HasMultipleCells;
+ }
+ }
+
+ nRow += it->size - nOffset;
+ nOffset = 0;
+ }
+
+ return bHasOne ? sc::MultiDataCellState::HasOneCell : sc::MultiDataCellState::Empty;
+}
+
+void ScColumn::DeleteBeforeCopyFromClip(
+ sc::CopyFromClipContext& rCxt, const ScColumn& rClipCol, sc::ColumnSpanSet& rBroadcastSpans )
+{
+ ScDocument& rDocument = GetDoc();
+ sc::CopyFromClipContext::Range aRange = rCxt.getDestRange();
+ if (!rDocument.ValidRow(aRange.mnRow1) || !rDocument.ValidRow(aRange.mnRow2))
+ return;
+
+ sc::ColumnBlockPosition* pBlockPos = rCxt.getBlockPosition(nTab, nCol);
+ if (!pBlockPos)
+ return;
+
+ InsertDeleteFlags nDelFlag = rCxt.getDeleteFlag();
+
+ if (!rCxt.isSkipEmptyCells())
+ {
+ // Delete the whole destination range.
+
+ if (nDelFlag & InsertDeleteFlags::CONTENTS)
+ {
+ auto xResult = DeleteCells(*pBlockPos, aRange.mnRow1, aRange.mnRow2, nDelFlag);
+ rBroadcastSpans.set(GetDoc(), nTab, nCol, xResult->aDeletedRows, true);
+
+ for (const auto& rRange : xResult->aFormulaRanges)
+ rCxt.setListeningFormulaSpans(
+ nTab, nCol, rRange.first, nCol, rRange.second);
+ }
+
+ if (nDelFlag & InsertDeleteFlags::NOTE)
+ DeleteCellNotes(*pBlockPos, aRange.mnRow1, aRange.mnRow2, false);
+
+ if (nDelFlag & InsertDeleteFlags::SPARKLINES)
+ DeleteSparklineCells(*pBlockPos, aRange.mnRow1, aRange.mnRow2);
+
+ if (nDelFlag & InsertDeleteFlags::EDITATTR)
+ RemoveEditAttribs(*pBlockPos, aRange.mnRow1, aRange.mnRow2);
+
+ if (nDelFlag & InsertDeleteFlags::ATTRIB)
+ {
+ pAttrArray->DeleteArea(aRange.mnRow1, aRange.mnRow2);
+
+ if (rCxt.isTableProtected())
+ {
+ ScPatternAttr aPattern(rDocument.GetPool());
+ aPattern.GetItemSet().Put(ScProtectionAttr(false));
+ ApplyPatternArea(aRange.mnRow1, aRange.mnRow2, aPattern);
+ }
+
+ ScConditionalFormatList* pCondList = rCxt.getCondFormatList();
+ if (pCondList)
+ pCondList->DeleteArea(nCol, aRange.mnRow1, nCol, aRange.mnRow2);
+ }
+ else if ((nDelFlag & InsertDeleteFlags::HARDATTR) == InsertDeleteFlags::HARDATTR)
+ pAttrArray->DeleteHardAttr(aRange.mnRow1, aRange.mnRow2);
+
+ return;
+ }
+
+ ScRange aClipRange = rCxt.getClipDoc()->GetClipParam().getWholeRange();
+ SCROW nClipRow1 = aClipRange.aStart.Row();
+ SCROW nClipRow2 = aClipRange.aEnd.Row();
+ SCROW nClipRowLen = nClipRow2 - nClipRow1 + 1;
+
+ // Check for non-empty cell ranges in the clip column.
+ sc::SingleColumnSpanSet aSpanSet(GetDoc().GetSheetLimits());
+ aSpanSet.scan(rClipCol, nClipRow1, nClipRow2);
+ sc::SingleColumnSpanSet::SpansType aSpans;
+ aSpanSet.getSpans(aSpans);
+
+ if (aSpans.empty())
+ // All cells in the range in the clip are empty. Nothing to delete.
+ return;
+
+ // Translate the clip column spans into the destination column, and repeat as needed.
+ std::vector<sc::RowSpan> aDestSpans;
+ SCROW nDestOffset = aRange.mnRow1 - nClipRow1;
+ bool bContinue = true;
+ while (bContinue)
+ {
+ for (const sc::RowSpan& r : aSpans)
+ {
+ SCROW nDestRow1 = r.mnRow1 + nDestOffset;
+ SCROW nDestRow2 = r.mnRow2 + nDestOffset;
+
+ if (nDestRow1 > aRange.mnRow2)
+ {
+ // We're done.
+ bContinue = false;
+ break;
+ }
+
+ if (nDestRow2 > aRange.mnRow2)
+ {
+ // Truncate this range, and set it as the last span.
+ nDestRow2 = aRange.mnRow2;
+ bContinue = false;
+ }
+
+ aDestSpans.emplace_back(nDestRow1, nDestRow2);
+
+ if (!bContinue)
+ break;
+ }
+
+ nDestOffset += nClipRowLen;
+ }
+
+ for (const auto& rDestSpan : aDestSpans)
+ {
+ SCROW nRow1 = rDestSpan.mnRow1;
+ SCROW nRow2 = rDestSpan.mnRow2;
+
+ if (nDelFlag & InsertDeleteFlags::CONTENTS)
+ {
+ auto xResult = DeleteCells(*pBlockPos, nRow1, nRow2, nDelFlag);
+ rBroadcastSpans.set(GetDoc(), nTab, nCol, xResult->aDeletedRows, true);
+
+ for (const auto& rRange : xResult->aFormulaRanges)
+ rCxt.setListeningFormulaSpans(
+ nTab, nCol, rRange.first, nCol, rRange.second);
+ }
+
+ if (nDelFlag & InsertDeleteFlags::NOTE)
+ DeleteCellNotes(*pBlockPos, nRow1, nRow2, false);
+
+ if (nDelFlag & InsertDeleteFlags::SPARKLINES)
+ DeleteSparklineCells(*pBlockPos, nRow1, nRow2);
+
+ if (nDelFlag & InsertDeleteFlags::EDITATTR)
+ RemoveEditAttribs(*pBlockPos, nRow1, nRow2);
+
+ // Delete attributes just now
+ if (nDelFlag & InsertDeleteFlags::ATTRIB)
+ {
+ pAttrArray->DeleteArea(nRow1, nRow2);
+
+ if (rCxt.isTableProtected())
+ {
+ ScPatternAttr aPattern(rDocument.GetPool());
+ aPattern.GetItemSet().Put(ScProtectionAttr(false));
+ ApplyPatternArea(nRow1, nRow2, aPattern);
+ }
+
+ ScConditionalFormatList* pCondList = rCxt.getCondFormatList();
+ if (pCondList)
+ pCondList->DeleteArea(nCol, nRow1, nCol, nRow2);
+ }
+ else if ((nDelFlag & InsertDeleteFlags::HARDATTR) == InsertDeleteFlags::HARDATTR)
+ pAttrArray->DeleteHardAttr(nRow1, nRow2);
+ }
+}
+
+void ScColumn::CopyOneCellFromClip( sc::CopyFromClipContext& rCxt, SCROW nRow1, SCROW nRow2, size_t nColOffset )
+{
+ assert(nRow1 <= nRow2);
+
+ size_t nDestSize = nRow2 - nRow1 + 1;
+ sc::ColumnBlockPosition* pBlockPos = rCxt.getBlockPosition(nTab, nCol);
+ if (!pBlockPos)
+ return;
+
+ ScDocument& rDocument = GetDoc();
+ bool bSameDocPool = (rCxt.getClipDoc()->GetPool() == rDocument.GetPool());
+
+ ScCellValue& rSrcCell = rCxt.getSingleCell(nColOffset);
+ sc::CellTextAttr& rSrcAttr = rCxt.getSingleCellAttr(nColOffset);
+
+ InsertDeleteFlags nFlags = rCxt.getInsertFlag();
+
+ if ((nFlags & InsertDeleteFlags::ATTRIB) != InsertDeleteFlags::NONE)
+ {
+ if (!rCxt.isSkipEmptyCells() || rSrcCell.meType != CELLTYPE_NONE)
+ {
+ const ScPatternAttr* pAttr = (bSameDocPool ? rCxt.getSingleCellPattern(nColOffset) :
+ rCxt.getSingleCellPattern(nColOffset)->PutInPool( &rDocument, rCxt.getClipDoc()));
+
+ auto pNewPattern = std::make_unique<ScPatternAttr>(*pAttr);
+ sal_uInt16 pItems[2];
+ pItems[0] = ATTR_CONDITIONAL;
+ pItems[1] = 0;
+ pNewPattern->ClearItems(pItems);
+ pAttrArray->SetPatternArea(nRow1, nRow2, std::move(pNewPattern), true);
+ }
+ }
+
+ if ((nFlags & InsertDeleteFlags::CONTENTS) != InsertDeleteFlags::NONE)
+ {
+ std::vector<sc::CellTextAttr> aTextAttrs(nDestSize, rSrcAttr);
+
+ switch (rSrcCell.meType)
+ {
+ case CELLTYPE_VALUE:
+ {
+ std::vector<double> aVals(nDestSize, rSrcCell.mfValue);
+ pBlockPos->miCellPos =
+ maCells.set(pBlockPos->miCellPos, nRow1, aVals.begin(), aVals.end());
+ pBlockPos->miCellTextAttrPos =
+ maCellTextAttrs.set(pBlockPos->miCellTextAttrPos, nRow1, aTextAttrs.begin(), aTextAttrs.end());
+ CellStorageModified();
+ }
+ break;
+ case CELLTYPE_STRING:
+ {
+ // Compare the ScDocumentPool* to determine if we are copying within the
+ // same document. If not, re-intern shared strings.
+ svl::SharedStringPool* pSharedStringPool = (bSameDocPool ? nullptr : &rDocument.GetSharedStringPool());
+ svl::SharedString aStr = (pSharedStringPool ?
+ pSharedStringPool->intern( rSrcCell.mpString->getString()) :
+ *rSrcCell.mpString);
+
+ std::vector<svl::SharedString> aStrs(nDestSize, aStr);
+ pBlockPos->miCellPos =
+ maCells.set(pBlockPos->miCellPos, nRow1, aStrs.begin(), aStrs.end());
+ pBlockPos->miCellTextAttrPos =
+ maCellTextAttrs.set(pBlockPos->miCellTextAttrPos, nRow1, aTextAttrs.begin(), aTextAttrs.end());
+ CellStorageModified();
+ }
+ break;
+ case CELLTYPE_EDIT:
+ {
+ std::vector<EditTextObject*> aStrs;
+ aStrs.reserve(nDestSize);
+ for (size_t i = 0; i < nDestSize; ++i)
+ aStrs.push_back(rSrcCell.mpEditText->Clone().release());
+
+ pBlockPos->miCellPos =
+ maCells.set(pBlockPos->miCellPos, nRow1, aStrs.begin(), aStrs.end());
+ pBlockPos->miCellTextAttrPos =
+ maCellTextAttrs.set(pBlockPos->miCellTextAttrPos, nRow1, aTextAttrs.begin(), aTextAttrs.end());
+ CellStorageModified();
+ }
+ break;
+ case CELLTYPE_FORMULA:
+ {
+ std::vector<sc::RowSpan> aRanges;
+ aRanges.reserve(1);
+ aRanges.emplace_back(nRow1, nRow2);
+ CloneFormulaCell(*pBlockPos, *rSrcCell.mpFormula, rSrcAttr, aRanges);
+ }
+ break;
+ default:
+ ;
+ }
+ }
+
+ ScAddress aDestPosition(nCol, nRow1, nTab);
+
+ duplicateSparkline(rCxt, pBlockPos, nColOffset, nDestSize, aDestPosition);
+
+ // Notes
+ const ScPostIt* pNote = rCxt.getSingleCellNote(nColOffset);
+ if (!(pNote && (nFlags & (InsertDeleteFlags::NOTE | InsertDeleteFlags::ADDNOTES)) != InsertDeleteFlags::NONE))
+ return;
+
+ // Duplicate the cell note over the whole pasted range.
+
+ ScDocument* pClipDoc = rCxt.getClipDoc();
+ const ScAddress aSrcPos = pClipDoc->GetClipParam().getWholeRange().aStart;
+ std::vector<ScPostIt*> aNotes;
+ aNotes.reserve(nDestSize);
+ for (size_t i = 0; i < nDestSize; ++i)
+ {
+ bool bCloneCaption = (nFlags & InsertDeleteFlags::NOCAPTIONS) == InsertDeleteFlags::NONE;
+ aNotes.push_back(pNote->Clone(aSrcPos, rDocument, aDestPosition, bCloneCaption).release());
+ aDestPosition.IncRow();
+ }
+
+ pBlockPos->miCellNotePos =
+ maCellNotes.set(
+ pBlockPos->miCellNotePos, nRow1, aNotes.begin(), aNotes.end());
+
+ // Notify our LOK clients.
+ aDestPosition.SetRow(nRow1);
+ for (size_t i = 0; i < nDestSize; ++i)
+ {
+ ScDocShell::LOKCommentNotify(LOKCommentNotificationType::Add, &rDocument, aDestPosition, aNotes[i]);
+ aDestPosition.IncRow();
+ }
+}
+
+void ScColumn::duplicateSparkline(sc::CopyFromClipContext& rContext, sc::ColumnBlockPosition* pBlockPos,
+ size_t nColOffset, size_t nDestSize, ScAddress aDestPosition)
+{
+ if ((rContext.getInsertFlag() & InsertDeleteFlags::SPARKLINES) == InsertDeleteFlags::NONE)
+ return;
+
+ auto pSparkline = rContext.getSingleSparkline(nColOffset);
+ if (pSparkline)
+ {
+ auto const& pSparklineGroup = pSparkline->getSparklineGroup();
+
+ auto pDuplicatedGroup = GetDoc().SearchSparklineGroup(pSparklineGroup->getID());
+ if (!pDuplicatedGroup)
+ pDuplicatedGroup = std::make_shared<sc::SparklineGroup>(*pSparklineGroup);
+
+ std::vector<sc::SparklineCell*> aSparklines(nDestSize, nullptr);
+ ScAddress aCurrentPosition = aDestPosition;
+ for (size_t i = 0; i < nDestSize; ++i)
+ {
+ auto pNewSparkline = std::make_shared<sc::Sparkline>(aCurrentPosition.Col(), aCurrentPosition.Row(), pDuplicatedGroup);
+ pNewSparkline->setInputRange(pSparkline->getInputRange());
+ aSparklines[i] = new sc::SparklineCell(pNewSparkline);
+ aCurrentPosition.IncRow();
+ }
+
+ pBlockPos->miSparklinePos = maSparklines.set(pBlockPos->miSparklinePos, aDestPosition.Row(), aSparklines.begin(), aSparklines.end());
+ }
+}
+
+void ScColumn::SetValues( const SCROW nRow, const std::vector<double>& rVals )
+{
+ if (!GetDoc().ValidRow(nRow))
+ return;
+
+ SCROW nLastRow = nRow + rVals.size() - 1;
+ if (nLastRow > GetDoc().MaxRow())
+ // Out of bound. Do nothing.
+ return;
+
+ sc::CellStoreType::position_type aPos = maCells.position(nRow);
+ std::vector<SCROW> aNewSharedRows;
+ DetachFormulaCells(aPos, rVals.size(), &aNewSharedRows);
+
+ maCells.set(nRow, rVals.begin(), rVals.end());
+ std::vector<sc::CellTextAttr> aDefaults(rVals.size());
+ maCellTextAttrs.set(nRow, aDefaults.begin(), aDefaults.end());
+
+ CellStorageModified();
+
+ StartListeningUnshared( aNewSharedRows);
+
+ std::vector<SCROW> aRows;
+ aRows.reserve(rVals.size());
+ for (SCROW i = nRow; i <= nLastRow; ++i)
+ aRows.push_back(i);
+
+ BroadcastCells(aRows, SfxHintId::ScDataChanged);
+}
+
+void ScColumn::TransferCellValuesTo( SCROW nRow, size_t nLen, sc::CellValues& rDest )
+{
+ if (!GetDoc().ValidRow(nRow))
+ return;
+
+ SCROW nLastRow = nRow + nLen - 1;
+ if (nLastRow > GetDoc().MaxRow())
+ // Out of bound. Do nothing.
+ return;
+
+ sc::CellStoreType::position_type aPos = maCells.position(nRow);
+ DetachFormulaCells(aPos, nLen, nullptr);
+
+ rDest.transferFrom(*this, nRow, nLen);
+
+ CellStorageModified();
+
+ std::vector<SCROW> aRows;
+ aRows.reserve(nLen);
+ for (SCROW i = nRow; i <= nLastRow; ++i)
+ aRows.push_back(i);
+
+ BroadcastCells(aRows, SfxHintId::ScDataChanged);
+}
+
+void ScColumn::CopyCellValuesFrom( SCROW nRow, const sc::CellValues& rSrc )
+{
+ if (!GetDoc().ValidRow(nRow))
+ return;
+
+ SCROW nLastRow = nRow + rSrc.size() - 1;
+ if (nLastRow > GetDoc().MaxRow())
+ // Out of bound. Do nothing
+ return;
+
+ sc::CellStoreType::position_type aPos = maCells.position(nRow);
+ DetachFormulaCells(aPos, rSrc.size(), nullptr);
+
+ rSrc.copyTo(*this, nRow);
+
+ CellStorageModified();
+
+ std::vector<SCROW> aRows;
+ aRows.reserve(rSrc.size());
+ for (SCROW i = nRow; i <= nLastRow; ++i)
+ aRows.push_back(i);
+
+ BroadcastCells(aRows, SfxHintId::ScDataChanged);
+}
+
+namespace {
+
+class ConvertFormulaToValueHandler
+{
+ sc::CellValues maResValues;
+ bool mbModified;
+
+public:
+ ConvertFormulaToValueHandler(ScSheetLimits const & rSheetLimits) :
+ mbModified(false)
+ {
+ maResValues.reset(rSheetLimits.GetMaxRowCount());
+ }
+
+ void operator() ( size_t nRow, const ScFormulaCell* pCell )
+ {
+ sc::FormulaResultValue aRes = pCell->GetResult();
+ switch (aRes.meType)
+ {
+ case sc::FormulaResultValue::Value:
+ maResValues.setValue(nRow, aRes.mfValue);
+ break;
+ case sc::FormulaResultValue::String:
+ maResValues.setValue(nRow, aRes.maString);
+ break;
+ case sc::FormulaResultValue::Error:
+ case sc::FormulaResultValue::Invalid:
+ default:
+ maResValues.setValue(nRow, svl::SharedString::getEmptyString());
+ }
+
+ mbModified = true;
+ }
+
+ bool isModified() const { return mbModified; }
+
+ sc::CellValues& getResValues() { return maResValues; }
+};
+
+}
+
+void ScColumn::ConvertFormulaToValue(
+ sc::EndListeningContext& rCxt, SCROW nRow1, SCROW nRow2, sc::TableValues* pUndo )
+{
+ if (!GetDoc().ValidRow(nRow1) || !GetDoc().ValidRow(nRow2) || nRow1 > nRow2)
+ return;
+
+ std::vector<SCROW> aBounds { nRow1 };
+ if (nRow2 < GetDoc().MaxRow()-1)
+ aBounds.push_back(nRow2+1);
+
+ // Split formula cell groups at top and bottom boundaries (if applicable).
+ sc::SharedFormulaUtil::splitFormulaCellGroups(GetDoc(), maCells, aBounds);
+
+ // Parse all formulas within the range and store their results into temporary storage.
+ ConvertFormulaToValueHandler aFunc(GetDoc().GetSheetLimits());
+ sc::ParseFormula(maCells.begin(), maCells, nRow1, nRow2, aFunc);
+ if (!aFunc.isModified())
+ // No formula cells encountered.
+ return;
+
+ DetachFormulaCells(rCxt, nRow1, nRow2);
+
+ // Undo storage to hold static values which will get swapped to the cell storage later.
+ sc::CellValues aUndoCells;
+ aFunc.getResValues().swap(aUndoCells);
+ aUndoCells.swapNonEmpty(*this);
+ if (pUndo)
+ pUndo->swap(nTab, nCol, aUndoCells);
+}
+
+namespace {
+
+class StartListeningHandler
+{
+ sc::StartListeningContext& mrCxt;
+
+public:
+ explicit StartListeningHandler( sc::StartListeningContext& rCxt ) :
+ mrCxt(rCxt) {}
+
+ void operator() (size_t /*nRow*/, ScFormulaCell* pCell)
+ {
+ pCell->StartListeningTo(mrCxt);
+ }
+};
+
+class EndListeningHandler
+{
+ sc::EndListeningContext& mrCxt;
+
+public:
+ explicit EndListeningHandler( sc::EndListeningContext& rCxt ) :
+ mrCxt(rCxt) {}
+
+ void operator() (size_t /*nRow*/, ScFormulaCell* pCell)
+ {
+ pCell->EndListeningTo(mrCxt);
+ }
+};
+
+}
+
+void ScColumn::SwapNonEmpty(
+ sc::TableValues& rValues, sc::StartListeningContext& rStartCxt, sc::EndListeningContext& rEndCxt )
+{
+ const ScRange& rRange = rValues.getRange();
+ std::vector<SCROW> aBounds { rRange.aStart.Row() };
+ if (rRange.aEnd.Row() < GetDoc().MaxRow()-1)
+ aBounds.push_back(rRange.aEnd.Row()+1);
+
+ // Split formula cell groups at top and bottom boundaries (if applicable).
+ sc::SharedFormulaUtil::splitFormulaCellGroups(GetDoc(), maCells, aBounds);
+ std::vector<sc::CellValueSpan> aSpans = rValues.getNonEmptySpans(nTab, nCol);
+
+ // Detach formula cells within the spans (if any).
+ EndListeningHandler aEndLisFunc(rEndCxt);
+ sc::CellStoreType::iterator itPos = maCells.begin();
+ for (const auto& rSpan : aSpans)
+ {
+ SCROW nRow1 = rSpan.mnRow1;
+ SCROW nRow2 = rSpan.mnRow2;
+ itPos = sc::ProcessFormula(itPos, maCells, nRow1, nRow2, aEndLisFunc);
+ }
+
+ rValues.swapNonEmpty(nTab, nCol, *this);
+ RegroupFormulaCells();
+
+ // Attach formula cells within the spans (if any).
+ StartListeningHandler aStartLisFunc(rStartCxt);
+ itPos = maCells.begin();
+ for (const auto& rSpan : aSpans)
+ {
+ SCROW nRow1 = rSpan.mnRow1;
+ SCROW nRow2 = rSpan.mnRow2;
+ itPos = sc::ProcessFormula(itPos, maCells, nRow1, nRow2, aStartLisFunc);
+ }
+
+ CellStorageModified();
+}
+
+void ScColumn::DeleteRanges( const std::vector<sc::RowSpan>& rRanges, InsertDeleteFlags nDelFlag )
+{
+ for (const auto& rSpan : rRanges)
+ DeleteArea(rSpan.mnRow1, rSpan.mnRow2, nDelFlag, false/*bBroadcast*/);
+}
+
+void ScColumn::CloneFormulaCell(
+ sc::ColumnBlockPosition& rBlockPos,
+ const ScFormulaCell& rSrc, const sc::CellTextAttr& rAttr,
+ const std::vector<sc::RowSpan>& rRanges )
+{
+ SCCOL nMatrixCols = 0;
+ SCROW nMatrixRows = 0;
+ ScMatrixMode nMatrixFlag = rSrc.GetMatrixFlag();
+ if (nMatrixFlag == ScMatrixMode::Formula)
+ {
+ rSrc.GetMatColsRows( nMatrixCols, nMatrixRows);
+ SAL_WARN_IF( nMatrixCols != 1 || nMatrixRows != 1, "sc.core",
+ "ScColumn::CloneFormulaCell - cloning array/matrix with not exactly one column or row as single cell");
+ }
+
+ ScDocument& rDocument = GetDoc();
+ std::vector<ScFormulaCell*> aFormulas;
+ for (const auto& rSpan : rRanges)
+ {
+ SCROW nRow1 = rSpan.mnRow1, nRow2 = rSpan.mnRow2;
+ size_t nLen = nRow2 - nRow1 + 1;
+ assert(nLen > 0);
+ aFormulas.clear();
+ aFormulas.reserve(nLen);
+
+ ScAddress aPos(nCol, nRow1, nTab);
+
+ if (nLen == 1 || !rSrc.GetCode()->IsShareable())
+ {
+ // Single, ungrouped formula cell, or create copies for
+ // non-shareable token arrays.
+ for (size_t i = 0; i < nLen; ++i, aPos.IncRow())
+ {
+ ScFormulaCell* pCell = new ScFormulaCell(rSrc, rDocument, aPos);
+ aFormulas.push_back(pCell);
+ }
+ }
+ else
+ {
+ // Create a group of formula cells.
+ ScFormulaCellGroupRef xGroup(new ScFormulaCellGroup);
+ xGroup->setCode(*rSrc.GetCode());
+ xGroup->compileCode(rDocument, aPos, rDocument.GetGrammar());
+ for (size_t i = 0; i < nLen; ++i, aPos.IncRow())
+ {
+ ScFormulaCell* pCell = new ScFormulaCell(rDocument, aPos, xGroup, rDocument.GetGrammar(), nMatrixFlag);
+ if (nMatrixFlag == ScMatrixMode::Formula)
+ pCell->SetMatColsRows( nMatrixCols, nMatrixRows);
+ if (i == 0)
+ {
+ xGroup->mpTopCell = pCell;
+ xGroup->mnLength = nLen;
+ }
+ aFormulas.push_back(pCell);
+ }
+ }
+
+ rBlockPos.miCellPos = maCells.set(rBlockPos.miCellPos, nRow1, aFormulas.begin(), aFormulas.end());
+
+ // Join the top and bottom of the pasted formula cells as needed.
+ sc::CellStoreType::position_type aPosObj = maCells.position(rBlockPos.miCellPos, nRow1);
+
+ assert(aPosObj.first->type == sc::element_type_formula);
+ ScFormulaCell* pCell = sc::formula_block::at(*aPosObj.first->data, aPosObj.second);
+ JoinNewFormulaCell(aPosObj, *pCell);
+
+ aPosObj = maCells.position(aPosObj.first, nRow2);
+ assert(aPosObj.first->type == sc::element_type_formula);
+ pCell = sc::formula_block::at(*aPosObj.first->data, aPosObj.second);
+ JoinNewFormulaCell(aPosObj, *pCell);
+
+ std::vector<sc::CellTextAttr> aTextAttrs(nLen, rAttr);
+ rBlockPos.miCellTextAttrPos = maCellTextAttrs.set(
+ rBlockPos.miCellTextAttrPos, nRow1, aTextAttrs.begin(), aTextAttrs.end());
+ }
+
+ CellStorageModified();
+}
+
+void ScColumn::CloneFormulaCell(
+ const ScFormulaCell& rSrc, const sc::CellTextAttr& rAttr,
+ const std::vector<sc::RowSpan>& rRanges )
+{
+ sc::ColumnBlockPosition aBlockPos;
+ InitBlockPosition(aBlockPos);
+ CloneFormulaCell(aBlockPos, rSrc, rAttr, rRanges);
+}
+
+std::unique_ptr<ScPostIt> ScColumn::ReleaseNote( SCROW nRow )
+{
+ if (!GetDoc().ValidRow(nRow))
+ return nullptr;
+
+ ScPostIt* p = nullptr;
+ maCellNotes.release(nRow, p);
+ return std::unique_ptr<ScPostIt>(p);
+}
+
+size_t ScColumn::GetNoteCount() const
+{
+ return std::accumulate(maCellNotes.begin(), maCellNotes.end(), size_t(0),
+ [](const size_t& rCount, const auto& rCellNote) {
+ if (rCellNote.type != sc::element_type_cellnote)
+ return rCount;
+ return rCount + rCellNote.size;
+ });
+}
+
+namespace {
+
+class NoteCaptionCreator
+{
+ ScAddress maPos;
+public:
+ NoteCaptionCreator( SCTAB nTab, SCCOL nCol ) : maPos(nCol,0,nTab) {}
+
+ void operator() ( size_t nRow, const ScPostIt* p )
+ {
+ maPos.SetRow(nRow);
+ p->GetOrCreateCaption(maPos);
+ }
+};
+
+class NoteCaptionCleaner
+{
+ bool mbPreserveData;
+public:
+ explicit NoteCaptionCleaner( bool bPreserveData ) : mbPreserveData(bPreserveData) {}
+
+ void operator() ( size_t /*nRow*/, ScPostIt* p )
+ {
+ p->ForgetCaption(mbPreserveData);
+ }
+};
+
+}
+
+void ScColumn::CreateAllNoteCaptions()
+{
+ NoteCaptionCreator aFunc(nTab, nCol);
+ sc::ProcessNote(maCellNotes, aFunc);
+}
+
+void ScColumn::ForgetNoteCaptions( SCROW nRow1, SCROW nRow2, bool bPreserveData )
+{
+ if (maCellNotes.empty())
+ return;
+
+ if (!GetDoc().ValidRow(nRow1) || !GetDoc().ValidRow(nRow2))
+ return;
+
+ NoteCaptionCleaner aFunc(bPreserveData);
+ sc::CellNoteStoreType::iterator it = maCellNotes.begin();
+ sc::ProcessNote(it, maCellNotes, nRow1, nRow2, aFunc);
+}
+
+SCROW ScColumn::GetNotePosition( size_t nIndex ) const
+{
+ // Return the row position of the nth note in the column.
+
+ size_t nCount = 0; // Number of notes encountered so far.
+ for (const auto& rCellNote : maCellNotes)
+ {
+ if (rCellNote.type != sc::element_type_cellnote)
+ // Skip the empty blocks.
+ continue;
+
+ if (nIndex < nCount + rCellNote.size)
+ {
+ // Index falls within this block.
+ size_t nOffset = nIndex - nCount;
+ return rCellNote.position + nOffset;
+ }
+
+ nCount += rCellNote.size;
+ }
+
+ return -1;
+}
+
+namespace {
+
+class NoteEntryCollector
+{
+ std::vector<sc::NoteEntry>& mrNotes;
+ SCTAB mnTab;
+ SCCOL mnCol;
+ SCROW mnStartRow;
+ SCROW mnEndRow;
+public:
+ NoteEntryCollector( std::vector<sc::NoteEntry>& rNotes, SCTAB nTab, SCCOL nCol,
+ SCROW nStartRow, SCROW nEndRow) :
+ mrNotes(rNotes), mnTab(nTab), mnCol(nCol),
+ mnStartRow(nStartRow), mnEndRow(nEndRow) {}
+
+ void operator() (const sc::CellNoteStoreType::value_type& node) const
+ {
+ if (node.type != sc::element_type_cellnote)
+ return;
+
+ size_t nTopRow = node.position;
+ sc::cellnote_block::const_iterator it = sc::cellnote_block::begin(*node.data);
+ sc::cellnote_block::const_iterator itEnd = sc::cellnote_block::end(*node.data);
+ size_t nOffset = 0;
+ if(nTopRow < o3tl::make_unsigned(mnStartRow))
+ {
+ std::advance(it, mnStartRow - nTopRow);
+ nOffset = mnStartRow - nTopRow;
+ }
+
+ for (; it != itEnd && nTopRow + nOffset <= o3tl::make_unsigned(mnEndRow);
+ ++it, ++nOffset)
+ {
+ ScAddress aPos(mnCol, nTopRow + nOffset, mnTab);
+ mrNotes.emplace_back(aPos, *it);
+ }
+ }
+};
+
+}
+
+void ScColumn::GetAllNoteEntries( std::vector<sc::NoteEntry>& rNotes ) const
+{
+ std::for_each(maCellNotes.begin(), maCellNotes.end(), NoteEntryCollector(rNotes, nTab, nCol, 0, GetDoc().MaxRow()));
+}
+
+void ScColumn::GetNotesInRange(SCROW nStartRow, SCROW nEndRow,
+ std::vector<sc::NoteEntry>& rNotes ) const
+{
+ std::pair<sc::CellNoteStoreType::const_iterator,size_t> aPos = maCellNotes.position(nStartRow);
+ sc::CellNoteStoreType::const_iterator it = aPos.first;
+ if (it == maCellNotes.end())
+ // Invalid row number.
+ return;
+
+ std::pair<sc::CellNoteStoreType::const_iterator,size_t> aEndPos =
+ maCellNotes.position(nEndRow);
+ sc::CellNoteStoreType::const_iterator itEnd = aEndPos.first;
+
+ std::for_each(it, ++itEnd, NoteEntryCollector(rNotes, nTab, nCol, nStartRow, nEndRow));
+}
+
+bool ScColumn::HasCellNote(SCROW nStartRow, SCROW nEndRow) const
+{
+ std::pair<sc::CellNoteStoreType::const_iterator,size_t> aStartPos =
+ maCellNotes.position(nStartRow);
+ if (aStartPos.first == maCellNotes.end())
+ // Invalid row number.
+ return false;
+
+ std::pair<sc::CellNoteStoreType::const_iterator,size_t> aEndPos =
+ maCellNotes.position(nEndRow);
+
+ for (sc::CellNoteStoreType::const_iterator it = aStartPos.first; it != aEndPos.first; ++it)
+ {
+ if (it->type != sc::element_type_cellnote)
+ continue;
+ size_t nTopRow = it->position;
+ sc::cellnote_block::const_iterator blockIt = sc::cellnote_block::begin(*(it->data));
+ sc::cellnote_block::const_iterator blockItEnd = sc::cellnote_block::end(*(it->data));
+ size_t nOffset = 0;
+ if(nTopRow < o3tl::make_unsigned(nStartRow))
+ {
+ std::advance(blockIt, nStartRow - nTopRow);
+ nOffset = nStartRow - nTopRow;
+ }
+
+ if (blockIt != blockItEnd && nTopRow + nOffset <= o3tl::make_unsigned(nEndRow))
+ return true;
+ }
+
+ return false;
+}
+
+void ScColumn::GetUnprotectedCells( SCROW nStartRow, SCROW nEndRow, ScRangeList& rRangeList ) const
+{
+ SCROW nTmpStartRow = nStartRow, nTmpEndRow = nEndRow;
+ const ScPatternAttr* pPattern = pAttrArray->GetPatternRange(nTmpStartRow, nTmpEndRow, nStartRow);
+ bool bProtection = pPattern->GetItem(ATTR_PROTECTION).GetProtection();
+ if (!bProtection)
+ {
+ // Limit the span to the range in question.
+ if (nTmpStartRow < nStartRow)
+ nTmpStartRow = nStartRow;
+ if (nTmpEndRow > nEndRow)
+ nTmpEndRow = nEndRow;
+ rRangeList.Join( ScRange( nCol, nTmpStartRow, nTab, nCol, nTmpEndRow, nTab));
+ }
+ while (nEndRow > nTmpEndRow)
+ {
+ nStartRow = nTmpEndRow + 1;
+ pPattern = pAttrArray->GetPatternRange(nTmpStartRow, nTmpEndRow, nStartRow);
+ bool bTmpProtection = pPattern->GetItem(ATTR_PROTECTION).GetProtection();
+ if (!bTmpProtection)
+ {
+ // Limit the span to the range in question.
+ // Only end row needs to be checked as we enter here only for spans
+ // below the original nStartRow.
+ if (nTmpEndRow > nEndRow)
+ nTmpEndRow = nEndRow;
+ rRangeList.Join( ScRange( nCol, nTmpStartRow, nTab, nCol, nTmpEndRow, nTab));
+ }
+ }
+}
+
+namespace {
+
+class RecompileByOpcodeHandler
+{
+ ScDocument* mpDoc;
+ const formula::unordered_opcode_set& mrOps;
+ sc::EndListeningContext& mrEndListenCxt;
+ sc::CompileFormulaContext& mrCompileFormulaCxt;
+
+public:
+ RecompileByOpcodeHandler(
+ ScDocument* pDoc, const formula::unordered_opcode_set& rOps,
+ sc::EndListeningContext& rEndListenCxt, sc::CompileFormulaContext& rCompileCxt ) :
+ mpDoc(pDoc),
+ mrOps(rOps),
+ mrEndListenCxt(rEndListenCxt),
+ mrCompileFormulaCxt(rCompileCxt) {}
+
+ void operator() ( sc::FormulaGroupEntry& rEntry )
+ {
+ // Perform end listening, remove from formula tree, and set them up
+ // for re-compilation.
+
+ ScFormulaCell* pTop = nullptr;
+
+ if (rEntry.mbShared)
+ {
+ // Only inspect the code from the top cell.
+ pTop = *rEntry.mpCells;
+ }
+ else
+ pTop = rEntry.mpCell;
+
+ ScTokenArray* pCode = pTop->GetCode();
+ bool bRecompile = pCode->HasOpCodes(mrOps);
+
+ if (!bRecompile)
+ return;
+
+ // Get the formula string.
+ OUString aFormula = pTop->GetFormula(mrCompileFormulaCxt);
+ sal_Int32 n = aFormula.getLength();
+ if (pTop->GetMatrixFlag() != ScMatrixMode::NONE && n > 0)
+ {
+ if (aFormula[0] == '{' && aFormula[n-1] == '}')
+ aFormula = aFormula.copy(1, n-2);
+ }
+
+ if (rEntry.mbShared)
+ {
+ ScFormulaCell** pp = rEntry.mpCells;
+ ScFormulaCell** ppEnd = pp + rEntry.mnLength;
+ for (; pp != ppEnd; ++pp)
+ {
+ ScFormulaCell* p = *pp;
+ p->EndListeningTo(mrEndListenCxt);
+ mpDoc->RemoveFromFormulaTree(p);
+ }
+ }
+ else
+ {
+ rEntry.mpCell->EndListeningTo(mrEndListenCxt);
+ mpDoc->RemoveFromFormulaTree(rEntry.mpCell);
+ }
+
+ pCode->Clear();
+ pTop->SetHybridFormula(aFormula, mpDoc->GetGrammar());
+ }
+};
+
+class CompileHybridFormulaHandler
+{
+ ScDocument& mrDoc;
+ sc::StartListeningContext& mrStartListenCxt;
+ sc::CompileFormulaContext& mrCompileFormulaCxt;
+
+public:
+ CompileHybridFormulaHandler(ScDocument& rDoc, sc::StartListeningContext& rStartListenCxt, sc::CompileFormulaContext& rCompileCxt ) :
+ mrDoc(rDoc),
+ mrStartListenCxt(rStartListenCxt),
+ mrCompileFormulaCxt(rCompileCxt) {}
+
+ void operator() ( sc::FormulaGroupEntry& rEntry )
+ {
+ if (rEntry.mbShared)
+ {
+ ScFormulaCell* pTop = *rEntry.mpCells;
+ OUString aFormula = pTop->GetHybridFormula();
+
+ if (!aFormula.isEmpty())
+ {
+ // Create a new token array from the hybrid formula string, and
+ // set it to the group.
+ ScCompiler aComp(mrCompileFormulaCxt, pTop->aPos);
+ std::unique_ptr<ScTokenArray> pNewCode = aComp.CompileString(aFormula);
+ ScFormulaCellGroupRef xGroup = pTop->GetCellGroup();
+ assert(xGroup);
+ xGroup->setCode(std::move(*pNewCode));
+ xGroup->compileCode(mrDoc, pTop->aPos, mrDoc.GetGrammar());
+
+ // Propagate the new token array to all formula cells in the group.
+ ScFormulaCell** pp = rEntry.mpCells;
+ ScFormulaCell** ppEnd = pp + rEntry.mnLength;
+ for (; pp != ppEnd; ++pp)
+ {
+ ScFormulaCell* p = *pp;
+ p->SyncSharedCode();
+ p->StartListeningTo(mrStartListenCxt);
+ p->SetDirty();
+ }
+ }
+ }
+ else
+ {
+ ScFormulaCell* pCell = rEntry.mpCell;
+ OUString aFormula = pCell->GetHybridFormula();
+
+ if (!aFormula.isEmpty())
+ {
+ // Create token array from formula string.
+ ScCompiler aComp(mrCompileFormulaCxt, pCell->aPos);
+ std::unique_ptr<ScTokenArray> pNewCode = aComp.CompileString(aFormula);
+
+ // Generate RPN tokens.
+ ScCompiler aComp2(mrDoc, pCell->aPos, *pNewCode, formula::FormulaGrammar::GRAM_UNSPECIFIED,
+ true, pCell->GetMatrixFlag() != ScMatrixMode::NONE);
+ aComp2.CompileTokenArray();
+
+ pCell->SetCode(std::move(pNewCode));
+ pCell->StartListeningTo(mrStartListenCxt);
+ pCell->SetDirty();
+ }
+ }
+ }
+};
+
+}
+
+void ScColumn::PreprocessRangeNameUpdate(
+ sc::EndListeningContext& rEndListenCxt, sc::CompileFormulaContext& rCompileCxt )
+{
+ // Collect all formula groups.
+ std::vector<sc::FormulaGroupEntry> aGroups = GetFormulaGroupEntries();
+
+ formula::unordered_opcode_set aOps;
+ aOps.insert(ocBad);
+ aOps.insert(ocColRowName);
+ aOps.insert(ocName);
+ RecompileByOpcodeHandler aFunc(&GetDoc(), aOps, rEndListenCxt, rCompileCxt);
+ std::for_each(aGroups.begin(), aGroups.end(), aFunc);
+}
+
+void ScColumn::PreprocessDBDataUpdate(
+ sc::EndListeningContext& rEndListenCxt, sc::CompileFormulaContext& rCompileCxt )
+{
+ // Collect all formula groups.
+ std::vector<sc::FormulaGroupEntry> aGroups = GetFormulaGroupEntries();
+
+ formula::unordered_opcode_set aOps;
+ aOps.insert(ocBad);
+ aOps.insert(ocColRowName);
+ aOps.insert(ocDBArea);
+ aOps.insert(ocTableRef);
+ RecompileByOpcodeHandler aFunc(&GetDoc(), aOps, rEndListenCxt, rCompileCxt);
+ std::for_each(aGroups.begin(), aGroups.end(), aFunc);
+}
+
+void ScColumn::CompileHybridFormula(
+ sc::StartListeningContext& rStartListenCxt, sc::CompileFormulaContext& rCompileCxt )
+{
+ // Collect all formula groups.
+ std::vector<sc::FormulaGroupEntry> aGroups = GetFormulaGroupEntries();
+
+ CompileHybridFormulaHandler aFunc(GetDoc(), rStartListenCxt, rCompileCxt);
+ std::for_each(aGroups.begin(), aGroups.end(), aFunc);
+}
+
+namespace {
+
+class ScriptTypeUpdater
+{
+ ScColumn& mrCol;
+ sc::CellTextAttrStoreType& mrTextAttrs;
+ sc::CellTextAttrStoreType::iterator miPosAttr;
+ ScConditionalFormatList* mpCFList;
+ SvNumberFormatter* mpFormatter;
+ ScAddress maPos;
+ bool mbUpdated;
+
+private:
+ void updateScriptType( size_t nRow, ScRefCellValue& rCell )
+ {
+ sc::CellTextAttrStoreType::position_type aAttrPos = mrTextAttrs.position(miPosAttr, nRow);
+ miPosAttr = aAttrPos.first;
+
+ if (aAttrPos.first->type != sc::element_type_celltextattr)
+ return;
+
+ sc::CellTextAttr& rAttr = sc::celltextattr_block::at(*aAttrPos.first->data, aAttrPos.second);
+ if (rAttr.mnScriptType != SvtScriptType::UNKNOWN)
+ // Script type already determined. Skip it.
+ return;
+
+ const ScPatternAttr* pPat = mrCol.GetPattern(nRow);
+ if (!pPat)
+ // In theory this should never return NULL. But let's be safe.
+ return;
+
+ const SfxItemSet* pCondSet = nullptr;
+ if (mpCFList)
+ {
+ maPos.SetRow(nRow);
+ const ScCondFormatItem& rItem = pPat->GetItem(ATTR_CONDITIONAL);
+ const ScCondFormatIndexes& rData = rItem.GetCondFormatData();
+ pCondSet = mrCol.GetDoc().GetCondResult(rCell, maPos, *mpCFList, rData);
+ }
+
+ const Color* pColor;
+ sal_uInt32 nFormat = pPat->GetNumberFormat(mpFormatter, pCondSet);
+ OUString aStr = ScCellFormat::GetString(rCell, nFormat, &pColor, *mpFormatter, mrCol.GetDoc());
+
+ rAttr.mnScriptType = mrCol.GetDoc().GetStringScriptType(aStr);
+ mbUpdated = true;
+ }
+
+public:
+ explicit ScriptTypeUpdater( ScColumn& rCol ) :
+ mrCol(rCol),
+ mrTextAttrs(rCol.GetCellAttrStore()),
+ miPosAttr(mrTextAttrs.begin()),
+ mpCFList(rCol.GetDoc().GetCondFormList(rCol.GetTab())),
+ mpFormatter(rCol.GetDoc().GetFormatTable()),
+ maPos(rCol.GetCol(), 0, rCol.GetTab()),
+ mbUpdated(false)
+ {}
+
+ void operator() ( size_t nRow, double fVal )
+ {
+ ScRefCellValue aCell(fVal);
+ updateScriptType(nRow, aCell);
+ }
+
+ void operator() ( size_t nRow, const svl::SharedString& rStr )
+ {
+ ScRefCellValue aCell(&rStr);
+ updateScriptType(nRow, aCell);
+ }
+
+ void operator() ( size_t nRow, const EditTextObject* pText )
+ {
+ ScRefCellValue aCell(pText);
+ updateScriptType(nRow, aCell);
+ }
+
+ void operator() ( size_t nRow, const ScFormulaCell* pCell )
+ {
+ ScRefCellValue aCell(const_cast<ScFormulaCell*>(pCell));
+ updateScriptType(nRow, aCell);
+ }
+
+ bool isUpdated() const { return mbUpdated; }
+};
+
+}
+
+void ScColumn::UpdateScriptTypes( SCROW nRow1, SCROW nRow2 )
+{
+ if (!GetDoc().ValidRow(nRow1) || !GetDoc().ValidRow(nRow2) || nRow1 > nRow2)
+ return;
+
+ ScriptTypeUpdater aFunc(*this);
+ sc::ParseAllNonEmpty(maCells.begin(), maCells, nRow1, nRow2, aFunc);
+ if (aFunc.isUpdated())
+ CellStorageModified();
+}
+
+void ScColumn::Swap( ScColumn& rOther, SCROW nRow1, SCROW nRow2, bool bPattern )
+{
+ maCells.swap(nRow1, nRow2, rOther.maCells, nRow1);
+ maCellTextAttrs.swap(nRow1, nRow2, rOther.maCellTextAttrs, nRow1);
+ maCellNotes.swap(nRow1, nRow2, rOther.maCellNotes, nRow1);
+ maBroadcasters.swap(nRow1, nRow2, rOther.maBroadcasters, nRow1);
+
+ // Update draw object anchors
+ ScDrawLayer* pDrawLayer = GetDoc().GetDrawLayer();
+ if (pDrawLayer)
+ {
+ std::map<SCROW, std::vector<SdrObject*>> aThisColRowDrawObjects
+ = pDrawLayer->GetObjectsAnchoredToRange(GetTab(), GetCol(), nRow1, nRow2);
+ std::map<SCROW, std::vector<SdrObject*>> aOtherColRowDrawObjects
+ = pDrawLayer->GetObjectsAnchoredToRange(GetTab(), rOther.GetCol(), nRow1, nRow2);
+ for (SCROW nRow = nRow1; nRow <= nRow2; ++nRow)
+ {
+ std::vector<SdrObject*>& rThisCellDrawObjects = aThisColRowDrawObjects[nRow];
+ if (!rThisCellDrawObjects.empty())
+ UpdateDrawObjectsForRow(rThisCellDrawObjects, rOther.GetCol(), nRow);
+ std::vector<SdrObject*>& rOtherCellDrawObjects = aOtherColRowDrawObjects[nRow];
+ if (!rOtherCellDrawObjects.empty())
+ rOther.UpdateDrawObjectsForRow(rOtherCellDrawObjects, GetCol(), nRow);
+ }
+ }
+
+ if (bPattern)
+ {
+ for (SCROW nRow = nRow1; nRow <= nRow2; ++nRow)
+ {
+ const ScPatternAttr* pPat1 = GetPattern(nRow);
+ const ScPatternAttr* pPat2 = rOther.GetPattern(nRow);
+ if (pPat1 != pPat2)
+ {
+ if (pPat1->GetRefCount() == 1)
+ pPat1 = &rOther.GetDoc().GetPool()->Put(*pPat1);
+ SetPattern(nRow, *pPat2);
+ rOther.SetPattern(nRow, *pPat1);
+ }
+ }
+ }
+
+ CellStorageModified();
+ rOther.CellStorageModified();
+}
+
+namespace {
+
+class FormulaColPosSetter
+{
+ SCCOL mnCol;
+ bool mbUpdateRefs;
+public:
+ FormulaColPosSetter( SCCOL nCol, bool bUpdateRefs ) : mnCol(nCol), mbUpdateRefs(bUpdateRefs) {}
+
+ void operator() ( size_t nRow, ScFormulaCell* pCell )
+ {
+ if (!pCell->IsShared() || pCell->IsSharedTop())
+ {
+ // Ensure that the references still point to the same locations
+ // after the position change.
+ ScAddress aOldPos = pCell->aPos;
+ pCell->aPos.SetCol(mnCol);
+ pCell->aPos.SetRow(nRow);
+ if (mbUpdateRefs)
+ pCell->GetCode()->AdjustReferenceOnMovedOrigin(aOldPos, pCell->aPos);
+ else
+ pCell->GetCode()->AdjustReferenceOnMovedOriginIfOtherSheet(aOldPos, pCell->aPos);
+ }
+ else
+ {
+ pCell->aPos.SetCol(mnCol);
+ pCell->aPos.SetRow(nRow);
+ }
+ }
+};
+
+}
+
+void ScColumn::ResetFormulaCellPositions( SCROW nRow1, SCROW nRow2, bool bUpdateRefs )
+{
+ FormulaColPosSetter aFunc(nCol, bUpdateRefs);
+ sc::ProcessFormula(maCells.begin(), maCells, nRow1, nRow2, aFunc);
+}
+
+namespace {
+
+class RelativeRefBoundChecker
+{
+ std::vector<SCROW> maBounds;
+ ScRange maBoundRange;
+
+public:
+ explicit RelativeRefBoundChecker( const ScRange& rBoundRange ) :
+ maBoundRange(rBoundRange) {}
+
+ void operator() ( size_t /*nRow*/, ScFormulaCell* pCell )
+ {
+ if (!pCell->IsSharedTop())
+ return;
+
+ pCell->GetCode()->CheckRelativeReferenceBounds(
+ pCell->aPos, pCell->GetSharedLength(), maBoundRange, maBounds);
+ }
+
+ void swapBounds( std::vector<SCROW>& rBounds )
+ {
+ rBounds.swap(maBounds);
+ }
+};
+
+}
+
+void ScColumn::SplitFormulaGroupByRelativeRef( const ScRange& rBoundRange )
+{
+ if (rBoundRange.aStart.Row() >= GetDoc().MaxRow())
+ // Nothing to split.
+ return;
+
+ std::vector<SCROW> aBounds;
+
+ // Cut at row boundaries first.
+ aBounds.push_back(rBoundRange.aStart.Row());
+ if (rBoundRange.aEnd.Row() < GetDoc().MaxRow())
+ aBounds.push_back(rBoundRange.aEnd.Row()+1);
+ sc::SharedFormulaUtil::splitFormulaCellGroups(GetDoc(), maCells, aBounds);
+
+ RelativeRefBoundChecker aFunc(rBoundRange);
+ sc::ProcessFormula(
+ maCells.begin(), maCells, rBoundRange.aStart.Row(), rBoundRange.aEnd.Row(), aFunc);
+ aFunc.swapBounds(aBounds);
+ sc::SharedFormulaUtil::splitFormulaCellGroups(GetDoc(), maCells, aBounds);
+}
+
+namespace {
+
+class ListenerCollector
+{
+ std::vector<SvtListener*>& mrListeners;
+public:
+ explicit ListenerCollector( std::vector<SvtListener*>& rListener ) :
+ mrListeners(rListener) {}
+
+ void operator() ( size_t /*nRow*/, SvtBroadcaster* p )
+ {
+ SvtBroadcaster::ListenersType& rLis = p->GetAllListeners();
+ mrListeners.insert(mrListeners.end(), rLis.begin(), rLis.end());
+ }
+};
+
+class FormulaCellCollector
+{
+ std::vector<ScFormulaCell*>& mrCells;
+public:
+ explicit FormulaCellCollector( std::vector<ScFormulaCell*>& rCells ) : mrCells(rCells) {}
+
+ void operator() ( size_t /*nRow*/, ScFormulaCell* p )
+ {
+ mrCells.push_back(p);
+ }
+};
+
+}
+
+void ScColumn::CollectListeners( std::vector<SvtListener*>& rListeners, SCROW nRow1, SCROW nRow2 )
+{
+ if (nRow2 < nRow1 || !GetDoc().ValidRow(nRow1) || !GetDoc().ValidRow(nRow2))
+ return;
+
+ ListenerCollector aFunc(rListeners);
+ sc::ProcessBroadcaster(maBroadcasters.begin(), maBroadcasters, nRow1, nRow2, aFunc);
+}
+
+void ScColumn::CollectFormulaCells( std::vector<ScFormulaCell*>& rCells, SCROW nRow1, SCROW nRow2 )
+{
+ if (nRow2 < nRow1 || !GetDoc().ValidRow(nRow1) || !GetDoc().ValidRow(nRow2))
+ return;
+
+ FormulaCellCollector aFunc(rCells);
+ sc::ProcessFormula(maCells.begin(), maCells, nRow1, nRow2, aFunc);
+}
+
+bool ScColumn::HasFormulaCell() const
+{
+ return mnBlkCountFormula != 0;
+}
+
+namespace {
+
+struct FindAnyFormula
+{
+ bool operator() ( size_t /*nRow*/, const ScFormulaCell* /*pCell*/ ) const
+ {
+ return true;
+ }
+};
+
+}
+
+bool ScColumn::HasFormulaCell( SCROW nRow1, SCROW nRow2 ) const
+{
+ if (!mnBlkCountFormula)
+ return false;
+
+ if (nRow2 < nRow1 || !GetDoc().ValidRow(nRow1) || !GetDoc().ValidRow(nRow2))
+ return false;
+
+ if (nRow1 == 0 && nRow2 == GetDoc().MaxRow())
+ return HasFormulaCell();
+
+ FindAnyFormula aFunc;
+ std::pair<sc::CellStoreType::const_iterator, size_t> aRet =
+ sc::FindFormula(maCells, nRow1, nRow2, aFunc);
+
+ return aRet.first != maCells.end();
+}
+
+namespace {
+
+void endListening( sc::EndListeningContext& rCxt, ScFormulaCell** pp, ScFormulaCell** ppEnd )
+{
+ for (; pp != ppEnd; ++pp)
+ {
+ ScFormulaCell& rFC = **pp;
+ rFC.EndListeningTo(rCxt);
+ }
+}
+
+class StartListeningFormulaCellsHandler
+{
+ sc::StartListeningContext& mrStartCxt;
+ sc::EndListeningContext& mrEndCxt;
+ SCROW mnStartRow;
+
+public:
+ StartListeningFormulaCellsHandler( sc::StartListeningContext& rStartCxt, sc::EndListeningContext& rEndCxt ) :
+ mrStartCxt(rStartCxt), mrEndCxt(rEndCxt), mnStartRow(-1) {}
+
+ void operator() ( const sc::CellStoreType::value_type& node, size_t nOffset, size_t nDataSize )
+ {
+ if (node.type != sc::element_type_formula)
+ // We are only interested in formulas.
+ return;
+
+ mnStartRow = node.position + nOffset;
+
+ ScFormulaCell** ppBeg = &sc::formula_block::at(*node.data, nOffset);
+ ScFormulaCell** ppEnd = ppBeg + nDataSize;
+
+ ScFormulaCell** pp = ppBeg;
+
+ // If the first formula cell belongs to a group and it's not the top
+ // cell, move up to the top cell of the group, and have all the extra
+ // formula cells stop listening.
+
+ ScFormulaCell* pFC = *pp;
+ if (pFC->IsShared() && !pFC->IsSharedTop())
+ {
+ SCROW nBackTrackSize = pFC->aPos.Row() - pFC->GetSharedTopRow();
+ if (nBackTrackSize > 0)
+ {
+ assert(o3tl::make_unsigned(nBackTrackSize) <= nOffset);
+ for (SCROW i = 0; i < nBackTrackSize; ++i)
+ --pp;
+ endListening(mrEndCxt, pp, ppBeg);
+ mnStartRow -= nBackTrackSize;
+ }
+ }
+
+ for (; pp != ppEnd; ++pp)
+ {
+ pFC = *pp;
+
+ if (!pFC->IsSharedTop())
+ {
+ assert(!pFC->IsShared());
+ pFC->StartListeningTo(mrStartCxt);
+ continue;
+ }
+
+ // If This is the last group in the range, see if the group
+ // extends beyond the range, in which case have the excess
+ // formula cells stop listening.
+ size_t nEndGroupPos = (pp - ppBeg) + pFC->GetSharedLength();
+ if (nEndGroupPos > nDataSize)
+ {
+ size_t nExcessSize = nEndGroupPos - nDataSize;
+ ScFormulaCell** ppGrpEnd = pp + pFC->GetSharedLength();
+ ScFormulaCell** ppGrp = ppGrpEnd - nExcessSize;
+ endListening(mrEndCxt, ppGrp, ppGrpEnd);
+
+ // Register formula cells as a group.
+ sc::SharedFormulaUtil::startListeningAsGroup(mrStartCxt, pp);
+ pp = ppEnd - 1; // Move to the one before the end position.
+ }
+ else
+ {
+ // Register formula cells as a group.
+ sc::SharedFormulaUtil::startListeningAsGroup(mrStartCxt, pp);
+ pp += pFC->GetSharedLength() - 1; // Move to the last one in the group.
+ }
+ }
+ }
+
+};
+
+class EndListeningFormulaCellsHandler
+{
+ sc::EndListeningContext& mrEndCxt;
+ SCROW mnStartRow;
+ SCROW mnEndRow;
+
+public:
+ explicit EndListeningFormulaCellsHandler( sc::EndListeningContext& rEndCxt ) :
+ mrEndCxt(rEndCxt), mnStartRow(-1), mnEndRow(-1) {}
+
+ void operator() ( const sc::CellStoreType::value_type& node, size_t nOffset, size_t nDataSize )
+ {
+ if (node.type != sc::element_type_formula)
+ // We are only interested in formulas.
+ return;
+
+ mnStartRow = node.position + nOffset;
+
+ ScFormulaCell** ppBeg = &sc::formula_block::at(*node.data, nOffset);
+ ScFormulaCell** ppEnd = ppBeg + nDataSize;
+
+ ScFormulaCell** pp = ppBeg;
+
+ // If the first formula cell belongs to a group and it's not the top
+ // cell, move up to the top cell of the group.
+
+ ScFormulaCell* pFC = *pp;
+ if (pFC->IsShared() && !pFC->IsSharedTop())
+ {
+ SCROW nBackTrackSize = pFC->aPos.Row() - pFC->GetSharedTopRow();
+ if (nBackTrackSize > 0)
+ {
+ assert(o3tl::make_unsigned(nBackTrackSize) <= nOffset);
+ for (SCROW i = 0; i < nBackTrackSize; ++i)
+ --pp;
+ mnStartRow -= nBackTrackSize;
+ }
+ }
+
+ for (; pp != ppEnd; ++pp)
+ {
+ pFC = *pp;
+
+ if (!pFC->IsSharedTop())
+ {
+ assert(!pFC->IsShared());
+ pFC->EndListeningTo(mrEndCxt);
+ continue;
+ }
+
+ size_t nEndGroupPos = (pp - ppBeg) + pFC->GetSharedLength();
+ mnEndRow = node.position + nOffset + nEndGroupPos - 1; // absolute row position of the last one in the group.
+
+ ScFormulaCell** ppGrpEnd = pp + pFC->GetSharedLength();
+ endListening(mrEndCxt, pp, ppGrpEnd);
+
+ if (nEndGroupPos > nDataSize)
+ {
+ // The group goes beyond the specified end row. Move to the
+ // one before the end position to finish the loop.
+ pp = ppEnd - 1;
+ }
+ else
+ {
+ // Move to the last one in the group.
+ pp += pFC->GetSharedLength() - 1;
+ }
+ }
+ }
+
+ SCROW getStartRow() const
+ {
+ return mnStartRow;
+ }
+
+ SCROW getEndRow() const
+ {
+ return mnEndRow;
+ }
+};
+
+}
+
+void ScColumn::StartListeningFormulaCells(
+ sc::StartListeningContext& rStartCxt, sc::EndListeningContext& rEndCxt,
+ SCROW nRow1, SCROW nRow2 )
+{
+ if (!HasFormulaCell())
+ return;
+
+ StartListeningFormulaCellsHandler aFunc(rStartCxt, rEndCxt);
+ sc::ProcessBlock(maCells.begin(), maCells, aFunc, nRow1, nRow2);
+}
+
+void ScColumn::EndListeningFormulaCells(
+ sc::EndListeningContext& rCxt, SCROW nRow1, SCROW nRow2,
+ SCROW* pStartRow, SCROW* pEndRow )
+{
+ if (!HasFormulaCell())
+ return;
+
+ EndListeningFormulaCellsHandler aFunc(rCxt);
+ sc::ProcessBlock(maCells.begin(), maCells, aFunc, nRow1, nRow2);
+
+ if (pStartRow)
+ *pStartRow = aFunc.getStartRow();
+
+ if (pEndRow)
+ *pEndRow = aFunc.getEndRow();
+}
+
+void ScColumn::EndListeningIntersectedGroup(
+ sc::EndListeningContext& rCxt, SCROW nRow, std::vector<ScAddress>* pGroupPos )
+{
+ if (!GetDoc().ValidRow(nRow))
+ return;
+
+ sc::CellStoreType::position_type aPos = maCells.position(nRow);
+ sc::CellStoreType::iterator it = aPos.first;
+ if (it->type != sc::element_type_formula)
+ // Only interested in a formula block.
+ return;
+
+ ScFormulaCell* pFC = sc::formula_block::at(*it->data, aPos.second);
+ ScFormulaCellGroupRef xGroup = pFC->GetCellGroup();
+ if (!xGroup)
+ // Not a formula group.
+ return;
+
+ // End listening.
+ pFC->EndListeningTo(rCxt);
+
+ if (pGroupPos)
+ {
+ if (!pFC->IsSharedTop())
+ // Record the position of the top cell of the group.
+ pGroupPos->push_back(xGroup->mpTopCell->aPos);
+
+ SCROW nGrpLastRow = pFC->GetSharedTopRow() + pFC->GetSharedLength() - 1;
+ if (nRow < nGrpLastRow)
+ // Record the last position of the group.
+ pGroupPos->push_back(ScAddress(nCol, nGrpLastRow, nTab));
+ }
+}
+
+void ScColumn::EndListeningIntersectedGroups(
+ sc::EndListeningContext& rCxt, SCROW nRow1, SCROW nRow2, std::vector<ScAddress>* pGroupPos )
+{
+ // Only end the intersected group.
+ sc::CellStoreType::position_type aPos = maCells.position(nRow1);
+ sc::CellStoreType::iterator it = aPos.first;
+ if (it->type == sc::element_type_formula)
+ {
+ ScFormulaCell* pFC = sc::formula_block::at(*it->data, aPos.second);
+ ScFormulaCellGroupRef xGroup = pFC->GetCellGroup();
+ if (xGroup)
+ {
+ if (!pFC->IsSharedTop())
+ // End listening.
+ pFC->EndListeningTo(rCxt);
+
+ if (pGroupPos)
+ // Record the position of the top cell of the group.
+ pGroupPos->push_back(xGroup->mpTopCell->aPos);
+ }
+ }
+
+ aPos = maCells.position(it, nRow2);
+ it = aPos.first;
+ if (it->type != sc::element_type_formula)
+ return;
+
+ ScFormulaCell* pFC = sc::formula_block::at(*it->data, aPos.second);
+ ScFormulaCellGroupRef xGroup = pFC->GetCellGroup();
+ if (!xGroup)
+ return;
+
+ if (!pFC->IsSharedTop())
+ // End listening.
+ pFC->EndListeningTo(rCxt);
+
+ if (pGroupPos)
+ {
+ // Record the position of the bottom cell of the group.
+ ScAddress aPosLast = xGroup->mpTopCell->aPos;
+ aPosLast.IncRow(xGroup->mnLength-1);
+ pGroupPos->push_back(aPosLast);
+ }
+}
+
+void ScColumn::EndListeningGroup( sc::EndListeningContext& rCxt, SCROW nRow )
+{
+ sc::CellStoreType::position_type aPos = maCells.position(nRow);
+ if (aPos.first->type != sc::element_type_formula)
+ // not a formula cell.
+ return;
+
+ ScFormulaCell** pp = &sc::formula_block::at(*aPos.first->data, aPos.second);
+
+ ScFormulaCellGroupRef xGroup = (*pp)->GetCellGroup();
+ if (!xGroup)
+ {
+ // not a formula group.
+ (*pp)->EndListeningTo(rCxt);
+ return;
+ }
+
+ // Move back to the top cell.
+ SCROW nTopDelta = (*pp)->aPos.Row() - xGroup->mpTopCell->aPos.Row();
+ assert(nTopDelta >= 0);
+ if (nTopDelta > 0)
+ pp -= nTopDelta;
+
+ // Set the needs listening flag to all cells in the group.
+ assert(*pp == xGroup->mpTopCell);
+ ScFormulaCell** ppEnd = pp + xGroup->mnLength;
+ for (; pp != ppEnd; ++pp)
+ (*pp)->EndListeningTo(rCxt);
+}
+
+void ScColumn::SetNeedsListeningGroup( SCROW nRow )
+{
+ sc::CellStoreType::position_type aPos = maCells.position(nRow);
+ if (aPos.first->type != sc::element_type_formula)
+ // not a formula cell.
+ return;
+
+ ScFormulaCell** pp = &sc::formula_block::at(*aPos.first->data, aPos.second);
+
+ ScFormulaCellGroupRef xGroup = (*pp)->GetCellGroup();
+ if (!xGroup)
+ {
+ // not a formula group.
+ (*pp)->SetNeedsListening(true);
+ return;
+ }
+
+ // Move back to the top cell.
+ SCROW nTopDelta = (*pp)->aPos.Row() - xGroup->mpTopCell->aPos.Row();
+ assert(nTopDelta >= 0);
+ if (nTopDelta > 0)
+ pp -= nTopDelta;
+
+ // Set the needs listening flag to all cells in the group.
+ assert(*pp == xGroup->mpTopCell);
+ ScFormulaCell** ppEnd = pp + xGroup->mnLength;
+ for (; pp != ppEnd; ++pp)
+ (*pp)->SetNeedsListening(true);
+}
+
+std::optional<sc::ColumnIterator> ScColumn::GetColumnIterator( SCROW nRow1, SCROW nRow2 ) const
+{
+ if (!GetDoc().ValidRow(nRow1) || !GetDoc().ValidRow(nRow2) || nRow1 > nRow2)
+ return {};
+
+ return sc::ColumnIterator(maCells, nRow1, nRow2);
+}
+
+static bool lcl_InterpretSpan(sc::formula_block::const_iterator& rSpanIter, SCROW nStartOffset, SCROW nEndOffset,
+ const ScFormulaCellGroupRef& mxParentGroup, bool& bAllowThreading, ScDocument& rDoc)
+{
+ bAllowThreading = true;
+ ScFormulaCell* pCellStart = nullptr;
+ SCROW nSpanStart = -1;
+ SCROW nSpanEnd = -1;
+ sc::formula_block::const_iterator itSpanStart;
+ bool bAnyDirty = false;
+ for (SCROW nFGOffset = nStartOffset; nFGOffset <= nEndOffset; ++rSpanIter, ++nFGOffset)
+ {
+ bool bThisDirty = (*rSpanIter)->NeedsInterpret();
+ if (!pCellStart && bThisDirty)
+ {
+ pCellStart = *rSpanIter;
+ itSpanStart = rSpanIter;
+ nSpanStart = nFGOffset;
+ bAnyDirty = true;
+ }
+
+ if (pCellStart && (!bThisDirty || nFGOffset == nEndOffset))
+ {
+ nSpanEnd = bThisDirty ? nFGOffset : nFGOffset - 1;
+ assert(nSpanStart >= nStartOffset && nSpanStart <= nSpanEnd && nSpanEnd <= nEndOffset);
+
+ // Found a completely dirty sub span [nSpanStart, nSpanEnd] inside the required span [nStartOffset, nEndOffset]
+ bool bGroupInterpreted = pCellStart->Interpret(nSpanStart, nSpanEnd);
+
+ if (bGroupInterpreted)
+ for (SCROW nIdx = nSpanStart; nIdx <= nSpanEnd; ++nIdx, ++itSpanStart)
+ assert(!(*itSpanStart)->NeedsInterpret());
+
+ ScRecursionHelper& rRecursionHelper = rDoc.GetRecursionHelper();
+ // child cell's Interpret could result in calling dependency calc
+ // and that could detect a cycle involving mxGroup
+ // and do early exit in that case.
+ // OR
+ // this call resulted from a dependency calculation for a multi-formula-group-threading and
+ // if intergroup dependency is found, return early.
+ if ((mxParentGroup && mxParentGroup->mbPartOfCycle) || !rRecursionHelper.AreGroupsIndependent())
+ {
+ bAllowThreading = false;
+ return bAnyDirty;
+ }
+
+ if (!bGroupInterpreted)
+ {
+ // Evaluate from second cell in non-grouped style (no point in trying group-interpret again).
+ ++itSpanStart;
+ for (SCROW nIdx = nSpanStart+1; nIdx <= nSpanEnd; ++nIdx, ++itSpanStart)
+ {
+ (*itSpanStart)->Interpret(); // We know for sure that this cell is dirty so directly call Interpret().
+ if ((*itSpanStart)->NeedsInterpret())
+ {
+ SAL_WARN("sc.core.formulagroup", "Internal error, cell " << (*itSpanStart)->aPos
+ << " failed running Interpret(), not allowing threading");
+ bAllowThreading = false;
+ return bAnyDirty;
+ }
+
+ // Allow early exit like above.
+ if ((mxParentGroup && mxParentGroup->mbPartOfCycle) || !rRecursionHelper.AreGroupsIndependent())
+ {
+ // Set this cell as dirty as this may be interpreted in InterpretTail()
+ pCellStart->SetDirtyVar();
+ bAllowThreading = false;
+ return bAnyDirty;
+ }
+ }
+ }
+
+ pCellStart = nullptr; // For next sub span start detection.
+ }
+ }
+
+ return bAnyDirty;
+}
+
+static void lcl_EvalDirty(sc::CellStoreType& rCells, SCROW nRow1, SCROW nRow2, ScDocument& rDoc,
+ const ScFormulaCellGroupRef& mxGroup, bool bThreadingDepEval, bool bSkipRunning,
+ bool& bIsDirty, bool& bAllowThreading)
+{
+ ScRecursionHelper& rRecursionHelper = rDoc.GetRecursionHelper();
+ std::pair<sc::CellStoreType::const_iterator,size_t> aPos = rCells.position(nRow1);
+ sc::CellStoreType::const_iterator it = aPos.first;
+ size_t nOffset = aPos.second;
+ SCROW nRow = nRow1;
+
+ bIsDirty = false;
+
+ for (;it != rCells.end() && nRow <= nRow2; ++it, nOffset = 0)
+ {
+ switch( it->type )
+ {
+ case sc::element_type_edittext:
+ // These require EditEngine (in ScEditUtils::GetString()), which is probably
+ // too complex for use in threads.
+ if (bThreadingDepEval)
+ {
+ bAllowThreading = false;
+ return;
+ }
+ break;
+ case sc::element_type_formula:
+ {
+ size_t nRowsToRead = nRow2 - nRow + 1;
+ const size_t nEnd = std::min(it->size, nOffset+nRowsToRead); // last row + 1
+ sc::formula_block::const_iterator itCell = sc::formula_block::begin(*it->data);
+ std::advance(itCell, nOffset);
+
+ // Loop inside the formula block.
+ size_t nCellIdx = nOffset;
+ while (nCellIdx < nEnd)
+ {
+ const ScFormulaCellGroupRef& mxGroupChild = (*itCell)->GetCellGroup();
+ ScFormulaCell* pChildTopCell = mxGroupChild ? mxGroupChild->mpTopCell : *itCell;
+
+ // Check if itCell is already in path.
+ // If yes use a cycle guard to mark all elements of the cycle
+ // and return false
+ if (bThreadingDepEval && pChildTopCell->GetSeenInPath())
+ {
+ ScFormulaGroupCycleCheckGuard aCycleCheckGuard(rRecursionHelper, pChildTopCell);
+ bAllowThreading = false;
+ return;
+ }
+
+ if (bSkipRunning && (*itCell)->IsRunning())
+ {
+ ++itCell;
+ nCellIdx += 1;
+ nRow += 1;
+ nRowsToRead -= 1;
+ continue;
+ }
+
+ if (mxGroupChild)
+ {
+ // It is a Formula-group, evaluate the necessary parts of it (spans).
+ const SCROW nFGStartOffset = (*itCell)->aPos.Row() - pChildTopCell->aPos.Row();
+ const SCROW nFGEndOffset = std::min(nFGStartOffset + static_cast<SCROW>(nRowsToRead) - 1, mxGroupChild->mnLength - 1);
+ assert(nFGEndOffset >= nFGStartOffset);
+ const SCROW nSpanLen = nFGEndOffset - nFGStartOffset + 1;
+ // The (main) span required to be evaluated is [nFGStartOffset, nFGEndOffset], but this span may contain
+ // non-dirty cells, so split this into sets of completely-dirty spans and try evaluate each of them in grouped-style.
+
+ bool bAnyDirtyInSpan = lcl_InterpretSpan(itCell, nFGStartOffset, nFGEndOffset, mxGroup, bAllowThreading, rDoc);
+ if (!bAllowThreading)
+ return;
+ // itCell will now point to cell just after the end of span [nFGStartOffset, nFGEndOffset].
+ bIsDirty = bIsDirty || bAnyDirtyInSpan;
+
+ // update the counters by nSpanLen.
+ // itCell already got updated.
+ nCellIdx += nSpanLen;
+ nRow += nSpanLen;
+ nRowsToRead -= nSpanLen;
+ }
+ else
+ {
+ // No formula-group here.
+ bool bDirtyFlag = false;
+ if( (*itCell)->NeedsInterpret())
+ {
+ bDirtyFlag = true;
+ (*itCell)->Interpret();
+ if ((*itCell)->NeedsInterpret())
+ {
+ SAL_WARN("sc.core.formulagroup", "Internal error, cell " << (*itCell)->aPos
+ << " failed running Interpret(), not allowing threading");
+ bAllowThreading = false;
+ return;
+ }
+ }
+ bIsDirty = bIsDirty || bDirtyFlag;
+
+ // child cell's Interpret could result in calling dependency calc
+ // and that could detect a cycle involving mxGroup
+ // and do early exit in that case.
+ // OR
+ // we are trying multi-formula-group-threading, but found intergroup dependency.
+ if (bThreadingDepEval && mxGroup &&
+ (mxGroup->mbPartOfCycle || !rRecursionHelper.AreGroupsIndependent()))
+ {
+ // Set itCell as dirty as itCell may be interpreted in InterpretTail()
+ (*itCell)->SetDirtyVar();
+ bAllowThreading = false;
+ return;
+ }
+
+ // update the counters by 1.
+ nCellIdx += 1;
+ nRow += 1;
+ nRowsToRead -= 1;
+ ++itCell;
+ }
+ }
+ break;
+ }
+ default:
+ // Skip this block.
+ nRow += it->size - nOffset;
+ continue;
+ }
+ }
+
+ if (bThreadingDepEval)
+ bAllowThreading = true;
+
+}
+
+// Returns true if at least one FC is dirty.
+bool ScColumn::EnsureFormulaCellResults( SCROW nRow1, SCROW nRow2, bool bSkipRunning )
+{
+ if (!GetDoc().ValidRow(nRow1) || !GetDoc().ValidRow(nRow2) || nRow1 > nRow2)
+ return false;
+
+ if (!HasFormulaCell(nRow1, nRow2))
+ return false;
+
+ bool bAnyDirty = false, bTmp = false;
+ lcl_EvalDirty(maCells, nRow1, nRow2, GetDoc(), nullptr, false, bSkipRunning, bAnyDirty, bTmp);
+ return bAnyDirty;
+}
+
+bool ScColumn::HandleRefArrayForParallelism( SCROW nRow1, SCROW nRow2, const ScFormulaCellGroupRef& mxGroup )
+{
+ if (nRow1 > nRow2)
+ return false;
+
+ bool bAllowThreading = true, bTmp = false;
+ lcl_EvalDirty(maCells, nRow1, nRow2, GetDoc(), mxGroup, true, false, bTmp, bAllowThreading);
+
+ return bAllowThreading;
+}
+
+namespace {
+
+class StoreToCacheFunc
+{
+ SvStream& mrStrm;
+public:
+
+ StoreToCacheFunc(SvStream& rStrm):
+ mrStrm(rStrm)
+ {
+ }
+
+ void operator() ( const sc::CellStoreType::value_type& node, size_t nOffset, size_t nDataSize )
+ {
+ SCROW nStartRow = node.position + nOffset;
+ mrStrm.WriteUInt64(nStartRow);
+ mrStrm.WriteUInt64(nDataSize);
+ switch (node.type)
+ {
+ case sc::element_type_empty:
+ {
+ mrStrm.WriteUChar(0);
+ }
+ break;
+ case sc::element_type_numeric:
+ {
+ mrStrm.WriteUChar(1);
+ sc::numeric_block::const_iterator it = sc::numeric_block::begin(*node.data);
+ std::advance(it, nOffset);
+ sc::numeric_block::const_iterator itEnd = it;
+ std::advance(itEnd, nDataSize);
+
+ for (; it != itEnd; ++it)
+ {
+ mrStrm.WriteDouble(*it);
+ }
+ }
+ break;
+ case sc::element_type_string:
+ {
+ mrStrm.WriteUChar(2);
+ sc::string_block::const_iterator it = sc::string_block::begin(*node.data);
+ std::advance(it, nOffset);
+ sc::string_block::const_iterator itEnd = it;
+ std::advance(itEnd, nDataSize);
+
+ for (; it != itEnd; ++it)
+ {
+ OString aStr = OUStringToOString(it->getString(), RTL_TEXTENCODING_UTF8);
+ sal_Int32 nStrLength = aStr.getLength();
+ mrStrm.WriteInt32(nStrLength);
+ mrStrm.WriteOString(aStr);
+ }
+ }
+ break;
+ case sc::element_type_formula:
+ {
+ mrStrm.WriteUChar(3);
+ sc::formula_block::const_iterator it = sc::formula_block::begin(*node.data);
+ std::advance(it, nOffset);
+ sc::formula_block::const_iterator itEnd = it;
+ std::advance(itEnd, nDataSize);
+
+ for (; it != itEnd; /* incrementing through std::advance*/)
+ {
+ const ScFormulaCell* pCell = *it;
+ OUString aFormula = pCell->GetFormula(formula::FormulaGrammar::GRAM_ENGLISH_XL_R1C1);
+ const auto& xCellGroup = pCell->GetCellGroup();
+ sal_uInt64 nGroupLength = 0;
+ if (xCellGroup)
+ {
+ nGroupLength = xCellGroup->mnLength;
+ }
+ else
+ {
+ nGroupLength = 1;
+ }
+ mrStrm.WriteUInt64(nGroupLength);
+ mrStrm.WriteInt32(aFormula.getLength());
+ mrStrm.WriteOString(OUStringToOString(aFormula, RTL_TEXTENCODING_UTF8));
+
+ // incrementing the iterator
+ std::advance(it, nGroupLength);
+ }
+ }
+ break;
+ }
+ }
+};
+
+}
+
+void ScColumn::StoreToCache(SvStream& rStrm) const
+{
+ rStrm.WriteUInt64(nCol);
+ SCROW nLastRow = GetLastDataPos();
+ rStrm.WriteUInt64(nLastRow + 1); // the rows are zero based
+
+ StoreToCacheFunc aFunc(rStrm);
+ sc::ParseBlock(maCells.begin(), maCells, aFunc, SCROW(0), nLastRow);
+}
+
+void ScColumn::RestoreFromCache(SvStream& rStrm)
+{
+ sal_uInt64 nStoredCol = 0;
+ rStrm.ReadUInt64(nStoredCol);
+ if (nStoredCol != static_cast<sal_uInt64>(nCol))
+ throw std::exception();
+
+ sal_uInt64 nLastRow = 0;
+ rStrm.ReadUInt64(nLastRow);
+ sal_uInt64 nReadRow = 0;
+ ScDocument& rDocument = GetDoc();
+ while (nReadRow < nLastRow)
+ {
+ sal_uInt64 nStartRow = 0;
+ sal_uInt64 nDataSize = 0;
+ rStrm.ReadUInt64(nStartRow);
+ rStrm.ReadUInt64(nDataSize);
+ sal_uInt8 nType = 0;
+ rStrm.ReadUChar(nType);
+ switch (nType)
+ {
+ case 0:
+ // nothing to do
+ maCells.set_empty(nStartRow, nDataSize);
+ break;
+ case 1:
+ {
+ // nDataSize double values
+ std::vector<double> aValues(nDataSize);
+ for (auto& rValue : aValues)
+ {
+ rStrm.ReadDouble(rValue);
+ }
+ maCells.set(nStartRow, aValues.begin(), aValues.end());
+ }
+ break;
+ case 2:
+ {
+ std::vector<svl::SharedString> aStrings(nDataSize);
+ svl::SharedStringPool& rPool = rDocument.GetSharedStringPool();
+ for (auto& rString : aStrings)
+ {
+ sal_Int32 nStrLength = 0;
+ rStrm.ReadInt32(nStrLength);
+ std::unique_ptr<char[]> pStr(new char[nStrLength]);
+ rStrm.ReadBytes(pStr.get(), nStrLength);
+ OString aOStr(pStr.get(), nStrLength);
+ OUString aStr = OStringToOUString(aOStr, RTL_TEXTENCODING_UTF8);
+ rString = rPool.intern(aStr);
+ }
+ maCells.set(nStartRow, aStrings.begin(), aStrings.end());
+
+ }
+ break;
+ case 3:
+ {
+ std::vector<ScFormulaCell*> aFormulaCells(nDataSize);
+
+ ScAddress aAddr(nCol, nStartRow, nTab);
+ const formula::FormulaGrammar::Grammar eGrammar = formula::FormulaGrammar::GRAM_ENGLISH_XL_R1C1;
+ for (SCROW nRow = 0; nRow < static_cast<SCROW>(nDataSize);)
+ {
+ sal_uInt64 nFormulaGroupSize = 0;
+ rStrm.ReadUInt64(nFormulaGroupSize);
+ sal_Int32 nStrLength = 0;
+ rStrm.ReadInt32(nStrLength);
+ std::unique_ptr<char[]> pStr(new char[nStrLength]);
+ rStrm.ReadBytes(pStr.get(), nStrLength);
+ OString aOStr(pStr.get(), nStrLength);
+ OUString aStr = OStringToOUString(aOStr, RTL_TEXTENCODING_UTF8);
+ for (sal_uInt64 i = 0; i < nFormulaGroupSize; ++i)
+ {
+ aFormulaCells[nRow + i] = new ScFormulaCell(rDocument, aAddr, aStr, eGrammar);
+ aAddr.IncRow();
+ }
+
+ nRow += nFormulaGroupSize;
+ }
+
+ maCells.set(nStartRow, aFormulaCells.begin(), aFormulaCells.end());
+ }
+ break;
+ }
+
+ nReadRow += nDataSize;
+ }
+}
+
+void ScColumn::CheckIntegrity() const
+{
+ const ScColumn* pColTest = maCells.event_handler().getColumn();
+
+ if (pColTest != this)
+ {
+ std::ostringstream os;
+ os << "cell store's event handler references wrong column instance (this=" << this
+ << "; stored=" << pColTest << ")";
+ throw std::runtime_error(os.str());
+ }
+
+ size_t nCount = std::count_if(maCells.cbegin(), maCells.cend(),
+ [](const auto& blk) { return blk.type == sc::element_type_formula; }
+ );
+
+ if (mnBlkCountFormula != nCount)
+ {
+ std::ostringstream os;
+ os << "incorrect cached formula block count (expected=" << nCount << "; actual="
+ << mnBlkCountFormula << ")";
+ throw std::runtime_error(os.str());
+ }
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sc/source/core/data/columniterator.cxx b/sc/source/core/data/columniterator.cxx
new file mode 100644
index 000000000..cec8f7a20
--- /dev/null
+++ b/sc/source/core/data/columniterator.cxx
@@ -0,0 +1,209 @@
+/* -*- 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/.
+ */
+
+#include <columniterator.hxx>
+#include <column.hxx>
+#include <document.hxx>
+#include <table.hxx>
+
+#include <osl/diagnose.h>
+
+ScColumnTextWidthIterator::ScColumnTextWidthIterator(const ScDocument& rDoc, ScColumn& rCol, SCROW nStartRow, SCROW nEndRow) :
+ mnEnd(static_cast<size_t>(nEndRow)),
+ mnCurPos(0)
+{
+ miBlockCur = rCol.maCellTextAttrs.begin();
+ miBlockEnd = rCol.maCellTextAttrs.end();
+ init(rDoc, nStartRow, nEndRow);
+}
+
+ScColumnTextWidthIterator::ScColumnTextWidthIterator(const ScDocument& rDoc, const ScAddress& rStartPos, SCROW nEndRow) :
+ mnEnd(static_cast<size_t>(nEndRow)),
+ mnCurPos(0)
+{
+ auto & rCellTextAttrs = rDoc.maTabs[rStartPos.Tab()]->aCol[rStartPos.Col()].maCellTextAttrs;
+ miBlockCur = rCellTextAttrs.begin();
+ miBlockEnd = rCellTextAttrs.end();
+ init(rDoc, rStartPos.Row(), nEndRow);
+}
+
+void ScColumnTextWidthIterator::next()
+{
+ ++miDataCur;
+ ++mnCurPos;
+
+ if (miDataCur != miDataEnd)
+ {
+ // Still in the same block. We're good.
+ checkEndRow();
+ return;
+ }
+
+ // Move to the next block.
+ for (++miBlockCur; miBlockCur != miBlockEnd; ++miBlockCur)
+ {
+ if (miBlockCur->type != sc::element_type_celltextattr)
+ {
+ // We don't iterator over this block.
+ mnCurPos += miBlockCur->size;
+ continue;
+ }
+
+ getDataIterators(0);
+ checkEndRow();
+ return;
+ }
+
+ // Reached the end.
+ assert(miBlockCur == miBlockEnd);
+}
+
+bool ScColumnTextWidthIterator::hasCell() const
+{
+ return miBlockCur != miBlockEnd;
+}
+
+SCROW ScColumnTextWidthIterator::getPos() const
+{
+ assert(miBlockCur != miBlockEnd && miDataCur != miDataEnd);
+ return static_cast<SCROW>(mnCurPos);
+}
+
+sal_uInt16 ScColumnTextWidthIterator::getValue() const
+{
+ assert(miBlockCur != miBlockEnd && miDataCur != miDataEnd);
+ return miDataCur->mnTextWidth;
+}
+
+void ScColumnTextWidthIterator::setValue(sal_uInt16 nVal)
+{
+ assert(miBlockCur != miBlockEnd && miDataCur != miDataEnd);
+ miDataCur->mnTextWidth = nVal;
+}
+
+void ScColumnTextWidthIterator::init(const ScDocument& rDoc, SCROW nStartRow, SCROW nEndRow)
+{
+ if (!rDoc.ValidRow(nStartRow) || !rDoc.ValidRow(nEndRow))
+ miBlockCur = miBlockEnd;
+
+ size_t nStart = static_cast<size_t>(nStartRow);
+
+ // Locate the start row position.
+ size_t nBlockStart = 0, nBlockEnd = 0;
+ for (; miBlockCur != miBlockEnd; ++miBlockCur, nBlockStart = nBlockEnd)
+ {
+ nBlockEnd = nBlockStart + miBlockCur->size; // non-inclusive end point.
+ if (nBlockStart <= nStart && nStart < nBlockEnd)
+ {
+ // Initial block is found!
+ break;
+ }
+ }
+
+ if (miBlockCur == miBlockEnd)
+ // Initial block not found for whatever reason... Bail out.
+ return;
+
+ // Locate the initial row position within this block.
+ if (miBlockCur->type == sc::element_type_celltextattr)
+ {
+ // This block stores text widths for non-empty cells.
+ size_t nOffsetInBlock = nStart - nBlockStart;
+ mnCurPos = nStart;
+ getDataIterators(nOffsetInBlock);
+ checkEndRow();
+ return;
+ }
+
+ // Current block is not of ushort type. Skip to the next block.
+ nBlockStart = nBlockEnd;
+ ++miBlockCur;
+
+ // Look for the first ushort block.
+ for (; miBlockCur != miBlockEnd; ++miBlockCur, nBlockStart = nBlockEnd)
+ {
+ nBlockEnd = nBlockStart + miBlockCur->size; // non-inclusive end point.
+ if (miBlockCur->type != sc::element_type_celltextattr)
+ continue;
+
+ // Found!
+ mnCurPos = nBlockStart;
+ getDataIterators(0);
+ checkEndRow();
+ return;
+ }
+
+ // Not found.
+ assert(miBlockCur == miBlockEnd);
+}
+
+void ScColumnTextWidthIterator::getDataIterators(size_t nOffsetInBlock)
+{
+ OSL_ENSURE(miBlockCur != miBlockEnd, "block is at end position");
+#if 0
+ // Does not compile
+ OSL_ENSURE(miBlockCur->type == sc::celltextattr_block,
+ "wrong block type - unsigned short block expected.");
+#endif
+ miDataCur = sc::celltextattr_block::begin(*miBlockCur->data);
+ miDataEnd = sc::celltextattr_block::end(*miBlockCur->data);
+
+ std::advance(miDataCur, nOffsetInBlock);
+}
+
+void ScColumnTextWidthIterator::checkEndRow()
+{
+ if (mnCurPos <= mnEnd)
+ // We're still good.
+ return;
+
+ // We're below the end position. End the iteration.
+ miBlockCur = miBlockEnd;
+}
+
+namespace sc {
+
+ColumnIterator::ColumnIterator( const CellStoreType& rCells, SCROW nRow1, SCROW nRow2 ) :
+ maPos(rCells.position(nRow1)),
+ maPosEnd(rCells.position(maPos.first, nRow2)),
+ mbComplete(false)
+{
+}
+
+void ColumnIterator::next()
+{
+ if ( maPos == maPosEnd)
+ mbComplete = true;
+ else
+ maPos = CellStoreType::next_position(maPos);
+}
+
+SCROW ColumnIterator::getRow() const
+{
+ return CellStoreType::logical_position(maPos);
+}
+
+bool ColumnIterator::hasCell() const
+{
+ return !mbComplete;
+}
+
+mdds::mtv::element_t ColumnIterator::getType() const
+{
+ return maPos.first->type;
+}
+
+ScRefCellValue ColumnIterator::getCell() const
+{
+ return toRefCell(maPos.first, maPos.second);
+}
+
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sc/source/core/data/columnset.cxx b/sc/source/core/data/columnset.cxx
new file mode 100644
index 000000000..5f91dd8c6
--- /dev/null
+++ b/sc/source/core/data/columnset.cxx
@@ -0,0 +1,67 @@
+/* -*- 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/.
+ */
+
+#include <columnset.hxx>
+#include <algorithm>
+
+namespace sc {
+
+void ColumnSet::set(SCTAB nTab, SCCOL nCol)
+{
+ TabsType::iterator itTab = maTabs.find(nTab);
+ if (itTab == maTabs.end())
+ {
+ std::pair<TabsType::iterator,bool> r =
+ maTabs.emplace(nTab, ColsType());
+
+ if (!r.second)
+ // insertion failed.
+ return;
+
+ itTab = r.first;
+ }
+
+ ColsType& rCols = itTab->second;
+ rCols.insert(nCol);
+}
+
+void ColumnSet::getColumns(SCTAB nTab, std::vector<SCCOL>& rCols) const
+{
+ std::vector<SCCOL> aCols;
+ TabsType::const_iterator itTab = maTabs.find(nTab);
+ if (itTab == maTabs.end())
+ {
+ rCols.swap(aCols); // empty it.
+ return;
+ }
+
+ const ColsType& rTabCols = itTab->second;
+ aCols.assign(rTabCols.begin(), rTabCols.end());
+
+ // Sort and remove duplicates.
+ std::sort(aCols.begin(), aCols.end());
+ std::vector<SCCOL>::iterator itCol = std::unique(aCols.begin(), aCols.end());
+ aCols.erase(itCol, aCols.end());
+
+ rCols.swap(aCols);
+}
+
+bool ColumnSet::hasTab(SCTAB nTab) const
+{
+ return maTabs.find(nTab) != maTabs.end();
+}
+
+bool ColumnSet::empty() const
+{
+ return maTabs.empty();
+}
+
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sc/source/core/data/columnspanset.cxx b/sc/source/core/data/columnspanset.cxx
new file mode 100644
index 000000000..eb09ea26b
--- /dev/null
+++ b/sc/source/core/data/columnspanset.cxx
@@ -0,0 +1,369 @@
+/* -*- 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/.
+ */
+
+#include <columnspanset.hxx>
+#include <column.hxx>
+#include <table.hxx>
+#include <document.hxx>
+#include <mtvfunctions.hxx>
+#include <markdata.hxx>
+#include <rangelst.hxx>
+#include <fstalgorithm.hxx>
+
+#include <algorithm>
+#include <memory>
+
+#include <o3tl/safeint.hxx>
+
+namespace sc {
+
+namespace {
+
+class ColumnNonEmptyRangesScanner
+{
+ ColumnSpanSet::ColumnSpansType& mrRanges;
+ bool mbVal;
+public:
+ ColumnNonEmptyRangesScanner(ColumnSpanSet::ColumnSpansType& rRanges, bool bVal) :
+ mrRanges(rRanges), mbVal(bVal) {}
+
+ void operator() (const sc::CellStoreType::value_type& node, size_t nOffset, size_t nDataSize)
+ {
+ if (node.type == sc::element_type_empty)
+ return;
+
+ size_t nRow = node.position + nOffset;
+ size_t nEndRow = nRow + nDataSize; // Last row of current block plus 1
+ mrRanges.insert_back(nRow, nEndRow, mbVal);
+ }
+};
+
+}
+
+RowSpan::RowSpan(SCROW nRow1, SCROW nRow2) : mnRow1(nRow1), mnRow2(nRow2) {}
+
+ColRowSpan::ColRowSpan(SCCOLROW nStart, SCCOLROW nEnd) : mnStart(nStart), mnEnd(nEnd) {}
+
+ColumnSpanSet::ColumnType::ColumnType(SCROW nStart, SCROW nEnd, bool bInit) :
+ maSpans(nStart, nEnd+1, bInit), miPos(maSpans.begin()) {}
+
+ColumnSpanSet::Action::~Action() {}
+void ColumnSpanSet::Action::startColumn(SCTAB /*nTab*/, SCCOL /*nCol*/) {}
+
+ColumnSpanSet::ColumnAction::~ColumnAction() {}
+
+ColumnSpanSet::ColumnSpanSet() {}
+
+ColumnSpanSet::~ColumnSpanSet()
+{
+}
+
+ColumnSpanSet::ColumnType& ColumnSpanSet::getColumn(const ScDocument& rDoc, SCTAB nTab, SCCOL nCol)
+{
+ if (o3tl::make_unsigned(nTab) >= maTables.size())
+ maTables.resize(nTab+1);
+
+ TableType& rTab = maTables[nTab];
+ if (o3tl::make_unsigned(nCol) >= rTab.size())
+ rTab.resize(nCol+1);
+
+ if (!rTab[nCol])
+ rTab[nCol].emplace(0, rDoc.MaxRow(), /*bInit*/false);
+
+ return *rTab[nCol];
+}
+
+void ColumnSpanSet::set(const ScDocument& rDoc, SCTAB nTab, SCCOL nCol, SCROW nRow, bool bVal)
+{
+ if (!ValidTab(nTab) || !rDoc.ValidCol(nCol) || !rDoc.ValidRow(nRow))
+ return;
+
+ ColumnType& rCol = getColumn(rDoc, nTab, nCol);
+ rCol.miPos = rCol.maSpans.insert(rCol.miPos, nRow, nRow+1, bVal).first;
+}
+
+void ColumnSpanSet::set(const ScDocument& rDoc, SCTAB nTab, SCCOL nCol, SCROW nRow1, SCROW nRow2, bool bVal)
+{
+ if (!ValidTab(nTab) || !rDoc.ValidCol(nCol) || !rDoc.ValidRow(nRow1) || !rDoc.ValidRow(nRow2))
+ return;
+
+ ColumnType& rCol = getColumn(rDoc, nTab, nCol);
+ rCol.miPos = rCol.maSpans.insert(rCol.miPos, nRow1, nRow2+1, bVal).first;
+}
+
+void ColumnSpanSet::set(const ScDocument& rDoc, const ScRange& rRange, bool bVal)
+{
+ for (SCTAB nTab = rRange.aStart.Tab(); nTab <= rRange.aEnd.Tab(); ++nTab)
+ {
+ for (SCCOL nCol = rRange.aStart.Col(); nCol <= rRange.aEnd.Col(); ++nCol)
+ {
+ ColumnType& rCol = getColumn(rDoc, nTab, nCol);
+ rCol.miPos = rCol.maSpans.insert(rCol.miPos, rRange.aStart.Row(), rRange.aEnd.Row()+1, bVal).first;
+ }
+ }
+}
+
+void ColumnSpanSet::set( const ScDocument& rDoc, SCTAB nTab, SCCOL nCol, const SingleColumnSpanSet& rSingleSet, bool bVal )
+{
+ SingleColumnSpanSet::SpansType aSpans;
+ rSingleSet.getSpans(aSpans);
+ for (const auto& rSpan : aSpans)
+ set(rDoc, nTab, nCol, rSpan.mnRow1, rSpan.mnRow2, bVal);
+}
+
+void ColumnSpanSet::scan(
+ const ScDocument& rDoc, SCTAB nTab, SCCOL nCol1, SCROW nRow1, SCCOL nCol2, SCROW nRow2, bool bVal)
+{
+ if (!rDoc.ValidColRow(nCol1, nRow1) || !rDoc.ValidColRow(nCol2, nRow2))
+ return;
+
+ if (nCol1 > nCol2 || nRow1 > nRow2)
+ return;
+
+ const ScTable* pTab = rDoc.FetchTable(nTab);
+ if (!pTab)
+ return;
+
+ nCol2 = pTab->ClampToAllocatedColumns(nCol2);
+ for (SCCOL nCol = nCol1; nCol <= nCol2; ++nCol)
+ {
+ ColumnType& rCol = getColumn(rDoc, nTab, nCol);
+
+ const CellStoreType& rSrcCells = pTab->aCol[nCol].maCells;
+
+ if( nRow1 > pTab->aCol[nCol].GetLastDataPos())
+ continue;
+
+ ColumnNonEmptyRangesScanner aScanner(rCol.maSpans, bVal);
+ ParseBlock(rSrcCells.begin(), rSrcCells, aScanner, nRow1, nRow2);
+ }
+}
+
+void ColumnSpanSet::executeAction(ScDocument& rDoc, Action& ac) const
+{
+ for (size_t nTab = 0; nTab < maTables.size(); ++nTab)
+ {
+ if (maTables[nTab].empty())
+ continue;
+
+ ScTable* pTab = rDoc.FetchTable(nTab);
+ if (!pTab)
+ continue;
+
+ const TableType& rTab = maTables[nTab];
+ for (SCCOL nCol = 0; nCol < static_cast<SCCOL>(rTab.size()); ++nCol)
+ {
+ if (!rTab[nCol])
+ continue;
+ if (nCol >= pTab->GetAllocatedColumnsCount())
+ break;
+
+ ac.startColumn(nTab, nCol);
+ const ColumnType& rCol = *rTab[nCol];
+ ColumnSpansType::const_iterator it = rCol.maSpans.begin(), itEnd = rCol.maSpans.end();
+ SCROW nRow1, nRow2;
+ nRow1 = it->first;
+ bool bVal = it->second;
+ for (++it; it != itEnd; ++it)
+ {
+ nRow2 = it->first-1;
+ ac.execute(ScAddress(nCol, nRow1, nTab), nRow2-nRow1+1, bVal);
+
+ nRow1 = nRow2+1; // for the next iteration.
+ bVal = it->second;
+ }
+ }
+ }
+}
+
+void ColumnSpanSet::executeColumnAction(ScDocument& rDoc, ColumnAction& ac) const
+{
+ for (size_t nTab = 0; nTab < maTables.size(); ++nTab)
+ {
+ if (maTables[nTab].empty())
+ continue;
+
+ ScTable* pTab = rDoc.FetchTable(nTab);
+ if (!pTab)
+ continue;
+
+ const TableType& rTab = maTables[nTab];
+ for (SCCOL nCol = 0; nCol < static_cast<SCCOL>(rTab.size()); ++nCol)
+ {
+ if (!rTab[nCol])
+ continue;
+ if (nCol >= pTab->GetAllocatedColumnsCount())
+ break;
+
+ ScColumn& rColumn = pTab->aCol[nCol];
+ ac.startColumn(&rColumn);
+ const ColumnType& rCol = *rTab[nCol];
+ ColumnSpansType::const_iterator it = rCol.maSpans.begin(), itEnd = rCol.maSpans.end();
+ SCROW nRow1, nRow2;
+ nRow1 = it->first;
+ bool bVal = it->second;
+ for (++it; it != itEnd; ++it)
+ {
+ nRow2 = it->first-1;
+ ac.execute(nRow1, nRow2, bVal);
+
+ nRow1 = nRow2+1; // for the next iteration.
+ bVal = it->second;
+ }
+ }
+ }
+}
+
+namespace {
+
+class NonEmptyRangesScanner
+{
+ SingleColumnSpanSet::ColumnSpansType& mrRanges;
+public:
+ explicit NonEmptyRangesScanner(SingleColumnSpanSet::ColumnSpansType& rRanges) : mrRanges(rRanges) {}
+
+ void operator() (const sc::CellStoreType::value_type& node, size_t nOffset, size_t nDataSize)
+ {
+ if (node.type == sc::element_type_empty)
+ return;
+
+ size_t nRow = node.position + nOffset;
+ size_t nEndRow = nRow + nDataSize; // Last row of current block plus 1
+ mrRanges.insert_back(nRow, nEndRow, true);
+ }
+};
+
+}
+
+SingleColumnSpanSet::SingleColumnSpanSet(ScSheetLimits const & rSheetLimits)
+ : mrSheetLimits(rSheetLimits),
+ maSpans(0, rSheetLimits.GetMaxRowCount(), false) {}
+
+void SingleColumnSpanSet::scan(const ScColumn& rColumn)
+{
+ const CellStoreType& rCells = rColumn.maCells;
+ SCROW nCurRow = 0;
+ for (const auto& rCell : rCells)
+ {
+ SCROW nEndRow = nCurRow + rCell.size; // Last row of current block plus 1.
+ if (rCell.type != sc::element_type_empty)
+ maSpans.insert_back(nCurRow, nEndRow, true);
+
+ nCurRow = nEndRow;
+ }
+}
+
+void SingleColumnSpanSet::scan(const ScColumn& rColumn, SCROW nStart, SCROW nEnd)
+{
+ if( nStart > rColumn.GetLastDataPos())
+ return;
+ const CellStoreType& rCells = rColumn.maCells;
+ NonEmptyRangesScanner aScanner(maSpans);
+ sc::ParseBlock(rCells.begin(), rCells, aScanner, nStart, nEnd);
+}
+
+void SingleColumnSpanSet::scan(
+ ColumnBlockConstPosition& rBlockPos, const ScColumn& rColumn, SCROW nStart, SCROW nEnd)
+{
+ if( nStart > rColumn.GetLastDataPos())
+ return;
+ const CellStoreType& rCells = rColumn.maCells;
+ NonEmptyRangesScanner aScanner(maSpans);
+ rBlockPos.miCellPos = sc::ParseBlock(rBlockPos.miCellPos, rCells, aScanner, nStart, nEnd);
+}
+
+void SingleColumnSpanSet::scan(const ScMarkData& rMark, SCTAB nTab, SCCOL nCol)
+{
+ if (!rMark.GetTableSelect(nTab))
+ // This table is not selected. Nothing to scan.
+ return;
+
+ ScRangeList aRanges = rMark.GetMarkedRangesForTab(nTab);
+ scan(aRanges, nTab, nCol);
+}
+
+void SingleColumnSpanSet::scan(const ScRangeList& rRanges, SCTAB nTab, SCCOL nCol)
+{
+ for (size_t i = 0, n = rRanges.size(); i < n; ++i)
+ {
+ const ScRange & rRange = rRanges[i];
+ if (nTab < rRange.aStart.Tab() || rRange.aEnd.Tab() < nTab)
+ continue;
+
+ if (nCol < rRange.aStart.Col() || rRange.aEnd.Col() < nCol)
+ // This column is not in this range. Skip it.
+ continue;
+
+ maSpans.insert_back(rRange.aStart.Row(), rRange.aEnd.Row()+1, true);
+ }
+}
+
+void SingleColumnSpanSet::set(SCROW nRow1, SCROW nRow2, bool bVal)
+{
+ maSpans.insert_back(nRow1, nRow2+1, bVal);
+}
+
+void SingleColumnSpanSet::getRows(std::vector<SCROW> &rRows) const
+{
+ std::vector<SCROW> aRows;
+
+ SpansType aRanges;
+ getSpans(aRanges);
+ for (const auto& rRange : aRanges)
+ {
+ for (SCROW nRow = rRange.mnRow1; nRow <= rRange.mnRow2; ++nRow)
+ aRows.push_back(nRow);
+ }
+
+ rRows.swap(aRows);
+}
+
+void SingleColumnSpanSet::getSpans(SpansType& rSpans) const
+{
+ SpansType aSpans = toSpanArray<SCROW,RowSpan>(maSpans);
+ rSpans.swap(aSpans);
+}
+
+void SingleColumnSpanSet::swap( SingleColumnSpanSet& r )
+{
+ maSpans.swap(r.maSpans);
+}
+
+bool SingleColumnSpanSet::empty() const
+{
+ // Empty if there's only the 0..rDoc.MaxRow() span with false.
+ ColumnSpansType::const_iterator it = maSpans.begin();
+ return (it->first == 0) && !(it->second) && (++it != maSpans.end()) && (it->first == mrSheetLimits.GetMaxRowCount());
+}
+
+
+void RangeColumnSpanSet::executeColumnAction(ScDocument& rDoc, sc::ColumnSpanSet::ColumnAction& ac) const
+{
+ for (SCTAB nTab = range.aStart.Tab(); nTab <= range.aEnd.Tab(); ++nTab)
+ {
+ ScTable* pTab = rDoc.FetchTable(nTab);
+ if (!pTab)
+ continue;
+
+ SCCOL nEndCol = pTab->ClampToAllocatedColumns(range.aEnd.Col());
+ for (SCCOL nCol = range.aStart.Col(); nCol <= nEndCol; ++nCol)
+ {
+ if (!rDoc.ValidCol(nCol))
+ break;
+
+ ScColumn& rColumn = pTab->aCol[nCol];
+ ac.startColumn(&rColumn);
+ ac.execute( range.aStart.Row(), range.aEnd.Row(), true );
+ }
+ }
+}
+
+} // namespace sc
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sc/source/core/data/compressedarray.cxx b/sc/source/core/data/compressedarray.cxx
new file mode 100644
index 000000000..793b43b43
--- /dev/null
+++ b/sc/source/core/data/compressedarray.cxx
@@ -0,0 +1,418 @@
+/* -*- 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 <compressedarray.hxx>
+#include <global.hxx>
+
+template< typename A, typename D >
+ScCompressedArray<A,D>::ScCompressedArray( A nMaxAccessP, const D& rValue )
+ : nCount(1)
+ , nLimit(1)
+ , pData( new DataEntry[1])
+ , nMaxAccess( nMaxAccessP)
+{
+ pData[0].aValue = rValue;
+ pData[0].nEnd = nMaxAccess;
+}
+
+template< typename A, typename D >
+size_t ScCompressedArray<A,D>::Search( A nAccess ) const
+{
+ if (nAccess == 0)
+ return 0;
+
+ tools::Long nLo = 0;
+ tools::Long nHi = static_cast<tools::Long>(nCount) - 1;
+ tools::Long nStart = 0;
+ tools::Long i = 0;
+ bool bFound = (nCount == 1);
+ while (!bFound && nLo <= nHi)
+ {
+ i = (nLo + nHi) / 2;
+ if (i > 0)
+ nStart = static_cast<tools::Long>(pData[i - 1].nEnd);
+ else
+ nStart = -1;
+ tools::Long nEnd = static_cast<tools::Long>(pData[i].nEnd);
+ if (nEnd < static_cast<tools::Long>(nAccess))
+ nLo = ++i;
+ else
+ if (nStart >= static_cast<tools::Long>(nAccess))
+ nHi = --i;
+ else
+ bFound = true;
+ }
+ return (bFound ? static_cast<size_t>(i) : (nAccess < 0 ? 0 : nCount-1));
+}
+
+template< typename A, typename D >
+void ScCompressedArray<A,D>::SetValue( A nStart, A nEnd, const D& rValue )
+{
+ if (!(0 <= nStart && nStart <= nMaxAccess && 0 <= nEnd && nEnd <= nMaxAccess
+ && nStart <= nEnd))
+ return;
+
+ if ((nStart == 0) && (nEnd == nMaxAccess))
+ Reset( rValue);
+ else
+ {
+ // Create a temporary copy in case we got a reference passed that
+ // points to a part of the array to be reallocated.
+ D aNewVal( rValue);
+ size_t nNeeded = nCount + 2;
+ if (nLimit < nNeeded)
+ {
+ nLimit *= 1.5;
+ if (nLimit < nNeeded)
+ nLimit = nNeeded;
+ std::unique_ptr<DataEntry[]> pNewData(new DataEntry[nLimit]);
+ memcpy( pNewData.get(), pData.get(), nCount*sizeof(DataEntry));
+ pData = std::move(pNewData);
+ }
+
+ size_t ni; // number of leading entries
+ size_t nInsert; // insert position (nMaxAccess+1 := no insert)
+ bool bCombined = false;
+ bool bSplit = false;
+ if (nStart > 0)
+ {
+ // skip leading
+ ni = this->Search( nStart);
+
+ nInsert = nMaxAccess+1;
+ if (!(pData[ni].aValue == aNewVal))
+ {
+ if (ni == 0 || (pData[ni-1].nEnd < nStart - 1))
+ { // may be a split or a simple insert or just a shrink,
+ // row adjustment is done further down
+ if (pData[ni].nEnd > nEnd)
+ bSplit = true;
+ ni++;
+ nInsert = ni;
+ }
+ else if (ni > 0 && pData[ni-1].nEnd == nStart - 1)
+ nInsert = ni;
+ }
+ if (ni > 0 && pData[ni-1].aValue == aNewVal)
+ { // combine
+ pData[ni-1].nEnd = nEnd;
+ nInsert = nMaxAccess+1;
+ bCombined = true;
+ }
+ }
+ else
+ {
+ nInsert = 0;
+ ni = 0;
+ }
+
+ size_t nj = ni; // stop position of range to replace
+ while (nj < nCount && pData[nj].nEnd <= nEnd)
+ nj++;
+ if (!bSplit)
+ {
+ if (nj < nCount && pData[nj].aValue == aNewVal)
+ { // combine
+ if (ni > 0)
+ {
+ if (pData[ni-1].aValue == aNewVal)
+ { // adjacent entries
+ pData[ni-1].nEnd = pData[nj].nEnd;
+ nj++;
+ }
+ else if (ni == nInsert)
+ pData[ni-1].nEnd = nStart - 1; // shrink
+ }
+ nInsert = nMaxAccess+1;
+ bCombined = true;
+ }
+ else if (ni > 0 && ni == nInsert)
+ pData[ni-1].nEnd = nStart - 1; // shrink
+ }
+ if (ni < nj)
+ { // remove middle entries
+ if (!bCombined)
+ { // replace one entry
+ pData[ni].nEnd = nEnd;
+ pData[ni].aValue = aNewVal;
+ ni++;
+ nInsert = nMaxAccess+1;
+ }
+ if (ni < nj)
+ { // remove entries
+ memmove( pData.get() + ni, pData.get() + nj,
+ (nCount - nj) * sizeof(DataEntry));
+ nCount -= nj - ni;
+ }
+ }
+
+ if (nInsert < static_cast<size_t>(nMaxAccess+1))
+ { // insert or append new entry
+ if (nInsert <= nCount)
+ {
+ if (!bSplit)
+ memmove( pData.get() + nInsert + 1, pData.get() + nInsert,
+ (nCount - nInsert) * sizeof(DataEntry));
+ else
+ {
+ memmove( pData.get() + nInsert + 2, pData.get() + nInsert,
+ (nCount - nInsert) * sizeof(DataEntry));
+ pData[nInsert+1] = pData[nInsert-1];
+ nCount++;
+ }
+ }
+ if (nInsert)
+ pData[nInsert-1].nEnd = nStart - 1;
+ pData[nInsert].nEnd = nEnd;
+ pData[nInsert].aValue = aNewVal;
+ nCount++;
+ }
+ }
+}
+
+template< typename A, typename D >
+void ScCompressedArray<A,D>::CopyFrom( const ScCompressedArray<A,D>& rArray, A nDestStart,
+ A nDestEnd, A nSrcStart )
+{
+ assert( this != &rArray && "cannot copy self->self" );
+ size_t nIndex = 0;
+ A nRegionEnd;
+ for (A j=nDestStart; j<=nDestEnd; ++j)
+ {
+ const D& rValue = (j==nDestStart ?
+ rArray.GetValue( j - nDestStart + nSrcStart, nIndex, nRegionEnd) :
+ rArray.GetNextValue( nIndex, nRegionEnd));
+ nRegionEnd = nRegionEnd - nSrcStart + nDestStart;
+ if (nRegionEnd > nDestEnd)
+ nRegionEnd = nDestEnd;
+ this->SetValue( j, nRegionEnd, rValue);
+ j = nRegionEnd;
+ }
+}
+
+template< typename A, typename D >
+const D& ScCompressedArray<A,D>::Insert( A nStart, size_t nAccessCount )
+{
+ size_t nIndex = this->Search( nStart);
+ // No real insertion is needed, simply extend the one entry and adapt all
+ // following. In case nStart points to the start row of an entry, extend
+ // the previous entry (inserting before nStart).
+ if (nIndex > 0 && pData[nIndex-1].nEnd+1 == nStart)
+ --nIndex;
+ const D& rValue = pData[nIndex].aValue; // the value "copied"
+ do
+ {
+ pData[nIndex].nEnd += nAccessCount;
+ if (pData[nIndex].nEnd >= nMaxAccess)
+ {
+ pData[nIndex].nEnd = nMaxAccess;
+ nCount = nIndex + 1; // discard trailing entries
+ }
+ } while (++nIndex < nCount);
+ return rValue;
+}
+
+template< typename A, typename D >
+void ScCompressedArray<A,D>::InsertPreservingSize( A nStart, size_t nAccessCount, const D& rFillValue )
+{
+ const A nPrevLastPos = GetLastPos();
+
+ Insert(nStart, nAccessCount);
+ for (A i = nStart; i < A(nStart + nAccessCount); ++i)
+ SetValue(i, rFillValue);
+
+ const A nNewLastPos = GetLastPos();
+ Remove(nPrevLastPos, nNewLastPos - nPrevLastPos);
+}
+
+template< typename A, typename D >
+void ScCompressedArray<A,D>::Remove( A nStart, size_t nAccessCount )
+{
+ A nEnd = nStart + nAccessCount - 1;
+ size_t nIndex = this->Search( nStart);
+ // equalize/combine/remove all entries in between
+ if (nEnd > pData[nIndex].nEnd)
+ this->SetValue( nStart, nEnd, pData[nIndex].aValue);
+ // remove an exactly matching entry by shifting up all following by one
+ if ((nStart == 0 || (nIndex > 0 && nStart == pData[nIndex-1].nEnd+1)) &&
+ pData[nIndex].nEnd == nEnd && nIndex < nCount-1)
+ {
+ // In case removing an entry results in two adjacent entries with
+ // identical data, combine them into one. This is also necessary to
+ // make the algorithm used in SetValue() work correctly, it relies on
+ // the fact that consecutive values actually differ.
+ size_t nRemove;
+ if (nIndex > 0 && pData[nIndex-1].aValue == pData[nIndex+1].aValue)
+ {
+ nRemove = 2;
+ --nIndex;
+ }
+ else
+ nRemove = 1;
+ memmove( pData.get() + nIndex, pData.get() + nIndex + nRemove, (nCount - (nIndex +
+ nRemove)) * sizeof(DataEntry));
+ nCount -= nRemove;
+ }
+ // adjust end rows, nIndex still being valid
+ do
+ {
+ pData[nIndex].nEnd -= nAccessCount;
+ } while (++nIndex < nCount);
+ pData[nCount-1].nEnd = nMaxAccess;
+}
+
+template< typename A, typename D >
+void ScCompressedArray<A,D>::RemovePreservingSize( A nStart, size_t nAccessCount, const D& rFillValue )
+{
+ const A nPrevLastPos = GetLastPos();
+
+ Remove(nStart, nAccessCount);
+
+ const A nNewLastPos = GetLastPos();
+ InsertPreservingSize(nNewLastPos, nNewLastPos - nPrevLastPos, rFillValue);
+}
+
+template< typename A, typename D >
+void ScCompressedArray<A,D>::Iterator::operator++()
+{
+ ++mnRegion;
+ if (mnRegion > mrArray.pData[mnIndex].nEnd)
+ ++mnIndex;
+}
+
+template< typename A, typename D >
+typename ScCompressedArray<A,D>::Iterator ScCompressedArray<A,D>::Iterator::operator+(size_t nAccessCount) const
+{
+ A nRegion = mnRegion + nAccessCount;
+ auto nIndex = mnIndex;
+ while (nRegion > mrArray.pData[nIndex].nEnd)
+ ++nIndex;
+ return Iterator(mrArray, nIndex, nRegion);
+}
+
+// === ScBitMaskCompressedArray ==============================================
+
+template< typename A, typename D >
+void ScBitMaskCompressedArray<A,D>::AndValue( A nStart, A nEnd,
+ const D& rValueToAnd )
+{
+ if (nStart > nEnd)
+ return;
+
+ size_t nIndex = this->Search( nStart);
+ do
+ {
+ if ((this->pData[nIndex].aValue & rValueToAnd) != this->pData[nIndex].aValue)
+ {
+ A nS = ::std::max<A>( (nIndex>0 ? this->pData[nIndex-1].nEnd+1 : 0), nStart);
+ A nE = ::std::min( this->pData[nIndex].nEnd, nEnd);
+ this->SetValue( nS, nE, this->pData[nIndex].aValue & rValueToAnd);
+ if (nE >= nEnd)
+ break; // while
+ nIndex = this->Search( nE + 1);
+ }
+ else if (this->pData[nIndex].nEnd >= nEnd)
+ break; // while
+ else
+ ++nIndex;
+ } while (nIndex < this->nCount);
+}
+
+template< typename A, typename D >
+void ScBitMaskCompressedArray<A,D>::OrValue( A nStart, A nEnd,
+ const D& rValueToOr )
+{
+ if (nStart > nEnd)
+ return;
+
+ size_t nIndex = this->Search( nStart);
+ do
+ {
+ if ((this->pData[nIndex].aValue | rValueToOr) != this->pData[nIndex].aValue)
+ {
+ A nS = ::std::max<A>( (nIndex>0 ? this->pData[nIndex-1].nEnd+1 : 0), nStart);
+ A nE = ::std::min( this->pData[nIndex].nEnd, nEnd);
+ this->SetValue( nS, nE, this->pData[nIndex].aValue | rValueToOr);
+ if (nE >= nEnd)
+ break; // while
+ nIndex = this->Search( nE + 1);
+ }
+ else if (this->pData[nIndex].nEnd >= nEnd)
+ break; // while
+ else
+ ++nIndex;
+ } while (nIndex < this->nCount);
+}
+
+template< typename A, typename D >
+void ScBitMaskCompressedArray<A,D>::CopyFromAnded(
+ const ScBitMaskCompressedArray<A,D>& rArray, A nStart, A nEnd,
+ const D& rValueToAnd )
+{
+ size_t nIndex = 0;
+ A nRegionEnd;
+ for (A j=nStart; j<=nEnd; ++j)
+ {
+ const D& rValue = (j==nStart ?
+ rArray.GetValue( j, nIndex, nRegionEnd) :
+ rArray.GetNextValue( nIndex, nRegionEnd));
+ if (nRegionEnd > nEnd)
+ nRegionEnd = nEnd;
+ this->SetValue( j, nRegionEnd, rValue & rValueToAnd);
+ j = nRegionEnd;
+ }
+}
+
+template< typename A, typename D >
+A ScBitMaskCompressedArray<A,D>::GetLastAnyBitAccess( const D& rBitMask ) const
+{
+ A nEnd = ::std::numeric_limits<A>::max();
+ size_t nIndex = this->nCount-1;
+ while (true)
+ {
+ if (this->pData[nIndex].aValue & rBitMask)
+ {
+ nEnd = this->pData[nIndex].nEnd;
+ break; // while
+ }
+ else
+ {
+ if (nIndex > 0)
+ {
+ --nIndex;
+ if (this->pData[nIndex].nEnd < 0)
+ break; // while
+ }
+ else
+ break; // while
+ }
+ }
+ return nEnd;
+}
+
+// === Force instantiation of specializations ================================
+
+template class ScCompressedArray< SCROW, CRFlags>; // flags, base class
+template class ScBitMaskCompressedArray< SCROW, CRFlags>; // flags
+template class ScCompressedArray< SCCOL, sal_uInt16>;
+template class ScCompressedArray< SCCOL, CRFlags>;
+template class ScCompressedArray< SCROW, sal_uInt16>;
+template class ScBitMaskCompressedArray< SCCOL, CRFlags>;
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sc/source/core/data/conditio.cxx b/sc/source/core/data/conditio.cxx
new file mode 100644
index 000000000..dae08455b
--- /dev/null
+++ b/sc/source/core/data/conditio.cxx
@@ -0,0 +1,2335 @@
+/* -*- 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 <scitems.hxx>
+#include <svl/numformat.hxx>
+#include <rtl/math.hxx>
+#include <sal/log.hxx>
+#include <unotools/collatorwrapper.hxx>
+
+#include <com/sun/star/sheet/ConditionOperator2.hpp>
+
+#include <attrib.hxx>
+#include <conditio.hxx>
+#include <formulacell.hxx>
+#include <document.hxx>
+#include <compiler.hxx>
+#include <rangelst.hxx>
+#include <rangenam.hxx>
+#include <rangeutl.hxx>
+#include <colorscale.hxx>
+#include <cellvalue.hxx>
+#include <editutil.hxx>
+#include <tokenarray.hxx>
+#include <fillinfo.hxx>
+#include <refupdatecontext.hxx>
+#include <formula/errorcodes.hxx>
+#include <svl/sharedstring.hxx>
+#include <svl/sharedstringpool.hxx>
+#include <memory>
+#include <numeric>
+
+using namespace formula;
+
+ScFormatEntry::ScFormatEntry(ScDocument* pDoc):
+ mpDoc(pDoc)
+{
+}
+
+bool ScFormatEntry::operator==( const ScFormatEntry& r ) const
+{
+ return IsEqual(r, false);
+}
+
+// virtual
+bool ScFormatEntry::IsEqual( const ScFormatEntry& /*r*/, bool /*bIgnoreSrcPos*/ ) const
+{
+ // By default, return false; this makes sense for all cases except ScConditionEntry
+ // As soon as databar and color scale are tested we need to think about the range
+ return false;
+}
+
+void ScFormatEntry::startRendering()
+{
+}
+
+void ScFormatEntry::endRendering()
+{
+}
+
+static bool lcl_HasRelRef( ScDocument* pDoc, const ScTokenArray* pFormula, sal_uInt16 nRecursion = 0 )
+{
+ if (pFormula)
+ {
+ FormulaTokenArrayPlainIterator aIter( *pFormula );
+ FormulaToken* t;
+ for( t = aIter.Next(); t; t = aIter.Next() )
+ {
+ switch( t->GetType() )
+ {
+ case svDoubleRef:
+ {
+ ScSingleRefData& rRef2 = t->GetDoubleRef()->Ref2;
+ if ( rRef2.IsColRel() || rRef2.IsRowRel() || rRef2.IsTabRel() )
+ return true;
+ [[fallthrough]];
+ }
+
+ case svSingleRef:
+ {
+ ScSingleRefData& rRef1 = *t->GetSingleRef();
+ if ( rRef1.IsColRel() || rRef1.IsRowRel() || rRef1.IsTabRel() )
+ return true;
+ }
+ break;
+
+ case svIndex:
+ {
+ if( t->GetOpCode() == ocName ) // DB areas always absolute
+ if( ScRangeData* pRangeData = pDoc->FindRangeNameBySheetAndIndex( t->GetSheet(), t->GetIndex()) )
+ if( (nRecursion < 42) && lcl_HasRelRef( pDoc, pRangeData->GetCode(), nRecursion + 1 ) )
+ return true;
+ }
+ break;
+
+ // #i34474# function result dependent on cell position
+ case svByte:
+ {
+ switch( t->GetOpCode() )
+ {
+ case ocRow: // ROW() returns own row index
+ case ocColumn: // COLUMN() returns own column index
+ case ocSheet: // SHEET() returns own sheet index
+ case ocCell: // CELL() may return own cell address
+ return true;
+ default:
+ {
+ // added to avoid warnings
+ }
+ }
+ }
+ break;
+
+ default:
+ {
+ // added to avoid warnings
+ }
+ }
+ }
+ }
+ return false;
+}
+
+namespace {
+
+void start_listen_to(ScFormulaListener& rListener, const ScTokenArray* pTokens, const ScRangeList& rRangeList)
+{
+ size_t n = rRangeList.size();
+ for (size_t i = 0; i < n; ++i)
+ {
+ const ScRange & rRange = rRangeList[i];
+ rListener.addTokenArray(pTokens, rRange);
+ }
+}
+
+}
+
+void ScConditionEntry::StartListening()
+{
+ if (!pCondFormat)
+ return;
+
+ const ScRangeList& rRanges = pCondFormat->GetRange();
+ mpListener->stopListening();
+ start_listen_to(*mpListener, pFormula1.get(), rRanges);
+ start_listen_to(*mpListener, pFormula2.get(), rRanges);
+
+ mpListener->setCallback([&]() { pCondFormat->DoRepaint();});
+}
+
+void ScConditionEntry::SetParent(ScConditionalFormat* pParent)
+{
+ pCondFormat = pParent;
+ StartListening();
+}
+
+ScConditionEntry::ScConditionEntry( const ScConditionEntry& r ) :
+ ScFormatEntry(r.mpDoc),
+ eOp(r.eOp),
+ nOptions(r.nOptions),
+ nVal1(r.nVal1),
+ nVal2(r.nVal2),
+ aStrVal1(r.aStrVal1),
+ aStrVal2(r.aStrVal2),
+ aStrNmsp1(r.aStrNmsp1),
+ aStrNmsp2(r.aStrNmsp2),
+ eTempGrammar1(r.eTempGrammar1),
+ eTempGrammar2(r.eTempGrammar2),
+ bIsStr1(r.bIsStr1),
+ bIsStr2(r.bIsStr2),
+ aSrcPos(r.aSrcPos),
+ aSrcString(r.aSrcString),
+ bRelRef1(r.bRelRef1),
+ bRelRef2(r.bRelRef2),
+ bFirstRun(true),
+ mpListener(new ScFormulaListener(*r.mpDoc)),
+ eConditionType( r.eConditionType ),
+ pCondFormat(r.pCondFormat)
+{
+ // ScTokenArray copy ctor creates a flat copy
+ if (r.pFormula1)
+ pFormula1.reset( new ScTokenArray( *r.pFormula1 ) );
+ if (r.pFormula2)
+ pFormula2.reset( new ScTokenArray( *r.pFormula2 ) );
+
+ StartListening();
+ // Formula cells are created at IsValid
+}
+
+ScConditionEntry::ScConditionEntry( ScDocument& rDocument, const ScConditionEntry& r ) :
+ ScFormatEntry(&rDocument),
+ eOp(r.eOp),
+ nOptions(r.nOptions),
+ nVal1(r.nVal1),
+ nVal2(r.nVal2),
+ aStrVal1(r.aStrVal1),
+ aStrVal2(r.aStrVal2),
+ aStrNmsp1(r.aStrNmsp1),
+ aStrNmsp2(r.aStrNmsp2),
+ eTempGrammar1(r.eTempGrammar1),
+ eTempGrammar2(r.eTempGrammar2),
+ bIsStr1(r.bIsStr1),
+ bIsStr2(r.bIsStr2),
+ aSrcPos(r.aSrcPos),
+ aSrcString(r.aSrcString),
+ bRelRef1(r.bRelRef1),
+ bRelRef2(r.bRelRef2),
+ bFirstRun(true),
+ mpListener(new ScFormulaListener(rDocument)),
+ eConditionType( r.eConditionType),
+ pCondFormat(r.pCondFormat)
+{
+ // Real copy of the formulas (for Ref Undo)
+ if (r.pFormula1)
+ pFormula1 = r.pFormula1->Clone();
+ if (r.pFormula2)
+ pFormula2 = r.pFormula2->Clone();
+
+ // Formula cells are created at IsValid
+ // TODO: But not in the Clipboard! So interpret beforehand!
+}
+
+ScConditionEntry::ScConditionEntry( ScConditionMode eOper,
+ const OUString& rExpr1, const OUString& rExpr2, ScDocument& rDocument, const ScAddress& rPos,
+ const OUString& rExprNmsp1, const OUString& rExprNmsp2,
+ FormulaGrammar::Grammar eGrammar1, FormulaGrammar::Grammar eGrammar2,
+ Type eType ) :
+ ScFormatEntry(&rDocument),
+ eOp(eOper),
+ nOptions(0),
+ nVal1(0.0),
+ nVal2(0.0),
+ aStrNmsp1(rExprNmsp1),
+ aStrNmsp2(rExprNmsp2),
+ eTempGrammar1(eGrammar1),
+ eTempGrammar2(eGrammar2),
+ bIsStr1(false),
+ bIsStr2(false),
+ aSrcPos(rPos),
+ bRelRef1(false),
+ bRelRef2(false),
+ bFirstRun(true),
+ mpListener(new ScFormulaListener(rDocument)),
+ eConditionType(eType),
+ pCondFormat(nullptr)
+{
+ Compile( rExpr1, rExpr2, rExprNmsp1, rExprNmsp2, eGrammar1, eGrammar2, false );
+
+ // Formula cells are created at IsValid
+}
+
+ScConditionEntry::ScConditionEntry( ScConditionMode eOper,
+ const ScTokenArray* pArr1, const ScTokenArray* pArr2,
+ ScDocument& rDocument, const ScAddress& rPos ) :
+ ScFormatEntry(&rDocument),
+ eOp(eOper),
+ nOptions(0),
+ nVal1(0.0),
+ nVal2(0.0),
+ eTempGrammar1(FormulaGrammar::GRAM_DEFAULT),
+ eTempGrammar2(FormulaGrammar::GRAM_DEFAULT),
+ bIsStr1(false),
+ bIsStr2(false),
+ aSrcPos(rPos),
+ bRelRef1(false),
+ bRelRef2(false),
+ bFirstRun(true),
+ mpListener(new ScFormulaListener(rDocument)),
+ eConditionType(ScFormatEntry::Type::Condition),
+ pCondFormat(nullptr)
+{
+ if ( pArr1 )
+ {
+ pFormula1.reset( new ScTokenArray( *pArr1 ) );
+ SimplifyCompiledFormula( pFormula1, nVal1, bIsStr1, aStrVal1 );
+ bRelRef1 = lcl_HasRelRef( mpDoc, pFormula1.get() );
+ }
+ if ( pArr2 )
+ {
+ pFormula2.reset( new ScTokenArray( *pArr2 ) );
+ SimplifyCompiledFormula( pFormula2, nVal2, bIsStr2, aStrVal2 );
+ bRelRef2 = lcl_HasRelRef( mpDoc, pFormula2.get() );
+ }
+
+ StartListening();
+
+ // Formula cells are created at IsValid
+}
+
+ScConditionEntry::~ScConditionEntry()
+{
+}
+
+void ScConditionEntry::SimplifyCompiledFormula( std::unique_ptr<ScTokenArray>& rFormula,
+ double& rVal,
+ bool& rIsStr,
+ OUString& rStrVal )
+{
+ if ( rFormula->GetLen() != 1 )
+ return;
+
+ // Single (constant number)?
+ FormulaToken* pToken = rFormula->FirstToken();
+ if ( pToken->GetOpCode() != ocPush )
+ return;
+
+ if ( pToken->GetType() == svDouble )
+ {
+ rVal = pToken->GetDouble();
+ rFormula.reset(); // Do not remember as formula
+ }
+ else if ( pToken->GetType() == svString )
+ {
+ rIsStr = true;
+ rStrVal = pToken->GetString().getString();
+ rFormula.reset(); // Do not remember as formula
+ }
+}
+
+void ScConditionEntry::SetOperation(ScConditionMode eMode)
+{
+ eOp = eMode;
+}
+
+void ScConditionEntry::Compile( const OUString& rExpr1, const OUString& rExpr2,
+ const OUString& rExprNmsp1, const OUString& rExprNmsp2,
+ FormulaGrammar::Grammar eGrammar1, FormulaGrammar::Grammar eGrammar2, bool bTextToReal )
+{
+ if ( !rExpr1.isEmpty() || !rExpr2.isEmpty() )
+ {
+ ScCompiler aComp( *mpDoc, aSrcPos );
+
+ if ( !rExpr1.isEmpty() )
+ {
+ pFormula1.reset();
+ aComp.SetGrammar( eGrammar1 );
+ if ( mpDoc->IsImportingXML() && !bTextToReal )
+ {
+ // temporary formula string as string tokens
+ pFormula1.reset( new ScTokenArray(*mpDoc) );
+ pFormula1->AssignXMLString( rExpr1, rExprNmsp1 );
+ // bRelRef1 is set when the formula is compiled again (CompileXML)
+ }
+ else
+ {
+ pFormula1 = aComp.CompileString( rExpr1, rExprNmsp1 );
+ SimplifyCompiledFormula( pFormula1, nVal1, bIsStr1, aStrVal1 );
+ bRelRef1 = lcl_HasRelRef( mpDoc, pFormula1.get() );
+ }
+ }
+
+ if ( !rExpr2.isEmpty() )
+ {
+ pFormula2.reset();
+ aComp.SetGrammar( eGrammar2 );
+ if ( mpDoc->IsImportingXML() && !bTextToReal )
+ {
+ // temporary formula string as string tokens
+ pFormula2.reset( new ScTokenArray(*mpDoc) );
+ pFormula2->AssignXMLString( rExpr2, rExprNmsp2 );
+ // bRelRef2 is set when the formula is compiled again (CompileXML)
+ }
+ else
+ {
+ pFormula2 = aComp.CompileString( rExpr2, rExprNmsp2 );
+ SimplifyCompiledFormula( pFormula2, nVal2, bIsStr2, aStrVal2 );
+ bRelRef2 = lcl_HasRelRef( mpDoc, pFormula2.get() );
+ }
+ }
+ }
+
+ StartListening();
+}
+
+/**
+ * Create formula cells
+ */
+void ScConditionEntry::MakeCells( const ScAddress& rPos )
+{
+ if ( mpDoc->IsClipOrUndo() ) // Never calculate in the Clipboard!
+ return;
+
+ if ( pFormula1 && !pFCell1 && !bRelRef1 )
+ {
+ // pFCell1 will hold a flat-copied ScTokenArray sharing ref-counted
+ // code tokens with pFormula1
+ pFCell1.reset( new ScFormulaCell(*mpDoc, rPos, *pFormula1) );
+ pFCell1->SetFreeFlying(true);
+ pFCell1->StartListeningTo( *mpDoc );
+ }
+
+ if ( pFormula2 && !pFCell2 && !bRelRef2 )
+ {
+ // pFCell2 will hold a flat-copied ScTokenArray sharing ref-counted
+ // code tokens with pFormula2
+ pFCell2.reset( new ScFormulaCell(*mpDoc, rPos, *pFormula2) );
+ pFCell2->SetFreeFlying(true);
+ pFCell2->StartListeningTo( *mpDoc );
+ }
+}
+
+void ScConditionEntry::SetIgnoreBlank(bool bSet)
+{
+ // The bit SC_COND_NOBLANKS is set if blanks are not ignored
+ // (only of valid)
+ if (bSet)
+ nOptions &= ~SC_COND_NOBLANKS;
+ else
+ nOptions |= SC_COND_NOBLANKS;
+}
+
+/**
+ * Delete formula cells, so we re-compile at the next IsValid
+ */
+void ScConditionEntry::CompileAll()
+{
+ pFCell1.reset();
+ pFCell2.reset();
+}
+
+void ScConditionEntry::CompileXML()
+{
+ // First parse the formula source position if it was stored as text
+ if ( !aSrcString.isEmpty() )
+ {
+ ScAddress aNew;
+ /* XML is always in OOo:A1 format, although R1C1 would be more amenable
+ * to compression */
+ if ( aNew.Parse( aSrcString, *mpDoc ) & ScRefFlags::VALID )
+ aSrcPos = aNew;
+ // if the position is invalid, there isn't much we can do at this time
+ aSrcString.clear();
+ }
+
+ // Convert the text tokens that were created during XML import into real tokens.
+ Compile( GetExpression(aSrcPos, 0, 0, eTempGrammar1),
+ GetExpression(aSrcPos, 1, 0, eTempGrammar2),
+ aStrNmsp1, aStrNmsp2, eTempGrammar1, eTempGrammar2, true );
+
+ // Importing ocDde/ocWebservice?
+ if (pFormula1)
+ mpDoc->CheckLinkFormulaNeedingCheck(*pFormula1);
+ if (pFormula2)
+ mpDoc->CheckLinkFormulaNeedingCheck(*pFormula2);
+}
+
+void ScConditionEntry::SetSrcString( const OUString& rNew )
+{
+ // aSrcString is only evaluated in CompileXML
+ SAL_WARN_IF( !mpDoc->IsImportingXML(), "sc", "SetSrcString is only valid for XML import" );
+
+ aSrcString = rNew;
+}
+
+void ScConditionEntry::SetFormula1( const ScTokenArray& rArray )
+{
+ pFormula1.reset();
+ if( rArray.GetLen() > 0 )
+ {
+ pFormula1.reset( new ScTokenArray( rArray ) );
+ bRelRef1 = lcl_HasRelRef( mpDoc, pFormula1.get() );
+ }
+
+ StartListening();
+}
+
+void ScConditionEntry::SetFormula2( const ScTokenArray& rArray )
+{
+ pFormula2.reset();
+ if( rArray.GetLen() > 0 )
+ {
+ pFormula2.reset( new ScTokenArray( rArray ) );
+ bRelRef2 = lcl_HasRelRef( mpDoc, pFormula2.get() );
+ }
+
+ StartListening();
+}
+
+void ScConditionEntry::UpdateReference( sc::RefUpdateContext& rCxt )
+{
+ if(pCondFormat)
+ aSrcPos = pCondFormat->GetRange().Combine().aStart;
+ ScAddress aOldSrcPos = aSrcPos;
+ bool bChangedPos = false;
+ if (rCxt.meMode == URM_INSDEL && rCxt.maRange.Contains(aSrcPos))
+ {
+ ScAddress aErrorPos( ScAddress::UNINITIALIZED );
+ if (!aSrcPos.Move(rCxt.mnColDelta, rCxt.mnRowDelta, rCxt.mnTabDelta, aErrorPos, *mpDoc))
+ {
+ assert(!"can't move ScConditionEntry");
+ }
+ bChangedPos = aSrcPos != aOldSrcPos;
+ }
+
+ if (pFormula1)
+ {
+ sc::RefUpdateResult aRes;
+ switch (rCxt.meMode)
+ {
+ case URM_INSDEL:
+ aRes = pFormula1->AdjustReferenceOnShift(rCxt, aOldSrcPos);
+ break;
+ case URM_MOVE:
+ aRes = pFormula1->AdjustReferenceOnMove(rCxt, aOldSrcPos, aSrcPos);
+ break;
+ default:
+ ;
+ }
+
+ if (aRes.mbReferenceModified || bChangedPos)
+ pFCell1.reset(); // is created again in IsValid
+ }
+
+ if (pFormula2)
+ {
+ sc::RefUpdateResult aRes;
+ switch (rCxt.meMode)
+ {
+ case URM_INSDEL:
+ aRes = pFormula2->AdjustReferenceOnShift(rCxt, aOldSrcPos);
+ break;
+ case URM_MOVE:
+ aRes = pFormula2->AdjustReferenceOnMove(rCxt, aOldSrcPos, aSrcPos);
+ break;
+ default:
+ ;
+ }
+
+ if (aRes.mbReferenceModified || bChangedPos)
+ pFCell2.reset(); // is created again in IsValid
+ }
+
+ StartListening();
+}
+
+void ScConditionEntry::UpdateInsertTab( sc::RefUpdateInsertTabContext& rCxt )
+{
+ if (pFormula1)
+ {
+ pFormula1->AdjustReferenceOnInsertedTab(rCxt, aSrcPos);
+ pFCell1.reset();
+ }
+
+ if (pFormula2)
+ {
+ pFormula2->AdjustReferenceOnInsertedTab(rCxt, aSrcPos);
+ pFCell2.reset();
+ }
+
+ ScRangeUpdater::UpdateInsertTab(aSrcPos, rCxt);
+}
+
+void ScConditionEntry::UpdateDeleteTab( sc::RefUpdateDeleteTabContext& rCxt )
+{
+ if (pFormula1)
+ {
+ pFormula1->AdjustReferenceOnDeletedTab(rCxt, aSrcPos);
+ pFCell1.reset();
+ }
+
+ if (pFormula2)
+ {
+ pFormula2->AdjustReferenceOnDeletedTab(rCxt, aSrcPos);
+ pFCell2.reset();
+ }
+
+ ScRangeUpdater::UpdateDeleteTab(aSrcPos, rCxt);
+ StartListening();
+}
+
+void ScConditionEntry::UpdateMoveTab( sc::RefUpdateMoveTabContext& rCxt )
+{
+ if (pFormula1)
+ {
+ pFormula1->AdjustReferenceOnMovedTab(rCxt, aSrcPos);
+ pFCell1.reset();
+ }
+
+ if (pFormula2)
+ {
+ pFormula2->AdjustReferenceOnMovedTab(rCxt, aSrcPos);
+ pFCell2.reset();
+ }
+
+ StartListening();
+}
+
+static bool lcl_IsEqual( const std::unique_ptr<ScTokenArray>& pArr1, const std::unique_ptr<ScTokenArray>& pArr2 )
+{
+ // We only compare the non-RPN array
+ if ( pArr1 && pArr2 )
+ return pArr1->EqualTokens( pArr2.get() );
+ else
+ return !pArr1 && !pArr2; // Both 0? -> the same
+}
+
+// virtual
+bool ScConditionEntry::IsEqual( const ScFormatEntry& rOther, bool bIgnoreSrcPos ) const
+{
+ if (GetType() != rOther.GetType())
+ return false;
+
+ const ScConditionEntry& r = static_cast<const ScConditionEntry&>(rOther);
+
+ bool bEq = (eOp == r.eOp && nOptions == r.nOptions &&
+ lcl_IsEqual( pFormula1, r.pFormula1 ) &&
+ lcl_IsEqual( pFormula2, r.pFormula2 ));
+
+ if (!bIgnoreSrcPos)
+ {
+ // for formulas, the reference positions must be compared, too
+ // (including aSrcString, for inserting the entries during XML import)
+ if ( bEq && ( pFormula1 || pFormula2 ) && ( aSrcPos != r.aSrcPos || aSrcString != r.aSrcString ) )
+ bEq = false;
+ }
+
+ // If not formulas, compare values
+ if ( bEq && !pFormula1 && ( nVal1 != r.nVal1 || aStrVal1 != r.aStrVal1 || bIsStr1 != r.bIsStr1 ) )
+ bEq = false;
+ if ( bEq && !pFormula2 && ( nVal2 != r.nVal2 || aStrVal2 != r.aStrVal2 || bIsStr2 != r.bIsStr2 ) )
+ bEq = false;
+
+ return bEq;
+}
+
+void ScConditionEntry::Interpret( const ScAddress& rPos )
+{
+ // Create formula cells
+ // Note: New Broadcaster (Note cells) may be inserted into the document!
+ if ( ( pFormula1 && !pFCell1 ) || ( pFormula2 && !pFCell2 ) )
+ MakeCells( rPos );
+
+ // Evaluate formulas
+ bool bDirty = false; // 1 and 2 separate?
+
+ std::unique_ptr<ScFormulaCell> pTemp1;
+ ScFormulaCell* pEff1 = pFCell1.get();
+ if ( bRelRef1 )
+ {
+ pTemp1.reset(pFormula1 ? new ScFormulaCell(*mpDoc, rPos, *pFormula1) : new ScFormulaCell(*mpDoc, rPos));
+ pEff1 = pTemp1.get();
+ pEff1->SetFreeFlying(true);
+ }
+ if ( pEff1 )
+ {
+ if (!pEff1->IsRunning()) // Don't create 522
+ {
+ //TODO: Query Changed instead of Dirty!
+ if (pEff1->GetDirty() && !bRelRef1 && mpDoc->GetAutoCalc())
+ bDirty = true;
+ if (pEff1->IsValue())
+ {
+ bIsStr1 = false;
+ nVal1 = pEff1->GetValue();
+ aStrVal1.clear();
+ }
+ else
+ {
+ bIsStr1 = true;
+ aStrVal1 = pEff1->GetString().getString();
+ nVal1 = 0.0;
+ }
+ }
+ }
+ pTemp1.reset();
+
+ std::unique_ptr<ScFormulaCell> pTemp2;
+ ScFormulaCell* pEff2 = pFCell2.get(); //@ 1!=2
+ if ( bRelRef2 )
+ {
+ pTemp2.reset(pFormula2 ? new ScFormulaCell(*mpDoc, rPos, *pFormula2) : new ScFormulaCell(*mpDoc, rPos));
+ pEff2 = pTemp2.get();
+ pEff2->SetFreeFlying(true);
+ }
+ if ( pEff2 )
+ {
+ if (!pEff2->IsRunning()) // Don't create 522
+ {
+ if (pEff2->GetDirty() && !bRelRef2 && mpDoc->GetAutoCalc())
+ bDirty = true;
+ if (pEff2->IsValue())
+ {
+ bIsStr2 = false;
+ nVal2 = pEff2->GetValue();
+ aStrVal2.clear();
+ }
+ else
+ {
+ bIsStr2 = true;
+ aStrVal2 = pEff2->GetString().getString();
+ nVal2 = 0.0;
+ }
+ }
+ }
+ pTemp2.reset();
+
+ // If IsRunning, the last values remain
+ if (bDirty && !bFirstRun)
+ {
+ // Repaint everything for dependent formats
+ DataChanged();
+ }
+
+ bFirstRun = false;
+}
+
+static bool lcl_GetCellContent( ScRefCellValue& rCell, bool bIsStr1, double& rArg, OUString& rArgStr,
+ const ScDocument* pDoc )
+{
+
+ if (rCell.isEmpty())
+ return !bIsStr1;
+
+ bool bVal = true;
+
+ switch (rCell.meType)
+ {
+ case CELLTYPE_VALUE:
+ rArg = rCell.mfValue;
+ break;
+ case CELLTYPE_FORMULA:
+ {
+ bVal = rCell.mpFormula->IsValue();
+ if (bVal)
+ rArg = rCell.mpFormula->GetValue();
+ else
+ rArgStr = rCell.mpFormula->GetString().getString();
+ }
+ break;
+ case CELLTYPE_STRING:
+ case CELLTYPE_EDIT:
+ bVal = false;
+ if (rCell.meType == CELLTYPE_STRING)
+ rArgStr = rCell.mpString->getString();
+ else if (rCell.mpEditText)
+ rArgStr = ScEditUtil::GetString(*rCell.mpEditText, pDoc);
+ break;
+ default:
+ ;
+ }
+
+ return bVal;
+}
+
+void ScConditionEntry::FillCache() const
+{
+ if(mpCache)
+ return;
+
+ const ScRangeList& rRanges = pCondFormat->GetRange();
+ mpCache.reset(new ScConditionEntryCache);
+ size_t nListCount = rRanges.size();
+ for( size_t i = 0; i < nListCount; i++ )
+ {
+ const ScRange & rRange = rRanges[i];
+ SCROW nRow = rRange.aEnd.Row();
+ SCCOL nCol = rRange.aEnd.Col();
+ SCCOL nColStart = rRange.aStart.Col();
+ SCROW nRowStart = rRange.aStart.Row();
+ SCTAB nTab = rRange.aStart.Tab();
+
+ // temporary fix to workaround slow duplicate entry
+ // conditions, prevent to use a whole row
+ if(nRow == mpDoc->MaxRow())
+ {
+ bool bShrunk = false;
+ mpDoc->ShrinkToUsedDataArea(bShrunk, nTab, nColStart, nRowStart,
+ nCol, nRow, false);
+ }
+
+ for( SCROW r = nRowStart; r <= nRow; r++ )
+ for( SCCOL c = nColStart; c <= nCol; c++ )
+ {
+ ScRefCellValue aCell(*mpDoc, ScAddress(c, r, nTab));
+ if (aCell.isEmpty())
+ continue;
+
+ double nVal = 0.0;
+ OUString aStr;
+ if (!lcl_GetCellContent(aCell, false, nVal, aStr, mpDoc))
+ {
+ std::pair<ScConditionEntryCache::StringCacheType::iterator, bool> aResult =
+ mpCache->maStrings.emplace(aStr, 1);
+
+ if(!aResult.second)
+ aResult.first->second++;
+ }
+ else
+ {
+ std::pair<ScConditionEntryCache::ValueCacheType::iterator, bool> aResult =
+ mpCache->maValues.emplace(nVal, 1);
+
+ if(!aResult.second)
+ aResult.first->second++;
+
+ ++(mpCache->nValueItems);
+ }
+ }
+ }
+}
+
+bool ScConditionEntry::IsDuplicate( double nArg, const OUString& rStr ) const
+{
+ FillCache();
+
+ if(rStr.isEmpty())
+ {
+ ScConditionEntryCache::ValueCacheType::iterator itr = mpCache->maValues.find(nArg);
+ if(itr == mpCache->maValues.end())
+ return false;
+ else
+ {
+ return itr->second > 1;
+ }
+ }
+ else
+ {
+ ScConditionEntryCache::StringCacheType::iterator itr = mpCache->maStrings.find(rStr);
+ if(itr == mpCache->maStrings.end())
+ return false;
+ else
+ {
+ return itr->second > 1;
+ }
+ }
+}
+
+bool ScConditionEntry::IsTopNElement( double nArg ) const
+{
+ FillCache();
+
+ if(mpCache->nValueItems <= nVal1)
+ return true;
+
+ size_t nCells = 0;
+ for(ScConditionEntryCache::ValueCacheType::const_reverse_iterator itr = mpCache->maValues.rbegin(),
+ itrEnd = mpCache->maValues.rend(); itr != itrEnd; ++itr)
+ {
+ if(nCells >= nVal1)
+ return false;
+ if(itr->first <= nArg)
+ return true;
+ nCells += itr->second;
+ }
+
+ return true;
+}
+
+bool ScConditionEntry::IsBottomNElement( double nArg ) const
+{
+ FillCache();
+
+ if(mpCache->nValueItems <= nVal1)
+ return true;
+
+ size_t nCells = 0;
+ for(const auto& [rVal, rCount] : mpCache->maValues)
+ {
+ if(nCells >= nVal1)
+ return false;
+ if(rVal >= nArg)
+ return true;
+ nCells += rCount;
+ }
+
+ return true;
+}
+
+bool ScConditionEntry::IsTopNPercent( double nArg ) const
+{
+ FillCache();
+
+ size_t nCells = 0;
+ size_t nLimitCells = static_cast<size_t>(mpCache->nValueItems*nVal1/100);
+ for(ScConditionEntryCache::ValueCacheType::const_reverse_iterator itr = mpCache->maValues.rbegin(),
+ itrEnd = mpCache->maValues.rend(); itr != itrEnd; ++itr)
+ {
+ if(nCells >= nLimitCells)
+ return false;
+ if(itr->first <= nArg)
+ return true;
+ nCells += itr->second;
+ }
+
+ return true;
+}
+
+bool ScConditionEntry::IsBottomNPercent( double nArg ) const
+{
+ FillCache();
+
+ size_t nCells = 0;
+ size_t nLimitCells = static_cast<size_t>(mpCache->nValueItems*nVal1/100);
+ for(const auto& [rVal, rCount] : mpCache->maValues)
+ {
+ if(nCells >= nLimitCells)
+ return false;
+ if(rVal >= nArg)
+ return true;
+ nCells += rCount;
+ }
+
+ return true;
+}
+
+bool ScConditionEntry::IsBelowAverage( double nArg, bool bEqual ) const
+{
+ FillCache();
+
+ double nSum = std::accumulate(mpCache->maValues.begin(), mpCache->maValues.end(), double(0),
+ [](const double& rSum, const ScConditionEntryCache::ValueCacheType::value_type& rEntry) {
+ return rSum + rEntry.first * rEntry.second; });
+
+ if(bEqual)
+ return (nArg <= nSum/mpCache->nValueItems);
+ else
+ return (nArg < nSum/mpCache->nValueItems);
+}
+
+bool ScConditionEntry::IsAboveAverage( double nArg, bool bEqual ) const
+{
+ FillCache();
+
+ double nSum = std::accumulate(mpCache->maValues.begin(), mpCache->maValues.end(), double(0),
+ [](const double& rSum, const ScConditionEntryCache::ValueCacheType::value_type& rEntry) {
+ return rSum + rEntry.first * rEntry.second; });
+
+ if(bEqual)
+ return (nArg >= nSum/mpCache->nValueItems);
+ else
+ return (nArg > nSum/mpCache->nValueItems);
+}
+
+bool ScConditionEntry::IsError( const ScAddress& rPos ) const
+{
+ ScRefCellValue rCell(*mpDoc, rPos);
+
+ if (rCell.meType == CELLTYPE_FORMULA)
+ {
+ if (rCell.mpFormula->GetErrCode() != FormulaError::NONE)
+ return true;
+ }
+
+ return false;
+}
+
+bool ScConditionEntry::IsValid( double nArg, const ScAddress& rPos ) const
+{
+ // Interpret must already have been called
+ if ( bIsStr1 )
+ {
+ switch( eOp )
+ {
+ case ScConditionMode::BeginsWith:
+ case ScConditionMode::EndsWith:
+ case ScConditionMode::ContainsText:
+ case ScConditionMode::NotContainsText:
+ break;
+ case ScConditionMode::NotEqual:
+ return true;
+ default:
+ return false;
+ }
+ }
+
+ if ( eOp == ScConditionMode::Between || eOp == ScConditionMode::NotBetween )
+ if ( bIsStr2 )
+ return false;
+
+ double nComp1 = nVal1; // Copy, so that it can be changed
+ double nComp2 = nVal2;
+
+ if ( eOp == ScConditionMode::Between || eOp == ScConditionMode::NotBetween )
+ if ( nComp1 > nComp2 )
+ {
+ // Right order for value range
+ double nTemp = nComp1; nComp1 = nComp2; nComp2 = nTemp;
+ }
+
+ // All corner cases need to be tested with ::rtl::math::approxEqual!
+ bool bValid = false;
+ switch (eOp)
+ {
+ case ScConditionMode::NONE:
+ break; // Always sal_False
+ case ScConditionMode::Equal:
+ bValid = ::rtl::math::approxEqual( nArg, nComp1 );
+ break;
+ case ScConditionMode::NotEqual:
+ bValid = !::rtl::math::approxEqual( nArg, nComp1 );
+ break;
+ case ScConditionMode::Greater:
+ bValid = ( nArg > nComp1 ) && !::rtl::math::approxEqual( nArg, nComp1 );
+ break;
+ case ScConditionMode::EqGreater:
+ bValid = ( nArg >= nComp1 ) || ::rtl::math::approxEqual( nArg, nComp1 );
+ break;
+ case ScConditionMode::Less:
+ bValid = ( nArg < nComp1 ) && !::rtl::math::approxEqual( nArg, nComp1 );
+ break;
+ case ScConditionMode::EqLess:
+ bValid = ( nArg <= nComp1 ) || ::rtl::math::approxEqual( nArg, nComp1 );
+ break;
+ case ScConditionMode::Between:
+ bValid = ( nArg >= nComp1 && nArg <= nComp2 ) ||
+ ::rtl::math::approxEqual( nArg, nComp1 ) || ::rtl::math::approxEqual( nArg, nComp2 );
+ break;
+ case ScConditionMode::NotBetween:
+ bValid = ( nArg < nComp1 || nArg > nComp2 ) &&
+ !::rtl::math::approxEqual( nArg, nComp1 ) && !::rtl::math::approxEqual( nArg, nComp2 );
+ break;
+ case ScConditionMode::Duplicate:
+ case ScConditionMode::NotDuplicate:
+ if( pCondFormat )
+ {
+ bValid = IsDuplicate( nArg, OUString() );
+ if( eOp == ScConditionMode::NotDuplicate )
+ bValid = !bValid;
+ }
+ break;
+ case ScConditionMode::Direct:
+ bValid = nComp1 != 0.0;
+ break;
+ case ScConditionMode::Top10:
+ bValid = IsTopNElement( nArg );
+ break;
+ case ScConditionMode::Bottom10:
+ bValid = IsBottomNElement( nArg );
+ break;
+ case ScConditionMode::TopPercent:
+ bValid = IsTopNPercent( nArg );
+ break;
+ case ScConditionMode::BottomPercent:
+ bValid = IsBottomNPercent( nArg );
+ break;
+ case ScConditionMode::AboveAverage:
+ case ScConditionMode::AboveEqualAverage:
+ bValid = IsAboveAverage( nArg, eOp == ScConditionMode::AboveEqualAverage );
+ break;
+ case ScConditionMode::BelowAverage:
+ case ScConditionMode::BelowEqualAverage:
+ bValid = IsBelowAverage( nArg, eOp == ScConditionMode::BelowEqualAverage );
+ break;
+ case ScConditionMode::Error:
+ case ScConditionMode::NoError:
+ bValid = IsError( rPos );
+ if( eOp == ScConditionMode::NoError )
+ bValid = !bValid;
+ break;
+ case ScConditionMode::BeginsWith:
+ if(aStrVal1.isEmpty())
+ {
+ OUString aStr = OUString::number(nVal1);
+ OUString aStr2 = OUString::number(nArg);
+ bValid = aStr2.startsWith(aStr);
+ }
+ else
+ {
+ OUString aStr2 = OUString::number(nArg);
+ bValid = aStr2.startsWith(aStrVal1);
+ }
+ break;
+ case ScConditionMode::EndsWith:
+ if(aStrVal1.isEmpty())
+ {
+ OUString aStr = OUString::number(nVal1);
+ OUString aStr2 = OUString::number(nArg);
+ bValid = aStr2.endsWith(aStr);
+ }
+ else
+ {
+ OUString aStr2 = OUString::number(nArg);
+ bValid = aStr2.endsWith(aStrVal1);
+ }
+ break;
+ case ScConditionMode::ContainsText:
+ case ScConditionMode::NotContainsText:
+ if(aStrVal1.isEmpty())
+ {
+ OUString aStr = OUString::number(nVal1);
+ OUString aStr2 = OUString::number(nArg);
+ bValid = aStr2.indexOf(aStr) != -1;
+ }
+ else
+ {
+ OUString aStr2 = OUString::number(nArg);
+ bValid = aStr2.indexOf(aStrVal1) != -1;
+ }
+
+ if( eOp == ScConditionMode::NotContainsText )
+ bValid = !bValid;
+ break;
+ default:
+ SAL_WARN("sc", "unknown operation at ScConditionEntry");
+ break;
+ }
+ return bValid;
+}
+
+bool ScConditionEntry::IsValidStr( const OUString& rArg, const ScAddress& rPos ) const
+{
+ bool bValid = false;
+ // Interpret must already have been called
+ if ( eOp == ScConditionMode::Direct ) // Formula is independent from the content
+ return nVal1 != 0.0;
+
+ if ( eOp == ScConditionMode::Duplicate || eOp == ScConditionMode::NotDuplicate )
+ {
+ if( pCondFormat && !rArg.isEmpty() )
+ {
+ bValid = IsDuplicate( 0.0, rArg );
+ if( eOp == ScConditionMode::NotDuplicate )
+ bValid = !bValid;
+ return bValid;
+ }
+ }
+
+ // If number contains condition, always false, except for "not equal".
+ if ( !bIsStr1 && (eOp != ScConditionMode::Error && eOp != ScConditionMode::NoError) )
+ return ( eOp == ScConditionMode::NotEqual );
+ if ( eOp == ScConditionMode::Between || eOp == ScConditionMode::NotBetween )
+ if ( !bIsStr2 )
+ return false;
+
+ OUString aUpVal1( aStrVal1 ); //TODO: As a member? (Also set in Interpret)
+ OUString aUpVal2( aStrVal2 );
+
+ if ( eOp == ScConditionMode::Between || eOp == ScConditionMode::NotBetween )
+ if (ScGlobal::GetCollator().compareString( aUpVal1, aUpVal2 ) > 0)
+ {
+ // Right order for value range
+ OUString aTemp( aUpVal1 ); aUpVal1 = aUpVal2; aUpVal2 = aTemp;
+ }
+
+ switch ( eOp )
+ {
+ case ScConditionMode::Equal:
+ bValid = (ScGlobal::GetCollator().compareString(
+ rArg, aUpVal1 ) == 0);
+ break;
+ case ScConditionMode::NotEqual:
+ bValid = (ScGlobal::GetCollator().compareString(
+ rArg, aUpVal1 ) != 0);
+ break;
+ case ScConditionMode::TopPercent:
+ case ScConditionMode::BottomPercent:
+ case ScConditionMode::Top10:
+ case ScConditionMode::Bottom10:
+ case ScConditionMode::AboveAverage:
+ case ScConditionMode::BelowAverage:
+ return false;
+ case ScConditionMode::Error:
+ case ScConditionMode::NoError:
+ bValid = IsError( rPos );
+ if(eOp == ScConditionMode::NoError)
+ bValid = !bValid;
+ break;
+ case ScConditionMode::BeginsWith:
+ bValid = ScGlobal::GetTransliteration().isMatch(aUpVal1, rArg);
+ break;
+ case ScConditionMode::EndsWith:
+ {
+ sal_Int32 nStart = rArg.getLength();
+ const sal_Int32 nLen = aUpVal1.getLength();
+ if (nLen > nStart)
+ bValid = false;
+ else
+ {
+ nStart = nStart - nLen;
+ sal_Int32 nMatch1(0), nMatch2(0);
+ bValid = ScGlobal::GetTransliteration().equals(rArg, nStart, nLen, nMatch1,
+ aUpVal1, 0, nLen, nMatch2);
+ }
+ }
+ break;
+ case ScConditionMode::ContainsText:
+ case ScConditionMode::NotContainsText:
+ {
+ const OUString aArgStr(ScGlobal::getCharClass().lowercase(rArg));
+ const OUString aValStr(ScGlobal::getCharClass().lowercase(aUpVal1));
+ bValid = aArgStr.indexOf(aValStr) != -1;
+
+ if(eOp == ScConditionMode::NotContainsText)
+ bValid = !bValid;
+ }
+ break;
+ default:
+ {
+ sal_Int32 nCompare = ScGlobal::GetCollator().compareString(
+ rArg, aUpVal1 );
+ switch ( eOp )
+ {
+ case ScConditionMode::Greater:
+ bValid = ( nCompare > 0 );
+ break;
+ case ScConditionMode::EqGreater:
+ bValid = ( nCompare >= 0 );
+ break;
+ case ScConditionMode::Less:
+ bValid = ( nCompare < 0 );
+ break;
+ case ScConditionMode::EqLess:
+ bValid = ( nCompare <= 0 );
+ break;
+ case ScConditionMode::Between:
+ case ScConditionMode::NotBetween:
+ // Test for NOTBETWEEN:
+ bValid = ( nCompare < 0 ||
+ ScGlobal::GetCollator().compareString( rArg,
+ aUpVal2 ) > 0 );
+ if ( eOp == ScConditionMode::Between )
+ bValid = !bValid;
+ break;
+ // ScConditionMode::Direct already handled above
+ default:
+ SAL_WARN("sc", "unknown operation in ScConditionEntry");
+ bValid = false;
+ break;
+ }
+ }
+ }
+ return bValid;
+}
+
+bool ScConditionEntry::IsCellValid( ScRefCellValue& rCell, const ScAddress& rPos ) const
+{
+ const_cast<ScConditionEntry*>(this)->Interpret(rPos); // Evaluate formula
+
+ if ( eOp == ScConditionMode::Direct )
+ return nVal1 != 0.0;
+
+ double nArg = 0.0;
+ OUString aArgStr;
+ bool bVal = lcl_GetCellContent( rCell, bIsStr1, nArg, aArgStr, mpDoc );
+ if (bVal)
+ return IsValid( nArg, rPos );
+ else
+ return IsValidStr( aArgStr, rPos );
+}
+
+OUString ScConditionEntry::GetExpression( const ScAddress& rCursor, sal_uInt16 nIndex,
+ sal_uInt32 nNumFmt,
+ const FormulaGrammar::Grammar eGrammar ) const
+{
+ assert( nIndex <= 1);
+ OUString aRet;
+
+ if ( FormulaGrammar::isEnglish( eGrammar) && nNumFmt == 0 )
+ nNumFmt = mpDoc->GetFormatTable()->GetStandardIndex( LANGUAGE_ENGLISH_US );
+
+ if ( nIndex==0 )
+ {
+ if ( pFormula1 )
+ {
+ ScCompiler aComp(*mpDoc, rCursor, *pFormula1, eGrammar);
+ OUStringBuffer aBuffer;
+ aComp.CreateStringFromTokenArray( aBuffer );
+ aRet = aBuffer.makeStringAndClear();
+ }
+ else if (bIsStr1)
+ {
+ aRet = "\"" + aStrVal1 + "\"";
+ }
+ else
+ mpDoc->GetFormatTable()->GetInputLineString(nVal1, nNumFmt, aRet);
+ }
+ else if ( nIndex==1 )
+ {
+ if ( pFormula2 )
+ {
+ ScCompiler aComp(*mpDoc, rCursor, *pFormula2, eGrammar);
+ OUStringBuffer aBuffer;
+ aComp.CreateStringFromTokenArray( aBuffer );
+ aRet = aBuffer.makeStringAndClear();
+ }
+ else if (bIsStr2)
+ {
+ aRet = "\"" + aStrVal2 + "\"";
+ }
+ else
+ mpDoc->GetFormatTable()->GetInputLineString(nVal2, nNumFmt, aRet);
+ }
+
+ return aRet;
+}
+
+std::unique_ptr<ScTokenArray> ScConditionEntry::CreateFlatCopiedTokenArray( sal_uInt16 nIndex ) const
+{
+ assert(nIndex <= 1);
+ std::unique_ptr<ScTokenArray> pRet;
+
+ if ( nIndex==0 )
+ {
+ if ( pFormula1 )
+ pRet.reset(new ScTokenArray( *pFormula1 ));
+ else
+ {
+ pRet.reset(new ScTokenArray(*mpDoc));
+ if (bIsStr1)
+ {
+ svl::SharedStringPool& rSPool = mpDoc->GetSharedStringPool();
+ pRet->AddString(rSPool.intern(aStrVal1));
+ }
+ else
+ pRet->AddDouble( nVal1 );
+ }
+ }
+ else if ( nIndex==1 )
+ {
+ if ( pFormula2 )
+ pRet.reset(new ScTokenArray( *pFormula2 ));
+ else
+ {
+ pRet.reset(new ScTokenArray(*mpDoc));
+ if (bIsStr2)
+ {
+ svl::SharedStringPool& rSPool = mpDoc->GetSharedStringPool();
+ pRet->AddString(rSPool.intern(aStrVal2));
+ }
+ else
+ pRet->AddDouble( nVal2 );
+ }
+ }
+
+ return pRet;
+}
+
+/**
+ * Return a position that's adjusted to allow textual representation
+ * of expressions if possible
+ */
+ScAddress ScConditionEntry::GetValidSrcPos() const
+{
+ SCTAB nMinTab = aSrcPos.Tab();
+ SCTAB nMaxTab = nMinTab;
+
+ for (sal_uInt16 nPass = 0; nPass < 2; nPass++)
+ {
+ ScTokenArray* pFormula = nPass ? pFormula2.get() : pFormula1.get();
+ if (pFormula)
+ {
+ for ( auto t: pFormula->References() )
+ {
+ ScSingleRefData& rRef1 = *t->GetSingleRef();
+ ScAddress aAbs = rRef1.toAbs(*mpDoc, aSrcPos);
+ if (!rRef1.IsTabDeleted())
+ {
+ if (aAbs.Tab() < nMinTab)
+ nMinTab = aAbs.Tab();
+ if (aAbs.Tab() > nMaxTab)
+ nMaxTab = aAbs.Tab();
+ }
+ if ( t->GetType() == svDoubleRef )
+ {
+ ScSingleRefData& rRef2 = t->GetDoubleRef()->Ref2;
+ aAbs = rRef2.toAbs(*mpDoc, aSrcPos);
+ if (!rRef2.IsTabDeleted())
+ {
+ if (aAbs.Tab() < nMinTab)
+ nMinTab = aAbs.Tab();
+ if (aAbs.Tab() > nMaxTab)
+ nMaxTab = aAbs.Tab();
+ }
+ }
+ }
+ }
+ }
+
+ ScAddress aValidPos = aSrcPos;
+ SCTAB nTabCount = mpDoc->GetTableCount();
+ if ( nMaxTab >= nTabCount && nMinTab > 0 )
+ aValidPos.SetTab( aSrcPos.Tab() - nMinTab ); // so the lowest tab ref will be on 0
+
+ if ( aValidPos.Tab() >= nTabCount )
+ aValidPos.SetTab( nTabCount - 1 ); // ensure a valid position even if some references will be invalid
+
+ return aValidPos;
+}
+
+void ScConditionEntry::DataChanged() const
+{
+ //FIXME: Nothing so far
+}
+
+bool ScConditionEntry::MarkUsedExternalReferences() const
+{
+ bool bAllMarked = false;
+ for (sal_uInt16 nPass = 0; !bAllMarked && nPass < 2; nPass++)
+ {
+ ScTokenArray* pFormula = nPass ? pFormula2.get() : pFormula1.get();
+ if (pFormula)
+ bAllMarked = mpDoc->MarkUsedExternalReferences(*pFormula, aSrcPos);
+ }
+ return bAllMarked;
+}
+
+ScFormatEntry* ScConditionEntry::Clone(ScDocument* pDoc) const
+{
+ return new ScConditionEntry(*pDoc, *this);
+}
+
+ScConditionMode ScConditionEntry::GetModeFromApi(css::sheet::ConditionOperator nOperation)
+{
+ ScConditionMode eMode = ScConditionMode::NONE;
+ switch (static_cast<sal_Int32>(nOperation))
+ {
+ case css::sheet::ConditionOperator2::EQUAL:
+ eMode = ScConditionMode::Equal;
+ break;
+ case css::sheet::ConditionOperator2::LESS:
+ eMode = ScConditionMode::Less;
+ break;
+ case css::sheet::ConditionOperator2::GREATER:
+ eMode = ScConditionMode::Greater;
+ break;
+ case css::sheet::ConditionOperator2::LESS_EQUAL:
+ eMode = ScConditionMode::EqLess;
+ break;
+ case css::sheet::ConditionOperator2::GREATER_EQUAL:
+ eMode = ScConditionMode::EqGreater;
+ break;
+ case css::sheet::ConditionOperator2::NOT_EQUAL:
+ eMode = ScConditionMode::NotEqual;
+ break;
+ case css::sheet::ConditionOperator2::BETWEEN:
+ eMode = ScConditionMode::Between;
+ break;
+ case css::sheet::ConditionOperator2::NOT_BETWEEN:
+ eMode = ScConditionMode::NotBetween;
+ break;
+ case css::sheet::ConditionOperator2::FORMULA:
+ eMode = ScConditionMode::Direct;
+ break;
+ case css::sheet::ConditionOperator2::DUPLICATE:
+ eMode = ScConditionMode::Duplicate;
+ break;
+ case css::sheet::ConditionOperator2::NOT_DUPLICATE:
+ eMode = ScConditionMode::NotDuplicate;
+ break;
+ default:
+ break;
+ }
+ return eMode;
+}
+
+void ScConditionEntry::startRendering()
+{
+ mpCache.reset();
+}
+
+void ScConditionEntry::endRendering()
+{
+ mpCache.reset();
+}
+
+bool ScConditionEntry::NeedsRepaint() const
+{
+ return mpListener->NeedsRepaint();
+}
+
+ScCondFormatEntry::ScCondFormatEntry( ScConditionMode eOper,
+ const OUString& rExpr1, const OUString& rExpr2,
+ ScDocument& rDocument, const ScAddress& rPos,
+ const OUString& rStyle,
+ const OUString& rExprNmsp1, const OUString& rExprNmsp2,
+ FormulaGrammar::Grammar eGrammar1,
+ FormulaGrammar::Grammar eGrammar2,
+ ScFormatEntry::Type eType ) :
+ ScConditionEntry( eOper, rExpr1, rExpr2, rDocument, rPos, rExprNmsp1, rExprNmsp2, eGrammar1, eGrammar2, eType ),
+ aStyleName( rStyle ),
+ eCondFormatType( eType )
+{
+}
+
+ScCondFormatEntry::ScCondFormatEntry( ScConditionMode eOper,
+ const ScTokenArray* pArr1, const ScTokenArray* pArr2,
+ ScDocument& rDocument, const ScAddress& rPos,
+ const OUString& rStyle ) :
+ ScConditionEntry( eOper, pArr1, pArr2, rDocument, rPos ),
+ aStyleName( rStyle )
+{
+}
+
+ScCondFormatEntry::ScCondFormatEntry( const ScCondFormatEntry& r ) :
+ ScConditionEntry( r ),
+ aStyleName( r.aStyleName ),
+ eCondFormatType( r.eCondFormatType)
+{
+}
+
+ScCondFormatEntry::ScCondFormatEntry( ScDocument& rDocument, const ScCondFormatEntry& r ) :
+ ScConditionEntry( rDocument, r ),
+ aStyleName( r.aStyleName ),
+ eCondFormatType( r.eCondFormatType)
+{
+}
+
+// virtual
+bool ScCondFormatEntry::IsEqual( const ScFormatEntry& r, bool bIgnoreSrcPos ) const
+{
+ return ScConditionEntry::IsEqual(r, bIgnoreSrcPos) &&
+ (aStyleName == static_cast<const ScCondFormatEntry&>(r).aStyleName);
+}
+
+ScCondFormatEntry::~ScCondFormatEntry()
+{
+}
+
+void ScCondFormatEntry::DataChanged() const
+{
+ if ( pCondFormat )
+ pCondFormat->DoRepaint();
+}
+
+ScFormatEntry* ScCondFormatEntry::Clone( ScDocument* pDoc ) const
+{
+ return new ScCondFormatEntry( *pDoc, *this );
+}
+
+void ScConditionEntry::CalcAll()
+{
+ if (pFCell1 || pFCell2)
+ {
+ if (pFCell1)
+ pFCell1->SetDirty();
+ if (pFCell2)
+ pFCell2->SetDirty();
+ pCondFormat->DoRepaint();
+ }
+}
+
+ScCondDateFormatEntry::ScCondDateFormatEntry( ScDocument* pDoc )
+ : ScFormatEntry( pDoc )
+ , meType(condformat::TODAY)
+{
+}
+
+ScCondDateFormatEntry::ScCondDateFormatEntry( ScDocument* pDoc, const ScCondDateFormatEntry& rFormat ):
+ ScFormatEntry( pDoc ),
+ meType( rFormat.meType ),
+ maStyleName( rFormat.maStyleName )
+{
+}
+
+bool ScCondDateFormatEntry::IsValid( const ScAddress& rPos ) const
+{
+ ScRefCellValue rCell(*mpDoc, rPos);
+
+ if (!rCell.hasNumeric())
+ // non-numerical cell.
+ return false;
+
+ if( !mpCache )
+ mpCache.reset( new Date( Date::SYSTEM ) );
+
+ const Date& rActDate = *mpCache;
+ SvNumberFormatter* pFormatter = mpDoc->GetFormatTable();
+ sal_Int32 nCurrentDate = rActDate - pFormatter->GetNullDate();
+
+ double nVal = rCell.getValue();
+ sal_Int32 nCellDate = static_cast<sal_Int32>(::rtl::math::approxFloor(nVal));
+ Date aCellDate = pFormatter->GetNullDate();
+ aCellDate.AddDays(nCellDate);
+
+ switch(meType)
+ {
+ case condformat::TODAY:
+ if( nCurrentDate == nCellDate )
+ return true;
+ break;
+ case condformat::TOMORROW:
+ if( nCurrentDate == nCellDate -1 )
+ return true;
+ break;
+ case condformat::YESTERDAY:
+ if( nCurrentDate == nCellDate + 1)
+ return true;
+ break;
+ case condformat::LAST7DAYS:
+ if( nCurrentDate >= nCellDate && nCurrentDate - 7 < nCellDate )
+ return true;
+ break;
+ case condformat::LASTWEEK:
+ {
+ const DayOfWeek eDay = rActDate.GetDayOfWeek();
+ if( eDay != SUNDAY )
+ {
+ Date aBegin(rActDate - (8 + static_cast<sal_Int32>(eDay)));
+ Date aEnd(rActDate - (2 + static_cast<sal_Int32>(eDay)));
+ return aCellDate.IsBetween( aBegin, aEnd );
+ }
+ else
+ {
+ Date aBegin(rActDate - 8);
+ Date aEnd(rActDate - 1);
+ return aCellDate.IsBetween( aBegin, aEnd );
+ }
+ }
+ break;
+ case condformat::THISWEEK:
+ {
+ const DayOfWeek eDay = rActDate.GetDayOfWeek();
+ if( eDay != SUNDAY )
+ {
+ Date aBegin(rActDate - (1 + static_cast<sal_Int32>(eDay)));
+ Date aEnd(rActDate + (5 - static_cast<sal_Int32>(eDay)));
+ return aCellDate.IsBetween( aBegin, aEnd );
+ }
+ else
+ {
+ Date aEnd( rActDate + 6);
+ return aCellDate.IsBetween( rActDate, aEnd );
+ }
+ }
+ break;
+ case condformat::NEXTWEEK:
+ {
+ const DayOfWeek eDay = rActDate.GetDayOfWeek();
+ if( eDay != SUNDAY )
+ {
+ return aCellDate.IsBetween( rActDate + (6 - static_cast<sal_Int32>(eDay)),
+ rActDate + (12 - static_cast<sal_Int32>(eDay)) );
+ }
+ else
+ {
+ return aCellDate.IsBetween( rActDate + 7, rActDate + 13 );
+ }
+ }
+ break;
+ case condformat::LASTMONTH:
+ if( rActDate.GetMonth() == 1 )
+ {
+ if( aCellDate.GetMonth() == 12 && rActDate.GetYear() == aCellDate.GetNextYear() )
+ return true;
+ }
+ else if( rActDate.GetYear() == aCellDate.GetYear() )
+ {
+ if( rActDate.GetMonth() == aCellDate.GetMonth() + 1)
+ return true;
+ }
+ break;
+ case condformat::THISMONTH:
+ if( rActDate.GetYear() == aCellDate.GetYear() )
+ {
+ if( rActDate.GetMonth() == aCellDate.GetMonth() )
+ return true;
+ }
+ break;
+ case condformat::NEXTMONTH:
+ if( rActDate.GetMonth() == 12 )
+ {
+ if( aCellDate.GetMonth() == 1 && rActDate.GetYear() == aCellDate.GetYear() - 1 )
+ return true;
+ }
+ else if( rActDate.GetYear() == aCellDate.GetYear() )
+ {
+ if( rActDate.GetMonth() == aCellDate.GetMonth() - 1)
+ return true;
+ }
+ break;
+ case condformat::LASTYEAR:
+ if( rActDate.GetYear() == aCellDate.GetNextYear() )
+ return true;
+ break;
+ case condformat::THISYEAR:
+ if( rActDate.GetYear() == aCellDate.GetYear() )
+ return true;
+ break;
+ case condformat::NEXTYEAR:
+ if( rActDate.GetYear() == aCellDate.GetYear() - 1 )
+ return true;
+ break;
+ }
+
+ return false;
+}
+
+void ScCondDateFormatEntry::SetDateType( condformat::ScCondFormatDateType eType )
+{
+ meType = eType;
+}
+
+void ScCondDateFormatEntry::SetStyleName( const OUString& rStyleName )
+{
+ maStyleName = rStyleName;
+}
+
+ScFormatEntry* ScCondDateFormatEntry::Clone( ScDocument* pDoc ) const
+{
+ return new ScCondDateFormatEntry( pDoc, *this );
+}
+
+void ScCondDateFormatEntry::startRendering()
+{
+ mpCache.reset();
+}
+
+void ScCondDateFormatEntry::endRendering()
+{
+ mpCache.reset();
+}
+
+ScConditionalFormat::ScConditionalFormat(sal_uInt32 nNewKey, ScDocument* pDocument) :
+ pDoc( pDocument ),
+ nKey( nNewKey )
+{
+}
+
+std::unique_ptr<ScConditionalFormat> ScConditionalFormat::Clone(ScDocument* pNewDoc) const
+{
+ // Real copy of the formula (for Ref Undo/between documents)
+ if (!pNewDoc)
+ pNewDoc = pDoc;
+
+ std::unique_ptr<ScConditionalFormat> pNew(new ScConditionalFormat(nKey, pNewDoc));
+ pNew->SetRange( maRanges ); // prerequisite for listeners
+
+ for (const auto& rxEntry : maEntries)
+ {
+ ScFormatEntry* pNewEntry = rxEntry->Clone(pNewDoc);
+ pNew->maEntries.push_back( std::unique_ptr<ScFormatEntry>(pNewEntry) );
+ pNewEntry->SetParent(pNew.get());
+ }
+
+ return pNew;
+}
+
+bool ScConditionalFormat::EqualEntries( const ScConditionalFormat& r, bool bIgnoreSrcPos ) const
+{
+ if( size() != r.size())
+ return false;
+
+ //TODO: Test for same entries in reverse order?
+ if (! std::equal(maEntries.begin(), maEntries.end(), r.maEntries.begin(),
+ [&bIgnoreSrcPos](const std::unique_ptr<ScFormatEntry>& p1, const std::unique_ptr<ScFormatEntry>& p2) -> bool
+ {
+ return p1->IsEqual(*p2, bIgnoreSrcPos);
+ }))
+ return false;
+
+ // right now don't check for same range
+ // we only use this method to merge same conditional formats from
+ // old ODF data structure
+ return true;
+}
+
+void ScConditionalFormat::SetRange( const ScRangeList& rRanges )
+{
+ maRanges = rRanges;
+ SAL_WARN_IF(maRanges.empty(), "sc", "the conditional format range is empty! will result in a crash later!");
+}
+
+void ScConditionalFormat::AddEntry( ScFormatEntry* pNew )
+{
+ maEntries.push_back( std::unique_ptr<ScFormatEntry>(pNew));
+ pNew->SetParent(this);
+}
+
+void ScConditionalFormat::RemoveEntry(size_t n)
+{
+ if (n < maEntries.size())
+ {
+ maEntries.erase(maEntries.begin() + n);
+ DoRepaint();
+ }
+}
+
+bool ScConditionalFormat::IsEmpty() const
+{
+ return maEntries.empty();
+}
+
+size_t ScConditionalFormat::size() const
+{
+ return maEntries.size();
+}
+
+ScDocument* ScConditionalFormat::GetDocument()
+{
+ return pDoc;
+}
+
+ScConditionalFormat::~ScConditionalFormat()
+{
+}
+
+const ScFormatEntry* ScConditionalFormat::GetEntry( sal_uInt16 nPos ) const
+{
+ if ( nPos < size() )
+ return maEntries[nPos].get();
+ else
+ return nullptr;
+}
+
+OUString ScConditionalFormat::GetCellStyle( ScRefCellValue& rCell, const ScAddress& rPos ) const
+{
+ for (const auto& rxEntry : maEntries)
+ {
+ if(rxEntry->GetType() == ScFormatEntry::Type::Condition ||
+ rxEntry->GetType() == ScFormatEntry::Type::ExtCondition)
+ {
+ const ScCondFormatEntry& rEntry = static_cast<const ScCondFormatEntry&>(*rxEntry);
+ if (rEntry.IsCellValid(rCell, rPos))
+ return rEntry.GetStyle();
+ }
+ else if(rxEntry->GetType() == ScFormatEntry::Type::Date)
+ {
+ const ScCondDateFormatEntry& rEntry = static_cast<const ScCondDateFormatEntry&>(*rxEntry);
+ if (rEntry.IsValid( rPos ))
+ return rEntry.GetStyleName();
+ }
+ }
+
+ return OUString();
+}
+
+ScCondFormatData ScConditionalFormat::GetData( ScRefCellValue& rCell, const ScAddress& rPos ) const
+{
+ ScCondFormatData aData;
+ for(const auto& rxEntry : maEntries)
+ {
+ if( (rxEntry->GetType() == ScFormatEntry::Type::Condition ||
+ rxEntry->GetType() == ScFormatEntry::Type::ExtCondition) &&
+ aData.aStyleName.isEmpty())
+ {
+ const ScCondFormatEntry& rEntry = static_cast<const ScCondFormatEntry&>(*rxEntry);
+ if (rEntry.IsCellValid(rCell, rPos))
+ aData.aStyleName = rEntry.GetStyle();
+ }
+ else if(rxEntry->GetType() == ScFormatEntry::Type::Colorscale && !aData.mxColorScale)
+ {
+ const ScColorScaleFormat& rEntry = static_cast<const ScColorScaleFormat&>(*rxEntry);
+ aData.mxColorScale = rEntry.GetColor(rPos);
+ }
+ else if(rxEntry->GetType() == ScFormatEntry::Type::Databar && !aData.pDataBar)
+ {
+ const ScDataBarFormat& rEntry = static_cast<const ScDataBarFormat&>(*rxEntry);
+ aData.pDataBar = rEntry.GetDataBarInfo(rPos);
+ }
+ else if(rxEntry->GetType() == ScFormatEntry::Type::Iconset && !aData.pIconSet)
+ {
+ const ScIconSetFormat& rEntry = static_cast<const ScIconSetFormat&>(*rxEntry);
+ aData.pIconSet = rEntry.GetIconSetInfo(rPos);
+ }
+ else if(rxEntry->GetType() == ScFormatEntry::Type::Date && aData.aStyleName.isEmpty())
+ {
+ const ScCondDateFormatEntry& rEntry = static_cast<const ScCondDateFormatEntry&>(*rxEntry);
+ if ( rEntry.IsValid( rPos ) )
+ aData.aStyleName = rEntry.GetStyleName();
+ }
+ }
+ return aData;
+}
+
+void ScConditionalFormat::DoRepaint()
+{
+ // all conditional format cells
+ pDoc->RepaintRange( maRanges );
+}
+
+void ScConditionalFormat::CompileAll()
+{
+ for(auto& rxEntry : maEntries)
+ if(rxEntry->GetType() == ScFormatEntry::Type::Condition ||
+ rxEntry->GetType() == ScFormatEntry::Type::ExtCondition)
+ static_cast<ScCondFormatEntry&>(*rxEntry).CompileAll();
+}
+
+void ScConditionalFormat::CompileXML()
+{
+ for(auto& rxEntry : maEntries)
+ if(rxEntry->GetType() == ScFormatEntry::Type::Condition ||
+ rxEntry->GetType() == ScFormatEntry::Type::ExtCondition)
+ static_cast<ScCondFormatEntry&>(*rxEntry).CompileXML();
+}
+
+void ScConditionalFormat::UpdateReference( sc::RefUpdateContext& rCxt, bool bCopyAsMove )
+{
+ if (rCxt.meMode == URM_COPY && bCopyAsMove)
+ {
+ // ScConditionEntry::UpdateReference() obtains its aSrcPos from
+ // maRanges and does not update it on URM_COPY, but it's needed later
+ // for the moved position, so update maRanges beforehand.
+ maRanges.UpdateReference(URM_MOVE, pDoc, rCxt.maRange, rCxt.mnColDelta, rCxt.mnRowDelta, rCxt.mnTabDelta);
+ for (auto& rxEntry : maEntries)
+ rxEntry->UpdateReference(rCxt);
+ }
+ else
+ {
+ for (auto& rxEntry : maEntries)
+ rxEntry->UpdateReference(rCxt);
+ maRanges.UpdateReference(rCxt.meMode, pDoc, rCxt.maRange, rCxt.mnColDelta, rCxt.mnRowDelta, rCxt.mnTabDelta);
+ }
+}
+
+void ScConditionalFormat::InsertRow(SCTAB nTab, SCCOL nColStart, SCCOL nColEnd, SCROW nRowPos, SCSIZE nSize)
+{
+ maRanges.InsertRow(nTab, nColStart, nColEnd, nRowPos, nSize);
+}
+
+void ScConditionalFormat::InsertCol(SCTAB nTab, SCROW nRowStart, SCROW nRowEnd, SCCOL nColPos, SCSIZE nSize)
+{
+ maRanges.InsertCol(nTab, nRowStart, nRowEnd, nColPos, nSize);
+}
+
+void ScConditionalFormat::UpdateInsertTab( sc::RefUpdateInsertTabContext& rCxt )
+{
+ for (size_t i = 0, n = maRanges.size(); i < n; ++i)
+ {
+ // We assume that the start and end sheet indices are equal.
+ ScRange & rRange = maRanges[i];
+ SCTAB nTab = rRange.aStart.Tab();
+
+ if (nTab < rCxt.mnInsertPos)
+ // Unaffected.
+ continue;
+
+ rRange.aStart.IncTab(rCxt.mnSheets);
+ rRange.aEnd.IncTab(rCxt.mnSheets);
+ }
+
+ for (auto& rxEntry : maEntries)
+ rxEntry->UpdateInsertTab(rCxt);
+}
+
+void ScConditionalFormat::UpdateDeleteTab( sc::RefUpdateDeleteTabContext& rCxt )
+{
+ for (size_t i = 0, n = maRanges.size(); i < n; ++i)
+ {
+ // We assume that the start and end sheet indices are equal.
+ ScRange & rRange = maRanges[i];
+ SCTAB nTab = rRange.aStart.Tab();
+
+ if (nTab < rCxt.mnDeletePos)
+ // Left of the deleted sheet(s). Unaffected.
+ continue;
+
+ if (nTab <= rCxt.mnDeletePos+rCxt.mnSheets-1)
+ {
+ // On the deleted sheet(s).
+ rRange.aStart.SetTab(-1);
+ rRange.aEnd.SetTab(-1);
+ continue;
+ }
+
+ // Right of the deleted sheet(s). Adjust the sheet indices.
+ rRange.aStart.IncTab(-1*rCxt.mnSheets);
+ rRange.aEnd.IncTab(-1*rCxt.mnSheets);
+ }
+
+ for (auto& rxEntry : maEntries)
+ rxEntry->UpdateDeleteTab(rCxt);
+}
+
+void ScConditionalFormat::UpdateMoveTab( sc::RefUpdateMoveTabContext& rCxt )
+{
+ size_t n = maRanges.size();
+ SCTAB nMinTab = std::min<SCTAB>(rCxt.mnOldPos, rCxt.mnNewPos);
+ SCTAB nMaxTab = std::max<SCTAB>(rCxt.mnOldPos, rCxt.mnNewPos);
+ for(size_t i = 0; i < n; ++i)
+ {
+ ScRange & rRange = maRanges[i];
+ SCTAB nTab = rRange.aStart.Tab();
+ if(nTab < nMinTab || nTab > nMaxTab)
+ {
+ continue;
+ }
+
+ if (nTab == rCxt.mnOldPos)
+ {
+ rRange.aStart.SetTab(rCxt.mnNewPos);
+ rRange.aEnd.SetTab(rCxt.mnNewPos);
+ continue;
+ }
+
+ if (rCxt.mnNewPos < rCxt.mnOldPos)
+ {
+ rRange.aStart.IncTab();
+ rRange.aEnd.IncTab();
+ }
+ else
+ {
+ rRange.aStart.IncTab(-1);
+ rRange.aEnd.IncTab(-1);
+ }
+ }
+
+ for (auto& rxEntry : maEntries)
+ rxEntry->UpdateMoveTab(rCxt);
+}
+
+void ScConditionalFormat::DeleteArea( SCCOL nCol1, SCROW nRow1, SCCOL nCol2, SCROW nRow2 )
+{
+ if (maRanges.empty())
+ return;
+
+ SCTAB nTab = maRanges[0].aStart.Tab();
+ maRanges.DeleteArea( nCol1, nRow1, nTab, nCol2, nRow2, nTab );
+}
+
+void ScConditionalFormat::RenameCellStyle(std::u16string_view rOld, const OUString& rNew)
+{
+ for(const auto& rxEntry : maEntries)
+ if(rxEntry->GetType() == ScFormatEntry::Type::Condition ||
+ rxEntry->GetType() == ScFormatEntry::Type::ExtCondition)
+ {
+ ScCondFormatEntry& rFormat = static_cast<ScCondFormatEntry&>(*rxEntry);
+ if(rFormat.GetStyle() == rOld)
+ rFormat.UpdateStyleName( rNew );
+ }
+}
+
+bool ScConditionalFormat::MarkUsedExternalReferences() const
+{
+ bool bAllMarked = false;
+ for(const auto& rxEntry : maEntries)
+ if(rxEntry->GetType() == ScFormatEntry::Type::Condition ||
+ rxEntry->GetType() == ScFormatEntry::Type::ExtCondition)
+ {
+ const ScCondFormatEntry& rFormat = static_cast<const ScCondFormatEntry&>(*rxEntry);
+ bAllMarked = rFormat.MarkUsedExternalReferences();
+ if (bAllMarked)
+ break;
+ }
+
+ return bAllMarked;
+}
+
+void ScConditionalFormat::startRendering()
+{
+ for(auto& rxEntry : maEntries)
+ {
+ rxEntry->startRendering();
+ }
+}
+
+void ScConditionalFormat::endRendering()
+{
+ for(auto& rxEntry : maEntries)
+ {
+ rxEntry->endRendering();
+ }
+}
+
+void ScConditionalFormat::CalcAll()
+{
+ for(const auto& rxEntry : maEntries)
+ {
+ if (rxEntry->GetType() == ScFormatEntry::Type::Condition ||
+ rxEntry->GetType() == ScFormatEntry::Type::ExtCondition)
+ {
+ ScCondFormatEntry& rFormat = static_cast<ScCondFormatEntry&>(*rxEntry);
+ rFormat.CalcAll();
+ }
+ }
+}
+
+ScConditionalFormatList::ScConditionalFormatList(const ScConditionalFormatList& rList)
+{
+ for(const auto& rxFormat : rList)
+ InsertNew( rxFormat->Clone() );
+}
+
+ScConditionalFormatList::ScConditionalFormatList(ScDocument& rDoc, const ScConditionalFormatList& rList)
+{
+ for(const auto& rxFormat : rList)
+ InsertNew( rxFormat->Clone(&rDoc) );
+}
+
+void ScConditionalFormatList::InsertNew( std::unique_ptr<ScConditionalFormat> pNew )
+{
+ m_ConditionalFormats.insert(std::move(pNew));
+}
+
+ScConditionalFormat* ScConditionalFormatList::GetFormat( sal_uInt32 nKey )
+{
+ auto itr = m_ConditionalFormats.find(nKey);
+ if (itr != m_ConditionalFormats.end())
+ return itr->get();
+
+ SAL_WARN("sc", "ScConditionalFormatList: Entry not found");
+ return nullptr;
+}
+
+const ScConditionalFormat* ScConditionalFormatList::GetFormat( sal_uInt32 nKey ) const
+{
+ auto itr = m_ConditionalFormats.find(nKey);
+ if (itr != m_ConditionalFormats.end())
+ return itr->get();
+
+ SAL_WARN("sc", "ScConditionalFormatList: Entry not found");
+ return nullptr;
+}
+
+void ScConditionalFormatList::CompileAll()
+{
+ for (auto const& it : m_ConditionalFormats)
+ {
+ it->CompileAll();
+ }
+}
+
+void ScConditionalFormatList::CompileXML()
+{
+ for (auto const& it : m_ConditionalFormats)
+ {
+ it->CompileXML();
+ }
+}
+
+void ScConditionalFormatList::UpdateReference( sc::RefUpdateContext& rCxt )
+{
+ for (auto const& it : m_ConditionalFormats)
+ {
+ it->UpdateReference(rCxt);
+ }
+
+ if (rCxt.meMode == URM_INSDEL)
+ {
+ // need to check which must be deleted
+ CheckAllEntries();
+ }
+}
+
+void ScConditionalFormatList::InsertRow(SCTAB nTab, SCCOL nColStart, SCCOL nColEnd, SCROW nRowPos, SCSIZE nSize)
+{
+ for (auto const& it : m_ConditionalFormats)
+ {
+ it->InsertRow(nTab, nColStart, nColEnd, nRowPos, nSize);
+ }
+}
+
+void ScConditionalFormatList::InsertCol(SCTAB nTab, SCROW nRowStart, SCROW nRowEnd, SCCOL nColPos, SCSIZE nSize)
+{
+ for (auto const& it : m_ConditionalFormats)
+ {
+ it->InsertCol(nTab, nRowStart, nRowEnd, nColPos, nSize);
+ }
+}
+
+void ScConditionalFormatList::UpdateInsertTab( sc::RefUpdateInsertTabContext& rCxt )
+{
+ for (auto const& it : m_ConditionalFormats)
+ {
+ it->UpdateInsertTab(rCxt);
+ }
+}
+
+void ScConditionalFormatList::UpdateDeleteTab( sc::RefUpdateDeleteTabContext& rCxt )
+{
+ for (auto const& it : m_ConditionalFormats)
+ {
+ it->UpdateDeleteTab(rCxt);
+ }
+}
+
+void ScConditionalFormatList::UpdateMoveTab( sc::RefUpdateMoveTabContext& rCxt )
+{
+ for (auto const& it : m_ConditionalFormats)
+ {
+ it->UpdateMoveTab(rCxt);
+ }
+}
+
+void ScConditionalFormatList::RenameCellStyle( std::u16string_view rOld, const OUString& rNew )
+{
+ for (auto const& it : m_ConditionalFormats)
+ {
+ it->RenameCellStyle(rOld, rNew);
+ }
+}
+
+bool ScConditionalFormatList::CheckAllEntries(const Link<ScConditionalFormat*,void>& rLink)
+{
+ bool bValid = true;
+
+ // need to check which must be deleted
+ iterator itr = m_ConditionalFormats.begin();
+ while(itr != m_ConditionalFormats.end())
+ {
+ if ((*itr)->GetRange().empty())
+ {
+ bValid = false;
+ if (rLink.IsSet())
+ rLink.Call(itr->get());
+ itr = m_ConditionalFormats.erase(itr);
+ }
+ else
+ ++itr;
+ }
+
+ return bValid;
+}
+
+void ScConditionalFormatList::DeleteArea( SCCOL nCol1, SCROW nRow1, SCCOL nCol2, SCROW nRow2 )
+{
+ for (auto& rxFormat : m_ConditionalFormats)
+ rxFormat->DeleteArea( nCol1, nRow1, nCol2, nRow2 );
+
+ CheckAllEntries();
+}
+
+ScConditionalFormatList::iterator ScConditionalFormatList::begin()
+{
+ return m_ConditionalFormats.begin();
+}
+
+ScConditionalFormatList::const_iterator ScConditionalFormatList::begin() const
+{
+ return m_ConditionalFormats.begin();
+}
+
+ScConditionalFormatList::iterator ScConditionalFormatList::end()
+{
+ return m_ConditionalFormats.end();
+}
+
+ScConditionalFormatList::const_iterator ScConditionalFormatList::end() const
+{
+ return m_ConditionalFormats.end();
+}
+
+ScRangeList ScConditionalFormatList::GetCombinedRange() const
+{
+ ScRangeList aRange;
+ for (auto& itr: m_ConditionalFormats)
+ {
+ const ScRangeList& rRange = itr->GetRange();
+ for (size_t i = 0, n = rRange.size(); i < n; ++i)
+ {
+ aRange.Join(rRange[i]);
+ }
+ }
+ return aRange;
+}
+
+void ScConditionalFormatList::RemoveFromDocument(ScDocument& rDoc) const
+{
+ ScRangeList aRange = GetCombinedRange();
+ ScMarkData aMark(rDoc.GetSheetLimits());
+ aMark.MarkFromRangeList(aRange, true);
+ sal_uInt16 const pItems[2] = { sal_uInt16(ATTR_CONDITIONAL),0};
+ rDoc.ClearSelectionItems(pItems, aMark);
+}
+
+void ScConditionalFormatList::AddToDocument(ScDocument& rDoc) const
+{
+ for (auto& itr: m_ConditionalFormats)
+ {
+ const ScRangeList& rRange = itr->GetRange();
+ if (rRange.empty())
+ continue;
+
+ SCTAB nTab = rRange.front().aStart.Tab();
+ rDoc.AddCondFormatData(rRange, nTab, itr->GetKey());
+ }
+}
+
+size_t ScConditionalFormatList::size() const
+{
+ return m_ConditionalFormats.size();
+}
+
+bool ScConditionalFormatList::empty() const
+{
+ return m_ConditionalFormats.empty();
+}
+
+void ScConditionalFormatList::erase( sal_uLong nIndex )
+{
+ auto itr = m_ConditionalFormats.find(nIndex);
+ if (itr != end())
+ m_ConditionalFormats.erase(itr);
+}
+
+void ScConditionalFormatList::startRendering()
+{
+ for (auto const& it : m_ConditionalFormats)
+ {
+ it->startRendering();
+ }
+}
+
+void ScConditionalFormatList::endRendering()
+{
+ for (auto const& it : m_ConditionalFormats)
+ {
+ it->endRendering();
+ }
+}
+
+void ScConditionalFormatList::clear()
+{
+ m_ConditionalFormats.clear();
+}
+
+sal_uInt32 ScConditionalFormatList::getMaxKey() const
+{
+ if (m_ConditionalFormats.empty())
+ return 0;
+ return (*m_ConditionalFormats.rbegin())->GetKey();
+}
+
+void ScConditionalFormatList::CalcAll()
+{
+ for (const auto& aEntry : m_ConditionalFormats)
+ {
+ aEntry->CalcAll();
+ }
+
+}
+
+ScCondFormatData::ScCondFormatData() {}
+
+ScCondFormatData::ScCondFormatData(ScCondFormatData&&) = default;
+
+ScCondFormatData::~ScCondFormatData() {}
+
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sc/source/core/data/dbdocutl.cxx b/sc/source/core/data/dbdocutl.cxx
new file mode 100644
index 000000000..db4517097
--- /dev/null
+++ b/sc/source/core/data/dbdocutl.cxx
@@ -0,0 +1,201 @@
+/* -*- 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 <com/sun/star/sdbc/DataType.hpp>
+#include <com/sun/star/sdbc/XRow.hpp>
+
+#include <svl/numformat.hxx>
+#include <svl/zforlist.hxx>
+
+#include <dbdocutl.hxx>
+#include <document.hxx>
+#include <formula/errorcodes.hxx>
+#include <stringutil.hxx>
+
+using namespace ::com::sun::star;
+
+ScDatabaseDocUtil::StrData::StrData() :
+ mbSimpleText(true), mnStrLength(0)
+{
+}
+
+void ScDatabaseDocUtil::PutData(ScDocument& rDoc, SCCOL nCol, SCROW nRow, SCTAB nTab,
+ const uno::Reference<sdbc::XRow>& xRow, sal_Int32 nRowPos,
+ tools::Long nType, bool bCurrency, StrData* pStrData)
+{
+ OUString aString;
+ double nVal = 0.0;
+ bool bValue = false;
+ bool bEmptyFlag = false;
+ bool bError = false;
+ sal_uLong nFormatIndex = 0;
+
+ // wasNull calls only if null value was found?
+
+ try
+ {
+ switch ( nType )
+ {
+ case sdbc::DataType::BIT:
+ case sdbc::DataType::BOOLEAN:
+ //TODO: use language from doc (here, date/time and currency)?
+ nFormatIndex = rDoc.GetFormatTable()->GetStandardFormat(
+ SvNumFormatType::LOGICAL, ScGlobal::eLnge );
+ nVal = (xRow->getBoolean(nRowPos) ? 1 : 0);
+ bEmptyFlag = ( nVal == 0.0 ) && xRow->wasNull();
+ bValue = true;
+ break;
+
+ case sdbc::DataType::TINYINT:
+ case sdbc::DataType::SMALLINT:
+ case sdbc::DataType::INTEGER:
+ case sdbc::DataType::BIGINT:
+ case sdbc::DataType::FLOAT:
+ case sdbc::DataType::REAL:
+ case sdbc::DataType::DOUBLE:
+ case sdbc::DataType::NUMERIC:
+ case sdbc::DataType::DECIMAL:
+ //TODO: do the conversion here?
+ nVal = xRow->getDouble(nRowPos);
+ bEmptyFlag = ( nVal == 0.0 ) && xRow->wasNull();
+ bValue = true;
+ break;
+
+ case sdbc::DataType::CHAR:
+ case sdbc::DataType::VARCHAR:
+ case sdbc::DataType::LONGVARCHAR:
+ aString = xRow->getString(nRowPos);
+ bEmptyFlag = ( aString.isEmpty() ) && xRow->wasNull();
+ break;
+
+ case sdbc::DataType::DATE:
+ {
+ util::Date aDate = xRow->getDate(nRowPos);
+ bEmptyFlag = xRow->wasNull();
+ if (bEmptyFlag)
+ nVal = 0.0;
+ else
+ {
+ SvNumberFormatter* pFormTable = rDoc.GetFormatTable();
+ nFormatIndex = pFormTable->GetStandardFormat(
+ SvNumFormatType::DATE, ScGlobal::eLnge );
+ nVal = Date( aDate ) - pFormTable->GetNullDate();
+ }
+ bValue = true;
+ }
+ break;
+
+ case sdbc::DataType::TIME:
+ {
+ SvNumberFormatter* pFormTable = rDoc.GetFormatTable();
+ nFormatIndex = pFormTable->GetStandardFormat(
+ SvNumFormatType::TIME, ScGlobal::eLnge );
+
+ util::Time aTime = xRow->getTime(nRowPos);
+ nVal = aTime.Hours / static_cast<double>(::tools::Time::hourPerDay) +
+ aTime.Minutes / static_cast<double>(::tools::Time::minutePerDay) +
+ aTime.Seconds / static_cast<double>(::tools::Time::secondPerDay) +
+ aTime.NanoSeconds / static_cast<double>(::tools::Time::nanoSecPerDay);
+ bEmptyFlag = xRow->wasNull();
+ bValue = true;
+ }
+ break;
+
+ case sdbc::DataType::TIMESTAMP:
+ {
+ SvNumberFormatter* pFormTable = rDoc.GetFormatTable();
+ nFormatIndex = pFormTable->GetStandardFormat(
+ SvNumFormatType::DATETIME, ScGlobal::eLnge );
+
+ util::DateTime aStamp = xRow->getTimestamp(nRowPos);
+ if (aStamp.Year != 0)
+ {
+ nVal = ( Date( aStamp.Day, aStamp.Month, aStamp.Year ) -
+ pFormTable->GetNullDate() ) +
+ aStamp.Hours / static_cast<double>(::tools::Time::hourPerDay) +
+ aStamp.Minutes / static_cast<double>(::tools::Time::minutePerDay) +
+ aStamp.Seconds / static_cast<double>(::tools::Time::secondPerDay) +
+ aStamp.NanoSeconds / static_cast<double>(::tools::Time::nanoSecPerDay);
+ bEmptyFlag = xRow->wasNull();
+ bValue = true;
+ }
+ }
+ break;
+
+ case sdbc::DataType::SQLNULL:
+ bEmptyFlag = true;
+ break;
+
+ case sdbc::DataType::BINARY:
+ case sdbc::DataType::VARBINARY:
+ case sdbc::DataType::LONGVARBINARY:
+ default:
+ bError = true; // unknown type
+ }
+ }
+ catch ( uno::Exception& )
+ {
+ bError = true;
+ }
+
+ if ( bValue && bCurrency )
+ nFormatIndex = rDoc.GetFormatTable()->GetStandardFormat(
+ SvNumFormatType::CURRENCY, ScGlobal::eLnge );
+
+ ScAddress aPos(nCol, nRow, nTab);
+ if (bEmptyFlag)
+ rDoc.SetEmptyCell(aPos);
+ else if (bError)
+ {
+ rDoc.SetError( nCol, nRow, nTab, FormulaError::NotAvailable );
+ }
+ else if (bValue)
+ {
+ rDoc.SetValue(aPos, nVal);
+ if (nFormatIndex)
+ rDoc.SetNumberFormat(aPos, nFormatIndex);
+ }
+ else
+ {
+ if (!aString.isEmpty())
+ {
+ if (ScStringUtil::isMultiline(aString))
+ {
+ rDoc.SetEditText(aPos, aString);
+ if (pStrData)
+ pStrData->mbSimpleText = false;
+ }
+ else
+ {
+ ScSetStringParam aParam;
+ aParam.setTextInput();
+ rDoc.SetString(aPos, aString, &aParam);
+ if (pStrData)
+ pStrData->mbSimpleText = true;
+ }
+
+ if (pStrData)
+ pStrData->mnStrLength = aString.getLength();
+ }
+ else
+ rDoc.SetEmptyCell(aPos);
+ }
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sc/source/core/data/dociter.cxx b/sc/source/core/data/dociter.cxx
new file mode 100644
index 000000000..38a4a218e
--- /dev/null
+++ b/sc/source/core/data/dociter.cxx
@@ -0,0 +1,1779 @@
+/* -*- 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 <svl/numformat.hxx>
+#include <svl/zforlist.hxx>
+
+#include <global.hxx>
+#include <dociter.hxx>
+#include <document.hxx>
+#include <table.hxx>
+#include <column.hxx>
+#include <formulacell.hxx>
+#include <attarray.hxx>
+#include <patattr.hxx>
+#include <docoptio.hxx>
+#include <cellform.hxx>
+#include <segmenttree.hxx>
+#include <progress.hxx>
+#include <queryparam.hxx>
+#include <queryentry.hxx>
+#include <globstr.hrc>
+#include <scresid.hxx>
+#include <cellvalue.hxx>
+#include <scmatrix.hxx>
+#include <rowheightcontext.hxx>
+#include <queryevaluator.hxx>
+
+#include <o3tl/safeint.hxx>
+#include <tools/fract.hxx>
+#include <editeng/editobj.hxx>
+#include <svl/sharedstring.hxx>
+#include <unotools/collatorwrapper.hxx>
+#include <osl/diagnose.h>
+#include <sal/log.hxx>
+
+#include <algorithm>
+#include <limits>
+#include <vector>
+
+using ::rtl::math::approxEqual;
+using ::std::vector;
+using ::std::set;
+
+// iterators have very high frequency use -> custom debug.
+// #define debugiter(...) fprintf(stderr, __VA_ARGS__)
+#define debugiter(...)
+
+static void ScAttrArray_IterGetNumberFormat( sal_uInt32& nFormat, const ScAttrArray*& rpArr,
+ SCROW& nAttrEndRow, const ScAttrArray* pNewArr, SCROW nRow,
+ const ScDocument& rDoc, const ScInterpreterContext* pContext = nullptr )
+{
+ if ( rpArr == pNewArr && nAttrEndRow >= nRow )
+ return;
+
+ SCROW nRowStart = 0;
+ SCROW nRowEnd = rDoc.MaxRow();
+ const ScPatternAttr* pPattern = pNewArr->GetPatternRange( nRowStart, nRowEnd, nRow );
+ if( !pPattern )
+ {
+ pPattern = rDoc.GetDefPattern();
+ nRowEnd = rDoc.MaxRow();
+ }
+
+ nFormat = pPattern->GetNumberFormat( pContext ? pContext->GetFormatTable() : rDoc.GetFormatTable() );
+ rpArr = pNewArr;
+ nAttrEndRow = nRowEnd;
+}
+
+ScValueIterator::ScValueIterator(ScInterpreterContext& rContext, ScDocument& rDocument, const ScRange& rRange,
+ SubtotalFlags nSubTotalFlags, bool bTextZero )
+ : mrDoc(rDocument)
+ , mrContext(rContext)
+ , pAttrArray(nullptr)
+ , nNumFormat(0) // Initialized in GetNumberFormat
+ , nNumFmtIndex(0)
+ , maStartPos(rRange.aStart)
+ , maEndPos(rRange.aEnd)
+ , mnCol(0)
+ , mnTab(0)
+ , nAttrEndRow(0)
+ , mnSubTotalFlags(nSubTotalFlags)
+ , nNumFmtType(SvNumFormatType::UNDEFINED)
+ , bNumValid(false)
+ , bCalcAsShown(rDocument.GetDocOptions().IsCalcAsShown())
+ , bTextAsZero(bTextZero)
+ , mpCells(nullptr)
+{
+ SCTAB nDocMaxTab = rDocument.GetTableCount() - 1;
+
+ if (!rDocument.ValidCol(maStartPos.Col())) maStartPos.SetCol(mrDoc.MaxCol());
+ if (!rDocument.ValidCol(maEndPos.Col())) maEndPos.SetCol(mrDoc.MaxCol());
+ if (!rDocument.ValidRow(maStartPos.Row())) maStartPos.SetRow(mrDoc.MaxRow());
+ if (!rDocument.ValidRow(maEndPos.Row())) maEndPos.SetRow(mrDoc.MaxRow());
+ if (!ValidTab(maStartPos.Tab()) || maStartPos.Tab() > nDocMaxTab) maStartPos.SetTab(nDocMaxTab);
+ if (!ValidTab(maEndPos.Tab()) || maEndPos.Tab() > nDocMaxTab) maEndPos.SetTab(nDocMaxTab);
+}
+
+SCROW ScValueIterator::GetRow() const
+{
+ // Position of the head of the current block + offset within the block
+ // equals the logical element position.
+ return maCurPos.first->position + maCurPos.second;
+}
+
+void ScValueIterator::IncBlock()
+{
+ ++maCurPos.first;
+ maCurPos.second = 0;
+}
+
+void ScValueIterator::IncPos()
+{
+ if (maCurPos.second + 1 < maCurPos.first->size)
+ // Move within the same block.
+ ++maCurPos.second;
+ else
+ // Move to the next block.
+ IncBlock();
+}
+
+bool ScValueIterator::GetThis(double& rValue, FormulaError& rErr)
+{
+ while (true)
+ {
+ bool bNextColumn = !mpCells || maCurPos.first == mpCells->end();
+ if (!bNextColumn)
+ {
+ if (GetRow() > maEndPos.Row())
+ bNextColumn = true;
+ }
+
+ ScColumn* pCol;
+ if (!bNextColumn)
+ pCol = &(mrDoc.maTabs[mnTab])->aCol[mnCol];
+ else
+ {
+ // Find the next available column.
+ do
+ {
+ ++mnCol;
+ while (mnCol > maEndPos.Col() || mnCol >= mrDoc.maTabs[mnTab]->GetAllocatedColumnsCount())
+ {
+ mnCol = maStartPos.Col();
+ ++mnTab;
+ if (mnTab > maEndPos.Tab())
+ {
+ rErr = FormulaError::NONE;
+ return false;
+ }
+ }
+ pCol = &(mrDoc.maTabs[mnTab])->aCol[mnCol];
+ }
+ while (pCol->IsEmptyData());
+
+ mpCells = &pCol->maCells;
+ maCurPos = mpCells->position(maStartPos.Row());
+ }
+
+ SCROW nCurRow = GetRow();
+ SCROW nLastRow;
+ // Skip all filtered or hidden rows, depending on mnSubTotalFlags
+ if ( ( ( mnSubTotalFlags & SubtotalFlags::IgnoreFiltered ) &&
+ mrDoc.RowFiltered( nCurRow, mnTab, nullptr, &nLastRow ) ) ||
+ ( ( mnSubTotalFlags & SubtotalFlags::IgnoreHidden ) &&
+ mrDoc.RowHidden( nCurRow, mnTab, nullptr, &nLastRow ) ) )
+ {
+ maCurPos = mpCells->position(maCurPos.first, nLastRow+1);
+ continue;
+ }
+
+ switch (maCurPos.first->type)
+ {
+ case sc::element_type_numeric:
+ {
+ bNumValid = false;
+ rValue = sc::numeric_block::at(*maCurPos.first->data, maCurPos.second);
+ rErr = FormulaError::NONE;
+ if (bCalcAsShown)
+ {
+ ScAttrArray_IterGetNumberFormat(nNumFormat, pAttrArray,
+ nAttrEndRow, pCol->pAttrArray.get(), nCurRow, mrDoc, &mrContext);
+ rValue = mrDoc.RoundValueAsShown(rValue, nNumFormat, &mrContext);
+ }
+ return true; // Found it!
+ }
+ break;
+ case sc::element_type_formula:
+ {
+ ScFormulaCell& rCell = *sc::formula_block::at(*maCurPos.first->data, maCurPos.second);
+ if ( ( mnSubTotalFlags & SubtotalFlags::IgnoreNestedStAg ) && rCell.IsSubTotal() )
+ {
+ // Skip subtotal formula cells.
+ IncPos();
+ break;
+ }
+
+ if (rCell.GetErrorOrValue(rErr, rValue))
+ {
+ if ( rErr != FormulaError::NONE && ( mnSubTotalFlags & SubtotalFlags::IgnoreErrVal ) )
+ {
+ IncPos();
+ break;
+ }
+ bNumValid = false;
+ return true; // Found it!
+ }
+ else if (bTextAsZero)
+ {
+ rValue = 0.0;
+ bNumValid = false;
+ return true;
+ }
+ IncPos();
+ }
+ break;
+ case sc::element_type_string :
+ case sc::element_type_edittext :
+ {
+ if (bTextAsZero)
+ {
+ rErr = FormulaError::NONE;
+ rValue = 0.0;
+ nNumFmtType = SvNumFormatType::NUMBER;
+ nNumFmtIndex = 0;
+ bNumValid = true;
+ return true;
+ }
+ IncBlock();
+ }
+ break;
+ case sc::element_type_empty:
+ default:
+ // Skip the whole block.
+ IncBlock();
+ }
+ }
+}
+
+void ScValueIterator::GetCurNumFmtInfo( const ScInterpreterContext& rContext, SvNumFormatType& nType, sal_uInt32& nIndex )
+{
+ if (!bNumValid && mnTab < mrDoc.GetTableCount())
+ {
+ SCROW nCurRow = GetRow();
+ const ScColumn* pCol = &(mrDoc.maTabs[mnTab])->aCol[mnCol];
+ nNumFmtIndex = pCol->GetNumberFormat(rContext, nCurRow);
+ nNumFmtType = rContext.GetNumberFormatType( nNumFmtIndex );
+ bNumValid = true;
+ }
+
+ nType = nNumFmtType;
+ nIndex = nNumFmtIndex;
+}
+
+bool ScValueIterator::GetFirst(double& rValue, FormulaError& rErr)
+{
+ mnCol = maStartPos.Col();
+ mnTab = maStartPos.Tab();
+
+ ScTable* pTab = mrDoc.FetchTable(mnTab);
+ if (!pTab)
+ return false;
+
+ nNumFormat = 0; // Initialized in GetNumberFormat
+ pAttrArray = nullptr;
+ nAttrEndRow = 0;
+
+ auto nCol = maStartPos.Col();
+ if (nCol < pTab->GetAllocatedColumnsCount())
+ {
+ mpCells = &pTab->aCol[nCol].maCells;
+ maCurPos = mpCells->position(maStartPos.Row());
+ }
+ else
+ mpCells = nullptr;
+ return GetThis(rValue, rErr);
+}
+
+bool ScValueIterator::GetNext(double& rValue, FormulaError& rErr)
+{
+ IncPos();
+ return GetThis(rValue, rErr);
+}
+
+ScDBQueryDataIterator::DataAccess::DataAccess()
+{
+}
+
+ScDBQueryDataIterator::DataAccess::~DataAccess()
+{
+}
+
+const sc::CellStoreType* ScDBQueryDataIterator::GetColumnCellStore(ScDocument& rDoc, SCTAB nTab, SCCOL nCol)
+{
+ ScTable* pTab = rDoc.FetchTable(nTab);
+ if (!pTab)
+ return nullptr;
+ if (nCol >= pTab->GetAllocatedColumnsCount())
+ return nullptr;
+ return &pTab->aCol[nCol].maCells;
+}
+
+const ScAttrArray* ScDBQueryDataIterator::GetAttrArrayByCol(ScDocument& rDoc, SCTAB nTab, SCCOL nCol)
+{
+ assert(nTab < rDoc.GetTableCount() && "index out of bounds, FIX IT");
+ ScColumn* pCol = &rDoc.maTabs[nTab]->aCol[nCol];
+ return pCol->pAttrArray.get();
+}
+
+bool ScDBQueryDataIterator::IsQueryValid(
+ ScDocument& rDoc, const ScQueryParam& rParam, SCTAB nTab, SCROW nRow, const ScRefCellValue* pCell)
+{
+ assert(nTab < rDoc.GetTableCount() && "index out of bounds, FIX IT");
+ ScQueryEvaluator queryEvaluator(rDoc, *rDoc.maTabs[nTab], rParam);
+ return queryEvaluator.ValidQuery(nRow, pCell);
+}
+
+ScDBQueryDataIterator::DataAccessInternal::DataAccessInternal(ScDBQueryParamInternal* pParam, ScDocument& rDoc, const ScInterpreterContext& rContext)
+ : mpCells(nullptr)
+ , mpParam(pParam)
+ , mrDoc(rDoc)
+ , mrContext(rContext)
+ , pAttrArray(nullptr)
+ , nNumFormat(0) // Initialized in GetNumberFormat
+ , nNumFmtIndex(0)
+ , nCol(mpParam->mnField)
+ , nRow(mpParam->nRow1)
+ , nAttrEndRow(0)
+ , nTab(mpParam->nTab)
+ , nNumFmtType(SvNumFormatType::ALL)
+ , bCalcAsShown(rDoc.GetDocOptions().IsCalcAsShown())
+{
+ SCSIZE i;
+ SCSIZE nCount = mpParam->GetEntryCount();
+ for (i=0; (i<nCount) && (mpParam->GetEntry(i).bDoQuery); i++)
+ {
+ ScQueryEntry& rEntry = mpParam->GetEntry(i);
+ ScQueryEntry::QueryItemsType& rItems = rEntry.GetQueryItems();
+ rItems.resize(1);
+ ScQueryEntry::Item& rItem = rItems.front();
+ sal_uInt32 nIndex = 0;
+ bool bNumber = mrDoc.GetFormatTable()->IsNumberFormat(
+ rItem.maString.getString(), nIndex, rItem.mfVal);
+ rItem.meType = bNumber ? ScQueryEntry::ByValue : ScQueryEntry::ByString;
+ }
+}
+
+ScDBQueryDataIterator::DataAccessInternal::~DataAccessInternal()
+{
+}
+
+bool ScDBQueryDataIterator::DataAccessInternal::getCurrent(Value& rValue)
+{
+ // Start with the current row position, and find the first row position
+ // that satisfies the query.
+
+ // If the query starts in the same column as the result vector we can
+ // prefetch the cell which saves us one fetch in the success case.
+ SCCOLROW nFirstQueryField = mpParam->GetEntry(0).nField;
+ ScRefCellValue aCell;
+
+ while (true)
+ {
+ if (maCurPos.first == mpCells->end() || nRow > mpParam->nRow2)
+ {
+ // Bottom of the range reached. Bail out.
+ rValue.mnError = FormulaError::NONE;
+ return false;
+ }
+
+ if (maCurPos.first->type == sc::element_type_empty)
+ {
+ // Skip the whole empty block.
+ incBlock();
+ continue;
+ }
+
+ ScRefCellValue* pCell = nullptr;
+ if (nCol == static_cast<SCCOL>(nFirstQueryField))
+ {
+ aCell = sc::toRefCell(maCurPos.first, maCurPos.second);
+ pCell = &aCell;
+ }
+
+ if (ScDBQueryDataIterator::IsQueryValid(mrDoc, *mpParam, nTab, nRow, pCell))
+ {
+ if (!pCell)
+ aCell = sc::toRefCell(maCurPos.first, maCurPos.second);
+ switch (aCell.meType)
+ {
+ case CELLTYPE_VALUE:
+ {
+ rValue.mfValue = aCell.mfValue;
+ rValue.mbIsNumber = true;
+ if ( bCalcAsShown )
+ {
+ const ScAttrArray* pNewAttrArray =
+ ScDBQueryDataIterator::GetAttrArrayByCol(mrDoc, nTab, nCol);
+ ScAttrArray_IterGetNumberFormat( nNumFormat, pAttrArray,
+ nAttrEndRow, pNewAttrArray, nRow, mrDoc );
+ rValue.mfValue = mrDoc.RoundValueAsShown( rValue.mfValue, nNumFormat );
+ }
+ nNumFmtType = SvNumFormatType::NUMBER;
+ nNumFmtIndex = 0;
+ rValue.mnError = FormulaError::NONE;
+ return true; // Found it!
+ }
+
+ case CELLTYPE_FORMULA:
+ {
+ if (aCell.mpFormula->IsValue())
+ {
+ rValue.mfValue = aCell.mpFormula->GetValue();
+ rValue.mbIsNumber = true;
+ mrDoc.GetNumberFormatInfo(
+ mrContext, nNumFmtType, nNumFmtIndex, ScAddress(nCol, nRow, nTab));
+ rValue.mnError = aCell.mpFormula->GetErrCode();
+ return true; // Found it!
+ }
+ else if(mpParam->mbSkipString)
+ incPos();
+ else
+ {
+ rValue.maString = aCell.mpFormula->GetString().getString();
+ rValue.mfValue = 0.0;
+ rValue.mnError = aCell.mpFormula->GetErrCode();
+ rValue.mbIsNumber = false;
+ return true;
+ }
+ }
+ break;
+ case CELLTYPE_STRING:
+ case CELLTYPE_EDIT:
+ if (mpParam->mbSkipString)
+ incPos();
+ else
+ {
+ rValue.maString = aCell.getString(&mrDoc);
+ rValue.mfValue = 0.0;
+ rValue.mnError = FormulaError::NONE;
+ rValue.mbIsNumber = false;
+ return true;
+ }
+ break;
+ default:
+ incPos();
+ }
+ }
+ else
+ incPos();
+ }
+// statement unreachable
+}
+
+bool ScDBQueryDataIterator::DataAccessInternal::getFirst(Value& rValue)
+{
+ if (mpParam->bHasHeader)
+ ++nRow;
+
+ mpCells = ScDBQueryDataIterator::GetColumnCellStore(mrDoc, nTab, nCol);
+ if (!mpCells)
+ return false;
+
+ maCurPos = mpCells->position(nRow);
+ return getCurrent(rValue);
+}
+
+bool ScDBQueryDataIterator::DataAccessInternal::getNext(Value& rValue)
+{
+ if (!mpCells || maCurPos.first == mpCells->end())
+ return false;
+
+ incPos();
+ return getCurrent(rValue);
+}
+
+void ScDBQueryDataIterator::DataAccessInternal::incBlock()
+{
+ ++maCurPos.first;
+ maCurPos.second = 0;
+
+ nRow = maCurPos.first->position;
+}
+
+void ScDBQueryDataIterator::DataAccessInternal::incPos()
+{
+ if (maCurPos.second + 1 < maCurPos.first->size)
+ {
+ // Move within the same block.
+ ++maCurPos.second;
+ ++nRow;
+ }
+ else
+ // Move to the next block.
+ incBlock();
+}
+
+ScDBQueryDataIterator::DataAccessMatrix::DataAccessMatrix(ScDBQueryParamMatrix* pParam)
+ : mpParam(pParam)
+ , mnCurRow(0)
+{
+ SCSIZE nC, nR;
+ mpParam->mpMatrix->GetDimensions(nC, nR);
+ mnRows = static_cast<SCROW>(nR);
+}
+
+ScDBQueryDataIterator::DataAccessMatrix::~DataAccessMatrix()
+{
+}
+
+bool ScDBQueryDataIterator::DataAccessMatrix::getCurrent(Value& rValue)
+{
+ // Starting from row == mnCurRow, get the first row that satisfies all the
+ // query parameters.
+ for ( ;mnCurRow < mnRows; ++mnCurRow)
+ {
+ const ScMatrix& rMat = *mpParam->mpMatrix;
+ if (rMat.IsEmpty(mpParam->mnField, mnCurRow))
+ // Don't take empty values into account.
+ continue;
+
+ bool bIsStrVal = rMat.IsStringOrEmpty(mpParam->mnField, mnCurRow);
+ if (bIsStrVal && mpParam->mbSkipString)
+ continue;
+
+ if (isValidQuery(mnCurRow, rMat))
+ {
+ rValue.maString = rMat.GetString(mpParam->mnField, mnCurRow).getString();
+ rValue.mfValue = rMat.GetDouble(mpParam->mnField, mnCurRow);
+ rValue.mbIsNumber = !bIsStrVal;
+ rValue.mnError = FormulaError::NONE;
+ return true;
+ }
+ }
+ return false;
+}
+
+bool ScDBQueryDataIterator::DataAccessMatrix::getFirst(Value& rValue)
+{
+ mnCurRow = mpParam->bHasHeader ? 1 : 0;
+ return getCurrent(rValue);
+}
+
+bool ScDBQueryDataIterator::DataAccessMatrix::getNext(Value& rValue)
+{
+ ++mnCurRow;
+ return getCurrent(rValue);
+}
+
+namespace {
+
+bool isQueryByValue(const ScQueryEntry::Item& rItem, const ScMatrix& rMat, SCSIZE nCol, SCSIZE nRow)
+{
+ if (rItem.meType == ScQueryEntry::ByString)
+ return false;
+
+ if (!rMat.IsValueOrEmpty(nCol, nRow))
+ return false;
+
+ return true;
+}
+
+bool isQueryByString(const ScQueryEntry& rEntry, const ScQueryEntry::Item& rItem, const ScMatrix& rMat, SCSIZE nCol, SCSIZE nRow)
+{
+ switch (rEntry.eOp)
+ {
+ case SC_EQUAL:
+ case SC_NOT_EQUAL:
+ case SC_CONTAINS:
+ case SC_DOES_NOT_CONTAIN:
+ case SC_BEGINS_WITH:
+ case SC_ENDS_WITH:
+ case SC_DOES_NOT_BEGIN_WITH:
+ case SC_DOES_NOT_END_WITH:
+ return true;
+ default:
+ ;
+ }
+
+ return rItem.meType == ScQueryEntry::ByString && rMat.IsStringOrEmpty(nCol, nRow);
+}
+
+}
+
+bool ScDBQueryDataIterator::DataAccessMatrix::isValidQuery(SCROW nRow, const ScMatrix& rMat) const
+{
+ SCSIZE nEntryCount = mpParam->GetEntryCount();
+ vector<bool> aResults;
+ aResults.reserve(nEntryCount);
+
+ const CollatorWrapper& rCollator = ScGlobal::GetCollator(mpParam->bCaseSens);
+
+ for (SCSIZE i = 0; i < nEntryCount; ++i)
+ {
+ const ScQueryEntry& rEntry = mpParam->GetEntry(i);
+ const ScQueryEntry::Item& rItem = rEntry.GetQueryItem();
+ if (!rEntry.bDoQuery)
+ continue;
+
+ switch (rEntry.eOp)
+ {
+ case SC_EQUAL:
+ case SC_LESS:
+ case SC_GREATER:
+ case SC_LESS_EQUAL:
+ case SC_GREATER_EQUAL:
+ case SC_NOT_EQUAL:
+ break;
+ default:
+ // Only the above operators are supported.
+ SAL_WARN("sc.core", "Unsupported operator " << rEntry.eOp
+ << " in ScDBQueryDataIterator::DataAccessMatrix::isValidQuery()");
+ continue;
+ }
+
+ bool bValid = false;
+
+ SCSIZE nField = static_cast<SCSIZE>(rEntry.nField);
+ if (isQueryByValue(rItem, rMat, nField, nRow))
+ {
+ // By value
+ double fMatVal = rMat.GetDouble(nField, nRow);
+ bool bEqual = approxEqual(fMatVal, rItem.mfVal);
+ switch (rEntry.eOp)
+ {
+ case SC_EQUAL:
+ bValid = bEqual;
+ break;
+ case SC_LESS:
+ bValid = (fMatVal < rItem.mfVal) && !bEqual;
+ break;
+ case SC_GREATER:
+ bValid = (fMatVal > rItem.mfVal) && !bEqual;
+ break;
+ case SC_LESS_EQUAL:
+ bValid = (fMatVal < rItem.mfVal) || bEqual;
+ break;
+ case SC_GREATER_EQUAL:
+ bValid = (fMatVal > rItem.mfVal) || bEqual;
+ break;
+ case SC_NOT_EQUAL:
+ bValid = !bEqual;
+ break;
+ default:
+ ;
+ }
+ }
+ else if (isQueryByString(rEntry, rItem, rMat, nField, nRow))
+ {
+ // By string
+ do
+ {
+ // Equality check first.
+ svl::SharedString aMatStr = rMat.GetString(nField, nRow);
+ svl::SharedString aQueryStr = rEntry.GetQueryItem().maString;
+ bool bDone = false;
+ rtl_uString* p1 = mpParam->bCaseSens ? aMatStr.getData() : aMatStr.getDataIgnoreCase();
+ rtl_uString* p2 = mpParam->bCaseSens ? aQueryStr.getData() : aQueryStr.getDataIgnoreCase();
+ switch (rEntry.eOp)
+ {
+ case SC_EQUAL:
+ bValid = (p1 == p2);
+ bDone = true;
+ break;
+ case SC_NOT_EQUAL:
+ bValid = (p1 != p2);
+ bDone = true;
+ break;
+ default:
+ ;
+ }
+
+ if (bDone)
+ break;
+
+ // Unequality check using collator.
+ sal_Int32 nCompare = rCollator.compareString(aMatStr.getString(), aQueryStr.getString());
+ switch (rEntry.eOp)
+ {
+ case SC_LESS :
+ bValid = (nCompare < 0);
+ break;
+ case SC_GREATER :
+ bValid = (nCompare > 0);
+ break;
+ case SC_LESS_EQUAL :
+ bValid = (nCompare <= 0);
+ break;
+ case SC_GREATER_EQUAL :
+ bValid = (nCompare >= 0);
+ break;
+ default:
+ ;
+ }
+ }
+ while (false);
+ }
+
+ if (aResults.empty())
+ // First query entry.
+ aResults.push_back(bValid);
+ else if (rEntry.eConnect == SC_AND)
+ {
+ // For AND op, tuck the result into the last result value.
+ size_t n = aResults.size();
+ aResults[n-1] = aResults[n-1] && bValid;
+ }
+ else
+ // For OR op, store its own result.
+ aResults.push_back(bValid);
+ }
+
+ // Row is valid as long as there is at least one result being true.
+ return std::find(aResults.begin(), aResults.end(), true) != aResults.end();
+}
+
+ScDBQueryDataIterator::Value::Value()
+ : mfValue(std::numeric_limits<double>::quiet_NaN())
+ , mnError(FormulaError::NONE), mbIsNumber(true)
+{
+}
+
+ScDBQueryDataIterator::ScDBQueryDataIterator(ScDocument& rDocument, const ScInterpreterContext& rContext, std::unique_ptr<ScDBQueryParamBase> pParam) :
+ mpParam (std::move(pParam))
+{
+ switch (mpParam->GetType())
+ {
+ case ScDBQueryParamBase::INTERNAL:
+ {
+ ScDBQueryParamInternal* p = static_cast<ScDBQueryParamInternal*>(mpParam.get());
+ mpData.reset(new DataAccessInternal(p, rDocument, rContext));
+ }
+ break;
+ case ScDBQueryParamBase::MATRIX:
+ {
+ ScDBQueryParamMatrix* p = static_cast<ScDBQueryParamMatrix*>(mpParam.get());
+ mpData.reset(new DataAccessMatrix(p));
+ }
+ }
+}
+
+bool ScDBQueryDataIterator::GetFirst(Value& rValue)
+{
+ return mpData->getFirst(rValue);
+}
+
+bool ScDBQueryDataIterator::GetNext(Value& rValue)
+{
+ return mpData->getNext(rValue);
+}
+
+ScFormulaGroupIterator::ScFormulaGroupIterator( ScDocument& rDoc ) :
+ mrDoc(rDoc),
+ mnTab(0),
+ mnCol(0),
+ mnIndex(0)
+{
+ ScTable *pTab = mrDoc.FetchTable(mnTab);
+ ScColumn *pCol = pTab ? pTab->FetchColumn(mnCol) : nullptr;
+ if (pCol)
+ {
+ mbNullCol = false;
+ maEntries = pCol->GetFormulaGroupEntries();
+ }
+ else
+ mbNullCol = true;
+}
+
+sc::FormulaGroupEntry* ScFormulaGroupIterator::first()
+{
+ return next();
+}
+
+sc::FormulaGroupEntry* ScFormulaGroupIterator::next()
+{
+ if (mnIndex >= maEntries.size() || mbNullCol)
+ {
+ while (mnIndex >= maEntries.size() || mbNullCol)
+ {
+ mnIndex = 0;
+ mnCol++;
+ if (mnCol > mrDoc.MaxCol())
+ {
+ mnCol = 0;
+ mnTab++;
+ if (mnTab >= mrDoc.GetTableCount())
+ return nullptr;
+ }
+ ScTable *pTab = mrDoc.FetchTable(mnTab);
+ ScColumn *pCol = pTab ? pTab->FetchColumn(mnCol) : nullptr;
+ if (pCol)
+ {
+ mbNullCol = false;
+ maEntries = pCol->GetFormulaGroupEntries();
+ }
+ else
+ mbNullCol = true;
+ }
+ }
+
+ return &maEntries[mnIndex++];
+}
+
+ScCellIterator::ScCellIterator( ScDocument& rDoc, const ScRange& rRange, SubtotalFlags nSubTotalFlags ) :
+ mrDoc(rDoc),
+ maStartPos(rRange.aStart),
+ maEndPos(rRange.aEnd),
+ mnSubTotalFlags(nSubTotalFlags)
+{
+ init();
+}
+
+void ScCellIterator::incBlock()
+{
+ ++maCurColPos.first;
+ maCurColPos.second = 0;
+
+ maCurPos.SetRow(maCurColPos.first->position);
+}
+
+void ScCellIterator::incPos()
+{
+ if (maCurColPos.second + 1 < maCurColPos.first->size)
+ {
+ // Move within the same block.
+ ++maCurColPos.second;
+ maCurPos.IncRow();
+ }
+ else
+ // Move to the next block.
+ incBlock();
+}
+
+void ScCellIterator::setPos(size_t nPos)
+{
+ maCurColPos = getColumn()->maCells.position(maCurColPos.first, nPos);
+ maCurPos.SetRow(nPos);
+}
+
+const ScColumn* ScCellIterator::getColumn() const
+{
+ return &mrDoc.maTabs[maCurPos.Tab()]->aCol[maCurPos.Col()];
+}
+
+void ScCellIterator::init()
+{
+ SCTAB nDocMaxTab = mrDoc.GetTableCount() - 1;
+
+ PutInOrder(maStartPos, maEndPos);
+
+ if (!mrDoc.ValidCol(maStartPos.Col())) maStartPos.SetCol(mrDoc.MaxCol());
+ if (!mrDoc.ValidCol(maEndPos.Col())) maEndPos.SetCol(mrDoc.MaxCol());
+ if (!mrDoc.ValidRow(maStartPos.Row())) maStartPos.SetRow(mrDoc.MaxRow());
+ if (!mrDoc.ValidRow(maEndPos.Row())) maEndPos.SetRow(mrDoc.MaxRow());
+ if (!ValidTab(maStartPos.Tab(), nDocMaxTab)) maStartPos.SetTab(nDocMaxTab);
+ if (!ValidTab(maEndPos.Tab(), nDocMaxTab)) maEndPos.SetTab(nDocMaxTab);
+
+ while (maEndPos.Tab() > 0 && !mrDoc.maTabs[maEndPos.Tab()])
+ maEndPos.IncTab(-1); // Only the tables in use
+
+ if (maStartPos.Tab() > maEndPos.Tab())
+ maStartPos.SetTab(maEndPos.Tab());
+
+ if (!mrDoc.maTabs[maStartPos.Tab()])
+ {
+ assert(!"Table not found");
+ maStartPos = ScAddress(mrDoc.MaxCol()+1, mrDoc.MaxRow()+1, MAXTAB+1); // -> Abort on GetFirst.
+ }
+ else
+ {
+ maStartPos.SetCol(mrDoc.maTabs[maStartPos.Tab()]->ClampToAllocatedColumns(maStartPos.Col()));
+ }
+
+ maCurPos = maStartPos;
+}
+
+bool ScCellIterator::getCurrent()
+{
+ const ScColumn* pCol = getColumn();
+
+ while (true)
+ {
+ bool bNextColumn = maCurColPos.first == pCol->maCells.end();
+ if (!bNextColumn)
+ {
+ if (maCurPos.Row() > maEndPos.Row())
+ bNextColumn = true;
+ }
+
+ if (bNextColumn)
+ {
+ // Move to the next column.
+ maCurPos.SetRow(maStartPos.Row());
+ do
+ {
+ maCurPos.IncCol();
+ while (maCurPos.Col() >= mrDoc.GetAllocatedColumnsCount(maCurPos.Tab())
+ || maCurPos.Col() > maEndPos.Col())
+ {
+ maCurPos.SetCol(maStartPos.Col());
+ maCurPos.IncTab();
+ if (maCurPos.Tab() > maEndPos.Tab())
+ {
+ maCurCell.clear();
+ return false;
+ }
+ }
+ pCol = getColumn();
+ }
+ while (pCol->IsEmptyData());
+
+ maCurColPos = pCol->maCells.position(maCurPos.Row());
+ }
+
+ if (maCurColPos.first->type == sc::element_type_empty)
+ {
+ incBlock();
+ continue;
+ }
+
+ SCROW nLastRow;
+ // Skip all filtered or hidden rows, depending on mSubTotalFlags
+ if ( ( ( mnSubTotalFlags & SubtotalFlags::IgnoreFiltered ) &&
+ pCol->GetDoc().RowFiltered(maCurPos.Row(), maCurPos.Tab(), nullptr, &nLastRow) ) ||
+ ( ( mnSubTotalFlags & SubtotalFlags::IgnoreHidden ) &&
+ pCol->GetDoc().RowHidden(maCurPos.Row(), maCurPos.Tab(), nullptr, &nLastRow) ) )
+ {
+ setPos(nLastRow+1);
+ continue;
+ }
+
+ if (maCurColPos.first->type == sc::element_type_formula)
+ {
+ if ( mnSubTotalFlags != SubtotalFlags::NONE )
+ {
+ ScFormulaCell* pCell = sc::formula_block::at(*maCurColPos.first->data, maCurColPos.second);
+ // Skip formula cells with Subtotal formulae or errors, depending on mnSubTotalFlags
+ if ( ( ( mnSubTotalFlags & SubtotalFlags::IgnoreNestedStAg ) && pCell->IsSubTotal() ) ||
+ ( ( mnSubTotalFlags & SubtotalFlags::IgnoreErrVal ) && pCell->GetErrCode() != FormulaError::NONE ) )
+ {
+ incPos();
+ continue;
+ }
+ }
+ }
+
+ maCurCell = sc::toRefCell(maCurColPos.first, maCurColPos.second);
+ return true;
+ }
+ return false;
+}
+
+OUString ScCellIterator::getString() const
+{
+ return maCurCell.getString(&mrDoc);
+}
+
+ScCellValue ScCellIterator::getCellValue() const
+{
+ ScCellValue aRet;
+ aRet.meType = maCurCell.meType;
+
+ switch (maCurCell.meType)
+ {
+ case CELLTYPE_STRING:
+ aRet.mpString = new svl::SharedString(*maCurCell.mpString);
+ break;
+ case CELLTYPE_EDIT:
+ aRet.mpEditText = maCurCell.mpEditText->Clone().release();
+ break;
+ case CELLTYPE_VALUE:
+ aRet.mfValue = maCurCell.mfValue;
+ break;
+ case CELLTYPE_FORMULA:
+ aRet.mpFormula = maCurCell.mpFormula->Clone();
+ break;
+ default:
+ ;
+ }
+
+ return aRet;
+}
+
+bool ScCellIterator::hasString() const
+{
+ return maCurCell.hasString();
+}
+
+bool ScCellIterator::isEmpty() const
+{
+ return maCurCell.isEmpty();
+}
+
+bool ScCellIterator::equalsWithoutFormat( const ScAddress& rPos ) const
+{
+ ScRefCellValue aOther(mrDoc, rPos);
+ return maCurCell.equalsWithoutFormat(aOther);
+}
+
+bool ScCellIterator::first()
+{
+ if (!ValidTab(maCurPos.Tab()))
+ return false;
+
+ maCurPos = maStartPos;
+ const ScColumn* pCol = getColumn();
+
+ maCurColPos = pCol->maCells.position(maCurPos.Row());
+ return getCurrent();
+}
+
+bool ScCellIterator::next()
+{
+ incPos();
+ return getCurrent();
+}
+
+ScHorizontalCellIterator::ScHorizontalCellIterator(ScDocument& rDocument, SCTAB nTable,
+ SCCOL nCol1, SCROW nRow1, SCCOL nCol2, SCROW nRow2 ) :
+ rDoc( rDocument ),
+ mnTab( nTable ),
+ nStartCol( nCol1 ),
+ nEndCol( nCol2 ),
+ nStartRow( nRow1 ),
+ nEndRow( nRow2 ),
+ mnCol( nCol1 ),
+ mnRow( nRow1 ),
+ mbMore( false )
+{
+ assert(mnTab < rDoc.GetTableCount() && "index out of bounds, FIX IT");
+
+ nEndCol = rDoc.maTabs[mnTab]->ClampToAllocatedColumns(nEndCol);
+ if (nEndCol < nStartCol) // E.g., somewhere completely outside allocated area
+ nEndCol = nStartCol - 1; // Empty
+
+ maColPositions.reserve( nEndCol-nStartCol+1 );
+
+ SetTab( mnTab );
+}
+
+ScHorizontalCellIterator::~ScHorizontalCellIterator()
+{
+}
+
+void ScHorizontalCellIterator::SetTab( SCTAB nTabP )
+{
+ mbMore = false;
+ mnTab = nTabP;
+ mnRow = nStartRow;
+ mnCol = nStartCol;
+ maColPositions.resize(0);
+
+ // Set the start position in each column.
+ for (SCCOL i = nStartCol; i <= nEndCol; ++i)
+ {
+ ScColumn* pCol = &rDoc.maTabs[mnTab]->aCol[i];
+ ColParam aParam;
+ aParam.maPos = pCol->maCells.position(nStartRow).first;
+ aParam.maEnd = pCol->maCells.end();
+ aParam.mnCol = i;
+
+ // find first non-empty element.
+ while (aParam.maPos != aParam.maEnd) {
+ if (aParam.maPos->type == sc::element_type_empty)
+ ++aParam.maPos;
+ else
+ {
+ maColPositions.push_back( aParam );
+ break;
+ }
+ }
+ }
+
+ if (maColPositions.empty())
+ return;
+
+ maColPos = maColPositions.begin();
+ mbMore = true;
+ SkipInvalid();
+}
+
+ScRefCellValue* ScHorizontalCellIterator::GetNext( SCCOL& rCol, SCROW& rRow )
+{
+ if (!mbMore)
+ {
+ debugiter("no more !\n");
+ return nullptr;
+ }
+
+ // Return the current non-empty cell, and move the cursor to the next one.
+ ColParam& r = *maColPos;
+
+ rCol = mnCol = r.mnCol;
+ rRow = mnRow;
+ debugiter("return col %d row %d\n", (int)rCol, (int)rRow);
+
+ size_t nOffset = static_cast<size_t>(mnRow) - r.maPos->position;
+ maCurCell = sc::toRefCell(r.maPos, nOffset);
+ Advance();
+ debugiter("advance to: col %d row %d\n", (int)maColPos->mnCol, (int)mnRow);
+
+ return &maCurCell;
+}
+
+bool ScHorizontalCellIterator::GetPos( SCCOL& rCol, SCROW& rRow )
+{
+ rCol = mnCol;
+ rRow = mnRow;
+ return mbMore;
+}
+
+// Skip any invalid / empty cells across the current row,
+// we only advance the cursor if the current entry is invalid.
+// if we return true we have a valid cursor (or hit the end)
+bool ScHorizontalCellIterator::SkipInvalidInRow()
+{
+ assert (mbMore);
+ assert (maColPos != maColPositions.end());
+
+ // Find the next non-empty cell in the current row.
+ while( maColPos != maColPositions.end() )
+ {
+ ColParam& r = *maColPos;
+ assert (r.maPos != r.maEnd);
+
+ size_t nRow = static_cast<size_t>(mnRow);
+
+ if (nRow >= r.maPos->position)
+ {
+ if (nRow < r.maPos->position + r.maPos->size)
+ {
+ mnCol = maColPos->mnCol;
+ debugiter("found valid cell at column %d, row %d\n",
+ (int)mnCol, (int)mnRow);
+ assert(r.maPos->type != sc::element_type_empty);
+ return true;
+ }
+ else
+ {
+ bool bMoreBlocksInColumn = false;
+ // This block is behind the current row position. Advance the block.
+ for (++r.maPos; r.maPos != r.maEnd; ++r.maPos)
+ {
+ if (nRow < r.maPos->position + r.maPos->size &&
+ r.maPos->type != sc::element_type_empty)
+ {
+ bMoreBlocksInColumn = true;
+ break;
+ }
+ }
+ if (!bMoreBlocksInColumn)
+ {
+ debugiter("remove column %d at row %d\n",
+ (int)maColPos->mnCol, (int)nRow);
+ maColPos = maColPositions.erase(maColPos);
+ if (maColPositions.empty())
+ {
+ debugiter("no more columns\n");
+ mbMore = false;
+ }
+ }
+ else
+ {
+ debugiter("advanced column %d to block starting row %d, retrying\n",
+ (int)maColPos->mnCol, r.maPos->position);
+ }
+ }
+ }
+ else
+ {
+ debugiter("skip empty cells at column %d, row %d\n",
+ (int)maColPos->mnCol, (int)nRow);
+ ++maColPos;
+ }
+ }
+
+ // No more columns with anything interesting in them ?
+ if (maColPositions.empty())
+ {
+ debugiter("no more live columns left - done\n");
+ mbMore = false;
+ return true;
+ }
+
+ return false;
+}
+
+/// Find the next row that has some real content in one of its columns.
+SCROW ScHorizontalCellIterator::FindNextNonEmptyRow()
+{
+ size_t nNextRow = rDoc.MaxRow()+1;
+
+ for (const ColParam& r : maColPositions)
+ {
+ assert(o3tl::make_unsigned(mnRow) <= r.maPos->position);
+ nNextRow = std::min (nNextRow, static_cast<size_t>(r.maPos->position));
+ }
+
+ SCROW nRow = std::max(static_cast<SCROW>(nNextRow), mnRow);
+ debugiter("Next non empty row is %d\n", (int) nRow);
+ return nRow;
+}
+
+void ScHorizontalCellIterator::Advance()
+{
+ assert (mbMore);
+ assert (maColPos != maColPositions.end());
+
+ ++maColPos;
+
+ SkipInvalid();
+}
+
+void ScHorizontalCellIterator::SkipInvalid()
+{
+ if (maColPos == maColPositions.end() ||
+ !SkipInvalidInRow())
+ {
+ mnRow++;
+
+ if (mnRow > nEndRow)
+ {
+ mbMore = false;
+ return;
+ }
+
+ maColPos = maColPositions.begin();
+ debugiter("moving to next row\n");
+ if (SkipInvalidInRow())
+ {
+ debugiter("moved to valid cell in next row (or end)\n");
+ return;
+ }
+
+ mnRow = FindNextNonEmptyRow();
+ maColPos = maColPositions.begin();
+ bool bCorrect = SkipInvalidInRow();
+ assert (bCorrect); (void) bCorrect;
+ }
+
+ if (mnRow > nEndRow)
+ mbMore = false;
+}
+
+ScHorizontalValueIterator::ScHorizontalValueIterator( ScDocument& rDocument,
+ const ScRange& rRange ) :
+ rDoc( rDocument ),
+ nEndTab( rRange.aEnd.Tab() ),
+ bCalcAsShown( rDocument.GetDocOptions().IsCalcAsShown() )
+{
+ SCCOL nStartCol = rRange.aStart.Col();
+ SCROW nStartRow = rRange.aStart.Row();
+ SCTAB nStartTab = rRange.aStart.Tab();
+ SCCOL nEndCol = rRange.aEnd.Col();
+ SCROW nEndRow = rRange.aEnd.Row();
+ PutInOrder( nStartCol, nEndCol);
+ PutInOrder( nStartRow, nEndRow);
+ PutInOrder( nStartTab, nEndTab );
+
+ if (!rDoc.ValidCol(nStartCol)) nStartCol = rDoc.MaxCol();
+ if (!rDoc.ValidCol(nEndCol)) nEndCol = rDoc.MaxCol();
+ if (!rDoc.ValidRow(nStartRow)) nStartRow = rDoc.MaxRow();
+ if (!rDoc.ValidRow(nEndRow)) nEndRow = rDoc.MaxRow();
+ if (!ValidTab(nStartTab)) nStartTab = MAXTAB;
+ if (!ValidTab(nEndTab)) nEndTab = MAXTAB;
+
+ nCurCol = nStartCol;
+ nCurRow = nStartRow;
+ nCurTab = nStartTab;
+
+ nNumFormat = 0; // Will be initialized in GetNumberFormat()
+ pAttrArray = nullptr;
+ nAttrEndRow = 0;
+
+ pCellIter.reset( new ScHorizontalCellIterator( rDoc, nStartTab, nStartCol,
+ nStartRow, nEndCol, nEndRow ) );
+}
+
+ScHorizontalValueIterator::~ScHorizontalValueIterator()
+{
+}
+
+bool ScHorizontalValueIterator::GetNext( double& rValue, FormulaError& rErr )
+{
+ bool bFound = false;
+ while ( !bFound )
+ {
+ ScRefCellValue* pCell = pCellIter->GetNext( nCurCol, nCurRow );
+ while ( !pCell )
+ {
+ if ( nCurTab < nEndTab )
+ {
+ pCellIter->SetTab( ++nCurTab);
+ pCell = pCellIter->GetNext( nCurCol, nCurRow );
+ }
+ else
+ return false;
+ }
+ switch (pCell->meType)
+ {
+ case CELLTYPE_VALUE:
+ {
+ rValue = pCell->mfValue;
+ rErr = FormulaError::NONE;
+ if ( bCalcAsShown )
+ {
+ ScColumn* pCol = &rDoc.maTabs[nCurTab]->aCol[nCurCol];
+ ScAttrArray_IterGetNumberFormat( nNumFormat, pAttrArray,
+ nAttrEndRow, pCol->pAttrArray.get(), nCurRow, rDoc );
+ rValue = rDoc.RoundValueAsShown( rValue, nNumFormat );
+ }
+ bFound = true;
+ }
+ break;
+ case CELLTYPE_FORMULA:
+ {
+ rErr = pCell->mpFormula->GetErrCode();
+ if (rErr != FormulaError::NONE || pCell->mpFormula->IsValue())
+ {
+ rValue = pCell->mpFormula->GetValue();
+ bFound = true;
+ }
+ }
+ break;
+ case CELLTYPE_STRING :
+ case CELLTYPE_EDIT :
+ break;
+ default: ; // nothing
+ }
+ }
+ return bFound;
+}
+
+ScHorizontalAttrIterator::ScHorizontalAttrIterator( ScDocument& rDocument, SCTAB nTable,
+ SCCOL nCol1, SCROW nRow1, SCCOL nCol2, SCROW nRow2 ) :
+ rDoc( rDocument ),
+ nTab( nTable ),
+ nStartCol( nCol1 ),
+ nStartRow( nRow1 ),
+ nEndCol( nCol2 ),
+ nEndRow( nRow2 )
+{
+ assert(nTab < rDoc.GetTableCount() && "index out of bounds, FIX IT");
+ assert(rDoc.maTabs[nTab]);
+
+ nRow = nStartRow;
+ nCol = nStartCol;
+
+ pIndices.reset( new SCSIZE[nEndCol-nStartCol+1] );
+ pNextEnd.reset( new SCROW[nEndCol-nStartCol+1] );
+ pHorizEnd.reset( new SCCOL[nEndCol-nStartCol+1] );
+ ppPatterns.reset( new const ScPatternAttr*[nEndCol-nStartCol+1] );
+
+ InitForNextRow(true);
+}
+
+ScHorizontalAttrIterator::~ScHorizontalAttrIterator()
+{
+}
+
+void ScHorizontalAttrIterator::InitForNextRow(bool bInitialization)
+{
+ nMinNextEnd = rDoc.MaxRow();
+ SCCOL nThisHead = 0;
+
+ for (SCCOL i=nStartCol; i<=nEndCol; i++)
+ {
+ SCCOL nPos = i - nStartCol;
+ if ( bInitialization || pNextEnd[nPos] < nRow )
+ {
+ const ScAttrArray& pArray = rDoc.maTabs[nTab]->ColumnData(i).AttrArray();
+
+ SCSIZE nIndex;
+ if (bInitialization)
+ {
+ if ( pArray.Count() )
+ pArray.Search( nStartRow, nIndex );
+ else
+ nIndex = 0;
+ pIndices[nPos] = nIndex;
+ pHorizEnd[nPos] = rDoc.MaxCol()+1; // only for assert()
+ }
+ else
+ nIndex = ++pIndices[nPos];
+
+ if ( !nIndex && !pArray.Count() )
+ {
+ pNextEnd[nPos] = rDoc.MaxRow();
+ assert( pNextEnd[nPos] >= nRow && "Sequence out of order" );
+ ppPatterns[nPos] = rDoc.GetDefPattern();
+ }
+ else if ( nIndex < pArray.Count() )
+ {
+ const ScPatternAttr* pPattern = pArray.mvData[nIndex].pPattern;
+ SCROW nThisEnd = pArray.mvData[nIndex].nEndRow;
+ pNextEnd[nPos] = nThisEnd;
+ assert( pNextEnd[nPos] >= nRow && "Sequence out of order" );
+ ppPatterns[nPos] = pPattern;
+ }
+ else
+ {
+ assert(!"AttrArray does not range to MAXROW");
+ pNextEnd[nPos] = rDoc.MaxRow();
+ ppPatterns[nPos] = nullptr;
+ }
+ }
+
+ if ( nMinNextEnd > pNextEnd[nPos] )
+ nMinNextEnd = pNextEnd[nPos];
+
+ // store positions of ScHorizontalAttrIterator elements (minimizing expensive ScPatternAttr comparisons)
+ if (i > nStartCol && ppPatterns[nThisHead] != ppPatterns[nPos])
+ {
+ pHorizEnd[nThisHead] = i - 1;
+ nThisHead = nPos; // start position of the next horizontal group
+ }
+ }
+
+ pHorizEnd[nThisHead] = nEndCol; // set the end position of the last horizontal group, too
+}
+
+const ScPatternAttr* ScHorizontalAttrIterator::GetNext( SCCOL& rCol1, SCCOL& rCol2, SCROW& rRow )
+{
+ assert(nTab < rDoc.GetTableCount() && "index out of bounds, FIX IT");
+ for (;;)
+ {
+ if ( nCol <= nEndCol )
+ {
+ const ScPatternAttr* pPat = ppPatterns[nCol-nStartCol];
+ rRow = nRow;
+ rCol1 = nCol;
+ assert( pHorizEnd[nCol-nStartCol] < rDoc.MaxCol()+1 && "missing stored data" );
+ nCol = pHorizEnd[nCol-nStartCol];
+ rCol2 = nCol;
+ ++nCol; // Count up for next call
+ return pPat; // Found it!
+ }
+
+ // Next row
+ ++nRow;
+ if ( nRow > nEndRow ) // Already at the end?
+ return nullptr; // Found nothing
+ nCol = nStartCol; // Start at the left again
+
+ if ( nRow > nMinNextEnd )
+ InitForNextRow(false);
+ }
+}
+
+static bool IsGreater( SCCOL nCol1, SCROW nRow1, SCCOL nCol2, SCROW nRow2 )
+{
+ return ( nRow1 > nRow2 ) || ( nRow1 == nRow2 && nCol1 > nCol2 );
+}
+
+ScUsedAreaIterator::ScUsedAreaIterator( ScDocument& rDocument, SCTAB nTable,
+ SCCOL nCol1, SCROW nRow1, SCCOL nCol2, SCROW nRow2 )
+ : aCellIter( rDocument, nTable, nCol1, nRow1, nCol2, nRow2 )
+ , aAttrIter( rDocument, nTable, nCol1, nRow1, nCol2, nRow2 )
+ , nNextCol( nCol1 )
+ , nNextRow( nRow1 )
+ , nCellCol( 0 )
+ , nCellRow( 0 )
+ , nAttrCol1( 0 )
+ , nAttrCol2( 0 )
+ , nAttrRow( 0 )
+ , nFoundStartCol( 0 )
+ , nFoundEndCol( 0 )
+ , nFoundRow( 0 )
+ , pFoundPattern( nullptr )
+{
+ pCell = aCellIter.GetNext( nCellCol, nCellRow );
+ pPattern = aAttrIter.GetNext( nAttrCol1, nAttrCol2, nAttrRow );
+}
+
+ScUsedAreaIterator::~ScUsedAreaIterator()
+{
+}
+
+bool ScUsedAreaIterator::GetNext()
+{
+ // Forward iterators
+ if ( pCell && IsGreater( nNextCol, nNextRow, nCellCol, nCellRow ) )
+ pCell = aCellIter.GetNext( nCellCol, nCellRow );
+
+ while (pCell && pCell->isEmpty())
+ pCell = aCellIter.GetNext( nCellCol, nCellRow );
+
+ if ( pPattern && IsGreater( nNextCol, nNextRow, nAttrCol2, nAttrRow ) )
+ pPattern = aAttrIter.GetNext( nAttrCol1, nAttrCol2, nAttrRow );
+
+ if ( pPattern && nAttrRow == nNextRow && nAttrCol1 < nNextCol )
+ nAttrCol1 = nNextCol;
+
+ // Find next area
+ bool bFound = true;
+ bool bUseCell = false;
+
+ if ( pCell && pPattern )
+ {
+ if ( IsGreater( nCellCol, nCellRow, nAttrCol1, nAttrRow ) ) // Only attributes at the beginning?
+ {
+ maFoundCell.clear();
+ pFoundPattern = pPattern;
+ nFoundRow = nAttrRow;
+ nFoundStartCol = nAttrCol1;
+ if ( nCellRow == nAttrRow && nCellCol <= nAttrCol2 ) // Area also contains cell?
+ nFoundEndCol = nCellCol - 1; // Only until right before the cell
+ else
+ nFoundEndCol = nAttrCol2; // Everything
+ }
+ else
+ {
+ bUseCell = true;
+ if ( nAttrRow == nCellRow && nAttrCol1 == nCellCol ) // Attributes on the cell?
+ pFoundPattern = pPattern;
+ else
+ pFoundPattern = nullptr;
+ }
+ }
+ else if ( pCell ) // Just a cell -> take over right away
+ {
+ pFoundPattern = nullptr;
+ bUseCell = true; // Cell position
+ }
+ else if ( pPattern ) // Just attributes -> take over right away
+ {
+ maFoundCell.clear();
+ pFoundPattern = pPattern;
+ nFoundRow = nAttrRow;
+ nFoundStartCol = nAttrCol1;
+ nFoundEndCol = nAttrCol2;
+ }
+ else // Nothing
+ bFound = false;
+
+ if ( bUseCell ) // Cell position
+ {
+ if (pCell)
+ maFoundCell = *pCell;
+ else
+ maFoundCell.clear();
+
+ nFoundRow = nCellRow;
+ nFoundStartCol = nFoundEndCol = nCellCol;
+ }
+
+ if (bFound)
+ {
+ nNextRow = nFoundRow;
+ nNextCol = nFoundEndCol + 1;
+ }
+
+ return bFound;
+}
+
+ScDocAttrIterator::ScDocAttrIterator(ScDocument& rDocument, SCTAB nTable,
+ SCCOL nCol1, SCROW nRow1,
+ SCCOL nCol2, SCROW nRow2) :
+ rDoc( rDocument ),
+ nTab( nTable ),
+ nEndCol( nCol2 ),
+ nStartRow( nRow1 ),
+ nEndRow( nRow2 ),
+ nCol( nCol1 )
+{
+ if ( ValidTab(nTab) && nTab < rDoc.GetTableCount() && rDoc.maTabs[nTab] )
+ pColIter = rDoc.maTabs[nTab]->ColumnData(nCol).CreateAttrIterator( nStartRow, nEndRow );
+}
+
+ScDocAttrIterator::~ScDocAttrIterator()
+{
+}
+
+const ScPatternAttr* ScDocAttrIterator::GetNext( SCCOL& rCol, SCROW& rRow1, SCROW& rRow2 )
+{
+ while ( pColIter )
+ {
+ const ScPatternAttr* pPattern = pColIter->Next( rRow1, rRow2 );
+ if ( pPattern )
+ {
+ rCol = nCol;
+ return pPattern;
+ }
+
+ ++nCol;
+ if ( nCol <= nEndCol )
+ pColIter = rDoc.maTabs[nTab]->ColumnData(nCol).CreateAttrIterator( nStartRow, nEndRow );
+ else
+ pColIter.reset();
+ }
+ return nullptr; // Nothing anymore
+}
+
+ScDocRowHeightUpdater::TabRanges::TabRanges(SCTAB nTab, SCROW nMaxRow) :
+ mnTab(nTab), maRanges(nMaxRow)
+{
+}
+
+ScDocRowHeightUpdater::ScDocRowHeightUpdater(ScDocument& rDoc, OutputDevice* pOutDev, double fPPTX, double fPPTY, const vector<TabRanges>* pTabRangesArray) :
+ mrDoc(rDoc), mpOutDev(pOutDev), mfPPTX(fPPTX), mfPPTY(fPPTY), mpTabRangesArray(pTabRangesArray)
+{
+}
+
+void ScDocRowHeightUpdater::update()
+{
+ if (!mpTabRangesArray || mpTabRangesArray->empty())
+ {
+ // No ranges defined. Update all rows in all tables.
+ updateAll();
+ return;
+ }
+
+ sal_uInt64 nCellCount = 0;
+ for (const auto& rTabRanges : *mpTabRangesArray)
+ {
+ const SCTAB nTab = rTabRanges.mnTab;
+ if (!ValidTab(nTab) || nTab >= mrDoc.GetTableCount() || !mrDoc.maTabs[nTab])
+ continue;
+
+ ScFlatBoolRowSegments::RangeData aData;
+ ScFlatBoolRowSegments::RangeIterator aRangeItr(rTabRanges.maRanges);
+ for (bool bFound = aRangeItr.getFirst(aData); bFound; bFound = aRangeItr.getNext(aData))
+ {
+ if (!aData.mbValue)
+ continue;
+
+ nCellCount += mrDoc.maTabs[nTab]->GetWeightedCount(aData.mnRow1, aData.mnRow2);
+ }
+ }
+
+ ScProgress aProgress(mrDoc.GetDocumentShell(), ScResId(STR_PROGRESS_HEIGHTING), nCellCount, true);
+
+ Fraction aZoom(1, 1);
+ sal_uInt64 nProgressStart = 0;
+ for (const auto& rTabRanges : *mpTabRangesArray)
+ {
+ const SCTAB nTab = rTabRanges.mnTab;
+ if (!ValidTab(nTab) || nTab >= mrDoc.GetTableCount() || !mrDoc.maTabs[nTab])
+ continue;
+
+ sc::RowHeightContext aCxt(mrDoc.MaxRow(), mfPPTX, mfPPTY, aZoom, aZoom, mpOutDev);
+ ScFlatBoolRowSegments::RangeData aData;
+ ScFlatBoolRowSegments::RangeIterator aRangeItr(rTabRanges.maRanges);
+ for (bool bFound = aRangeItr.getFirst(aData); bFound; bFound = aRangeItr.getNext(aData))
+ {
+ if (!aData.mbValue)
+ continue;
+
+ mrDoc.maTabs[nTab]->SetOptimalHeight(
+ aCxt, aData.mnRow1, aData.mnRow2, true, &aProgress, nProgressStart);
+
+ nProgressStart += mrDoc.maTabs[nTab]->GetWeightedCount(aData.mnRow1, aData.mnRow2);
+ }
+ }
+}
+
+void ScDocRowHeightUpdater::updateAll()
+{
+ sal_uInt64 nCellCount = 0;
+ for (SCTAB nTab = 0; nTab < mrDoc.GetTableCount(); ++nTab)
+ {
+ if (!ValidTab(nTab) || !mrDoc.maTabs[nTab])
+ continue;
+
+ nCellCount += mrDoc.maTabs[nTab]->GetWeightedCount();
+ }
+
+ ScProgress aProgress(mrDoc.GetDocumentShell(), ScResId(STR_PROGRESS_HEIGHTING), nCellCount, true);
+
+ Fraction aZoom(1, 1);
+ sc::RowHeightContext aCxt(mrDoc.MaxRow(), mfPPTX, mfPPTY, aZoom, aZoom, mpOutDev);
+ sal_uInt64 nProgressStart = 0;
+ for (SCTAB nTab = 0; nTab < mrDoc.GetTableCount(); ++nTab)
+ {
+ if (!ValidTab(nTab) || !mrDoc.maTabs[nTab])
+ continue;
+
+ mrDoc.maTabs[nTab]->SetOptimalHeight(aCxt, 0, mrDoc.MaxRow(), true, &aProgress, nProgressStart);
+ nProgressStart += mrDoc.maTabs[nTab]->GetWeightedCount();
+ }
+}
+
+ScAttrRectIterator::ScAttrRectIterator(ScDocument& rDocument, SCTAB nTable,
+ SCCOL nCol1, SCROW nRow1,
+ SCCOL nCol2, SCROW nRow2) :
+ rDoc( rDocument ),
+ nTab( nTable ),
+ nEndCol( nCol2 ),
+ nStartRow( nRow1 ),
+ nEndRow( nRow2 ),
+ nIterStartCol( nCol1 ),
+ nIterEndCol( nCol1 )
+{
+ if ( ValidTab(nTab) && nTab < rDoc.GetTableCount() && rDoc.maTabs[nTab] )
+ {
+ pColIter = rDoc.maTabs[nTab]->ColumnData(nIterStartCol).CreateAttrIterator( nStartRow, nEndRow );
+ while ( nIterEndCol < nEndCol &&
+ rDoc.maTabs[nTab]->ColumnData(nIterEndCol).IsAllAttrEqual(
+ rDoc.maTabs[nTab]->ColumnData(nIterEndCol+1), nStartRow, nEndRow ) )
+ ++nIterEndCol;
+ }
+}
+
+ScAttrRectIterator::~ScAttrRectIterator()
+{
+}
+
+void ScAttrRectIterator::DataChanged()
+{
+ if (pColIter)
+ {
+ SCROW nNextRow = pColIter->GetNextRow();
+ pColIter = rDoc.maTabs[nTab]->ColumnData(nIterStartCol).CreateAttrIterator( nNextRow, nEndRow );
+ }
+}
+
+const ScPatternAttr* ScAttrRectIterator::GetNext( SCCOL& rCol1, SCCOL& rCol2,
+ SCROW& rRow1, SCROW& rRow2 )
+{
+ while ( pColIter )
+ {
+ const ScPatternAttr* pPattern = pColIter->Next( rRow1, rRow2 );
+ if ( pPattern )
+ {
+ rCol1 = nIterStartCol;
+ rCol2 = nIterEndCol;
+ return pPattern;
+ }
+
+ nIterStartCol = nIterEndCol+1;
+ if ( nIterStartCol <= nEndCol )
+ {
+ nIterEndCol = nIterStartCol;
+ pColIter = rDoc.maTabs[nTab]->ColumnData(nIterStartCol).CreateAttrIterator( nStartRow, nEndRow );
+ while ( nIterEndCol < nEndCol &&
+ rDoc.maTabs[nTab]->ColumnData(nIterEndCol).IsAllAttrEqual(
+ rDoc.maTabs[nTab]->ColumnData(nIterEndCol+1), nStartRow, nEndRow ) )
+ ++nIterEndCol;
+ }
+ else
+ pColIter.reset();
+ }
+ return nullptr; // Nothing anymore
+}
+
+ScRowBreakIterator::ScRowBreakIterator(set<SCROW>& rBreaks) :
+ mrBreaks(rBreaks),
+ maItr(rBreaks.begin()), maEnd(rBreaks.end())
+{
+}
+
+SCROW ScRowBreakIterator::first()
+{
+ maItr = mrBreaks.begin();
+ return maItr == maEnd ? NOT_FOUND : *maItr;
+}
+
+SCROW ScRowBreakIterator::next()
+{
+ ++maItr;
+ return maItr == maEnd ? NOT_FOUND : *maItr;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sc/source/core/data/docparam.cxx b/sc/source/core/data/docparam.cxx
new file mode 100644
index 000000000..3e2dea1b6
--- /dev/null
+++ b/sc/source/core/data/docparam.cxx
@@ -0,0 +1,25 @@
+/* -*- 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 <docparam.hxx>
+
+ScColWidthParam::ScColWidthParam() :
+ mnMaxTextRow(-1), mnMaxTextLen(0), mbSimpleText(true) {}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sc/source/core/data/docpool.cxx b/sc/source/core/data/docpool.cxx
new file mode 100644
index 000000000..e0a07582e
--- /dev/null
+++ b/sc/source/core/data/docpool.cxx
@@ -0,0 +1,619 @@
+/* -*- 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 <sal/config.h>
+
+#include <scitems.hxx>
+
+#include <comphelper/string.hxx>
+#include <i18nutil/unicode.hxx>
+#include <vcl/outdev.hxx>
+#include <vcl/svapp.hxx>
+#include <vcl/settings.hxx>
+#include <svl/itemiter.hxx>
+#include <svl/stritem.hxx>
+#include <svx/algitem.hxx>
+#include <editeng/boxitem.hxx>
+#include <editeng/lineitem.hxx>
+#include <editeng/brushitem.hxx>
+#include <editeng/charreliefitem.hxx>
+#include <editeng/contouritem.hxx>
+#include <editeng/colritem.hxx>
+#include <editeng/crossedoutitem.hxx>
+#include <editeng/emphasismarkitem.hxx>
+#include <editeng/fhgtitem.hxx>
+#include <editeng/fontitem.hxx>
+#include <editeng/forbiddenruleitem.hxx>
+#include <editeng/frmdiritem.hxx>
+#include <editeng/hngpnctitem.hxx>
+#include <editeng/itemtype.hxx>
+#include <editeng/editrids.hrc>
+#include <editeng/eerdll.hxx>
+#include <editeng/langitem.hxx>
+#include <editeng/lrspitem.hxx>
+#include <svx/pageitem.hxx>
+#include <editeng/pbinitem.hxx>
+#include <editeng/postitem.hxx>
+#include <svx/rotmodit.hxx>
+#include <editeng/scriptspaceitem.hxx>
+#include <editeng/shaditem.hxx>
+#include <editeng/shdditem.hxx>
+#include <editeng/sizeitem.hxx>
+#include <editeng/udlnitem.hxx>
+#include <editeng/ulspitem.hxx>
+#include <editeng/wghtitem.hxx>
+#include <editeng/wrlmitem.hxx>
+#include <editeng/xmlcnitm.hxx>
+#include <editeng/justifyitem.hxx>
+
+#include <docpool.hxx>
+#include <global.hxx>
+#include <attrib.hxx>
+#include <patattr.hxx>
+#include <globstr.hrc>
+#include <scresid.hxx>
+#include <scmod.hxx>
+#include <sc.hrc>
+
+// ATTR_FONT_TWOLINES (not used) was changed to ATTR_USERDEF (not saved in binary format) in 641c
+
+namespace {
+
+SvxFontItem* getDefaultFontItem(LanguageType eLang, DefaultFontType nFontType, sal_uInt16 nItemId)
+{
+ vcl::Font aDefFont = OutputDevice::GetDefaultFont( nFontType, eLang, GetDefaultFontFlags::OnlyOne );
+ SvxFontItem* pNewItem = new SvxFontItem( aDefFont.GetFamilyType(), aDefFont.GetFamilyName(), aDefFont.GetStyleName(),
+ aDefFont.GetPitch(), aDefFont.GetCharSet(), nItemId );
+
+ return pNewItem;
+}
+
+}
+
+SfxItemInfo const aItemInfos[] =
+{
+ { SID_ATTR_CHAR_FONT, true }, // ATTR_FONT
+ { SID_ATTR_CHAR_FONTHEIGHT, true }, // ATTR_FONT_HEIGHT
+ { SID_ATTR_CHAR_WEIGHT, true }, // ATTR_FONT_WEIGHT
+ { SID_ATTR_CHAR_POSTURE, true }, // ATTR_FONT_POSTURE
+ { SID_ATTR_CHAR_UNDERLINE, true }, // ATTR_FONT_UNDERLINE
+ { SID_ATTR_CHAR_OVERLINE, true }, // ATTR_FONT_OVERLINE
+ { SID_ATTR_CHAR_STRIKEOUT, true }, // ATTR_FONT_CROSSEDOUT
+ { SID_ATTR_CHAR_CONTOUR, true }, // ATTR_FONT_CONTOUR
+ { SID_ATTR_CHAR_SHADOWED, true }, // ATTR_FONT_SHADOWED
+ { SID_ATTR_CHAR_COLOR, true }, // ATTR_FONT_COLOR
+ { SID_ATTR_CHAR_LANGUAGE, true }, // ATTR_FONT_LANGUAGE
+ { SID_ATTR_CHAR_CJK_FONT, true }, // ATTR_CJK_FONT from 614
+ { SID_ATTR_CHAR_CJK_FONTHEIGHT, true }, // ATTR_CJK_FONT_HEIGHT from 614
+ { SID_ATTR_CHAR_CJK_WEIGHT, true }, // ATTR_CJK_FONT_WEIGHT from 614
+ { SID_ATTR_CHAR_CJK_POSTURE, true }, // ATTR_CJK_FONT_POSTURE from 614
+ { SID_ATTR_CHAR_CJK_LANGUAGE, true }, // ATTR_CJK_FONT_LANGUAGE from 614
+ { SID_ATTR_CHAR_CTL_FONT, true }, // ATTR_CTL_FONT from 614
+ { SID_ATTR_CHAR_CTL_FONTHEIGHT, true }, // ATTR_CTL_FONT_HEIGHT from 614
+ { SID_ATTR_CHAR_CTL_WEIGHT, true }, // ATTR_CTL_FONT_WEIGHT from 614
+ { SID_ATTR_CHAR_CTL_POSTURE, true }, // ATTR_CTL_FONT_POSTURE from 614
+ { SID_ATTR_CHAR_CTL_LANGUAGE, true }, // ATTR_CTL_FONT_LANGUAGE from 614
+ { SID_ATTR_CHAR_EMPHASISMARK, true }, // ATTR_FONT_EMPHASISMARK from 614
+ { 0, true }, // ATTR_USERDEF from 614 / 641c
+ { SID_ATTR_CHAR_WORDLINEMODE, true }, // ATTR_FONT_WORDLINE from 632b
+ { SID_ATTR_CHAR_RELIEF, true }, // ATTR_FONT_RELIEF from 632b
+ { SID_ATTR_ALIGN_HYPHENATION, true }, // ATTR_HYPHENATE from 632b
+ { 0, true }, // ATTR_SCRIPTSPACE from 614d
+ { 0, true }, // ATTR_HANGPUNCTUATION from 614d
+ { SID_ATTR_PARA_FORBIDDEN_RULES,true }, // ATTR_FORBIDDEN_RULES from 614d
+ { SID_ATTR_ALIGN_HOR_JUSTIFY, true }, // ATTR_HOR_JUSTIFY
+ { SID_ATTR_ALIGN_HOR_JUSTIFY_METHOD, true }, // ATTR_HOR_JUSTIFY_METHOD
+ { SID_ATTR_ALIGN_INDENT, true }, // ATTR_INDENT from 350
+ { SID_ATTR_ALIGN_VER_JUSTIFY, true }, // ATTR_VER_JUSTIFY
+ { SID_ATTR_ALIGN_VER_JUSTIFY_METHOD, true }, // ATTR_VER_JUSTIFY_METHOD
+ { SID_ATTR_ALIGN_STACKED, true }, // ATTR_STACKED from 680/dr14 (replaces ATTR_ORIENTATION)
+ { SID_ATTR_ALIGN_DEGREES, true }, // ATTR_ROTATE_VALUE from 367
+ { SID_ATTR_ALIGN_LOCKPOS, true }, // ATTR_ROTATE_MODE from 367
+ { SID_ATTR_ALIGN_ASIANVERTICAL, true }, // ATTR_VERTICAL_ASIAN from 642
+ { SID_ATTR_FRAMEDIRECTION, true }, // ATTR_WRITINGDIR from 643
+ { SID_ATTR_ALIGN_LINEBREAK, true }, // ATTR_LINEBREAK
+ { SID_ATTR_ALIGN_SHRINKTOFIT, true }, // ATTR_SHRINKTOFIT from 680/dr14
+ { SID_ATTR_BORDER_DIAG_TLBR, true }, // ATTR_BORDER_TLBR from 680/dr14
+ { SID_ATTR_BORDER_DIAG_BLTR, true }, // ATTR_BORDER_BLTR from 680/dr14
+ { SID_ATTR_ALIGN_MARGIN, true }, // ATTR_MARGIN
+ { 0, true }, // ATTR_MERGE
+ { 0, true }, // ATTR_MERGE_FLAG
+ { SID_ATTR_NUMBERFORMAT_VALUE, true }, // ATTR_VALUE_FORMAT
+ { 0, true }, // ATTR_LANGUAGE_FORMAT from 329, is combined with SID_ATTR_NUMBERFORMAT_VALUE in the dialog
+ { SID_ATTR_BRUSH, true }, // ATTR_BACKGROUND
+ { SID_SCATTR_PROTECTION, true }, // ATTR_PROTECTION
+ { SID_ATTR_BORDER_OUTER, true }, // ATTR_BORDER
+ { SID_ATTR_BORDER_INNER, true }, // ATTR_BORDER_INNER
+ { SID_ATTR_BORDER_SHADOW, true }, // ATTR_SHADOW
+ { 0, true }, // ATTR_VALIDDATA
+ { 0, true }, // ATTR_CONDITIONAL
+ { 0, true }, // ATTR_HYPERLINK
+ { 0, true }, // ATTR_PATTERN
+ { SID_ATTR_LRSPACE, true }, // ATTR_LRSPACE
+ { SID_ATTR_ULSPACE, true }, // ATTR_ULSPACE
+ { SID_ATTR_PAGE, true }, // ATTR_PAGE
+ { SID_ATTR_PAGE_PAPERBIN, true }, // ATTR_PAGE_PAPERBIN
+ { SID_ATTR_PAGE_SIZE, true }, // ATTR_PAGE_SIZE
+ { SID_ATTR_PAGE_EXT1, true }, // ATTR_PAGE_HORCENTER
+ { SID_ATTR_PAGE_EXT2, true }, // ATTR_PAGE_VERCENTER
+ { SID_ATTR_PAGE_ON, true }, // ATTR_PAGE_ON
+ { SID_ATTR_PAGE_DYNAMIC, true }, // ATTR_PAGE_DYNAMIC
+ { SID_ATTR_PAGE_SHARED, true }, // ATTR_PAGE_SHARED
+ { SID_ATTR_PAGE_SHARED_FIRST, true }, // ATTR_PAGE_SHARED_FIRST
+ { 0, true }, // ATTR_PAGE_NOTES aka. SID_SCATTR_PAGE_NOTES
+ { 0, true }, // ATTR_PAGE_GRID aka. SID_SCATTR_PAGE_GRID
+ { 0, true }, // ATTR_PAGE_HEADERS aka. SID_SCATTR_PAGE_HEADERS
+ { 0, true }, // ATTR_PAGE_CHARTS aka. SID_SCATTR_PAGE_CHARTS
+ { 0, true }, // ATTR_PAGE_OBJECTS aka. SID_SCATTR_PAGE_OBJECTS
+ { 0, true }, // ATTR_PAGE_DRAWINGS aka. SID_SCATTR_PAGE_DRAWINGS
+ { 0, true }, // ATTR_PAGE_TOPDOWN aka. SID_SCATTR_PAGE_TOPDOWN
+ { 0, true }, // ATTR_PAGE_SCALE aka SID_SCATTR_PAGE_SCALE
+ { 0, true }, // ATTR_PAGE_SCALETOPAGES aka SID_SCATTR_PAGE_SCALETOPAGES
+ { 0, true }, // ATTR_PAGE_FIRSTPAGENO aka SID_SCATTR_PAGE_FIRSTPAGENO
+ { 0, true }, // ATTR_PAGE_HEADERLEFT aka SID_SCATTR_PAGE_HEADERLEFT
+ { 0, true }, // ATTR_PAGE_FOOTERLEFT aka SID_SCATTR_PAGE_FOOTERLEFT
+ { 0, true }, // ATTR_PAGE_HEADERRIGHT aka SID_SCATTR_PAGE_HEADERRIGHT
+ { 0, true }, // ATTR_PAGE_FOOTERRIGHT aka. SID_SCATTR_PAGE_FOOTERRIGHT
+ { 0, true }, // ATTR_PAGE_HEADERFIRST aka. SID_SCATTR_PAGE_HEADERFIRST
+ { 0, true }, // ATTR_PAGE_FOOTERFIRST aka. SID_SCATTR_PAGE_FOOTERFIRST`
+ { SID_ATTR_PAGE_HEADERSET, true }, // ATTR_PAGE_HEADERSET
+ { SID_ATTR_PAGE_FOOTERSET, true }, // ATTR_PAGE_FOOTERSET
+ { 0, true }, // ATTR_PAGE_FORMULAS aka. SID_SCATTR_PAGE_FORMULAS
+ { 0, true }, // ATTR_PAGE_NULLVALS aka. SID_SCATTR_PAGE_NULLVALS
+ { 0, true }, // ATTR_PAGE_SCALETO aka. SID_SCATTR_PAGE_SCALETO
+ { 0, true } // ATTR_HIDDEN
+};
+static_assert(
+ SAL_N_ELEMENTS(aItemInfos) == ATTR_ENDINDEX - ATTR_STARTINDEX + 1, "these must match");
+
+ScDocumentPool::ScDocumentPool()
+
+ : SfxItemPool ( "ScDocumentPool",
+ ATTR_STARTINDEX, ATTR_ENDINDEX,
+ aItemInfos, nullptr ),
+ mvPoolDefaults(ATTR_ENDINDEX-ATTR_STARTINDEX+1),
+ mnCurrentMaxKey(0)
+{
+
+ LanguageType nDefLang, nCjkLang, nCtlLang;
+ bool bAutoSpell;
+ ScModule::GetSpellSettings( nDefLang, nCjkLang, nCtlLang, bAutoSpell );
+
+ // latin font from GetDefaultFonts is not used, DEFAULTFONT_LATIN_SPREADSHEET instead
+ SvxFontItem* pStdFont = getDefaultFontItem(nDefLang, DefaultFontType::LATIN_SPREADSHEET, ATTR_FONT);
+ SvxFontItem* pCjkFont = getDefaultFontItem(nCjkLang, DefaultFontType::CJK_SPREADSHEET, ATTR_CJK_FONT);
+ SvxFontItem* pCtlFont = getDefaultFontItem(nCtlLang, DefaultFontType::CTL_SPREADSHEET, ATTR_CTL_FONT);
+
+ SvxBoxInfoItem* pGlobalBorderInnerAttr = new SvxBoxInfoItem( ATTR_BORDER_INNER );
+ SfxItemSet aSetItemItemSet( *this,
+ svl::Items<ATTR_BACKGROUND, ATTR_BACKGROUND,
+ ATTR_BORDER, ATTR_SHADOW,
+ ATTR_LRSPACE, ATTR_ULSPACE,
+ ATTR_PAGE_SIZE, ATTR_PAGE_SIZE,
+ ATTR_PAGE_ON, ATTR_PAGE_SHARED_FIRST> );
+
+ pGlobalBorderInnerAttr->SetLine(nullptr, SvxBoxInfoItemLine::HORI);
+ pGlobalBorderInnerAttr->SetLine(nullptr, SvxBoxInfoItemLine::VERT);
+ pGlobalBorderInnerAttr->SetTable(true);
+ pGlobalBorderInnerAttr->SetDist(true);
+ pGlobalBorderInnerAttr->SetMinDist(false);
+
+ mvPoolDefaults[ ATTR_FONT - ATTR_STARTINDEX ] = pStdFont;
+ mvPoolDefaults[ ATTR_FONT_HEIGHT - ATTR_STARTINDEX ] = new SvxFontHeightItem( 200, 100, ATTR_FONT_HEIGHT ); // 10 pt;
+ mvPoolDefaults[ ATTR_FONT_WEIGHT - ATTR_STARTINDEX ] = new SvxWeightItem( WEIGHT_NORMAL, ATTR_FONT_WEIGHT );
+ mvPoolDefaults[ ATTR_FONT_POSTURE - ATTR_STARTINDEX ] = new SvxPostureItem( ITALIC_NONE, ATTR_FONT_POSTURE );
+ mvPoolDefaults[ ATTR_FONT_UNDERLINE - ATTR_STARTINDEX ] = new SvxUnderlineItem( LINESTYLE_NONE, ATTR_FONT_UNDERLINE );
+ mvPoolDefaults[ ATTR_FONT_OVERLINE - ATTR_STARTINDEX ] = new SvxOverlineItem( LINESTYLE_NONE, ATTR_FONT_OVERLINE );
+ mvPoolDefaults[ ATTR_FONT_CROSSEDOUT - ATTR_STARTINDEX ] = new SvxCrossedOutItem( STRIKEOUT_NONE, ATTR_FONT_CROSSEDOUT );
+ mvPoolDefaults[ ATTR_FONT_CONTOUR - ATTR_STARTINDEX ] = new SvxContourItem( false, ATTR_FONT_CONTOUR );
+ mvPoolDefaults[ ATTR_FONT_SHADOWED - ATTR_STARTINDEX ] = new SvxShadowedItem( false, ATTR_FONT_SHADOWED );
+ mvPoolDefaults[ ATTR_FONT_COLOR - ATTR_STARTINDEX ] = new SvxColorItem( COL_AUTO, ATTR_FONT_COLOR );
+ mvPoolDefaults[ ATTR_FONT_LANGUAGE - ATTR_STARTINDEX ] = new SvxLanguageItem( LANGUAGE_DONTKNOW, ATTR_FONT_LANGUAGE );
+ mvPoolDefaults[ ATTR_CJK_FONT - ATTR_STARTINDEX ] = pCjkFont;
+ mvPoolDefaults[ ATTR_CJK_FONT_HEIGHT - ATTR_STARTINDEX ] = new SvxFontHeightItem( 200, 100, ATTR_CJK_FONT_HEIGHT );
+ mvPoolDefaults[ ATTR_CJK_FONT_WEIGHT - ATTR_STARTINDEX ] = new SvxWeightItem( WEIGHT_NORMAL, ATTR_CJK_FONT_WEIGHT );
+ mvPoolDefaults[ ATTR_CJK_FONT_POSTURE- ATTR_STARTINDEX ] = new SvxPostureItem( ITALIC_NONE, ATTR_CJK_FONT_POSTURE );
+ mvPoolDefaults[ ATTR_CJK_FONT_LANGUAGE-ATTR_STARTINDEX ] = new SvxLanguageItem( LANGUAGE_DONTKNOW, ATTR_CJK_FONT_LANGUAGE );
+ mvPoolDefaults[ ATTR_CTL_FONT - ATTR_STARTINDEX ] = pCtlFont;
+ mvPoolDefaults[ ATTR_CTL_FONT_HEIGHT - ATTR_STARTINDEX ] = new SvxFontHeightItem( 200, 100, ATTR_CTL_FONT_HEIGHT );
+ mvPoolDefaults[ ATTR_CTL_FONT_WEIGHT - ATTR_STARTINDEX ] = new SvxWeightItem( WEIGHT_NORMAL, ATTR_CTL_FONT_WEIGHT );
+ mvPoolDefaults[ ATTR_CTL_FONT_POSTURE- ATTR_STARTINDEX ] = new SvxPostureItem( ITALIC_NONE, ATTR_CTL_FONT_POSTURE );
+ mvPoolDefaults[ ATTR_CTL_FONT_LANGUAGE-ATTR_STARTINDEX ] = new SvxLanguageItem( LANGUAGE_DONTKNOW, ATTR_CTL_FONT_LANGUAGE );
+ mvPoolDefaults[ ATTR_FONT_EMPHASISMARK-ATTR_STARTINDEX ] = new SvxEmphasisMarkItem( FontEmphasisMark::NONE, ATTR_FONT_EMPHASISMARK );
+ mvPoolDefaults[ ATTR_USERDEF - ATTR_STARTINDEX ] = new SvXMLAttrContainerItem( ATTR_USERDEF );
+ mvPoolDefaults[ ATTR_FONT_WORDLINE - ATTR_STARTINDEX ] = new SvxWordLineModeItem(false, ATTR_FONT_WORDLINE );
+ mvPoolDefaults[ ATTR_FONT_RELIEF - ATTR_STARTINDEX ] = new SvxCharReliefItem( FontRelief::NONE, ATTR_FONT_RELIEF );
+ mvPoolDefaults[ ATTR_HYPHENATE - ATTR_STARTINDEX ] = new ScHyphenateCell();
+ mvPoolDefaults[ ATTR_SCRIPTSPACE - ATTR_STARTINDEX ] = new SvxScriptSpaceItem( false, ATTR_SCRIPTSPACE);
+ mvPoolDefaults[ ATTR_HANGPUNCTUATION - ATTR_STARTINDEX ] = new SvxHangingPunctuationItem( false, ATTR_HANGPUNCTUATION);
+ mvPoolDefaults[ ATTR_FORBIDDEN_RULES - ATTR_STARTINDEX ] = new SvxForbiddenRuleItem( false, ATTR_FORBIDDEN_RULES);
+ mvPoolDefaults[ ATTR_HOR_JUSTIFY - ATTR_STARTINDEX ] = new SvxHorJustifyItem( SvxCellHorJustify::Standard, ATTR_HOR_JUSTIFY);
+ mvPoolDefaults[ ATTR_HOR_JUSTIFY_METHOD - ATTR_STARTINDEX ] = new SvxJustifyMethodItem( SvxCellJustifyMethod::Auto, ATTR_HOR_JUSTIFY_METHOD);
+ mvPoolDefaults[ ATTR_INDENT - ATTR_STARTINDEX ] = new ScIndentItem( 0 );
+ mvPoolDefaults[ ATTR_VER_JUSTIFY - ATTR_STARTINDEX ] = new SvxVerJustifyItem( SvxCellVerJustify::Standard, ATTR_VER_JUSTIFY);
+ mvPoolDefaults[ ATTR_VER_JUSTIFY_METHOD - ATTR_STARTINDEX ] = new SvxJustifyMethodItem( SvxCellJustifyMethod::Auto, ATTR_VER_JUSTIFY_METHOD);
+ mvPoolDefaults[ ATTR_STACKED - ATTR_STARTINDEX ] = new ScVerticalStackCell(false);
+ mvPoolDefaults[ ATTR_ROTATE_VALUE - ATTR_STARTINDEX ] = new ScRotateValueItem( 0_deg100 );
+ mvPoolDefaults[ ATTR_ROTATE_MODE - ATTR_STARTINDEX ] = new SvxRotateModeItem( SVX_ROTATE_MODE_BOTTOM, ATTR_ROTATE_MODE );
+ mvPoolDefaults[ ATTR_VERTICAL_ASIAN - ATTR_STARTINDEX ] = new SfxBoolItem( ATTR_VERTICAL_ASIAN );
+ // The default for the ATTR_WRITINGDIR cell attribute must by SvxFrameDirection::Environment,
+ // so that value is returned when asking for a default cell's attributes.
+ // The value from the page style is set as DefaultHorizontalTextDirection for the EditEngine.
+ mvPoolDefaults[ ATTR_WRITINGDIR - ATTR_STARTINDEX ] = new SvxFrameDirectionItem( SvxFrameDirection::Environment, ATTR_WRITINGDIR );
+ mvPoolDefaults[ ATTR_LINEBREAK - ATTR_STARTINDEX ] = new ScLineBreakCell();
+ mvPoolDefaults[ ATTR_SHRINKTOFIT - ATTR_STARTINDEX ] = new ScShrinkToFitCell();
+ mvPoolDefaults[ ATTR_BORDER_TLBR - ATTR_STARTINDEX ] = new SvxLineItem( ATTR_BORDER_TLBR );
+ mvPoolDefaults[ ATTR_BORDER_BLTR - ATTR_STARTINDEX ] = new SvxLineItem( ATTR_BORDER_BLTR );
+ mvPoolDefaults[ ATTR_MARGIN - ATTR_STARTINDEX ] = new SvxMarginItem( ATTR_MARGIN );
+ mvPoolDefaults[ ATTR_MERGE - ATTR_STARTINDEX ] = new ScMergeAttr;
+ mvPoolDefaults[ ATTR_MERGE_FLAG - ATTR_STARTINDEX ] = new ScMergeFlagAttr;
+ mvPoolDefaults[ ATTR_VALUE_FORMAT - ATTR_STARTINDEX ] = new SfxUInt32Item( ATTR_VALUE_FORMAT, 0 );
+ mvPoolDefaults[ ATTR_LANGUAGE_FORMAT - ATTR_STARTINDEX ] = new SvxLanguageItem( ScGlobal::eLnge, ATTR_LANGUAGE_FORMAT );
+ mvPoolDefaults[ ATTR_BACKGROUND - ATTR_STARTINDEX ] = new SvxBrushItem( COL_TRANSPARENT, ATTR_BACKGROUND );
+ mvPoolDefaults[ ATTR_PROTECTION - ATTR_STARTINDEX ] = new ScProtectionAttr;
+ mvPoolDefaults[ ATTR_BORDER - ATTR_STARTINDEX ] = new SvxBoxItem( ATTR_BORDER );
+ mvPoolDefaults[ ATTR_BORDER_INNER - ATTR_STARTINDEX ] = pGlobalBorderInnerAttr;
+ mvPoolDefaults[ ATTR_SHADOW - ATTR_STARTINDEX ] = new SvxShadowItem( ATTR_SHADOW );
+ mvPoolDefaults[ ATTR_VALIDDATA - ATTR_STARTINDEX ] = new SfxUInt32Item( ATTR_VALIDDATA, 0 );
+ mvPoolDefaults[ ATTR_CONDITIONAL - ATTR_STARTINDEX ] = new ScCondFormatItem;
+ mvPoolDefaults[ ATTR_HYPERLINK - ATTR_STARTINDEX ] = new SfxStringItem( ATTR_HYPERLINK, OUString() ) ;
+
+ // GetRscString only works after ScGlobal::Init (indicated by the EmptyBrushItem)
+ // TODO: Write additional method ScGlobal::IsInit() or somesuch
+ // or detect whether this is the Secondary Pool for a MessagePool
+ if ( ScGlobal::GetEmptyBrushItem() )
+
+ mvPoolDefaults[ ATTR_PATTERN - ATTR_STARTINDEX ] =
+ new ScPatternAttr( SfxItemSetFixed<ATTR_PATTERN_START, ATTR_PATTERN_END>( *this ),
+ ScResId(STR_STYLENAME_STANDARD) );
+ else
+ mvPoolDefaults[ ATTR_PATTERN - ATTR_STARTINDEX ] =
+ new ScPatternAttr( SfxItemSetFixed<ATTR_PATTERN_START, ATTR_PATTERN_END>( *this ),
+ STRING_STANDARD ); // FIXME: without name?
+
+ mvPoolDefaults[ ATTR_LRSPACE - ATTR_STARTINDEX ] = new SvxLRSpaceItem( ATTR_LRSPACE );
+ mvPoolDefaults[ ATTR_ULSPACE - ATTR_STARTINDEX ] = new SvxULSpaceItem( ATTR_ULSPACE );
+ mvPoolDefaults[ ATTR_PAGE - ATTR_STARTINDEX ] = new SvxPageItem( ATTR_PAGE );
+ mvPoolDefaults[ ATTR_PAGE_PAPERBIN - ATTR_STARTINDEX ] = new SvxPaperBinItem( ATTR_PAGE_PAPERBIN );
+ mvPoolDefaults[ ATTR_PAGE_SIZE - ATTR_STARTINDEX ] = new SvxSizeItem( ATTR_PAGE_SIZE );
+ mvPoolDefaults[ ATTR_PAGE_HORCENTER - ATTR_STARTINDEX ] = new SfxBoolItem( ATTR_PAGE_HORCENTER );
+ mvPoolDefaults[ ATTR_PAGE_VERCENTER - ATTR_STARTINDEX ] = new SfxBoolItem( ATTR_PAGE_VERCENTER );
+ mvPoolDefaults[ ATTR_PAGE_ON - ATTR_STARTINDEX ] = new SfxBoolItem( ATTR_PAGE_ON, true );
+ mvPoolDefaults[ ATTR_PAGE_DYNAMIC - ATTR_STARTINDEX ] = new SfxBoolItem( ATTR_PAGE_DYNAMIC, true );
+ mvPoolDefaults[ ATTR_PAGE_SHARED - ATTR_STARTINDEX ] = new SfxBoolItem( ATTR_PAGE_SHARED, true );
+ mvPoolDefaults[ ATTR_PAGE_SHARED_FIRST- ATTR_STARTINDEX ] = new SfxBoolItem( ATTR_PAGE_SHARED_FIRST, true );
+ mvPoolDefaults[ ATTR_PAGE_NOTES - ATTR_STARTINDEX ] = new SfxBoolItem( ATTR_PAGE_NOTES, false );
+ mvPoolDefaults[ ATTR_PAGE_GRID - ATTR_STARTINDEX ] = new SfxBoolItem( ATTR_PAGE_GRID, false );
+ mvPoolDefaults[ ATTR_PAGE_HEADERS - ATTR_STARTINDEX ] = new SfxBoolItem( ATTR_PAGE_HEADERS, false );
+ mvPoolDefaults[ ATTR_PAGE_CHARTS - ATTR_STARTINDEX ] = new ScViewObjectModeItem( ATTR_PAGE_CHARTS );
+ mvPoolDefaults[ ATTR_PAGE_OBJECTS - ATTR_STARTINDEX ] = new ScViewObjectModeItem( ATTR_PAGE_OBJECTS );
+ mvPoolDefaults[ ATTR_PAGE_DRAWINGS - ATTR_STARTINDEX ] = new ScViewObjectModeItem( ATTR_PAGE_DRAWINGS );
+ mvPoolDefaults[ ATTR_PAGE_TOPDOWN - ATTR_STARTINDEX ] = new SfxBoolItem( ATTR_PAGE_TOPDOWN, true );
+ mvPoolDefaults[ ATTR_PAGE_SCALE - ATTR_STARTINDEX ] = new SfxUInt16Item( ATTR_PAGE_SCALE, 100 );
+ mvPoolDefaults[ ATTR_PAGE_SCALETOPAGES-ATTR_STARTINDEX ] = new SfxUInt16Item( ATTR_PAGE_SCALETOPAGES, 1 );
+ mvPoolDefaults[ ATTR_PAGE_FIRSTPAGENO- ATTR_STARTINDEX ] = new SfxUInt16Item( ATTR_PAGE_FIRSTPAGENO, 1 );
+ mvPoolDefaults[ ATTR_PAGE_HEADERLEFT - ATTR_STARTINDEX ] = new ScPageHFItem( ATTR_PAGE_HEADERLEFT );
+ mvPoolDefaults[ ATTR_PAGE_FOOTERLEFT - ATTR_STARTINDEX ] = new ScPageHFItem( ATTR_PAGE_FOOTERLEFT );
+ mvPoolDefaults[ ATTR_PAGE_HEADERRIGHT- ATTR_STARTINDEX ] = new ScPageHFItem( ATTR_PAGE_HEADERRIGHT );
+ mvPoolDefaults[ ATTR_PAGE_FOOTERRIGHT- ATTR_STARTINDEX ] = new ScPageHFItem( ATTR_PAGE_FOOTERRIGHT );
+ mvPoolDefaults[ ATTR_PAGE_HEADERFIRST- ATTR_STARTINDEX ] = new ScPageHFItem( ATTR_PAGE_HEADERFIRST );
+ mvPoolDefaults[ ATTR_PAGE_FOOTERFIRST- ATTR_STARTINDEX ] = new ScPageHFItem( ATTR_PAGE_FOOTERFIRST );
+ mvPoolDefaults[ ATTR_PAGE_HEADERSET - ATTR_STARTINDEX ] = new SvxSetItem( ATTR_PAGE_HEADERSET, aSetItemItemSet );
+ mvPoolDefaults[ ATTR_PAGE_FOOTERSET - ATTR_STARTINDEX ] = new SvxSetItem( ATTR_PAGE_FOOTERSET, aSetItemItemSet );
+ mvPoolDefaults[ ATTR_PAGE_FORMULAS - ATTR_STARTINDEX ] = new SfxBoolItem( ATTR_PAGE_FORMULAS, false );
+ mvPoolDefaults[ ATTR_PAGE_NULLVALS - ATTR_STARTINDEX ] = new SfxBoolItem( ATTR_PAGE_NULLVALS, true );
+ mvPoolDefaults[ ATTR_PAGE_SCALETO - ATTR_STARTINDEX ] = new ScPageScaleToItem( 1, 1 );
+ mvPoolDefaults[ ATTR_HIDDEN - ATTR_STARTINDEX ] = new SfxBoolItem( ATTR_HIDDEN, false );
+
+ SetDefaults( &mvPoolDefaults );
+}
+
+ScDocumentPool::~ScDocumentPool()
+{
+ Delete();
+
+ for ( sal_uInt16 i=0; i < ATTR_ENDINDEX-ATTR_STARTINDEX+1; i++ )
+ {
+ ClearRefCount( *mvPoolDefaults[i] );
+ delete mvPoolDefaults[i];
+ }
+}
+
+const SfxPoolItem& ScDocumentPool::PutImpl( const SfxPoolItem& rItem, sal_uInt16 nWhich, bool bPassingOwnership )
+{
+ if ( rItem.Which() != ATTR_PATTERN ) // Only Pattern is special
+ return SfxItemPool::PutImpl( rItem, nWhich, bPassingOwnership );
+
+ // Don't copy the default pattern of this Pool
+ if (&rItem == mvPoolDefaults[ ATTR_PATTERN - ATTR_STARTINDEX ])
+ return rItem;
+
+ // Else Put must always happen, because it could be another Pool
+ const SfxPoolItem& rNew = SfxItemPool::PutImpl( rItem, nWhich, bPassingOwnership );
+ sal_uInt32 nRef = rNew.GetRefCount();
+ if (nRef == 1)
+ {
+ ++mnCurrentMaxKey;
+ const_cast<ScPatternAttr&>(static_cast<const ScPatternAttr&>(rNew)).SetKey(mnCurrentMaxKey);
+ }
+ return rNew;
+}
+
+void ScDocumentPool::StyleDeleted( const ScStyleSheet* pStyle )
+{
+ for (const SfxPoolItem* pItem : GetItemSurrogates( ATTR_PATTERN ))
+ {
+ ScPatternAttr* pPattern = const_cast<ScPatternAttr*>(dynamic_cast<const ScPatternAttr*>(pItem));
+ if ( pPattern && pPattern->GetStyleSheet() == pStyle )
+ pPattern->StyleToName();
+ }
+}
+
+void ScDocumentPool::CellStyleCreated( std::u16string_view rName, const ScDocument& rDoc )
+{
+ // If a style was created, don't keep any pattern with its name string in the pool,
+ // because it would compare equal to a pattern with a pointer to the new style.
+ // Calling StyleSheetChanged isn't enough because the pool may still contain items
+ // for undo or clipboard content.
+
+ for (const SfxPoolItem* pItem : GetItemSurrogates( ATTR_PATTERN ))
+ {
+ auto pPattern = const_cast<ScPatternAttr*>(dynamic_cast<const ScPatternAttr*>(pItem));
+ if ( pPattern && pPattern->GetStyleSheet() == nullptr )
+ {
+ const OUString* pStyleName = pPattern->GetStyleName();
+ if ( pStyleName && *pStyleName == rName )
+ pPattern->UpdateStyleSheet(rDoc); // find and store style pointer
+ }
+ }
+}
+
+rtl::Reference<SfxItemPool> ScDocumentPool::Clone() const
+{
+ return new SfxItemPool (*this, true);
+}
+
+static bool lcl_HFPresentation
+(
+ const SfxPoolItem& rItem,
+ MapUnit eCoreMetric,
+ MapUnit ePresentationMetric,
+ OUString& rText,
+ const IntlWrapper& rIntl
+)
+{
+ const SfxItemSet& rSet = static_cast<const SfxSetItem&>(rItem).GetItemSet();
+
+ if ( const SfxBoolItem* pItem = rSet.GetItemIfSet(ATTR_PAGE_ON,false) )
+ {
+ if( !pItem->GetValue() )
+ return false;
+ }
+
+ SfxItemIter aIter( rSet );
+
+ for (const SfxPoolItem* pItem = aIter.GetCurItem(); pItem; pItem = aIter.NextItem())
+ {
+ sal_uInt16 nWhich = pItem->Which();
+
+ OUString aText;
+
+ switch( nWhich )
+ {
+ case ATTR_PAGE_ON:
+ case ATTR_PAGE_DYNAMIC:
+ case ATTR_PAGE_SHARED:
+ case ATTR_PAGE_SHARED_FIRST:
+ break;
+
+ case ATTR_LRSPACE:
+ {
+ const SvxLRSpaceItem& rLRItem = static_cast<const SvxLRSpaceItem&>(*pItem);
+ sal_uInt16 nPropLeftMargin = rLRItem.GetPropLeft();
+ sal_uInt16 nPropRightMargin = rLRItem.GetPropRight();
+ sal_uInt16 nLeftMargin, nRightMargin;
+ tools::Long nTmp;
+ nTmp = rLRItem.GetLeft();
+ nLeftMargin = nTmp < 0 ? 0 : sal_uInt16(nTmp);
+ nTmp = rLRItem.GetRight();
+ nRightMargin = nTmp < 0 ? 0 : sal_uInt16(nTmp);
+
+ aText = EditResId(RID_SVXITEMS_LRSPACE_LEFT);
+ if ( 100 != nPropLeftMargin )
+ {
+ aText += unicode::formatPercent(nPropLeftMargin,
+ Application::GetSettings().GetUILanguageTag());
+ }
+ else
+ {
+ aText += GetMetricText( static_cast<tools::Long>(nLeftMargin),
+ eCoreMetric, ePresentationMetric, &rIntl ) +
+ " " + EditResId(GetMetricId(ePresentationMetric));
+ }
+ aText += cpDelim +
+ // We don't have a nPropFirstLineOffset
+ EditResId(RID_SVXITEMS_LRSPACE_RIGHT);
+ if ( 100 != nPropRightMargin )
+ {
+ aText += unicode::formatPercent(nPropLeftMargin,
+ Application::GetSettings().GetUILanguageTag());
+ }
+ else
+ {
+ aText += GetMetricText( static_cast<tools::Long>(nRightMargin),
+ eCoreMetric, ePresentationMetric, &rIntl ) +
+ " " + EditResId(GetMetricId(ePresentationMetric));
+ }
+ }
+ break;
+
+ default:
+ pItem->GetPresentation( SfxItemPresentation::Complete, eCoreMetric, ePresentationMetric, aText, rIntl );
+
+ }
+
+ if ( aText.getLength() )
+ {
+ rText += aText + " + ";
+ }
+ }
+
+ rText = comphelper::string::stripEnd(rText, ' ');
+ rText = comphelper::string::stripEnd(rText, '+');
+ rText = comphelper::string::stripEnd(rText, ' ');
+ return true;
+}
+
+bool ScDocumentPool::GetPresentation(
+ const SfxPoolItem& rItem,
+ MapUnit ePresentationMetric,
+ OUString& rText,
+ const IntlWrapper& rIntl ) const
+{
+ sal_uInt16 nW = rItem.Which();
+ OUString aStrYes ( ScResId(STR_YES) );
+ OUString aStrNo ( ScResId(STR_NO) );
+ OUString aStrSep(": ");
+
+ bool ePresentationRet = true;
+ switch( nW )
+ {
+ case ATTR_PAGE_TOPDOWN:
+ rText = ScResId(STR_SCATTR_PAGE_PRINTDIR) + aStrSep;
+ rText += static_cast<const SfxBoolItem&>(rItem).GetValue() ?
+ ScResId(STR_SCATTR_PAGE_TOPDOWN) :
+ ScResId(STR_SCATTR_PAGE_LEFTRIGHT) ;
+ break;
+
+ case ATTR_PAGE_HEADERS:
+ rText = ScResId(STR_SCATTR_PAGE_HEADERS) + aStrSep;
+ rText += static_cast<const SfxBoolItem&>(rItem).GetValue() ? aStrYes : aStrNo ;
+ break;
+
+ case ATTR_PAGE_NULLVALS:
+ rText = ScResId(STR_SCATTR_PAGE_NULLVALS) + aStrSep;
+ rText += static_cast<const SfxBoolItem&>(rItem).GetValue() ? aStrYes : aStrNo ;
+ break;
+
+ case ATTR_PAGE_FORMULAS:
+ rText = ScResId(STR_SCATTR_PAGE_FORMULAS) + aStrSep;
+ rText += static_cast<const SfxBoolItem&>(rItem).GetValue() ? aStrYes : aStrNo ;
+ break;
+
+ case ATTR_PAGE_NOTES:
+ rText = ScResId(STR_SCATTR_PAGE_NOTES) + aStrSep;
+ rText += static_cast<const SfxBoolItem&>(rItem).GetValue() ? aStrYes : aStrNo ;
+ break;
+
+ case ATTR_PAGE_GRID:
+ rText = ScResId(STR_SCATTR_PAGE_GRID) + aStrSep;
+ rText += static_cast<const SfxBoolItem&>(rItem).GetValue() ? aStrYes : aStrNo ;
+ break;
+
+ case ATTR_PAGE_SCALETOPAGES:
+ {
+ sal_uInt16 nPagNo = static_cast<const SfxUInt16Item&>(rItem).GetValue();
+
+ if( nPagNo )
+ {
+ rText = ScResId( STR_SCATTR_PAGE_SCALETOPAGES ) + aStrSep;
+ OUString aPages(ScResId(STR_SCATTR_PAGE_SCALE_PAGES, nPagNo));
+ aPages = aPages.replaceFirst( "%1", OUString::number( nPagNo ) );
+ rText += aPages;
+ }
+ else
+ {
+ ePresentationRet = false;
+ }
+ }
+ break;
+
+ case ATTR_PAGE_FIRSTPAGENO:
+ {
+ sal_uInt16 nPagNo = static_cast<const SfxUInt16Item&>(rItem).GetValue();
+
+ if( nPagNo )
+ {
+ rText = ScResId(STR_SCATTR_PAGE_FIRSTPAGENO) + aStrSep;
+ rText += OUString::number( nPagNo );
+ }
+ else
+ {
+ ePresentationRet = false;
+ }
+ }
+ break;
+
+ case ATTR_PAGE_SCALE:
+ {
+ sal_uInt16 nPercent = static_cast<const SfxUInt16Item &>(rItem).GetValue();
+
+ if( nPercent )
+ {
+ rText = ScResId(STR_SCATTR_PAGE_SCALE) + aStrSep;
+ rText += unicode::formatPercent(nPercent,
+ Application::GetSettings().GetUILanguageTag());
+ }
+ else
+ {
+ ePresentationRet = false;
+ }
+ }
+ break;
+
+ case ATTR_PAGE_HEADERSET:
+ {
+ OUString aBuffer;
+
+ if( lcl_HFPresentation( rItem, GetMetric( nW ), ePresentationMetric, aBuffer, rIntl ) )
+ {
+ rText = ScResId(STR_HEADER) + " ( " + aBuffer + " ) ";
+ }
+ }
+ break;
+
+ case ATTR_PAGE_FOOTERSET:
+ {
+ OUString aBuffer;
+
+ if( lcl_HFPresentation( rItem, GetMetric( nW ), ePresentationMetric, aBuffer, rIntl ) )
+ {
+ rText = ScResId(STR_FOOTER) + " ( " + aBuffer + " ) ";
+ }
+ }
+ break;
+
+ default:
+ ePresentationRet = rItem.GetPresentation( SfxItemPresentation::Complete, GetMetric( nW ), ePresentationMetric, rText, rIntl );
+ break;
+ }
+
+ return ePresentationRet;
+}
+
+MapUnit ScDocumentPool::GetMetric( sal_uInt16 nWhich ) const
+{
+ // Own attributes in Twips, everything else in 1/100 mm
+ if ( nWhich >= ATTR_STARTINDEX && nWhich <= ATTR_ENDINDEX )
+ return MapUnit::MapTwip;
+ else
+ return MapUnit::Map100thMM;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sc/source/core/data/documen2.cxx b/sc/source/core/data/documen2.cxx
new file mode 100644
index 000000000..c29025f43
--- /dev/null
+++ b/sc/source/core/data/documen2.cxx
@@ -0,0 +1,1471 @@
+/* -*- 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 <scextopt.hxx>
+#include <autonamecache.hxx>
+
+#include <o3tl/safeint.hxx>
+#include <osl/thread.h>
+#include <svx/xtable.hxx>
+#include <sfx2/bindings.hxx>
+#include <sfx2/objsh.hxx>
+#include <sfx2/docfile.hxx>
+#include <sfx2/printer.hxx>
+#include <svl/asiancfg.hxx>
+#include <vcl/virdev.hxx>
+#include <svl/sharedstringpool.hxx>
+#include <tools/urlobj.hxx>
+#include <rtl/crc.h>
+#include <basic/basmgr.hxx>
+#include <comphelper/threadpool.hxx>
+#include <sal/log.hxx>
+#include <osl/diagnose.h>
+#include <unotools/configmgr.hxx>
+
+#include <scmod.hxx>
+#include <document.hxx>
+#include <table.hxx>
+#include <patattr.hxx>
+#include <rangenam.hxx>
+#include <dbdata.hxx>
+#include <chartlock.hxx>
+#include <rechead.hxx>
+#include <global.hxx>
+#include <bcaslot.hxx>
+#include <adiasync.hxx>
+#include <addinlis.hxx>
+#include <chartlis.hxx>
+#include <markdata.hxx>
+#include <validat.hxx>
+#include <detdata.hxx>
+#include <defaultsoptions.hxx>
+#include <ddelink.hxx>
+#include <chgtrack.hxx>
+#include <chgviset.hxx>
+#include <editutil.hxx>
+#include <hints.hxx>
+#include <dpobject.hxx>
+#include <scrdata.hxx>
+#include <poolhelp.hxx>
+#include <unoreflist.hxx>
+#include <listenercalls.hxx>
+#include <recursionhelper.hxx>
+#include <lookupcache.hxx>
+#include <rangecache.hxx>
+#include <externalrefmgr.hxx>
+#include <viewdata.hxx>
+#include <viewutil.hxx>
+#include <tabprotection.hxx>
+#include <formulaparserpool.hxx>
+#include <clipparam.hxx>
+#include <macromgr.hxx>
+#include <formulacell.hxx>
+#include <clipcontext.hxx>
+#include <refupdatecontext.hxx>
+#include <refreshtimerprotector.hxx>
+#include <scopetools.hxx>
+#include <documentlinkmgr.hxx>
+#include <interpre.hxx>
+#include <tokenstringcontext.hxx>
+#include <docsh.hxx>
+#include <clipoptions.hxx>
+#include <listenercontext.hxx>
+#include <datamapper.hxx>
+#include <drwlayer.hxx>
+#include <sharedstringpoolpurge.hxx>
+#include <dociter.hxx>
+#include <config_features.h>
+
+using namespace com::sun::star;
+
+const sal_uInt16 ScDocument::nSrcVer = SC_CURRENT_VERSION;
+
+ScSheetLimits ScSheetLimits::CreateDefault()
+{
+#if HAVE_FEATURE_JUMBO_SHEETS
+ bool jumboSheets = false;
+ if( SC_MOD())
+ jumboSheets = SC_MOD()->GetDefaultsOptions().GetInitJumboSheets();
+ else
+ assert( getenv("LO_TESTNAME") != nullptr ); // in unittests
+ if (jumboSheets)
+ return ScSheetLimits(MAXCOL_JUMBO, MAXROW_JUMBO);
+ else
+#endif
+ return ScSheetLimits(MAXCOL, MAXROW);
+}
+
+ScDocument::ScDocument( ScDocumentMode eMode, SfxObjectShell* pDocShell ) :
+ mpCellStringPool(std::make_shared<svl::SharedStringPool>(ScGlobal::getCharClass())),
+ mpDocLinkMgr(new sc::DocumentLinkManager(pDocShell)),
+ mbFormulaGroupCxtBlockDiscard(false),
+ maCalcConfig( ScInterpreter::GetGlobalConfig()),
+ mpUndoManager( nullptr ),
+ mpShell( pDocShell ),
+ mpPrinter( nullptr ),
+ mpVirtualDevice_100th_mm( nullptr ),
+ pFormatExchangeList( nullptr ),
+ mxSheetLimits(new ScSheetLimits(ScSheetLimits::CreateDefault())),
+ pFormulaTree( nullptr ),
+ pEOFormulaTree( nullptr ),
+ pFormulaTrack( nullptr ),
+ pEOFormulaTrack( nullptr ),
+ pPreviewCellStyle( nullptr ),
+ maPreviewSelection(*mxSheetLimits),
+ nUnoObjectId( 0 ),
+ nRangeOverflowType( 0 ),
+ aCurTextWidthCalcPos(MaxCol(),0,0),
+ aTrackIdle("sc ScDocument Track Idle"),
+ nFormulaCodeInTree(0),
+ nXMLImportedFormulaCount( 0 ),
+ nInterpretLevel(0),
+ nMacroInterpretLevel(0),
+ nInterpreterTableOpLevel(0),
+ maInterpreterContext( *this, nullptr ),
+ mxScSortedRangeCache(new ScSortedRangeCacheMap),
+ nFormulaTrackCount(0),
+ eHardRecalcState(HardRecalcState::OFF),
+ nVisibleTab( 0 ),
+ nPosLeft( 0 ),
+ nPosTop( 0 ),
+ eLinkMode(LM_UNKNOWN),
+ bAutoCalc( eMode == SCDOCMODE_DOCUMENT || eMode == SCDOCMODE_FUNCTIONACCESS ),
+ bAutoCalcShellDisabled( false ),
+ bForcedFormulaPending( false ),
+ bCalculatingFormulaTree( false ),
+ bIsClip( eMode == SCDOCMODE_CLIP ),
+ bIsUndo( eMode == SCDOCMODE_UNDO ),
+ bIsFunctionAccess( eMode == SCDOCMODE_FUNCTIONACCESS ),
+ bIsVisible( false ),
+ bIsEmbedded( false ),
+ bInsertingFromOtherDoc( false ),
+ bLoadingMedium( false ),
+ bImportingXML( false ),
+ bCalcingAfterLoad( false ),
+ bNoListening( false ),
+ mbIdleEnabled(true),
+ bInLinkUpdate( false ),
+ bChartListenerCollectionNeedsUpdate( false ),
+ bHasForcedFormulas( false ),
+ bInDtorClear( false ),
+ bExpandRefs( false ),
+ bDetectiveDirty( false ),
+ bDelayedDeletingBroadcasters( false ),
+ bLinkFormulaNeedingCheck( false ),
+ nAsianCompression(CharCompressType::Invalid),
+ nAsianKerning(SC_ASIANKERNING_INVALID),
+ bPastingDrawFromOtherDoc( false ),
+ nInDdeLinkUpdate( 0 ),
+ bInUnoBroadcast( false ),
+ bInUnoListenerCall( false ),
+ nAdjustHeightLock(0),
+ eGrammar( formula::FormulaGrammar::GRAM_NATIVE ),
+ bStyleSheetUsageInvalid( true ),
+ mbUndoEnabled( true ),
+ mbExecuteLinkEnabled( true ),
+ mbChangeReadOnlyEnabled( false ),
+ mbStreamValidLocked( false ),
+ mbUserInteractionEnabled(true),
+ mnNamedRangesLockCount(0),
+ mbEmbedFonts(false),
+ mbEmbedUsedFontsOnly(false),
+ mbEmbedFontScriptLatin(true),
+ mbEmbedFontScriptAsian(true),
+ mbEmbedFontScriptComplex(true),
+ mnImagePreferredDPI(0),
+ mbTrackFormulasPending(false),
+ mbFinalTrackFormulas(false),
+ mbDocShellRecalc(false),
+ mbLayoutStrings(false),
+ mnMutationGuardFlags(0)
+{
+ maPreviewSelection = { *mxSheetLimits };
+ aCurTextWidthCalcPos = { MaxCol(), 0, 0 };
+
+ SetStorageGrammar( formula::FormulaGrammar::GRAM_STORAGE_DEFAULT);
+
+ eSrcSet = osl_getThreadTextEncoding();
+
+ /* TODO: for SCDOCMODE_FUNCTIONACCESS it might not even be necessary to
+ * have all of these available. */
+ if ( eMode == SCDOCMODE_DOCUMENT || eMode == SCDOCMODE_FUNCTIONACCESS )
+ {
+ mxPoolHelper = new ScPoolHelper( *this );
+ if (!utl::ConfigManager::IsFuzzing()) //just too slow
+ pBASM.reset( new ScBroadcastAreaSlotMachine( this ) );
+ pChartListenerCollection.reset( new ScChartListenerCollection( *this ) );
+ pRefreshTimerControl.reset( new ScRefreshTimerControl );
+ }
+ else
+ {
+ pChartListenerCollection = nullptr;
+ }
+ pDBCollection.reset( new ScDBCollection(*this) );
+ pSelectionAttr = nullptr;
+ apTemporaryChartLock.reset( new ScTemporaryChartLock(this) );
+ xColNameRanges = new ScRangePairList;
+ xRowNameRanges = new ScRangePairList;
+ ImplCreateOptions();
+ // languages for a visible document are set by docshell later (from options)
+ SetLanguage( ScGlobal::eLnge, ScGlobal::eLnge, ScGlobal::eLnge );
+
+ aTrackIdle.SetInvokeHandler( LINK( this, ScDocument, TrackTimeHdl ) );
+}
+
+sfx2::LinkManager* ScDocument::GetLinkManager()
+{
+ return GetDocLinkManager().getLinkManager();
+}
+
+const sfx2::LinkManager* ScDocument::GetLinkManager() const
+{
+ return GetDocLinkManager().getExistingLinkManager();
+}
+
+sc::DocumentLinkManager& ScDocument::GetDocLinkManager()
+{
+ return *mpDocLinkMgr;
+}
+
+const sc::DocumentLinkManager& ScDocument::GetDocLinkManager() const
+{
+ return const_cast<ScDocument*>(this)->GetDocLinkManager();
+}
+
+void ScDocument::SetStorageGrammar( formula::FormulaGrammar::Grammar eGram )
+{
+ OSL_PRECOND(
+ eGram == formula::FormulaGrammar::GRAM_ODFF ||
+ eGram == formula::FormulaGrammar::GRAM_PODF,
+ "ScDocument::SetStorageGrammar: wrong storage grammar");
+
+ eStorageGrammar = eGram;
+}
+
+void ScDocument::SetDocVisible( bool bSet )
+{
+ // called from view ctor - only for a visible document,
+ // each new sheet's RTL flag is initialized from the locale
+ bIsVisible = bSet;
+}
+
+sal_uInt32 ScDocument::GetDocumentID() const
+{
+ const ScDocument* pThis = this;
+ sal_uInt32 nCrc = rtl_crc32( 0, &pThis, sizeof(ScDocument*) );
+ // the this pointer only might not be sufficient
+ nCrc = rtl_crc32( nCrc, &mpShell, sizeof(SfxObjectShell*) );
+ return nCrc;
+}
+
+void ScDocument::StartChangeTracking()
+{
+ if (!pChangeTrack)
+ pChangeTrack.reset( new ScChangeTrack( *this ) );
+}
+
+void ScDocument::EndChangeTracking()
+{
+ pChangeTrack.reset();
+}
+
+void ScDocument::SetChangeTrack( std::unique_ptr<ScChangeTrack> pTrack )
+{
+ OSL_ENSURE( &pTrack->GetDocument() == this, "SetChangeTrack: different documents" );
+ if ( !pTrack || pTrack == pChangeTrack || &pTrack->GetDocument() != this )
+ return ;
+ EndChangeTracking();
+ pChangeTrack = std::move(pTrack);
+}
+
+IMPL_LINK_NOARG(ScDocument, TrackTimeHdl, Timer *, void)
+{
+ if ( ScDdeLink::IsInUpdate() ) // do not nest
+ {
+ aTrackIdle.Start(); // try again later
+ }
+ else if (mpShell) // execute
+ {
+ TrackFormulas();
+ mpShell->Broadcast( SfxHint( SfxHintId::ScDataChanged ) );
+
+ if (!mpShell->IsModified())
+ {
+ mpShell->SetModified();
+ SfxBindings* pBindings = GetViewBindings();
+ if (pBindings)
+ {
+ pBindings->Invalidate( SID_SAVEDOC );
+ pBindings->Invalidate( SID_DOC_MODIFIED );
+ }
+ }
+ }
+}
+
+void ScDocument::SetExpandRefs( bool bVal )
+{
+ bExpandRefs = bVal;
+}
+
+void ScDocument::StartTrackTimer()
+{
+ if (!aTrackIdle.IsActive()) // do not postpone for forever
+ aTrackIdle.Start();
+}
+
+void ScDocument::ClosingClipboardSource()
+{
+ if (!bIsClip)
+ return;
+
+ ForgetNoteCaptions( ScRangeList( ScRange( 0,0,0, MaxCol(), MaxRow(), GetTableCount()-1)), true);
+}
+
+ScDocument::~ScDocument()
+{
+ OSL_PRECOND( !bInLinkUpdate, "bInLinkUpdate in dtor" );
+
+ // Join any pending(recalc) threads in global threadpool
+ comphelper::ThreadPool::getSharedOptimalPool().joinThreadsIfIdle();
+
+ bInDtorClear = true;
+
+ // first of all disable all refresh timers by deleting the control
+ if ( pRefreshTimerControl )
+ { // To be sure there isn't anything running do it with a protector,
+ // this ensures also that nothing needs the control anymore.
+ ScRefreshTimerProtector aProt( GetRefreshTimerControlAddress() );
+ pRefreshTimerControl.reset();
+ }
+
+ mxFormulaParserPool.reset();
+ // Destroy the external ref mgr instance here because it has a timer
+ // which needs to be stopped before the app closes.
+ pExternalRefMgr.reset();
+
+ ScAddInAsync::RemoveDocument( this );
+ ScAddInListener::RemoveDocument( this );
+ pChartListenerCollection.reset(); // before pBASM because of potential Listener!
+
+ ClearLookupCaches(); // before pBASM because of listeners
+
+ // destroy BroadcastAreas first to avoid un-needed Single-EndListenings of Formula-Cells
+ pBASM.reset(); // BroadcastAreaSlotMachine
+
+ pUnoBroadcaster.reset(); // broadcasts SfxHintId::Dying again
+
+ pUnoRefUndoList.reset();
+ pUnoListenerCalls.reset();
+
+ Clear( true ); // true = from destructor (needed for SdrModel::ClearModel)
+
+ pValidationList.reset();
+ pRangeName.reset();
+ pDBCollection.reset();
+ pSelectionAttr.reset();
+ apTemporaryChartLock.reset();
+ DeleteDrawLayer();
+ mpPrinter.disposeAndClear();
+ ImplDeleteOptions();
+ pConsolidateDlgData.reset();
+ pClipData.reset();
+ pDetOpList.reset(); // also deletes entries
+ pChangeTrack.reset();
+ mpEditEngine.reset();
+ mpNoteEngine.reset();
+ pChangeViewSettings.reset(); // and delete
+ mpVirtualDevice_100th_mm.disposeAndClear();
+
+ pDPCollection.reset();
+ mpAnonymousDBData.reset();
+
+ // delete the EditEngine before destroying the mxPoolHelper
+ pCacheFieldEditEngine.reset();
+
+ if ( mxPoolHelper.is() && !bIsClip && !bIsUndo)
+ mxPoolHelper->SourceDocumentGone();
+ mxPoolHelper.clear();
+
+ pScriptTypeData.reset();
+ maNonThreaded.xRecursionHelper.reset();
+ assert(!maThreadSpecific.xRecursionHelper);
+
+ pPreviewFont.reset();
+ SAL_WARN_IF( pAutoNameCache, "sc.core", "AutoNameCache still set in dtor" );
+
+ mpFormulaGroupCxt.reset();
+ // Purge unused items if the string pool will be still used (e.g. by undo history).
+ if(mpCellStringPool.use_count() > 1)
+ {
+ // Calling purge() may be somewhat expensive with large documents, so
+ // try to delay and compress it for temporary documents.
+ if(IsClipOrUndo())
+ ScGlobal::GetSharedStringPoolPurge().delayedPurge(mpCellStringPool);
+ else
+ mpCellStringPool->purge();
+ }
+ mpCellStringPool.reset();
+
+ assert( pDelayedFormulaGrouping == nullptr );
+ assert( pDelayedStartListeningFormulaCells.empty());
+}
+
+void ScDocument::InitClipPtrs( ScDocument* pSourceDoc )
+{
+ OSL_ENSURE(bIsClip, "InitClipPtrs and not bIsClip");
+
+ ScMutationGuard aGuard(*this, ScMutationGuardFlags::CORE);
+
+ pValidationList.reset();
+
+ Clear();
+
+ SharePooledResources(pSourceDoc);
+
+ // conditional Formats / validations
+ // TODO: Copy Templates?
+ const ScValidationDataList* pSourceValid = pSourceDoc->pValidationList.get();
+ if ( pSourceValid )
+ pValidationList.reset(new ScValidationDataList(*this, *pSourceValid));
+
+ // store Links in Stream
+ pClipData.reset();
+ if (pSourceDoc->GetDocLinkManager().hasDdeLinks())
+ {
+ pClipData.reset( new SvMemoryStream );
+ pSourceDoc->SaveDdeLinks(*pClipData);
+ }
+
+ // Options pointers exist (ImplCreateOptions) for any document.
+ // Must be copied for correct results in OLE objects (#i42666#).
+ SetDocOptions( pSourceDoc->GetDocOptions() );
+ SetViewOptions( pSourceDoc->GetViewOptions() );
+}
+
+SvNumberFormatter* ScDocument::GetFormatTable() const
+{
+ assert(!IsThreadedGroupCalcInProgress());
+ return mxPoolHelper->GetFormTable();
+}
+
+SfxItemPool* ScDocument::GetEditPool() const
+{
+ return mxPoolHelper->GetEditPool();
+}
+
+SfxItemPool* ScDocument::GetEnginePool() const
+{
+ return mxPoolHelper->GetEnginePool();
+}
+
+ScFieldEditEngine& ScDocument::GetEditEngine()
+{
+ if ( !mpEditEngine )
+ {
+ mpEditEngine.reset( new ScFieldEditEngine(this, GetEnginePool(), GetEditPool()) );
+ mpEditEngine->SetUpdateLayout( false );
+ mpEditEngine->EnableUndo( false );
+ mpEditEngine->SetRefMapMode(MapMode(MapUnit::Map100thMM));
+ ApplyAsianEditSettings( *mpEditEngine );
+ }
+ return *mpEditEngine;
+}
+
+ScNoteEditEngine& ScDocument::GetNoteEngine()
+{
+ if ( !mpNoteEngine )
+ {
+ ScMutationGuard aGuard(*this, ScMutationGuardFlags::CORE);
+ mpNoteEngine.reset( new ScNoteEditEngine( GetEnginePool(), GetEditPool() ) );
+ mpNoteEngine->SetUpdateLayout( false );
+ mpNoteEngine->EnableUndo( false );
+ mpNoteEngine->SetRefMapMode(MapMode(MapUnit::Map100thMM));
+ ApplyAsianEditSettings( *mpNoteEngine );
+ const SfxItemSet& rItemSet = GetDefPattern()->GetItemSet();
+ SfxItemSet aEEItemSet( mpNoteEngine->GetEmptyItemSet() );
+ ScPatternAttr::FillToEditItemSet( aEEItemSet, rItemSet );
+ mpNoteEngine->SetDefaults( std::move(aEEItemSet) ); // edit engine takes ownership
+ }
+ return *mpNoteEngine;
+}
+
+void ScDocument::ResetClip( ScDocument* pSourceDoc, const ScMarkData* pMarks )
+{
+ if (bIsClip)
+ {
+ InitClipPtrs(pSourceDoc);
+
+ for (SCTAB i = 0; i < static_cast<SCTAB>(pSourceDoc->maTabs.size()); i++)
+ if (pSourceDoc->maTabs[i])
+ if (!pMarks || pMarks->GetTableSelect(i))
+ {
+ OUString aString = pSourceDoc->maTabs[i]->GetName();
+ if ( i < static_cast<SCTAB>(maTabs.size()) )
+ {
+ maTabs[i].reset( new ScTable(*this, i, aString) );
+
+ }
+ else
+ {
+ if( i > static_cast<SCTAB>(maTabs.size()) )
+ {
+ maTabs.resize(i);
+ }
+ maTabs.emplace_back(new ScTable(*this, i, aString));
+ }
+ maTabs[i]->SetLayoutRTL( pSourceDoc->maTabs[i]->IsLayoutRTL() );
+ }
+ }
+ else
+ {
+ OSL_FAIL("ResetClip");
+ }
+}
+
+void ScDocument::ResetClip( ScDocument* pSourceDoc, SCTAB nTab )
+{
+ if (bIsClip)
+ {
+ InitClipPtrs(pSourceDoc);
+ if (nTab >= static_cast<SCTAB>(maTabs.size()))
+ {
+ maTabs.resize(nTab+1);
+ }
+ maTabs[nTab].reset( new ScTable(*this, nTab, "baeh") );
+ if (nTab < static_cast<SCTAB>(pSourceDoc->maTabs.size()) && pSourceDoc->maTabs[nTab])
+ maTabs[nTab]->SetLayoutRTL( pSourceDoc->maTabs[nTab]->IsLayoutRTL() );
+ }
+ else
+ {
+ OSL_FAIL("ResetClip");
+ }
+}
+
+void ScDocument::EnsureTable( SCTAB nTab )
+{
+ bool bExtras = !bIsUndo; // Column-Widths, Row-Heights, Flags
+ if (o3tl::make_unsigned(nTab) >= maTabs.size())
+ maTabs.resize(nTab+1);
+
+ if (!maTabs[nTab])
+ maTabs[nTab].reset( new ScTable(*this, nTab, "temp", bExtras, bExtras) );
+}
+
+ScRefCellValue ScDocument::GetRefCellValue( const ScAddress& rPos )
+{
+ if (!TableExists(rPos.Tab()))
+ return ScRefCellValue(); // empty
+
+ return maTabs[rPos.Tab()]->GetRefCellValue(rPos.Col(), rPos.Row());
+}
+
+ScRefCellValue ScDocument::GetRefCellValue( const ScAddress& rPos, sc::ColumnBlockPosition& rBlockPos )
+{
+ if (!TableExists(rPos.Tab()))
+ return ScRefCellValue(); // empty
+
+ return maTabs[rPos.Tab()]->GetRefCellValue(rPos.Col(), rPos.Row(), rBlockPos);
+}
+
+svl::SharedStringPool& ScDocument::GetSharedStringPool()
+{
+ return *mpCellStringPool;
+}
+
+const svl::SharedStringPool& ScDocument::GetSharedStringPool() const
+{
+ return *mpCellStringPool;
+}
+
+bool ScDocument::GetPrintArea( SCTAB nTab, SCCOL& rEndCol, SCROW& rEndRow,
+ bool bNotes) const
+{
+ if (ValidTab(nTab) && nTab < static_cast<SCTAB>(maTabs.size()) && maTabs[nTab])
+ {
+ bool bAny = maTabs[nTab]->GetPrintArea( rEndCol, rEndRow, bNotes, /*bCalcHiddens*/false);
+ if (mpDrawLayer)
+ {
+ ScRange aDrawRange(0,0,nTab, MaxCol(),MaxRow(),nTab);
+ if (DrawGetPrintArea( aDrawRange, true, true ))
+ {
+ if (aDrawRange.aEnd.Col()>rEndCol) rEndCol=aDrawRange.aEnd.Col();
+ if (aDrawRange.aEnd.Row()>rEndRow) rEndRow=aDrawRange.aEnd.Row();
+ bAny = true;
+ }
+ }
+ return bAny;
+ }
+
+ rEndCol = 0;
+ rEndRow = 0;
+ return false;
+}
+
+bool ScDocument::GetPrintAreaHor( SCTAB nTab, SCROW nStartRow, SCROW nEndRow,
+ SCCOL& rEndCol ) const
+{
+ if (ValidTab(nTab) && nTab < static_cast<SCTAB>(maTabs.size()) && maTabs[nTab])
+ {
+ bool bAny = maTabs[nTab]->GetPrintAreaHor( nStartRow, nEndRow, rEndCol );
+ if (mpDrawLayer)
+ {
+ ScRange aDrawRange(0,nStartRow,nTab, MaxCol(),nEndRow,nTab);
+ if (DrawGetPrintArea( aDrawRange, true, false ))
+ {
+ if (aDrawRange.aEnd.Col()>rEndCol) rEndCol=aDrawRange.aEnd.Col();
+ bAny = true;
+ }
+ }
+ return bAny;
+ }
+
+ rEndCol = 0;
+ return false;
+}
+
+bool ScDocument::GetPrintAreaVer( SCTAB nTab, SCCOL nStartCol, SCCOL nEndCol,
+ SCROW& rEndRow, bool bNotes ) const
+{
+ if (ValidTab(nTab) && nTab < static_cast<SCTAB>(maTabs.size()) && maTabs[nTab])
+ {
+ bool bAny = maTabs[nTab]->GetPrintAreaVer( nStartCol, nEndCol, rEndRow, bNotes );
+ if (mpDrawLayer)
+ {
+ ScRange aDrawRange(nStartCol,0,nTab, nEndCol,MaxRow(),nTab);
+ if (DrawGetPrintArea( aDrawRange, false, true ))
+ {
+ if (aDrawRange.aEnd.Row()>rEndRow) rEndRow=aDrawRange.aEnd.Row();
+ bAny = true;
+ }
+ }
+ return bAny;
+ }
+
+ rEndRow = 0;
+ return false;
+}
+
+bool ScDocument::GetDataStart( SCTAB nTab, SCCOL& rStartCol, SCROW& rStartRow ) const
+{
+ if (ValidTab(nTab) && nTab < static_cast<SCTAB>(maTabs.size()) && maTabs[nTab])
+ {
+ bool bAny = maTabs[nTab]->GetDataStart( rStartCol, rStartRow );
+ if (mpDrawLayer)
+ {
+ ScRange aDrawRange(0,0,nTab, MaxCol(),MaxRow(),nTab);
+ if (DrawGetPrintArea( aDrawRange, true, true ))
+ {
+ if (aDrawRange.aStart.Col()<rStartCol) rStartCol=aDrawRange.aStart.Col();
+ if (aDrawRange.aStart.Row()<rStartRow) rStartRow=aDrawRange.aStart.Row();
+ bAny = true;
+ }
+ }
+ return bAny;
+ }
+
+ rStartCol = 0;
+ rStartRow = 0;
+ return false;
+}
+
+void ScDocument::GetTiledRenderingArea(SCTAB nTab, SCCOL& rEndCol, SCROW& rEndRow) const
+{
+ bool bHasPrintArea = GetPrintArea(nTab, rEndCol, rEndRow, false);
+
+ // we need some reasonable minimal document size
+ ScViewData* pViewData = ScDocShell::GetViewData();
+ if (!pViewData)
+ {
+ if (!bHasPrintArea)
+ {
+ rEndCol = 20;
+ rEndRow = 50;
+ }
+ else
+ {
+ rEndCol += 20;
+ rEndRow += 50;
+ }
+ }
+ else if (!bHasPrintArea)
+ {
+ rEndCol = pViewData->GetMaxTiledCol();
+ rEndRow = pViewData->GetMaxTiledRow();
+ }
+ else
+ {
+ rEndCol = std::max(rEndCol, pViewData->GetMaxTiledCol());
+ rEndRow = std::max(rEndRow, pViewData->GetMaxTiledRow());
+ }
+}
+
+bool ScDocument::MoveTab( SCTAB nOldPos, SCTAB nNewPos, ScProgress* pProgress )
+{
+ if (nOldPos == nNewPos)
+ return false;
+
+ SCTAB nTabCount = static_cast<SCTAB>(maTabs.size());
+ if(nTabCount < 2)
+ return false;
+
+ bool bValid = false;
+ if (ValidTab(nOldPos) && nOldPos < nTabCount )
+ {
+ if (maTabs[nOldPos])
+ {
+ sc::AutoCalcSwitch aACSwitch(*this, false);
+ sc::DelayDeletingBroadcasters delayDeletingBroadcasters(*this);
+
+ SetNoListening( true );
+ if (nNewPos == SC_TAB_APPEND || nNewPos >= nTabCount)
+ nNewPos = nTabCount-1;
+
+ // Update Reference
+ // TODO: combine with UpdateReference!
+
+ sc::RefUpdateMoveTabContext aCxt( *this, nOldPos, nNewPos);
+
+ SCTAB nDz = nNewPos - nOldPos;
+ ScRange aSourceRange( 0,0,nOldPos, MaxCol(),MaxRow(),nOldPos );
+ if (pRangeName)
+ pRangeName->UpdateMoveTab(aCxt);
+
+ pDBCollection->UpdateMoveTab( nOldPos, nNewPos );
+ xColNameRanges->UpdateReference( URM_REORDER, this, aSourceRange, 0,0,nDz );
+ xRowNameRanges->UpdateReference( URM_REORDER, this, aSourceRange, 0,0,nDz );
+ if (pDPCollection)
+ pDPCollection->UpdateReference( URM_REORDER, aSourceRange, 0,0,nDz );
+ if (pDetOpList)
+ pDetOpList->UpdateReference( this, URM_REORDER, aSourceRange, 0,0,nDz );
+ UpdateChartRef( URM_REORDER,
+ 0,0,nOldPos, MaxCol(),MaxRow(),nOldPos, 0,0,nDz );
+ UpdateRefAreaLinks( URM_REORDER, aSourceRange, 0,0,nDz );
+ if ( pValidationList )
+ pValidationList->UpdateMoveTab(aCxt);
+ if ( pUnoBroadcaster )
+ pUnoBroadcaster->Broadcast( ScUpdateRefHint( URM_REORDER,
+ aSourceRange, 0,0,nDz ) );
+
+ ScTableUniquePtr pSaveTab = std::move(maTabs[nOldPos]);
+ maTabs.erase(maTabs.begin()+nOldPos);
+ maTabs.insert(maTabs.begin()+nNewPos, std::move(pSaveTab));
+ for (SCTAB i = 0; i < nTabCount; i++)
+ if (maTabs[i])
+ maTabs[i]->UpdateMoveTab(aCxt, i, pProgress);
+ for (auto& rxTab : maTabs)
+ if (rxTab)
+ rxTab->UpdateCompile();
+ SetNoListening( false );
+ StartAllListeners();
+
+ sc::SetFormulaDirtyContext aFormulaDirtyCxt;
+ SetAllFormulasDirty(aFormulaDirtyCxt);
+
+ if (mpDrawLayer)
+ mpDrawLayer->ScMovePage( static_cast<sal_uInt16>(nOldPos), static_cast<sal_uInt16>(nNewPos) );
+
+ bValid = true;
+ }
+ }
+ return bValid;
+}
+
+bool ScDocument::CopyTab( SCTAB nOldPos, SCTAB nNewPos, const ScMarkData* pOnlyMarked )
+{
+ if (SC_TAB_APPEND == nNewPos || nNewPos >= static_cast<SCTAB>(maTabs.size()))
+ nNewPos = static_cast<SCTAB>(maTabs.size());
+ OUString aName;
+ GetName(nOldPos, aName);
+
+ // check first if Prefix is valid; if not, then only avoid duplicates
+ bool bPrefix = ValidTabName( aName );
+ OSL_ENSURE(bPrefix, "invalid table name");
+ SCTAB nDummy;
+
+ CreateValidTabName(aName);
+
+ bool bValid;
+ if (bPrefix)
+ bValid = ValidNewTabName(aName);
+ else
+ bValid = !GetTable( aName, nDummy );
+
+ sc::AutoCalcSwitch aACSwitch(*this, false);
+ sc::RefUpdateInsertTabContext aCxt( *this, nNewPos, 1);
+
+ if (bValid)
+ {
+ if (nNewPos >= static_cast<SCTAB>(maTabs.size()))
+ {
+ nNewPos = static_cast<SCTAB>(maTabs.size());
+ maTabs.emplace_back(new ScTable(*this, nNewPos, aName));
+ }
+ else
+ {
+ if (ValidTab(nNewPos) && (nNewPos < static_cast<SCTAB>(maTabs.size())))
+ {
+ SetNoListening( true );
+
+ ScRange aRange( 0,0,nNewPos, MaxCol(),MaxRow(),MAXTAB );
+ xColNameRanges->UpdateReference( URM_INSDEL, this, aRange, 0,0,1 );
+ xRowNameRanges->UpdateReference( URM_INSDEL, this, aRange, 0,0,1 );
+ if (pRangeName)
+ pRangeName->UpdateInsertTab(aCxt);
+
+ pDBCollection->UpdateReference(
+ URM_INSDEL, 0,0,nNewPos, MaxCol(),MaxRow(),MAXTAB, 0,0,1 );
+ if (pDPCollection)
+ pDPCollection->UpdateReference( URM_INSDEL, aRange, 0,0,1 );
+ if (pDetOpList)
+ pDetOpList->UpdateReference( this, URM_INSDEL, aRange, 0,0,1 );
+ UpdateChartRef( URM_INSDEL, 0,0,nNewPos, MaxCol(),MaxRow(),MAXTAB, 0,0,1 );
+ UpdateRefAreaLinks( URM_INSDEL, aRange, 0,0,1 );
+ if ( pUnoBroadcaster )
+ pUnoBroadcaster->Broadcast( ScUpdateRefHint( URM_INSDEL, aRange, 0,0,1 ) );
+
+ for (TableContainer::iterator it = maTabs.begin(); it != maTabs.end(); ++it)
+ if (*it && it != (maTabs.begin() + nOldPos))
+ (*it)->UpdateInsertTab(aCxt);
+ if (nNewPos <= nOldPos)
+ nOldPos++;
+ maTabs.emplace(maTabs.begin() + nNewPos, new ScTable(*this, nNewPos, aName));
+ bValid = true;
+ for (TableContainer::iterator it = maTabs.begin(); it != maTabs.end(); ++it)
+ if (*it && it != maTabs.begin()+nOldPos && it != maTabs.begin() + nNewPos)
+ (*it)->UpdateCompile();
+ SetNoListening( false );
+ sc::StartListeningContext aSLCxt(*this);
+ for (TableContainer::iterator it = maTabs.begin(); it != maTabs.end(); ++it)
+ if (*it && it != maTabs.begin()+nOldPos && it != maTabs.begin()+nNewPos)
+ (*it)->StartListeners(aSLCxt, true);
+
+ if (pValidationList)
+ pValidationList->UpdateInsertTab(aCxt);
+ }
+ else
+ bValid = false;
+ }
+ }
+
+ if (bValid)
+ {
+ SetNoListening( true ); // not yet at CopyToTable/Insert
+
+ const bool bGlobalNamesToLocal = true;
+ const SCTAB nRealOldPos = (nNewPos < nOldPos) ? nOldPos - 1 : nOldPos;
+ const ScRangeName* pNames = GetRangeName( nOldPos);
+ if (pNames)
+ pNames->CopyUsedNames( nOldPos, nRealOldPos, nNewPos, *this, *this, bGlobalNamesToLocal);
+ GetRangeName()->CopyUsedNames( -1, nRealOldPos, nNewPos, *this, *this, bGlobalNamesToLocal);
+
+ sc::CopyToDocContext aCopyDocCxt(*this);
+ pDBCollection->CopyToTable(nOldPos, nNewPos);
+ maTabs[nOldPos]->CopyToTable(aCopyDocCxt, 0, 0, MaxCol(), MaxRow(), InsertDeleteFlags::ALL,
+ (pOnlyMarked != nullptr), maTabs[nNewPos].get(), pOnlyMarked,
+ false /*bAsLink*/, true /*bColRowFlags*/, bGlobalNamesToLocal, false /*bCopyCaptions*/ );
+ maTabs[nNewPos]->SetTabBgColor(maTabs[nOldPos]->GetTabBgColor());
+
+ SCTAB nDz = nNewPos - nOldPos;
+ sc::RefUpdateContext aRefCxt(*this);
+ aRefCxt.meMode = URM_COPY;
+ aRefCxt.maRange = ScRange(0, 0, nNewPos, MaxCol(), MaxRow(), nNewPos);
+ aRefCxt.mnTabDelta = nDz;
+ maTabs[nNewPos]->UpdateReference(aRefCxt);
+
+ maTabs[nNewPos]->UpdateInsertTabAbs(nNewPos); // move all paragraphs up by one!!
+ maTabs[nOldPos]->UpdateInsertTab(aCxt);
+
+ maTabs[nOldPos]->UpdateCompile();
+ maTabs[nNewPos]->UpdateCompile( true ); // maybe already compiled in Clone, but used names need recompilation
+ SetNoListening( false );
+ sc::StartListeningContext aSLCxt(*this);
+ maTabs[nOldPos]->StartListeners(aSLCxt, true);
+ maTabs[nNewPos]->StartListeners(aSLCxt, true);
+
+ sc::SetFormulaDirtyContext aFormulaDirtyCxt;
+ SetAllFormulasDirty(aFormulaDirtyCxt);
+
+ if (mpDrawLayer) // Skip cloning Note caption object
+ // page is already created in ScTable ctor
+ mpDrawLayer->ScCopyPage( static_cast<sal_uInt16>(nOldPos), static_cast<sal_uInt16>(nNewPos) );
+
+ if (pDPCollection)
+ pDPCollection->CopyToTab(nOldPos, nNewPos);
+
+ maTabs[nNewPos]->SetPageStyle( maTabs[nOldPos]->GetPageStyle() );
+ maTabs[nNewPos]->SetPendingRowHeights( maTabs[nOldPos]->IsPendingRowHeights() );
+
+ // Copy the custom print range if exists.
+ maTabs[nNewPos]->CopyPrintRange(*maTabs[nOldPos]);
+
+ // Copy the RTL settings
+ maTabs[nNewPos]->SetLayoutRTL(maTabs[nOldPos]->IsLayoutRTL());
+ maTabs[nNewPos]->SetLoadingRTL(maTabs[nOldPos]->IsLoadingRTL());
+
+ // Finally copy the note captions, which need
+ // 1. the updated source ScColumn::nTab members if nNewPos <= nOldPos
+ // 2. row heights and column widths of the destination
+ // 3. RTL settings of the destination
+ maTabs[nOldPos]->CopyCaptionsToTable( 0, 0, MaxCol(), MaxRow(), maTabs[nNewPos].get(), true /*bCloneCaption*/);
+ }
+
+ return bValid;
+}
+
+sal_uLong ScDocument::TransferTab( ScDocument& rSrcDoc, SCTAB nSrcPos,
+ SCTAB nDestPos, bool bInsertNew,
+ bool bResultsOnly )
+{
+ sal_uLong nRetVal = 1; // 0 => error 1 = ok
+ // 3 => NameBox
+ // 4 => both
+
+ if (rSrcDoc.mpShell->GetMedium())
+ {
+ rSrcDoc.maFileURL = rSrcDoc.mpShell->GetMedium()->GetURLObject().GetMainURL(INetURLObject::DecodeMechanism::ToIUri);
+ // for unsaved files use the title name and adjust during save of file
+ if (rSrcDoc.maFileURL.isEmpty())
+ rSrcDoc.maFileURL = rSrcDoc.mpShell->GetName();
+ }
+ else
+ {
+ rSrcDoc.maFileURL = rSrcDoc.mpShell->GetName();
+ }
+
+ bool bValid = true;
+ if (bInsertNew) // re-insert
+ {
+ OUString aName;
+ rSrcDoc.GetName(nSrcPos, aName);
+ CreateValidTabName(aName);
+ bValid = InsertTab(nDestPos, aName);
+
+ // Copy the RTL settings
+ maTabs[nDestPos]->SetLayoutRTL(rSrcDoc.maTabs[nSrcPos]->IsLayoutRTL());
+ maTabs[nDestPos]->SetLoadingRTL(rSrcDoc.maTabs[nSrcPos]->IsLoadingRTL());
+ }
+ else // replace existing tables
+ {
+ if (ValidTab(nDestPos) && nDestPos < static_cast<SCTAB>(maTabs.size()) && maTabs[nDestPos])
+ {
+ maTabs[nDestPos]->DeleteArea( 0,0, MaxCol(),MaxRow(), InsertDeleteFlags::ALL );
+ }
+ else
+ bValid = false;
+ }
+
+ if (bValid)
+ {
+ bool bOldAutoCalcSrc = false;
+ bool bOldAutoCalc = GetAutoCalc();
+ SetAutoCalc( false ); // avoid repeated calculations
+ SetNoListening( true );
+ if ( bResultsOnly )
+ {
+ bOldAutoCalcSrc = rSrcDoc.GetAutoCalc();
+ rSrcDoc.SetAutoCalc( true ); // in case something needs calculation
+ }
+
+ {
+ NumFmtMergeHandler aNumFmtMergeHdl(*this, rSrcDoc);
+
+ sc::CopyToDocContext aCxt(*this);
+ nDestPos = std::min(nDestPos, static_cast<SCTAB>(GetTableCount() - 1));
+ { // scope for bulk broadcast
+ ScBulkBroadcast aBulkBroadcast( pBASM.get(), SfxHintId::ScDataChanged);
+ if (!bResultsOnly)
+ {
+ const bool bGlobalNamesToLocal = false;
+ const ScRangeName* pNames = rSrcDoc.GetRangeName( nSrcPos);
+ if (pNames)
+ pNames->CopyUsedNames( nSrcPos, nSrcPos, nDestPos, rSrcDoc, *this, bGlobalNamesToLocal);
+ rSrcDoc.GetRangeName()->CopyUsedNames( -1, nSrcPos, nDestPos, rSrcDoc, *this, bGlobalNamesToLocal);
+ }
+ rSrcDoc.maTabs[nSrcPos]->CopyToTable(aCxt, 0, 0, MaxCol(), MaxRow(),
+ ( bResultsOnly ? InsertDeleteFlags::ALL & ~InsertDeleteFlags::FORMULA : InsertDeleteFlags::ALL),
+ false, maTabs[nDestPos].get(), /*pMarkData*/nullptr, /*bAsLink*/false, /*bColRowFlags*/true,
+ /*bGlobalNamesToLocal*/false, /*bCopyCaptions*/true );
+ }
+ }
+ maTabs[nDestPos]->SetTabNo(nDestPos);
+ maTabs[nDestPos]->SetTabBgColor(rSrcDoc.maTabs[nSrcPos]->GetTabBgColor());
+
+ if ( !bResultsOnly )
+ {
+ sc::RefUpdateContext aRefCxt(*this);
+ aRefCxt.meMode = URM_COPY;
+ aRefCxt.maRange = ScRange(0, 0, nDestPos, MaxCol(), MaxRow(), nDestPos);
+ aRefCxt.mnTabDelta = nDestPos - nSrcPos;
+ maTabs[nDestPos]->UpdateReference(aRefCxt);
+
+ // Readjust self-contained absolute references to this sheet
+ maTabs[nDestPos]->TestTabRefAbs(nSrcPos);
+ sc::CompileFormulaContext aFormulaCxt(*this);
+ maTabs[nDestPos]->CompileAll(aFormulaCxt);
+ }
+
+ SetNoListening( false );
+ if ( !bResultsOnly )
+ {
+ sc::StartListeningContext aSLCxt(*this);
+ maTabs[nDestPos]->StartListeners(aSLCxt, true);
+ }
+ SetDirty( ScRange( 0, 0, nDestPos, MaxCol(), MaxRow(), nDestPos), false);
+
+ if ( bResultsOnly )
+ rSrcDoc.SetAutoCalc( bOldAutoCalcSrc );
+ SetAutoCalc( bOldAutoCalc );
+
+ // copy Drawing
+
+ if (bInsertNew)
+ TransferDrawPage( rSrcDoc, nSrcPos, nDestPos );
+
+ maTabs[nDestPos]->SetPendingRowHeights( rSrcDoc.maTabs[nSrcPos]->IsPendingRowHeights() );
+ }
+ if (!bValid)
+ nRetVal = 0;
+ bool bVbaEnabled = IsInVBAMode();
+
+ if ( bVbaEnabled )
+ {
+ SfxObjectShell* pSrcShell = rSrcDoc.GetDocumentShell();
+ if ( pSrcShell )
+ {
+ OUString aLibName("Standard");
+#if HAVE_FEATURE_SCRIPTING
+ const BasicManager *pBasicManager = pSrcShell->GetBasicManager();
+ if (pBasicManager && !pBasicManager->GetName().isEmpty())
+ {
+ aLibName = pSrcShell->GetBasicManager()->GetName();
+ }
+#endif
+ OUString sSource;
+ uno::Reference< script::XLibraryContainer > xLibContainer = pSrcShell->GetBasicContainer();
+ uno::Reference< container::XNameContainer > xLib;
+ if( xLibContainer.is() )
+ {
+ uno::Any aLibAny = xLibContainer->getByName(aLibName);
+ aLibAny >>= xLib;
+ }
+
+ if( xLib.is() )
+ {
+ OUString sSrcCodeName;
+ rSrcDoc.GetCodeName( nSrcPos, sSrcCodeName );
+ OUString sRTLSource;
+ xLib->getByName( sSrcCodeName ) >>= sRTLSource;
+ sSource = sRTLSource;
+ }
+ VBA_InsertModule( *this, nDestPos, sSource );
+ }
+ }
+
+ return nRetVal;
+}
+
+void ScDocument::SetError( SCCOL nCol, SCROW nRow, SCTAB nTab, const FormulaError nError)
+{
+ if (ValidTab(nTab) && nTab < static_cast<SCTAB>(maTabs.size()))
+ if (maTabs[nTab])
+ maTabs[nTab]->SetError( nCol, nRow, nError );
+}
+
+void ScDocument::SetFormula(
+ const ScAddress& rPos, const ScTokenArray& rArray )
+{
+ if (!TableExists(rPos.Tab()))
+ return;
+
+ maTabs[rPos.Tab()]->SetFormula(rPos.Col(), rPos.Row(), rArray, formula::FormulaGrammar::GRAM_DEFAULT);
+}
+
+void ScDocument::SetFormula(
+ const ScAddress& rPos, const OUString& rFormula, formula::FormulaGrammar::Grammar eGram )
+{
+ if (!TableExists(rPos.Tab()))
+ return;
+
+ maTabs[rPos.Tab()]->SetFormula(rPos.Col(), rPos.Row(), rFormula, eGram);
+}
+
+ScFormulaCell* ScDocument::SetFormulaCell( const ScAddress& rPos, ScFormulaCell* pCell )
+{
+ if (!TableExists(rPos.Tab()))
+ {
+ delete pCell;
+ return nullptr;
+ }
+
+ return maTabs[rPos.Tab()]->SetFormulaCell(rPos.Col(), rPos.Row(), pCell);
+}
+
+bool ScDocument::SetFormulaCells( const ScAddress& rPos, std::vector<ScFormulaCell*>& rCells )
+{
+ if (rCells.empty())
+ return false;
+
+ ScTable* pTab = FetchTable(rPos.Tab());
+ if (!pTab)
+ return false;
+
+ return pTab->SetFormulaCells(rPos.Col(), rPos.Row(), rCells);
+}
+
+void ScDocument::SetConsolidateDlgData( std::unique_ptr<ScConsolidateParam> pData )
+{
+ pConsolidateDlgData = std::move(pData);
+}
+
+void ScDocument::SetChangeViewSettings(const ScChangeViewSettings& rNew)
+{
+ if (pChangeViewSettings==nullptr)
+ pChangeViewSettings.reset( new ScChangeViewSettings );
+
+ *pChangeViewSettings=rNew;
+}
+
+std::unique_ptr<ScFieldEditEngine> ScDocument::CreateFieldEditEngine()
+{
+ std::unique_ptr<ScFieldEditEngine> pNewEditEngine;
+ if (!pCacheFieldEditEngine)
+ {
+ pNewEditEngine.reset( new ScFieldEditEngine(
+ this, GetEnginePool(), GetEditPool(), false) );
+ }
+ else
+ {
+ if ( !bImportingXML )
+ {
+ // #i66209# previous use might not have restored update mode,
+ // ensure same state as for a new EditEngine (UpdateMode = true)
+ pCacheFieldEditEngine->SetUpdateLayout(true);
+ }
+
+ pNewEditEngine = std::move(pCacheFieldEditEngine);
+ }
+ return pNewEditEngine;
+}
+
+void ScDocument::DisposeFieldEditEngine(std::unique_ptr<ScFieldEditEngine>& rpEditEngine)
+{
+ if (!pCacheFieldEditEngine && rpEditEngine)
+ {
+ pCacheFieldEditEngine = std::move( rpEditEngine );
+ pCacheFieldEditEngine->Clear();
+ }
+ else
+ rpEditEngine.reset();
+}
+
+ScLookupCache & ScDocument::GetLookupCache( const ScRange & rRange, ScInterpreterContext* pContext )
+{
+ ScLookupCache* pCache = nullptr;
+ if (!pContext->mxScLookupCache)
+ pContext->mxScLookupCache.reset(new ScLookupCacheMap);
+ ScLookupCacheMap* pCacheMap = pContext->mxScLookupCache.get();
+ // insert with temporary value to avoid doing two lookups
+ auto [findIt, bInserted] = pCacheMap->aCacheMap.emplace(rRange, nullptr);
+ if (bInserted)
+ {
+ findIt->second = std::make_unique<ScLookupCache>(this, rRange, *pCacheMap);
+ pCache = findIt->second.get();
+ // The StartListeningArea() call is not thread-safe, as all threads
+ // would access the same SvtBroadcaster.
+ std::unique_lock guard( mScLookupMutex );
+ StartListeningArea(rRange, false, pCache);
+ }
+ else
+ pCache = (*findIt).second.get();
+
+ return *pCache;
+}
+
+ScSortedRangeCache& ScDocument::GetSortedRangeCache( const ScRange & rRange, const ScQueryParam& param,
+ ScInterpreterContext* pContext )
+{
+ assert(mxScSortedRangeCache);
+ ScSortedRangeCache::HashKey key = ScSortedRangeCache::makeHashKey(rRange, param);
+ // This should be created just once for one range, and repeated calls should reuse it, even
+ // between threads (it doesn't make sense to use ScInterpreterContext and have all threads
+ // build their own copy of the same data). So first try read-only access, which should
+ // in most cases be enough.
+ {
+ std::shared_lock guard(mScLookupMutex);
+ auto findIt = mxScSortedRangeCache->aCacheMap.find(key);
+ if( findIt != mxScSortedRangeCache->aCacheMap.end())
+ return *findIt->second;
+ }
+ // Avoid recursive calls because of some cells in the range being dirty and triggering
+ // interpreting, which may call into this again. Threaded calculation makes sure
+ // no cells are dirty. If some cells in the range cannot be interpreted and remain
+ // dirty e.g. because of circular dependencies, create only an invalid empty cache to prevent
+ // a possible recursive deadlock.
+ bool invalid = false;
+ if(!IsThreadedGroupCalcInProgress())
+ if(!InterpretCellsIfNeeded(rRange))
+ invalid = true;
+ std::unique_lock guard(mScLookupMutex);
+ auto [findIt, bInserted] = mxScSortedRangeCache->aCacheMap.emplace(key, nullptr);
+ if (bInserted)
+ {
+ findIt->second = std::make_unique<ScSortedRangeCache>(this, rRange, param, pContext, invalid);
+ StartListeningArea(rRange, false, findIt->second.get());
+ }
+ return *findIt->second;
+}
+
+void ScDocument::RemoveLookupCache( ScLookupCache & rCache )
+{
+ // Data changes leading to this should never happen during calculation (they are either
+ // a result of user input or recalc). If it turns out this can be the case, locking is needed
+ // here and also in ScLookupCache::Notify().
+ assert(!IsThreadedGroupCalcInProgress());
+ auto & cacheMap = rCache.getCacheMap();
+ auto it(cacheMap.aCacheMap.find(rCache.getRange()));
+ if (it != cacheMap.aCacheMap.end())
+ {
+ ScLookupCache* pCache = (*it).second.release();
+ cacheMap.aCacheMap.erase(it);
+ assert(!IsThreadedGroupCalcInProgress()); // EndListeningArea() is not thread-safe
+ EndListeningArea(pCache->getRange(), false, &rCache);
+ return;
+ }
+ OSL_FAIL( "ScDocument::RemoveLookupCache: range not found in hash map");
+}
+
+void ScDocument::RemoveSortedRangeCache( ScSortedRangeCache & rCache )
+{
+ // Data changes leading to this should never happen during calculation (they are either
+ // a result of user input or recalc). If it turns out this can be the case, locking is needed
+ // here and also in ScSortedRangeCache::Notify().
+ assert(!IsThreadedGroupCalcInProgress());
+ auto it(mxScSortedRangeCache->aCacheMap.find(rCache.getHashKey()));
+ if (it != mxScSortedRangeCache->aCacheMap.end())
+ {
+ ScSortedRangeCache* pCache = (*it).second.release();
+ mxScSortedRangeCache->aCacheMap.erase(it);
+ assert(!IsThreadedGroupCalcInProgress()); // EndListeningArea() is not thread-safe
+ EndListeningArea(pCache->getRange(), false, &rCache);
+ return;
+ }
+ OSL_FAIL( "ScDocument::RemoveSortedRangeCache: range not found in hash map");
+}
+
+void ScDocument::ClearLookupCaches()
+{
+ assert(!IsThreadedGroupCalcInProgress());
+ GetNonThreadedContext().mxScLookupCache.reset();
+ mxScSortedRangeCache->aCacheMap.clear();
+ // Clear lookup cache in all interpreter-contexts in the (threaded/non-threaded) pools.
+ ScInterpreterContextPool::ClearLookupCaches();
+}
+
+bool ScDocument::IsCellInChangeTrack(const ScAddress &cell,Color *pColCellBorder)
+{
+ ScChangeTrack* pTrack = GetChangeTrack();
+ ScChangeViewSettings* pSettings = GetChangeViewSettings();
+ if ( !pTrack || !pTrack->GetFirst() || !pSettings || !pSettings->ShowChanges() )
+ return false; // missing or turned-off
+ ScActionColorChanger aColorChanger(*pTrack);
+ // Clipping happens from outside
+ //! TODO: without Clipping; only paint affected cells ??!??!?
+ const ScChangeAction* pAction = pTrack->GetFirst();
+ while (pAction)
+ {
+ if ( pAction->IsVisible() )
+ {
+ ScChangeActionType eType = pAction->GetType();
+ const ScBigRange& rBig = pAction->GetBigRange();
+ if ( rBig.aStart.Tab() == cell.Tab())
+ {
+ ScRange aRange = rBig.MakeRange( *this );
+ if ( eType == SC_CAT_DELETE_ROWS )
+ aRange.aEnd.SetRow( aRange.aStart.Row() );
+ else if ( eType == SC_CAT_DELETE_COLS )
+ aRange.aEnd.SetCol( aRange.aStart.Col() );
+ if (ScViewUtil::IsActionShown( *pAction, *pSettings, *this ) )
+ {
+ if (aRange.Contains(cell))
+ {
+ if (pColCellBorder != nullptr)
+ {
+ aColorChanger.Update( *pAction );
+ Color aColor( aColorChanger.GetColor() );
+ *pColCellBorder = aColor;
+ }
+ return true;
+ }
+ }
+ }
+ if ( eType == SC_CAT_MOVE &&
+ static_cast<const ScChangeActionMove*>(pAction)->
+ GetFromRange().aStart.Tab() == cell.Col() )
+ {
+ ScRange aRange = static_cast<const ScChangeActionMove*>(pAction)->
+ GetFromRange().MakeRange( *this );
+ if (ScViewUtil::IsActionShown( *pAction, *pSettings, *this ) )
+ {
+ if (aRange.Contains(cell))
+ {
+ if (pColCellBorder != nullptr)
+ {
+ aColorChanger.Update( *pAction );
+ Color aColor( aColorChanger.GetColor() );
+ *pColCellBorder = aColor;
+ }
+ return true;
+ }
+ }
+ }
+ }
+ pAction = pAction->GetNext();
+ }
+ return false;
+}
+
+void ScDocument::GetCellChangeTrackNote( const ScAddress &aCellPos, OUString &aTrackText,bool &bLeftEdge)
+{
+ aTrackText.clear();
+ // Change-Tracking
+ ScChangeTrack* pTrack = GetChangeTrack();
+ ScChangeViewSettings* pSettings = GetChangeViewSettings();
+ if ( !(pTrack && pTrack->GetFirst() && pSettings && pSettings->ShowChanges()))
+ return;
+
+ const ScChangeAction* pFound = nullptr;
+ const ScChangeAction* pFoundContent = nullptr;
+ const ScChangeAction* pFoundMove = nullptr;
+ const ScChangeAction* pAction = pTrack->GetFirst();
+ while (pAction)
+ {
+ if ( pAction->IsVisible() &&
+ ScViewUtil::IsActionShown( *pAction, *pSettings, *this ) )
+ {
+ ScChangeActionType eType = pAction->GetType();
+ const ScBigRange& rBig = pAction->GetBigRange();
+ if ( rBig.aStart.Tab() == aCellPos.Tab())
+ {
+ ScRange aRange = rBig.MakeRange( *this );
+ if ( eType == SC_CAT_DELETE_ROWS )
+ aRange.aEnd.SetRow( aRange.aStart.Row() );
+ else if ( eType == SC_CAT_DELETE_COLS )
+ aRange.aEnd.SetCol( aRange.aStart.Col() );
+ if ( aRange.Contains( aCellPos ) )
+ {
+ pFound = pAction; // the last wins
+ switch ( eType )
+ {
+ case SC_CAT_CONTENT :
+ pFoundContent = pAction;
+ break;
+ case SC_CAT_MOVE :
+ pFoundMove = pAction;
+ break;
+ default:
+ break;
+ }
+ }
+ }
+ if ( eType == SC_CAT_MOVE )
+ {
+ ScRange aRange =
+ static_cast<const ScChangeActionMove*>(pAction)->
+ GetFromRange().MakeRange( *this );
+ if ( aRange.Contains( aCellPos ) )
+ {
+ pFound = pAction;
+ }
+ }
+ }
+ pAction = pAction->GetNext();
+ }
+ if ( !pFound )
+ return;
+
+ if ( pFoundContent && pFound->GetType() != SC_CAT_CONTENT )
+ pFound = pFoundContent; // Content wins
+ if ( pFoundMove && pFound->GetType() != SC_CAT_MOVE &&
+ pFoundMove->GetActionNumber() >
+ pFound->GetActionNumber() )
+ pFound = pFoundMove; // Move wins
+ // for deleted columns: arrow on left side of row
+ if ( pFound->GetType() == SC_CAT_DELETE_COLS )
+ bLeftEdge = true;
+ DateTime aDT = pFound->GetDateTime();
+ aTrackText = pFound->GetUser();
+ aTrackText += ", ";
+ aTrackText += ScGlobal::getLocaleData().getDate(aDT);
+ aTrackText += " ";
+ aTrackText += ScGlobal::getLocaleData().getTime(aDT);
+ aTrackText += ":\n";
+ OUString aComStr = pFound->GetComment();
+ if(!aComStr.isEmpty())
+ {
+ aTrackText += aComStr;
+ aTrackText += "\n( ";
+ }
+ aTrackText = pFound->GetDescription( *this );
+ if (!aComStr.isEmpty())
+ {
+ aTrackText += ")";
+ }
+}
+
+void ScDocument::SetPreviewFont( std::unique_ptr<SfxItemSet> pFont )
+{
+ pPreviewFont = std::move(pFont);
+}
+
+void ScDocument::SetPreviewSelection( const ScMarkData& rSel )
+{
+ maPreviewSelection = rSel;
+}
+
+SfxItemSet* ScDocument::GetPreviewFont( SCCOL nCol, SCROW nRow, SCTAB nTab )
+{
+ SfxItemSet* pRet = nullptr;
+ if ( pPreviewFont )
+ {
+ ScMarkData aSel = GetPreviewSelection();
+ if ( aSel.IsCellMarked( nCol, nRow ) && aSel.GetFirstSelected() == nTab )
+ pRet = pPreviewFont.get();
+ }
+ return pRet;
+}
+
+ScStyleSheet* ScDocument::GetPreviewCellStyle( SCCOL nCol, SCROW nRow, SCTAB nTab )
+{
+ ScStyleSheet* pRet = nullptr;
+ ScMarkData aSel = GetPreviewSelection();
+ if ( pPreviewCellStyle && aSel.IsCellMarked( nCol, nRow ) && aSel.GetFirstSelected() == nTab )
+ pRet = pPreviewCellStyle;
+ return pRet;
+}
+
+sc::IconSetBitmapMap& ScDocument::GetIconSetBitmapMap()
+{
+ if (!m_pIconSetBitmapMap)
+ {
+ m_pIconSetBitmapMap.reset(new sc::IconSetBitmapMap);
+ }
+ return *m_pIconSetBitmapMap;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sc/source/core/data/documen3.cxx b/sc/source/core/data/documen3.cxx
new file mode 100644
index 000000000..77afc2ff7
--- /dev/null
+++ b/sc/source/core/data/documen3.cxx
@@ -0,0 +1,2155 @@
+/* -*- 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 <com/sun/star/script/vba/XVBAEventProcessor.hpp>
+#include <com/sun/star/sheet/TableValidationVisibility.hpp>
+#include <scitems.hxx>
+#include <editeng/langitem.hxx>
+#include <svl/srchitem.hxx>
+#include <sfx2/linkmgr.hxx>
+#include <sfx2/bindings.hxx>
+#include <sfx2/objsh.hxx>
+#include <sfx2/viewsh.hxx>
+#include <vcl/svapp.hxx>
+#include <osl/thread.hxx>
+#include <osl/diagnose.h>
+#include <document.hxx>
+#include <attrib.hxx>
+#include <table.hxx>
+#include <rangenam.hxx>
+#include <dbdata.hxx>
+#include <docpool.hxx>
+#include <poolhelp.hxx>
+#include <rangelst.hxx>
+#include <chartlock.hxx>
+#include <refupdat.hxx>
+#include <docoptio.hxx>
+#include <scmod.hxx>
+#include <clipoptions.hxx>
+#include <viewopti.hxx>
+#include <scextopt.hxx>
+#include <tablink.hxx>
+#include <externalrefmgr.hxx>
+#include <markdata.hxx>
+#include <validat.hxx>
+#include <dociter.hxx>
+#include <detdata.hxx>
+#include <inputopt.hxx>
+#include <chartlis.hxx>
+#include <sc.hrc>
+#include <hints.hxx>
+#include <dpobject.hxx>
+#include <drwlayer.hxx>
+#include <unoreflist.hxx>
+#include <listenercalls.hxx>
+#include <tabprotection.hxx>
+#include <formulaparserpool.hxx>
+#include <clipparam.hxx>
+#include <sheetevents.hxx>
+#include <queryentry.hxx>
+#include <formulacell.hxx>
+#include <refupdatecontext.hxx>
+#include <scopetools.hxx>
+#include <filterentries.hxx>
+#include <queryparam.hxx>
+
+#include <globalnames.hxx>
+#include <LibreOfficeKit/LibreOfficeKitEnums.h>
+#include <comphelper/lok.hxx>
+#include <config_fuzzers.h>
+#include <memory>
+
+using namespace com::sun::star;
+
+namespace {
+
+void sortAndRemoveDuplicates(std::vector<ScTypedStrData>& rStrings, bool bCaseSens)
+{
+ if (bCaseSens)
+ {
+ std::sort(rStrings.begin(), rStrings.end(), ScTypedStrData::LessCaseSensitive());
+ std::vector<ScTypedStrData>::iterator it =
+ std::unique(rStrings.begin(), rStrings.end(), ScTypedStrData::EqualCaseSensitive());
+ rStrings.erase(it, rStrings.end());
+ }
+ else
+ {
+ std::sort(rStrings.begin(), rStrings.end(), ScTypedStrData::LessCaseInsensitive());
+ std::vector<ScTypedStrData>::iterator it =
+ std::unique(rStrings.begin(), rStrings.end(), ScTypedStrData::EqualCaseInsensitive());
+ rStrings.erase(it, rStrings.end());
+ }
+}
+
+}
+
+void ScDocument::GetAllTabRangeNames(ScRangeName::TabNameCopyMap& rNames) const
+{
+ ScRangeName::TabNameCopyMap aNames;
+ for (SCTAB i = 0; i < static_cast<SCTAB>(maTabs.size()); ++i)
+ {
+ if (!maTabs[i])
+ // no more tables to iterate through.
+ break;
+
+ const ScRangeName* p = maTabs[i]->mpRangeName.get();
+ if (!p || p->empty())
+ // ignore empty ones.
+ continue;
+
+ aNames.emplace(i, p);
+ }
+ rNames.swap(aNames);
+}
+
+void ScDocument::SetAllRangeNames(const std::map<OUString, std::unique_ptr<ScRangeName>>& rRangeMap)
+{
+ for (const auto& [rName, rxRangeName] : rRangeMap)
+ {
+ if (rName == STR_GLOBAL_RANGE_NAME)
+ {
+ pRangeName.reset();
+ const ScRangeName *const pName = rxRangeName.get();
+ if (!pName->empty())
+ pRangeName.reset( new ScRangeName( *pName ) );
+ }
+ else
+ {
+ const ScRangeName *const pName = rxRangeName.get();
+ SCTAB nTab;
+ bool bFound = GetTable(rName, nTab);
+ assert(bFound); (void)bFound; // fouled up?
+ if (pName->empty())
+ SetRangeName( nTab, nullptr );
+ else
+ SetRangeName( nTab, std::unique_ptr<ScRangeName>(new ScRangeName( *pName )) );
+ }
+ }
+}
+
+void ScDocument::GetRangeNameMap(std::map<OUString, ScRangeName*>& aRangeNameMap)
+{
+ for (SCTAB i = 0; i < static_cast<SCTAB>(maTabs.size()); ++i)
+ {
+ if (!maTabs[i])
+ continue;
+ ScRangeName* p = maTabs[i]->GetRangeName();
+ if (!p )
+ {
+ p = new ScRangeName();
+ SetRangeName(i, std::unique_ptr<ScRangeName>(p));
+ }
+ OUString aTableName = maTabs[i]->GetName();
+ aRangeNameMap.insert(std::pair<OUString, ScRangeName*>(aTableName,p));
+ }
+ if (!pRangeName)
+ {
+ pRangeName.reset(new ScRangeName());
+ }
+ aRangeNameMap.insert(std::pair<OUString, ScRangeName*>(STR_GLOBAL_RANGE_NAME, pRangeName.get()));
+}
+
+ScRangeName* ScDocument::GetRangeName(SCTAB nTab) const
+{
+ if (!ValidTab(nTab) || nTab >= static_cast<SCTAB>(maTabs.size()) || !maTabs[nTab])
+ return nullptr;
+
+ return maTabs[nTab]->GetRangeName();
+}
+
+ScRangeName* ScDocument::GetRangeName() const
+{
+ if (!pRangeName)
+ pRangeName.reset(new ScRangeName);
+ return pRangeName.get();
+}
+
+void ScDocument::SetRangeName(SCTAB nTab, std::unique_ptr<ScRangeName> pNew)
+{
+ if (!ValidTab(nTab) || nTab >= static_cast<SCTAB>(maTabs.size()) || !maTabs[nTab])
+ return;
+
+ return maTabs[nTab]->SetRangeName(std::move(pNew));
+}
+
+void ScDocument::SetRangeName( std::unique_ptr<ScRangeName> pNewRangeName )
+{
+ pRangeName = std::move(pNewRangeName);
+}
+
+bool ScDocument::IsAddressInRangeName( RangeNameScope eScope, const ScAddress& rAddress )
+{
+ ScRangeName* pRangeNames;
+ ScRange aNameRange;
+
+ if (eScope == RangeNameScope::GLOBAL)
+ pRangeNames= GetRangeName();
+ else
+ pRangeNames= GetRangeName(rAddress.Tab());
+
+ for (const auto& rEntry : *pRangeNames)
+ {
+ if (rEntry.second->IsValidReference(aNameRange))
+ {
+ if (aNameRange.Contains(rAddress))
+ return true;
+ }
+ }
+
+ return false;
+}
+
+bool ScDocument::InsertNewRangeName( const OUString& rName, const ScAddress& rPos, const OUString& rExpr )
+{
+ ScRangeName* pGlobalNames = GetRangeName();
+ if (!pGlobalNames)
+ return false;
+
+ ScRangeData* pName = new ScRangeData(*this, rName, rExpr, rPos, ScRangeData::Type::Name, GetGrammar());
+ return pGlobalNames->insert(pName);
+}
+
+bool ScDocument::InsertNewRangeName( SCTAB nTab, const OUString& rName, const ScAddress& rPos, const OUString& rExpr )
+{
+ ScRangeName* pLocalNames = GetRangeName(nTab);
+ if (!pLocalNames)
+ return false;
+
+ ScRangeData* pName = new ScRangeData(*this, rName, rExpr, rPos, ScRangeData::Type::Name, GetGrammar());
+ return pLocalNames->insert(pName);
+}
+
+const ScRangeData* ScDocument::GetRangeAtBlock( const ScRange& rBlock, OUString& rName, bool* pSheetLocal ) const
+{
+ const ScRangeData* pData = nullptr;
+ if (rBlock.aStart.Tab() == rBlock.aEnd.Tab())
+ {
+ const ScRangeName* pLocalNames = GetRangeName(rBlock.aStart.Tab());
+ if (pLocalNames)
+ {
+ pData = pLocalNames->findByRange( rBlock );
+ if (pData)
+ {
+ rName = pData->GetName();
+ if (pSheetLocal)
+ *pSheetLocal = true;
+ return pData;
+ }
+ }
+ }
+ if ( pRangeName )
+ {
+ pData = pRangeName->findByRange( rBlock );
+ if (pData)
+ {
+ rName = pData->GetName();
+ if (pSheetLocal)
+ *pSheetLocal = false;
+ }
+ }
+ return pData;
+}
+
+ScRangeData* ScDocument::FindRangeNameBySheetAndIndex( SCTAB nTab, sal_uInt16 nIndex ) const
+{
+ const ScRangeName* pRN = (nTab < 0 ? GetRangeName() : GetRangeName(nTab));
+ return (pRN ? pRN->findByIndex( nIndex) : nullptr);
+}
+
+void ScDocument::SetDBCollection( std::unique_ptr<ScDBCollection> pNewDBCollection, bool bRemoveAutoFilter )
+{
+ if (pDBCollection && bRemoveAutoFilter)
+ {
+ // remove auto filter attribute if new db data don't contain auto filter flag
+ // start position is also compared, so bRemoveAutoFilter must not be set from ref-undo!
+
+ ScDBCollection::NamedDBs& rNamedDBs = pDBCollection->getNamedDBs();
+ for (const auto& rxNamedDB : rNamedDBs)
+ {
+ const ScDBData& rOldData = *rxNamedDB;
+ if (!rOldData.HasAutoFilter())
+ continue;
+
+ ScRange aOldRange;
+ rOldData.GetArea(aOldRange);
+
+ bool bFound = false;
+ if (pNewDBCollection)
+ {
+ ScDBData* pNewData = pNewDBCollection->getNamedDBs().findByUpperName(rOldData.GetUpperName());
+ if (pNewData)
+ {
+ if (pNewData->HasAutoFilter())
+ {
+ ScRange aNewRange;
+ pNewData->GetArea(aNewRange);
+ if (aOldRange.aStart == aNewRange.aStart)
+ bFound = true;
+ }
+ }
+ }
+
+ if (!bFound)
+ {
+ aOldRange.aEnd.SetRow(aOldRange.aStart.Row());
+ RemoveFlagsTab( aOldRange.aStart.Col(), aOldRange.aStart.Row(),
+ aOldRange.aEnd.Col(), aOldRange.aEnd.Row(),
+ aOldRange.aStart.Tab(), ScMF::Auto );
+ RepaintRange( aOldRange );
+ }
+ }
+ }
+
+ pDBCollection = std::move(pNewDBCollection);
+}
+
+const ScDBData* ScDocument::GetDBAtCursor(SCCOL nCol, SCROW nRow, SCTAB nTab, ScDBDataPortion ePortion) const
+{
+ if (pDBCollection)
+ return pDBCollection->GetDBAtCursor(nCol, nRow, nTab, ePortion);
+ else
+ return nullptr;
+}
+
+ScDBData* ScDocument::GetDBAtCursor(SCCOL nCol, SCROW nRow, SCTAB nTab, ScDBDataPortion ePortion)
+{
+ if (pDBCollection)
+ return pDBCollection->GetDBAtCursor(nCol, nRow, nTab, ePortion);
+ else
+ return nullptr;
+}
+
+const ScDBData* ScDocument::GetDBAtArea(SCTAB nTab, SCCOL nCol1, SCROW nRow1, SCCOL nCol2, SCROW nRow2) const
+{
+ if (pDBCollection)
+ return pDBCollection->GetDBAtArea(nTab, nCol1, nRow1, nCol2, nRow2);
+ else
+ return nullptr;
+}
+
+ScDBData* ScDocument::GetDBAtArea(SCTAB nTab, SCCOL nCol1, SCROW nRow1, SCCOL nCol2, SCROW nRow2)
+{
+ if (pDBCollection)
+ return pDBCollection->GetDBAtArea(nTab, nCol1, nRow1, nCol2, nRow2);
+ else
+ return nullptr;
+}
+
+void ScDocument::RefreshDirtyTableColumnNames()
+{
+ if (pDBCollection)
+ pDBCollection->RefreshDirtyTableColumnNames();
+}
+
+bool ScDocument::HasPivotTable() const
+{
+ return pDPCollection && pDPCollection->GetCount();
+}
+
+ScDPCollection* ScDocument::GetDPCollection()
+{
+ if (!pDPCollection)
+ pDPCollection.reset( new ScDPCollection(*this) );
+ return pDPCollection.get();
+}
+
+const ScDPCollection* ScDocument::GetDPCollection() const
+{
+ return pDPCollection.get();
+}
+
+ScDPObject* ScDocument::GetDPAtCursor(SCCOL nCol, SCROW nRow, SCTAB nTab) const
+{
+ if (!pDPCollection)
+ return nullptr;
+
+ sal_uInt16 nCount = pDPCollection->GetCount();
+ ScAddress aPos( nCol, nRow, nTab );
+ for (sal_uInt16 i=0; i<nCount; i++)
+ if ( (*pDPCollection)[i].GetOutRange().Contains( aPos ) )
+ return &(*pDPCollection)[i];
+
+ return nullptr;
+}
+
+ScDPObject* ScDocument::GetDPAtBlock( const ScRange & rBlock ) const
+{
+ if (!pDPCollection)
+ return nullptr;
+
+ /* Walk the collection in reverse order to get something of an
+ * approximation of MS Excels 'most recent' effect. */
+ sal_uInt16 i = pDPCollection->GetCount();
+ while ( i-- > 0 )
+ if ( (*pDPCollection)[i].GetOutRange().Contains( rBlock ) )
+ return &(*pDPCollection)[i];
+
+ return nullptr;
+}
+
+void ScDocument::StopTemporaryChartLock()
+{
+ if (apTemporaryChartLock)
+ apTemporaryChartLock->StopLocking();
+}
+
+void ScDocument::SetChartListenerCollection(
+ std::unique_ptr<ScChartListenerCollection> pNewChartListenerCollection,
+ bool bSetChartRangeLists )
+{
+ std::unique_ptr<ScChartListenerCollection> pOld = std::move(pChartListenerCollection);
+ pChartListenerCollection = std::move(pNewChartListenerCollection);
+ if ( pChartListenerCollection )
+ {
+ if ( pOld )
+ pChartListenerCollection->SetDiffDirty( *pOld, bSetChartRangeLists );
+ pChartListenerCollection->StartAllListeners();
+ }
+}
+
+void ScDocument::SetScenario( SCTAB nTab, bool bFlag )
+{
+ if (ValidTab(nTab) && nTab < static_cast<SCTAB>(maTabs.size()) && maTabs[nTab])
+ maTabs[nTab]->SetScenario(bFlag);
+}
+
+bool ScDocument::IsScenario( SCTAB nTab ) const
+{
+ return ValidTab(nTab) && nTab < static_cast<SCTAB>(maTabs.size()) && maTabs[nTab] &&maTabs[nTab]->IsScenario();
+}
+
+void ScDocument::SetScenarioData( SCTAB nTab, const OUString& rComment,
+ const Color& rColor, ScScenarioFlags nFlags )
+{
+ if (ValidTab(nTab) && nTab < static_cast<SCTAB>(maTabs.size()) && maTabs[nTab] && maTabs[nTab]->IsScenario())
+ {
+ maTabs[nTab]->SetScenarioComment( rComment );
+ maTabs[nTab]->SetScenarioColor( rColor );
+ maTabs[nTab]->SetScenarioFlags( nFlags );
+ }
+}
+
+Color ScDocument::GetTabBgColor( SCTAB nTab ) const
+{
+ if (ValidTab(nTab) && nTab < static_cast<SCTAB>(maTabs.size()) && maTabs[nTab])
+ return maTabs[nTab]->GetTabBgColor();
+ return COL_AUTO;
+}
+
+void ScDocument::SetTabBgColor( SCTAB nTab, const Color& rColor )
+{
+ if (ValidTab(nTab) && nTab < static_cast<SCTAB>(maTabs.size()) && maTabs[nTab])
+ maTabs[nTab]->SetTabBgColor(rColor);
+}
+
+bool ScDocument::IsDefaultTabBgColor( SCTAB nTab ) const
+{
+ if (ValidTab(nTab) && nTab < static_cast<SCTAB>(maTabs.size()) && maTabs[nTab])
+ return maTabs[nTab]->GetTabBgColor() == COL_AUTO;
+ return true;
+}
+
+void ScDocument::GetScenarioData( SCTAB nTab, OUString& rComment,
+ Color& rColor, ScScenarioFlags& rFlags ) const
+{
+ if (ValidTab(nTab) && nTab < static_cast<SCTAB>(maTabs.size()) && maTabs[nTab] && maTabs[nTab]->IsScenario())
+ {
+ maTabs[nTab]->GetScenarioComment( rComment );
+ rColor = maTabs[nTab]->GetScenarioColor();
+ rFlags = maTabs[nTab]->GetScenarioFlags();
+ }
+}
+
+void ScDocument::GetScenarioFlags( SCTAB nTab, ScScenarioFlags& rFlags ) const
+{
+ if (ValidTab(nTab) && nTab < static_cast<SCTAB>(maTabs.size()) && maTabs[nTab] && maTabs[nTab]->IsScenario())
+ rFlags = maTabs[nTab]->GetScenarioFlags();
+}
+
+bool ScDocument::IsLinked( SCTAB nTab ) const
+{
+ return ValidTab(nTab) && nTab < static_cast<SCTAB>(maTabs.size()) && maTabs[nTab] && maTabs[nTab]->IsLinked();
+ // equivalent to
+ //if (ValidTab(nTab) && pTab[nTab])
+ // return pTab[nTab]->IsLinked();
+ //return false;
+}
+
+formula::FormulaGrammar::AddressConvention ScDocument::GetAddressConvention() const
+{
+ return formula::FormulaGrammar::extractRefConvention(eGrammar);
+}
+
+void ScDocument::SetGrammar( formula::FormulaGrammar::Grammar eGram )
+{
+ eGrammar = eGram;
+}
+
+ScLinkMode ScDocument::GetLinkMode( SCTAB nTab ) const
+{
+ if (ValidTab(nTab) && nTab < static_cast<SCTAB>(maTabs.size()) && maTabs[nTab])
+ return maTabs[nTab]->GetLinkMode();
+ return ScLinkMode::NONE;
+}
+
+OUString ScDocument::GetLinkDoc( SCTAB nTab ) const
+{
+ if (ValidTab(nTab) && nTab < static_cast<SCTAB>(maTabs.size()) && maTabs[nTab])
+ return maTabs[nTab]->GetLinkDoc();
+ return OUString();
+}
+
+OUString ScDocument::GetLinkFlt( SCTAB nTab ) const
+{
+ if (ValidTab(nTab) && nTab < static_cast<SCTAB>(maTabs.size()) && maTabs[nTab])
+ return maTabs[nTab]->GetLinkFlt();
+ return OUString();
+}
+
+OUString ScDocument::GetLinkOpt( SCTAB nTab ) const
+{
+ if (ValidTab(nTab) && nTab < static_cast<SCTAB>(maTabs.size()) && maTabs[nTab])
+ return maTabs[nTab]->GetLinkOpt();
+ return OUString();
+}
+
+OUString ScDocument::GetLinkTab( SCTAB nTab ) const
+{
+ if (ValidTab(nTab) && nTab < static_cast<SCTAB>(maTabs.size()) && maTabs[nTab])
+ return maTabs[nTab]->GetLinkTab();
+ return OUString();
+}
+
+sal_uLong ScDocument::GetLinkRefreshDelay( SCTAB nTab ) const
+{
+ if (ValidTab(nTab) && nTab < static_cast<SCTAB>(maTabs.size()) && maTabs[nTab])
+ return maTabs[nTab]->GetLinkRefreshDelay();
+ return 0;
+}
+
+void ScDocument::SetLink( SCTAB nTab, ScLinkMode nMode, const OUString& rDoc,
+ const OUString& rFilter, const OUString& rOptions,
+ const OUString& rTabName, sal_uLong nRefreshDelay )
+{
+ if (ValidTab(nTab) && nTab < static_cast<SCTAB>(maTabs.size()) && maTabs[nTab])
+ maTabs[nTab]->SetLink( nMode, rDoc, rFilter, rOptions, rTabName, nRefreshDelay );
+}
+
+bool ScDocument::HasLink( std::u16string_view rDoc,
+ std::u16string_view rFilter, std::u16string_view rOptions ) const
+{
+ SCTAB nCount = static_cast<SCTAB>(maTabs.size());
+ for (SCTAB i=0; i<nCount; i++)
+ if (maTabs[i]->IsLinked()
+ && maTabs[i]->GetLinkDoc() == rDoc
+ && maTabs[i]->GetLinkFlt() == rFilter
+ && maTabs[i]->GetLinkOpt() == rOptions)
+ return true;
+
+ return false;
+}
+
+bool ScDocument::LinkExternalTab( SCTAB& rTab, const OUString& aDocTab,
+ const OUString& aFileName, const OUString& aTabName )
+{
+ if ( IsClipboard() )
+ {
+ OSL_FAIL( "LinkExternalTab in Clipboard" );
+ return false;
+ }
+ rTab = 0;
+#if ENABLE_FUZZERS
+ (void)aDocTab;
+ (void)aFileName;
+ (void)aTabName;
+ return false;
+#else
+ OUString aFilterName; // Is filled by the Loader
+ OUString aOptions; // Filter options
+ sal_uInt32 nLinkCnt = pExtDocOptions ? pExtDocOptions->GetDocSettings().mnLinkCnt : 0;
+ ScDocumentLoader aLoader( aFileName, aFilterName, aOptions, nLinkCnt + 1 );
+ if ( aLoader.IsError() )
+ return false;
+ ScDocument* pSrcDoc = aLoader.GetDocument();
+
+ // Copy table
+ SCTAB nSrcTab;
+ if ( pSrcDoc->GetTable( aTabName, nSrcTab ) )
+ {
+ if ( !InsertTab( SC_TAB_APPEND, aDocTab, true ) )
+ {
+ OSL_FAIL("can't insert external document table");
+ return false;
+ }
+ rTab = GetTableCount() - 1;
+ // Don't insert anew, just the results
+ TransferTab( *pSrcDoc, nSrcTab, rTab, false, true );
+ }
+ else
+ return false;
+
+ sal_uLong nRefreshDelay = 0;
+
+ bool bWasThere = HasLink( aFileName, aFilterName, aOptions );
+ SetLink( rTab, ScLinkMode::VALUE, aFileName, aFilterName, aOptions, aTabName, nRefreshDelay );
+ if ( !bWasThere ) // Add link only once per source document
+ {
+ ScTableLink* pLink = new ScTableLink( mpShell, aFileName, aFilterName, aOptions, nRefreshDelay );
+ pLink->SetInCreate( true );
+ OUString aFilName = aFilterName;
+ GetLinkManager()->InsertFileLink( *pLink, sfx2::SvBaseLinkObjectType::ClientFile, aFileName, &aFilName );
+ pLink->Update();
+ pLink->SetInCreate( false );
+ SfxBindings* pBindings = GetViewBindings();
+ if (pBindings)
+ pBindings->Invalidate( SID_LINKS );
+ }
+ return true;
+#endif
+}
+
+ScExternalRefManager* ScDocument::GetExternalRefManager() const
+{
+ ScDocument* pThis = const_cast<ScDocument*>(this);
+ if (!pExternalRefMgr)
+ pThis->pExternalRefMgr.reset( new ScExternalRefManager(*pThis));
+
+ return pExternalRefMgr.get();
+}
+
+bool ScDocument::IsInExternalReferenceMarking() const
+{
+ return pExternalRefMgr && pExternalRefMgr->isInReferenceMarking();
+}
+
+void ScDocument::MarkUsedExternalReferences()
+{
+ if (!pExternalRefMgr)
+ return;
+ if (!pExternalRefMgr->hasExternalData())
+ return;
+ // Charts.
+ pExternalRefMgr->markUsedByLinkListeners();
+ // Formula cells.
+ pExternalRefMgr->markUsedExternalRefCells();
+
+ /* NOTE: Conditional formats and validation objects are marked when
+ * collecting them during export. */
+}
+
+ScFormulaParserPool& ScDocument::GetFormulaParserPool() const
+{
+ if (!mxFormulaParserPool)
+ mxFormulaParserPool.reset( new ScFormulaParserPool( *this ) );
+ return *mxFormulaParserPool;
+}
+
+const ScSheetEvents* ScDocument::GetSheetEvents( SCTAB nTab ) const
+{
+ if (ValidTab(nTab) && nTab < static_cast<SCTAB>(maTabs.size()) && maTabs[nTab])
+ return maTabs[nTab]->GetSheetEvents();
+ return nullptr;
+}
+
+void ScDocument::SetSheetEvents( SCTAB nTab, std::unique_ptr<ScSheetEvents> pNew )
+{
+ if (ValidTab(nTab) && nTab < static_cast<SCTAB>(maTabs.size()) && maTabs[nTab])
+ maTabs[nTab]->SetSheetEvents( std::move(pNew) );
+}
+
+bool ScDocument::HasSheetEventScript( SCTAB nTab, ScSheetEventId nEvent, bool bWithVbaEvents ) const
+{
+ if (nTab < static_cast<SCTAB>(maTabs.size()) && maTabs[nTab])
+ {
+ // check if any event handler script has been configured
+ const ScSheetEvents* pEvents = maTabs[nTab]->GetSheetEvents();
+ if ( pEvents && pEvents->GetScript( nEvent ) )
+ return true;
+ // check if VBA event handlers exist
+ if (bWithVbaEvents && mxVbaEvents.is()) try
+ {
+ uno::Sequence< uno::Any > aArgs{ uno::Any(nTab) };
+ if (mxVbaEvents->hasVbaEventHandler( ScSheetEvents::GetVbaSheetEventId( nEvent ), aArgs ) ||
+ mxVbaEvents->hasVbaEventHandler( ScSheetEvents::GetVbaDocumentEventId( nEvent ), uno::Sequence< uno::Any >() ))
+ return true;
+ }
+ catch( uno::Exception& )
+ {
+ }
+ }
+ return false;
+}
+
+bool ScDocument::HasAnySheetEventScript( ScSheetEventId nEvent, bool bWithVbaEvents ) const
+{
+ SCTAB nSize = static_cast<SCTAB>(maTabs.size());
+ for (SCTAB nTab = 0; nTab < nSize; nTab++)
+ if (HasSheetEventScript( nTab, nEvent, bWithVbaEvents ))
+ return true;
+ return false;
+}
+
+bool ScDocument::HasAnyCalcNotification() const
+{
+ SCTAB nSize = static_cast<SCTAB>(maTabs.size());
+ for (SCTAB nTab = 0; nTab < nSize; nTab++)
+ if (maTabs[nTab] && maTabs[nTab]->GetCalcNotification())
+ return true;
+ return false;
+}
+
+bool ScDocument::HasCalcNotification( SCTAB nTab ) const
+{
+ if (ValidTab(nTab) && nTab < static_cast<SCTAB>(maTabs.size()) && maTabs[nTab])
+ return maTabs[nTab]->GetCalcNotification();
+ return false;
+}
+
+void ScDocument::SetCalcNotification( SCTAB nTab )
+{
+ // set only if not set before
+ if (ValidTab(nTab) && nTab < static_cast<SCTAB>(maTabs.size()) && maTabs[nTab] && !maTabs[nTab]->GetCalcNotification())
+ maTabs[nTab]->SetCalcNotification(true);
+}
+
+void ScDocument::ResetCalcNotifications()
+{
+ SCTAB nSize = static_cast<SCTAB>(maTabs.size());
+ for (SCTAB nTab = 0; nTab < nSize; nTab++)
+ if (maTabs[nTab] && maTabs[nTab]->GetCalcNotification())
+ maTabs[nTab]->SetCalcNotification(false);
+}
+
+ScOutlineTable* ScDocument::GetOutlineTable( SCTAB nTab, bool bCreate )
+{
+ ScOutlineTable* pVal = nullptr;
+
+ if (ValidTab(nTab) && nTab < static_cast<SCTAB>(maTabs.size()))
+ if (maTabs[nTab])
+ {
+ pVal = maTabs[nTab]->GetOutlineTable();
+ if (!pVal && bCreate)
+ {
+ maTabs[nTab]->StartOutlineTable();
+ pVal = maTabs[nTab]->GetOutlineTable();
+ }
+ }
+
+ return pVal;
+}
+
+bool ScDocument::SetOutlineTable( SCTAB nTab, const ScOutlineTable* pNewOutline )
+{
+ return ValidTab(nTab) && nTab < static_cast<SCTAB>(maTabs.size()) && maTabs[nTab] && maTabs[nTab]->SetOutlineTable(pNewOutline);
+}
+
+void ScDocument::DoAutoOutline( SCCOL nStartCol, SCROW nStartRow,
+ SCCOL nEndCol, SCROW nEndRow, SCTAB nTab )
+{
+ if (ValidTab(nTab) && nTab < static_cast<SCTAB>(maTabs.size()) && maTabs[nTab])
+ maTabs[nTab]->DoAutoOutline( nStartCol, nStartRow, nEndCol, nEndRow );
+}
+
+bool ScDocument::TestRemoveSubTotals( SCTAB nTab, const ScSubTotalParam& rParam )
+{
+ return ValidTab(nTab) && nTab < static_cast<SCTAB>(maTabs.size()) && maTabs[nTab] && maTabs[nTab]->TestRemoveSubTotals( rParam );
+}
+
+void ScDocument::RemoveSubTotals( SCTAB nTab, ScSubTotalParam& rParam )
+{
+ if ( ValidTab(nTab) && nTab < static_cast<SCTAB>(maTabs.size()) && maTabs[nTab] )
+ maTabs[nTab]->RemoveSubTotals( rParam );
+}
+
+bool ScDocument::DoSubTotals( SCTAB nTab, ScSubTotalParam& rParam )
+{
+ return ValidTab(nTab) && nTab < static_cast<SCTAB>(maTabs.size()) && maTabs[nTab] && maTabs[nTab]->DoSubTotals( rParam );
+}
+
+bool ScDocument::HasSubTotalCells( const ScRange& rRange )
+{
+ ScCellIterator aIter(*this, rRange);
+ for (bool bHas = aIter.first(); bHas; bHas = aIter.next())
+ {
+ if (aIter.getType() != CELLTYPE_FORMULA)
+ continue;
+
+ if (aIter.getFormulaCell()->IsSubTotal())
+ return true;
+ }
+ return false; // none found
+}
+
+/**
+ * From this document this method copies the cells of positions at which
+ * there are also cells in pPosDoc to pDestDoc
+ */
+void ScDocument::CopyUpdated( ScDocument* pPosDoc, ScDocument* pDestDoc )
+{
+ SCTAB nCount = static_cast<SCTAB>(maTabs.size());
+ for (SCTAB nTab=0; nTab<nCount; nTab++)
+ if (maTabs[nTab] && pPosDoc->maTabs[nTab] && pDestDoc->maTabs[nTab])
+ maTabs[nTab]->CopyUpdated( pPosDoc->maTabs[nTab].get(), pDestDoc->maTabs[nTab].get() );
+}
+
+void ScDocument::CopyScenario( SCTAB nSrcTab, SCTAB nDestTab, bool bNewScenario )
+{
+ if (!(ValidTab(nSrcTab) && ValidTab(nDestTab) && nSrcTab < static_cast<SCTAB>(maTabs.size())
+ && nDestTab < static_cast<SCTAB>(maTabs.size()) && maTabs[nSrcTab] && maTabs[nDestTab]))
+ return;
+
+ // Set flags correctly for active scenarios
+ // and write current values back to recently active scenarios
+ ScRangeList aRanges = *maTabs[nSrcTab]->GetScenarioRanges();
+
+ // nDestTab is the target table
+ for ( SCTAB nTab = nDestTab+1;
+ nTab< static_cast<SCTAB>(maTabs.size()) && maTabs[nTab] && maTabs[nTab]->IsScenario();
+ nTab++ )
+ {
+ if ( maTabs[nTab]->IsActiveScenario() ) // Even if it's the same scenario
+ {
+ bool bTouched = false;
+ for ( size_t nR=0, nRangeCount = aRanges.size(); nR < nRangeCount && !bTouched; nR++ )
+ {
+ const ScRange& rRange = aRanges[ nR ];
+ if ( maTabs[nTab]->HasScenarioRange( rRange ) )
+ bTouched = true;
+ }
+ if (bTouched)
+ {
+ maTabs[nTab]->SetActiveScenario(false);
+ if ( maTabs[nTab]->GetScenarioFlags() & ScScenarioFlags::TwoWay )
+ maTabs[nTab]->CopyScenarioFrom( maTabs[nDestTab].get() );
+ }
+ }
+ }
+
+ maTabs[nSrcTab]->SetActiveScenario(true); // This is where it's from ...
+ if (!bNewScenario) // Copy data from the selected scenario
+ {
+ sc::AutoCalcSwitch aACSwitch(*this, false);
+ maTabs[nSrcTab]->CopyScenarioTo( maTabs[nDestTab].get() );
+
+ sc::SetFormulaDirtyContext aCxt;
+ SetAllFormulasDirty(aCxt);
+ }
+}
+
+void ScDocument::MarkScenario( SCTAB nSrcTab, SCTAB nDestTab, ScMarkData& rDestMark,
+ bool bResetMark, ScScenarioFlags nNeededBits ) const
+{
+ if (bResetMark)
+ rDestMark.ResetMark();
+
+ if (ValidTab(nSrcTab) && nSrcTab < static_cast<SCTAB>(maTabs.size()) && maTabs[nSrcTab])
+ maTabs[nSrcTab]->MarkScenarioIn( rDestMark, nNeededBits );
+
+ rDestMark.SetAreaTab( nDestTab );
+}
+
+bool ScDocument::HasScenarioRange( SCTAB nTab, const ScRange& rRange ) const
+{
+ return ValidTab(nTab) && nTab < static_cast<SCTAB>(maTabs.size()) && maTabs[nTab] && maTabs[nTab]->HasScenarioRange( rRange );
+}
+
+const ScRangeList* ScDocument::GetScenarioRanges( SCTAB nTab ) const
+{
+ if (ValidTab(nTab) && nTab < static_cast<SCTAB>(maTabs.size()) && maTabs[nTab])
+ return maTabs[nTab]->GetScenarioRanges();
+
+ return nullptr;
+}
+
+bool ScDocument::IsActiveScenario( SCTAB nTab ) const
+{
+ return ValidTab(nTab) && nTab < static_cast<SCTAB>(maTabs.size()) && maTabs[nTab] && maTabs[nTab]->IsActiveScenario( );
+}
+
+void ScDocument::SetActiveScenario( SCTAB nTab, bool bActive )
+{
+ if (ValidTab(nTab) && nTab < static_cast<SCTAB>(maTabs.size()) && maTabs[nTab])
+ maTabs[nTab]->SetActiveScenario( bActive );
+}
+
+bool ScDocument::TestCopyScenario( SCTAB nSrcTab, SCTAB nDestTab ) const
+{
+ if (ValidTab(nSrcTab) && nSrcTab < static_cast<SCTAB>(maTabs.size())
+ && nDestTab < static_cast<SCTAB>(maTabs.size())&& ValidTab(nDestTab))
+ return maTabs[nSrcTab]->TestCopyScenarioTo( maTabs[nDestTab].get() );
+
+ OSL_FAIL("wrong table at TestCopyScenario");
+ return false;
+}
+
+void ScDocument::AddUnoObject( SfxListener& rObject )
+{
+ if (!pUnoBroadcaster)
+ pUnoBroadcaster.reset( new SfxBroadcaster );
+
+ rObject.StartListening( *pUnoBroadcaster );
+}
+
+void ScDocument::RemoveUnoObject( SfxListener& rObject )
+{
+ if (pUnoBroadcaster)
+ {
+ rObject.EndListening( *pUnoBroadcaster );
+
+ if ( bInUnoBroadcast )
+ {
+ // Broadcasts from ScDocument::BroadcastUno are the only way that
+ // uno object methods are called without holding a reference.
+ //
+ // If RemoveUnoObject is called from an object dtor in the finalizer thread
+ // while the main thread is calling BroadcastUno, the dtor thread must wait
+ // (or the object's Notify might try to access a deleted object).
+ // The SolarMutex can't be locked here because if a component is called from
+ // a VCL event, the main thread has the SolarMutex locked all the time.
+ //
+ // This check is done after calling EndListening, so a later BroadcastUno call
+ // won't touch this object.
+
+ vcl::SolarMutexTryAndBuyGuard g;
+ if (g.isAcquired())
+ {
+ // BroadcastUno is always called with the SolarMutex locked, so if it
+ // can be acquired, this is within the same thread (should not happen)
+ OSL_FAIL( "RemoveUnoObject called from BroadcastUno" );
+ }
+ else
+ {
+ // Let the thread that called BroadcastUno continue
+ while ( bInUnoBroadcast )
+ {
+ osl::Thread::yield();
+ }
+ }
+ }
+ }
+ else
+ {
+ OSL_FAIL("No Uno broadcaster");
+ }
+}
+
+void ScDocument::BroadcastUno( const SfxHint &rHint )
+{
+ if (!pUnoBroadcaster)
+ return;
+
+ bInUnoBroadcast = true;
+ pUnoBroadcaster->Broadcast( rHint );
+ bInUnoBroadcast = false;
+
+ // During Broadcast notification, Uno objects can add to pUnoListenerCalls.
+ // The listener calls must be processed after completing the broadcast,
+ // because they can add or remove objects from pUnoBroadcaster.
+
+ if ( pUnoListenerCalls &&
+ rHint.GetId() == SfxHintId::DataChanged &&
+ !bInUnoListenerCall )
+ {
+ // Listener calls may lead to BroadcastUno calls again. The listener calls
+ // are not nested, instead the calls are collected in the list, and the
+ // outermost call executes them all.
+
+ ScChartLockGuard aChartLockGuard(this);
+ bInUnoListenerCall = true;
+ pUnoListenerCalls->ExecuteAndClear();
+ bInUnoListenerCall = false;
+ }
+}
+
+void ScDocument::AddUnoListenerCall( const uno::Reference<util::XModifyListener>& rListener,
+ const lang::EventObject& rEvent )
+{
+ OSL_ENSURE( bInUnoBroadcast, "AddUnoListenerCall is supposed to be called from BroadcastUno only" );
+
+ if ( !pUnoListenerCalls )
+ pUnoListenerCalls.reset( new ScUnoListenerCalls );
+ pUnoListenerCalls->Add( rListener, rEvent );
+}
+
+void ScDocument::BeginUnoRefUndo()
+{
+ OSL_ENSURE( !pUnoRefUndoList, "BeginUnoRefUndo twice" );
+ pUnoRefUndoList.reset( new ScUnoRefList );
+}
+
+std::unique_ptr<ScUnoRefList> ScDocument::EndUnoRefUndo()
+{
+ return std::move(pUnoRefUndoList);
+ // Must be deleted by caller!
+}
+
+void ScDocument::AddUnoRefChange( sal_Int64 nId, const ScRangeList& rOldRanges )
+{
+ if ( pUnoRefUndoList )
+ pUnoRefUndoList->Add( nId, rOldRanges );
+}
+
+void ScDocument::UpdateReference(
+ sc::RefUpdateContext& rCxt, ScDocument* pUndoDoc, bool bIncludeDraw, bool bUpdateNoteCaptionPos )
+{
+ if (!ValidRange(rCxt.maRange) && !(rCxt.meMode == URM_INSDEL &&
+ ((rCxt.mnColDelta < 0 && // convention from ScDocument::DeleteCol()
+ rCxt.maRange.aStart.Col() == GetMaxColCount() && rCxt.maRange.aEnd.Col() == GetMaxColCount()) ||
+ (rCxt.mnRowDelta < 0 && // convention from ScDocument::DeleteRow()
+ rCxt.maRange.aStart.Row() == GetMaxRowCount() && rCxt.maRange.aEnd.Row() == GetMaxRowCount()))))
+ return;
+
+ std::unique_ptr<sc::ExpandRefsSwitch> pExpandRefsSwitch;
+ if (rCxt.isInserted())
+ pExpandRefsSwitch.reset(new sc::ExpandRefsSwitch(*this, SC_MOD()->GetInputOptions().GetExpandRefs()));
+
+ size_t nFirstTab, nLastTab;
+ if (rCxt.meMode == URM_COPY)
+ {
+ nFirstTab = rCxt.maRange.aStart.Tab();
+ nLastTab = rCxt.maRange.aEnd.Tab();
+ }
+ else
+ {
+ // TODO: Have these methods use the context object directly.
+ ScRange aRange = rCxt.maRange;
+ UpdateRefMode eUpdateRefMode = rCxt.meMode;
+ SCCOL nDx = rCxt.mnColDelta;
+ SCROW nDy = rCxt.mnRowDelta;
+ SCTAB nDz = rCxt.mnTabDelta;
+ SCCOL nCol1 = rCxt.maRange.aStart.Col(), nCol2 = rCxt.maRange.aEnd.Col();
+ SCROW nRow1 = rCxt.maRange.aStart.Row(), nRow2 = rCxt.maRange.aEnd.Row();
+ SCTAB nTab1 = rCxt.maRange.aStart.Tab(), nTab2 = rCxt.maRange.aEnd.Tab();
+
+ xColNameRanges->UpdateReference( eUpdateRefMode, this, aRange, nDx, nDy, nDz );
+ xRowNameRanges->UpdateReference( eUpdateRefMode, this, aRange, nDx, nDy, nDz );
+ pDBCollection->UpdateReference( eUpdateRefMode, nCol1, nRow1, nTab1, nCol2, nRow2, nTab2, nDx, nDy, nDz );
+ if (pRangeName)
+ pRangeName->UpdateReference(rCxt);
+ if ( pDPCollection )
+ pDPCollection->UpdateReference( eUpdateRefMode, aRange, nDx, nDy, nDz );
+ UpdateChartRef( eUpdateRefMode, nCol1, nRow1, nTab1, nCol2, nRow2, nTab2, nDx, nDy, nDz );
+ UpdateRefAreaLinks( eUpdateRefMode, aRange, nDx, nDy, nDz );
+ if ( pValidationList )
+ {
+ ScMutationGuard aGuard(*this, ScMutationGuardFlags::CORE);
+ pValidationList->UpdateReference(rCxt);
+ }
+ if ( pDetOpList )
+ pDetOpList->UpdateReference( this, eUpdateRefMode, aRange, nDx, nDy, nDz );
+ if ( pUnoBroadcaster )
+ pUnoBroadcaster->Broadcast( ScUpdateRefHint(
+ eUpdateRefMode, aRange, nDx, nDy, nDz ) );
+
+ nFirstTab = 0;
+ nLastTab = maTabs.size()-1;
+ }
+
+ for (size_t i = nFirstTab, n = maTabs.size() ; i <= nLastTab && i < n; ++i)
+ {
+ if (!maTabs[i])
+ continue;
+
+ maTabs[i]->UpdateReference(rCxt, pUndoDoc, bIncludeDraw, bUpdateNoteCaptionPos);
+ }
+
+ if ( bIsEmbedded )
+ {
+ SCCOL theCol1;
+ SCROW theRow1;
+ SCTAB theTab1;
+ SCCOL theCol2;
+ SCROW theRow2;
+ SCTAB theTab2;
+ theCol1 = aEmbedRange.aStart.Col();
+ theRow1 = aEmbedRange.aStart.Row();
+ theTab1 = aEmbedRange.aStart.Tab();
+ theCol2 = aEmbedRange.aEnd.Col();
+ theRow2 = aEmbedRange.aEnd.Row();
+ theTab2 = aEmbedRange.aEnd.Tab();
+
+ // TODO: Have ScRefUpdate::Update() use the context object directly.
+ UpdateRefMode eUpdateRefMode = rCxt.meMode;
+ SCCOL nDx = rCxt.mnColDelta;
+ SCROW nDy = rCxt.mnRowDelta;
+ SCTAB nDz = rCxt.mnTabDelta;
+ SCCOL nCol1 = rCxt.maRange.aStart.Col(), nCol2 = rCxt.maRange.aEnd.Col();
+ SCROW nRow1 = rCxt.maRange.aStart.Row(), nRow2 = rCxt.maRange.aEnd.Row();
+ SCTAB nTab1 = rCxt.maRange.aStart.Tab(), nTab2 = rCxt.maRange.aEnd.Tab();
+
+ if ( ScRefUpdate::Update( this, eUpdateRefMode, nCol1,nRow1,nTab1, nCol2,nRow2,nTab2,
+ nDx,nDy,nDz, theCol1,theRow1,theTab1, theCol2,theRow2,theTab2 ) )
+ {
+ aEmbedRange = ScRange( theCol1,theRow1,theTab1, theCol2,theRow2,theTab2 );
+ }
+ }
+
+ // After moving, no clipboard move ref-updates are possible
+ if (rCxt.meMode != URM_COPY && IsClipboardSource())
+ {
+ ScDocument* pClipDoc = ScModule::GetClipDoc();
+ if (pClipDoc)
+ pClipDoc->GetClipParam().mbCutMode = false;
+ }
+}
+
+void ScDocument::UpdateTranspose( const ScAddress& rDestPos, ScDocument* pClipDoc,
+ const ScMarkData& rMark, ScDocument* pUndoDoc )
+{
+ OSL_ENSURE(pClipDoc->bIsClip, "UpdateTranspose: No Clip");
+
+ ScRange aSource;
+ ScClipParam& rClipParam = pClipDoc->GetClipParam();
+ if (!rClipParam.maRanges.empty())
+ aSource = rClipParam.maRanges.front();
+ ScAddress aDest = rDestPos;
+
+ SCTAB nClipTab = 0;
+ for (SCTAB nDestTab=0; nDestTab< static_cast<SCTAB>(maTabs.size()) && maTabs[nDestTab]; nDestTab++)
+ if (rMark.GetTableSelect(nDestTab))
+ {
+ while (!pClipDoc->maTabs[nClipTab]) nClipTab = (nClipTab+1) % (MAXTAB+1);
+ aSource.aStart.SetTab( nClipTab );
+ aSource.aEnd.SetTab( nClipTab );
+ aDest.SetTab( nDestTab );
+
+ // Like UpdateReference
+ if (pRangeName)
+ pRangeName->UpdateTranspose( aSource, aDest ); // Before the cells!
+ for (SCTAB i=0; i< static_cast<SCTAB>(maTabs.size()); i++)
+ if (maTabs[i])
+ maTabs[i]->UpdateTranspose( aSource, aDest, pUndoDoc );
+
+ nClipTab = (nClipTab+1) % (MAXTAB+1);
+ }
+}
+
+void ScDocument::UpdateGrow( const ScRange& rArea, SCCOL nGrowX, SCROW nGrowY )
+{
+ //TODO: pDBCollection
+ //TODO: pPivotCollection
+ //TODO: UpdateChartRef
+
+ if (pRangeName)
+ pRangeName->UpdateGrow( rArea, nGrowX, nGrowY );
+
+ for (SCTAB i=0; i< static_cast<SCTAB>(maTabs.size()) && maTabs[i]; i++)
+ maTabs[i]->UpdateGrow( rArea, nGrowX, nGrowY );
+}
+
+void ScDocument::Fill(SCCOL nCol1, SCROW nRow1, SCCOL nCol2, SCROW nRow2, ScProgress* pProgress, const ScMarkData& rMark,
+ sal_uInt64 nFillCount, FillDir eFillDir, FillCmd eFillCmd, FillDateCmd eFillDateCmd,
+ double nStepValue, double nMaxValue)
+{
+ PutInOrder( nCol1, nCol2 );
+ PutInOrder( nRow1, nRow2 );
+ const ScRange& aRange = rMark.GetMarkArea();
+ SCTAB nMax = maTabs.size();
+ for (const auto& rTab : rMark)
+ {
+ if (rTab >= nMax)
+ break;
+ if (maTabs[rTab])
+ {
+ maTabs[rTab]->Fill(nCol1, nRow1, nCol2, nRow2,
+ nFillCount, eFillDir, eFillCmd, eFillDateCmd,
+ nStepValue, nMaxValue, pProgress);
+ RefreshAutoFilter(aRange.aStart.Col(), aRange.aStart.Row(), aRange.aEnd.Col(), aRange.aEnd.Row(), rTab);
+ }
+ }
+}
+
+OUString ScDocument::GetAutoFillPreview( const ScRange& rSource, SCCOL nEndX, SCROW nEndY )
+{
+ SCTAB nTab = rSource.aStart.Tab();
+ if (nTab < static_cast<SCTAB>(maTabs.size()) && maTabs[nTab])
+ return maTabs[nTab]->GetAutoFillPreview( rSource, nEndX, nEndY );
+
+ return OUString();
+}
+
+void ScDocument::AutoFormat( SCCOL nStartCol, SCROW nStartRow, SCCOL nEndCol, SCROW nEndRow,
+ sal_uInt16 nFormatNo, const ScMarkData& rMark )
+{
+ PutInOrder( nStartCol, nEndCol );
+ PutInOrder( nStartRow, nEndRow );
+ SCTAB nMax = maTabs.size();
+ for (const auto& rTab : rMark)
+ {
+ if (rTab >= nMax)
+ break;
+ if (maTabs[rTab])
+ maTabs[rTab]->AutoFormat( nStartCol, nStartRow, nEndCol, nEndRow, nFormatNo );
+ }
+}
+
+void ScDocument::GetAutoFormatData(SCTAB nTab, SCCOL nStartCol, SCROW nStartRow, SCCOL nEndCol, SCROW nEndRow,
+ ScAutoFormatData& rData)
+{
+ if (ValidTab(nTab) && nTab < static_cast<SCTAB>(maTabs.size()))
+ {
+ if (maTabs[nTab])
+ {
+ PutInOrder(nStartCol, nEndCol);
+ PutInOrder(nStartRow, nEndRow);
+ maTabs[nTab]->GetAutoFormatData(nStartCol, nStartRow, nEndCol, nEndRow, rData);
+ }
+ }
+}
+
+void ScDocument::GetSearchAndReplaceStart( const SvxSearchItem& rSearchItem,
+ SCCOL& rCol, SCROW& rRow )
+{
+ SvxSearchCmd nCommand = rSearchItem.GetCommand();
+ bool bReplace = ( nCommand == SvxSearchCmd::REPLACE ||
+ nCommand == SvxSearchCmd::REPLACE_ALL );
+ if ( rSearchItem.GetBackward() )
+ {
+ if ( rSearchItem.GetRowDirection() )
+ {
+ if ( rSearchItem.GetPattern() )
+ {
+ rCol = MaxCol();
+ rRow = MaxRow()+1;
+ }
+ else if ( bReplace )
+ {
+ rCol = MaxCol();
+ rRow = MaxRow();
+ }
+ else
+ {
+ rCol = MaxCol()+1;
+ rRow = MaxRow();
+ }
+ }
+ else
+ {
+ if ( rSearchItem.GetPattern() )
+ {
+ rCol = MaxCol()+1;
+ rRow = MaxRow();
+ }
+ else if ( bReplace )
+ {
+ rCol = MaxCol();
+ rRow = MaxRow();
+ }
+ else
+ {
+ rCol = MaxCol();
+ rRow = MaxRow()+1;
+ }
+ }
+ }
+ else
+ {
+ if ( rSearchItem.GetRowDirection() )
+ {
+ if ( rSearchItem.GetPattern() )
+ {
+ rCol = 0;
+ rRow = SCROW(-1);
+ }
+ else if ( bReplace )
+ {
+ rCol = 0;
+ rRow = 0;
+ }
+ else
+ {
+ rCol = SCCOL(-1);
+ rRow = 0;
+ }
+ }
+ else
+ {
+ if ( rSearchItem.GetPattern() )
+ {
+ rCol = SCCOL(-1);
+ rRow = 0;
+ }
+ else if ( bReplace )
+ {
+ rCol = 0;
+ rRow = 0;
+ }
+ else
+ {
+ rCol = 0;
+ rRow = SCROW(-1);
+ }
+ }
+ }
+}
+
+// static
+bool ScDocument::IsEmptyCellSearch( const SvxSearchItem& rSearchItem )
+{
+ return !rSearchItem.GetPattern() && (rSearchItem.GetCellType() != SvxSearchCellType::NOTE)
+ && (rSearchItem.GetSearchOptions().searchString.isEmpty()
+ || (rSearchItem.GetRegExp() && rSearchItem.GetSearchOptions().searchString == "^$"));
+}
+
+bool ScDocument::SearchAndReplace(
+ const SvxSearchItem& rSearchItem, SCCOL& rCol, SCROW& rRow, SCTAB& rTab,
+ const ScMarkData& rMark, ScRangeList& rMatchedRanges,
+ OUString& rUndoStr, ScDocument* pUndoDoc)
+{
+ // FIXME: Manage separated marks per table!
+ bool bFound = false;
+ if (rTab >= static_cast<SCTAB>(maTabs.size()))
+ OSL_FAIL("table out of range");
+ if (ValidTab(rTab))
+ {
+ SCCOL nCol;
+ SCROW nRow;
+ SCTAB nTab;
+ SvxSearchCmd nCommand = rSearchItem.GetCommand();
+ if ( nCommand == SvxSearchCmd::FIND_ALL ||
+ nCommand == SvxSearchCmd::REPLACE_ALL )
+ {
+ SCTAB nMax = maTabs.size();
+ for (const auto& rMarkedTab : rMark)
+ {
+ if (rMarkedTab >= nMax)
+ break;
+ if (maTabs[rMarkedTab])
+ {
+ nCol = 0;
+ nRow = 0;
+ bFound |= maTabs[rMarkedTab]->SearchAndReplace(
+ rSearchItem, nCol, nRow, rMark, rMatchedRanges, rUndoStr, pUndoDoc);
+ }
+ }
+
+ // Mark is set completely inside already
+ }
+ else
+ {
+ nCol = rCol;
+ nRow = rRow;
+ if (rSearchItem.GetBackward())
+ {
+ for (nTab = rTab; (nTab >= 0) && !bFound; nTab--)
+ if (maTabs[nTab])
+ {
+ if (rMark.GetTableSelect(nTab))
+ {
+ bFound = maTabs[nTab]->SearchAndReplace(
+ rSearchItem, nCol, nRow, rMark, rMatchedRanges, rUndoStr, pUndoDoc);
+ if (bFound)
+ {
+ rCol = nCol;
+ rRow = nRow;
+ rTab = nTab;
+ }
+ else
+ {
+ ScDocument::GetSearchAndReplaceStart(
+ rSearchItem, nCol, nRow );
+
+ // notify LibreOfficeKit about changed page
+ if (comphelper::LibreOfficeKit::isActive())
+ {
+ OString aPayload = OString::number(nTab);
+ if (SfxViewShell* pViewShell = SfxViewShell::Current())
+ pViewShell->libreOfficeKitViewCallback(LOK_CALLBACK_SET_PART, aPayload.getStr());
+ }
+ }
+ }
+ }
+ }
+ else
+ {
+ for (nTab = rTab; (nTab < static_cast<SCTAB>(maTabs.size())) && !bFound; nTab++)
+ if (maTabs[nTab])
+ {
+ if (rMark.GetTableSelect(nTab))
+ {
+ bFound = maTabs[nTab]->SearchAndReplace(
+ rSearchItem, nCol, nRow, rMark, rMatchedRanges, rUndoStr, pUndoDoc);
+ if (bFound)
+ {
+ rCol = nCol;
+ rRow = nRow;
+ rTab = nTab;
+ }
+ else
+ {
+ ScDocument::GetSearchAndReplaceStart(
+ rSearchItem, nCol, nRow );
+
+ // notify LibreOfficeKit about changed page
+ if (comphelper::LibreOfficeKit::isActive())
+ {
+ OString aPayload = OString::number(nTab);
+ if(SfxViewShell* pViewShell = SfxViewShell::Current())
+ pViewShell->libreOfficeKitViewCallback(LOK_CALLBACK_SET_PART, aPayload.getStr());
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ return bFound;
+}
+
+/**
+ * Adapt Outline
+ */
+bool ScDocument::UpdateOutlineCol( SCCOL nStartCol, SCCOL nEndCol, SCTAB nTab, bool bShow )
+{
+ if ( ValidTab(nTab) && nTab < static_cast<SCTAB>(maTabs.size()) && maTabs[nTab] )
+ return maTabs[nTab]->UpdateOutlineCol( nStartCol, nEndCol, bShow );
+
+ OSL_FAIL("missing tab");
+ return false;
+}
+
+bool ScDocument::UpdateOutlineRow( SCROW nStartRow, SCROW nEndRow, SCTAB nTab, bool bShow )
+{
+ if ( ValidTab(nTab) && nTab < static_cast<SCTAB>(maTabs.size()) && maTabs[nTab] )
+ return maTabs[nTab]->UpdateOutlineRow( nStartRow, nEndRow, bShow );
+
+ OSL_FAIL("missing tab");
+ return false;
+}
+
+void ScDocument::Sort(
+ SCTAB nTab, const ScSortParam& rSortParam, bool bKeepQuery, bool bUpdateRefs,
+ ScProgress* pProgress, sc::ReorderParam* pUndo )
+{
+ if ( ValidTab(nTab) && nTab < static_cast<SCTAB>(maTabs.size()) && maTabs[nTab] )
+ {
+ bool bOldEnableIdle = IsIdleEnabled();
+ EnableIdle(false);
+ maTabs[nTab]->Sort(rSortParam, bKeepQuery, bUpdateRefs, pProgress, pUndo);
+ EnableIdle(bOldEnableIdle);
+ }
+}
+
+void ScDocument::Reorder( const sc::ReorderParam& rParam )
+{
+ ScTable* pTab = FetchTable(rParam.maSortRange.aStart.Tab());
+ if (!pTab)
+ return;
+
+ bool bOldEnableIdle = IsIdleEnabled();
+ EnableIdle(false);
+ pTab->Reorder(rParam);
+ EnableIdle(bOldEnableIdle);
+}
+
+void ScDocument::PrepareQuery( SCTAB nTab, ScQueryParam& rQueryParam )
+{
+ if( ValidTab(nTab) && nTab < static_cast<SCTAB>(maTabs.size()) && maTabs[nTab] )
+ maTabs[nTab]->PrepareQuery(rQueryParam);
+ else
+ {
+ OSL_FAIL("missing tab");
+ return;
+ }
+}
+
+SCSIZE ScDocument::Query(SCTAB nTab, const ScQueryParam& rQueryParam, bool bKeepSub)
+{
+ if ( ValidTab(nTab) && nTab < static_cast<SCTAB>(maTabs.size()) && maTabs[nTab] )
+ return maTabs[nTab]->Query(rQueryParam, bKeepSub);
+
+ OSL_FAIL("missing tab");
+ return 0;
+}
+
+OUString ScDocument::GetUpperCellString(SCCOL nCol, SCROW nRow, SCTAB nTab)
+{
+ if ( ValidTab(nTab) && nTab < static_cast<SCTAB>(maTabs.size()) && maTabs[nTab] )
+ return maTabs[nTab]->GetUpperCellString( nCol, nRow );
+ else
+ return OUString();
+}
+
+bool ScDocument::CreateQueryParam( const ScRange& rRange, ScQueryParam& rQueryParam )
+{
+ ScTable* pTab = FetchTable(rRange.aStart.Tab());
+ if (!pTab)
+ {
+ OSL_FAIL("missing tab");
+ return false;
+ }
+
+ return pTab->CreateQueryParam(
+ rRange.aStart.Col(), rRange.aStart.Row(), rRange.aEnd.Col(), rRange.aEnd.Row(), rQueryParam);
+}
+
+bool ScDocument::HasAutoFilter( SCCOL nCurCol, SCROW nCurRow, SCTAB nCurTab )
+{
+ const ScDBData* pDBData = GetDBAtCursor( nCurCol, nCurRow, nCurTab, ScDBDataPortion::AREA );
+ bool bHasAutoFilter = (pDBData != nullptr);
+
+ if ( pDBData )
+ {
+ if ( pDBData->HasHeader() )
+ {
+ SCCOL nCol;
+ SCROW nRow;
+ ScMF nFlag;
+
+ ScQueryParam aParam;
+ pDBData->GetQueryParam( aParam );
+ nRow = aParam.nRow1;
+
+ for ( nCol=aParam.nCol1; nCol<=aParam.nCol2 && bHasAutoFilter; nCol++ )
+ {
+ nFlag = GetAttr( nCol, nRow, nCurTab, ATTR_MERGE_FLAG )->GetValue();
+
+ if ( !(nFlag & ScMF::Auto) )
+ bHasAutoFilter = false;
+ }
+ }
+ else
+ bHasAutoFilter = false;
+ }
+
+ return bHasAutoFilter;
+}
+
+bool ScDocument::HasColHeader( SCCOL nStartCol, SCROW nStartRow, SCCOL nEndCol, SCROW nEndRow,
+ SCTAB nTab )
+{
+ return ValidTab(nTab) && maTabs[nTab] && maTabs[nTab]->HasColHeader( nStartCol, nStartRow, nEndCol, nEndRow );
+}
+
+bool ScDocument::HasRowHeader( SCCOL nStartCol, SCROW nStartRow, SCCOL nEndCol, SCROW nEndRow,
+ SCTAB nTab )
+{
+ return ValidTab(nTab) && maTabs[nTab] && maTabs[nTab]->HasRowHeader( nStartCol, nStartRow, nEndCol, nEndRow );
+}
+
+void ScDocument::GetFilterSelCount( SCCOL nCol, SCROW nRow, SCTAB nTab, SCSIZE& nSelected, SCSIZE& nTotal )
+{
+ nSelected = 0;
+ nTotal = 0;
+ if ( ValidTab(nTab) && nTab < static_cast<SCTAB>(maTabs.size()) && maTabs[nTab] )
+ {
+ ScDBData* pDBData = GetDBAtCursor( nCol, nRow, nTab, ScDBDataPortion::AREA );
+ if( pDBData && pDBData->HasAutoFilter() )
+ pDBData->GetFilterSelCount( nSelected, nTotal );
+ }
+}
+
+/**
+ * Entries for AutoFilter listbox
+ */
+void ScDocument::GetFilterEntries(
+ SCCOL nCol, SCROW nRow, SCTAB nTab, ScFilterEntries& rFilterEntries )
+{
+ if ( !(ValidTab(nTab) && nTab < static_cast<SCTAB>(maTabs.size()) && maTabs[nTab] && pDBCollection) )
+ return;
+
+ ScDBData* pDBData = pDBCollection->GetDBAtCursor(nCol, nRow, nTab, ScDBDataPortion::AREA); //!??
+ if (!pDBData)
+ return;
+
+ pDBData->ExtendDataArea(*this);
+ SCTAB nAreaTab;
+ SCCOL nStartCol;
+ SCROW nStartRow;
+ SCCOL nEndCol;
+ SCROW nEndRow;
+ pDBData->GetArea( nAreaTab, nStartCol, nStartRow, nEndCol, nEndRow );
+
+ if (pDBData->HasHeader())
+ ++nStartRow;
+
+ ScQueryParam aParam;
+ pDBData->GetQueryParam( aParam );
+
+ // Return all filter entries, if a filter condition is connected with a boolean OR
+ bool bFilter = true;
+ SCSIZE nEntryCount = aParam.GetEntryCount();
+ for ( SCSIZE i = 0; i < nEntryCount && aParam.GetEntry(i).bDoQuery; ++i )
+ {
+ ScQueryEntry& rEntry = aParam.GetEntry(i);
+ if ( rEntry.eConnect != SC_AND )
+ {
+ bFilter = false;
+ break;
+ }
+ }
+
+ if ( bFilter )
+ {
+ maTabs[nTab]->GetFilteredFilterEntries( nCol, nStartRow, nEndRow, aParam, rFilterEntries, bFilter );
+ }
+ else
+ {
+ maTabs[nTab]->GetFilterEntries( nCol, nStartRow, nEndRow, rFilterEntries );
+ }
+
+ sortAndRemoveDuplicates( rFilterEntries.maStrData, aParam.bCaseSens);
+}
+
+/**
+ * Entries for Filter dialog
+ */
+void ScDocument::GetFilterEntriesArea(
+ SCCOL nCol, SCROW nStartRow, SCROW nEndRow, SCTAB nTab, bool bCaseSens,
+ ScFilterEntries& rFilterEntries )
+{
+ if ( ValidTab(nTab) && nTab < static_cast<SCTAB>(maTabs.size()) && maTabs[nTab] )
+ {
+ maTabs[nTab]->GetFilterEntries( nCol, nStartRow, nEndRow, rFilterEntries, true );
+ sortAndRemoveDuplicates( rFilterEntries.maStrData, bCaseSens);
+ }
+}
+
+/**
+ * Entries for selection list listbox (no numbers/formulas)
+ */
+void ScDocument::GetDataEntries(
+ SCCOL nCol, SCROW nRow, SCTAB nTab,
+ std::vector<ScTypedStrData>& rStrings, bool bValidation )
+{
+ if( bValidation )
+ {
+ /* Try to generate the list from list validation. This part is skipped,
+ if bValidation==false, because in that case this function is called to get
+ cell values for auto completion on input. */
+ sal_uInt32 nValidation = GetAttr( nCol, nRow, nTab, ATTR_VALIDDATA )->GetValue();
+ if( nValidation )
+ {
+ const ScValidationData* pData = GetValidationEntry( nValidation );
+ if( pData && pData->FillSelectionList( rStrings, ScAddress( nCol, nRow, nTab ) ) )
+ {
+ if (pData->GetListType() == css::sheet::TableValidationVisibility::SORTEDASCENDING)
+ sortAndRemoveDuplicates(rStrings, true/*bCaseSens*/);
+
+ return;
+ }
+ }
+ }
+
+ if (!ValidTab(nTab) || nTab >= static_cast<SCTAB>(maTabs.size()))
+ return;
+
+ if (!maTabs[nTab])
+ return;
+
+ std::set<ScTypedStrData> aStrings;
+ if (maTabs[nTab]->GetDataEntries(nCol, nRow, aStrings))
+ {
+ rStrings.insert(rStrings.end(), aStrings.begin(), aStrings.end());
+ sortAndRemoveDuplicates(rStrings, true/*bCaseSens*/);
+ }
+}
+
+/**
+ * Entries for Formula auto input
+ */
+void ScDocument::GetFormulaEntries( ScTypedCaseStrSet& rStrings )
+{
+
+ // Range name
+ if ( pRangeName )
+ {
+ for (const auto& rEntry : *pRangeName)
+ rStrings.insert(ScTypedStrData(rEntry.second->GetName(), 0.0, 0.0, ScTypedStrData::Name));
+ }
+
+ // Database collection
+ if ( pDBCollection )
+ {
+ const ScDBCollection::NamedDBs& rDBs = pDBCollection->getNamedDBs();
+ for (const auto& rxDB : rDBs)
+ rStrings.insert(ScTypedStrData(rxDB->GetName(), 0.0, 0.0, ScTypedStrData::DbName));
+ }
+
+ // Content of name ranges
+ ScRangePairList* pLists[2];
+ pLists[0] = GetColNameRanges();
+ pLists[1] = GetRowNameRanges();
+ for (ScRangePairList* pList : pLists)
+ {
+ if (!pList)
+ continue;
+
+ for ( size_t i = 0, nPairs = pList->size(); i < nPairs; ++i )
+ {
+ const ScRangePair & rPair = (*pList)[i];
+ const ScRange & rRange = rPair.GetRange(0);
+ ScCellIterator aIter( *this, rRange );
+ for (bool bHas = aIter.first(); bHas; bHas = aIter.next())
+ {
+ if (!aIter.hasString())
+ continue;
+
+ OUString aStr = aIter.getString();
+ rStrings.insert(ScTypedStrData(aStr, 0.0, 0.0, ScTypedStrData::Header));
+ }
+ }
+ }
+}
+
+void ScDocument::GetEmbedded( ScRange& rRange ) const
+{
+ rRange = aEmbedRange;
+}
+
+tools::Rectangle ScDocument::GetEmbeddedRect() const // 1/100 mm
+{
+ tools::Rectangle aRect;
+ ScTable* pTable = nullptr;
+ if ( aEmbedRange.aStart.Tab() < static_cast<SCTAB>(maTabs.size()) )
+ pTable = maTabs[aEmbedRange.aStart.Tab()].get();
+ else
+ OSL_FAIL("table out of range");
+ if (!pTable)
+ {
+ OSL_FAIL("GetEmbeddedRect without a table");
+ }
+ else
+ {
+ SCCOL i;
+
+ for (i=0; i<aEmbedRange.aStart.Col(); i++)
+ aRect.AdjustLeft(pTable->GetColWidth(i) );
+ aRect.AdjustTop(pTable->GetRowHeight( 0, aEmbedRange.aStart.Row() - 1) );
+ aRect.SetRight( aRect.Left() );
+ for (i=aEmbedRange.aStart.Col(); i<=aEmbedRange.aEnd.Col(); i++)
+ aRect.AdjustRight(pTable->GetColWidth(i) );
+ aRect.SetBottom( aRect.Top() );
+ aRect.AdjustBottom(pTable->GetRowHeight( aEmbedRange.aStart.Row(), aEmbedRange.aEnd.Row()) );
+
+ aRect = o3tl::convert(aRect, o3tl::Length::twip, o3tl::Length::mm100);
+ }
+ return aRect;
+}
+
+void ScDocument::SetEmbedded( const ScRange& rRange )
+{
+ bIsEmbedded = true;
+ aEmbedRange = rRange;
+}
+
+void ScDocument::ResetEmbedded()
+{
+ bIsEmbedded = false;
+ aEmbedRange = ScRange();
+}
+
+/** Similar to ScViewData::AddPixelsWhile(), but add height twips and only
+ while result is less than nStopTwips.
+ @return true if advanced at least one row.
+ */
+static bool lcl_AddTwipsWhile( tools::Long & rTwips, tools::Long nStopTwips, SCROW & rPosY, SCROW nEndRow, const ScTable * pTable, bool bHiddenAsZero )
+{
+ SCROW nRow = rPosY;
+ bool bAdded = false;
+ bool bStop = false;
+ while (rTwips < nStopTwips && nRow <= nEndRow && !bStop)
+ {
+ SCROW nHeightEndRow;
+ sal_uInt16 nHeight = pTable->GetRowHeight( nRow, nullptr, &nHeightEndRow, bHiddenAsZero );
+ if (nHeightEndRow > nEndRow)
+ nHeightEndRow = nEndRow;
+ if (!nHeight)
+ nRow = nHeightEndRow + 1;
+ else
+ {
+ SCROW nRows = nHeightEndRow - nRow + 1;
+ sal_Int64 nAdd = static_cast<sal_Int64>(nHeight) * nRows;
+ if (nAdd + rTwips >= nStopTwips)
+ {
+ sal_Int64 nDiff = nAdd + rTwips - nStopTwips;
+ nRows -= static_cast<SCROW>(nDiff / nHeight);
+ nAdd = static_cast<sal_Int64>(nHeight) * nRows;
+ // We're looking for a value that satisfies loop condition.
+ if (nAdd + rTwips >= nStopTwips)
+ {
+ --nRows;
+ nAdd -= nHeight;
+ }
+ bStop = true;
+ }
+ rTwips += static_cast<tools::Long>(nAdd);
+ nRow += nRows;
+ }
+ }
+ if (nRow > rPosY)
+ {
+ --nRow;
+ bAdded = true;
+ }
+ rPosY = nRow;
+ return bAdded;
+}
+
+ScRange ScDocument::GetRange( SCTAB nTab, const tools::Rectangle& rMMRect, bool bHiddenAsZero ) const
+{
+ ScTable* pTable = nullptr;
+ if (nTab < static_cast<SCTAB>(maTabs.size()))
+ pTable = maTabs[nTab].get();
+ else
+ OSL_FAIL("table out of range");
+ if (!pTable)
+ {
+ OSL_FAIL("GetRange without a table");
+ return ScRange();
+ }
+
+ tools::Rectangle aPosRect = o3tl::convert(rMMRect, o3tl::Length::mm100, o3tl::Length::twip);
+ if ( IsNegativePage( nTab ) )
+ ScDrawLayer::MirrorRectRTL( aPosRect ); // Always with positive (LTR) values
+
+ tools::Long nSize;
+ tools::Long nTwips;
+ tools::Long nAdd;
+ bool bEnd;
+
+ nSize = 0;
+ nTwips = aPosRect.Left();
+
+ SCCOL nX1 = 0;
+ bEnd = false;
+ while (!bEnd)
+ {
+ nAdd = pTable->GetColWidth(nX1, bHiddenAsZero);
+ if (nSize+nAdd <= nTwips+1 && nX1<MaxCol())
+ {
+ nSize += nAdd;
+ ++nX1;
+ }
+ else
+ bEnd = true;
+ }
+
+
+ SCCOL nX2 = nX1;
+ if (!aPosRect.IsEmpty())
+ {
+ bEnd = false;
+ nTwips = aPosRect.Right();
+ while (!bEnd)
+ {
+ nAdd = pTable->GetColWidth(nX2, bHiddenAsZero);
+ if (nSize+nAdd < nTwips && nX2<MaxCol())
+ {
+ nSize += nAdd;
+ ++nX2;
+ }
+ else
+ bEnd = true;
+ }
+ }
+
+ nSize = 0;
+ nTwips = aPosRect.Top();
+
+ SCROW nY1 = 0;
+ // Was if(nSize+nAdd<=nTwips+1) inside loop => if(nSize+nAdd<nTwips+2)
+ if (lcl_AddTwipsWhile( nSize, nTwips+2, nY1, MaxRow(), pTable, bHiddenAsZero) && nY1 < MaxRow())
+ ++nY1; // original loop ended on last matched +1 unless that was rDoc.MaxRow()
+
+ SCROW nY2 = nY1;
+ if (!aPosRect.IsEmpty())
+ {
+ nTwips = aPosRect.Bottom();
+ // Was if(nSize+nAdd<nTwips) inside loop => if(nSize+nAdd<nTwips)
+ if (lcl_AddTwipsWhile( nSize, nTwips, nY2, MaxRow(), pTable, bHiddenAsZero) && nY2 < MaxRow())
+ ++nY2; // original loop ended on last matched +1 unless that was rDoc.MaxRow()
+ }
+
+ return ScRange( nX1,nY1,nTab, nX2,nY2,nTab );
+}
+
+void ScDocument::SetEmbedded( SCTAB nTab, const tools::Rectangle& rRect ) // From VisArea (1/100 mm)
+{
+ bIsEmbedded = true;
+ aEmbedRange = GetRange( nTab, rRect );
+}
+
+ScDocProtection* ScDocument::GetDocProtection() const
+{
+ return pDocProtection.get();
+}
+
+void ScDocument::SetDocProtection(const ScDocProtection* pProtect)
+{
+ if (pProtect)
+ pDocProtection.reset(new ScDocProtection(*pProtect));
+ else
+ pDocProtection.reset();
+}
+
+bool ScDocument::IsDocProtected() const
+{
+ return pDocProtection && pDocProtection->isProtected();
+}
+
+bool ScDocument::IsDocEditable() const
+{
+ // Import into read-only document is possible
+ return !IsDocProtected() && ( bImportingXML || mbChangeReadOnlyEnabled || !mpShell || !mpShell->IsReadOnly() );
+}
+
+bool ScDocument::IsTabProtected( SCTAB nTab ) const
+{
+ if (ValidTab(nTab) && nTab < static_cast<SCTAB>(maTabs.size()) && maTabs[nTab])
+ return maTabs[nTab]->IsProtected();
+
+ OSL_FAIL("Wrong table number");
+ return false;
+}
+
+const ScTableProtection* ScDocument::GetTabProtection(SCTAB nTab) const
+{
+ if (ValidTab(nTab) && nTab < static_cast<SCTAB>(maTabs.size()) && maTabs[nTab])
+ return maTabs[nTab]->GetProtection();
+
+ return nullptr;
+}
+
+void ScDocument::SetTabProtection(SCTAB nTab, const ScTableProtection* pProtect)
+{
+ if (!ValidTab(nTab) || nTab >= static_cast<SCTAB>(maTabs.size()))
+ return;
+
+ maTabs[nTab]->SetProtection(pProtect);
+}
+
+void ScDocument::CopyTabProtection(SCTAB nTabSrc, SCTAB nTabDest)
+{
+ if (!ValidTab(nTabSrc) || nTabSrc >= static_cast<SCTAB>(maTabs.size()) || nTabDest >= static_cast<SCTAB>(maTabs.size()) || !ValidTab(nTabDest))
+ return;
+
+ maTabs[nTabDest]->SetProtection( maTabs[nTabSrc]->GetProtection() );
+}
+
+const ScDocOptions& ScDocument::GetDocOptions() const
+{
+ assert(pDocOptions && "No DocOptions! :-(");
+ return *pDocOptions;
+}
+
+void ScDocument::SetDocOptions( const ScDocOptions& rOpt )
+{
+ assert(pDocOptions && "No DocOptions! :-(");
+
+ *pDocOptions = rOpt;
+ if (mxPoolHelper)
+ mxPoolHelper->SetFormTableOpt(rOpt);
+}
+
+const ScViewOptions& ScDocument::GetViewOptions() const
+{
+ assert(pViewOptions && "No ViewOptions! :-(");
+ return *pViewOptions;
+}
+
+void ScDocument::SetViewOptions( const ScViewOptions& rOpt )
+{
+ assert(pViewOptions && "No ViewOptions! :-(");
+ *pViewOptions = rOpt;
+}
+
+void ScDocument::GetLanguage( LanguageType& rLatin, LanguageType& rCjk, LanguageType& rCtl ) const
+{
+ rLatin = eLanguage;
+ rCjk = eCjkLanguage;
+ rCtl = eCtlLanguage;
+}
+
+void ScDocument::SetLanguage( LanguageType eLatin, LanguageType eCjk, LanguageType eCtl )
+{
+ eLanguage = eLatin;
+ eCjkLanguage = eCjk;
+ eCtlLanguage = eCtl;
+ if ( mxPoolHelper.is() )
+ {
+ ScDocumentPool* pPool = mxPoolHelper->GetDocPool();
+ pPool->SetPoolDefaultItem( SvxLanguageItem( eLanguage, ATTR_FONT_LANGUAGE ) );
+ pPool->SetPoolDefaultItem( SvxLanguageItem( eCjkLanguage, ATTR_CJK_FONT_LANGUAGE ) );
+ pPool->SetPoolDefaultItem( SvxLanguageItem( eCtlLanguage, ATTR_CTL_FONT_LANGUAGE ) );
+ }
+
+ UpdateDrawLanguages(); // Set edit engine defaults in drawing layer pool
+}
+
+tools::Rectangle ScDocument::GetMMRect( SCCOL nStartCol, SCROW nStartRow, SCCOL nEndCol, SCROW nEndRow, SCTAB nTab, bool bHiddenAsZero ) const
+{
+ if (!ValidTab(nTab) || nTab >= static_cast<SCTAB>(maTabs.size()) || !maTabs[nTab])
+ {
+ OSL_FAIL("GetMMRect: wrong table");
+ return tools::Rectangle(0,0,0,0);
+ }
+
+ SCCOL i;
+ tools::Rectangle aRect;
+
+ for (i=0; i<nStartCol; i++)
+ aRect.AdjustLeft(GetColWidth(i,nTab, bHiddenAsZero ) );
+ aRect.AdjustTop(GetRowHeight( 0, nStartRow-1, nTab, bHiddenAsZero ) );
+
+ aRect.SetRight( aRect.Left() );
+ aRect.SetBottom( aRect.Top() );
+
+ for (i=nStartCol; i<=nEndCol; i++)
+ aRect.AdjustRight(GetColWidth(i,nTab, bHiddenAsZero) );
+ aRect.AdjustBottom(GetRowHeight( nStartRow, nEndRow, nTab, bHiddenAsZero ) );
+
+ aRect = o3tl::convert(aRect, o3tl::Length::twip, o3tl::Length::mm100);
+
+ if ( IsNegativePage( nTab ) )
+ ScDrawLayer::MirrorRectRTL( aRect );
+
+ return aRect;
+}
+
+void ScDocument::SetExtDocOptions( std::unique_ptr<ScExtDocOptions> pNewOptions )
+{
+ pExtDocOptions = std::move(pNewOptions);
+}
+
+void ScDocument::SetClipOptions(std::unique_ptr<ScClipOptions> pClipOptions)
+{
+ mpClipOptions = std::move(pClipOptions);
+}
+
+void ScDocument::DoMergeContents( SCCOL nStartCol, SCROW nStartRow,
+ SCCOL nEndCol, SCROW nEndRow, SCTAB nTab )
+{
+ OUStringBuffer aTotal;
+ OUString aCellStr;
+ SCCOL nCol;
+ SCROW nRow;
+ for (nRow=nStartRow; nRow<=nEndRow; nRow++)
+ for (nCol=nStartCol; nCol<=nEndCol; nCol++)
+ {
+ aCellStr = GetString(nCol, nRow, nTab);
+ if (!aCellStr.isEmpty())
+ {
+ if (!aTotal.isEmpty())
+ aTotal.append(' ');
+ aTotal.append(aCellStr);
+ }
+ if (nCol != nStartCol || nRow != nStartRow)
+ SetString(nCol,nRow,nTab,"");
+ }
+
+ SetString(nStartCol,nStartRow,nTab,aTotal.makeStringAndClear());
+}
+
+void ScDocument::DoEmptyBlock( SCCOL nStartCol, SCROW nStartRow,
+ SCCOL nEndCol, SCROW nEndRow, SCTAB nTab )
+{
+ SCCOL nCol;
+ SCROW nRow;
+ for (nRow=nStartRow; nRow<=nEndRow; nRow++)
+ for (nCol=nStartCol; nCol<=nEndCol; nCol++)
+ { // empty block except first cell
+ if (nCol != nStartCol || nRow != nStartRow)
+ SetString(nCol,nRow,nTab,"");
+ }
+}
+
+void ScDocument::DoMerge( SCCOL nStartCol, SCROW nStartRow,
+ SCCOL nEndCol, SCROW nEndRow, SCTAB nTab, bool bDeleteCaptions )
+{
+ ScTable* pTab = FetchTable(nTab);
+ if (!pTab)
+ return;
+
+ pTab->SetMergedCells(nStartCol, nStartRow, nEndCol, nEndRow);
+
+ // Remove all covered notes (removed captions are collected by drawing undo if active)
+ InsertDeleteFlags nDelFlag = InsertDeleteFlags::NOTE | (bDeleteCaptions ? InsertDeleteFlags::NONE : InsertDeleteFlags::NOCAPTIONS);
+ if( nStartCol < nEndCol )
+ DeleteAreaTab( nStartCol + 1, nStartRow, nEndCol, nStartRow, nTab, nDelFlag );
+ if( nStartRow < nEndRow )
+ DeleteAreaTab( nStartCol, nStartRow + 1, nEndCol, nEndRow, nTab, nDelFlag );
+}
+
+void ScDocument::RemoveMerge( SCCOL nCol, SCROW nRow, SCTAB nTab )
+{
+ const ScMergeAttr* pAttr = GetAttr( nCol, nRow, nTab, ATTR_MERGE );
+
+ if ( pAttr->GetColMerge() <= 1 && pAttr->GetRowMerge() <= 1 )
+ return;
+
+ SCCOL nEndCol = nCol + pAttr->GetColMerge() - 1;
+ SCROW nEndRow = nRow + pAttr->GetRowMerge() - 1;
+
+ RemoveFlagsTab( nCol, nRow, nEndCol, nEndRow, nTab, ScMF::Hor | ScMF::Ver );
+
+ const ScMergeAttr* pDefAttr = &mxPoolHelper->GetDocPool()->GetDefaultItem( ATTR_MERGE );
+ ApplyAttr( nCol, nRow, nTab, *pDefAttr );
+}
+
+void ScDocument::ExtendPrintArea( OutputDevice* pDev, SCTAB nTab,
+ SCCOL nStartCol, SCROW nStartRow, SCCOL& rEndCol, SCROW nEndRow ) const
+{
+ if ( ValidTab(nTab) && nTab < static_cast<SCTAB>(maTabs.size()) && maTabs[nTab] )
+ maTabs[nTab]->ExtendPrintArea( pDev, nStartCol, nStartRow, rEndCol, nEndRow );
+}
+
+SCSIZE ScDocument::GetPatternCount( SCTAB nTab, SCCOL nCol ) const
+{
+ if( ValidTab(nTab) && nTab < static_cast<SCTAB>(maTabs.size()) && maTabs[nTab] )
+ return maTabs[nTab]->GetPatternCount( nCol );
+ else
+ return 0;
+}
+
+SCSIZE ScDocument::GetPatternCount( SCTAB nTab, SCCOL nCol, SCROW nRow1, SCROW nRow2 ) const
+{
+ if( ValidTab(nTab) && nTab < static_cast<SCTAB>(maTabs.size()) && maTabs[nTab] )
+ return maTabs[nTab]->GetPatternCount( nCol, nRow1, nRow2 );
+ else
+ return 0;
+}
+
+void ScDocument::ReservePatternCount( SCTAB nTab, SCCOL nCol, SCSIZE nReserve )
+{
+ if( ValidTab(nTab) && nTab < static_cast<SCTAB>(maTabs.size()) && maTabs[nTab] )
+ maTabs[nTab]->ReservePatternCount( nCol, nReserve );
+}
+
+void ScDocument::GetSortParam( ScSortParam& rParam, SCTAB nTab )
+{
+ rParam = mSheetSortParams[ nTab ];
+}
+
+void ScDocument::SetSortParam( const ScSortParam& rParam, SCTAB nTab )
+{
+ mSheetSortParams[ nTab ] = rParam;
+}
+
+SCCOL ScDocument::ClampToAllocatedColumns(SCTAB nTab, SCCOL nCol) const
+{
+ return maTabs[nTab]->ClampToAllocatedColumns(nCol);
+}
+
+SCCOL ScDocument::GetAllocatedColumnsCount(SCTAB nTab) const
+{
+ return maTabs[nTab]->GetAllocatedColumnsCount();
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sc/source/core/data/documen4.cxx b/sc/source/core/data/documen4.cxx
new file mode 100644
index 000000000..9aa39d66c
--- /dev/null
+++ b/sc/source/core/data/documen4.cxx
@@ -0,0 +1,1367 @@
+/* -*- 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 <svl/numformat.hxx>
+#include <svl/zforlist.hxx>
+#include <svl/zformat.hxx>
+#include <formula/token.hxx>
+#include <sal/log.hxx>
+#include <unotools/configmgr.hxx>
+#include <osl/diagnose.h>
+#include <o3tl/string_view.hxx>
+
+#include <document.hxx>
+#include <table.hxx>
+#include <globstr.hrc>
+#include <scresid.hxx>
+#include <subtotal.hxx>
+#include <docoptio.hxx>
+#include <markdata.hxx>
+#include <validat.hxx>
+#include <scitems.hxx>
+#include <stlpool.hxx>
+#include <poolhelp.hxx>
+#include <detdata.hxx>
+#include <patattr.hxx>
+#include <chgtrack.hxx>
+#include <progress.hxx>
+#include <paramisc.hxx>
+#include <compiler.hxx>
+#include <externalrefmgr.hxx>
+#include <attrib.hxx>
+#include <formulacell.hxx>
+#include <tokenarray.hxx>
+#include <tokenstringcontext.hxx>
+#include <memory>
+
+using namespace formula;
+
+/** (Goal Seek) Find a value of x that is a root of f(x)
+
+ This function is used internally for the goal seek operation. It uses the
+ Regula Falsi (aka false position) algorithm to find a root of f(x). The
+ start value and the target value are to be given by the user in the
+ goal seek dialog. The f(x) in this case is defined as the formula in the
+ formula cell minus target value. This function may also perform additional
+ search in the horizontal directions when the f(x) is discrete in order to
+ ensure a non-zero slope necessary for deriving a subsequent x that is
+ reasonably close to the root of interest.
+
+ @change 24.10.2004 by Kohei Yoshida (kohei@openoffice.org)
+
+ @see #i28955#
+
+ @change 6 Aug 2013, fdo37341
+*/
+bool ScDocument::Solver(SCCOL nFCol, SCROW nFRow, SCTAB nFTab,
+ SCCOL nVCol, SCROW nVRow, SCTAB nVTab,
+ const OUString& sValStr, double& nX)
+{
+ bool bRet = false;
+ nX = 0.0;
+ if ( ValidColRow( nFCol, nFRow ) && ValidTab( nFTab ) &&
+ ValidColRow( nVCol, nVRow ) && ValidTab( nVTab ) &&
+ nFTab < static_cast<SCTAB>( maTabs.size() ) && maTabs[nFTab] &&
+ nVTab < static_cast<SCTAB>( maTabs.size() ) && maTabs[nVTab] )
+ {
+ CellType eFType = GetCellType(nFCol, nFRow, nFTab);
+ CellType eVType = GetCellType(nVCol, nVRow, nVTab);
+ // #i108005# convert target value to number using default format,
+ // as previously done in ScInterpreter::GetDouble
+ ScFormulaCell* pFormula = nullptr;
+ double fTargetVal = 0.0;
+ sal_uInt32 nFIndex = 0;
+ if ( eFType == CELLTYPE_FORMULA && eVType == CELLTYPE_VALUE &&
+ GetFormatTable()->IsNumberFormat( sValStr, nFIndex, fTargetVal ) )
+ {
+ ScAddress aFormulaAdr( nFCol, nFRow, nFTab );
+ pFormula = GetFormulaCell( aFormulaAdr );
+ }
+ if (pFormula)
+ {
+ bool bDoneIteration = false;
+ ScAddress aValueAdr( nVCol, nVRow, nVTab );
+ double* pVCell = GetValueCell( aValueAdr );
+
+ ScRange aVRange( aValueAdr, aValueAdr ); // for SetDirty
+ // Original value to be restored later if necessary
+ double fSaveVal = *pVCell;
+
+ const sal_uInt16 nMaxIter = 100;
+ const double fEps = 1E-10;
+ const double fDelta = 1E-6;
+
+ double fBestX, fXPrev;
+ double fBestF, fFPrev;
+ fBestX = fXPrev = fSaveVal;
+
+ pFormula->Interpret();
+ bool bError = ( pFormula->GetErrCode() != FormulaError::NONE );
+ // bError always corresponds with fF
+
+ fFPrev = pFormula->GetValue() - fTargetVal;
+
+ fBestF = fabs( fFPrev );
+ if ( fBestF < fDelta )
+ bDoneIteration = true;
+
+ double fX = fXPrev + fEps;
+ double fF = fFPrev;
+ double fSlope;
+
+ sal_uInt16 nIter = 0;
+
+ bool bHorMoveError = false;
+ // Conform Regula Falsi Method
+ while ( !bDoneIteration && ( nIter++ < nMaxIter ) )
+ {
+ *pVCell = fX;
+ SetDirty( aVRange, false );
+ pFormula->Interpret();
+ bError = ( pFormula->GetErrCode() != FormulaError::NONE );
+ fF = pFormula->GetValue() - fTargetVal;
+
+ if ( fF == fFPrev && !bError )
+ {
+ // HORIZONTAL SEARCH: Keep moving x in both directions until the f(x)
+ // becomes different from the previous f(x). This routine is needed
+ // when a given function is discrete, in which case the resulting slope
+ // may become zero which ultimately causes the goal seek operation
+ // to fail. #i28955#
+
+ sal_uInt16 nHorIter = 0;
+ const double fHorStepAngle = 5.0;
+ const double fHorMaxAngle = 80.0;
+ int const nHorMaxIter = static_cast<int>( fHorMaxAngle / fHorStepAngle );
+ bool bDoneHorMove = false;
+
+ while ( !bDoneHorMove && !bHorMoveError && nHorIter++ < nHorMaxIter )
+ {
+ double fHorAngle = fHorStepAngle * static_cast<double>( nHorIter );
+ double fHorTangent = std::tan(basegfx::deg2rad(fHorAngle));
+
+ sal_uInt16 nIdx = 0;
+ while( nIdx++ < 2 && !bDoneHorMove )
+ {
+ double fHorX;
+ if ( nIdx == 1 )
+ fHorX = fX + fabs( fF ) * fHorTangent;
+ else
+ fHorX = fX - fabs( fF ) * fHorTangent;
+
+ *pVCell = fHorX;
+ SetDirty( aVRange, false );
+ pFormula->Interpret();
+ bHorMoveError = ( pFormula->GetErrCode() != FormulaError::NONE );
+ if ( bHorMoveError )
+ break;
+
+ fF = pFormula->GetValue() - fTargetVal;
+ if ( fF != fFPrev )
+ {
+ fX = fHorX;
+ bDoneHorMove = true;
+ }
+ }
+ }
+ if ( !bDoneHorMove )
+ bHorMoveError = true;
+ }
+
+ if ( bError )
+ {
+ // move closer to last valid value (fXPrev), keep fXPrev & fFPrev
+ double fDiff = ( fXPrev - fX ) / 2;
+ if ( fabs( fDiff ) < fEps )
+ fDiff = ( fDiff < 0.0 ? - fEps : fEps );
+ fX += fDiff;
+ }
+ else if ( bHorMoveError )
+ break;
+ else if ( fabs(fF) < fDelta )
+ {
+ // converged to root
+ fBestX = fX;
+ bDoneIteration = true;
+ }
+ else
+ {
+ if ( fabs(fF) + fDelta < fBestF )
+ {
+ fBestX = fX;
+ fBestF = fabs( fF );
+ }
+
+ if ( ( fXPrev - fX ) != 0 )
+ {
+ fSlope = ( fFPrev - fF ) / ( fXPrev - fX );
+ if ( fabs( fSlope ) < fEps )
+ fSlope = fSlope < 0.0 ? -fEps : fEps;
+ }
+ else
+ fSlope = fEps;
+
+ fXPrev = fX;
+ fFPrev = fF;
+ fX = fX - ( fF / fSlope );
+ }
+ }
+
+ // Try a nice rounded input value if possible.
+ const double fNiceDelta = ( bDoneIteration && fabs( fBestX ) >= 1e-3 ? 1e-3 : fDelta );
+ nX = ::rtl::math::approxFloor( ( fBestX / fNiceDelta ) + 0.5 ) * fNiceDelta;
+
+ if ( bDoneIteration )
+ {
+ *pVCell = nX;
+ SetDirty( aVRange, false );
+ pFormula->Interpret();
+ if ( fabs( pFormula->GetValue() - fTargetVal ) > fabs( fF ) )
+ nX = fBestX;
+ bRet = true;
+ }
+ else if ( bError || bHorMoveError )
+ {
+ nX = fBestX;
+ }
+ *pVCell = fSaveVal;
+ SetDirty( aVRange, false );
+ pFormula->Interpret();
+ if ( !bDoneIteration )
+ {
+ SetError( nVCol, nVRow, nVTab, FormulaError::NotAvailable );
+ }
+ }
+ else
+ {
+ SetError( nVCol, nVRow, nVTab, FormulaError::NotAvailable );
+ }
+ }
+ return bRet;
+}
+
+void ScDocument::InsertMatrixFormula(SCCOL nCol1, SCROW nRow1,
+ SCCOL nCol2, SCROW nRow2,
+ const ScMarkData& rMark,
+ const OUString& rFormula,
+ const ScTokenArray* pArr,
+ const formula::FormulaGrammar::Grammar eGram )
+{
+ PutInOrder(nCol1, nCol2);
+ PutInOrder(nRow1, nRow2);
+ nCol2 = std::min<SCCOL>(nCol2, MaxCol());
+ nRow2 = std::min<SCROW>(nRow2, MaxRow());
+ if (!rMark.GetSelectCount())
+ {
+ SAL_WARN("sc", "ScDocument::InsertMatrixFormula: No table marked");
+ return;
+ }
+ if (utl::ConfigManager::IsFuzzing())
+ {
+ // just too slow
+ if (nCol2 - nCol1 > 64)
+ return;
+ if (nRow2 - nRow1 > 64)
+ return;
+ }
+ assert( ValidColRow( nCol1, nRow1) && ValidColRow( nCol2, nRow2));
+
+ SCTAB nTab1 = *rMark.begin();
+
+ ScFormulaCell* pCell;
+ ScAddress aPos( nCol1, nRow1, nTab1 );
+ if (pArr)
+ pCell = new ScFormulaCell(*this, aPos, *pArr, eGram, ScMatrixMode::Formula);
+ else
+ pCell = new ScFormulaCell(*this, aPos, rFormula, eGram, ScMatrixMode::Formula);
+ pCell->SetMatColsRows( nCol2 - nCol1 + 1, nRow2 - nRow1 + 1 );
+ SCTAB nMax = static_cast<SCTAB>(maTabs.size());
+ for (const auto& rTab : rMark)
+ {
+ if (rTab >= nMax)
+ break;
+
+ if (!maTabs[rTab])
+ continue;
+
+ if (rTab == nTab1)
+ {
+ pCell = maTabs[rTab]->SetFormulaCell(nCol1, nRow1, pCell);
+ if (!pCell) //NULL if nCol1/nRow1 is invalid, which it can't be here
+ break;
+ }
+ else
+ maTabs[rTab]->SetFormulaCell(
+ nCol1, nRow1,
+ new ScFormulaCell(
+ *pCell, *this, ScAddress(nCol1, nRow1, rTab), ScCloneFlags::StartListening));
+ }
+
+ ScSingleRefData aRefData;
+ aRefData.InitFlags();
+ aRefData.SetRelCol(0);
+ aRefData.SetRelRow(0);
+ aRefData.SetRelTab(0); // 2D matrix, always same sheet
+
+ ScTokenArray aArr(*this); // consists only of one single reference token.
+ formula::FormulaToken* t = aArr.AddMatrixSingleReference(aRefData);
+
+ for (const SCTAB& nTab : rMark)
+ {
+ if (nTab >= nMax)
+ break;
+
+ ScTable* pTab = FetchTable(nTab);
+ if (!pTab)
+ continue;
+
+ for (SCCOL nCol : GetWritableColumnsRange(nTab, nCol1, nCol2))
+ {
+ aRefData.SetRelCol(nCol1 - nCol);
+ for (SCROW nRow = nRow1; nRow <= nRow2; ++nRow)
+ {
+ if (nCol == nCol1 && nRow == nRow1)
+ // Skip the base position.
+ continue;
+
+ // Reference in each cell must point to the origin cell relative to the current cell.
+ aRefData.SetRelRow(nRow1 - nRow);
+ *t->GetSingleRef() = aRefData;
+ // Token array must be cloned so that each formula cell receives its own copy.
+ ScTokenArray aTokArr(aArr.CloneValue());
+ aPos = ScAddress(nCol, nRow, nTab);
+ pCell = new ScFormulaCell(*this, aPos, aTokArr, eGram, ScMatrixMode::Reference);
+ pTab->SetFormulaCell(nCol, nRow, pCell);
+ }
+ }
+ }
+}
+
+void ScDocument::InsertTableOp(const ScTabOpParam& rParam, // multiple (repeated?) operation
+ SCCOL nCol1, SCROW nRow1, SCCOL nCol2, SCROW nRow2,
+ const ScMarkData& rMark)
+{
+ PutInOrder(nCol1, nCol2);
+ PutInOrder(nRow1, nRow2);
+ assert( ValidColRow( nCol1, nRow1) && ValidColRow( nCol2, nRow2));
+ SCTAB i, nTab1;
+ SCCOL j;
+ SCROW k;
+ i = 0;
+ bool bStop = false;
+ SCTAB nMax = static_cast<SCTAB>(maTabs.size());
+ for (const auto& rTab : rMark)
+ {
+ if (rTab >= nMax)
+ break;
+
+ if (maTabs[rTab])
+ {
+ i = rTab;
+ bStop = true;
+ break;
+ }
+ }
+ nTab1 = i;
+ if (!bStop)
+ {
+ OSL_FAIL("ScDocument::InsertTableOp: No table marked");
+ return;
+ }
+
+ ScRefAddress aRef;
+ OUStringBuffer aForString;
+ aForString.append('=');
+ aForString.append(ScCompiler::GetNativeSymbol(ocTableOp));
+ aForString.append(ScCompiler::GetNativeSymbol( ocOpen));
+
+ const OUString& sSep = ScCompiler::GetNativeSymbol( ocSep);
+ if (rParam.meMode == ScTabOpParam::Column) // column only
+ {
+ aRef.Set( rParam.aRefFormulaCell.GetAddress(), true, false, false );
+ aForString.append(aRef.GetRefString(*this, nTab1));
+ aForString.append(sSep);
+ aForString.append(rParam.aRefColCell.GetRefString(*this, nTab1));
+ aForString.append(sSep);
+ aRef.Set( nCol1, nRow1, nTab1, false, true, true );
+ aForString.append(aRef.GetRefString(*this, nTab1));
+ nCol1++;
+ nCol2 = std::min( nCol2, static_cast<SCCOL>(rParam.aRefFormulaEnd.Col() -
+ rParam.aRefFormulaCell.Col() + nCol1 + 1));
+ }
+ else if (rParam.meMode == ScTabOpParam::Row) // row only
+ {
+ aRef.Set( rParam.aRefFormulaCell.GetAddress(), false, true, false );
+ aForString.append(aRef.GetRefString(*this, nTab1));
+ aForString.append(sSep);
+ aForString.append(rParam.aRefRowCell.GetRefString(*this, nTab1));
+ aForString.append(sSep);
+ aRef.Set( nCol1, nRow1, nTab1, true, false, true );
+ aForString.append(aRef.GetRefString(*this, nTab1));
+ nRow1++;
+ nRow2 = std::min( nRow2, static_cast<SCROW>(rParam.aRefFormulaEnd.Row() -
+ rParam.aRefFormulaCell.Row() + nRow1 + 1));
+ }
+ else // both
+ {
+ aForString.append(rParam.aRefFormulaCell.GetRefString(*this, nTab1));
+ aForString.append(sSep);
+ aForString.append(rParam.aRefColCell.GetRefString(*this, nTab1));
+ aForString.append(sSep);
+ aRef.Set( nCol1, nRow1 + 1, nTab1, false, true, true );
+ aForString.append(aRef.GetRefString(*this, nTab1));
+ aForString.append(sSep);
+ aForString.append(rParam.aRefRowCell.GetRefString(*this, nTab1));
+ aForString.append(sSep);
+ aRef.Set( nCol1 + 1, nRow1, nTab1, true, false, true );
+ aForString.append(aRef.GetRefString(*this, nTab1));
+ nCol1++; nRow1++;
+ }
+ aForString.append(ScCompiler::GetNativeSymbol( ocClose ));
+
+ ScFormulaCell aRefCell( *this, ScAddress( nCol1, nRow1, nTab1 ), aForString.makeStringAndClear(),
+ formula::FormulaGrammar::GRAM_NATIVE, ScMatrixMode::NONE );
+ for( j = nCol1; j <= nCol2; j++ )
+ for( k = nRow1; k <= nRow2; k++ )
+ for (i = 0; i < static_cast<SCTAB>(maTabs.size()); i++)
+ {
+ for (const auto& rTab : rMark)
+ {
+ if (rTab >= nMax)
+ break;
+ if( maTabs[rTab] )
+ maTabs[rTab]->SetFormulaCell(
+ j, k, new ScFormulaCell(aRefCell, *this, ScAddress(j, k, rTab), ScCloneFlags::StartListening));
+ }
+ }
+}
+
+namespace {
+
+bool setCacheTableReferenced(const ScDocument& rDoc, formula::FormulaToken& rToken, ScExternalRefManager& rRefMgr, const ScAddress& rPos)
+{
+ switch (rToken.GetType())
+ {
+ case svExternalSingleRef:
+ return rRefMgr.setCacheTableReferenced(
+ rToken.GetIndex(), rToken.GetString().getString(), 1);
+ case svExternalDoubleRef:
+ {
+ const ScComplexRefData& rRef = *rToken.GetDoubleRef();
+ ScRange aAbs = rRef.toAbs(rDoc, rPos);
+ size_t nSheets = aAbs.aEnd.Tab() - aAbs.aStart.Tab() + 1;
+ return rRefMgr.setCacheTableReferenced(
+ rToken.GetIndex(), rToken.GetString().getString(), nSheets);
+ }
+ case svExternalName:
+ /* TODO: external names aren't supported yet, but would
+ * have to be marked as well, if so. Mechanism would be
+ * different. */
+ OSL_FAIL("ScDocument::MarkUsedExternalReferences: implement the svExternalName case!");
+ break;
+ default:
+ break;
+ }
+ return false;
+}
+
+}
+
+bool ScDocument::MarkUsedExternalReferences( const ScTokenArray& rArr, const ScAddress& rPos )
+{
+ if (!rArr.GetLen())
+ return false;
+
+ ScExternalRefManager* pRefMgr = nullptr;
+ formula::FormulaTokenArrayPlainIterator aIter( rArr );
+ bool bAllMarked = false;
+ while (!bAllMarked)
+ {
+ formula::FormulaToken* t = aIter.GetNextReferenceOrName();
+ if (!t)
+ break;
+ if (t->IsExternalRef())
+ {
+ if (!pRefMgr)
+ pRefMgr = GetExternalRefManager();
+
+ bAllMarked = setCacheTableReferenced(*this, *t, *pRefMgr, rPos);
+ }
+ else if (t->GetType() == svIndex)
+ {
+ // this is a named range. Check if the range contains an external
+ // reference.
+ ScRangeData* pRangeData = GetRangeName()->findByIndex(t->GetIndex());
+ if (!pRangeData)
+ continue;
+
+ ScTokenArray* pArray = pRangeData->GetCode();
+ formula::FormulaTokenArrayPlainIterator aArrayIter(*pArray);
+ for (t = aArrayIter.First(); t; t = aArrayIter.Next())
+ {
+ if (!t->IsExternalRef())
+ continue;
+
+ if (!pRefMgr)
+ pRefMgr = GetExternalRefManager();
+
+ bAllMarked = setCacheTableReferenced(*this, *t, *pRefMgr, rPos);
+ }
+ }
+ }
+ return bAllMarked;
+}
+
+bool ScDocument::GetNextSpellingCell(SCCOL& nCol, SCROW& nRow, SCTAB nTab,
+ bool bInSel, const ScMarkData& rMark) const
+{
+ if (ValidTab(nTab) && nTab < static_cast<SCTAB>(maTabs.size()) && maTabs[nTab])
+ return maTabs[nTab]->GetNextSpellingCell( nCol, nRow, bInSel, rMark );
+ else
+ return false;
+}
+
+bool ScDocument::GetNextMarkedCell( SCCOL& rCol, SCROW& rRow, SCTAB nTab,
+ const ScMarkData& rMark )
+{
+ if (ValidTab(nTab) && nTab < static_cast<SCTAB>(maTabs.size()) && maTabs[nTab])
+ return maTabs[nTab]->GetNextMarkedCell( rCol, rRow, rMark );
+ else
+ return false;
+}
+
+void ScDocument::ReplaceStyle(const SvxSearchItem& rSearchItem,
+ SCCOL nCol, SCROW nRow, SCTAB nTab,
+ const ScMarkData& rMark)
+{
+ if (nTab < static_cast<SCTAB>(maTabs.size()) && maTabs[nTab])
+ maTabs[nTab]->ReplaceStyle(rSearchItem, nCol, nRow, rMark, true/*bIsUndoP*/);
+}
+
+void ScDocument::CompileDBFormula()
+{
+ sc::CompileFormulaContext aCxt(*this);
+ for (auto& rxTab : maTabs)
+ {
+ if (rxTab)
+ rxTab->CompileDBFormula(aCxt);
+ }
+}
+
+void ScDocument::CompileColRowNameFormula()
+{
+ sc::CompileFormulaContext aCxt(*this);
+ for (auto& rxTab : maTabs)
+ {
+ if (rxTab)
+ rxTab->CompileColRowNameFormula(aCxt);
+ }
+}
+
+void ScDocument::InvalidateTableArea()
+{
+ for (auto& rxTab : maTabs)
+ {
+ if (!rxTab)
+ break;
+ rxTab->InvalidateTableArea();
+ if ( rxTab->IsScenario() )
+ rxTab->InvalidateScenarioRanges();
+ }
+}
+
+sal_Int32 ScDocument::GetMaxStringLen( SCTAB nTab, SCCOL nCol,
+ SCROW nRowStart, SCROW nRowEnd, rtl_TextEncoding eCharSet ) const
+{
+ if (ValidTab(nTab) && nTab < static_cast<SCTAB>(maTabs.size()) && maTabs[nTab])
+ return maTabs[nTab]->GetMaxStringLen( nCol, nRowStart, nRowEnd, eCharSet );
+ else
+ return 0;
+}
+
+sal_Int32 ScDocument::GetMaxNumberStringLen( sal_uInt16& nPrecision, SCTAB nTab,
+ SCCOL nCol,
+ SCROW nRowStart, SCROW nRowEnd ) const
+{
+ if (ValidTab(nTab) && nTab < static_cast<SCTAB>(maTabs.size()) && maTabs[nTab])
+ return maTabs[nTab]->GetMaxNumberStringLen( nPrecision, nCol,
+ nRowStart, nRowEnd );
+ else
+ return 0;
+}
+
+bool ScDocument::GetSelectionFunction( ScSubTotalFunc eFunc,
+ const ScAddress& rCursor, const ScMarkData& rMark,
+ double& rResult )
+{
+ ScFunctionData aData(eFunc);
+
+ ScMarkData aMark(rMark);
+ aMark.MarkToMulti();
+ if (!aMark.IsMultiMarked() && !aMark.IsCellMarked(rCursor.Col(), rCursor.Row()))
+ aMark.SetMarkArea(rCursor);
+
+ SCTAB nMax = static_cast<SCTAB>(maTabs.size());
+ ScMarkData::const_iterator itr = aMark.begin(), itrEnd = aMark.end();
+
+ for (; itr != itrEnd && *itr < nMax && !aData.getError(); ++itr)
+ if (maTabs[*itr])
+ maTabs[*itr]->UpdateSelectionFunction(aData, aMark);
+
+ rResult = aData.getResult();
+ if (aData.getError())
+ rResult = 0.0;
+
+ return !aData.getError();
+}
+
+double ScDocument::RoundValueAsShown( double fVal, sal_uInt32 nFormat, const ScInterpreterContext* pContext ) const
+{
+ const SvNumberFormatter* pFormatter = pContext ? pContext->GetFormatTable() : GetFormatTable();
+ const SvNumberformat* pFormat = pFormatter->GetEntry( nFormat );
+ if (!pFormat)
+ return fVal;
+ SvNumFormatType nType = pFormat->GetMaskedType();
+ if (nType != SvNumFormatType::DATE && nType != SvNumFormatType::TIME && nType != SvNumFormatType::DATETIME )
+ {
+ // MSVC doesn't recognize all paths init nPrecision and wails about
+ // "potentially uninitialized local variable 'nPrecision' used"
+ // so init to some random sensible value preserving all decimals.
+ short nPrecision = 20;
+ bool bStdPrecision = ((nFormat % SV_COUNTRY_LANGUAGE_OFFSET) == 0);
+ if (!bStdPrecision)
+ {
+ sal_uInt16 nIdx = pFormat->GetSubformatIndex( fVal );
+ nPrecision = static_cast<short>(pFormat->GetFormatPrecision( nIdx ));
+ switch ( nType )
+ {
+ case SvNumFormatType::PERCENT: // 0.41% == 0.0041
+ nPrecision += 2;
+ break;
+ case SvNumFormatType::SCIENTIFIC: // 1.23e-3 == 0.00123
+ {
+ short nExp = 0;
+ if ( fVal > 0.0 )
+ nExp = static_cast<short>(floor( log10( fVal ) ));
+ else if ( fVal < 0.0 )
+ nExp = static_cast<short>(floor( log10( -fVal ) ));
+ nPrecision -= nExp;
+ short nInteger = static_cast<short>(pFormat->GetFormatIntegerDigits( nIdx ));
+ if ( nInteger > 1 ) // Engineering notation
+ {
+ short nIncrement = nExp % nInteger;
+ if ( nIncrement != 0 )
+ {
+ nPrecision += nIncrement;
+ if (nExp < 0 )
+ nPrecision += nInteger;
+ }
+ }
+ break;
+ }
+ case SvNumFormatType::FRACTION: // get value of fraction representation
+ {
+ return pFormat->GetRoundFractionValue( fVal );
+ }
+ case SvNumFormatType::NUMBER:
+ case SvNumFormatType::CURRENCY:
+ { // tdf#106253 Thousands divisors for format "0,"
+ const sal_uInt16 nTD = pFormat->GetThousandDivisorPrecision( nIdx );
+ if (nTD == SvNumberFormatter::UNLIMITED_PRECISION)
+ // Format contains General keyword, handled below.
+ bStdPrecision = true;
+ else
+ nPrecision -= nTD;
+ break;
+ }
+ default: break;
+ }
+ }
+ if (bStdPrecision)
+ {
+ nPrecision = static_cast<short>(GetDocOptions().GetStdPrecision());
+ // #i115512# no rounding for automatic decimals
+ if (nPrecision == static_cast<short>(SvNumberFormatter::UNLIMITED_PRECISION))
+ return fVal;
+ }
+ double fRound = ::rtl::math::round( fVal, nPrecision );
+ if ( ::rtl::math::approxEqual( fVal, fRound ) )
+ return fVal; // rounding might introduce some error
+ else
+ return fRound;
+ }
+ else
+ return fVal;
+}
+
+// conditional formats and validation ranges
+
+sal_uLong ScDocument::AddCondFormat( std::unique_ptr<ScConditionalFormat> pNew, SCTAB nTab )
+{
+ if(!pNew)
+ return 0;
+
+ if(ValidTab(nTab) && nTab < static_cast<SCTAB>(maTabs.size()) && maTabs[nTab])
+ return maTabs[nTab]->AddCondFormat( std::move(pNew) );
+
+ return 0;
+}
+
+sal_uLong ScDocument::AddValidationEntry( const ScValidationData& rNew )
+{
+ if (rNew.IsEmpty())
+ return 0; // empty is always 0
+
+ if (!pValidationList)
+ {
+ ScMutationGuard aGuard(*this, ScMutationGuardFlags::CORE);
+ pValidationList.reset(new ScValidationDataList);
+ }
+
+ sal_uLong nMax = 0;
+ for( const auto& rxData : *pValidationList )
+ {
+ const ScValidationData* pData = rxData.get();
+ sal_uLong nKey = pData->GetKey();
+ if ( pData->EqualEntries( rNew ) )
+ return nKey;
+ if ( nKey > nMax )
+ nMax = nKey;
+ }
+
+ // might be called from ScPatternAttr::PutInPool; thus clone (real copy)
+
+ sal_uLong nNewKey = nMax + 1;
+ std::unique_ptr<ScValidationData> pInsert(rNew.Clone(this));
+ pInsert->SetKey( nNewKey );
+ ScMutationGuard aGuard(*this, ScMutationGuardFlags::CORE);
+ pValidationList->InsertNew( std::move(pInsert) );
+ return nNewKey;
+}
+
+const SfxPoolItem* ScDocument::GetEffItem(
+ SCCOL nCol, SCROW nRow, SCTAB nTab, sal_uInt16 nWhich ) const
+{
+ const ScPatternAttr* pPattern = GetPattern( nCol, nRow, nTab );
+ if ( pPattern )
+ {
+ const SfxItemSet& rSet = pPattern->GetItemSet();
+ if ( rSet.GetItemState( ATTR_CONDITIONAL ) == SfxItemState::SET )
+ {
+ const ScCondFormatIndexes& rIndex = pPattern->GetItem(ATTR_CONDITIONAL).GetCondFormatData();
+ ScConditionalFormatList* pCondFormList = GetCondFormList( nTab );
+ if (!rIndex.empty() && pCondFormList)
+ {
+ for(const auto& rItem : rIndex)
+ {
+ const ScConditionalFormat* pForm = pCondFormList->GetFormat( rItem );
+ if ( pForm )
+ {
+ ScAddress aPos(nCol, nRow, nTab);
+ ScRefCellValue aCell(const_cast<ScDocument&>(*this), aPos);
+ const OUString& aStyle = pForm->GetCellStyle(aCell, aPos);
+ if (!aStyle.isEmpty())
+ {
+ SfxStyleSheetBase* pStyleSheet = mxPoolHelper->GetStylePool()->Find(
+ aStyle, SfxStyleFamily::Para );
+ const SfxPoolItem* pItem = nullptr;
+ if ( pStyleSheet && pStyleSheet->GetItemSet().GetItemState(
+ nWhich, true, &pItem ) == SfxItemState::SET )
+ return pItem;
+ }
+ }
+ }
+ }
+ }
+ return &rSet.Get( nWhich );
+ }
+ OSL_FAIL("no pattern");
+ return nullptr;
+}
+
+const SfxItemSet* ScDocument::GetCondResult( SCCOL nCol, SCROW nRow, SCTAB nTab, ScRefCellValue* pCell ) const
+{
+ ScConditionalFormatList* pFormatList = GetCondFormList(nTab);
+ if (!pFormatList)
+ return nullptr;
+
+ ScAddress aPos(nCol, nRow, nTab);
+ ScRefCellValue aCell;
+ if( pCell == nullptr )
+ {
+ aCell.assign(const_cast<ScDocument&>(*this), aPos);
+ pCell = &aCell;
+ }
+ const ScPatternAttr* pPattern = GetPattern( nCol, nRow, nTab );
+ const ScCondFormatIndexes& rIndex =
+ pPattern->GetItem(ATTR_CONDITIONAL).GetCondFormatData();
+
+ return GetCondResult(*pCell, aPos, *pFormatList, rIndex);
+}
+
+const SfxItemSet* ScDocument::GetCondResult(
+ ScRefCellValue& rCell, const ScAddress& rPos, const ScConditionalFormatList& rList,
+ const ScCondFormatIndexes& rIndex ) const
+{
+ for (const auto& rItem : rIndex)
+ {
+ const ScConditionalFormat* pForm = rList.GetFormat(rItem);
+ if (!pForm)
+ continue;
+
+ const OUString& aStyle = pForm->GetCellStyle(rCell, rPos);
+ if (!aStyle.isEmpty())
+ {
+ SfxStyleSheetBase* pStyleSheet =
+ mxPoolHelper->GetStylePool()->Find(aStyle, SfxStyleFamily::Para);
+
+ if (pStyleSheet)
+ return &pStyleSheet->GetItemSet();
+
+ // if style is not there, treat like no condition
+ }
+ }
+
+ return nullptr;
+}
+
+ScConditionalFormat* ScDocument::GetCondFormat(
+ SCCOL nCol, SCROW nRow, SCTAB nTab ) const
+{
+ sal_uInt32 nIndex = 0;
+ const ScCondFormatIndexes& rCondFormats = GetAttr(nCol, nRow, nTab, ATTR_CONDITIONAL)->GetCondFormatData();
+
+ if(!rCondFormats.empty())
+ nIndex = rCondFormats[0];
+
+ if (nIndex)
+ {
+ ScConditionalFormatList* pCondFormList = GetCondFormList(nTab);
+ if (pCondFormList)
+ return pCondFormList->GetFormat( nIndex );
+ else
+ {
+ OSL_FAIL("pCondFormList is 0");
+ }
+ }
+
+ return nullptr;
+}
+
+ScConditionalFormatList* ScDocument::GetCondFormList(SCTAB nTab) const
+{
+ if(ValidTab(nTab) && nTab < static_cast<SCTAB>(maTabs.size()) && maTabs[nTab])
+ return maTabs[nTab]->GetCondFormList();
+
+ return nullptr;
+}
+
+void ScDocument::SetCondFormList( ScConditionalFormatList* pList, SCTAB nTab )
+{
+ if(ValidTab(nTab) && nTab < static_cast<SCTAB>(maTabs.size()) && maTabs[nTab])
+ maTabs[nTab]->SetCondFormList(pList);
+}
+
+const ScValidationData* ScDocument::GetValidationEntry( sal_uLong nIndex ) const
+{
+ if ( pValidationList )
+ return pValidationList->GetData( nIndex );
+ else
+ return nullptr;
+}
+
+void ScDocument::DeleteConditionalFormat(sal_uLong nOldIndex, SCTAB nTab)
+{
+ if(ValidTab(nTab) && nTab < static_cast<SCTAB>(maTabs.size()) && maTabs[nTab])
+ maTabs[nTab]->DeleteConditionalFormat(nOldIndex);
+}
+
+bool ScDocument::HasDetectiveOperations() const
+{
+ return pDetOpList && pDetOpList->Count();
+}
+
+void ScDocument::AddDetectiveOperation( const ScDetOpData& rData )
+{
+ if (!pDetOpList)
+ pDetOpList.reset(new ScDetOpList);
+
+ pDetOpList->Append( rData );
+}
+
+void ScDocument::ClearDetectiveOperations()
+{
+ pDetOpList.reset(); // deletes also the entries
+}
+
+void ScDocument::SetDetOpList(std::unique_ptr<ScDetOpList> pNew)
+{
+ pDetOpList = std::move(pNew);
+}
+
+// Comparison of Documents
+
+// Pfriemel-Factors
+#define SC_DOCCOMP_MAXDIFF 256
+#define SC_DOCCOMP_MINGOOD 128
+#define SC_DOCCOMP_COLUMNS 10
+#define SC_DOCCOMP_ROWS 100
+
+sal_uInt16 ScDocument::RowDifferences( SCROW nThisRow, SCTAB nThisTab,
+ ScDocument& rOtherDoc, SCROW nOtherRow, SCTAB nOtherTab,
+ SCCOL nMaxCol, const SCCOLROW* pOtherCols )
+{
+ sal_uLong nDif = 0;
+ sal_uLong nUsed = 0;
+ for (SCCOL nThisCol=0; nThisCol<=nMaxCol; nThisCol++)
+ {
+ SCCOL nOtherCol;
+ if ( pOtherCols )
+ nOtherCol = static_cast<SCCOL>(pOtherCols[nThisCol]);
+ else
+ nOtherCol = nThisCol;
+
+ if (ValidCol(nOtherCol)) // only compare columns that are common to both docs
+ {
+ ScRefCellValue aThisCell(*this, ScAddress(nThisCol, nThisRow, nThisTab));
+ ScRefCellValue aOtherCell(rOtherDoc, ScAddress(nOtherCol, nOtherRow, nOtherTab));
+ if (!aThisCell.equalsWithoutFormat(aOtherCell))
+ {
+ if (!aThisCell.isEmpty() && !aOtherCell.isEmpty())
+ nDif += 3;
+ else
+ nDif += 4; // content <-> empty counts more
+ }
+
+ if (!aThisCell.isEmpty() || !aOtherCell.isEmpty())
+ ++nUsed;
+ }
+ }
+
+ if (nUsed > 0)
+ return static_cast<sal_uInt16>((nDif*64)/nUsed); // max.256 (SC_DOCCOMP_MAXDIFF)
+
+ OSL_ENSURE(!nDif,"Diff without Used");
+ return 0;
+}
+
+sal_uInt16 ScDocument::ColDifferences( SCCOL nThisCol, SCTAB nThisTab,
+ ScDocument& rOtherDoc, SCCOL nOtherCol, SCTAB nOtherTab,
+ SCROW nMaxRow, const SCCOLROW* pOtherRows )
+{
+
+ //TODO: optimize e.g. with iterator?
+
+ sal_uInt64 nDif = 0;
+ sal_uInt64 nUsed = 0;
+ for (SCROW nThisRow=0; nThisRow<=nMaxRow; nThisRow++)
+ {
+ SCROW nOtherRow;
+ if ( pOtherRows )
+ nOtherRow = pOtherRows[nThisRow];
+ else
+ nOtherRow = nThisRow;
+
+ if (ValidRow(nOtherRow)) // only compare rows that are common to both docs
+ {
+ ScRefCellValue aThisCell(*this, ScAddress(nThisCol, nThisRow, nThisTab));
+ ScRefCellValue aOtherCell(rOtherDoc, ScAddress(nOtherCol, nOtherRow, nOtherTab));
+ if (!aThisCell.equalsWithoutFormat(aOtherCell))
+ {
+ if (!aThisCell.isEmpty() && !aOtherCell.isEmpty())
+ nDif += 3;
+ else
+ nDif += 4; // content <-> empty counts more
+ }
+
+ if (!aThisCell.isEmpty() || !aOtherCell.isEmpty())
+ ++nUsed;
+ }
+ }
+
+ if (nUsed > 0)
+ return static_cast<sal_uInt16>((nDif*64)/nUsed); // max.256
+
+ OSL_ENSURE(!nDif,"Diff without Used");
+ return 0;
+}
+
+void ScDocument::FindOrder( SCCOLROW* pOtherRows, SCCOLROW nThisEndRow, SCCOLROW nOtherEndRow,
+ bool bColumns, ScDocument& rOtherDoc, SCTAB nThisTab, SCTAB nOtherTab,
+ SCCOLROW nEndCol, const SCCOLROW* pTranslate, ScProgress* pProgress, sal_uInt64 nProAdd )
+{
+ // bColumns=true: rows are columns and vice versa
+
+ SCCOLROW nMaxCont; // continue by how much
+ SCCOLROW nMinGood; // what is a hit (incl.)
+ if ( bColumns )
+ {
+ nMaxCont = SC_DOCCOMP_COLUMNS; // 10 columns
+ nMinGood = SC_DOCCOMP_MINGOOD;
+
+ //TODO: additional pass with nMinGood = 0 ????
+
+ }
+ else
+ {
+ nMaxCont = SC_DOCCOMP_ROWS; // 100 rows
+ nMinGood = SC_DOCCOMP_MINGOOD;
+ }
+ bool bUseTotal = bColumns && !pTranslate; // only for the 1st pass
+
+ SCCOLROW nOtherRow = 0;
+ sal_uInt16 nComp;
+ SCCOLROW nThisRow;
+ bool bTotal = false; // hold for several nThisRow
+ SCCOLROW nUnknown = 0;
+ for (nThisRow = 0; nThisRow <= nThisEndRow; nThisRow++)
+ {
+ SCCOLROW nTempOther = nOtherRow;
+ bool bFound = false;
+ sal_uInt16 nBest = SC_DOCCOMP_MAXDIFF;
+ SCCOLROW nMax = std::min( nOtherEndRow, static_cast<SCCOLROW>(( nTempOther + nMaxCont + nUnknown )) );
+ for (SCCOLROW i=nTempOther; i<=nMax && nBest>0; i++) // stop at 0
+ {
+ if (bColumns)
+ nComp = ColDifferences( static_cast<SCCOL>(nThisRow), nThisTab, rOtherDoc, static_cast<SCCOL>(i), nOtherTab, nEndCol, pTranslate );
+ else
+ nComp = RowDifferences( nThisRow, nThisTab, rOtherDoc, i, nOtherTab, static_cast<SCCOL>(nEndCol), pTranslate );
+ if ( nComp < nBest && ( nComp <= nMinGood || bTotal ) )
+ {
+ nTempOther = i;
+ nBest = nComp;
+ bFound = true;
+ }
+ if ( nComp < SC_DOCCOMP_MAXDIFF || bFound )
+ bTotal = false;
+ else if ( i == nTempOther && bUseTotal )
+ bTotal = true; // only at the very top
+ }
+ if ( bFound )
+ {
+ pOtherRows[nThisRow] = nTempOther;
+ nOtherRow = nTempOther + 1;
+ nUnknown = 0;
+ }
+ else
+ {
+ pOtherRows[nThisRow] = SCROW_MAX;
+ ++nUnknown;
+ }
+
+ if (pProgress)
+ pProgress->SetStateOnPercent(nProAdd+static_cast<sal_uLong>(nThisRow));
+ }
+
+ // fill in blocks that don't match
+
+ SCROW nFillStart = 0;
+ SCROW nFillPos = 0;
+ bool bInFill = false;
+ for (nThisRow = 0; nThisRow <= nThisEndRow+1; nThisRow++)
+ {
+ SCROW nThisOther = ( nThisRow <= nThisEndRow ) ? pOtherRows[nThisRow] : (nOtherEndRow+1);
+ if ( ValidRow(nThisOther) )
+ {
+ if ( bInFill )
+ {
+ if ( nThisOther > nFillStart ) // is there something to distribute?
+ {
+ SCROW nDiff1 = nThisOther - nFillStart;
+ SCROW nDiff2 = nThisRow - nFillPos;
+ SCROW nMinDiff = std::min(nDiff1, nDiff2);
+ for (SCROW i=0; i<nMinDiff; i++)
+ pOtherRows[nFillPos+i] = nFillStart+i;
+ }
+
+ bInFill = false;
+ }
+ nFillStart = nThisOther + 1;
+ nFillPos = nThisRow + 1;
+ }
+ else
+ bInFill = true;
+ }
+}
+
+void ScDocument::CompareDocument( ScDocument& rOtherDoc )
+{
+ if (!pChangeTrack)
+ return;
+
+ SCTAB nThisCount = GetTableCount();
+ SCTAB nOtherCount = rOtherDoc.GetTableCount();
+ std::unique_ptr<SCTAB[]> pOtherTabs(new SCTAB[nThisCount]);
+ SCTAB nThisTab;
+
+ // compare tables with identical names
+ OUString aThisName;
+ OUString aOtherName;
+ for (nThisTab=0; nThisTab<nThisCount; nThisTab++)
+ {
+ SCTAB nOtherTab = SCTAB_MAX;
+ if (!IsScenario(nThisTab)) // skip scenarios
+ {
+ GetName( nThisTab, aThisName );
+ for (SCTAB nTemp=0; nTemp<nOtherCount && nOtherTab>MAXTAB; nTemp++)
+ if (!rOtherDoc.IsScenario(nTemp))
+ {
+ rOtherDoc.GetName( nTemp, aOtherName );
+ if ( aThisName == aOtherName )
+ nOtherTab = nTemp;
+ }
+ }
+ pOtherTabs[nThisTab] = nOtherTab;
+ }
+ // fill in, so that un-named tables don't get lost
+ SCTAB nFillStart = 0;
+ SCTAB nFillPos = 0;
+ bool bInFill = false;
+ for (nThisTab = 0; nThisTab <= nThisCount; nThisTab++)
+ {
+ SCTAB nThisOther = ( nThisTab < nThisCount ) ? pOtherTabs[nThisTab] : nOtherCount;
+ if ( ValidTab(nThisOther) )
+ {
+ if ( bInFill )
+ {
+ if ( nThisOther > nFillStart ) // is there something to distribute?
+ {
+ SCTAB nDiff1 = nThisOther - nFillStart;
+ SCTAB nDiff2 = nThisTab - nFillPos;
+ SCTAB nMinDiff = std::min(nDiff1, nDiff2);
+ for (SCTAB i=0; i<nMinDiff; i++)
+ if ( !IsScenario(nFillPos+i) && !rOtherDoc.IsScenario(nFillStart+i) )
+ pOtherTabs[nFillPos+i] = nFillStart+i;
+ }
+
+ bInFill = false;
+ }
+ nFillStart = nThisOther + 1;
+ nFillPos = nThisTab + 1;
+ }
+ else
+ bInFill = true;
+ }
+
+ // compare tables in the original order
+
+ for (nThisTab=0; nThisTab<nThisCount; nThisTab++)
+ {
+ SCTAB nOtherTab = pOtherTabs[nThisTab];
+ if ( ValidTab(nOtherTab) )
+ {
+ SCCOL nThisEndCol = 0;
+ SCROW nThisEndRow = 0;
+ SCCOL nOtherEndCol = 0;
+ SCROW nOtherEndRow = 0;
+ GetCellArea( nThisTab, nThisEndCol, nThisEndRow );
+ rOtherDoc.GetCellArea( nOtherTab, nOtherEndCol, nOtherEndRow );
+ SCCOL nEndCol = std::max(nThisEndCol, nOtherEndCol);
+ SCROW nEndRow = std::max(nThisEndRow, nOtherEndRow);
+ SCCOL nThisCol;
+ SCROW nThisRow;
+ sal_uLong n1,n2; // for AppendDeleteRange
+
+ //TODO: one Progress over all tables ???
+
+ OUString aTabName;
+ GetName( nThisTab, aTabName );
+ OUString aTemplate = ScResId(STR_PROGRESS_COMPARING);
+ sal_Int32 nIndex = 0;
+ OUString aProText = o3tl::getToken(aTemplate, 0, '#', nIndex ) +
+ aTabName +
+ o3tl::getToken(aTemplate, 0, '#', nIndex );
+ ScProgress aProgress( GetDocumentShell(), aProText, 3*nThisEndRow, true ); // 2x FindOrder, 1x here
+ tools::Long nProgressStart = 2*nThisEndRow; // start for here
+
+ std::unique_ptr<SCCOLROW[]> pTempRows(new SCCOLROW[nThisEndRow+1]);
+ std::unique_ptr<SCCOLROW[]> pOtherRows(new SCCOLROW[nThisEndRow+1]);
+ std::unique_ptr<SCCOLROW[]> pOtherCols(new SCCOLROW[nThisEndCol+1]);
+
+ // find inserted/deleted columns/rows:
+ // Two attempts:
+ // 1) compare original rows (pTempRows)
+ // 2) compare original columns (pOtherCols)
+ // with this column order compare rows (pOtherRows)
+
+ //TODO: compare columns twice with different nMinGood ???
+
+ // 1
+ FindOrder( pTempRows.get(), nThisEndRow, nOtherEndRow, false,
+ rOtherDoc, nThisTab, nOtherTab, nEndCol, nullptr, &aProgress, 0 );
+ // 2
+ FindOrder( pOtherCols.get(), nThisEndCol, nOtherEndCol, true,
+ rOtherDoc, nThisTab, nOtherTab, nEndRow, nullptr, nullptr, 0 );
+ FindOrder( pOtherRows.get(), nThisEndRow, nOtherEndRow, false,
+ rOtherDoc, nThisTab, nOtherTab, nThisEndCol,
+ pOtherCols.get(), &aProgress, nThisEndRow );
+
+ sal_uLong nMatch1 = 0; // pTempRows, no columns
+ for (nThisRow = 0; nThisRow<=nThisEndRow; nThisRow++)
+ if (ValidRow(pTempRows[nThisRow]))
+ nMatch1 += SC_DOCCOMP_MAXDIFF -
+ RowDifferences( nThisRow, nThisTab, rOtherDoc, pTempRows[nThisRow],
+ nOtherTab, nEndCol, nullptr );
+
+ sal_uLong nMatch2 = 0; // pOtherRows, pOtherCols
+ for (nThisRow = 0; nThisRow<=nThisEndRow; nThisRow++)
+ if (ValidRow(pOtherRows[nThisRow]))
+ nMatch2 += SC_DOCCOMP_MAXDIFF -
+ RowDifferences( nThisRow, nThisTab, rOtherDoc, pOtherRows[nThisRow],
+ nOtherTab, nThisEndCol, pOtherCols.get() );
+
+ if ( nMatch1 >= nMatch2 ) // without columns ?
+ {
+ // reset columns
+ for (nThisCol = 0; nThisCol<=nThisEndCol; nThisCol++)
+ pOtherCols[nThisCol] = nThisCol;
+
+ // swap row-arrays (they get both deleted anyway)
+ pTempRows.swap(pOtherRows);
+ }
+ else
+ {
+ // remains for pOtherCols, pOtherRows
+ }
+
+ // Generate Change-Actions
+ // 1) columns from the right
+ // 2) rows from below
+ // 3) single cells in normal order
+
+ // Actions for inserted/deleted columns
+
+ SCCOL nLastOtherCol = static_cast<SCCOL>(nOtherEndCol + 1);
+ // nThisEndCol ... 0
+ for ( nThisCol = nThisEndCol+1; nThisCol > 0; )
+ {
+ --nThisCol;
+ SCCOL nOtherCol = static_cast<SCCOL>(pOtherCols[nThisCol]);
+ if ( ValidCol(nOtherCol) && nOtherCol+1 < nLastOtherCol )
+ {
+ // gap -> deleted
+ ScRange aDelRange( nOtherCol+1, 0, nOtherTab,
+ nLastOtherCol-1, MaxRow(), nOtherTab );
+ pChangeTrack->AppendDeleteRange( aDelRange, &rOtherDoc, n1, n2 );
+ }
+ if ( nOtherCol > MaxCol() ) // inserted
+ {
+ // combine
+ if ( nThisCol == nThisEndCol || ValidCol(static_cast<SCCOL>(pOtherCols[nThisCol+1])) )
+ {
+ SCCOL nFirstNew = nThisCol;
+ while ( nFirstNew > 0 && pOtherCols[nFirstNew-1] > MaxCol() )
+ --nFirstNew;
+ SCCOL nDiff = nThisCol - nFirstNew;
+ ScRange aRange( nLastOtherCol, 0, nOtherTab,
+ nLastOtherCol+nDiff, MaxRow(), nOtherTab );
+ pChangeTrack->AppendInsert( aRange );
+ }
+ }
+ else
+ nLastOtherCol = nOtherCol;
+ }
+ if ( nLastOtherCol > 0 ) // deleted at the very top
+ {
+ ScRange aDelRange( 0, 0, nOtherTab,
+ nLastOtherCol-1, MaxRow(), nOtherTab );
+ pChangeTrack->AppendDeleteRange( aDelRange, &rOtherDoc, n1, n2 );
+ }
+
+ // Actions for inserted/deleted rows
+
+ SCROW nLastOtherRow = nOtherEndRow + 1;
+ // nThisEndRow ... 0
+ for ( nThisRow = nThisEndRow+1; nThisRow > 0; )
+ {
+ --nThisRow;
+ SCROW nOtherRow = pOtherRows[nThisRow];
+ if ( ValidRow(nOtherRow) && nOtherRow+1 < nLastOtherRow )
+ {
+ // gap -> deleted
+ ScRange aDelRange( 0, nOtherRow+1, nOtherTab,
+ MaxCol(), nLastOtherRow-1, nOtherTab );
+ pChangeTrack->AppendDeleteRange( aDelRange, &rOtherDoc, n1, n2 );
+ }
+ if ( nOtherRow > MaxRow() ) // inserted
+ {
+ // combine
+ if ( nThisRow == nThisEndRow || ValidRow(pOtherRows[nThisRow+1]) )
+ {
+ SCROW nFirstNew = nThisRow;
+ while ( nFirstNew > 0 && pOtherRows[nFirstNew-1] > MaxRow() )
+ --nFirstNew;
+ SCROW nDiff = nThisRow - nFirstNew;
+ ScRange aRange( 0, nLastOtherRow, nOtherTab,
+ MaxCol(), nLastOtherRow+nDiff, nOtherTab );
+ pChangeTrack->AppendInsert( aRange );
+ }
+ }
+ else
+ nLastOtherRow = nOtherRow;
+ }
+ if ( nLastOtherRow > 0 ) // deleted at the very top
+ {
+ ScRange aDelRange( 0, 0, nOtherTab,
+ MaxCol(), nLastOtherRow-1, nOtherTab );
+ pChangeTrack->AppendDeleteRange( aDelRange, &rOtherDoc, n1, n2 );
+ }
+
+ // walk rows to find single cells
+
+ for (nThisRow = 0; nThisRow <= nThisEndRow; nThisRow++)
+ {
+ SCROW nOtherRow = pOtherRows[nThisRow];
+ for (nThisCol = 0; nThisCol <= nThisEndCol; nThisCol++)
+ {
+ SCCOL nOtherCol = static_cast<SCCOL>(pOtherCols[nThisCol]);
+ ScAddress aThisPos( nThisCol, nThisRow, nThisTab );
+ ScCellValue aThisCell;
+ aThisCell.assign(*this, aThisPos);
+ ScCellValue aOtherCell; // start empty
+ if ( ValidCol(nOtherCol) && ValidRow(nOtherRow) )
+ {
+ ScAddress aOtherPos( nOtherCol, nOtherRow, nOtherTab );
+ aOtherCell.assign(rOtherDoc, aOtherPos);
+ }
+
+ if (!aThisCell.equalsWithoutFormat(aOtherCell))
+ {
+ ScRange aRange( aThisPos );
+ ScChangeActionContent* pAction = new ScChangeActionContent( aRange );
+ pAction->SetOldValue(aOtherCell, &rOtherDoc, this);
+ pAction->SetNewValue(aThisCell, this);
+ pChangeTrack->Append( pAction );
+ }
+ }
+ aProgress.SetStateOnPercent(nProgressStart+nThisRow);
+ }
+ }
+ }
+}
+
+sal_Unicode ScDocument::GetSheetSeparator() const
+{
+ const ScCompiler::Convention* pConv = ScCompiler::GetRefConvention(
+ FormulaGrammar::extractRefConvention( GetGrammar()));
+ assert(pConv);
+ return pConv ? pConv->getSpecialSymbol( ScCompiler::Convention::SHEET_SEPARATOR) : '.';
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sc/source/core/data/documen5.cxx b/sc/source/core/data/documen5.cxx
new file mode 100644
index 000000000..c4f76433b
--- /dev/null
+++ b/sc/source/core/data/documen5.cxx
@@ -0,0 +1,657 @@
+/* -*- 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 <com/sun/star/util/XModifiable.hpp>
+#include <com/sun/star/chart/ChartDataRowSource.hpp>
+#include <com/sun/star/chart2/XChartDocument.hpp>
+#include <com/sun/star/chart2/data/XDataProvider.hpp>
+#include <com/sun/star/chart2/data/XDataReceiver.hpp>
+#include <com/sun/star/embed/XEmbeddedObject.hpp>
+
+#include <sfx2/objsh.hxx>
+#include <svx/svditer.hxx>
+#include <svx/svdoole2.hxx>
+#include <svtools/embedhlp.hxx>
+
+#include <document.hxx>
+#include <table.hxx>
+#include <drwlayer.hxx>
+#include <chartlis.hxx>
+#include <chartlock.hxx>
+#include <refupdat.hxx>
+
+#include <miscuno.hxx>
+#include <chart2uno.hxx>
+#include <charthelper.hxx>
+
+using namespace ::com::sun::star;
+
+static void lcl_GetChartParameters( const uno::Reference< chart2::XChartDocument >& xChartDoc,
+ OUString& rRanges, chart::ChartDataRowSource& rDataRowSource,
+ bool& rHasCategories, bool& rFirstCellAsLabel )
+{
+ rHasCategories = rFirstCellAsLabel = false; // default if not in sequence
+
+ uno::Reference< chart2::data::XDataReceiver > xReceiver( xChartDoc, uno::UNO_QUERY );
+
+ uno::Reference< chart2::data::XDataSource > xDataSource = xReceiver->getUsedData();
+ uno::Reference< chart2::data::XDataProvider > xProvider = xChartDoc->getDataProvider();
+
+ if ( !xProvider.is() )
+ return;
+
+ const uno::Sequence< beans::PropertyValue > aArgs( xProvider->detectArguments( xDataSource ) );
+
+ for (const beans::PropertyValue& rProp : aArgs)
+ {
+ OUString aPropName(rProp.Name);
+
+ if ( aPropName == "CellRangeRepresentation" )
+ rProp.Value >>= rRanges;
+ else if ( aPropName == "DataRowSource" )
+ rDataRowSource = static_cast<chart::ChartDataRowSource>(ScUnoHelpFunctions::GetEnumFromAny( rProp.Value ));
+ else if ( aPropName == "HasCategories" )
+ rHasCategories = ScUnoHelpFunctions::GetBoolFromAny( rProp.Value );
+ else if ( aPropName == "FirstCellAsLabel" )
+ rFirstCellAsLabel = ScUnoHelpFunctions::GetBoolFromAny( rProp.Value );
+ }
+}
+
+static void lcl_SetChartParameters( const uno::Reference< chart2::data::XDataReceiver >& xReceiver,
+ const OUString& rRanges, chart::ChartDataRowSource eDataRowSource,
+ bool bHasCategories, bool bFirstCellAsLabel )
+{
+ if ( !xReceiver.is() )
+ return;
+
+ uno::Sequence< beans::PropertyValue > aArgs{
+ beans::PropertyValue(
+ "CellRangeRepresentation", -1,
+ uno::Any( rRanges ), beans::PropertyState_DIRECT_VALUE ),
+ beans::PropertyValue(
+ "HasCategories", -1,
+ uno::Any( bHasCategories ), beans::PropertyState_DIRECT_VALUE ),
+ beans::PropertyValue(
+ "FirstCellAsLabel", -1,
+ uno::Any( bFirstCellAsLabel ), beans::PropertyState_DIRECT_VALUE ),
+ beans::PropertyValue(
+ "DataRowSource", -1,
+ uno::Any( eDataRowSource ), beans::PropertyState_DIRECT_VALUE )
+ };
+ xReceiver->setArguments( aArgs );
+}
+
+bool ScDocument::HasChartAtPoint( SCTAB nTab, const Point& rPos, OUString& rName )
+{
+ if (mpDrawLayer && nTab < static_cast<SCTAB>(maTabs.size()) && maTabs[nTab])
+ {
+ SdrPage* pPage = mpDrawLayer->GetPage(static_cast<sal_uInt16>(nTab));
+ OSL_ENSURE(pPage,"Page ?");
+
+ SdrObjListIter aIter( pPage, SdrIterMode::DeepNoGroups );
+ SdrObject* pObject = aIter.Next();
+ while (pObject)
+ {
+ if ( pObject->GetObjIdentifier() == SdrObjKind::OLE2 &&
+ pObject->GetCurrentBoundRect().Contains(rPos) )
+ {
+ // also Chart-Objects that are not in the Collection
+
+ if (IsChart(pObject))
+ {
+ rName = static_cast<SdrOle2Obj*>(pObject)->GetPersistName();
+ return true;
+ }
+ }
+ pObject = aIter.Next();
+ }
+ }
+
+ rName.clear();
+ return false; // nothing found
+}
+
+void ScDocument::UpdateChartArea( const OUString& rChartName,
+ const ScRange& rNewArea, bool bColHeaders, bool bRowHeaders,
+ bool bAdd )
+{
+ ScRangeListRef aRLR( new ScRangeList(rNewArea) );
+ UpdateChartArea( rChartName, aRLR, bColHeaders, bRowHeaders, bAdd );
+}
+
+uno::Reference< chart2::XChartDocument > ScDocument::GetChartByName( std::u16string_view rChartName )
+{
+ uno::Reference< chart2::XChartDocument > xReturn;
+
+ if (mpDrawLayer)
+ {
+ sal_uInt16 nCount = mpDrawLayer->GetPageCount();
+ SCTAB nSize = static_cast<SCTAB>(maTabs.size());
+ for (sal_uInt16 nTab=0; nTab<nCount && nTab < nSize; nTab++)
+ {
+ SdrPage* pPage = mpDrawLayer->GetPage(nTab);
+ OSL_ENSURE(pPage,"Page ?");
+
+ SdrObjListIter aIter( pPage, SdrIterMode::DeepNoGroups );
+ SdrObject* pObject = aIter.Next();
+ while (pObject)
+ {
+ if ( pObject->GetObjIdentifier() == SdrObjKind::OLE2 &&
+ static_cast<SdrOle2Obj*>(pObject)->GetPersistName() == rChartName )
+ {
+ xReturn.set( ScChartHelper::GetChartFromSdrObject( pObject ) );
+ return xReturn;
+ }
+ pObject = aIter.Next();
+ }
+ }
+ }
+ return xReturn;
+}
+
+void ScDocument::GetChartRanges( std::u16string_view rChartName, ::std::vector< ScRangeList >& rRangesVector, const ScDocument& rSheetNameDoc )
+{
+ rRangesVector.clear();
+ uno::Reference< chart2::XChartDocument > xChartDoc( GetChartByName( rChartName ) );
+ if ( xChartDoc.is() )
+ {
+ std::vector< OUString > aRangeStrings;
+ ScChartHelper::GetChartRanges( xChartDoc, aRangeStrings );
+ for(const OUString & aRangeString : aRangeStrings)
+ {
+ ScRangeList aRanges;
+ aRanges.Parse( aRangeString, rSheetNameDoc, rSheetNameDoc.GetAddressConvention() );
+ rRangesVector.push_back(aRanges);
+ }
+ }
+}
+
+void ScDocument::SetChartRanges( std::u16string_view rChartName, const ::std::vector< ScRangeList >& rRangesVector )
+{
+ uno::Reference< chart2::XChartDocument > xChartDoc( GetChartByName( rChartName ) );
+ if ( !xChartDoc.is() )
+ return;
+
+ sal_Int32 nCount = static_cast<sal_Int32>( rRangesVector.size() );
+ uno::Sequence< OUString > aRangeStrings(nCount);
+ auto aRangeStringsRange = asNonConstRange(aRangeStrings);
+ for( sal_Int32 nN=0; nN<nCount; nN++ )
+ {
+ ScRangeList aScRangeList( rRangesVector[nN] );
+ OUString sRangeStr;
+ aScRangeList.Format( sRangeStr, ScRefFlags::RANGE_ABS_3D, *this, GetAddressConvention() );
+ aRangeStringsRange[nN]=sRangeStr;
+ }
+ ScChartHelper::SetChartRanges( xChartDoc, aRangeStrings );
+}
+
+void ScDocument::GetOldChartParameters( std::u16string_view rName,
+ ScRangeList& rRanges, bool& rColHeaders, bool& rRowHeaders )
+{
+ // used for undo of changing chart source area
+
+ if (!mpDrawLayer)
+ return;
+
+ sal_uInt16 nCount = mpDrawLayer->GetPageCount();
+ for (sal_uInt16 nTab=0; nTab<nCount && nTab < static_cast<SCTAB>(maTabs.size()); nTab++)
+ {
+ SdrPage* pPage = mpDrawLayer->GetPage(nTab);
+ OSL_ENSURE(pPage,"Page ?");
+
+ SdrObjListIter aIter( pPage, SdrIterMode::DeepNoGroups );
+ SdrObject* pObject = aIter.Next();
+ while (pObject)
+ {
+ if ( pObject->GetObjIdentifier() == SdrObjKind::OLE2 &&
+ static_cast<SdrOle2Obj*>(pObject)->GetPersistName() == rName )
+ {
+ uno::Reference< chart2::XChartDocument > xChartDoc( ScChartHelper::GetChartFromSdrObject( pObject ) );
+ if ( xChartDoc.is() )
+ {
+ chart::ChartDataRowSource eDataRowSource = chart::ChartDataRowSource_COLUMNS;
+ bool bHasCategories = false;
+ bool bFirstCellAsLabel = false;
+ OUString aRangesStr;
+ lcl_GetChartParameters( xChartDoc, aRangesStr, eDataRowSource, bHasCategories, bFirstCellAsLabel );
+
+ rRanges.Parse( aRangesStr, *this, GetAddressConvention());
+ if ( eDataRowSource == chart::ChartDataRowSource_COLUMNS )
+ {
+ rRowHeaders = bHasCategories;
+ rColHeaders = bFirstCellAsLabel;
+ }
+ else
+ {
+ rColHeaders = bHasCategories;
+ rRowHeaders = bFirstCellAsLabel;
+ }
+ }
+ return;
+ }
+ pObject = aIter.Next();
+ }
+ }
+}
+
+void ScDocument::UpdateChartArea( const OUString& rChartName,
+ const ScRangeListRef& rNewList, bool bColHeaders, bool bRowHeaders,
+ bool bAdd )
+{
+ if (!mpDrawLayer)
+ return;
+
+ for (SCTAB nTab=0; nTab< static_cast<SCTAB>(maTabs.size()) && maTabs[nTab]; nTab++)
+ {
+ SdrPage* pPage = mpDrawLayer->GetPage(static_cast<sal_uInt16>(nTab));
+ OSL_ENSURE(pPage,"Page ?");
+
+ SdrObjListIter aIter( pPage, SdrIterMode::DeepNoGroups );
+ SdrObject* pObject = aIter.Next();
+ while (pObject)
+ {
+ if ( pObject->GetObjIdentifier() == SdrObjKind::OLE2 &&
+ static_cast<SdrOle2Obj*>(pObject)->GetPersistName() == rChartName )
+ {
+ uno::Reference< chart2::XChartDocument > xChartDoc( ScChartHelper::GetChartFromSdrObject( pObject ) );
+ uno::Reference< chart2::data::XDataReceiver > xReceiver( xChartDoc, uno::UNO_QUERY );
+ if ( xChartDoc.is() && xReceiver.is() )
+ {
+ ScRangeListRef aNewRanges;
+ chart::ChartDataRowSource eDataRowSource = chart::ChartDataRowSource_COLUMNS;
+ bool bHasCategories = false;
+ bool bFirstCellAsLabel = false;
+ OUString aRangesStr;
+ lcl_GetChartParameters( xChartDoc, aRangesStr, eDataRowSource, bHasCategories, bFirstCellAsLabel );
+
+ bool bInternalData = xChartDoc->hasInternalDataProvider();
+
+ if ( bAdd && !bInternalData )
+ {
+ // append to old ranges, keep other settings
+
+ aNewRanges = new ScRangeList;
+ aNewRanges->Parse( aRangesStr, *this, GetAddressConvention());
+ aNewRanges->insert( aNewRanges->begin(), rNewList->begin(), rNewList->end() );
+ }
+ else
+ {
+ // directly use new ranges (only eDataRowSource is used from old settings)
+
+ if ( eDataRowSource == chart::ChartDataRowSource_COLUMNS )
+ {
+ bHasCategories = bRowHeaders;
+ bFirstCellAsLabel = bColHeaders;
+ }
+ else
+ {
+ bHasCategories = bColHeaders;
+ bFirstCellAsLabel = bRowHeaders;
+ }
+ aNewRanges = rNewList;
+ }
+
+ if ( bInternalData && mpShell )
+ {
+ // Calc -> DataProvider
+ uno::Reference< chart2::data::XDataProvider > xDataProvider = new ScChart2DataProvider( this );
+ xReceiver->attachDataProvider( xDataProvider );
+ uno::Reference< util::XNumberFormatsSupplier > xNumberFormatsSupplier(
+ mpShell->GetModel(), uno::UNO_QUERY );
+ xReceiver->attachNumberFormatsSupplier( xNumberFormatsSupplier );
+ }
+
+ OUString sRangeStr;
+ aNewRanges->Format( sRangeStr, ScRefFlags::RANGE_ABS_3D, *this, GetAddressConvention() );
+
+ lcl_SetChartParameters( xReceiver, sRangeStr, eDataRowSource, bHasCategories, bFirstCellAsLabel );
+
+ pChartListenerCollection->ChangeListening( rChartName, aNewRanges );
+
+ return; // do not search anymore
+ }
+ }
+ pObject = aIter.Next();
+ }
+ }
+}
+
+void ScDocument::UpdateChart( const OUString& rChartName )
+{
+ if (!mpDrawLayer || bInDtorClear)
+ return;
+ uno::Reference< chart2::XChartDocument > xChartDoc( GetChartByName( rChartName ) );
+ if (xChartDoc && (!mpShell || mpShell->IsEnableSetModified()))
+ {
+ try
+ {
+ uno::Reference< util::XModifiable > xModif( xChartDoc, uno::UNO_QUERY_THROW );
+ if (apTemporaryChartLock)
+ apTemporaryChartLock->AlsoLockThisChart( uno::Reference< frame::XModel >( xModif, uno::UNO_QUERY ) );
+ xModif->setModified( true );
+ }
+ catch ( uno::Exception& )
+ {
+ }
+ }
+
+ // After the update, chart keeps track of its own data source ranges,
+ // the listener doesn't need to listen anymore, except the chart has
+ // an internal data provider.
+ if ( !( xChartDoc.is() && xChartDoc->hasInternalDataProvider() ) && pChartListenerCollection )
+ {
+ pChartListenerCollection->ChangeListening( rChartName, new ScRangeList );
+ }
+}
+
+void ScDocument::RestoreChartListener( const OUString& rName )
+{
+ if (!pChartListenerCollection)
+ return;
+
+ // Read the data ranges from the chart object, and start listening to those ranges again
+ // (called when a chart is saved, because then it might be swapped out and stop listening itself).
+
+ uno::Reference< embed::XEmbeddedObject > xObject = FindOleObjectByName( rName );
+ if ( !xObject.is() )
+ return;
+
+ uno::Reference< util::XCloseable > xComponent = xObject->getComponent();
+ uno::Reference< chart2::XChartDocument > xChartDoc( xComponent, uno::UNO_QUERY );
+ uno::Reference< chart2::data::XDataReceiver > xReceiver( xComponent, uno::UNO_QUERY );
+ if ( !xChartDoc.is() || !xReceiver.is() || xChartDoc->hasInternalDataProvider() )
+ return;
+
+ const uno::Sequence<OUString> aRepresentations( xReceiver->getUsedRangeRepresentations() );
+ ScRangeListRef aRanges = new ScRangeList;
+ for ( const auto& rRepresentation : aRepresentations )
+ {
+ ScRange aRange;
+ ScAddress::Details aDetails(GetAddressConvention(), 0, 0);
+ if ( aRange.ParseAny( rRepresentation, *this, aDetails ) & ScRefFlags::VALID )
+ aRanges->push_back( aRange );
+ }
+
+ pChartListenerCollection->ChangeListening( rName, aRanges );
+}
+
+void ScDocument::UpdateChartRef( UpdateRefMode eUpdateRefMode,
+ SCCOL nCol1, SCROW nRow1, SCTAB nTab1,
+ SCCOL nCol2, SCROW nRow2, SCTAB nTab2,
+ SCCOL nDx, SCROW nDy, SCTAB nDz )
+{
+ if (!mpDrawLayer)
+ return;
+
+ ScChartListenerCollection::ListenersType& rListeners = pChartListenerCollection->getListeners();
+ for (auto const& it : rListeners)
+ {
+ ScChartListener *const pChartListener = it.second.get();
+ ScRangeListRef aRLR( pChartListener->GetRangeList() );
+ ScRangeListRef aNewRLR( new ScRangeList );
+ bool bChanged = false;
+ bool bDataChanged = false;
+ for ( size_t i = 0, nListSize = aRLR->size(); i < nListSize; ++i )
+ {
+ ScRange& rRange = (*aRLR)[i];
+ SCCOL theCol1 = rRange.aStart.Col();
+ SCROW theRow1 = rRange.aStart.Row();
+ SCTAB theTab1 = rRange.aStart.Tab();
+ SCCOL theCol2 = rRange.aEnd.Col();
+ SCROW theRow2 = rRange.aEnd.Row();
+ SCTAB theTab2 = rRange.aEnd.Tab();
+ ScRefUpdateRes eRes = ScRefUpdate::Update(
+ this, eUpdateRefMode,
+ nCol1,nRow1,nTab1, nCol2,nRow2,nTab2,
+ nDx,nDy,nDz,
+ theCol1,theRow1,theTab1,
+ theCol2,theRow2,theTab2 );
+ if ( eRes != UR_NOTHING )
+ {
+ bChanged = true;
+ aNewRLR->push_back( ScRange(
+ theCol1, theRow1, theTab1,
+ theCol2, theRow2, theTab2 ));
+ if ( eUpdateRefMode == URM_INSDEL
+ && !bDataChanged
+ && (eRes == UR_INVALID ||
+ ((rRange.aEnd.Col() - rRange.aStart.Col()
+ != theCol2 - theCol1)
+ || (rRange.aEnd.Row() - rRange.aStart.Row()
+ != theRow2 - theRow1)
+ || (rRange.aEnd.Tab() - rRange.aStart.Tab()
+ != theTab2 - theTab1))) )
+ {
+ bDataChanged = true;
+ }
+ }
+ else
+ aNewRLR->push_back( rRange );
+ }
+ if ( bChanged )
+ {
+ // Force the chart to be loaded now, so it registers itself for UNO events.
+ // UNO broadcasts are done after UpdateChartRef, so the chart will get this
+ // reference change.
+
+ uno::Reference<embed::XEmbeddedObject> xIPObj =
+ FindOleObjectByName(pChartListener->GetName());
+
+ svt::EmbeddedObjectRef::TryRunningState( xIPObj );
+
+ // After the change, chart keeps track of its own data source ranges,
+ // the listener doesn't need to listen anymore, except the chart has
+ // an internal data provider.
+ bool bInternalDataProvider = false;
+ if ( xIPObj.is() )
+ {
+ try
+ {
+ uno::Reference< chart2::XChartDocument > xChartDoc( xIPObj->getComponent(), uno::UNO_QUERY_THROW );
+ bInternalDataProvider = xChartDoc->hasInternalDataProvider();
+ }
+ catch ( uno::Exception& )
+ {
+ }
+ }
+ if ( bInternalDataProvider )
+ {
+ pChartListener->ChangeListening( aNewRLR, bDataChanged );
+ }
+ else
+ {
+ pChartListener->ChangeListening( new ScRangeList, bDataChanged );
+ }
+ }
+ }
+}
+
+void ScDocument::SetChartRangeList( std::u16string_view rChartName,
+ const ScRangeListRef& rNewRangeListRef )
+{
+ // called from ChartListener
+
+ if (!mpDrawLayer)
+ return;
+
+ for (SCTAB nTab=0; nTab< static_cast<SCTAB>(maTabs.size()) && maTabs[nTab]; nTab++)
+ {
+ SdrPage* pPage = mpDrawLayer->GetPage(static_cast<sal_uInt16>(nTab));
+ OSL_ENSURE(pPage,"Page ?");
+
+ SdrObjListIter aIter( pPage, SdrIterMode::DeepNoGroups );
+ SdrObject* pObject = aIter.Next();
+ while (pObject)
+ {
+ if ( pObject->GetObjIdentifier() == SdrObjKind::OLE2 &&
+ static_cast<SdrOle2Obj*>(pObject)->GetPersistName() == rChartName )
+ {
+ uno::Reference< chart2::XChartDocument > xChartDoc( ScChartHelper::GetChartFromSdrObject( pObject ) );
+ uno::Reference< chart2::data::XDataReceiver > xReceiver( xChartDoc, uno::UNO_QUERY );
+ if ( xChartDoc.is() && xReceiver.is() )
+ {
+ chart::ChartDataRowSource eDataRowSource = chart::ChartDataRowSource_COLUMNS;
+ bool bHasCategories = false;
+ bool bFirstCellAsLabel = false;
+ OUString aRangesStr;
+ lcl_GetChartParameters( xChartDoc, aRangesStr, eDataRowSource, bHasCategories, bFirstCellAsLabel );
+
+ OUString sRangeStr;
+ rNewRangeListRef->Format( sRangeStr, ScRefFlags::RANGE_ABS_3D, *this, GetAddressConvention() );
+
+ lcl_SetChartParameters( xReceiver, sRangeStr, eDataRowSource, bHasCategories, bFirstCellAsLabel );
+
+ // don't modify pChartListenerCollection here, called from there
+ return;
+ }
+ }
+ pObject = aIter.Next();
+ }
+ }
+}
+
+bool ScDocument::HasData( SCCOL nCol, SCROW nRow, SCTAB nTab )
+{
+ if ( ValidTab(nTab) && nTab < static_cast<SCTAB>(maTabs.size()) && maTabs[nTab]
+ && nCol < maTabs[nTab]->GetAllocatedColumnsCount())
+ return maTabs[nTab]->HasData( nCol, nRow );
+ else
+ return false;
+}
+
+uno::Reference< embed::XEmbeddedObject >
+ ScDocument::FindOleObjectByName( std::u16string_view rName )
+{
+ if (!mpDrawLayer)
+ return uno::Reference< embed::XEmbeddedObject >();
+
+ // take the pages here from Draw-Layer, as they might not match with the tables
+ // (e.g. delete Redo of table; Draw-Redo happens before DeleteTab)
+
+ sal_uInt16 nCount = mpDrawLayer->GetPageCount();
+ for (sal_uInt16 nTab=0; nTab<nCount; nTab++)
+ {
+ SdrPage* pPage = mpDrawLayer->GetPage(nTab);
+ OSL_ENSURE(pPage,"Page ?");
+
+ SdrObjListIter aIter( pPage, SdrIterMode::DeepNoGroups );
+ SdrObject* pObject = aIter.Next();
+ while (pObject)
+ {
+ if ( pObject->GetObjIdentifier() == SdrObjKind::OLE2 )
+ {
+ SdrOle2Obj * pOleObject ( dynamic_cast< SdrOle2Obj * >( pObject ));
+ if( pOleObject &&
+ pOleObject->GetPersistName() == rName )
+ {
+ return pOleObject->GetObjRef();
+ }
+ }
+ pObject = aIter.Next();
+ }
+ }
+
+ return uno::Reference< embed::XEmbeddedObject >();
+}
+
+void ScDocument::UpdateChartListenerCollection()
+{
+ assert(pChartListenerCollection);
+
+ bChartListenerCollectionNeedsUpdate = false;
+ if (!mpDrawLayer)
+ return;
+
+ for (SCTAB nTab=0; nTab< static_cast<SCTAB>(maTabs.size()); nTab++)
+ {
+ if (!maTabs[nTab])
+ continue;
+
+ SdrPage* pPage = mpDrawLayer->GetPage(static_cast<sal_uInt16>(nTab));
+ OSL_ENSURE(pPage,"Page ?");
+
+ if (!pPage)
+ continue;
+
+ SdrObjListIter aIter( pPage, SdrIterMode::DeepNoGroups );
+ ScChartListenerCollection::StringSetType& rNonOleObjects =
+ pChartListenerCollection->getNonOleObjectNames();
+
+ for (SdrObject* pObject = aIter.Next(); pObject; pObject = aIter.Next())
+ {
+ if ( pObject->GetObjIdentifier() != SdrObjKind::OLE2 )
+ continue;
+
+ OUString aObjName = static_cast<SdrOle2Obj*>(pObject)->GetPersistName();
+ ScChartListener* pListener = pChartListenerCollection->findByName(aObjName);
+
+ if (pListener)
+ pListener->SetUsed(true);
+ else if (rNonOleObjects.count(aObjName) > 0)
+ {
+ // non-chart OLE object -> don't touch
+ }
+ else
+ {
+ uno::Reference< embed::XEmbeddedObject > xIPObj = static_cast<SdrOle2Obj*>(pObject)->GetObjRef();
+ OSL_ENSURE( xIPObj.is(), "No embedded object is given!");
+ uno::Reference< css::chart2::data::XDataReceiver > xReceiver;
+ if( xIPObj.is())
+ xReceiver.set( xIPObj->getComponent(), uno::UNO_QUERY );
+
+ // if the object is a chart2::XDataReceiver, we must attach as XDataProvider
+ if( xReceiver.is() &&
+ !PastingDrawFromOtherDoc())
+ {
+ // NOTE: this currently does not work as we are
+ // unable to set the data. So a chart from the
+ // same document is treated like a chart with
+ // own data for the time being.
+
+ // data provider
+ // number formats supplier
+
+ // data ?
+ // how to set?? Defined in XML-file, which is already loaded!!!
+ // => we have to do this stuff here, BEFORE the chart is actually loaded
+ }
+
+ // put into list of other ole objects, so the object doesn't have to
+ // be swapped in the next time UpdateChartListenerCollection is called
+ //TODO: remove names when objects are no longer there?
+ // (object names aren't used again before reloading the document)
+
+ rNonOleObjects.insert(aObjName);
+ }
+ }
+ }
+ // delete all that are not set SetUsed
+ pChartListenerCollection->FreeUnused();
+}
+
+void ScDocument::AddOLEObjectToCollection(const OUString& rName)
+{
+ assert(pChartListenerCollection);
+ ScChartListenerCollection::StringSetType& rNonOleObjects =
+ pChartListenerCollection->getNonOleObjectNames();
+
+ rNonOleObjects.insert(rName);
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sc/source/core/data/documen6.cxx b/sc/source/core/data/documen6.cxx
new file mode 100644
index 000000000..49d433ffe
--- /dev/null
+++ b/sc/source/core/data/documen6.cxx
@@ -0,0 +1,210 @@
+/* -*- 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 <scitems.hxx>
+
+#include <com/sun/star/i18n/BreakIterator.hpp>
+#include <com/sun/star/i18n/ScriptType.hpp>
+#include <comphelper/processfactory.hxx>
+
+#include <document.hxx>
+#include <cellform.hxx>
+#include <patattr.hxx>
+#include <scrdata.hxx>
+#include <poolhelp.hxx>
+#include <attrib.hxx>
+#include <columnspanset.hxx>
+#include <table.hxx>
+
+using namespace com::sun::star;
+
+// this file is compiled with exceptions enabled
+// put functions here that need exceptions!
+
+const uno::Reference< i18n::XBreakIterator >& ScDocument::GetBreakIterator()
+{
+ if ( !pScriptTypeData )
+ pScriptTypeData.reset( new ScScriptTypeData );
+ if ( !pScriptTypeData->xBreakIter.is() )
+ {
+ pScriptTypeData->xBreakIter = i18n::BreakIterator::create( comphelper::getProcessComponentContext() );
+ }
+ return pScriptTypeData->xBreakIter;
+}
+
+bool ScDocument::HasStringWeakCharacters( const OUString& rString )
+{
+ if (!rString.isEmpty())
+ {
+ uno::Reference<i18n::XBreakIterator> xBreakIter = GetBreakIterator();
+ if ( xBreakIter.is() )
+ {
+ sal_Int32 nLen = rString.getLength();
+
+ sal_Int32 nPos = 0;
+ do
+ {
+ sal_Int16 nType = xBreakIter->getScriptType( rString, nPos );
+ if ( nType == i18n::ScriptType::WEAK )
+ return true; // found
+
+ nPos = xBreakIter->endOfScript( rString, nPos, nType );
+ }
+ while ( nPos >= 0 && nPos < nLen );
+ }
+ }
+
+ return false; // none found
+}
+
+SvtScriptType ScDocument::GetStringScriptType( const OUString& rString )
+{
+ SvtScriptType nRet = SvtScriptType::NONE;
+ if (!rString.isEmpty())
+ {
+ uno::Reference<i18n::XBreakIterator> xBreakIter = GetBreakIterator();
+ if ( xBreakIter.is() )
+ {
+ sal_Int32 nLen = rString.getLength();
+
+ sal_Int32 nPos = 0;
+ do
+ {
+ sal_Int16 nType = xBreakIter->getScriptType( rString, nPos );
+ switch ( nType )
+ {
+ case i18n::ScriptType::LATIN:
+ nRet |= SvtScriptType::LATIN;
+ break;
+ case i18n::ScriptType::ASIAN:
+ nRet |= SvtScriptType::ASIAN;
+ break;
+ case i18n::ScriptType::COMPLEX:
+ nRet |= SvtScriptType::COMPLEX;
+ break;
+ // WEAK is ignored
+ }
+ nPos = xBreakIter->endOfScript( rString, nPos, nType );
+ }
+ while ( nPos >= 0 && nPos < nLen );
+ }
+ }
+ return nRet;
+}
+
+SvtScriptType ScDocument::GetCellScriptType( const ScAddress& rPos, sal_uInt32 nNumberFormat,
+ const ScRefCellValue* pCell )
+{
+ SvtScriptType nStored = GetScriptType(rPos);
+ if ( nStored != SvtScriptType::UNKNOWN ) // stored value valid?
+ return nStored; // use stored value
+
+ const Color* pColor;
+ OUString aStr;
+ if( pCell )
+ aStr = ScCellFormat::GetString(*pCell, nNumberFormat, &pColor, *mxPoolHelper->GetFormTable(), *this);
+ else
+ aStr = ScCellFormat::GetString(*this, rPos, nNumberFormat, &pColor, *mxPoolHelper->GetFormTable());
+
+ SvtScriptType nRet = GetStringScriptType( aStr );
+
+ SetScriptType(rPos, nRet); // store for later calls
+
+ return nRet;
+}
+
+SvtScriptType ScDocument::GetScriptType( SCCOL nCol, SCROW nRow, SCTAB nTab, const ScRefCellValue* pCell )
+{
+ // if script type is set, don't have to get number formats
+
+ ScAddress aPos(nCol, nRow, nTab);
+ SvtScriptType nStored = GetScriptType(aPos);
+ if ( nStored != SvtScriptType::UNKNOWN ) // stored value valid?
+ return nStored; // use stored value
+
+ // include number formats from conditional formatting
+
+ const ScPatternAttr* pPattern = GetPattern( nCol, nRow, nTab );
+ if (!pPattern) return SvtScriptType::NONE;
+ const SfxItemSet* pCondSet = nullptr;
+ if ( !pPattern->GetItem(ATTR_CONDITIONAL).GetCondFormatData().empty() )
+ pCondSet = GetCondResult( nCol, nRow, nTab );
+
+ sal_uInt32 nFormat = pPattern->GetNumberFormat( mxPoolHelper->GetFormTable(), pCondSet );
+
+ return GetCellScriptType(aPos, nFormat, pCell);
+}
+
+namespace {
+
+class ScriptTypeAggregator : public sc::ColumnSpanSet::Action
+{
+ ScDocument& mrDoc;
+ sc::ColumnBlockPosition maBlockPos;
+ SvtScriptType mnScriptType;
+
+public:
+ explicit ScriptTypeAggregator(ScDocument& rDoc) : mrDoc(rDoc), mnScriptType(SvtScriptType::NONE) {}
+
+ virtual void startColumn(SCTAB nTab, SCCOL nCol) override
+ {
+ mrDoc.InitColumnBlockPosition(maBlockPos, nTab, nCol);
+ }
+
+ virtual void execute(const ScAddress& rPos, SCROW nLength, bool bVal) override
+ {
+ if (!bVal)
+ return;
+
+ mnScriptType |= mrDoc.GetRangeScriptType(maBlockPos, rPos, nLength);
+ };
+
+ SvtScriptType getScriptType() const { return mnScriptType; }
+};
+
+}
+
+SvtScriptType ScDocument::GetRangeScriptType(
+ sc::ColumnBlockPosition& rBlockPos, const ScAddress& rPos, SCROW nLength )
+{
+ if (!TableExists(rPos.Tab()))
+ return SvtScriptType::NONE;
+
+ return maTabs[rPos.Tab()]->GetRangeScriptType(rBlockPos, rPos.Col(), rPos.Row(), rPos.Row()+nLength-1);
+}
+
+SvtScriptType ScDocument::GetRangeScriptType( const ScRangeList& rRanges )
+{
+ sc::ColumnSpanSet aSet;
+ for (size_t i = 0, n = rRanges.size(); i < n; ++i)
+ {
+ const ScRange& rRange = rRanges[i];
+ SCTAB nTab = rRange.aStart.Tab();
+ SCROW nRow1 = rRange.aStart.Row();
+ SCROW nRow2 = rRange.aEnd.Row();
+ for (SCCOL nCol = rRange.aStart.Col(); nCol <= rRange.aEnd.Col(); ++nCol)
+ aSet.set(*this, nTab, nCol, nRow1, nRow2, true);
+ }
+
+ ScriptTypeAggregator aAction(*this);
+ aSet.executeAction(*this, aAction);
+ return aAction.getScriptType();
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sc/source/core/data/documen7.cxx b/sc/source/core/data/documen7.cxx
new file mode 100644
index 000000000..61f6b68f0
--- /dev/null
+++ b/sc/source/core/data/documen7.cxx
@@ -0,0 +1,621 @@
+/* -*- 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 <sal/log.hxx>
+#include <osl/diagnose.h>
+
+#include <document.hxx>
+#include <brdcst.hxx>
+#include <bcaslot.hxx>
+#include <formulacell.hxx>
+#include <table.hxx>
+#include <progress.hxx>
+#include <scmod.hxx>
+#include <inputopt.hxx>
+#include <sheetevents.hxx>
+#include <tokenarray.hxx>
+#include <listenercontext.hxx>
+
+void ScDocument::StartListeningArea(
+ const ScRange& rRange, bool bGroupListening, SvtListener* pListener )
+{
+ if (!pBASM)
+ return;
+
+ // Ensure sane ranges for the slots, specifically don't attempt to listen
+ // to more sheets than the document has. The slot machine handles it but
+ // with memory waste. Binary import filters can set out-of-bounds ranges
+ // in formula expressions' references, so all middle layers would have to
+ // check it, rather have this central point here.
+ ScRange aLimitedRange( ScAddress::UNINITIALIZED );
+ bool bEntirelyOut;
+ if (!LimitRangeToAvailableSheets( rRange, aLimitedRange, bEntirelyOut))
+ {
+ pBASM->StartListeningArea(rRange, bGroupListening, pListener);
+ return;
+ }
+
+ // If both sheets are out-of-bounds in the same direction then just bail out.
+ if (bEntirelyOut)
+ return;
+
+ pBASM->StartListeningArea( aLimitedRange, bGroupListening, pListener);
+}
+
+void ScDocument::EndListeningArea( const ScRange& rRange, bool bGroupListening, SvtListener* pListener )
+{
+ if (!pBASM)
+ return;
+
+ // End listening has to limit the range exactly the same as in
+ // StartListeningArea(), otherwise the range would not be found.
+ ScRange aLimitedRange( ScAddress::UNINITIALIZED );
+ bool bEntirelyOut;
+ if (!LimitRangeToAvailableSheets( rRange, aLimitedRange, bEntirelyOut))
+ {
+ pBASM->EndListeningArea(rRange, bGroupListening, pListener);
+ return;
+ }
+
+ // If both sheets are out-of-bounds in the same direction then just bail out.
+ if (bEntirelyOut)
+ return;
+
+ pBASM->EndListeningArea( aLimitedRange, bGroupListening, pListener);
+}
+
+bool ScDocument::LimitRangeToAvailableSheets( const ScRange& rRange, ScRange& o_rRange,
+ bool& o_bEntirelyOutOfBounds ) const
+{
+ const SCTAB nMaxTab = GetTableCount() - 1;
+ if (ValidTab( rRange.aStart.Tab(), nMaxTab) && ValidTab( rRange.aEnd.Tab(), nMaxTab))
+ return false;
+
+ // Originally BCA_LISTEN_ALWAYS uses an implicit tab 0 and should had been
+ // valid already, but in case that would change...
+ if (rRange == BCA_LISTEN_ALWAYS)
+ return false;
+
+ SCTAB nTab1 = rRange.aStart.Tab();
+ SCTAB nTab2 = rRange.aEnd.Tab();
+ SAL_WARN("sc.core","ScDocument::LimitRangeToAvailableSheets - bad sheet range: " << nTab1 << ".." << nTab2 <<
+ ", sheets: 0.." << nMaxTab);
+
+ // Both sheets are out-of-bounds in the same direction.
+ if ((nTab1 < 0 && nTab2 < 0) || (nMaxTab < nTab1 && nMaxTab < nTab2))
+ {
+ o_bEntirelyOutOfBounds = true;
+ return true;
+ }
+
+ // Limit the sheet range to bounds.
+ o_bEntirelyOutOfBounds = false;
+ nTab1 = std::clamp<SCTAB>( nTab1, 0, nMaxTab);
+ nTab2 = std::clamp<SCTAB>( nTab2, 0, nMaxTab);
+ o_rRange = rRange;
+ o_rRange.aStart.SetTab(nTab1);
+ o_rRange.aEnd.SetTab(nTab2);
+ return true;
+}
+
+void ScDocument::Broadcast( const ScHint& rHint )
+{
+ if ( !pBASM )
+ return ; // Clipboard or Undo
+ if ( eHardRecalcState == HardRecalcState::OFF )
+ {
+ ScBulkBroadcast aBulkBroadcast( pBASM.get(), rHint.GetId()); // scoped bulk broadcast
+ bool bIsBroadcasted = BroadcastHintInternal(rHint);
+ if ( pBASM->AreaBroadcast( rHint ) || bIsBroadcasted )
+ TrackFormulas( rHint.GetId() );
+ }
+
+ if ( rHint.GetStartAddress() != BCA_BRDCST_ALWAYS )
+ {
+ SCTAB nTab = rHint.GetStartAddress().Tab();
+ if (nTab < static_cast<SCTAB>(maTabs.size()) && maTabs[nTab])
+ maTabs[nTab]->SetStreamValid(false);
+ }
+}
+
+bool ScDocument::BroadcastHintInternal( const ScHint& rHint )
+{
+ bool bIsBroadcasted = false;
+ const ScAddress address(rHint.GetStartAddress());
+ SvtBroadcaster* pLastBC = nullptr;
+ // Process all broadcasters for the given row range.
+ for( SCROW nRow = 0; nRow < rHint.GetRowCount(); ++nRow )
+ {
+ ScAddress a(address);
+ a.SetRow(address.Row() + nRow);
+ SvtBroadcaster* pBC = GetBroadcaster(a);
+ if ( pBC && pBC != pLastBC )
+ {
+ pBC->Broadcast( rHint );
+ bIsBroadcasted = true;
+ pLastBC = pBC;
+ }
+ }
+ return bIsBroadcasted;
+}
+
+void ScDocument::BroadcastCells( const ScRange& rRange, SfxHintId nHint, bool bBroadcastSingleBroadcasters )
+{
+ PrepareFormulaCalc();
+
+ if (!pBASM)
+ return; // Clipboard or Undo
+
+ SCTAB nTab1 = rRange.aStart.Tab();
+ SCTAB nTab2 = rRange.aEnd.Tab();
+ SCROW nRow1 = rRange.aStart.Row();
+ SCROW nRow2 = rRange.aEnd.Row();
+ SCCOL nCol1 = rRange.aStart.Col();
+ SCCOL nCol2 = rRange.aEnd.Col();
+
+ if (eHardRecalcState == HardRecalcState::OFF)
+ {
+ ScBulkBroadcast aBulkBroadcast( pBASM.get(), nHint); // scoped bulk broadcast
+ bool bIsBroadcasted = false;
+
+ if (bBroadcastSingleBroadcasters)
+ {
+ for (SCTAB nTab = nTab1; nTab <= nTab2; ++nTab)
+ {
+ ScTable* pTab = FetchTable(nTab);
+ if (!pTab)
+ continue;
+
+ bIsBroadcasted |= pTab->BroadcastBroadcasters( nCol1, nRow1, nCol2, nRow2, nHint);
+ }
+ }
+
+ if (pBASM->AreaBroadcast(rRange, nHint) || bIsBroadcasted)
+ TrackFormulas(nHint);
+ }
+
+ for (SCTAB nTab = nTab1; nTab <= nTab2; ++nTab)
+ {
+ ScTable* pTab = FetchTable(nTab);
+ if (pTab)
+ pTab->SetStreamValid(false);
+ }
+
+ BroadcastUno(SfxHint(SfxHintId::ScDataChanged));
+}
+
+void ScDocument::AreaBroadcast( const ScHint& rHint )
+{
+ if ( !pBASM )
+ return ; // Clipboard or Undo
+ if (eHardRecalcState == HardRecalcState::OFF)
+ {
+ ScBulkBroadcast aBulkBroadcast( pBASM.get(), rHint.GetId()); // scoped bulk broadcast
+ if ( pBASM->AreaBroadcast( rHint ) )
+ TrackFormulas( rHint.GetId() );
+ }
+}
+
+void ScDocument::DelBroadcastAreasInRange( const ScRange& rRange )
+{
+ if ( pBASM )
+ pBASM->DelBroadcastAreasInRange( rRange );
+}
+
+void ScDocument::StartListeningCell( const ScAddress& rAddress,
+ SvtListener* pListener )
+{
+ OSL_ENSURE(pListener, "StartListeningCell: pListener Null");
+ SCTAB nTab = rAddress.Tab();
+ if (ValidTab(nTab) && nTab < static_cast<SCTAB>(maTabs.size()) && maTabs[nTab])
+ maTabs[nTab]->StartListening( rAddress, pListener );
+}
+
+void ScDocument::EndListeningCell( const ScAddress& rAddress,
+ SvtListener* pListener )
+{
+ OSL_ENSURE(pListener, "EndListeningCell: pListener Null");
+ SCTAB nTab = rAddress.Tab();
+ if (ValidTab(nTab) && nTab < static_cast<SCTAB>(maTabs.size()) && maTabs[nTab])
+ maTabs[nTab]->EndListening( rAddress, pListener );
+}
+
+void ScDocument::StartListeningCell(
+ sc::StartListeningContext& rCxt, const ScAddress& rPos, SvtListener& rListener )
+{
+ ScTable* pTab = FetchTable(rPos.Tab());
+ if (!pTab)
+ return;
+
+ pTab->StartListening(rCxt, rPos, rListener);
+}
+
+void ScDocument::EndListeningCell(
+ sc::EndListeningContext& rCxt, const ScAddress& rPos, SvtListener& rListener )
+{
+ ScTable* pTab = FetchTable(rPos.Tab());
+ if (!pTab)
+ return;
+
+ pTab->EndListening(rCxt, rPos, rListener);
+}
+
+void ScDocument::EndListeningFormulaCells( std::vector<ScFormulaCell*>& rCells )
+{
+ if (rCells.empty())
+ return;
+
+ sc::EndListeningContext aCxt(*this);
+ for (auto& pCell : rCells)
+ pCell->EndListeningTo(aCxt);
+
+ aCxt.purgeEmptyBroadcasters();
+}
+
+void ScDocument::PutInFormulaTree( ScFormulaCell* pCell )
+{
+ OSL_ENSURE( pCell, "PutInFormulaTree: pCell Null" );
+ RemoveFromFormulaTree( pCell );
+ // append
+ ScMutationGuard aGuard(*this, ScMutationGuardFlags::CORE);
+ if ( pEOFormulaTree )
+ pEOFormulaTree->SetNext( pCell );
+ else
+ pFormulaTree = pCell; // No end, no beginning...
+ pCell->SetPrevious( pEOFormulaTree );
+ pCell->SetNext( nullptr );
+ pEOFormulaTree = pCell;
+ nFormulaCodeInTree += pCell->GetCode()->GetCodeLen();
+}
+
+void ScDocument::RemoveFromFormulaTree( ScFormulaCell* pCell )
+{
+ ScMutationGuard aGuard(*this, ScMutationGuardFlags::CORE);
+ OSL_ENSURE( pCell, "RemoveFromFormulaTree: pCell Null" );
+ ScFormulaCell* pPrev = pCell->GetPrevious();
+ assert(pPrev != pCell); // pointing to itself?!?
+ // if the cell is first or somewhere in chain
+ if ( pPrev || pFormulaTree == pCell )
+ {
+ ScFormulaCell* pNext = pCell->GetNext();
+ assert(pNext != pCell); // pointing to itself?!?
+ if ( pPrev )
+ {
+ assert(pFormulaTree != pCell); // if this cell is also head something's wrong
+ pPrev->SetNext( pNext ); // predecessor exists, set successor
+ }
+ else
+ {
+ pFormulaTree = pNext; // this cell was first cell
+ }
+ if ( pNext )
+ {
+ assert(pEOFormulaTree != pCell); // if this cell is also tail something's wrong
+ pNext->SetPrevious( pPrev ); // successor exists, set predecessor
+ }
+ else
+ {
+ pEOFormulaTree = pPrev; // this cell was last cell
+ }
+ pCell->SetPrevious( nullptr );
+ pCell->SetNext( nullptr );
+ sal_uInt16 nRPN = pCell->GetCode()->GetCodeLen();
+ if ( nFormulaCodeInTree >= nRPN )
+ nFormulaCodeInTree -= nRPN;
+ else
+ {
+ OSL_FAIL( "RemoveFromFormulaTree: nFormulaCodeInTree < nRPN" );
+ nFormulaCodeInTree = 0;
+ }
+ }
+ else if ( !pFormulaTree && nFormulaCodeInTree )
+ {
+ OSL_FAIL( "!pFormulaTree && nFormulaCodeInTree != 0" );
+ nFormulaCodeInTree = 0;
+ }
+}
+
+void ScDocument::CalcFormulaTree( bool bOnlyForced, bool bProgressBar, bool bSetAllDirty )
+{
+ OSL_ENSURE( !IsCalculatingFormulaTree(), "CalcFormulaTree recursion" );
+ // never ever recurse into this, might end up lost in infinity
+ if ( IsCalculatingFormulaTree() )
+ return ;
+
+ ScMutationGuard aGuard(*this, ScMutationGuardFlags::CORE);
+ mpFormulaGroupCxt.reset();
+ bCalculatingFormulaTree = true;
+
+ SetForcedFormulaPending( false );
+ bool bOldIdleEnabled = IsIdleEnabled();
+ EnableIdle(false);
+ bool bOldAutoCalc = GetAutoCalc();
+ //ATTENTION: _not_ SetAutoCalc( true ) because this might call CalcFormulaTree( true )
+ //ATTENTION: if it was disabled before and bHasForcedFormulas is set
+ bAutoCalc = true;
+ if (eHardRecalcState == HardRecalcState::ETERNAL)
+ CalcAll();
+ else
+ {
+ ::std::vector<ScFormulaCell*> vAlwaysDirty;
+ ScFormulaCell* pCell = pFormulaTree;
+ while ( pCell )
+ {
+ if ( pCell->GetDirty() )
+ ; // nothing to do
+ else if ( pCell->GetCode()->IsRecalcModeAlways() )
+ {
+ // pCell and dependents are to be set dirty again, collect
+ // them first and broadcast afterwards to not break the
+ // FormulaTree chain here.
+ vAlwaysDirty.push_back( pCell);
+ }
+ else if ( bSetAllDirty )
+ {
+ // Force calculating all in tree, without broadcasting.
+ pCell->SetDirtyVar();
+ }
+ pCell = pCell->GetNext();
+ }
+ for (const auto& rpCell : vAlwaysDirty)
+ {
+ pCell = rpCell;
+ if (!pCell->GetDirty())
+ pCell->SetDirty();
+ }
+
+ bool bProgress = !bOnlyForced && nFormulaCodeInTree && bProgressBar;
+ if ( bProgress )
+ ScProgress::CreateInterpretProgress( this );
+
+ pCell = pFormulaTree;
+ ScFormulaCell* pLastNoGood = nullptr;
+ while ( pCell )
+ {
+ // Interpret resets bDirty and calls Remove, also the referenced!
+ // the Cell remains when ScRecalcMode::ALWAYS.
+ if ( bOnlyForced )
+ {
+ if ( pCell->GetCode()->IsRecalcModeForced() )
+ pCell->Interpret();
+ }
+ else
+ {
+ pCell->Interpret();
+ }
+ if ( pCell->GetPrevious() || pCell == pFormulaTree )
+ { // (IsInFormulaTree(pCell)) no Remove was called => next
+ pLastNoGood = pCell;
+ pCell = pCell->GetNext();
+ }
+ else
+ {
+ if ( pFormulaTree )
+ {
+ if ( pFormulaTree->GetDirty() && !bOnlyForced )
+ {
+ pCell = pFormulaTree;
+ pLastNoGood = nullptr;
+ }
+ else
+ {
+ // IsInFormulaTree(pLastNoGood)
+ if ( pLastNoGood && (pLastNoGood->GetPrevious() ||
+ pLastNoGood == pFormulaTree) )
+ pCell = pLastNoGood->GetNext();
+ else
+ {
+ pCell = pFormulaTree;
+ while ( pCell && !pCell->GetDirty() )
+ pCell = pCell->GetNext();
+ if ( pCell )
+ pLastNoGood = pCell->GetPrevious();
+ }
+ }
+ }
+ else
+ pCell = nullptr;
+ }
+ }
+ if ( bProgress )
+ ScProgress::DeleteInterpretProgress();
+ }
+ bAutoCalc = bOldAutoCalc;
+ EnableIdle(bOldIdleEnabled);
+ bCalculatingFormulaTree = false;
+
+ mpFormulaGroupCxt.reset();
+}
+
+void ScDocument::ClearFormulaTree()
+{
+ ScFormulaCell* pCell;
+ ScFormulaCell* pTree = pFormulaTree;
+ while ( pTree )
+ {
+ pCell = pTree;
+ pTree = pCell->GetNext();
+ if ( !pCell->GetCode()->IsRecalcModeAlways() )
+ RemoveFromFormulaTree( pCell );
+ }
+}
+
+void ScDocument::AppendToFormulaTrack( ScFormulaCell* pCell )
+{
+ OSL_ENSURE( pCell, "AppendToFormulaTrack: pCell Null" );
+ // The cell can not be in both lists at the same time
+ RemoveFromFormulaTrack( pCell );
+ RemoveFromFormulaTree( pCell );
+ if ( pEOFormulaTrack )
+ pEOFormulaTrack->SetNextTrack( pCell );
+ else
+ pFormulaTrack = pCell; // No end, no beginning...
+ pCell->SetPreviousTrack( pEOFormulaTrack );
+ pCell->SetNextTrack( nullptr );
+ pEOFormulaTrack = pCell;
+ ++nFormulaTrackCount;
+}
+
+void ScDocument::RemoveFromFormulaTrack( ScFormulaCell* pCell )
+{
+ OSL_ENSURE( pCell, "RemoveFromFormulaTrack: pCell Null" );
+ ScFormulaCell* pPrev = pCell->GetPreviousTrack();
+ assert(pPrev != pCell); // pointing to itself?!?
+ // if the cell is first or somewhere in chain
+ if ( !(pPrev || pFormulaTrack == pCell) )
+ return;
+
+ ScFormulaCell* pNext = pCell->GetNextTrack();
+ assert(pNext != pCell); // pointing to itself?!?
+ if ( pPrev )
+ {
+ assert(pFormulaTrack != pCell); // if this cell is also head something's wrong
+ pPrev->SetNextTrack( pNext ); // predecessor exists, set successor
+ }
+ else
+ {
+ pFormulaTrack = pNext; // this cell was first cell
+ }
+ if ( pNext )
+ {
+ assert(pEOFormulaTrack != pCell); // if this cell is also tail something's wrong
+ pNext->SetPreviousTrack( pPrev ); // successor exists, set predecessor
+ }
+ else
+ {
+ pEOFormulaTrack = pPrev; // this cell was last cell
+ }
+ pCell->SetPreviousTrack( nullptr );
+ pCell->SetNextTrack( nullptr );
+ --nFormulaTrackCount;
+}
+
+void ScDocument::FinalTrackFormulas( SfxHintId nHintId )
+{
+ mbTrackFormulasPending = false;
+ mbFinalTrackFormulas = true;
+ {
+ ScBulkBroadcast aBulk( GetBASM(), nHintId);
+ // Collect all pending formula cells in bulk.
+ TrackFormulas( nHintId );
+ }
+ // A final round not in bulk to track all remaining formula cells and their
+ // dependents that were collected during ScBulkBroadcast dtor.
+ TrackFormulas( nHintId );
+ mbFinalTrackFormulas = false;
+}
+
+/*
+ The first is broadcasted,
+ the ones that are created through this are appended to the Track by Notify.
+ The next is broadcasted again, and so on.
+ View initiates Interpret.
+ */
+void ScDocument::TrackFormulas( SfxHintId nHintId )
+{
+ if (!pBASM)
+ return;
+
+ if (pBASM->IsInBulkBroadcast() && !IsFinalTrackFormulas() &&
+ (nHintId == SfxHintId::ScDataChanged || nHintId == SfxHintId::ScHiddenRowsChanged))
+ {
+ SetTrackFormulasPending();
+ return;
+ }
+
+ if ( pFormulaTrack )
+ {
+ // outside the loop, check if any sheet has a "calculate" event script
+ bool bCalcEvent = HasAnySheetEventScript( ScSheetEventId::CALCULATE, true );
+ for( ScFormulaCell* pTrack = pFormulaTrack; pTrack != nullptr; pTrack = pTrack->GetNextTrack())
+ {
+ SCROW rowCount = 1;
+ ScAddress address = pTrack->aPos;
+ // Compress to include all adjacent cells in the same column.
+ for(ScFormulaCell* pNext = pTrack->GetNextTrack(); pNext != nullptr; pNext = pNext->GetNextTrack())
+ {
+ if(pNext->aPos != ScAddress(address.Col(), address.Row() + rowCount, address.Tab()))
+ break;
+ ++rowCount;
+ pTrack = pNext;
+ }
+ ScHint aHint( nHintId, address, rowCount );
+ BroadcastHintInternal( aHint );
+ pBASM->AreaBroadcast( aHint );
+ // for "calculate" event, keep track of which sheets are affected by tracked formulas
+ if ( bCalcEvent )
+ SetCalcNotification( address.Tab() );
+ }
+ bool bHaveForced = false;
+ for( ScFormulaCell* pTrack = pFormulaTrack; pTrack != nullptr;)
+ {
+ ScFormulaCell* pNext = pTrack->GetNextTrack();
+ RemoveFromFormulaTrack( pTrack );
+ PutInFormulaTree( pTrack );
+ if ( pTrack->GetCode()->IsRecalcModeForced() )
+ bHaveForced = true;
+ pTrack = pNext;
+ }
+ if ( bHaveForced )
+ {
+ SetForcedFormulas( true );
+ if ( bAutoCalc && !IsAutoCalcShellDisabled() && !IsInInterpreter()
+ && !IsCalculatingFormulaTree() )
+ CalcFormulaTree( true );
+ else
+ SetForcedFormulaPending( true );
+ }
+ }
+ OSL_ENSURE( nFormulaTrackCount==0, "TrackFormulas: nFormulaTrackCount!=0" );
+}
+
+void ScDocument::StartAllListeners()
+{
+ sc::StartListeningContext aCxt(*this);
+ for ( auto const & i: maTabs )
+ if ( i )
+ i->StartListeners(aCxt, true);
+}
+
+void ScDocument::UpdateBroadcastAreas( UpdateRefMode eUpdateRefMode,
+ const ScRange& rRange, SCCOL nDx, SCROW nDy, SCTAB nDz
+ )
+{
+ bool bExpandRefsOld = IsExpandRefs();
+ if ( eUpdateRefMode == URM_INSDEL && (nDx > 0 || nDy > 0 || nDz > 0) )
+ SetExpandRefs( SC_MOD()->GetInputOptions().GetExpandRefs() );
+ if ( pBASM )
+ pBASM->UpdateBroadcastAreas( eUpdateRefMode, rRange, nDx, nDy, nDz );
+ SetExpandRefs( bExpandRefsOld );
+}
+
+void ScDocument::SetAutoCalc( bool bNewAutoCalc )
+{
+ bool bOld = bAutoCalc;
+ bAutoCalc = bNewAutoCalc;
+ if ( !bOld && bNewAutoCalc && bHasForcedFormulas )
+ {
+ if ( IsAutoCalcShellDisabled() )
+ SetForcedFormulaPending( true );
+ else if ( !IsInInterpreter() )
+ CalcFormulaTree( true );
+ }
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sc/source/core/data/documen8.cxx b/sc/source/core/data/documen8.cxx
new file mode 100644
index 000000000..e0abb1d3b
--- /dev/null
+++ b/sc/source/core/data/documen8.cxx
@@ -0,0 +1,1329 @@
+/* -*- 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 <scitems.hxx>
+#include <comphelper/fileformat.h>
+#include <comphelper/processfactory.hxx>
+#include <comphelper/servicehelper.hxx>
+#include <officecfg/Office/Common.hxx>
+#include <tools/urlobj.hxx>
+#include <editeng/frmdiritem.hxx>
+#include <editeng/langitem.hxx>
+#include <sfx2/linkmgr.hxx>
+#include <sfx2/bindings.hxx>
+#include <sfx2/objsh.hxx>
+#include <sfx2/printer.hxx>
+#include <sfx2/viewfrm.hxx>
+#include <sfx2/viewsh.hxx>
+#include <svl/flagitem.hxx>
+#include <svl/intitem.hxx>
+#include <svl/numformat.hxx>
+#include <svl/zformat.hxx>
+#include <svl/ctloptions.hxx>
+#include <unotools/transliterationwrapper.hxx>
+#include <sal/log.hxx>
+#include <osl/diagnose.h>
+
+#include <vcl/svapp.hxx>
+#include <vcl/virdev.hxx>
+#include <vcl/weld.hxx>
+#include <vcl/TaskStopwatch.hxx>
+
+#include <inputopt.hxx>
+#include <global.hxx>
+#include <table.hxx>
+#include <column.hxx>
+#include <poolhelp.hxx>
+#include <docpool.hxx>
+#include <stlpool.hxx>
+#include <stlsheet.hxx>
+#include <docoptio.hxx>
+#include <viewopti.hxx>
+#include <scextopt.hxx>
+#include <rechead.hxx>
+#include <ddelink.hxx>
+#include <scmatrix.hxx>
+#include <arealink.hxx>
+#include <patattr.hxx>
+#include <editutil.hxx>
+#include <progress.hxx>
+#include <document.hxx>
+#include <chartlis.hxx>
+#include <chartlock.hxx>
+#include <refupdat.hxx>
+#include <markdata.hxx>
+#include <scmod.hxx>
+#include <externalrefmgr.hxx>
+#include <globstr.hrc>
+#include <strings.hrc>
+#include <sc.hrc>
+#include <charthelper.hxx>
+#include <macromgr.hxx>
+#include <docuno.hxx>
+#include <scresid.hxx>
+#include <columniterator.hxx>
+#include <globalnames.hxx>
+#include <stringutil.hxx>
+#include <documentlinkmgr.hxx>
+#include <tokenarray.hxx>
+#include <recursionhelper.hxx>
+
+#include <memory>
+#include <utility>
+
+using namespace com::sun::star;
+
+namespace {
+
+sal_uInt16 getScaleValue(SfxStyleSheetBase& rStyle, sal_uInt16 nWhich)
+{
+ return static_cast<const SfxUInt16Item&>(rStyle.GetItemSet().Get(nWhich)).GetValue();
+}
+
+}
+
+void ScDocument::ImplCreateOptions()
+{
+ pDocOptions.reset( new ScDocOptions() );
+ pViewOptions.reset( new ScViewOptions() );
+}
+
+void ScDocument::ImplDeleteOptions()
+{
+ pDocOptions.reset();
+ pViewOptions.reset();
+ pExtDocOptions.reset();
+}
+
+SfxPrinter* ScDocument::GetPrinter(bool bCreateIfNotExist)
+{
+ if ( !mpPrinter && bCreateIfNotExist )
+ {
+ auto pSet =
+ std::make_unique<SfxItemSetFixed
+ <SID_PRINTER_NOTFOUND_WARN, SID_PRINTER_NOTFOUND_WARN,
+ SID_PRINTER_CHANGESTODOC, SID_PRINTER_CHANGESTODOC,
+ SID_PRINT_SELECTEDSHEET, SID_PRINT_SELECTEDSHEET,
+ SID_SCPRINTOPTIONS, SID_SCPRINTOPTIONS>>(*mxPoolHelper->GetDocPool());
+
+ SfxPrinterChangeFlags nFlags = SfxPrinterChangeFlags::NONE;
+ if (officecfg::Office::Common::Print::Warning::PaperOrientation::get())
+ nFlags |= SfxPrinterChangeFlags::CHG_ORIENTATION;
+ if (officecfg::Office::Common::Print::Warning::PaperSize::get())
+ nFlags |= SfxPrinterChangeFlags::CHG_SIZE;
+ pSet->Put( SfxFlagItem( SID_PRINTER_CHANGESTODOC, static_cast<int>(nFlags) ) );
+ pSet->Put( SfxBoolItem( SID_PRINTER_NOTFOUND_WARN, officecfg::Office::Common::Print::Warning::NotFound::get() ) );
+
+ mpPrinter = VclPtr<SfxPrinter>::Create( std::move(pSet) );
+ mpPrinter->SetMapMode(MapMode(MapUnit::Map100thMM));
+ UpdateDrawPrinter();
+ mpPrinter->SetDigitLanguage( SC_MOD()->GetOptDigitLanguage() );
+ }
+
+ return mpPrinter;
+}
+
+void ScDocument::SetPrinter( VclPtr<SfxPrinter> const & pNewPrinter )
+{
+ if ( pNewPrinter == mpPrinter.get() )
+ {
+ // #i6706# SetPrinter is called with the same printer again if
+ // the JobSetup has changed. In that case just call UpdateDrawPrinter
+ // (SetRefDevice for drawing layer) because of changed text sizes.
+ UpdateDrawPrinter();
+ }
+ else
+ {
+ ScopedVclPtr<SfxPrinter> xKeepAlive( mpPrinter );
+ mpPrinter = pNewPrinter;
+ UpdateDrawPrinter();
+ mpPrinter->SetDigitLanguage( SC_MOD()->GetOptDigitLanguage() );
+ }
+ InvalidateTextWidth(nullptr, nullptr, false); // in both cases
+}
+
+void ScDocument::SetPrintOptions()
+{
+ if ( !mpPrinter ) GetPrinter(); // this sets mpPrinter
+ OSL_ENSURE( mpPrinter, "Error in printer creation :-/" );
+
+ if ( !mpPrinter )
+ return;
+
+ SfxItemSet aOptSet( mpPrinter->GetOptions() );
+
+ SfxPrinterChangeFlags nFlags = SfxPrinterChangeFlags::NONE;
+ if (officecfg::Office::Common::Print::Warning::PaperOrientation::get())
+ nFlags |= SfxPrinterChangeFlags::CHG_ORIENTATION;
+ if (officecfg::Office::Common::Print::Warning::PaperSize::get())
+ nFlags |= SfxPrinterChangeFlags::CHG_SIZE;
+ aOptSet.Put( SfxFlagItem( SID_PRINTER_CHANGESTODOC, static_cast<int>(nFlags) ) );
+ aOptSet.Put( SfxBoolItem( SID_PRINTER_NOTFOUND_WARN, officecfg::Office::Common::Print::Warning::NotFound::get() ) );
+
+ mpPrinter->SetOptions( aOptSet );
+}
+
+VirtualDevice* ScDocument::GetVirtualDevice_100th_mm()
+{
+ if (!mpVirtualDevice_100th_mm)
+ {
+#ifdef IOS
+ mpVirtualDevice_100th_mm = VclPtr<VirtualDevice>::Create(DeviceFormat::GRAYSCALE);
+#else
+ mpVirtualDevice_100th_mm = VclPtr<VirtualDevice>::Create(DeviceFormat::DEFAULT);
+#endif
+ mpVirtualDevice_100th_mm->SetReferenceDevice(VirtualDevice::RefDevMode::MSO1);
+ MapMode aMapMode( mpVirtualDevice_100th_mm->GetMapMode() );
+ aMapMode.SetMapUnit( MapUnit::Map100thMM );
+ mpVirtualDevice_100th_mm->SetMapMode( aMapMode );
+ }
+ return mpVirtualDevice_100th_mm;
+}
+
+OutputDevice* ScDocument::GetRefDevice()
+{
+ // Create printer like ref device, see Writer...
+ OutputDevice* pRefDevice = nullptr;
+ if ( SC_MOD()->GetInputOptions().GetTextWysiwyg() )
+ pRefDevice = GetPrinter();
+ else
+ pRefDevice = GetVirtualDevice_100th_mm();
+ return pRefDevice;
+}
+
+void ScDocument::ModifyStyleSheet( SfxStyleSheetBase& rStyleSheet,
+ const SfxItemSet& rChanges )
+{
+ SfxItemSet& rSet = rStyleSheet.GetItemSet();
+
+ switch ( rStyleSheet.GetFamily() )
+ {
+ case SfxStyleFamily::Page:
+ {
+ const sal_uInt16 nOldScale = getScaleValue(rStyleSheet, ATTR_PAGE_SCALE);
+ const sal_uInt16 nOldScaleToPages = getScaleValue(rStyleSheet, ATTR_PAGE_SCALETOPAGES);
+ rSet.Put( rChanges );
+ const sal_uInt16 nNewScale = getScaleValue(rStyleSheet, ATTR_PAGE_SCALE);
+ const sal_uInt16 nNewScaleToPages = getScaleValue(rStyleSheet, ATTR_PAGE_SCALETOPAGES);
+
+ if ( (nOldScale != nNewScale) || (nOldScaleToPages != nNewScaleToPages) )
+ InvalidateTextWidth( rStyleSheet.GetName() );
+
+ if( SvtCTLOptions().IsCTLFontEnabled() )
+ {
+ if( rChanges.GetItemState(ATTR_WRITINGDIR ) == SfxItemState::SET )
+ ScChartHelper::DoUpdateAllCharts( *this );
+ }
+ }
+ break;
+
+ case SfxStyleFamily::Para:
+ {
+ bool bNumFormatChanged;
+ if ( ScGlobal::CheckWidthInvalidate( bNumFormatChanged,
+ rSet, rChanges ) )
+ InvalidateTextWidth( nullptr, nullptr, bNumFormatChanged );
+
+ for (SCTAB nTab=0; nTab<=MAXTAB; ++nTab)
+ if (maTabs[nTab])
+ maTabs[nTab]->SetStreamValid( false );
+
+ sal_uLong nOldFormat =
+ rSet.Get( ATTR_VALUE_FORMAT ).GetValue();
+ sal_uLong nNewFormat =
+ rChanges.Get( ATTR_VALUE_FORMAT ).GetValue();
+ LanguageType eNewLang, eOldLang;
+ eNewLang = eOldLang = LANGUAGE_DONTKNOW;
+ if ( nNewFormat != nOldFormat )
+ {
+ SvNumberFormatter* pFormatter = GetFormatTable();
+ eOldLang = pFormatter->GetEntry( nOldFormat )->GetLanguage();
+ eNewLang = pFormatter->GetEntry( nNewFormat )->GetLanguage();
+ }
+
+ // Explanation to Items in rChanges:
+ // Set Item - take over change
+ // Dontcare - Set Default
+ // Default - No change
+ // ("no change" is not possible with PutExtended, thus the loop)
+ for (sal_uInt16 nWhich = ATTR_PATTERN_START; nWhich <= ATTR_PATTERN_END; nWhich++)
+ {
+ const SfxPoolItem* pItem;
+ SfxItemState eState = rChanges.GetItemState( nWhich, false, &pItem );
+ if ( eState == SfxItemState::SET )
+ rSet.Put( *pItem );
+ else if ( eState == SfxItemState::DONTCARE )
+ rSet.ClearItem( nWhich );
+ // when Default nothing
+ }
+
+ if ( eNewLang != eOldLang )
+ rSet.Put(
+ SvxLanguageItem( eNewLang, ATTR_LANGUAGE_FORMAT ) );
+ }
+ break;
+ default:
+ {
+ // added to avoid warnings
+ }
+ }
+}
+
+void ScDocument::CopyStdStylesFrom( const ScDocument& rSrcDoc )
+{
+ // number format exchange list has to be handled here, too
+ NumFmtMergeHandler aNumFmtMergeHdl(*this, rSrcDoc);
+ mxPoolHelper->GetStylePool()->CopyStdStylesFrom( rSrcDoc.mxPoolHelper->GetStylePool() );
+}
+
+void ScDocument::InvalidateTextWidth( std::u16string_view rStyleName )
+{
+ const SCTAB nCount = GetTableCount();
+ for ( SCTAB i=0; i<nCount && maTabs[i]; i++ )
+ if ( maTabs[i]->GetPageStyle() == rStyleName )
+ InvalidateTextWidth( i );
+}
+
+void ScDocument::InvalidateTextWidth( SCTAB nTab )
+{
+ ScAddress aAdrFrom( 0, 0, nTab );
+ ScAddress aAdrTo ( MaxCol(), MaxRow(), nTab );
+ InvalidateTextWidth( &aAdrFrom, &aAdrTo, false );
+}
+
+bool ScDocument::IsPageStyleInUse( std::u16string_view rStrPageStyle, SCTAB* pInTab )
+{
+ bool bInUse = false;
+ const SCTAB nCount = GetTableCount();
+ SCTAB i;
+
+ for ( i = 0; !bInUse && i < nCount && maTabs[i]; i++ )
+ bInUse = ( maTabs[i]->GetPageStyle() == rStrPageStyle );
+
+ if ( pInTab )
+ *pInTab = i-1;
+
+ return bInUse;
+}
+
+bool ScDocument::RemovePageStyleInUse( std::u16string_view rStyle )
+{
+ bool bWasInUse = false;
+ const SCTAB nCount = GetTableCount();
+
+ for ( SCTAB i=0; i<nCount && maTabs[i]; i++ )
+ if ( maTabs[i]->GetPageStyle() == rStyle )
+ {
+ bWasInUse = true;
+ maTabs[i]->SetPageStyle( ScResId(STR_STYLENAME_STANDARD) );
+ }
+
+ return bWasInUse;
+}
+
+bool ScDocument::RenamePageStyleInUse( std::u16string_view rOld, const OUString& rNew )
+{
+ bool bWasInUse = false;
+ const SCTAB nCount = GetTableCount();
+
+ for ( SCTAB i=0; i<nCount && maTabs[i]; i++ )
+ if ( maTabs[i]->GetPageStyle() == rOld )
+ {
+ bWasInUse = true;
+ maTabs[i]->SetPageStyle( rNew );
+ }
+
+ return bWasInUse;
+}
+
+EEHorizontalTextDirection ScDocument::GetEditTextDirection(SCTAB nTab) const
+{
+ EEHorizontalTextDirection eRet = EEHorizontalTextDirection::Default;
+
+ OUString aStyleName = GetPageStyle( nTab );
+ SfxStyleSheetBase* pStyle = mxPoolHelper->GetStylePool()->Find( aStyleName, SfxStyleFamily::Page );
+ if ( pStyle )
+ {
+ SfxItemSet& rStyleSet = pStyle->GetItemSet();
+ SvxFrameDirection eDirection =
+ rStyleSet.Get( ATTR_WRITINGDIR ).GetValue();
+
+ if ( eDirection == SvxFrameDirection::Horizontal_LR_TB )
+ eRet = EEHorizontalTextDirection::L2R;
+ else if ( eDirection == SvxFrameDirection::Horizontal_RL_TB )
+ eRet = EEHorizontalTextDirection::R2L;
+ // else (invalid for EditEngine): keep "default"
+ }
+
+ return eRet;
+}
+
+ScMacroManager* ScDocument::GetMacroManager()
+{
+ if (!mpMacroMgr)
+ mpMacroMgr.reset(new ScMacroManager(*this));
+ return mpMacroMgr.get();
+}
+
+void ScDocument::FillMatrix(
+ ScMatrix& rMat, SCTAB nTab, SCCOL nCol1, SCROW nRow1, SCCOL nCol2, SCROW nRow2, svl::SharedStringPool* pPool ) const
+{
+ const ScTable* pTab = FetchTable(nTab);
+ if (!pTab)
+ return;
+
+ if (nCol1 > nCol2 || nRow1 > nRow2)
+ return;
+
+ SCSIZE nC, nR;
+ rMat.GetDimensions(nC, nR);
+ if (static_cast<SCROW>(nR) != nRow2 - nRow1 + 1 || static_cast<SCCOL>(nC) != nCol2 - nCol1 + 1)
+ return;
+
+ pTab->FillMatrix(rMat, nCol1, nRow1, nCol2, nRow2, pPool);
+}
+
+void ScDocument::SetFormulaResults( const ScAddress& rTopPos, const double* pResults, size_t nLen )
+{
+ ScTable* pTab = FetchTable(rTopPos.Tab());
+ if (!pTab)
+ return;
+
+ pTab->SetFormulaResults(rTopPos.Col(), rTopPos.Row(), pResults, nLen);
+}
+
+void ScDocument::CalculateInColumnInThread( ScInterpreterContext& rContext, const ScRange& rCalcRange, unsigned nThisThread, unsigned nThreadsTotal)
+{
+ ScTable* pTab = FetchTable(rCalcRange.aStart.Tab());
+ if (!pTab)
+ return;
+
+ assert(IsThreadedGroupCalcInProgress());
+
+ maThreadSpecific.pContext = &rContext;
+ pTab->CalculateInColumnInThread(rContext, rCalcRange.aStart.Col(), rCalcRange.aEnd.Col(), rCalcRange.aStart.Row(), rCalcRange.aEnd.Row(), nThisThread, nThreadsTotal);
+
+ assert(IsThreadedGroupCalcInProgress());
+ maThreadSpecific.pContext = nullptr;
+ // If any of the thread_local data would cause problems if they stay around for too long
+ // (and e.g. outlive the ScDocument), clean them up here, they cannot be cleaned up
+ // later from the main thread.
+ if(maThreadSpecific.xRecursionHelper)
+ maThreadSpecific.xRecursionHelper->Clear();
+}
+
+void ScDocument::HandleStuffAfterParallelCalculation( SCCOL nColStart, SCCOL nColEnd, SCROW nRow, size_t nLen, SCTAB nTab, ScInterpreter* pInterpreter )
+{
+ assert(!IsThreadedGroupCalcInProgress());
+ for( const DelayedSetNumberFormat& data : GetNonThreadedContext().maDelayedSetNumberFormat)
+ SetNumberFormat( ScAddress( data.mCol, data.mRow, nTab ), data.mnNumberFormat );
+ GetNonThreadedContext().maDelayedSetNumberFormat.clear();
+
+ ScTable* pTab = FetchTable(nTab);
+ if (!pTab)
+ return;
+
+ pTab->HandleStuffAfterParallelCalculation(nColStart, nColEnd, nRow, nLen, pInterpreter);
+}
+
+void ScDocument::InvalidateTextWidth( const ScAddress* pAdrFrom, const ScAddress* pAdrTo,
+ bool bNumFormatChanged )
+{
+ bool bBroadcast = (bNumFormatChanged && GetDocOptions().IsCalcAsShown() && !IsImportingXML() && !IsClipboard());
+ if ( pAdrFrom && !pAdrTo )
+ {
+ const SCTAB nTab = pAdrFrom->Tab();
+
+ if (nTab < static_cast<SCTAB>(maTabs.size()) && maTabs[nTab] )
+ maTabs[nTab]->InvalidateTextWidth( pAdrFrom, nullptr, bNumFormatChanged, bBroadcast );
+ }
+ else
+ {
+ const SCTAB nTabStart = pAdrFrom ? pAdrFrom->Tab() : 0;
+ const SCTAB nTabEnd = pAdrTo ? pAdrTo->Tab() : MAXTAB;
+
+ for ( SCTAB nTab=nTabStart; nTab<=nTabEnd && nTab < static_cast<SCTAB>(maTabs.size()); nTab++ )
+ if ( maTabs[nTab] )
+ maTabs[nTab]->InvalidateTextWidth( pAdrFrom, pAdrTo, bNumFormatChanged, bBroadcast );
+ }
+}
+
+#define CALCMAX 1000 // Calculations
+
+namespace {
+
+class IdleCalcTextWidthScope : public TaskStopwatch
+{
+ ScDocument& mrDoc;
+ ScAddress& mrCalcPos;
+ MapMode maOldMapMode;
+ ScStyleSheetPool* mpStylePool;
+ bool mbNeedMore;
+ bool mbProgress;
+
+public:
+ IdleCalcTextWidthScope(ScDocument& rDoc, ScAddress& rCalcPos) :
+ mrDoc(rDoc),
+ mrCalcPos(rCalcPos),
+ mpStylePool(rDoc.GetStyleSheetPool()),
+ mbNeedMore(false),
+ mbProgress(false)
+ {
+ mrDoc.EnableIdle(false);
+ }
+
+ ~IdleCalcTextWidthScope() COVERITY_NOEXCEPT_FALSE
+ {
+ SfxPrinter* pDev = mrDoc.GetPrinter();
+ if (pDev)
+ pDev->SetMapMode(maOldMapMode);
+
+ if (mbProgress)
+ ScProgress::DeleteInterpretProgress();
+
+ mrDoc.EnableIdle(true);
+ }
+
+ SCTAB Tab() const { return mrCalcPos.Tab(); }
+ SCCOL Col() const { return mrCalcPos.Col(); }
+ SCROW Row() const { return mrCalcPos.Row(); }
+
+ void setTab(SCTAB nTab) { mrCalcPos.SetTab(nTab); }
+ void setCol(SCCOL nCol) { mrCalcPos.SetCol(nCol); }
+ void setRow(SCROW nRow) { mrCalcPos.SetRow(nRow); }
+
+ void incTab() { mrCalcPos.IncTab(); }
+ void incCol(SCCOL nInc) { mrCalcPos.IncCol(nInc); }
+
+ void setOldMapMode(const MapMode& rOldMapMode) { maOldMapMode = rOldMapMode; }
+
+ void setNeedMore(bool b) { mbNeedMore = b; }
+ bool getNeedMore() const { return mbNeedMore; }
+
+ void createProgressBar()
+ {
+ ScProgress::CreateInterpretProgress(&mrDoc, false);
+ mbProgress = true;
+ }
+
+ bool hasProgressBar() const { return mbProgress; }
+
+ ScStyleSheetPool* getStylePool() { return mpStylePool; }
+};
+
+}
+
+bool ScDocument::IdleCalcTextWidth() // true = try next again
+{
+ // #i75610# if a printer hasn't been set or created yet, don't create one for this
+ if (!mbIdleEnabled || IsInLinkUpdate() || GetPrinter(false) == nullptr)
+ return false;
+
+ IdleCalcTextWidthScope aScope(*this, aCurTextWidthCalcPos);
+
+ if (!ValidRow(aScope.Row()))
+ {
+ aScope.setRow(0);
+ aScope.incCol(-1);
+ }
+
+ if (aScope.Col() < 0)
+ {
+ aScope.setCol(MaxCol());
+ aScope.incTab();
+ }
+
+ if (!ValidTab(aScope.Tab()) || aScope.Tab() >= static_cast<SCTAB>(maTabs.size()) || !maTabs[aScope.Tab()])
+ aScope.setTab(0);
+
+ ScTable* pTab = maTabs[aScope.Tab()].get();
+ ScStyleSheet* pStyle = static_cast<ScStyleSheet*>(aScope.getStylePool()->Find(pTab->aPageStyle, SfxStyleFamily::Page));
+ OSL_ENSURE( pStyle, "Missing StyleSheet :-/" );
+
+ if (!pStyle || getScaleValue(*pStyle, ATTR_PAGE_SCALETOPAGES) == 0)
+ {
+ // Move to the next sheet as the current one has scale-to-pages set,
+ // and bail out.
+ aScope.incTab();
+ return false;
+ }
+
+ sal_uInt16 nZoom = getScaleValue(*pStyle, ATTR_PAGE_SCALE);
+ Fraction aZoomFract(nZoom, 100);
+
+ aScope.setCol(pTab->ClampToAllocatedColumns(aScope.Col()));
+ // Start at specified cell position (nCol, nRow, nTab).
+ ScColumn* pCol = &pTab->aCol[aScope.Col()];
+ std::optional<ScColumnTextWidthIterator> pColIter(std::in_place, *this, *pCol, aScope.Row(), MaxRow());
+
+ OutputDevice* pDev = nullptr;
+ sal_uInt16 nRestart = 0;
+ sal_uInt16 nCount = 0;
+ while ( (nZoom > 0) && (nCount < CALCMAX) && (nRestart < 2) )
+ {
+ if (pColIter->hasCell())
+ {
+ // More cell in this column.
+ SCROW nRow = pColIter->getPos();
+ aScope.setRow(nRow);
+
+ if (pColIter->getValue() == TEXTWIDTH_DIRTY)
+ {
+ // Calculate text width for this cell.
+ double nPPTX = 0.0;
+ double nPPTY = 0.0;
+ if (!pDev)
+ {
+ pDev = GetPrinter();
+ aScope.setOldMapMode(pDev->GetMapMode());
+ pDev->SetMapMode(MapMode(MapUnit::MapPixel)); // Important for GetNeededSize
+
+ Point aPix1000 = pDev->LogicToPixel(Point(1000,1000), MapMode(MapUnit::MapTwip));
+ nPPTX = aPix1000.X() / 1000.0;
+ nPPTY = aPix1000.Y() / 1000.0;
+ }
+
+ if (!aScope.hasProgressBar() && pCol->IsFormulaDirty(nRow))
+ aScope.createProgressBar();
+
+ sal_uInt16 nNewWidth = static_cast<sal_uInt16>(GetNeededSize(
+ aScope.Col(), aScope.Row(), aScope.Tab(),
+ pDev, nPPTX, nPPTY, aZoomFract,aZoomFract, true, true)); // bTotalSize
+
+ pColIter->setValue(nNewWidth);
+ aScope.setNeedMore(true);
+ }
+ pColIter->next();
+ }
+ else
+ {
+ // No more cell in this column. Move to the left column and start at row 0.
+
+ bool bNewTab = false;
+
+ aScope.setRow(0);
+ aScope.incCol(-1);
+
+ if (aScope.Col() < 0)
+ {
+ // No more column to the left. Move to the right-most column of the next sheet.
+ aScope.setCol(MaxCol());
+ aScope.incTab();
+ bNewTab = true;
+ }
+
+ if (!ValidTab(aScope.Tab()) || aScope.Tab() >= static_cast<SCTAB>(maTabs.size()) || !maTabs[aScope.Tab()] )
+ {
+ // Sheet doesn't exist at specified sheet position. Restart at sheet 0.
+ aScope.setTab(0);
+ nRestart++;
+ bNewTab = true;
+ }
+
+ if ( nRestart < 2 )
+ {
+ if ( bNewTab )
+ {
+ pTab = maTabs[aScope.Tab()].get();
+ aScope.setCol(pTab->ClampToAllocatedColumns(aScope.Col()));
+ pStyle = static_cast<ScStyleSheet*>(aScope.getStylePool()->Find(
+ pTab->aPageStyle, SfxStyleFamily::Page));
+
+ if ( pStyle )
+ {
+ // Check if the scale-to-pages setting is set. If
+ // set, we exit the loop. If not, get the page
+ // scale factor of the new sheet.
+ if (getScaleValue(*pStyle, ATTR_PAGE_SCALETOPAGES) == 0)
+ {
+ nZoom = getScaleValue(*pStyle, ATTR_PAGE_SCALE);
+ aZoomFract = Fraction(nZoom, 100);
+ }
+ else
+ nZoom = 0;
+ }
+ else
+ {
+ OSL_FAIL( "Missing StyleSheet :-/" );
+ }
+ }
+
+ if ( nZoom > 0 )
+ {
+ pCol = &pTab->aCol[aScope.Col()];
+ pColIter.emplace(*this, *pCol, aScope.Row(), MaxRow());
+ }
+ else
+ {
+ aScope.incTab(); // Move to the next sheet as the current one has scale-to-pages set.
+ return false;
+ }
+ }
+ }
+
+ ++nCount;
+
+ if (!aScope.continueIter())
+ break;
+ }
+
+ return aScope.getNeedMore();
+}
+
+void ScDocument::RepaintRange( const ScRange& rRange )
+{
+ if ( bIsVisible && mpShell )
+ {
+ ScModelObj* pModel = comphelper::getFromUnoTunnel<ScModelObj>( mpShell->GetModel() );
+ if ( pModel )
+ pModel->RepaintRange( rRange ); // locked repaints are checked there
+ }
+}
+
+void ScDocument::RepaintRange( const ScRangeList& rRange )
+{
+ if ( bIsVisible && mpShell )
+ {
+ ScModelObj* pModel = comphelper::getFromUnoTunnel<ScModelObj>( mpShell->GetModel() );
+ if ( pModel )
+ pModel->RepaintRange( rRange ); // locked repaints are checked there
+ }
+}
+
+void ScDocument::SaveDdeLinks(SvStream& rStream) const
+{
+ // when 4.0-Export, remove all with mode != DEFAULT
+ bool bExport40 = ( rStream.GetVersion() <= SOFFICE_FILEFORMAT_40 );
+
+ const ::sfx2::SvBaseLinks& rLinks = GetLinkManager()->GetLinks();
+ sal_uInt16 nCount = rLinks.size();
+
+ // Count them first
+
+ sal_uInt16 nDdeCount = 0;
+ sal_uInt16 i;
+ for (i=0; i<nCount; i++)
+ {
+ ::sfx2::SvBaseLink* pBase = rLinks[i].get();
+ if (ScDdeLink* pLink = dynamic_cast<ScDdeLink*>(pBase))
+ if ( !bExport40 || pLink->GetMode() == SC_DDE_DEFAULT )
+ ++nDdeCount;
+ }
+
+ // Header
+
+ ScMultipleWriteHeader aHdr( rStream );
+ rStream.WriteUInt16( nDdeCount );
+
+ // Save links
+
+ for (i=0; i<nCount; i++)
+ {
+ ::sfx2::SvBaseLink* pBase = rLinks[i].get();
+ if (ScDdeLink* pLink = dynamic_cast<ScDdeLink*>(pBase))
+ {
+ if ( !bExport40 || pLink->GetMode() == SC_DDE_DEFAULT )
+ pLink->Store( rStream, aHdr );
+ }
+ }
+}
+
+void ScDocument::LoadDdeLinks(SvStream& rStream)
+{
+ sfx2::LinkManager* pMgr = GetDocLinkManager().getLinkManager(bAutoCalc);
+ if (!pMgr)
+ return;
+
+ ScMultipleReadHeader aHdr( rStream );
+
+ sal_uInt16 nCount(0);
+ rStream.ReadUInt16( nCount );
+
+ const rtl_TextEncoding eCharSet = rStream.GetStreamCharSet();
+ const size_t nMinStringSize = eCharSet == RTL_TEXTENCODING_UNICODE ? sizeof(sal_uInt32) : sizeof(sal_uInt16);
+ const size_t nMinRecordSize = 1 + nMinStringSize*3;
+ const size_t nMaxRecords = rStream.remainingSize() / nMinRecordSize;
+ if (nCount > nMaxRecords)
+ {
+ SAL_WARN("sc", "Parsing error: " << nMaxRecords <<
+ " max possible entries, but " << nCount << " claimed, truncating");
+ nCount = nMaxRecords;
+ }
+
+ for (sal_uInt16 i=0; i<nCount; ++i)
+ {
+ ScDdeLink* pLink = new ScDdeLink( *this, rStream, aHdr );
+ pMgr->InsertDDELink(pLink, pLink->GetAppl(), pLink->GetTopic(), pLink->GetItem());
+ }
+}
+
+void ScDocument::SetInLinkUpdate(bool bSet)
+{
+ // called from TableLink and AreaLink
+
+ OSL_ENSURE( bInLinkUpdate != bSet, "SetInLinkUpdate twice" );
+ bInLinkUpdate = bSet;
+}
+
+bool ScDocument::IsInLinkUpdate() const
+{
+ return bInLinkUpdate || IsInDdeLinkUpdate();
+}
+
+void ScDocument::UpdateExternalRefLinks(weld::Window* pWin)
+{
+ if (!pExternalRefMgr)
+ return;
+
+ sfx2::LinkManager* pMgr = GetDocLinkManager().getLinkManager(bAutoCalc);
+ if (!pMgr)
+ return;
+
+ const ::sfx2::SvBaseLinks& rLinks = pMgr->GetLinks();
+ sal_uInt16 nCount = rLinks.size();
+
+ bool bAny = false;
+
+ // Collect all the external ref links first.
+ std::vector<ScExternalRefLink*> aRefLinks;
+ for (sal_uInt16 i = 0; i < nCount; ++i)
+ {
+ ::sfx2::SvBaseLink* pBase = rLinks[i].get();
+ ScExternalRefLink* pRefLink = dynamic_cast<ScExternalRefLink*>(pBase);
+ if (pRefLink)
+ aRefLinks.push_back(pRefLink);
+ }
+
+ weld::WaitObject aWaitSwitch(pWin);
+
+ pExternalRefMgr->enableDocTimer(false);
+ ScProgress aProgress(GetDocumentShell(), ScResId(SCSTR_UPDATE_EXTDOCS), aRefLinks.size(), true);
+ for (size_t i = 0, n = aRefLinks.size(); i < n; ++i)
+ {
+ aProgress.SetState(i+1);
+
+ ScExternalRefLink* pRefLink = aRefLinks[i];
+ if (pRefLink->Update())
+ {
+ bAny = true;
+ continue;
+ }
+
+ // Update failed. Notify the user.
+
+ OUString aFile;
+ sfx2::LinkManager::GetDisplayNames(pRefLink, nullptr, &aFile);
+ // Decode encoded URL for display friendliness.
+ INetURLObject aUrl(aFile,INetURLObject::EncodeMechanism::WasEncoded);
+ aFile = aUrl.GetMainURL(INetURLObject::DecodeMechanism::Unambiguous);
+
+ OUString sMessage = ScResId(SCSTR_EXTDOC_NOT_LOADED) +
+ "\n\n" +
+ aFile;
+ std::unique_ptr<weld::MessageDialog> xBox(Application::CreateMessageDialog(pWin,
+ VclMessageType::Warning, VclButtonsType::Ok,
+ sMessage));
+ xBox->run();
+ }
+
+ pExternalRefMgr->enableDocTimer(true);
+
+ if (!bAny)
+ return;
+
+ TrackFormulas();
+ mpShell->Broadcast( SfxHint(SfxHintId::ScDataChanged) );
+
+ // #i101960# set document modified, as in TrackTimeHdl for DDE links
+ if (!mpShell->IsModified())
+ {
+ mpShell->SetModified();
+ SfxBindings* pBindings = GetViewBindings();
+ if (pBindings)
+ {
+ pBindings->Invalidate( SID_SAVEDOC );
+ pBindings->Invalidate( SID_DOC_MODIFIED );
+ }
+ }
+}
+
+void ScDocument::CopyDdeLinks( ScDocument& rDestDoc ) const
+{
+ if (bIsClip) // Create from Stream
+ {
+ if (pClipData)
+ {
+ pClipData->Seek(0);
+ rDestDoc.LoadDdeLinks(*pClipData);
+ }
+
+ return;
+ }
+
+ const sfx2::LinkManager* pMgr = GetDocLinkManager().getExistingLinkManager();
+ if (!pMgr)
+ return;
+
+ sfx2::LinkManager* pDestMgr = rDestDoc.GetDocLinkManager().getLinkManager(rDestDoc.bAutoCalc);
+ if (!pDestMgr)
+ return;
+
+ const sfx2::SvBaseLinks& rLinks = pMgr->GetLinks();
+ for (const auto & rLink : rLinks)
+ {
+ const sfx2::SvBaseLink* pBase = rLink.get();
+ if (const ScDdeLink* p = dynamic_cast<const ScDdeLink*>(pBase))
+ {
+ ScDdeLink* pNew = new ScDdeLink(rDestDoc, *p);
+ pDestMgr->InsertDDELink(
+ pNew, pNew->GetAppl(), pNew->GetTopic(), pNew->GetItem());
+ }
+ }
+}
+
+namespace {
+
+/** Tries to find the specified DDE link.
+ @param pnDdePos (out-param) if not 0, the index of the DDE link is returned here
+ (does not include other links from link manager).
+ @return The DDE link, if it exists, otherwise 0. */
+ScDdeLink* lclGetDdeLink(
+ const sfx2::LinkManager* pLinkManager,
+ std::u16string_view rAppl, std::u16string_view rTopic, std::u16string_view rItem, sal_uInt8 nMode,
+ size_t* pnDdePos = nullptr )
+{
+ if( pLinkManager )
+ {
+ const ::sfx2::SvBaseLinks& rLinks = pLinkManager->GetLinks();
+ size_t nCount = rLinks.size();
+ if( pnDdePos ) *pnDdePos = 0;
+ for( size_t nIndex = 0; nIndex < nCount; ++nIndex )
+ {
+ ::sfx2::SvBaseLink* pLink = rLinks[ nIndex ].get();
+ if( ScDdeLink* pDdeLink = dynamic_cast<ScDdeLink*>( pLink ) )
+ {
+ if( (pDdeLink->GetAppl() == rAppl) &&
+ (pDdeLink->GetTopic() == rTopic) &&
+ (pDdeLink->GetItem() == rItem) &&
+ ((nMode == SC_DDE_IGNOREMODE) || (nMode == pDdeLink->GetMode())) )
+ return pDdeLink;
+ if( pnDdePos ) ++*pnDdePos;
+ }
+ }
+ }
+ return nullptr;
+}
+
+/** Returns a pointer to the specified DDE link.
+ @param nDdePos Index of the DDE link (does not include other links from link manager).
+ @return The DDE link, if it exists, otherwise 0. */
+ScDdeLink* lclGetDdeLink( const sfx2::LinkManager* pLinkManager, size_t nDdePos )
+{
+ if( pLinkManager )
+ {
+ const ::sfx2::SvBaseLinks& rLinks = pLinkManager->GetLinks();
+ size_t nCount = rLinks.size();
+ size_t nDdeIndex = 0; // counts only the DDE links
+ for( size_t nIndex = 0; nIndex < nCount; ++nIndex )
+ {
+ ::sfx2::SvBaseLink* pLink = rLinks[ nIndex ].get();
+ if( ScDdeLink* pDdeLink = dynamic_cast<ScDdeLink*>( pLink ) )
+ {
+ if( nDdeIndex == nDdePos )
+ return pDdeLink;
+ ++nDdeIndex;
+ }
+ }
+ }
+ return nullptr;
+}
+
+} // namespace
+
+bool ScDocument::FindDdeLink( std::u16string_view rAppl, std::u16string_view rTopic, std::u16string_view rItem,
+ sal_uInt8 nMode, size_t& rnDdePos )
+{
+ return lclGetDdeLink( GetLinkManager(), rAppl, rTopic, rItem, nMode, &rnDdePos ) != nullptr;
+}
+
+bool ScDocument::GetDdeLinkData( size_t nDdePos, OUString& rAppl, OUString& rTopic, OUString& rItem ) const
+{
+ if( const ScDdeLink* pDdeLink = lclGetDdeLink( GetLinkManager(), nDdePos ) )
+ {
+ rAppl = pDdeLink->GetAppl();
+ rTopic = pDdeLink->GetTopic();
+ rItem = pDdeLink->GetItem();
+ return true;
+ }
+ return false;
+}
+
+bool ScDocument::GetDdeLinkMode( size_t nDdePos, sal_uInt8& rnMode ) const
+{
+ if( const ScDdeLink* pDdeLink = lclGetDdeLink( GetLinkManager(), nDdePos ) )
+ {
+ rnMode = pDdeLink->GetMode();
+ return true;
+ }
+ return false;
+}
+
+const ScMatrix* ScDocument::GetDdeLinkResultMatrix( size_t nDdePos ) const
+{
+ const ScDdeLink* pDdeLink = lclGetDdeLink( GetLinkManager(), nDdePos );
+ return pDdeLink ? pDdeLink->GetResult() : nullptr;
+}
+
+bool ScDocument::CreateDdeLink( const OUString& rAppl, const OUString& rTopic, const OUString& rItem, sal_uInt8 nMode, const ScMatrixRef& pResults )
+{
+ /* Create a DDE link without updating it (i.e. for Excel import), to prevent
+ unwanted connections. First try to find existing link. Set result array
+ on existing and new links. */
+ //TODO: store DDE links additionally at document (for efficiency)?
+ OSL_ENSURE( nMode != SC_DDE_IGNOREMODE, "ScDocument::CreateDdeLink - SC_DDE_IGNOREMODE not allowed here" );
+
+ sfx2::LinkManager* pMgr = GetDocLinkManager().getLinkManager(bAutoCalc);
+ if (!pMgr)
+ return false;
+
+ if (nMode != SC_DDE_IGNOREMODE)
+ {
+ ScDdeLink* pDdeLink = lclGetDdeLink(pMgr, rAppl, rTopic, rItem, nMode);
+ if( !pDdeLink )
+ {
+ // create a new DDE link, but without TryUpdate
+ pDdeLink = new ScDdeLink( *this, rAppl, rTopic, rItem, nMode );
+ pMgr->InsertDDELink(pDdeLink, rAppl, rTopic, rItem);
+ }
+
+ // insert link results
+ if( pResults )
+ pDdeLink->SetResult( pResults );
+
+ return true;
+ }
+ return false;
+}
+
+bool ScDocument::SetDdeLinkResultMatrix( size_t nDdePos, const ScMatrixRef& pResults )
+{
+ if( ScDdeLink* pDdeLink = lclGetDdeLink( GetLinkManager(), nDdePos ) )
+ {
+ pDdeLink->SetResult( pResults );
+ return true;
+ }
+ return false;
+}
+
+bool ScDocument::HasAreaLinks() const
+{
+ const sfx2::LinkManager* pMgr = GetDocLinkManager().getExistingLinkManager();
+ if (!pMgr)
+ return false;
+
+ const ::sfx2::SvBaseLinks& rLinks = pMgr->GetLinks();
+ sal_uInt16 nCount = rLinks.size();
+ for (sal_uInt16 i=0; i<nCount; i++)
+ if (nullptr != dynamic_cast<const ScAreaLink* >(rLinks[i].get()))
+ return true;
+
+ return false;
+}
+
+void ScDocument::UpdateAreaLinks()
+{
+ sfx2::LinkManager* pMgr = GetDocLinkManager().getLinkManager(false);
+ if (!pMgr)
+ return;
+
+ const ::sfx2::SvBaseLinks& rLinks = pMgr->GetLinks();
+ for (const auto & rLink : rLinks)
+ {
+ ::sfx2::SvBaseLink* pBase = rLink.get();
+ if (dynamic_cast<const ScAreaLink*>( pBase) != nullptr)
+ pBase->Update();
+ }
+}
+
+void ScDocument::DeleteAreaLinksOnTab( SCTAB nTab )
+{
+ sfx2::LinkManager* pMgr = GetDocLinkManager().getLinkManager(false);
+ if (!pMgr)
+ return;
+
+ const ::sfx2::SvBaseLinks& rLinks = pMgr->GetLinks();
+ sfx2::SvBaseLinks::size_type nPos = 0;
+ while ( nPos < rLinks.size() )
+ {
+ const ::sfx2::SvBaseLink* pBase = rLinks[nPos].get();
+ const ScAreaLink* pLink = dynamic_cast<const ScAreaLink*>(pBase);
+ if (pLink && pLink->GetDestArea().aStart.Tab() == nTab)
+ pMgr->Remove(nPos);
+ else
+ ++nPos;
+ }
+}
+
+void ScDocument::UpdateRefAreaLinks( UpdateRefMode eUpdateRefMode,
+ const ScRange& rRange, SCCOL nDx, SCROW nDy, SCTAB nDz )
+{
+ sfx2::LinkManager* pMgr = GetDocLinkManager().getLinkManager(false);
+ if (!pMgr)
+ return;
+
+ bool bAnyUpdate = false;
+
+ const ::sfx2::SvBaseLinks& rLinks = pMgr->GetLinks();
+ sal_uInt16 nCount = rLinks.size();
+ for (sal_uInt16 i=0; i<nCount; i++)
+ {
+ ::sfx2::SvBaseLink* pBase = rLinks[i].get();
+ if (ScAreaLink* pLink = dynamic_cast<ScAreaLink*>(pBase))
+ {
+ ScRange aOutRange = pLink->GetDestArea();
+
+ SCCOL nCol1 = aOutRange.aStart.Col();
+ SCROW nRow1 = aOutRange.aStart.Row();
+ SCTAB nTab1 = aOutRange.aStart.Tab();
+ SCCOL nCol2 = aOutRange.aEnd.Col();
+ SCROW nRow2 = aOutRange.aEnd.Row();
+ SCTAB nTab2 = aOutRange.aEnd.Tab();
+
+ ScRefUpdateRes eRes =
+ ScRefUpdate::Update( this, eUpdateRefMode,
+ rRange.aStart.Col(), rRange.aStart.Row(), rRange.aStart.Tab(),
+ rRange.aEnd.Col(), rRange.aEnd.Row(), rRange.aEnd.Tab(), nDx, nDy, nDz,
+ nCol1, nRow1, nTab1, nCol2, nRow2, nTab2 );
+ if ( eRes != UR_NOTHING )
+ {
+ pLink->SetDestArea( ScRange( nCol1, nRow1, nTab1, nCol2, nRow2, nTab2 ) );
+ bAnyUpdate = true;
+ }
+ }
+ }
+
+ if ( !bAnyUpdate )
+ return;
+
+ // #i52120# Look for duplicates (after updating all positions).
+ // If several links start at the same cell, the one with the lower index is removed
+ // (file format specifies only one link definition for a cell).
+
+ sal_uInt16 nFirstIndex = 0;
+ while ( nFirstIndex < nCount )
+ {
+ bool bFound = false;
+ ::sfx2::SvBaseLink* pFirst = rLinks[nFirstIndex].get();
+ if (ScAreaLink* pFirstLink = dynamic_cast<ScAreaLink*>(pFirst))
+ {
+ ScAddress aFirstPos = pFirstLink->GetDestArea().aStart;
+ for ( sal_uInt16 nSecondIndex = nFirstIndex + 1; nSecondIndex < nCount && !bFound; ++nSecondIndex )
+ {
+ ::sfx2::SvBaseLink* pSecond = rLinks[nSecondIndex].get();
+ ScAreaLink* pSecondLink = dynamic_cast<ScAreaLink*>(pSecond);
+ if (pSecondLink && pSecondLink->GetDestArea().aStart == aFirstPos)
+ {
+ // remove the first link, exit the inner loop, don't increment nFirstIndex
+ pMgr->Remove(pFirst);
+ nCount = rLinks.size();
+ bFound = true;
+ }
+ }
+ }
+ if (!bFound)
+ ++nFirstIndex;
+ }
+}
+
+void ScDocument::CheckLinkFormulaNeedingCheck( const ScTokenArray& rCode )
+{
+ if (HasLinkFormulaNeedingCheck())
+ return;
+
+ // Prefer RPN over tokenized formula if available.
+ if (rCode.GetCodeLen())
+ {
+ if (rCode.HasOpCodeRPN(ocDde) || rCode.HasOpCodeRPN(ocWebservice))
+ SetLinkFormulaNeedingCheck(true);
+ }
+ else if (rCode.GetLen())
+ {
+ if (rCode.HasOpCode(ocDde) || rCode.HasOpCode(ocWebservice))
+ SetLinkFormulaNeedingCheck(true);
+ }
+ else
+ {
+ // Possible with named expression without expression like Excel
+ // internal print ranges, obscure user define names, ... formula error
+ // cells without formula ...
+ SAL_WARN("sc.core","ScDocument::CheckLinkFormulaNeedingCheck - called with empty ScTokenArray");
+ }
+}
+
+// TimerDelays etc.
+void ScDocument::KeyInput()
+{
+ if ( pChartListenerCollection->hasListeners() )
+ pChartListenerCollection->StartTimer();
+ if (apTemporaryChartLock)
+ apTemporaryChartLock->StartOrContinueLocking();
+}
+
+SfxBindings* ScDocument::GetViewBindings()
+{
+ // used to invalidate slots after changes to this document
+
+ if ( !mpShell )
+ return nullptr; // no ObjShell -> no view
+
+ // first check current view
+ SfxViewFrame* pViewFrame = SfxViewFrame::Current();
+ if ( pViewFrame && pViewFrame->GetObjectShell() != mpShell ) // wrong document?
+ pViewFrame = nullptr;
+
+ // otherwise use first view for this doc
+ if ( !pViewFrame )
+ pViewFrame = SfxViewFrame::GetFirst( mpShell );
+
+ if (pViewFrame)
+ return &pViewFrame->GetBindings();
+ else
+ return nullptr;
+}
+
+void ScDocument::TransliterateText( const ScMarkData& rMultiMark, TransliterationFlags nType )
+{
+ OSL_ENSURE( rMultiMark.IsMultiMarked(), "TransliterateText: no selection" );
+
+ utl::TransliterationWrapper aTransliterationWrapper( comphelper::getProcessComponentContext(), nType );
+ bool bConsiderLanguage = aTransliterationWrapper.needLanguageForTheMode();
+ LanguageType nLanguage = LANGUAGE_SYSTEM;
+
+ std::unique_ptr<ScEditEngineDefaulter> pEngine; // not using mpEditEngine member because of defaults
+
+ SCTAB nCount = GetTableCount();
+ for (const SCTAB& nTab : rMultiMark)
+ {
+ if (nTab >= nCount)
+ break;
+
+ if ( maTabs[nTab] )
+ {
+ SCCOL nCol = 0;
+ SCROW nRow = 0;
+
+ bool bFound = rMultiMark.IsCellMarked( nCol, nRow );
+ if (!bFound)
+ bFound = GetNextMarkedCell( nCol, nRow, nTab, rMultiMark );
+
+ while (bFound)
+ {
+ ScRefCellValue aCell(*this, ScAddress(nCol, nRow, nTab));
+
+ // fdo#32786 TITLE_CASE/SENTENCE_CASE need the extra handling in EditEngine (loop over words/sentences).
+ // Still use TransliterationWrapper directly for text cells with other transliteration types,
+ // for performance reasons.
+ if (aCell.meType == CELLTYPE_EDIT ||
+ (aCell.meType == CELLTYPE_STRING &&
+ ( nType == TransliterationFlags::SENTENCE_CASE || nType == TransliterationFlags::TITLE_CASE)))
+ {
+ if (!pEngine)
+ pEngine.reset(new ScFieldEditEngine(this, GetEnginePool(), GetEditPool()));
+
+ // defaults from cell attributes must be set so right language is used
+ const ScPatternAttr* pPattern = GetPattern( nCol, nRow, nTab );
+ SfxItemSet aDefaults( pEngine->GetEmptyItemSet() );
+ if ( ScStyleSheet* pPreviewStyle = GetPreviewCellStyle( nCol, nRow, nTab ) )
+ {
+ ScPatternAttr aPreviewPattern( *pPattern );
+ aPreviewPattern.SetStyleSheet(pPreviewStyle);
+ aPreviewPattern.FillEditItemSet( &aDefaults );
+ }
+ else
+ {
+ SfxItemSet* pFontSet = GetPreviewFont( nCol, nRow, nTab );
+ pPattern->FillEditItemSet( &aDefaults, pFontSet );
+ }
+ pEngine->SetDefaults( std::move(aDefaults) );
+ if (aCell.meType == CELLTYPE_STRING)
+ pEngine->SetTextCurrentDefaults(aCell.mpString->getString());
+ else if (aCell.mpEditText)
+ pEngine->SetTextCurrentDefaults(*aCell.mpEditText);
+
+ pEngine->ClearModifyFlag();
+
+ sal_Int32 nLastPar = pEngine->GetParagraphCount();
+ if (nLastPar)
+ --nLastPar;
+ sal_Int32 nTxtLen = pEngine->GetTextLen(nLastPar);
+ ESelection aSelAll( 0, 0, nLastPar, nTxtLen );
+
+ pEngine->TransliterateText( aSelAll, nType );
+
+ if ( pEngine->IsModified() )
+ {
+ ScEditAttrTester aTester( pEngine.get() );
+ if ( aTester.NeedsObject() )
+ {
+ // remove defaults (paragraph attributes) before creating text object
+ pEngine->SetDefaults( std::make_unique<SfxItemSet>( pEngine->GetEmptyItemSet() ) );
+
+ // The cell will take ownership of the text object instance.
+ SetEditText(ScAddress(nCol,nRow,nTab), pEngine->CreateTextObject());
+ }
+ else
+ {
+ ScSetStringParam aParam;
+ aParam.setTextInput();
+ SetString(ScAddress(nCol,nRow,nTab), pEngine->GetText(), &aParam);
+ }
+ }
+ }
+
+ else if (aCell.meType == CELLTYPE_STRING)
+ {
+ OUString aOldStr = aCell.mpString->getString();
+ sal_Int32 nOldLen = aOldStr.getLength();
+
+ if ( bConsiderLanguage )
+ {
+ SvtScriptType nScript = GetStringScriptType( aOldStr ); //TODO: cell script type?
+ sal_uInt16 nWhich = ( nScript == SvtScriptType::ASIAN ) ? ATTR_CJK_FONT_LANGUAGE :
+ ( ( nScript == SvtScriptType::COMPLEX ) ? ATTR_CTL_FONT_LANGUAGE :
+ ATTR_FONT_LANGUAGE );
+ nLanguage = static_cast<const SvxLanguageItem*>(GetAttr( nCol, nRow, nTab, nWhich ))->GetValue();
+ }
+
+ uno::Sequence<sal_Int32> aOffsets;
+ OUString aNewStr = aTransliterationWrapper.transliterate( aOldStr, nLanguage, 0, nOldLen, &aOffsets );
+
+ if ( aNewStr != aOldStr )
+ {
+ ScSetStringParam aParam;
+ aParam.setTextInput();
+ SetString(ScAddress(nCol,nRow,nTab), aNewStr, &aParam);
+ }
+ }
+ bFound = GetNextMarkedCell( nCol, nRow, nTab, rMultiMark );
+ }
+ }
+ }
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sc/source/core/data/documen9.cxx b/sc/source/core/data/documen9.cxx
new file mode 100644
index 000000000..28f0e2333
--- /dev/null
+++ b/sc/source/core/data/documen9.cxx
@@ -0,0 +1,681 @@
+/* -*- 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 <scitems.hxx>
+#include <editeng/eeitem.hxx>
+
+#include <editeng/autokernitem.hxx>
+#include <editeng/fontitem.hxx>
+#include <editeng/langitem.hxx>
+#include <o3tl/unit_conversion.hxx>
+#include <osl/thread.h>
+#include <osl/diagnose.h>
+#include <svl/asiancfg.hxx>
+#include <svx/svditer.hxx>
+#include <svx/svdograf.hxx>
+#include <svx/svdoole2.hxx>
+#include <svx/svdpage.hxx>
+#include <svx/svdundo.hxx>
+#include <svx/xtable.hxx>
+#include <sfx2/objsh.hxx>
+#include <sfx2/printer.hxx>
+
+#include <document.hxx>
+#include <docoptio.hxx>
+#include <table.hxx>
+#include <drwlayer.hxx>
+#include <markdata.hxx>
+#include <patattr.hxx>
+#include <rechead.hxx>
+#include <poolhelp.hxx>
+#include <docpool.hxx>
+#include <editutil.hxx>
+#include <charthelper.hxx>
+#include <conditio.hxx>
+#include <documentlinkmgr.hxx>
+
+using namespace ::com::sun::star;
+
+SfxBroadcaster* ScDocument::GetDrawBroadcaster()
+{
+ return mpDrawLayer.get();
+}
+
+void ScDocument::BeginDrawUndo()
+{
+ if (mpDrawLayer)
+ mpDrawLayer->BeginCalcUndo(false);
+}
+
+void ScDocument::TransferDrawPage(const ScDocument& rSrcDoc, SCTAB nSrcPos, SCTAB nDestPos)
+{
+ if (mpDrawLayer && rSrcDoc.mpDrawLayer)
+ {
+ SdrPage* pOldPage = rSrcDoc.mpDrawLayer->GetPage(static_cast<sal_uInt16>(nSrcPos));
+ SdrPage* pNewPage = mpDrawLayer->GetPage(static_cast<sal_uInt16>(nDestPos));
+
+ if (pOldPage && pNewPage)
+ {
+ SdrObjListIter aIter( pOldPage, SdrIterMode::Flat );
+ SdrObject* pOldObject = aIter.Next();
+ while (pOldObject)
+ {
+ // Clone to target SdrModel
+ SdrObject* pNewObject(pOldObject->CloneSdrObject(*mpDrawLayer));
+ pNewObject->NbcMove(Size(0,0));
+ pNewPage->InsertObject( pNewObject );
+
+ if (mpDrawLayer->IsRecording())
+ mpDrawLayer->AddCalcUndo( std::make_unique<SdrUndoInsertObj>( *pNewObject ) );
+
+ pOldObject = aIter.Next();
+ }
+ }
+ }
+
+ // make sure the data references of charts are adapted
+ // (this must be after InsertObject!)
+ ScChartHelper::AdjustRangesOfChartsOnDestinationPage( rSrcDoc, *this, nSrcPos, nDestPos );
+ ScChartHelper::UpdateChartsOnDestinationPage(*this, nDestPos);
+}
+
+void ScDocument::InitDrawLayer( SfxObjectShell* pDocShell )
+{
+ if (pDocShell && !mpShell)
+ {
+ ScMutationGuard aGuard(*this, ScMutationGuardFlags::CORE);
+ mpShell = pDocShell;
+ }
+
+ if (mpDrawLayer)
+ return;
+
+ ScMutationGuard aGuard(*this, ScMutationGuardFlags::CORE);
+ OUString aName;
+ if ( mpShell && !mpShell->IsLoading() ) // don't call GetTitle while loading
+ aName = mpShell->GetTitle();
+ mpDrawLayer.reset(new ScDrawLayer( this, aName ));
+
+ sfx2::LinkManager* pMgr = GetDocLinkManager().getLinkManager(bAutoCalc);
+ if (pMgr)
+ mpDrawLayer->SetLinkManager(pMgr);
+
+ // set DrawingLayer's SfxItemPool at Calc's SfxItemPool as
+ // secondary pool to support DrawingLayer FillStyle ranges (and similar)
+ // in SfxItemSets using the Calc SfxItemPool. This is e.g. needed when
+ // the PageStyle using SvxBrushItem is visualized and will be potentially
+ // used more intense in the future
+ if (mxPoolHelper.is() && !IsClipOrUndo()) //Using IsClipOrUndo as a proxy for SharePooledResources called
+ {
+ ScDocumentPool* pLocalPool = mxPoolHelper->GetDocPool();
+
+ if (pLocalPool)
+ {
+ OSL_ENSURE(!pLocalPool->GetSecondaryPool(), "OOps, already a secondary pool set where the DrawingLayer ItemPool is to be placed (!)");
+ pLocalPool->SetSecondaryPool(&mpDrawLayer->GetItemPool());
+ }
+ }
+
+ // Drawing pages are accessed by table number, so they must also be present
+ // for preceding table numbers, even if the tables aren't allocated
+ // (important for clipboard documents).
+
+ SCTAB nDrawPages = 0;
+ SCTAB nTab;
+ for (nTab=0; nTab < static_cast<SCTAB>(maTabs.size()); nTab++)
+ if (maTabs[nTab])
+ nDrawPages = nTab + 1; // needed number of pages
+
+ for (nTab=0; nTab<nDrawPages && nTab < static_cast<SCTAB>(maTabs.size()); nTab++)
+ {
+ mpDrawLayer->ScAddPage( nTab ); // always add page, with or without the table
+ if (maTabs[nTab])
+ {
+ OUString aTabName = maTabs[nTab]->GetName();
+ mpDrawLayer->ScRenamePage( nTab, aTabName );
+
+ maTabs[nTab]->SetDrawPageSize(false,false); // set the right size immediately
+ }
+ }
+
+ mpDrawLayer->SetDefaultTabulator( GetDocOptions().GetTabDistance() );
+
+ UpdateDrawPrinter();
+
+ // set draw defaults directly
+ SfxItemPool& rDrawPool = mpDrawLayer->GetItemPool();
+ rDrawPool.SetPoolDefaultItem( SvxAutoKernItem( true, EE_CHAR_PAIRKERNING ) );
+
+ UpdateDrawLanguages();
+ if (bImportingXML)
+ mpDrawLayer->EnableAdjust(false);
+
+ mpDrawLayer->SetForbiddenCharsTable( xForbiddenCharacters );
+ mpDrawLayer->SetCharCompressType( GetAsianCompression() );
+ mpDrawLayer->SetKernAsianPunctuation( GetAsianKerning() );
+}
+
+void ScDocument::UpdateDrawLanguages()
+{
+ if (mpDrawLayer)
+ {
+ SfxItemPool& rDrawPool = mpDrawLayer->GetItemPool();
+ rDrawPool.SetPoolDefaultItem( SvxLanguageItem( eLanguage, EE_CHAR_LANGUAGE ) );
+ rDrawPool.SetPoolDefaultItem( SvxLanguageItem( eCjkLanguage, EE_CHAR_LANGUAGE_CJK ) );
+ rDrawPool.SetPoolDefaultItem( SvxLanguageItem( eCtlLanguage, EE_CHAR_LANGUAGE_CTL ) );
+ }
+}
+
+void ScDocument::UpdateDrawPrinter()
+{
+ if (mpDrawLayer)
+ {
+ // use the printer even if IsValid is false
+ // Application::GetDefaultDevice causes trouble with changing MapModes
+ mpDrawLayer->SetRefDevice(GetRefDevice());
+ }
+}
+
+void ScDocument::SetDrawPageSize(SCTAB nTab)
+{
+ if (!ValidTab(nTab) || nTab >= static_cast<SCTAB>(maTabs.size()) || !maTabs[nTab])
+ return;
+
+ maTabs[nTab]->SetDrawPageSize();
+}
+
+bool ScDocument::IsChart( const SdrObject* pObject )
+{
+ // IsChart() implementation moved to svx drawinglayer
+ if(pObject && SdrObjKind::OLE2 == pObject->GetObjIdentifier())
+ {
+ return static_cast<const SdrOle2Obj*>(pObject)->IsChart();
+ }
+
+ return false;
+}
+
+IMPL_LINK( ScDocument, GetUserDefinedColor, sal_uInt16, nColorIndex, Color* )
+{
+ rtl::Reference<XColorList> xColorList;
+ if (mpDrawLayer)
+ xColorList = mpDrawLayer->GetColorList();
+ else
+ {
+ ScMutationGuard aGuard(*this, ScMutationGuardFlags::CORE);
+ if (!pColorList.is())
+ pColorList = XColorList::CreateStdColorList();
+ xColorList = pColorList;
+ }
+ return const_cast<Color*>(&(xColorList->GetColor(nColorIndex)->GetColor()));
+}
+
+void ScDocument::DeleteDrawLayer()
+{
+ ScMutationGuard aGuard(*this, ScMutationGuardFlags::CORE);
+
+ // remove DrawingLayer's SfxItemPool from Calc's SfxItemPool where
+ // it is registered as secondary pool
+ if (mxPoolHelper.is() && !IsClipOrUndo()) //Using IsClipOrUndo as a proxy for SharePooledResources called
+ {
+ ScDocumentPool* pLocalPool = mxPoolHelper->GetDocPool();
+
+ if(pLocalPool && pLocalPool->GetSecondaryPool())
+ {
+ pLocalPool->SetSecondaryPool(nullptr);
+ }
+ }
+ mpDrawLayer.reset();
+}
+
+bool ScDocument::DrawGetPrintArea( ScRange& rRange, bool bSetHor, bool bSetVer ) const
+{
+ return mpDrawLayer->GetPrintArea( rRange, bSetHor, bSetVer );
+}
+
+void ScDocument::DeleteObjectsInArea( SCCOL nCol1, SCROW nRow1, SCCOL nCol2, SCROW nRow2,
+ const ScMarkData& rMark, bool bAnchored)
+{
+ if (!mpDrawLayer)
+ return;
+
+ SCTAB nTabCount = GetTableCount();
+ for (const auto& rTab : rMark)
+ {
+ if (rTab >= nTabCount)
+ break;
+ if (maTabs[rTab])
+ mpDrawLayer->DeleteObjectsInArea( rTab, nCol1, nRow1, nCol2, nRow2, bAnchored);
+ }
+}
+
+void ScDocument::DeleteObjectsInSelection( const ScMarkData& rMark )
+{
+ if (!mpDrawLayer)
+ return;
+
+ mpDrawLayer->DeleteObjectsInSelection( rMark );
+}
+
+bool ScDocument::HasOLEObjectsInArea( const ScRange& rRange, const ScMarkData* pTabMark )
+{
+ // pTabMark is used only for selected tables. If pTabMark is 0, all tables of rRange are used.
+
+ if (!mpDrawLayer)
+ return false;
+
+ SCTAB nStartTab = 0;
+ SCTAB nEndTab = static_cast<SCTAB>(maTabs.size());
+ if ( !pTabMark )
+ {
+ nStartTab = rRange.aStart.Tab();
+ nEndTab = rRange.aEnd.Tab();
+ }
+
+ for (SCTAB nTab = nStartTab; nTab <= nEndTab; nTab++)
+ {
+ if ( !pTabMark || pTabMark->GetTableSelect(nTab) )
+ {
+ tools::Rectangle aMMRect = GetMMRect( rRange.aStart.Col(), rRange.aStart.Row(),
+ rRange.aEnd.Col(), rRange.aEnd.Row(), nTab );
+
+ SdrPage* pPage = mpDrawLayer->GetPage(static_cast<sal_uInt16>(nTab));
+ OSL_ENSURE(pPage,"Page ?");
+ if (pPage)
+ {
+ SdrObjListIter aIter( pPage, SdrIterMode::Flat );
+ SdrObject* pObject = aIter.Next();
+ while (pObject)
+ {
+ if ( pObject->GetObjIdentifier() == SdrObjKind::OLE2 &&
+ aMMRect.Contains( pObject->GetCurrentBoundRect() ) )
+ return true;
+
+ pObject = aIter.Next();
+ }
+ }
+ }
+ }
+
+ return false;
+}
+
+void ScDocument::StartAnimations( SCTAB nTab )
+{
+ if (!mpDrawLayer)
+ return;
+ SdrPage* pPage = mpDrawLayer->GetPage(static_cast<sal_uInt16>(nTab));
+ OSL_ENSURE(pPage,"Page ?");
+ if (!pPage)
+ return;
+
+ SdrObjListIter aIter( pPage, SdrIterMode::Flat );
+ SdrObject* pObject = aIter.Next();
+ while (pObject)
+ {
+ if (SdrGrafObj* pGrafObj = dynamic_cast<SdrGrafObj*>(pObject))
+ {
+ if ( pGrafObj->IsAnimated() )
+ {
+ pGrafObj->StartAnimation();
+ }
+ }
+ pObject = aIter.Next();
+ }
+}
+
+bool ScDocument::HasBackgroundDraw( SCTAB nTab, const tools::Rectangle& rMMRect ) const
+{
+ // Are there objects in the background layer who are (partly) affected by rMMRect
+ // (for Drawing optimization, no deletion in front of the background
+ if (!mpDrawLayer)
+ return false;
+ SdrPage* pPage = mpDrawLayer->GetPage(static_cast<sal_uInt16>(nTab));
+ OSL_ENSURE(pPage,"Page ?");
+ if (!pPage)
+ return false;
+
+ bool bFound = false;
+
+ SdrObjListIter aIter( pPage, SdrIterMode::Flat );
+ SdrObject* pObject = aIter.Next();
+ while (pObject && !bFound)
+ {
+ if ( pObject->GetLayer() == SC_LAYER_BACK && pObject->GetCurrentBoundRect().Overlaps( rMMRect ) )
+ bFound = true;
+ pObject = aIter.Next();
+ }
+
+ return bFound;
+}
+
+bool ScDocument::HasAnyDraw( SCTAB nTab, const tools::Rectangle& rMMRect ) const
+{
+ // Are there any objects at all who are (partly) affected by rMMRect?
+ // (To detect blank pages when printing)
+ if (!mpDrawLayer)
+ return false;
+ SdrPage* pPage = mpDrawLayer->GetPage(static_cast<sal_uInt16>(nTab));
+ OSL_ENSURE(pPage,"Page ?");
+ if (!pPage)
+ return false;
+
+ bool bFound = false;
+
+ SdrObjListIter aIter( pPage, SdrIterMode::Flat );
+ SdrObject* pObject = aIter.Next();
+ while (pObject && !bFound)
+ {
+ if ( pObject->GetCurrentBoundRect().Overlaps( rMMRect ) )
+ bFound = true;
+ pObject = aIter.Next();
+ }
+
+ return bFound;
+}
+
+void ScDocument::EnsureGraphicNames()
+{
+ if (mpDrawLayer)
+ mpDrawLayer->EnsureGraphicNames();
+}
+
+SdrObject* ScDocument::GetObjectAtPoint( SCTAB nTab, const Point& rPos )
+{
+ // for Drag&Drop on draw object
+ SdrObject* pFound = nullptr;
+ if (mpDrawLayer && nTab < static_cast<SCTAB>(maTabs.size()) && maTabs[nTab])
+ {
+ SdrPage* pPage = mpDrawLayer->GetPage(static_cast<sal_uInt16>(nTab));
+ OSL_ENSURE(pPage,"Page ?");
+ if (pPage)
+ {
+ SdrObjListIter aIter( pPage, SdrIterMode::Flat );
+ SdrObject* pObject = aIter.Next();
+ while (pObject)
+ {
+ if ( pObject->GetCurrentBoundRect().Contains(rPos) )
+ {
+ // Intern is of no interest
+ // Only object form background layer, when no object form another layer is found
+ SdrLayerID nLayer = pObject->GetLayer();
+ if ( (nLayer != SC_LAYER_INTERN) && (nLayer != SC_LAYER_HIDDEN) )
+ {
+ if ( nLayer != SC_LAYER_BACK ||
+ !pFound || pFound->GetLayer() == SC_LAYER_BACK )
+ {
+ pFound = pObject;
+ }
+ }
+ }
+ // Continue search -> take last (on top) found object
+ pObject = aIter.Next();
+ }
+ }
+ }
+ return pFound;
+}
+
+bool ScDocument::IsPrintEmpty( SCCOL nStartCol, SCROW nStartRow,
+ SCCOL nEndCol, SCROW nEndRow,
+ SCTAB nTab, bool bLeftIsEmpty,
+ ScRange* pLastRange, tools::Rectangle* pLastMM ) const
+{
+ if (!IsBlockEmpty( nStartCol, nStartRow, nEndCol, nEndRow, nTab ))
+ return false;
+
+ if (HasAttrib(ScRange(nStartCol, nStartRow, nTab, nEndCol, nEndRow, nTab), HasAttrFlags::Lines))
+ // We want to print sheets with borders even if there is no cell content.
+ return false;
+
+ tools::Rectangle aMMRect;
+ if ( pLastRange && pLastMM && nTab == pLastRange->aStart.Tab() &&
+ nStartRow == pLastRange->aStart.Row() && nEndRow == pLastRange->aEnd.Row() )
+ {
+ // keep vertical part of aMMRect, only update horizontal position
+ aMMRect = *pLastMM;
+
+ tools::Long nLeft = 0;
+ SCCOL i;
+ for (i=0; i<nStartCol; i++)
+ nLeft += GetColWidth(i,nTab);
+ tools::Long nRight = nLeft;
+ for (i=nStartCol; i<=nEndCol; i++)
+ nRight += GetColWidth(i,nTab);
+
+ aMMRect.SetLeft(o3tl::convert(nLeft, o3tl::Length::twip, o3tl::Length::mm100));
+ aMMRect.SetRight(o3tl::convert(nRight, o3tl::Length::twip, o3tl::Length::mm100));
+ }
+ else
+ aMMRect = GetMMRect( nStartCol, nStartRow, nEndCol, nEndRow, nTab );
+
+ if ( pLastRange && pLastMM )
+ {
+ *pLastRange = ScRange( nStartCol, nStartRow, nTab, nEndCol, nEndRow, nTab );
+ *pLastMM = aMMRect;
+ }
+
+ if ( HasAnyDraw( nTab, aMMRect ))
+ return false;
+
+ if ( nStartCol > 0 && !bLeftIsEmpty )
+ {
+ // similar to in ScPrintFunc::AdjustPrintArea
+ // ExtendPrintArea starting only from the start column of the print area
+
+ SCCOL nExtendCol = nStartCol - 1;
+ SCROW nTmpRow = nEndRow;
+
+ // ExtendMerge() is non-const, but called without refresh. GetPrinter()
+ // might create and assign a printer.
+ ScDocument* pThis = const_cast<ScDocument*>(this);
+
+ pThis->ExtendMerge( 0,nStartRow, nExtendCol,nTmpRow, nTab ); // no Refresh, incl. Attrs
+
+ OutputDevice* pDev = pThis->GetPrinter();
+ pDev->SetMapMode(MapMode(MapUnit::MapPixel)); // Important for GetNeededSize
+ ExtendPrintArea( pDev, nTab, 0, nStartRow, nExtendCol, nEndRow );
+ if ( nExtendCol >= nStartCol )
+ return false;
+ }
+
+ return true;
+}
+
+void ScDocument::Clear( bool bFromDestructor )
+{
+ for (auto& rxTab : maTabs)
+ if (rxTab)
+ rxTab->GetCondFormList()->clear();
+
+ maTabs.clear();
+ pSelectionAttr.reset();
+
+ if (mpDrawLayer)
+ {
+ mpDrawLayer->ClearModel( bFromDestructor );
+ }
+}
+
+bool ScDocument::HasDetectiveObjects(SCTAB nTab) const
+{
+ // looks for detective objects, annotations don't count
+ // (used to adjust scale so detective objects hit their cells better)
+
+ bool bFound = false;
+
+ if (mpDrawLayer)
+ {
+ SdrPage* pPage = mpDrawLayer->GetPage(static_cast<sal_uInt16>(nTab));
+ OSL_ENSURE(pPage,"Page ?");
+ if (pPage)
+ {
+ SdrObjListIter aIter( pPage, SdrIterMode::DeepNoGroups );
+ SdrObject* pObject = aIter.Next();
+ while (pObject && !bFound)
+ {
+ // anything on the internal layer except captions (annotations)
+ if ( (pObject->GetLayer() == SC_LAYER_INTERN) && !ScDrawLayer::IsNoteCaption( pObject ) )
+ bFound = true;
+
+ pObject = aIter.Next();
+ }
+ }
+ }
+
+ return bFound;
+}
+
+void ScDocument::UpdateFontCharSet()
+{
+ // In old versions (until 4.0 without SP), when switching between systems,
+ // the Font attribute was not adjusted.
+ // This has to be redone for Documents until SP2:
+ // Everything that is not SYMBOL is set to system CharSet.
+ // CharSet should be correct for new documents (version SC_FONTCHARSET)
+
+ bool bUpdateOld = ( nSrcVer < SC_FONTCHARSET );
+
+ rtl_TextEncoding eSysSet = osl_getThreadTextEncoding();
+ if ( !(eSrcSet != eSysSet || bUpdateOld) )
+ return;
+
+ ScDocumentPool* pPool = mxPoolHelper->GetDocPool();
+ for (const SfxPoolItem* pItem : pPool->GetItemSurrogates(ATTR_FONT))
+ {
+ auto pFontItem = const_cast<SvxFontItem*>(dynamic_cast<const SvxFontItem*>(pItem));
+ if ( pFontItem && ( pFontItem->GetCharSet() == eSrcSet ||
+ ( bUpdateOld && pFontItem->GetCharSet() != RTL_TEXTENCODING_SYMBOL ) ) )
+ pFontItem->SetCharSet(eSysSet);
+ }
+
+ if ( mpDrawLayer )
+ {
+ SfxItemPool& rDrawPool = mpDrawLayer->GetItemPool();
+ for (const SfxPoolItem* pItem : rDrawPool.GetItemSurrogates(EE_CHAR_FONTINFO))
+ {
+ SvxFontItem* pFontItem = const_cast<SvxFontItem*>(dynamic_cast<const SvxFontItem*>(pItem));
+ if ( pFontItem && ( pFontItem->GetCharSet() == eSrcSet ||
+ ( bUpdateOld && pFontItem->GetCharSet() != RTL_TEXTENCODING_SYMBOL ) ) )
+ pFontItem->SetCharSet( eSysSet );
+ }
+ }
+}
+
+void ScDocument::SetLoadingMedium( bool bVal )
+{
+ bLoadingMedium = bVal;
+ for (auto& rxTab : maTabs)
+ {
+ if (!rxTab)
+ return;
+
+ rxTab->SetLoadingMedium(bVal);
+ }
+}
+
+void ScDocument::SetImportingXML( bool bVal )
+{
+ bImportingXML = bVal;
+ if (mpDrawLayer)
+ mpDrawLayer->EnableAdjust(!bImportingXML);
+
+ if ( !bVal )
+ {
+ // #i57869# after loading, do the real RTL mirroring for the sheets that have the LoadingRTL flag set
+
+ for ( SCTAB nTab=0; nTab< static_cast<SCTAB>(maTabs.size()) && maTabs[nTab]; nTab++ )
+ if ( maTabs[nTab]->IsLoadingRTL() )
+ {
+ // SetLayoutRTL => SetDrawPageSize => ScDrawLayer::SetPageSize, includes RTL-mirroring;
+ // bImportingXML must be cleared first
+ maTabs[nTab]->SetLoadingRTL( false );
+ SetLayoutRTL( nTab, true, ScObjectHandling::MoveRTLMode );
+ }
+ }
+
+ SetLoadingMedium(bVal);
+}
+
+const std::shared_ptr<SvxForbiddenCharactersTable>& ScDocument::GetForbiddenCharacters() const
+{
+ return xForbiddenCharacters;
+}
+
+void ScDocument::SetForbiddenCharacters(const std::shared_ptr<SvxForbiddenCharactersTable>& rNew)
+{
+ xForbiddenCharacters = rNew;
+ if ( mpEditEngine )
+ EditEngine::SetForbiddenCharsTable( xForbiddenCharacters );
+ if ( mpDrawLayer )
+ mpDrawLayer->SetForbiddenCharsTable( xForbiddenCharacters );
+}
+
+bool ScDocument::IsValidAsianCompression() const
+{
+ return nAsianCompression != CharCompressType::Invalid;
+}
+
+CharCompressType ScDocument::GetAsianCompression() const
+{
+ if ( nAsianCompression == CharCompressType::Invalid )
+ return CharCompressType::NONE;
+ else
+ return nAsianCompression;
+}
+
+void ScDocument::SetAsianCompression(CharCompressType nNew)
+{
+ nAsianCompression = nNew;
+ if ( mpEditEngine )
+ mpEditEngine->SetAsianCompressionMode( nAsianCompression );
+ if ( mpDrawLayer )
+ mpDrawLayer->SetCharCompressType( nAsianCompression );
+}
+
+bool ScDocument::IsValidAsianKerning() const
+{
+ return ( nAsianKerning != SC_ASIANKERNING_INVALID );
+}
+
+bool ScDocument::GetAsianKerning() const
+{
+ if ( nAsianKerning == SC_ASIANKERNING_INVALID )
+ return false;
+ else
+ return static_cast<bool>(nAsianKerning);
+}
+
+void ScDocument::SetAsianKerning(bool bNew)
+{
+ nAsianKerning = static_cast<sal_uInt8>(bNew);
+ if ( mpEditEngine )
+ mpEditEngine->SetKernAsianPunctuation( static_cast<bool>( nAsianKerning ) );
+ if ( mpDrawLayer )
+ mpDrawLayer->SetKernAsianPunctuation( static_cast<bool>( nAsianKerning ) );
+}
+
+void ScDocument::ApplyAsianEditSettings( ScEditEngineDefaulter& rEngine )
+{
+ EditEngine::SetForbiddenCharsTable( xForbiddenCharacters );
+ rEngine.SetAsianCompressionMode( GetAsianCompression() );
+ rEngine.SetKernAsianPunctuation( GetAsianKerning() );
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sc/source/core/data/document.cxx b/sc/source/core/data/document.cxx
new file mode 100644
index 000000000..9753a16e8
--- /dev/null
+++ b/sc/source/core/data/document.cxx
@@ -0,0 +1,7089 @@
+/* -*- 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 <scitems.hxx>
+
+#include <editeng/boxitem.hxx>
+#include <editeng/editobj.hxx>
+#include <o3tl/safeint.hxx>
+#include <svx/sdrundomanager.hxx>
+#include <svx/svditer.hxx>
+#include <sfx2/objsh.hxx>
+#include <sfx2/docfile.hxx>
+#include <svl/numformat.hxx>
+#include <svl/poolcach.hxx>
+#include <svl/zforlist.hxx>
+#include <unotools/charclass.hxx>
+#include <unotools/transliterationwrapper.hxx>
+#include <tools/urlobj.hxx>
+#include <sal/log.hxx>
+#include <osl/diagnose.h>
+
+#include <com/sun/star/text/WritingMode2.hpp>
+#include <com/sun/star/script/vba/XVBACompatibility.hpp>
+#include <com/sun/star/sheet/TablePageBreakData.hpp>
+#include <com/sun/star/lang/NotInitializedException.hpp>
+
+#include <document.hxx>
+#include <docuno.hxx>
+#include <table.hxx>
+#include <column.hxx>
+#include <attrib.hxx>
+#include <attarray.hxx>
+#include <patattr.hxx>
+#include <rangenam.hxx>
+#include <poolhelp.hxx>
+#include <docpool.hxx>
+#include <stlpool.hxx>
+#include <stlsheet.hxx>
+#include <globstr.hrc>
+#include <scresid.hxx>
+#include <dbdata.hxx>
+#include <chartlis.hxx>
+#include <rangelst.hxx>
+#include <markdata.hxx>
+#include <drwlayer.hxx>
+#include <validat.hxx>
+#include <prnsave.hxx>
+#include <chgtrack.hxx>
+#include <hints.hxx>
+#include <detdata.hxx>
+#include <dpobject.hxx>
+#include <detfunc.hxx>
+#include <scmod.hxx>
+#include <dociter.hxx>
+#include <progress.hxx>
+#include <autonamecache.hxx>
+#include <bcaslot.hxx>
+#include <postit.hxx>
+#include <clipparam.hxx>
+#include <defaultsoptions.hxx>
+#include <editutil.hxx>
+#include <stringutil.hxx>
+#include <formulaiter.hxx>
+#include <formulacell.hxx>
+#include <clipcontext.hxx>
+#include <listenercontext.hxx>
+#include <scopetools.hxx>
+#include <refupdatecontext.hxx>
+#include <formulagroup.hxx>
+#include <tokenstringcontext.hxx>
+#include <compressedarray.hxx>
+#include <recursionhelper.hxx>
+#include <SparklineGroup.hxx>
+#include <SparklineList.hxx>
+
+#include <formula/vectortoken.hxx>
+
+#include <limits>
+#include <memory>
+#include <utility>
+
+#include <comphelper/lok.hxx>
+#include <comphelper/servicehelper.hxx>
+
+#include <vcl/uitest/logger.hxx>
+#include <vcl/uitest/eventdescription.hxx>
+
+#include <mtvelements.hxx>
+#include <sfx2/lokhelper.hxx>
+
+using ::editeng::SvxBorderLine;
+using namespace ::com::sun::star;
+
+namespace WritingMode2 = ::com::sun::star::text::WritingMode2;
+using ::com::sun::star::uno::Sequence;
+using ::com::sun::star::sheet::TablePageBreakData;
+using ::std::set;
+
+namespace {
+
+std::pair<SCTAB,SCTAB> getMarkedTableRange(const std::vector<ScTableUniquePtr>& rTables, const ScMarkData& rMark)
+{
+ SCTAB nTabStart = MAXTAB;
+ SCTAB nTabEnd = 0;
+ SCTAB nMax = static_cast<SCTAB>(rTables.size());
+ for (const auto& rTab : rMark)
+ {
+ if (rTab >= nMax)
+ break;
+
+ if (!rTables[rTab])
+ continue;
+
+ if (rTab < nTabStart)
+ nTabStart = rTab;
+ nTabEnd = rTab;
+ }
+
+ return std::pair<SCTAB,SCTAB>(nTabStart,nTabEnd);
+}
+
+void collectUIInformation(std::map<OUString, OUString>&& aParameters, const OUString& rAction)
+{
+ EventDescription aDescription;
+ aDescription.aID = "grid_window";
+ aDescription.aAction = rAction;
+ aDescription.aParameters = std::move(aParameters);
+ aDescription.aParent = "MainWindow";
+ aDescription.aKeyWord = "ScGridWinUIObject";
+
+ UITestLogger::getInstance().logEvent(aDescription);
+}
+
+struct ScDefaultAttr
+{
+ const ScPatternAttr* pAttr;
+ SCROW nFirst;
+ SCSIZE nCount;
+ explicit ScDefaultAttr(const ScPatternAttr* pPatAttr) : pAttr(pPatAttr), nFirst(0), nCount(0) {}
+};
+
+struct ScLessDefaultAttr
+{
+ bool operator() (const ScDefaultAttr& rValue1, const ScDefaultAttr& rValue2) const
+ {
+ return rValue1.pAttr < rValue2.pAttr;
+ }
+};
+
+}
+
+typedef std::set<ScDefaultAttr, ScLessDefaultAttr> ScDefaultAttrSet;
+
+void ScDocument::MakeTable( SCTAB nTab,bool _bNeedsNameCheck )
+{
+ if ( !(ValidTab(nTab) && ( nTab >= static_cast<SCTAB>(maTabs.size()) ||!maTabs[nTab])) )
+ return;
+
+ // Get Custom prefix
+ const ScDefaultsOptions& rOpt = SC_MOD()->GetDefaultsOptions();
+ OUString aString = rOpt.GetInitTabPrefix() + OUString::number(nTab+1);
+ if ( _bNeedsNameCheck )
+ CreateValidTabName( aString ); // no doubles
+ if (nTab < static_cast<SCTAB>(maTabs.size()))
+ {
+ maTabs[nTab].reset( new ScTable(*this, nTab, aString) );
+ }
+ else
+ {
+ while(nTab > static_cast<SCTAB>(maTabs.size()))
+ maTabs.push_back(nullptr);
+ maTabs.emplace_back( new ScTable(*this, nTab, aString) );
+ }
+ maTabs[nTab]->SetLoadingMedium(bLoadingMedium);
+}
+
+bool ScDocument::HasTable( SCTAB nTab ) const
+{
+ if (ValidTab(nTab) && nTab < static_cast<SCTAB>(maTabs.size()))
+ if (maTabs[nTab])
+ return true;
+
+ return false;
+}
+
+bool ScDocument::GetHashCode( SCTAB nTab, sal_Int64& rHashCode ) const
+{
+ if (ValidTab(nTab) && nTab < static_cast<SCTAB>(maTabs.size()))
+ {
+ if (maTabs[nTab])
+ {
+ rHashCode = maTabs[nTab]->GetHashCode();
+ return true;
+ }
+ }
+ return false;
+}
+
+bool ScDocument::GetName( SCTAB nTab, OUString& rName ) const
+{
+ if (ValidTab(nTab) && nTab < static_cast<SCTAB>(maTabs.size()))
+ {
+ if (maTabs[nTab])
+ {
+ rName = maTabs[nTab]->GetName();
+ return true;
+ }
+ }
+ rName.clear();
+ return false;
+}
+
+OUString ScDocument::GetCopyTabName( SCTAB nTab ) const
+{
+ if (ValidTab(nTab) && nTab < static_cast<SCTAB>(maTabNames.size()))
+ return maTabNames[nTab];
+ return OUString();
+}
+
+bool ScDocument::SetCodeName( SCTAB nTab, const OUString& rName )
+{
+ if (ValidTab(nTab) && nTab < static_cast<SCTAB>(maTabs.size()))
+ {
+ if (maTabs[nTab])
+ {
+ maTabs[nTab]->SetCodeName( rName );
+ return true;
+ }
+ }
+ SAL_WARN("sc", "can't set code name " << rName );
+ return false;
+}
+
+bool ScDocument::GetCodeName( SCTAB nTab, OUString& rName ) const
+{
+ if (ValidTab(nTab) && nTab < static_cast<SCTAB>(maTabs.size()))
+ if (maTabs[nTab])
+ {
+ rName = maTabs[nTab]->GetCodeName();
+ return true;
+ }
+ rName.clear();
+ return false;
+}
+
+bool ScDocument::GetTable( const OUString& rName, SCTAB& rTab ) const
+{
+ static OUString aCacheName, aCacheUpperName;
+
+ assert(!IsThreadedGroupCalcInProgress());
+ if (aCacheName != rName)
+ {
+ aCacheName = rName;
+ // surprisingly slow ...
+ aCacheUpperName = ScGlobal::getCharClass().uppercase(rName);
+ }
+ const OUString aUpperName = aCacheUpperName;
+
+ for (SCTAB i=0; i< static_cast<SCTAB>(maTabs.size()); i++)
+ if (maTabs[i])
+ {
+ if (aUpperName == maTabs[i]->GetUpperName())
+ {
+ rTab = i;
+ return true;
+ }
+ }
+ rTab = 0;
+ return false;
+}
+
+std::vector<OUString> ScDocument::GetAllTableNames() const
+{
+ std::vector<OUString> aNames;
+ aNames.reserve(maTabs.size());
+ for (const auto& a : maTabs)
+ {
+ // Positions need to be preserved for ScCompiler and address convention
+ // context, so still push an empty string for NULL tabs.
+ OUString aName;
+ if (a)
+ {
+ const ScTable& rTab = *a;
+ aName = rTab.GetName();
+ }
+ aNames.push_back(aName);
+ }
+
+ return aNames;
+}
+
+ScDBData* ScDocument::GetAnonymousDBData(SCTAB nTab)
+{
+ if (ValidTab(nTab) && nTab < static_cast<SCTAB>(maTabs.size()) && maTabs[nTab])
+ return maTabs[nTab]->GetAnonymousDBData();
+ return nullptr;
+}
+
+SCTAB ScDocument::GetTableCount() const
+{
+ return static_cast<SCTAB>(maTabs.size());
+}
+
+void ScDocument::SetAnonymousDBData(SCTAB nTab, std::unique_ptr<ScDBData> pDBData)
+{
+ if (ValidTab(nTab) && nTab < static_cast<SCTAB>(maTabs.size()) && maTabs[nTab])
+ maTabs[nTab]->SetAnonymousDBData(std::move(pDBData));
+}
+
+void ScDocument::SetAnonymousDBData( std::unique_ptr<ScDBData> pDBData )
+{
+ mpAnonymousDBData = std::move(pDBData);
+}
+
+ScDBData* ScDocument::GetAnonymousDBData()
+{
+ return mpAnonymousDBData.get();
+}
+
+bool ScDocument::ValidTabName( const OUString& rName )
+{
+ if (rName.isEmpty())
+ return false;
+ sal_Int32 nLen = rName.getLength();
+
+#if 1
+ // Restrict sheet names to what Excel accepts.
+ /* TODO: We may want to remove this restriction for full ODFF compliance.
+ * Merely loading and calculating ODF documents using these characters in
+ * sheet names is not affected by this, but all sheet name editing and
+ * copying functionality is, maybe falling back to "Sheet4" or similar. */
+ for (sal_Int32 i = 0; i < nLen; ++i)
+ {
+ const sal_Unicode c = rName[i];
+ switch (c)
+ {
+ case ':':
+ case '\\':
+ case '/':
+ case '?':
+ case '*':
+ case '[':
+ case ']':
+ // these characters are not allowed to match XL's convention.
+ return false;
+ case '\'':
+ if (i == 0 || i == nLen - 1)
+ // single quote is not allowed at the first or last
+ // character position.
+ return false;
+ break;
+ }
+ }
+#endif
+
+ return true;
+}
+
+bool ScDocument::ValidNewTabName( const OUString& rName ) const
+{
+ bool bValid = ValidTabName(rName);
+ if (!bValid)
+ return false;
+ OUString aUpperName = ScGlobal::getCharClass().uppercase(rName);
+ for (const auto& a : maTabs)
+ {
+ if (!a)
+ continue;
+ const OUString& rOldName = a->GetUpperName();
+ bValid = rOldName != aUpperName;
+ if (!bValid)
+ break;
+ }
+ return bValid;
+}
+
+void ScDocument::CreateValidTabName(OUString& rName) const
+{
+ if ( !ValidTabName(rName) )
+ {
+ // Find new one
+
+ // Get Custom prefix
+ const ScDefaultsOptions& rOpt = SC_MOD()->GetDefaultsOptions();
+ const OUString& aStrTable = rOpt.GetInitTabPrefix();
+
+ bool bOk = false;
+
+ // First test if the prefix is valid, if so only avoid doubles
+ bool bPrefix = ValidTabName( aStrTable );
+ OSL_ENSURE(bPrefix, "Invalid Table Name");
+ SCTAB nDummy;
+
+ for ( SCTAB i = static_cast<SCTAB>(maTabs.size())+1; !bOk ; i++ )
+ {
+ rName = aStrTable + OUString::number(static_cast<sal_Int32>(i));
+ if (bPrefix)
+ bOk = ValidNewTabName( rName );
+ else
+ bOk = !GetTable( rName, nDummy );
+ }
+ }
+ else
+ {
+ // testing the supplied Name
+
+ if ( !ValidNewTabName(rName) )
+ {
+ SCTAB i = 1;
+ OUStringBuffer aName;
+ do
+ {
+ i++;
+ aName = rName;
+ aName.append('_');
+ aName.append(static_cast<sal_Int32>(i));
+ }
+ while (!ValidNewTabName(aName.toString()) && (i < MAXTAB+1));
+ rName = aName.makeStringAndClear();
+ }
+ }
+}
+
+void ScDocument::CreateValidTabNames(std::vector<OUString>& aNames, SCTAB nCount) const
+{
+ aNames.clear();//ensure that the vector is empty
+
+ // Get Custom prefix
+ const ScDefaultsOptions& rOpt = SC_MOD()->GetDefaultsOptions();
+ const OUString& aStrTable = rOpt.GetInitTabPrefix();
+
+ OUStringBuffer rName;
+
+ // First test if the prefix is valid, if so only avoid doubles
+ bool bPrefix = ValidTabName( aStrTable );
+ OSL_ENSURE(bPrefix, "Invalid Table Name");
+ SCTAB nDummy;
+ SCTAB i = static_cast<SCTAB>(maTabs.size())+1;
+
+ for (SCTAB j = 0; j < nCount; ++j)
+ {
+ bool bOk = false;
+ while(!bOk)
+ {
+ rName = aStrTable;
+ rName.append(static_cast<sal_Int32>(i));
+ if (bPrefix)
+ bOk = ValidNewTabName( rName.toString() );
+ else
+ bOk = !GetTable( rName.toString(), nDummy );
+ i++;
+ }
+ aNames.push_back(rName.makeStringAndClear());
+ }
+}
+
+void ScDocument::AppendTabOnLoad(const OUString& rName)
+{
+ SCTAB nTabCount = static_cast<SCTAB>(maTabs.size());
+ if (!ValidTab(nTabCount))
+ // max table count reached. No more tables.
+ return;
+
+ OUString aName = rName;
+ CreateValidTabName(aName);
+ maTabs.emplace_back( new ScTable(*this, nTabCount, aName) );
+}
+
+void ScDocument::SetTabNameOnLoad(SCTAB nTab, const OUString& rName)
+{
+ if (!ValidTab(nTab) || static_cast<SCTAB>(maTabs.size()) <= nTab)
+ return;
+
+ if (!ValidTabName(rName))
+ return;
+
+ maTabs[nTab]->SetName(rName);
+}
+
+void ScDocument::InvalidateStreamOnSave()
+{
+ for (const auto& a : maTabs)
+ {
+ if (a)
+ a->SetStreamValid(false);
+ }
+}
+
+bool ScDocument::InsertTab(
+ SCTAB nPos, const OUString& rName, bool bExternalDocument, bool bUndoDeleteTab )
+{
+ SCTAB nTabCount = static_cast<SCTAB>(maTabs.size());
+ bool bValid = ValidTab(nTabCount);
+ if ( !bExternalDocument ) // else test rName == "'Doc'!Tab" first
+ bValid = (bValid && ValidNewTabName(rName));
+ if (bValid)
+ {
+ if (nPos == SC_TAB_APPEND || nPos >= nTabCount)
+ {
+ nPos = maTabs.size();
+ maTabs.emplace_back( new ScTable(*this, nTabCount, rName) );
+ if ( bExternalDocument )
+ maTabs[nTabCount]->SetVisible( false );
+ }
+ else
+ {
+ if (ValidTab(nPos) && (nPos < nTabCount))
+ {
+ sc::RefUpdateInsertTabContext aCxt( *this, nPos, 1);
+
+ ScRange aRange( 0,0,nPos, MaxCol(),MaxRow(),MAXTAB );
+ xColNameRanges->UpdateReference( URM_INSDEL, this, aRange, 0,0,1 );
+ xRowNameRanges->UpdateReference( URM_INSDEL, this, aRange, 0,0,1 );
+ if (pRangeName)
+ pRangeName->UpdateInsertTab(aCxt);
+ pDBCollection->UpdateReference(
+ URM_INSDEL, 0,0,nPos, MaxCol(),MaxRow(),MAXTAB, 0,0,1 );
+ if (pDPCollection)
+ pDPCollection->UpdateReference( URM_INSDEL, aRange, 0,0,1 );
+ if (pDetOpList)
+ pDetOpList->UpdateReference( this, URM_INSDEL, aRange, 0,0,1 );
+ UpdateChartRef( URM_INSDEL, 0,0,nPos, MaxCol(),MaxRow(),MAXTAB, 0,0,1 );
+ UpdateRefAreaLinks( URM_INSDEL, aRange, 0,0,1 );
+ if ( pUnoBroadcaster )
+ pUnoBroadcaster->Broadcast( ScUpdateRefHint( URM_INSDEL, aRange, 0,0,1 ) );
+
+ for (const auto& a : maTabs)
+ {
+ if (a)
+ a->UpdateInsertTab(aCxt);
+ }
+ maTabs.emplace(maTabs.begin() + nPos, new ScTable(*this, nPos, rName));
+
+ // UpdateBroadcastAreas must be called between UpdateInsertTab,
+ // which ends listening, and StartAllListeners, to not modify
+ // areas that are to be inserted by starting listeners.
+ UpdateBroadcastAreas( URM_INSDEL, aRange, 0,0,1);
+ for (const auto& a : maTabs)
+ {
+ if (a)
+ a->UpdateCompile();
+ }
+
+ StartAllListeners();
+
+ if (pValidationList)
+ {
+ ScMutationGuard aGuard(*this, ScMutationGuardFlags::CORE);
+ pValidationList->UpdateInsertTab(aCxt);
+ }
+
+ bValid = true;
+ }
+ else
+ bValid = false;
+ }
+ }
+
+ if (bValid)
+ {
+ sc::SetFormulaDirtyContext aCxt;
+ aCxt.mbClearTabDeletedFlag = bUndoDeleteTab;
+ aCxt.mnTabDeletedStart = nPos;
+ aCxt.mnTabDeletedEnd = nPos;
+ SetAllFormulasDirty(aCxt);
+
+ if (comphelper::LibreOfficeKit::isActive() && GetDrawLayer())
+ {
+ ScModelObj* pModel = comphelper::getFromUnoTunnel<ScModelObj>(this->GetDocumentShell()->GetModel());
+ SfxLokHelper::notifyDocumentSizeChangedAllViews(pModel);
+ }
+ }
+
+ return bValid;
+}
+
+bool ScDocument::InsertTabs( SCTAB nPos, const std::vector<OUString>& rNames,
+ bool bNamesValid )
+{
+ SCTAB nNewSheets = static_cast<SCTAB>(rNames.size());
+ SCTAB nTabCount = static_cast<SCTAB>(maTabs.size());
+ bool bValid = bNamesValid || ValidTab(nTabCount+nNewSheets);
+
+ if (bValid)
+ {
+ if (nPos == SC_TAB_APPEND || nPos >= nTabCount)
+ {
+ for ( SCTAB i = 0; i < nNewSheets; ++i )
+ {
+ maTabs.emplace_back( new ScTable(*this, nTabCount + i, rNames.at(i)) );
+ }
+ }
+ else
+ {
+ if (ValidTab(nPos) && (nPos < nTabCount))
+ {
+ sc::RefUpdateInsertTabContext aCxt( *this, nPos, nNewSheets);
+ ScRange aRange( 0,0,nPos, MaxCol(),MaxRow(),MAXTAB );
+ xColNameRanges->UpdateReference( URM_INSDEL, this, aRange, 0,0,nNewSheets );
+ xRowNameRanges->UpdateReference( URM_INSDEL, this, aRange, 0,0,nNewSheets );
+ if (pRangeName)
+ pRangeName->UpdateInsertTab(aCxt);
+ pDBCollection->UpdateReference(
+ URM_INSDEL, 0,0,nPos, MaxCol(),MaxRow(),MAXTAB, 0,0,nNewSheets );
+ if (pDPCollection)
+ pDPCollection->UpdateReference( URM_INSDEL, aRange, 0,0,nNewSheets );
+ if (pDetOpList)
+ pDetOpList->UpdateReference( this, URM_INSDEL, aRange, 0,0,nNewSheets );
+ UpdateChartRef( URM_INSDEL, 0,0,nPos, MaxCol(),MaxRow(),MAXTAB, 0,0,nNewSheets );
+ UpdateRefAreaLinks( URM_INSDEL, aRange, 0,0, nNewSheets );
+ if ( pUnoBroadcaster )
+ pUnoBroadcaster->Broadcast( ScUpdateRefHint( URM_INSDEL, aRange, 0,0,nNewSheets ) );
+
+ for (const auto& a : maTabs)
+ {
+ if (a)
+ a->UpdateInsertTab(aCxt);
+ }
+ for (SCTAB i = 0; i < nNewSheets; ++i)
+ {
+ maTabs.emplace(maTabs.begin() + nPos + i, new ScTable(*this, nPos + i, rNames.at(i)) );
+ }
+
+ // UpdateBroadcastAreas must be called between UpdateInsertTab,
+ // which ends listening, and StartAllListeners, to not modify
+ // areas that are to be inserted by starting listeners.
+ UpdateBroadcastAreas( URM_INSDEL, aRange, 0,0,nNewSheets);
+ for (const auto& a : maTabs)
+ {
+ if (a)
+ a->UpdateCompile();
+ }
+
+ StartAllListeners();
+
+ if (pValidationList)
+ {
+ ScMutationGuard aGuard(*this, ScMutationGuardFlags::CORE);
+ pValidationList->UpdateInsertTab(aCxt);
+ }
+
+ bValid = true;
+ }
+ else
+ bValid = false;
+ }
+ }
+
+ if (bValid)
+ {
+ sc::SetFormulaDirtyContext aCxt;
+ SetAllFormulasDirty(aCxt);
+ }
+
+ return bValid;
+}
+
+bool ScDocument::DeleteTab( SCTAB nTab )
+{
+ bool bValid = false;
+ if (ValidTab(nTab) && nTab < static_cast<SCTAB>(maTabs.size()))
+ {
+ if (maTabs[nTab])
+ {
+ SCTAB nTabCount = static_cast<SCTAB>(maTabs.size());
+ if (nTabCount > 1)
+ {
+ sc::AutoCalcSwitch aACSwitch(*this, false);
+ sc::RefUpdateDeleteTabContext aCxt( *this, nTab, 1);
+ sc::DelayDeletingBroadcasters delayDeletingBroadcasters(*this);
+
+ ScRange aRange( 0, 0, nTab, MaxCol(), MaxRow(), nTab );
+ DelBroadcastAreasInRange( aRange );
+
+ // #i8180# remove database ranges etc. that are on the deleted tab
+ // (restored in undo with ScRefUndoData)
+
+ xColNameRanges->DeleteOnTab( nTab );
+ xRowNameRanges->DeleteOnTab( nTab );
+ pDBCollection->DeleteOnTab( nTab );
+ if (pDPCollection)
+ pDPCollection->DeleteOnTab( nTab );
+ if (pDetOpList)
+ pDetOpList->DeleteOnTab( nTab );
+ DeleteAreaLinksOnTab( nTab );
+
+ // normal reference update
+
+ aRange.aEnd.SetTab( static_cast<SCTAB>(maTabs.size())-1 );
+ xColNameRanges->UpdateReference( URM_INSDEL, this, aRange, 0,0,-1 );
+ xRowNameRanges->UpdateReference( URM_INSDEL, this, aRange, 0,0,-1 );
+ if (pRangeName)
+ pRangeName->UpdateDeleteTab(aCxt);
+ pDBCollection->UpdateReference(
+ URM_INSDEL, 0,0,nTab, MaxCol(),MaxRow(),MAXTAB, 0,0,-1 );
+ if (pDPCollection)
+ pDPCollection->UpdateReference( URM_INSDEL, aRange, 0,0,-1 );
+ if (pDetOpList)
+ pDetOpList->UpdateReference( this, URM_INSDEL, aRange, 0,0,-1 );
+ UpdateChartRef( URM_INSDEL, 0,0,nTab, MaxCol(),MaxRow(),MAXTAB, 0,0,-1 );
+ UpdateRefAreaLinks( URM_INSDEL, aRange, 0,0,-1 );
+ if (pValidationList)
+ {
+ ScMutationGuard aGuard(*this, ScMutationGuardFlags::CORE);
+ pValidationList->UpdateDeleteTab(aCxt);
+ }
+ if ( pUnoBroadcaster )
+ pUnoBroadcaster->Broadcast( ScUpdateRefHint( URM_INSDEL, aRange, 0,0,-1 ) );
+
+ for (auto & pTab : maTabs)
+ if (pTab)
+ pTab->UpdateDeleteTab(aCxt);
+
+ // tdf#149502 make sure ScTable destructor called after the erase is finished, when
+ // maTabs[x].nTab==x is true again, as it should be always true.
+ // In the end of maTabs.erase, maTabs indexes change, but nTab updated before erase.
+ // ~ScTable expect that maTabs[x].nTab==x so it shouldn't be called during erase.
+ ScTableUniquePtr pErasedTab = std::move(maTabs[nTab]);
+ maTabs.erase(maTabs.begin() + nTab);
+ delete pErasedTab.release();
+
+ // UpdateBroadcastAreas must be called between UpdateDeleteTab,
+ // which ends listening, and StartAllListeners, to not modify
+ // areas that are to be inserted by starting listeners.
+ UpdateBroadcastAreas( URM_INSDEL, aRange, 0,0,-1);
+ for (const auto& a : maTabs)
+ {
+ if (a)
+ a->UpdateCompile();
+ }
+ // Excel-Filter deletes some Tables while loading, Listeners will
+ // only be triggered after the loading is done.
+ if ( !bInsertingFromOtherDoc )
+ {
+ StartAllListeners();
+
+ sc::SetFormulaDirtyContext aFormulaDirtyCxt;
+ SetAllFormulasDirty(aFormulaDirtyCxt);
+ }
+
+ if (comphelper::LibreOfficeKit::isActive())
+ {
+ ScModelObj* pModel = comphelper::getFromUnoTunnel<ScModelObj>(this->GetDocumentShell()->GetModel());
+ SfxLokHelper::notifyDocumentSizeChangedAllViews(pModel);
+ }
+
+ bValid = true;
+ }
+ }
+ }
+ return bValid;
+}
+
+bool ScDocument::DeleteTabs( SCTAB nTab, SCTAB nSheets )
+{
+ bool bValid = false;
+ if (ValidTab(nTab) && (nTab + nSheets) <= static_cast<SCTAB>(maTabs.size()))
+ {
+ if (maTabs[nTab])
+ {
+ SCTAB nTabCount = static_cast<SCTAB>(maTabs.size());
+ if (nTabCount > nSheets)
+ {
+ sc::AutoCalcSwitch aACSwitch(*this, false);
+ sc::RefUpdateDeleteTabContext aCxt( *this, nTab, nSheets);
+ sc::DelayDeletingBroadcasters delayDeletingBroadcasters(*this);
+
+ for (SCTAB aTab = 0; aTab < nSheets; ++aTab)
+ {
+ ScRange aRange( 0, 0, nTab, MaxCol(), MaxRow(), nTab + aTab );
+ DelBroadcastAreasInRange( aRange );
+
+ // #i8180# remove database ranges etc. that are on the deleted tab
+ // (restored in undo with ScRefUndoData)
+
+ xColNameRanges->DeleteOnTab( nTab + aTab );
+ xRowNameRanges->DeleteOnTab( nTab + aTab );
+ pDBCollection->DeleteOnTab( nTab + aTab );
+ if (pDPCollection)
+ pDPCollection->DeleteOnTab( nTab + aTab );
+ if (pDetOpList)
+ pDetOpList->DeleteOnTab( nTab + aTab );
+ DeleteAreaLinksOnTab( nTab + aTab );
+ }
+
+ if (pRangeName)
+ pRangeName->UpdateDeleteTab(aCxt);
+
+ // normal reference update
+
+ ScRange aRange( 0, 0, nTab, MaxCol(), MaxRow(), nTabCount - 1 );
+ xColNameRanges->UpdateReference( URM_INSDEL, this, aRange, 0,0,-1*nSheets );
+ xRowNameRanges->UpdateReference( URM_INSDEL, this, aRange, 0,0,-1*nSheets );
+ pDBCollection->UpdateReference(
+ URM_INSDEL, 0,0,nTab, MaxCol(),MaxRow(),MAXTAB, 0,0,-1*nSheets );
+ if (pDPCollection)
+ pDPCollection->UpdateReference( URM_INSDEL, aRange, 0,0,-1*nSheets );
+ if (pDetOpList)
+ pDetOpList->UpdateReference( this, URM_INSDEL, aRange, 0,0,-1*nSheets );
+ UpdateChartRef( URM_INSDEL, 0,0,nTab, MaxCol(),MaxRow(),MAXTAB, 0,0,-1*nSheets );
+ UpdateRefAreaLinks( URM_INSDEL, aRange, 0,0,-1*nSheets );
+ if (pValidationList)
+ {
+ ScMutationGuard aGuard(*this, ScMutationGuardFlags::CORE);
+ pValidationList->UpdateDeleteTab(aCxt);
+ }
+ if ( pUnoBroadcaster )
+ pUnoBroadcaster->Broadcast( ScUpdateRefHint( URM_INSDEL, aRange, 0,0,-1*nSheets ) );
+
+ for (auto & pTab : maTabs)
+ if (pTab)
+ pTab->UpdateDeleteTab(aCxt);
+
+ maTabs.erase(maTabs.begin() + nTab, maTabs.begin() + nTab + nSheets);
+ // UpdateBroadcastAreas must be called between UpdateDeleteTab,
+ // which ends listening, and StartAllListeners, to not modify
+ // areas that are to be inserted by starting listeners.
+ UpdateBroadcastAreas( URM_INSDEL, aRange, 0,0,-1*nSheets);
+ for (const auto& a : maTabs)
+ {
+ if (a)
+ a->UpdateCompile();
+ }
+ // Excel-Filter deletes some Tables while loading, Listeners will
+ // only be triggered after the loading is done.
+ if ( !bInsertingFromOtherDoc )
+ {
+ StartAllListeners();
+
+ sc::SetFormulaDirtyContext aFormulaDirtyCxt;
+ SetAllFormulasDirty(aFormulaDirtyCxt);
+ }
+
+ if (comphelper::LibreOfficeKit::isActive())
+ {
+ ScModelObj* pModel = comphelper::getFromUnoTunnel<ScModelObj>(this->GetDocumentShell()->GetModel());
+ SfxLokHelper::notifyDocumentSizeChangedAllViews(pModel);
+ }
+
+ bValid = true;
+ }
+ }
+ }
+ return bValid;
+}
+
+bool ScDocument::RenameTab( SCTAB nTab, const OUString& rName, bool bExternalDocument )
+{
+ bool bValid = false;
+ SCTAB i;
+ if (ValidTab(nTab))
+ {
+ if (maTabs[nTab])
+ {
+ if ( bExternalDocument )
+ bValid = true; // composed name
+ else
+ bValid = ValidTabName(rName);
+ for (i=0; (i< static_cast<SCTAB>(maTabs.size())) && bValid; i++)
+ if (maTabs[i] && (i != nTab))
+ {
+ OUString aOldName = maTabs[i]->GetName();
+ bValid = !ScGlobal::GetTransliteration().isEqual( rName, aOldName );
+ }
+ if (bValid)
+ {
+ // #i75258# update charts before renaming, so they can get their live data objects.
+ // Once the charts are live, the sheet can be renamed without problems.
+ if ( pChartListenerCollection )
+ pChartListenerCollection->UpdateChartsContainingTab( nTab );
+ maTabs[nTab]->SetName(rName);
+
+ // If formulas refer to the renamed sheet, the TokenArray remains valid,
+ // but the XML stream must be re-generated.
+ for (const auto& a : maTabs)
+ {
+ if (a)
+ a->SetStreamValid( false );
+ }
+
+ if (comphelper::LibreOfficeKit::isActive() && GetDrawLayer())
+ {
+ ScModelObj* pModel = comphelper::getFromUnoTunnel<ScModelObj>(this->GetDocumentShell()->GetModel());
+ SfxLokHelper::notifyDocumentSizeChangedAllViews(pModel);
+ }
+ }
+ }
+ }
+
+ collectUIInformation({{"NewName", rName}}, "Rename_Sheet");
+
+ return bValid;
+}
+
+void ScDocument::SetVisible( SCTAB nTab, bool bVisible )
+{
+ if (ValidTab(nTab) && nTab < static_cast<SCTAB> (maTabs.size()))
+ if (maTabs[nTab])
+ maTabs[nTab]->SetVisible(bVisible);
+}
+
+bool ScDocument::IsVisible( SCTAB nTab ) const
+{
+ if (ValidTab(nTab) && nTab < static_cast<SCTAB> (maTabs.size()))
+ if (maTabs[nTab])
+ return maTabs[nTab]->IsVisible();
+
+ return false;
+}
+
+bool ScDocument::IsStreamValid( SCTAB nTab ) const
+{
+ if ( ValidTab(nTab) && nTab < static_cast<SCTAB> (maTabs.size()) && maTabs[nTab] )
+ return maTabs[nTab]->IsStreamValid();
+
+ return false;
+}
+
+void ScDocument::SetStreamValid( SCTAB nTab, bool bSet, bool bIgnoreLock )
+{
+ if ( ValidTab(nTab) && nTab < static_cast<SCTAB> (maTabs.size()) && maTabs[nTab] )
+ maTabs[nTab]->SetStreamValid( bSet, bIgnoreLock );
+}
+
+void ScDocument::LockStreamValid( bool bLock )
+{
+ mbStreamValidLocked = bLock;
+}
+
+bool ScDocument::IsPendingRowHeights( SCTAB nTab ) const
+{
+ if ( ValidTab(nTab) && nTab < static_cast<SCTAB> (maTabs.size()) && maTabs[nTab] )
+ return maTabs[nTab]->IsPendingRowHeights();
+
+ return false;
+}
+
+void ScDocument::SetPendingRowHeights( SCTAB nTab, bool bSet )
+{
+ if ( ValidTab(nTab) && nTab < static_cast<SCTAB> (maTabs.size()) && maTabs[nTab] )
+ maTabs[nTab]->SetPendingRowHeights( bSet );
+}
+
+void ScDocument::SetLayoutRTL( SCTAB nTab, bool bRTL, ScObjectHandling eObjectHandling)
+{
+ if ( !(ValidTab(nTab) && nTab < static_cast<SCTAB> (maTabs.size()) && maTabs[nTab]) )
+ return;
+
+ if ( bImportingXML )
+ {
+ // #i57869# only set the LoadingRTL flag, the real setting (including mirroring)
+ // is applied in SetImportingXML(false). This is so the shapes can be loaded in
+ // normal LTR mode.
+
+ maTabs[nTab]->SetLoadingRTL( bRTL );
+ return;
+ }
+
+ maTabs[nTab]->SetLayoutRTL( bRTL ); // only sets the flag
+ maTabs[nTab]->SetDrawPageSize(true, true, eObjectHandling);
+
+ // objects are already repositioned via SetDrawPageSize, only writing mode is missing
+ if (!mpDrawLayer)
+ return;
+
+ SdrPage* pPage = mpDrawLayer->GetPage(static_cast<sal_uInt16>(nTab));
+ OSL_ENSURE(pPage,"Page ?");
+ if (!pPage)
+ return;
+
+ SdrObjListIter aIter( pPage, SdrIterMode::DeepNoGroups );
+ SdrObject* pObject = aIter.Next();
+ while (pObject)
+ {
+ pObject->SetContextWritingMode( bRTL ? WritingMode2::RL_TB : WritingMode2::LR_TB );
+ pObject = aIter.Next();
+ }
+}
+
+bool ScDocument::IsLayoutRTL( SCTAB nTab ) const
+{
+ if ( ValidTab(nTab) && nTab < static_cast<SCTAB> (maTabs.size()) && maTabs[nTab] )
+ return maTabs[nTab]->IsLayoutRTL();
+
+ return false;
+}
+
+bool ScDocument::IsNegativePage( SCTAB nTab ) const
+{
+ // Negative page area is always used for RTL layout.
+ // The separate method is used to find all RTL handling of drawing objects.
+ return IsLayoutRTL( nTab );
+}
+
+/* ----------------------------------------------------------------------------
+ used search area:
+
+ GetCellArea - Only Data
+ GetTableArea - Data / Attributes
+ GetPrintArea - intended for character objects,
+ sweeps attributes all the way to bottom / right
+---------------------------------------------------------------------------- */
+
+bool ScDocument::GetCellArea( SCTAB nTab, SCCOL& rEndCol, SCROW& rEndRow ) const
+{
+ if (ValidTab(nTab) && nTab < static_cast<SCTAB> (maTabs.size()))
+ if (maTabs[nTab])
+ return maTabs[nTab]->GetCellArea( rEndCol, rEndRow );
+
+ rEndCol = 0;
+ rEndRow = 0;
+ return false;
+}
+
+bool ScDocument::GetTableArea( SCTAB nTab, SCCOL& rEndCol, SCROW& rEndRow, bool bCalcHiddens) const
+{
+ if (ValidTab(nTab) && nTab < static_cast<SCTAB> (maTabs.size()))
+ if (maTabs[nTab])
+ return maTabs[nTab]->GetTableArea( rEndCol, rEndRow, bCalcHiddens);
+
+ rEndCol = 0;
+ rEndRow = 0;
+ return false;
+}
+
+bool ScDocument::ShrinkToDataArea(SCTAB nTab, SCCOL& rStartCol, SCROW& rStartRow, SCCOL& rEndCol, SCROW& rEndRow) const
+{
+ if (!ValidTab(nTab) || nTab >= static_cast<SCTAB> (maTabs.size()) || !maTabs[nTab])
+ return false;
+
+ SCCOL nCol1, nCol2;
+ SCROW nRow1, nRow2;
+ maTabs[nTab]->GetFirstDataPos(nCol1, nRow1);
+ maTabs[nTab]->GetLastDataPos(nCol2, nRow2);
+
+ if (nCol1 > nCol2 || nRow1 > nRow2)
+ // invalid range.
+ return false;
+
+ // Make sure the area only shrinks, and doesn't grow.
+ if (rStartCol < nCol1)
+ rStartCol = nCol1;
+ if (nCol2 < rEndCol)
+ rEndCol = nCol2;
+ if (rStartRow < nRow1)
+ rStartRow = nRow1;
+ if (nRow2 < rEndRow)
+ rEndRow = nRow2;
+
+ if (rStartCol > rEndCol || rStartRow > rEndRow)
+ // invalid range.
+ return false;
+
+ return true; // success!
+}
+
+bool ScDocument::ShrinkToUsedDataArea( bool& o_bShrunk, SCTAB nTab, SCCOL& rStartCol,
+ SCROW& rStartRow, SCCOL& rEndCol, SCROW& rEndRow, bool bColumnsOnly,
+ bool bStickyTopRow, bool bStickyLeftCol, ScDataAreaExtras* pDataAreaExtras ) const
+{
+ if (!ValidTab(nTab) || nTab >= static_cast<SCTAB> (maTabs.size()) || !maTabs[nTab])
+ {
+ o_bShrunk = false;
+ return false;
+ }
+ return maTabs[nTab]->ShrinkToUsedDataArea(
+ o_bShrunk, rStartCol, rStartRow, rEndCol, rEndRow, bColumnsOnly, bStickyTopRow,
+ bStickyLeftCol, pDataAreaExtras);
+}
+
+SCROW ScDocument::GetLastDataRow( SCTAB nTab, SCCOL nCol1, SCCOL nCol2, SCROW nLastRow ) const
+{
+ const ScTable* pTab = FetchTable(nTab);
+ if (!pTab)
+ return -1;
+
+ return pTab->GetLastDataRow(nCol1, nCol2, nLastRow);
+}
+
+// connected area
+
+void ScDocument::GetDataArea( SCTAB nTab, SCCOL& rStartCol, SCROW& rStartRow,
+ SCCOL& rEndCol, SCROW& rEndRow, bool bIncludeOld, bool bOnlyDown ) const
+{
+ if (ValidTab(nTab) && nTab < static_cast<SCTAB> (maTabs.size()) && maTabs[nTab])
+ maTabs[nTab]->GetDataArea( rStartCol, rStartRow, rEndCol, rEndRow, bIncludeOld, bOnlyDown );
+}
+
+bool ScDocument::GetDataAreaSubrange(ScRange& rRange) const
+{
+ SCTAB nTab = rRange.aStart.Tab();
+ if (nTab != rRange.aEnd.Tab())
+ return true;
+
+ if (ValidTab(nTab) && nTab < static_cast<SCTAB> (maTabs.size()) && maTabs[nTab])
+ return maTabs[nTab]->GetDataAreaSubrange(rRange);
+
+ return true;
+}
+
+void ScDocument::LimitChartArea( SCTAB nTab, SCCOL& rStartCol, SCROW& rStartRow,
+ SCCOL& rEndCol, SCROW& rEndRow )
+{
+ if (ValidTab(nTab) && nTab < static_cast<SCTAB> (maTabs.size()))
+ if (maTabs[nTab])
+ maTabs[nTab]->LimitChartArea( rStartCol, rStartRow, rEndCol, rEndRow );
+}
+
+void ScDocument::LimitChartIfAll( ScRangeListRef& rRangeList )
+{
+ ScRangeListRef aNew = new ScRangeList;
+ if (rRangeList.is())
+ {
+ for ( size_t i = 0, nCount = rRangeList->size(); i < nCount; i++ )
+ {
+ ScRange aRange( (*rRangeList)[i] );
+ if ( ( aRange.aStart.Col() == 0 && aRange.aEnd.Col() == MaxCol() ) ||
+ ( aRange.aStart.Row() == 0 && aRange.aEnd.Row() == MaxRow() ) )
+ {
+ SCCOL nStartCol = aRange.aStart.Col();
+ SCROW nStartRow = aRange.aStart.Row();
+ SCCOL nEndCol = aRange.aEnd.Col();
+ SCROW nEndRow = aRange.aEnd.Row();
+ SCTAB nTab = aRange.aStart.Tab();
+ if ( nTab < static_cast<SCTAB> (maTabs.size()) && maTabs[nTab])
+ maTabs[nTab]->LimitChartArea(nStartCol, nStartRow, nEndCol, nEndRow);
+ aRange.aStart.SetCol( nStartCol );
+ aRange.aStart.SetRow( nStartRow );
+ aRange.aEnd.SetCol( nEndCol );
+ aRange.aEnd.SetRow( nEndRow );
+ }
+ aNew->push_back(aRange);
+ }
+ }
+ else
+ {
+ OSL_FAIL("LimitChartIfAll: Ref==0");
+ }
+ rRangeList = aNew;
+}
+
+static void lcl_GetFirstTabRange( SCTAB& rTabRangeStart, SCTAB& rTabRangeEnd, const ScMarkData* pTabMark, SCTAB aMaxTab )
+{
+ // without ScMarkData, leave start/end unchanged
+ if ( !pTabMark )
+ return;
+
+ for (SCTAB nTab=0; nTab< aMaxTab; ++nTab)
+ if (pTabMark->GetTableSelect(nTab))
+ {
+ // find first range of consecutive selected sheets
+ rTabRangeStart = pTabMark->GetFirstSelected();
+ while ( nTab+1 < aMaxTab && pTabMark->GetTableSelect(nTab+1) )
+ ++nTab;
+ rTabRangeEnd = nTab;
+ return;
+ }
+}
+
+static bool lcl_GetNextTabRange( SCTAB& rTabRangeStart, SCTAB& rTabRangeEnd, const ScMarkData* pTabMark, SCTAB aMaxTab )
+{
+ if ( pTabMark )
+ {
+ // find next range of consecutive selected sheets after rTabRangeEnd
+ for (SCTAB nTab=rTabRangeEnd+1; nTab< aMaxTab; ++nTab)
+ if (pTabMark->GetTableSelect(nTab))
+ {
+ rTabRangeStart = nTab;
+ while ( nTab+1 < aMaxTab && pTabMark->GetTableSelect(nTab+1) )
+ ++nTab;
+ rTabRangeEnd = nTab;
+ return true;
+ }
+ }
+ return false;
+}
+
+bool ScDocument::CanInsertRow( const ScRange& rRange ) const
+{
+ SCCOL nStartCol = rRange.aStart.Col();
+ SCROW nStartRow = rRange.aStart.Row();
+ SCTAB nStartTab = rRange.aStart.Tab();
+ SCCOL nEndCol = rRange.aEnd.Col();
+ SCROW nEndRow = rRange.aEnd.Row();
+ SCTAB nEndTab = rRange.aEnd.Tab();
+ PutInOrder( nStartCol, nEndCol );
+ PutInOrder( nStartRow, nEndRow );
+ PutInOrder( nStartTab, nEndTab );
+ SCSIZE nSize = static_cast<SCSIZE>(nEndRow - nStartRow + 1);
+
+ bool bTest = true;
+ for (SCTAB i=nStartTab; i<=nEndTab && bTest && i < static_cast<SCTAB>(maTabs.size()); i++)
+ if (maTabs[i])
+ bTest &= maTabs[i]->TestInsertRow( nStartCol, nEndCol, nStartRow, nSize );
+
+ return bTest;
+}
+
+namespace {
+
+struct SetDirtyIfPostponedHandler
+{
+ void operator() (const ScTableUniquePtr & p)
+ {
+ if (p)
+ p->SetDirtyIfPostponed();
+ }
+};
+
+struct BroadcastRecalcOnRefMoveHandler
+{
+ void operator() (const ScTableUniquePtr & p)
+ {
+ if (p)
+ p->BroadcastRecalcOnRefMove();
+ }
+};
+
+struct BroadcastRecalcOnRefMoveGuard
+{
+ explicit BroadcastRecalcOnRefMoveGuard( ScDocument* pDoc ) :
+ aSwitch( *pDoc, false),
+ aBulk( pDoc->GetBASM(), SfxHintId::ScDataChanged)
+ {
+ }
+
+private:
+ sc::AutoCalcSwitch aSwitch; // first for ctor/dtor order, destroy second
+ ScBulkBroadcast aBulk; // second for ctor/dtor order, destroy first
+};
+
+}
+
+bool ScDocument::InsertRow( SCCOL nStartCol, SCTAB nStartTab,
+ SCCOL nEndCol, SCTAB nEndTab,
+ SCROW nStartRow, SCSIZE nSize, ScDocument* pRefUndoDoc,
+ const ScMarkData* pTabMark )
+{
+ SCTAB i;
+
+ PutInOrder( nStartCol, nEndCol );
+ PutInOrder( nStartTab, nEndTab );
+ if ( pTabMark )
+ {
+ nStartTab = 0;
+ nEndTab = static_cast<SCTAB>(maTabs.size()) -1;
+ }
+
+ bool bTest = true;
+ bool bRet = false;
+ bool bOldAutoCalc = GetAutoCalc();
+ SetAutoCalc( false ); // avoid multiple calculations
+ bool oldDelayedDeleteBroadcasters = IsDelayedDeletingBroadcasters();
+ EnableDelayDeletingBroadcasters( true );
+ for ( i = nStartTab; i <= nEndTab && bTest && i < static_cast<SCTAB>(maTabs.size()); i++)
+ if (maTabs[i] && (!pTabMark || pTabMark->GetTableSelect(i)))
+ bTest &= maTabs[i]->TestInsertRow(nStartCol, nEndCol, nStartRow, nSize);
+ if (bTest)
+ {
+ // UpdateBroadcastAreas have to be called before UpdateReference, so that entries
+ // aren't shifted that would be rebuild at UpdateReference
+
+ // handle chunks of consecutive selected sheets together
+ SCTAB nTabRangeStart = nStartTab;
+ SCTAB nTabRangeEnd = nEndTab;
+ lcl_GetFirstTabRange( nTabRangeStart, nTabRangeEnd, pTabMark, static_cast<SCTAB>(maTabs.size()) );
+ ScRange aShiftedRange(nStartCol, nStartRow, nTabRangeStart, nEndCol, MaxRow(), nTabRangeEnd);
+ sc::EndListeningContext aEndListenCxt(*this);
+
+ std::vector<ScAddress> aGroupPos;
+ do
+ {
+ aShiftedRange.aStart.SetTab(nTabRangeStart);
+ aShiftedRange.aEnd.SetTab(nTabRangeEnd);
+
+ // Collect all formula groups that will get split by the shifting,
+ // and end all their listening. Record the position of the top
+ // cell of the topmost group, and the position of the bottom cell
+ // of the bottommost group.
+ EndListeningIntersectedGroups(aEndListenCxt, aShiftedRange, &aGroupPos);
+
+ UpdateBroadcastAreas(URM_INSDEL, aShiftedRange, 0, static_cast<SCROW>(nSize), 0);
+ }
+ while ( lcl_GetNextTabRange( nTabRangeStart, nTabRangeEnd, pTabMark, static_cast<SCTAB>(maTabs.size()) ) );
+
+ lcl_GetFirstTabRange( nTabRangeStart, nTabRangeEnd, pTabMark, static_cast<SCTAB>(maTabs.size()) );
+
+ sc::RefUpdateContext aCxt(*this);
+ aCxt.meMode = URM_INSDEL;
+ aCxt.maRange = aShiftedRange;
+ aCxt.mnRowDelta = nSize;
+ do
+ {
+ aCxt.maRange.aStart.SetTab(nTabRangeStart);
+ aCxt.maRange.aEnd.SetTab(nTabRangeEnd);
+ UpdateReference(aCxt, pRefUndoDoc, false); // without drawing objects
+ }
+ while ( lcl_GetNextTabRange( nTabRangeStart, nTabRangeEnd, pTabMark, static_cast<SCTAB>(maTabs.size()) ) );
+
+ // UpdateReference should have set "needs listening" flags to those
+ // whose references have been modified. We also need to set this flag
+ // to those that were in the groups that got split by shifting.
+ SetNeedsListeningGroups(aGroupPos);
+
+ for (i=nStartTab; i<=nEndTab && i < static_cast<SCTAB>(maTabs.size()); i++)
+ if (maTabs[i] && (!pTabMark || pTabMark->GetTableSelect(i)))
+ maTabs[i]->InsertRow( nStartCol, nEndCol, nStartRow, nSize );
+
+ // UpdateRef for drawing layer must be after inserting,
+ // when the new row heights are known.
+ for (i=nStartTab; i<=nEndTab && i < static_cast<SCTAB>(maTabs.size()); i++)
+ if (maTabs[i] && (!pTabMark || pTabMark->GetTableSelect(i)))
+ maTabs[i]->UpdateDrawRef( URM_INSDEL,
+ nStartCol, nStartRow, nStartTab, nEndCol, MaxRow(), nEndTab,
+ 0, static_cast<SCROW>(nSize), 0 );
+
+ if ( pChangeTrack && pChangeTrack->IsInDeleteUndo() )
+ { // A new Listening is needed when references to deleted ranges are restored,
+ // previous Listeners were removed in FormulaCell UpdateReference.
+ StartAllListeners();
+ }
+ else
+ { // Listeners have been removed in UpdateReference
+ StartNeededListeners();
+
+ // At least all cells using range names pointing relative to the
+ // moved range must be recalculated, and all cells marked postponed
+ // dirty.
+ for (const auto& a : maTabs)
+ {
+ if (a)
+ a->SetDirtyIfPostponed();
+ }
+
+ {
+ BroadcastRecalcOnRefMoveGuard g(this);
+ std::for_each(maTabs.begin(), maTabs.end(), BroadcastRecalcOnRefMoveHandler());
+ }
+ }
+ bRet = true;
+ }
+ EnableDelayDeletingBroadcasters( oldDelayedDeleteBroadcasters );
+ SetAutoCalc( bOldAutoCalc );
+ if ( bRet && pChartListenerCollection )
+ pChartListenerCollection->UpdateDirtyCharts();
+ return bRet;
+}
+
+bool ScDocument::InsertRow( const ScRange& rRange )
+{
+ return InsertRow( rRange.aStart.Col(), rRange.aStart.Tab(),
+ rRange.aEnd.Col(), rRange.aEnd.Tab(),
+ rRange.aStart.Row(), static_cast<SCSIZE>(rRange.aEnd.Row()-rRange.aStart.Row()+1) );
+}
+
+void ScDocument::DeleteRow( SCCOL nStartCol, SCTAB nStartTab,
+ SCCOL nEndCol, SCTAB nEndTab,
+ SCROW nStartRow, SCSIZE nSize,
+ ScDocument* pRefUndoDoc, bool* pUndoOutline,
+ const ScMarkData* pTabMark )
+{
+ SCTAB i;
+
+ PutInOrder( nStartCol, nEndCol );
+ PutInOrder( nStartTab, nEndTab );
+ if ( pTabMark )
+ {
+ nStartTab = 0;
+ nEndTab = static_cast<SCTAB>(maTabs.size())-1;
+ }
+
+ sc::AutoCalcSwitch aACSwitch(*this, false); // avoid multiple calculations
+
+ // handle chunks of consecutive selected sheets together
+ SCTAB nTabRangeStart = nStartTab;
+ SCTAB nTabRangeEnd = nEndTab;
+ lcl_GetFirstTabRange( nTabRangeStart, nTabRangeEnd, pTabMark, static_cast<SCTAB>(maTabs.size()) );
+ do
+ {
+ if ( ValidRow(nStartRow+nSize) )
+ {
+ DelBroadcastAreasInRange( ScRange(
+ ScAddress( nStartCol, nStartRow, nTabRangeStart ),
+ ScAddress( nEndCol, nStartRow+nSize-1, nTabRangeEnd ) ) );
+ UpdateBroadcastAreas( URM_INSDEL, ScRange(
+ ScAddress( nStartCol, nStartRow+nSize, nTabRangeStart ),
+ ScAddress( nEndCol, MaxRow(), nTabRangeEnd )), 0, -static_cast<SCROW>(nSize), 0 );
+ }
+ else
+ DelBroadcastAreasInRange( ScRange(
+ ScAddress( nStartCol, nStartRow, nTabRangeStart ),
+ ScAddress( nEndCol, MaxRow(), nTabRangeEnd ) ) );
+ }
+ while ( lcl_GetNextTabRange( nTabRangeStart, nTabRangeEnd, pTabMark, static_cast<SCTAB>(maTabs.size()) ) );
+
+ sc::RefUpdateContext aCxt(*this);
+ const bool bLastRowIncluded = (static_cast<SCROW>(nStartRow + nSize) == GetMaxRowCount() && ValidRow(nStartRow));
+ if ( ValidRow(nStartRow+nSize) || bLastRowIncluded )
+ {
+ lcl_GetFirstTabRange( nTabRangeStart, nTabRangeEnd, pTabMark, static_cast<SCTAB>(maTabs.size()) );
+ aCxt.meMode = URM_INSDEL;
+ aCxt.mnRowDelta = -static_cast<SCROW>(nSize);
+ if (bLastRowIncluded)
+ {
+ // Last row is included, shift a virtually non-existent row in.
+ aCxt.maRange = ScRange( nStartCol, GetMaxRowCount(), nTabRangeStart, nEndCol, GetMaxRowCount(), nTabRangeEnd);
+ }
+ else
+ {
+ aCxt.maRange = ScRange( nStartCol, nStartRow+nSize, nTabRangeStart, nEndCol, MaxRow(), nTabRangeEnd);
+ }
+ do
+ {
+ UpdateReference(aCxt, pRefUndoDoc, true, false);
+ }
+ while ( lcl_GetNextTabRange( nTabRangeStart, nTabRangeEnd, pTabMark, static_cast<SCTAB>(maTabs.size()) ) );
+ }
+
+ if (pUndoOutline)
+ *pUndoOutline = false;
+
+ // Keep track of the positions of all formula groups that have been joined
+ // during row deletion.
+ std::vector<ScAddress> aGroupPos;
+
+ for ( i = nStartTab; i <= nEndTab && i < static_cast<SCTAB>(maTabs.size()); i++)
+ if (maTabs[i] && (!pTabMark || pTabMark->GetTableSelect(i)))
+ maTabs[i]->DeleteRow(aCxt.maRegroupCols, nStartCol, nEndCol, nStartRow, nSize, pUndoOutline, &aGroupPos);
+
+ // Newly joined groups have some of their members still listening. We
+ // need to make sure none of them are listening.
+ EndListeningGroups(aGroupPos);
+
+ // Mark all joined groups for group listening.
+ SetNeedsListeningGroups(aGroupPos);
+
+ if ( ValidRow(nStartRow+nSize) || bLastRowIncluded )
+ {
+ // Listeners have been removed in UpdateReference
+ StartNeededListeners();
+
+ // At least all cells using range names pointing relative to the moved
+ // range must be recalculated, and all cells marked postponed dirty.
+ for (const auto& a : maTabs)
+ {
+ if (a)
+ a->SetDirtyIfPostponed();
+ }
+
+ {
+ BroadcastRecalcOnRefMoveGuard g(this);
+ std::for_each(maTabs.begin(), maTabs.end(), BroadcastRecalcOnRefMoveHandler());
+ }
+ }
+
+ if (pChartListenerCollection)
+ pChartListenerCollection->UpdateDirtyCharts();
+}
+
+void ScDocument::DeleteRow( const ScRange& rRange )
+{
+ DeleteRow( rRange.aStart.Col(), rRange.aStart.Tab(),
+ rRange.aEnd.Col(), rRange.aEnd.Tab(),
+ rRange.aStart.Row(), static_cast<SCSIZE>(rRange.aEnd.Row()-rRange.aStart.Row()+1) );
+}
+
+bool ScDocument::CanInsertCol( const ScRange& rRange ) const
+{
+ SCCOL nStartCol = rRange.aStart.Col();
+ SCROW nStartRow = rRange.aStart.Row();
+ SCTAB nStartTab = rRange.aStart.Tab();
+ SCCOL nEndCol = rRange.aEnd.Col();
+ SCROW nEndRow = rRange.aEnd.Row();
+ SCTAB nEndTab = rRange.aEnd.Tab();
+ PutInOrder( nStartCol, nEndCol );
+ PutInOrder( nStartRow, nEndRow );
+ PutInOrder( nStartTab, nEndTab );
+ SCSIZE nSize = static_cast<SCSIZE>(nEndCol - nStartCol + 1);
+
+ bool bTest = true;
+ for (SCTAB i=nStartTab; i<=nEndTab && bTest && i < static_cast<SCTAB>(maTabs.size()); i++)
+ if (maTabs[i])
+ bTest &= maTabs[i]->TestInsertCol( nStartRow, nEndRow, nSize );
+
+ return bTest;
+}
+
+bool ScDocument::InsertCol( SCROW nStartRow, SCTAB nStartTab,
+ SCROW nEndRow, SCTAB nEndTab,
+ SCCOL nStartCol, SCSIZE nSize, ScDocument* pRefUndoDoc,
+ const ScMarkData* pTabMark )
+{
+ SCTAB i;
+
+ PutInOrder( nStartRow, nEndRow );
+ PutInOrder( nStartTab, nEndTab );
+ if ( pTabMark )
+ {
+ nStartTab = 0;
+ nEndTab = static_cast<SCTAB>(maTabs.size())-1;
+ }
+
+ bool bTest = true;
+ bool bRet = false;
+ bool bOldAutoCalc = GetAutoCalc();
+ SetAutoCalc( false ); // avoid multiple calculations
+ bool oldDelayedDeleteBroadcasters = IsDelayedDeletingBroadcasters();
+ EnableDelayDeletingBroadcasters( true );
+ for ( i = nStartTab; i <= nEndTab && bTest && i < static_cast<SCTAB>(maTabs.size()); i++)
+ if (maTabs[i] && (!pTabMark || pTabMark->GetTableSelect(i)))
+ bTest &= maTabs[i]->TestInsertCol( nStartRow, nEndRow, nSize );
+ if (bTest)
+ {
+ // handle chunks of consecutive selected sheets together
+ SCTAB nTabRangeStart = nStartTab;
+ SCTAB nTabRangeEnd = nEndTab;
+ lcl_GetFirstTabRange( nTabRangeStart, nTabRangeEnd, pTabMark, static_cast<SCTAB>(maTabs.size()) );
+ do
+ {
+ UpdateBroadcastAreas( URM_INSDEL, ScRange(
+ ScAddress( nStartCol, nStartRow, nTabRangeStart ),
+ ScAddress( MaxCol(), nEndRow, nTabRangeEnd )), static_cast<SCCOL>(nSize), 0, 0 );
+ }
+ while ( lcl_GetNextTabRange( nTabRangeStart, nTabRangeEnd, pTabMark, static_cast<SCTAB>(maTabs.size()) ) );
+
+ lcl_GetFirstTabRange( nTabRangeStart, nTabRangeEnd, pTabMark, static_cast<SCTAB>(maTabs.size()) );
+
+ sc::RefUpdateContext aCxt(*this);
+ aCxt.meMode = URM_INSDEL;
+ aCxt.maRange = ScRange(nStartCol, nStartRow, nTabRangeStart, MaxCol(), nEndRow, nTabRangeEnd);
+ aCxt.mnColDelta = nSize;
+ do
+ {
+ UpdateReference(aCxt, pRefUndoDoc, true, false);
+ }
+ while ( lcl_GetNextTabRange( nTabRangeStart, nTabRangeEnd, pTabMark, static_cast<SCTAB>(maTabs.size()) ) );
+
+ for (i=nStartTab; i<=nEndTab && i < static_cast<SCTAB>(maTabs.size()); i++)
+ if (maTabs[i] && (!pTabMark || pTabMark->GetTableSelect(i)))
+ maTabs[i]->InsertCol(aCxt.maRegroupCols, nStartCol, nStartRow, nEndRow, nSize);
+
+ if ( pChangeTrack && pChangeTrack->IsInDeleteUndo() )
+ { // A new Listening is needed when references to deleted ranges are restored,
+ // previous Listeners were removed in FormulaCell UpdateReference.
+ StartAllListeners();
+ }
+ else
+ {
+ // Listeners have been removed in UpdateReference
+ StartNeededListeners();
+ // At least all cells using range names pointing relative to the
+ // moved range must be recalculated, and all cells marked postponed
+ // dirty.
+ {
+ ScBulkBroadcast aBulkBroadcast(GetBASM(), SfxHintId::ScDataChanged);
+ std::for_each(maTabs.begin(), maTabs.end(), SetDirtyIfPostponedHandler());
+ }
+ // Cells containing functions such as CELL, COLUMN or ROW may have
+ // changed their values on relocation. Broadcast them.
+ {
+ BroadcastRecalcOnRefMoveGuard g(this);
+ std::for_each(maTabs.begin(), maTabs.end(), BroadcastRecalcOnRefMoveHandler());
+ }
+ }
+ bRet = true;
+ }
+ EnableDelayDeletingBroadcasters( oldDelayedDeleteBroadcasters );
+ SetAutoCalc( bOldAutoCalc );
+ if ( bRet && pChartListenerCollection )
+ pChartListenerCollection->UpdateDirtyCharts();
+ return bRet;
+}
+
+bool ScDocument::InsertCol( const ScRange& rRange )
+{
+ return InsertCol( rRange.aStart.Row(), rRange.aStart.Tab(),
+ rRange.aEnd.Row(), rRange.aEnd.Tab(),
+ rRange.aStart.Col(), static_cast<SCSIZE>(rRange.aEnd.Col()-rRange.aStart.Col()+1) );
+}
+
+void ScDocument::DeleteCol(SCROW nStartRow, SCTAB nStartTab, SCROW nEndRow, SCTAB nEndTab,
+ SCCOL nStartCol, SCSIZE nSize, ScDocument* pRefUndoDoc,
+ bool* pUndoOutline, const ScMarkData* pTabMark )
+{
+ SCTAB i;
+
+ PutInOrder( nStartRow, nEndRow );
+ PutInOrder( nStartTab, nEndTab );
+ if ( pTabMark )
+ {
+ nStartTab = 0;
+ nEndTab = static_cast<SCTAB>(maTabs.size())-1;
+ }
+
+ sc::AutoCalcSwitch aACSwitch(*this, false); // avoid multiple calculations
+ ScBulkBroadcast aBulkBroadcast(GetBASM(), SfxHintId::ScDataChanged);
+
+ // handle chunks of consecutive selected sheets together
+ SCTAB nTabRangeStart = nStartTab;
+ SCTAB nTabRangeEnd = nEndTab;
+ lcl_GetFirstTabRange( nTabRangeStart, nTabRangeEnd, pTabMark, static_cast<SCTAB>(maTabs.size()) );
+ do
+ {
+ if ( ValidCol(sal::static_int_cast<SCCOL>(nStartCol+nSize)) )
+ {
+ DelBroadcastAreasInRange( ScRange(
+ ScAddress( nStartCol, nStartRow, nTabRangeStart ),
+ ScAddress( sal::static_int_cast<SCCOL>(nStartCol+nSize-1), nEndRow, nTabRangeEnd ) ) );
+ UpdateBroadcastAreas( URM_INSDEL, ScRange(
+ ScAddress( sal::static_int_cast<SCCOL>(nStartCol+nSize), nStartRow, nTabRangeStart ),
+ ScAddress( MaxCol(), nEndRow, nTabRangeEnd )), -static_cast<SCCOL>(nSize), 0, 0 );
+ }
+ else
+ DelBroadcastAreasInRange( ScRange(
+ ScAddress( nStartCol, nStartRow, nTabRangeStart ),
+ ScAddress( MaxCol(), nEndRow, nTabRangeEnd ) ) );
+ }
+ while ( lcl_GetNextTabRange( nTabRangeStart, nTabRangeEnd, pTabMark, static_cast<SCTAB>(maTabs.size()) ) );
+
+ sc::RefUpdateContext aCxt(*this);
+ const bool bLastColIncluded = (static_cast<SCCOL>(nStartCol + nSize) == GetMaxColCount() && ValidCol(nStartCol));
+ if ( ValidCol(sal::static_int_cast<SCCOL>(nStartCol+nSize)) || bLastColIncluded )
+ {
+ lcl_GetFirstTabRange( nTabRangeStart, nTabRangeEnd, pTabMark, static_cast<SCTAB>(maTabs.size()) );
+ aCxt.meMode = URM_INSDEL;
+ aCxt.mnColDelta = -static_cast<SCCOL>(nSize);
+ if (bLastColIncluded)
+ {
+ // Last column is included, shift a virtually non-existent column in.
+ aCxt.maRange = ScRange( GetMaxColCount(), nStartRow, nTabRangeStart, GetMaxColCount(), nEndRow, nTabRangeEnd);
+ }
+ else
+ {
+ aCxt.maRange = ScRange( sal::static_int_cast<SCCOL>(nStartCol+nSize), nStartRow, nTabRangeStart,
+ MaxCol(), nEndRow, nTabRangeEnd);
+ }
+ do
+ {
+ UpdateReference(aCxt, pRefUndoDoc, true, false);
+ }
+ while ( lcl_GetNextTabRange( nTabRangeStart, nTabRangeEnd, pTabMark, static_cast<SCTAB>(maTabs.size()) ) );
+ }
+
+ if (pUndoOutline)
+ *pUndoOutline = false;
+
+ for (i = nStartTab; i <= nEndTab && i < static_cast<SCTAB>(maTabs.size()); ++i)
+ {
+ if (maTabs[i] && (!pTabMark || pTabMark->GetTableSelect(i)))
+ maTabs[i]->DeleteCol(aCxt.maRegroupCols, nStartCol, nStartRow, nEndRow, nSize, pUndoOutline);
+ }
+
+ if ( ValidCol(sal::static_int_cast<SCCOL>(nStartCol+nSize)) || bLastColIncluded )
+ {
+ // Listeners have been removed in UpdateReference
+ StartNeededListeners();
+
+ // At least all cells using range names pointing relative to the moved
+ // range must be recalculated, and all cells marked postponed dirty.
+ for (const auto& a : maTabs)
+ {
+ if (a)
+ a->SetDirtyIfPostponed();
+ }
+
+ {
+ BroadcastRecalcOnRefMoveGuard g(this);
+ std::for_each(maTabs.begin(), maTabs.end(), BroadcastRecalcOnRefMoveHandler());
+ }
+ }
+
+ if (pChartListenerCollection)
+ pChartListenerCollection->UpdateDirtyCharts();
+}
+
+void ScDocument::DeleteCol( const ScRange& rRange )
+{
+ DeleteCol( rRange.aStart.Row(), rRange.aStart.Tab(),
+ rRange.aEnd.Row(), rRange.aEnd.Tab(),
+ rRange.aStart.Col(), static_cast<SCSIZE>(rRange.aEnd.Col()-rRange.aStart.Col()+1) );
+}
+
+// for Area-Links: Insert/delete cells, when the range is changed.
+// (without Paint)
+
+static void lcl_GetInsDelRanges( const ScRange& rOld, const ScRange& rNew,
+ ScRange& rColRange, bool& rInsCol, bool& rDelCol,
+ ScRange& rRowRange, bool& rInsRow, bool& rDelRow )
+{
+ OSL_ENSURE( rOld.aStart == rNew.aStart, "FitBlock: Beginning is different" );
+
+ rInsCol = rDelCol = rInsRow = rDelRow = false;
+
+ SCCOL nStartX = rOld.aStart.Col();
+ SCROW nStartY = rOld.aStart.Row();
+ SCCOL nOldEndX = rOld.aEnd.Col();
+ SCROW nOldEndY = rOld.aEnd.Row();
+ SCCOL nNewEndX = rNew.aEnd.Col();
+ SCROW nNewEndY = rNew.aEnd.Row();
+ SCTAB nTab = rOld.aStart.Tab();
+
+ // if more rows, columns are inserted/deleted at the old height.
+ bool bGrowY = ( nNewEndY > nOldEndY );
+ SCROW nColEndY = bGrowY ? nOldEndY : nNewEndY;
+ SCCOL nRowEndX = bGrowY ? nNewEndX : nOldEndX;
+
+ // Columns
+
+ if ( nNewEndX > nOldEndX ) // Insert columns
+ {
+ rColRange = ScRange( nOldEndX+1, nStartY, nTab, nNewEndX, nColEndY, nTab );
+ rInsCol = true;
+ }
+ else if ( nNewEndX < nOldEndX ) // Delete columns
+ {
+ rColRange = ScRange( nNewEndX+1, nStartY, nTab, nOldEndX, nColEndY, nTab );
+ rDelCol = true;
+ }
+
+ // Rows
+
+ if ( nNewEndY > nOldEndY ) // Insert rows
+ {
+ rRowRange = ScRange( nStartX, nOldEndY+1, nTab, nRowEndX, nNewEndY, nTab );
+ rInsRow = true;
+ }
+ else if ( nNewEndY < nOldEndY ) // Delete rows
+ {
+ rRowRange = ScRange( nStartX, nNewEndY+1, nTab, nRowEndX, nOldEndY, nTab );
+ rDelRow = true;
+ }
+}
+
+bool ScDocument::HasPartOfMerged( const ScRange& rRange )
+{
+ bool bPart = false;
+ SCTAB nTab = rRange.aStart.Tab();
+
+ SCCOL nStartX = rRange.aStart.Col();
+ SCROW nStartY = rRange.aStart.Row();
+ SCCOL nEndX = rRange.aEnd.Col();
+ SCROW nEndY = rRange.aEnd.Row();
+
+ if (HasAttrib( nStartX, nStartY, nTab, nEndX, nEndY, nTab,
+ HasAttrFlags::Merged | HasAttrFlags::Overlapped ))
+ {
+ ExtendMerge( nStartX, nStartY, nEndX, nEndY, nTab );
+ ExtendOverlapped( nStartX, nStartY, nEndX, nEndY, nTab );
+
+ bPart = ( nStartX != rRange.aStart.Col() || nEndX != rRange.aEnd.Col() ||
+ nStartY != rRange.aStart.Row() || nEndY != rRange.aEnd.Row() );
+ }
+ return bPart;
+}
+
+formula::FormulaTokenRef ScDocument::ResolveStaticReference( const ScAddress& rPos )
+{
+ SCTAB nTab = rPos.Tab();
+ if (!TableExists(nTab))
+ return formula::FormulaTokenRef();
+
+ return maTabs[nTab]->ResolveStaticReference(rPos.Col(), rPos.Row());
+}
+
+formula::FormulaTokenRef ScDocument::ResolveStaticReference( const ScRange& rRange )
+{
+ SCTAB nTab = rRange.aStart.Tab();
+ if (nTab != rRange.aEnd.Tab() || !TableExists(nTab))
+ return formula::FormulaTokenRef();
+
+ return maTabs[nTab]->ResolveStaticReference(
+ rRange.aStart.Col(), rRange.aStart.Row(), rRange.aEnd.Col(), rRange.aEnd.Row());
+}
+
+formula::VectorRefArray ScDocument::FetchVectorRefArray( const ScAddress& rPos, SCROW nLength )
+{
+ SCTAB nTab = rPos.Tab();
+ if (!TableExists(nTab))
+ return formula::VectorRefArray();
+
+ return maTabs[nTab]->FetchVectorRefArray(rPos.Col(), rPos.Row(), rPos.Row()+nLength-1);
+}
+
+#ifdef DBG_UTIL
+void ScDocument::AssertNoInterpretNeeded( const ScAddress& rPos, SCROW nLength )
+{
+ SCTAB nTab = rPos.Tab();
+ assert(TableExists(nTab));
+ return maTabs[nTab]->AssertNoInterpretNeeded(rPos.Col(), rPos.Row(), rPos.Row()+nLength-1);
+}
+#endif
+
+void ScDocument::UnlockAdjustHeight()
+{
+ assert(nAdjustHeightLock > 0);
+ if(nAdjustHeightLock > 0)
+ --nAdjustHeightLock;
+}
+
+bool ScDocument::HandleRefArrayForParallelism( const ScAddress& rPos, SCROW nLength, const ScFormulaCellGroupRef& mxGroup )
+{
+ SCTAB nTab = rPos.Tab();
+ if (!TableExists(nTab))
+ return false;
+
+ return maTabs[nTab]->HandleRefArrayForParallelism(rPos.Col(), rPos.Row(), rPos.Row()+nLength-1, mxGroup);
+}
+
+bool ScDocument::CanFitBlock( const ScRange& rOld, const ScRange& rNew )
+{
+ if ( rOld == rNew )
+ return true;
+
+ bool bOk = true;
+ bool bInsCol,bDelCol,bInsRow,bDelRow;
+ ScRange aColRange,aRowRange;
+ lcl_GetInsDelRanges( rOld, rNew, aColRange,bInsCol,bDelCol, aRowRange,bInsRow,bDelRow );
+
+ if ( bInsCol && !CanInsertCol( aColRange ) ) // Cells at the edge ?
+ bOk = false;
+ if ( bInsRow && !CanInsertRow( aRowRange ) ) // Cells at the edge ?
+ bOk = false;
+
+ if ( bInsCol || bDelCol )
+ {
+ aColRange.aEnd.SetCol(MaxCol());
+ if ( HasPartOfMerged(aColRange) )
+ bOk = false;
+ }
+ if ( bInsRow || bDelRow )
+ {
+ aRowRange.aEnd.SetRow(MaxRow());
+ if ( HasPartOfMerged(aRowRange) )
+ bOk = false;
+ }
+
+ return bOk;
+}
+
+void ScDocument::FitBlock( const ScRange& rOld, const ScRange& rNew, bool bClear )
+{
+ if (bClear)
+ DeleteAreaTab( rOld, InsertDeleteFlags::ALL );
+
+ bool bInsCol,bDelCol,bInsRow,bDelRow;
+ ScRange aColRange,aRowRange;
+ lcl_GetInsDelRanges( rOld, rNew, aColRange,bInsCol,bDelCol, aRowRange,bInsRow,bDelRow );
+
+ if ( bInsCol )
+ InsertCol( aColRange ); // First insert columns
+ if ( bInsRow )
+ InsertRow( aRowRange );
+
+ if ( bDelRow )
+ DeleteRow( aRowRange ); // First delete rows
+ if ( bDelCol )
+ DeleteCol( aColRange );
+
+ // Expand references to inserted rows
+
+ if ( bInsCol || bInsRow )
+ {
+ ScRange aGrowSource = rOld;
+ aGrowSource.aEnd.SetCol(std::min( rOld.aEnd.Col(), rNew.aEnd.Col() ));
+ aGrowSource.aEnd.SetRow(std::min( rOld.aEnd.Row(), rNew.aEnd.Row() ));
+ SCCOL nGrowX = bInsCol ? ( rNew.aEnd.Col() - rOld.aEnd.Col() ) : 0;
+ SCROW nGrowY = bInsRow ? ( rNew.aEnd.Row() - rOld.aEnd.Row() ) : 0;
+ UpdateGrow( aGrowSource, nGrowX, nGrowY );
+ }
+}
+
+void ScDocument::DeleteArea(
+ SCCOL nCol1, SCROW nRow1, SCCOL nCol2, SCROW nRow2, const ScMarkData& rMark,
+ InsertDeleteFlags nDelFlag, bool bBroadcast, sc::ColumnSpanSet* pBroadcastSpans )
+{
+ sc::AutoCalcSwitch aACSwitch(*this, false);
+
+ PutInOrder( nCol1, nCol2 );
+ PutInOrder( nRow1, nRow2 );
+
+ std::vector<ScAddress> aGroupPos;
+ // Destroy and reconstruct listeners only if content is affected.
+ bool bDelContent = ((nDelFlag & ~InsertDeleteFlags::CONTENTS) != nDelFlag);
+ if (bDelContent)
+ {
+ // Record the positions of top and/or bottom formula groups that intersect
+ // the area borders.
+ sc::EndListeningContext aCxt(*this);
+ ScRange aRange(nCol1, nRow1, 0, nCol2, nRow2, 0);
+ for (SCTAB i = 0; i < static_cast<SCTAB>(maTabs.size()); i++)
+ {
+ if (rMark.GetTableSelect(i))
+ {
+ aRange.aStart.SetTab(i);
+ aRange.aEnd.SetTab(i);
+
+ EndListeningIntersectedGroups(aCxt, aRange, &aGroupPos);
+ }
+ }
+ aCxt.purgeEmptyBroadcasters();
+ }
+
+ for (SCTAB i = 0; i < static_cast<SCTAB>(maTabs.size()); i++)
+ if (maTabs[i])
+ if ( rMark.GetTableSelect(i) || bIsUndo )
+ maTabs[i]->DeleteArea(nCol1, nRow1, nCol2, nRow2, nDelFlag, bBroadcast, pBroadcastSpans);
+
+ if (!bDelContent)
+ return;
+
+ // Re-start listeners on those top bottom groups that have been split.
+ SetNeedsListeningGroups(aGroupPos);
+ StartNeededListeners();
+
+ // If formula groups were split their listeners were destroyed and may
+ // need to be notified now that they're restored, ScTable::DeleteArea()
+ // couldn't do that.
+ if (aGroupPos.empty())
+ return;
+
+ ScRange aRange(nCol1, nRow1, 0, nCol2, nRow2, 0);
+ for (SCTAB i = 0; i < static_cast<SCTAB>(maTabs.size()); i++)
+ {
+ if (rMark.GetTableSelect(i))
+ {
+ aRange.aStart.SetTab(i);
+ aRange.aEnd.SetTab(i);
+ SetDirty( aRange, true);
+ }
+ }
+}
+
+void ScDocument::DeleteAreaTab(SCCOL nCol1, SCROW nRow1,
+ SCCOL nCol2, SCROW nRow2,
+ SCTAB nTab, InsertDeleteFlags nDelFlag)
+{
+ PutInOrder( nCol1, nCol2 );
+ PutInOrder( nRow1, nRow2 );
+ if ( ValidTab(nTab) && nTab < static_cast<SCTAB>(maTabs.size()) && maTabs[nTab] )
+ {
+ bool bOldAutoCalc = GetAutoCalc();
+ SetAutoCalc( false ); // avoid multiple calculations
+ maTabs[nTab]->DeleteArea(nCol1, nRow1, nCol2, nRow2, nDelFlag);
+ SetAutoCalc( bOldAutoCalc );
+ }
+}
+
+void ScDocument::DeleteAreaTab( const ScRange& rRange, InsertDeleteFlags nDelFlag )
+{
+ for ( SCTAB nTab = rRange.aStart.Tab(); nTab <= rRange.aEnd.Tab(); nTab++ )
+ DeleteAreaTab( rRange.aStart.Col(), rRange.aStart.Row(),
+ rRange.aEnd.Col(), rRange.aEnd.Row(),
+ nTab, nDelFlag );
+}
+
+void ScDocument::InitUndoSelected(const ScDocument& rSrcDoc, const ScMarkData& rTabSelection,
+ bool bColInfo, bool bRowInfo )
+{
+ if (bIsUndo)
+ {
+ Clear();
+
+ SharePooledResources(&rSrcDoc);
+
+ for (SCTAB nTab = 0; nTab <= rTabSelection.GetLastSelected(); nTab++)
+ if ( rTabSelection.GetTableSelect( nTab ) )
+ {
+ ScTableUniquePtr pTable(new ScTable(*this, nTab, OUString(), bColInfo, bRowInfo));
+ if (nTab < static_cast<SCTAB>(maTabs.size()))
+ maTabs[nTab] = std::move(pTable);
+ else
+ maTabs.push_back(std::move(pTable));
+ }
+ else
+ {
+ if (nTab < static_cast<SCTAB>(maTabs.size()))
+ maTabs[nTab]=nullptr;
+ else
+ maTabs.push_back(nullptr);
+ }
+ }
+ else
+ {
+ OSL_FAIL("InitUndo");
+ }
+}
+
+void ScDocument::InitUndo( const ScDocument& rSrcDoc, SCTAB nTab1, SCTAB nTab2,
+ bool bColInfo, bool bRowInfo )
+{
+ if (!bIsUndo)
+ {
+ OSL_FAIL("InitUndo");
+ return;
+ }
+
+ Clear();
+
+ // Undo document shares its pooled resources with the source document.
+ SharePooledResources(&rSrcDoc);
+
+ if (rSrcDoc.mpShell->GetMedium())
+ maFileURL = rSrcDoc.mpShell->GetMedium()->GetURLObject().GetMainURL(INetURLObject::DecodeMechanism::ToIUri);
+
+ if ( nTab2 >= static_cast<SCTAB>(maTabs.size()))
+ maTabs.resize(nTab2 + 1);
+ for (SCTAB nTab = nTab1; nTab <= nTab2; nTab++)
+ {
+ maTabs[nTab].reset(new ScTable(*this, nTab, OUString(), bColInfo, bRowInfo));
+ }
+}
+
+void ScDocument::AddUndoTab( SCTAB nTab1, SCTAB nTab2, bool bColInfo, bool bRowInfo )
+{
+ if (!bIsUndo)
+ {
+ OSL_FAIL("AddUndoTab");
+ return;
+ }
+
+ if (nTab2 >= static_cast<SCTAB>(maTabs.size()))
+ {
+ maTabs.resize(nTab2+1);
+ }
+
+ for (SCTAB nTab = nTab1; nTab <= nTab2; nTab++)
+ if (!maTabs[nTab])
+ {
+ maTabs[nTab].reset( new ScTable(*this, nTab, OUString(), bColInfo, bRowInfo) );
+ }
+}
+
+void ScDocument::SetCutMode( bool bVal )
+{
+ if (bIsClip)
+ GetClipParam().mbCutMode = bVal;
+ else
+ {
+ OSL_FAIL("SetCutMode without bIsClip");
+ }
+}
+
+bool ScDocument::IsCutMode()
+{
+ if (bIsClip)
+ return GetClipParam().mbCutMode;
+ else
+ {
+ OSL_FAIL("IsCutMode without bIsClip");
+ return false;
+ }
+}
+
+void ScDocument::CopyToDocument(SCCOL nCol1, SCROW nRow1, SCTAB nTab1,
+ SCCOL nCol2, SCROW nRow2, SCTAB nTab2,
+ InsertDeleteFlags nFlags, bool bOnlyMarked, ScDocument& rDestDoc,
+ const ScMarkData* pMarks, bool bColRowFlags )
+{
+ if (ValidTab(nTab1) && ValidTab(nTab2))
+ {
+ ScRange aThisRange(nCol1, nRow1, nTab1, nCol2, nRow2, nTab2);
+ CopyToDocument(aThisRange, nFlags, bOnlyMarked, rDestDoc, pMarks, bColRowFlags);
+ }
+}
+
+void ScDocument::UndoToDocument(SCCOL nCol1, SCROW nRow1, SCTAB nTab1,
+ SCCOL nCol2, SCROW nRow2, SCTAB nTab2,
+ InsertDeleteFlags nFlags, bool bOnlyMarked, ScDocument& rDestDoc)
+{
+ PutInOrder( nCol1, nCol2 );
+ PutInOrder( nRow1, nRow2 );
+ PutInOrder( nTab1, nTab2 );
+ if (!(ValidTab(nTab1) && ValidTab(nTab2)))
+ return;
+
+ sc::AutoCalcSwitch aACSwitch(rDestDoc, false); // avoid multiple calculations
+
+ if (nTab1 > 0)
+ CopyToDocument(0, 0, 0, MaxCol(), MaxRow(), nTab1-1, InsertDeleteFlags::FORMULA, false, rDestDoc);
+
+ sc::CopyToDocContext aCxt(rDestDoc);
+ assert( nTab2 < static_cast<SCTAB>(maTabs.size()) && nTab2 < static_cast<SCTAB>(rDestDoc.maTabs.size()));
+ for (SCTAB i = nTab1; i <= nTab2; i++)
+ {
+ if (maTabs[i] && rDestDoc.maTabs[i])
+ maTabs[i]->UndoToTable(aCxt, nCol1, nRow1, nCol2, nRow2, nFlags,
+ bOnlyMarked, rDestDoc.maTabs[i].get());
+ }
+
+ if (nTab2 < MAXTAB)
+ CopyToDocument(0, 0, nTab2+1, MaxCol(), MaxRow(), MAXTAB, InsertDeleteFlags::FORMULA, false, rDestDoc);
+}
+
+void ScDocument::CopyToDocument(const ScRange& rRange,
+ InsertDeleteFlags nFlags, bool bOnlyMarked, ScDocument& rDestDoc,
+ const ScMarkData* pMarks, bool bColRowFlags)
+{
+ ScRange aNewRange = rRange;
+ aNewRange.PutInOrder();
+
+ if (rDestDoc.aDocName.isEmpty())
+ rDestDoc.aDocName = aDocName;
+
+ sc::AutoCalcSwitch aACSwitch(rDestDoc, false); // avoid multiple calculations
+ ScBulkBroadcast aBulkBroadcast(rDestDoc.GetBASM(), SfxHintId::ScDataChanged);
+ sc::DelayDeletingBroadcasters delayDeletingBroadcasters(*this);
+
+ sc::CopyToDocContext aCxt(rDestDoc);
+ aCxt.setStartListening(false);
+
+ SCTAB nMinSizeBothTabs = static_cast<SCTAB>(std::min(maTabs.size(), rDestDoc.maTabs.size()));
+ for (SCTAB i = aNewRange.aStart.Tab(); i <= aNewRange.aEnd.Tab() && i < nMinSizeBothTabs; i++)
+ {
+ ScTable* pTab = FetchTable(i);
+ ScTable* pDestTab = rDestDoc.FetchTable(i);
+ if (!pTab || !pDestTab)
+ continue;
+
+ pTab->CopyToTable(
+ aCxt, aNewRange.aStart.Col(), aNewRange.aStart.Row(), aNewRange.aEnd.Col(), aNewRange.aEnd.Row(),
+ nFlags, bOnlyMarked, pDestTab, pMarks, false, bColRowFlags,
+ /*bGlobalNamesToLocal*/false, /*bCopyCaptions*/true);
+ }
+
+ rDestDoc.StartAllListeners(aNewRange);
+}
+
+void ScDocument::UndoToDocument(const ScRange& rRange,
+ InsertDeleteFlags nFlags, bool bOnlyMarked, ScDocument& rDestDoc)
+{
+ sc::AutoCalcSwitch aAutoCalcSwitch(*this, false);
+
+ ScRange aNewRange = rRange;
+ aNewRange.PutInOrder();
+ SCTAB nTab1 = aNewRange.aStart.Tab();
+ SCTAB nTab2 = aNewRange.aEnd.Tab();
+
+ sc::CopyToDocContext aCxt(rDestDoc);
+ if (nTab1 > 0)
+ CopyToDocument(0, 0, 0, MaxCol(), MaxRow(), nTab1-1, InsertDeleteFlags::FORMULA, false, rDestDoc);
+
+ SCTAB nMinSizeBothTabs = static_cast<SCTAB>(std::min(maTabs.size(), rDestDoc.maTabs.size()));
+ for (SCTAB i = nTab1; i <= nTab2 && i < nMinSizeBothTabs; i++)
+ {
+ if (maTabs[i] && rDestDoc.maTabs[i])
+ maTabs[i]->UndoToTable(aCxt, aNewRange.aStart.Col(), aNewRange.aStart.Row(),
+ aNewRange.aEnd.Col(), aNewRange.aEnd.Row(),
+ nFlags, bOnlyMarked, rDestDoc.maTabs[i].get());
+ }
+
+ if (nTab2 < static_cast<SCTAB>(maTabs.size()))
+ CopyToDocument(0, 0 , nTab2+1, MaxCol(), MaxRow(), maTabs.size(), InsertDeleteFlags::FORMULA, false, rDestDoc);
+}
+
+void ScDocument::CopyToClip(const ScClipParam& rClipParam,
+ ScDocument* pClipDoc, const ScMarkData* pMarks,
+ bool bKeepScenarioFlags, bool bIncludeObjects )
+{
+ OSL_ENSURE( pMarks, "CopyToClip: ScMarkData fails" );
+
+ if (bIsClip)
+ return;
+
+ if (!pClipDoc)
+ {
+ SAL_WARN("sc", "CopyToClip: no ClipDoc");
+ pClipDoc = ScModule::GetClipDoc();
+ }
+
+ if (mpShell->GetMedium())
+ {
+ pClipDoc->maFileURL = mpShell->GetMedium()->GetURLObject().GetMainURL(INetURLObject::DecodeMechanism::ToIUri);
+ // for unsaved files use the title name and adjust during save of file
+ if (pClipDoc->maFileURL.isEmpty())
+ pClipDoc->maFileURL = mpShell->GetName();
+ }
+ else
+ {
+ pClipDoc->maFileURL = mpShell->GetName();
+ }
+
+ //init maTabNames
+ for (const auto& rxTab : maTabs)
+ {
+ if( rxTab )
+ {
+ OUString aTabName = rxTab->GetName();
+ pClipDoc->maTabNames.push_back(aTabName);
+ }
+ else
+ pClipDoc->maTabNames.emplace_back();
+ }
+
+ pClipDoc->aDocName = aDocName;
+ pClipDoc->SetClipParam(rClipParam);
+ ScRange aClipRange = rClipParam.getWholeRange();
+ SCTAB nEndTab = static_cast<SCTAB>(maTabs.size());
+
+ pClipDoc->ResetClip(this, pMarks);
+
+ sc::CopyToClipContext aCxt(*pClipDoc, bKeepScenarioFlags);
+ CopyRangeNamesToClip(pClipDoc, aClipRange, pMarks);
+
+ for (SCTAB i = 0; i < nEndTab; ++i)
+ {
+ if (!maTabs[i] || i >= static_cast<SCTAB>(pClipDoc->maTabs.size()) || !pClipDoc->maTabs[i])
+ continue;
+
+ if ( pMarks && !pMarks->GetTableSelect(i) )
+ continue;
+
+ maTabs[i]->CopyToClip(aCxt, rClipParam.maRanges, pClipDoc->maTabs[i].get());
+
+ if (mpDrawLayer && bIncludeObjects)
+ {
+ // also copy drawing objects
+ tools::Rectangle aObjRect = GetMMRect(
+ aClipRange.aStart.Col(), aClipRange.aStart.Row(), aClipRange.aEnd.Col(), aClipRange.aEnd.Row(), i);
+ mpDrawLayer->CopyToClip(pClipDoc, i, aObjRect);
+ }
+ }
+
+ // Make sure to mark overlapped cells.
+ pClipDoc->ExtendMerge(aClipRange, true);
+}
+
+void ScDocument::CopyStaticToDocument(const ScRange& rSrcRange, SCTAB nDestTab, ScDocument& rDestDoc)
+{
+ ScTable* pSrcTab = rSrcRange.aStart.Tab() < static_cast<SCTAB>(maTabs.size()) ? maTabs[rSrcRange.aStart.Tab()].get() : nullptr;
+ ScTable* pDestTab = nDestTab < static_cast<SCTAB>(rDestDoc.maTabs.size()) ? rDestDoc.maTabs[nDestTab].get() : nullptr;
+
+ if (!pSrcTab || !pDestTab)
+ return;
+
+ rDestDoc.GetFormatTable()->MergeFormatter(*GetFormatTable());
+ SvNumberFormatterMergeMap aMap = rDestDoc.GetFormatTable()->ConvertMergeTableToMap();
+
+ pSrcTab->CopyStaticToDocument(
+ rSrcRange.aStart.Col(), rSrcRange.aStart.Row(), rSrcRange.aEnd.Col(), rSrcRange.aEnd.Row(),
+ aMap, pDestTab);
+}
+
+void ScDocument::CopyCellToDocument( const ScAddress& rSrcPos, const ScAddress& rDestPos, ScDocument& rDestDoc )
+{
+ if (!TableExists(rSrcPos.Tab()) || !rDestDoc.TableExists(rDestPos.Tab()))
+ return;
+
+ ScTable& rSrcTab = *maTabs[rSrcPos.Tab()];
+ ScTable& rDestTab = *rDestDoc.maTabs[rDestPos.Tab()];
+
+ rSrcTab.CopyCellToDocument(rSrcPos.Col(), rSrcPos.Row(), rDestPos.Col(), rDestPos.Row(), rDestTab);
+}
+
+void ScDocument::CopyTabToClip(SCCOL nCol1, SCROW nRow1,
+ SCCOL nCol2, SCROW nRow2,
+ SCTAB nTab, ScDocument* pClipDoc)
+{
+ if (bIsClip)
+ return;
+
+ if (!pClipDoc)
+ {
+ SAL_WARN("sc", "CopyTabToClip: no ClipDoc");
+ pClipDoc = ScModule::GetClipDoc();
+ }
+
+ if (mpShell->GetMedium())
+ {
+ pClipDoc->maFileURL = mpShell->GetMedium()->GetURLObject().GetMainURL(INetURLObject::DecodeMechanism::ToIUri);
+ // for unsaved files use the title name and adjust during save of file
+ if (pClipDoc->maFileURL.isEmpty())
+ pClipDoc->maFileURL = mpShell->GetName();
+ }
+ else
+ {
+ pClipDoc->maFileURL = mpShell->GetName();
+ }
+
+ //init maTabNames
+ for (const auto& rxTab : maTabs)
+ {
+ if( rxTab )
+ {
+ OUString aTabName = rxTab->GetName();
+ pClipDoc->maTabNames.push_back(aTabName);
+ }
+ else
+ pClipDoc->maTabNames.emplace_back();
+ }
+
+ PutInOrder( nCol1, nCol2 );
+ PutInOrder( nRow1, nRow2 );
+
+ ScClipParam& rClipParam = pClipDoc->GetClipParam();
+ pClipDoc->aDocName = aDocName;
+ rClipParam.maRanges.RemoveAll();
+ rClipParam.maRanges.push_back(ScRange(nCol1, nRow1, 0, nCol2, nRow2, 0));
+ pClipDoc->ResetClip( this, nTab );
+
+ sc::CopyToClipContext aCxt(*pClipDoc, false);
+ if (nTab < static_cast<SCTAB>(maTabs.size()) && nTab < static_cast<SCTAB>(pClipDoc->maTabs.size()))
+ if (maTabs[nTab] && pClipDoc->maTabs[nTab])
+ maTabs[nTab]->CopyToClip(aCxt, nCol1, nRow1, nCol2, nRow2, pClipDoc->maTabs[nTab].get());
+
+ pClipDoc->GetClipParam().mbCutMode = false;
+}
+
+void ScDocument::TransposeClip(ScDocument* pTransClip, InsertDeleteFlags nFlags, bool bAsLink,
+ bool bIncludeFiltered)
+{
+ OSL_ENSURE( bIsClip && pTransClip && pTransClip->bIsClip,
+ "TransposeClip with wrong Document" );
+
+ // initialize
+ // -> pTransClip has to be deleted before the original document!
+
+ pTransClip->ResetClip(this, nullptr); // all
+
+ // Take over range
+
+ if (pRangeName)
+ {
+ pTransClip->GetRangeName()->clear();
+ for (const auto& rEntry : *pRangeName)
+ {
+ sal_uInt16 nIndex = rEntry.second->GetIndex();
+ ScRangeData* pData = new ScRangeData(*rEntry.second);
+ if (pTransClip->pRangeName->insert(pData))
+ pData->SetIndex(nIndex);
+ }
+ }
+
+ ScRange aCombinedClipRange = GetClipParam().getWholeRange();
+
+ if (!ValidRow(aCombinedClipRange.aEnd.Row() - aCombinedClipRange.aStart.Row()))
+ {
+ SAL_WARN("sc", "TransposeClip: Too big");
+ return;
+ }
+
+ // Transpose of filtered multi range row selection is a special case since filtering
+ // and selection are in the same dimension (i.e. row).
+ // The filtered row status and the selection ranges are not available at the same time,
+ // handle this case specially, do not use GetClipParam().getWholeRange(),
+ // instead loop through the ranges, calculate the row offset and handle filtered rows and
+ // create in ScClipParam::transpose() a unified range.
+ const bool bIsMultiRangeRowFilteredTranspose
+ = !bIncludeFiltered && GetClipParam().isMultiRange()
+ && HasFilteredRows(aCombinedClipRange.aStart.Row(), aCombinedClipRange.aEnd.Row(),
+ aCombinedClipRange.aStart.Tab())
+ && GetClipParam().meDirection == ScClipParam::Row;
+
+ ScRangeList aClipRanges;
+ if (bIsMultiRangeRowFilteredTranspose)
+ aClipRanges = GetClipParam().maRanges;
+ else
+ aClipRanges = ScRangeList(aCombinedClipRange);
+
+ // The data
+ ScRange aClipRange;
+ SCROW nRowCount = 0; // next consecutive row
+ for (size_t j = 0, n = aClipRanges.size(); j < n; ++j)
+ {
+ aClipRange = aClipRanges[j];
+
+ SCROW nRowOffset = 0;
+ if (bIsMultiRangeRowFilteredTranspose)
+ {
+ // adjust for the rows that are filtered
+ nRowOffset = nRowCount;
+
+ // calculate filtered rows of current clip range
+ SCROW nRowCountNonFiltered = CountNonFilteredRows(
+ aClipRange.aStart.Row(), aClipRange.aEnd.Row(), aClipRange.aStart.Tab());
+ assert(!bIncludeFiltered && "bIsMultiRangeRowFilteredTranspose can only be true if bIncludeFiltered is false");
+ nRowCount += nRowCountNonFiltered; // for next iteration
+ }
+
+ for (SCTAB i = 0; i < static_cast<SCTAB>(maTabs.size()); i++)
+ {
+ if (maTabs[i])
+ {
+ OSL_ENSURE(pTransClip->maTabs[i], "TransposeClip: Table not there");
+ maTabs[i]->TransposeClip(
+ aClipRange.aStart.Col(), aClipRange.aStart.Row(), aClipRange.aEnd.Col(),
+ aClipRange.aEnd.Row(), aCombinedClipRange.aStart.Row(), nRowOffset,
+ pTransClip->maTabs[i].get(), nFlags, bAsLink, bIncludeFiltered);
+
+ if ( mpDrawLayer && ( nFlags & InsertDeleteFlags::OBJECTS ) )
+ {
+ // Drawing objects are copied to the new area without transposing.
+ // CopyFromClip is used to adjust the objects to the transposed block's
+ // cell range area.
+ // (mpDrawLayer in the original clipboard document is set only if there
+ // are drawing objects to copy)
+
+ pTransClip->InitDrawLayer();
+ tools::Rectangle aSourceRect = GetMMRect( aClipRange.aStart.Col(), aClipRange.aStart.Row(),
+ aClipRange.aEnd.Col(), aClipRange.aEnd.Row(), i );
+ tools::Rectangle aDestRect = pTransClip->GetMMRect( 0, 0,
+ static_cast<SCCOL>(aClipRange.aEnd.Row() - aClipRange.aStart.Row()),
+ static_cast<SCROW>(aClipRange.aEnd.Col() - aClipRange.aStart.Col()), i );
+ pTransClip->mpDrawLayer->CopyFromClip( mpDrawLayer.get(), i, aSourceRect, ScAddress(0,0,i), aDestRect );
+ }
+ }
+ }
+ }
+
+ pTransClip->SetClipParam(GetClipParam());
+ pTransClip->GetClipParam().transpose(*this, bIncludeFiltered,
+ bIsMultiRangeRowFilteredTranspose);
+
+ // This happens only when inserting...
+
+ GetClipParam().mbCutMode = false;
+}
+
+namespace {
+
+void copyUsedNamesToClip(ScRangeName* pClipRangeName, ScRangeName* pRangeName,
+ const sc::UpdatedRangeNames::NameIndicesType& rUsedNames)
+{
+ pClipRangeName->clear();
+ for (const auto& rEntry : *pRangeName) //TODO: also DB and Pivot regions!!!
+ {
+ sal_uInt16 nIndex = rEntry.second->GetIndex();
+ bool bInUse = (rUsedNames.count(nIndex) > 0);
+ if (!bInUse)
+ continue;
+
+ ScRangeData* pData = new ScRangeData(*rEntry.second);
+ if (pClipRangeName->insert(pData))
+ pData->SetIndex(nIndex);
+ }
+}
+
+}
+
+void ScDocument::CopyRangeNamesToClip(ScDocument* pClipDoc, const ScRange& rClipRange, const ScMarkData* pMarks)
+{
+ if (!pRangeName || pRangeName->empty())
+ return;
+
+ sc::UpdatedRangeNames aUsedNames; // indexes of named ranges that are used in the copied cells
+ SCTAB nMinSizeBothTabs = static_cast<SCTAB>(std::min(maTabs.size(), pClipDoc->maTabs.size()));
+ for (SCTAB i = 0; i < nMinSizeBothTabs; ++i)
+ if (maTabs[i] && pClipDoc->maTabs[i])
+ if ( !pMarks || pMarks->GetTableSelect(i) )
+ maTabs[i]->FindRangeNamesInUse(
+ rClipRange.aStart.Col(), rClipRange.aStart.Row(),
+ rClipRange.aEnd.Col(), rClipRange.aEnd.Row(), aUsedNames);
+
+ /* TODO: handle also sheet-local names */
+ sc::UpdatedRangeNames::NameIndicesType aUsedGlobalNames( aUsedNames.getUpdatedNames(-1));
+ copyUsedNamesToClip(pClipDoc->GetRangeName(), pRangeName.get(), aUsedGlobalNames);
+}
+
+ScDocument::NumFmtMergeHandler::NumFmtMergeHandler(ScDocument& rDoc, const ScDocument& rSrcDoc)
+ : mrDoc(rDoc)
+{
+ mrDoc.MergeNumberFormatter(rSrcDoc);
+}
+
+ScDocument::NumFmtMergeHandler::~NumFmtMergeHandler()
+{
+ ScMutationGuard aGuard(mrDoc, ScMutationGuardFlags::CORE);
+ mrDoc.pFormatExchangeList = nullptr;
+}
+
+void ScDocument::PrepareFormulaCalc()
+{
+ ScMutationGuard aGuard(*this, ScMutationGuardFlags::CORE);
+ mpFormulaGroupCxt.reset();
+}
+
+SvtBroadcaster* ScDocument::GetBroadcaster( const ScAddress& rPos )
+{
+ ScTable* pTab = FetchTable(rPos.Tab());
+ if (!pTab)
+ return nullptr;
+
+ return pTab->GetBroadcaster(rPos.Col(), rPos.Row());
+}
+
+const SvtBroadcaster* ScDocument::GetBroadcaster( const ScAddress& rPos ) const
+{
+ const ScTable* pTab = FetchTable(rPos.Tab());
+ if (!pTab)
+ return nullptr;
+
+ return pTab->GetBroadcaster(rPos.Col(), rPos.Row());
+}
+
+void ScDocument::DeleteBroadcasters( sc::ColumnBlockPosition& rBlockPos, const ScAddress& rTopPos, SCROW nLength )
+{
+ ScTable* pTab = FetchTable(rTopPos.Tab());
+ if (!pTab || nLength <= 0)
+ return;
+
+ pTab->DeleteBroadcasters(rBlockPos, rTopPos.Col(), rTopPos.Row(), rTopPos.Row()+nLength-1);
+}
+
+#if DUMP_COLUMN_STORAGE
+void ScDocument::DumpColumnStorage( SCTAB nTab, SCCOL nCol ) const
+{
+ const ScTable* pTab = FetchTable(nTab);
+ if (!pTab)
+ return;
+
+ pTab->DumpColumnStorage(nCol);
+}
+#endif
+
+#if DEBUG_AREA_BROADCASTER
+void ScDocument::DumpAreaBroadcasters() const
+{
+ if (pBASM)
+ pBASM->Dump();
+}
+#endif
+
+bool ScDocument::TableExists( SCTAB nTab ) const
+{
+ return ValidTab(nTab) && o3tl::make_unsigned(nTab) < maTabs.size() && maTabs[nTab];
+}
+
+ScTable* ScDocument::FetchTable( SCTAB nTab )
+{
+ if (!TableExists(nTab))
+ return nullptr;
+
+ return maTabs[nTab].get();
+}
+
+const ScTable* ScDocument::FetchTable( SCTAB nTab ) const
+{
+ if (!TableExists(nTab))
+ return nullptr;
+
+ return maTabs[nTab].get();
+}
+
+ScColumnsRange ScDocument::GetWritableColumnsRange( SCTAB nTab, SCCOL nColBegin, SCCOL nColEnd)
+{
+ if (!TableExists(nTab))
+ {
+ SAL_WARN("sc", "GetWritableColumnsRange() called for non-existent table");
+ return ScColumnsRange(-1, -1);
+ }
+ return maTabs[nTab]->GetWritableColumnsRange(nColBegin, nColEnd);
+}
+
+ScColumnsRange ScDocument::GetAllocatedColumnsRange( SCTAB nTab, SCCOL nColBegin, SCCOL nColEnd) const
+{
+ if (!TableExists(nTab))
+ return ScColumnsRange(-1, -1);
+ return maTabs[nTab]->GetAllocatedColumnsRange(nColBegin, nColEnd);
+}
+
+ScColumnsRange ScDocument::GetColumnsRange( SCTAB nTab, SCCOL nColBegin, SCCOL nColEnd) const
+{
+ if (!TableExists(nTab))
+ return ScColumnsRange(-1, -1);
+ return maTabs[nTab]->GetColumnsRange(nColBegin, nColEnd);
+}
+
+void ScDocument::MergeNumberFormatter(const ScDocument& rSrcDoc)
+{
+ SvNumberFormatter* pThisFormatter = mxPoolHelper->GetFormTable();
+ SvNumberFormatter* pOtherFormatter = rSrcDoc.mxPoolHelper->GetFormTable();
+ if (pOtherFormatter && pOtherFormatter != pThisFormatter)
+ {
+ SvNumberFormatterIndexTable* pExchangeList =
+ pThisFormatter->MergeFormatter(*pOtherFormatter);
+ if (!pExchangeList->empty())
+ {
+ ScMutationGuard aGuard(*this, ScMutationGuardFlags::CORE);
+ pFormatExchangeList = pExchangeList;
+ }
+ }
+}
+
+ScClipParam& ScDocument::GetClipParam()
+{
+ if (!mpClipParam)
+ mpClipParam.reset(new ScClipParam);
+
+ return *mpClipParam;
+}
+
+void ScDocument::SetClipParam(const ScClipParam& rParam)
+{
+ mpClipParam.reset(new ScClipParam(rParam));
+}
+
+bool ScDocument::IsClipboardSource() const
+{
+ if (bIsClip || mpShell == nullptr || mpShell->IsLoading())
+ return false;
+
+ ScDocument* pClipDoc = ScModule::GetClipDoc();
+ return pClipDoc && pClipDoc->bIsClip && pClipDoc->mxPoolHelper.is() && mxPoolHelper.is() &&
+ mxPoolHelper->GetDocPool() == pClipDoc->mxPoolHelper->GetDocPool();
+}
+
+void ScDocument::StartListeningFromClip(
+ sc::StartListeningContext& rStartCxt, sc::EndListeningContext& rEndCxt,
+ SCTAB nTab, SCCOL nCol1, SCROW nRow1, SCCOL nCol2, SCROW nRow2 )
+{
+ ScTable* pTab = FetchTable(nTab);
+ if (!pTab)
+ return;
+
+ pTab->StartListeningFormulaCells(rStartCxt, rEndCxt, nCol1, nRow1, nCol2, nRow2);
+}
+
+void ScDocument::StartListeningFromClip( SCCOL nCol1, SCROW nRow1,
+ SCCOL nCol2, SCROW nRow2,
+ const ScMarkData& rMark, InsertDeleteFlags nInsFlag )
+{
+ if (!(nInsFlag & InsertDeleteFlags::CONTENTS))
+ return;
+
+ auto pSet = std::make_shared<sc::ColumnBlockPositionSet>(*this);
+
+ sc::StartListeningContext aStartCxt(*this, pSet);
+ sc::EndListeningContext aEndCxt(*this, pSet, nullptr);
+
+ for (SCTAB nTab : rMark)
+ StartListeningFromClip(aStartCxt, aEndCxt, nTab, nCol1, nRow1, nCol2, nRow2);
+}
+
+void ScDocument::SetDirtyFromClip(
+ SCCOL nCol1, SCROW nRow1, SCCOL nCol2, SCROW nRow2, const ScMarkData& rMark,
+ InsertDeleteFlags nInsFlag, sc::ColumnSpanSet& rBroadcastSpans )
+{
+ if (nInsFlag & InsertDeleteFlags::CONTENTS)
+ {
+ SCTAB nMax = static_cast<SCTAB>(maTabs.size());
+ for (const auto& rTab : rMark)
+ {
+ if (rTab >= nMax)
+ break;
+ if (maTabs[rTab])
+ maTabs[rTab]->SetDirtyFromClip(nCol1, nRow1, nCol2, nRow2, rBroadcastSpans);
+ }
+ }
+}
+
+bool ScDocument::InitColumnBlockPosition( sc::ColumnBlockPosition& rBlockPos, SCTAB nTab, SCCOL nCol )
+{
+ if (!TableExists(nTab))
+ return false;
+
+ return maTabs[nTab]->InitColumnBlockPosition(rBlockPos, nCol);
+}
+
+void ScDocument::CopyBlockFromClip(
+ sc::CopyFromClipContext& rCxt, SCCOL nCol1, SCROW nRow1, SCCOL nCol2, SCROW nRow2,
+ const ScMarkData& rMark, SCCOL nDx, SCROW nDy )
+{
+ TableContainer& rClipTabs = rCxt.getClipDoc()->maTabs;
+ SCTAB nTabEnd = rCxt.getTabEnd();
+ SCTAB nClipTab = 0;
+ for (SCTAB i = rCxt.getTabStart(); i <= nTabEnd && i < static_cast<SCTAB>(maTabs.size()); i++)
+ {
+ if (maTabs[i] && rMark.GetTableSelect(i) )
+ {
+ while (!rClipTabs[nClipTab]) nClipTab = (nClipTab+1) % static_cast<SCTAB>(rClipTabs.size());
+
+ maTabs[i]->CopyFromClip(
+ rCxt, nCol1, nRow1, nCol2, nRow2, nDx, nDy, rClipTabs[nClipTab].get());
+
+ if (rCxt.getClipDoc()->mpDrawLayer && (rCxt.getInsertFlag() & InsertDeleteFlags::OBJECTS))
+ {
+ // also copy drawing objects
+
+ // drawing layer must be created before calling CopyFromClip
+ // (ScDocShell::MakeDrawLayer also does InitItems etc.)
+ OSL_ENSURE( mpDrawLayer, "CopyBlockFromClip: No drawing layer" );
+ if ( mpDrawLayer )
+ {
+ // For GetMMRect, the row heights in the target document must already be valid
+ // (copied in an extra step before pasting, or updated after pasting cells, but
+ // before pasting objects).
+
+ tools::Rectangle aSourceRect = rCxt.getClipDoc()->GetMMRect(
+ nCol1-nDx, nRow1-nDy, nCol2-nDx, nRow2-nDy, nClipTab );
+ tools::Rectangle aDestRect = GetMMRect( nCol1, nRow1, nCol2, nRow2, i );
+ mpDrawLayer->CopyFromClip(rCxt.getClipDoc()->mpDrawLayer.get(), nClipTab, aSourceRect,
+ ScAddress( nCol1, nRow1, i ), aDestRect );
+ }
+ }
+
+ nClipTab = (nClipTab+1) % static_cast<SCTAB>(rClipTabs.size());
+ }
+ }
+ if (!(rCxt.getInsertFlag() & InsertDeleteFlags::CONTENTS))
+ return;
+
+ nClipTab = 0;
+ for (SCTAB i = rCxt.getTabStart(); i <= nTabEnd && i < static_cast<SCTAB>(maTabs.size()); i++)
+ {
+ if (maTabs[i] && rMark.GetTableSelect(i) )
+ {
+ while (!rClipTabs[nClipTab]) nClipTab = (nClipTab+1) % static_cast<SCTAB>(rClipTabs.size());
+ SCTAB nDz = i - nClipTab;
+
+ // ranges of consecutive selected tables (in clipboard and dest. doc)
+ // must be handled in one UpdateReference call
+ SCTAB nFollow = 0;
+ while ( i + nFollow < nTabEnd
+ && rMark.GetTableSelect( i + nFollow + 1 )
+ && nClipTab + nFollow < MAXTAB
+ && rClipTabs[(nClipTab + nFollow + 1) % static_cast<SCTAB>(rClipTabs.size())] )
+ ++nFollow;
+
+ sc::RefUpdateContext aRefCxt(*this, rCxt.getClipDoc());
+ aRefCxt.maRange = ScRange(nCol1, nRow1, i, nCol2, nRow2, i+nFollow);
+ aRefCxt.mnColDelta = nDx;
+ aRefCxt.mnRowDelta = nDy;
+ aRefCxt.mnTabDelta = nDz;
+ aRefCxt.setBlockPositionReference(rCxt.getBlockPositionSet()); // share mdds position caching
+ if (rCxt.getClipDoc()->GetClipParam().mbCutMode)
+ {
+ // Update references only if cut originates from the same
+ // document we are pasting into.
+ if (rCxt.getClipDoc()->GetPool() == GetPool())
+ {
+ bool bOldInserting = IsInsertingFromOtherDoc();
+ SetInsertingFromOtherDoc( true);
+ aRefCxt.meMode = URM_MOVE;
+ UpdateReference(aRefCxt, rCxt.getUndoDoc(), false);
+
+ // For URM_MOVE group listeners may have been removed,
+ // re-establish them.
+ if (!aRefCxt.maRegroupCols.empty())
+ {
+ /* TODO: holding the ColumnSet in a shared_ptr at
+ * RefUpdateContext would eliminate the need of
+ * copying it here. */
+ auto pColSet = std::make_shared<sc::ColumnSet>( aRefCxt.maRegroupCols);
+ StartNeededListeners( pColSet);
+ }
+
+ SetInsertingFromOtherDoc( bOldInserting);
+ }
+ }
+ else
+ {
+ aRefCxt.meMode = URM_COPY;
+ UpdateReference(aRefCxt, rCxt.getUndoDoc(), false);
+ }
+
+ nClipTab = (nClipTab+nFollow+1) % static_cast<SCTAB>(rClipTabs.size());
+ i = sal::static_int_cast<SCTAB>( i + nFollow );
+ }
+ }
+}
+
+SCROW ScDocument::CopyNonFilteredFromClip(sc::CopyFromClipContext& rCxt, SCCOL nCol1, SCROW nRow1,
+ SCCOL nCol2, SCROW nRow2, const ScMarkData& rMark,
+ SCCOL nDx, SCROW& rClipStartRow, SCROW nClipEndRow)
+{
+ // call CopyBlockFromClip for ranges of consecutive non-filtered rows
+ // nCol1/nRow1 etc. is in target doc
+
+ // filtered state is taken from first used table in clipboard (as in GetClipArea)
+ SCTAB nFlagTab = 0;
+ TableContainer& rClipTabs = rCxt.getClipDoc()->maTabs;
+ while ( nFlagTab < static_cast<SCTAB>(rClipTabs.size()) && !rClipTabs[nFlagTab] )
+ ++nFlagTab;
+
+ SCROW nSourceRow = rClipStartRow;
+ SCROW nSourceEnd = nClipEndRow;
+ SCROW nDestRow = nRow1;
+ SCROW nFilteredRows = 0;
+
+ while ( nSourceRow <= nSourceEnd && nDestRow <= nRow2 )
+ {
+ // skip filtered rows
+ SCROW nSourceRowOriginal = nSourceRow;
+ nSourceRow = rCxt.getClipDoc()->FirstNonFilteredRow(nSourceRow, nSourceEnd, nFlagTab);
+ nFilteredRows += nSourceRow - nSourceRowOriginal;
+
+ if ( nSourceRow <= nSourceEnd )
+ {
+ // look for more non-filtered rows following
+ SCROW nLastRow = nSourceRow;
+ (void)rCxt.getClipDoc()->RowFiltered(nSourceRow, nFlagTab, nullptr, &nLastRow);
+ SCROW nFollow = nLastRow - nSourceRow;
+
+ if (nFollow > nSourceEnd - nSourceRow)
+ nFollow = nSourceEnd - nSourceRow;
+ if (nFollow > nRow2 - nDestRow)
+ nFollow = nRow2 - nDestRow;
+
+ SCROW nNewDy = nDestRow - nSourceRow;
+ CopyBlockFromClip(
+ rCxt, nCol1, nDestRow, nCol2, nDestRow + nFollow, rMark, nDx, nNewDy);
+
+ nSourceRow += nFollow + 1;
+ nDestRow += nFollow + 1;
+ }
+ }
+ rClipStartRow = nSourceRow;
+ return nFilteredRows;
+}
+
+namespace {
+
+class BroadcastAction : public sc::ColumnSpanSet::ColumnAction
+{
+ ScDocument& mrDoc;
+ ScColumn* mpCol;
+
+public:
+ explicit BroadcastAction( ScDocument& rDoc ) : mrDoc(rDoc), mpCol(nullptr) {}
+
+ virtual void startColumn( ScColumn* pCol ) override
+ {
+ mpCol = pCol;
+ }
+
+ virtual void execute( SCROW nRow1, SCROW nRow2, bool bVal ) override
+ {
+ if (!bVal)
+ return;
+
+ assert(mpCol);
+ ScRange aRange(mpCol->GetCol(), nRow1, mpCol->GetTab());
+ aRange.aEnd.SetRow(nRow2);
+ mrDoc.BroadcastCells(aRange, SfxHintId::ScDataChanged);
+ };
+};
+
+}
+
+void ScDocument::CopyFromClip(
+ const ScRange& rDestRange, const ScMarkData& rMark, InsertDeleteFlags nInsFlag,
+ ScDocument* pRefUndoDoc, ScDocument* pClipDoc, bool bResetCut,
+ bool bAsLink, bool bIncludeFiltered, bool bSkipEmptyCells,
+ const ScRangeList * pDestRanges )
+{
+ if (bIsClip)
+ return;
+
+ if (!pClipDoc)
+ {
+ OSL_FAIL("CopyFromClip: no ClipDoc");
+ pClipDoc = ScModule::GetClipDoc();
+ }
+
+ if (!pClipDoc->bIsClip || !pClipDoc->GetTableCount())
+ return;
+
+ sc::AutoCalcSwitch aACSwitch(*this, false); // temporarily turn off auto calc.
+
+ NumFmtMergeHandler aNumFmtMergeHdl(*this, *pClipDoc);
+
+ SCCOL nAllCol1 = rDestRange.aStart.Col();
+ SCROW nAllRow1 = rDestRange.aStart.Row();
+ SCCOL nAllCol2 = rDestRange.aEnd.Col();
+ SCROW nAllRow2 = rDestRange.aEnd.Row();
+
+ SCCOL nXw = 0;
+ SCROW nYw = 0;
+ ScRange aClipRange = pClipDoc->GetClipParam().getWholeRange();
+ for (SCTAB nTab = 0; nTab < static_cast<SCTAB>(pClipDoc->maTabs.size()); nTab++) // find largest merge overlap
+ if (pClipDoc->maTabs[nTab]) // all sheets of the clipboard content
+ {
+ SCCOL nThisEndX = aClipRange.aEnd.Col();
+ SCROW nThisEndY = aClipRange.aEnd.Row();
+ pClipDoc->ExtendMerge( aClipRange.aStart.Col(),
+ aClipRange.aStart.Row(),
+ nThisEndX, nThisEndY, nTab );
+ // only extra value from ExtendMerge
+ nThisEndX = sal::static_int_cast<SCCOL>( nThisEndX - aClipRange.aEnd.Col() );
+ nThisEndY = sal::static_int_cast<SCROW>( nThisEndY - aClipRange.aEnd.Row() );
+ if ( nThisEndX > nXw )
+ nXw = nThisEndX;
+ if ( nThisEndY > nYw )
+ nYw = nThisEndY;
+ }
+
+ SCCOL nDestAddX;
+ SCROW nDestAddY;
+ pClipDoc->GetClipArea( nDestAddX, nDestAddY, bIncludeFiltered );
+ nXw = sal::static_int_cast<SCCOL>( nXw + nDestAddX );
+ nYw = sal::static_int_cast<SCROW>( nYw + nDestAddY ); // ClipArea, plus ExtendMerge value
+
+ /* Decide which contents to delete before copying. Delete all
+ contents if nInsFlag contains any real content flag.
+ #i102056# Notes are pasted from clipboard in a second pass,
+ together with the special flag InsertDeleteFlags::ADDNOTES that states to not
+ overwrite/delete existing cells but to insert the notes into
+ these cells. In this case, just delete old notes from the
+ destination area. */
+ InsertDeleteFlags nDelFlag = InsertDeleteFlags::NONE;
+ if ( (nInsFlag & (InsertDeleteFlags::CONTENTS | InsertDeleteFlags::ADDNOTES)) == (InsertDeleteFlags::NOTE | InsertDeleteFlags::ADDNOTES) )
+ nDelFlag |= InsertDeleteFlags::NOTE;
+ else if ( nInsFlag & InsertDeleteFlags::CONTENTS )
+ nDelFlag |= InsertDeleteFlags::CONTENTS;
+
+ if (nInsFlag & InsertDeleteFlags::ATTRIB)
+ nDelFlag |= InsertDeleteFlags::ATTRIB;
+
+ sc::CopyFromClipContext aCxt(*this, pRefUndoDoc, pClipDoc, nInsFlag, bAsLink, bSkipEmptyCells);
+ std::pair<SCTAB,SCTAB> aTabRanges = getMarkedTableRange(maTabs, rMark);
+ aCxt.setTabRange(aTabRanges.first, aTabRanges.second);
+ aCxt.setDeleteFlag(nDelFlag);
+
+ ScRangeList aLocalRangeList;
+ if (!pDestRanges)
+ {
+ aLocalRangeList.push_back( rDestRange);
+ pDestRanges = &aLocalRangeList;
+ }
+
+ bInsertingFromOtherDoc = true; // No Broadcast/Listener created at Insert
+
+ sc::ColumnSpanSet aBroadcastSpans;
+
+ SCCOL nClipStartCol = aClipRange.aStart.Col();
+ SCROW nClipStartRow = aClipRange.aStart.Row();
+ SCROW nClipEndRow = aClipRange.aEnd.Row();
+ for ( size_t nRange = 0; nRange < pDestRanges->size(); ++nRange )
+ {
+ const ScRange & rRange = (*pDestRanges)[nRange];
+ SCCOL nCol1 = rRange.aStart.Col();
+ SCROW nRow1 = rRange.aStart.Row();
+ SCCOL nCol2 = rRange.aEnd.Col();
+ SCROW nRow2 = rRange.aEnd.Row();
+
+ aCxt.setDestRange(nCol1, nRow1, nCol2, nRow2);
+ DeleteBeforeCopyFromClip(aCxt, rMark, aBroadcastSpans); // <- this removes existing formula listeners
+
+ if (CopyOneCellFromClip(aCxt, nCol1, nRow1, nCol2, nRow2))
+ continue;
+
+ SCCOL nC1 = nCol1;
+ SCROW nR1 = nRow1;
+ SCCOL nC2 = nC1 + nXw;
+ if (nC2 > nCol2)
+ nC2 = nCol2;
+ SCROW nR2 = nR1 + nYw;
+ if (nR2 > nRow2)
+ nR2 = nRow2;
+
+ const SCCOLROW nThreshold = 8192;
+ bool bPreallocatePattern = ((nInsFlag & InsertDeleteFlags::ATTRIB) && (nRow2 - nRow1 > nThreshold));
+ std::vector< SCTAB > vTables;
+
+ if (bPreallocatePattern)
+ {
+ for (SCTAB i = aCxt.getTabStart(); i <= aCxt.getTabEnd(); ++i)
+ if (maTabs[i] && rMark.GetTableSelect( i ) )
+ vTables.push_back( i );
+ }
+
+ do
+ {
+ // Pasting is done column-wise, when pasting to a filtered
+ // area this results in partitioning and we have to
+ // remember and reset the start row for each column until
+ // it can be advanced for the next chunk of unfiltered
+ // rows.
+ SCROW nSaveClipStartRow = nClipStartRow;
+ do
+ {
+ nClipStartRow = nSaveClipStartRow;
+ SCCOL nDx = nC1 - nClipStartCol;
+ SCROW nDy = nR1 - nClipStartRow;
+ if ( bIncludeFiltered )
+ {
+ CopyBlockFromClip(
+ aCxt, nC1, nR1, nC2, nR2, rMark, nDx, nDy);
+ nClipStartRow += nR2 - nR1 + 1;
+ }
+ else
+ {
+ CopyNonFilteredFromClip(aCxt, nC1, nR1, nC2, nR2, rMark, nDx, nClipStartRow,
+ nClipEndRow);
+ }
+ nC1 = nC2 + 1;
+ nC2 = std::min(static_cast<SCCOL>(nC1 + nXw), nCol2);
+ } while (nC1 <= nCol2);
+ if (nClipStartRow > nClipEndRow)
+ nClipStartRow = aClipRange.aStart.Row();
+ nC1 = nCol1;
+ nC2 = nC1 + nXw;
+ if (nC2 > nCol2)
+ nC2 = nCol2;
+
+ // Preallocate pattern memory once if further chunks are to be pasted.
+ if (bPreallocatePattern && (nR2+1) <= nRow2)
+ {
+ SCROW nR3 = nR2 + 1;
+ for (SCTAB nTab : vTables)
+ {
+ for (SCCOL nCol = nCol1; nCol <= nCol2; ++nCol)
+ {
+ // Pattern count of the first chunk pasted.
+ SCSIZE nChunk = GetPatternCount( nTab, nCol, nR1, nR2);
+ // If it is only one pattern per chunk and chunks are
+ // pasted consecutively then it will get its range
+ // enlarged for each chunk and no further allocation
+ // happens. For non-consecutive chunks we're out of
+ // luck in this case.
+ if (nChunk > 1)
+ {
+ SCSIZE nNeeded = nChunk * (nRow2 - nR3 + 1) / (nYw + 1);
+ SCSIZE nRemain = GetPatternCount( nTab, nCol, nR3, nRow2);
+ if (nNeeded > nRemain)
+ {
+ SCSIZE nCurr = GetPatternCount( nTab, nCol);
+ ReservePatternCount( nTab, nCol, nCurr + nNeeded);
+ }
+ }
+ }
+ }
+ bPreallocatePattern = false;
+ }
+
+ nR1 = nR2 + 1;
+ nR2 = std::min(static_cast<SCROW>(nR1 + nYw), nRow2);
+ } while (nR1 <= nRow2);
+ }
+
+ bInsertingFromOtherDoc = false;
+
+ if (nInsFlag & InsertDeleteFlags::CONTENTS)
+ {
+ for (SCTAB nTab : rMark)
+ aCxt.setListeningFormulaSpans(nTab, nAllCol1, nAllRow1, nAllCol2, nAllRow2);
+ }
+
+ // Create Listener after everything has been inserted
+ aCxt.startListeningFormulas();
+
+ {
+ ScBulkBroadcast aBulkBroadcast( GetBASM(), SfxHintId::ScDataChanged);
+
+ // Set all formula cells dirty, and collect non-empty non-formula cell
+ // positions so that we can broadcast on them below.
+ SetDirtyFromClip(nAllCol1, nAllRow1, nAllCol2, nAllRow2, rMark, nInsFlag, aBroadcastSpans);
+
+ BroadcastAction aAction(*this);
+ aBroadcastSpans.executeColumnAction(*this, aAction);
+ }
+
+ if (bResetCut)
+ pClipDoc->GetClipParam().mbCutMode = false;
+}
+
+void ScDocument::CopyMultiRangeFromClip(const ScAddress& rDestPos, const ScMarkData& rMark,
+ InsertDeleteFlags nInsFlag, ScDocument* pClipDoc,
+ bool bResetCut, bool bAsLink, bool bIncludeFiltered,
+ bool bSkipAttrForEmpty)
+{
+ if (bIsClip)
+ return;
+
+ if (!pClipDoc->bIsClip || !pClipDoc->GetTableCount())
+ // There is nothing in the clip doc to copy.
+ return;
+
+ // Right now, we don't allow pasting into filtered rows, so we don't even handle it here.
+
+ sc::AutoCalcSwitch aACSwitch(*this, false); // turn of auto calc temporarily.
+ NumFmtMergeHandler aNumFmtMergeHdl(*this, *pClipDoc);
+
+ const ScRange& aDestRange = rMark.GetMarkArea();
+
+ bInsertingFromOtherDoc = true; // No Broadcast/Listener created at Insert
+
+ SCCOL nCol1 = rDestPos.Col();
+ SCROW nRow1 = rDestPos.Row();
+ ScClipParam& rClipParam = pClipDoc->GetClipParam();
+
+ sc::ColumnSpanSet aBroadcastSpans;
+
+ if (!bSkipAttrForEmpty)
+ {
+ // Do the deletion first.
+ SCCOL nColSize = rClipParam.getPasteColSize();
+ SCROW nRowSize = rClipParam.getPasteRowSize(*pClipDoc, bIncludeFiltered);
+
+ DeleteArea(nCol1, nRow1, nCol1+nColSize-1, nRow1+nRowSize-1, rMark, InsertDeleteFlags::CONTENTS, false, &aBroadcastSpans);
+ }
+
+ sc::CopyFromClipContext aCxt(*this, nullptr, pClipDoc, nInsFlag, bAsLink, bSkipAttrForEmpty);
+ std::pair<SCTAB,SCTAB> aTabRanges = getMarkedTableRange(maTabs, rMark);
+ aCxt.setTabRange(aTabRanges.first, aTabRanges.second);
+
+ for (size_t i = 0, n = rClipParam.maRanges.size(); i < n; ++i)
+ {
+ const ScRange & rRange = rClipParam.maRanges[i];
+
+ SCROW nRowCount = rRange.aEnd.Row() - rRange.aStart.Row() + 1;
+ SCCOL nDx = static_cast<SCCOL>(nCol1 - rRange.aStart.Col());
+ SCROW nDy = static_cast<SCROW>(nRow1 - rRange.aStart.Row());
+ SCCOL nCol2 = nCol1 + rRange.aEnd.Col() - rRange.aStart.Col();
+ SCROW nEndRow = nRow1 + nRowCount - 1;
+ SCROW nFilteredRows = 0;
+
+ if (bIncludeFiltered)
+ {
+ CopyBlockFromClip(aCxt, nCol1, nRow1, nCol2, nEndRow, rMark, nDx, nDy);
+ }
+ else
+ {
+ SCROW nClipStartRow = rRange.aStart.Row();
+ SCROW nClipEndRow = rRange.aEnd.Row();
+ nFilteredRows += CopyNonFilteredFromClip(aCxt, nCol1, nRow1, nCol2, nEndRow, rMark, nDx,
+ nClipStartRow, nClipEndRow);
+ nRowCount -= nFilteredRows;
+ }
+
+ switch (rClipParam.meDirection)
+ {
+ case ScClipParam::Row:
+ // Begin row for the next range being pasted.
+ nRow1 += nRowCount;
+ break;
+ case ScClipParam::Column:
+ nCol1 += rRange.aEnd.Col() - rRange.aStart.Col() + 1;
+ break;
+ default:
+ ;
+ }
+ }
+
+ bInsertingFromOtherDoc = false;
+
+ // Create Listener after everything has been inserted
+ StartListeningFromClip(aDestRange.aStart.Col(), aDestRange.aStart.Row(),
+ aDestRange.aEnd.Col(), aDestRange.aEnd.Row(), rMark, nInsFlag );
+
+ {
+ ScBulkBroadcast aBulkBroadcast( GetBASM(), SfxHintId::ScDataChanged);
+
+ // Set formula cells dirty and collect non-formula cells.
+ SetDirtyFromClip(
+ aDestRange.aStart.Col(), aDestRange.aStart.Row(), aDestRange.aEnd.Col(), aDestRange.aEnd.Row(),
+ rMark, nInsFlag, aBroadcastSpans);
+
+ BroadcastAction aAction(*this);
+ aBroadcastSpans.executeColumnAction(*this, aAction);
+ }
+
+ if (bResetCut)
+ pClipDoc->GetClipParam().mbCutMode = false;
+}
+
+void ScDocument::SetClipArea( const ScRange& rArea, bool bCut )
+{
+ if (bIsClip)
+ {
+ ScClipParam& rClipParam = GetClipParam();
+ rClipParam.maRanges.RemoveAll();
+ rClipParam.maRanges.push_back(rArea);
+ rClipParam.mbCutMode = bCut;
+ }
+ else
+ {
+ OSL_FAIL("SetClipArea: No Clip");
+ }
+}
+
+void ScDocument::GetClipArea(SCCOL& nClipX, SCROW& nClipY, bool bIncludeFiltered)
+{
+ if (!bIsClip)
+ {
+ OSL_FAIL("GetClipArea: No Clip");
+ return;
+ }
+
+ ScRangeList& rClipRanges = GetClipParam().maRanges;
+ if (rClipRanges.empty())
+ // No clip range. Bail out.
+ return;
+
+ ScRange const & rRange = rClipRanges.front();
+ SCCOL nStartCol = rRange.aStart.Col();
+ SCCOL nEndCol = rRange.aEnd.Col();
+ SCROW nStartRow = rRange.aStart.Row();
+ SCROW nEndRow = rRange.aEnd.Row();
+ for ( size_t i = 1, n = rClipRanges.size(); i < n; ++i )
+ {
+ ScRange const & rRange2 = rClipRanges[ i ];
+ if (rRange2.aStart.Col() < nStartCol)
+ nStartCol = rRange2.aStart.Col();
+ if (rRange2.aStart.Row() < nStartRow)
+ nStartRow = rRange2.aStart.Row();
+ if (rRange2.aEnd.Col() > nEndCol)
+ nEndCol = rRange2.aEnd.Col();
+ if (rRange2.aEnd.Row() > nEndRow)
+ nEndRow = rRange2.aEnd.Row();
+ }
+
+ nClipX = nEndCol - nStartCol;
+
+ if ( bIncludeFiltered )
+ nClipY = nEndRow - nStartRow;
+ else
+ {
+ // count non-filtered rows
+ // count on first used table in clipboard
+ SCTAB nCountTab = 0;
+ while ( nCountTab < static_cast<SCTAB>(maTabs.size()) && !maTabs[nCountTab] )
+ ++nCountTab;
+
+ SCROW nResult = CountNonFilteredRows(nStartRow, nEndRow, nCountTab);
+
+ if ( nResult > 0 )
+ nClipY = nResult - 1;
+ else
+ nClipY = 0; // always return at least 1 row
+ }
+}
+
+void ScDocument::GetClipStart(SCCOL& nClipX, SCROW& nClipY)
+{
+ if (bIsClip)
+ {
+ ScRangeList& rClipRanges = GetClipParam().maRanges;
+ if ( !rClipRanges.empty() )
+ {
+ nClipX = rClipRanges.front().aStart.Col();
+ nClipY = rClipRanges.front().aStart.Row();
+ }
+ }
+ else
+ {
+ OSL_FAIL("GetClipStart: No Clip");
+ }
+}
+
+bool ScDocument::HasClipFilteredRows()
+{
+ // count on first used table in clipboard
+ SCTAB nCountTab = 0;
+ while ( nCountTab < static_cast<SCTAB>(maTabs.size()) && !maTabs[nCountTab] )
+ ++nCountTab;
+
+ ScRangeList& rClipRanges = GetClipParam().maRanges;
+ if ( rClipRanges.empty() )
+ return false;
+
+ for ( size_t i = 0, n = rClipRanges.size(); i < n; ++i )
+ {
+ ScRange & rRange = rClipRanges[ i ];
+ bool bAnswer = maTabs[nCountTab]->HasFilteredRows(rRange.aStart.Row(), rRange.aEnd.Row());
+ if (bAnswer)
+ return true;
+ }
+ return false;
+}
+
+void ScDocument::MixDocument( const ScRange& rRange, ScPasteFunc nFunction, bool bSkipEmpty,
+ ScDocument& rSrcDoc )
+{
+ SCTAB nTab1 = rRange.aStart.Tab();
+ SCTAB nTab2 = rRange.aEnd.Tab();
+ sc::MixDocContext aCxt(*this);
+ SCTAB nMinSizeBothTabs = static_cast<SCTAB>(std::min(maTabs.size(), rSrcDoc.maTabs.size()));
+ for (SCTAB i = nTab1; i <= nTab2 && i < nMinSizeBothTabs; i++)
+ {
+ ScTable* pTab = FetchTable(i);
+ const ScTable* pSrcTab = rSrcDoc.FetchTable(i);
+ if (!pTab || !pSrcTab)
+ continue;
+
+ pTab->MixData(
+ aCxt, rRange.aStart.Col(), rRange.aStart.Row(), rRange.aEnd.Col(), rRange.aEnd.Row(),
+ nFunction, bSkipEmpty, pSrcTab);
+ }
+}
+
+void ScDocument::FillTab( const ScRange& rSrcArea, const ScMarkData& rMark,
+ InsertDeleteFlags nFlags, ScPasteFunc nFunction,
+ bool bSkipEmpty, bool bAsLink )
+{
+ InsertDeleteFlags nDelFlags = nFlags;
+ if (nDelFlags & InsertDeleteFlags::CONTENTS)
+ nDelFlags |= InsertDeleteFlags::CONTENTS; // Either all contents or delete nothing!
+
+ SCTAB nSrcTab = rSrcArea.aStart.Tab();
+
+ if (ValidTab(nSrcTab) && nSrcTab < static_cast<SCTAB>(maTabs.size()) && maTabs[nSrcTab])
+ {
+ SCCOL nStartCol = rSrcArea.aStart.Col();
+ SCROW nStartRow = rSrcArea.aStart.Row();
+ SCCOL nEndCol = rSrcArea.aEnd.Col();
+ SCROW nEndRow = rSrcArea.aEnd.Row();
+ ScDocumentUniquePtr pMixDoc;
+ bool bDoMix = ( bSkipEmpty || nFunction != ScPasteFunc::NONE ) && ( nFlags & InsertDeleteFlags::CONTENTS );
+
+ bool bOldAutoCalc = GetAutoCalc();
+ SetAutoCalc( false ); // avoid multiple calculations
+
+ sc::CopyToDocContext aCxt(*this);
+ sc::MixDocContext aMixDocCxt(*this);
+
+ SCTAB nCount = static_cast<SCTAB>(maTabs.size());
+ for (const SCTAB& i : rMark)
+ {
+ if (i >= nCount)
+ break;
+ if (i != nSrcTab && maTabs[i])
+ {
+ if (bDoMix)
+ {
+ if (!pMixDoc)
+ {
+ pMixDoc.reset(new ScDocument(SCDOCMODE_UNDO));
+ pMixDoc->InitUndo( *this, i, i );
+ }
+ else
+ pMixDoc->AddUndoTab( i, i );
+
+ // context used for copying content to the temporary mix document.
+ sc::CopyToDocContext aMixCxt(*pMixDoc);
+ maTabs[i]->CopyToTable(aMixCxt, nStartCol,nStartRow, nEndCol,nEndRow,
+ InsertDeleteFlags::CONTENTS, false, pMixDoc->maTabs[i].get(),
+ /*pMarkData*/nullptr, /*bAsLink*/false, /*bColRowFlags*/true,
+ /*bGlobalNamesToLocal*/false, /*bCopyCaptions*/true );
+ }
+ maTabs[i]->DeleteArea( nStartCol,nStartRow, nEndCol,nEndRow, nDelFlags);
+ maTabs[nSrcTab]->CopyToTable(aCxt, nStartCol,nStartRow, nEndCol,nEndRow,
+ nFlags, false, maTabs[i].get(), nullptr, bAsLink,
+ /*bColRowFlags*/true, /*bGlobalNamesToLocal*/false, /*bCopyCaptions*/true );
+
+ if (bDoMix)
+ maTabs[i]->MixData(aMixDocCxt, nStartCol,nStartRow, nEndCol,nEndRow,
+ nFunction, bSkipEmpty, pMixDoc->maTabs[i].get() );
+ }
+ }
+
+ SetAutoCalc( bOldAutoCalc );
+ }
+ else
+ {
+ OSL_FAIL("wrong table");
+ }
+}
+
+void ScDocument::FillTabMarked( SCTAB nSrcTab, const ScMarkData& rMark,
+ InsertDeleteFlags nFlags, ScPasteFunc nFunction,
+ bool bSkipEmpty, bool bAsLink )
+{
+ InsertDeleteFlags nDelFlags = nFlags;
+ if (nDelFlags & InsertDeleteFlags::CONTENTS)
+ nDelFlags |= InsertDeleteFlags::CONTENTS; // Either all contents or delete nothing!
+
+ if (ValidTab(nSrcTab) && nSrcTab < static_cast<SCTAB>(maTabs.size()) && maTabs[nSrcTab])
+ {
+ ScDocumentUniquePtr pMixDoc;
+ bool bDoMix = ( bSkipEmpty || nFunction != ScPasteFunc::NONE ) && ( nFlags & InsertDeleteFlags::CONTENTS );
+
+ bool bOldAutoCalc = GetAutoCalc();
+ SetAutoCalc( false ); // avoid multiple calculations
+
+ const ScRange& aArea = rMark.GetMultiMarkArea();
+ SCCOL nStartCol = aArea.aStart.Col();
+ SCROW nStartRow = aArea.aStart.Row();
+ SCCOL nEndCol = aArea.aEnd.Col();
+ SCROW nEndRow = aArea.aEnd.Row();
+
+ sc::CopyToDocContext aCxt(*this);
+ sc::MixDocContext aMixDocCxt(*this);
+ SCTAB nCount = static_cast<SCTAB>(maTabs.size());
+ for (const SCTAB& i : rMark)
+ {
+ if (i >= nCount)
+ break;
+ if ( i != nSrcTab && maTabs[i] )
+ {
+ if (bDoMix)
+ {
+ if (!pMixDoc)
+ {
+ pMixDoc.reset(new ScDocument(SCDOCMODE_UNDO));
+ pMixDoc->InitUndo( *this, i, i );
+ }
+ else
+ pMixDoc->AddUndoTab( i, i );
+
+ sc::CopyToDocContext aMixCxt(*pMixDoc);
+ maTabs[i]->CopyToTable(aMixCxt, nStartCol,nStartRow, nEndCol,nEndRow,
+ InsertDeleteFlags::CONTENTS, true, pMixDoc->maTabs[i].get(), &rMark,
+ /*bAsLink*/false, /*bColRowFlags*/true, /*bGlobalNamesToLocal*/false,
+ /*bCopyCaptions*/true );
+ }
+
+ maTabs[i]->DeleteSelection( nDelFlags, rMark );
+ maTabs[nSrcTab]->CopyToTable(aCxt, nStartCol,nStartRow, nEndCol,nEndRow,
+ nFlags, true, maTabs[i].get(), &rMark, bAsLink,
+ /*bColRowFlags*/true, /*bGlobalNamesToLocal*/false, /*bCopyCaptions*/true );
+
+ if (bDoMix)
+ maTabs[i]->MixMarked(aMixDocCxt, rMark, nFunction, bSkipEmpty, pMixDoc->maTabs[i].get());
+ }
+ }
+
+ SetAutoCalc( bOldAutoCalc );
+ }
+ else
+ {
+ OSL_FAIL("wrong table");
+ }
+}
+
+bool ScDocument::SetString( SCCOL nCol, SCROW nRow, SCTAB nTab, const OUString& rString,
+ const ScSetStringParam* pParam )
+{
+ ScTable* pTab = FetchTable(nTab);
+ if (!pTab)
+ return false;
+
+ const ScFormulaCell* pCurCellFormula = pTab->GetFormulaCell(nCol, nRow);
+ if (pCurCellFormula && pCurCellFormula->IsShared())
+ {
+ // In case setting this string affects an existing formula group, end
+ // its listening to purge then empty cell broadcasters. Affected
+ // remaining split group listeners will be set up again via
+ // ScColumn::DetachFormulaCell() and
+ // ScColumn::StartListeningUnshared().
+
+ sc::EndListeningContext aCxt(*this);
+ ScAddress aPos(nCol, nRow, nTab);
+ EndListeningIntersectedGroup(aCxt, aPos, nullptr);
+ aCxt.purgeEmptyBroadcasters();
+ }
+
+ return pTab->SetString(nCol, nRow, nTab, rString, pParam);
+}
+
+bool ScDocument::SetString(
+ const ScAddress& rPos, const OUString& rString, const ScSetStringParam* pParam )
+{
+ return SetString(rPos.Col(), rPos.Row(), rPos.Tab(), rString, pParam);
+}
+
+bool ScDocument::SetEditText( const ScAddress& rPos, std::unique_ptr<EditTextObject> pEditText )
+{
+ if (!TableExists(rPos.Tab()))
+ {
+ return false;
+ }
+
+ return maTabs[rPos.Tab()]->SetEditText(rPos.Col(), rPos.Row(), std::move(pEditText));
+}
+
+void ScDocument::SetEditText( const ScAddress& rPos, const EditTextObject& rEditText, const SfxItemPool* pEditPool )
+{
+ if (!TableExists(rPos.Tab()))
+ return;
+
+ maTabs[rPos.Tab()]->SetEditText(rPos.Col(), rPos.Row(), rEditText, pEditPool);
+}
+
+void ScDocument::SetEditText( const ScAddress& rPos, const OUString& rStr )
+{
+ if (!TableExists(rPos.Tab()))
+ return;
+
+ ScFieldEditEngine& rEngine = GetEditEngine();
+ rEngine.SetTextCurrentDefaults(rStr);
+ maTabs[rPos.Tab()]->SetEditText(rPos.Col(), rPos.Row(), rEngine.CreateTextObject());
+}
+
+SCROW ScDocument::GetFirstEditTextRow( const ScRange& rRange ) const
+{
+ const ScTable* pTab = FetchTable(rRange.aStart.Tab());
+ if (!pTab)
+ return -1;
+
+ return pTab->GetFirstEditTextRow(rRange.aStart.Col(), rRange.aStart.Row(), rRange.aEnd.Col(), rRange.aEnd.Row());
+}
+
+void ScDocument::SetTextCell( const ScAddress& rPos, const OUString& rStr )
+{
+ if (!TableExists(rPos.Tab()))
+ return;
+
+ if (ScStringUtil::isMultiline(rStr))
+ {
+ ScFieldEditEngine& rEngine = GetEditEngine();
+ rEngine.SetTextCurrentDefaults(rStr);
+ maTabs[rPos.Tab()]->SetEditText(rPos.Col(), rPos.Row(), rEngine.CreateTextObject());
+ }
+ else
+ {
+ ScSetStringParam aParam;
+ aParam.setTextInput();
+ maTabs[rPos.Tab()]->SetString(rPos.Col(), rPos.Row(), rPos.Tab(), rStr, &aParam);
+ }
+}
+
+void ScDocument::SetEmptyCell( const ScAddress& rPos )
+{
+ if (!TableExists(rPos.Tab()))
+ return;
+
+ maTabs[rPos.Tab()]->SetEmptyCell(rPos.Col(), rPos.Row());
+}
+
+void ScDocument::SetValue( SCCOL nCol, SCROW nRow, SCTAB nTab, const double& rVal )
+{
+ SetValue(ScAddress(nCol, nRow, nTab), rVal);
+}
+
+void ScDocument::SetValue( const ScAddress& rPos, double fVal )
+{
+ ScTable* pTab = FetchTable(rPos.Tab());
+ if (!pTab)
+ return;
+
+ const ScFormulaCell* pCurCellFormula = pTab->GetFormulaCell(rPos.Col(), rPos.Row());
+ if (pCurCellFormula && pCurCellFormula->IsShared())
+ {
+ // In case setting this value affects an existing formula group, end
+ // its listening to purge then empty cell broadcasters. Affected
+ // remaining split group listeners will be set up again via
+ // ScColumn::DetachFormulaCell() and
+ // ScColumn::StartListeningUnshared().
+
+ sc::EndListeningContext aCxt(*this);
+ EndListeningIntersectedGroup(aCxt, rPos, nullptr);
+ aCxt.purgeEmptyBroadcasters();
+ }
+
+ pTab->SetValue(rPos.Col(), rPos.Row(), fVal);
+}
+
+OUString ScDocument::GetString( SCCOL nCol, SCROW nRow, SCTAB nTab, const ScInterpreterContext* pContext ) const
+{
+ if (TableExists(nTab))
+ return maTabs[nTab]->GetString(nCol, nRow, pContext);
+ return OUString();
+}
+
+OUString ScDocument::GetString( const ScAddress& rPos, const ScInterpreterContext* pContext ) const
+{
+ if (!TableExists(rPos.Tab()))
+ return OUString();
+
+ return maTabs[rPos.Tab()]->GetString(rPos.Col(), rPos.Row(), pContext);
+}
+
+double* ScDocument::GetValueCell( const ScAddress& rPos )
+{
+ if (!TableExists(rPos.Tab()))
+ return nullptr;
+
+ return maTabs[rPos.Tab()]->GetValueCell(rPos.Col(), rPos.Row());
+}
+
+svl::SharedString ScDocument::GetSharedString( const ScAddress& rPos ) const
+{
+ if (!TableExists(rPos.Tab()))
+ return svl::SharedString();
+
+ return maTabs[rPos.Tab()]->GetSharedString(rPos.Col(), rPos.Row());
+}
+
+std::shared_ptr<sc::FormulaGroupContext>& ScDocument::GetFormulaGroupContext()
+{
+ ScMutationGuard aGuard(*this, ScMutationGuardFlags::CORE);
+ if (!mpFormulaGroupCxt)
+ mpFormulaGroupCxt = std::make_shared<sc::FormulaGroupContext>();
+
+ return mpFormulaGroupCxt;
+}
+
+void ScDocument::DiscardFormulaGroupContext()
+{
+ assert(!IsThreadedGroupCalcInProgress());
+ if( !mbFormulaGroupCxtBlockDiscard )
+ mpFormulaGroupCxt.reset();
+}
+
+OUString ScDocument::GetInputString(SCCOL nCol, SCROW nRow, SCTAB nTab, bool bForceSystemLocale ) const
+{
+ if ( ValidTab(nTab) && nTab < static_cast<SCTAB>(maTabs.size()) && maTabs[nTab] )
+ return maTabs[nTab]->GetInputString( nCol, nRow, nullptr, bForceSystemLocale );
+ else
+ return OUString();
+}
+
+FormulaError ScDocument::GetStringForFormula( const ScAddress& rPos, OUString& rString )
+{
+ // Used in formulas (add-in parameters etc), so it must use the same semantics as
+ // ScInterpreter::GetCellString: always format values as numbers.
+ // The return value is the error code.
+
+ ScRefCellValue aCell(*this, rPos);
+ if (aCell.isEmpty())
+ {
+ rString.clear();
+ return FormulaError::NONE;
+ }
+
+ FormulaError nErr = FormulaError::NONE;
+ OUString aStr;
+ SvNumberFormatter* pFormatter = GetFormatTable();
+ switch (aCell.meType)
+ {
+ case CELLTYPE_STRING:
+ case CELLTYPE_EDIT:
+ aStr = aCell.getString(this);
+ break;
+ case CELLTYPE_FORMULA:
+ {
+ ScFormulaCell* pFCell = aCell.mpFormula;
+ nErr = pFCell->GetErrCode();
+ if (pFCell->IsValue())
+ {
+ double fVal = pFCell->GetValue();
+ sal_uInt32 nIndex = pFormatter->GetStandardFormat(
+ SvNumFormatType::NUMBER,
+ ScGlobal::eLnge);
+ pFormatter->GetInputLineString(fVal, nIndex, aStr);
+ }
+ else
+ aStr = pFCell->GetString().getString();
+ }
+ break;
+ case CELLTYPE_VALUE:
+ {
+ double fVal = aCell.mfValue;
+ sal_uInt32 nIndex = pFormatter->GetStandardFormat(
+ SvNumFormatType::NUMBER,
+ ScGlobal::eLnge);
+ pFormatter->GetInputLineString(fVal, nIndex, aStr);
+ }
+ break;
+ default:
+ ;
+ }
+
+ rString = aStr;
+ return nErr;
+}
+
+const EditTextObject* ScDocument::GetEditText( const ScAddress& rPos ) const
+{
+ SCTAB nTab = rPos.Tab();
+ if (!TableExists(nTab))
+ return nullptr;
+
+ return maTabs[nTab]->GetEditText(rPos.Col(), rPos.Row());
+}
+
+void ScDocument::RemoveEditTextCharAttribs( const ScAddress& rPos, const ScPatternAttr& rAttr )
+{
+ if (!TableExists(rPos.Tab()))
+ return;
+
+ return maTabs[rPos.Tab()]->RemoveEditTextCharAttribs(rPos.Col(), rPos.Row(), rAttr);
+}
+
+double ScDocument::GetValue( const ScAddress& rPos ) const
+{
+ SCTAB nTab = rPos.Tab();
+ if ( nTab < static_cast<SCTAB>(maTabs.size()) && maTabs[nTab] )
+ return maTabs[nTab]->GetValue(rPos.Col(), rPos.Row());
+ return 0.0;
+}
+
+double ScDocument::GetValue( SCCOL nCol, SCROW nRow, SCTAB nTab ) const
+{
+ ScAddress aAdr(nCol, nRow, nTab);
+ return GetValue(aAdr);
+}
+
+sal_uInt32 ScDocument::GetNumberFormat( SCCOL nCol, SCROW nRow, SCTAB nTab ) const
+{
+ if (ValidTab(nTab) && nTab < static_cast<SCTAB>(maTabs.size()))
+ if (maTabs[nTab])
+ {
+ return maTabs[nTab]->GetNumberFormat( nCol, nRow );
+ }
+ return 0;
+}
+
+sal_uInt32 ScDocument::GetNumberFormat( const ScRange& rRange ) const
+{
+ SCTAB nTab1 = rRange.aStart.Tab(), nTab2 = rRange.aEnd.Tab();
+ SCCOL nCol1 = rRange.aStart.Col(), nCol2 = rRange.aEnd.Col();
+ SCROW nRow1 = rRange.aStart.Row(), nRow2 = rRange.aEnd.Row();
+
+ if (!TableExists(nTab1) || !TableExists(nTab2))
+ return 0;
+
+ sal_uInt32 nFormat = 0;
+ bool bFirstItem = true;
+ for (SCTAB nTab = nTab1; nTab <= nTab2 && nTab < static_cast<SCTAB>(maTabs.size()) ; ++nTab)
+ for (SCCOL nCol = nCol1; nCol <= nCol2; ++nCol)
+ {
+ sal_uInt32 nThisFormat = maTabs[nTab]->GetNumberFormat(nCol, nRow1, nRow2);
+ if (bFirstItem)
+ {
+ nFormat = nThisFormat;
+ bFirstItem = false;
+ }
+ else if (nThisFormat != nFormat)
+ return 0;
+ }
+
+ return nFormat;
+}
+
+sal_uInt32 ScDocument::GetNumberFormat( const ScInterpreterContext& rContext, const ScAddress& rPos ) const
+{
+ SCTAB nTab = rPos.Tab();
+ if (!TableExists(nTab))
+ return 0;
+
+ return maTabs[nTab]->GetNumberFormat( rContext, rPos );
+}
+
+void ScDocument::SetNumberFormat( const ScAddress& rPos, sal_uInt32 nNumberFormat )
+{
+ assert(!IsThreadedGroupCalcInProgress());
+ SCTAB nTab = rPos.Tab();
+ if (!TableExists(nTab))
+ return;
+
+ maTabs[nTab]->SetNumberFormat(rPos.Col(), rPos.Row(), nNumberFormat);
+}
+
+void ScDocument::GetNumberFormatInfo( const ScInterpreterContext& rContext, SvNumFormatType& nType, sal_uInt32& nIndex,
+ const ScAddress& rPos ) const
+{
+ SCTAB nTab = rPos.Tab();
+ if ( nTab < static_cast<SCTAB>(maTabs.size()) && maTabs[nTab] )
+ {
+ nIndex = maTabs[nTab]->GetNumberFormat( rContext, rPos );
+ nType = rContext.GetNumberFormatType( nIndex );
+ }
+ else
+ {
+ nType = SvNumFormatType::UNDEFINED;
+ nIndex = 0;
+ }
+}
+
+OUString ScDocument::GetFormula( SCCOL nCol, SCROW nRow, SCTAB nTab ) const
+{
+ if ( ValidTab(nTab) && nTab < static_cast<SCTAB>(maTabs.size()) && maTabs[nTab] )
+ return maTabs[nTab]->GetFormula( nCol, nRow );
+
+ return OUString();
+}
+
+const ScFormulaCell* ScDocument::GetFormulaCell( const ScAddress& rPos ) const
+{
+ if (!TableExists(rPos.Tab()))
+ return nullptr;
+
+ return maTabs[rPos.Tab()]->GetFormulaCell(rPos.Col(), rPos.Row());
+}
+
+ScFormulaCell* ScDocument::GetFormulaCell( const ScAddress& rPos )
+{
+ if (!TableExists(rPos.Tab()))
+ return nullptr;
+
+ return maTabs[rPos.Tab()]->GetFormulaCell(rPos.Col(), rPos.Row());
+}
+
+CellType ScDocument::GetCellType( const ScAddress& rPos ) const
+{
+ SCTAB nTab = rPos.Tab();
+ if ( nTab < static_cast<SCTAB>(maTabs.size()) && maTabs[nTab] )
+ return maTabs[nTab]->GetCellType( rPos );
+ return CELLTYPE_NONE;
+}
+
+CellType ScDocument::GetCellType( SCCOL nCol, SCROW nRow, SCTAB nTab ) const
+{
+ if (ValidTab(nTab) && nTab < static_cast<SCTAB>(maTabs.size()) && maTabs[nTab])
+ return maTabs[nTab]->GetCellType( nCol, nRow );
+
+ return CELLTYPE_NONE;
+}
+
+bool ScDocument::HasStringData( SCCOL nCol, SCROW nRow, SCTAB nTab ) const
+{
+ if ( ValidTab(nTab) && nTab < static_cast<SCTAB>(maTabs.size()) && maTabs[nTab]
+ && nCol < maTabs[nTab]->GetAllocatedColumnsCount())
+ return maTabs[nTab]->HasStringData( nCol, nRow );
+ else
+ return false;
+}
+
+bool ScDocument::HasValueData( SCCOL nCol, SCROW nRow, SCTAB nTab ) const
+{
+ if ( ValidTab(nTab) && nTab < static_cast<SCTAB>(maTabs.size()) && maTabs[nTab]
+ && nCol < maTabs[nTab]->GetAllocatedColumnsCount())
+ return maTabs[nTab]->HasValueData( nCol, nRow );
+ else
+ return false;
+}
+
+bool ScDocument::HasValueData( const ScAddress& rPos ) const
+{
+ return HasValueData(rPos.Col(), rPos.Row(), rPos.Tab());
+}
+
+bool ScDocument::HasStringCells( const ScRange& rRange ) const
+{
+ // true, if String- or Edit cells in range
+
+ SCCOL nStartCol = rRange.aStart.Col();
+ SCROW nStartRow = rRange.aStart.Row();
+ SCTAB nStartTab = rRange.aStart.Tab();
+ SCCOL nEndCol = rRange.aEnd.Col();
+ SCROW nEndRow = rRange.aEnd.Row();
+ SCTAB nEndTab = rRange.aEnd.Tab();
+
+ for ( SCTAB nTab=nStartTab; nTab<=nEndTab && nTab < static_cast<SCTAB>(maTabs.size()); nTab++ )
+ if ( maTabs[nTab] && maTabs[nTab]->HasStringCells( nStartCol, nStartRow, nEndCol, nEndRow ) )
+ return true;
+
+ return false;
+}
+
+bool ScDocument::HasSelectionData( SCCOL nCol, SCROW nRow, SCTAB nTab ) const
+{
+ sal_uInt32 nValidation = GetAttr( nCol, nRow, nTab, ATTR_VALIDDATA )->GetValue();
+ if( nValidation )
+ {
+ const ScValidationData* pData = GetValidationEntry( nValidation );
+ if( pData && pData->HasSelectionList() )
+ return true;
+ }
+ return HasStringCells( ScRange( nCol, 0, nTab, nCol, MaxRow(), nTab ) );
+}
+
+bool ScDocument::HasValidationData( SCCOL nCol, SCROW nRow, SCTAB nTab ) const
+{
+ sal_uInt32 nValidation = GetAttr( nCol, nRow, nTab, ATTR_VALIDDATA )->GetValue();
+ if( nValidation )
+ {
+ const ScValidationData* pData = GetValidationEntry( nValidation );
+ if( pData && pData->GetDataMode() != ScValidationMode::SC_VALID_ANY )
+ return true;
+ }
+ return false;
+}
+
+void ScDocument::CheckVectorizationState()
+{
+ bool bOldAutoCalc = GetAutoCalc();
+ bAutoCalc = false; // no multiple calculations
+
+ for (const auto& a : maTabs)
+ {
+ if (a)
+ a->CheckVectorizationState();
+ }
+
+ SetAutoCalc(bOldAutoCalc);
+}
+
+void ScDocument::SetAllFormulasDirty( const sc::SetFormulaDirtyContext& rCxt )
+{
+ bool bOldAutoCalc = GetAutoCalc();
+ bAutoCalc = false; // no multiple calculations
+ { // scope for bulk broadcast
+ ScBulkBroadcast aBulkBroadcast( GetBASM(), SfxHintId::ScDataChanged);
+ for (const auto& a : maTabs)
+ {
+ if (a)
+ a->SetAllFormulasDirty(rCxt);
+ }
+ }
+
+ // Although Charts are also set to dirty in Tracking without AutoCalc
+ // if all formulas are dirty, the charts can no longer be caught
+ // (#45205#) - that is why all Charts have to be explicitly handled again
+ if (pChartListenerCollection)
+ pChartListenerCollection->SetDirty();
+
+ SetAutoCalc( bOldAutoCalc );
+}
+
+void ScDocument::SetDirty( const ScRange& rRange, bool bIncludeEmptyCells )
+{
+ bool bOldAutoCalc = GetAutoCalc();
+ bAutoCalc = false; // no multiple calculations
+ { // scope for bulk broadcast
+ ScBulkBroadcast aBulkBroadcast( GetBASM(), SfxHintId::ScDataChanged);
+ SCTAB nTab2 = rRange.aEnd.Tab();
+ for (SCTAB i=rRange.aStart.Tab(); i<=nTab2 && i < static_cast<SCTAB>(maTabs.size()); i++)
+ if (maTabs[i]) maTabs[i]->SetDirty( rRange,
+ (bIncludeEmptyCells ? ScColumn::BROADCAST_BROADCASTERS : ScColumn::BROADCAST_DATA_POSITIONS));
+
+ /* TODO: this now also notifies conditional formatting and does a UNO
+ * broadcast, which wasn't done here before. Is that an actually
+ * desired side effect, or should we come up with a method that
+ * doesn't? */
+ if (bIncludeEmptyCells)
+ BroadcastCells( rRange, SfxHintId::ScDataChanged, false);
+ }
+ SetAutoCalc( bOldAutoCalc );
+}
+
+void ScDocument::SetTableOpDirty( const ScRange& rRange )
+{
+ bool bOldAutoCalc = GetAutoCalc();
+ bAutoCalc = false; // no multiple recalculation
+ SCTAB nTab2 = rRange.aEnd.Tab();
+ for (SCTAB i=rRange.aStart.Tab(); i<=nTab2 && i < static_cast<SCTAB>(maTabs.size()); i++)
+ if (maTabs[i]) maTabs[i]->SetTableOpDirty( rRange );
+ SetAutoCalc( bOldAutoCalc );
+}
+
+void ScDocument::InterpretDirtyCells( const ScRangeList& rRanges )
+{
+ if (!GetAutoCalc())
+ return;
+
+ PrepareFormulaCalc();
+
+ for (size_t nPos=0, nRangeCount = rRanges.size(); nPos < nRangeCount; nPos++)
+ {
+ const ScRange& rRange = rRanges[nPos];
+ for (SCTAB nTab = rRange.aStart.Tab(); nTab <= rRange.aEnd.Tab(); ++nTab)
+ {
+ ScTable* pTab = FetchTable(nTab);
+ if (!pTab)
+ return;
+
+ pTab->InterpretDirtyCells(
+ rRange.aStart.Col(), rRange.aStart.Row(), rRange.aEnd.Col(), rRange.aEnd.Row());
+ }
+ }
+
+ ScMutationGuard aGuard(*this, ScMutationGuardFlags::CORE);
+ mpFormulaGroupCxt.reset();
+}
+
+bool ScDocument::InterpretCellsIfNeeded( const ScRangeList& rRanges )
+{
+ bool allInterpreted = true;
+ for (size_t nPos=0, nRangeCount = rRanges.size(); nPos < nRangeCount; nPos++)
+ {
+ const ScRange& rRange = rRanges[nPos];
+ for (SCTAB nTab = rRange.aStart.Tab(); nTab <= rRange.aEnd.Tab(); ++nTab)
+ {
+ ScTable* pTab = FetchTable(nTab);
+ if (!pTab)
+ break;
+
+ if( !pTab->InterpretCellsIfNeeded(
+ rRange.aStart.Col(), rRange.aStart.Row(), rRange.aEnd.Col(), rRange.aEnd.Row()))
+ {
+ allInterpreted = false;
+ }
+ }
+ }
+ return allInterpreted;
+}
+
+void ScDocument::AddTableOpFormulaCell( ScFormulaCell* pCell )
+{
+ if (m_TableOpList.empty())
+ return;
+
+ ScInterpreterTableOpParams *const p = m_TableOpList.back();
+ if ( p->bCollectNotifications )
+ {
+ if ( p->bRefresh )
+ { // refresh pointers only
+ p->aNotifiedFormulaCells.push_back( pCell );
+ }
+ else
+ { // init both, address and pointer
+ p->aNotifiedFormulaCells.push_back( pCell );
+ p->aNotifiedFormulaPos.push_back( pCell->aPos );
+ }
+ }
+}
+
+void ScDocument::CalcAll()
+{
+ PrepareFormulaCalc();
+ ClearLookupCaches(); // Ensure we don't deliver zombie data.
+ sc::AutoCalcSwitch aSwitch(*this, true);
+ for (const auto& a : maTabs)
+ {
+ if (a)
+ a->SetDirtyVar();
+ }
+ for (const auto& a : maTabs)
+ {
+ if (a)
+ a->CalcAll();
+ }
+ ClearFormulaTree();
+
+ // In eternal hard recalc state caches were not added as listeners,
+ // invalidate them so the next non-CalcAll() normal lookup will not be
+ // presented with outdated data.
+ if (GetHardRecalcState() == HardRecalcState::ETERNAL)
+ ClearLookupCaches();
+}
+
+void ScDocument::CompileAll()
+{
+ sc::CompileFormulaContext aCxt(*this);
+ for (const auto& a : maTabs)
+ {
+ if (a)
+ a->CompileAll(aCxt);
+ }
+
+ sc::SetFormulaDirtyContext aFormulaDirtyCxt;
+ SetAllFormulasDirty(aFormulaDirtyCxt);
+}
+
+void ScDocument::CompileXML()
+{
+ bool bOldAutoCalc = GetAutoCalc();
+ SetAutoCalc( false );
+ ScProgress aProgress( GetDocumentShell(), ScResId(
+ STR_PROGRESS_CALCULATING ), GetXMLImportedFormulaCount(), true );
+
+ sc::CompileFormulaContext aCxt(*this);
+
+ // set AutoNameCache to speed up automatic name lookup
+ OSL_ENSURE( !pAutoNameCache, "AutoNameCache already set" );
+ pAutoNameCache.reset( new ScAutoNameCache( *this ) );
+
+ if (pRangeName)
+ pRangeName->CompileUnresolvedXML(aCxt);
+
+ std::for_each(maTabs.begin(), maTabs.end(),
+ [&](ScTableUniquePtr & pTab)
+ {
+ if (pTab)
+ pTab->CompileXML(aCxt, aProgress);
+ }
+ );
+ StartAllListeners();
+
+ pAutoNameCache.reset(); // valid only during CompileXML, where cell contents don't change
+
+ if ( pValidationList )
+ {
+ ScMutationGuard aGuard(*this, ScMutationGuardFlags::CORE);
+ pValidationList->CompileXML();
+ }
+
+ // Track all formula cells that were appended to the FormulaTrack during
+ // import or CompileXML().
+ TrackFormulas();
+
+ SetAutoCalc( bOldAutoCalc );
+}
+
+bool ScDocument::CompileErrorCells(FormulaError nErrCode)
+{
+ bool bCompiled = false;
+ sc::CompileFormulaContext aCxt(*this);
+ for (const auto& a : maTabs)
+ {
+ if (!a)
+ continue;
+
+ if (a->CompileErrorCells(aCxt, nErrCode))
+ bCompiled = true;
+ }
+
+ return bCompiled;
+}
+
+void ScDocument::CalcAfterLoad( bool bStartListening )
+{
+ if (bIsClip) // Excel data is loaded from the Clipboard to a Clip-Doc
+ return; // the calculation is then only performed when inserting into the real document
+
+ bCalcingAfterLoad = true;
+ sc::CompileFormulaContext aCxt(*this);
+ {
+ for (const auto& a : maTabs)
+ {
+ if (a)
+ a->CalcAfterLoad(aCxt, bStartListening);
+ }
+ for (const auto& a : maTabs)
+ {
+ if (a)
+ a->SetDirtyAfterLoad();
+ }
+ }
+ bCalcingAfterLoad = false;
+
+ SetDetectiveDirty(false); // No real changes yet
+
+ // #i112436# If formula cells are already dirty, they don't broadcast further changes.
+ // So the source ranges of charts must be interpreted even if they are not visible,
+ // similar to ScMyShapeResizer::CreateChartListener for loading own files (i104899).
+ if (pChartListenerCollection)
+ {
+ const ScChartListenerCollection::ListenersType& rListeners = pChartListenerCollection->getListeners();
+ for (auto const& it : rListeners)
+ {
+ const ScChartListener *const p = it.second.get();
+ InterpretDirtyCells(*p->GetRangeList());
+ }
+ }
+}
+
+FormulaError ScDocument::GetErrCode( const ScAddress& rPos ) const
+{
+ SCTAB nTab = rPos.Tab();
+ if ( nTab < static_cast<SCTAB>(maTabs.size()) && maTabs[nTab] )
+ return maTabs[nTab]->GetErrCode( rPos );
+ return FormulaError::NONE;
+}
+
+void ScDocument::ResetChanged( const ScRange& rRange )
+{
+ SCTAB nTabSize = static_cast<SCTAB>(maTabs.size());
+ SCTAB nTab1 = rRange.aStart.Tab();
+ SCTAB nTab2 = rRange.aEnd.Tab();
+ for (SCTAB nTab = nTab1; nTab1 <= nTab2 && nTab < nTabSize; ++nTab)
+ if (maTabs[nTab])
+ maTabs[nTab]->ResetChanged(rRange);
+}
+
+// Column widths / Row heights --------------------------------------
+
+void ScDocument::SetColWidth( SCCOL nCol, SCTAB nTab, sal_uInt16 nNewWidth )
+{
+ if ( ValidTab(nTab) && nTab < static_cast<SCTAB>(maTabs.size()) && maTabs[nTab] )
+ maTabs[nTab]->SetColWidth( nCol, nNewWidth );
+}
+
+void ScDocument::SetColWidthOnly( SCCOL nCol, SCTAB nTab, sal_uInt16 nNewWidth )
+{
+ if ( ValidTab(nTab) && nTab < static_cast<SCTAB>(maTabs.size()) && maTabs[nTab] )
+ maTabs[nTab]->SetColWidthOnly( nCol, nNewWidth );
+}
+
+void ScDocument::SetRowHeight( SCROW nRow, SCTAB nTab, sal_uInt16 nNewHeight )
+{
+ if ( ValidTab(nTab) && nTab < static_cast<SCTAB>(maTabs.size()) && maTabs[nTab] )
+ maTabs[nTab]->SetRowHeight( nRow, nNewHeight );
+}
+
+void ScDocument::SetRowHeightRange( SCROW nStartRow, SCROW nEndRow, SCTAB nTab, sal_uInt16 nNewHeight )
+{
+ if ( ValidTab(nTab) && nTab < static_cast<SCTAB>(maTabs.size()) && maTabs[nTab] )
+ maTabs[nTab]->SetRowHeightRange
+ ( nStartRow, nEndRow, nNewHeight, 1.0, true );
+}
+
+void ScDocument::SetRowHeightOnly( SCROW nStartRow, SCROW nEndRow, SCTAB nTab, sal_uInt16 nNewHeight )
+{
+ if ( ValidTab(nTab) && nTab < static_cast<SCTAB>(maTabs.size()) && maTabs[nTab] )
+ maTabs[nTab]->SetRowHeightOnly( nStartRow, nEndRow, nNewHeight );
+}
+
+void ScDocument::SetManualHeight( SCROW nStartRow, SCROW nEndRow, SCTAB nTab, bool bManual )
+{
+ if ( ValidTab(nTab) && nTab < static_cast<SCTAB>(maTabs.size()) && maTabs[nTab] )
+ maTabs[nTab]->SetManualHeight( nStartRow, nEndRow, bManual );
+}
+
+sal_uInt16 ScDocument::GetColWidth( SCCOL nCol, SCTAB nTab, bool bHiddenAsZero ) const
+{
+ if ( ValidTab(nTab) && nTab < static_cast<SCTAB>(maTabs.size()) && maTabs[nTab] )
+ return maTabs[nTab]->GetColWidth( nCol, bHiddenAsZero );
+ OSL_FAIL("wrong table number");
+ return 0;
+}
+
+tools::Long ScDocument::GetColWidth( SCCOL nStartCol, SCCOL nEndCol, SCTAB nTab ) const
+{
+ const ScTable* pTab = FetchTable(nTab);
+ if (!pTab)
+ return 0;
+
+ return pTab->GetColWidth(nStartCol, nEndCol);
+}
+
+sal_uInt16 ScDocument::GetOriginalWidth( SCCOL nCol, SCTAB nTab ) const
+{
+ if ( ValidTab(nTab) && nTab < static_cast<SCTAB>(maTabs.size()) && maTabs[nTab] )
+ return maTabs[nTab]->GetOriginalWidth( nCol );
+ OSL_FAIL("wrong table number");
+ return 0;
+}
+
+sal_uInt16 ScDocument::GetCommonWidth( SCCOL nEndCol, SCTAB nTab ) const
+{
+ if ( ValidTab(nTab) && nTab < static_cast<SCTAB>(maTabs.size()) && maTabs[nTab] )
+ return maTabs[nTab]->GetCommonWidth( nEndCol );
+ OSL_FAIL("Wrong table number");
+ return 0;
+}
+
+sal_uInt16 ScDocument::GetOriginalHeight( SCROW nRow, SCTAB nTab ) const
+{
+ if ( ValidTab(nTab) && nTab < static_cast<SCTAB>(maTabs.size()) && maTabs[nTab] )
+ return maTabs[nTab]->GetOriginalHeight( nRow );
+ OSL_FAIL("Wrong table number");
+ return 0;
+}
+
+sal_uInt16 ScDocument::GetRowHeight( SCROW nRow, SCTAB nTab, bool bHiddenAsZero ) const
+{
+ if ( ValidTab(nTab) && nTab < static_cast<SCTAB>(maTabs.size()) && maTabs[nTab] )
+ return maTabs[nTab]->GetRowHeight( nRow, nullptr, nullptr, bHiddenAsZero );
+ OSL_FAIL("Wrong sheet number");
+ return 0;
+}
+
+sal_uInt16 ScDocument::GetRowHeight( SCROW nRow, SCTAB nTab, SCROW* pStartRow, SCROW* pEndRow, bool bHiddenAsZero ) const
+{
+ if ( ValidTab(nTab) && nTab < static_cast<SCTAB>(maTabs.size()) && maTabs[nTab] )
+ return maTabs[nTab]->GetRowHeight( nRow, pStartRow, pEndRow, bHiddenAsZero );
+ OSL_FAIL("Wrong sheet number");
+ return 0;
+}
+
+tools::Long ScDocument::GetRowHeight( SCROW nStartRow, SCROW nEndRow, SCTAB nTab, bool bHiddenAsZero ) const
+{
+ if (nStartRow == nEndRow)
+ return GetRowHeight( nStartRow, nTab, bHiddenAsZero ); // faster for a single row
+
+ // check bounds because this method replaces former for(i=start;i<=end;++i) loops
+ if (nStartRow > nEndRow)
+ return 0;
+
+ if ( ValidTab(nTab) && nTab < static_cast<SCTAB>(maTabs.size()) && maTabs[nTab] )
+ return maTabs[nTab]->GetRowHeight( nStartRow, nEndRow, bHiddenAsZero );
+
+ OSL_FAIL("wrong sheet number");
+ return 0;
+}
+
+SCROW ScDocument::GetRowForHeight( SCTAB nTab, tools::Long nHeight ) const
+{
+ return maTabs[nTab]->GetRowForHeight(nHeight);
+}
+
+tools::Long ScDocument::GetScaledRowHeight( SCROW nStartRow, SCROW nEndRow,
+ SCTAB nTab, double fScale ) const
+{
+ // faster for a single row
+ if (nStartRow == nEndRow)
+ return static_cast<tools::Long>(GetRowHeight( nStartRow, nTab) * fScale);
+
+ // check bounds because this method replaces former for(i=start;i<=end;++i) loops
+ if (nStartRow > nEndRow)
+ return 0;
+
+ if ( ValidTab(nTab) && nTab < static_cast<SCTAB>(maTabs.size()) && maTabs[nTab] )
+ return maTabs[nTab]->GetScaledRowHeight( nStartRow, nEndRow, fScale);
+
+ OSL_FAIL("wrong sheet number");
+ return 0;
+}
+
+SCROW ScDocument::GetHiddenRowCount( SCROW nRow, SCTAB nTab ) const
+{
+ if ( ValidTab(nTab) && nTab < static_cast<SCTAB>(maTabs.size()) && maTabs[nTab] )
+ return maTabs[nTab]->GetHiddenRowCount( nRow );
+ OSL_FAIL("wrong table number");
+ return 0;
+}
+
+tools::Long ScDocument::GetColOffset( SCCOL nCol, SCTAB nTab, bool bHiddenAsZero ) const
+{
+ if ( ValidTab(nTab) && nTab < static_cast<SCTAB>(maTabs.size()) && maTabs[nTab] )
+ return maTabs[nTab]->GetColOffset( nCol, bHiddenAsZero );
+ OSL_FAIL("wrong table number");
+ return 0;
+}
+
+tools::Long ScDocument::GetRowOffset( SCROW nRow, SCTAB nTab, bool bHiddenAsZero ) const
+{
+ if ( ValidTab(nTab) && nTab < static_cast<SCTAB>(maTabs.size()) && maTabs[nTab] )
+ return maTabs[nTab]->GetRowOffset( nRow, bHiddenAsZero );
+ OSL_FAIL("wrong table number");
+ return 0;
+}
+
+sal_uInt16 ScDocument::GetOptimalColWidth( SCCOL nCol, SCTAB nTab, OutputDevice* pDev,
+ double nPPTX, double nPPTY,
+ const Fraction& rZoomX, const Fraction& rZoomY,
+ bool bFormula, const ScMarkData* pMarkData,
+ const ScColWidthParam* pParam )
+{
+ if ( ValidTab(nTab) && nTab < static_cast<SCTAB>(maTabs.size()) && maTabs[nTab] )
+ return maTabs[nTab]->GetOptimalColWidth( nCol, pDev, nPPTX, nPPTY,
+ rZoomX, rZoomY, bFormula, pMarkData, pParam );
+ OSL_FAIL("wrong table number");
+ return 0;
+}
+
+tools::Long ScDocument::GetNeededSize( SCCOL nCol, SCROW nRow, SCTAB nTab,
+ OutputDevice* pDev,
+ double nPPTX, double nPPTY,
+ const Fraction& rZoomX, const Fraction& rZoomY,
+ bool bWidth, bool bTotalSize, bool bInPrintTwips )
+{
+ if ( ValidTab(nTab) && nTab < static_cast<SCTAB>(maTabs.size()) && maTabs[nTab] )
+ return maTabs[nTab]->GetNeededSize
+ ( nCol, nRow, pDev, nPPTX, nPPTY, rZoomX, rZoomY, bWidth, bTotalSize, bInPrintTwips );
+ OSL_FAIL("wrong table number");
+ return 0;
+}
+
+bool ScDocument::SetOptimalHeight( sc::RowHeightContext& rCxt, SCROW nStartRow, SCROW nEndRow, SCTAB nTab, bool bApi )
+{
+ ScTable* pTab = FetchTable(nTab);
+ if (!pTab)
+ return false;
+
+ return pTab->SetOptimalHeight(rCxt, nStartRow, nEndRow, bApi);
+}
+
+void ScDocument::UpdateAllRowHeights( sc::RowHeightContext& rCxt, const ScMarkData* pTabMark )
+{
+ // one progress across all (selected) sheets
+
+ sal_uInt64 nCellCount = 0;
+ for ( SCTAB nTab=0; nTab< static_cast<SCTAB>(maTabs.size()); nTab++ )
+ if ( maTabs[nTab] && ( !pTabMark || pTabMark->GetTableSelect(nTab) ) )
+ nCellCount += maTabs[nTab]->GetWeightedCount();
+
+ ScProgress aProgress( GetDocumentShell(), ScResId(STR_PROGRESS_HEIGHTING), nCellCount, true );
+
+ sal_uInt64 nProgressStart = 0;
+ for ( SCTAB nTab=0; nTab< static_cast<SCTAB>(maTabs.size()); nTab++ )
+ if ( maTabs[nTab] && ( !pTabMark || pTabMark->GetTableSelect(nTab) ) )
+ {
+ maTabs[nTab]->SetOptimalHeightOnly(rCxt, 0, MaxRow(), &aProgress, nProgressStart);
+ maTabs[nTab]->SetDrawPageSize();
+ nProgressStart += maTabs[nTab]->GetWeightedCount();
+ }
+}
+
+// Column/Row - Flags ----------------------------------------------
+
+void ScDocument::ShowCol(SCCOL nCol, SCTAB nTab, bool bShow)
+{
+ if ( ValidTab(nTab) && nTab < static_cast<SCTAB>(maTabs.size()) && maTabs[nTab] )
+ maTabs[nTab]->ShowCol( nCol, bShow );
+}
+
+void ScDocument::ShowRow(SCROW nRow, SCTAB nTab, bool bShow)
+{
+ if ( ValidTab(nTab) && nTab < static_cast<SCTAB>(maTabs.size()) && maTabs[nTab] )
+ maTabs[nTab]->ShowRow( nRow, bShow );
+}
+
+void ScDocument::ShowRows(SCROW nRow1, SCROW nRow2, SCTAB nTab, bool bShow)
+{
+ if ( ValidTab(nTab) && nTab < static_cast<SCTAB>(maTabs.size()) && maTabs[nTab] )
+ maTabs[nTab]->ShowRows( nRow1, nRow2, bShow );
+}
+
+void ScDocument::SetRowFlags( SCROW nRow, SCTAB nTab, CRFlags nNewFlags )
+{
+ if ( ValidTab(nTab) && nTab < static_cast<SCTAB>(maTabs.size()) && maTabs[nTab] )
+ maTabs[nTab]->SetRowFlags( nRow, nNewFlags );
+}
+
+void ScDocument::SetRowFlags( SCROW nStartRow, SCROW nEndRow, SCTAB nTab, CRFlags nNewFlags )
+{
+ if ( ValidTab(nTab) && nTab < static_cast<SCTAB>(maTabs.size()) && maTabs[nTab] )
+ maTabs[nTab]->SetRowFlags( nStartRow, nEndRow, nNewFlags );
+}
+
+CRFlags ScDocument::GetColFlags( SCCOL nCol, SCTAB nTab ) const
+{
+ if ( ValidTab(nTab) && nTab < static_cast<SCTAB>(maTabs.size()) && maTabs[nTab] )
+ return maTabs[nTab]->GetColFlags( nCol );
+ OSL_FAIL("wrong table number");
+ return CRFlags::NONE;
+}
+
+CRFlags ScDocument::GetRowFlags( SCROW nRow, SCTAB nTab ) const
+{
+ if ( ValidTab(nTab) && nTab < static_cast<SCTAB>(maTabs.size()) && maTabs[nTab] )
+ return maTabs[nTab]->GetRowFlags( nRow );
+ OSL_FAIL("wrong table number");
+ return CRFlags::NONE;
+}
+
+void ScDocument::GetAllRowBreaks(set<SCROW>& rBreaks, SCTAB nTab, bool bPage, bool bManual) const
+{
+ if (!ValidTab(nTab) || nTab >= static_cast<SCTAB>(maTabs.size()) || !maTabs[nTab])
+ return;
+ maTabs[nTab]->GetAllRowBreaks(rBreaks, bPage, bManual);
+}
+
+void ScDocument::GetAllColBreaks(set<SCCOL>& rBreaks, SCTAB nTab, bool bPage, bool bManual) const
+{
+ if (!ValidTab(nTab) || !maTabs[nTab])
+ return;
+
+ maTabs[nTab]->GetAllColBreaks(rBreaks, bPage, bManual);
+}
+
+ScBreakType ScDocument::HasRowBreak(SCROW nRow, SCTAB nTab) const
+{
+ ScBreakType nType = ScBreakType::NONE;
+ if (!ValidTab(nTab) || nTab >= static_cast<SCTAB>(maTabs.size()) || !maTabs[nTab] || !ValidRow(nRow))
+ return nType;
+
+ if (maTabs[nTab]->HasRowPageBreak(nRow))
+ nType |= ScBreakType::Page;
+
+ if (maTabs[nTab]->HasRowManualBreak(nRow))
+ nType |= ScBreakType::Manual;
+
+ return nType;
+}
+
+ScBreakType ScDocument::HasColBreak(SCCOL nCol, SCTAB nTab) const
+{
+ ScBreakType nType = ScBreakType::NONE;
+ if (!ValidTab(nTab) || nTab >= static_cast<SCTAB>(maTabs.size()) || !maTabs[nTab] || !ValidCol(nCol))
+ return nType;
+
+ if (maTabs[nTab]->HasColPageBreak(nCol))
+ nType |= ScBreakType::Page;
+
+ if (maTabs[nTab]->HasColManualBreak(nCol))
+ nType |= ScBreakType::Manual;
+
+ return nType;
+}
+
+void ScDocument::SetRowBreak(SCROW nRow, SCTAB nTab, bool bPage, bool bManual)
+{
+ if (!ValidTab(nTab) || nTab >= static_cast<SCTAB>(maTabs.size()) || !maTabs[nTab] || !ValidRow(nRow))
+ return;
+
+ maTabs[nTab]->SetRowBreak(nRow, bPage, bManual);
+}
+
+void ScDocument::SetColBreak(SCCOL nCol, SCTAB nTab, bool bPage, bool bManual)
+{
+ if (!ValidTab(nTab) || nTab >= static_cast<SCTAB>(maTabs.size()) || !maTabs[nTab] || !ValidCol(nCol))
+ return;
+
+ maTabs[nTab]->SetColBreak(nCol, bPage, bManual);
+}
+
+void ScDocument::RemoveRowBreak(SCROW nRow, SCTAB nTab, bool bPage, bool bManual)
+{
+ if (!ValidTab(nTab) || nTab >= static_cast<SCTAB>(maTabs.size()) || !maTabs[nTab] || !ValidRow(nRow))
+ return;
+
+ maTabs[nTab]->RemoveRowBreak(nRow, bPage, bManual);
+}
+
+void ScDocument::RemoveColBreak(SCCOL nCol, SCTAB nTab, bool bPage, bool bManual)
+{
+ if (!ValidTab(nTab) || nTab >= static_cast<SCTAB>(maTabs.size()) || !maTabs[nTab] || !ValidCol(nCol))
+ return;
+
+ maTabs[nTab]->RemoveColBreak(nCol, bPage, bManual);
+}
+
+Sequence<TablePageBreakData> ScDocument::GetRowBreakData(SCTAB nTab) const
+{
+ if (!ValidTab(nTab) || nTab >= static_cast<SCTAB>(maTabs.size()) || !maTabs[nTab])
+ return Sequence<TablePageBreakData>();
+
+ return maTabs[nTab]->GetRowBreakData();
+}
+
+bool ScDocument::RowHidden(SCROW nRow, SCTAB nTab, SCROW* pFirstRow, SCROW* pLastRow) const
+{
+ if (!ValidTab(nTab) || nTab >= static_cast<SCTAB>(maTabs.size()) || !maTabs[nTab])
+ return false;
+
+ return maTabs[nTab]->RowHidden(nRow, pFirstRow, pLastRow);
+}
+
+bool ScDocument::HasHiddenRows(SCROW nStartRow, SCROW nEndRow, SCTAB nTab) const
+{
+ if (!ValidTab(nTab) || nTab >= static_cast<SCTAB>(maTabs.size()) || !maTabs[nTab])
+ return false;
+
+ return maTabs[nTab]->HasHiddenRows(nStartRow, nEndRow);
+}
+
+bool ScDocument::ColHidden(SCCOL nCol, SCTAB nTab, SCCOL* pFirstCol, SCCOL* pLastCol) const
+{
+ if (!ValidTab(nTab) || nTab >= static_cast<SCTAB>(maTabs.size()) || !maTabs[nTab])
+ {
+ if (pFirstCol)
+ *pFirstCol = nCol;
+ if (pLastCol)
+ *pLastCol = nCol;
+ return false;
+ }
+
+ return maTabs[nTab]->ColHidden(nCol, pFirstCol, pLastCol);
+}
+
+void ScDocument::SetRowHidden(SCROW nStartRow, SCROW nEndRow, SCTAB nTab, bool bHidden)
+{
+ if (!ValidTab(nTab) || nTab >= static_cast<SCTAB>(maTabs.size()) || !maTabs[nTab])
+ return;
+
+ maTabs[nTab]->SetRowHidden(nStartRow, nEndRow, bHidden);
+}
+
+void ScDocument::SetColHidden(SCCOL nStartCol, SCCOL nEndCol, SCTAB nTab, bool bHidden)
+{
+ if (!ValidTab(nTab) || nTab >= static_cast<SCTAB>(maTabs.size()) || !maTabs[nTab])
+ return;
+
+ maTabs[nTab]->SetColHidden(nStartCol, nEndCol, bHidden);
+}
+
+SCROW ScDocument::FirstVisibleRow(SCROW nStartRow, SCROW nEndRow, SCTAB nTab) const
+{
+ if (!ValidTab(nTab) || nTab >= static_cast<SCTAB>(maTabs.size()) || !maTabs[nTab])
+ return ::std::numeric_limits<SCROW>::max();
+
+ return maTabs[nTab]->FirstVisibleRow(nStartRow, nEndRow);
+}
+
+SCROW ScDocument::LastVisibleRow(SCROW nStartRow, SCROW nEndRow, SCTAB nTab) const
+{
+ if (!ValidTab(nTab) || nTab >= static_cast<SCTAB>(maTabs.size()) || !maTabs[nTab])
+ return ::std::numeric_limits<SCROW>::max();
+
+ return maTabs[nTab]->LastVisibleRow(nStartRow, nEndRow);
+}
+
+SCROW ScDocument::CountVisibleRows(SCROW nStartRow, SCROW nEndRow, SCTAB nTab) const
+{
+ if (!ValidTab(nTab) || nTab >= static_cast<SCTAB>(maTabs.size()) || !maTabs[nTab])
+ return 0;
+
+ return maTabs[nTab]->CountVisibleRows(nStartRow, nEndRow);
+}
+
+bool ScDocument::RowFiltered(SCROW nRow, SCTAB nTab, SCROW* pFirstRow, SCROW* pLastRow) const
+{
+ if (!ValidTab(nTab) || nTab >= static_cast<SCTAB>(maTabs.size()) || !maTabs[nTab])
+ return false;
+
+ return maTabs[nTab]->RowFiltered(nRow, pFirstRow, pLastRow);
+}
+
+bool ScDocument::HasFilteredRows(SCROW nStartRow, SCROW nEndRow, SCTAB nTab) const
+{
+ if (!ValidTab(nTab) || nTab >= static_cast<SCTAB>(maTabs.size()) || !maTabs[nTab])
+ return false;
+
+ return maTabs[nTab]->HasFilteredRows(nStartRow, nEndRow);
+}
+
+bool ScDocument::ColFiltered(SCCOL nCol, SCTAB nTab) const
+{
+ if (!ValidTab(nTab) || nTab >= static_cast<SCTAB>(maTabs.size()) || !maTabs[nTab])
+ return false;
+
+ return maTabs[nTab]->ColFiltered(nCol);
+}
+
+void ScDocument::SetRowFiltered(SCROW nStartRow, SCROW nEndRow, SCTAB nTab, bool bFiltered)
+{
+ if (!ValidTab(nTab) || nTab >= static_cast<SCTAB>(maTabs.size()) || !maTabs[nTab])
+ return;
+
+ maTabs[nTab]->SetRowFiltered(nStartRow, nEndRow, bFiltered);
+}
+
+SCROW ScDocument::FirstNonFilteredRow(SCROW nStartRow, SCROW nEndRow, SCTAB nTab) const
+{
+ if (!ValidTab(nTab) || nTab >= static_cast<SCTAB>(maTabs.size()) || !maTabs[nTab])
+ return ::std::numeric_limits<SCROW>::max();
+
+ return maTabs[nTab]->FirstNonFilteredRow(nStartRow, nEndRow);
+}
+
+SCROW ScDocument::LastNonFilteredRow(SCROW nStartRow, SCROW nEndRow, SCTAB nTab) const
+{
+ if (!ValidTab(nTab) || nTab >= static_cast<SCTAB>(maTabs.size()) || !maTabs[nTab])
+ return ::std::numeric_limits<SCROW>::max();
+
+ return maTabs[nTab]->LastNonFilteredRow(nStartRow, nEndRow);
+}
+
+SCROW ScDocument::CountNonFilteredRows(SCROW nStartRow, SCROW nEndRow, SCTAB nTab) const
+{
+ if (!ValidTab(nTab) || nTab >= static_cast<SCTAB>(maTabs.size()) || !maTabs[nTab])
+ return 0;
+
+ return maTabs[nTab]->CountNonFilteredRows(nStartRow, nEndRow);
+}
+
+bool ScDocument::IsManualRowHeight(SCROW nRow, SCTAB nTab) const
+{
+ if (!ValidTab(nTab) || nTab >= static_cast<SCTAB>(maTabs.size()) || !maTabs[nTab])
+ return false;
+
+ return maTabs[nTab]->IsManualRowHeight(nRow);
+}
+
+void ScDocument::SyncColRowFlags()
+{
+ for (const auto& a : maTabs)
+ {
+ if (a)
+ a->SyncColRowFlags();
+ }
+}
+
+SCROW ScDocument::GetLastFlaggedRow( SCTAB nTab ) const
+{
+ if ( ValidTab(nTab) && nTab < static_cast<SCTAB>(maTabs.size()) && maTabs[nTab] )
+ return maTabs[nTab]->GetLastFlaggedRow();
+ return 0;
+}
+
+SCCOL ScDocument::GetLastChangedColFlagsWidth( SCTAB nTab ) const
+{
+ if ( ValidTab(nTab) && nTab < static_cast<SCTAB>(maTabs.size()) && maTabs[nTab] )
+ return maTabs[nTab]->GetLastChangedColFlagsWidth();
+ return 0;
+}
+
+SCROW ScDocument::GetLastChangedRowFlagsWidth( SCTAB nTab ) const
+{
+ if ( ValidTab(nTab) && nTab < static_cast<SCTAB>(maTabs.size()) && maTabs[nTab] )
+ return maTabs[nTab]->GetLastChangedRowFlagsWidth();
+ return 0;
+}
+
+SCCOL ScDocument::GetNextDifferentChangedColFlagsWidth( SCTAB nTab, SCCOL nStart) const
+{
+ if ( ValidTab(nTab) && nTab < static_cast<SCTAB>(maTabs.size()) && maTabs[nTab] )
+ {
+ CRFlags nStartFlags = maTabs[nTab]->GetColFlags(nStart);
+ sal_uInt16 nStartWidth = maTabs[nTab]->GetOriginalWidth(nStart);
+ for (SCCOL nCol : maTabs[nTab]->GetColumnsRange( nStart + 1, MaxCol()))
+ {
+ if (((nStartFlags & CRFlags::ManualBreak) != (maTabs[nTab]->GetColFlags(nCol) & CRFlags::ManualBreak)) ||
+ (nStartWidth != maTabs[nTab]->GetOriginalWidth(nCol)) ||
+ ((nStartFlags & CRFlags::Hidden) != (maTabs[nTab]->GetColFlags(nCol) & CRFlags::Hidden)) )
+ return nCol;
+ }
+ return MaxCol()+1;
+ }
+ return 0;
+}
+
+SCROW ScDocument::GetNextDifferentChangedRowFlagsWidth( SCTAB nTab, SCROW nStart) const
+{
+ if (!ValidTab(nTab) || nTab >= static_cast<SCTAB>(maTabs.size()) || !maTabs[nTab])
+ return 0;
+
+ const ScBitMaskCompressedArray<SCROW, CRFlags>* pRowFlagsArray = maTabs[nTab]->GetRowFlagsArray();
+ if (!pRowFlagsArray)
+ return 0;
+
+ if (!maTabs[nTab]->mpRowHeights || !maTabs[nTab]->mpHiddenRows)
+ return 0;
+
+ size_t nIndex; // ignored
+ SCROW nFlagsEndRow;
+ SCROW nHiddenEndRow;
+ SCROW nHeightEndRow;
+ CRFlags nFlags;
+ bool bHidden;
+ sal_uInt16 nHeight;
+ CRFlags nStartFlags = nFlags = pRowFlagsArray->GetValue( nStart, nIndex, nFlagsEndRow);
+ bool bStartHidden = bHidden = maTabs[nTab]->RowHidden( nStart, nullptr, &nHiddenEndRow);
+ sal_uInt16 nStartHeight = nHeight = maTabs[nTab]->GetRowHeight( nStart, nullptr, &nHeightEndRow, false);
+ SCROW nRow;
+ while ((nRow = std::min( nHiddenEndRow, std::min( nFlagsEndRow, nHeightEndRow)) + 1) <= MaxRow())
+ {
+ if (nFlagsEndRow < nRow)
+ nFlags = pRowFlagsArray->GetValue( nRow, nIndex, nFlagsEndRow);
+ if (nHiddenEndRow < nRow)
+ bHidden = maTabs[nTab]->RowHidden( nRow, nullptr, &nHiddenEndRow);
+ if (nHeightEndRow < nRow)
+ nHeight = maTabs[nTab]->GetRowHeight( nRow, nullptr, &nHeightEndRow, false);
+
+ if (((nStartFlags & CRFlags::ManualBreak) != (nFlags & CRFlags::ManualBreak)) ||
+ ((nStartFlags & CRFlags::ManualSize) != (nFlags & CRFlags::ManualSize)) ||
+ (bStartHidden != bHidden) ||
+ (nStartHeight != nHeight))
+ return nRow;
+ }
+
+ return MaxRow()+1;
+}
+
+void ScDocument::GetColDefault( SCTAB nTab, SCCOL nCol, SCROW nLastRow, SCROW& nDefault)
+{
+ nDefault = 0;
+ ScDocAttrIterator aDocAttrItr(*this, nTab, nCol, 0, nCol, nLastRow);
+ SCCOL nColumn;
+ SCROW nStartRow;
+ SCROW nEndRow;
+ const ScPatternAttr* pAttr = aDocAttrItr.GetNext(nColumn, nStartRow, nEndRow);
+ if (nEndRow >= nLastRow)
+ return;
+
+ ScDefaultAttrSet aSet;
+ ScDefaultAttrSet::iterator aItr = aSet.end();
+ while (pAttr)
+ {
+ ScDefaultAttr aAttr(pAttr);
+ aItr = aSet.find(aAttr);
+ if (aItr == aSet.end())
+ {
+ aAttr.nCount = static_cast<SCSIZE>(nEndRow - nStartRow + 1);
+ aAttr.nFirst = nStartRow;
+ aSet.insert(aAttr);
+ }
+ else
+ {
+ aAttr.nCount = aItr->nCount + static_cast<SCSIZE>(nEndRow - nStartRow + 1);
+ aAttr.nFirst = aItr->nFirst;
+ aSet.erase(aItr);
+ aSet.insert(aAttr);
+ }
+ pAttr = aDocAttrItr.GetNext(nColumn, nStartRow, nEndRow);
+ }
+ ScDefaultAttrSet::iterator aDefaultItr = aSet.begin();
+ aItr = aDefaultItr;
+ ++aItr;
+ while (aItr != aSet.end())
+ {
+ // for entries with equal count, use the one with the lowest start row,
+ // don't use the random order of pointer comparisons
+ if ( aItr->nCount > aDefaultItr->nCount ||
+ ( aItr->nCount == aDefaultItr->nCount && aItr->nFirst < aDefaultItr->nFirst ) )
+ aDefaultItr = aItr;
+ ++aItr;
+ }
+ nDefault = aDefaultItr->nFirst;
+}
+
+void ScDocument::StripHidden( SCCOL& rX1, SCROW& rY1, SCCOL& rX2, SCROW& rY2, SCTAB nTab )
+{
+ if ( ValidTab(nTab) && nTab < static_cast<SCTAB>(maTabs.size()) && maTabs[nTab] )
+ maTabs[nTab]->StripHidden( rX1, rY1, rX2, rY2 );
+}
+
+void ScDocument::ExtendHidden( SCCOL& rX1, SCROW& rY1, SCCOL& rX2, SCROW& rY2, SCTAB nTab )
+{
+ if ( ValidTab(nTab) && maTabs[nTab] )
+ maTabs[nTab]->ExtendHidden( rX1, rY1, rX2, rY2 );
+}
+
+// Attribute ----------------------------------------------------------
+
+const SfxPoolItem* ScDocument::GetAttr( SCCOL nCol, SCROW nRow, SCTAB nTab, sal_uInt16 nWhich ) const
+{
+ if ( ValidTab(nTab) && nTab < static_cast<SCTAB>(maTabs.size()) && maTabs[nTab] )
+ {
+ const SfxPoolItem* pTemp = maTabs[nTab]->GetAttr( nCol, nRow, nWhich );
+ if (pTemp)
+ return pTemp;
+ else
+ {
+ OSL_FAIL( "Attribute Null" );
+ }
+ }
+ return &mxPoolHelper->GetDocPool()->GetDefaultItem( nWhich );
+}
+
+const SfxPoolItem* ScDocument::GetAttr( SCCOL nCol, SCROW nRow, SCTAB nTab, sal_uInt16 nWhich, SCROW& nStartRow, SCROW& nEndRow ) const
+{
+ if ( ValidTab(nTab) && nTab < static_cast<SCTAB>(maTabs.size()) && maTabs[nTab] )
+ {
+ const SfxPoolItem* pTemp = maTabs[nTab]->GetAttr( nCol, nRow, nWhich, nStartRow, nEndRow );
+ if (pTemp)
+ return pTemp;
+ else
+ {
+ OSL_FAIL( "Attribute Null" );
+ }
+ }
+ return &mxPoolHelper->GetDocPool()->GetDefaultItem( nWhich );
+}
+
+const SfxPoolItem* ScDocument::GetAttr( const ScAddress& rPos, sal_uInt16 nWhich ) const
+{
+ return GetAttr(rPos.Col(), rPos.Row(), rPos.Tab(), nWhich);
+}
+
+const ScPatternAttr* ScDocument::GetPattern( SCCOL nCol, SCROW nRow, SCTAB nTab ) const
+{
+ if (TableExists(nTab))
+ return maTabs[nTab]->GetPattern( nCol, nRow );
+ return nullptr;
+}
+
+const ScPatternAttr* ScDocument::GetPattern( const ScAddress& rPos ) const
+{
+ if (TableExists(rPos.Tab()))
+ return maTabs[rPos.Tab()]->GetPattern(rPos.Col(), rPos.Row());
+
+ return nullptr;
+}
+
+const ScPatternAttr* ScDocument::GetMostUsedPattern( SCCOL nCol, SCROW nStartRow, SCROW nEndRow, SCTAB nTab ) const
+{
+ if ( ValidTab(nTab) && nTab < static_cast<SCTAB>(maTabs.size()) && maTabs[nTab] )
+ return maTabs[nTab]->GetMostUsedPattern( nCol, nStartRow, nEndRow );
+ return nullptr;
+}
+
+void ScDocument::ApplyAttr( SCCOL nCol, SCROW nRow, SCTAB nTab, const SfxPoolItem& rAttr )
+{
+ if ( ValidTab(nTab) && nTab < static_cast<SCTAB>(maTabs.size()) && maTabs[nTab] )
+ maTabs[nTab]->ApplyAttr( nCol, nRow, rAttr );
+}
+
+void ScDocument::ApplyPattern( SCCOL nCol, SCROW nRow, SCTAB nTab, const ScPatternAttr& rAttr )
+{
+ if ( ValidTab(nTab) && nTab < static_cast<SCTAB>(maTabs.size()) && maTabs[nTab] )
+ maTabs[nTab]->ApplyPattern( nCol, nRow, rAttr );
+}
+
+void ScDocument::ApplyPatternArea( SCCOL nStartCol, SCROW nStartRow,
+ SCCOL nEndCol, SCROW nEndRow,
+ const ScMarkData& rMark,
+ const ScPatternAttr& rAttr,
+ ScEditDataArray* pDataArray,
+ bool* const pIsChanged )
+{
+ SCTAB nMax = static_cast<SCTAB>(maTabs.size());
+ for (const auto& rTab : rMark)
+ {
+ if (rTab >= nMax)
+ break;
+ if (maTabs[rTab])
+ maTabs[rTab]->ApplyPatternArea( nStartCol, nStartRow, nEndCol, nEndRow, rAttr, pDataArray, pIsChanged );
+ }
+}
+
+void ScDocument::ApplyPatternAreaTab( SCCOL nStartCol, SCROW nStartRow,
+ SCCOL nEndCol, SCROW nEndRow, SCTAB nTab, const ScPatternAttr& rAttr )
+{
+ if (ValidTab(nTab) && nTab < static_cast<SCTAB>(maTabs.size()))
+ if (maTabs[nTab])
+ maTabs[nTab]->ApplyPatternArea( nStartCol, nStartRow, nEndCol, nEndRow, rAttr );
+}
+
+void ScDocument::ApplyPatternIfNumberformatIncompatible( const ScRange& rRange,
+ const ScMarkData& rMark, const ScPatternAttr& rPattern, SvNumFormatType nNewType )
+{
+ SCTAB nMax = static_cast<SCTAB>(maTabs.size());
+ for (const auto& rTab : rMark)
+ {
+ if (rTab >= nMax)
+ break;
+ if (maTabs[rTab])
+ maTabs[rTab]->ApplyPatternIfNumberformatIncompatible( rRange, rPattern, nNewType );
+ }
+}
+
+void ScDocument::AddCondFormatData( const ScRangeList& rRange, SCTAB nTab, sal_uInt32 nIndex )
+{
+ if(o3tl::make_unsigned(nTab) >= maTabs.size())
+ return;
+
+ if(!maTabs[nTab])
+ return;
+
+ maTabs[nTab]->AddCondFormatData(rRange, nIndex);
+}
+
+void ScDocument::RemoveCondFormatData( const ScRangeList& rRange, SCTAB nTab, sal_uInt32 nIndex )
+{
+ if(o3tl::make_unsigned(nTab) >= maTabs.size())
+ return;
+
+ if(!maTabs[nTab])
+ return;
+
+ maTabs[nTab]->RemoveCondFormatData(rRange, nIndex);
+}
+
+void ScDocument::ApplyStyle( SCCOL nCol, SCROW nRow, SCTAB nTab, const ScStyleSheet& rStyle)
+{
+ if (ValidTab(nTab) && nTab < static_cast<SCTAB>(maTabs.size()))
+ if (maTabs[nTab])
+ maTabs[nTab]->ApplyStyle( nCol, nRow, &rStyle );
+}
+
+void ScDocument::ApplyStyleArea( SCCOL nStartCol, SCROW nStartRow,
+ SCCOL nEndCol, SCROW nEndRow,
+ const ScMarkData& rMark,
+ const ScStyleSheet& rStyle)
+{
+ SCTAB nMax = static_cast<SCTAB>(maTabs.size());
+ for (const auto& rTab : rMark)
+ {
+ if (rTab >= nMax)
+ break;
+ if (maTabs[rTab])
+ maTabs[rTab]->ApplyStyleArea( nStartCol, nStartRow, nEndCol, nEndRow, rStyle );
+ }
+}
+
+void ScDocument::ApplyStyleAreaTab( SCCOL nStartCol, SCROW nStartRow,
+ SCCOL nEndCol, SCROW nEndRow, SCTAB nTab, const ScStyleSheet& rStyle)
+{
+ if (ValidTab(nTab) && nTab < static_cast<SCTAB>(maTabs.size()))
+ if (maTabs[nTab])
+ maTabs[nTab]->ApplyStyleArea( nStartCol, nStartRow, nEndCol, nEndRow, rStyle );
+}
+
+void ScDocument::ApplySelectionStyle(const ScStyleSheet& rStyle, const ScMarkData& rMark)
+{
+ // ApplySelectionStyle needs multi mark
+ if ( rMark.IsMarked() && !rMark.IsMultiMarked() )
+ {
+ const ScRange& aRange = rMark.GetMarkArea();
+ ApplyStyleArea( aRange.aStart.Col(), aRange.aStart.Row(),
+ aRange.aEnd.Col(), aRange.aEnd.Row(), rMark, rStyle );
+ }
+ else
+ {
+ SCTAB nMax = static_cast<SCTAB>(maTabs.size());
+ for (const auto& rTab : rMark)
+ {
+ if (rTab >= nMax)
+ break;
+ if ( maTabs[rTab] )
+ maTabs[rTab]->ApplySelectionStyle( rStyle, rMark );
+ }
+ }
+}
+
+void ScDocument::ApplySelectionLineStyle( const ScMarkData& rMark,
+ const SvxBorderLine* pLine, bool bColorOnly )
+{
+ if ( bColorOnly && !pLine )
+ return;
+
+ SCTAB nMax = static_cast<SCTAB>(maTabs.size());
+ for (const auto& rTab : rMark)
+ {
+ if (rTab >= nMax)
+ break;
+ if (maTabs[rTab])
+ maTabs[rTab]->ApplySelectionLineStyle( rMark, pLine, bColorOnly );
+ }
+}
+
+const ScStyleSheet* ScDocument::GetStyle( SCCOL nCol, SCROW nRow, SCTAB nTab ) const
+{
+ if ( ValidTab(nTab) && nTab < static_cast<SCTAB>(maTabs.size()) && maTabs[nTab] )
+ return maTabs[nTab]->GetStyle(nCol, nRow);
+ else
+ return nullptr;
+}
+
+const ScStyleSheet* ScDocument::GetSelectionStyle( const ScMarkData& rMark ) const
+{
+ bool bEqual = true;
+ bool bFound;
+
+ const ScStyleSheet* pStyle = nullptr;
+ const ScStyleSheet* pNewStyle;
+
+ if ( rMark.IsMultiMarked() )
+ {
+ SCTAB nMax = static_cast<SCTAB>(maTabs.size());
+ for (const auto& rTab : rMark)
+ {
+ if (rTab >= nMax)
+ break;
+
+ if (maTabs[rTab])
+ {
+ pNewStyle = maTabs[rTab]->GetSelectionStyle( rMark, bFound );
+ if (bFound)
+ {
+ if ( !pNewStyle || ( pStyle && pNewStyle != pStyle ) )
+ bEqual = false; // different
+ pStyle = pNewStyle;
+ }
+ }
+ }
+ }
+ if ( rMark.IsMarked() )
+ {
+ const ScRange& aRange = rMark.GetMarkArea();
+ for (SCTAB i=aRange.aStart.Tab(); i<=aRange.aEnd.Tab() && bEqual && i < static_cast<SCTAB>(maTabs.size()); i++)
+ if (maTabs[i] && rMark.GetTableSelect(i))
+ {
+ pNewStyle = maTabs[i]->GetAreaStyle( bFound,
+ aRange.aStart.Col(), aRange.aStart.Row(),
+ aRange.aEnd.Col(), aRange.aEnd.Row() );
+ if (bFound)
+ {
+ if ( !pNewStyle || ( pStyle && pNewStyle != pStyle ) )
+ bEqual = false; // different
+ pStyle = pNewStyle;
+ }
+ }
+ }
+
+ return bEqual ? pStyle : nullptr;
+}
+
+void ScDocument::StyleSheetChanged( const SfxStyleSheetBase* pStyleSheet, bool bRemoved,
+ OutputDevice* pDev,
+ double nPPTX, double nPPTY,
+ const Fraction& rZoomX, const Fraction& rZoomY )
+{
+ for (const auto& a : maTabs)
+ {
+ if (a)
+ a->StyleSheetChanged
+ ( pStyleSheet, bRemoved, pDev, nPPTX, nPPTY, rZoomX, rZoomY );
+ }
+
+ if ( pStyleSheet && pStyleSheet->GetName() == ScResId(STR_STYLENAME_STANDARD) )
+ {
+ // update attributes for all note objects
+ ScDetectiveFunc::UpdateAllComments( *this );
+ }
+}
+
+bool ScDocument::IsStyleSheetUsed( const ScStyleSheet& rStyle ) const
+{
+ if ( bStyleSheetUsageInvalid || rStyle.GetUsage() == ScStyleSheet::Usage::UNKNOWN )
+ {
+ SfxStyleSheetIterator aIter( mxPoolHelper->GetStylePool(),
+ SfxStyleFamily::Para );
+ for ( const SfxStyleSheetBase* pStyle = aIter.First(); pStyle;
+ pStyle = aIter.Next() )
+ {
+ if (pStyle->isScStyleSheet())
+ {
+ const ScStyleSheet* pScStyle = static_cast<const ScStyleSheet*>( pStyle );
+ pScStyle->SetUsage( ScStyleSheet::Usage::NOTUSED );
+ }
+ }
+
+ bool bIsUsed = false;
+
+ for (const auto& a : maTabs)
+ {
+ if (a && a->IsStyleSheetUsed( rStyle ) )
+ bIsUsed = true;
+ }
+
+ bStyleSheetUsageInvalid = false;
+
+ return bIsUsed;
+ }
+
+ return rStyle.GetUsage() == ScStyleSheet::Usage::USED;
+}
+
+bool ScDocument::ApplyFlagsTab( SCCOL nStartCol, SCROW nStartRow,
+ SCCOL nEndCol, SCROW nEndRow, SCTAB nTab, ScMF nFlags )
+{
+ if (ValidTab(nTab) && nTab < static_cast<SCTAB>(maTabs.size()))
+ if (maTabs[nTab])
+ return maTabs[nTab]->ApplyFlags( nStartCol, nStartRow, nEndCol, nEndRow, nFlags );
+
+ OSL_FAIL("ApplyFlags: wrong table");
+ return false;
+}
+
+bool ScDocument::RemoveFlagsTab( SCCOL nStartCol, SCROW nStartRow,
+ SCCOL nEndCol, SCROW nEndRow, SCTAB nTab, ScMF nFlags )
+{
+ if (ValidTab(nTab) && nTab < static_cast<SCTAB>(maTabs.size()))
+ if (maTabs[nTab])
+ return maTabs[nTab]->RemoveFlags( nStartCol, nStartRow, nEndCol, nEndRow, nFlags );
+
+ OSL_FAIL("RemoveFlags: wrong table");
+ return false;
+}
+
+const ScPatternAttr* ScDocument::SetPattern( SCCOL nCol, SCROW nRow, SCTAB nTab, std::unique_ptr<ScPatternAttr> pAttr )
+{
+ if (ValidTab(nTab) && nTab < static_cast<SCTAB>(maTabs.size()))
+ if (maTabs[nTab])
+ return maTabs[nTab]->SetPattern( nCol, nRow, std::move(pAttr) );
+ return nullptr;
+}
+
+const ScPatternAttr* ScDocument::SetPattern( const ScAddress& rPos, std::unique_ptr<ScPatternAttr> pAttr )
+{
+ return SetPattern(rPos.Col(), rPos.Row(), rPos.Tab(), std::move(pAttr));
+}
+
+void ScDocument::SetPattern( SCCOL nCol, SCROW nRow, SCTAB nTab, const ScPatternAttr& rAttr )
+{
+ if (ValidTab(nTab) && nTab < static_cast<SCTAB>(maTabs.size()))
+ if (maTabs[nTab])
+ maTabs[nTab]->SetPattern( nCol, nRow, rAttr );
+}
+
+void ScDocument::SetPattern( const ScAddress& rPos, const ScPatternAttr& rAttr )
+{
+ SCTAB nTab = rPos.Tab();
+ if ( nTab < static_cast<SCTAB>(maTabs.size()) && maTabs[nTab])
+ maTabs[nTab]->SetPattern( rPos, rAttr );
+}
+
+std::unique_ptr<ScPatternAttr> ScDocument::CreateSelectionPattern( const ScMarkData& rMark, bool bDeep )
+{
+ ScMergePatternState aState;
+
+ if ( rMark.IsMultiMarked() ) // multi selection
+ {
+ SCTAB nMax = static_cast<SCTAB>(maTabs.size());
+ for (const auto& rTab : rMark)
+ {
+ if (rTab >= nMax)
+ break;
+ if (maTabs[rTab])
+ maTabs[rTab]->MergeSelectionPattern( aState, rMark, bDeep );
+ }
+ }
+ if ( rMark.IsMarked() ) // single selection
+ {
+ const ScRange& aRange = rMark.GetMarkArea();
+ SCTAB nMax = static_cast<SCTAB>(maTabs.size());
+ for (const auto& rTab : rMark)
+ {
+ if (rTab >= nMax)
+ break;
+ if (maTabs[rTab])
+ maTabs[rTab]->MergePatternArea( aState,
+ aRange.aStart.Col(), aRange.aStart.Row(),
+ aRange.aEnd.Col(), aRange.aEnd.Row(), bDeep );
+ }
+ }
+
+ OSL_ENSURE( aState.pItemSet, "SelectionPattern Null" );
+ if (aState.pItemSet)
+ {
+ std::unique_ptr<ScPatternAttr> pPattern(new ScPatternAttr( std::move(*aState.pItemSet) ));
+ if (aState.mbValidPatternId)
+ pPattern->SetKey(aState.mnPatternId);
+
+ return pPattern;
+ }
+ else
+ return std::unique_ptr<ScPatternAttr>(new ScPatternAttr( GetPool() )); // empty
+}
+
+const ScPatternAttr* ScDocument::GetSelectionPattern( const ScMarkData& rMark )
+{
+ pSelectionAttr = CreateSelectionPattern( rMark );
+ return pSelectionAttr.get();
+}
+
+void ScDocument::GetSelectionFrame( const ScMarkData& rMark,
+ SvxBoxItem& rLineOuter,
+ SvxBoxInfoItem& rLineInner )
+{
+ rLineOuter.SetLine(nullptr, SvxBoxItemLine::TOP);
+ rLineOuter.SetLine(nullptr, SvxBoxItemLine::BOTTOM);
+ rLineOuter.SetLine(nullptr, SvxBoxItemLine::LEFT);
+ rLineOuter.SetLine(nullptr, SvxBoxItemLine::RIGHT);
+ rLineOuter.SetAllDistances(0);
+
+ rLineInner.SetLine(nullptr, SvxBoxInfoItemLine::HORI);
+ rLineInner.SetLine(nullptr, SvxBoxInfoItemLine::VERT);
+ rLineInner.SetTable(true);
+ rLineInner.SetDist(true);
+ rLineInner.SetMinDist(false);
+
+ ScLineFlags aFlags;
+
+ if( rMark.IsMultiMarked() )
+ {
+ ScRangeList aRangeList;
+ rMark.FillRangeListWithMarks( &aRangeList, false );
+ size_t nRangeCount = aRangeList.size();
+ bool bMultipleRows = false, bMultipleCols = false;
+ for( size_t nRangeIdx = 0; nRangeIdx < nRangeCount; ++nRangeIdx )
+ {
+ const ScRange & rRange = aRangeList[ nRangeIdx ];
+ bMultipleRows = ( bMultipleRows || ( rRange.aStart.Row() != rRange.aEnd.Row() ) );
+ bMultipleCols = ( bMultipleCols || ( rRange.aStart.Col() != rRange.aEnd.Col() ) );
+ SCTAB nMax = static_cast<SCTAB>(maTabs.size());
+ for (const auto& rTab : rMark)
+ {
+ if (rTab >= nMax)
+ break;
+
+ if (maTabs[rTab])
+ maTabs[rTab]->MergeBlockFrame( &rLineOuter, &rLineInner, aFlags,
+ rRange.aStart.Col(), rRange.aStart.Row(),
+ rRange.aEnd.Col(), rRange.aEnd.Row() );
+ }
+ }
+ rLineInner.EnableHor( bMultipleRows );
+ rLineInner.EnableVer( bMultipleCols );
+ }
+ else if( rMark.IsMarked() )
+ {
+ const ScRange& aRange = rMark.GetMarkArea();
+ rLineInner.EnableHor( aRange.aStart.Row() != aRange.aEnd.Row() );
+ rLineInner.EnableVer( aRange.aStart.Col() != aRange.aEnd.Col() );
+ SCTAB nMax = static_cast<SCTAB>(maTabs.size());
+ for (const auto& rTab : rMark)
+ {
+ if (rTab >= nMax)
+ break;
+
+ if (maTabs[rTab])
+ maTabs[rTab]->MergeBlockFrame( &rLineOuter, &rLineInner, aFlags,
+ aRange.aStart.Col(), aRange.aStart.Row(),
+ aRange.aEnd.Col(), aRange.aEnd.Row() );
+ }
+ }
+
+ // Evaluate don't care Status
+
+ rLineInner.SetValid( SvxBoxInfoItemValidFlags::LEFT, ( aFlags.nLeft != SC_LINE_DONTCARE ) );
+ rLineInner.SetValid( SvxBoxInfoItemValidFlags::RIGHT, ( aFlags.nRight != SC_LINE_DONTCARE ) );
+ rLineInner.SetValid( SvxBoxInfoItemValidFlags::TOP, ( aFlags.nTop != SC_LINE_DONTCARE ) );
+ rLineInner.SetValid( SvxBoxInfoItemValidFlags::BOTTOM, ( aFlags.nBottom != SC_LINE_DONTCARE ) );
+ rLineInner.SetValid( SvxBoxInfoItemValidFlags::HORI, ( aFlags.nHori != SC_LINE_DONTCARE ) );
+ rLineInner.SetValid( SvxBoxInfoItemValidFlags::VERT, ( aFlags.nVert != SC_LINE_DONTCARE ) );
+}
+
+static HasAttrFlags OptimizeHasAttrib( HasAttrFlags nMask, const ScDocumentPool* pPool )
+{
+ if ( nMask & HasAttrFlags::Rotate )
+ {
+ // Is attribute used in document?
+ // (as in fillinfo)
+
+ bool bAnyItem = false;
+ for (const SfxPoolItem* pItem : pPool->GetItemSurrogates(ATTR_ROTATE_VALUE))
+ {
+ // 90 or 270 degrees is former SvxOrientationItem - only look for other values
+ // (see ScPatternAttr::GetCellOrientation)
+ Degree100 nAngle = static_cast<const ScRotateValueItem*>(pItem)->GetValue();
+ if ( nAngle && nAngle != 9000_deg100 && nAngle != 27000_deg100 )
+ {
+ bAnyItem = true;
+ break;
+ }
+ }
+ if (!bAnyItem)
+ nMask &= ~HasAttrFlags::Rotate;
+ }
+ return nMask;
+}
+
+bool ScDocument::HasAttrib( SCCOL nCol1, SCROW nRow1, SCTAB nTab1,
+ SCCOL nCol2, SCROW nRow2, SCTAB nTab2, HasAttrFlags nMask ) const
+{
+ nMask = OptimizeHasAttrib( nMask, mxPoolHelper->GetDocPool());
+
+ if (nMask == HasAttrFlags::NONE)
+ return false;
+
+ for (SCTAB i=nTab1; i<=nTab2 && i < static_cast<SCTAB>(maTabs.size()); i++)
+ if (maTabs[i])
+ {
+ if ( nMask & HasAttrFlags::RightOrCenter )
+ {
+ // On a RTL sheet, don't start to look for the default left value
+ // (which is then logically right), instead always assume true.
+ // That way, ScAttrArray::HasAttrib doesn't have to handle RTL sheets.
+
+ if ( IsLayoutRTL(i) )
+ return true;
+ }
+
+ if( maTabs[i]->HasAttrib( nCol1, nRow1, nCol2, nRow2, nMask ))
+ return true;
+ }
+
+ return false;
+}
+
+bool ScDocument::HasAttrib( SCCOL nCol, SCROW nRow, SCTAB nTab, HasAttrFlags nMask, SCROW* nStartRow, SCROW* nEndRow ) const
+{
+ nMask = OptimizeHasAttrib( nMask, mxPoolHelper->GetDocPool());
+
+ if (nMask == HasAttrFlags::NONE || nTab >= static_cast<SCTAB>(maTabs.size()))
+ {
+ if( nStartRow )
+ *nStartRow = 0;
+ if( nEndRow )
+ *nEndRow = MaxRow();
+ return false;
+ }
+
+ if ( nMask & HasAttrFlags::RightOrCenter )
+ {
+ // On a RTL sheet, don't start to look for the default left value
+ // (which is then logically right), instead always assume true.
+ // That way, ScAttrArray::HasAttrib doesn't have to handle RTL sheets.
+
+ if ( IsLayoutRTL(nTab) )
+ {
+ if( nStartRow )
+ *nStartRow = 0;
+ if( nEndRow )
+ *nEndRow = MaxRow();
+ return true;
+ }
+ }
+
+ return maTabs[nTab]->HasAttrib( nCol, nRow, nMask, nStartRow, nEndRow );
+}
+
+bool ScDocument::HasAttrib( const ScRange& rRange, HasAttrFlags nMask ) const
+{
+ return HasAttrib( rRange.aStart.Col(), rRange.aStart.Row(), rRange.aStart.Tab(),
+ rRange.aEnd.Col(), rRange.aEnd.Row(), rRange.aEnd.Tab(),
+ nMask );
+}
+
+void ScDocument::FindMaxRotCol( SCTAB nTab, RowInfo* pRowInfo, SCSIZE nArrCount,
+ SCCOL nX1, SCCOL nX2 ) const
+{
+ if ( ValidTab(nTab) && nTab < static_cast<SCTAB>(maTabs.size()) && maTabs[nTab] )
+ maTabs[nTab]->FindMaxRotCol( pRowInfo, nArrCount, nX1, nX2 );
+ else
+ {
+ OSL_FAIL("FindMaxRotCol: wrong table");
+ }
+}
+
+void ScDocument::GetBorderLines( SCCOL nCol, SCROW nRow, SCTAB nTab,
+ const SvxBorderLine** ppLeft, const SvxBorderLine** ppTop,
+ const SvxBorderLine** ppRight, const SvxBorderLine** ppBottom ) const
+{
+ //TODO: consider page limits for printing !!!!!
+
+ const SvxBoxItem* pThisAttr = GetEffItem( nCol, nRow, nTab, ATTR_BORDER );
+ OSL_ENSURE(pThisAttr,"where is the attribute?");
+
+ const SvxBorderLine* pLeftLine = pThisAttr->GetLeft();
+ const SvxBorderLine* pTopLine = pThisAttr->GetTop();
+ const SvxBorderLine* pRightLine = pThisAttr->GetRight();
+ const SvxBorderLine* pBottomLine = pThisAttr->GetBottom();
+
+ if ( nCol > 0 )
+ {
+ const SvxBorderLine* pOther = GetEffItem( nCol-1, nRow, nTab, ATTR_BORDER )->GetRight();
+ if ( ScHasPriority( pOther, pLeftLine ) )
+ pLeftLine = pOther;
+ }
+ if ( nRow > 0 )
+ {
+ const SvxBorderLine* pOther = GetEffItem( nCol, nRow-1, nTab, ATTR_BORDER )->GetBottom();
+ if ( ScHasPriority( pOther, pTopLine ) )
+ pTopLine = pOther;
+ }
+ if ( nCol < MaxCol() )
+ {
+ const SvxBorderLine* pOther = GetEffItem( nCol+1, nRow, nTab, ATTR_BORDER )->GetLeft();
+ if ( ScHasPriority( pOther, pRightLine ) )
+ pRightLine = pOther;
+ }
+ if ( nRow < MaxRow() )
+ {
+ const SvxBorderLine* pOther = GetEffItem( nCol, nRow+1, nTab, ATTR_BORDER )->GetTop();
+ if ( ScHasPriority( pOther, pBottomLine ) )
+ pBottomLine = pOther;
+ }
+
+ if (ppLeft)
+ *ppLeft = pLeftLine;
+ if (ppTop)
+ *ppTop = pTopLine;
+ if (ppRight)
+ *ppRight = pRightLine;
+ if (ppBottom)
+ *ppBottom = pBottomLine;
+}
+
+bool ScDocument::IsBlockEmpty(SCCOL nStartCol, SCROW nStartRow,
+ SCCOL nEndCol, SCROW nEndRow, SCTAB nTab) const
+{
+ if (ValidTab(nTab) && nTab < static_cast<SCTAB>(maTabs.size()))
+ if (maTabs[nTab])
+ return maTabs[nTab]->IsBlockEmpty( nStartCol, nStartRow, nEndCol, nEndRow );
+
+ OSL_FAIL("wrong table number");
+ return false;
+}
+
+void ScDocument::LockTable(SCTAB nTab)
+{
+ if ( ValidTab(nTab) && nTab < static_cast<SCTAB>(maTabs.size()) && maTabs[nTab] )
+ maTabs[nTab]->LockTable();
+ else
+ {
+ OSL_FAIL("wrong table number");
+ }
+}
+
+void ScDocument::UnlockTable(SCTAB nTab)
+{
+ if ( ValidTab(nTab) && nTab < static_cast<SCTAB>(maTabs.size()) && maTabs[nTab] )
+ maTabs[nTab]->UnlockTable();
+ else
+ {
+ OSL_FAIL("wrong table number");
+ }
+}
+
+bool ScDocument::IsBlockEditable( SCTAB nTab, SCCOL nStartCol, SCROW nStartRow,
+ SCCOL nEndCol, SCROW nEndRow,
+ bool* pOnlyNotBecauseOfMatrix /* = NULL */,
+ bool bNoMatrixAtAll ) const
+{
+ // import into read-only document is possible
+ if (!bImportingXML && !mbChangeReadOnlyEnabled && mpShell && mpShell->IsReadOnly())
+ {
+ if ( pOnlyNotBecauseOfMatrix )
+ *pOnlyNotBecauseOfMatrix = false;
+ return false;
+ }
+
+ if (ValidTab(nTab) && nTab < static_cast<SCTAB>(maTabs.size()))
+ if (maTabs[nTab])
+ return maTabs[nTab]->IsBlockEditable( nStartCol, nStartRow, nEndCol,
+ nEndRow, pOnlyNotBecauseOfMatrix, bNoMatrixAtAll );
+
+ OSL_FAIL("wrong table number");
+ if ( pOnlyNotBecauseOfMatrix )
+ *pOnlyNotBecauseOfMatrix = false;
+ return false;
+}
+
+bool ScDocument::IsSelectionEditable( const ScMarkData& rMark,
+ bool* pOnlyNotBecauseOfMatrix /* = NULL */ ) const
+{
+ // import into read-only document is possible
+ if ( !bImportingXML && !mbChangeReadOnlyEnabled && mpShell && mpShell->IsReadOnly() )
+ {
+ if ( pOnlyNotBecauseOfMatrix )
+ *pOnlyNotBecauseOfMatrix = false;
+ return false;
+ }
+
+ const ScRange& aRange = rMark.GetMarkArea();
+
+ bool bOk = true;
+ bool bMatrix = ( pOnlyNotBecauseOfMatrix != nullptr );
+ SCTAB nMax = static_cast<SCTAB>(maTabs.size());
+ for (const auto& rTab : rMark)
+ {
+ if (rTab >= nMax)
+ break;
+
+ if ( maTabs[rTab] )
+ {
+ if (rMark.IsMarked())
+ {
+ if ( !maTabs[rTab]->IsBlockEditable( aRange.aStart.Col(),
+ aRange.aStart.Row(), aRange.aEnd.Col(),
+ aRange.aEnd.Row(), pOnlyNotBecauseOfMatrix ) )
+ {
+ bOk = false;
+ if ( pOnlyNotBecauseOfMatrix )
+ bMatrix = *pOnlyNotBecauseOfMatrix;
+ }
+ }
+ if (rMark.IsMultiMarked())
+ {
+ if ( !maTabs[rTab]->IsSelectionEditable( rMark, pOnlyNotBecauseOfMatrix ) )
+ {
+ bOk = false;
+ if ( pOnlyNotBecauseOfMatrix )
+ bMatrix = *pOnlyNotBecauseOfMatrix;
+ }
+ }
+ }
+
+ if (!bOk && !bMatrix)
+ break;
+ }
+
+ if ( pOnlyNotBecauseOfMatrix )
+ *pOnlyNotBecauseOfMatrix = ( !bOk && bMatrix );
+
+ return bOk;
+}
+
+bool ScDocument::HasSelectedBlockMatrixFragment( SCCOL nStartCol, SCROW nStartRow,
+ SCCOL nEndCol, SCROW nEndRow,
+ const ScMarkData& rMark ) const
+{
+ bool bOk = true;
+ SCTAB nMax = static_cast<SCTAB>(maTabs.size());
+ for (const auto& rTab : rMark)
+ {
+ if (rTab >= nMax)
+ break;
+
+ if (maTabs[rTab] && maTabs[rTab]->HasBlockMatrixFragment( nStartCol, nStartRow, nEndCol, nEndRow ))
+ bOk = false;
+
+ if (!bOk)
+ break;
+ }
+
+ return !bOk;
+}
+
+bool ScDocument::GetMatrixFormulaRange( const ScAddress& rCellPos, ScRange& rMatrix )
+{
+ // if rCell is part of a matrix formula, return its complete range
+
+ ScFormulaCell* pFCell = GetFormulaCell(rCellPos);
+ if (!pFCell)
+ // not a formula cell. Bail out.
+ return false;
+
+ ScAddress aOrigin = rCellPos;
+ if (!pFCell->GetMatrixOrigin(*this, aOrigin))
+ // Failed to get the address of the matrix origin.
+ return false;
+
+ if (aOrigin != rCellPos)
+ {
+ pFCell = GetFormulaCell(aOrigin);
+ if (!pFCell)
+ // The matrix origin cell is not a formula cell !? Something is up...
+ return false;
+ }
+
+ SCCOL nSizeX;
+ SCROW nSizeY;
+ pFCell->GetMatColsRows(nSizeX, nSizeY);
+ if (nSizeX <= 0 || nSizeY <= 0)
+ {
+ // GetMatrixEdge computes also dimensions of the matrix
+ // if not already done (may occur if document is loaded
+ // from old file format).
+ // Needs an "invalid" initialized address.
+ aOrigin.SetInvalid();
+ pFCell->GetMatrixEdge(*this, aOrigin);
+ pFCell->GetMatColsRows(nSizeX, nSizeY);
+ }
+
+ if (nSizeX <= 0 || nSizeY <= 0)
+ // Matrix size is still invalid. Give up.
+ return false;
+
+ ScAddress aEnd( aOrigin.Col() + nSizeX - 1,
+ aOrigin.Row() + nSizeY - 1,
+ aOrigin.Tab() );
+
+ rMatrix.aStart = aOrigin;
+ rMatrix.aEnd = aEnd;
+
+ return true;
+}
+
+void ScDocument::ExtendOverlapped( SCCOL& rStartCol, SCROW& rStartRow,
+ SCCOL nEndCol, SCROW nEndRow, SCTAB nTab ) const
+{
+ if ( ValidColRow(rStartCol,rStartRow) && ValidColRow(nEndCol,nEndRow) && ValidTab(nTab) )
+ {
+ if (nTab < static_cast<SCTAB>(maTabs.size()) && maTabs[nTab])
+ {
+ SCCOL nCol;
+ SCCOL nOldCol = rStartCol;
+ SCROW nOldRow = rStartRow;
+ for (nCol=nOldCol; nCol<=nEndCol; nCol++)
+ while (GetAttr(nCol,rStartRow,nTab,ATTR_MERGE_FLAG)->IsVerOverlapped())
+ --rStartRow;
+
+ //TODO: pass on ?
+
+ const ScAttrArray& pAttrArray = maTabs[nTab]->ColumnData(nOldCol).AttrArray();
+ SCSIZE nIndex;
+ if ( pAttrArray.Count() )
+ pAttrArray.Search( nOldRow, nIndex );
+ else
+ nIndex = 0;
+ SCROW nAttrPos = nOldRow;
+ while (nAttrPos<=nEndRow)
+ {
+ OSL_ENSURE( nIndex < pAttrArray.Count(), "Wrong index in AttrArray" );
+
+ bool bHorOverlapped;
+ if ( pAttrArray.Count() )
+ bHorOverlapped = pAttrArray.mvData[nIndex].pPattern->GetItem(ATTR_MERGE_FLAG).IsHorOverlapped();
+ else
+ bHorOverlapped = GetDefPattern()->GetItem(ATTR_MERGE_FLAG).IsHorOverlapped();
+ if ( bHorOverlapped )
+ {
+ SCROW nEndRowSeg = (pAttrArray.Count()) ? pAttrArray.mvData[nIndex].nEndRow : MaxRow();
+ SCROW nLoopEndRow = std::min( nEndRow, nEndRowSeg );
+ for (SCROW nAttrRow = nAttrPos; nAttrRow <= nLoopEndRow; nAttrRow++)
+ {
+ SCCOL nTempCol = nOldCol;
+ do
+ --nTempCol;
+ while (GetAttr(nTempCol,nAttrRow,nTab,ATTR_MERGE_FLAG)->IsHorOverlapped());
+ if (nTempCol < rStartCol)
+ rStartCol = nTempCol;
+ }
+ }
+ if ( pAttrArray.Count() )
+ {
+ nAttrPos = pAttrArray.mvData[nIndex].nEndRow + 1;
+ ++nIndex;
+ }
+ else
+ nAttrPos = MaxRow() + 1;
+ }
+ }
+ }
+ else
+ {
+ OSL_FAIL("ExtendOverlapped: invalid range");
+ }
+}
+
+void ScDocument::ExtendMergeSel( SCCOL nStartCol, SCROW nStartRow,
+ SCCOL& rEndCol, SCROW& rEndRow,
+ const ScMarkData& rMark, bool bRefresh )
+{
+ // use all selected sheets from rMark
+
+ SCCOL nOldEndCol = rEndCol;
+ SCROW nOldEndRow = rEndRow;
+
+ SCTAB nMax = static_cast<SCTAB>(maTabs.size());
+ for (const auto& rTab : rMark)
+ {
+ if (rTab >= nMax)
+ break;
+
+ if ( maTabs[rTab] )
+ {
+ SCCOL nThisEndCol = nOldEndCol;
+ SCROW nThisEndRow = nOldEndRow;
+ ExtendMerge( nStartCol, nStartRow, nThisEndCol, nThisEndRow, rTab, bRefresh );
+ if ( nThisEndCol > rEndCol )
+ rEndCol = nThisEndCol;
+ if ( nThisEndRow > rEndRow )
+ rEndRow = nThisEndRow;
+ }
+ }
+}
+
+bool ScDocument::ExtendMerge( SCCOL nStartCol, SCROW nStartRow,
+ SCCOL& rEndCol, SCROW& rEndRow,
+ SCTAB nTab, bool bRefresh )
+{
+ bool bFound = false;
+ if ( ValidColRow(nStartCol,nStartRow) && ValidColRow(rEndCol,rEndRow) && ValidTab(nTab) )
+ {
+ if (nTab < static_cast<SCTAB>(maTabs.size()) && maTabs[nTab])
+ bFound = maTabs[nTab]->ExtendMerge( nStartCol, nStartRow, rEndCol, rEndRow, bRefresh );
+
+ if (bRefresh)
+ RefreshAutoFilter( nStartCol, nStartRow, rEndCol, rEndRow, nTab );
+ }
+ else
+ {
+ OSL_FAIL("ExtendMerge: invalid range");
+ }
+
+ return bFound;
+}
+
+bool ScDocument::ExtendMerge( ScRange& rRange, bool bRefresh )
+{
+ bool bFound = false;
+ SCTAB nStartTab = rRange.aStart.Tab();
+ SCTAB nEndTab = rRange.aEnd.Tab();
+ SCCOL nEndCol = rRange.aEnd.Col();
+ SCROW nEndRow = rRange.aEnd.Row();
+
+ PutInOrder( nStartTab, nEndTab );
+ for (SCTAB nTab = nStartTab; nTab <= nEndTab && nTab < static_cast<SCTAB>(maTabs.size()); nTab++ )
+ {
+ SCCOL nExtendCol = rRange.aEnd.Col();
+ SCROW nExtendRow = rRange.aEnd.Row();
+ if (ExtendMerge( rRange.aStart.Col(), rRange.aStart.Row(),
+ nExtendCol, nExtendRow,
+ nTab, bRefresh ) )
+ {
+ bFound = true;
+ if (nExtendCol > nEndCol) nEndCol = nExtendCol;
+ if (nExtendRow > nEndRow) nEndRow = nExtendRow;
+ }
+ }
+
+ rRange.aEnd.SetCol(nEndCol);
+ rRange.aEnd.SetRow(nEndRow);
+
+ return bFound;
+}
+
+void ScDocument::ExtendTotalMerge( ScRange& rRange ) const
+{
+ // Extend range to merged cells without including any new non-overlapped cells
+ ScRange aExt = rRange;
+ // ExtendMerge() is non-const, but called without refresh.
+ if (!const_cast<ScDocument*>(this)->ExtendMerge( aExt ))
+ return;
+
+ if ( aExt.aEnd.Row() > rRange.aEnd.Row() )
+ {
+ ScRange aTest = aExt;
+ aTest.aStart.SetRow( rRange.aEnd.Row() + 1 );
+ if ( HasAttrib( aTest, HasAttrFlags::NotOverlapped ) )
+ aExt.aEnd.SetRow(rRange.aEnd.Row());
+ }
+ if ( aExt.aEnd.Col() > rRange.aEnd.Col() )
+ {
+ ScRange aTest = aExt;
+ aTest.aStart.SetCol( rRange.aEnd.Col() + 1 );
+ if ( HasAttrib( aTest, HasAttrFlags::NotOverlapped ) )
+ aExt.aEnd.SetCol(rRange.aEnd.Col());
+ }
+
+ rRange = aExt;
+}
+
+void ScDocument::ExtendOverlapped( ScRange& rRange ) const
+{
+ SCTAB nStartTab = rRange.aStart.Tab();
+ SCTAB nEndTab = rRange.aEnd.Tab();
+ SCCOL nStartCol = rRange.aStart.Col();
+ SCROW nStartRow = rRange.aStart.Row();
+
+ PutInOrder( nStartTab, nEndTab );
+ for (SCTAB nTab = nStartTab; nTab <= nEndTab && nTab < static_cast<SCTAB>(maTabs.size()); nTab++ )
+ {
+ SCCOL nExtendCol = rRange.aStart.Col();
+ SCROW nExtendRow = rRange.aStart.Row();
+ ExtendOverlapped( nExtendCol, nExtendRow,
+ rRange.aEnd.Col(), rRange.aEnd.Row(), nTab );
+ if (nExtendCol < nStartCol)
+ {
+ nStartCol = nExtendCol;
+ }
+ if (nExtendRow < nStartRow)
+ {
+ nStartRow = nExtendRow;
+ }
+ }
+
+ rRange.aStart.SetCol(nStartCol);
+ rRange.aStart.SetRow(nStartRow);
+}
+
+bool ScDocument::RefreshAutoFilter( SCCOL nStartCol, SCROW nStartRow,
+ SCCOL nEndCol, SCROW nEndRow, SCTAB nTab )
+{
+ SCTAB nDBTab;
+ SCCOL nDBStartCol;
+ SCROW nDBStartRow;
+ SCCOL nDBEndCol;
+ SCROW nDBEndRow;
+
+ // Delete Autofilter
+
+ bool bChange = RemoveFlagsTab( nStartCol,nStartRow, nEndCol,nEndRow, nTab, ScMF::Auto );
+
+ // Set Autofilter
+
+ const ScDBData* pData = nullptr;
+ ScDBCollection::NamedDBs& rDBs = pDBCollection->getNamedDBs();
+ for (const auto& rxDB : rDBs)
+ {
+ if (rxDB->HasAutoFilter())
+ {
+ rxDB->GetArea(nDBTab, nDBStartCol,nDBStartRow, nDBEndCol,nDBEndRow);
+ if ( nDBTab==nTab && nDBStartRow<=nEndRow && nDBEndRow>=nStartRow &&
+ nDBStartCol<=nEndCol && nDBEndCol>=nStartCol )
+ {
+ if (ApplyFlagsTab( nDBStartCol,nDBStartRow, nDBEndCol,nDBStartRow,
+ nDBTab, ScMF::Auto ))
+ bChange = true;
+ }
+ }
+ }
+ if (nTab < static_cast<SCTAB>(maTabs.size()) && maTabs[nTab])
+ pData = maTabs[nTab]->GetAnonymousDBData();
+ else
+ pData=nullptr;
+ if (pData && pData->HasAutoFilter())
+ {
+ pData->GetArea( nDBTab, nDBStartCol,nDBStartRow, nDBEndCol,nDBEndRow );
+ if ( nDBTab==nTab && nDBStartRow<=nEndRow && nDBEndRow>=nStartRow &&
+ nDBStartCol<=nEndCol && nDBEndCol>=nStartCol )
+ {
+ if (ApplyFlagsTab( nDBStartCol,nDBStartRow, nDBEndCol,nDBStartRow,
+ nDBTab, ScMF::Auto ))
+ bChange = true;
+ }
+ }
+ return bChange;
+}
+
+void ScDocument::SkipOverlapped( SCCOL& rCol, SCROW& rRow, SCTAB nTab ) const
+{
+ while (IsHorOverlapped(rCol, rRow, nTab))
+ --rCol;
+ while (IsVerOverlapped(rCol, rRow, nTab))
+ --rRow;
+}
+
+bool ScDocument::IsHorOverlapped( SCCOL nCol, SCROW nRow, SCTAB nTab ) const
+{
+ const ScMergeFlagAttr* pAttr = GetAttr( nCol, nRow, nTab, ATTR_MERGE_FLAG );
+ if (pAttr)
+ return pAttr->IsHorOverlapped();
+ else
+ {
+ OSL_FAIL("Overlapped: Attr==0");
+ return false;
+ }
+}
+
+bool ScDocument::IsVerOverlapped( SCCOL nCol, SCROW nRow, SCTAB nTab, SCROW* nStartRow, SCROW* nEndRow ) const
+{
+ SCROW dummy;
+ const ScMergeFlagAttr* pAttr = GetAttr( nCol, nRow, nTab, ATTR_MERGE_FLAG,
+ nStartRow ? *nStartRow : dummy, nEndRow ? *nEndRow : dummy );
+ if (pAttr)
+ return pAttr->IsVerOverlapped();
+ else
+ {
+ OSL_FAIL("Overlapped: Attr==0");
+ return false;
+ }
+}
+
+void ScDocument::ApplySelectionFrame( const ScMarkData& rMark,
+ const SvxBoxItem& rLineOuter,
+ const SvxBoxInfoItem* pLineInner )
+{
+ ScRangeList aRangeList;
+ rMark.FillRangeListWithMarks( &aRangeList, false );
+ size_t nRangeCount = aRangeList.size();
+ SCTAB nMax = static_cast<SCTAB>(maTabs.size());
+ for (const auto& rTab : rMark)
+ {
+ if (rTab >= nMax)
+ break;
+
+ if (maTabs[rTab])
+ {
+ for ( size_t j=0; j < nRangeCount; j++ )
+ {
+ const ScRange & rRange = aRangeList[ j ];
+ maTabs[rTab]->ApplyBlockFrame( rLineOuter, pLineInner,
+ rRange.aStart.Col(), rRange.aStart.Row(),
+ rRange.aEnd.Col(), rRange.aEnd.Row() );
+ }
+ }
+ }
+ if (!rLineOuter.IsRemoveAdjacentCellBorder())
+ return;
+
+ SvxBoxItem aTmp0(rLineOuter);
+ aTmp0.SetLine( nullptr, SvxBoxItemLine::TOP );
+ aTmp0.SetLine( nullptr, SvxBoxItemLine::BOTTOM );
+ aTmp0.SetLine( nullptr, SvxBoxItemLine::LEFT );
+ aTmp0.SetLine( nullptr, SvxBoxItemLine::RIGHT );
+ SvxBoxItem aLeft( aTmp0 );
+ SvxBoxItem aRight( aTmp0 );
+ SvxBoxItem aTop( aTmp0 );
+ SvxBoxItem aBottom( aTmp0 );
+
+ SvxBoxInfoItem aTmp1( *pLineInner );
+ aTmp1.SetTable( false );
+ aTmp1.SetLine( nullptr, SvxBoxInfoItemLine::HORI );
+ aTmp1.SetLine( nullptr, SvxBoxInfoItemLine::VERT );
+ aTmp1.SetValid( SvxBoxInfoItemValidFlags::ALL, false );
+ aTmp1.SetValid( SvxBoxInfoItemValidFlags::DISTANCE );
+ SvxBoxInfoItem aLeftInfo( aTmp1 );
+ SvxBoxInfoItem aRightInfo( aTmp1 );
+ SvxBoxInfoItem aTopInfo( aTmp1 );
+ SvxBoxInfoItem aBottomInfo( aTmp1 );
+
+ if (pLineInner->IsValid( SvxBoxInfoItemValidFlags::TOP ) && !rLineOuter.GetTop())
+ aTopInfo.SetValid( SvxBoxInfoItemValidFlags::BOTTOM );
+
+ if (pLineInner->IsValid( SvxBoxInfoItemValidFlags::BOTTOM ) && !rLineOuter.GetBottom())
+ aBottomInfo.SetValid( SvxBoxInfoItemValidFlags::TOP );
+
+ if (pLineInner->IsValid( SvxBoxInfoItemValidFlags::LEFT ) && !rLineOuter.GetLeft())
+ aLeftInfo.SetValid( SvxBoxInfoItemValidFlags::RIGHT );
+
+ if (pLineInner->IsValid( SvxBoxInfoItemValidFlags::RIGHT ) && !rLineOuter.GetRight())
+ aRightInfo.SetValid( SvxBoxInfoItemValidFlags::LEFT );
+
+ const ScRangeList& rRangeListTopEnvelope = rMark.GetTopEnvelope();
+ const ScRangeList& rRangeListBottomEnvelope = rMark.GetBottomEnvelope();
+ const ScRangeList& rRangeListLeftEnvelope = rMark.GetLeftEnvelope();
+ const ScRangeList& rRangeListRightEnvelope = rMark.GetRightEnvelope();
+
+ for (const auto& rTab : rMark)
+ {
+ if (rTab >= nMax)
+ break;
+
+ if ( maTabs[rTab] )
+ {
+ size_t nEnvelopeRangeCount = rRangeListTopEnvelope.size();
+ for ( size_t j=0; j < nEnvelopeRangeCount; j++ )
+ {
+ const ScRange & rRange = rRangeListTopEnvelope[ j ];
+ maTabs[rTab]->ApplyBlockFrame( aTop, &aTopInfo,
+ rRange.aStart.Col(), rRange.aStart.Row(),
+ rRange.aEnd.Col(), rRange.aEnd.Row() );
+ }
+ nEnvelopeRangeCount = rRangeListBottomEnvelope.size();
+ for ( size_t j=0; j < nEnvelopeRangeCount; j++ )
+ {
+ const ScRange & rRange = rRangeListBottomEnvelope[ j ];
+ maTabs[rTab]->ApplyBlockFrame( aBottom, &aBottomInfo,
+ rRange.aStart.Col(), rRange.aStart.Row(),
+ rRange.aEnd.Col(), rRange.aEnd.Row() );
+ }
+ nEnvelopeRangeCount = rRangeListLeftEnvelope.size();
+ for ( size_t j=0; j < nEnvelopeRangeCount; j++ )
+ {
+ const ScRange & rRange = rRangeListLeftEnvelope[ j ];
+ maTabs[rTab]->ApplyBlockFrame( aLeft, &aLeftInfo,
+ rRange.aStart.Col(), rRange.aStart.Row(),
+ rRange.aEnd.Col(), rRange.aEnd.Row() );
+ }
+ nEnvelopeRangeCount = rRangeListRightEnvelope.size();
+ for ( size_t j=0; j < nEnvelopeRangeCount; j++ )
+ {
+ const ScRange & rRange = rRangeListRightEnvelope[ j ];
+ maTabs[rTab]->ApplyBlockFrame( aRight, &aRightInfo,
+ rRange.aStart.Col(), rRange.aStart.Row(),
+ rRange.aEnd.Col(), rRange.aEnd.Row() );
+ }
+ }
+ }
+}
+
+void ScDocument::ApplyFrameAreaTab(const ScRange& rRange,
+ const SvxBoxItem& rLineOuter,
+ const SvxBoxInfoItem& rLineInner)
+{
+ SCTAB nStartTab = rRange.aStart.Tab();
+ SCTAB nEndTab = rRange.aStart.Tab();
+ for (SCTAB nTab=nStartTab; nTab<=nEndTab && nTab < static_cast<SCTAB>(maTabs.size()); nTab++)
+ if (maTabs[nTab])
+ maTabs[nTab]->ApplyBlockFrame(rLineOuter, &rLineInner,
+ rRange.aStart.Col(), rRange.aStart.Row(),
+ rRange.aEnd.Col(), rRange.aEnd.Row());
+}
+
+void ScDocument::ApplySelectionPattern( const ScPatternAttr& rAttr, const ScMarkData& rMark, ScEditDataArray* pDataArray, bool* const pIsChanged )
+{
+ const SfxItemSet* pSet = &rAttr.GetItemSet();
+ bool bSet = false;
+ sal_uInt16 i;
+ for (i=ATTR_PATTERN_START; i<=ATTR_PATTERN_END && !bSet; i++)
+ if (pSet->GetItemState(i) == SfxItemState::SET)
+ bSet = true;
+
+ if (!bSet)
+ return;
+
+ // ApplySelectionCache needs multi mark
+ if ( rMark.IsMarked() && !rMark.IsMultiMarked() )
+ {
+ const ScRange& aRange = rMark.GetMarkArea();
+ ApplyPatternArea( aRange.aStart.Col(), aRange.aStart.Row(),
+ aRange.aEnd.Col(), aRange.aEnd.Row(), rMark, rAttr, pDataArray, pIsChanged );
+ }
+ else
+ {
+ SfxItemPoolCache aCache( mxPoolHelper->GetDocPool(), pSet );
+ SCTAB nMax = static_cast<SCTAB>(maTabs.size());
+ for (const auto& rTab : rMark)
+ {
+ if (rTab >= nMax)
+ break;
+ if (maTabs[rTab])
+ maTabs[rTab]->ApplySelectionCache( &aCache, rMark, pDataArray, pIsChanged );
+ }
+ }
+}
+
+void ScDocument::ChangeSelectionIndent( bool bIncrement, const ScMarkData& rMark )
+{
+ SCTAB nMax = static_cast<SCTAB>(maTabs.size());
+ for (const auto& rTab : rMark)
+ {
+ if (rTab >= nMax)
+ break;
+ if (maTabs[rTab])
+ maTabs[rTab]->ChangeSelectionIndent( bIncrement, rMark );
+ }
+}
+
+void ScDocument::ClearSelectionItems( const sal_uInt16* pWhich, const ScMarkData& rMark )
+{
+ SCTAB nMax = static_cast<SCTAB>(maTabs.size());
+ for (const auto& rTab : rMark)
+ {
+ if (rTab >= nMax)
+ break;
+ if (maTabs[rTab])
+ maTabs[rTab]->ClearSelectionItems( pWhich, rMark );
+ }
+}
+
+void ScDocument::DeleteSelection( InsertDeleteFlags nDelFlag, const ScMarkData& rMark, bool bBroadcast )
+{
+ sc::AutoCalcSwitch aACSwitch(*this, false);
+
+ std::vector<ScAddress> aGroupPos;
+ // Destroy and reconstruct listeners only if content is affected.
+ bool bDelContent = ((nDelFlag & ~InsertDeleteFlags::CONTENTS) != nDelFlag);
+ if (bDelContent)
+ {
+ // Record the positions of top and/or bottom formula groups that
+ // intersect the area borders.
+ sc::EndListeningContext aCxt(*this);
+ ScRangeList aRangeList;
+ rMark.FillRangeListWithMarks( &aRangeList, false);
+ for (size_t i = 0; i < aRangeList.size(); ++i)
+ {
+ const ScRange & rRange = aRangeList[i];
+ EndListeningIntersectedGroups( aCxt, rRange, &aGroupPos);
+ }
+ aCxt.purgeEmptyBroadcasters();
+ }
+
+ SCTAB nMax = static_cast<SCTAB>(maTabs.size());
+ for (const auto& rTab : rMark)
+ {
+ if (rTab >= nMax)
+ break;
+ if (maTabs[rTab])
+ maTabs[rTab]->DeleteSelection(nDelFlag, rMark, bBroadcast);
+ }
+
+ if (!bDelContent)
+ return;
+
+ // Re-start listeners on those top bottom groups that have been split.
+ SetNeedsListeningGroups(aGroupPos);
+ StartNeededListeners();
+
+ // If formula groups were split their listeners were destroyed and may
+ // need to be notified now that they're restored,
+ // ScTable::DeleteSelection() couldn't do that.
+ if (aGroupPos.empty())
+ return;
+
+ ScRangeList aRangeList;
+ rMark.FillRangeListWithMarks( &aRangeList, false);
+ for (size_t i = 0; i < aRangeList.size(); ++i)
+ {
+ SetDirty( aRangeList[i], true);
+ }
+ //Notify listeners on top and bottom of the group that has been split
+ for (size_t i = 0; i < aGroupPos.size(); ++i) {
+ ScFormulaCell *pFormulaCell = GetFormulaCell(aGroupPos[i]);
+ if (pFormulaCell)
+ pFormulaCell->SetDirty(true);
+ }
+}
+
+void ScDocument::DeleteSelectionTab(
+ SCTAB nTab, InsertDeleteFlags nDelFlag, const ScMarkData& rMark )
+{
+ if (ValidTab(nTab) && nTab < static_cast<SCTAB>(maTabs.size()) && maTabs[nTab])
+ {
+ sc::AutoCalcSwitch aACSwitch(*this, false);
+
+ std::vector<ScAddress> aGroupPos;
+ // Destroy and reconstruct listeners only if content is affected.
+ bool bDelContent = ((nDelFlag & ~InsertDeleteFlags::CONTENTS) != nDelFlag);
+ if (bDelContent)
+ {
+ // Record the positions of top and/or bottom formula groups that
+ // intersect the area borders.
+ sc::EndListeningContext aCxt(*this);
+ ScRangeList aRangeList;
+ rMark.FillRangeListWithMarks( &aRangeList, false);
+ for (size_t i = 0; i < aRangeList.size(); ++i)
+ {
+ const ScRange & rRange = aRangeList[i];
+ if (rRange.aStart.Tab() <= nTab && nTab <= rRange.aEnd.Tab())
+ {
+ ScRange aRange( rRange);
+ aRange.aStart.SetTab( nTab);
+ aRange.aEnd.SetTab( nTab);
+ EndListeningIntersectedGroups( aCxt, aRange, &aGroupPos);
+ }
+ }
+ aCxt.purgeEmptyBroadcasters();
+ }
+
+ maTabs[nTab]->DeleteSelection(nDelFlag, rMark);
+
+ if (bDelContent)
+ {
+ // Re-start listeners on those top bottom groups that have been split.
+ SetNeedsListeningGroups(aGroupPos);
+ StartNeededListeners();
+
+ // If formula groups were split their listeners were destroyed and may
+ // need to be notified now that they're restored,
+ // ScTable::DeleteSelection() couldn't do that.
+ if (!aGroupPos.empty())
+ {
+ ScRangeList aRangeList;
+ rMark.FillRangeListWithMarks( &aRangeList, false);
+ for (size_t i = 0; i < aRangeList.size(); ++i)
+ {
+ const ScRange & rRange = aRangeList[i];
+ if (rRange.aStart.Tab() <= nTab && nTab <= rRange.aEnd.Tab())
+ {
+ ScRange aRange( rRange);
+ aRange.aStart.SetTab( nTab);
+ aRange.aEnd.SetTab( nTab);
+ SetDirty( aRange, true);
+ }
+ }
+ }
+ }
+ }
+ else
+ {
+ OSL_FAIL("wrong table");
+ }
+}
+
+ScPatternAttr* ScDocument::GetDefPattern() const
+{
+ return const_cast<ScPatternAttr*>(&mxPoolHelper->GetDocPool()->GetDefaultItem(ATTR_PATTERN));
+}
+
+ScDocumentPool* ScDocument::GetPool()
+{
+ return mxPoolHelper ? mxPoolHelper->GetDocPool() : nullptr;
+}
+
+ScStyleSheetPool* ScDocument::GetStyleSheetPool() const
+{
+ return mxPoolHelper ? mxPoolHelper->GetStylePool() : nullptr;
+}
+
+bool ScDocument::IsEmptyData(SCCOL nStartCol, SCROW nStartRow, SCCOL nEndCol, SCROW nEndRow, SCTAB nTab) const
+{
+ if (ValidTab(nTab) && nTab < static_cast<SCTAB>(maTabs.size()) && maTabs[nTab])
+ return maTabs[nTab]->IsEmptyData(nStartCol, nStartRow, nEndCol, nEndRow);
+ return true;
+}
+
+SCSIZE ScDocument::GetEmptyLinesInBlock( SCCOL nStartCol, SCROW nStartRow, SCTAB nStartTab,
+ SCCOL nEndCol, SCROW nEndRow, SCTAB nEndTab, ScDirection eDir )
+{
+ PutInOrder(nStartCol, nEndCol);
+ PutInOrder(nStartRow, nEndRow);
+ PutInOrder(nStartTab, nEndTab);
+ if (ValidTab(nStartTab) && nStartTab < static_cast<SCTAB>(maTabs.size()))
+ {
+ if (maTabs[nStartTab])
+ return maTabs[nStartTab]->GetEmptyLinesInBlock(nStartCol, nStartRow, nEndCol, nEndRow, eDir);
+ else
+ return 0;
+ }
+ else
+ return 0;
+}
+
+void ScDocument::FindAreaPos( SCCOL& rCol, SCROW& rRow, SCTAB nTab, ScMoveDirection eDirection ) const
+{
+ if (ValidTab(nTab) && nTab < static_cast<SCTAB>(maTabs.size()) && maTabs[nTab])
+ maTabs[nTab]->FindAreaPos( rCol, rRow, eDirection );
+}
+
+void ScDocument::GetNextPos( SCCOL& rCol, SCROW& rRow, SCTAB nTab, SCCOL nMovX, SCROW nMovY,
+ bool bMarked, bool bUnprotected, const ScMarkData& rMark, SCCOL nTabStartCol ) const
+{
+ OSL_ENSURE( !nMovX || !nMovY, "GetNextPos: only X or Y" );
+
+ ScMarkData aCopyMark = rMark;
+ aCopyMark.SetMarking(false);
+ aCopyMark.MarkToMulti();
+
+ if (ValidTab(nTab) && nTab < static_cast<SCTAB>(maTabs.size()) && maTabs[nTab])
+ maTabs[nTab]->GetNextPos( rCol, rRow, nMovX, nMovY, bMarked, bUnprotected, aCopyMark, nTabStartCol );
+}
+
+// Data operations
+
+void ScDocument::UpdStlShtPtrsFrmNms()
+{
+ ScDocumentPool* pPool = mxPoolHelper->GetDocPool();
+
+ for (const SfxPoolItem* pItem : pPool->GetItemSurrogates(ATTR_PATTERN))
+ {
+ auto pPattern = const_cast<ScPatternAttr*>(dynamic_cast<const ScPatternAttr*>(pItem));
+ if (pPattern)
+ pPattern->UpdateStyleSheet(*this);
+ }
+ const_cast<ScPatternAttr&>(pPool->GetDefaultItem(ATTR_PATTERN)).UpdateStyleSheet(*this);
+}
+
+void ScDocument::StylesToNames()
+{
+ ScDocumentPool* pPool = mxPoolHelper->GetDocPool();
+
+ for (const SfxPoolItem* pItem : pPool->GetItemSurrogates(ATTR_PATTERN))
+ {
+ auto pPattern = const_cast<ScPatternAttr*>(dynamic_cast<const ScPatternAttr*>(pItem));
+ if (pPattern)
+ pPattern->StyleToName();
+ }
+ const_cast<ScPatternAttr&>(pPool->GetDefaultItem(ATTR_PATTERN)).StyleToName();
+}
+
+sal_uInt64 ScDocument::GetCellCount() const
+{
+ sal_uInt64 nCellCount = 0;
+
+ for (const auto& a : maTabs)
+ {
+ if (a)
+ nCellCount += a->GetCellCount();
+ }
+
+ return nCellCount;
+}
+
+sal_uInt64 ScDocument::GetFormulaGroupCount() const
+{
+ sal_uInt64 nFormulaGroupCount = 0;
+
+ ScFormulaGroupIterator aIter( *const_cast<ScDocument*>(this) );
+ for ( sc::FormulaGroupEntry* ptr = aIter.first(); ptr; ptr = aIter.next())
+ {
+ nFormulaGroupCount++;
+ }
+
+ return nFormulaGroupCount;
+}
+
+sal_uInt64 ScDocument::GetCodeCount() const
+{
+ sal_uInt64 nCodeCount = 0;
+
+ for (const auto& a : maTabs)
+ {
+ if (a)
+ nCodeCount += a->GetCodeCount();
+ }
+
+ return nCodeCount;
+}
+
+void ScDocument::PageStyleModified( SCTAB nTab, const OUString& rNewName )
+{
+ if ( ValidTab(nTab) && nTab < static_cast<SCTAB>(maTabs.size()) && maTabs[nTab] )
+ maTabs[nTab]->PageStyleModified( rNewName );
+}
+
+void ScDocument::SetPageStyle( SCTAB nTab, const OUString& rName )
+{
+ if ( ValidTab(nTab) && nTab < static_cast<SCTAB>(maTabs.size()) && maTabs[nTab] )
+ maTabs[nTab]->SetPageStyle( rName );
+}
+
+OUString ScDocument::GetPageStyle( SCTAB nTab ) const
+{
+ if ( ValidTab(nTab) && nTab < static_cast<SCTAB>(maTabs.size()) && maTabs[nTab] )
+ return maTabs[nTab]->GetPageStyle();
+
+ return OUString();
+}
+
+void ScDocument::SetPageSize( SCTAB nTab, const Size& rSize )
+{
+ if ( ValidTab(nTab) && nTab < static_cast<SCTAB>(maTabs.size()) && maTabs[nTab] )
+ maTabs[nTab]->SetPageSize( rSize );
+}
+
+Size ScDocument::GetPageSize( SCTAB nTab ) const
+{
+ if ( ValidTab(nTab) && nTab < static_cast<SCTAB>(maTabs.size()) && maTabs[nTab] )
+ return maTabs[nTab]->GetPageSize();
+
+ OSL_FAIL("invalid tab");
+ return Size();
+}
+
+void ScDocument::SetRepeatArea( SCTAB nTab, SCCOL nStartCol, SCCOL nEndCol, SCROW nStartRow, SCROW nEndRow )
+{
+ if ( ValidTab(nTab) && nTab < static_cast<SCTAB>(maTabs.size()) && maTabs[nTab] )
+ maTabs[nTab]->SetRepeatArea( nStartCol, nEndCol, nStartRow, nEndRow );
+}
+
+void ScDocument::InvalidatePageBreaks(SCTAB nTab)
+{
+ if (ValidTab(nTab) && nTab < static_cast<SCTAB>(maTabs.size()) && maTabs[nTab])
+ maTabs[nTab]->InvalidatePageBreaks();
+}
+
+void ScDocument::UpdatePageBreaks( SCTAB nTab, const ScRange* pUserArea )
+{
+ if ( ValidTab(nTab) && nTab < static_cast<SCTAB>(maTabs.size()) && maTabs[nTab] )
+ maTabs[nTab]->UpdatePageBreaks( pUserArea );
+}
+
+void ScDocument::RemoveManualBreaks( SCTAB nTab )
+{
+ if ( ValidTab(nTab) && nTab < static_cast<SCTAB>(maTabs.size()) && maTabs[nTab] )
+ maTabs[nTab]->RemoveManualBreaks();
+}
+
+bool ScDocument::HasManualBreaks( SCTAB nTab ) const
+{
+ if ( ValidTab(nTab) && nTab < static_cast<SCTAB>(maTabs.size()) && maTabs[nTab] )
+ return maTabs[nTab]->HasManualBreaks();
+
+ OSL_FAIL("invalid tab");
+ return false;
+}
+
+void ScDocument::GetDocStat( ScDocStat& rDocStat )
+{
+ rDocStat.nTableCount = GetTableCount();
+ rDocStat.aDocName = aDocName;
+ rDocStat.nFormulaCount = GetFormulaGroupCount();
+ rDocStat.nCellCount = GetCellCount();
+}
+
+bool ScDocument::HasPrintRange()
+{
+ bool bResult = false;
+
+ for (const auto& a : maTabs)
+ {
+ if (!a)
+ continue;
+ bResult = a->IsPrintEntireSheet() || (a->GetPrintRangeCount() > 0);
+ if (bResult)
+ break;
+ }
+
+ return bResult;
+}
+
+bool ScDocument::IsPrintEntireSheet( SCTAB nTab ) const
+{
+ return (ValidTab(nTab) ) && nTab < static_cast<SCTAB>(maTabs.size()) && maTabs[nTab] && maTabs[nTab]->IsPrintEntireSheet();
+}
+
+sal_uInt16 ScDocument::GetPrintRangeCount( SCTAB nTab )
+{
+ if (ValidTab(nTab) && nTab < static_cast<SCTAB>(maTabs.size()) && maTabs[nTab])
+ return maTabs[nTab]->GetPrintRangeCount();
+
+ return 0;
+}
+
+const ScRange* ScDocument::GetPrintRange( SCTAB nTab, sal_uInt16 nPos )
+{
+ if (ValidTab(nTab) && nTab < static_cast<SCTAB>(maTabs.size()) && maTabs[nTab])
+ return maTabs[nTab]->GetPrintRange(nPos);
+
+ return nullptr;
+}
+
+std::optional<ScRange> ScDocument::GetRepeatColRange( SCTAB nTab )
+{
+ if (ValidTab(nTab) && nTab < static_cast<SCTAB>(maTabs.size()) && maTabs[nTab])
+ return maTabs[nTab]->GetRepeatColRange();
+
+ return std::nullopt;
+}
+
+std::optional<ScRange> ScDocument::GetRepeatRowRange( SCTAB nTab )
+{
+ if (ValidTab(nTab) && nTab < static_cast<SCTAB>(maTabs.size()) && maTabs[nTab])
+ return maTabs[nTab]->GetRepeatRowRange();
+
+ return std::nullopt;
+}
+
+void ScDocument::ClearPrintRanges( SCTAB nTab )
+{
+ if (ValidTab(nTab) && nTab < static_cast<SCTAB>(maTabs.size()) && maTabs[nTab])
+ maTabs[nTab]->ClearPrintRanges();
+}
+
+void ScDocument::AddPrintRange( SCTAB nTab, const ScRange& rNew )
+{
+ if (ValidTab(nTab) && nTab < static_cast<SCTAB>(maTabs.size()) && maTabs[nTab])
+ maTabs[nTab]->AddPrintRange( rNew );
+}
+
+void ScDocument::SetPrintEntireSheet( SCTAB nTab )
+{
+ if (ValidTab(nTab) && nTab < static_cast<SCTAB>(maTabs.size()) && maTabs[nTab])
+ maTabs[nTab]->SetPrintEntireSheet();
+}
+
+void ScDocument::SetRepeatColRange( SCTAB nTab, std::optional<ScRange> oNew )
+{
+ if (ValidTab(nTab) && nTab < static_cast<SCTAB>(maTabs.size()) && maTabs[nTab])
+ maTabs[nTab]->SetRepeatColRange( std::move(oNew) );
+}
+
+void ScDocument::SetRepeatRowRange( SCTAB nTab, std::optional<ScRange> oNew )
+{
+ if (ValidTab(nTab) && nTab < static_cast<SCTAB>(maTabs.size()) && maTabs[nTab])
+ maTabs[nTab]->SetRepeatRowRange( std::move(oNew) );
+}
+
+std::unique_ptr<ScPrintRangeSaver> ScDocument::CreatePrintRangeSaver() const
+{
+ const SCTAB nCount = static_cast<SCTAB>(maTabs.size());
+ std::unique_ptr<ScPrintRangeSaver> pNew(new ScPrintRangeSaver( nCount ));
+ for (SCTAB i=0; i<nCount; i++)
+ if (maTabs[i])
+ maTabs[i]->FillPrintSaver( pNew->GetTabData(i) );
+ return pNew;
+}
+
+void ScDocument::RestorePrintRanges( const ScPrintRangeSaver& rSaver )
+{
+ const SCTAB nCount = rSaver.GetTabCount();
+ const SCTAB maxIndex = std::min(nCount, static_cast<SCTAB>(maTabs.size()));
+ for (SCTAB i=0; i<maxIndex; i++)
+ if (maTabs[i])
+ maTabs[i]->RestorePrintRanges( rSaver.GetTabData(i) );
+}
+
+bool ScDocument::NeedPageResetAfterTab( SCTAB nTab ) const
+{
+ // The page number count restarts at a sheet, if another template is set at
+ // the preceding one (only compare names) and if a pagenumber is specified (not 0)
+
+ if ( nTab + 1 < static_cast<SCTAB>(maTabs.size()) && maTabs[nTab] && maTabs[nTab+1] )
+ {
+ const OUString & rNew = maTabs[nTab+1]->GetPageStyle();
+ if ( rNew != maTabs[nTab]->GetPageStyle() )
+ {
+ SfxStyleSheetBase* pStyle = mxPoolHelper->GetStylePool()->Find( rNew, SfxStyleFamily::Page );
+ if ( pStyle )
+ {
+ const SfxItemSet& rSet = pStyle->GetItemSet();
+ sal_uInt16 nFirst = rSet.Get(ATTR_PAGE_FIRSTPAGENO).GetValue();
+ if ( nFirst != 0 )
+ return true; // Specify page number in new template
+ }
+ }
+ }
+
+ return false; // otherwise not
+}
+
+SfxUndoManager* ScDocument::GetUndoManager()
+{
+ if (!mpUndoManager)
+ {
+ // to support enhanced text edit for draw objects, use an SdrUndoManager
+ ScMutationGuard aGuard(*this, ScMutationGuardFlags::CORE);
+
+ SdrUndoManager* pUndoManager = new SdrUndoManager;
+ pUndoManager->SetDocShell(GetDocumentShell());
+ mpUndoManager = pUndoManager;
+ }
+
+ return mpUndoManager;
+}
+
+ScRowBreakIterator* ScDocument::GetRowBreakIterator(SCTAB nTab) const
+{
+ if (ValidTab(nTab) && nTab < static_cast<SCTAB>(maTabs.size()) && maTabs[nTab])
+ return new ScRowBreakIterator(maTabs[nTab]->maRowPageBreaks);
+ return nullptr;
+}
+
+void ScDocument::AddSubTotalCell(ScFormulaCell* pCell)
+{
+ maSubTotalCells.insert(pCell);
+}
+
+void ScDocument::RemoveSubTotalCell(ScFormulaCell* pCell)
+{
+ maSubTotalCells.erase(pCell);
+}
+
+namespace {
+
+bool lcl_hasDirtyRange(const ScDocument& rDoc, ScFormulaCell* pCell, const ScRange& rDirtyRange)
+{
+ ScDetectiveRefIter aRefIter(rDoc, pCell);
+ ScRange aRange;
+ while (aRefIter.GetNextRef(aRange))
+ {
+ if (aRange.Intersects(rDirtyRange))
+ return true;
+ }
+ return false;
+}
+
+}
+
+void ScDocument::SetSubTotalCellsDirty(const ScRange& rDirtyRange)
+{
+ // to update the list by skipping cells that no longer contain subtotal function.
+ set<ScFormulaCell*> aNewSet;
+
+ bool bOldRecalc = GetAutoCalc();
+ SetAutoCalc(false);
+ for (ScFormulaCell* pCell : maSubTotalCells)
+ {
+ if (pCell->IsSubTotal())
+ {
+ aNewSet.insert(pCell);
+ if (lcl_hasDirtyRange(*this, pCell, rDirtyRange))
+ pCell->SetDirty();
+ }
+ }
+
+ SetAutoCalc(bOldRecalc);
+ maSubTotalCells.swap(aNewSet); // update the list.
+}
+
+sal_uInt16 ScDocument::GetTextWidth( const ScAddress& rPos ) const
+{
+ SCTAB nTab = rPos.Tab();
+ if (ValidTab(nTab) && nTab < static_cast<SCTAB>(maTabs.size()) && maTabs[nTab])
+ return maTabs[nTab]->GetTextWidth(rPos.Col(), rPos.Row());
+
+ return 0;
+}
+
+SvtScriptType ScDocument::GetScriptType( const ScAddress& rPos ) const
+{
+ SCTAB nTab = rPos.Tab();
+ if (ValidTab(nTab) && nTab < static_cast<SCTAB>(maTabs.size()) && maTabs[nTab])
+ return maTabs[nTab]->GetScriptType(rPos.Col(), rPos.Row());
+
+ return SvtScriptType::NONE;
+}
+
+void ScDocument::SetScriptType( const ScAddress& rPos, SvtScriptType nType )
+{
+ SCTAB nTab = rPos.Tab();
+ if (ValidTab(nTab) && nTab < static_cast<SCTAB>(maTabs.size()) && maTabs[nTab])
+ maTabs[nTab]->SetScriptType(rPos.Col(), rPos.Row(), nType);
+}
+
+void ScDocument::EnableUndo( bool bVal )
+{
+ // The undo manager increases lock count every time undo is disabled.
+ // Because of this, we shouldn't disable undo unless it's currently
+ // enabled, or else re-enabling it may not actually re-enable undo unless
+ // the lock count becomes zero.
+
+ if (bVal != GetUndoManager()->IsUndoEnabled())
+ {
+ GetUndoManager()->EnableUndo(bVal);
+ if( mpDrawLayer ) mpDrawLayer->EnableUndo(bVal);
+ }
+
+ mbUndoEnabled = bVal;
+}
+
+void ScDocument::EnableUserInteraction( bool bVal )
+{
+ mbUserInteractionEnabled = bVal;
+}
+
+bool ScDocument::IsInVBAMode() const
+{
+ if (!mpShell)
+ return false;
+
+ try
+ {
+ uno::Reference<script::vba::XVBACompatibility> xVBA(
+ mpShell->GetBasicContainer(), uno::UNO_QUERY);
+
+ return xVBA.is() && xVBA->getVBACompatibilityMode();
+ }
+ catch (const lang::NotInitializedException&) {}
+
+ return false;
+}
+
+// Sparklines
+std::shared_ptr<sc::Sparkline> ScDocument::GetSparkline(ScAddress const& rPosition)
+{
+ SCTAB nTab = rPosition.Tab();
+
+ if (ValidTab(nTab) && nTab < SCTAB(maTabs.size()))
+ {
+ return maTabs[nTab]->GetSparkline(rPosition.Col(), rPosition.Row());
+ }
+ return std::shared_ptr<sc::Sparkline>();
+}
+
+bool ScDocument::HasSparkline(ScAddress const & rPosition)
+{
+ return bool(GetSparkline(rPosition));
+}
+
+sc::Sparkline* ScDocument::CreateSparkline(ScAddress const& rPosition, std::shared_ptr<sc::SparklineGroup> const& pSparklineGroup)
+{
+ SCTAB nTab = rPosition.Tab();
+
+ if (ValidTab(nTab) && nTab < SCTAB(maTabs.size()))
+ {
+ return maTabs[nTab]->CreateSparkline(rPosition.Col(), rPosition.Row(), pSparklineGroup);
+ }
+
+ return nullptr;
+}
+
+bool ScDocument::DeleteSparkline(ScAddress const & rPosition)
+{
+ SCTAB nTab = rPosition.Tab();
+
+ if (TableExists(nTab))
+ {
+ return maTabs[nTab]->DeleteSparkline(rPosition.Col(), rPosition.Row());
+ }
+
+ return false;
+}
+
+sc::SparklineList* ScDocument::GetSparklineList(SCTAB nTab)
+{
+ if (TableExists(nTab))
+ {
+ return &maTabs[nTab]->GetSparklineList();
+ }
+ return nullptr;
+}
+
+bool ScDocument::HasOneSparklineGroup(ScRange const& rRange)
+{
+ std::shared_ptr<sc::SparklineGroup> pSparklineGroup;
+ return GetSparklineGroupInRange(rRange, pSparklineGroup);
+}
+
+bool ScDocument::GetSparklineGroupInRange(ScRange const& rRange, std::shared_ptr<sc::SparklineGroup>& rGroup)
+{
+ std::shared_ptr<sc::SparklineGroup> pFoundGroup;
+ SCTAB nTab = rRange.aStart.Tab();
+
+ for (SCCOL nX = rRange.aStart.Col(); nX <= rRange.aEnd.Col(); nX++)
+ {
+ for (SCROW nY = rRange.aStart.Row(); nY <= rRange.aEnd.Row(); nY++)
+ {
+ auto pSparkline = GetSparkline(ScAddress(nX, nY, nTab));
+ if (!pSparkline)
+ {
+ return false;
+ }
+ else if (!pFoundGroup)
+ {
+ pFoundGroup = pSparkline->getSparklineGroup();
+ }
+ else if (pFoundGroup != pSparkline->getSparklineGroup())
+ {
+ return false;
+ }
+ }
+ }
+
+ rGroup = pFoundGroup;
+ return true;
+}
+
+std::shared_ptr<sc::SparklineGroup> ScDocument::SearchSparklineGroup(tools::Guid const& rGuid)
+{
+ for (auto const& rTable : maTabs)
+ {
+ if (!rTable)
+ continue;
+
+ auto& rSparklineList = rTable->GetSparklineList();
+
+ for (auto const& pSparklineGroup : rSparklineList.getSparklineGroups())
+ {
+ if (pSparklineGroup->getID() == rGuid)
+ return pSparklineGroup;
+ }
+ }
+
+ return std::shared_ptr<sc::SparklineGroup>();
+}
+
+// Notes
+
+ScPostIt* ScDocument::GetNote(const ScAddress& rPos)
+{
+ return GetNote(rPos.Col(), rPos.Row(), rPos.Tab());
+}
+
+ScPostIt* ScDocument::GetNote(SCCOL nCol, SCROW nRow, SCTAB nTab)
+{
+ if (ValidTab(nTab) && nTab < static_cast<SCTAB>(maTabs.size()))
+ return maTabs[nTab]->GetNote(nCol, nRow);
+ else
+ return nullptr;
+
+}
+
+void ScDocument::SetNote(const ScAddress& rPos, std::unique_ptr<ScPostIt> pNote)
+{
+ return SetNote(rPos.Col(), rPos.Row(), rPos.Tab(), std::move(pNote));
+}
+
+void ScDocument::SetNote(SCCOL nCol, SCROW nRow, SCTAB nTab, std::unique_ptr<ScPostIt> pNote)
+{
+ if (ValidTab(nTab) && nTab < static_cast<SCTAB>(maTabs.size()))
+ maTabs[nTab]->SetNote(nCol, nRow, std::move(pNote));
+}
+
+bool ScDocument::HasNote(const ScAddress& rPos) const
+{
+ return HasNote(rPos.Col(), rPos.Row(), rPos.Tab());
+}
+
+bool ScDocument::HasNote(SCCOL nCol, SCROW nRow, SCTAB nTab) const
+{
+ if (!ValidColRow(nCol, nRow))
+ return false;
+
+ const ScTable* pTab = FetchTable(nTab);
+ if (!pTab)
+ return false;
+
+ if (nCol >= pTab->GetAllocatedColumnsCount())
+ return false;
+
+ const ScPostIt* pNote = pTab->aCol[nCol].GetCellNote(nRow);
+ return pNote != nullptr;
+}
+
+bool ScDocument::HasNote(SCTAB nTab, SCCOL nStartCol, SCROW nStartRow, SCCOL nEndCol, SCROW nEndRow) const
+{
+ const ScTable* pTab = FetchTable(nTab);
+ if (!pTab)
+ return false;
+
+ nStartCol = pTab->ClampToAllocatedColumns(nStartCol);
+ nEndCol = pTab->ClampToAllocatedColumns(nEndCol);
+ for (SCCOL nCol = nStartCol; nCol < nEndCol; ++nCol)
+ if (pTab->aCol[nCol].HasCellNote(nStartRow, nEndRow))
+ return true;
+ return false;
+}
+
+bool ScDocument::HasColNotes(SCCOL nCol, SCTAB nTab) const
+{
+ if (!ValidCol(nCol))
+ return false;
+
+ const ScTable* pTab = FetchTable(nTab);
+ if (!pTab)
+ return false;
+
+ if (nCol >= pTab->GetAllocatedColumnsCount())
+ return false;
+
+ return pTab->aCol[nCol].HasCellNotes();
+}
+
+bool ScDocument::HasTabNotes(SCTAB nTab) const
+{
+ const ScTable* pTab = FetchTable(nTab);
+
+ if ( !pTab )
+ return false;
+
+ for (SCCOL nCol=0, nColSize = pTab->aCol.size(); nCol < nColSize; ++nCol)
+ if ( HasColNotes(nCol, nTab) )
+ return true;
+
+ return false;
+}
+
+bool ScDocument::HasNotes() const
+{
+ for (SCTAB i = 0; i <= MAXTAB; ++i)
+ {
+ if (HasTabNotes(i))
+ return true;
+ }
+ return false;
+}
+
+std::unique_ptr<ScPostIt> ScDocument::ReleaseNote(const ScAddress& rPos)
+{
+ ScTable* pTab = FetchTable(rPos.Tab());
+ if (!pTab)
+ return nullptr;
+
+ return pTab->ReleaseNote(rPos.Col(), rPos.Row());
+}
+
+ScPostIt* ScDocument::GetOrCreateNote(const ScAddress& rPos)
+{
+ if (HasNote(rPos))
+ return GetNote(rPos);
+ else
+ return CreateNote(rPos);
+}
+
+ScPostIt* ScDocument::CreateNote(const ScAddress& rPos)
+{
+ ScPostIt* pPostIt = new ScPostIt(*this, rPos);
+ SetNote(rPos, std::unique_ptr<ScPostIt>(pPostIt));
+ return pPostIt;
+}
+
+size_t ScDocument::GetNoteCount( SCTAB nTab, SCCOL nCol ) const
+{
+ const ScTable* pTab = FetchTable(nTab);
+ if (!pTab)
+ return 0;
+
+ return pTab->GetNoteCount(nCol);
+}
+
+void ScDocument::CreateAllNoteCaptions()
+{
+ for (const auto& a : maTabs)
+ {
+ if (a)
+ a->CreateAllNoteCaptions();
+ }
+}
+
+void ScDocument::ForgetNoteCaptions( const ScRangeList& rRanges, bool bPreserveData )
+{
+ for (size_t i = 0, n = rRanges.size(); i < n; ++i)
+ {
+ const ScRange & rRange = rRanges[i];
+ const ScAddress& s = rRange.aStart;
+ const ScAddress& e = rRange.aEnd;
+ for (SCTAB nTab = s.Tab(); nTab <= e.Tab(); ++nTab)
+ {
+ ScTable* pTab = FetchTable(nTab);
+ if (!pTab)
+ continue;
+
+ pTab->ForgetNoteCaptions(s.Col(), s.Row(), e.Col(), e.Row(), bPreserveData);
+ }
+ }
+}
+
+CommentCaptionState ScDocument::GetAllNoteCaptionsState( const ScRangeList& rRanges )
+{
+ CommentCaptionState aTmpState = CommentCaptionState::ALLHIDDEN;
+ CommentCaptionState aState = CommentCaptionState::ALLHIDDEN;
+ bool bFirstControl = true;
+ std::vector<sc::NoteEntry> aNotes;
+
+ for (size_t i = 0, n = rRanges.size(); i < n; ++i)
+ {
+ const ScRange & rRange = rRanges[i];
+
+ for( SCTAB nTab = rRange.aStart.Tab(); nTab <= rRange.aEnd.Tab(); ++nTab )
+ {
+ aState = maTabs[nTab]->GetAllNoteCaptionsState( rRange, aNotes );
+
+ if (aState == CommentCaptionState::MIXED)
+ return aState;
+
+ if (bFirstControl) // it is possible that a range is ALLSHOWN, another range is ALLHIDDEN,
+ { // we have to detect that situation as mixed.
+ aTmpState = aState;
+ bFirstControl = false;
+ }
+ else if(aTmpState != aState)
+ {
+ aState = CommentCaptionState::MIXED;
+ return aState;
+ }
+ }
+ }
+ return aState;
+}
+
+ScAddress ScDocument::GetNotePosition( size_t nIndex ) const
+{
+ for (size_t nTab = 0; nTab < maTabs.size(); ++nTab)
+ {
+ for (SCCOL nCol : GetAllocatedColumnsRange(nTab, 0, MaxCol()))
+ {
+ size_t nColNoteCount = GetNoteCount(nTab, nCol);
+ if (!nColNoteCount)
+ continue;
+
+ if (nIndex >= nColNoteCount)
+ {
+ nIndex -= nColNoteCount;
+ continue;
+ }
+
+ SCROW nRow = GetNotePosition(nTab, nCol, nIndex);
+ if (nRow >= 0)
+ return ScAddress(nCol, nRow, nTab);
+
+ OSL_FAIL("note not found");
+ return ScAddress::INITIALIZE_INVALID;
+ }
+ }
+
+ OSL_FAIL("note not found");
+ return ScAddress::INITIALIZE_INVALID;
+}
+
+ScAddress ScDocument::GetNotePosition( size_t nIndex, SCTAB nTab ) const
+{
+ for (SCCOL nCol : GetAllocatedColumnsRange(nTab, 0, MaxCol()))
+ {
+ size_t nColNoteCount = GetNoteCount(nTab, nCol);
+ if (!nColNoteCount)
+ continue;
+
+ if (nIndex >= nColNoteCount)
+ {
+ nIndex -= nColNoteCount;
+ continue;
+ }
+
+ SCROW nRow = GetNotePosition(nTab, nCol, nIndex);
+ if (nRow >= 0)
+ return ScAddress(nCol, nRow, nTab);
+
+ OSL_FAIL("note not found");
+ return ScAddress::INITIALIZE_INVALID;
+ }
+
+ OSL_FAIL("note not found");
+ return ScAddress::INITIALIZE_INVALID;
+}
+
+SCROW ScDocument::GetNotePosition( SCTAB nTab, SCCOL nCol, size_t nIndex ) const
+{
+ const ScTable* pTab = FetchTable(nTab);
+ if (!pTab)
+ return -1;
+
+ return pTab->GetNotePosition(nCol, nIndex);
+}
+
+void ScDocument::GetAllNoteEntries( std::vector<sc::NoteEntry>& rNotes ) const
+{
+ for (const auto & pTab : maTabs)
+ {
+ if (!pTab)
+ continue;
+
+ pTab->GetAllNoteEntries(rNotes);
+ }
+}
+
+void ScDocument::GetAllNoteEntries( SCTAB nTab, std::vector<sc::NoteEntry>& rNotes ) const
+{
+ const ScTable* pTab = FetchTable(nTab);
+ if (!pTab)
+ return;
+
+ return pTab->GetAllNoteEntries( rNotes );
+}
+
+void ScDocument::GetNotesInRange( const ScRangeList& rRangeList, std::vector<sc::NoteEntry>& rNotes ) const
+{
+ for( size_t i = 0; i < rRangeList.size(); ++i)
+ {
+ const ScRange & rRange = rRangeList[i];
+ for( SCTAB nTab = rRange.aStart.Tab(); nTab <= rRange.aEnd.Tab(); ++nTab )
+ {
+ maTabs[nTab]->GetNotesInRange( rRange, rNotes );
+ }
+ }
+}
+
+void ScDocument::GetUnprotectedCells( ScRangeList& rRangeList, SCTAB nTab ) const
+{
+ maTabs[nTab]->GetUnprotectedCells( rRangeList );
+}
+
+bool ScDocument::ContainsNotesInRange( const ScRangeList& rRangeList ) const
+{
+ for( size_t i = 0; i < rRangeList.size(); ++i)
+ {
+ const ScRange & rRange = rRangeList[i];
+ for( SCTAB nTab = rRange.aStart.Tab(); nTab <= rRange.aEnd.Tab(); ++nTab )
+ {
+ bool bContainsNote = maTabs[nTab]->ContainsNotesInRange( rRange );
+ if(bContainsNote)
+ return true;
+ }
+ }
+
+ return false;
+}
+
+void ScDocument::SetAutoNameCache( std::unique_ptr<ScAutoNameCache> pCache )
+{
+ pAutoNameCache = std::move(pCache);
+}
+
+thread_local ScDocumentThreadSpecific ScDocument::maThreadSpecific;
+
+ScRecursionHelper& ScDocument::GetRecursionHelper()
+{
+ if (!IsThreadedGroupCalcInProgress())
+ {
+ if (!maNonThreaded.xRecursionHelper)
+ maNonThreaded.xRecursionHelper = std::make_unique<ScRecursionHelper>();
+ return *maNonThreaded.xRecursionHelper;
+ }
+ else
+ {
+ if (!maThreadSpecific.xRecursionHelper)
+ maThreadSpecific.xRecursionHelper = std::make_unique<ScRecursionHelper>();
+ return *maThreadSpecific.xRecursionHelper;
+ }
+}
+
+void ScDocument::SetupContextFromNonThreadedContext(ScInterpreterContext& /*threadedContext*/, int /*threadNumber*/)
+{
+ (void)this;
+ // lookup cache is now only in pooled ScInterpreterContext's
+}
+
+void ScDocument::MergeContextBackIntoNonThreadedContext(ScInterpreterContext& threadedContext, int /*threadNumber*/)
+{
+ // Move data from a context used by a calculation thread to the main thread's context.
+ // Called from the main thread after the calculation thread has already finished.
+ assert(!IsThreadedGroupCalcInProgress());
+ maInterpreterContext.maDelayedSetNumberFormat.insert(
+ maInterpreterContext.maDelayedSetNumberFormat.end(),
+ std::make_move_iterator(threadedContext.maDelayedSetNumberFormat.begin()),
+ std::make_move_iterator(threadedContext.maDelayedSetNumberFormat.end()));
+ // lookup cache is now only in pooled ScInterpreterContext's
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sc/source/core/data/document10.cxx b/sc/source/core/data/document10.cxx
new file mode 100644
index 000000000..ec61fa530
--- /dev/null
+++ b/sc/source/core/data/document10.cxx
@@ -0,0 +1,1106 @@
+/* -*- 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/.
+ */
+
+#include <memory>
+#include <document.hxx>
+#include <clipcontext.hxx>
+#include <clipparam.hxx>
+#include <table.hxx>
+#include <tokenarray.hxx>
+#include <listenercontext.hxx>
+#include <tokenstringcontext.hxx>
+#include <poolhelp.hxx>
+#include <cellvalues.hxx>
+#include <docpool.hxx>
+#include <columniterator.hxx>
+
+#include <refupdatecontext.hxx>
+#include <sal/log.hxx>
+#include <editeng/colritem.hxx>
+#include <scitems.hxx>
+#include <datamapper.hxx>
+#include <docsh.hxx>
+
+// Add totally brand-new methods to this source file.
+
+bool ScDocument::IsMerged( const ScAddress& rPos ) const
+{
+ const ScTable* pTab = FetchTable(rPos.Tab());
+ if (!pTab)
+ return false;
+
+ return pTab->IsMerged(rPos.Col(), rPos.Row());
+}
+
+sc::MultiDataCellState ScDocument::HasMultipleDataCells( const ScRange& rRange ) const
+{
+ if (rRange.aStart.Tab() != rRange.aEnd.Tab())
+ // Currently we only support a single-sheet range.
+ return sc::MultiDataCellState();
+
+ const ScTable* pTab = FetchTable(rRange.aStart.Tab());
+ if (!pTab)
+ return sc::MultiDataCellState(sc::MultiDataCellState::Empty);
+
+ const ScAddress& s = rRange.aStart;
+ const ScAddress& e = rRange.aEnd;
+ return pTab->HasMultipleDataCells(s.Col(), s.Row(), e.Col(), e.Row());
+}
+
+void ScDocument::DeleteBeforeCopyFromClip(
+ sc::CopyFromClipContext& rCxt, const ScMarkData& rMark, sc::ColumnSpanSet& rBroadcastSpans )
+{
+ SCTAB nClipTab = 0;
+ const TableContainer& rClipTabs = rCxt.getClipDoc()->maTabs;
+ SCTAB nClipTabCount = rClipTabs.size();
+
+ for (SCTAB nTab = rCxt.getTabStart(); nTab <= rCxt.getTabEnd(); ++nTab)
+ {
+ ScTable* pTab = FetchTable(nTab);
+ if (!pTab)
+ continue;
+
+ if (!rMark.GetTableSelect(nTab))
+ continue;
+
+ while (!rClipTabs[nClipTab])
+ nClipTab = (nClipTab+1) % nClipTabCount;
+
+ pTab->DeleteBeforeCopyFromClip(rCxt, *rClipTabs[nClipTab], rBroadcastSpans);
+
+ nClipTab = (nClipTab+1) % nClipTabCount;
+ }
+}
+
+bool ScDocument::CopyOneCellFromClip(
+ sc::CopyFromClipContext& rCxt, SCCOL nCol1, SCROW nRow1, SCCOL nCol2, SCROW nRow2 )
+{
+ ScDocument* pClipDoc = rCxt.getClipDoc();
+ if (pClipDoc->GetClipParam().mbCutMode)
+ // We don't handle cut and paste or moving of cells here.
+ return false;
+
+ ScRange aClipRange = pClipDoc->GetClipParam().getWholeRange();
+ if (aClipRange.aStart.Row() != aClipRange.aEnd.Row())
+ // The source is not really a single row. Bail out.
+ return false;
+
+ SCCOL nSrcColSize = aClipRange.aEnd.Col() - aClipRange.aStart.Col() + 1;
+ SCCOL nDestColSize = nCol2 - nCol1 + 1;
+ if (nDestColSize < nSrcColSize)
+ return false;
+
+ if (pClipDoc->maTabs.size() > 1)
+ // Copying from multiple source sheets is not handled here.
+ return false;
+
+ ScAddress aSrcPos = aClipRange.aStart;
+
+ for (SCCOL nCol = aClipRange.aStart.Col(); nCol <= aClipRange.aEnd.Col(); ++nCol)
+ {
+ ScAddress aTestPos = aSrcPos;
+ aTestPos.SetCol(nCol);
+ if (pClipDoc->IsMerged(aTestPos))
+ // We don't handle merged source cell for this.
+ return false;
+ }
+
+ ScTable* pSrcTab = pClipDoc->FetchTable(aSrcPos.Tab());
+ if (!pSrcTab)
+ return false;
+
+ rCxt.setSingleCellColumnSize(nSrcColSize);
+
+ for (SCCOL nColOffset = 0; nColOffset < nSrcColSize; ++nColOffset, aSrcPos.IncCol())
+ {
+ const ScPatternAttr* pAttr = pClipDoc->GetPattern(aSrcPos);
+ rCxt.setSingleCellPattern(nColOffset, pAttr);
+
+ if ((rCxt.getInsertFlag() & (InsertDeleteFlags::NOTE | InsertDeleteFlags::ADDNOTES)) != InsertDeleteFlags::NONE)
+ rCxt.setSingleCellNote(nColOffset, pClipDoc->GetNote(aSrcPos));
+
+ if ((rCxt.getInsertFlag() & InsertDeleteFlags::SPARKLINES) != InsertDeleteFlags::NONE)
+ rCxt.setSingleSparkline(nColOffset, pClipDoc->GetSparkline(aSrcPos));
+
+ ScColumn* pSrcCol = pSrcTab->FetchColumn(aSrcPos.Col());
+ assert(pSrcCol);
+ // Determine the script type of the copied single cell.
+ pSrcCol->UpdateScriptTypes(aSrcPos.Row(), aSrcPos.Row());
+ rCxt.setSingleCell(aSrcPos, *pSrcCol);
+ }
+
+ // All good. Proceed with the pasting.
+
+ SCTAB nTabEnd = rCxt.getTabEnd();
+ for (SCTAB i = rCxt.getTabStart(); i <= nTabEnd && i < static_cast<SCTAB>(maTabs.size()); ++i)
+ {
+ maTabs[i]->CopyOneCellFromClip(rCxt, nCol1, nRow1, nCol2, nRow2, aClipRange.aStart.Row(), pSrcTab);
+ }
+
+ sc::RefUpdateContext aRefCxt(*this);
+ aRefCxt.maRange = ScRange(nCol1, nRow1, rCxt.getTabStart(), nCol2, nRow2, nTabEnd);
+ aRefCxt.mnColDelta = nCol1 - aSrcPos.Col();
+ aRefCxt.mnRowDelta = nRow1 - aSrcPos.Row();
+ aRefCxt.mnTabDelta = rCxt.getTabStart() - aSrcPos.Tab();
+ // Only Copy&Paste, for Cut&Paste we already bailed out early.
+ aRefCxt.meMode = URM_COPY;
+ UpdateReference(aRefCxt, rCxt.getUndoDoc(), false);
+
+ return true;
+}
+
+void ScDocument::SetValues( const ScAddress& rPos, const std::vector<double>& rVals )
+{
+ ScTable* pTab = FetchTable(rPos.Tab());
+ if (!pTab)
+ return;
+
+ pTab->SetValues(rPos.Col(), rPos.Row(), rVals);
+}
+
+void ScDocument::TransferCellValuesTo( const ScAddress& rTopPos, size_t nLen, sc::CellValues& rDest )
+{
+ ScTable* pTab = FetchTable(rTopPos.Tab());
+ if (!pTab)
+ return;
+
+ pTab->TransferCellValuesTo(rTopPos.Col(), rTopPos.Row(), nLen, rDest);
+}
+
+void ScDocument::CopyCellValuesFrom( const ScAddress& rTopPos, const sc::CellValues& rSrc )
+{
+ ScTable* pTab = FetchTable(rTopPos.Tab());
+ if (!pTab)
+ return;
+
+ pTab->CopyCellValuesFrom(rTopPos.Col(), rTopPos.Row(), rSrc);
+}
+
+std::set<Color> ScDocument::GetDocColors()
+{
+ std::set<Color> aDocColors;
+ ScDocumentPool *pPool = GetPool();
+ const sal_uInt16 pAttribs[] = {ATTR_BACKGROUND, ATTR_FONT_COLOR};
+ for (sal_uInt16 nAttrib : pAttribs)
+ {
+ for (const SfxPoolItem* pItem : pPool->GetItemSurrogates(nAttrib))
+ {
+ const SvxColorItem *pColorItem = static_cast<const SvxColorItem*>(pItem);
+ Color aColor( pColorItem->GetValue() );
+ if (COL_AUTO != aColor)
+ aDocColors.insert(aColor);
+ }
+ }
+ return aDocColors;
+}
+
+void ScDocument::SetCalcConfig( const ScCalcConfig& rConfig )
+{
+ ScMutationGuard aGuard(*this, ScMutationGuardFlags::CORE);
+ maCalcConfig = rConfig;
+}
+
+void ScDocument::ConvertFormulaToValue( const ScRange& rRange, sc::TableValues* pUndo )
+{
+ sc::EndListeningContext aCxt(*this);
+
+ for (SCTAB nTab = rRange.aStart.Tab(); nTab <= rRange.aEnd.Tab(); ++nTab)
+ {
+ ScTable* pTab = FetchTable(nTab);
+ if (!pTab)
+ continue;
+
+ pTab->ConvertFormulaToValue(
+ aCxt, rRange.aStart.Col(), rRange.aStart.Row(), rRange.aEnd.Col(), rRange.aEnd.Row(),
+ pUndo);
+ }
+
+ aCxt.purgeEmptyBroadcasters();
+}
+
+void ScDocument::SwapNonEmpty( sc::TableValues& rValues )
+{
+ const ScRange& rRange = rValues.getRange();
+ if (!rRange.IsValid())
+ return;
+
+ auto pPosSet = std::make_shared<sc::ColumnBlockPositionSet>(*this);
+ sc::StartListeningContext aStartCxt(*this, pPosSet);
+ sc::EndListeningContext aEndCxt(*this, pPosSet);
+
+ for (SCTAB nTab = rRange.aStart.Tab(); nTab <= rRange.aEnd.Tab(); ++nTab)
+ {
+ ScTable* pTab = FetchTable(nTab);
+ if (!pTab)
+ continue;
+
+ pTab->SwapNonEmpty(rValues, aStartCxt, aEndCxt);
+ }
+
+ aEndCxt.purgeEmptyBroadcasters();
+}
+
+void ScDocument::PreprocessAllRangeNamesUpdate( const std::map<OUString, std::unique_ptr<ScRangeName>>& rRangeMap )
+{
+ // Update all existing names with new names.
+ // The prerequisites are that the name dialog preserves ScRangeData index
+ // for changes and does not reuse free index slots for new names.
+ // ScDocument::SetAllRangeNames() hereafter then will replace the
+ // ScRangeName containers of ScRangeData instances with empty
+ // ScRangeData::maNewName.
+ std::map<OUString, ScRangeName*> aRangeNameMap;
+ GetRangeNameMap( aRangeNameMap);
+ for (const auto& itTab : aRangeNameMap)
+ {
+ ScRangeName* pOldRangeNames = itTab.second;
+ if (!pOldRangeNames)
+ continue;
+
+ const auto& itNewTab( rRangeMap.find( itTab.first));
+ if (itNewTab == rRangeMap.end())
+ continue;
+
+ const ScRangeName* pNewRangeNames = itNewTab->second.get();
+ if (!pNewRangeNames)
+ continue;
+
+ for (const auto& rEntry : *pOldRangeNames)
+ {
+ ScRangeData* pOldData = rEntry.second.get();
+ if (!pOldData)
+ continue;
+
+ const ScRangeData* pNewData = pNewRangeNames->findByIndex( pOldData->GetIndex());
+ if (pNewData)
+ pOldData->SetNewName( pNewData->GetName());
+ }
+ }
+
+ sc::EndListeningContext aEndListenCxt(*this);
+ sc::CompileFormulaContext aCompileCxt(*this);
+
+ for (const auto& rxTab : maTabs)
+ {
+ ScTable* p = rxTab.get();
+ p->PreprocessRangeNameUpdate(aEndListenCxt, aCompileCxt);
+ }
+}
+
+void ScDocument::PreprocessRangeNameUpdate()
+{
+ sc::EndListeningContext aEndListenCxt(*this);
+ sc::CompileFormulaContext aCompileCxt(*this);
+
+ for (const auto& rxTab : maTabs)
+ {
+ ScTable* p = rxTab.get();
+ p->PreprocessRangeNameUpdate(aEndListenCxt, aCompileCxt);
+ }
+}
+
+void ScDocument::PreprocessDBDataUpdate()
+{
+ sc::EndListeningContext aEndListenCxt(*this);
+ sc::CompileFormulaContext aCompileCxt(*this);
+
+ for (const auto& rxTab : maTabs)
+ {
+ ScTable* p = rxTab.get();
+ p->PreprocessDBDataUpdate(aEndListenCxt, aCompileCxt);
+ }
+}
+
+void ScDocument::CompileHybridFormula()
+{
+ sc::StartListeningContext aStartListenCxt(*this);
+ sc::CompileFormulaContext aCompileCxt(*this);
+ for (const auto& rxTab : maTabs)
+ {
+ ScTable* p = rxTab.get();
+ p->CompileHybridFormula(aStartListenCxt, aCompileCxt);
+ }
+}
+
+void ScDocument::SharePooledResources( const ScDocument* pSrcDoc )
+{
+ ScMutationGuard aGuard(*this, ScMutationGuardFlags::CORE);
+ mxPoolHelper = pSrcDoc->mxPoolHelper;
+ mpCellStringPool = pSrcDoc->mpCellStringPool;
+}
+
+void ScDocument::UpdateScriptTypes( const ScAddress& rPos, SCCOL nColSize, SCROW nRowSize )
+{
+ ScTable* pTab = FetchTable(rPos.Tab());
+ if (!pTab)
+ return;
+
+ pTab->UpdateScriptTypes(rPos.Col(), rPos.Row(), rPos.Col()+nColSize-1, rPos.Row()+nRowSize-1);
+}
+
+bool ScDocument::HasUniformRowHeight( SCTAB nTab, SCROW nRow1, SCROW nRow2 ) const
+{
+ const ScTable* pTab = FetchTable(nTab);
+ if (!pTab)
+ return false;
+
+ return pTab->HasUniformRowHeight(nRow1, nRow2);
+}
+
+void ScDocument::UnshareFormulaCells( SCTAB nTab, SCCOL nCol, std::vector<SCROW>& rRows )
+{
+ ScTable* pTab = FetchTable(nTab);
+ if (!pTab)
+ return;
+
+ pTab->UnshareFormulaCells(nCol, rRows);
+}
+
+void ScDocument::RegroupFormulaCells( SCTAB nTab, SCCOL nCol )
+{
+ ScTable* pTab = FetchTable(nTab);
+ if (!pTab)
+ return;
+
+ pTab->RegroupFormulaCells(nCol);
+}
+
+void ScDocument::RegroupFormulaCells( const ScRange& rRange )
+{
+ for( SCTAB tab = rRange.aStart.Tab(); tab <= rRange.aEnd.Tab(); ++tab )
+ for( SCCOL col = rRange.aStart.Col(); col <= rRange.aEnd.Col(); ++col )
+ RegroupFormulaCells( tab, col );
+}
+
+void ScDocument::DelayFormulaGrouping( bool delay )
+{
+ if( delay )
+ {
+ if( !pDelayedFormulaGrouping )
+ pDelayedFormulaGrouping.reset( new ScRange( ScAddress::INITIALIZE_INVALID ));
+ }
+ else
+ {
+ if( pDelayedFormulaGrouping && pDelayedFormulaGrouping->IsValid())
+ RegroupFormulaCells( *pDelayedFormulaGrouping );
+ pDelayedFormulaGrouping.reset();
+ }
+}
+
+void ScDocument::AddDelayedFormulaGroupingCell( const ScFormulaCell* cell )
+{
+ if( !pDelayedFormulaGrouping->Contains( cell->aPos ))
+ pDelayedFormulaGrouping->ExtendTo( cell->aPos );
+}
+
+void ScDocument::EnableDelayStartListeningFormulaCells( ScColumn* column, bool delay )
+{
+ if( delay )
+ {
+ if( pDelayedStartListeningFormulaCells.find( column ) == pDelayedStartListeningFormulaCells.end())
+ pDelayedStartListeningFormulaCells[ column ] = std::pair<SCROW, SCROW>( -1, -1 );
+ }
+ else
+ {
+ auto it = pDelayedStartListeningFormulaCells.find( column );
+ if( it != pDelayedStartListeningFormulaCells.end())
+ {
+ if( it->second.first != -1 )
+ {
+ auto pPosSet = std::make_shared<sc::ColumnBlockPositionSet>(*this);
+ sc::StartListeningContext aStartCxt(*this, pPosSet);
+ sc::EndListeningContext aEndCxt(*this, pPosSet);
+ column->StartListeningFormulaCells(aStartCxt, aEndCxt, it->second.first, it->second.second);
+ }
+ pDelayedStartListeningFormulaCells.erase( it );
+ }
+ }
+}
+
+bool ScDocument::IsEnabledDelayStartListeningFormulaCells( ScColumn* column ) const
+{
+ return pDelayedStartListeningFormulaCells.find( column ) != pDelayedStartListeningFormulaCells.end();
+}
+
+bool ScDocument::CanDelayStartListeningFormulaCells( ScColumn* column, SCROW row1, SCROW row2 )
+{
+ auto it = pDelayedStartListeningFormulaCells.find( column );
+ if( it == pDelayedStartListeningFormulaCells.end())
+ return false; // not enabled
+ if( it->second.first == -1 && it->second.second == -1 ) // uninitialized
+ pDelayedStartListeningFormulaCells[ column ] = std::make_pair( row1, row2 );
+ else
+ {
+ if( row1 > it->second.second + 1 || row2 < it->second.first - 1 )
+ { // two non-adjacent ranges, just bail out
+ return false;
+ }
+ it->second.first = std::min( it->second.first, row1 );
+ it->second.second = std::max( it->second.second, row2 );
+ }
+ return true;
+}
+
+void ScDocument::EnableDelayDeletingBroadcasters( bool set )
+{
+ if( bDelayedDeletingBroadcasters == set )
+ return;
+ bDelayedDeletingBroadcasters = set;
+ if( !bDelayedDeletingBroadcasters )
+ {
+ for (auto& rxTab : maTabs)
+ if (rxTab)
+ rxTab->DeleteEmptyBroadcasters();
+ }
+}
+
+bool ScDocument::HasFormulaCell( const ScRange& rRange ) const
+{
+ if (!rRange.IsValid())
+ return false;
+
+ for (SCTAB nTab = rRange.aStart.Tab(); nTab <= rRange.aEnd.Tab(); ++nTab)
+ {
+ const ScTable* pTab = FetchTable(nTab);
+ if (!pTab)
+ continue;
+
+ if (pTab->HasFormulaCell(rRange.aStart.Col(), rRange.aStart.Row(), rRange.aEnd.Col(), rRange.aEnd.Row()))
+ return true;
+ }
+
+ return false;
+}
+
+void ScDocument::EndListeningIntersectedGroup(
+ sc::EndListeningContext& rCxt, const ScAddress& rPos, std::vector<ScAddress>* pGroupPos )
+{
+ ScTable* pTab = FetchTable(rPos.Tab());
+ if (!pTab)
+ return;
+
+ pTab->EndListeningIntersectedGroup(rCxt, rPos.Col(), rPos.Row(), pGroupPos);
+}
+
+void ScDocument::EndListeningIntersectedGroups(
+ sc::EndListeningContext& rCxt, const ScRange& rRange, std::vector<ScAddress>* pGroupPos )
+{
+ for (SCTAB nTab = rRange.aStart.Tab(); nTab <= rRange.aEnd.Tab(); ++nTab)
+ {
+ ScTable* pTab = FetchTable(nTab);
+ if (!pTab)
+ continue;
+
+ pTab->EndListeningIntersectedGroups(
+ rCxt, rRange.aStart.Col(), rRange.aStart.Row(), rRange.aEnd.Col(), rRange.aEnd.Row(),
+ pGroupPos);
+ }
+}
+
+void ScDocument::EndListeningGroups( const std::vector<ScAddress>& rPosArray )
+{
+ sc::EndListeningContext aCxt(*this);
+ for (const ScAddress& rPos : rPosArray)
+ {
+ ScTable* pTab = FetchTable(rPos.Tab());
+ if (!pTab)
+ return;
+
+ pTab->EndListeningGroup(aCxt, rPos.Col(), rPos.Row());
+ }
+
+ aCxt.purgeEmptyBroadcasters();
+}
+
+void ScDocument::SetNeedsListeningGroups( const std::vector<ScAddress>& rPosArray )
+{
+ for (const ScAddress& rPos : rPosArray)
+ {
+ ScTable* pTab = FetchTable(rPos.Tab());
+ if (!pTab)
+ return;
+
+ pTab->SetNeedsListeningGroup(rPos.Col(), rPos.Row());
+ }
+}
+
+namespace {
+
+class StartNeededListenersHandler
+{
+ std::shared_ptr<sc::StartListeningContext> mpCxt;
+public:
+ explicit StartNeededListenersHandler( ScDocument& rDoc ) : mpCxt(std::make_shared<sc::StartListeningContext>(rDoc)) {}
+ explicit StartNeededListenersHandler( ScDocument& rDoc, const std::shared_ptr<const sc::ColumnSet>& rpColSet ) :
+ mpCxt(std::make_shared<sc::StartListeningContext>(rDoc))
+ {
+ mpCxt->setColumnSet( rpColSet);
+ }
+
+ void operator() (const ScTableUniquePtr & p)
+ {
+ if (p)
+ p->StartListeners(*mpCxt, false);
+ }
+};
+
+}
+
+void ScDocument::StartNeededListeners()
+{
+ std::for_each(maTabs.begin(), maTabs.end(), StartNeededListenersHandler(*this));
+}
+
+void ScDocument::StartNeededListeners( const std::shared_ptr<const sc::ColumnSet>& rpColSet )
+{
+ std::for_each(maTabs.begin(), maTabs.end(), StartNeededListenersHandler(*this, rpColSet));
+}
+
+void ScDocument::StartAllListeners( const ScRange& rRange )
+{
+ if (IsClipOrUndo() || GetNoListening())
+ return;
+
+ auto pPosSet = std::make_shared<sc::ColumnBlockPositionSet>(*this);
+ sc::StartListeningContext aStartCxt(*this, pPosSet);
+ sc::EndListeningContext aEndCxt(*this, pPosSet);
+
+ for (SCTAB nTab = rRange.aStart.Tab(); nTab <= rRange.aEnd.Tab(); ++nTab)
+ {
+ ScTable* pTab = FetchTable(nTab);
+ if (!pTab)
+ continue;
+
+ pTab->StartListeningFormulaCells(
+ aStartCxt, aEndCxt,
+ rRange.aStart.Col(), rRange.aStart.Row(), rRange.aEnd.Col(), rRange.aEnd.Row());
+ }
+}
+
+void ScDocument::finalizeOutlineImport()
+{
+ for (const auto& rxTab : maTabs)
+ {
+ ScTable* p = rxTab.get();
+ p->finalizeOutlineImport();
+ }
+}
+
+bool ScDocument::FindRangeNamesReferencingSheet( sc::UpdatedRangeNames& rIndexes,
+ SCTAB nTokenTab, const sal_uInt16 nTokenIndex,
+ SCTAB nGlobalRefTab, SCTAB nLocalRefTab, SCTAB nOldTokenTab, SCTAB nOldTokenTabReplacement,
+ bool bSameDoc, int nRecursion) const
+{
+ if (nTokenTab < -1)
+ {
+ SAL_WARN("sc.core", "ScDocument::FindRangeNamesReferencingSheet - nTokenTab < -1 : " <<
+ nTokenTab << ", nTokenIndex " << nTokenIndex << " Fix the creator!");
+#if OSL_DEBUG_LEVEL > 0
+ const ScRangeData* pData = FindRangeNameBySheetAndIndex( nTokenTab, nTokenIndex);
+ SAL_WARN_IF( pData, "sc.core", "ScDocument::FindRangeNamesReferencingSheet - named expression is: " << pData->GetName());
+#endif
+ nTokenTab = -1;
+ }
+ SCTAB nRefTab = nGlobalRefTab;
+ if (nTokenTab == nOldTokenTab)
+ {
+ nTokenTab = nOldTokenTabReplacement;
+ nRefTab = nLocalRefTab;
+ }
+ else if (nTokenTab == nOldTokenTabReplacement)
+ {
+ nRefTab = nLocalRefTab;
+ }
+
+ if (rIndexes.isNameUpdated( nTokenTab, nTokenIndex))
+ return true;
+
+ ScRangeData* pData = FindRangeNameBySheetAndIndex( nTokenTab, nTokenIndex);
+ if (!pData)
+ return false;
+
+ ScTokenArray* pCode = pData->GetCode();
+ if (!pCode)
+ return false;
+
+ bool bRef = !bSameDoc; // include every name used when copying to other doc
+ if (nRecursion < 126) // whatever... 42*3
+ {
+ formula::FormulaTokenArrayPlainIterator aIter(*pCode);
+ for (const formula::FormulaToken* p = aIter.First(); p; p = aIter.Next())
+ {
+ if (p->GetOpCode() == ocName)
+ {
+ bRef |= FindRangeNamesReferencingSheet( rIndexes, p->GetSheet(), p->GetIndex(),
+ nGlobalRefTab, nLocalRefTab, nOldTokenTab, nOldTokenTabReplacement, bSameDoc, nRecursion+1);
+ }
+ }
+ }
+
+ if (!bRef)
+ {
+ SCTAB nPosTab = pData->GetPos().Tab();
+ if (nPosTab == nOldTokenTab)
+ nPosTab = nOldTokenTabReplacement;
+ bRef = pCode->ReferencesSheet( nRefTab, nPosTab);
+ }
+ if (bRef)
+ rIndexes.setUpdatedName( nTokenTab, nTokenIndex);
+
+ return bRef;
+}
+
+namespace {
+
+enum MightReferenceSheet
+{
+ UNKNOWN,
+ NONE,
+ CODE,
+ NAME
+};
+
+MightReferenceSheet mightRangeNameReferenceSheet( ScRangeData* pData, SCTAB nRefTab)
+{
+ ScTokenArray* pCode = pData->GetCode();
+ if (!pCode)
+ return MightReferenceSheet::NONE;
+
+ formula::FormulaTokenArrayPlainIterator aIter(*pCode);
+ for (const formula::FormulaToken* p = aIter.First(); p; p = aIter.Next())
+ {
+ if (p->GetOpCode() == ocName)
+ return MightReferenceSheet::NAME;
+ }
+
+ return pCode->ReferencesSheet( nRefTab, pData->GetPos().Tab()) ?
+ MightReferenceSheet::CODE : MightReferenceSheet::NONE;
+}
+
+ScRangeData* copyRangeName( const ScRangeData* pOldRangeData, ScDocument& rNewDoc, const ScDocument& rOldDoc,
+ const ScAddress& rNewPos, const ScAddress& rOldPos, bool bGlobalNamesToLocal,
+ SCTAB nOldSheet, const SCTAB nNewSheet, bool bSameDoc)
+{
+ ScAddress aRangePos( pOldRangeData->GetPos());
+ if (nNewSheet >= 0)
+ aRangePos.SetTab( nNewSheet);
+ ScRangeData* pRangeData = new ScRangeData(*pOldRangeData, &rNewDoc, &aRangePos);
+ pRangeData->SetIndex(0); // needed for insert to assign a new index
+ ScTokenArray* pRangeNameToken = pRangeData->GetCode();
+ if (bSameDoc && nNewSheet >= 0)
+ {
+ if (bGlobalNamesToLocal && nOldSheet < 0)
+ {
+ nOldSheet = rOldPos.Tab();
+ if (rNewPos.Tab() <= nOldSheet)
+ // Sheet was inserted before and references already updated.
+ ++nOldSheet;
+ }
+ pRangeNameToken->AdjustSheetLocalNameReferences( nOldSheet, nNewSheet);
+ }
+ if (!bSameDoc)
+ {
+ pRangeNameToken->ReadjustAbsolute3DReferences(rOldDoc, rNewDoc, pRangeData->GetPos(), true);
+ pRangeNameToken->AdjustAbsoluteRefs(rOldDoc, rOldPos, rNewPos, true);
+ }
+
+ bool bInserted;
+ if (nNewSheet < 0)
+ bInserted = rNewDoc.GetRangeName()->insert(pRangeData);
+ else
+ bInserted = rNewDoc.GetRangeName(nNewSheet)->insert(pRangeData);
+
+ return bInserted ? pRangeData : nullptr;
+}
+
+struct SheetIndex
+{
+ SCTAB mnSheet;
+ sal_uInt16 mnIndex;
+
+ SheetIndex( SCTAB nSheet, sal_uInt16 nIndex ) : mnSheet(nSheet < -1 ? -1 : nSheet), mnIndex(nIndex) {}
+ bool operator<( const SheetIndex& r ) const
+ {
+ // Ascending order sheet, index
+ if (mnSheet < r.mnSheet)
+ return true;
+ if (mnSheet == r.mnSheet)
+ return mnIndex < r.mnIndex;
+ return false;
+ }
+};
+typedef std::map< SheetIndex, SheetIndex > SheetIndexMap;
+
+ScRangeData* copyRangeNames( SheetIndexMap& rSheetIndexMap, std::vector<ScRangeData*>& rRangeDataVec,
+ const sc::UpdatedRangeNames& rReferencingNames, SCTAB nTab,
+ const ScRangeData* pOldRangeData, ScDocument& rNewDoc, const ScDocument& rOldDoc,
+ const ScAddress& rNewPos, const ScAddress& rOldPos, bool bGlobalNamesToLocal,
+ const SCTAB nOldSheet, const SCTAB nNewSheet, bool bSameDoc)
+{
+ ScRangeData* pRangeData = nullptr;
+ const ScRangeName* pOldRangeName = (nTab < 0 ? rOldDoc.GetRangeName() : rOldDoc.GetRangeName(nTab));
+ if (pOldRangeName)
+ {
+ const ScRangeName* pNewRangeName = (nNewSheet < 0 ? rNewDoc.GetRangeName() : rNewDoc.GetRangeName(nNewSheet));
+ sc::UpdatedRangeNames::NameIndicesType aSet( rReferencingNames.getUpdatedNames(nTab));
+ for (auto const & rIndex : aSet)
+ {
+ const ScRangeData* pCopyData = pOldRangeName->findByIndex(rIndex);
+ if (pCopyData)
+ {
+ // Match the original pOldRangeData to adapt the current
+ // token's values later. For that no check for an already
+ // copied name is needed as we only enter here if there was
+ // none.
+ if (pCopyData == pOldRangeData)
+ {
+ pRangeData = copyRangeName( pCopyData, rNewDoc, rOldDoc, rNewPos, rOldPos,
+ bGlobalNamesToLocal, nOldSheet, nNewSheet, bSameDoc);
+ if (pRangeData)
+ {
+ rRangeDataVec.push_back(pRangeData);
+ rSheetIndexMap.insert( std::make_pair( SheetIndex( nOldSheet, pCopyData->GetIndex()),
+ SheetIndex( nNewSheet, pRangeData->GetIndex())));
+ }
+ }
+ else
+ {
+ // First check if the name is already available as copy.
+ const ScRangeData* pFoundData = pNewRangeName->findByUpperName( pCopyData->GetUpperName());
+ if (pFoundData)
+ {
+ // Just add the resulting sheet/index mapping.
+ rSheetIndexMap.insert( std::make_pair( SheetIndex( nOldSheet, pCopyData->GetIndex()),
+ SheetIndex( nNewSheet, pFoundData->GetIndex())));
+ }
+ else
+ {
+ ScRangeData* pTmpData = copyRangeName( pCopyData, rNewDoc, rOldDoc, rNewPos, rOldPos,
+ bGlobalNamesToLocal, nOldSheet, nNewSheet, bSameDoc);
+ if (pTmpData)
+ {
+ rRangeDataVec.push_back(pTmpData);
+ rSheetIndexMap.insert( std::make_pair( SheetIndex( nOldSheet, pCopyData->GetIndex()),
+ SheetIndex( nNewSheet, pTmpData->GetIndex())));
+ }
+ }
+ }
+ }
+ }
+ }
+ return pRangeData;
+}
+
+} // namespace
+
+bool ScDocument::CopyAdjustRangeName( SCTAB& rSheet, sal_uInt16& rIndex, ScRangeData*& rpRangeData,
+ ScDocument& rNewDoc, const ScAddress& rNewPos, const ScAddress& rOldPos, const bool bGlobalNamesToLocal,
+ const bool bUsedByFormula ) const
+{
+ ScDocument* pThis = const_cast<ScDocument*>(this);
+ const bool bSameDoc = (rNewDoc.GetPool() == pThis->GetPool());
+ if (bSameDoc && ((rSheet < 0 && !bGlobalNamesToLocal) || (rSheet >= 0
+ && (rSheet != rOldPos.Tab() || (IsClipboard() && pThis->IsCutMode())))))
+ // Same doc and global name, if not copied to local name, or
+ // sheet-local name on other sheet stays the same. Sheet-local on
+ // same sheet also in a clipboard cut&paste / move operation.
+ return false;
+
+ // Ensure we don't fiddle with the references until exit.
+ const SCTAB nOldSheet = rSheet;
+ const sal_uInt16 nOldIndex = rIndex;
+
+ SAL_WARN_IF( !bSameDoc && nOldSheet >= 0 && nOldSheet != rOldPos.Tab(),
+ "sc.core", "adjustCopyRangeName - sheet-local name was on other sheet in other document");
+ /* TODO: can we do something about that? e.g. loop over sheets? */
+
+ OUString aRangeName;
+ ScRangeData* pOldRangeData = nullptr;
+
+ // XXX bGlobalNamesToLocal is also a synonym for copied sheet.
+ bool bInsertingBefore = (bGlobalNamesToLocal && bSameDoc && rNewPos.Tab() <= rOldPos.Tab());
+
+ // The Tab where an old local name is to be found or that a global name
+ // references. May differ below from nOldSheet if a sheet was inserted
+ // before the old position. Global names and local names other than on the
+ // old sheet or new sheet are already updated, local names on the old sheet
+ // or inserted sheet will be updated later. Confusing stuff. Watch out.
+ SCTAB nOldTab = (nOldSheet < 0 ? rOldPos.Tab() : nOldSheet);
+ if (bInsertingBefore)
+ // Sheet was already inserted before old position.
+ ++nOldTab;
+
+ // Search the name of the RangeName.
+ if (nOldSheet >= 0)
+ {
+ const ScRangeName* pNames = GetRangeName(nOldTab);
+ pOldRangeData = pNames ? pNames->findByIndex(nOldIndex) : nullptr;
+ if (!pOldRangeData)
+ return false; // might be an error in the formula array
+ aRangeName = pOldRangeData->GetUpperName();
+ }
+ else
+ {
+ pOldRangeData = GetRangeName()->findByIndex(nOldIndex);
+ if (!pOldRangeData)
+ return false; // might be an error in the formula array
+ aRangeName = pOldRangeData->GetUpperName();
+ }
+
+ // Find corresponding range name in new document.
+ // First search for local range name then global range names.
+ SCTAB nNewSheet = rNewPos.Tab();
+ ScRangeName* pNewNames = rNewDoc.GetRangeName(nNewSheet);
+ // Search local range names.
+ if (pNewNames)
+ {
+ rpRangeData = pNewNames->findByUpperName(aRangeName);
+ }
+ // Search global range names.
+ if (!rpRangeData && !bGlobalNamesToLocal)
+ {
+ nNewSheet = -1;
+ pNewNames = rNewDoc.GetRangeName();
+ if (pNewNames)
+ rpRangeData = pNewNames->findByUpperName(aRangeName);
+ }
+ // If no range name was found copy it.
+ if (!rpRangeData)
+ {
+ // Do not copy global name if it doesn't reference sheet or is not used
+ // by a formula copied to another document.
+ bool bEarlyBailOut = (nOldSheet < 0 && (bSameDoc || !bUsedByFormula));
+ MightReferenceSheet eMightReference = mightRangeNameReferenceSheet( pOldRangeData, nOldTab);
+ if (bEarlyBailOut && eMightReference == MightReferenceSheet::NONE)
+ return false;
+
+ if (eMightReference == MightReferenceSheet::NAME)
+ {
+ // Name these to clarify what is passed where.
+ const SCTAB nGlobalRefTab = nOldTab;
+ const SCTAB nLocalRefTab = (bInsertingBefore ? nOldTab-1 : nOldTab);
+ const SCTAB nOldTokenTab = (nOldSheet < 0 ? (bInsertingBefore ? nOldTab-1 : nOldTab) : nOldSheet);
+ const SCTAB nOldTokenTabReplacement = nOldTab;
+ sc::UpdatedRangeNames aReferencingNames;
+ FindRangeNamesReferencingSheet( aReferencingNames, nOldSheet, nOldIndex,
+ nGlobalRefTab, nLocalRefTab, nOldTokenTab, nOldTokenTabReplacement, bSameDoc, 0);
+ if (bEarlyBailOut && aReferencingNames.isEmpty(-1) && aReferencingNames.isEmpty(nOldTokenTabReplacement))
+ return false;
+
+ SheetIndexMap aSheetIndexMap;
+ std::vector<ScRangeData*> aRangeDataVec;
+ if (!aReferencingNames.isEmpty(nOldTokenTabReplacement))
+ {
+ const SCTAB nTmpOldSheet = (nOldSheet < 0 ? nOldTab : nOldSheet);
+ nNewSheet = rNewPos.Tab();
+ rpRangeData = copyRangeNames( aSheetIndexMap, aRangeDataVec, aReferencingNames, nOldTab,
+ pOldRangeData, rNewDoc, *this, rNewPos, rOldPos,
+ bGlobalNamesToLocal, nTmpOldSheet, nNewSheet, bSameDoc);
+ }
+ if ((bGlobalNamesToLocal || !bSameDoc) && !aReferencingNames.isEmpty(-1))
+ {
+ const SCTAB nTmpOldSheet = -1;
+ const SCTAB nTmpNewSheet = (bGlobalNamesToLocal ? rNewPos.Tab() : -1);
+ ScRangeData* pTmpData = copyRangeNames( aSheetIndexMap, aRangeDataVec, aReferencingNames, -1,
+ pOldRangeData, rNewDoc, *this, rNewPos, rOldPos,
+ bGlobalNamesToLocal, nTmpOldSheet, nTmpNewSheet, bSameDoc);
+ if (!rpRangeData)
+ {
+ rpRangeData = pTmpData;
+ nNewSheet = nTmpNewSheet;
+ }
+ }
+
+ // Adjust copied nested names to new sheet/index.
+ for (auto & iRD : aRangeDataVec)
+ {
+ ScTokenArray* pCode = iRD->GetCode();
+ if (pCode)
+ {
+ formula::FormulaTokenArrayPlainIterator aIter(*pCode);
+ for (formula::FormulaToken* p = aIter.First(); p; p = aIter.Next())
+ {
+ if (p->GetOpCode() == ocName)
+ {
+ auto it = aSheetIndexMap.find( SheetIndex( p->GetSheet(), p->GetIndex()));
+ if (it != aSheetIndexMap.end())
+ {
+ p->SetSheet( it->second.mnSheet);
+ p->SetIndex( it->second.mnIndex);
+ }
+ else if (!bSameDoc)
+ {
+ SAL_WARN("sc.core","adjustCopyRangeName - mapping to new name in other doc missing");
+ p->SetIndex(0); // #NAME? error instead of arbitrary name.
+ }
+ }
+ }
+ }
+ }
+ }
+ else
+ {
+ nNewSheet = ((nOldSheet < 0 && !bGlobalNamesToLocal) ? -1 : rNewPos.Tab());
+ rpRangeData = copyRangeName( pOldRangeData, rNewDoc, *this, rNewPos, rOldPos, bGlobalNamesToLocal,
+ nOldSheet, nNewSheet, bSameDoc);
+ }
+
+ if (rpRangeData && !rNewDoc.IsClipOrUndo())
+ {
+ ScDocShell* pDocSh = static_cast<ScDocShell*>(rNewDoc.GetDocumentShell());
+ if (pDocSh)
+ pDocSh->SetAreasChangedNeedBroadcast();
+ }
+ }
+
+ rSheet = nNewSheet;
+ rIndex = rpRangeData ? rpRangeData->GetIndex() : 0; // 0 means not inserted
+ return true;
+}
+
+bool ScDocument::IsEditActionAllowed(
+ sc::ColRowEditAction eAction, SCTAB nTab, SCCOLROW nStart, SCCOLROW nEnd ) const
+{
+ const ScTable* pTab = FetchTable(nTab);
+ if (!pTab)
+ return false;
+
+ return pTab->IsEditActionAllowed(eAction, nStart, nEnd);
+}
+
+bool ScDocument::IsEditActionAllowed(
+ sc::ColRowEditAction eAction, const ScMarkData& rMark, SCCOLROW nStart, SCCOLROW nEnd ) const
+{
+ return std::all_of(rMark.begin(), rMark.end(),
+ [this, &eAction, &nStart, &nEnd](const SCTAB& rTab) { return IsEditActionAllowed(eAction, rTab, nStart, nEnd); });
+}
+
+std::optional<sc::ColumnIterator> ScDocument::GetColumnIterator( SCTAB nTab, SCCOL nCol, SCROW nRow1, SCROW nRow2 ) const
+{
+ const ScTable* pTab = FetchTable(nTab);
+ if (!pTab)
+ return {};
+
+ return pTab->GetColumnIterator(nCol, nRow1, nRow2);
+}
+
+void ScDocument::CreateColumnIfNotExists( SCTAB nTab, SCCOL nCol )
+{
+ ScTable* pTab = FetchTable(nTab);
+ if (!pTab)
+ return;
+
+ pTab->CreateColumnIfNotExists(nCol);
+}
+
+bool ScDocument::EnsureFormulaCellResults( const ScRange& rRange, bool bSkipRunning )
+{
+ bool bAnyDirty = false;
+ for (SCTAB nTab = rRange.aStart.Tab(); nTab <= rRange.aEnd.Tab(); ++nTab)
+ {
+ ScTable* pTab = FetchTable(nTab);
+ if (!pTab)
+ continue;
+
+ bool bRet = pTab->EnsureFormulaCellResults(
+ rRange.aStart.Col(), rRange.aStart.Row(), rRange.aEnd.Col(), rRange.aEnd.Row(), bSkipRunning);
+ bAnyDirty = bAnyDirty || bRet;
+ }
+
+ return bAnyDirty;
+}
+
+sc::ExternalDataMapper& ScDocument::GetExternalDataMapper()
+{
+ if (!mpDataMapper)
+ mpDataMapper.reset(new sc::ExternalDataMapper(*this));
+
+ return *mpDataMapper;
+}
+
+void ScDocument::StoreTabToCache(SCTAB nTab, SvStream& rStrm) const
+{
+ const ScTable* pTab = FetchTable(nTab);
+ if (!pTab)
+ return;
+
+ pTab->StoreToCache(rStrm);
+}
+
+void ScDocument::RestoreTabFromCache(SCTAB nTab, SvStream& rStrm)
+{
+ ScTable* pTab = FetchTable(nTab);
+ if (!pTab)
+ return;
+
+ pTab->RestoreFromCache(rStrm);
+}
+
+OString ScDocument::dumpSheetGeomData(SCTAB nTab, bool bColumns, SheetGeomType eGeomType)
+{
+ ScTable* pTab = FetchTable(nTab);
+ if (!pTab)
+ return "";
+
+ return pTab->dumpSheetGeomData(bColumns, eGeomType);
+}
+
+SCCOL ScDocument::GetLOKFreezeCol(SCTAB nTab) const
+{
+ const ScTable* pTab = FetchTable(nTab);
+ if (!pTab)
+ return -1;
+
+ return pTab->GetLOKFreezeCol();
+}
+SCROW ScDocument::GetLOKFreezeRow(SCTAB nTab) const
+{
+ const ScTable* pTab = FetchTable(nTab);
+ if (!pTab)
+ return -1;
+
+ return pTab->GetLOKFreezeRow();
+}
+
+bool ScDocument::SetLOKFreezeCol(SCCOL nFreezeCol, SCTAB nTab)
+{
+ ScTable* pTab = FetchTable(nTab);
+ if (!pTab)
+ return false;
+
+ return pTab->SetLOKFreezeCol(nFreezeCol);
+}
+
+bool ScDocument::SetLOKFreezeRow(SCROW nFreezeRow, SCTAB nTab)
+{
+ ScTable* pTab = FetchTable(nTab);
+ if (!pTab)
+ return false;
+
+ return pTab->SetLOKFreezeRow(nFreezeRow);
+}
+
+std::set<SCCOL> ScDocument::QueryColumnsWithFormulaCells( SCTAB nTab ) const
+{
+ const ScTable* pTab = FetchTable(nTab);
+ if (!pTab)
+ return std::set<SCCOL>{};
+
+ return pTab->QueryColumnsWithFormulaCells();
+}
+
+void ScDocument::CheckIntegrity( SCTAB nTab ) const
+{
+ const ScTable* pTab = FetchTable(nTab);
+ if (!pTab)
+ return;
+
+ pTab->CheckIntegrity();
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sc/source/core/data/documentimport.cxx b/sc/source/core/data/documentimport.cxx
new file mode 100644
index 000000000..7ce2d0a00
--- /dev/null
+++ b/sc/source/core/data/documentimport.cxx
@@ -0,0 +1,859 @@
+/* -*- 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/.
+ */
+
+#include <documentimport.hxx>
+#include <document.hxx>
+#include <table.hxx>
+#include <column.hxx>
+#include <formulacell.hxx>
+#include <docoptio.hxx>
+#include <mtvelements.hxx>
+#include <tokenarray.hxx>
+#include <stringutil.hxx>
+#include <compiler.hxx>
+#include <paramisc.hxx>
+#include <listenercontext.hxx>
+#include <attarray.hxx>
+#include <sharedformula.hxx>
+#include <bcaslot.hxx>
+#include <scopetools.hxx>
+#include <numformat.hxx>
+
+#include <o3tl/safeint.hxx>
+#include <svl/sharedstringpool.hxx>
+#include <svl/languageoptions.hxx>
+#include <unotools/configmgr.hxx>
+#include <unordered_map>
+
+namespace {
+
+struct ColAttr
+{
+ bool mbLatinNumFmtOnly;
+
+ ColAttr() : mbLatinNumFmtOnly(false) {}
+};
+
+struct TabAttr
+{
+ std::vector<ColAttr> maCols;
+};
+
+}
+
+struct ScDocumentImportImpl
+{
+ ScDocument& mrDoc;
+ sc::StartListeningContext maListenCxt;
+ std::vector<sc::TableColumnBlockPositionSet> maBlockPosSet;
+ SvtScriptType mnDefaultScriptNumeric;
+ bool mbFuzzing;
+ std::vector<TabAttr> maTabAttrs;
+ std::unordered_map<sal_uInt32, bool> maIsLatinScriptMap;
+
+ explicit ScDocumentImportImpl(ScDocument& rDoc) :
+ mrDoc(rDoc),
+ maListenCxt(rDoc),
+ mnDefaultScriptNumeric(SvtScriptType::UNKNOWN),
+ mbFuzzing(utl::ConfigManager::IsFuzzing())
+ {}
+
+ bool isValid( size_t nTab, size_t nCol )
+ {
+ return (nTab <= o3tl::make_unsigned(MAXTAB) && nCol <= o3tl::make_unsigned(mrDoc.MaxCol()));
+ }
+
+ ColAttr* getColAttr( size_t nTab, size_t nCol )
+ {
+ if (!isValid(nTab, nCol))
+ return nullptr;
+
+ if (nTab >= maTabAttrs.size())
+ maTabAttrs.resize(nTab+1);
+
+ TabAttr& rTab = maTabAttrs[nTab];
+ if (nCol >= rTab.maCols.size())
+ rTab.maCols.resize(nCol+1);
+
+ return &rTab.maCols[nCol];
+ }
+
+ sc::ColumnBlockPosition* getBlockPosition( SCTAB nTab, SCCOL nCol )
+ {
+ if (!isValid(nTab, nCol))
+ return nullptr;
+
+ if (o3tl::make_unsigned(nTab) >= maBlockPosSet.size())
+ {
+ for (SCTAB i = maBlockPosSet.size(); i <= nTab; ++i)
+ maBlockPosSet.emplace_back(mrDoc, i);
+ }
+
+ sc::TableColumnBlockPositionSet& rTab = maBlockPosSet[nTab];
+ return rTab.getBlockPosition(nCol);
+ }
+
+ void invalidateBlockPositionSet(SCTAB nTab)
+ {
+ if (o3tl::make_unsigned(nTab) >= maBlockPosSet.size())
+ return;
+
+ sc::TableColumnBlockPositionSet& rTab = maBlockPosSet[nTab];
+ rTab.invalidate();
+ }
+
+ void initForSheets()
+ {
+ size_t n = mrDoc.GetTableCount();
+ for (size_t i = maBlockPosSet.size(); i < n; ++i)
+ maBlockPosSet.emplace_back(mrDoc, i);
+
+ if (maTabAttrs.size() < n)
+ maTabAttrs.resize(n);
+ }
+};
+
+ScDocumentImport::Attrs::Attrs() : mbLatinNumFmtOnly(false) {}
+
+ScDocumentImport::Attrs::~Attrs() {}
+
+ScDocumentImport::ScDocumentImport(ScDocument& rDoc) : mpImpl(new ScDocumentImportImpl(rDoc)) {}
+
+ScDocumentImport::~ScDocumentImport()
+{
+}
+
+ScDocument& ScDocumentImport::getDoc()
+{
+ return mpImpl->mrDoc;
+}
+
+const ScDocument& ScDocumentImport::getDoc() const
+{
+ return mpImpl->mrDoc;
+}
+
+void ScDocumentImport::initForSheets()
+{
+ mpImpl->initForSheets();
+}
+
+void ScDocumentImport::setDefaultNumericScript(SvtScriptType nScript)
+{
+ mpImpl->mnDefaultScriptNumeric = nScript;
+}
+
+void ScDocumentImport::setCellStyleToSheet(SCTAB nTab, const ScStyleSheet& rStyle)
+{
+ ScTable* pTab = mpImpl->mrDoc.FetchTable(nTab);
+ if (!pTab)
+ return;
+
+ pTab->ApplyStyleArea(0, 0, getDoc().MaxCol(), getDoc().MaxRow(), rStyle);
+}
+
+SCTAB ScDocumentImport::getSheetIndex(const OUString& rName) const
+{
+ SCTAB nTab = -1;
+ if (!mpImpl->mrDoc.GetTable(rName, nTab))
+ return -1;
+
+ return nTab;
+}
+
+SCTAB ScDocumentImport::getSheetCount() const
+{
+ return mpImpl->mrDoc.maTabs.size();
+}
+
+bool ScDocumentImport::appendSheet(const OUString& rName)
+{
+ SCTAB nTabCount = mpImpl->mrDoc.maTabs.size();
+ if (!ValidTab(nTabCount))
+ return false;
+
+ mpImpl->mrDoc.maTabs.emplace_back(new ScTable(mpImpl->mrDoc, nTabCount, rName));
+ return true;
+}
+
+void ScDocumentImport::setSheetName(SCTAB nTab, const OUString& rName)
+{
+ mpImpl->mrDoc.SetTabNameOnLoad(nTab, rName);
+}
+
+void ScDocumentImport::setOriginDate(sal_uInt16 nYear, sal_uInt16 nMonth, sal_uInt16 nDay)
+{
+ if (!mpImpl->mrDoc.pDocOptions)
+ mpImpl->mrDoc.pDocOptions.reset( new ScDocOptions );
+
+ mpImpl->mrDoc.pDocOptions->SetDate(nDay, nMonth, nYear);
+}
+
+void ScDocumentImport::invalidateBlockPositionSet(SCTAB nTab)
+{
+ mpImpl->invalidateBlockPositionSet(nTab);
+}
+
+void ScDocumentImport::setAutoInput(const ScAddress& rPos, const OUString& rStr, const ScSetStringParam* pStringParam)
+{
+ ScTable* pTab = mpImpl->mrDoc.FetchTable(rPos.Tab());
+ if (!pTab)
+ return;
+
+ sc::ColumnBlockPosition* pBlockPos = mpImpl->getBlockPosition(rPos.Tab(), rPos.Col());
+
+ if (!pBlockPos)
+ return;
+
+ // If ScSetStringParam was given, ScColumn::ParseString() shall take care
+ // of checking. Ensure caller said so.
+ assert(!pStringParam || pStringParam->mbCheckLinkFormula);
+
+ ScCellValue aCell;
+ pTab->aCol[rPos.Col()].ParseString(
+ aCell, rPos.Row(), rPos.Tab(), rStr, mpImpl->mrDoc.GetAddressConvention(), pStringParam);
+
+ sc::CellStoreType& rCells = pTab->aCol[rPos.Col()].maCells;
+ switch (aCell.meType)
+ {
+ case CELLTYPE_STRING:
+ // string is copied.
+ pBlockPos->miCellPos = rCells.set(pBlockPos->miCellPos, rPos.Row(), *aCell.mpString);
+ break;
+ case CELLTYPE_EDIT:
+ // Cell takes the ownership of the text object.
+ pBlockPos->miCellPos = rCells.set(pBlockPos->miCellPos, rPos.Row(), aCell.mpEditText);
+ aCell.mpEditText = nullptr;
+ break;
+ case CELLTYPE_VALUE:
+ pBlockPos->miCellPos = rCells.set(pBlockPos->miCellPos, rPos.Row(), aCell.mfValue);
+ break;
+ case CELLTYPE_FORMULA:
+ if (!pStringParam)
+ mpImpl->mrDoc.CheckLinkFormulaNeedingCheck( *aCell.mpFormula->GetCode());
+ // This formula cell instance is directly placed in the document without copying.
+ pBlockPos->miCellPos = rCells.set(pBlockPos->miCellPos, rPos.Row(), aCell.mpFormula);
+ aCell.mpFormula = nullptr;
+ break;
+ default:
+ pBlockPos->miCellPos = rCells.set_empty(pBlockPos->miCellPos, rPos.Row(), rPos.Row());
+ }
+}
+
+void ScDocumentImport::setNumericCell(const ScAddress& rPos, double fVal)
+{
+ ScTable* pTab = mpImpl->mrDoc.FetchTable(rPos.Tab());
+ if (!pTab)
+ return;
+
+ sc::ColumnBlockPosition* pBlockPos = mpImpl->getBlockPosition(rPos.Tab(), rPos.Col());
+
+ if (!pBlockPos)
+ return;
+
+ sc::CellStoreType& rCells = pTab->aCol[rPos.Col()].maCells;
+ pBlockPos->miCellPos = rCells.set(pBlockPos->miCellPos, rPos.Row(), fVal);
+}
+
+void ScDocumentImport::setStringCell(const ScAddress& rPos, const OUString& rStr)
+{
+ ScTable* pTab = mpImpl->mrDoc.FetchTable(rPos.Tab());
+ if (!pTab)
+ return;
+
+ sc::ColumnBlockPosition* pBlockPos = mpImpl->getBlockPosition(rPos.Tab(), rPos.Col());
+
+ if (!pBlockPos)
+ return;
+
+ svl::SharedString aSS = mpImpl->mrDoc.GetSharedStringPool().intern(rStr);
+ if (!aSS.getData())
+ return;
+
+ sc::CellStoreType& rCells = pTab->aCol[rPos.Col()].maCells;
+ pBlockPos->miCellPos = rCells.set(pBlockPos->miCellPos, rPos.Row(), aSS);
+}
+
+void ScDocumentImport::setEditCell(const ScAddress& rPos, std::unique_ptr<EditTextObject> pEditText)
+{
+ ScTable* pTab = mpImpl->mrDoc.FetchTable(rPos.Tab());
+ if (!pTab)
+ return;
+
+ sc::ColumnBlockPosition* pBlockPos = mpImpl->getBlockPosition(rPos.Tab(), rPos.Col());
+
+ if (!pBlockPos)
+ return;
+
+ pEditText->NormalizeString(mpImpl->mrDoc.GetSharedStringPool());
+ sc::CellStoreType& rCells = pTab->aCol[rPos.Col()].maCells;
+ pBlockPos->miCellPos = rCells.set(pBlockPos->miCellPos, rPos.Row(), pEditText.release());
+}
+
+void ScDocumentImport::setFormulaCell(
+ const ScAddress& rPos, const OUString& rFormula, formula::FormulaGrammar::Grammar eGrammar,
+ const double* pResult )
+{
+ ScTable* pTab = mpImpl->mrDoc.FetchTable(rPos.Tab());
+ if (!pTab)
+ return;
+
+ sc::ColumnBlockPosition* pBlockPos = mpImpl->getBlockPosition(rPos.Tab(), rPos.Col());
+
+ if (!pBlockPos)
+ return;
+
+ std::unique_ptr<ScFormulaCell> pFC =
+ std::make_unique<ScFormulaCell>(mpImpl->mrDoc, rPos, rFormula, eGrammar);
+
+ mpImpl->mrDoc.CheckLinkFormulaNeedingCheck( *pFC->GetCode());
+
+ if (pResult)
+ {
+ // Set cached result to this formula cell.
+ pFC->SetResultDouble(*pResult);
+ }
+
+ sc::CellStoreType& rCells = pTab->aCol[rPos.Col()].maCells;
+ pBlockPos->miCellPos =
+ rCells.set(pBlockPos->miCellPos, rPos.Row(), pFC.release());
+}
+
+void ScDocumentImport::setFormulaCell(
+ const ScAddress& rPos, const OUString& rFormula, formula::FormulaGrammar::Grammar eGrammar,
+ const OUString& rResult )
+{
+ ScTable* pTab = mpImpl->mrDoc.FetchTable(rPos.Tab());
+ if (!pTab)
+ return;
+
+ sc::ColumnBlockPosition* pBlockPos = mpImpl->getBlockPosition(rPos.Tab(), rPos.Col());
+
+ if (!pBlockPos)
+ return;
+
+ std::unique_ptr<ScFormulaCell> pFC =
+ std::make_unique<ScFormulaCell>(mpImpl->mrDoc, rPos, rFormula, eGrammar);
+
+ mpImpl->mrDoc.CheckLinkFormulaNeedingCheck( *pFC->GetCode());
+
+ // Set cached result to this formula cell.
+ pFC->SetHybridString(mpImpl->mrDoc.GetSharedStringPool().intern(rResult));
+
+ sc::CellStoreType& rCells = pTab->aCol[rPos.Col()].maCells;
+ pBlockPos->miCellPos =
+ rCells.set(pBlockPos->miCellPos, rPos.Row(), pFC.release());
+}
+
+void ScDocumentImport::setFormulaCell(const ScAddress& rPos, std::unique_ptr<ScTokenArray> pArray)
+{
+ ScTable* pTab = mpImpl->mrDoc.FetchTable(rPos.Tab());
+ if (!pTab)
+ return;
+
+ sc::ColumnBlockPosition* pBlockPos = mpImpl->getBlockPosition(rPos.Tab(), rPos.Col());
+
+ if (!pBlockPos)
+ return;
+
+ std::unique_ptr<ScFormulaCell> pFC =
+ std::make_unique<ScFormulaCell>(mpImpl->mrDoc, rPos, std::move(pArray));
+
+ mpImpl->mrDoc.CheckLinkFormulaNeedingCheck( *pFC->GetCode());
+
+ sc::CellStoreType& rCells = pTab->aCol[rPos.Col()].maCells;
+ pBlockPos->miCellPos =
+ rCells.set(pBlockPos->miCellPos, rPos.Row(), pFC.release());
+}
+
+void ScDocumentImport::setFormulaCell(const ScAddress& rPos, ScFormulaCell* pCell)
+{
+ ScTable* pTab = mpImpl->mrDoc.FetchTable(rPos.Tab());
+ if (!pTab)
+ return;
+
+ sc::ColumnBlockPosition* pBlockPos = mpImpl->getBlockPosition(rPos.Tab(), rPos.Col());
+
+ if (!pBlockPos)
+ return;
+
+ if (pCell)
+ mpImpl->mrDoc.CheckLinkFormulaNeedingCheck( *pCell->GetCode());
+
+ sc::CellStoreType& rCells = pTab->aCol[rPos.Col()].maCells;
+
+ sc::CellStoreType::position_type aPos = rCells.position(rPos.Row());
+ if (aPos.first != rCells.end() && aPos.first->type == sc::element_type_formula)
+ {
+ ScFormulaCell* p = sc::formula_block::at(*aPos.first->data, aPos.second);
+ sc::SharedFormulaUtil::unshareFormulaCell(aPos, *p);
+ }
+
+ pBlockPos->miCellPos =
+ rCells.set(pBlockPos->miCellPos, rPos.Row(), pCell);
+}
+
+void ScDocumentImport::setMatrixCells(
+ const ScRange& rRange, const ScTokenArray& rArray, formula::FormulaGrammar::Grammar eGram)
+{
+ const ScAddress& rBasePos = rRange.aStart;
+
+ ScTable* pTab = mpImpl->mrDoc.FetchTable(rBasePos.Tab());
+ if (!pTab)
+ return;
+
+ sc::ColumnBlockPosition* pBlockPos = mpImpl->getBlockPosition(rBasePos.Tab(), rBasePos.Col());
+
+ if (!pBlockPos)
+ return;
+
+ if (utl::ConfigManager::IsFuzzing()) //just too slow
+ return;
+
+ sc::CellStoreType& rCells = pTab->aCol[rBasePos.Col()].maCells;
+
+ // Set the master cell.
+ ScFormulaCell* pCell = new ScFormulaCell(mpImpl->mrDoc, rBasePos, rArray, eGram, ScMatrixMode::Formula);
+
+ mpImpl->mrDoc.CheckLinkFormulaNeedingCheck( *pCell->GetCode());
+
+ pBlockPos->miCellPos =
+ rCells.set(pBlockPos->miCellPos, rBasePos.Row(), pCell);
+
+ // Matrix formulas currently need re-calculation on import.
+ pCell->SetMatColsRows(
+ rRange.aEnd.Col()-rRange.aStart.Col()+1, rRange.aEnd.Row()-rRange.aStart.Row()+1);
+
+ // Set the reference cells.
+ ScSingleRefData aRefData;
+ aRefData.InitFlags();
+ aRefData.SetColRel(true);
+ aRefData.SetRowRel(true);
+ aRefData.SetTabRel(true);
+ aRefData.SetAddress(mpImpl->mrDoc.GetSheetLimits(), rBasePos, rBasePos);
+
+ ScTokenArray aArr(mpImpl->mrDoc); // consists only of one single reference token.
+ formula::FormulaToken* t = aArr.AddMatrixSingleReference(aRefData);
+
+ ScAddress aPos = rBasePos;
+ for (SCROW nRow = rRange.aStart.Row()+1; nRow <= rRange.aEnd.Row(); ++nRow)
+ {
+ // Token array must be cloned so that each formula cell receives its own copy.
+ aPos.SetRow(nRow);
+ // Reference in each cell must point to the origin cell relative to the current cell.
+ aRefData.SetAddress(mpImpl->mrDoc.GetSheetLimits(), rBasePos, aPos);
+ *t->GetSingleRef() = aRefData;
+ ScTokenArray aTokArr(aArr.CloneValue());
+ pCell = new ScFormulaCell(mpImpl->mrDoc, aPos, aTokArr, eGram, ScMatrixMode::Reference);
+ pBlockPos->miCellPos =
+ rCells.set(pBlockPos->miCellPos, aPos.Row(), pCell);
+ }
+
+ for (SCCOL nCol = rRange.aStart.Col()+1; nCol <= rRange.aEnd.Col(); ++nCol)
+ {
+ pBlockPos = mpImpl->getBlockPosition(rBasePos.Tab(), nCol);
+ if (!pBlockPos)
+ return;
+
+ sc::CellStoreType& rColCells = pTab->aCol[nCol].maCells;
+
+ aPos.SetCol(nCol);
+ for (SCROW nRow = rRange.aStart.Row(); nRow <= rRange.aEnd.Row(); ++nRow)
+ {
+ aPos.SetRow(nRow);
+ aRefData.SetAddress(mpImpl->mrDoc.GetSheetLimits(), rBasePos, aPos);
+ *t->GetSingleRef() = aRefData;
+ ScTokenArray aTokArr(aArr.CloneValue());
+ pCell = new ScFormulaCell(mpImpl->mrDoc, aPos, aTokArr, eGram, ScMatrixMode::Reference);
+ pBlockPos->miCellPos =
+ rColCells.set(pBlockPos->miCellPos, aPos.Row(), pCell);
+ }
+ }
+}
+
+void ScDocumentImport::setTableOpCells(const ScRange& rRange, const ScTabOpParam& rParam)
+{
+ SCTAB nTab = rRange.aStart.Tab();
+ SCCOL nCol1 = rRange.aStart.Col();
+ SCROW nRow1 = rRange.aStart.Row();
+ SCCOL nCol2 = rRange.aEnd.Col();
+ SCROW nRow2 = rRange.aEnd.Row();
+
+ ScTable* pTab = mpImpl->mrDoc.FetchTable(nTab);
+ if (!pTab)
+ return;
+
+ ScDocument& rDoc = mpImpl->mrDoc;
+ ScRefAddress aRef;
+ OUStringBuffer aFormulaBuf;
+ aFormulaBuf.append('=');
+ aFormulaBuf.append(ScCompiler::GetNativeSymbol(ocTableOp));
+ aFormulaBuf.append(ScCompiler::GetNativeSymbol(ocOpen));
+
+ OUString aSep = ScCompiler::GetNativeSymbol(ocSep);
+ if (rParam.meMode == ScTabOpParam::Column) // column only
+ {
+ aRef.Set(rParam.aRefFormulaCell.GetAddress(), true, false, false);
+ aFormulaBuf.append(aRef.GetRefString(rDoc, nTab));
+ aFormulaBuf.append(aSep);
+ aFormulaBuf.append(rParam.aRefColCell.GetRefString(rDoc, nTab));
+ aFormulaBuf.append(aSep);
+ aRef.Set(nCol1, nRow1, nTab, false, true, true);
+ aFormulaBuf.append(aRef.GetRefString(rDoc, nTab));
+ nCol1++;
+ nCol2 = std::min( nCol2, static_cast<SCCOL>(rParam.aRefFormulaEnd.Col() -
+ rParam.aRefFormulaCell.Col() + nCol1 + 1));
+ }
+ else if (rParam.meMode == ScTabOpParam::Row) // row only
+ {
+ aRef.Set(rParam.aRefFormulaCell.GetAddress(), false, true, false);
+ aFormulaBuf.append(aRef.GetRefString(rDoc, nTab));
+ aFormulaBuf.append(aSep);
+ aFormulaBuf.append(rParam.aRefRowCell.GetRefString(rDoc, nTab));
+ aFormulaBuf.append(aSep);
+ aRef.Set(nCol1, nRow1, nTab, true, false, true);
+ aFormulaBuf.append(aRef.GetRefString(rDoc, nTab));
+ ++nRow1;
+ nRow2 = std::min(
+ nRow2, rParam.aRefFormulaEnd.Row() - rParam.aRefFormulaCell.Row() + nRow1 + 1);
+ }
+ else // both
+ {
+ aFormulaBuf.append(rParam.aRefFormulaCell.GetRefString(rDoc, nTab));
+ aFormulaBuf.append(aSep);
+ aFormulaBuf.append(rParam.aRefColCell.GetRefString(rDoc, nTab));
+ aFormulaBuf.append(aSep);
+ aRef.Set(nCol1, nRow1 + 1, nTab, false, true, true);
+ aFormulaBuf.append(aRef.GetRefString(rDoc, nTab));
+ aFormulaBuf.append(aSep);
+ aFormulaBuf.append(rParam.aRefRowCell.GetRefString(rDoc, nTab));
+ aFormulaBuf.append(aSep);
+ aRef.Set(nCol1 + 1, nRow1, nTab, true, false, true);
+ aFormulaBuf.append(aRef.GetRefString(rDoc, nTab));
+ ++nCol1;
+ ++nRow1;
+ }
+
+ aFormulaBuf.append(ScCompiler::GetNativeSymbol(ocClose));
+
+ ScFormulaCell aRefCell(
+ rDoc, ScAddress(nCol1, nRow1, nTab), aFormulaBuf.makeStringAndClear(),
+ formula::FormulaGrammar::GRAM_NATIVE, ScMatrixMode::NONE);
+
+ for (SCCOL nCol = nCol1; nCol <= nCol2; ++nCol)
+ {
+ sc::ColumnBlockPosition* pBlockPos = mpImpl->getBlockPosition(nTab, nCol);
+
+ if (!pBlockPos)
+ // Something went horribly wrong.
+ return;
+
+ sc::CellStoreType& rColCells = pTab->aCol[nCol].maCells;
+
+ for (SCROW nRow = nRow1; nRow <= nRow2; ++nRow)
+ {
+ ScAddress aPos(nCol, nRow, nTab);
+ ScFormulaCell* pCell = new ScFormulaCell(aRefCell, rDoc, aPos);
+ pBlockPos->miCellPos =
+ rColCells.set(pBlockPos->miCellPos, nRow, pCell);
+ }
+ }
+}
+
+void ScDocumentImport::fillDownCells(const ScAddress& rPos, SCROW nFillSize)
+{
+ ScTable* pTab = mpImpl->mrDoc.FetchTable(rPos.Tab());
+ if (!pTab)
+ return;
+
+ sc::ColumnBlockPosition* pBlockPos = mpImpl->getBlockPosition(rPos.Tab(), rPos.Col());
+
+ if (!pBlockPos)
+ return;
+
+ sc::CellStoreType& rCells = pTab->aCol[rPos.Col()].maCells;
+ ScRefCellValue aRefCell = pTab->aCol[rPos.Col()].GetCellValue(*pBlockPos, rPos.Row());
+
+ switch (aRefCell.meType)
+ {
+ case CELLTYPE_VALUE:
+ {
+ std::vector<double> aCopied(nFillSize, aRefCell.mfValue);
+ pBlockPos->miCellPos = rCells.set(
+ pBlockPos->miCellPos, rPos.Row()+1, aCopied.begin(), aCopied.end());
+ break;
+ }
+ case CELLTYPE_STRING:
+ {
+ std::vector<svl::SharedString> aCopied(nFillSize, *aRefCell.mpString);
+ pBlockPos->miCellPos = rCells.set(
+ pBlockPos->miCellPos, rPos.Row()+1, aCopied.begin(), aCopied.end());
+ break;
+ }
+ default:
+ break;
+ }
+}
+
+void ScDocumentImport::setAttrEntries( SCTAB nTab, SCCOL nColStart, SCCOL nColEnd, Attrs&& rAttrs )
+{
+ ScTable* pTab = mpImpl->mrDoc.FetchTable(nTab);
+ if (!pTab)
+ return;
+
+ for(SCCOL nCol = nColStart; nCol <= nColEnd; ++nCol )
+ {
+ ColAttr* pColAttr = mpImpl->getColAttr(nTab, nCol);
+ if (pColAttr)
+ pColAttr->mbLatinNumFmtOnly = rAttrs.mbLatinNumFmtOnly;
+ }
+
+ pTab->SetAttrEntries( nColStart, nColEnd, std::move( rAttrs.mvData ));
+}
+
+void ScDocumentImport::setRowsVisible(SCTAB nTab, SCROW nRowStart, SCROW nRowEnd, bool bVisible)
+{
+ if (!bVisible)
+ {
+ getDoc().ShowRows(nRowStart, nRowEnd, nTab, false);
+ getDoc().SetDrawPageSize(nTab);
+ getDoc().UpdatePageBreaks( nTab );
+ }
+ else
+ {
+ assert(false);
+ }
+}
+
+void ScDocumentImport::setMergedCells(SCTAB nTab, SCCOL nCol1, SCROW nRow1, SCCOL nCol2, SCROW nRow2)
+{
+ ScTable* pTab = mpImpl->mrDoc.FetchTable(nTab);
+ if (!pTab)
+ return;
+
+ pTab->SetMergedCells(nCol1, nRow1, nCol2, nRow2);
+}
+
+namespace {
+
+class CellStoreInitializer
+{
+ // The pimpl pattern here is intentional.
+ //
+ // The problem with having the attributes in CellStoreInitializer
+ // directly is that, as a functor, it might be copied around. In
+ // that case miPos in _copied_ object points to maAttrs in the
+ // original object, not in the copy. So later, deep in mdds, we end
+ // up comparing iterators from different sequences.
+ //
+ // This could be solved by defining copy constructor and operator=,
+ // but given the limited usage of the class, I think it is simpler
+ // to let copies share the state.
+ struct Impl
+ {
+ sc::CellTextAttrStoreType maAttrs;
+ sc::CellTextAttrStoreType::iterator miPos;
+ SvtScriptType mnScriptNumeric;
+
+ explicit Impl(const ScSheetLimits& rSheetLimits, const SvtScriptType nScriptNumeric)
+ : maAttrs(rSheetLimits.GetMaxRowCount()), miPos(maAttrs.begin()), mnScriptNumeric(nScriptNumeric)
+ {}
+ };
+
+ ScDocumentImportImpl& mrDocImpl;
+ SCTAB mnTab;
+ SCCOL mnCol;
+
+public:
+ CellStoreInitializer( ScDocumentImportImpl& rDocImpl, SCTAB nTab, SCCOL nCol ) :
+ mrDocImpl(rDocImpl),
+ mnTab(nTab),
+ mnCol(nCol),
+ mpImpl(std::make_shared<Impl>(rDocImpl.mrDoc.GetSheetLimits(), mrDocImpl.mnDefaultScriptNumeric))
+ {}
+
+ std::shared_ptr<Impl> mpImpl;
+
+ void operator() (const sc::CellStoreType::value_type& node)
+ {
+ if (node.type == sc::element_type_empty)
+ return;
+
+ // Fill with default values for non-empty cell segments.
+ sc::CellTextAttr aDefault;
+ switch (node.type)
+ {
+ case sc::element_type_numeric:
+ {
+ aDefault.mnScriptType = mpImpl->mnScriptNumeric;
+ const ColAttr* p = mrDocImpl.getColAttr(mnTab, mnCol);
+ if (p && p->mbLatinNumFmtOnly)
+ aDefault.mnScriptType = SvtScriptType::LATIN;
+ }
+ break;
+ case sc::element_type_formula:
+ {
+ const ColAttr* p = mrDocImpl.getColAttr(mnTab, mnCol);
+ if (p && p->mbLatinNumFmtOnly)
+ {
+ // We can assume latin script type if the block only
+ // contains formula cells with numeric results.
+ ScFormulaCell** pp = &sc::formula_block::at(*node.data, 0);
+ ScFormulaCell** ppEnd = pp + node.size;
+ bool bNumResOnly = true;
+ for (; pp != ppEnd; ++pp)
+ {
+ const ScFormulaCell& rCell = **pp;
+ if (!rCell.IsValueNoError())
+ {
+ bNumResOnly = false;
+ break;
+ }
+ }
+
+ if (bNumResOnly)
+ aDefault.mnScriptType = SvtScriptType::LATIN;
+ }
+ }
+ break;
+ default:
+ ;
+ }
+
+ std::vector<sc::CellTextAttr> aDefaults(node.size, aDefault);
+ mpImpl->miPos = mpImpl->maAttrs.set(mpImpl->miPos, node.position, aDefaults.begin(), aDefaults.end());
+
+ if (node.type != sc::element_type_formula)
+ return;
+
+ if (mrDocImpl.mbFuzzing) // skip listening when fuzzing
+ return;
+
+ // Have all formula cells start listening to the document.
+ ScFormulaCell** pp = &sc::formula_block::at(*node.data, 0);
+ ScFormulaCell** ppEnd = pp + node.size;
+ for (; pp != ppEnd; ++pp)
+ {
+ ScFormulaCell& rFC = **pp;
+ if (rFC.IsSharedTop())
+ {
+ // Register formula cells as a group.
+ sc::SharedFormulaUtil::startListeningAsGroup(mrDocImpl.maListenCxt, pp);
+ pp += rFC.GetSharedLength() - 1; // Move to the last one in the group.
+ }
+ else
+ rFC.StartListeningTo(mrDocImpl.maListenCxt);
+ }
+ }
+
+ void swap(sc::CellTextAttrStoreType& rAttrs)
+ {
+ mpImpl->maAttrs.swap(rAttrs);
+ }
+};
+
+}
+
+void ScDocumentImport::finalize()
+{
+ // Populate the text width and script type arrays in all columns. Also
+ // activate all formula cells.
+ for (auto& rxTab : mpImpl->mrDoc.maTabs)
+ {
+ if (!rxTab)
+ continue;
+
+ ScTable& rTab = *rxTab;
+ SCCOL nNumCols = rTab.aCol.size();
+ for (SCCOL nColIdx = 0; nColIdx < nNumCols; ++nColIdx)
+ initColumn(rTab.aCol[nColIdx]);
+ }
+
+ mpImpl->mrDoc.finalizeOutlineImport();
+}
+
+void ScDocumentImport::initColumn(ScColumn& rCol)
+{
+ rCol.RegroupFormulaCells();
+
+ CellStoreInitializer aFunc(*mpImpl, rCol.nTab, rCol.nCol);
+ std::for_each(rCol.maCells.begin(), rCol.maCells.end(), aFunc);
+ aFunc.swap(rCol.maCellTextAttrs);
+
+ rCol.CellStorageModified();
+}
+
+namespace {
+
+class CellStoreAfterImportBroadcaster
+{
+public:
+
+ CellStoreAfterImportBroadcaster() {}
+
+ void operator() (const sc::CellStoreType::value_type& node)
+ {
+ if (node.type == sc::element_type_formula)
+ {
+ // Broadcast all formula cells marked for recalc.
+ ScFormulaCell** pp = &sc::formula_block::at(*node.data, 0);
+ ScFormulaCell** ppEnd = pp + node.size;
+ for (; pp != ppEnd; ++pp)
+ {
+ if ((*pp)->GetCode()->IsRecalcModeMustAfterImport())
+ (*pp)->SetDirty();
+ }
+ }
+ }
+};
+
+}
+
+void ScDocumentImport::broadcastRecalcAfterImport()
+{
+ sc::AutoCalcSwitch aACSwitch( mpImpl->mrDoc, false);
+ ScBulkBroadcast aBulkBroadcast( mpImpl->mrDoc.GetBASM(), SfxHintId::ScDataChanged);
+
+ for (auto& rxTab : mpImpl->mrDoc.maTabs)
+ {
+ if (!rxTab)
+ continue;
+
+ ScTable& rTab = *rxTab;
+ SCCOL nNumCols = rTab.aCol.size();
+ for (SCCOL nColIdx = 0; nColIdx < nNumCols; ++nColIdx)
+ broadcastRecalcAfterImportColumn(rTab.aCol[nColIdx]);
+ }
+}
+
+void ScDocumentImport::broadcastRecalcAfterImportColumn(ScColumn& rCol)
+{
+ CellStoreAfterImportBroadcaster aFunc;
+ std::for_each(rCol.maCells.begin(), rCol.maCells.end(), aFunc);
+}
+
+
+bool ScDocumentImport::isLatinScript(const ScPatternAttr& rPatAttr)
+{
+ SvNumberFormatter* pFormatter = mpImpl->mrDoc.GetFormatTable();
+ sal_uInt32 nKey = rPatAttr.GetNumberFormat(pFormatter);
+ return isLatinScript(nKey);
+}
+
+bool ScDocumentImport::isLatinScript(sal_uInt32 nFormat)
+{
+ auto it = mpImpl->maIsLatinScriptMap.find(nFormat);
+ if (it != mpImpl->maIsLatinScriptMap.end())
+ return it->second;
+ bool b = sc::NumFmtUtil::isLatinScript(nFormat, mpImpl->mrDoc);
+ mpImpl->maIsLatinScriptMap.emplace(nFormat, b);
+ return b;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sc/source/core/data/documentstreamaccess.cxx b/sc/source/core/data/documentstreamaccess.cxx
new file mode 100644
index 000000000..6b3581058
--- /dev/null
+++ b/sc/source/core/data/documentstreamaccess.cxx
@@ -0,0 +1,147 @@
+/* -*- 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/.
+ */
+
+#include <documentstreamaccess.hxx>
+#include <document.hxx>
+#include <table.hxx>
+#include <column.hxx>
+#include <mtvelements.hxx>
+
+#include <svl/sharedstringpool.hxx>
+
+namespace sc {
+
+struct DocumentStreamAccessImpl
+{
+ ScDocument& mrDoc;
+ ColumnBlockPositionSet maBlockPosSet;
+
+ explicit DocumentStreamAccessImpl( ScDocument& rDoc ) :
+ mrDoc(rDoc),
+ maBlockPosSet(rDoc)
+ {}
+};
+
+DocumentStreamAccess::DocumentStreamAccess( ScDocument& rDoc ) :
+ mpImpl(new DocumentStreamAccessImpl(rDoc)) {}
+
+DocumentStreamAccess::~DocumentStreamAccess()
+{
+}
+
+void DocumentStreamAccess::setNumericCell( const ScAddress& rPos, double fVal )
+{
+ ScTable* pTab = mpImpl->mrDoc.FetchTable(rPos.Tab());
+ if (!pTab)
+ return;
+
+ ColumnBlockPosition* pBlockPos =
+ mpImpl->maBlockPosSet.getBlockPosition(rPos.Tab(), rPos.Col());
+
+ if (!pBlockPos)
+ return;
+
+ // Set the numeric value.
+ CellStoreType& rCells = pTab->aCol[rPos.Col()].maCells;
+ pBlockPos->miCellPos = rCells.set(pBlockPos->miCellPos, rPos.Row(), fVal);
+
+ // Be sure to set the corresponding text attribute to the default value.
+ CellTextAttrStoreType& rAttrs = pTab->aCol[rPos.Col()].maCellTextAttrs;
+ pBlockPos->miCellTextAttrPos = rAttrs.set(pBlockPos->miCellTextAttrPos, rPos.Row(), CellTextAttr());
+}
+
+void DocumentStreamAccess::setStringCell( const ScAddress& rPos, const OUString& rStr )
+{
+ ScTable* pTab = mpImpl->mrDoc.FetchTable(rPos.Tab());
+ if (!pTab)
+ return;
+
+ ColumnBlockPosition* pBlockPos =
+ mpImpl->maBlockPosSet.getBlockPosition(rPos.Tab(), rPos.Col());
+
+ if (!pBlockPos)
+ return;
+
+ svl::SharedString aSS = mpImpl->mrDoc.GetSharedStringPool().intern(rStr);
+ if (!aSS.getData())
+ return;
+
+ // Set the string.
+ CellStoreType& rCells = pTab->aCol[rPos.Col()].maCells;
+ pBlockPos->miCellPos = rCells.set(pBlockPos->miCellPos, rPos.Row(), aSS);
+
+ // Be sure to set the corresponding text attribute to the default value.
+ CellTextAttrStoreType& rAttrs = pTab->aCol[rPos.Col()].maCellTextAttrs;
+ pBlockPos->miCellTextAttrPos = rAttrs.set(pBlockPos->miCellTextAttrPos, rPos.Row(), CellTextAttr());
+}
+
+void DocumentStreamAccess::reset()
+{
+ mpImpl->maBlockPosSet.clear();
+}
+
+void DocumentStreamAccess::shiftRangeUp( const ScRange& rRange )
+{
+ ScTable* pTab = mpImpl->mrDoc.FetchTable(rRange.aStart.Tab());
+ if (!pTab)
+ return;
+
+ SCROW nTopRow = rRange.aStart.Row();
+ SCROW nLastRow = rRange.aEnd.Row();
+
+ for (SCCOL nCol = rRange.aStart.Col(); nCol <= rRange.aEnd.Col(); ++nCol)
+ {
+ ColumnBlockPosition* pBlockPos =
+ mpImpl->maBlockPosSet.getBlockPosition(rRange.aStart.Tab(), nCol);
+
+ if (!pBlockPos)
+ return;
+
+ CellStoreType& rCells = pTab->aCol[nCol].maCells;
+ rCells.erase(nTopRow, nTopRow); // Erase the top, and shift the rest up.
+ pBlockPos->miCellPos = rCells.insert_empty(nLastRow, 1);
+
+ // Do the same for the text attribute storage.
+ CellTextAttrStoreType& rAttrs = pTab->aCol[nCol].maCellTextAttrs;
+ rAttrs.erase(nTopRow, nTopRow);
+ pBlockPos->miCellTextAttrPos = rAttrs.insert_empty(nLastRow, 1);
+ }
+}
+
+void DocumentStreamAccess::shiftRangeDown( const ScRange& rRange )
+{
+ ScTable* pTab = mpImpl->mrDoc.FetchTable(rRange.aStart.Tab());
+ if (!pTab)
+ return;
+
+ SCROW nTopRow = rRange.aStart.Row();
+ SCROW nLastRow = rRange.aEnd.Row();
+
+ for (SCCOL nCol = rRange.aStart.Col(); nCol <= rRange.aEnd.Col(); ++nCol)
+ {
+ ColumnBlockPosition* pBlockPos =
+ mpImpl->maBlockPosSet.getBlockPosition(rRange.aStart.Tab(), nCol);
+
+ if (!pBlockPos)
+ return;
+
+ CellStoreType& rCells = pTab->aCol[nCol].maCells;
+ rCells.erase(nLastRow, nLastRow); // Erase the bottom.
+ pBlockPos->miCellPos = rCells.insert_empty(nTopRow, 1); // insert at the top and shift everything down.
+
+ // Do the same for the text attribute storage.
+ CellTextAttrStoreType& rAttrs = pTab->aCol[nCol].maCellTextAttrs;
+ rAttrs.erase(nLastRow, nLastRow);
+ pBlockPos->miCellTextAttrPos = rAttrs.insert_empty(nTopRow, 1);
+ }
+}
+
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sc/source/core/data/dpcache.cxx b/sc/source/core/data/dpcache.cxx
new file mode 100644
index 000000000..52109c673
--- /dev/null
+++ b/sc/source/core/data/dpcache.cxx
@@ -0,0 +1,1522 @@
+/* -*- 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 <dpcache.hxx>
+
+#include <document.hxx>
+#include <queryentry.hxx>
+#include <queryparam.hxx>
+#include <dpobject.hxx>
+#include <globstr.hrc>
+#include <scresid.hxx>
+#include <docoptio.hxx>
+#include <dpitemdata.hxx>
+#include <dputil.hxx>
+#include <dpnumgroupinfo.hxx>
+#include <columniterator.hxx>
+#include <cellvalue.hxx>
+
+#include <comphelper/parallelsort.hxx>
+#include <rtl/math.hxx>
+#include <unotools/charclass.hxx>
+#include <unotools/textsearch.hxx>
+#include <unotools/localedatawrapper.hxx>
+#include <unotools/collatorwrapper.hxx>
+#include <svl/numformat.hxx>
+#include <svl/zforlist.hxx>
+#include <o3tl/safeint.hxx>
+#include <osl/diagnose.h>
+
+#if DUMP_PIVOT_TABLE
+#include <com/sun/star/sheet/DataPilotFieldGroupBy.hpp>
+#endif
+
+// TODO : Threaded pivot cache operation is disabled until we can figure out
+// ways to make the edit engine and number formatter codes thread-safe in a
+// proper fashion.
+#define ENABLE_THREADED_PIVOT_CACHE 0
+
+#if ENABLE_THREADED_PIVOT_CACHE
+#include <thread>
+#include <future>
+#include <queue>
+#endif
+
+using namespace ::com::sun::star;
+
+using ::com::sun::star::uno::Exception;
+
+ScDPCache::GroupItems::GroupItems() : mnGroupType(0) {}
+
+ScDPCache::GroupItems::GroupItems(const ScDPNumGroupInfo& rInfo, sal_Int32 nGroupType) :
+ maInfo(rInfo), mnGroupType(nGroupType) {}
+
+ScDPCache::Field::Field() : mnNumFormat(0) {}
+
+ScDPCache::ScDPCache(ScDocument& rDoc) :
+ mrDoc( rDoc ),
+ mnColumnCount ( 0 ),
+ maEmptyRows(0, rDoc.GetMaxRowCount(), true),
+ mnDataSize(-1),
+ mnRowCount(0),
+ mbDisposing(false)
+{
+}
+
+namespace {
+
+struct ClearObjectSource
+{
+ void operator() (ScDPObject* p) const
+ {
+ p->ClearTableData();
+ }
+};
+
+}
+
+ScDPCache::~ScDPCache()
+{
+ // Make sure no live ScDPObject instances hold reference to this cache any
+ // more.
+ mbDisposing = true;
+ std::for_each(maRefObjects.begin(), maRefObjects.end(), ClearObjectSource());
+}
+
+namespace {
+
+/**
+ * While the macro interpret level is incremented, the formula cells are
+ * (semi-)guaranteed to be interpreted.
+ */
+class MacroInterpretIncrementer
+{
+public:
+ explicit MacroInterpretIncrementer(ScDocument& rDoc) :
+ mrDoc(rDoc)
+ {
+ mrDoc.IncMacroInterpretLevel();
+ }
+ ~MacroInterpretIncrementer()
+ {
+ mrDoc.DecMacroInterpretLevel();
+ }
+private:
+ ScDocument& mrDoc;
+};
+
+rtl_uString* internString( ScDPCache::StringSetType& rPool, const OUString& rStr )
+{
+ return rPool.insert(rStr).first->pData;
+}
+
+OUString createLabelString( const ScDocument& rDoc, SCCOL nCol, const ScRefCellValue& rCell )
+{
+ OUString aDocStr = rCell.getRawString(rDoc);
+
+ if (aDocStr.isEmpty())
+ {
+ // Replace an empty label string with column name.
+
+ ScAddress aColAddr(nCol, 0, 0);
+ aDocStr = ScResId(STR_COLUMN) + " " + aColAddr.Format(ScRefFlags::COL_VALID);
+ }
+ return aDocStr;
+}
+
+void initFromCell(
+ ScDPCache::StringSetType& rStrPool, const ScDocument& rDoc, const ScAddress& rPos,
+ const ScRefCellValue& rCell, ScDPItemData& rData, sal_uInt32& rNumFormat)
+{
+ OUString aDocStr = rCell.getRawString(rDoc);
+ rNumFormat = 0;
+
+ if (rCell.hasError())
+ {
+ rData.SetErrorStringInterned(internString(rStrPool, rDoc.GetString(rPos.Col(), rPos.Row(), rPos.Tab())));
+ }
+ else if (rCell.hasNumeric())
+ {
+ double fVal = rCell.getRawValue();
+ rNumFormat = rDoc.GetNumberFormat(rPos);
+ rData.SetValue(fVal);
+ }
+ else if (!rCell.isEmpty())
+ {
+ rData.SetStringInterned(internString(rStrPool, aDocStr));
+ }
+ else
+ rData.SetEmpty();
+}
+
+struct Bucket
+{
+ ScDPItemData maValue;
+ SCROW mnOrderIndex;
+ SCROW mnDataIndex;
+ Bucket() :
+ mnOrderIndex(0), mnDataIndex(0) {}
+ Bucket(const ScDPItemData& rValue, SCROW nData) :
+ maValue(rValue), mnOrderIndex(0), mnDataIndex(nData) {}
+};
+
+#if DEBUG_PIVOT_TABLE
+#include <iostream>
+using std::cout;
+using std::endl;
+
+struct PrintBucket
+{
+ void operator() (const Bucket& v) const
+ {
+ cout << "value: " << v.maValue.GetValue() << " order index: " << v.mnOrderIndex << " data index: " << v.mnDataIndex << endl;
+ }
+};
+
+#endif
+
+struct LessByValue
+{
+ bool operator() (const Bucket& left, const Bucket& right) const
+ {
+ return left.maValue < right.maValue;
+ }
+};
+
+struct LessByOrderIndex
+{
+ bool operator() (const Bucket& left, const Bucket& right) const
+ {
+ return left.mnOrderIndex < right.mnOrderIndex;
+ }
+};
+
+struct LessByDataIndex
+{
+ bool operator() (const Bucket& left, const Bucket& right) const
+ {
+ return left.mnDataIndex < right.mnDataIndex;
+ }
+};
+
+struct EqualByOrderIndex
+{
+ bool operator() (const Bucket& left, const Bucket& right) const
+ {
+ return left.mnOrderIndex == right.mnOrderIndex;
+ }
+};
+
+class PushBackValue
+{
+ ScDPCache::ScDPItemDataVec& mrItems;
+public:
+ explicit PushBackValue(ScDPCache::ScDPItemDataVec& _items) : mrItems(_items) {}
+ void operator() (const Bucket& v)
+ {
+ mrItems.push_back(v.maValue);
+ }
+};
+
+class PushBackOrderIndex
+{
+ ScDPCache::IndexArrayType& mrData;
+public:
+ explicit PushBackOrderIndex(ScDPCache::IndexArrayType& _items) : mrData(_items) {}
+ void operator() (const Bucket& v)
+ {
+ mrData.push_back(v.mnOrderIndex);
+ }
+};
+
+void processBuckets(std::vector<Bucket>& aBuckets, ScDPCache::Field& rField)
+{
+ if (aBuckets.empty())
+ return;
+
+ // Sort by the value.
+ comphelper::parallelSort(aBuckets.begin(), aBuckets.end(), LessByValue());
+
+ {
+ // Set order index such that unique values have identical index value.
+ SCROW nCurIndex = 0;
+ std::vector<Bucket>::iterator it = aBuckets.begin(), itEnd = aBuckets.end();
+ ScDPItemData aPrev = it->maValue;
+ it->mnOrderIndex = nCurIndex;
+ for (++it; it != itEnd; ++it)
+ {
+ if (!aPrev.IsCaseInsEqual(it->maValue))
+ ++nCurIndex;
+
+ it->mnOrderIndex = nCurIndex;
+ aPrev = it->maValue;
+ }
+ }
+
+ // Re-sort the bucket this time by the data index.
+ comphelper::parallelSort(aBuckets.begin(), aBuckets.end(), LessByDataIndex());
+
+ // Copy the order index series into the field object.
+ rField.maData.reserve(aBuckets.size());
+ std::for_each(aBuckets.begin(), aBuckets.end(), PushBackOrderIndex(rField.maData));
+
+ // Sort by the value again.
+ comphelper::parallelSort(aBuckets.begin(), aBuckets.end(), LessByOrderIndex());
+
+ // Unique by value.
+ std::vector<Bucket>::iterator itUniqueEnd =
+ std::unique(aBuckets.begin(), aBuckets.end(), EqualByOrderIndex());
+
+ // Copy the unique values into items.
+ std::vector<Bucket>::iterator itBeg = aBuckets.begin();
+ size_t nLen = distance(itBeg, itUniqueEnd);
+ rField.maItems.reserve(nLen);
+ std::for_each(itBeg, itUniqueEnd, PushBackValue(rField.maItems));
+}
+
+struct InitColumnData
+{
+ ScDPCache::EmptyRowsType maEmptyRows;
+ OUString maLabel;
+
+ ScDPCache::StringSetType* mpStrPool;
+ ScDPCache::Field* mpField;
+
+ SCCOL mnCol;
+
+ InitColumnData(ScSheetLimits const & rSheetLimits) :
+ maEmptyRows(0, rSheetLimits.GetMaxRowCount(), true),
+ mpStrPool(nullptr),
+ mpField(nullptr),
+ mnCol(-1) {}
+
+ void init( SCCOL nCol, ScDPCache::StringSetType* pStrPool, ScDPCache::Field* pField )
+ {
+ mpStrPool = pStrPool;
+ mpField = pField;
+ mnCol = nCol;
+ }
+};
+
+struct InitDocData
+{
+ ScDocument& mrDoc;
+ SCTAB mnDocTab;
+ SCROW mnStartRow;
+ SCROW mnEndRow;
+ bool mbTailEmptyRows;
+
+ InitDocData(ScDocument& rDoc) :
+ mrDoc(rDoc),
+ mnDocTab(-1),
+ mnStartRow(-1),
+ mnEndRow(-1),
+ mbTailEmptyRows(false) {}
+};
+
+typedef std::unordered_set<OUString> LabelSet;
+
+void normalizeAddLabel(const OUString& rLabel, std::vector<OUString>& rLabels, LabelSet& rExistingNames)
+{
+ const OUString aLabelLower = ScGlobal::getCharClass().lowercase(rLabel);
+ sal_Int32 nSuffix = 1;
+ OUString aNewLabel = rLabel;
+ OUString aNewLabelLower = aLabelLower;
+ while (true)
+ {
+ if (!rExistingNames.count(aNewLabelLower))
+ {
+ // this is a unique label.
+ rLabels.push_back(aNewLabel);
+ rExistingNames.insert(aNewLabelLower);
+ break;
+ }
+
+ // This name already exists.
+ aNewLabel = rLabel + OUString::number(++nSuffix);
+ aNewLabelLower = aLabelLower + OUString::number(nSuffix);
+ }
+}
+
+std::vector<OUString> normalizeLabels(const std::vector<InitColumnData>& rColData)
+{
+ std::vector<OUString> aLabels;
+ aLabels.reserve(rColData.size() + 1);
+
+ LabelSet aExistingNames;
+ normalizeAddLabel(ScResId(STR_PIVOT_DATA), aLabels, aExistingNames);
+
+ for (const InitColumnData& rCol : rColData)
+ normalizeAddLabel(rCol.maLabel, aLabels, aExistingNames);
+
+ return aLabels;
+}
+
+std::vector<OUString> normalizeLabels(const ScDPCache::DBConnector& rDB, const sal_Int32 nLabelCount)
+{
+ std::vector<OUString> aLabels;
+ aLabels.reserve(nLabelCount + 1);
+
+ LabelSet aExistingNames;
+ normalizeAddLabel(ScResId(STR_PIVOT_DATA), aLabels, aExistingNames);
+
+ for (sal_Int32 nCol = 0; nCol < nLabelCount; ++nCol)
+ {
+ OUString aColTitle = rDB.getColumnLabel(nCol);
+ normalizeAddLabel(aColTitle, aLabels, aExistingNames);
+ }
+
+ return aLabels;
+}
+
+void initColumnFromDoc( InitDocData& rDocData, InitColumnData &rColData )
+{
+ ScDPCache::Field& rField = *rColData.mpField;
+ ScDocument& rDoc = rDocData.mrDoc;
+ SCTAB nDocTab = rDocData.mnDocTab;
+ SCCOL nCol = rColData.mnCol;
+ SCROW nStartRow = rDocData.mnStartRow;
+ SCROW nEndRow = rDocData.mnEndRow;
+ bool bTailEmptyRows = rDocData.mbTailEmptyRows;
+
+ std::optional<sc::ColumnIterator> pIter =
+ rDoc.GetColumnIterator(nDocTab, nCol, nStartRow, nEndRow);
+ assert(pIter);
+ assert(pIter->hasCell());
+
+ ScDPItemData aData;
+
+ rColData.maLabel = createLabelString(rDoc, nCol, pIter->getCell());
+ pIter->next();
+
+ std::vector<Bucket> aBuckets;
+ aBuckets.reserve(nEndRow-nStartRow); // skip the topmost label cell.
+
+ // Push back all original values.
+ for (SCROW i = 0, n = nEndRow-nStartRow; i < n; ++i, pIter->next())
+ {
+ assert(pIter->hasCell());
+
+ sal_uInt32 nNumFormat = 0;
+ ScAddress aPos(nCol, pIter->getRow(), nDocTab);
+ initFromCell(*rColData.mpStrPool, rDoc, aPos, pIter->getCell(), aData, nNumFormat);
+
+ aBuckets.emplace_back(aData, i);
+
+ if (!aData.IsEmpty())
+ {
+ rColData.maEmptyRows.insert_back(i, i+1, false);
+ if (nNumFormat)
+ // Only take non-default number format.
+ rField.mnNumFormat = nNumFormat;
+ }
+ }
+
+ processBuckets(aBuckets, rField);
+
+ if (bTailEmptyRows)
+ {
+ // If the last item is not empty, append one. Note that the items
+ // are sorted, and empty item should come last when sorted.
+ if (rField.maItems.empty() || !rField.maItems.back().IsEmpty())
+ {
+ aData.SetEmpty();
+ rField.maItems.push_back(aData);
+ }
+ }
+}
+
+#if ENABLE_THREADED_PIVOT_CACHE
+
+class ThreadQueue
+{
+ using FutureType = std::future<void>;
+ std::queue<FutureType> maQueue;
+ std::mutex maMutex;
+ std::condition_variable maCond;
+
+ size_t mnMaxQueue;
+
+public:
+ ThreadQueue( size_t nMaxQueue ) : mnMaxQueue(nMaxQueue) {}
+
+ void push( std::function<void()> aFunc )
+ {
+ std::unique_lock<std::mutex> lock(maMutex);
+
+ while (maQueue.size() >= mnMaxQueue)
+ maCond.wait(lock);
+
+ FutureType f = std::async(std::launch::async, aFunc);
+ maQueue.push(std::move(f));
+ lock.unlock();
+
+ maCond.notify_one();
+ }
+
+ void waitForOne()
+ {
+ std::unique_lock<std::mutex> lock(maMutex);
+
+ while (maQueue.empty())
+ maCond.wait(lock);
+
+ FutureType ret = std::move(maQueue.front());
+ maQueue.pop();
+ lock.unlock();
+
+ ret.get(); // This may throw if an exception was thrown on the async thread.
+
+ maCond.notify_one();
+ }
+};
+
+class ThreadScopedGuard
+{
+ std::thread maThread;
+public:
+ ThreadScopedGuard(std::thread thread) : maThread(std::move(thread)) {}
+ ThreadScopedGuard(ThreadScopedGuard&& other) : maThread(std::move(other.maThread)) {}
+
+ ThreadScopedGuard(const ThreadScopedGuard&) = delete;
+ ThreadScopedGuard& operator= (const ThreadScopedGuard&) = delete;
+
+ ~ThreadScopedGuard()
+ {
+ maThread.join();
+ }
+};
+
+#endif
+
+}
+
+void ScDPCache::InitFromDoc(ScDocument& rDoc, const ScRange& rRange)
+{
+ Clear();
+
+ InitDocData aDocData(rDoc);
+
+ // Make sure the formula cells within the data range are interpreted
+ // during this call, for this method may be called from the interpretation
+ // of GETPIVOTDATA, which disables nested formula interpretation without
+ // increasing the macro level.
+ MacroInterpretIncrementer aMacroInc(rDoc);
+
+ aDocData.mnStartRow = rRange.aStart.Row(); // start of data
+ aDocData.mnEndRow = rRange.aEnd.Row();
+
+ // Sanity check
+ if (!GetDoc().ValidRow(aDocData.mnStartRow) || !GetDoc().ValidRow(aDocData.mnEndRow) || aDocData.mnEndRow <= aDocData.mnStartRow)
+ return;
+
+ SCCOL nStartCol = rRange.aStart.Col();
+ SCCOL nEndCol = rRange.aEnd.Col();
+ aDocData.mnDocTab = rRange.aStart.Tab();
+
+ mnColumnCount = nEndCol - nStartCol + 1;
+
+ // this row count must include the trailing empty rows.
+ mnRowCount = aDocData.mnEndRow - aDocData.mnStartRow; // skip the topmost label row.
+
+ // Skip trailing empty rows if exists.
+ SCCOL nCol1 = nStartCol, nCol2 = nEndCol;
+ SCROW nRow1 = aDocData.mnStartRow, nRow2 = aDocData.mnEndRow;
+ rDoc.ShrinkToDataArea(aDocData.mnDocTab, nCol1, nRow1, nCol2, nRow2);
+ aDocData.mbTailEmptyRows = aDocData.mnEndRow > nRow2; // Trailing empty rows exist.
+ aDocData.mnEndRow = nRow2;
+
+ if (aDocData.mnEndRow <= aDocData.mnStartRow)
+ {
+ // Check this again since the end row position has changed. It's
+ // possible that the new end row becomes lower than the start row
+ // after the shrinkage.
+ Clear();
+ return;
+ }
+
+ maStringPools.resize(mnColumnCount);
+ std::vector<InitColumnData> aColData(mnColumnCount, InitColumnData(rDoc.GetSheetLimits()));
+ maFields.reserve(mnColumnCount);
+ for (SCCOL i = 0; i < mnColumnCount; ++i)
+ maFields.push_back(std::make_unique<Field>());
+
+ maLabelNames.reserve(mnColumnCount+1);
+
+ // Ensure that none of the formula cells in the data range are dirty.
+ rDoc.EnsureFormulaCellResults(rRange);
+
+#if ENABLE_THREADED_PIVOT_CACHE
+ ThreadQueue aQueue(std::thread::hardware_concurrency());
+
+ auto aFuncLaunchFieldThreads = [&]()
+ {
+ for (sal_uInt16 nCol = nStartCol; nCol <= nEndCol; ++nCol)
+ {
+ size_t nDim = nCol - nStartCol;
+ InitColumnData& rColData = aColData[nDim];
+ rColData.init(nCol, &maStringPools[nDim], maFields[nDim].get());
+
+ auto func = [&aDocData,&rColData]()
+ {
+ initColumnFromDoc(aDocData, rColData);
+ };
+
+ aQueue.push(std::move(func));
+ }
+ };
+
+ {
+ // Launch a separate thread that in turn spawns async threads to populate the fields.
+ std::thread t(aFuncLaunchFieldThreads);
+ ThreadScopedGuard sg(std::move(t));
+
+ // Wait for all the async threads to complete on the main thread.
+ for (SCCOL i = 0; i < mnColumnCount; ++i)
+ aQueue.waitForOne();
+ }
+
+#else
+ for (sal_uInt16 nCol = nStartCol; nCol <= nEndCol; ++nCol)
+ {
+ size_t nDim = nCol - nStartCol;
+ InitColumnData& rColData = aColData[nDim];
+ rColData.init(nCol, &maStringPools[nDim], maFields[nDim].get());
+
+ initColumnFromDoc(aDocData, rColData);
+ }
+#endif
+
+ maLabelNames = normalizeLabels(aColData);
+
+ // Merge all non-empty rows data.
+ for (const InitColumnData& rCol : aColData)
+ {
+ EmptyRowsType::const_segment_iterator it = rCol.maEmptyRows.begin_segment();
+ EmptyRowsType::const_segment_iterator ite = rCol.maEmptyRows.end_segment();
+ EmptyRowsType::const_iterator pos = maEmptyRows.begin();
+
+ for (; it != ite; ++it)
+ {
+ if (!it->value)
+ // Non-empty segment found. Record it.
+ pos = maEmptyRows.insert(pos, it->start, it->end, false).first;
+ }
+ }
+
+ PostInit();
+}
+
+bool ScDPCache::InitFromDataBase(DBConnector& rDB)
+{
+ Clear();
+
+ try
+ {
+ mnColumnCount = rDB.getColumnCount();
+ maStringPools.resize(mnColumnCount);
+ maFields.clear();
+ maFields.reserve(mnColumnCount);
+ for (SCCOL i = 0; i < mnColumnCount; ++i)
+ maFields.push_back(std::make_unique<Field>());
+
+ // Get column titles and types.
+ maLabelNames = normalizeLabels(rDB, mnColumnCount);
+
+ std::vector<Bucket> aBuckets;
+ ScDPItemData aData;
+ for (sal_Int32 nCol = 0; nCol < mnColumnCount; ++nCol)
+ {
+ if (!rDB.first())
+ continue;
+
+ aBuckets.clear();
+ Field& rField = *maFields[nCol];
+ SCROW nRow = 0;
+ do
+ {
+ SvNumFormatType nFormatType = SvNumFormatType::UNDEFINED;
+ aData.SetEmpty();
+ rDB.getValue(nCol, aData, nFormatType);
+ aBuckets.emplace_back(aData, nRow);
+ if (!aData.IsEmpty())
+ {
+ maEmptyRows.insert_back(nRow, nRow+1, false);
+ SvNumberFormatter* pFormatter = mrDoc.GetFormatTable();
+ rField.mnNumFormat = pFormatter ? pFormatter->GetStandardFormat(nFormatType) : 0;
+ }
+
+ ++nRow;
+ }
+ while (rDB.next());
+
+ processBuckets(aBuckets, rField);
+ }
+
+ rDB.finish();
+
+ if (!maFields.empty())
+ mnRowCount = maFields[0]->maData.size();
+
+ PostInit();
+ return true;
+ }
+ catch (const Exception&)
+ {
+ return false;
+ }
+}
+
+bool ScDPCache::ValidQuery( SCROW nRow, const ScQueryParam &rParam) const
+{
+ if (!rParam.GetEntryCount())
+ return true;
+
+ if (!rParam.GetEntry(0).bDoQuery)
+ return true;
+
+ bool bMatchWholeCell = mrDoc.GetDocOptions().IsMatchWholeCell();
+
+ SCSIZE nEntryCount = rParam.GetEntryCount();
+ std::vector<bool> aPassed(nEntryCount, false);
+
+ tools::Long nPos = -1;
+ CollatorWrapper& rCollator = ScGlobal::GetCollator(rParam.bCaseSens);
+ ::utl::TransliterationWrapper& rTransliteration = ScGlobal::GetTransliteration(rParam.bCaseSens);
+
+ for (size_t i = 0; i < nEntryCount && rParam.GetEntry(i).bDoQuery; ++i)
+ {
+ const ScQueryEntry& rEntry = rParam.GetEntry(i);
+ const ScQueryEntry::Item& rItem = rEntry.GetQueryItem();
+ // we can only handle one single direct query
+ // #i115431# nField in QueryParam is the sheet column, not the field within the source range
+ SCCOL nQueryCol = static_cast<SCCOL>(rEntry.nField);
+ if ( nQueryCol < rParam.nCol1 )
+ nQueryCol = rParam.nCol1;
+ if ( nQueryCol > rParam.nCol2 )
+ nQueryCol = rParam.nCol2;
+ SCCOL nSourceField = nQueryCol - rParam.nCol1;
+ SCROW nId = GetItemDataId( nSourceField, nRow, false );
+ const ScDPItemData* pCellData = GetItemDataById( nSourceField, nId );
+
+ bool bOk = false;
+
+ if (rEntry.GetQueryItem().meType == ScQueryEntry::ByEmpty)
+ {
+ if (rEntry.IsQueryByEmpty())
+ bOk = pCellData->IsEmpty();
+ else
+ {
+ assert(rEntry.IsQueryByNonEmpty());
+ bOk = !pCellData->IsEmpty();
+ }
+ }
+ else if (rEntry.GetQueryItem().meType != ScQueryEntry::ByString && pCellData->IsValue())
+ { // by Value
+ double nCellVal = pCellData->GetValue();
+
+ switch (rEntry.eOp)
+ {
+ case SC_EQUAL :
+ bOk = ::rtl::math::approxEqual(nCellVal, rItem.mfVal);
+ break;
+ case SC_LESS :
+ bOk = (nCellVal < rItem.mfVal) && !::rtl::math::approxEqual(nCellVal, rItem.mfVal);
+ break;
+ case SC_GREATER :
+ bOk = (nCellVal > rItem.mfVal) && !::rtl::math::approxEqual(nCellVal, rItem.mfVal);
+ break;
+ case SC_LESS_EQUAL :
+ bOk = (nCellVal < rItem.mfVal) || ::rtl::math::approxEqual(nCellVal, rItem.mfVal);
+ break;
+ case SC_GREATER_EQUAL :
+ bOk = (nCellVal > rItem.mfVal) || ::rtl::math::approxEqual(nCellVal, rItem.mfVal);
+ break;
+ case SC_NOT_EQUAL :
+ bOk = !::rtl::math::approxEqual(nCellVal, rItem.mfVal);
+ break;
+ default:
+ bOk= false;
+ break;
+ }
+ }
+ else if ((rEntry.eOp == SC_EQUAL || rEntry.eOp == SC_NOT_EQUAL)
+ || (rEntry.GetQueryItem().meType == ScQueryEntry::ByString
+ && pCellData->HasStringData() )
+ )
+ { // by String
+ OUString aCellStr = pCellData->GetString();
+
+ bool bRealWildOrRegExp = (rParam.eSearchType != utl::SearchParam::SearchType::Normal &&
+ ((rEntry.eOp == SC_EQUAL) || (rEntry.eOp == SC_NOT_EQUAL)));
+ if (bRealWildOrRegExp)
+ {
+ sal_Int32 nStart = 0;
+ sal_Int32 nEnd = aCellStr.getLength();
+
+ bool bMatch = rEntry.GetSearchTextPtr( rParam.eSearchType, rParam.bCaseSens, bMatchWholeCell )
+ ->SearchForward( aCellStr, &nStart, &nEnd );
+ // from 614 on, nEnd is behind the found text
+ if (bMatch && bMatchWholeCell
+ && (nStart != 0 || nEnd != aCellStr.getLength()))
+ bMatch = false; // RegExp must match entire cell string
+
+ bOk = ((rEntry.eOp == SC_NOT_EQUAL) ? !bMatch : bMatch);
+ }
+ else
+ {
+ if (rEntry.eOp == SC_EQUAL || rEntry.eOp == SC_NOT_EQUAL)
+ {
+ if (bMatchWholeCell)
+ {
+ // TODO: Use shared string for fast equality check.
+ OUString aStr = rEntry.GetQueryItem().maString.getString();
+ bOk = rTransliteration.isEqual(aCellStr, aStr);
+ bool bHasStar = false;
+ sal_Int32 nIndex;
+ if (( nIndex = aStr.indexOf('*') ) != -1)
+ bHasStar = true;
+ if (bHasStar && (nIndex>0))
+ {
+ for (sal_Int32 j=0;(j<nIndex) && (j< aCellStr.getLength()) ; j++)
+ {
+ if (aCellStr[j] == aStr[j])
+ {
+ bOk=true;
+ }
+ else
+ {
+ bOk=false;
+ break;
+ }
+ }
+ }
+ }
+ else
+ {
+ OUString aQueryStr = rEntry.GetQueryItem().maString.getString();
+ css::uno::Sequence< sal_Int32 > xOff;
+ const LanguageType nLang = ScGlobal::oSysLocale->GetLanguageTag().getLanguageType();
+ OUString aCell = rTransliteration.transliterate(
+ aCellStr, nLang, 0, aCellStr.getLength(), &xOff);
+ OUString aQuer = rTransliteration.transliterate(
+ aQueryStr, nLang, 0, aQueryStr.getLength(), &xOff);
+ bOk = (aCell.indexOf( aQuer ) != -1);
+ }
+ if (rEntry.eOp == SC_NOT_EQUAL)
+ bOk = !bOk;
+ }
+ else
+ { // use collator here because data was probably sorted
+ sal_Int32 nCompare = rCollator.compareString(
+ aCellStr, rEntry.GetQueryItem().maString.getString());
+ switch (rEntry.eOp)
+ {
+ case SC_LESS :
+ bOk = (nCompare < 0);
+ break;
+ case SC_GREATER :
+ bOk = (nCompare > 0);
+ break;
+ case SC_LESS_EQUAL :
+ bOk = (nCompare <= 0);
+ break;
+ case SC_GREATER_EQUAL :
+ bOk = (nCompare >= 0);
+ break;
+ case SC_NOT_EQUAL:
+ OSL_FAIL("SC_NOT_EQUAL");
+ break;
+ case SC_TOPVAL:
+ case SC_BOTVAL:
+ case SC_TOPPERC:
+ case SC_BOTPERC:
+ default:
+ break;
+ }
+ }
+ }
+ }
+
+ if (nPos == -1)
+ {
+ nPos++;
+ aPassed[nPos] = bOk;
+ }
+ else
+ {
+ if (rEntry.eConnect == SC_AND)
+ {
+ aPassed[nPos] = aPassed[nPos] && bOk;
+ }
+ else
+ {
+ nPos++;
+ aPassed[nPos] = bOk;
+ }
+ }
+ }
+
+ for (tools::Long j=1; j <= nPos; j++)
+ aPassed[0] = aPassed[0] || aPassed[j];
+
+ bool bRet = aPassed[0];
+ return bRet;
+}
+
+ScDocument& ScDPCache::GetDoc() const
+{
+ return mrDoc;
+}
+
+tools::Long ScDPCache::GetColumnCount() const
+{
+ return mnColumnCount;
+}
+
+bool ScDPCache::IsRowEmpty(SCROW nRow) const
+{
+ bool bEmpty = true;
+ maEmptyRows.search_tree(nRow, bEmpty);
+ return bEmpty;
+}
+
+const ScDPCache::GroupItems* ScDPCache::GetGroupItems(tools::Long nDim) const
+{
+ if (nDim < 0)
+ return nullptr;
+
+ tools::Long nSourceCount = static_cast<tools::Long>(maFields.size());
+ if (nDim < nSourceCount)
+ return maFields[nDim]->mpGroup.get();
+
+ nDim -= nSourceCount;
+ if (nDim < static_cast<tools::Long>(maGroupFields.size()))
+ return maGroupFields[nDim].get();
+
+ return nullptr;
+}
+
+OUString ScDPCache::GetDimensionName(std::vector<OUString>::size_type nDim) const
+{
+ OSL_ENSURE(nDim < maLabelNames.size()-1 , "ScDPTableDataCache::GetDimensionName");
+ OSL_ENSURE(maLabelNames.size() == static_cast <sal_uInt16> (mnColumnCount+1), "ScDPTableDataCache::GetDimensionName");
+
+ if ( nDim+1 < maLabelNames.size() )
+ {
+ return maLabelNames[nDim+1];
+ }
+ else
+ return OUString();
+}
+
+void ScDPCache::PostInit()
+{
+ OSL_ENSURE(!maFields.empty(), "Cache not initialized!");
+
+ maEmptyRows.build_tree();
+ auto it = maEmptyRows.rbegin();
+ OSL_ENSURE(it != maEmptyRows.rend(), "corrupt flat_segment_tree instance!");
+ mnDataSize = maFields[0]->maData.size();
+ ++it; // Skip the first position.
+ OSL_ENSURE(it != maEmptyRows.rend(), "buggy version of flat_segment_tree is used.");
+ if (it->second)
+ {
+ SCROW nLastNonEmpty = it->first - 1;
+ if (nLastNonEmpty+1 < mnDataSize)
+ mnDataSize = nLastNonEmpty+1;
+ }
+}
+
+void ScDPCache::Clear()
+{
+ mnColumnCount = 0;
+ mnRowCount = 0;
+ maFields.clear();
+ maLabelNames.clear();
+ maGroupFields.clear();
+ maEmptyRows.clear();
+ maStringPools.clear();
+}
+
+SCROW ScDPCache::GetItemDataId(sal_uInt16 nDim, SCROW nRow, bool bRepeatIfEmpty) const
+{
+ OSL_ENSURE(nDim < mnColumnCount, "ScDPTableDataCache::GetItemDataId ");
+
+ const Field& rField = *maFields[nDim];
+ if (o3tl::make_unsigned(nRow) >= rField.maData.size())
+ {
+ // nRow is in the trailing empty rows area.
+ if (bRepeatIfEmpty)
+ nRow = rField.maData.size()-1; // Move to the last non-empty row.
+ else
+ // Return the last item, which should always be empty if the
+ // initialization has skipped trailing empty rows.
+ return rField.maItems.size()-1;
+
+ }
+ else if (bRepeatIfEmpty)
+ {
+ while (nRow > 0 && rField.maItems[rField.maData[nRow]].IsEmpty())
+ --nRow;
+ }
+
+ return rField.maData[nRow];
+}
+
+const ScDPItemData* ScDPCache::GetItemDataById(tools::Long nDim, SCROW nId) const
+{
+ if (nDim < 0 || nId < 0)
+ return nullptr;
+
+ size_t nSourceCount = maFields.size();
+ size_t nDimPos = static_cast<size_t>(nDim);
+ size_t nItemId = static_cast<size_t>(nId);
+ if (nDimPos < nSourceCount)
+ {
+ // source field.
+ const Field& rField = *maFields[nDimPos];
+ if (nItemId < rField.maItems.size())
+ return &rField.maItems[nItemId];
+
+ if (!rField.mpGroup)
+ return nullptr;
+
+ nItemId -= rField.maItems.size();
+ const ScDPItemDataVec& rGI = rField.mpGroup->maItems;
+ if (nItemId >= rGI.size())
+ return nullptr;
+
+ return &rGI[nItemId];
+ }
+
+ // Try group fields.
+ nDimPos -= nSourceCount;
+ if (nDimPos >= maGroupFields.size())
+ return nullptr;
+
+ const ScDPItemDataVec& rGI = maGroupFields[nDimPos]->maItems;
+ if (nItemId >= rGI.size())
+ return nullptr;
+
+ return &rGI[nItemId];
+}
+
+size_t ScDPCache::GetFieldCount() const
+{
+ return maFields.size();
+}
+
+size_t ScDPCache::GetGroupFieldCount() const
+{
+ return maGroupFields.size();
+}
+
+SCROW ScDPCache::GetRowCount() const
+{
+ return mnRowCount;
+}
+
+SCROW ScDPCache::GetDataSize() const
+{
+ OSL_ENSURE(mnDataSize <= GetRowCount(), "Data size should never be larger than the row count.");
+ return mnDataSize >= 0 ? mnDataSize : 0;
+}
+
+const ScDPCache::IndexArrayType* ScDPCache::GetFieldIndexArray( size_t nDim ) const
+{
+ if (nDim >= maFields.size())
+ return nullptr;
+
+ return &maFields[nDim]->maData;
+}
+
+const ScDPCache::ScDPItemDataVec& ScDPCache::GetDimMemberValues(SCCOL nDim) const
+{
+ OSL_ENSURE( nDim>=0 && nDim < mnColumnCount ," nDim < mnColumnCount ");
+ return maFields.at(nDim)->maItems;
+}
+
+sal_uInt32 ScDPCache::GetNumberFormat( tools::Long nDim ) const
+{
+ if ( nDim >= mnColumnCount )
+ return 0;
+
+ // TODO: Find a way to determine the dominant number format in presence of
+ // multiple number formats in the same field.
+ return maFields[nDim]->mnNumFormat;
+}
+
+bool ScDPCache::IsDateDimension( tools::Long nDim ) const
+{
+ if (nDim >= mnColumnCount)
+ return false;
+
+ SvNumberFormatter* pFormatter = mrDoc.GetFormatTable();
+ if (!pFormatter)
+ return false;
+
+ SvNumFormatType eType = pFormatter->GetType(maFields[nDim]->mnNumFormat);
+ return (eType == SvNumFormatType::DATE) || (eType == SvNumFormatType::DATETIME);
+}
+
+tools::Long ScDPCache::GetDimMemberCount(tools::Long nDim) const
+{
+ OSL_ENSURE( nDim>=0 && nDim < mnColumnCount ," ScDPTableDataCache::GetDimMemberCount : out of bound ");
+ return maFields[nDim]->maItems.size();
+}
+
+SCCOL ScDPCache::GetDimensionIndex(std::u16string_view sName) const
+{
+ for (size_t i = 1; i < maLabelNames.size(); ++i)
+ {
+ if (maLabelNames[i] == sName)
+ return static_cast<SCCOL>(i-1);
+ }
+ return -1;
+}
+
+rtl_uString* ScDPCache::InternString( size_t nDim, const OUString& rStr )
+{
+ assert(nDim < maStringPools.size());
+ return internString(maStringPools[nDim], rStr);
+}
+
+void ScDPCache::AddReference(ScDPObject* pObj) const
+{
+ maRefObjects.insert(pObj);
+}
+
+void ScDPCache::RemoveReference(ScDPObject* pObj) const
+{
+ if (mbDisposing)
+ // Object being deleted.
+ return;
+
+ maRefObjects.erase(pObj);
+ if (maRefObjects.empty())
+ mrDoc.GetDPCollection()->RemoveCache(this);
+}
+
+const ScDPCache::ScDPObjectSet& ScDPCache::GetAllReferences() const
+{
+ return maRefObjects;
+}
+
+SCROW ScDPCache::GetIdByItemData(tools::Long nDim, const ScDPItemData& rItem) const
+{
+ if (nDim < 0)
+ return -1;
+
+ if (nDim < mnColumnCount)
+ {
+ // source field.
+ const ScDPItemDataVec& rItems = maFields[nDim]->maItems;
+ for (size_t i = 0, n = rItems.size(); i < n; ++i)
+ {
+ if (rItems[i] == rItem)
+ return i;
+ }
+
+ if (!maFields[nDim]->mpGroup)
+ return -1;
+
+ // grouped source field.
+ const ScDPItemDataVec& rGI = maFields[nDim]->mpGroup->maItems;
+ for (size_t i = 0, n = rGI.size(); i < n; ++i)
+ {
+ if (rGI[i] == rItem)
+ return rItems.size() + i;
+ }
+ return -1;
+ }
+
+ // group field.
+ nDim -= mnColumnCount;
+ if (o3tl::make_unsigned(nDim) < maGroupFields.size())
+ {
+ const ScDPItemDataVec& rGI = maGroupFields[nDim]->maItems;
+ for (size_t i = 0, n = rGI.size(); i < n; ++i)
+ {
+ if (rGI[i] == rItem)
+ return i;
+ }
+ }
+
+ return -1;
+}
+
+// static
+sal_uInt32 ScDPCache::GetLocaleIndependentFormat( SvNumberFormatter& rFormatter, sal_uInt32 nNumFormat )
+{
+ // For a date or date+time format use ISO format so it works across locales
+ // and can be matched against string based item queries. For time use 24h
+ // format. All others use General format, no currency, percent, ...
+ // Use en-US locale for all.
+ switch (rFormatter.GetType( nNumFormat))
+ {
+ case SvNumFormatType::DATE:
+ return rFormatter.GetFormatIndex( NF_DATE_ISO_YYYYMMDD, LANGUAGE_ENGLISH_US);
+ case SvNumFormatType::TIME:
+ return rFormatter.GetFormatIndex( NF_TIME_HHMMSS, LANGUAGE_ENGLISH_US);
+ case SvNumFormatType::DATETIME:
+ return rFormatter.GetFormatIndex( NF_DATETIME_ISO_YYYYMMDD_HHMMSS, LANGUAGE_ENGLISH_US);
+ default:
+ return rFormatter.GetFormatIndex( NF_NUMBER_STANDARD, LANGUAGE_ENGLISH_US);
+ }
+}
+
+// static
+OUString ScDPCache::GetLocaleIndependentFormattedNumberString( double fValue )
+{
+ return rtl::math::doubleToUString( fValue, rtl_math_StringFormat_Automatic, rtl_math_DecimalPlaces_Max, '.', true);
+}
+
+// static
+OUString ScDPCache::GetLocaleIndependentFormattedString( double fValue,
+ SvNumberFormatter& rFormatter, sal_uInt32 nNumFormat )
+{
+ nNumFormat = GetLocaleIndependentFormat( rFormatter, nNumFormat);
+ if ((nNumFormat % SV_COUNTRY_LANGUAGE_OFFSET) == 0)
+ return GetLocaleIndependentFormattedNumberString( fValue);
+
+ OUString aStr;
+ const Color* pColor = nullptr;
+ rFormatter.GetOutputString( fValue, nNumFormat, aStr, &pColor);
+ return aStr;
+}
+
+OUString ScDPCache::GetFormattedString(tools::Long nDim, const ScDPItemData& rItem, bool bLocaleIndependent) const
+{
+ if (nDim < 0)
+ return rItem.GetString();
+
+ ScDPItemData::Type eType = rItem.GetType();
+ if (eType == ScDPItemData::Value)
+ {
+ // Format value using the stored number format.
+ SvNumberFormatter* pFormatter = mrDoc.GetFormatTable();
+ if (pFormatter)
+ {
+ sal_uInt32 nNumFormat = GetNumberFormat(nDim);
+ if (bLocaleIndependent)
+ return GetLocaleIndependentFormattedString( rItem.GetValue(), *pFormatter, nNumFormat);
+
+ OUString aStr;
+ const Color* pColor = nullptr;
+ pFormatter->GetOutputString(rItem.GetValue(), nNumFormat, aStr, &pColor);
+ return aStr;
+ }
+
+ // Last resort...
+ return GetLocaleIndependentFormattedNumberString( rItem.GetValue());
+ }
+
+ if (eType == ScDPItemData::GroupValue)
+ {
+ ScDPItemData::GroupValueAttr aAttr = rItem.GetGroupValue();
+ double fStart = 0.0, fEnd = 0.0;
+ const GroupItems* p = GetGroupItems(nDim);
+ if (p)
+ {
+ fStart = p->maInfo.mfStart;
+ fEnd = p->maInfo.mfEnd;
+ }
+ return ScDPUtil::getDateGroupName(
+ aAttr.mnGroupType, aAttr.mnValue, mrDoc.GetFormatTable(), fStart, fEnd);
+ }
+
+ if (eType == ScDPItemData::RangeStart)
+ {
+ double fVal = rItem.GetValue();
+ const GroupItems* p = GetGroupItems(nDim);
+ if (!p)
+ return rItem.GetString();
+
+ sal_Unicode cDecSep = ScGlobal::getLocaleData().getNumDecimalSep()[0];
+ return ScDPUtil::getNumGroupName(fVal, p->maInfo, cDecSep, mrDoc.GetFormatTable());
+ }
+
+ return rItem.GetString();
+}
+
+SvNumberFormatter* ScDPCache::GetNumberFormatter() const
+{
+ return mrDoc.GetFormatTable();
+}
+
+tools::Long ScDPCache::AppendGroupField()
+{
+ maGroupFields.push_back(std::make_unique<GroupItems>());
+ return static_cast<tools::Long>(maFields.size() + maGroupFields.size() - 1);
+}
+
+void ScDPCache::ResetGroupItems(tools::Long nDim, const ScDPNumGroupInfo& rNumInfo, sal_Int32 nGroupType)
+{
+ if (nDim < 0)
+ return;
+
+ tools::Long nSourceCount = static_cast<tools::Long>(maFields.size());
+ if (nDim < nSourceCount)
+ {
+ maFields.at(nDim)->mpGroup.reset(new GroupItems(rNumInfo, nGroupType));
+ return;
+ }
+
+ nDim -= nSourceCount;
+ if (nDim < static_cast<tools::Long>(maGroupFields.size()))
+ {
+ GroupItems& rGI = *maGroupFields[nDim];
+ rGI.maItems.clear();
+ rGI.maInfo = rNumInfo;
+ rGI.mnGroupType = nGroupType;
+ }
+}
+
+SCROW ScDPCache::SetGroupItem(tools::Long nDim, const ScDPItemData& rData)
+{
+ if (nDim < 0)
+ return -1;
+
+ tools::Long nSourceCount = static_cast<tools::Long>(maFields.size());
+ if (nDim < nSourceCount)
+ {
+ GroupItems& rGI = *maFields.at(nDim)->mpGroup;
+ rGI.maItems.push_back(rData);
+ SCROW nId = maFields[nDim]->maItems.size() + rGI.maItems.size() - 1;
+ return nId;
+ }
+
+ nDim -= nSourceCount;
+ if (nDim < static_cast<tools::Long>(maGroupFields.size()))
+ {
+ ScDPItemDataVec& rItems = maGroupFields.at(nDim)->maItems;
+ rItems.push_back(rData);
+ return rItems.size()-1;
+ }
+
+ return -1;
+}
+
+void ScDPCache::GetGroupDimMemberIds(tools::Long nDim, std::vector<SCROW>& rIds) const
+{
+ if (nDim < 0)
+ return;
+
+ tools::Long nSourceCount = static_cast<tools::Long>(maFields.size());
+ if (nDim < nSourceCount)
+ {
+ if (!maFields.at(nDim)->mpGroup)
+ return;
+
+ size_t nOffset = maFields[nDim]->maItems.size();
+ const ScDPItemDataVec& rGI = maFields[nDim]->mpGroup->maItems;
+ for (size_t i = 0, n = rGI.size(); i < n; ++i)
+ rIds.push_back(static_cast<SCROW>(i + nOffset));
+
+ return;
+ }
+
+ nDim -= nSourceCount;
+ if (nDim < static_cast<tools::Long>(maGroupFields.size()))
+ {
+ const ScDPItemDataVec& rGI = maGroupFields.at(nDim)->maItems;
+ for (size_t i = 0, n = rGI.size(); i < n; ++i)
+ rIds.push_back(static_cast<SCROW>(i));
+ }
+}
+
+namespace {
+
+struct ClearGroupItems
+{
+ void operator() (const std::unique_ptr<ScDPCache::Field>& r) const
+ {
+ r->mpGroup.reset();
+ }
+};
+
+}
+
+void ScDPCache::ClearGroupFields()
+{
+ maGroupFields.clear();
+}
+
+void ScDPCache::ClearAllFields()
+{
+ ClearGroupFields();
+ std::for_each(maFields.begin(), maFields.end(), ClearGroupItems());
+}
+
+const ScDPNumGroupInfo* ScDPCache::GetNumGroupInfo(tools::Long nDim) const
+{
+ if (nDim < 0)
+ return nullptr;
+
+ tools::Long nSourceCount = static_cast<tools::Long>(maFields.size());
+ if (nDim < nSourceCount)
+ {
+ if (!maFields.at(nDim)->mpGroup)
+ return nullptr;
+
+ return &maFields[nDim]->mpGroup->maInfo;
+ }
+
+ nDim -= nSourceCount;
+ if (nDim < static_cast<tools::Long>(maGroupFields.size()))
+ return &maGroupFields.at(nDim)->maInfo;
+
+ return nullptr;
+}
+
+sal_Int32 ScDPCache::GetGroupType(tools::Long nDim) const
+{
+ if (nDim < 0)
+ return 0;
+
+ tools::Long nSourceCount = static_cast<tools::Long>(maFields.size());
+ if (nDim < nSourceCount)
+ {
+ if (!maFields.at(nDim)->mpGroup)
+ return 0;
+
+ return maFields[nDim]->mpGroup->mnGroupType;
+ }
+
+ nDim -= nSourceCount;
+ if (nDim < static_cast<tools::Long>(maGroupFields.size()))
+ return maGroupFields.at(nDim)->mnGroupType;
+
+ return 0;
+}
+
+#if DUMP_PIVOT_TABLE
+
+namespace {
+
+void dumpItems(const ScDPCache& rCache, tools::Long nDim, const ScDPCache::ScDPItemDataVec& rItems, size_t nOffset)
+{
+ for (size_t i = 0; i < rItems.size(); ++i)
+ cout << " " << (i+nOffset) << ": " << rCache.GetFormattedString(nDim, rItems[i], false) << endl;
+}
+
+void dumpSourceData(const ScDPCache& rCache, tools::Long nDim, const ScDPCache::ScDPItemDataVec& rItems, const ScDPCache::IndexArrayType& rArray)
+{
+ for (const auto& rIndex : rArray)
+ cout << " '" << rCache.GetFormattedString(nDim, rItems[rIndex], false) << "'" << endl;
+}
+
+const char* getGroupTypeName(sal_Int32 nType)
+{
+ static const char* pNames[] = {
+ "", "years", "quarters", "months", "days", "hours", "minutes", "seconds"
+ };
+
+ switch (nType)
+ {
+ case sheet::DataPilotFieldGroupBy::YEARS: return pNames[1];
+ case sheet::DataPilotFieldGroupBy::QUARTERS: return pNames[2];
+ case sheet::DataPilotFieldGroupBy::MONTHS: return pNames[3];
+ case sheet::DataPilotFieldGroupBy::DAYS: return pNames[4];
+ case sheet::DataPilotFieldGroupBy::HOURS: return pNames[5];
+ case sheet::DataPilotFieldGroupBy::MINUTES: return pNames[6];
+ case sheet::DataPilotFieldGroupBy::SECONDS: return pNames[7];
+ default:
+ ;
+ }
+
+ return pNames[0];
+}
+
+}
+
+void ScDPCache::Dump() const
+{
+ // Change these flags to fit your debugging needs.
+ bool bDumpItems = false;
+ bool bDumpSourceData = false;
+
+ cout << "--- pivot cache dump" << endl;
+ {
+ size_t i = 0;
+ for (const auto& rxField : maFields)
+ {
+ const Field& fld = *rxField;
+ cout << "* source dimension: " << GetDimensionName(i) << " (ID = " << i << ")" << endl;
+ cout << " item count: " << fld.maItems.size() << endl;
+ if (bDumpItems)
+ dumpItems(*this, i, fld.maItems, 0);
+ if (fld.mpGroup)
+ {
+ cout << " group item count: " << fld.mpGroup->maItems.size() << endl;
+ cout << " group type: " << getGroupTypeName(fld.mpGroup->mnGroupType) << endl;
+ if (bDumpItems)
+ dumpItems(*this, i, fld.mpGroup->maItems, fld.maItems.size());
+ }
+
+ if (bDumpSourceData)
+ {
+ cout << " source data (re-constructed):" << endl;
+ dumpSourceData(*this, i, fld.maItems, fld.maData);
+ }
+
+ ++i;
+ }
+ }
+
+ {
+ size_t i = maFields.size();
+ for (const auto& rxGroupField : maGroupFields)
+ {
+ const GroupItems& gi = *rxGroupField;
+ cout << "* group dimension: (unnamed) (ID = " << i << ")" << endl;
+ cout << " item count: " << gi.maItems.size() << endl;
+ cout << " group type: " << getGroupTypeName(gi.mnGroupType) << endl;
+ if (bDumpItems)
+ dumpItems(*this, i, gi.maItems, 0);
+ ++i;
+ }
+ }
+
+ {
+ struct { SCROW start; SCROW end; bool empty; } aRange;
+ cout << "* empty rows: " << endl;
+ mdds::flat_segment_tree<SCROW, bool>::const_iterator it = maEmptyRows.begin(), itEnd = maEmptyRows.end();
+ if (it != itEnd)
+ {
+ aRange.start = it->first;
+ aRange.empty = it->second;
+
+ for (++it; it != itEnd; ++it)
+ {
+ aRange.end = it->first-1;
+ cout << " rows " << aRange.start << "-" << aRange.end << ": " << (aRange.empty ? "empty" : "not-empty") << endl;
+ aRange.start = it->first;
+ aRange.empty = it->second;
+ }
+ }
+ }
+
+ cout << "---" << endl;
+}
+
+#endif
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sc/source/core/data/dpdimsave.cxx b/sc/source/core/data/dpdimsave.cxx
new file mode 100644
index 000000000..07ba25f0a
--- /dev/null
+++ b/sc/source/core/data/dpdimsave.cxx
@@ -0,0 +1,791 @@
+/* -*- 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 <dpcache.hxx>
+#include <dpdimsave.hxx>
+#include <dpgroup.hxx>
+#include <dpobject.hxx>
+#include <dputil.hxx>
+#include <document.hxx>
+
+#include <com/sun/star/sheet/DataPilotFieldGroupBy.hpp>
+
+#include <svl/numformat.hxx>
+#include <osl/diagnose.h>
+#include <rtl/math.hxx>
+#include <algorithm>
+
+#include <globstr.hrc>
+#include <scresid.hxx>
+
+using namespace com::sun::star;
+
+ScDPSaveGroupItem::ScDPSaveGroupItem( const OUString& rName ) :
+ aGroupName(rName) {}
+
+ScDPSaveGroupItem::~ScDPSaveGroupItem() {}
+
+void ScDPSaveGroupItem::AddElement( const OUString& rName )
+{
+ aElements.push_back(rName);
+}
+
+void ScDPSaveGroupItem::AddElementsFromGroup( const ScDPSaveGroupItem& rGroup )
+{
+ // add all elements of the other group (used for nested grouping)
+ aElements.insert( aElements.end(), rGroup.aElements.begin(), rGroup.aElements.end() );
+}
+
+bool ScDPSaveGroupItem::RemoveElement( const OUString& rName )
+{
+ auto it = std::find(aElements.begin(), aElements.end(), rName); //TODO: ignore case
+ if (it != aElements.end())
+ {
+ aElements.erase(it);
+ return true;
+ }
+ return false; // not found
+}
+
+bool ScDPSaveGroupItem::IsEmpty() const
+{
+ return aElements.empty();
+}
+
+size_t ScDPSaveGroupItem::GetElementCount() const
+{
+ return aElements.size();
+}
+
+const OUString* ScDPSaveGroupItem::GetElementByIndex(size_t nIndex) const
+{
+ return (nIndex < aElements.size()) ? &aElements[ nIndex ] : nullptr;
+}
+
+void ScDPSaveGroupItem::Rename( const OUString& rNewName )
+{
+ aGroupName = rNewName;
+}
+
+void ScDPSaveGroupItem::RemoveElementsFromGroups( ScDPSaveGroupDimension& rDimension ) const
+{
+ // remove this group's elements from their groups in rDimension
+ // (rDimension must be a different dimension from the one which contains this)
+
+ for ( const auto& rElement : aElements )
+ rDimension.RemoveFromGroups( rElement );
+}
+
+void ScDPSaveGroupItem::ConvertElementsToItems(SvNumberFormatter* pFormatter) const
+{
+ maItems.reserve(aElements.size());
+ for (const auto& rElement : aElements)
+ {
+ sal_uInt32 nFormat = 0;
+ double fValue;
+ ScDPItemData aData;
+ if (pFormatter->IsNumberFormat(rElement, nFormat, fValue))
+ aData.SetValue(fValue);
+ else
+ aData.SetString(rElement);
+
+ maItems.push_back(aData);
+ }
+}
+
+bool ScDPSaveGroupItem::HasInGroup(const ScDPItemData& rItem) const
+{
+ return std::find(maItems.begin(), maItems.end(), rItem) != maItems.end();
+}
+
+void ScDPSaveGroupItem::AddToData(ScDPGroupDimension& rDataDim) const
+{
+ ScDPGroupItem aGroup(aGroupName);
+ for (const auto& rItem : maItems)
+ aGroup.AddElement(rItem);
+
+ rDataDim.AddItem(aGroup);
+}
+
+ScDPSaveGroupDimension::ScDPSaveGroupDimension( const OUString& rSource, const OUString& rName ) :
+ aSourceDim( rSource ),
+ aGroupDimName( rName ),
+ nDatePart( 0 )
+{
+}
+
+ScDPSaveGroupDimension::ScDPSaveGroupDimension( const OUString& rSource, const OUString& rName, const ScDPNumGroupInfo& rDateInfo, sal_Int32 nPart ) :
+ aSourceDim( rSource ),
+ aGroupDimName( rName ),
+ aDateInfo( rDateInfo ),
+ nDatePart( nPart )
+{
+}
+
+void ScDPSaveGroupDimension::SetDateInfo( const ScDPNumGroupInfo& rInfo, sal_Int32 nPart )
+{
+ aDateInfo = rInfo;
+ nDatePart = nPart;
+}
+
+void ScDPSaveGroupDimension::AddGroupItem( const ScDPSaveGroupItem& rItem )
+{
+ aGroups.push_back( rItem );
+}
+
+OUString ScDPSaveGroupDimension::CreateGroupName(std::u16string_view rPrefix)
+{
+ // create a name for a new group, using "Group1", "Group2" etc. (translated prefix in rPrefix)
+
+ //TODO: look in all dimensions, to avoid clashes with automatic groups (=name of base element)?
+ //TODO: (only dimensions for the same base)
+
+ sal_Int32 nAdd = 1; // first try is "Group1"
+ const sal_Int32 nMaxAdd = nAdd + aGroups.size(); // limit the loop
+ while ( nAdd <= nMaxAdd )
+ {
+ OUString aGroupName = rPrefix + OUString::number( nAdd );
+
+ // look for existing groups
+ bool bExists = std::any_of(aGroups.begin(), aGroups.end(),
+ [&aGroupName](const ScDPSaveGroupItem& rGroup) {
+ return rGroup.GetGroupName() == aGroupName; //TODO: ignore case
+ });
+
+ if ( !bExists )
+ return aGroupName; // found a new name
+
+ ++nAdd; // continue with higher number
+ }
+
+ OSL_FAIL("CreateGroupName: no valid name found");
+ return OUString();
+}
+
+const ScDPSaveGroupItem* ScDPSaveGroupDimension::GetNamedGroup( const OUString& rGroupName ) const
+{
+ return const_cast< ScDPSaveGroupDimension* >( this )->GetNamedGroupAcc( rGroupName );
+}
+
+ScDPSaveGroupItem* ScDPSaveGroupDimension::GetNamedGroupAcc( const OUString& rGroupName )
+{
+ auto aIter = std::find_if(aGroups.begin(), aGroups.end(),
+ [&rGroupName](const ScDPSaveGroupItem& rGroup) {
+ return rGroup.GetGroupName() == rGroupName; //TODO: ignore case
+ });
+ if (aIter != aGroups.end())
+ return &*aIter;
+
+ return nullptr; // none found
+}
+
+tools::Long ScDPSaveGroupDimension::GetGroupCount() const
+{
+ return aGroups.size();
+}
+
+const ScDPSaveGroupItem& ScDPSaveGroupDimension::GetGroupByIndex( tools::Long nIndex ) const
+{
+ return aGroups[nIndex];
+}
+
+
+void ScDPSaveGroupDimension::RemoveFromGroups( const OUString& rItemName )
+{
+ // if the item is in any group, remove it from the group,
+ // also remove the group if it is empty afterwards
+
+ for ( ScDPSaveGroupItemVec::iterator aIter(aGroups.begin()); aIter != aGroups.end(); ++aIter )
+ if ( aIter->RemoveElement( rItemName ) )
+ {
+ if ( aIter->IsEmpty() ) // removed last item from the group?
+ aGroups.erase( aIter ); // then remove the group
+
+ return; // don't have to look further
+ }
+}
+
+void ScDPSaveGroupDimension::RemoveGroup(const OUString& rGroupName)
+{
+ auto aIter = std::find_if(aGroups.begin(), aGroups.end(),
+ [&rGroupName](const ScDPSaveGroupItem& rGroup) {
+ return rGroup.GetGroupName() == rGroupName; //TODO: ignore case
+ });
+ if (aIter != aGroups.end())
+ aGroups.erase( aIter );
+}
+
+bool ScDPSaveGroupDimension::IsEmpty() const
+{
+ return aGroups.empty();
+}
+
+bool ScDPSaveGroupDimension::HasOnlyHidden(const ScDPUniqueStringSet& rVisible)
+{
+ // check if there are only groups that don't appear in the list of visible names
+
+ return std::none_of(aGroups.begin(), aGroups.end(),
+ [&rVisible](const ScDPSaveGroupItem& rGroup) { return rVisible.count(rGroup.GetGroupName()) > 0; });
+}
+
+void ScDPSaveGroupDimension::Rename( const OUString& rNewName )
+{
+ aGroupDimName = rNewName;
+}
+
+bool ScDPSaveGroupDimension::IsInGroup(const ScDPItemData& rItem) const
+{
+ return std::any_of(aGroups.begin(), aGroups.end(),
+ [&rItem](const ScDPSaveGroupItem& rGroup) { return rGroup.HasInGroup(rItem); });
+}
+
+namespace {
+
+bool isInteger(double fValue)
+{
+ return rtl::math::approxEqual(fValue, rtl::math::approxFloor(fValue));
+}
+
+void fillDateGroupDimension(
+ ScDPCache& rCache, ScDPNumGroupInfo& rDateInfo, tools::Long nSourceDim, tools::Long nGroupDim,
+ sal_Int32 nDatePart, const SvNumberFormatter* pFormatter)
+{
+ // Auto min/max is only used for "Years" part, but the loop is always
+ // needed.
+ double fSourceMin = 0.0;
+ double fSourceMax = 0.0;
+ bool bFirst = true;
+
+ const ScDPCache::ScDPItemDataVec& rItems = rCache.GetDimMemberValues(nSourceDim);
+ for (const ScDPItemData& rItem : rItems)
+ {
+ if (rItem.GetType() != ScDPItemData::Value)
+ continue;
+
+ double fVal = rItem.GetValue();
+ if (bFirst)
+ {
+ fSourceMin = fSourceMax = fVal;
+ bFirst = false;
+ }
+ else
+ {
+ if (fVal < fSourceMin)
+ fSourceMin = fVal;
+ if ( fVal > fSourceMax )
+ fSourceMax = fVal;
+ }
+ }
+
+ // For the start/end values, use the same date rounding as in
+ // ScDPNumGroupDimension::GetNumEntries (but not for the list of
+ // available years).
+ if (rDateInfo.mbAutoStart)
+ rDateInfo.mfStart = rtl::math::approxFloor(fSourceMin);
+ if (rDateInfo.mbAutoEnd)
+ rDateInfo.mfEnd = rtl::math::approxFloor(fSourceMax) + 1;
+
+ //TODO: if not automatic, limit fSourceMin/fSourceMax for list of year values?
+
+ tools::Long nStart = 0, nEnd = 0; // end is inclusive
+
+ switch (nDatePart)
+ {
+ case sheet::DataPilotFieldGroupBy::YEARS:
+ nStart = ScDPUtil::getDatePartValue(
+ fSourceMin, nullptr, sheet::DataPilotFieldGroupBy::YEARS, pFormatter);
+ nEnd = ScDPUtil::getDatePartValue(fSourceMax, nullptr, sheet::DataPilotFieldGroupBy::YEARS, pFormatter);
+ break;
+ case sheet::DataPilotFieldGroupBy::QUARTERS: nStart = 1; nEnd = 4; break;
+ case sheet::DataPilotFieldGroupBy::MONTHS: nStart = 1; nEnd = 12; break;
+ case sheet::DataPilotFieldGroupBy::DAYS: nStart = 1; nEnd = 366; break;
+ case sheet::DataPilotFieldGroupBy::HOURS: nStart = 0; nEnd = 23; break;
+ case sheet::DataPilotFieldGroupBy::MINUTES: nStart = 0; nEnd = 59; break;
+ case sheet::DataPilotFieldGroupBy::SECONDS: nStart = 0; nEnd = 59; break;
+ default:
+ OSL_FAIL("invalid date part");
+ }
+
+ // Now, populate the group items in the cache.
+ rCache.ResetGroupItems(nGroupDim, rDateInfo, nDatePart);
+
+ for (tools::Long nValue = nStart; nValue <= nEnd; ++nValue)
+ rCache.SetGroupItem(nGroupDim, ScDPItemData(nDatePart, nValue));
+
+ // add first/last entry (min/max)
+ rCache.SetGroupItem(nGroupDim, ScDPItemData(nDatePart, ScDPItemData::DateFirst));
+ rCache.SetGroupItem(nGroupDim, ScDPItemData(nDatePart, ScDPItemData::DateLast));
+}
+
+}
+
+void ScDPSaveGroupDimension::AddToData( ScDPGroupTableData& rData ) const
+{
+ tools::Long nSourceIndex = rData.GetDimensionIndex( aSourceDim );
+ if ( nSourceIndex < 0 )
+ return;
+
+ ScDPGroupDimension aDim( nSourceIndex, aGroupDimName );
+ if ( nDatePart )
+ {
+ // date grouping
+
+ aDim.SetDateDimension();
+ }
+ else
+ {
+ // normal (manual) grouping
+
+ for (const auto& rGroup : aGroups)
+ rGroup.AddToData(aDim);
+ }
+
+ rData.AddGroupDimension( aDim );
+}
+
+void ScDPSaveGroupDimension::AddToCache(ScDPCache& rCache) const
+{
+ tools::Long nSourceDim = rCache.GetDimensionIndex(aSourceDim);
+ if (nSourceDim < 0)
+ return;
+
+ tools::Long nDim = rCache.AppendGroupField();
+ SvNumberFormatter* pFormatter = rCache.GetDoc().GetFormatTable();
+
+ if (nDatePart)
+ {
+ fillDateGroupDimension(rCache, aDateInfo, nSourceDim, nDim, nDatePart, pFormatter);
+ return;
+ }
+
+ rCache.ResetGroupItems(nDim, aDateInfo, 0);
+ for (const ScDPSaveGroupItem& rGI : aGroups)
+ {
+ rGI.ConvertElementsToItems(pFormatter);
+ rCache.SetGroupItem(nDim, ScDPItemData(rGI.GetGroupName()));
+ }
+
+ const ScDPCache::ScDPItemDataVec& rItems = rCache.GetDimMemberValues(nSourceDim);
+ for (const ScDPItemData& rItem : rItems)
+ {
+ if (!IsInGroup(rItem))
+ // Not in any group. Add as its own group.
+ rCache.SetGroupItem(nDim, rItem);
+ }
+}
+
+ScDPSaveNumGroupDimension::ScDPSaveNumGroupDimension( const OUString& rName, const ScDPNumGroupInfo& rInfo ) :
+ aDimensionName( rName ),
+ aGroupInfo( rInfo ),
+ nDatePart( 0 )
+{
+}
+
+ScDPSaveNumGroupDimension::ScDPSaveNumGroupDimension( const OUString& rName, const ScDPNumGroupInfo& rDateInfo, sal_Int32 nPart ) :
+ aDimensionName( rName ),
+ aDateInfo( rDateInfo ),
+ nDatePart( nPart )
+{
+}
+
+void ScDPSaveNumGroupDimension::AddToData( ScDPGroupTableData& rData ) const
+{
+ tools::Long nSource = rData.GetDimensionIndex( aDimensionName );
+ if ( nSource >= 0 )
+ {
+ ScDPNumGroupDimension aDim( aGroupInfo ); // aGroupInfo: value grouping
+ if ( nDatePart )
+ aDim.SetDateDimension();
+
+ rData.SetNumGroupDimension( nSource, aDim );
+ }
+}
+
+void ScDPSaveNumGroupDimension::AddToCache(ScDPCache& rCache) const
+{
+ tools::Long nDim = rCache.GetDimensionIndex(aDimensionName);
+ if (nDim < 0)
+ return;
+
+ if (aDateInfo.mbEnable)
+ {
+ // Date grouping
+ SvNumberFormatter* pFormatter = rCache.GetDoc().GetFormatTable();
+ fillDateGroupDimension(rCache, aDateInfo, nDim, nDim, nDatePart, pFormatter);
+ }
+ else if (aGroupInfo.mbEnable)
+ {
+ // Number-range grouping
+
+ // Look through the source entries for non-integer numbers, minimum
+ // and maximum.
+
+ // non-integer GroupInfo values count, too
+ aGroupInfo.mbIntegerOnly =
+ (aGroupInfo.mbAutoStart || isInteger(aGroupInfo.mfStart)) &&
+ (aGroupInfo.mbAutoEnd || isInteger(aGroupInfo.mfEnd)) &&
+ isInteger(aGroupInfo.mfStep);
+
+ double fSourceMin = 0.0;
+ double fSourceMax = 0.0;
+ bool bFirst = true;
+
+ const ScDPCache::ScDPItemDataVec& rItems = rCache.GetDimMemberValues(nDim);
+ for (const ScDPItemData& rItem : rItems)
+ {
+ if (rItem.GetType() != ScDPItemData::Value)
+ continue;
+
+ double fValue = rItem.GetValue();
+ if (bFirst)
+ {
+ fSourceMin = fSourceMax = fValue;
+ bFirst = false;
+ continue;
+ }
+
+ if (fValue < fSourceMin)
+ fSourceMin = fValue;
+ if (fValue > fSourceMax)
+ fSourceMax = fValue;
+
+ if (aGroupInfo.mbIntegerOnly && !isInteger(fValue))
+ {
+ // If any non-integer numbers are involved, the group labels
+ // are shown including their upper limit.
+ aGroupInfo.mbIntegerOnly = false;
+ }
+ }
+
+ if (aGroupInfo.mbDateValues)
+ {
+ // special handling for dates: always integer, round down limits
+ aGroupInfo.mbIntegerOnly = true;
+ fSourceMin = rtl::math::approxFloor(fSourceMin);
+ fSourceMax = rtl::math::approxFloor(fSourceMax) + 1;
+ }
+
+ if (aGroupInfo.mbAutoStart)
+ aGroupInfo.mfStart = fSourceMin;
+ if (aGroupInfo.mbAutoEnd)
+ aGroupInfo.mfEnd = fSourceMax;
+
+ //TODO: limit number of entries?
+
+ tools::Long nLoopCount = 0;
+ double fLoop = aGroupInfo.mfStart;
+
+ rCache.ResetGroupItems(nDim, aGroupInfo, 0);
+
+ // Use "less than" instead of "less or equal" for the loop - don't
+ // create a group that consists only of the end value. Instead, the
+ // end value is then included in the last group (last group is bigger
+ // than the others). The first group has to be created nonetheless.
+ // GetNumGroupForValue has corresponding logic.
+
+ bool bFirstGroup = true;
+ while (bFirstGroup || (fLoop < aGroupInfo.mfEnd && !rtl::math::approxEqual(fLoop, aGroupInfo.mfEnd)))
+ {
+ ScDPItemData aItem;
+ aItem.SetRangeStart(fLoop);
+ rCache.SetGroupItem(nDim, aItem);
+ ++nLoopCount;
+ fLoop = aGroupInfo.mfStart + nLoopCount * aGroupInfo.mfStep;
+ bFirstGroup = false;
+
+ // ScDPItemData values are compared with approxEqual
+ }
+
+ ScDPItemData aItem;
+ aItem.SetRangeFirst();
+ rCache.SetGroupItem(nDim, aItem);
+
+ aItem.SetRangeLast();
+ rCache.SetGroupItem(nDim, aItem);
+ }
+}
+
+void ScDPSaveNumGroupDimension::SetGroupInfo( const ScDPNumGroupInfo& rNew )
+{
+ aGroupInfo = rNew;
+}
+
+void ScDPSaveNumGroupDimension::SetDateInfo( const ScDPNumGroupInfo& rInfo, sal_Int32 nPart )
+{
+ aDateInfo = rInfo;
+ nDatePart = nPart;
+}
+
+namespace {
+
+struct ScDPSaveGroupDimNameFunc
+{
+ OUString maDimName;
+ explicit ScDPSaveGroupDimNameFunc( const OUString& rDimName ) : maDimName( rDimName ) {}
+ bool operator()( const ScDPSaveGroupDimension& rGroupDim ) const { return rGroupDim.GetGroupDimName() == maDimName; }
+};
+
+struct ScDPSaveGroupSourceNameFunc
+{
+ OUString maSrcDimName;
+ explicit ScDPSaveGroupSourceNameFunc( const OUString& rSrcDimName ) : maSrcDimName( rSrcDimName ) {}
+ bool operator()( const ScDPSaveGroupDimension& rGroupDim ) const { return rGroupDim.GetSourceDimName() == maSrcDimName; }
+};
+
+} // namespace
+
+ScDPDimensionSaveData::ScDPDimensionSaveData()
+{
+}
+
+bool ScDPDimensionSaveData::operator==( const ScDPDimensionSaveData& ) const
+{
+ return false;
+}
+
+void ScDPDimensionSaveData::AddGroupDimension( const ScDPSaveGroupDimension& rGroupDim )
+{
+ OSL_ENSURE( ::std::none_of( maGroupDims.begin(), maGroupDims.end(), ScDPSaveGroupDimNameFunc( rGroupDim.GetGroupDimName() ) ),
+ "ScDPDimensionSaveData::AddGroupDimension - group dimension exists already" );
+ // ReplaceGroupDimension() adds new or replaces existing
+ ReplaceGroupDimension( rGroupDim );
+}
+
+void ScDPDimensionSaveData::ReplaceGroupDimension( const ScDPSaveGroupDimension& rGroupDim )
+{
+ ScDPSaveGroupDimVec::iterator aIt = ::std::find_if(
+ maGroupDims.begin(), maGroupDims.end(), ScDPSaveGroupDimNameFunc( rGroupDim.GetGroupDimName() ) );
+ if( aIt == maGroupDims.end() )
+ maGroupDims.push_back( rGroupDim );
+ else
+ *aIt = rGroupDim;
+}
+
+void ScDPDimensionSaveData::RemoveGroupDimension( const OUString& rGroupDimName )
+{
+ ScDPSaveGroupDimVec::iterator aIt = ::std::find_if(
+ maGroupDims.begin(), maGroupDims.end(), ScDPSaveGroupDimNameFunc( rGroupDimName ) );
+ if( aIt != maGroupDims.end() )
+ maGroupDims.erase( aIt );
+}
+
+void ScDPDimensionSaveData::AddNumGroupDimension( const ScDPSaveNumGroupDimension& rGroupDim )
+{
+ OSL_ENSURE( maNumGroupDims.count( rGroupDim.GetDimensionName() ) == 0,
+ "ScDPDimensionSaveData::AddNumGroupDimension - numeric group dimension exists already" );
+ // ReplaceNumGroupDimension() adds new or replaces existing
+ ReplaceNumGroupDimension( rGroupDim );
+}
+
+void ScDPDimensionSaveData::ReplaceNumGroupDimension( const ScDPSaveNumGroupDimension& rGroupDim )
+{
+ ScDPSaveNumGroupDimMap::iterator aIt = maNumGroupDims.find( rGroupDim.GetDimensionName() );
+ if( aIt == maNumGroupDims.end() )
+ maNumGroupDims.emplace( rGroupDim.GetDimensionName(), rGroupDim );
+ else
+ aIt->second = rGroupDim;
+}
+
+void ScDPDimensionSaveData::RemoveNumGroupDimension( const OUString& rGroupDimName )
+{
+ maNumGroupDims.erase( rGroupDimName );
+}
+
+void ScDPDimensionSaveData::WriteToData( ScDPGroupTableData& rData ) const
+{
+ // rData is assumed to be empty
+ // AddToData also handles date grouping
+
+ for( const auto& rGroupDim : maGroupDims )
+ rGroupDim.AddToData( rData );
+
+ for( const auto& rEntry : maNumGroupDims )
+ rEntry.second.AddToData( rData );
+}
+
+void ScDPDimensionSaveData::WriteToCache(ScDPCache& rCache) const
+{
+ for (const auto& rEntry : maGroupDims)
+ rEntry.AddToCache(rCache);
+ for (const auto& rEntry : maNumGroupDims)
+ rEntry.second.AddToCache(rCache);
+}
+
+const ScDPSaveGroupDimension* ScDPDimensionSaveData::GetGroupDimForBase( const OUString& rBaseDimName ) const
+{
+ return const_cast< ScDPDimensionSaveData* >( this )->GetGroupDimAccForBase( rBaseDimName );
+}
+
+const ScDPSaveGroupDimension* ScDPDimensionSaveData::GetNamedGroupDim( const OUString& rGroupDimName ) const
+{
+ return const_cast< ScDPDimensionSaveData* >( this )->GetNamedGroupDimAcc( rGroupDimName );
+}
+
+const ScDPSaveGroupDimension* ScDPDimensionSaveData::GetFirstNamedGroupDim( const OUString& rBaseDimName ) const
+{
+ return const_cast< ScDPDimensionSaveData* >( this )->GetFirstNamedGroupDimAcc( rBaseDimName );
+}
+
+const ScDPSaveGroupDimension* ScDPDimensionSaveData::GetNextNamedGroupDim( const OUString& rGroupDimName ) const
+{
+ return const_cast< ScDPDimensionSaveData* >( this )->GetNextNamedGroupDimAcc( rGroupDimName );
+}
+
+const ScDPSaveNumGroupDimension* ScDPDimensionSaveData::GetNumGroupDim( const OUString& rGroupDimName ) const
+{
+ return const_cast< ScDPDimensionSaveData* >( this )->GetNumGroupDimAcc( rGroupDimName );
+}
+
+ScDPSaveGroupDimension* ScDPDimensionSaveData::GetGroupDimAccForBase( const OUString& rBaseDimName )
+{
+ ScDPSaveGroupDimension* pGroupDim = GetFirstNamedGroupDimAcc( rBaseDimName );
+ return pGroupDim ? pGroupDim : GetNextNamedGroupDimAcc( rBaseDimName );
+}
+
+ScDPSaveGroupDimension* ScDPDimensionSaveData::GetNamedGroupDimAcc( const OUString& rGroupDimName )
+{
+ ScDPSaveGroupDimVec::iterator aIt = ::std::find_if(
+ maGroupDims.begin(), maGroupDims.end(), ScDPSaveGroupDimNameFunc( rGroupDimName ) );
+ return (aIt == maGroupDims.end()) ? nullptr : &*aIt;
+}
+
+ScDPSaveGroupDimension* ScDPDimensionSaveData::GetFirstNamedGroupDimAcc( const OUString& rBaseDimName )
+{
+ ScDPSaveGroupDimVec::iterator aIt = ::std::find_if(
+ maGroupDims.begin(), maGroupDims.end(), ScDPSaveGroupSourceNameFunc( rBaseDimName ) );
+ return (aIt == maGroupDims.end()) ? nullptr : &*aIt;
+}
+
+ScDPSaveGroupDimension* ScDPDimensionSaveData::GetNextNamedGroupDimAcc( const OUString& rGroupDimName )
+{
+ // find the group dimension with the passed name
+ ScDPSaveGroupDimVec::iterator aIt = ::std::find_if(
+ maGroupDims.begin(), maGroupDims.end(), ScDPSaveGroupDimNameFunc( rGroupDimName ) );
+ // find next group dimension based on the same source dimension name
+ if( aIt != maGroupDims.end() )
+ aIt = ::std::find_if( aIt + 1, maGroupDims.end(), ScDPSaveGroupSourceNameFunc( aIt->GetSourceDimName() ) );
+ return (aIt == maGroupDims.end()) ? nullptr : &*aIt;
+}
+
+ScDPSaveNumGroupDimension* ScDPDimensionSaveData::GetNumGroupDimAcc( const OUString& rGroupDimName )
+{
+ ScDPSaveNumGroupDimMap::iterator aIt = maNumGroupDims.find( rGroupDimName );
+ return (aIt == maNumGroupDims.end()) ? nullptr : &aIt->second;
+}
+
+bool ScDPDimensionSaveData::HasGroupDimensions() const
+{
+ return !maGroupDims.empty() || !maNumGroupDims.empty();
+}
+
+sal_Int32 ScDPDimensionSaveData::CollectDateParts( const OUString& rBaseDimName ) const
+{
+ sal_Int32 nParts = 0;
+ // start with part of numeric group
+ if( const ScDPSaveNumGroupDimension* pNumDim = GetNumGroupDim( rBaseDimName ) )
+ nParts |= pNumDim->GetDatePart();
+ // collect parts from all matching group dimensions
+ for( const ScDPSaveGroupDimension* pGroupDim = GetFirstNamedGroupDim( rBaseDimName ); pGroupDim; pGroupDim = GetNextNamedGroupDim( pGroupDim->GetGroupDimName() ) )
+ nParts |= pGroupDim->GetDatePart();
+
+ return nParts;
+}
+
+OUString ScDPDimensionSaveData::CreateGroupDimName(
+ const OUString& rSourceName, const ScDPObject& rObject, bool bAllowSource,
+ const std::vector<OUString>* pDeletedNames )
+{
+ // create a name for the new dimension by appending a number to the original
+ // dimension's name
+
+ bool bUseSource = bAllowSource; // if set, try the unchanged original name first
+
+ sal_Int32 nAdd = 2; // first try is "Name2"
+ const sal_Int32 nMaxAdd = 1000; // limit the loop
+ while ( nAdd <= nMaxAdd )
+ {
+ OUString aDimName( rSourceName );
+ if ( !bUseSource )
+ aDimName += OUString::number(nAdd);
+
+ // look for existing group dimensions
+ bool bExists = std::any_of(maGroupDims.begin(), maGroupDims.end(),
+ [&aDimName](const ScDPSaveGroupDimension& rDim) {
+ return rDim.GetGroupDimName() == aDimName; //TODO: ignore case
+ });
+
+ // look for base dimensions that happen to have that name
+ if ( !bExists && rObject.IsDimNameInUse( aDimName ) )
+ {
+ if ( pDeletedNames &&
+ std::find( pDeletedNames->begin(), pDeletedNames->end(), aDimName ) != pDeletedNames->end() )
+ {
+ // allow the name anyway if the name is in pDeletedNames
+ }
+ else
+ bExists = true;
+ }
+
+ if ( !bExists )
+ return aDimName; // found a new name
+
+ if ( bUseSource )
+ bUseSource = false;
+ else
+ ++nAdd; // continue with higher number
+ }
+ OSL_FAIL("CreateGroupDimName: no valid name found");
+ return OUString();
+}
+
+namespace
+{
+ const TranslateId aDatePartIds[] =
+ {
+ STR_DPFIELD_GROUP_BY_SECONDS,
+ STR_DPFIELD_GROUP_BY_MINUTES,
+ STR_DPFIELD_GROUP_BY_HOURS,
+ STR_DPFIELD_GROUP_BY_DAYS,
+ STR_DPFIELD_GROUP_BY_MONTHS,
+ STR_DPFIELD_GROUP_BY_QUARTERS,
+ STR_DPFIELD_GROUP_BY_YEARS
+ };
+}
+
+OUString ScDPDimensionSaveData::CreateDateGroupDimName(
+ sal_Int32 nDatePart, const ScDPObject& rObject, bool bAllowSource,
+ const std::vector<OUString>* pDeletedNames )
+{
+ using namespace css::sheet::DataPilotFieldGroupBy;
+ OUString aPartName;
+ switch( nDatePart )
+ {
+ case SECONDS: aPartName = ScResId(aDatePartIds[0]); break;
+ case MINUTES: aPartName = ScResId(aDatePartIds[1]); break;
+ case HOURS: aPartName = ScResId(aDatePartIds[2]); break;
+ case DAYS: aPartName = ScResId(aDatePartIds[3]); break;
+ case MONTHS: aPartName = ScResId(aDatePartIds[4]); break;
+ case QUARTERS: aPartName = ScResId(aDatePartIds[5]); break;
+ case YEARS: aPartName = ScResId(aDatePartIds[6]); break;
+ }
+ OSL_ENSURE(!aPartName.isEmpty(), "ScDPDimensionSaveData::CreateDateGroupDimName - invalid date part");
+ return CreateGroupDimName( aPartName, rObject, bAllowSource, pDeletedNames );
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sc/source/core/data/dpfilteredcache.cxx b/sc/source/core/data/dpfilteredcache.cxx
new file mode 100644
index 000000000..b47fc43ae
--- /dev/null
+++ b/sc/source/core/data/dpfilteredcache.cxx
@@ -0,0 +1,433 @@
+/* -*- 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 <dpcache.hxx>
+#include <dpfilteredcache.hxx>
+#include <address.hxx>
+#include <queryparam.hxx>
+#include <dpitemdata.hxx>
+#include <com/sun/star/uno/Sequence.hxx>
+#include <o3tl/safeint.hxx>
+#include <osl/diagnose.h>
+#include <algorithm>
+
+using ::std::vector;
+using ::com::sun::star::uno::Sequence;
+using ::com::sun::star::uno::Any;
+
+ScDPFilteredCache::SingleFilter::SingleFilter(const ScDPItemData& rItem) :
+ maItem(rItem) {}
+
+bool ScDPFilteredCache::SingleFilter::match(const ScDPItemData& rCellData) const
+{
+ return maItem == rCellData;
+}
+
+std::vector<ScDPItemData> ScDPFilteredCache::SingleFilter::getMatchValues() const
+{
+ return { maItem };
+}
+
+ScDPFilteredCache::GroupFilter::GroupFilter()
+{
+}
+
+bool ScDPFilteredCache::GroupFilter::match(const ScDPItemData& rCellData) const
+{
+ return std::find(maItems.begin(), maItems.end(), rCellData) != maItems.end();
+}
+
+std::vector<ScDPItemData> ScDPFilteredCache::GroupFilter::getMatchValues() const
+{
+ return maItems;
+}
+
+void ScDPFilteredCache::GroupFilter::addMatchItem(const ScDPItemData& rItem)
+{
+ maItems.push_back(rItem);
+}
+
+size_t ScDPFilteredCache::GroupFilter::getMatchItemCount() const
+{
+ return maItems.size();
+}
+
+ScDPFilteredCache::Criterion::Criterion() :
+ mnFieldIndex(-1)
+{
+}
+
+ScDPFilteredCache::ScDPFilteredCache(const ScDPCache& rCache) :
+ maShowByFilter(0, MAXROW+1, false), maShowByPage(0, MAXROW+1, true), mrCache(rCache)
+{
+}
+
+ScDPFilteredCache::~ScDPFilteredCache()
+{
+}
+
+sal_Int32 ScDPFilteredCache::getRowSize() const
+{
+ return mrCache.GetRowCount();
+}
+
+sal_Int32 ScDPFilteredCache::getColSize() const
+{
+ return mrCache.GetColumnCount();
+}
+
+void ScDPFilteredCache::fillTable(
+ const ScQueryParam& rQuery, bool bIgnoreEmptyRows, bool bRepeatIfEmpty)
+{
+ SCROW nRowCount = getRowSize();
+ SCROW nDataSize = mrCache.GetDataSize();
+ SCCOL nColCount = getColSize();
+ if (nRowCount <= 0 || nColCount <= 0)
+ return;
+
+ maShowByFilter.clear();
+ maShowByPage.clear();
+ maShowByPage.build_tree();
+
+ // Process the non-empty data rows.
+ for (SCROW nRow = 0; nRow < nDataSize; ++nRow)
+ {
+ if (!getCache().ValidQuery(nRow, rQuery))
+ continue;
+
+ if (bIgnoreEmptyRows && getCache().IsRowEmpty(nRow))
+ continue;
+
+ maShowByFilter.insert_back(nRow, nRow+1, true);
+ }
+
+ // Process the trailing empty rows.
+ if (!bIgnoreEmptyRows)
+ maShowByFilter.insert_back(nDataSize, nRowCount, true);
+
+ maShowByFilter.build_tree();
+
+ // Initialize field entries container.
+ maFieldEntries.clear();
+ maFieldEntries.reserve(nColCount);
+
+ // Build unique field entries.
+ for (SCCOL nCol = 0; nCol < nColCount; ++nCol)
+ {
+ maFieldEntries.emplace_back( );
+ SCROW nMemCount = getCache().GetDimMemberCount( nCol );
+ if (!nMemCount)
+ continue;
+
+ std::vector<SCROW> aAdded(nMemCount, -1);
+ bool bShow = false;
+ SCROW nEndSegment = -1;
+ for (SCROW nRow = 0; nRow < nRowCount; ++nRow)
+ {
+ if (nRow > nEndSegment)
+ {
+ if (!maShowByFilter.search_tree(nRow, bShow, nullptr, &nEndSegment).second)
+ {
+ OSL_FAIL("Tree search failed!");
+ continue;
+ }
+ --nEndSegment; // End position is not inclusive. Move back one.
+ }
+
+ if (!bShow)
+ {
+ nRow = nEndSegment;
+ continue;
+ }
+
+ SCROW nIndex = getCache().GetItemDataId(nCol, nRow, bRepeatIfEmpty);
+ aAdded[nIndex] = nIndex;
+
+ // tdf#96588 - large numbers of trailing identical empty
+ // rows generate the same nIndex & nOrder.
+ if (nRow == nDataSize)
+ break;
+ }
+ for (SCROW nRow = 0; nRow < nMemCount; ++nRow)
+ {
+ if (aAdded[nRow] != -1)
+ maFieldEntries.back().push_back(aAdded[nRow]);
+ }
+ }
+}
+
+void ScDPFilteredCache::fillTable()
+{
+ SCROW nRowCount = getRowSize();
+ SCCOL nColCount = getColSize();
+ if (nRowCount <= 0 || nColCount <= 0)
+ return;
+
+ maShowByPage.clear();
+ maShowByPage.build_tree();
+
+ maShowByFilter.clear();
+ maShowByFilter.insert_front(0, nRowCount, true);
+ maShowByFilter.build_tree();
+
+ // Initialize field entries container.
+ maFieldEntries.clear();
+ maFieldEntries.reserve(nColCount);
+
+ // Data rows
+ for (SCCOL nCol = 0; nCol < nColCount; ++nCol)
+ {
+ maFieldEntries.emplace_back( );
+ SCROW nMemCount = getCache().GetDimMemberCount( nCol );
+ if (!nMemCount)
+ continue;
+
+ std::vector<SCROW> aAdded(nMemCount, -1);
+
+ for (SCROW nRow = 0; nRow < nRowCount; ++nRow)
+ {
+ SCROW nIndex = getCache().GetItemDataId(nCol, nRow, false);
+ aAdded[nIndex] = nIndex;
+ }
+ for (SCROW nRow = 0; nRow < nMemCount; ++nRow)
+ {
+ if (aAdded[nRow] != -1)
+ maFieldEntries.back().push_back(aAdded[nRow]);
+ }
+ }
+}
+
+bool ScDPFilteredCache::isRowActive(sal_Int32 nRow, sal_Int32* pLastRow) const
+{
+ bool bFilter = false, bPage = true;
+ SCROW nLastRowFilter = MAXROW, nLastRowPage = MAXROW;
+ maShowByFilter.search_tree(nRow, bFilter, nullptr, &nLastRowFilter);
+ maShowByPage.search_tree(nRow, bPage, nullptr, &nLastRowPage);
+ if (pLastRow)
+ {
+ // Return the last row of current segment.
+ *pLastRow = std::min(nLastRowFilter, nLastRowPage);
+ *pLastRow -= 1; // End position is not inclusive. Move back one.
+ }
+
+ return bFilter && bPage;
+}
+
+void ScDPFilteredCache::filterByPageDimension(const vector<Criterion>& rCriteria, const std::unordered_set<sal_Int32>& rRepeatIfEmptyDims)
+{
+ SCROW nRowSize = getRowSize();
+ SCROW nDataSize = mrCache.GetDataSize();
+
+ maShowByPage.clear();
+
+ for (SCROW nRow = 0; nRow < nDataSize; ++nRow)
+ {
+ bool bShow = isRowQualified(nRow, rCriteria, rRepeatIfEmptyDims);
+ maShowByPage.insert_back(nRow, nRow+1, bShow);
+ }
+
+ // tdf#96588 - rapidly extend for blank rows with identical data
+ if (nDataSize < nRowSize)
+ {
+ bool bBlankShow = isRowQualified(nDataSize, rCriteria, rRepeatIfEmptyDims);
+ maShowByPage.insert_back(nDataSize, nRowSize, bBlankShow);
+ }
+
+ maShowByPage.build_tree();
+}
+
+const ScDPItemData* ScDPFilteredCache::getCell(SCCOL nCol, SCROW nRow, bool bRepeatIfEmpty) const
+{
+ SCROW nId= mrCache.GetItemDataId(nCol, nRow, bRepeatIfEmpty);
+ return mrCache.GetItemDataById( nCol, nId );
+}
+
+void ScDPFilteredCache::getValue( ScDPValue& rVal, SCCOL nCol, SCROW nRow) const
+{
+ const ScDPItemData* pData = getCell( nCol, nRow, false/*bRepeatIfEmpty*/ );
+
+ if (pData)
+ {
+ rVal.mfValue = pData->IsValue() ? pData->GetValue() : 0.0;
+ rVal.meType = pData->GetCellType();
+ }
+ else
+ rVal.Set(0.0, ScDPValue::Empty);
+}
+
+OUString ScDPFilteredCache::getFieldName(SCCOL nIndex) const
+{
+ return mrCache.GetDimensionName(nIndex);
+}
+
+const ::std::vector<SCROW>& ScDPFilteredCache::getFieldEntries( sal_Int32 nColumn ) const
+{
+ if (nColumn < 0 || o3tl::make_unsigned(nColumn) >= maFieldEntries.size())
+ {
+ // index out of bound. Hopefully this code will never be reached.
+ static const ::std::vector<SCROW> emptyEntries{};
+ return emptyEntries;
+ }
+ return maFieldEntries[nColumn];
+}
+
+void ScDPFilteredCache::filterTable(const vector<Criterion>& rCriteria, Sequence< Sequence<Any> >& rTabData,
+ const std::unordered_set<sal_Int32>& rRepeatIfEmptyDims)
+{
+ sal_Int32 nRowSize = getRowSize();
+ SCCOL nColSize = getColSize();
+
+ if (!nRowSize)
+ // no data to filter.
+ return;
+
+ // Row first, then column.
+ vector< Sequence<Any> > tableData;
+ tableData.reserve(nRowSize+1);
+
+ // Header first.
+ Sequence<Any> headerRow(nColSize);
+ auto pRow = headerRow.getArray();
+ for (SCCOL nCol = 0; nCol < nColSize; ++nCol)
+ {
+ OUString str = getFieldName( nCol);
+ Any any;
+ any <<= str;
+ pRow[nCol] = any;
+ }
+ tableData.push_back(headerRow);
+
+ for (sal_Int32 nRow = 0; nRow < nRowSize; ++nRow)
+ {
+ sal_Int32 nLastRow;
+ if (!isRowActive(nRow, &nLastRow))
+ {
+ // This row is filtered out.
+ nRow = nLastRow;
+ continue;
+ }
+
+ if (!isRowQualified(nRow, rCriteria, rRepeatIfEmptyDims))
+ continue;
+
+ // Insert this row into table.
+
+ Sequence<Any> row(nColSize);
+ pRow = row.getArray();
+ for (SCCOL nCol = 0; nCol < nColSize; ++nCol)
+ {
+ Any any;
+ bool bRepeatIfEmpty = rRepeatIfEmptyDims.count(nCol) > 0;
+ const ScDPItemData* pData= getCell(nCol, nRow, bRepeatIfEmpty);
+ if ( pData->IsValue() )
+ any <<= pData->GetValue();
+ else
+ {
+ OUString string (pData->GetString() );
+ any <<= string;
+ }
+ pRow[nCol] = any;
+ }
+ tableData.push_back(row);
+ }
+
+ // convert vector to Sequence
+ sal_Int32 nTabSize = static_cast<sal_Int32>(tableData.size());
+ rTabData.realloc(nTabSize);
+ auto pTabData = rTabData.getArray();
+ for (sal_Int32 i = 0; i < nTabSize; ++i)
+ pTabData[i] = tableData[i];
+}
+
+void ScDPFilteredCache::clear()
+{
+ maFieldEntries.clear();
+ maShowByFilter.clear();
+ maShowByPage.clear();
+}
+
+bool ScDPFilteredCache::empty() const
+{
+ return maFieldEntries.empty();
+}
+
+bool ScDPFilteredCache::isRowQualified(sal_Int32 nRow, const vector<Criterion>& rCriteria,
+ const std::unordered_set<sal_Int32>& rRepeatIfEmptyDims) const
+{
+ sal_Int32 nColSize = getColSize();
+ for (const auto& rCriterion : rCriteria)
+ {
+ if (rCriterion.mnFieldIndex >= nColSize)
+ // specified field is outside the source data columns. Don't
+ // use this criterion.
+ continue;
+
+ // Check if the 'repeat if empty' flag is set for this field.
+ bool bRepeatIfEmpty = rRepeatIfEmptyDims.count(rCriterion.mnFieldIndex) > 0;
+ const ScDPItemData* pCellData = getCell(static_cast<SCCOL>(rCriterion.mnFieldIndex), nRow, bRepeatIfEmpty);
+ if (!rCriterion.mpFilter->match(*pCellData))
+ return false;
+ }
+ return true;
+}
+
+#if DUMP_PIVOT_TABLE
+
+void ScDPFilteredCache::dumpRowFlag( const RowFlagType& rFlag )
+{
+ RowFlagType::const_iterator it = rFlag.begin(), itEnd = rFlag.end();
+ bool bShow = it->second;
+ SCROW nRow1 = it->first;
+ for (++it; it != itEnd; ++it)
+ {
+ SCROW nRow2 = it->first;
+ cout << " * range " << nRow1 << "-" << nRow2 << ": " << (bShow ? "on" : "off") << endl;
+ bShow = it->second;
+ nRow1 = nRow2;
+ }
+}
+
+void ScDPFilteredCache::dump() const
+{
+ cout << "--- pivot filtered cache dump" << endl;
+
+ cout << endl;
+ cout << "* show by filter" << endl;
+ dumpRowFlag(maShowByFilter);
+
+ cout << endl;
+ cout << "* show by page dimensions" << endl;
+ dumpRowFlag(maShowByPage);
+
+ cout << endl;
+ cout << "* field entries" << endl;
+ size_t nFieldCount = maFieldEntries.size();
+ for (size_t i = 0; i < nFieldCount; ++i)
+ {
+ const vector<SCROW>& rField = maFieldEntries[i];
+ cout << " * field " << i << endl;
+ for (size_t j = 0, n = rField.size(); j < n; ++j)
+ cout << " ID: " << rField[j] << endl;
+ }
+ cout << "---" << endl;
+}
+
+#endif
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sc/source/core/data/dpglobal.cxx b/sc/source/core/data/dpglobal.cxx
new file mode 100644
index 000000000..ac72054e7
--- /dev/null
+++ b/sc/source/core/data/dpglobal.cxx
@@ -0,0 +1,20 @@
+/* -*- 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/.
+ */
+
+#include <dpglobal.hxx>
+
+ScDPValue::ScDPValue() : mfValue(0.0), meType(String) {}
+
+void ScDPValue::Set( double fV, Type eT )
+{
+ mfValue = fV;
+ meType = eT;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sc/source/core/data/dpgroup.cxx b/sc/source/core/data/dpgroup.cxx
new file mode 100644
index 000000000..52bc6476f
--- /dev/null
+++ b/sc/source/core/data/dpgroup.cxx
@@ -0,0 +1,1030 @@
+/* -*- 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 <dpgroup.hxx>
+
+#include <dpcache.hxx>
+#include <document.hxx>
+#include <dpfilteredcache.hxx>
+#include <dputil.hxx>
+
+#include <osl/diagnose.h>
+#include <rtl/math.hxx>
+#include <svl/numformat.hxx>
+
+#include <com/sun/star/sheet/DataPilotFieldGroupBy.hpp>
+
+#include <unordered_map>
+#include <unordered_set>
+#include <vector>
+#include <algorithm>
+
+using namespace ::com::sun::star;
+using ::com::sun::star::uno::Any;
+using ::com::sun::star::uno::Sequence;
+
+using ::std::vector;
+using ::std::shared_ptr;
+
+const sal_uInt16 SC_DP_LEAPYEAR = 1648; // arbitrary leap year for date calculations
+
+namespace {
+
+class ScDPGroupNumFilter : public ScDPFilteredCache::FilterBase
+{
+public:
+ ScDPGroupNumFilter(std::vector<ScDPItemData>&& rValues, const ScDPNumGroupInfo& rInfo);
+
+ virtual bool match(const ScDPItemData &rCellData) const override;
+ virtual std::vector<ScDPItemData> getMatchValues() const override;
+private:
+ std::vector<ScDPItemData> maValues;
+ ScDPNumGroupInfo maNumInfo;
+};
+
+}
+
+ScDPGroupNumFilter::ScDPGroupNumFilter( std::vector<ScDPItemData>&& rValues, const ScDPNumGroupInfo& rInfo) :
+ maValues(std::move(rValues)), maNumInfo(rInfo) {}
+
+bool ScDPGroupNumFilter::match(const ScDPItemData& rCellData) const
+{
+ if (rCellData.GetType() != ScDPItemData::Value)
+ return false;
+
+ for (const auto& rValue : maValues)
+ {
+ double fVal = rValue.GetValue();
+ if (std::isinf(fVal))
+ {
+ if (std::signbit(fVal))
+ {
+ // Less than the min value.
+ if (rCellData.GetValue() < maNumInfo.mfStart)
+ return true;
+ }
+
+ // Greater than the max value.
+ if (maNumInfo.mfEnd < rCellData.GetValue())
+ return true;
+
+ continue;
+ }
+
+ double low = fVal;
+ double high = low + maNumInfo.mfStep;
+ if (maNumInfo.mbIntegerOnly)
+ high += 1.0;
+
+ if (low <= rCellData.GetValue() && rCellData.GetValue() < high)
+ return true;
+ }
+
+ return false;
+}
+
+std::vector<ScDPItemData> ScDPGroupNumFilter::getMatchValues() const
+{
+ return std::vector<ScDPItemData>();
+}
+
+namespace {
+
+class ScDPGroupDateFilter : public ScDPFilteredCache::FilterBase
+{
+public:
+ ScDPGroupDateFilter(
+ std::vector<ScDPItemData>&& rValues, const Date& rNullDate, const ScDPNumGroupInfo& rNumInfo);
+
+ virtual bool match(const ScDPItemData & rCellData) const override;
+ virtual std::vector<ScDPItemData> getMatchValues() const override;
+
+private:
+ std::vector<ScDPItemData> maValues;
+ Date maNullDate;
+ ScDPNumGroupInfo maNumInfo;
+};
+
+}
+
+ScDPGroupDateFilter::ScDPGroupDateFilter(
+ std::vector<ScDPItemData>&& rValues, const Date& rNullDate, const ScDPNumGroupInfo& rNumInfo) :
+ maValues(std::move(rValues)),
+ maNullDate(rNullDate),
+ maNumInfo(rNumInfo)
+{
+}
+
+bool ScDPGroupDateFilter::match( const ScDPItemData & rCellData ) const
+{
+ using namespace ::com::sun::star::sheet;
+ using ::rtl::math::approxFloor;
+ using ::rtl::math::approxEqual;
+
+ if ( !rCellData.IsValue() )
+ return false;
+
+ for (const ScDPItemData& rValue : maValues)
+ {
+ if (rValue.GetType() != ScDPItemData::GroupValue)
+ continue;
+
+ sal_Int32 nGroupType = rValue.GetGroupValue().mnGroupType;
+ sal_Int32 nValue = rValue.GetGroupValue().mnValue;
+
+ // Start and end dates are inclusive. (An end date without a time value
+ // is included, while an end date with a time value is not.)
+
+ if (rCellData.GetValue() < maNumInfo.mfStart && !approxEqual(rCellData.GetValue(), maNumInfo.mfStart))
+ {
+ if (nValue == ScDPItemData::DateFirst)
+ return true;
+ continue;
+ }
+
+ if (rCellData.GetValue() > maNumInfo.mfEnd && !approxEqual(rCellData.GetValue(), maNumInfo.mfEnd))
+ {
+ if (nValue == ScDPItemData::DateLast)
+ return true;
+ continue;
+ }
+
+ if (nGroupType == DataPilotFieldGroupBy::HOURS || nGroupType == DataPilotFieldGroupBy::MINUTES ||
+ nGroupType == DataPilotFieldGroupBy::SECONDS)
+ {
+ // handle time
+ // (do as in the cell functions, ScInterpreter::ScGetHour() etc.)
+
+ sal_uInt16 nHour, nMinute, nSecond;
+ double fFractionOfSecond;
+ tools::Time::GetClock( rCellData.GetValue(), nHour, nMinute, nSecond, fFractionOfSecond, 0);
+
+ switch (nGroupType)
+ {
+ case DataPilotFieldGroupBy::HOURS:
+ {
+ if (nHour == nValue)
+ return true;
+ }
+ break;
+ case DataPilotFieldGroupBy::MINUTES:
+ {
+ if (nMinute == nValue)
+ return true;
+ }
+ break;
+ case DataPilotFieldGroupBy::SECONDS:
+ {
+ if (nSecond == nValue)
+ return true;
+ }
+ break;
+ default:
+ OSL_FAIL("invalid time part");
+ }
+
+ continue;
+ }
+
+ Date date = maNullDate + static_cast<sal_Int32>(approxFloor(rCellData.GetValue()));
+ switch (nGroupType)
+ {
+ case DataPilotFieldGroupBy::YEARS:
+ {
+ sal_Int32 year = static_cast<sal_Int32>(date.GetYear());
+ if (year == nValue)
+ return true;
+ }
+ break;
+ case DataPilotFieldGroupBy::QUARTERS:
+ {
+ sal_Int32 qtr = 1 + (static_cast<sal_Int32>(date.GetMonth()) - 1) / 3;
+ if (qtr == nValue)
+ return true;
+ }
+ break;
+ case DataPilotFieldGroupBy::MONTHS:
+ {
+ sal_Int32 month = static_cast<sal_Int32>(date.GetMonth());
+ if (month == nValue)
+ return true;
+ }
+ break;
+ case DataPilotFieldGroupBy::DAYS:
+ {
+ Date yearStart(1, 1, date.GetYear());
+ sal_Int32 days = (date - yearStart) + 1; // Jan 01 has value 1
+ if (days >= 60 && !date.IsLeapYear())
+ {
+ // This is not a leap year. Adjust the value accordingly.
+ ++days;
+ }
+ if (days == nValue)
+ return true;
+ }
+ break;
+ default:
+ OSL_FAIL("invalid date part");
+ }
+ }
+
+ return false;
+}
+
+std::vector<ScDPItemData> ScDPGroupDateFilter::getMatchValues() const
+{
+ return std::vector<ScDPItemData>();
+}
+
+namespace {
+
+bool isDateInGroup(const ScDPItemData& rGroupItem, const ScDPItemData& rChildItem)
+{
+ if (rGroupItem.GetType() != ScDPItemData::GroupValue || rChildItem.GetType() != ScDPItemData::GroupValue)
+ return false;
+
+ sal_Int32 nGroupPart = rGroupItem.GetGroupValue().mnGroupType;
+ sal_Int32 nGroupValue = rGroupItem.GetGroupValue().mnValue;
+ sal_Int32 nChildPart = rChildItem.GetGroupValue().mnGroupType;
+ sal_Int32 nChildValue = rChildItem.GetGroupValue().mnValue;
+
+ if (nGroupValue == ScDPItemData::DateFirst || nGroupValue == ScDPItemData::DateLast ||
+ nChildValue == ScDPItemData::DateFirst || nChildValue == ScDPItemData::DateLast)
+ {
+ // first/last entry matches only itself
+ return nGroupValue == nChildValue;
+ }
+
+ switch (nChildPart) // inner part
+ {
+ case css::sheet::DataPilotFieldGroupBy::MONTHS:
+ // a month is only contained in its quarter
+ if (nGroupPart == css::sheet::DataPilotFieldGroupBy::QUARTERS)
+ // months and quarters are both 1-based
+ return (nGroupValue - 1 == (nChildValue - 1) / 3);
+ break;
+ case css::sheet::DataPilotFieldGroupBy::DAYS:
+ // a day is only contained in its quarter or month
+ if (nGroupPart == css::sheet::DataPilotFieldGroupBy::MONTHS ||
+ nGroupPart == css::sheet::DataPilotFieldGroupBy::QUARTERS)
+ {
+ Date aDate(1, 1, SC_DP_LEAPYEAR);
+ aDate.AddDays(nChildValue - 1); // days are 1-based
+ sal_Int32 nCompare = aDate.GetMonth();
+ if (nGroupPart == css::sheet::DataPilotFieldGroupBy::QUARTERS)
+ nCompare = ( ( nCompare - 1 ) / 3 ) + 1; // get quarter from date
+
+ return nGroupValue == nCompare;
+ }
+ break;
+ default:
+ ;
+ }
+
+ return true;
+}
+
+}
+
+ScDPGroupItem::ScDPGroupItem( const ScDPItemData& rName ) :
+ aGroupName( rName )
+{
+}
+
+void ScDPGroupItem::AddElement( const ScDPItemData& rName )
+{
+ aElements.push_back( rName );
+}
+
+bool ScDPGroupItem::HasElement( const ScDPItemData& rData ) const
+{
+ return std::any_of(aElements.begin(), aElements.end(),
+ [&rData](const ScDPItemData& rElement) { return rElement.IsCaseInsEqual(rData); });
+}
+
+bool ScDPGroupItem::HasCommonElement( const ScDPGroupItem& rOther ) const
+{
+ return std::any_of(aElements.begin(), aElements.end(),
+ [&rOther](const ScDPItemData& rElement) { return rOther.HasElement(rElement); });
+}
+
+void ScDPGroupItem::FillGroupFilter( ScDPFilteredCache::GroupFilter& rFilter ) const
+{
+ for (const auto& rElement : aElements)
+ rFilter.addMatchItem(rElement);
+}
+
+ScDPGroupDimension::ScDPGroupDimension( tools::Long nSource, const OUString& rNewName ) :
+ nSourceDim( nSource ),
+ nGroupDim( -1 ),
+ aGroupName( rNewName ),
+ mbDateDimension(false)
+{
+}
+
+ScDPGroupDimension::~ScDPGroupDimension()
+{
+ maMemberEntries.clear();
+}
+
+ScDPGroupDimension::ScDPGroupDimension( const ScDPGroupDimension& rOther ) :
+ nSourceDim( rOther.nSourceDim ),
+ nGroupDim( rOther.nGroupDim ),
+ aGroupName( rOther.aGroupName ),
+ aItems( rOther.aItems ),
+ mbDateDimension(rOther.mbDateDimension)
+{
+}
+
+ScDPGroupDimension& ScDPGroupDimension::operator=( const ScDPGroupDimension& rOther )
+{
+ nSourceDim = rOther.nSourceDim;
+ nGroupDim = rOther.nGroupDim;
+ aGroupName = rOther.aGroupName;
+ aItems = rOther.aItems;
+ mbDateDimension = rOther.mbDateDimension;
+ return *this;
+}
+
+void ScDPGroupDimension::AddItem( const ScDPGroupItem& rItem )
+{
+ aItems.push_back( rItem );
+}
+
+void ScDPGroupDimension::SetGroupDim( tools::Long nDim )
+{
+ nGroupDim = nDim;
+}
+
+const std::vector<SCROW>& ScDPGroupDimension::GetColumnEntries(
+ const ScDPFilteredCache& rCacheTable) const
+{
+ if (!maMemberEntries.empty())
+ return maMemberEntries;
+
+ rCacheTable.getCache().GetGroupDimMemberIds(nGroupDim, maMemberEntries);
+ return maMemberEntries;
+}
+
+const ScDPGroupItem* ScDPGroupDimension::GetGroupForData( const ScDPItemData& rData ) const
+{
+ auto aIter = std::find_if(aItems.begin(), aItems.end(),
+ [&rData](const ScDPGroupItem& rItem) { return rItem.HasElement(rData); });
+ if (aIter != aItems.end())
+ return &*aIter;
+
+ return nullptr;
+}
+
+const ScDPGroupItem* ScDPGroupDimension::GetGroupForName( const ScDPItemData& rName ) const
+{
+ auto aIter = std::find_if(aItems.begin(), aItems.end(),
+ [&rName](const ScDPGroupItem& rItem) { return rItem.GetName().IsCaseInsEqual(rName); });
+ if (aIter != aItems.end())
+ return &*aIter;
+
+ return nullptr;
+}
+
+const ScDPGroupItem* ScDPGroupDimension::GetGroupByIndex( size_t nIndex ) const
+{
+ if (nIndex >= aItems.size())
+ return nullptr;
+
+ return &aItems[nIndex];
+}
+
+void ScDPGroupDimension::DisposeData()
+{
+ maMemberEntries.clear();
+}
+
+void ScDPGroupDimension::SetDateDimension()
+{
+ mbDateDimension = true;
+}
+
+ScDPNumGroupDimension::ScDPNumGroupDimension() : mbDateDimension(false) {}
+
+ScDPNumGroupDimension::ScDPNumGroupDimension( const ScDPNumGroupInfo& rInfo ) :
+ aGroupInfo(rInfo), mbDateDimension(false) {}
+
+ScDPNumGroupDimension::ScDPNumGroupDimension( const ScDPNumGroupDimension& rOther ) :
+ aGroupInfo(rOther.aGroupInfo), mbDateDimension(rOther.mbDateDimension) {}
+
+ScDPNumGroupDimension& ScDPNumGroupDimension::operator=( const ScDPNumGroupDimension& rOther )
+{
+ aGroupInfo = rOther.aGroupInfo;
+ mbDateDimension = rOther.mbDateDimension;
+ return *this;
+}
+
+void ScDPNumGroupDimension::DisposeData()
+{
+ aGroupInfo = ScDPNumGroupInfo();
+ maMemberEntries.clear();
+}
+
+ScDPNumGroupDimension::~ScDPNumGroupDimension()
+{
+}
+
+void ScDPNumGroupDimension::SetDateDimension()
+{
+ aGroupInfo.mbEnable = true; //TODO: or query both?
+ mbDateDimension = true;
+}
+
+const std::vector<SCROW>& ScDPNumGroupDimension::GetNumEntries(
+ SCCOL nSourceDim, const ScDPCache* pCache) const
+{
+ if (!maMemberEntries.empty())
+ return maMemberEntries;
+
+ pCache->GetGroupDimMemberIds(nSourceDim, maMemberEntries);
+ return maMemberEntries;
+}
+
+ScDPGroupTableData::ScDPGroupTableData( const shared_ptr<ScDPTableData>& pSource, ScDocument* pDocument ) :
+ ScDPTableData(pDocument),
+ pSourceData( pSource ),
+ pDoc( pDocument )
+{
+ OSL_ENSURE( pSource, "ScDPGroupTableData: pSource can't be NULL" );
+
+ CreateCacheTable();
+ nSourceCount = pSource->GetColumnCount(); // real columns, excluding data layout
+ pNumGroups.reset( new ScDPNumGroupDimension[nSourceCount] );
+}
+
+ScDPGroupTableData::~ScDPGroupTableData()
+{
+}
+
+void ScDPGroupTableData::AddGroupDimension( const ScDPGroupDimension& rGroup )
+{
+ ScDPGroupDimension aNewGroup( rGroup );
+ aNewGroup.SetGroupDim( GetColumnCount() ); // new dimension will be at the end
+ aGroups.push_back( aNewGroup );
+}
+
+void ScDPGroupTableData::SetNumGroupDimension( sal_Int32 nIndex, const ScDPNumGroupDimension& rGroup )
+{
+ if ( nIndex < nSourceCount )
+ {
+ pNumGroups[nIndex] = rGroup;
+
+ // automatic minimum / maximum is handled in GetNumEntries
+ }
+}
+
+sal_Int32 ScDPGroupTableData::GetDimensionIndex( std::u16string_view rName )
+{
+ for (tools::Long i = 0; i < nSourceCount; ++i) // nSourceCount excludes data layout
+ if (pSourceData->getDimensionName(i) == rName) //TODO: ignore case?
+ return i;
+ return -1; // none
+}
+
+sal_Int32 ScDPGroupTableData::GetColumnCount()
+{
+ return nSourceCount + aGroups.size();
+}
+
+bool ScDPGroupTableData::IsNumGroupDimension( tools::Long nDimension ) const
+{
+ return ( nDimension < nSourceCount && pNumGroups[nDimension].GetInfo().mbEnable );
+}
+
+void ScDPGroupTableData::GetNumGroupInfo(tools::Long nDimension, ScDPNumGroupInfo& rInfo)
+{
+ if ( nDimension < nSourceCount )
+ rInfo = pNumGroups[nDimension].GetInfo();
+}
+sal_Int32 ScDPGroupTableData::GetMembersCount( sal_Int32 nDim )
+{
+ const std::vector< SCROW >& members = GetColumnEntries( nDim );
+ return members.size();
+}
+const std::vector< SCROW >& ScDPGroupTableData::GetColumnEntries( sal_Int32 nColumn )
+{
+ if ( nColumn >= nSourceCount )
+ {
+ if ( getIsDataLayoutDimension( nColumn) ) // data layout dimension?
+ nColumn = nSourceCount; // index of data layout in source data
+ else
+ {
+ const ScDPGroupDimension& rGroupDim = aGroups[nColumn - nSourceCount];
+ return rGroupDim.GetColumnEntries( GetCacheTable() );
+ }
+ }
+
+ if ( IsNumGroupDimension( nColumn ) )
+ {
+ // dimension number is unchanged for numerical groups
+ return pNumGroups[nColumn].GetNumEntries(
+ static_cast<SCCOL>(nColumn), &GetCacheTable().getCache());
+ }
+
+ return pSourceData->GetColumnEntries( nColumn );
+}
+
+const ScDPItemData* ScDPGroupTableData::GetMemberById( sal_Int32 nDim, sal_Int32 nId )
+{
+ return pSourceData->GetMemberById( nDim, nId );
+}
+
+OUString ScDPGroupTableData::getDimensionName(sal_Int32 nColumn)
+{
+ if ( nColumn >= nSourceCount )
+ {
+ if ( nColumn == sal::static_int_cast<tools::Long>( nSourceCount + aGroups.size() ) ) // data layout dimension?
+ nColumn = nSourceCount; // index of data layout in source data
+ else
+ return aGroups[nColumn - nSourceCount].GetName();
+ }
+
+ return pSourceData->getDimensionName( nColumn );
+}
+
+bool ScDPGroupTableData::getIsDataLayoutDimension(sal_Int32 nColumn)
+{
+ // position of data layout dimension is moved from source data
+ return ( nColumn == sal::static_int_cast<tools::Long>( nSourceCount + aGroups.size() ) ); // data layout dimension?
+}
+
+bool ScDPGroupTableData::IsDateDimension(sal_Int32 nDim)
+{
+ if ( nDim >= nSourceCount )
+ {
+ if ( nDim == sal::static_int_cast<tools::Long>( nSourceCount + aGroups.size() ) ) // data layout dimension?
+ nDim = nSourceCount; // index of data layout in source data
+ else
+ nDim = aGroups[nDim - nSourceCount].GetSourceDim(); // look at original dimension
+ }
+
+ return pSourceData->IsDateDimension( nDim );
+}
+
+sal_uInt32 ScDPGroupTableData::GetNumberFormat(sal_Int32 nDim)
+{
+ if ( nDim >= nSourceCount )
+ {
+ if ( nDim == sal::static_int_cast<tools::Long>( nSourceCount + aGroups.size() ) ) // data layout dimension?
+ nDim = nSourceCount; // index of data layout in source data
+ else
+ nDim = aGroups[nDim - nSourceCount].GetSourceDim(); // look at original dimension
+ }
+
+ return pSourceData->GetNumberFormat( nDim );
+}
+
+void ScDPGroupTableData::DisposeData()
+{
+ for ( auto& rGroup : aGroups )
+ rGroup.DisposeData();
+
+ for ( tools::Long i=0; i<nSourceCount; i++ )
+ pNumGroups[i].DisposeData();
+
+ pSourceData->DisposeData();
+}
+
+void ScDPGroupTableData::SetEmptyFlags( bool bIgnoreEmptyRows, bool bRepeatIfEmpty )
+{
+ pSourceData->SetEmptyFlags( bIgnoreEmptyRows, bRepeatIfEmpty );
+}
+
+bool ScDPGroupTableData::IsRepeatIfEmpty()
+{
+ return pSourceData->IsRepeatIfEmpty();
+}
+
+void ScDPGroupTableData::CreateCacheTable()
+{
+ pSourceData->CreateCacheTable();
+}
+
+namespace {
+
+class FindCaseInsensitive
+{
+ ScDPItemData maValue;
+public:
+ explicit FindCaseInsensitive(const ScDPItemData& rVal) : maValue(rVal) {}
+
+ bool operator() (const ScDPItemData& rItem) const
+ {
+ return maValue.IsCaseInsEqual(rItem);
+ }
+};
+
+}
+
+void ScDPGroupTableData::ModifyFilterCriteria(vector<ScDPFilteredCache::Criterion>& rCriteria)
+{
+ // Build dimension ID to object map for group dimensions.
+ typedef std::unordered_map<tools::Long, const ScDPGroupDimension*> GroupFieldMapType;
+ GroupFieldMapType aGroupFieldIds;
+
+ for (const auto& rGroup : aGroups)
+ {
+ aGroupFieldIds.emplace(rGroup.GetGroupDim(), &rGroup);
+ }
+
+ vector<ScDPFilteredCache::Criterion> aNewCriteria;
+ aNewCriteria.reserve(rCriteria.size() + aGroups.size());
+
+ // Go through all the filtered field names and process them appropriately.
+
+ const ScDPCache& rCache = GetCacheTable().getCache();
+ GroupFieldMapType::const_iterator itrGrpEnd = aGroupFieldIds.end();
+ for (const auto& rCriterion : rCriteria)
+ {
+ std::vector<ScDPItemData> aMatchValues = rCriterion.mpFilter->getMatchValues();
+
+ GroupFieldMapType::const_iterator itrGrp = aGroupFieldIds.find(rCriterion.mnFieldIndex);
+ if (itrGrp == itrGrpEnd)
+ {
+ if (IsNumGroupDimension(rCriterion.mnFieldIndex))
+ {
+ // internal number group field
+ const ScDPNumGroupInfo* pNumInfo = rCache.GetNumGroupInfo(rCriterion.mnFieldIndex);
+ if (!pNumInfo)
+ // Number group dimension without num info? Something is wrong...
+ continue;
+
+ ScDPFilteredCache::Criterion aCri;
+ aCri.mnFieldIndex = rCriterion.mnFieldIndex;
+ const ScDPNumGroupDimension& rNumGrpDim = pNumGroups[rCriterion.mnFieldIndex];
+
+ if (rNumGrpDim.IsDateDimension())
+ {
+ // grouped by dates.
+ aCri.mpFilter =
+ std::make_shared<ScDPGroupDateFilter>(
+ std::move(aMatchValues), pDoc->GetFormatTable()->GetNullDate(), *pNumInfo);
+ }
+ else
+ {
+ // This dimension is grouped by numeric ranges.
+ aCri.mpFilter =
+ std::make_shared<ScDPGroupNumFilter>(std::move(aMatchValues), *pNumInfo);
+ }
+
+ aNewCriteria.push_back(aCri);
+ }
+ else
+ {
+ // This is a regular source field.
+ aNewCriteria.push_back(rCriterion);
+ }
+ }
+ else
+ {
+ // This is an ordinary group field or external number group field.
+
+ const ScDPGroupDimension* pGrpDim = itrGrp->second;
+ tools::Long nSrcDim = pGrpDim->GetSourceDim();
+ tools::Long nGrpDim = pGrpDim->GetGroupDim();
+ const ScDPNumGroupInfo* pNumInfo = rCache.GetNumGroupInfo(nGrpDim);
+
+ if (pGrpDim->IsDateDimension() && pNumInfo)
+ {
+ // external number group
+ ScDPFilteredCache::Criterion aCri;
+ aCri.mnFieldIndex = nSrcDim; // use the source dimension, not the group dimension.
+ aCri.mpFilter =
+ std::make_shared<ScDPGroupDateFilter>(
+ std::move(aMatchValues), pDoc->GetFormatTable()->GetNullDate(), *pNumInfo);
+
+ aNewCriteria.push_back(aCri);
+ }
+ else
+ {
+ // normal group
+
+ ScDPFilteredCache::Criterion aCri;
+ aCri.mnFieldIndex = nSrcDim;
+ aCri.mpFilter = std::make_shared<ScDPFilteredCache::GroupFilter>();
+ ScDPFilteredCache::GroupFilter* pGrpFilter =
+ static_cast<ScDPFilteredCache::GroupFilter*>(aCri.mpFilter.get());
+
+ size_t nGroupItemCount = pGrpDim->GetItemCount();
+ for (size_t i = 0; i < nGroupItemCount; ++i)
+ {
+ const ScDPGroupItem* pGrpItem = pGrpDim->GetGroupByIndex(i);
+ if (!pGrpItem)
+ continue;
+
+ // Make sure this group name equals one of the match values.
+ if (std::none_of(aMatchValues.begin(), aMatchValues.end(), FindCaseInsensitive(pGrpItem->GetName())))
+ continue;
+
+ pGrpItem->FillGroupFilter(*pGrpFilter);
+ }
+
+ aNewCriteria.push_back(aCri);
+ }
+ }
+ }
+ rCriteria.swap(aNewCriteria);
+}
+
+void ScDPGroupTableData::FilterCacheTable(std::vector<ScDPFilteredCache::Criterion>&& rCriteria, std::unordered_set<sal_Int32>&& rCatDims)
+{
+ ModifyFilterCriteria(rCriteria);
+ pSourceData->FilterCacheTable(std::move(rCriteria), std::move(rCatDims));
+}
+
+void ScDPGroupTableData::GetDrillDownData(std::vector<ScDPFilteredCache::Criterion>&& rCriteria, std::unordered_set<sal_Int32>&&rCatDims, Sequence< Sequence<Any> >& rData)
+{
+ ModifyFilterCriteria(rCriteria);
+ pSourceData->GetDrillDownData(std::move(rCriteria), std::move(rCatDims), rData);
+}
+
+void ScDPGroupTableData::CalcResults(CalcInfo& rInfo, bool bAutoShow)
+{
+ // #i111435# Inside FillRowDataFromCacheTable/GetItemData, virtual methods
+ // getIsDataLayoutDimension and GetSourceDim are used, so it has to be called
+ // with original rInfo, containing dimension indexes of the grouped data.
+
+ const ScDPFilteredCache& rCacheTable = pSourceData->GetCacheTable();
+ sal_Int32 nRowSize = rCacheTable.getRowSize();
+ for (sal_Int32 nRow = 0; nRow < nRowSize; ++nRow)
+ {
+ sal_Int32 nLastRow;
+ if (!rCacheTable.isRowActive(nRow, &nLastRow))
+ {
+ nRow = nLastRow;
+ continue;
+ }
+
+ CalcRowData aData;
+ FillRowDataFromCacheTable(nRow, rCacheTable, rInfo, aData);
+
+ if ( !rInfo.aColLevelDims.empty() )
+ FillGroupValues(aData.aColData, rInfo.aColLevelDims);
+ if ( !rInfo.aRowLevelDims.empty() )
+ FillGroupValues(aData.aRowData, rInfo.aRowLevelDims);
+ if ( !rInfo.aPageDims.empty() )
+ FillGroupValues(aData.aPageData, rInfo.aPageDims);
+
+ ProcessRowData(rInfo, aData, bAutoShow);
+ }
+}
+
+const ScDPFilteredCache& ScDPGroupTableData::GetCacheTable() const
+{
+ return pSourceData->GetCacheTable();
+}
+
+void ScDPGroupTableData::ReloadCacheTable()
+{
+ pSourceData->ReloadCacheTable();
+}
+
+void ScDPGroupTableData::FillGroupValues(vector<SCROW>& rItems, const vector<sal_Int32>& rDims)
+{
+ sal_Int32 nGroupedColumns = aGroups.size();
+
+ const ScDPCache& rCache = GetCacheTable().getCache();
+ size_t i = 0;
+ for (tools::Long nColumn : rDims)
+ {
+ bool bDateDim = false;
+
+ sal_Int32 nSourceDim = nColumn;
+ if ( nColumn >= nSourceCount && nColumn < nSourceCount + nGroupedColumns )
+ {
+ const ScDPGroupDimension& rGroupDim = aGroups[nColumn - nSourceCount];
+ nSourceDim= rGroupDim.GetSourceDim();
+ bDateDim = rGroupDim.IsDateDimension();
+ if (!bDateDim) // date is handled below
+ {
+ const ScDPItemData& rItem = *GetMemberById(nSourceDim, rItems[i]);
+ const ScDPGroupItem* pGroupItem = rGroupDim.GetGroupForData(rItem);
+ if (pGroupItem)
+ {
+ rItems[i] =
+ rCache.GetIdByItemData(nColumn, pGroupItem->GetName());
+ }
+ else
+ rItems[i] = rCache.GetIdByItemData(nColumn, rItem);
+ }
+ }
+ else if ( IsNumGroupDimension( nColumn ) )
+ {
+ bDateDim = pNumGroups[nColumn].IsDateDimension();
+ if (!bDateDim) // date is handled below
+ {
+ const ScDPItemData* pData = rCache.GetItemDataById(nSourceDim, rItems[i]);
+ if (pData->GetType() == ScDPItemData::Value)
+ {
+ ScDPNumGroupInfo aNumInfo;
+ GetNumGroupInfo(nColumn, aNumInfo);
+ double fGroupValue = ScDPUtil::getNumGroupStartValue(pData->GetValue(), aNumInfo);
+ ScDPItemData aItemData;
+ aItemData.SetRangeStart(fGroupValue);
+ rItems[i] = rCache.GetIdByItemData(nSourceDim, aItemData);
+ }
+ // else (textual) keep original value
+ }
+ }
+
+ const ScDPNumGroupInfo* pNumInfo = rCache.GetNumGroupInfo(nColumn);
+
+ if (bDateDim && pNumInfo)
+ {
+ // This is a date group dimension.
+ sal_Int32 nDatePart = rCache.GetGroupType(nColumn);
+ const ScDPItemData* pData = rCache.GetItemDataById(nSourceDim, rItems[i]);
+ if (pData->GetType() == ScDPItemData::Value)
+ {
+ SvNumberFormatter* pFormatter = pDoc->GetFormatTable();
+ sal_Int32 nPartValue = ScDPUtil::getDatePartValue(
+ pData->GetValue(), pNumInfo, nDatePart, pFormatter);
+
+ ScDPItemData aItem(nDatePart, nPartValue);
+ rItems[i] = rCache.GetIdByItemData(nColumn, aItem);
+ }
+ }
+
+ ++i;
+ }
+}
+
+bool ScDPGroupTableData::IsBaseForGroup(sal_Int32 nDim) const
+{
+ return std::any_of(aGroups.begin(), aGroups.end(),
+ [&nDim](const ScDPGroupDimension& rDim) { return rDim.GetSourceDim() == nDim; });
+}
+
+sal_Int32 ScDPGroupTableData::GetGroupBase(sal_Int32 nGroupDim) const
+{
+ auto aIter = std::find_if(aGroups.begin(), aGroups.end(),
+ [&nGroupDim](const ScDPGroupDimension& rDim) { return rDim.GetGroupDim() == nGroupDim; });
+ if (aIter != aGroups.end())
+ return aIter->GetSourceDim();
+
+ return -1; // none
+}
+
+bool ScDPGroupTableData::IsNumOrDateGroup(sal_Int32 nDimension) const
+{
+ // Virtual method from ScDPTableData, used in result data to force text labels.
+
+ if ( nDimension < nSourceCount )
+ {
+ return pNumGroups[nDimension].GetInfo().mbEnable ||
+ pNumGroups[nDimension].IsDateDimension();
+ }
+
+ auto aIter = std::find_if(aGroups.begin(), aGroups.end(),
+ [&nDimension](const ScDPGroupDimension& rDim) { return rDim.GetGroupDim() == nDimension; });
+ if (aIter != aGroups.end())
+ return aIter->IsDateDimension();
+
+ return false;
+}
+
+bool ScDPGroupTableData::IsInGroup( const ScDPItemData& rGroupData, sal_Int32 nGroupIndex,
+ const ScDPItemData& rBaseData, sal_Int32 nBaseIndex ) const
+{
+ auto aIter = std::find_if(aGroups.begin(), aGroups.end(),
+ [&nGroupIndex, &nBaseIndex](const ScDPGroupDimension& rDim) {
+ return rDim.GetGroupDim() == nGroupIndex && rDim.GetSourceDim() == nBaseIndex; });
+ if (aIter != aGroups.end())
+ {
+ const ScDPGroupDimension& rDim = *aIter;
+ if (rDim.IsDateDimension())
+ {
+ return isDateInGroup(rGroupData, rBaseData);
+ }
+ else
+ {
+ // If the item is in a group, only that group is valid.
+ // If the item is not in any group, its own name is valid.
+
+ const ScDPGroupItem* pGroup = rDim.GetGroupForData( rBaseData );
+ return pGroup ? pGroup->GetName().IsCaseInsEqual( rGroupData ) :
+ rGroupData.IsCaseInsEqual( rBaseData );
+ }
+ }
+
+ OSL_FAIL("IsInGroup: no group dimension found");
+ return true;
+}
+
+bool ScDPGroupTableData::HasCommonElement( const ScDPItemData& rFirstData, sal_Int32 nFirstIndex,
+ const ScDPItemData& rSecondData, sal_Int32 nSecondIndex ) const
+{
+ const ScDPGroupDimension* pFirstDim = nullptr;
+ const ScDPGroupDimension* pSecondDim = nullptr;
+ for ( const auto& rDim : aGroups )
+ {
+ const ScDPGroupDimension* pDim = &rDim;
+ if ( pDim->GetGroupDim() == nFirstIndex )
+ pFirstDim = pDim;
+ else if ( pDim->GetGroupDim() == nSecondIndex )
+ pSecondDim = pDim;
+ }
+ if ( pFirstDim && pSecondDim )
+ {
+ bool bFirstDate = pFirstDim->IsDateDimension();
+ bool bSecondDate = pSecondDim->IsDateDimension();
+ if (bFirstDate || bSecondDate)
+ {
+ // If one is a date group dimension, the other one must be, too.
+ if (!bFirstDate || !bSecondDate)
+ {
+ OSL_FAIL( "mix of date and non-date groups" );
+ return true;
+ }
+
+ return isDateInGroup(rFirstData, rSecondData);
+ }
+
+ const ScDPGroupItem* pFirstItem = pFirstDim->GetGroupForName( rFirstData );
+ const ScDPGroupItem* pSecondItem = pSecondDim->GetGroupForName( rSecondData );
+ if ( pFirstItem && pSecondItem )
+ {
+ // two existing groups -> sal_True if they have a common element
+ return pFirstItem->HasCommonElement( *pSecondItem );
+ }
+ else if ( pFirstItem )
+ {
+ // "automatic" group contains only its own name
+ return pFirstItem->HasElement( rSecondData );
+ }
+ else if ( pSecondItem )
+ {
+ // "automatic" group contains only its own name
+ return pSecondItem->HasElement( rFirstData );
+ }
+ else
+ {
+ // no groups -> sal_True if equal
+ return rFirstData.IsCaseInsEqual( rSecondData );
+ }
+ }
+
+ OSL_FAIL("HasCommonElement: no group dimension found");
+ return true;
+}
+
+sal_Int32 ScDPGroupTableData::GetSourceDim( sal_Int32 nDim )
+{
+ if ( getIsDataLayoutDimension( nDim ) )
+ return nSourceCount;
+ if ( nDim >= nSourceCount && nDim < nSourceCount +static_cast<tools::Long>(aGroups.size()) )
+ {
+ const ScDPGroupDimension& rGroupDim = aGroups[nDim - nSourceCount];
+ return rGroupDim.GetSourceDim();
+ }
+ return nDim;
+}
+
+sal_Int32 ScDPGroupTableData::Compare(sal_Int32 nDim, sal_Int32 nDataId1, sal_Int32 nDataId2)
+{
+ if ( getIsDataLayoutDimension(nDim) )
+ return 0;
+ const ScDPItemData* rItem1 = GetMemberById(nDim, nDataId1);
+ const ScDPItemData* rItem2 = GetMemberById(nDim, nDataId2);
+ if (rItem1 == nullptr || rItem2 == nullptr)
+ return 0;
+ return ScDPItemData::Compare( *rItem1,*rItem2);
+}
+
+#if DUMP_PIVOT_TABLE
+
+void ScDPGroupTableData::Dump() const
+{
+ cout << "--- ScDPGroupTableData" << endl;
+ for (tools::Long i = 0; i < nSourceCount; ++i)
+ {
+ cout << "* dimension: " << i << endl;
+ const ScDPNumGroupDimension& rGrp = pNumGroups[i];
+ rGrp.GetInfo().Dump();
+ }
+ cout << "---" << endl;
+}
+#endif
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sc/source/core/data/dpitemdata.cxx b/sc/source/core/data/dpitemdata.cxx
new file mode 100644
index 000000000..e4efe75ee
--- /dev/null
+++ b/sc/source/core/data/dpitemdata.cxx
@@ -0,0 +1,371 @@
+/* -*- 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 <dpitemdata.hxx>
+#include <global.hxx>
+
+#include <unotools/collatorwrapper.hxx>
+#include <unotools/transliterationwrapper.hxx>
+#include <rtl/math.hxx>
+
+const sal_Int32 ScDPItemData::DateFirst = -1;
+const sal_Int32 ScDPItemData::DateLast = 10000;
+
+sal_Int32 ScDPItemData::Compare(const ScDPItemData& rA, const ScDPItemData& rB)
+{
+ if (rA.meType != rB.meType)
+ {
+ // group value, value and string in this order. Ensure that the empty
+ // type comes last.
+ return rA.meType < rB.meType ? -1 : 1;
+ }
+
+ switch (rA.meType)
+ {
+ case GroupValue:
+ {
+ if (rA.maGroupValue.mnGroupType == rB.maGroupValue.mnGroupType)
+ {
+ if (rA.maGroupValue.mnValue == rB.maGroupValue.mnValue)
+ return 0;
+
+ return rA.maGroupValue.mnValue < rB.maGroupValue.mnValue ? -1 : 1;
+ }
+
+ return rA.maGroupValue.mnGroupType < rB.maGroupValue.mnGroupType ? -1 : 1;
+ }
+ case Value:
+ case RangeStart:
+ {
+ if (rA.mfValue == rB.mfValue)
+ return 0;
+
+ return rA.mfValue < rB.mfValue ? -1 : 1;
+ }
+ case String:
+ case Error:
+ if (rA.mpString == rB.mpString)
+ // strings may be interned.
+ return 0;
+
+ return ScGlobal::GetCollator().compareString(rA.GetString(), rB.GetString());
+ default:
+ ;
+ }
+ return 0;
+}
+
+ScDPItemData::ScDPItemData() :
+ mfValue(0.0), meType(Empty), mbStringInterned(false) {}
+
+ScDPItemData::ScDPItemData(const ScDPItemData& r) :
+ meType(r.meType), mbStringInterned(r.mbStringInterned)
+{
+ switch (r.meType)
+ {
+ case String:
+ case Error:
+ mpString = r.mpString;
+ if (!mbStringInterned)
+ rtl_uString_acquire(mpString);
+ break;
+ case Value:
+ case RangeStart:
+ mfValue = r.mfValue;
+ break;
+ case GroupValue:
+ maGroupValue.mnGroupType = r.maGroupValue.mnGroupType;
+ maGroupValue.mnValue = r.maGroupValue.mnValue;
+ break;
+ case Empty:
+ default:
+ mfValue = 0.0;
+ }
+}
+
+void ScDPItemData::DisposeString()
+{
+ if (!mbStringInterned)
+ {
+ if (meType == String || meType == Error)
+ rtl_uString_release(mpString);
+ }
+
+ mbStringInterned = false;
+}
+
+ScDPItemData::ScDPItemData(const OUString& rStr) :
+ mpString(rStr.pData), meType(String), mbStringInterned(false)
+{
+ rtl_uString_acquire(mpString);
+}
+
+ScDPItemData::ScDPItemData(sal_Int32 nGroupType, sal_Int32 nValue) :
+ meType(GroupValue), mbStringInterned(false)
+{
+ maGroupValue.mnGroupType = nGroupType;
+ maGroupValue.mnValue = nValue;
+}
+
+ScDPItemData::~ScDPItemData()
+{
+ DisposeString();
+}
+
+void ScDPItemData::SetEmpty()
+{
+ DisposeString();
+ meType = Empty;
+}
+
+void ScDPItemData::SetString(const OUString& rS)
+{
+ DisposeString();
+ mpString = rS.pData;
+ rtl_uString_acquire(mpString);
+ meType = String;
+}
+
+void ScDPItemData::SetStringInterned( rtl_uString* pS )
+{
+ DisposeString();
+ mpString = pS;
+ meType = String;
+ mbStringInterned = true;
+}
+
+void ScDPItemData::SetValue(double fVal)
+{
+ DisposeString();
+ mfValue = fVal;
+ meType = Value;
+}
+
+void ScDPItemData::SetRangeStart(double fVal)
+{
+ DisposeString();
+ mfValue = fVal;
+ meType = RangeStart;
+}
+
+void ScDPItemData::SetRangeFirst()
+{
+ DisposeString();
+ mfValue = -std::numeric_limits<double>::infinity();
+ meType = RangeStart;
+}
+
+void ScDPItemData::SetRangeLast()
+{
+ DisposeString();
+ mfValue = std::numeric_limits<double>::infinity();
+ meType = RangeStart;
+}
+
+void ScDPItemData::SetErrorStringInterned( rtl_uString* pS )
+{
+ SetStringInterned(pS);
+ meType = Error;
+}
+
+bool ScDPItemData::IsCaseInsEqual(const ScDPItemData& r) const
+{
+ if (meType != r.meType)
+ return false;
+
+ switch (meType)
+ {
+ case Value:
+ case RangeStart:
+ return rtl::math::approxEqual(mfValue, r.mfValue);
+ case GroupValue:
+ return maGroupValue.mnGroupType == r.maGroupValue.mnGroupType &&
+ maGroupValue.mnValue == r.maGroupValue.mnValue;
+ default:
+ ;
+ }
+
+ if (mpString == r.mpString)
+ // Fast equality check for interned strings.
+ return true;
+
+ return ScGlobal::GetTransliteration().isEqual(GetString(), r.GetString());
+}
+
+bool ScDPItemData::operator== (const ScDPItemData& r) const
+{
+ if (meType != r.meType)
+ return false;
+
+ switch (meType)
+ {
+ case Value:
+ case RangeStart:
+ return rtl::math::approxEqual(mfValue, r.mfValue);
+ case GroupValue:
+ return maGroupValue.mnGroupType == r.maGroupValue.mnGroupType &&
+ maGroupValue.mnValue == r.maGroupValue.mnValue;
+ default:
+ ;
+ }
+
+ // need exact equality until we have a safe case insensitive string hash
+ return GetString() == r.GetString();
+}
+
+bool ScDPItemData::operator< (const ScDPItemData& r) const
+{
+ return Compare(*this, r) == -1;
+}
+
+ScDPItemData& ScDPItemData::operator= (const ScDPItemData& r)
+{
+ DisposeString();
+ meType = r.meType;
+ switch (r.meType)
+ {
+ case String:
+ case Error:
+ mbStringInterned = r.mbStringInterned;
+ mpString = r.mpString;
+ if (!mbStringInterned)
+ rtl_uString_acquire(mpString);
+ break;
+ case Value:
+ case RangeStart:
+ mfValue = r.mfValue;
+ break;
+ case GroupValue:
+ maGroupValue.mnGroupType = r.maGroupValue.mnGroupType;
+ maGroupValue.mnValue = r.maGroupValue.mnValue;
+ break;
+ case Empty:
+ default:
+ mfValue = 0.0;
+ }
+ return *this;
+}
+
+ScDPValue::Type ScDPItemData::GetCellType() const
+{
+ switch (meType)
+ {
+ case Error:
+ return ScDPValue::Error;
+ case Empty:
+ return ScDPValue::Empty;
+ case Value:
+ return ScDPValue::Value;
+ default:
+ ;
+ }
+
+ return ScDPValue::String;
+}
+
+#if DEBUG_PIVOT_TABLE
+
+void ScDPItemData::Dump(const char* msg) const
+{
+ printf("--- (%s)\n", msg);
+ switch (meType)
+ {
+ case Empty:
+ printf("empty\n");
+ break;
+ case Error:
+ printf("error: %s\n",
+ OUStringToOString(OUString(mpString), RTL_TEXTENCODING_UTF8).getStr());
+ break;
+ case GroupValue:
+ printf("group value: group type = %d value = %d\n",
+ maGroupValue.mnGroupType, maGroupValue.mnValue);
+ break;
+ case String:
+ printf("string: %s\n",
+ OUStringToOString(OUString(mpString), RTL_TEXTENCODING_UTF8).getStr());
+ break;
+ case Value:
+ printf("value: %g\n", mfValue);
+ break;
+ case RangeStart:
+ printf("range start: %g\n", mfValue);
+ break;
+ default:
+ printf("unknown type\n");
+ }
+ printf("---\n");
+}
+#endif
+
+bool ScDPItemData::IsEmpty() const
+{
+ return meType == Empty;
+}
+
+bool ScDPItemData::IsValue() const
+{
+ return meType == Value;
+}
+
+OUString ScDPItemData::GetString() const
+{
+ switch (meType)
+ {
+ case String:
+ case Error:
+ return OUString(mpString);
+ case Value:
+ case RangeStart:
+ return OUString::number(mfValue);
+ case GroupValue:
+ return OUString::number(maGroupValue.mnValue);
+ case Empty:
+ default:
+ ;
+ }
+
+ return OUString();
+}
+
+double ScDPItemData::GetValue() const
+{
+ if (meType == Value || meType == RangeStart)
+ return mfValue;
+
+ return 0.0;
+}
+
+ScDPItemData::GroupValueAttr ScDPItemData::GetGroupValue() const
+{
+ if (meType == GroupValue)
+ return maGroupValue;
+
+ GroupValueAttr aGV;
+ aGV.mnGroupType = -1;
+ aGV.mnValue = -1;
+ return aGV;
+}
+
+bool ScDPItemData::HasStringData() const
+{
+ return meType == String || meType == Error;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sc/source/core/data/dpnumgroupinfo.cxx b/sc/source/core/data/dpnumgroupinfo.cxx
new file mode 100644
index 000000000..f57822e16
--- /dev/null
+++ b/sc/source/core/data/dpnumgroupinfo.cxx
@@ -0,0 +1,35 @@
+/* -*- 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/.
+ */
+
+#include <dpnumgroupinfo.hxx>
+
+ScDPNumGroupInfo::ScDPNumGroupInfo() :
+ mbEnable(false),
+ mbDateValues(false),
+ mbAutoStart(false),
+ mbAutoEnd(false),
+ mbIntegerOnly(true),
+ mfStart(0.0), mfEnd(0.0), mfStep(0.0) {}
+
+#if DUMP_PIVOT_TABLE
+
+void ScDPNumGroupInfo::Dump() const
+{
+ cout << "--- ScDPNumGroupInfo" << endl;
+ cout << " enabled: " << mbEnable << endl;
+ cout << " auto start: " << mbAutoStart << endl;
+ cout << " auto end: " << mbAutoEnd << endl;
+ cout << " start: " << mfStart << endl;
+ cout << " end: " << mfEnd << endl;
+ cout << " step: " << mfStep << endl;
+ cout << "---" << endl;
+}
+#endif
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sc/source/core/data/dpobject.cxx b/sc/source/core/data/dpobject.cxx
new file mode 100644
index 000000000..fc31af3c6
--- /dev/null
+++ b/sc/source/core/data/dpobject.cxx
@@ -0,0 +1,3952 @@
+/* -*- 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 <docsh.hxx>
+#include <dpcache.hxx>
+#include <dpobject.hxx>
+#include <dptabsrc.hxx>
+#include <dpsave.hxx>
+#include <dpdimsave.hxx>
+#include <dpoutput.hxx>
+#include <dpshttab.hxx>
+#include <dpsdbtab.hxx>
+#include <dpgroup.hxx>
+#include <document.hxx>
+#include <pivot.hxx>
+#include <dapiuno.hxx>
+#include <miscuno.hxx>
+#include <refupdat.hxx>
+#include <attrib.hxx>
+#include <scitems.hxx>
+#include <unonames.hxx>
+#include <dpglobal.hxx>
+#include <globstr.hrc>
+#include <queryentry.hxx>
+#include <dputil.hxx>
+
+#include <com/sun/star/beans/XPropertySet.hpp>
+#include <com/sun/star/sdb/XCompletedExecution.hpp>
+#include <com/sun/star/sdbc/DataType.hpp>
+#include <com/sun/star/sdbc/SQLException.hpp>
+#include <com/sun/star/sdbc/XResultSetMetaData.hpp>
+#include <com/sun/star/sdbc/XResultSetMetaDataSupplier.hpp>
+#include <com/sun/star/sdbc/XRow.hpp>
+#include <com/sun/star/sdbc/XRowSet.hpp>
+#include <com/sun/star/sheet/GeneralFunction2.hpp>
+#include <com/sun/star/sheet/DataPilotFieldFilter.hpp>
+#include <com/sun/star/sheet/DataPilotFieldOrientation.hpp>
+#include <com/sun/star/sheet/DataPilotFieldReferenceType.hpp>
+#include <com/sun/star/sheet/DataPilotTableHeaderData.hpp>
+#include <com/sun/star/sheet/DataPilotTablePositionData.hpp>
+#include <com/sun/star/sheet/DataPilotTablePositionType.hpp>
+#include <com/sun/star/sheet/DimensionFlags.hpp>
+#include <com/sun/star/task/InteractionHandler.hpp>
+#include <com/sun/star/lang/XMultiServiceFactory.hpp>
+#include <com/sun/star/lang/XSingleServiceFactory.hpp>
+#include <com/sun/star/lang/XSingleComponentFactory.hpp>
+#include <com/sun/star/lang/XInitialization.hpp>
+#include <com/sun/star/container/XContentEnumerationAccess.hpp>
+#include <com/sun/star/sheet/XDrillDownDataSupplier.hpp>
+
+#include <unotools/charclass.hxx>
+#include <comphelper/processfactory.hxx>
+#include <comphelper/string.hxx>
+#include <comphelper/types.hxx>
+#include <o3tl/safeint.hxx>
+#include <sal/macros.h>
+#include <svl/numformat.hxx>
+#include <tools/diagnose_ex.h>
+#include <svl/zforlist.hxx>
+#include <vcl/svapp.hxx>
+#include <vcl/weld.hxx>
+
+#include <utility>
+#include <vector>
+#include <memory>
+#include <algorithm>
+
+using namespace com::sun::star;
+using ::std::vector;
+using ::std::shared_ptr;
+using ::com::sun::star::uno::Sequence;
+using ::com::sun::star::uno::Reference;
+using ::com::sun::star::uno::UNO_QUERY;
+using ::com::sun::star::uno::Any;
+using ::com::sun::star::uno::Exception;
+using ::com::sun::star::lang::XComponent;
+using ::com::sun::star::sheet::DataPilotTableHeaderData;
+using ::com::sun::star::sheet::DataPilotTablePositionData;
+using ::com::sun::star::sheet::XDimensionsSupplier;
+using ::com::sun::star::beans::XPropertySet;
+
+constexpr OUStringLiteral SC_SERVICE_ROWSET = u"com.sun.star.sdb.RowSet";
+
+constexpr OUStringLiteral SC_DBPROP_DATASOURCENAME = u"DataSourceName";
+constexpr OUStringLiteral SC_DBPROP_COMMAND = u"Command";
+constexpr OUStringLiteral SC_DBPROP_COMMANDTYPE = u"CommandType";
+
+constexpr OUStringLiteral SCDPSOURCE_SERVICE = u"com.sun.star.sheet.DataPilotSource";
+
+namespace {
+
+/**
+ * Database connection implementation for UNO database API. Note that in
+ * the UNO database API, column index is 1-based, whereas the interface
+ * requires that column index be 0-based.
+ */
+class DBConnector : public ScDPCache::DBConnector
+{
+ ScDPCache& mrCache;
+
+ uno::Reference<sdbc::XRowSet> mxRowSet;
+ uno::Reference<sdbc::XRow> mxRow;
+ uno::Reference<sdbc::XResultSetMetaData> mxMetaData;
+ Date maNullDate;
+
+public:
+ DBConnector(ScDPCache& rCache, const uno::Reference<sdbc::XRowSet>& xRowSet, const Date& rNullDate);
+
+ bool isValid() const;
+
+ virtual void getValue(tools::Long nCol, ScDPItemData &rData, SvNumFormatType& rNumType) const override;
+ virtual OUString getColumnLabel(tools::Long nCol) const override;
+ virtual tools::Long getColumnCount() const override;
+ virtual bool first() override;
+ virtual bool next() override;
+ virtual void finish() override;
+};
+
+DBConnector::DBConnector(ScDPCache& rCache, const uno::Reference<sdbc::XRowSet>& xRowSet, const Date& rNullDate) :
+ mrCache(rCache), mxRowSet(xRowSet), maNullDate(rNullDate)
+{
+ Reference<sdbc::XResultSetMetaDataSupplier> xMetaSupp(mxRowSet, UNO_QUERY);
+ if (xMetaSupp.is())
+ mxMetaData = xMetaSupp->getMetaData();
+
+ mxRow.set(mxRowSet, UNO_QUERY);
+}
+
+bool DBConnector::isValid() const
+{
+ return mxRowSet.is() && mxRow.is() && mxMetaData.is();
+}
+
+bool DBConnector::first()
+{
+ return mxRowSet->first();
+}
+
+bool DBConnector::next()
+{
+ return mxRowSet->next();
+}
+
+void DBConnector::finish()
+{
+ mxRowSet->beforeFirst();
+}
+
+tools::Long DBConnector::getColumnCount() const
+{
+ return mxMetaData->getColumnCount();
+}
+
+OUString DBConnector::getColumnLabel(tools::Long nCol) const
+{
+ return mxMetaData->getColumnLabel(nCol+1);
+}
+
+void DBConnector::getValue(tools::Long nCol, ScDPItemData &rData, SvNumFormatType& rNumType) const
+{
+ rNumType = SvNumFormatType::NUMBER;
+ sal_Int32 nType = mxMetaData->getColumnType(nCol+1);
+
+ try
+ {
+ double fValue = 0.0;
+ switch (nType)
+ {
+ case sdbc::DataType::BIT:
+ case sdbc::DataType::BOOLEAN:
+ {
+ rNumType = SvNumFormatType::LOGICAL;
+ fValue = mxRow->getBoolean(nCol+1) ? 1 : 0;
+ rData.SetValue(fValue);
+ break;
+ }
+ case sdbc::DataType::TINYINT:
+ case sdbc::DataType::SMALLINT:
+ case sdbc::DataType::INTEGER:
+ case sdbc::DataType::BIGINT:
+ case sdbc::DataType::FLOAT:
+ case sdbc::DataType::REAL:
+ case sdbc::DataType::DOUBLE:
+ case sdbc::DataType::NUMERIC:
+ case sdbc::DataType::DECIMAL:
+ {
+ //TODO: do the conversion here?
+ fValue = mxRow->getDouble(nCol+1);
+ rData.SetValue(fValue);
+ break;
+ }
+ case sdbc::DataType::DATE:
+ {
+ rNumType = SvNumFormatType::DATE;
+
+ util::Date aDate = mxRow->getDate(nCol+1);
+ fValue = Date(aDate.Day, aDate.Month, aDate.Year) - maNullDate;
+ rData.SetValue(fValue);
+ break;
+ }
+ case sdbc::DataType::TIME:
+ {
+ rNumType = SvNumFormatType::TIME;
+
+ util::Time aTime = mxRow->getTime(nCol+1);
+ fValue = aTime.Hours / static_cast<double>(::tools::Time::hourPerDay) +
+ aTime.Minutes / static_cast<double>(::tools::Time::minutePerDay) +
+ aTime.Seconds / static_cast<double>(::tools::Time::secondPerDay) +
+ aTime.NanoSeconds / static_cast<double>(::tools::Time::nanoSecPerDay);
+ rData.SetValue(fValue);
+ break;
+ }
+ case sdbc::DataType::TIMESTAMP:
+ {
+ rNumType = SvNumFormatType::DATETIME;
+
+ util::DateTime aStamp = mxRow->getTimestamp(nCol+1);
+ fValue = ( Date( aStamp.Day, aStamp.Month, aStamp.Year ) - maNullDate ) +
+ aStamp.Hours / static_cast<double>(::tools::Time::hourPerDay) +
+ aStamp.Minutes / static_cast<double>(::tools::Time::minutePerDay) +
+ aStamp.Seconds / static_cast<double>(::tools::Time::secondPerDay) +
+ aStamp.NanoSeconds / static_cast<double>(::tools::Time::nanoSecPerDay);
+ rData.SetValue(fValue);
+ break;
+ }
+ case sdbc::DataType::CHAR:
+ case sdbc::DataType::VARCHAR:
+ case sdbc::DataType::LONGVARCHAR:
+ case sdbc::DataType::SQLNULL:
+ case sdbc::DataType::BINARY:
+ case sdbc::DataType::VARBINARY:
+ case sdbc::DataType::LONGVARBINARY:
+ default:
+ // nCol is 0-based, and the left-most column always has nCol == 0.
+ rData.SetStringInterned(
+ mrCache.InternString(nCol, mxRow->getString(nCol+1)));
+ }
+ }
+ catch (uno::Exception&)
+ {
+ rData.SetEmpty();
+ }
+}
+
+}
+
+static sheet::DataPilotFieldOrientation lcl_GetDataGetOrientation( const uno::Reference<sheet::XDimensionsSupplier>& xSource )
+{
+ sheet::DataPilotFieldOrientation nRet = sheet::DataPilotFieldOrientation_HIDDEN;
+ if ( xSource.is() )
+ {
+ uno::Reference<container::XNameAccess> xDimNameAccess = xSource->getDimensions();
+ const uno::Sequence<OUString> aDimNames = xDimNameAccess->getElementNames();
+ for (const OUString& rDimName : aDimNames)
+ {
+ uno::Reference<beans::XPropertySet> xDimProp(xDimNameAccess->getByName(rDimName),
+ uno::UNO_QUERY);
+ if ( xDimProp.is() )
+ {
+ const bool bFound = ScUnoHelpFunctions::GetBoolProperty( xDimProp,
+ SC_UNO_DP_ISDATALAYOUT );
+ //TODO: error checking -- is "IsDataLayoutDimension" property required??
+ if (bFound)
+ {
+ nRet = ScUnoHelpFunctions::GetEnumProperty(
+ xDimProp, SC_UNO_DP_ORIENTATION,
+ sheet::DataPilotFieldOrientation_HIDDEN );
+ break;
+ }
+ }
+ }
+ }
+ return nRet;
+}
+
+ScDPServiceDesc::ScDPServiceDesc(
+ const OUString& rServ, const OUString& rSrc, const OUString& rNam,
+ const OUString& rUser, const OUString& rPass ) :
+ aServiceName( rServ ),
+ aParSource( rSrc ),
+ aParName( rNam ),
+ aParUser( rUser ),
+ aParPass( rPass ) {}
+
+bool ScDPServiceDesc::operator== ( const ScDPServiceDesc& rOther ) const
+{
+ return aServiceName == rOther.aServiceName &&
+ aParSource == rOther.aParSource &&
+ aParName == rOther.aParName &&
+ aParUser == rOther.aParUser &&
+ aParPass == rOther.aParPass;
+}
+
+ScDPObject::ScDPObject( ScDocument* pD ) :
+ pDoc( pD ),
+ nHeaderRows( 0 ),
+ mbHeaderLayout(false),
+ bAllowMove(false),
+ bSettingsChanged(false),
+ mbEnableGetPivotData(true)
+{
+}
+
+ScDPObject::ScDPObject(const ScDPObject& r) :
+ pDoc( r.pDoc ),
+ aTableName( r.aTableName ),
+ aTableTag( r.aTableTag ),
+ aOutRange( r.aOutRange ),
+ maInteropGrabBag(r.maInteropGrabBag),
+ nHeaderRows( r.nHeaderRows ),
+ mbHeaderLayout( r.mbHeaderLayout ),
+ bAllowMove(false),
+ bSettingsChanged(false),
+ mbEnableGetPivotData(r.mbEnableGetPivotData)
+{
+ if (r.pSaveData)
+ pSaveData.reset( new ScDPSaveData(*r.pSaveData) );
+ if (r.pSheetDesc)
+ pSheetDesc.reset( new ScSheetSourceDesc(*r.pSheetDesc) );
+ if (r.pImpDesc)
+ pImpDesc.reset( new ScImportSourceDesc(*r.pImpDesc) );
+ if (r.pServDesc)
+ pServDesc.reset( new ScDPServiceDesc(*r.pServDesc) );
+ // xSource (and pOutput) is not copied
+}
+
+ScDPObject::~ScDPObject()
+{
+ Clear();
+}
+
+ScDPObject& ScDPObject::operator= (const ScDPObject& r)
+{
+ if (this != &r)
+ {
+ Clear();
+
+ pDoc = r.pDoc;
+ aTableName = r.aTableName;
+ aTableTag = r.aTableTag;
+ aOutRange = r.aOutRange;
+ maInteropGrabBag = r.maInteropGrabBag;
+ nHeaderRows = r.nHeaderRows;
+ mbHeaderLayout = r.mbHeaderLayout;
+ bAllowMove = false;
+ bSettingsChanged = false;
+ mbEnableGetPivotData = r.mbEnableGetPivotData;
+
+ if (r.pSaveData)
+ pSaveData.reset( new ScDPSaveData(*r.pSaveData) );
+ if (r.pSheetDesc)
+ pSheetDesc.reset( new ScSheetSourceDesc(*r.pSheetDesc) );
+ if (r.pImpDesc)
+ pImpDesc.reset( new ScImportSourceDesc(*r.pImpDesc) );
+ if (r.pServDesc)
+ pServDesc.reset( new ScDPServiceDesc(*r.pServDesc) );
+ }
+ return *this;
+}
+
+void ScDPObject::EnableGetPivotData(bool b)
+{
+ mbEnableGetPivotData = b;
+}
+
+void ScDPObject::SetAllowMove(bool bSet)
+{
+ bAllowMove = bSet;
+}
+
+void ScDPObject::SetSaveData(const ScDPSaveData& rData)
+{
+ if ( pSaveData.get() != &rData ) // API implementation modifies the original SaveData object
+ {
+ pSaveData.reset( new ScDPSaveData( rData ) );
+ }
+
+ InvalidateData(); // re-init source from SaveData
+}
+
+void ScDPObject::SetHeaderLayout (bool bUseGrid)
+{
+ mbHeaderLayout = bUseGrid;
+}
+
+void ScDPObject::SetOutRange(const ScRange& rRange)
+{
+ aOutRange = rRange;
+
+ if ( pOutput )
+ pOutput->SetPosition( rRange.aStart );
+}
+
+const ScRange& ScDPObject::GetOutRange() const
+{
+ return aOutRange;
+}
+
+void ScDPObject::SetSheetDesc(const ScSheetSourceDesc& rDesc)
+{
+ if ( pSheetDesc && rDesc == *pSheetDesc )
+ return; // nothing to do
+
+ pImpDesc.reset();
+ pServDesc.reset();
+
+ pSheetDesc.reset( new ScSheetSourceDesc(rDesc) );
+
+ // make valid QueryParam
+
+ const ScRange& rSrcRange = pSheetDesc->GetSourceRange();
+ ScQueryParam aParam = pSheetDesc->GetQueryParam();
+ aParam.nCol1 = rSrcRange.aStart.Col();
+ aParam.nRow1 = rSrcRange.aStart.Row();
+ aParam.nCol2 = rSrcRange.aEnd.Col();
+ aParam.nRow2 = rSrcRange.aEnd.Row();
+ aParam.bHasHeader = true;
+ pSheetDesc->SetQueryParam(aParam);
+
+ ClearTableData(); // new source must be created
+}
+
+void ScDPObject::SetImportDesc(const ScImportSourceDesc& rDesc)
+{
+ if ( pImpDesc && rDesc == *pImpDesc )
+ return; // nothing to do
+
+ pSheetDesc.reset();
+ pServDesc.reset();
+
+ pImpDesc.reset( new ScImportSourceDesc(rDesc) );
+
+ ClearTableData(); // new source must be created
+}
+
+void ScDPObject::SetServiceData(const ScDPServiceDesc& rDesc)
+{
+ if ( pServDesc && rDesc == *pServDesc )
+ return; // nothing to do
+
+ pSheetDesc.reset();
+ pImpDesc.reset();
+
+ pServDesc.reset( new ScDPServiceDesc(rDesc) );
+
+ ClearTableData(); // new source must be created
+}
+
+void ScDPObject::WriteSourceDataTo( ScDPObject& rDest ) const
+{
+ if ( pSheetDesc )
+ rDest.SetSheetDesc( *pSheetDesc );
+ else if ( pImpDesc )
+ rDest.SetImportDesc( *pImpDesc );
+ else if ( pServDesc )
+ rDest.SetServiceData( *pServDesc );
+
+ // name/tag are not source data, but needed along with source data
+
+ rDest.aTableName = aTableName;
+ rDest.aTableTag = aTableTag;
+}
+
+void ScDPObject::WriteTempDataTo( ScDPObject& rDest ) const
+{
+ rDest.nHeaderRows = nHeaderRows;
+}
+
+bool ScDPObject::IsSheetData() const
+{
+ return ( pSheetDesc != nullptr );
+}
+
+void ScDPObject::SetName(const OUString& rNew)
+{
+ aTableName = rNew;
+}
+
+void ScDPObject::SetTag(const OUString& rNew)
+{
+ aTableTag = rNew;
+}
+
+bool ScDPObject::IsDataDescriptionCell(const ScAddress& rPos)
+{
+ if (!pSaveData)
+ return false;
+
+ tools::Long nDataDimCount = pSaveData->GetDataDimensionCount();
+ if (nDataDimCount != 1)
+ // There has to be exactly one data dimension for the description to
+ // appear at top-left corner.
+ return false;
+
+ CreateOutput();
+ ScRange aTabRange = pOutput->GetOutputRange(sheet::DataPilotOutputRangeType::TABLE);
+ return (rPos == aTabRange.aStart);
+}
+
+uno::Reference<sheet::XDimensionsSupplier> const & ScDPObject::GetSource()
+{
+ CreateObjects();
+ return xSource;
+}
+
+void ScDPObject::CreateOutput()
+{
+ CreateObjects();
+ if (pOutput)
+ return;
+
+ bool bFilterButton = IsSheetData() && pSaveData && pSaveData->GetFilterButton();
+ pOutput.reset( new ScDPOutput( pDoc, xSource, aOutRange.aStart, bFilterButton ) );
+ pOutput->SetHeaderLayout ( mbHeaderLayout );
+
+ sal_Int32 nOldRows = nHeaderRows;
+ nHeaderRows = pOutput->GetHeaderRows();
+
+ if ( !(bAllowMove && nHeaderRows != nOldRows) )
+ return;
+
+ sal_Int32 nDiff = nOldRows - nHeaderRows;
+ if ( nOldRows == 0 )
+ --nDiff;
+ if ( nHeaderRows == 0 )
+ ++nDiff;
+
+ sal_Int32 nNewRow = aOutRange.aStart.Row() + nDiff;
+ if ( nNewRow < 0 )
+ nNewRow = 0;
+
+ ScAddress aStart( aOutRange.aStart );
+ aStart.SetRow(nNewRow);
+ pOutput->SetPosition( aStart );
+
+ //TODO: modify aOutRange?
+
+ bAllowMove = false; // use only once
+}
+
+namespace {
+
+class DisableGetPivotData
+{
+ ScDPObject& mrDPObj;
+ bool mbOldState;
+public:
+ DisableGetPivotData(ScDPObject& rObj, bool bOld) : mrDPObj(rObj), mbOldState(bOld)
+ {
+ mrDPObj.EnableGetPivotData(false);
+ }
+
+ ~DisableGetPivotData()
+ {
+ mrDPObj.EnableGetPivotData(mbOldState);
+ }
+};
+
+class FindIntersectingTable
+{
+ ScRange maRange;
+public:
+ explicit FindIntersectingTable(const ScRange& rRange) : maRange(rRange) {}
+
+ bool operator() (const std::unique_ptr<ScDPObject>& rObj) const
+ {
+ return maRange.Intersects(rObj->GetOutRange());
+ }
+};
+
+class FindIntersectingTableByColumns
+{
+ SCCOL mnCol1;
+ SCCOL mnCol2;
+ SCROW mnRow;
+ SCTAB mnTab;
+public:
+ FindIntersectingTableByColumns(SCCOL nCol1, SCCOL nCol2, SCROW nRow, SCTAB nTab) :
+ mnCol1(nCol1), mnCol2(nCol2), mnRow(nRow), mnTab(nTab) {}
+
+ bool operator() (const std::unique_ptr<ScDPObject>& rObj) const
+ {
+ const ScRange& rRange = rObj->GetOutRange();
+ if (mnTab != rRange.aStart.Tab())
+ // Not on this sheet.
+ return false;
+
+ if (rRange.aEnd.Row() < mnRow)
+ // This table is above the row. It's safe.
+ return false;
+
+ if (mnCol1 <= rRange.aStart.Col() && rRange.aEnd.Col() <= mnCol2)
+ // This table is fully enclosed in this column range.
+ return false;
+
+ if (rRange.aEnd.Col() < mnCol1 || mnCol2 < rRange.aStart.Col())
+ // This table is entirely outside this column range.
+ return false;
+
+ // This table must be intersected by this column range.
+ return true;
+ }
+};
+
+class FindIntersectingTableByRows
+{
+ SCCOL mnCol;
+ SCROW mnRow1;
+ SCROW mnRow2;
+ SCTAB mnTab;
+public:
+ FindIntersectingTableByRows(SCCOL nCol, SCROW nRow1, SCROW nRow2, SCTAB nTab) :
+ mnCol(nCol), mnRow1(nRow1), mnRow2(nRow2), mnTab(nTab) {}
+
+ bool operator() (const std::unique_ptr<ScDPObject>& rObj) const
+ {
+ const ScRange& rRange = rObj->GetOutRange();
+ if (mnTab != rRange.aStart.Tab())
+ // Not on this sheet.
+ return false;
+
+ if (rRange.aEnd.Col() < mnCol)
+ // This table is to the left of the column. It's safe.
+ return false;
+
+ if (mnRow1 <= rRange.aStart.Row() && rRange.aEnd.Row() <= mnRow2)
+ // This table is fully enclosed in this row range.
+ return false;
+
+ if (rRange.aEnd.Row() < mnRow1 || mnRow2 < rRange.aStart.Row())
+ // This table is entirely outside this row range.
+ return false;
+
+ // This table must be intersected by this row range.
+ return true;
+ }
+};
+
+class AccumulateOutputRanges
+{
+ ScRangeList maRanges;
+ SCTAB mnTab;
+public:
+ explicit AccumulateOutputRanges(SCTAB nTab) : mnTab(nTab) {}
+ AccumulateOutputRanges(const AccumulateOutputRanges& r) : maRanges(r.maRanges), mnTab(r.mnTab) {}
+
+ void operator() (const std::unique_ptr<ScDPObject>& rObj)
+ {
+ const ScRange& rRange = rObj->GetOutRange();
+ if (mnTab != rRange.aStart.Tab())
+ // Not on this sheet.
+ return;
+
+ maRanges.Join(rRange);
+ }
+
+ const ScRangeList& getRanges() const { return maRanges; }
+};
+
+}
+
+ScDPTableData* ScDPObject::GetTableData()
+{
+ if (!mpTableData)
+ {
+ shared_ptr<ScDPTableData> pData;
+ const ScDPDimensionSaveData* pDimData = pSaveData ? pSaveData->GetExistingDimensionData() : nullptr;
+
+ if ( pImpDesc )
+ {
+ // database data
+ const ScDPCache* pCache = pImpDesc->CreateCache(pDimData);
+ if (pCache)
+ {
+ pCache->AddReference(this);
+ pData = std::make_shared<ScDatabaseDPData>(pDoc, *pCache);
+ }
+ }
+ else
+ {
+ // cell data
+ if (!pSheetDesc)
+ {
+ OSL_FAIL("no source descriptor");
+ pSheetDesc.reset( new ScSheetSourceDesc(pDoc) ); // dummy defaults
+ }
+
+ {
+ // Temporarily disable GETPIVOTDATA to avoid having
+ // GETPIVOTDATA called onto itself from within the source
+ // range.
+ DisableGetPivotData aSwitch(*this, mbEnableGetPivotData);
+ const ScDPCache* pCache = pSheetDesc->CreateCache(pDimData);
+ if (pCache)
+ {
+ pCache->AddReference(this);
+ pData = std::make_shared<ScSheetDPData>(pDoc, *pSheetDesc, *pCache);
+ }
+ }
+ }
+
+ // grouping (for cell or database data)
+ if (pData && pDimData)
+ {
+ auto pGroupData = std::make_shared<ScDPGroupTableData>(pData, pDoc);
+ pDimData->WriteToData(*pGroupData);
+ pData = pGroupData;
+ }
+
+ mpTableData = pData; // after SetCacheId
+ }
+
+ return mpTableData.get();
+}
+
+void ScDPObject::CreateObjects()
+{
+ if (!xSource.is())
+ {
+ pOutput.reset(); // not valid when xSource is changed
+
+ if ( pServDesc )
+ {
+ xSource = CreateSource( *pServDesc );
+ }
+
+ if ( !xSource.is() ) // database or sheet data, or error in CreateSource
+ {
+ OSL_ENSURE( !pServDesc, "DPSource could not be created" );
+ ScDPTableData* pData = GetTableData();
+ if (pData)
+ {
+ if (pSaveData)
+ // Make sure to transfer these flags to the table data
+ // since they may have changed.
+ pData->SetEmptyFlags(pSaveData->GetIgnoreEmptyRows(), pSaveData->GetRepeatIfEmpty());
+
+ pData->ReloadCacheTable();
+ xSource = new ScDPSource( pData );
+ }
+ }
+
+ if (pSaveData)
+ pSaveData->WriteToSource( xSource );
+ }
+ else if (bSettingsChanged)
+ {
+ pOutput.reset(); // not valid when xSource is changed
+
+ uno::Reference<util::XRefreshable> xRef( xSource, uno::UNO_QUERY );
+ if (xRef.is())
+ {
+ try
+ {
+ xRef->refresh();
+ }
+ catch(uno::Exception&)
+ {
+ TOOLS_WARN_EXCEPTION( "sc", "exception in refresh");
+ }
+ }
+
+ if (pSaveData)
+ pSaveData->WriteToSource( xSource );
+ }
+ bSettingsChanged = false;
+}
+
+void ScDPObject::InvalidateData()
+{
+ bSettingsChanged = true;
+}
+
+void ScDPObject::Clear()
+{
+ pOutput.reset();
+ pSaveData.reset();
+ pSheetDesc.reset();
+ pImpDesc.reset();
+ pServDesc.reset();
+ ClearTableData();
+ maInteropGrabBag.clear();
+}
+
+void ScDPObject::ClearTableData()
+{
+ ClearSource();
+
+ if (mpTableData)
+ mpTableData->GetCacheTable().getCache().RemoveReference(this);
+ mpTableData.reset();
+}
+
+void ScDPObject::ReloadGroupTableData()
+{
+ ClearSource();
+
+ if (!mpTableData)
+ // Table data not built yet. No need to reload the group data.
+ return;
+
+ if (!pSaveData)
+ // How could it not have the save data... but whatever.
+ return;
+
+ const ScDPDimensionSaveData* pDimData = pSaveData->GetExistingDimensionData();
+ if (!pDimData || !pDimData->HasGroupDimensions())
+ {
+ // No group dimensions exist. Check if it currently has group
+ // dimensions, and if so, remove all of them.
+ ScDPGroupTableData* pData = dynamic_cast<ScDPGroupTableData*>(mpTableData.get());
+ if (pData)
+ {
+ // Replace the existing group table data with the source data.
+ mpTableData = pData->GetSourceTableData();
+ }
+ return;
+ }
+
+ ScDPGroupTableData* pData = dynamic_cast<ScDPGroupTableData*>(mpTableData.get());
+ if (pData)
+ {
+ // This is already a group table data. Salvage the source data and
+ // re-create a new group data.
+ const shared_ptr<ScDPTableData>& pSource = pData->GetSourceTableData();
+ auto pGroupData = std::make_shared<ScDPGroupTableData>(pSource, pDoc);
+ pDimData->WriteToData(*pGroupData);
+ mpTableData = pGroupData;
+ }
+ else
+ {
+ // This is a source data. Create a group data based on it.
+ auto pGroupData = std::make_shared<ScDPGroupTableData>(mpTableData, pDoc);
+ pDimData->WriteToData(*pGroupData);
+ mpTableData = pGroupData;
+ }
+
+ bSettingsChanged = true;
+}
+
+void ScDPObject::ClearSource()
+{
+ Reference< XComponent > xObjectComp( xSource, UNO_QUERY );
+ if (xObjectComp.is())
+ {
+ try
+ {
+ xObjectComp->dispose();
+ }
+ catch( const Exception& )
+ {
+ DBG_UNHANDLED_EXCEPTION("sc.core");
+ }
+ }
+ xSource = nullptr;
+}
+
+ScRange ScDPObject::GetNewOutputRange( bool& rOverflow )
+{
+ CreateOutput(); // create xSource and pOutput if not already done
+
+ rOverflow = pOutput->HasError(); // range overflow or exception from source
+ if ( rOverflow )
+ return ScRange( aOutRange.aStart );
+ else
+ {
+ // don't store the result in aOutRange, because nothing has been output yet
+ return pOutput->GetOutputRange();
+ }
+}
+
+void ScDPObject::Output( const ScAddress& rPos )
+{
+ // clear old output area
+ pDoc->DeleteAreaTab( aOutRange.aStart.Col(), aOutRange.aStart.Row(),
+ aOutRange.aEnd.Col(), aOutRange.aEnd.Row(),
+ aOutRange.aStart.Tab(), InsertDeleteFlags::ALL );
+ pDoc->RemoveFlagsTab( aOutRange.aStart.Col(), aOutRange.aStart.Row(),
+ aOutRange.aEnd.Col(), aOutRange.aEnd.Row(),
+ aOutRange.aStart.Tab(), ScMF::Auto );
+
+ CreateOutput(); // create xSource and pOutput if not already done
+
+ pOutput->SetPosition( rPos );
+
+ pOutput->Output();
+
+ // aOutRange is always the range that was last output to the document
+ aOutRange = pOutput->GetOutputRange();
+ const ScAddress& s = aOutRange.aStart;
+ const ScAddress& e = aOutRange.aEnd;
+ pDoc->ApplyFlagsTab(s.Col(), s.Row(), e.Col(), e.Row(), s.Tab(), ScMF::DpTable);
+}
+
+ScRange ScDPObject::GetOutputRangeByType( sal_Int32 nType )
+{
+ CreateOutput();
+
+ if (pOutput->HasError())
+ return ScRange(aOutRange.aStart);
+
+ return pOutput->GetOutputRange(nType);
+}
+
+ScRange ScDPObject::GetOutputRangeByType( sal_Int32 nType ) const
+{
+ if (!pOutput || pOutput->HasError())
+ return ScRange(ScAddress::INITIALIZE_INVALID);
+
+ return pOutput->GetOutputRange(nType);
+}
+
+static bool lcl_HasButton( const ScDocument* pDoc, SCCOL nCol, SCROW nRow, SCTAB nTab )
+{
+ return pDoc->GetAttr( nCol, nRow, nTab, ATTR_MERGE_FLAG )->HasPivotButton();
+}
+
+void ScDPObject::RefreshAfterLoad()
+{
+ // apply drop-down attribute, initialize nHeaderRows, without accessing the source
+ // (button attribute must be present)
+
+ // simple test: block of button cells at the top, followed by an empty cell
+
+ SCCOL nFirstCol = aOutRange.aStart.Col();
+ SCROW nFirstRow = aOutRange.aStart.Row();
+ SCTAB nTab = aOutRange.aStart.Tab();
+
+ SCROW nInitial = 0;
+ SCROW nOutRows = aOutRange.aEnd.Row() + 1 - aOutRange.aStart.Row();
+ while ( nInitial + 1 < nOutRows && lcl_HasButton( pDoc, nFirstCol, nFirstRow + nInitial, nTab ) )
+ ++nInitial;
+
+ if ( nInitial + 1 < nOutRows &&
+ pDoc->IsBlockEmpty( nFirstCol, nFirstRow + nInitial, nFirstCol, nFirstRow + nInitial, nTab ) &&
+ aOutRange.aEnd.Col() > nFirstCol )
+ {
+ nHeaderRows = nInitial;
+ }
+ else
+ nHeaderRows = 0; // nothing found, no drop-down lists
+}
+
+void ScDPObject::BuildAllDimensionMembers()
+{
+ if (!pSaveData)
+ return;
+
+ // #i111857# don't always create empty mpTableData for external service.
+ if (pServDesc)
+ return;
+
+ ScDPTableData* pTableData = GetTableData();
+ if(pTableData)
+ pSaveData->BuildAllDimensionMembers(pTableData);
+}
+
+bool ScDPObject::SyncAllDimensionMembers()
+{
+ if (!pSaveData)
+ return false;
+
+ // #i111857# don't always create empty mpTableData for external service.
+ // Ideally, xSource should be used instead of mpTableData.
+ if (pServDesc)
+ return false;
+
+ ScDPTableData* pData = GetTableData();
+ if (!pData)
+ // No table data exists. This can happen when refreshing from an
+ // external source which doesn't exist.
+ return false;
+
+ // Refresh the cache wrapper since the cache may have changed.
+ pData->SetEmptyFlags(pSaveData->GetIgnoreEmptyRows(), pSaveData->GetRepeatIfEmpty());
+ pData->ReloadCacheTable();
+ pSaveData->SyncAllDimensionMembers(pData);
+ return true;
+}
+
+bool ScDPObject::GetMemberNames( sal_Int32 nDim, Sequence<OUString>& rNames )
+{
+ vector<ScDPLabelData::Member> aMembers;
+ if (!GetMembers(nDim, GetUsedHierarchy(nDim), aMembers))
+ return false;
+
+ size_t n = aMembers.size();
+ rNames.realloc(n);
+ auto pNames = rNames.getArray();
+ for (size_t i = 0; i < n; ++i)
+ pNames[i] = aMembers[i].maName;
+
+ return true;
+}
+
+bool ScDPObject::GetMembers( sal_Int32 nDim, sal_Int32 nHier, vector<ScDPLabelData::Member>& rMembers )
+{
+ Reference< sheet::XMembersAccess > xMembersNA;
+ if (!GetMembersNA( nDim, nHier, xMembersNA ))
+ return false;
+
+ Reference<container::XIndexAccess> xMembersIA( new ScNameToIndexAccess(xMembersNA) );
+ sal_Int32 nCount = xMembersIA->getCount();
+ vector<ScDPLabelData::Member> aMembers;
+ aMembers.reserve(nCount);
+
+ for (sal_Int32 i = 0; i < nCount; ++i)
+ {
+ Reference<container::XNamed> xMember;
+ try
+ {
+ xMember = Reference<container::XNamed>(xMembersIA->getByIndex(i), UNO_QUERY);
+ }
+ catch (const container::NoSuchElementException&)
+ {
+ TOOLS_WARN_EXCEPTION("sc", "ScNameToIndexAccess getByIndex failed");
+ }
+
+ ScDPLabelData::Member aMem;
+
+ if (xMember.is())
+ aMem.maName = xMember->getName();
+
+ Reference<beans::XPropertySet> xMemProp(xMember, UNO_QUERY);
+ if (xMemProp.is())
+ {
+ aMem.mbVisible = ScUnoHelpFunctions::GetBoolProperty(xMemProp, SC_UNO_DP_ISVISIBLE);
+ aMem.mbShowDetails = ScUnoHelpFunctions::GetBoolProperty(xMemProp, SC_UNO_DP_SHOWDETAILS);
+
+ aMem.maLayoutName = ScUnoHelpFunctions::GetStringProperty(
+ xMemProp, SC_UNO_DP_LAYOUTNAME, OUString());
+ }
+
+ aMembers.push_back(aMem);
+ }
+ rMembers.swap(aMembers);
+ return true;
+}
+
+void ScDPObject::UpdateReference( UpdateRefMode eUpdateRefMode,
+ const ScRange& rRange, SCCOL nDx, SCROW nDy, SCTAB nDz )
+{
+ // Output area
+
+ SCCOL nCol1 = aOutRange.aStart.Col();
+ SCROW nRow1 = aOutRange.aStart.Row();
+ SCTAB nTab1 = aOutRange.aStart.Tab();
+ SCCOL nCol2 = aOutRange.aEnd.Col();
+ SCROW nRow2 = aOutRange.aEnd.Row();
+ SCTAB nTab2 = aOutRange.aEnd.Tab();
+
+ ScRefUpdateRes eRes =
+ ScRefUpdate::Update( pDoc, eUpdateRefMode,
+ rRange.aStart.Col(), rRange.aStart.Row(), rRange.aStart.Tab(),
+ rRange.aEnd.Col(), rRange.aEnd.Row(), rRange.aEnd.Tab(), nDx, nDy, nDz,
+ nCol1, nRow1, nTab1, nCol2, nRow2, nTab2 );
+ if ( eRes != UR_NOTHING )
+ SetOutRange( ScRange( nCol1, nRow1, nTab1, nCol2, nRow2, nTab2 ) );
+
+ // sheet source data
+
+ if ( !pSheetDesc )
+ return;
+
+ const OUString& rRangeName = pSheetDesc->GetRangeName();
+ if (!rRangeName.isEmpty())
+ // Source range is a named range. No need to update.
+ return;
+
+ const ScRange& rSrcRange = pSheetDesc->GetSourceRange();
+ nCol1 = rSrcRange.aStart.Col();
+ nRow1 = rSrcRange.aStart.Row();
+ nTab1 = rSrcRange.aStart.Tab();
+ nCol2 = rSrcRange.aEnd.Col();
+ nRow2 = rSrcRange.aEnd.Row();
+ nTab2 = rSrcRange.aEnd.Tab();
+
+ eRes = ScRefUpdate::Update( pDoc, eUpdateRefMode,
+ rRange.aStart.Col(), rRange.aStart.Row(), rRange.aStart.Tab(),
+ rRange.aEnd.Col(), rRange.aEnd.Row(), rRange.aEnd.Tab(), nDx, nDy, nDz,
+ nCol1, nRow1, nTab1, nCol2, nRow2, nTab2 );
+ if ( eRes == UR_NOTHING )
+ return;
+
+ SCCOL nDiffX = nCol1 - pSheetDesc->GetSourceRange().aStart.Col();
+ SCROW nDiffY = nRow1 - pSheetDesc->GetSourceRange().aStart.Row();
+
+ ScQueryParam aParam = pSheetDesc->GetQueryParam();
+ aParam.nCol1 = sal::static_int_cast<SCCOL>( aParam.nCol1 + nDiffX );
+ aParam.nCol2 = sal::static_int_cast<SCCOL>( aParam.nCol2 + nDiffX );
+ aParam.nRow1 += nDiffY; //TODO: used?
+ aParam.nRow2 += nDiffY; //TODO: used?
+ SCSIZE nEC = aParam.GetEntryCount();
+ for (SCSIZE i=0; i<nEC; i++)
+ if (aParam.GetEntry(i).bDoQuery)
+ aParam.GetEntry(i).nField += nDiffX;
+
+ pSheetDesc->SetQueryParam(aParam);
+ pSheetDesc->SetSourceRange(ScRange(nCol1, nRow1, nTab1, nCol2, nRow2, nTab2));
+}
+
+bool ScDPObject::RefsEqual( const ScDPObject& r ) const
+{
+ if ( aOutRange != r.aOutRange )
+ return false;
+
+ if ( pSheetDesc && r.pSheetDesc )
+ {
+ if ( pSheetDesc->GetSourceRange() != r.pSheetDesc->GetSourceRange() )
+ return false;
+ }
+ else if ( pSheetDesc || r.pSheetDesc )
+ {
+ OSL_FAIL("RefsEqual: SheetDesc set at only one object");
+ return false;
+ }
+
+ return true;
+}
+
+void ScDPObject::WriteRefsTo( ScDPObject& r ) const
+{
+ r.SetOutRange( aOutRange );
+ if ( pSheetDesc )
+ r.SetSheetDesc( *pSheetDesc );
+}
+
+void ScDPObject::GetPositionData(const ScAddress& rPos, DataPilotTablePositionData& rPosData)
+{
+ CreateOutput();
+ pOutput->GetPositionData(rPos, rPosData);
+}
+
+bool ScDPObject::GetDataFieldPositionData(
+ const ScAddress& rPos, Sequence<sheet::DataPilotFieldFilter>& rFilters)
+{
+ CreateOutput();
+
+ vector<sheet::DataPilotFieldFilter> aFilters;
+ if (!pOutput->GetDataResultPositionData(aFilters, rPos))
+ return false;
+
+ sal_Int32 n = static_cast<sal_Int32>(aFilters.size());
+ rFilters.realloc(n);
+ auto pFilters = rFilters.getArray();
+ for (sal_Int32 i = 0; i < n; ++i)
+ pFilters[i] = aFilters[i];
+
+ return true;
+}
+
+void ScDPObject::GetDrillDownData(const ScAddress& rPos, Sequence< Sequence<Any> >& rTableData)
+{
+ CreateOutput();
+
+ Reference<sheet::XDrillDownDataSupplier> xDrillDownData(xSource, UNO_QUERY);
+ if (!xDrillDownData.is())
+ return;
+
+ Sequence<sheet::DataPilotFieldFilter> filters;
+ if (!GetDataFieldPositionData(rPos, filters))
+ return;
+
+ rTableData = xDrillDownData->getDrillDownData(filters);
+}
+
+bool ScDPObject::IsDimNameInUse(std::u16string_view rName) const
+{
+ if (!xSource.is())
+ return false;
+
+ Reference<container::XNameAccess> xDims = xSource->getDimensions();
+ const Sequence<OUString> aDimNames = xDims->getElementNames();
+ for (const OUString& rDimName : aDimNames)
+ {
+ if (rDimName.equalsIgnoreAsciiCase(rName))
+ return true;
+
+ Reference<beans::XPropertySet> xPropSet(xDims->getByName(rDimName), UNO_QUERY);
+ if (!xPropSet.is())
+ continue;
+
+ OUString aLayoutName = ScUnoHelpFunctions::GetStringProperty(
+ xPropSet, SC_UNO_DP_LAYOUTNAME, OUString());
+ if (aLayoutName.equalsIgnoreAsciiCase(rName))
+ return true;
+ }
+ return false;
+}
+
+OUString ScDPObject::GetDimName( tools::Long nDim, bool& rIsDataLayout, sal_Int32* pFlags )
+{
+ rIsDataLayout = false;
+ OUString aRet;
+
+ if ( xSource.is() )
+ {
+ uno::Reference<container::XNameAccess> xDimsName = xSource->getDimensions();
+ uno::Reference<container::XIndexAccess> xDims = new ScNameToIndexAccess( xDimsName );
+ tools::Long nDimCount = xDims->getCount();
+ if ( nDim < nDimCount )
+ {
+ uno::Reference<uno::XInterface> xIntDim(xDims->getByIndex(nDim), uno::UNO_QUERY);
+ uno::Reference<container::XNamed> xDimName( xIntDim, uno::UNO_QUERY );
+ uno::Reference<beans::XPropertySet> xDimProp( xIntDim, uno::UNO_QUERY );
+ if ( xDimName.is() && xDimProp.is() )
+ {
+ bool bData = ScUnoHelpFunctions::GetBoolProperty( xDimProp,
+ SC_UNO_DP_ISDATALAYOUT );
+ //TODO: error checking -- is "IsDataLayoutDimension" property required??
+
+ OUString aName;
+ try
+ {
+ aName = xDimName->getName();
+ }
+ catch(uno::Exception&)
+ {
+ }
+ if ( bData )
+ rIsDataLayout = true;
+ else
+ aRet = aName;
+
+ if (pFlags)
+ *pFlags = ScUnoHelpFunctions::GetLongProperty( xDimProp,
+ SC_UNO_DP_FLAGS );
+ }
+ }
+ }
+ else if (ScDPTableData* pData = GetTableData())
+ {
+ aRet = pData->getDimensionName(nDim);
+ rIsDataLayout = pData->getIsDataLayoutDimension(nDim);
+ }
+
+ return aRet;
+}
+
+bool ScDPObject::IsDuplicated( tools::Long nDim )
+{
+ bool bDuplicated = false;
+ if ( xSource.is() )
+ {
+ uno::Reference<container::XNameAccess> xDimsName = xSource->getDimensions();
+ uno::Reference<container::XIndexAccess> xDims = new ScNameToIndexAccess( xDimsName );
+ tools::Long nDimCount = xDims->getCount();
+ if ( nDim < nDimCount )
+ {
+ uno::Reference<beans::XPropertySet> xDimProp(xDims->getByIndex(nDim), uno::UNO_QUERY);
+ if ( xDimProp.is() )
+ {
+ try
+ {
+ uno::Any aOrigAny = xDimProp->getPropertyValue( SC_UNO_DP_ORIGINAL );
+ uno::Reference<uno::XInterface> xIntOrig;
+ if ( (aOrigAny >>= xIntOrig) && xIntOrig.is() )
+ bDuplicated = true;
+ }
+ catch(uno::Exception&)
+ {
+ }
+ }
+ }
+ }
+ return bDuplicated;
+}
+
+tools::Long ScDPObject::GetDimCount()
+{
+ tools::Long nRet = 0;
+ if ( xSource.is() )
+ {
+ try
+ {
+ uno::Reference<container::XNameAccess> xDimsName = xSource->getDimensions();
+ if ( xDimsName.is() )
+ nRet = xDimsName->getElementNames().getLength();
+ }
+ catch(uno::Exception&)
+ {
+ }
+ }
+ return nRet;
+}
+
+void ScDPObject::GetHeaderPositionData(const ScAddress& rPos, DataPilotTableHeaderData& rData)
+{
+ CreateOutput(); // create xSource and pOutput if not already done
+
+ // Reset member values to invalid state.
+ rData.Dimension = rData.Hierarchy = rData.Level = -1;
+ rData.Flags = 0;
+
+ DataPilotTablePositionData aPosData;
+ pOutput->GetPositionData(rPos, aPosData);
+ const sal_Int32 nPosType = aPosData.PositionType;
+ if (nPosType == css::sheet::DataPilotTablePositionType::COLUMN_HEADER || nPosType == css::sheet::DataPilotTablePositionType::ROW_HEADER)
+ aPosData.PositionData >>= rData;
+}
+
+namespace {
+
+class FindByName
+{
+ OUString maName; // must be all uppercase.
+public:
+ explicit FindByName(const OUString& rName) : maName(rName) {}
+ bool operator() (const ScDPSaveDimension* pDim) const
+ {
+ // Layout name takes precedence.
+ const std::optional<OUString> & pLayoutName = pDim->GetLayoutName();
+ if (pLayoutName && ScGlobal::getCharClass().uppercase(*pLayoutName) == maName)
+ return true;
+
+ ScGeneralFunction eGenFunc = pDim->GetFunction();
+ ScSubTotalFunc eFunc = ScDPUtil::toSubTotalFunc(eGenFunc);
+ OUString aSrcName = ScDPUtil::getSourceDimensionName(pDim->GetName());
+ OUString aFuncName = ScDPUtil::getDisplayedMeasureName(aSrcName, eFunc);
+ if (maName == ScGlobal::getCharClass().uppercase(aFuncName))
+ return true;
+
+ return maName == ScGlobal::getCharClass().uppercase(aSrcName);
+ }
+};
+
+class LessByDimOrder
+{
+ const ScDPSaveData::DimOrderType& mrDimOrder;
+
+public:
+ explicit LessByDimOrder(const ScDPSaveData::DimOrderType& rDimOrder) : mrDimOrder(rDimOrder) {}
+
+ bool operator() (const sheet::DataPilotFieldFilter& r1, const sheet::DataPilotFieldFilter& r2) const
+ {
+ size_t nRank1 = mrDimOrder.size();
+ size_t nRank2 = mrDimOrder.size();
+ ScDPSaveData::DimOrderType::const_iterator it1 = mrDimOrder.find(
+ ScGlobal::getCharClass().uppercase(r1.FieldName));
+ if (it1 != mrDimOrder.end())
+ nRank1 = it1->second;
+
+ ScDPSaveData::DimOrderType::const_iterator it2 = mrDimOrder.find(
+ ScGlobal::getCharClass().uppercase(r2.FieldName));
+ if (it2 != mrDimOrder.end())
+ nRank2 = it2->second;
+
+ return nRank1 < nRank2;
+ }
+};
+
+}
+
+double ScDPObject::GetPivotData(const OUString& rDataFieldName, std::vector<sheet::DataPilotFieldFilter>& rFilters)
+{
+ if (!mbEnableGetPivotData)
+ return std::numeric_limits<double>::quiet_NaN();
+
+ CreateObjects();
+
+ std::vector<const ScDPSaveDimension*> aDataDims;
+ pSaveData->GetAllDimensionsByOrientation(sheet::DataPilotFieldOrientation_DATA, aDataDims);
+ if (aDataDims.empty())
+ return std::numeric_limits<double>::quiet_NaN();
+
+ std::vector<const ScDPSaveDimension*>::iterator it = std::find_if(
+ aDataDims.begin(), aDataDims.end(),
+ FindByName(ScGlobal::getCharClass().uppercase(rDataFieldName)));
+
+ if (it == aDataDims.end())
+ return std::numeric_limits<double>::quiet_NaN();
+
+ size_t nDataIndex = std::distance(aDataDims.begin(), it);
+
+ uno::Reference<sheet::XDataPilotResults> xDPResults(xSource, uno::UNO_QUERY);
+ if (!xDPResults.is())
+ return std::numeric_limits<double>::quiet_NaN();
+
+ // Dimensions must be sorted in order of appearance, and row dimensions
+ // must come before column dimensions.
+ std::sort(rFilters.begin(), rFilters.end(), LessByDimOrder(pSaveData->GetDimensionSortOrder()));
+
+ size_t n = rFilters.size();
+ uno::Sequence<sheet::DataPilotFieldFilter> aFilters(n);
+ auto aFiltersRange = asNonConstRange(aFilters);
+ for (size_t i = 0; i < n; ++i)
+ aFiltersRange[i] = rFilters[i];
+
+ uno::Sequence<double> aRes = xDPResults->getFilteredResults(aFilters);
+ if (nDataIndex >= o3tl::make_unsigned(aRes.getLength()))
+ return std::numeric_limits<double>::quiet_NaN();
+
+ return aRes[nDataIndex];
+}
+
+bool ScDPObject::IsFilterButton( const ScAddress& rPos )
+{
+ CreateOutput(); // create xSource and pOutput if not already done
+
+ return pOutput->IsFilterButton( rPos );
+}
+
+tools::Long ScDPObject::GetHeaderDim( const ScAddress& rPos, sheet::DataPilotFieldOrientation& rOrient )
+{
+ CreateOutput(); // create xSource and pOutput if not already done
+
+ return pOutput->GetHeaderDim( rPos, rOrient );
+}
+
+bool ScDPObject::GetHeaderDrag( const ScAddress& rPos, bool bMouseLeft, bool bMouseTop, tools::Long nDragDim,
+ tools::Rectangle& rPosRect, sheet::DataPilotFieldOrientation& rOrient, tools::Long& rDimPos )
+{
+ CreateOutput(); // create xSource and pOutput if not already done
+
+ return pOutput->GetHeaderDrag( rPos, bMouseLeft, bMouseTop, nDragDim, rPosRect, rOrient, rDimPos );
+}
+
+void ScDPObject::GetMemberResultNames(ScDPUniqueStringSet& rNames, tools::Long nDimension)
+{
+ CreateOutput(); // create xSource and pOutput if not already done
+
+ pOutput->GetMemberResultNames(rNames, nDimension); // used only with table data -> level not needed
+}
+
+OUString ScDPObject::GetFormattedString(std::u16string_view rDimName, const double fValue)
+{
+ ScDPTableData* pTableData = GetTableData();
+ if(!pTableData)
+ return OUString();
+
+ tools::Long nDim;
+ for (nDim = 0; nDim < pTableData->GetColumnCount(); ++nDim)
+ {
+ if(rDimName == pTableData->getDimensionName(nDim))
+ break;
+ }
+ ScDPItemData aItemData;
+ aItemData.SetValue(fValue);
+ return GetTableData()->GetFormattedString(nDim, aItemData, false);
+}
+
+
+namespace {
+
+bool dequote( const OUString& rSource, sal_Int32 nStartPos, sal_Int32& rEndPos, OUString& rResult )
+{
+ // nStartPos has to point to opening quote
+
+ const sal_Unicode cQuote = '\'';
+
+ if (rSource[nStartPos] == cQuote)
+ {
+ OUStringBuffer aBuffer;
+ sal_Int32 nPos = nStartPos + 1;
+ const sal_Int32 nLen = rSource.getLength();
+
+ while ( nPos < nLen )
+ {
+ const sal_Unicode cNext = rSource[nPos];
+ if ( cNext == cQuote )
+ {
+ if (nPos+1 < nLen && rSource[nPos+1] == cQuote)
+ {
+ // double quote is used for an embedded quote
+ aBuffer.append( cNext ); // append one quote
+ ++nPos; // skip the next one
+ }
+ else
+ {
+ // end of quoted string
+ rResult = aBuffer.makeStringAndClear();
+ rEndPos = nPos + 1; // behind closing quote
+ return true;
+ }
+ }
+ else
+ aBuffer.append( cNext );
+
+ ++nPos;
+ }
+ // no closing quote before the end of the string -> error (bRet still false)
+ }
+
+ return false;
+}
+
+struct ScGetPivotDataFunctionEntry
+{
+ const char* pName;
+ sal_Int16 eFunc;
+};
+
+bool parseFunction( const OUString& rList, sal_Int32 nStartPos, sal_Int32& rEndPos, sal_Int16& rFunc )
+{
+ static const ScGetPivotDataFunctionEntry aFunctions[] =
+ {
+ // our names
+ { "Sum", sheet::GeneralFunction2::SUM },
+ { "Count", sheet::GeneralFunction2::COUNT },
+ { "Average", sheet::GeneralFunction2::AVERAGE },
+ { "Max", sheet::GeneralFunction2::MAX },
+ { "Min", sheet::GeneralFunction2::MIN },
+ { "Product", sheet::GeneralFunction2::PRODUCT },
+ { "CountNums", sheet::GeneralFunction2::COUNTNUMS },
+ { "StDev", sheet::GeneralFunction2::STDEV },
+ { "StDevp", sheet::GeneralFunction2::STDEVP },
+ { "Var", sheet::GeneralFunction2::VAR },
+ { "VarP", sheet::GeneralFunction2::VARP },
+ // compatibility names
+ { "Count Nums", sheet::GeneralFunction2::COUNTNUMS },
+ { "StdDev", sheet::GeneralFunction2::STDEV },
+ { "StdDevp", sheet::GeneralFunction2::STDEVP }
+ };
+
+ const sal_Int32 nListLen = rList.getLength();
+ while (nStartPos < nListLen && rList[nStartPos] == ' ')
+ ++nStartPos;
+
+ bool bParsed = false;
+ bool bFound = false;
+ OUString aFuncStr;
+ sal_Int32 nFuncEnd = 0;
+ if (nStartPos < nListLen && rList[nStartPos] == '\'')
+ bParsed = dequote( rList, nStartPos, nFuncEnd, aFuncStr );
+ else
+ {
+ nFuncEnd = rList.indexOf(']', nStartPos);
+ if (nFuncEnd >= 0)
+ {
+ aFuncStr = rList.copy(nStartPos, nFuncEnd - nStartPos);
+ bParsed = true;
+ }
+ }
+
+ if ( bParsed )
+ {
+ aFuncStr = comphelper::string::strip(aFuncStr, ' ');
+
+ const sal_Int32 nFuncCount = SAL_N_ELEMENTS(aFunctions);
+ for ( sal_Int32 nFunc=0; nFunc<nFuncCount && !bFound; nFunc++ )
+ {
+ if (aFuncStr.equalsIgnoreAsciiCaseAscii(aFunctions[nFunc].pName))
+ {
+ rFunc = aFunctions[nFunc].eFunc;
+ bFound = true;
+
+ while (nFuncEnd < nListLen && rList[nFuncEnd] == ' ')
+ ++nFuncEnd;
+ rEndPos = nFuncEnd;
+ }
+ }
+ }
+
+ return bFound;
+}
+
+bool extractAtStart( const OUString& rList, sal_Int32& rMatched, bool bAllowBracket, sal_Int16* pFunc,
+ OUString& rDequoted )
+{
+ sal_Int32 nMatchList = 0;
+ sal_Unicode cFirst = rList[0];
+ bool bParsed = false;
+ if ( cFirst == '\'' || cFirst == '[' )
+ {
+ // quoted string or string in brackets must match completely
+
+ OUString aDequoted;
+ sal_Int32 nQuoteEnd = 0;
+
+ if ( cFirst == '\'' )
+ bParsed = dequote( rList, 0, nQuoteEnd, aDequoted );
+ else if ( cFirst == '[' )
+ {
+ // skip spaces after the opening bracket
+
+ sal_Int32 nStartPos = 1;
+ const sal_Int32 nListLen = rList.getLength();
+ while (nStartPos < nListLen && rList[nStartPos] == ' ')
+ ++nStartPos;
+
+ if (nStartPos < nListLen && rList[nStartPos] == '\'') // quoted within the brackets?
+ {
+ if ( dequote( rList, nStartPos, nQuoteEnd, aDequoted ) )
+ {
+ // after the quoted string, there must be the closing bracket, optionally preceded by spaces,
+ // and/or a function name
+ while (nQuoteEnd < nListLen && rList[nQuoteEnd] == ' ')
+ ++nQuoteEnd;
+
+ // semicolon separates function name
+ if (nQuoteEnd < nListLen && rList[nQuoteEnd] == ';' && pFunc)
+ {
+ sal_Int32 nFuncEnd = 0;
+ if ( parseFunction( rList, nQuoteEnd + 1, nFuncEnd, *pFunc ) )
+ nQuoteEnd = nFuncEnd;
+ }
+ if (nQuoteEnd < nListLen && rList[nQuoteEnd] == ']')
+ {
+ ++nQuoteEnd; // include the closing bracket for the matched length
+ bParsed = true;
+ }
+ }
+ }
+ else
+ {
+ // implicit quoting to the closing bracket
+
+ sal_Int32 nClosePos = rList.indexOf(']', nStartPos);
+ if (nClosePos >= 0)
+ {
+ sal_Int32 nNameEnd = nClosePos;
+ sal_Int32 nSemiPos = rList.indexOf(';', nStartPos);
+ if (nSemiPos >= 0 && nSemiPos < nClosePos && pFunc)
+ {
+ sal_Int32 nFuncEnd = 0;
+ if (parseFunction(rList, nSemiPos+1, nFuncEnd, *pFunc))
+ nNameEnd = nSemiPos;
+ }
+
+ aDequoted = rList.copy(nStartPos, nNameEnd - nStartPos);
+ // spaces before the closing bracket or semicolon
+ aDequoted = comphelper::string::stripEnd(aDequoted, ' ');
+ nQuoteEnd = nClosePos + 1;
+ bParsed = true;
+ }
+ }
+ }
+
+ if ( bParsed )
+ {
+ nMatchList = nQuoteEnd; // match count in the list string, including quotes
+ rDequoted = aDequoted;
+ }
+ }
+
+ if (bParsed)
+ {
+ // look for following space or end of string
+
+ bool bValid = false;
+ if ( sal::static_int_cast<sal_Int32>(nMatchList) >= rList.getLength() )
+ bValid = true;
+ else
+ {
+ sal_Unicode cNext = rList[nMatchList];
+ if ( cNext == ' ' || ( bAllowBracket && cNext == '[' ) )
+ bValid = true;
+ }
+
+ if ( bValid )
+ {
+ rMatched = nMatchList;
+ return true;
+ }
+ }
+
+ return false;
+}
+
+bool isAtStart(
+ const OUString& rList, const OUString& rSearch, sal_Int32& rMatched,
+ bool bAllowBracket, sal_Int16* pFunc )
+{
+ sal_Int32 nMatchList = 0;
+ sal_Int32 nMatchSearch = 0;
+ sal_Unicode cFirst = rList[0];
+ if ( cFirst == '\'' || cFirst == '[' )
+ {
+ OUString aDequoted;
+ bool bParsed = extractAtStart( rList, rMatched, bAllowBracket, pFunc, aDequoted);
+ if ( bParsed && ScGlobal::GetTransliteration().isEqual( aDequoted, rSearch ) )
+ {
+ nMatchList = rMatched; // match count in the list string, including quotes
+ nMatchSearch = rSearch.getLength();
+ }
+ }
+ else
+ {
+ // otherwise look for search string at the start of rList
+ ScGlobal::GetTransliteration().equals(
+ rList, 0, rList.getLength(), nMatchList, rSearch, 0, rSearch.getLength(), nMatchSearch);
+ }
+
+ if (nMatchSearch == rSearch.getLength())
+ {
+ // search string is at start of rList - look for following space or end of string
+
+ bool bValid = false;
+ if ( sal::static_int_cast<sal_Int32>(nMatchList) >= rList.getLength() )
+ bValid = true;
+ else
+ {
+ sal_Unicode cNext = rList[nMatchList];
+ if ( cNext == ' ' || ( bAllowBracket && cNext == '[' ) )
+ bValid = true;
+ }
+
+ if ( bValid )
+ {
+ rMatched = nMatchList;
+ return true;
+ }
+ }
+
+ return false;
+}
+
+} // anonymous namespace
+
+bool ScDPObject::ParseFilters(
+ OUString& rDataFieldName,
+ std::vector<sheet::DataPilotFieldFilter>& rFilters,
+ std::vector<sal_Int16>& rFilterFuncs, std::u16string_view rFilterList )
+{
+ // parse the string rFilterList into parameters for GetPivotData
+
+ CreateObjects(); // create xSource if not already done
+
+ std::vector<OUString> aDataNames; // data fields (source name)
+ std::vector<OUString> aGivenNames; // data fields (compound name)
+ std::vector<OUString> aFieldNames; // column/row/data fields
+ std::vector< uno::Sequence<OUString> > aFieldValueNames;
+ std::vector< uno::Sequence<OUString> > aFieldValues;
+
+ // get all the field and item names
+
+ uno::Reference<container::XNameAccess> xDimsName = xSource->getDimensions();
+ uno::Reference<container::XIndexAccess> xIntDims = new ScNameToIndexAccess( xDimsName );
+ sal_Int32 nDimCount = xIntDims->getCount();
+ for ( sal_Int32 nDim = 0; nDim<nDimCount; nDim++ )
+ {
+ uno::Reference<uno::XInterface> xIntDim(xIntDims->getByIndex(nDim), uno::UNO_QUERY);
+ uno::Reference<container::XNamed> xDim( xIntDim, uno::UNO_QUERY );
+ uno::Reference<beans::XPropertySet> xDimProp( xDim, uno::UNO_QUERY );
+ uno::Reference<sheet::XHierarchiesSupplier> xDimSupp( xDim, uno::UNO_QUERY );
+ bool bDataLayout = ScUnoHelpFunctions::GetBoolProperty( xDimProp,
+ SC_UNO_DP_ISDATALAYOUT );
+ sheet::DataPilotFieldOrientation nOrient = ScUnoHelpFunctions::GetEnumProperty(
+ xDimProp, SC_UNO_DP_ORIENTATION,
+ sheet::DataPilotFieldOrientation_HIDDEN );
+ if ( !bDataLayout )
+ {
+ if ( nOrient == sheet::DataPilotFieldOrientation_DATA )
+ {
+ OUString aSourceName;
+ OUString aGivenName;
+ ScDPOutput::GetDataDimensionNames( aSourceName, aGivenName, xIntDim );
+ aDataNames.push_back( aSourceName );
+ aGivenNames.push_back( aGivenName );
+ }
+ else if ( nOrient != sheet::DataPilotFieldOrientation_HIDDEN )
+ {
+ // get level names, as in ScDPOutput
+
+ uno::Reference<container::XIndexAccess> xHiers = new ScNameToIndexAccess( xDimSupp->getHierarchies() );
+ sal_Int32 nHierarchy = ScUnoHelpFunctions::GetLongProperty( xDimProp,
+ SC_UNO_DP_USEDHIERARCHY );
+ if ( nHierarchy >= xHiers->getCount() )
+ nHierarchy = 0;
+
+ uno::Reference<sheet::XLevelsSupplier> xHierSupp(xHiers->getByIndex(nHierarchy),
+ uno::UNO_QUERY);
+ if ( xHierSupp.is() )
+ {
+ uno::Reference<container::XIndexAccess> xLevels = new ScNameToIndexAccess( xHierSupp->getLevels() );
+ sal_Int32 nLevCount = xLevels->getCount();
+ for (sal_Int32 nLev=0; nLev<nLevCount; nLev++)
+ {
+ uno::Reference<uno::XInterface> xLevel(xLevels->getByIndex(nLev),
+ uno::UNO_QUERY);
+ uno::Reference<container::XNamed> xLevNam( xLevel, uno::UNO_QUERY );
+ uno::Reference<sheet::XMembersSupplier> xLevSupp( xLevel, uno::UNO_QUERY );
+ if ( xLevNam.is() && xLevSupp.is() )
+ {
+ uno::Reference<sheet::XMembersAccess> xMembers = xLevSupp->getMembers();
+
+ OUString aFieldName( xLevNam->getName() );
+ // getElementNames() and getLocaleIndependentElementNames()
+ // must be consecutive calls to obtain strings in matching order.
+ uno::Sequence<OUString> aMemberValueNames( xMembers->getElementNames() );
+ uno::Sequence<OUString> aMemberValues( xMembers->getLocaleIndependentElementNames() );
+
+ aFieldNames.push_back( aFieldName );
+ aFieldValueNames.push_back( aMemberValueNames );
+ aFieldValues.push_back( aMemberValues );
+ }
+ }
+ }
+ }
+ }
+ }
+
+ // compare and build filters
+
+ SCSIZE nDataFields = aDataNames.size();
+ SCSIZE nFieldCount = aFieldNames.size();
+ OSL_ENSURE( aGivenNames.size() == nDataFields && aFieldValueNames.size() == nFieldCount &&
+ aFieldValues.size() == nFieldCount, "wrong count" );
+
+ bool bError = false;
+ bool bHasData = false;
+ OUString aRemaining(comphelper::string::strip(rFilterList, ' '));
+ while (!aRemaining.isEmpty() && !bError)
+ {
+ bool bUsed = false;
+
+ // look for data field name
+
+ for ( SCSIZE nDataPos=0; nDataPos<nDataFields && !bUsed; nDataPos++ )
+ {
+ OUString aFound;
+ sal_Int32 nMatched = 0;
+ if (isAtStart(aRemaining, aDataNames[nDataPos], nMatched, false, nullptr))
+ aFound = aDataNames[nDataPos];
+ else if (isAtStart(aRemaining, aGivenNames[nDataPos], nMatched, false, nullptr))
+ aFound = aGivenNames[nDataPos];
+
+ if (!aFound.isEmpty())
+ {
+ rDataFieldName = aFound;
+ aRemaining = aRemaining.copy(nMatched);
+ bHasData = true;
+ bUsed = true;
+ }
+ }
+
+ // look for field name
+
+ OUString aSpecField;
+ bool bHasFieldName = false;
+ if ( !bUsed )
+ {
+ sal_Int32 nMatched = 0;
+ for ( SCSIZE nField=0; nField<nFieldCount && !bHasFieldName; nField++ )
+ {
+ if (isAtStart(aRemaining, aFieldNames[nField], nMatched, true, nullptr))
+ {
+ aSpecField = aFieldNames[nField];
+ aRemaining = aRemaining.copy(nMatched);
+ aRemaining = comphelper::string::stripStart(aRemaining, ' ');
+
+ // field name has to be followed by item name in brackets
+ if (aRemaining.startsWith("["))
+ {
+ bHasFieldName = true;
+ // bUsed remains false - still need the item
+ }
+ else
+ {
+ bUsed = true;
+ bError = true;
+ }
+ }
+ }
+ }
+
+ // look for field item
+
+ if ( !bUsed )
+ {
+ bool bItemFound = false;
+ sal_Int32 nMatched = 0;
+ OUString aFoundName;
+ OUString aFoundValueName;
+ OUString aFoundValue;
+ sal_Int16 eFunc = sheet::GeneralFunction2::NONE;
+ sal_Int16 eFoundFunc = sheet::GeneralFunction2::NONE;
+
+ OUString aQueryValueName;
+ const bool bHasQuery = extractAtStart( aRemaining, nMatched, false, &eFunc, aQueryValueName);
+
+ OUString aQueryValue = aQueryValueName;
+ if (mpTableData)
+ {
+ SvNumberFormatter* pFormatter = mpTableData->GetCacheTable().getCache().GetNumberFormatter();
+ if (pFormatter)
+ {
+ // Parse possible number from aQueryValueName and format
+ // locale independent as aQueryValue.
+ sal_uInt32 nNumFormat = 0;
+ double fValue;
+ if (pFormatter->IsNumberFormat( aQueryValueName, nNumFormat, fValue))
+ aQueryValue = ScDPCache::GetLocaleIndependentFormattedString( fValue, *pFormatter, nNumFormat);
+ }
+ }
+
+ for ( SCSIZE nField=0; nField<nFieldCount; nField++ )
+ {
+ // If a field name is given, look in that field only, otherwise in all fields.
+ // aSpecField is initialized from aFieldNames array, so exact comparison can be used.
+ if ( !bHasFieldName || aFieldNames[nField] == aSpecField )
+ {
+ const uno::Sequence<OUString>& rItemNames = aFieldValueNames[nField];
+ const uno::Sequence<OUString>& rItemValues = aFieldValues[nField];
+ sal_Int32 nItemCount = rItemNames.getLength();
+ assert(nItemCount == rItemValues.getLength());
+ const OUString* pItemNamesArr = rItemNames.getConstArray();
+ const OUString* pItemValuesArr = rItemValues.getConstArray();
+ for ( sal_Int32 nItem=0; nItem<nItemCount; nItem++ )
+ {
+ bool bThisItemFound;
+ if (bHasQuery)
+ {
+ // First check given value name against both.
+ bThisItemFound = ScGlobal::GetTransliteration().isEqual(
+ aQueryValueName, pItemNamesArr[nItem]);
+ if (!bThisItemFound && pItemValuesArr[nItem] != pItemNamesArr[nItem])
+ bThisItemFound = ScGlobal::GetTransliteration().isEqual(
+ aQueryValueName, pItemValuesArr[nItem]);
+ if (!bThisItemFound && aQueryValueName != aQueryValue)
+ {
+ // Second check locale independent value
+ // against both.
+ /* TODO: or check only value string against
+ * value string, not against the value name? */
+ bThisItemFound = ScGlobal::GetTransliteration().isEqual(
+ aQueryValue, pItemNamesArr[nItem]);
+ if (!bThisItemFound && pItemValuesArr[nItem] != pItemNamesArr[nItem])
+ bThisItemFound = ScGlobal::GetTransliteration().isEqual(
+ aQueryValue, pItemValuesArr[nItem]);
+ }
+ }
+ else
+ {
+ bThisItemFound = isAtStart( aRemaining, pItemNamesArr[nItem], nMatched, false, &eFunc );
+ if (!bThisItemFound && pItemValuesArr[nItem] != pItemNamesArr[nItem])
+ bThisItemFound = isAtStart( aRemaining, pItemValuesArr[nItem], nMatched, false, &eFunc );
+ /* TODO: this checks only the given value name,
+ * check also locale independent value. But we'd
+ * have to do that in each iteration of the loop
+ * inside isAtStart() since a query could not be
+ * extracted and a match could be on the passed
+ * item value name string or item value string
+ * starting at aRemaining. */
+ }
+ if (bThisItemFound)
+ {
+ if ( bItemFound )
+ bError = true; // duplicate (also across fields)
+ else
+ {
+ aFoundName = aFieldNames[nField];
+ aFoundValueName = pItemNamesArr[nItem];
+ aFoundValue = pItemValuesArr[nItem];
+ eFoundFunc = eFunc;
+ bItemFound = true;
+ bUsed = true;
+ }
+ }
+ }
+ }
+ }
+
+ if ( bItemFound && !bError )
+ {
+ sheet::DataPilotFieldFilter aField;
+ aField.FieldName = aFoundName;
+ aField.MatchValueName = aFoundValueName;
+ aField.MatchValue = aFoundValue;
+ rFilters.push_back(aField);
+ rFilterFuncs.push_back(eFoundFunc);
+ aRemaining = aRemaining.copy(nMatched);
+ }
+ }
+
+ if ( !bUsed )
+ bError = true;
+
+ // remove any number of spaces between entries
+ aRemaining = comphelper::string::stripStart(aRemaining, ' ');
+ }
+
+ if ( !bError && !bHasData && aDataNames.size() == 1 )
+ {
+ // if there's only one data field, its name need not be specified
+ rDataFieldName = aDataNames[0];
+ bHasData = true;
+ }
+
+ return bHasData && !bError;
+}
+
+void ScDPObject::ToggleDetails(const DataPilotTableHeaderData& rElemDesc, ScDPObject* pDestObj)
+{
+ CreateObjects(); // create xSource if not already done
+
+ // find dimension name
+
+ uno::Reference<container::XNamed> xDim;
+ uno::Reference<container::XNameAccess> xDimsName = xSource->getDimensions();
+ uno::Reference<container::XIndexAccess> xIntDims = new ScNameToIndexAccess( xDimsName );
+ tools::Long nIntCount = xIntDims->getCount();
+ if ( rElemDesc.Dimension < nIntCount )
+ {
+ xDim.set(xIntDims->getByIndex(rElemDesc.Dimension), uno::UNO_QUERY);
+ }
+ OSL_ENSURE( xDim.is(), "dimension not found" );
+ if ( !xDim.is() ) return;
+ OUString aDimName = xDim->getName();
+
+ uno::Reference<beans::XPropertySet> xDimProp( xDim, uno::UNO_QUERY );
+ bool bDataLayout = ScUnoHelpFunctions::GetBoolProperty( xDimProp,
+ SC_UNO_DP_ISDATALAYOUT );
+ if (bDataLayout)
+ {
+ // the elements of the data layout dimension can't be found by their names
+ // -> don't change anything
+ return;
+ }
+
+ // query old state
+
+ tools::Long nHierCount = 0;
+ uno::Reference<container::XIndexAccess> xHiers;
+ uno::Reference<sheet::XHierarchiesSupplier> xHierSupp( xDim, uno::UNO_QUERY );
+ if ( xHierSupp.is() )
+ {
+ uno::Reference<container::XNameAccess> xHiersName = xHierSupp->getHierarchies();
+ xHiers = new ScNameToIndexAccess( xHiersName );
+ nHierCount = xHiers->getCount();
+ }
+ uno::Reference<uno::XInterface> xHier;
+ if ( rElemDesc.Hierarchy < nHierCount )
+ xHier.set(xHiers->getByIndex(rElemDesc.Hierarchy), uno::UNO_QUERY);
+ OSL_ENSURE( xHier.is(), "hierarchy not found" );
+ if ( !xHier.is() ) return;
+
+ tools::Long nLevCount = 0;
+ uno::Reference<container::XIndexAccess> xLevels;
+ uno::Reference<sheet::XLevelsSupplier> xLevSupp( xHier, uno::UNO_QUERY );
+ if ( xLevSupp.is() )
+ {
+ uno::Reference<container::XNameAccess> xLevsName = xLevSupp->getLevels();
+ xLevels = new ScNameToIndexAccess( xLevsName );
+ nLevCount = xLevels->getCount();
+ }
+ uno::Reference<uno::XInterface> xLevel;
+ if ( rElemDesc.Level < nLevCount )
+ xLevel.set(xLevels->getByIndex(rElemDesc.Level), uno::UNO_QUERY);
+ OSL_ENSURE( xLevel.is(), "level not found" );
+ if ( !xLevel.is() ) return;
+
+ uno::Reference<sheet::XMembersAccess> xMembers;
+ uno::Reference<sheet::XMembersSupplier> xMbrSupp( xLevel, uno::UNO_QUERY );
+ if ( xMbrSupp.is() )
+ xMembers = xMbrSupp->getMembers();
+
+ bool bFound = false;
+ bool bShowDetails = true;
+
+ if ( xMembers.is() )
+ {
+ if ( xMembers->hasByName(rElemDesc.MemberName) )
+ {
+ uno::Reference<beans::XPropertySet> xMbrProp(xMembers->getByName(rElemDesc.MemberName),
+ uno::UNO_QUERY);
+ if ( xMbrProp.is() )
+ {
+ bShowDetails = ScUnoHelpFunctions::GetBoolProperty( xMbrProp,
+ SC_UNO_DP_SHOWDETAILS );
+ //TODO: don't set bFound if property is unknown?
+ bFound = true;
+ }
+ }
+ }
+
+ OSL_ENSURE( bFound, "member not found" );
+
+ //TODO: use Hierarchy and Level in SaveData !!!!
+
+ // modify pDestObj if set, this object otherwise
+ ScDPSaveData* pModifyData = pDestObj ? ( pDestObj->pSaveData.get() ) : pSaveData.get();
+ OSL_ENSURE( pModifyData, "no data?" );
+ if ( pModifyData )
+ {
+ const OUString aName = rElemDesc.MemberName;
+ pModifyData->GetDimensionByName(aDimName)->
+ GetMemberByName(aName)->SetShowDetails( !bShowDetails ); // toggle
+
+ if ( pDestObj )
+ pDestObj->InvalidateData(); // re-init source from SaveData
+ else
+ InvalidateData(); // re-init source from SaveData
+ }
+}
+
+static PivotFunc lcl_FirstSubTotal( const uno::Reference<beans::XPropertySet>& xDimProp ) // PIVOT_FUNC mask
+{
+ uno::Reference<sheet::XHierarchiesSupplier> xDimSupp( xDimProp, uno::UNO_QUERY );
+ if ( xDimProp.is() && xDimSupp.is() )
+ {
+ uno::Reference<container::XIndexAccess> xHiers = new ScNameToIndexAccess( xDimSupp->getHierarchies() );
+ tools::Long nHierarchy = ScUnoHelpFunctions::GetLongProperty( xDimProp,
+ SC_UNO_DP_USEDHIERARCHY );
+ if ( nHierarchy >= xHiers->getCount() )
+ nHierarchy = 0;
+
+ uno::Reference<sheet::XLevelsSupplier> xHierSupp(xHiers->getByIndex(nHierarchy),
+ uno::UNO_QUERY);
+ if ( xHierSupp.is() )
+ {
+ uno::Reference<container::XIndexAccess> xLevels = new ScNameToIndexAccess( xHierSupp->getLevels() );
+ uno::Reference<uno::XInterface> xLevel(xLevels->getByIndex(0), uno::UNO_QUERY);
+ uno::Reference<beans::XPropertySet> xLevProp( xLevel, uno::UNO_QUERY );
+ if ( xLevProp.is() )
+ {
+ uno::Any aSubAny;
+ try
+ {
+ aSubAny = xLevProp->getPropertyValue( SC_UNO_DP_SUBTOTAL2 );
+ }
+ catch(uno::Exception&)
+ {
+ }
+ uno::Sequence<sal_Int16> aSeq;
+ if ( aSubAny >>= aSeq )
+ {
+ PivotFunc nMask = PivotFunc::NONE;
+ for (const sal_Int16 nElem : std::as_const(aSeq))
+ nMask |= ScDataPilotConversion::FunctionBit(nElem);
+ return nMask;
+ }
+ }
+ }
+ }
+
+ OSL_FAIL("FirstSubTotal: NULL");
+ return PivotFunc::NONE;
+}
+
+namespace {
+
+class FindByColumn
+{
+ SCCOL mnCol;
+ PivotFunc mnMask;
+public:
+ FindByColumn(SCCOL nCol, PivotFunc nMask) : mnCol(nCol), mnMask(nMask) {}
+ bool operator() (const ScPivotField& r) const
+ {
+ return r.nCol == mnCol && r.nFuncMask == mnMask;
+ }
+};
+
+}
+
+static void lcl_FillOldFields( ScPivotFieldVector& rFields,
+ const uno::Reference<sheet::XDimensionsSupplier>& xSource,
+ sheet::DataPilotFieldOrientation nOrient, bool bAddData )
+{
+ ScPivotFieldVector aFields;
+
+ bool bDataFound = false;
+
+ //TODO: merge multiple occurrences (data field with different functions)
+ //TODO: force data field in one dimension
+
+ vector<tools::Long> aPos;
+
+ uno::Reference<container::XNameAccess> xDimsName = xSource->getDimensions();
+ uno::Reference<container::XIndexAccess> xDims = new ScNameToIndexAccess( xDimsName );
+ tools::Long nDimCount = xDims->getCount();
+ for (tools::Long nDim = 0; nDim < nDimCount; ++nDim)
+ {
+ // dimension properties
+ uno::Reference<beans::XPropertySet> xDimProp(xDims->getByIndex(nDim), uno::UNO_QUERY);
+
+ // dimension orientation, hidden by default.
+ sheet::DataPilotFieldOrientation nDimOrient = ScUnoHelpFunctions::GetEnumProperty(
+ xDimProp, SC_UNO_DP_ORIENTATION,
+ sheet::DataPilotFieldOrientation_HIDDEN );
+
+ if ( xDimProp.is() && nDimOrient == nOrient )
+ {
+ // Let's take this dimension.
+
+ // function mask.
+ PivotFunc nMask = PivotFunc::NONE;
+ if ( nOrient == sheet::DataPilotFieldOrientation_DATA )
+ {
+ sal_Int16 eFunc = ScUnoHelpFunctions::GetShortProperty(
+ xDimProp, SC_UNO_DP_FUNCTION2,
+ sheet::GeneralFunction2::NONE );
+ if ( eFunc == sheet::GeneralFunction2::AUTO )
+ {
+ //TODO: test for numeric data
+ eFunc = sheet::GeneralFunction2::SUM;
+ }
+ nMask = ScDataPilotConversion::FunctionBit(eFunc);
+ }
+ else
+ nMask = lcl_FirstSubTotal( xDimProp ); // from first hierarchy
+
+ // is this data layout dimension?
+ bool bDataLayout = ScUnoHelpFunctions::GetBoolProperty(
+ xDimProp, SC_UNO_DP_ISDATALAYOUT);
+
+ // is this dimension cloned?
+ tools::Long nDupSource = -1;
+ try
+ {
+ uno::Any aOrigAny = xDimProp->getPropertyValue(SC_UNO_DP_ORIGINAL_POS);
+ sal_Int32 nTmp = 0;
+ if (aOrigAny >>= nTmp)
+ nDupSource = nTmp;
+ }
+ catch(uno::Exception&)
+ {
+ }
+
+ sal_uInt8 nDupCount = 0;
+ if (nDupSource >= 0)
+ {
+ // this dimension is cloned.
+
+ SCCOL nCompCol; // ID of the original dimension.
+ if ( bDataLayout )
+ nCompCol = PIVOT_DATA_FIELD;
+ else
+ nCompCol = static_cast<SCCOL>(nDupSource); //TODO: seek source column from name
+
+ ScPivotFieldVector::iterator it = std::find_if(aFields.begin(), aFields.end(), FindByColumn(nCompCol, nMask));
+ if (it != aFields.end())
+ nDupCount = it->mnDupCount + 1;
+ }
+
+ aFields.emplace_back();
+ ScPivotField& rField = aFields.back();
+ if (bDataLayout)
+ {
+ rField.nCol = PIVOT_DATA_FIELD;
+ bDataFound = true;
+ }
+ else
+ {
+ rField.mnOriginalDim = nDupSource;
+ rField.nCol = static_cast<SCCOL>(nDim); //TODO: seek source column from name
+ }
+
+ rField.nFuncMask = nMask;
+ rField.mnDupCount = nDupCount;
+ tools::Long nPos = ScUnoHelpFunctions::GetLongProperty(
+ xDimProp, SC_UNO_DP_POSITION);
+ aPos.push_back(nPos);
+
+ try
+ {
+ if (nOrient == sheet::DataPilotFieldOrientation_DATA)
+ xDimProp->getPropertyValue(SC_UNO_DP_REFVALUE)
+ >>= rField.maFieldRef;
+ }
+ catch (uno::Exception&)
+ {
+ }
+ }
+ }
+
+ // sort by getPosition() value
+
+ size_t nOutCount = aFields.size();
+ if (nOutCount >= 1)
+ {
+ for (size_t i = 0; i < nOutCount - 1; ++i)
+ {
+ for (size_t j = 0; j + i < nOutCount - 1; ++j)
+ {
+ if ( aPos[j+1] < aPos[j] )
+ {
+ std::swap( aPos[j], aPos[j+1] );
+ std::swap( aFields[j], aFields[j+1] );
+ }
+ }
+ }
+ }
+
+ if (bAddData && !bDataFound)
+ aFields.emplace_back(PIVOT_DATA_FIELD);
+
+ rFields.swap(aFields);
+}
+
+void ScDPObject::FillOldParam(ScPivotParam& rParam) const
+{
+ const_cast<ScDPObject*>(this)->CreateObjects(); // xSource is needed for field numbers
+
+ if (!xSource.is())
+ return;
+
+ rParam.nCol = aOutRange.aStart.Col();
+ rParam.nRow = aOutRange.aStart.Row();
+ rParam.nTab = aOutRange.aStart.Tab();
+ // ppLabelArr / nLabels is not changed
+
+ bool bAddData = ( lcl_GetDataGetOrientation( xSource ) == sheet::DataPilotFieldOrientation_HIDDEN );
+ lcl_FillOldFields(
+ rParam.maPageFields, xSource, sheet::DataPilotFieldOrientation_PAGE, false);
+ lcl_FillOldFields(
+ rParam.maColFields, xSource, sheet::DataPilotFieldOrientation_COLUMN, bAddData);
+ lcl_FillOldFields(
+ rParam.maRowFields, xSource, sheet::DataPilotFieldOrientation_ROW, false);
+ lcl_FillOldFields(
+ rParam.maDataFields, xSource, sheet::DataPilotFieldOrientation_DATA, false);
+
+ uno::Reference<beans::XPropertySet> xProp( xSource, uno::UNO_QUERY );
+ if (!xProp.is())
+ return;
+
+ try
+ {
+ rParam.bMakeTotalCol = ScUnoHelpFunctions::GetBoolProperty( xProp,
+ SC_UNO_DP_COLGRAND, true );
+ rParam.bMakeTotalRow = ScUnoHelpFunctions::GetBoolProperty( xProp,
+ SC_UNO_DP_ROWGRAND, true );
+
+ // following properties may be missing for external sources
+ rParam.bIgnoreEmptyRows = ScUnoHelpFunctions::GetBoolProperty( xProp,
+ SC_UNO_DP_IGNOREEMPTY );
+ rParam.bDetectCategories = ScUnoHelpFunctions::GetBoolProperty( xProp,
+ SC_UNO_DP_REPEATEMPTY );
+ }
+ catch(uno::Exception&)
+ {
+ // no error
+ }
+}
+
+static void lcl_FillLabelData( ScDPLabelData& rData, const uno::Reference< beans::XPropertySet >& xDimProp )
+{
+ uno::Reference<sheet::XHierarchiesSupplier> xDimSupp( xDimProp, uno::UNO_QUERY );
+ if (!xDimProp.is() || !xDimSupp.is())
+ return;
+
+ uno::Reference<container::XIndexAccess> xHiers = new ScNameToIndexAccess( xDimSupp->getHierarchies() );
+ tools::Long nHierarchy = ScUnoHelpFunctions::GetLongProperty(
+ xDimProp, SC_UNO_DP_USEDHIERARCHY);
+ if ( nHierarchy >= xHiers->getCount() )
+ nHierarchy = 0;
+ rData.mnUsedHier = nHierarchy;
+
+ uno::Reference<sheet::XLevelsSupplier> xHierSupp(xHiers->getByIndex(nHierarchy),
+ uno::UNO_QUERY);
+ if (!xHierSupp.is())
+ return;
+
+ uno::Reference<container::XIndexAccess> xLevels =
+ new ScNameToIndexAccess( xHierSupp->getLevels() );
+
+ uno::Reference<beans::XPropertySet> xLevProp(xLevels->getByIndex(0), uno::UNO_QUERY);
+ if (!xLevProp.is())
+ return;
+
+ rData.mbShowAll = ScUnoHelpFunctions::GetBoolProperty(
+ xLevProp, SC_UNO_DP_SHOWEMPTY);
+
+ rData.mbRepeatItemLabels = ScUnoHelpFunctions::GetBoolProperty(
+ xLevProp, SC_UNO_DP_REPEATITEMLABELS);
+
+ try
+ {
+ xLevProp->getPropertyValue( SC_UNO_DP_SORTING )
+ >>= rData.maSortInfo;
+ xLevProp->getPropertyValue( SC_UNO_DP_LAYOUT )
+ >>= rData.maLayoutInfo;
+ xLevProp->getPropertyValue( SC_UNO_DP_AUTOSHOW )
+ >>= rData.maShowInfo;
+ }
+ catch(uno::Exception&)
+ {
+ }
+}
+
+void ScDPObject::FillLabelDataForDimension(
+ const uno::Reference<container::XIndexAccess>& xDims, sal_Int32 nDim, ScDPLabelData& rLabelData)
+{
+ uno::Reference<uno::XInterface> xIntDim(xDims->getByIndex(nDim), uno::UNO_QUERY);
+ uno::Reference<container::XNamed> xDimName( xIntDim, uno::UNO_QUERY );
+ uno::Reference<beans::XPropertySet> xDimProp( xIntDim, uno::UNO_QUERY );
+
+ if (!xDimName.is() || !xDimProp.is())
+ return;
+
+ bool bData = ScUnoHelpFunctions::GetBoolProperty(
+ xDimProp, SC_UNO_DP_ISDATALAYOUT);
+ //TODO: error checking -- is "IsDataLayoutDimension" property required??
+
+ sal_Int32 nOrigPos = -1;
+ OUString aFieldName;
+ try
+ {
+ aFieldName = xDimName->getName();
+ uno::Any aOrigAny = xDimProp->getPropertyValue(SC_UNO_DP_ORIGINAL_POS);
+ aOrigAny >>= nOrigPos;
+ }
+ catch(uno::Exception&)
+ {
+ }
+
+ OUString aLayoutName = ScUnoHelpFunctions::GetStringProperty(
+ xDimProp, SC_UNO_DP_LAYOUTNAME, OUString());
+
+ OUString aSubtotalName = ScUnoHelpFunctions::GetStringProperty(
+ xDimProp, SC_UNO_DP_FIELD_SUBTOTALNAME, OUString());
+
+ // Name from the UNO dimension object may have trailing '*'s in which
+ // case it's a duplicate dimension. Convert that to a duplicate index.
+
+ sal_uInt8 nDupCount = ScDPUtil::getDuplicateIndex(aFieldName);
+ aFieldName = ScDPUtil::getSourceDimensionName(aFieldName);
+
+ rLabelData.maName = aFieldName;
+ rLabelData.mnCol = static_cast<SCCOL>(nDim);
+ rLabelData.mnDupCount = nDupCount;
+ rLabelData.mbDataLayout = bData;
+ rLabelData.mbIsValue = true; //TODO: check
+
+ if (bData)
+ return;
+
+ rLabelData.mnOriginalDim = static_cast<tools::Long>(nOrigPos);
+ rLabelData.maLayoutName = aLayoutName;
+ rLabelData.maSubtotalName = aSubtotalName;
+ if (nOrigPos >= 0)
+ // This is a duplicated dimension. Use the original dimension index.
+ nDim = nOrigPos;
+ GetHierarchies(nDim, rLabelData.maHiers);
+ GetMembers(nDim, GetUsedHierarchy(nDim), rLabelData.maMembers);
+ lcl_FillLabelData(rLabelData, xDimProp);
+ rLabelData.mnFlags = ScUnoHelpFunctions::GetLongProperty(
+ xDimProp, SC_UNO_DP_FLAGS );
+}
+
+void ScDPObject::FillLabelData(sal_Int32 nDim, ScDPLabelData& rLabels)
+{
+ CreateObjects();
+ if (!xSource.is())
+ return;
+
+ uno::Reference<container::XNameAccess> xDimsName = xSource->getDimensions();
+ uno::Reference<container::XIndexAccess> xDims = new ScNameToIndexAccess( xDimsName );
+ sal_Int32 nDimCount = xDims->getCount();
+ if (nDimCount <= 0 || nDim >= nDimCount)
+ return;
+
+ FillLabelDataForDimension(xDims, nDim, rLabels);
+}
+
+void ScDPObject::FillLabelData(ScPivotParam& rParam)
+{
+ rParam.maLabelArray.clear();
+
+ CreateObjects();
+ if (!xSource.is())
+ return;
+
+ uno::Reference<container::XNameAccess> xDimsName = xSource->getDimensions();
+ uno::Reference<container::XIndexAccess> xDims = new ScNameToIndexAccess( xDimsName );
+ sal_Int32 nDimCount = xDims->getCount();
+ if (nDimCount <= 0)
+ return;
+
+ for (sal_Int32 nDim = 0; nDim < nDimCount; ++nDim)
+ {
+ ScDPLabelData* pNewLabel = new ScDPLabelData;
+ FillLabelDataForDimension(xDims, nDim, *pNewLabel);
+ rParam.maLabelArray.push_back(std::unique_ptr<ScDPLabelData>(pNewLabel));
+ }
+}
+
+bool ScDPObject::GetHierarchiesNA( sal_Int32 nDim, uno::Reference< container::XNameAccess >& xHiers )
+{
+ bool bRet = false;
+ uno::Reference<container::XNameAccess> xDimsName( GetSource()->getDimensions() );
+ uno::Reference<container::XIndexAccess> xIntDims(new ScNameToIndexAccess( xDimsName ));
+ if( xIntDims.is() )
+ {
+ uno::Reference<sheet::XHierarchiesSupplier> xHierSup(xIntDims->getByIndex( nDim ), uno::UNO_QUERY);
+ if (xHierSup.is())
+ {
+ xHiers.set( xHierSup->getHierarchies() );
+ bRet = xHiers.is();
+ }
+ }
+ return bRet;
+}
+
+void ScDPObject::GetHierarchies( sal_Int32 nDim, uno::Sequence< OUString >& rHiers )
+{
+ uno::Reference< container::XNameAccess > xHiersNA;
+ if( GetHierarchiesNA( nDim, xHiersNA ) )
+ {
+ rHiers = xHiersNA->getElementNames();
+ }
+}
+
+sal_Int32 ScDPObject::GetUsedHierarchy( sal_Int32 nDim )
+{
+ sal_Int32 nHier = 0;
+ uno::Reference<container::XNameAccess> xDimsName( GetSource()->getDimensions() );
+ uno::Reference<container::XIndexAccess> xIntDims(new ScNameToIndexAccess( xDimsName ));
+ uno::Reference<beans::XPropertySet> xDim(xIntDims->getByIndex( nDim ), uno::UNO_QUERY);
+ if (xDim.is())
+ nHier = ScUnoHelpFunctions::GetLongProperty( xDim, SC_UNO_DP_USEDHIERARCHY );
+ return nHier;
+}
+
+bool ScDPObject::GetMembersNA( sal_Int32 nDim, uno::Reference< sheet::XMembersAccess >& xMembers )
+{
+ return GetMembersNA( nDim, GetUsedHierarchy( nDim ), xMembers );
+}
+
+bool ScDPObject::GetMembersNA( sal_Int32 nDim, sal_Int32 nHier, uno::Reference< sheet::XMembersAccess >& xMembers )
+{
+ bool bRet = false;
+ uno::Reference<container::XNameAccess> xDimsName( GetSource()->getDimensions() );
+ uno::Reference<container::XIndexAccess> xIntDims(new ScNameToIndexAccess( xDimsName ));
+ uno::Reference<beans::XPropertySet> xDim(xIntDims->getByIndex( nDim ), uno::UNO_QUERY);
+ if (xDim.is())
+ {
+ uno::Reference<sheet::XHierarchiesSupplier> xHierSup(xDim, uno::UNO_QUERY);
+ if (xHierSup.is())
+ {
+ uno::Reference<container::XIndexAccess> xHiers(new ScNameToIndexAccess(xHierSup->getHierarchies()));
+ uno::Reference<sheet::XLevelsSupplier> xLevSupp( xHiers->getByIndex(nHier), uno::UNO_QUERY );
+ if ( xLevSupp.is() )
+ {
+ uno::Reference<container::XIndexAccess> xLevels(new ScNameToIndexAccess( xLevSupp->getLevels()));
+ if (xLevels.is())
+ {
+ sal_Int32 nLevCount = xLevels->getCount();
+ if (nLevCount > 0)
+ {
+ uno::Reference<sheet::XMembersSupplier> xMembSupp( xLevels->getByIndex(0), uno::UNO_QUERY );
+ if ( xMembSupp.is() )
+ {
+ xMembers.set(xMembSupp->getMembers());
+ bRet = true;
+ }
+ }
+ }
+ }
+ }
+ }
+ return bRet;
+}
+
+// convert old pivot tables into new datapilot tables
+
+namespace {
+
+OUString lcl_GetDimName( const uno::Reference<sheet::XDimensionsSupplier>& xSource, tools::Long nDim )
+{
+ OUString aName;
+ if ( xSource.is() )
+ {
+ uno::Reference<container::XNameAccess> xDimsName = xSource->getDimensions();
+ uno::Reference<container::XIndexAccess> xDims = new ScNameToIndexAccess( xDimsName );
+ tools::Long nDimCount = xDims->getCount();
+ if ( nDim < nDimCount )
+ {
+ uno::Reference<container::XNamed> xDimName(xDims->getByIndex(nDim), uno::UNO_QUERY);
+ if (xDimName.is())
+ {
+ try
+ {
+ aName = xDimName->getName();
+ }
+ catch(uno::Exception&)
+ {
+ }
+ }
+ }
+ }
+ return aName;
+}
+
+bool hasFieldColumn(const vector<ScPivotField>* pRefFields, SCCOL nCol)
+{
+ if (!pRefFields)
+ return false;
+
+ return std::any_of(pRefFields->begin(), pRefFields->end(),
+ [&nCol](const ScPivotField& rField) {
+ // This array of fields contains the specified column.
+ return rField.nCol == nCol; });
+}
+
+class FindByOriginalDim
+{
+ tools::Long mnDim;
+public:
+ explicit FindByOriginalDim(tools::Long nDim) : mnDim(nDim) {}
+ bool operator() (const ScPivotField& r) const
+ {
+ return mnDim == r.getOriginalDim();
+ }
+};
+
+}
+
+void ScDPObject::ConvertOrientation(
+ ScDPSaveData& rSaveData, const ScPivotFieldVector& rFields, sheet::DataPilotFieldOrientation nOrient,
+ const Reference<XDimensionsSupplier>& xSource,
+ const ScDPLabelDataVector& rLabels,
+ const ScPivotFieldVector* pRefColFields,
+ const ScPivotFieldVector* pRefRowFields,
+ const ScPivotFieldVector* pRefPageFields )
+{
+ ScPivotFieldVector::const_iterator itr, itrBeg = rFields.begin(), itrEnd = rFields.end();
+ for (itr = itrBeg; itr != itrEnd; ++itr)
+ {
+ const ScPivotField& rField = *itr;
+
+ tools::Long nCol = rField.getOriginalDim();
+ PivotFunc nFuncs = rField.nFuncMask;
+ const sheet::DataPilotFieldReference& rFieldRef = rField.maFieldRef;
+
+ ScDPSaveDimension* pDim = nullptr;
+ if ( nCol == PIVOT_DATA_FIELD )
+ pDim = rSaveData.GetDataLayoutDimension();
+ else
+ {
+ OUString aDocStr = lcl_GetDimName( xSource, nCol ); // cols must start at 0
+ if (!aDocStr.isEmpty())
+ pDim = rSaveData.GetDimensionByName(aDocStr);
+ else
+ pDim = nullptr;
+ }
+
+ if (!pDim)
+ continue;
+
+ if ( nOrient == sheet::DataPilotFieldOrientation_DATA ) // set summary function
+ {
+ // generate an individual entry for each function
+ bool bFirst = true;
+
+ // if a dimension is used for column/row/page and data,
+ // use duplicated dimensions for all data occurrences
+ if (hasFieldColumn(pRefColFields, nCol))
+ bFirst = false;
+
+ if (bFirst && hasFieldColumn(pRefRowFields, nCol))
+ bFirst = false;
+
+ if (bFirst && hasFieldColumn(pRefPageFields, nCol))
+ bFirst = false;
+
+ if (bFirst)
+ {
+ // if set via api, a data column may occur several times
+ // (if the function hasn't been changed yet) -> also look for duplicate data column
+ bFirst = std::none_of(itrBeg, itr, FindByOriginalDim(nCol));
+ }
+
+ ScGeneralFunction eFunc = ScDataPilotConversion::FirstFunc(rField.nFuncMask);
+ if (!bFirst)
+ pDim = rSaveData.DuplicateDimension(pDim->GetName());
+ pDim->SetOrientation(nOrient);
+ pDim->SetFunction(eFunc);
+
+ if( rFieldRef.ReferenceType == sheet::DataPilotFieldReferenceType::NONE )
+ pDim->SetReferenceValue(nullptr);
+ else
+ pDim->SetReferenceValue(&rFieldRef);
+ }
+ else // set SubTotals
+ {
+ pDim->SetOrientation( nOrient );
+
+ std::vector<ScGeneralFunction> nSubTotalFuncs;
+ nSubTotalFuncs.reserve(16);
+ sal_uInt16 nMask = 1;
+ for (sal_uInt16 nBit=0; nBit<16; nBit++)
+ {
+ if ( nFuncs & static_cast<PivotFunc>(nMask) )
+ nSubTotalFuncs.push_back( ScDataPilotConversion::FirstFunc( static_cast<PivotFunc>(nMask) ) );
+ nMask *= 2;
+ }
+ pDim->SetSubTotals( std::move(nSubTotalFuncs) );
+
+ // ShowEmpty was implicit in old tables,
+ // must be set for data layout dimension (not accessible in dialog)
+ if ( nCol == PIVOT_DATA_FIELD )
+ pDim->SetShowEmpty( true );
+ }
+
+ size_t nDimIndex = rField.nCol;
+ pDim->RemoveLayoutName();
+ pDim->RemoveSubtotalName();
+ if (nDimIndex < rLabels.size())
+ {
+ const ScDPLabelData& rLabel = *rLabels[nDimIndex];
+ if (!rLabel.maLayoutName.isEmpty())
+ pDim->SetLayoutName(rLabel.maLayoutName);
+ if (!rLabel.maSubtotalName.isEmpty())
+ pDim->SetSubtotalName(rLabel.maSubtotalName);
+ }
+ }
+}
+
+bool ScDPObject::IsOrientationAllowed( sheet::DataPilotFieldOrientation nOrient, sal_Int32 nDimFlags )
+{
+ bool bAllowed = true;
+ switch (nOrient)
+ {
+ case sheet::DataPilotFieldOrientation_PAGE:
+ bAllowed = ( nDimFlags & sheet::DimensionFlags::NO_PAGE_ORIENTATION ) == 0;
+ break;
+ case sheet::DataPilotFieldOrientation_COLUMN:
+ bAllowed = ( nDimFlags & sheet::DimensionFlags::NO_COLUMN_ORIENTATION ) == 0;
+ break;
+ case sheet::DataPilotFieldOrientation_ROW:
+ bAllowed = ( nDimFlags & sheet::DimensionFlags::NO_ROW_ORIENTATION ) == 0;
+ break;
+ case sheet::DataPilotFieldOrientation_DATA:
+ bAllowed = ( nDimFlags & sheet::DimensionFlags::NO_DATA_ORIENTATION ) == 0;
+ break;
+ default:
+ {
+ // allowed to remove from previous orientation
+ }
+ }
+ return bAllowed;
+}
+
+bool ScDPObject::HasRegisteredSources()
+{
+ bool bFound = false;
+
+ uno::Reference<lang::XMultiServiceFactory> xManager = comphelper::getProcessServiceFactory();
+ uno::Reference<container::XContentEnumerationAccess> xEnAc( xManager, uno::UNO_QUERY );
+ if ( xEnAc.is() )
+ {
+ uno::Reference<container::XEnumeration> xEnum = xEnAc->createContentEnumeration(
+ SCDPSOURCE_SERVICE );
+ if ( xEnum.is() && xEnum->hasMoreElements() )
+ bFound = true;
+ }
+
+ return bFound;
+}
+
+std::vector<OUString> ScDPObject::GetRegisteredSources()
+{
+ std::vector<OUString> aVec;
+
+ // use implementation names...
+
+ uno::Reference<lang::XMultiServiceFactory> xManager = comphelper::getProcessServiceFactory();
+ uno::Reference<container::XContentEnumerationAccess> xEnAc( xManager, uno::UNO_QUERY );
+ if ( xEnAc.is() )
+ {
+ uno::Reference<container::XEnumeration> xEnum = xEnAc->createContentEnumeration(
+ SCDPSOURCE_SERVICE );
+ if ( xEnum.is() )
+ {
+ while ( xEnum->hasMoreElements() )
+ {
+ uno::Any aAddInAny = xEnum->nextElement();
+// if ( aAddInAny.getReflection()->getTypeClass() == TypeClass_INTERFACE )
+ {
+ uno::Reference<uno::XInterface> xIntFac;
+ aAddInAny >>= xIntFac;
+ if ( xIntFac.is() )
+ {
+ uno::Reference<lang::XServiceInfo> xInfo( xIntFac, uno::UNO_QUERY );
+ if ( xInfo.is() )
+ {
+ OUString sName = xInfo->getImplementationName();
+ aVec.push_back( sName );
+ }
+ }
+ }
+ }
+ }
+ }
+
+ return aVec;
+}
+
+uno::Reference<sheet::XDimensionsSupplier> ScDPObject::CreateSource( const ScDPServiceDesc& rDesc )
+{
+ OUString aImplName = rDesc.aServiceName;
+ uno::Reference<sheet::XDimensionsSupplier> xRet;
+
+ uno::Reference<lang::XMultiServiceFactory> xManager = comphelper::getProcessServiceFactory();
+ uno::Reference<container::XContentEnumerationAccess> xEnAc(xManager, uno::UNO_QUERY);
+ if (!xEnAc.is())
+ return xRet;
+
+ uno::Reference<container::XEnumeration> xEnum =
+ xEnAc->createContentEnumeration(SCDPSOURCE_SERVICE);
+ if (!xEnum.is())
+ return xRet;
+
+ while (xEnum->hasMoreElements() && !xRet.is())
+ {
+ uno::Any aAddInAny = xEnum->nextElement();
+ uno::Reference<uno::XInterface> xIntFac;
+ aAddInAny >>= xIntFac;
+ if (!xIntFac.is())
+ continue;
+
+ uno::Reference<lang::XServiceInfo> xInfo(xIntFac, uno::UNO_QUERY);
+ if (!xInfo.is() || xInfo->getImplementationName() != aImplName)
+ continue;
+
+ try
+ {
+ // #i113160# try XSingleComponentFactory in addition to (old) XSingleServiceFactory,
+ // passing the context to the component (see ScUnoAddInCollection::Initialize)
+
+ uno::Reference<uno::XInterface> xInterface;
+ uno::Reference<uno::XComponentContext> xCtx(
+ comphelper::getComponentContext(xManager));
+ uno::Reference<lang::XSingleComponentFactory> xCFac( xIntFac, uno::UNO_QUERY );
+ if (xCFac.is())
+ xInterface = xCFac->createInstanceWithContext(xCtx);
+
+ if (!xInterface.is())
+ {
+ uno::Reference<lang::XSingleServiceFactory> xFac( xIntFac, uno::UNO_QUERY );
+ if ( xFac.is() )
+ xInterface = xFac->createInstance();
+ }
+
+ uno::Reference<lang::XInitialization> xInit( xInterface, uno::UNO_QUERY );
+ if (xInit.is())
+ {
+ // initialize
+ uno::Sequence<uno::Any> aSeq(4);
+ uno::Any* pArray = aSeq.getArray();
+ pArray[0] <<= rDesc.aParSource;
+ pArray[1] <<= rDesc.aParName;
+ pArray[2] <<= rDesc.aParUser;
+ pArray[3] <<= rDesc.aParPass;
+ xInit->initialize( aSeq );
+ }
+ xRet.set( xInterface, uno::UNO_QUERY );
+ }
+ catch(uno::Exception&)
+ {
+ }
+ }
+
+ return xRet;
+}
+
+#if DUMP_PIVOT_TABLE
+
+void ScDPObject::Dump() const
+{
+ if (pSaveData)
+ pSaveData->Dump();
+
+ if (mpTableData)
+ mpTableData->Dump();
+}
+
+void ScDPObject::DumpCache() const
+{
+ if (!mpTableData)
+ return;
+
+ const ScDPCache &rCache = mpTableData->GetCacheTable().getCache();
+
+ rCache.Dump();
+}
+#endif
+
+ScDPCollection::SheetCaches::SheetCaches(ScDocument& rDoc) : mrDoc(rDoc) {}
+
+namespace {
+
+struct FindInvalidRange
+{
+ bool operator() (const ScRange& r) const
+ {
+ return !r.IsValid();
+ }
+};
+
+void setGroupItemsToCache( ScDPCache& rCache, const o3tl::sorted_vector<ScDPObject*>& rRefs )
+{
+ // Go through all referencing pivot tables, and re-fill the group dimension info.
+ for (const ScDPObject* pObj : rRefs)
+ {
+ const ScDPSaveData* pSave = pObj->GetSaveData();
+ if (!pSave)
+ continue;
+
+ const ScDPDimensionSaveData* pGroupDims = pSave->GetExistingDimensionData();
+ if (!pGroupDims)
+ continue;
+
+ pGroupDims->WriteToCache(rCache);
+ }
+}
+
+}
+
+bool ScDPCollection::SheetCaches::hasCache(const ScRange& rRange) const
+{
+ RangeIndexType::const_iterator it = std::find(maRanges.begin(), maRanges.end(), rRange);
+ if (it == maRanges.end())
+ return false;
+
+ // Already cached.
+ size_t nIndex = std::distance(maRanges.begin(), it);
+ CachesType::const_iterator const itCache = m_Caches.find(nIndex);
+ return itCache != m_Caches.end();
+}
+
+const ScDPCache* ScDPCollection::SheetCaches::getCache(const ScRange& rRange, const ScDPDimensionSaveData* pDimData)
+{
+ RangeIndexType::iterator it = std::find(maRanges.begin(), maRanges.end(), rRange);
+ if (it != maRanges.end())
+ {
+ // Already cached.
+ size_t nIndex = std::distance(maRanges.begin(), it);
+ CachesType::iterator const itCache = m_Caches.find(nIndex);
+ if (itCache == m_Caches.end())
+ {
+ OSL_FAIL("Cache pool and index pool out-of-sync !!!");
+ return nullptr;
+ }
+
+ if (pDimData)
+ {
+ (itCache->second)->ClearGroupFields();
+ pDimData->WriteToCache(*itCache->second);
+ }
+
+ return itCache->second.get();
+ }
+
+ // Not cached. Create a new cache.
+ ::std::unique_ptr<ScDPCache> pCache(new ScDPCache(mrDoc));
+ pCache->InitFromDoc(mrDoc, rRange);
+ if (pDimData)
+ pDimData->WriteToCache(*pCache);
+
+ // Get the smallest available range index.
+ it = std::find_if(maRanges.begin(), maRanges.end(), FindInvalidRange());
+
+ size_t nIndex = maRanges.size();
+ if (it == maRanges.end())
+ {
+ // All range indices are valid. Append a new index.
+ maRanges.push_back(rRange);
+ }
+ else
+ {
+ // Slot with invalid range. Re-use this slot.
+ *it = rRange;
+ nIndex = std::distance(maRanges.begin(), it);
+ }
+
+ const ScDPCache* p = pCache.get();
+ m_Caches.insert(std::make_pair(nIndex, std::move(pCache)));
+ return p;
+}
+
+ScDPCache* ScDPCollection::SheetCaches::getExistingCache(const ScRange& rRange)
+{
+ RangeIndexType::iterator it = std::find(maRanges.begin(), maRanges.end(), rRange);
+ if (it == maRanges.end())
+ // Not cached.
+ return nullptr;
+
+ // Already cached.
+ size_t nIndex = std::distance(maRanges.begin(), it);
+ CachesType::iterator const itCache = m_Caches.find(nIndex);
+ if (itCache == m_Caches.end())
+ {
+ OSL_FAIL("Cache pool and index pool out-of-sync !!!");
+ return nullptr;
+ }
+
+ return itCache->second.get();
+}
+
+const ScDPCache* ScDPCollection::SheetCaches::getExistingCache(const ScRange& rRange) const
+{
+ RangeIndexType::const_iterator it = std::find(maRanges.begin(), maRanges.end(), rRange);
+ if (it == maRanges.end())
+ // Not cached.
+ return nullptr;
+
+ // Already cached.
+ size_t nIndex = std::distance(maRanges.begin(), it);
+ CachesType::const_iterator const itCache = m_Caches.find(nIndex);
+ if (itCache == m_Caches.end())
+ {
+ OSL_FAIL("Cache pool and index pool out-of-sync !!!");
+ return nullptr;
+ }
+
+ return itCache->second.get();
+}
+
+size_t ScDPCollection::SheetCaches::size() const
+{
+ return m_Caches.size();
+}
+
+void ScDPCollection::SheetCaches::updateReference(
+ UpdateRefMode eMode, const ScRange& r, SCCOL nDx, SCROW nDy, SCTAB nDz)
+{
+ if (maRanges.empty())
+ // No caches.
+ return;
+
+ for (ScRange& rKeyRange : maRanges)
+ {
+ SCCOL nCol1 = rKeyRange.aStart.Col();
+ SCROW nRow1 = rKeyRange.aStart.Row();
+ SCTAB nTab1 = rKeyRange.aStart.Tab();
+ SCCOL nCol2 = rKeyRange.aEnd.Col();
+ SCROW nRow2 = rKeyRange.aEnd.Row();
+ SCTAB nTab2 = rKeyRange.aEnd.Tab();
+
+ ScRefUpdateRes eRes = ScRefUpdate::Update(
+ &mrDoc, eMode,
+ r.aStart.Col(), r.aStart.Row(), r.aStart.Tab(),
+ r.aEnd.Col(), r.aEnd.Row(), r.aEnd.Tab(), nDx, nDy, nDz,
+ nCol1, nRow1, nTab1, nCol2, nRow2, nTab2);
+
+ if (eRes != UR_NOTHING)
+ {
+ // range updated.
+ ScRange aNew(nCol1, nRow1, nTab1, nCol2, nRow2, nTab2);
+ rKeyRange = aNew;
+ }
+ }
+}
+
+void ScDPCollection::SheetCaches::updateCache(const ScRange& rRange, o3tl::sorted_vector<ScDPObject*>& rRefs)
+{
+ RangeIndexType::iterator it = std::find(maRanges.begin(), maRanges.end(), rRange);
+ if (it == maRanges.end())
+ {
+ // Not cached. Nothing to do.
+ rRefs.clear();
+ return;
+ }
+
+ size_t nIndex = std::distance(maRanges.begin(), it);
+ CachesType::iterator const itCache = m_Caches.find(nIndex);
+ if (itCache == m_Caches.end())
+ {
+ OSL_FAIL("Cache pool and index pool out-of-sync !!!");
+ rRefs.clear();
+ return;
+ }
+
+ ScDPCache& rCache = *itCache->second;
+
+ // Update the cache with new cell values. This will clear all group dimension info.
+ rCache.InitFromDoc(mrDoc, rRange);
+
+ o3tl::sorted_vector<ScDPObject*> aRefs(rCache.GetAllReferences());
+ rRefs.swap(aRefs);
+
+ // Make sure to re-populate the group dimension info.
+ setGroupItemsToCache(rCache, rRefs);
+}
+
+bool ScDPCollection::SheetCaches::remove(const ScDPCache* p)
+{
+ CachesType::iterator it = std::find_if(m_Caches.begin(), m_Caches.end(),
+ [&p](const CachesType::value_type& rEntry) { return rEntry.second.get() == p; });
+ if (it != m_Caches.end())
+ {
+ size_t idx = it->first;
+ m_Caches.erase(it);
+ maRanges[idx].SetInvalid();
+ return true;
+ }
+ return false;
+}
+
+const std::vector<ScRange>& ScDPCollection::SheetCaches::getAllRanges() const
+{
+ return maRanges;
+}
+
+ScDPCollection::NameCaches::NameCaches(ScDocument& rDoc) : mrDoc(rDoc) {}
+
+bool ScDPCollection::NameCaches::hasCache(const OUString& rName) const
+{
+ return m_Caches.count(rName) != 0;
+}
+
+const ScDPCache* ScDPCollection::NameCaches::getCache(
+ const OUString& rName, const ScRange& rRange, const ScDPDimensionSaveData* pDimData)
+{
+ CachesType::const_iterator const itr = m_Caches.find(rName);
+ if (itr != m_Caches.end())
+ // already cached.
+ return itr->second.get();
+
+ ::std::unique_ptr<ScDPCache> pCache(new ScDPCache(mrDoc));
+ pCache->InitFromDoc(mrDoc, rRange);
+ if (pDimData)
+ pDimData->WriteToCache(*pCache);
+
+ const ScDPCache *const p = pCache.get();
+ m_Caches.insert(std::make_pair(rName, std::move(pCache)));
+ return p;
+}
+
+ScDPCache* ScDPCollection::NameCaches::getExistingCache(const OUString& rName)
+{
+ CachesType::iterator const itr = m_Caches.find(rName);
+ return itr != m_Caches.end() ? itr->second.get() : nullptr;
+}
+
+size_t ScDPCollection::NameCaches::size() const
+{
+ return m_Caches.size();
+}
+
+void ScDPCollection::NameCaches::updateCache(
+ const OUString& rName, const ScRange& rRange, o3tl::sorted_vector<ScDPObject*>& rRefs)
+{
+ CachesType::iterator const itr = m_Caches.find(rName);
+ if (itr == m_Caches.end())
+ {
+ rRefs.clear();
+ return;
+ }
+
+ ScDPCache& rCache = *itr->second;
+ // Update the cache with new cell values. This will clear all group dimension info.
+ rCache.InitFromDoc(mrDoc, rRange);
+
+ o3tl::sorted_vector<ScDPObject*> aRefs(rCache.GetAllReferences());
+ rRefs.swap(aRefs);
+
+ // Make sure to re-populate the group dimension info.
+ setGroupItemsToCache(rCache, rRefs);
+}
+
+bool ScDPCollection::NameCaches::remove(const ScDPCache* p)
+{
+ CachesType::iterator it = std::find_if(m_Caches.begin(), m_Caches.end(),
+ [&p](const CachesType::value_type& rEntry) { return rEntry.second.get() == p; });
+ if (it != m_Caches.end())
+ {
+ m_Caches.erase(it);
+ return true;
+ }
+ return false;
+}
+
+ScDPCollection::DBType::DBType(sal_Int32 nSdbType, const OUString& rDBName, const OUString& rCommand) :
+ mnSdbType(nSdbType), maDBName(rDBName), maCommand(rCommand) {}
+
+bool ScDPCollection::DBType::less::operator() (const DBType& left, const DBType& right) const
+{
+ return left < right;
+}
+
+ScDPCollection::DBCaches::DBCaches(ScDocument& rDoc) : mrDoc(rDoc) {}
+
+bool ScDPCollection::DBCaches::hasCache(sal_Int32 nSdbType, const OUString& rDBName, const OUString& rCommand) const
+{
+ DBType aType(nSdbType, rDBName, rCommand);
+ CachesType::const_iterator const itr = m_Caches.find(aType);
+ return itr != m_Caches.end();
+}
+
+const ScDPCache* ScDPCollection::DBCaches::getCache(
+ sal_Int32 nSdbType, const OUString& rDBName, const OUString& rCommand,
+ const ScDPDimensionSaveData* pDimData)
+{
+ DBType aType(nSdbType, rDBName, rCommand);
+ CachesType::const_iterator const itr = m_Caches.find(aType);
+ if (itr != m_Caches.end())
+ // already cached.
+ return itr->second.get();
+
+ uno::Reference<sdbc::XRowSet> xRowSet = createRowSet(nSdbType, rDBName, rCommand);
+ if (!xRowSet.is())
+ return nullptr;
+
+ ::std::unique_ptr<ScDPCache> pCache(new ScDPCache(mrDoc));
+ SvNumberFormatter aFormat( comphelper::getProcessComponentContext(), ScGlobal::eLnge);
+ DBConnector aDB(*pCache, xRowSet, aFormat.GetNullDate());
+ if (!aDB.isValid())
+ return nullptr;
+
+ if (!pCache->InitFromDataBase(aDB))
+ {
+ // initialization failed.
+ comphelper::disposeComponent(xRowSet);
+ return nullptr;
+ }
+
+ if (pDimData)
+ pDimData->WriteToCache(*pCache);
+
+ ::comphelper::disposeComponent(xRowSet);
+ const ScDPCache* p = pCache.get();
+ m_Caches.insert(std::make_pair(aType, std::move(pCache)));
+ return p;
+}
+
+ScDPCache* ScDPCollection::DBCaches::getExistingCache(
+ sal_Int32 nSdbType, const OUString& rDBName, const OUString& rCommand)
+{
+ DBType aType(nSdbType, rDBName, rCommand);
+ CachesType::iterator const itr = m_Caches.find(aType);
+ return itr != m_Caches.end() ? itr->second.get() : nullptr;
+}
+
+uno::Reference<sdbc::XRowSet> ScDPCollection::DBCaches::createRowSet(
+ sal_Int32 nSdbType, const OUString& rDBName, const OUString& rCommand)
+{
+ uno::Reference<sdbc::XRowSet> xRowSet;
+ try
+ {
+ xRowSet.set(comphelper::getProcessServiceFactory()->createInstance(
+ SC_SERVICE_ROWSET),
+ UNO_QUERY);
+
+ uno::Reference<beans::XPropertySet> xRowProp(xRowSet, UNO_QUERY);
+ OSL_ENSURE( xRowProp.is(), "can't get RowSet" );
+ if (!xRowProp.is())
+ {
+ xRowSet.set(nullptr);
+ return xRowSet;
+ }
+
+ // set source parameters
+
+ xRowProp->setPropertyValue( SC_DBPROP_DATASOURCENAME, Any(rDBName) );
+ xRowProp->setPropertyValue( SC_DBPROP_COMMAND, Any(rCommand) );
+ xRowProp->setPropertyValue( SC_DBPROP_COMMANDTYPE, Any(nSdbType) );
+
+ uno::Reference<sdb::XCompletedExecution> xExecute( xRowSet, uno::UNO_QUERY );
+ if ( xExecute.is() )
+ {
+ uno::Reference<task::XInteractionHandler> xHandler(
+ task::InteractionHandler::createWithParent(comphelper::getProcessComponentContext(), nullptr),
+ uno::UNO_QUERY_THROW);
+ xExecute->executeWithCompletion( xHandler );
+ }
+ else
+ xRowSet->execute();
+
+ return xRowSet;
+ }
+ catch ( const sdbc::SQLException& rError )
+ {
+ //! store error message
+ std::unique_ptr<weld::MessageDialog> xInfoBox(Application::CreateMessageDialog(ScDocShell::GetActiveDialogParent(),
+ VclMessageType::Info, VclButtonsType::Ok,
+ rError.Message));
+ xInfoBox->run();
+ }
+ catch ( uno::Exception& )
+ {
+ TOOLS_WARN_EXCEPTION( "sc", "Unexpected exception in database");
+ }
+
+ xRowSet.set(nullptr);
+ return xRowSet;
+}
+
+void ScDPCollection::DBCaches::updateCache(
+ sal_Int32 nSdbType, const OUString& rDBName, const OUString& rCommand,
+ o3tl::sorted_vector<ScDPObject*>& rRefs)
+{
+ DBType aType(nSdbType, rDBName, rCommand);
+ CachesType::iterator const it = m_Caches.find(aType);
+ if (it == m_Caches.end())
+ {
+ // not cached.
+ rRefs.clear();
+ return;
+ }
+
+ ScDPCache& rCache = *it->second;
+
+ uno::Reference<sdbc::XRowSet> xRowSet = createRowSet(nSdbType, rDBName, rCommand);
+ if (!xRowSet.is())
+ {
+ rRefs.clear();
+ return;
+ }
+
+ SvNumberFormatter aFormat( comphelper::getProcessComponentContext(), ScGlobal::eLnge);
+ DBConnector aDB(rCache, xRowSet, aFormat.GetNullDate());
+ if (!aDB.isValid())
+ return;
+
+ if (!rCache.InitFromDataBase(aDB))
+ {
+ // initialization failed.
+ rRefs.clear();
+ comphelper::disposeComponent(xRowSet);
+ return;
+ }
+
+ comphelper::disposeComponent(xRowSet);
+ o3tl::sorted_vector<ScDPObject*> aRefs(rCache.GetAllReferences());
+ aRefs.swap(rRefs);
+
+ // Make sure to re-populate the group dimension info.
+ setGroupItemsToCache(rCache, rRefs);
+}
+
+bool ScDPCollection::DBCaches::remove(const ScDPCache* p)
+{
+ CachesType::iterator it = std::find_if(m_Caches.begin(), m_Caches.end(),
+ [&p](const CachesType::value_type& rEntry) { return rEntry.second.get() == p; });
+ if (it != m_Caches.end())
+ {
+ m_Caches.erase(it);
+ return true;
+ }
+ return false;
+}
+
+ScDPCollection::ScDPCollection(ScDocument& rDocument) :
+ mrDoc(rDocument),
+ maSheetCaches(rDocument),
+ maNameCaches(rDocument),
+ maDBCaches(rDocument)
+{
+}
+
+ScDPCollection::ScDPCollection(const ScDPCollection& r) :
+ mrDoc(r.mrDoc),
+ maSheetCaches(r.mrDoc),
+ maNameCaches(r.mrDoc),
+ maDBCaches(r.mrDoc)
+{
+}
+
+ScDPCollection::~ScDPCollection()
+{
+ maTables.clear();
+}
+
+namespace {
+
+/**
+ * Unary predicate to match DP objects by the table ID.
+ */
+class MatchByTable
+{
+ SCTAB mnTab;
+public:
+ explicit MatchByTable(SCTAB nTab) : mnTab(nTab) {}
+
+ bool operator() (const std::unique_ptr<ScDPObject>& rObj) const
+ {
+ return rObj->GetOutRange().aStart.Tab() == mnTab;
+ }
+};
+
+}
+
+TranslateId ScDPCollection::ReloadCache(const ScDPObject* pDPObj, o3tl::sorted_vector<ScDPObject*>& rRefs)
+{
+ if (!pDPObj)
+ return STR_ERR_DATAPILOTSOURCE;
+
+ if (pDPObj->IsSheetData())
+ {
+ // data source is internal sheet.
+ const ScSheetSourceDesc* pDesc = pDPObj->GetSheetDesc();
+ if (!pDesc)
+ return STR_ERR_DATAPILOTSOURCE;
+
+ TranslateId pErrId = pDesc->CheckSourceRange();
+ if (pErrId)
+ return pErrId;
+
+ if (pDesc->HasRangeName())
+ {
+ // cache by named range
+ ScDPCollection::NameCaches& rCaches = GetNameCaches();
+ if (rCaches.hasCache(pDesc->GetRangeName()))
+ rCaches.updateCache(pDesc->GetRangeName(), pDesc->GetSourceRange(), rRefs);
+ else
+ {
+ // Not cached yet. Collect all tables that use this named
+ // range as data source.
+ GetAllTables(pDesc->GetRangeName(), rRefs);
+ }
+ }
+ else
+ {
+ // cache by cell range
+ ScDPCollection::SheetCaches& rCaches = GetSheetCaches();
+ if (rCaches.hasCache(pDesc->GetSourceRange()))
+ rCaches.updateCache(pDesc->GetSourceRange(), rRefs);
+ else
+ {
+ // Not cached yet. Collect all tables that use this range as
+ // data source.
+ GetAllTables(pDesc->GetSourceRange(), rRefs);
+ }
+ }
+ }
+ else if (pDPObj->IsImportData())
+ {
+ // data source is external database.
+ const ScImportSourceDesc* pDesc = pDPObj->GetImportSourceDesc();
+ if (!pDesc)
+ return STR_ERR_DATAPILOTSOURCE;
+
+ ScDPCollection::DBCaches& rCaches = GetDBCaches();
+ if (rCaches.hasCache(pDesc->GetCommandType(), pDesc->aDBName, pDesc->aObject))
+ rCaches.updateCache(
+ pDesc->GetCommandType(), pDesc->aDBName, pDesc->aObject, rRefs);
+ else
+ {
+ // Not cached yet. Collect all tables that use this range as
+ // data source.
+ GetAllTables(pDesc->GetCommandType(), pDesc->aDBName, pDesc->aObject, rRefs);
+ }
+ }
+ return {};
+}
+
+bool ScDPCollection::ReloadGroupsInCache(const ScDPObject* pDPObj, o3tl::sorted_vector<ScDPObject*>& rRefs)
+{
+ if (!pDPObj)
+ return false;
+
+ const ScDPSaveData* pSaveData = pDPObj->GetSaveData();
+ if (!pSaveData)
+ return false;
+
+ // Note: Unlike reloading cache, when modifying the group dimensions the
+ // cache may not have all its references when this method is called.
+ // Therefore, we need to always call GetAllTables to get its correct
+ // references even when the cache exists. This may become a non-issue
+ // if/when we implement loading and saving of pivot caches.
+
+ ScDPCache* pCache = nullptr;
+
+ if (pDPObj->IsSheetData())
+ {
+ // data source is internal sheet.
+ const ScSheetSourceDesc* pDesc = pDPObj->GetSheetDesc();
+ if (!pDesc)
+ return false;
+
+ if (pDesc->HasRangeName())
+ {
+ // cache by named range
+ ScDPCollection::NameCaches& rCaches = GetNameCaches();
+ if (rCaches.hasCache(pDesc->GetRangeName()))
+ pCache = rCaches.getExistingCache(pDesc->GetRangeName());
+ else
+ {
+ // Not cached yet. Cache the source dimensions. Groups will
+ // be added below.
+ pCache = const_cast<ScDPCache*>(
+ rCaches.getCache(pDesc->GetRangeName(), pDesc->GetSourceRange(), nullptr));
+ }
+ GetAllTables(pDesc->GetRangeName(), rRefs);
+ }
+ else
+ {
+ // cache by cell range
+ ScDPCollection::SheetCaches& rCaches = GetSheetCaches();
+ if (rCaches.hasCache(pDesc->GetSourceRange()))
+ pCache = rCaches.getExistingCache(pDesc->GetSourceRange());
+ else
+ {
+ // Not cached yet. Cache the source dimensions. Groups will
+ // be added below.
+ pCache = const_cast<ScDPCache*>(
+ rCaches.getCache(pDesc->GetSourceRange(), nullptr));
+ }
+ GetAllTables(pDesc->GetSourceRange(), rRefs);
+ }
+ }
+ else if (pDPObj->IsImportData())
+ {
+ // data source is external database.
+ const ScImportSourceDesc* pDesc = pDPObj->GetImportSourceDesc();
+ if (!pDesc)
+ return false;
+
+ ScDPCollection::DBCaches& rCaches = GetDBCaches();
+ if (rCaches.hasCache(pDesc->GetCommandType(), pDesc->aDBName, pDesc->aObject))
+ pCache = rCaches.getExistingCache(
+ pDesc->GetCommandType(), pDesc->aDBName, pDesc->aObject);
+ else
+ {
+ // Not cached yet. Cache the source dimensions. Groups will
+ // be added below.
+ pCache = const_cast<ScDPCache*>(
+ rCaches.getCache(pDesc->GetCommandType(), pDesc->aDBName, pDesc->aObject, nullptr));
+ }
+ GetAllTables(pDesc->GetCommandType(), pDesc->aDBName, pDesc->aObject, rRefs);
+ }
+
+ if (!pCache)
+ return false;
+
+ // Clear the existing group/field data from the cache, and rebuild it from the
+ // dimension data.
+ pCache->ClearAllFields();
+ const ScDPDimensionSaveData* pDimData = pSaveData->GetExistingDimensionData();
+ if (pDimData)
+ pDimData->WriteToCache(*pCache);
+ return true;
+}
+
+bool ScDPCollection::GetReferenceGroups(const ScDPObject& rDPObj, const ScDPDimensionSaveData** pGroups) const
+{
+ for (const std::unique_ptr<ScDPObject>& aTable : maTables)
+ {
+ const ScDPObject& rRefObj = *aTable;
+
+ if (&rRefObj == &rDPObj)
+ continue;
+
+ if (rDPObj.IsSheetData()){
+ if(!rRefObj.IsSheetData())
+ continue;
+
+ const ScSheetSourceDesc* pDesc = rDPObj.GetSheetDesc();
+ const ScSheetSourceDesc* pRefDesc = rRefObj.GetSheetDesc();
+ if (pDesc == nullptr || pRefDesc == nullptr)
+ continue;
+
+ if (pDesc->HasRangeName())
+ {
+ if (!pRefDesc->HasRangeName())
+ continue;
+
+ if (pDesc->GetRangeName() == pRefDesc->GetRangeName())
+ {
+ *pGroups = rRefObj.GetSaveData()->GetExistingDimensionData();
+ return true;
+ }
+ }
+ else
+ {
+ if (pRefDesc->HasRangeName())
+ continue;
+
+ if (pDesc->GetSourceRange() == pRefDesc->GetSourceRange())
+ {
+ *pGroups = rRefObj.GetSaveData()->GetExistingDimensionData();
+ return true;
+ }
+ }
+ }
+ else if (rDPObj.IsImportData())
+ {
+ if (!rRefObj.IsImportData ())
+ continue;
+
+ const ScImportSourceDesc* pDesc = rDPObj.GetImportSourceDesc();
+ const ScImportSourceDesc* pRefDesc = rRefObj.GetImportSourceDesc();
+ if (pDesc == nullptr || pRefDesc == nullptr)
+ continue;
+
+ if (pDesc->aDBName == pRefDesc->aDBName &&
+ pDesc->aObject == pRefDesc->aObject &&
+ pDesc->GetCommandType() == pRefDesc->GetCommandType())
+ {
+ *pGroups = rRefObj.GetSaveData()->GetExistingDimensionData();
+ return true;
+ }
+
+ }
+ }
+ return false;
+}
+
+
+void ScDPCollection::DeleteOnTab( SCTAB nTab )
+{
+ maTables.erase( std::remove_if(maTables.begin(), maTables.end(), MatchByTable(nTab)), maTables.end());
+}
+
+void ScDPCollection::UpdateReference( UpdateRefMode eUpdateRefMode,
+ const ScRange& r, SCCOL nDx, SCROW nDy, SCTAB nDz )
+{
+ for (auto& rxTable : maTables)
+ rxTable->UpdateReference(eUpdateRefMode, r, nDx, nDy, nDz);
+
+ // Update the source ranges of the caches.
+ maSheetCaches.updateReference(eUpdateRefMode, r, nDx, nDy, nDz);
+}
+
+void ScDPCollection::CopyToTab( SCTAB nOld, SCTAB nNew )
+{
+ TablesType aAdded;
+ for (const auto& rxTable : maTables)
+ {
+ const ScDPObject& rObj = *rxTable;
+ ScRange aOutRange = rObj.GetOutRange();
+ if (aOutRange.aStart.Tab() != nOld)
+ continue;
+
+ ScAddress& s = aOutRange.aStart;
+ ScAddress& e = aOutRange.aEnd;
+ s.SetTab(nNew);
+ e.SetTab(nNew);
+ ScDPObject* pNew = new ScDPObject(rObj);
+ pNew->SetOutRange(aOutRange);
+ mrDoc.ApplyFlagsTab(s.Col(), s.Row(), e.Col(), e.Row(), s.Tab(), ScMF::DpTable);
+ aAdded.push_back(std::unique_ptr<ScDPObject>(pNew));
+ }
+
+ std::move(aAdded.begin(), aAdded.end(), std::back_inserter(maTables));
+}
+
+bool ScDPCollection::RefsEqual( const ScDPCollection& r ) const
+{
+ return std::equal(maTables.begin(), maTables.end(), r.maTables.begin(), r.maTables.end(),
+ [](const TablesType::value_type& a, const TablesType::value_type& b) { return a->RefsEqual(*b); });
+}
+
+void ScDPCollection::WriteRefsTo( ScDPCollection& r ) const
+{
+ if ( maTables.size() == r.maTables.size() )
+ {
+ //TODO: assert equal names?
+ TablesType::iterator itr2 = r.maTables.begin();
+ for (const auto& rxTable : maTables)
+ {
+ rxTable->WriteRefsTo(**itr2);
+ ++itr2;
+ }
+ }
+ else
+ {
+ // #i8180# If data pilot tables were deleted with their sheet,
+ // this collection contains extra entries that must be restored.
+ // Matching objects are found by their names.
+ size_t nSrcSize = maTables.size();
+ size_t nDestSize = r.maTables.size();
+ OSL_ENSURE( nSrcSize >= nDestSize, "WriteRefsTo: missing entries in document" );
+ for (size_t nSrcPos = 0; nSrcPos < nSrcSize; ++nSrcPos)
+ {
+ const ScDPObject& rSrcObj = *maTables[nSrcPos];
+ const OUString& aName = rSrcObj.GetName();
+ bool bFound = false;
+ for (size_t nDestPos = 0; nDestPos < nDestSize && !bFound; ++nDestPos)
+ {
+ ScDPObject& rDestObj = *r.maTables[nDestPos];
+ if (rDestObj.GetName() == aName)
+ {
+ rSrcObj.WriteRefsTo(rDestObj); // found object, copy refs
+ bFound = true;
+ }
+ }
+
+ if (!bFound)
+ {
+ // none found, re-insert deleted object (see ScUndoDataPilot::Undo)
+ r.InsertNewTable(std::make_unique<ScDPObject>(rSrcObj));
+ }
+ }
+ OSL_ENSURE( maTables.size() == r.maTables.size(), "WriteRefsTo: couldn't restore all entries" );
+ }
+}
+
+size_t ScDPCollection::GetCount() const
+{
+ return maTables.size();
+}
+
+ScDPObject& ScDPCollection::operator [](size_t nIndex)
+{
+ return *maTables[nIndex];
+}
+
+const ScDPObject& ScDPCollection::operator [](size_t nIndex) const
+{
+ return *maTables[nIndex];
+}
+
+ScDPObject* ScDPCollection::GetByName(std::u16string_view rName) const
+{
+ for (std::unique_ptr<ScDPObject> const & pObject : maTables)
+ {
+ if (pObject->GetName() == rName)
+ return pObject.get();
+ }
+
+ return nullptr;
+}
+
+OUString ScDPCollection::CreateNewName() const
+{
+ size_t n = maTables.size();
+ for (size_t nAdd = 0; nAdd <= n; ++nAdd) // nCount+1 tries
+ {
+ OUString aNewName = "DataPilot" + OUString::number(1 + nAdd);
+ if (std::none_of(maTables.begin(), maTables.end(),
+ [&aNewName](const TablesType::value_type& rxObj) { return rxObj->GetName() == aNewName; }))
+ return aNewName; // found unused Name
+ }
+ return OUString(); // should not happen
+}
+
+void ScDPCollection::FreeTable(const ScDPObject* pDPObject)
+{
+ const ScRange& rOutRange = pDPObject->GetOutRange();
+ const ScAddress& s = rOutRange.aStart;
+ const ScAddress& e = rOutRange.aEnd;
+ mrDoc.RemoveFlagsTab(s.Col(), s.Row(), e.Col(), e.Row(), s.Tab(), ScMF::DpTable);
+
+ auto funcRemoveCondition = [pDPObject] (std::unique_ptr<ScDPObject> const & pCurrent)
+ {
+ return pCurrent.get() == pDPObject;
+ };
+
+ maTables.erase(std::remove_if(maTables.begin(), maTables.end(), funcRemoveCondition), maTables.end());
+}
+
+ScDPObject* ScDPCollection::InsertNewTable(std::unique_ptr<ScDPObject> pDPObj)
+{
+ const ScRange& rOutRange = pDPObj->GetOutRange();
+ const ScAddress& s = rOutRange.aStart;
+ const ScAddress& e = rOutRange.aEnd;
+ mrDoc.ApplyFlagsTab(s.Col(), s.Row(), e.Col(), e.Row(), s.Tab(), ScMF::DpTable);
+
+ maTables.push_back(std::move(pDPObj));
+ return maTables.back().get();
+}
+
+bool ScDPCollection::HasTable(const ScDPObject* pDPObj) const
+{
+ for (const std::unique_ptr<ScDPObject>& aTable : maTables)
+ {
+ if (aTable.get() == pDPObj)
+ {
+ return true;
+ }
+ }
+ return false;
+}
+
+ScDPCollection::SheetCaches& ScDPCollection::GetSheetCaches()
+{
+ return maSheetCaches;
+}
+
+const ScDPCollection::SheetCaches& ScDPCollection::GetSheetCaches() const
+{
+ return maSheetCaches;
+}
+
+ScDPCollection::NameCaches& ScDPCollection::GetNameCaches()
+{
+ return maNameCaches;
+}
+
+const ScDPCollection::NameCaches& ScDPCollection::GetNameCaches() const
+{
+ return maNameCaches;
+}
+
+ScDPCollection::DBCaches& ScDPCollection::GetDBCaches()
+{
+ return maDBCaches;
+}
+
+const ScDPCollection::DBCaches& ScDPCollection::GetDBCaches() const
+{
+ return maDBCaches;
+}
+
+ScRangeList ScDPCollection::GetAllTableRanges( SCTAB nTab ) const
+{
+ return std::for_each(maTables.begin(), maTables.end(), AccumulateOutputRanges(nTab)).getRanges();
+}
+
+bool ScDPCollection::IntersectsTableByColumns( SCCOL nCol1, SCCOL nCol2, SCROW nRow, SCTAB nTab ) const
+{
+ return std::any_of(maTables.begin(), maTables.end(), FindIntersectingTableByColumns(nCol1, nCol2, nRow, nTab));
+}
+
+bool ScDPCollection::IntersectsTableByRows( SCCOL nCol, SCROW nRow1, SCROW nRow2, SCTAB nTab ) const
+{
+ return std::any_of(maTables.begin(), maTables.end(), FindIntersectingTableByRows(nCol, nRow1, nRow2, nTab));
+}
+
+bool ScDPCollection::HasTable( const ScRange& rRange ) const
+{
+ return std::any_of(maTables.begin(), maTables.end(), FindIntersectingTable(rRange));
+}
+
+#if DEBUG_PIVOT_TABLE
+
+namespace {
+
+struct DumpTable
+{
+ void operator() (const std::unique_ptr<ScDPObject>& rObj) const
+ {
+ cout << "-- '" << rObj->GetName() << "'" << endl;
+ ScDPSaveData* pSaveData = rObj->GetSaveData();
+ if (!pSaveData)
+ return;
+
+ pSaveData->Dump();
+
+ cout << endl; // blank line
+ }
+};
+
+}
+
+void ScDPCollection::DumpTables() const
+{
+ std::for_each(maTables.begin(), maTables.end(), DumpTable());
+}
+
+#endif
+
+void ScDPCollection::RemoveCache(const ScDPCache* pCache)
+{
+ if (maSheetCaches.remove(pCache))
+ // sheet cache removed.
+ return;
+
+ if (maNameCaches.remove(pCache))
+ // named range cache removed.
+ return;
+
+ if (maDBCaches.remove(pCache))
+ // database cache removed.
+ return;
+}
+
+void ScDPCollection::GetAllTables(const ScRange& rSrcRange, o3tl::sorted_vector<ScDPObject*>& rRefs) const
+{
+ o3tl::sorted_vector<ScDPObject*> aRefs;
+ for (const auto& rxTable : maTables)
+ {
+ const ScDPObject& rObj = *rxTable;
+ if (!rObj.IsSheetData())
+ // Source is not a sheet range.
+ continue;
+
+ const ScSheetSourceDesc* pDesc = rObj.GetSheetDesc();
+ if (!pDesc)
+ continue;
+
+ if (pDesc->HasRangeName())
+ // This table has a range name as its source.
+ continue;
+
+ if (pDesc->GetSourceRange() != rSrcRange)
+ // Different source range.
+ continue;
+
+ aRefs.insert(const_cast<ScDPObject*>(&rObj));
+ }
+
+ rRefs.swap(aRefs);
+}
+
+void ScDPCollection::GetAllTables(std::u16string_view rSrcName, o3tl::sorted_vector<ScDPObject*>& rRefs) const
+{
+ o3tl::sorted_vector<ScDPObject*> aRefs;
+ for (const auto& rxTable : maTables)
+ {
+ const ScDPObject& rObj = *rxTable;
+ if (!rObj.IsSheetData())
+ // Source is not a sheet range.
+ continue;
+
+ const ScSheetSourceDesc* pDesc = rObj.GetSheetDesc();
+ if (!pDesc)
+ continue;
+
+ if (!pDesc->HasRangeName())
+ // This table probably has a sheet range as its source.
+ continue;
+
+ if (pDesc->GetRangeName() != rSrcName)
+ // Different source name.
+ continue;
+
+ aRefs.insert(const_cast<ScDPObject*>(&rObj));
+ }
+
+ rRefs.swap(aRefs);
+}
+
+void ScDPCollection::GetAllTables(
+ sal_Int32 nSdbType, std::u16string_view rDBName, std::u16string_view rCommand,
+ o3tl::sorted_vector<ScDPObject*>& rRefs) const
+{
+ o3tl::sorted_vector<ScDPObject*> aRefs;
+ for (const auto& rxTable : maTables)
+ {
+ const ScDPObject& rObj = *rxTable;
+ if (!rObj.IsImportData())
+ // Source data is not a database.
+ continue;
+
+ const ScImportSourceDesc* pDesc = rObj.GetImportSourceDesc();
+ if (!pDesc)
+ continue;
+
+ if (pDesc->aDBName != rDBName || pDesc->aObject != rCommand || pDesc->GetCommandType() != nSdbType)
+ // Different database source.
+ continue;
+
+ aRefs.insert(const_cast<ScDPObject*>(&rObj));
+ }
+
+ rRefs.swap(aRefs);
+}
+
+bool operator<(const ScDPCollection::DBType& left, const ScDPCollection::DBType& right)
+{
+ if (left.mnSdbType != right.mnSdbType)
+ return left.mnSdbType < right.mnSdbType;
+
+ if (left.maDBName != right.maDBName)
+ return left.maDBName < right.maDBName;
+
+ return left.maCommand < right.maCommand;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sc/source/core/data/dpoutput.cxx b/sc/source/core/data/dpoutput.cxx
new file mode 100644
index 000000000..bf2109b30
--- /dev/null
+++ b/sc/source/core/data/dpoutput.cxx
@@ -0,0 +1,1781 @@
+/* -*- 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 <scitems.hxx>
+
+#include <comphelper/sequence.hxx>
+#include <editeng/borderline.hxx>
+#include <editeng/boxitem.hxx>
+#include <editeng/wghtitem.hxx>
+#include <editeng/justifyitem.hxx>
+#include <o3tl/safeint.hxx>
+#include <osl/diagnose.h>
+#include <svl/itemset.hxx>
+
+#include <dpoutput.hxx>
+#include <document.hxx>
+#include <attrib.hxx>
+#include <formula/errorcodes.hxx>
+#include <miscuno.hxx>
+#include <globstr.hrc>
+#include <stlpool.hxx>
+#include <stlsheet.hxx>
+#include <scresid.hxx>
+#include <unonames.hxx>
+#include <strings.hrc>
+#include <stringutil.hxx>
+#include <dputil.hxx>
+
+#include <com/sun/star/beans/XPropertySet.hpp>
+#include <com/sun/star/sheet/DataPilotTableHeaderData.hpp>
+#include <com/sun/star/sheet/DataPilotFieldOrientation.hpp>
+#include <com/sun/star/sheet/DataPilotTablePositionData.hpp>
+#include <com/sun/star/sheet/DataPilotTableResultData.hpp>
+#include <com/sun/star/sheet/MemberResultFlags.hpp>
+#include <com/sun/star/sheet/DataResultFlags.hpp>
+#include <com/sun/star/sheet/DataPilotTablePositionType.hpp>
+#include <com/sun/star/sheet/GeneralFunction2.hpp>
+#include <com/sun/star/sheet/MemberResult.hpp>
+#include <com/sun/star/sheet/XDataPilotMemberResults.hpp>
+#include <com/sun/star/sheet/XDataPilotResults.hpp>
+#include <com/sun/star/sheet/XDimensionsSupplier.hpp>
+#include <com/sun/star/sheet/XHierarchiesSupplier.hpp>
+#include <com/sun/star/sheet/XLevelsSupplier.hpp>
+#include <com/sun/star/sheet/XMembersAccess.hpp>
+#include <com/sun/star/sheet/XMembersSupplier.hpp>
+
+#include <limits>
+#include <string_view>
+#include <vector>
+
+using namespace com::sun::star;
+using ::std::vector;
+using ::com::sun::star::beans::XPropertySet;
+using ::com::sun::star::uno::Sequence;
+using ::com::sun::star::uno::UNO_QUERY;
+using ::com::sun::star::uno::Reference;
+using ::com::sun::star::sheet::DataPilotTablePositionData;
+using ::com::sun::star::sheet::DataPilotTableResultData;
+
+#define SC_DP_FRAME_INNER_BOLD 20
+#define SC_DP_FRAME_OUTER_BOLD 40
+
+#define SC_DP_FRAME_COLOR Color(0,0,0) //( 0x20, 0x40, 0x68 )
+
+struct ScDPOutLevelData
+{
+ tools::Long mnDim;
+ tools::Long mnHier;
+ tools::Long mnLevel;
+ tools::Long mnDimPos;
+ sal_uInt32 mnSrcNumFmt; /// Prevailing number format used in the source data.
+ uno::Sequence<sheet::MemberResult> maResult;
+ OUString maName; /// Name is the internal field name.
+ OUString maCaption; /// Caption is the name visible in the output table.
+ bool mbHasHiddenMember:1;
+ bool mbDataLayout:1;
+ bool mbPageDim:1;
+
+ ScDPOutLevelData(tools::Long nDim, tools::Long nHier, tools::Long nLevel, tools::Long nDimPos, sal_uInt32 nSrcNumFmt, const uno::Sequence<sheet::MemberResult> &aResult,
+ const OUString &aName, const OUString &aCaption, bool bHasHiddenMember, bool bDataLayout, bool bPageDim) :
+ mnDim(nDim), mnHier(nHier), mnLevel(nLevel), mnDimPos(nDimPos), mnSrcNumFmt(nSrcNumFmt), maResult(aResult),
+ maName(aName), maCaption(aCaption), mbHasHiddenMember(bHasHiddenMember), mbDataLayout(bDataLayout),
+ mbPageDim(bPageDim)
+ {
+ }
+
+ // bug (73840) in uno::Sequence - copy and then assign doesn't work!
+};
+
+
+
+namespace {
+ struct ScDPOutLevelDataComparator
+ {
+ bool operator()(const ScDPOutLevelData & rA, const ScDPOutLevelData & rB)
+ {
+ return rA.mnDimPos<rB.mnDimPos || ( rA.mnDimPos==rB.mnDimPos && rA.mnHier<rB.mnHier ) ||
+ ( rA.mnDimPos==rB.mnDimPos && rA.mnHier==rB.mnHier && rA.mnLevel<rB.mnLevel );
+ }
+ };
+
+
+class ScDPOutputImpl
+{
+ ScDocument* mpDoc;
+ sal_uInt16 mnTab;
+ ::std::vector< bool > mbNeedLineCols;
+ ::std::vector< SCCOL > mnCols;
+
+ ::std::vector< bool > mbNeedLineRows;
+ ::std::vector< SCROW > mnRows;
+
+ SCCOL mnTabStartCol;
+ SCROW mnTabStartRow;
+
+ SCCOL mnDataStartCol;
+ SCROW mnDataStartRow;
+ SCCOL mnTabEndCol;
+ SCROW mnTabEndRow;
+
+public:
+ ScDPOutputImpl( ScDocument* pDoc, sal_uInt16 nTab,
+ SCCOL nTabStartCol,
+ SCROW nTabStartRow,
+ SCCOL nDataStartCol,
+ SCROW nDataStartRow,
+ SCCOL nTabEndCol,
+ SCROW nTabEndRow );
+ bool AddRow( SCROW nRow );
+ bool AddCol( SCCOL nCol );
+
+ void OutputDataArea();
+ void OutputBlockFrame ( SCCOL nStartCol, SCROW nStartRow, SCCOL nEndCol, SCROW nEndRow, bool bHori = false );
+
+};
+
+void ScDPOutputImpl::OutputDataArea()
+{
+ AddRow( mnDataStartRow );
+ AddCol( mnDataStartCol );
+
+ mnCols.push_back( mnTabEndCol+1); //set last row bottom
+ mnRows.push_back( mnTabEndRow+1); //set last col bottom
+
+ bool bAllRows = ( ( mnTabEndRow - mnDataStartRow + 2 ) == static_cast<SCROW>(mnRows.size()) );
+
+ std::sort( mnCols.begin(), mnCols.end());
+ std::sort( mnRows.begin(), mnRows.end());
+
+ for( SCCOL nCol = 0; nCol < static_cast<SCCOL>(mnCols.size())-1; nCol ++ )
+ {
+ if ( !bAllRows )
+ {
+ if ( nCol < static_cast<SCCOL>(mnCols.size())-2)
+ {
+ for ( SCROW i = nCol%2; i < static_cast<SCROW>(mnRows.size())-2; i +=2 )
+ OutputBlockFrame( mnCols[nCol], mnRows[i], mnCols[nCol+1]-1, mnRows[i+1]-1 );
+ if ( mnRows.size()>=2 )
+ OutputBlockFrame( mnCols[nCol], mnRows[mnRows.size()-2], mnCols[nCol+1]-1, mnRows[mnRows.size()-1]-1 );
+ }
+ else
+ {
+ for ( SCROW i = 0 ; i < static_cast<SCROW>(mnRows.size())-1; i++ )
+ OutputBlockFrame( mnCols[nCol], mnRows[i], mnCols[nCol+1]-1, mnRows[i+1]-1 );
+ }
+ }
+ else
+ OutputBlockFrame( mnCols[nCol], mnRows.front(), mnCols[nCol+1]-1, mnRows.back()-1, bAllRows );
+ }
+ //out put rows area outer framer
+ if ( mnTabStartCol != mnDataStartCol )
+ {
+ if ( mnTabStartRow != mnDataStartRow )
+ OutputBlockFrame( mnTabStartCol, mnTabStartRow, mnDataStartCol-1, mnDataStartRow-1 );
+ OutputBlockFrame( mnTabStartCol, mnDataStartRow, mnDataStartCol-1, mnTabEndRow );
+ }
+ //out put cols area outer framer
+ OutputBlockFrame( mnDataStartCol, mnTabStartRow, mnTabEndCol, mnDataStartRow-1 );
+}
+
+ScDPOutputImpl::ScDPOutputImpl( ScDocument* pDoc, sal_uInt16 nTab,
+ SCCOL nTabStartCol,
+ SCROW nTabStartRow,
+ SCCOL nDataStartCol,
+ SCROW nDataStartRow,
+ SCCOL nTabEndCol,
+ SCROW nTabEndRow ):
+ mpDoc( pDoc ),
+ mnTab( nTab ),
+ mnTabStartCol( nTabStartCol ),
+ mnTabStartRow( nTabStartRow ),
+ mnDataStartCol ( nDataStartCol ),
+ mnDataStartRow ( nDataStartRow ),
+ mnTabEndCol( nTabEndCol ),
+ mnTabEndRow( nTabEndRow )
+{
+ mbNeedLineCols.resize( nTabEndCol-nDataStartCol+1, false );
+ mbNeedLineRows.resize( nTabEndRow-nDataStartRow+1, false );
+
+}
+
+bool ScDPOutputImpl::AddRow( SCROW nRow )
+{
+ if ( !mbNeedLineRows[ nRow - mnDataStartRow ] )
+ {
+ mbNeedLineRows[ nRow - mnDataStartRow ] = true;
+ mnRows.push_back( nRow );
+ return true;
+ }
+ else
+ return false;
+}
+
+bool ScDPOutputImpl::AddCol( SCCOL nCol )
+{
+
+ if ( !mbNeedLineCols[ nCol - mnDataStartCol ] )
+ {
+ mbNeedLineCols[ nCol - mnDataStartCol ] = true;
+ mnCols.push_back( nCol );
+ return true;
+ }
+ else
+ return false;
+}
+
+void ScDPOutputImpl::OutputBlockFrame ( SCCOL nStartCol, SCROW nStartRow, SCCOL nEndCol, SCROW nEndRow, bool bHori )
+{
+ Color color = SC_DP_FRAME_COLOR;
+ ::editeng::SvxBorderLine aLine( &color, SC_DP_FRAME_INNER_BOLD );
+ ::editeng::SvxBorderLine aOutLine( &color, SC_DP_FRAME_OUTER_BOLD );
+
+ SvxBoxItem aBox( ATTR_BORDER );
+
+ if ( nStartCol == mnTabStartCol )
+ aBox.SetLine(&aOutLine, SvxBoxItemLine::LEFT);
+ else
+ aBox.SetLine(&aLine, SvxBoxItemLine::LEFT);
+
+ if ( nStartRow == mnTabStartRow )
+ aBox.SetLine(&aOutLine, SvxBoxItemLine::TOP);
+ else
+ aBox.SetLine(&aLine, SvxBoxItemLine::TOP);
+
+ if ( nEndCol == mnTabEndCol ) //bottom row
+ aBox.SetLine(&aOutLine, SvxBoxItemLine::RIGHT);
+ else
+ aBox.SetLine(&aLine, SvxBoxItemLine::RIGHT);
+
+ if ( nEndRow == mnTabEndRow ) //bottom
+ aBox.SetLine(&aOutLine, SvxBoxItemLine::BOTTOM);
+ else
+ aBox.SetLine(&aLine, SvxBoxItemLine::BOTTOM);
+
+ SvxBoxInfoItem aBoxInfo( ATTR_BORDER_INNER );
+ aBoxInfo.SetValid(SvxBoxInfoItemValidFlags::VERT,false );
+ if ( bHori )
+ {
+ aBoxInfo.SetValid(SvxBoxInfoItemValidFlags::HORI);
+ aBoxInfo.SetLine( &aLine, SvxBoxInfoItemLine::HORI );
+ }
+ else
+ aBoxInfo.SetValid(SvxBoxInfoItemValidFlags::HORI,false );
+
+ aBoxInfo.SetValid(SvxBoxInfoItemValidFlags::DISTANCE,false);
+
+ mpDoc->ApplyFrameAreaTab(ScRange(nStartCol, nStartRow, mnTab, nEndCol, nEndRow , mnTab), aBox, aBoxInfo);
+
+}
+
+void lcl_SetStyleById(ScDocument* pDoc, SCTAB nTab,
+ SCCOL nCol1, SCROW nRow1, SCCOL nCol2, SCROW nRow2,
+ TranslateId pStrId)
+{
+ if ( nCol1 > nCol2 || nRow1 > nRow2 )
+ {
+ OSL_FAIL("SetStyleById: invalid range");
+ return;
+ }
+
+ OUString aStyleName = ScResId(pStrId);
+ ScStyleSheetPool* pStlPool = pDoc->GetStyleSheetPool();
+ ScStyleSheet* pStyle = static_cast<ScStyleSheet*>( pStlPool->Find( aStyleName, SfxStyleFamily::Para ) );
+ if (!pStyle)
+ {
+ // create new style (was in ScPivot::SetStyle)
+
+ pStyle = static_cast<ScStyleSheet*>( &pStlPool->Make( aStyleName, SfxStyleFamily::Para,
+ SfxStyleSearchBits::UserDefined ) );
+ pStyle->SetParent( ScResId(STR_STYLENAME_STANDARD) );
+ SfxItemSet& rSet = pStyle->GetItemSet();
+ if (pStrId == STR_PIVOT_STYLENAME_RESULT || pStrId == STR_PIVOT_STYLENAME_TITLE){
+ rSet.Put( SvxWeightItem( WEIGHT_BOLD, ATTR_FONT_WEIGHT ) );
+ rSet.Put( SvxWeightItem( WEIGHT_BOLD, ATTR_CJK_FONT_WEIGHT ) );
+ rSet.Put( SvxWeightItem( WEIGHT_BOLD, ATTR_CTL_FONT_WEIGHT ) );
+ }
+ if (pStrId == STR_PIVOT_STYLENAME_CATEGORY || pStrId == STR_PIVOT_STYLENAME_TITLE)
+ rSet.Put( SvxHorJustifyItem( SvxCellHorJustify::Left, ATTR_HOR_JUSTIFY ) );
+ }
+
+ pDoc->ApplyStyleAreaTab( nCol1, nRow1, nCol2, nRow2, nTab, *pStyle );
+}
+
+void lcl_SetFrame( ScDocument* pDoc, SCTAB nTab,
+ SCCOL nCol1, SCROW nRow1, SCCOL nCol2, SCROW nRow2,
+ sal_uInt16 nWidth )
+{
+ ::editeng::SvxBorderLine aLine(nullptr, nWidth, SvxBorderLineStyle::SOLID);
+ SvxBoxItem aBox( ATTR_BORDER );
+ aBox.SetLine(&aLine, SvxBoxItemLine::LEFT);
+ aBox.SetLine(&aLine, SvxBoxItemLine::TOP);
+ aBox.SetLine(&aLine, SvxBoxItemLine::RIGHT);
+ aBox.SetLine(&aLine, SvxBoxItemLine::BOTTOM);
+ SvxBoxInfoItem aBoxInfo( ATTR_BORDER_INNER );
+ aBoxInfo.SetValid(SvxBoxInfoItemValidFlags::HORI,false);
+ aBoxInfo.SetValid(SvxBoxInfoItemValidFlags::VERT,false);
+ aBoxInfo.SetValid(SvxBoxInfoItemValidFlags::DISTANCE,false);
+
+ pDoc->ApplyFrameAreaTab(ScRange(nCol1, nRow1, nTab, nCol2, nRow2, nTab), aBox, aBoxInfo);
+}
+
+void lcl_FillNumberFormats( std::unique_ptr<sal_uInt32[]>& rFormats, sal_Int32& rCount,
+ const uno::Reference<sheet::XDataPilotMemberResults>& xLevRes,
+ const uno::Reference<container::XIndexAccess>& xDims )
+{
+ if ( rFormats )
+ return; // already set
+
+ // xLevRes is from the data layout dimension
+ //TODO: use result sequence from ScDPOutLevelData!
+
+ uno::Sequence<sheet::MemberResult> aResult = xLevRes->getResults();
+
+ tools::Long nSize = aResult.getLength();
+ if (!nSize)
+ return;
+
+ // get names/formats for all data dimensions
+ //TODO: merge this with the loop to collect ScDPOutLevelData?
+
+ std::vector <OUString> aDataNames;
+ std::vector <sal_uInt32> aDataFormats;
+ sal_Int32 nDimCount = xDims->getCount();
+ sal_Int32 nDim = 0;
+ for ( ; nDim < nDimCount ; nDim++)
+ {
+ uno::Reference<uno::XInterface> xDim(xDims->getByIndex(nDim), uno::UNO_QUERY);
+ uno::Reference<beans::XPropertySet> xDimProp( xDim, uno::UNO_QUERY );
+ uno::Reference<container::XNamed> xDimName( xDim, uno::UNO_QUERY );
+ if ( xDimProp.is() && xDimName.is() )
+ {
+ sheet::DataPilotFieldOrientation eDimOrient =
+ ScUnoHelpFunctions::GetEnumProperty(
+ xDimProp, SC_UNO_DP_ORIENTATION,
+ sheet::DataPilotFieldOrientation_HIDDEN );
+ if ( eDimOrient == sheet::DataPilotFieldOrientation_DATA )
+ {
+ aDataNames.push_back(xDimName->getName());
+ tools::Long nFormat = ScUnoHelpFunctions::GetLongProperty(
+ xDimProp,
+ SC_UNONAME_NUMFMT );
+ aDataFormats.push_back(nFormat);
+ }
+ }
+ }
+
+ if (aDataFormats.empty())
+ return;
+
+ const sheet::MemberResult* pArray = aResult.getConstArray();
+
+ OUString aName;
+ sal_uInt32* pNumFmt = new sal_uInt32[nSize];
+ if (aDataFormats.size() == 1)
+ {
+ // only one data dimension -> use its numberformat everywhere
+ tools::Long nFormat = aDataFormats[0];
+ for (tools::Long nPos=0; nPos<nSize; nPos++)
+ pNumFmt[nPos] = nFormat;
+ }
+ else
+ {
+ for (tools::Long nPos=0; nPos<nSize; nPos++)
+ {
+ // if CONTINUE bit is set, keep previous name
+ //TODO: keep number format instead!
+ if ( !(pArray[nPos].Flags & sheet::MemberResultFlags::CONTINUE) )
+ aName = pArray[nPos].Name;
+
+ sal_uInt32 nFormat = 0;
+ for (size_t i=0; i<aDataFormats.size(); i++)
+ if (aName == aDataNames[i]) //TODO: search more efficiently?
+ {
+ nFormat = aDataFormats[i];
+ break;
+ }
+ pNumFmt[nPos] = nFormat;
+ }
+ }
+
+ rFormats.reset( pNumFmt );
+ rCount = nSize;
+}
+
+sal_uInt32 lcl_GetFirstNumberFormat( const uno::Reference<container::XIndexAccess>& xDims )
+{
+ tools::Long nDimCount = xDims->getCount();
+ for (tools::Long nDim=0; nDim<nDimCount; nDim++)
+ {
+ uno::Reference<beans::XPropertySet> xDimProp(xDims->getByIndex(nDim), uno::UNO_QUERY);
+ if ( xDimProp.is() )
+ {
+ sheet::DataPilotFieldOrientation eDimOrient =
+ ScUnoHelpFunctions::GetEnumProperty(
+ xDimProp, SC_UNO_DP_ORIENTATION,
+ sheet::DataPilotFieldOrientation_HIDDEN );
+ if ( eDimOrient == sheet::DataPilotFieldOrientation_DATA )
+ {
+ tools::Long nFormat = ScUnoHelpFunctions::GetLongProperty(
+ xDimProp,
+ SC_UNONAME_NUMFMT );
+
+ return nFormat; // use format from first found data dimension
+ }
+ }
+ }
+
+ return 0; // none found
+}
+
+bool lcl_MemberEmpty( const uno::Sequence<sheet::MemberResult>& rSeq )
+{
+ // used to skip levels that have no members
+
+ return std::none_of(rSeq.begin(), rSeq.end(),
+ [](const sheet::MemberResult& rMem) {
+ return rMem.Flags & sheet::MemberResultFlags::HASMEMBER; });
+}
+
+/**
+ * Get visible page dimension members as results, except that, if all
+ * members are visible, then this function returns empty result.
+ */
+uno::Sequence<sheet::MemberResult> getVisiblePageMembersAsResults( const uno::Reference<uno::XInterface>& xLevel )
+{
+ if (!xLevel.is())
+ return uno::Sequence<sheet::MemberResult>();
+
+ uno::Reference<sheet::XMembersSupplier> xMSupplier(xLevel, UNO_QUERY);
+ if (!xMSupplier.is())
+ return uno::Sequence<sheet::MemberResult>();
+
+ uno::Reference<sheet::XMembersAccess> xNA = xMSupplier->getMembers();
+ if (!xNA.is())
+ return uno::Sequence<sheet::MemberResult>();
+
+ std::vector<sheet::MemberResult> aRes;
+ const uno::Sequence<OUString> aNames = xNA->getElementNames();
+ for (const OUString& rName : aNames)
+ {
+ xNA->getByName(rName);
+
+ uno::Reference<beans::XPropertySet> xMemPS(xNA->getByName(rName), UNO_QUERY);
+ if (!xMemPS.is())
+ continue;
+
+ OUString aCaption = ScUnoHelpFunctions::GetStringProperty(xMemPS, SC_UNO_DP_LAYOUTNAME, OUString());
+ if (aCaption.isEmpty())
+ aCaption = rName;
+
+ bool bVisible = ScUnoHelpFunctions::GetBoolProperty(xMemPS, SC_UNO_DP_ISVISIBLE);
+
+ if (bVisible)
+ {
+ /* TODO: any numeric value to obtain? */
+ aRes.emplace_back(rName, aCaption, 0, std::numeric_limits<double>::quiet_NaN());
+ }
+ }
+
+ if (o3tl::make_unsigned(aNames.getLength()) == aRes.size())
+ // All members are visible. Return empty result.
+ return uno::Sequence<sheet::MemberResult>();
+
+ return ScUnoHelpFunctions::VectorToSequence(aRes);
+}
+
+}
+
+ScDPOutput::ScDPOutput( ScDocument* pD, const uno::Reference<sheet::XDimensionsSupplier>& xSrc,
+ const ScAddress& rPos, bool bFilter ) :
+ pDoc( pD ),
+ xSource( xSrc ),
+ aStartPos( rPos ),
+ nColFmtCount( 0 ),
+ nRowFmtCount( 0 ),
+ nSingleNumFmt( 0 ),
+ nColCount(0),
+ nRowCount(0),
+ nHeaderSize(0),
+ bDoFilter(bFilter),
+ bResultsError(false),
+ bSizesValid(false),
+ bSizeOverflow(false),
+ mbHeaderLayout(false)
+{
+ nTabStartCol = nMemberStartCol = nDataStartCol = nTabEndCol = 0;
+ nTabStartRow = nMemberStartRow = nDataStartRow = nTabEndRow = 0;
+
+ uno::Reference<sheet::XDataPilotResults> xResult( xSource, uno::UNO_QUERY );
+ if ( xSource.is() && xResult.is() )
+ {
+ // get dimension results:
+
+ uno::Reference<container::XIndexAccess> xDims =
+ new ScNameToIndexAccess( xSource->getDimensions() );
+ tools::Long nDimCount = xDims->getCount();
+ for (tools::Long nDim=0; nDim<nDimCount; nDim++)
+ {
+ uno::Reference<uno::XInterface> xDim(xDims->getByIndex(nDim), uno::UNO_QUERY);
+ uno::Reference<beans::XPropertySet> xDimProp( xDim, uno::UNO_QUERY );
+ uno::Reference<sheet::XHierarchiesSupplier> xDimSupp( xDim, uno::UNO_QUERY );
+ if ( xDimProp.is() && xDimSupp.is() )
+ {
+ sheet::DataPilotFieldOrientation eDimOrient =
+ ScUnoHelpFunctions::GetEnumProperty(
+ xDimProp, SC_UNO_DP_ORIENTATION,
+ sheet::DataPilotFieldOrientation_HIDDEN );
+ tools::Long nDimPos = ScUnoHelpFunctions::GetLongProperty( xDimProp,
+ SC_UNO_DP_POSITION );
+ bool bIsDataLayout = ScUnoHelpFunctions::GetBoolProperty(
+ xDimProp, SC_UNO_DP_ISDATALAYOUT);
+ bool bHasHiddenMember = ScUnoHelpFunctions::GetBoolProperty(
+ xDimProp, SC_UNO_DP_HAS_HIDDEN_MEMBER);
+ sal_Int32 nNumFmt = ScUnoHelpFunctions::GetLongProperty(
+ xDimProp, SC_UNO_DP_NUMBERFO);
+
+ if ( eDimOrient != sheet::DataPilotFieldOrientation_HIDDEN )
+ {
+ uno::Reference<container::XIndexAccess> xHiers =
+ new ScNameToIndexAccess( xDimSupp->getHierarchies() );
+ tools::Long nHierarchy = ScUnoHelpFunctions::GetLongProperty(
+ xDimProp,
+ SC_UNO_DP_USEDHIERARCHY );
+ if ( nHierarchy >= xHiers->getCount() )
+ nHierarchy = 0;
+
+ uno::Reference<sheet::XLevelsSupplier> xHierSupp(xHiers->getByIndex(nHierarchy),
+ uno::UNO_QUERY);
+ if ( xHierSupp.is() )
+ {
+ uno::Reference<container::XIndexAccess> xLevels =
+ new ScNameToIndexAccess( xHierSupp->getLevels() );
+ tools::Long nLevCount = xLevels->getCount();
+ for (tools::Long nLev=0; nLev<nLevCount; nLev++)
+ {
+ uno::Reference<uno::XInterface> xLevel(xLevels->getByIndex(nLev),
+ uno::UNO_QUERY);
+ uno::Reference<container::XNamed> xLevNam( xLevel, uno::UNO_QUERY );
+ uno::Reference<sheet::XDataPilotMemberResults> xLevRes(
+ xLevel, uno::UNO_QUERY );
+ if ( xLevNam.is() && xLevRes.is() )
+ {
+ OUString aName = xLevNam->getName();
+ Reference<XPropertySet> xPropSet(xLevel, UNO_QUERY);
+ // Caption equals the field name by default.
+ // #i108948# use ScUnoHelpFunctions::GetStringProperty, because
+ // LayoutName is new and may not be present in external implementation
+ OUString aCaption = ScUnoHelpFunctions::GetStringProperty( xPropSet,
+ SC_UNO_DP_LAYOUTNAME, aName );
+
+ switch ( eDimOrient )
+ {
+ case sheet::DataPilotFieldOrientation_COLUMN:
+ {
+ uno::Sequence<sheet::MemberResult> aResult = xLevRes->getResults();
+ if (!lcl_MemberEmpty(aResult))
+ {
+ ScDPOutLevelData tmp(nDim, nHierarchy, nLev, nDimPos, nNumFmt, aResult, aName,
+ aCaption, bHasHiddenMember, bIsDataLayout, false);
+ pColFields.push_back(tmp);
+ }
+ }
+ break;
+ case sheet::DataPilotFieldOrientation_ROW:
+ {
+ uno::Sequence<sheet::MemberResult> aResult = xLevRes->getResults();
+ if (!lcl_MemberEmpty(aResult))
+ {
+ ScDPOutLevelData tmp(nDim, nHierarchy, nLev, nDimPos, nNumFmt, aResult, aName,
+ aCaption, bHasHiddenMember, bIsDataLayout, false);
+ pRowFields.push_back(tmp);
+ }
+ }
+ break;
+ case sheet::DataPilotFieldOrientation_PAGE:
+ {
+ uno::Sequence<sheet::MemberResult> aResult = getVisiblePageMembersAsResults(xLevel);
+ // no check on results for page fields
+ ScDPOutLevelData tmp(nDim, nHierarchy, nLev, nDimPos, nNumFmt, aResult, aName,
+ aCaption, bHasHiddenMember, false, true);
+ pPageFields.push_back(tmp);
+ }
+ break;
+ default:
+ {
+ // added to avoid warnings
+ }
+ }
+
+ // get number formats from data dimensions
+ if ( bIsDataLayout )
+ {
+ OSL_ENSURE( nLevCount == 1, "data layout: multiple levels?" );
+ if ( eDimOrient == sheet::DataPilotFieldOrientation_COLUMN )
+ lcl_FillNumberFormats( pColNumFmt, nColFmtCount, xLevRes, xDims );
+ else if ( eDimOrient == sheet::DataPilotFieldOrientation_ROW )
+ lcl_FillNumberFormats( pRowNumFmt, nRowFmtCount, xLevRes, xDims );
+ }
+ }
+ }
+ }
+ }
+ else if ( bIsDataLayout )
+ {
+ // data layout dimension is hidden (allowed if there is only one data dimension)
+ // -> use the number format from the first data dimension for all results
+
+ nSingleNumFmt = lcl_GetFirstNumberFormat( xDims );
+ }
+ }
+ }
+ std::sort(pColFields.begin(), pColFields.end(), ScDPOutLevelDataComparator());
+ std::sort(pRowFields.begin(), pRowFields.end(), ScDPOutLevelDataComparator());
+ std::sort(pPageFields.begin(), pPageFields.end(), ScDPOutLevelDataComparator());
+
+ // get data results:
+
+ try
+ {
+ aData = xResult->getResults();
+ }
+ catch (const uno::RuntimeException&)
+ {
+ bResultsError = true;
+ }
+ }
+
+ // get "DataDescription" property (may be missing in external sources)
+
+ uno::Reference<beans::XPropertySet> xSrcProp( xSource, uno::UNO_QUERY );
+ if ( !xSrcProp.is() )
+ return;
+
+ try
+ {
+ uno::Any aAny = xSrcProp->getPropertyValue( SC_UNO_DP_DATADESC );
+ OUString aUStr;
+ aAny >>= aUStr;
+ aDataDescription = aUStr;
+ }
+ catch(const uno::Exception&)
+ {
+ }
+}
+
+ScDPOutput::~ScDPOutput()
+{
+}
+
+void ScDPOutput::SetPosition( const ScAddress& rPos )
+{
+ aStartPos = rPos;
+ bSizesValid = bSizeOverflow = false;
+}
+
+void ScDPOutput::DataCell( SCCOL nCol, SCROW nRow, SCTAB nTab, const sheet::DataResult& rData )
+{
+ tools::Long nFlags = rData.Flags;
+ if ( nFlags & sheet::DataResultFlags::ERROR )
+ {
+ pDoc->SetError( nCol, nRow, nTab, FormulaError::NoValue );
+ }
+ else if ( nFlags & sheet::DataResultFlags::HASDATA )
+ {
+ pDoc->SetValue( nCol, nRow, nTab, rData.Value );
+
+ // use number formats from source
+
+ OSL_ENSURE( bSizesValid, "DataCell: !bSizesValid" );
+ sal_uInt32 nFormat = 0;
+ bool bApplyFormat = false;
+ if ( pColNumFmt )
+ {
+ if ( nCol >= nDataStartCol )
+ {
+ tools::Long nIndex = nCol - nDataStartCol;
+ if ( nIndex < nColFmtCount )
+ {
+ nFormat = pColNumFmt[nIndex];
+ bApplyFormat = true;
+ }
+ }
+ }
+ else if ( pRowNumFmt )
+ {
+ if ( nRow >= nDataStartRow )
+ {
+ tools::Long nIndex = nRow - nDataStartRow;
+ if ( nIndex < nRowFmtCount )
+ {
+ nFormat = pRowNumFmt[nIndex];
+ bApplyFormat = true;
+ }
+ }
+ }
+ else if ( nSingleNumFmt != 0 )
+ {
+ nFormat = nSingleNumFmt; // single format is used everywhere
+ bApplyFormat = true;
+ }
+
+ if (bApplyFormat)
+ pDoc->ApplyAttr(nCol, nRow, nTab, SfxUInt32Item(ATTR_VALUE_FORMAT, nFormat));
+ }
+ // SubTotal formatting is controlled by headers
+}
+
+void ScDPOutput::HeaderCell( SCCOL nCol, SCROW nRow, SCTAB nTab,
+ const sheet::MemberResult& rData, bool bColHeader, tools::Long nLevel )
+{
+ tools::Long nFlags = rData.Flags;
+
+ if ( nFlags & sheet::MemberResultFlags::HASMEMBER )
+ {
+ bool bNumeric = (nFlags & sheet::MemberResultFlags::NUMERIC) != 0;
+ if (bNumeric && std::isfinite( rData.Value))
+ {
+ pDoc->SetValue( nCol, nRow, nTab, rData.Value);
+ }
+ else
+ {
+ ScSetStringParam aParam;
+ if (bNumeric)
+ aParam.setNumericInput();
+ else
+ aParam.setTextInput();
+
+ pDoc->SetString(nCol, nRow, nTab, rData.Caption, &aParam);
+ }
+ }
+
+ if ( !(nFlags & sheet::MemberResultFlags::SUBTOTAL) )
+ return;
+
+ ScDPOutputImpl outputimp( pDoc, nTab,
+ nTabStartCol, nTabStartRow,
+ nDataStartCol, nDataStartRow, nTabEndCol, nTabEndRow );
+ //TODO: limit frames to horizontal or vertical?
+ if (bColHeader)
+ {
+ outputimp.OutputBlockFrame( nCol,nMemberStartRow+static_cast<SCROW>(nLevel), nCol,nDataStartRow-1 );
+
+ lcl_SetStyleById( pDoc,nTab, nCol,nMemberStartRow+static_cast<SCROW>(nLevel), nCol,nDataStartRow-1,
+ STR_PIVOT_STYLENAME_TITLE );
+ lcl_SetStyleById( pDoc,nTab, nCol,nDataStartRow, nCol,nTabEndRow,
+ STR_PIVOT_STYLENAME_RESULT );
+ }
+ else
+ {
+ outputimp.OutputBlockFrame( nMemberStartCol+static_cast<SCCOL>(nLevel),nRow, nDataStartCol-1,nRow );
+ lcl_SetStyleById( pDoc,nTab, nMemberStartCol+static_cast<SCCOL>(nLevel),nRow, nDataStartCol-1,nRow,
+ STR_PIVOT_STYLENAME_TITLE );
+ lcl_SetStyleById( pDoc,nTab, nDataStartCol,nRow, nTabEndCol,nRow,
+ STR_PIVOT_STYLENAME_RESULT );
+ }
+}
+
+void ScDPOutput::FieldCell(
+ SCCOL nCol, SCROW nRow, SCTAB nTab, const ScDPOutLevelData& rData, bool bInTable)
+{
+ // Avoid unwanted automatic format detection.
+ ScSetStringParam aParam;
+ aParam.mbDetectNumberFormat = false;
+ aParam.meSetTextNumFormat = ScSetStringParam::Always;
+ aParam.mbHandleApostrophe = false;
+ pDoc->SetString(nCol, nRow, nTab, rData.maCaption, &aParam);
+
+ if (bInTable)
+ lcl_SetFrame( pDoc,nTab, nCol,nRow, nCol,nRow, 20 );
+
+ // For field button drawing
+ ScMF nMergeFlag = ScMF::NONE;
+ if (rData.mbHasHiddenMember)
+ nMergeFlag |= ScMF::HiddenMember;
+
+ if (rData.mbPageDim)
+ {
+ nMergeFlag |= ScMF::ButtonPopup;
+ pDoc->ApplyFlagsTab(nCol, nRow, nCol, nRow, nTab, ScMF::Button);
+ pDoc->ApplyFlagsTab(nCol+1, nRow, nCol+1, nRow, nTab, nMergeFlag);
+ }
+ else
+ {
+ nMergeFlag |= ScMF::Button;
+ if (!rData.mbDataLayout)
+ nMergeFlag |= ScMF::ButtonPopup;
+ pDoc->ApplyFlagsTab(nCol, nRow, nCol, nRow, nTab, nMergeFlag);
+ }
+
+ lcl_SetStyleById( pDoc,nTab, nCol,nRow, nCol,nRow, STR_PIVOT_STYLENAME_FIELDNAME );
+}
+
+static void lcl_DoFilterButton( ScDocument* pDoc, SCCOL nCol, SCROW nRow, SCTAB nTab )
+{
+ pDoc->SetString( nCol, nRow, nTab, ScResId(STR_CELL_FILTER) );
+ pDoc->ApplyFlagsTab(nCol, nRow, nCol, nRow, nTab, ScMF::Button);
+}
+
+void ScDPOutput::CalcSizes()
+{
+ if (bSizesValid)
+ return;
+
+ // get column size of data from first row
+ //TODO: allow different sizes (and clear following areas) ???
+
+ nRowCount = aData.getLength();
+ const uno::Sequence<sheet::DataResult>* pRowAry = aData.getConstArray();
+ nColCount = nRowCount ? ( pRowAry[0].getLength() ) : 0;
+
+ nHeaderSize = 1;
+ if (GetHeaderLayout() && pColFields.empty())
+ // Insert an extra header row only when there is no column field.
+ nHeaderSize = 2;
+
+ // calculate output positions and sizes
+
+ tools::Long nPageSize = 0; // use page fields!
+ if ( bDoFilter || !pPageFields.empty() )
+ {
+ nPageSize += pPageFields.size() + 1; // plus one empty row
+ if ( bDoFilter )
+ ++nPageSize; // filter button above the page fields
+ }
+
+ if ( aStartPos.Col() + static_cast<tools::Long>(pRowFields.size()) + nColCount - 1 > pDoc->MaxCol() ||
+ aStartPos.Row() + nPageSize + nHeaderSize + static_cast<tools::Long>(pColFields.size()) + nRowCount > pDoc->MaxRow())
+ {
+ bSizeOverflow = true;
+ }
+
+ nTabStartCol = aStartPos.Col();
+ nTabStartRow = aStartPos.Row() + static_cast<SCROW>(nPageSize); // below page fields
+ nMemberStartCol = nTabStartCol;
+ nMemberStartRow = nTabStartRow + static_cast<SCROW>(nHeaderSize);
+ nDataStartCol = nMemberStartCol + static_cast<SCCOL>(pRowFields.size());
+ nDataStartRow = nMemberStartRow + static_cast<SCROW>(pColFields.size());
+ if ( nColCount > 0 )
+ nTabEndCol = nDataStartCol + static_cast<SCCOL>(nColCount) - 1;
+ else
+ nTabEndCol = nDataStartCol; // single column will remain empty
+ // if page fields are involved, include the page selection cells
+ if ( !pPageFields.empty() && nTabEndCol < nTabStartCol + 1 )
+ nTabEndCol = nTabStartCol + 1;
+ if ( nRowCount > 0 )
+ nTabEndRow = nDataStartRow + static_cast<SCROW>(nRowCount) - 1;
+ else
+ nTabEndRow = nDataStartRow; // single row will remain empty
+ bSizesValid = true;
+}
+
+sal_Int32 ScDPOutput::GetPositionType(const ScAddress& rPos)
+{
+ using namespace ::com::sun::star::sheet;
+
+ SCCOL nCol = rPos.Col();
+ SCROW nRow = rPos.Row();
+ SCTAB nTab = rPos.Tab();
+ if ( nTab != aStartPos.Tab() )
+ return DataPilotTablePositionType::NOT_IN_TABLE;
+
+ CalcSizes();
+
+ // Make sure the cursor is within the table.
+ if (nCol < nTabStartCol || nRow < nTabStartRow || nCol > nTabEndCol || nRow > nTabEndRow)
+ return DataPilotTablePositionType::NOT_IN_TABLE;
+
+ // test for result data area.
+ if (nCol >= nDataStartCol && nCol <= nTabEndCol && nRow >= nDataStartRow && nRow <= nTabEndRow)
+ return DataPilotTablePositionType::RESULT;
+
+ bool bInColHeader = (nRow >= nTabStartRow && nRow < nDataStartRow);
+ bool bInRowHeader = (nCol >= nTabStartCol && nCol < nDataStartCol);
+
+ if (bInColHeader && bInRowHeader)
+ // probably in that ugly little box at the upper-left corner of the table.
+ return DataPilotTablePositionType::OTHER;
+
+ if (bInColHeader)
+ {
+ if (nRow == nTabStartRow)
+ // first row in the column header area is always used for column
+ // field buttons.
+ return DataPilotTablePositionType::OTHER;
+
+ return DataPilotTablePositionType::COLUMN_HEADER;
+ }
+
+ if (bInRowHeader)
+ return DataPilotTablePositionType::ROW_HEADER;
+
+ return DataPilotTablePositionType::OTHER;
+}
+
+void ScDPOutput::Output()
+{
+ SCTAB nTab = aStartPos.Tab();
+ const uno::Sequence<sheet::DataResult>* pRowAry = aData.getConstArray();
+
+ // calculate output positions and sizes
+
+ CalcSizes();
+ if ( bSizeOverflow || bResultsError ) // does output area exceed sheet limits?
+ return; // nothing
+
+ // clear whole (new) output area
+ // when modifying table, clear old area !
+ //TODO: include InsertDeleteFlags::OBJECTS ???
+ pDoc->DeleteAreaTab( aStartPos.Col(), aStartPos.Row(), nTabEndCol, nTabEndRow, nTab, InsertDeleteFlags::ALL );
+
+ if ( bDoFilter )
+ lcl_DoFilterButton( pDoc, aStartPos.Col(), aStartPos.Row(), nTab );
+
+ // output page fields:
+
+ for (size_t nField=0; nField<pPageFields.size(); ++nField)
+ {
+ SCCOL nHdrCol = aStartPos.Col();
+ SCROW nHdrRow = aStartPos.Row() + nField + ( bDoFilter ? 1 : 0 );
+ // draw without frame for consistency with filter button:
+ FieldCell(nHdrCol, nHdrRow, nTab, pPageFields[nField], false);
+ SCCOL nFldCol = nHdrCol + 1;
+
+ OUString aPageValue = ScResId(SCSTR_ALL);
+ const uno::Sequence<sheet::MemberResult>& rRes = pPageFields[nField].maResult;
+ sal_Int32 n = rRes.getLength();
+ if (n == 1)
+ {
+ if (rRes[0].Caption.isEmpty())
+ aPageValue = ScResId(STR_EMPTYDATA);
+ else
+ aPageValue = rRes[0].Caption;
+ }
+ else if (n > 1)
+ aPageValue = ScResId(SCSTR_MULTIPLE);
+
+ ScSetStringParam aParam;
+ aParam.setTextInput();
+ pDoc->SetString(nFldCol, nHdrRow, nTab, aPageValue, &aParam);
+
+ lcl_SetFrame( pDoc,nTab, nFldCol,nHdrRow, nFldCol,nHdrRow, 20 );
+ }
+
+ // data description
+ // (may get overwritten by first row field)
+
+ if (aDataDescription.isEmpty())
+ {
+ //TODO: use default string ("result") ?
+ }
+ pDoc->SetString(nTabStartCol, nTabStartRow, nTab, aDataDescription);
+
+ // set STR_PIVOT_STYLENAME_INNER for whole data area (subtotals are overwritten)
+
+ if ( nDataStartRow > nTabStartRow )
+ lcl_SetStyleById( pDoc, nTab, nTabStartCol, nTabStartRow, nTabEndCol, nDataStartRow-1,
+ STR_PIVOT_STYLENAME_TOP );
+ lcl_SetStyleById( pDoc, nTab, nDataStartCol, nDataStartRow, nTabEndCol, nTabEndRow,
+ STR_PIVOT_STYLENAME_INNER );
+
+ // output column headers:
+ ScDPOutputImpl outputimp( pDoc, nTab,
+ nTabStartCol, nTabStartRow,
+ nDataStartCol, nDataStartRow, nTabEndCol, nTabEndRow );
+ for (size_t nField=0; nField<pColFields.size(); nField++)
+ {
+ SCCOL nHdrCol = nDataStartCol + static_cast<SCCOL>(nField); //TODO: check for overflow
+ FieldCell(nHdrCol, nTabStartRow, nTab, pColFields[nField], true);
+
+ SCROW nRowPos = nMemberStartRow + static_cast<SCROW>(nField); //TODO: check for overflow
+ const uno::Sequence<sheet::MemberResult> rSequence = pColFields[nField].maResult;
+ const sheet::MemberResult* pArray = rSequence.getConstArray();
+ tools::Long nThisColCount = rSequence.getLength();
+ OSL_ENSURE( nThisColCount == nColCount, "count mismatch" ); //TODO: ???
+ for (tools::Long nCol=0; nCol<nThisColCount; nCol++)
+ {
+ SCCOL nColPos = nDataStartCol + static_cast<SCCOL>(nCol); //TODO: check for overflow
+ HeaderCell( nColPos, nRowPos, nTab, pArray[nCol], true, nField );
+ if ( ( pArray[nCol].Flags & sheet::MemberResultFlags::HASMEMBER ) &&
+ !( pArray[nCol].Flags & sheet::MemberResultFlags::SUBTOTAL ) )
+ {
+ tools::Long nEnd = nCol;
+ while ( nEnd+1 < nThisColCount && ( pArray[nEnd+1].Flags & sheet::MemberResultFlags::CONTINUE ) )
+ ++nEnd;
+ SCCOL nEndColPos = nDataStartCol + static_cast<SCCOL>(nEnd); //TODO: check for overflow
+ if ( nField+1 < pColFields.size())
+ {
+ if ( nField == pColFields.size() - 2 )
+ {
+ outputimp.AddCol( nColPos );
+ if ( nColPos + 1 == nEndColPos )
+ outputimp.OutputBlockFrame( nColPos,nRowPos, nEndColPos,nRowPos+1, true );
+ }
+ else
+ outputimp.OutputBlockFrame( nColPos,nRowPos, nEndColPos,nRowPos );
+
+ lcl_SetStyleById( pDoc, nTab, nColPos,nRowPos, nEndColPos,nDataStartRow-1, STR_PIVOT_STYLENAME_CATEGORY );
+ }
+ else
+ lcl_SetStyleById( pDoc, nTab, nColPos,nRowPos, nColPos,nDataStartRow-1, STR_PIVOT_STYLENAME_CATEGORY );
+ }
+ else if ( pArray[nCol].Flags & sheet::MemberResultFlags::SUBTOTAL )
+ outputimp.AddCol( nColPos );
+
+ // Apply the same number format as in data source.
+ pDoc->ApplyAttr(nColPos, nRowPos, nTab, SfxUInt32Item(ATTR_VALUE_FORMAT, pColFields[nField].mnSrcNumFmt));
+ }
+ if ( nField== 0 && pColFields.size() == 1 )
+ outputimp.OutputBlockFrame( nDataStartCol,nTabStartRow, nTabEndCol,nRowPos-1 );
+ }
+
+ // output row headers:
+ std::vector<bool> vbSetBorder;
+ vbSetBorder.resize( nTabEndRow - nDataStartRow + 1, false );
+ for (size_t nField=0; nField<pRowFields.size(); nField++)
+ {
+ SCCOL nHdrCol = nTabStartCol + static_cast<SCCOL>(nField); //TODO: check for overflow
+ SCROW nHdrRow = nDataStartRow - 1;
+ FieldCell(nHdrCol, nHdrRow, nTab, pRowFields[nField], true);
+
+ SCCOL nColPos = nMemberStartCol + static_cast<SCCOL>(nField); //TODO: check for overflow
+ const uno::Sequence<sheet::MemberResult> rSequence = pRowFields[nField].maResult;
+ const sheet::MemberResult* pArray = rSequence.getConstArray();
+ sal_Int32 nThisRowCount = rSequence.getLength();
+ OSL_ENSURE( nThisRowCount == nRowCount, "count mismatch" ); //TODO: ???
+ for (sal_Int32 nRow=0; nRow<nThisRowCount; nRow++)
+ {
+ SCROW nRowPos = nDataStartRow + static_cast<SCROW>(nRow); //TODO: check for overflow
+ HeaderCell( nColPos, nRowPos, nTab, pArray[nRow], false, nField );
+ if ( ( pArray[nRow].Flags & sheet::MemberResultFlags::HASMEMBER ) &&
+ !( pArray[nRow].Flags & sheet::MemberResultFlags::SUBTOTAL ) )
+ {
+ if ( nField+1 < pRowFields.size() )
+ {
+ tools::Long nEnd = nRow;
+ while ( nEnd+1 < nThisRowCount && ( pArray[nEnd+1].Flags & sheet::MemberResultFlags::CONTINUE ) )
+ ++nEnd;
+ SCROW nEndRowPos = nDataStartRow + static_cast<SCROW>(nEnd); //TODO: check for overflow
+ outputimp.AddRow( nRowPos );
+ if ( !vbSetBorder[ nRow ] )
+ {
+ outputimp.OutputBlockFrame( nColPos, nRowPos, nTabEndCol, nEndRowPos );
+ vbSetBorder[ nRow ] = true;
+ }
+ outputimp.OutputBlockFrame( nColPos, nRowPos, nColPos, nEndRowPos );
+
+ if ( nField == pRowFields.size() - 2 )
+ outputimp.OutputBlockFrame( nColPos+1, nRowPos, nColPos+1, nEndRowPos );
+
+ lcl_SetStyleById( pDoc, nTab, nColPos,nRowPos, nDataStartCol-1,nEndRowPos, STR_PIVOT_STYLENAME_CATEGORY );
+ }
+ else
+ lcl_SetStyleById( pDoc, nTab, nColPos,nRowPos, nDataStartCol-1,nRowPos, STR_PIVOT_STYLENAME_CATEGORY );
+ }
+ else if ( pArray[nRow].Flags & sheet::MemberResultFlags::SUBTOTAL )
+ outputimp.AddRow( nRowPos );
+
+ // Apply the same number format as in data source.
+ pDoc->ApplyAttr(nColPos, nRowPos, nTab, SfxUInt32Item(ATTR_VALUE_FORMAT, pRowFields[nField].mnSrcNumFmt));
+ }
+ }
+
+ if (nColCount == 1 && nRowCount > 0 && pColFields.empty())
+ {
+ // the table contains exactly one data field and no column fields.
+ // Display data description at top right corner.
+ ScSetStringParam aParam;
+ aParam.setTextInput();
+ pDoc->SetString(nDataStartCol, nDataStartRow-1, nTab, aDataDescription, &aParam);
+ }
+
+ // output data results:
+
+ for (sal_Int32 nRow=0; nRow<nRowCount; nRow++)
+ {
+ SCROW nRowPos = nDataStartRow + static_cast<SCROW>(nRow); //TODO: check for overflow
+ const sheet::DataResult* pColAry = pRowAry[nRow].getConstArray();
+ sal_Int32 nThisColCount = pRowAry[nRow].getLength();
+ OSL_ENSURE( nThisColCount == nColCount, "count mismatch" ); //TODO: ???
+ for (sal_Int32 nCol=0; nCol<nThisColCount; nCol++)
+ {
+ SCCOL nColPos = nDataStartCol + static_cast<SCCOL>(nCol); //TODO: check for overflow
+ DataCell( nColPos, nRowPos, nTab, pColAry[nCol] );
+ }
+ }
+
+ outputimp.OutputDataArea();
+}
+
+ScRange ScDPOutput::GetOutputRange( sal_Int32 nRegionType )
+{
+ using namespace ::com::sun::star::sheet;
+
+ CalcSizes();
+
+ SCTAB nTab = aStartPos.Tab();
+ switch (nRegionType)
+ {
+ case DataPilotOutputRangeType::RESULT:
+ return ScRange(nDataStartCol, nDataStartRow, nTab, nTabEndCol, nTabEndRow, nTab);
+ case DataPilotOutputRangeType::TABLE:
+ return ScRange(aStartPos.Col(), nTabStartRow, nTab, nTabEndCol, nTabEndRow, nTab);
+ default:
+ OSL_ENSURE(nRegionType == DataPilotOutputRangeType::WHOLE, "ScDPOutput::GetOutputRange: unknown region type");
+ break;
+ }
+ return ScRange(aStartPos.Col(), aStartPos.Row(), nTab, nTabEndCol, nTabEndRow, nTab);
+}
+
+bool ScDPOutput::HasError()
+{
+ CalcSizes();
+
+ return bSizeOverflow || bResultsError;
+}
+
+sal_Int32 ScDPOutput::GetHeaderRows() const
+{
+ return pPageFields.size() + ( bDoFilter ? 1 : 0 );
+}
+
+namespace
+{
+ void insertNames(ScDPUniqueStringSet& rNames, const uno::Sequence<sheet::MemberResult>& rMemberResults)
+ {
+ for (const sheet::MemberResult& rMemberResult : rMemberResults)
+ {
+ if (rMemberResult.Flags & sheet::MemberResultFlags::HASMEMBER)
+ rNames.insert(rMemberResult.Name);
+ }
+ }
+}
+
+void ScDPOutput::GetMemberResultNames(ScDPUniqueStringSet& rNames, tools::Long nDimension)
+{
+ // Return the list of all member names in a dimension's MemberResults.
+ // Only the dimension has to be compared because this is only used with table data,
+ // where each dimension occurs only once.
+
+ auto lFindDimension = [nDimension](const ScDPOutLevelData& rField) { return rField.mnDim == nDimension; };
+
+ // look in column fields
+ auto colit = std::find_if(pColFields.begin(), pColFields.end(), lFindDimension);
+ if (colit != pColFields.end())
+ {
+ // collect the member names
+ insertNames(rNames, colit->maResult);
+ return;
+ }
+
+ // look in row fields
+ auto rowit = std::find_if(pRowFields.begin(), pRowFields.end(), lFindDimension);
+ if (rowit != pRowFields.end())
+ {
+ // collect the member names
+ insertNames(rNames, rowit->maResult);
+ }
+}
+
+void ScDPOutput::SetHeaderLayout(bool bUseGrid)
+{
+ mbHeaderLayout = bUseGrid;
+ bSizesValid = false;
+}
+
+namespace {
+
+void lcl_GetTableVars( sal_Int32& rGrandTotalCols, sal_Int32& rGrandTotalRows, sal_Int32& rDataLayoutIndex,
+ std::vector<OUString>& rDataNames, std::vector<OUString>& rGivenNames,
+ sheet::DataPilotFieldOrientation& rDataOrient,
+ const uno::Reference<sheet::XDimensionsSupplier>& xSource )
+{
+ rDataLayoutIndex = -1; // invalid
+ rGrandTotalCols = 0;
+ rGrandTotalRows = 0;
+ rDataOrient = sheet::DataPilotFieldOrientation_HIDDEN;
+
+ uno::Reference<beans::XPropertySet> xSrcProp( xSource, uno::UNO_QUERY );
+ bool bColGrand = ScUnoHelpFunctions::GetBoolProperty(
+ xSrcProp, SC_UNO_DP_COLGRAND);
+ if ( bColGrand )
+ rGrandTotalCols = 1; // default if data layout not in columns
+
+ bool bRowGrand = ScUnoHelpFunctions::GetBoolProperty(
+ xSrcProp, SC_UNO_DP_ROWGRAND);
+ if ( bRowGrand )
+ rGrandTotalRows = 1; // default if data layout not in rows
+
+ if ( !xSource.is() )
+ return;
+
+ // find index and orientation of "data layout" dimension, count data dimensions
+
+ sal_Int32 nDataCount = 0;
+
+ uno::Reference<container::XIndexAccess> xDims = new ScNameToIndexAccess( xSource->getDimensions() );
+ tools::Long nDimCount = xDims->getCount();
+ for (tools::Long nDim=0; nDim<nDimCount; nDim++)
+ {
+ uno::Reference<uno::XInterface> xDim(xDims->getByIndex(nDim), uno::UNO_QUERY);
+ uno::Reference<beans::XPropertySet> xDimProp( xDim, uno::UNO_QUERY );
+ if ( xDimProp.is() )
+ {
+ sheet::DataPilotFieldOrientation eDimOrient =
+ ScUnoHelpFunctions::GetEnumProperty(
+ xDimProp, SC_UNO_DP_ORIENTATION,
+ sheet::DataPilotFieldOrientation_HIDDEN );
+ if ( ScUnoHelpFunctions::GetBoolProperty( xDimProp,
+ SC_UNO_DP_ISDATALAYOUT ) )
+ {
+ rDataLayoutIndex = nDim;
+ rDataOrient = eDimOrient;
+ }
+ if ( eDimOrient == sheet::DataPilotFieldOrientation_DATA )
+ {
+ OUString aSourceName;
+ OUString aGivenName;
+ ScDPOutput::GetDataDimensionNames( aSourceName, aGivenName, xDim );
+ try
+ {
+ uno::Any aValue = xDimProp->getPropertyValue( SC_UNO_DP_LAYOUTNAME );
+
+ if( aValue.hasValue() )
+ {
+ OUString strLayoutName;
+
+ if( ( aValue >>= strLayoutName ) && !strLayoutName.isEmpty() )
+ aGivenName = strLayoutName;
+ }
+ }
+ catch(const uno::Exception&)
+ {
+ }
+ rDataNames.push_back( aSourceName );
+ rGivenNames.push_back( aGivenName );
+
+ ++nDataCount;
+ }
+ }
+ }
+
+ if ( ( rDataOrient == sheet::DataPilotFieldOrientation_COLUMN ) && bColGrand )
+ rGrandTotalCols = nDataCount;
+ else if ( ( rDataOrient == sheet::DataPilotFieldOrientation_ROW ) && bRowGrand )
+ rGrandTotalRows = nDataCount;
+}
+
+}
+
+void ScDPOutput::GetPositionData(const ScAddress& rPos, DataPilotTablePositionData& rPosData)
+{
+ using namespace ::com::sun::star::sheet;
+
+ SCCOL nCol = rPos.Col();
+ SCROW nRow = rPos.Row();
+ SCTAB nTab = rPos.Tab();
+ if ( nTab != aStartPos.Tab() )
+ return; // wrong sheet
+
+ // calculate output positions and sizes
+
+ CalcSizes();
+
+ rPosData.PositionType = GetPositionType(rPos);
+ switch (rPosData.PositionType)
+ {
+ case DataPilotTablePositionType::RESULT:
+ {
+ vector<DataPilotFieldFilter> aFilters;
+ GetDataResultPositionData(aFilters, rPos);
+
+ DataPilotTableResultData aResData;
+ aResData.FieldFilters = comphelper::containerToSequence(aFilters);
+ aResData.DataFieldIndex = 0;
+ Reference<beans::XPropertySet> xPropSet(xSource, UNO_QUERY);
+ if (xPropSet.is())
+ {
+ sal_Int32 nDataFieldCount = ScUnoHelpFunctions::GetLongProperty( xPropSet,
+ SC_UNO_DP_DATAFIELDCOUNT );
+ if (nDataFieldCount > 0)
+ aResData.DataFieldIndex = (nRow - nDataStartRow) % nDataFieldCount;
+ }
+
+ // Copy appropriate DataResult object from the cached sheet::DataResult table.
+ if (aData.getLength() > nRow - nDataStartRow &&
+ aData[nRow-nDataStartRow].getLength() > nCol-nDataStartCol)
+ aResData.Result = aData[nRow-nDataStartRow][nCol-nDataStartCol];
+
+ rPosData.PositionData <<= aResData;
+ return;
+ }
+ case DataPilotTablePositionType::COLUMN_HEADER:
+ {
+ tools::Long nField = nRow - nTabStartRow - 1; // 1st line is used for the buttons
+ if (nField < 0)
+ break;
+
+ if (pColFields.size() < o3tl::make_unsigned(nField) + 1 )
+ break;
+ const uno::Sequence<sheet::MemberResult> rSequence = pColFields[nField].maResult;
+ if (!rSequence.hasElements())
+ break;
+ const sheet::MemberResult* pArray = rSequence.getConstArray();
+
+ tools::Long nItem = nCol - nDataStartCol;
+ // get origin of "continue" fields
+ while (nItem > 0 && ( pArray[nItem].Flags & sheet::MemberResultFlags::CONTINUE) )
+ --nItem;
+
+ if (nItem < 0)
+ break;
+
+ DataPilotTableHeaderData aHeaderData;
+ aHeaderData.MemberName = pArray[nItem].Name;
+ aHeaderData.Flags = pArray[nItem].Flags;
+ aHeaderData.Dimension = static_cast<sal_Int32>(pColFields[nField].mnDim);
+ aHeaderData.Hierarchy = static_cast<sal_Int32>(pColFields[nField].mnHier);
+ aHeaderData.Level = static_cast<sal_Int32>(pColFields[nField].mnLevel);
+
+ rPosData.PositionData <<= aHeaderData;
+ return;
+ }
+ case DataPilotTablePositionType::ROW_HEADER:
+ {
+ tools::Long nField = nCol - nTabStartCol;
+ if (nField < 0)
+ break;
+
+ if (pRowFields.size() < o3tl::make_unsigned(nField) + 1 )
+ break;
+ const uno::Sequence<sheet::MemberResult> rSequence = pRowFields[nField].maResult;
+ if (!rSequence.hasElements())
+ break;
+ const sheet::MemberResult* pArray = rSequence.getConstArray();
+
+ tools::Long nItem = nRow - nDataStartRow;
+ // get origin of "continue" fields
+ while ( nItem > 0 && (pArray[nItem].Flags & sheet::MemberResultFlags::CONTINUE) )
+ --nItem;
+
+ if (nItem < 0)
+ break;
+
+ DataPilotTableHeaderData aHeaderData;
+ aHeaderData.MemberName = pArray[nItem].Name;
+ aHeaderData.Flags = pArray[nItem].Flags;
+ aHeaderData.Dimension = static_cast<sal_Int32>(pRowFields[nField].mnDim);
+ aHeaderData.Hierarchy = static_cast<sal_Int32>(pRowFields[nField].mnHier);
+ aHeaderData.Level = static_cast<sal_Int32>(pRowFields[nField].mnLevel);
+
+ rPosData.PositionData <<= aHeaderData;
+ return;
+ }
+ }
+}
+
+bool ScDPOutput::GetDataResultPositionData(vector<sheet::DataPilotFieldFilter>& rFilters, const ScAddress& rPos)
+{
+ // Check to make sure there is at least one data field.
+ Reference<beans::XPropertySet> xPropSet(xSource, UNO_QUERY);
+ if (!xPropSet.is())
+ return false;
+
+ sal_Int32 nDataFieldCount = ScUnoHelpFunctions::GetLongProperty( xPropSet,
+ SC_UNO_DP_DATAFIELDCOUNT );
+ if (nDataFieldCount == 0)
+ // No data field is present in this datapilot table.
+ return false;
+
+ // #i111421# use lcl_GetTableVars for correct size of totals and data layout position
+ sal_Int32 nGrandTotalCols;
+ sal_Int32 nGrandTotalRows;
+ sal_Int32 nDataLayoutIndex;
+ std::vector<OUString> aDataNames;
+ std::vector<OUString> aGivenNames;
+ sheet::DataPilotFieldOrientation eDataOrient;
+ lcl_GetTableVars( nGrandTotalCols, nGrandTotalRows, nDataLayoutIndex, aDataNames, aGivenNames, eDataOrient, xSource );
+
+ SCCOL nCol = rPos.Col();
+ SCROW nRow = rPos.Row();
+ SCTAB nTab = rPos.Tab();
+ if ( nTab != aStartPos.Tab() )
+ return false; // wrong sheet
+
+ CalcSizes();
+
+ // test for data area.
+ if (nCol < nDataStartCol || nCol > nTabEndCol || nRow < nDataStartRow || nRow > nTabEndRow)
+ {
+ // Cell is outside the data field area.
+ return false;
+ }
+
+ bool bFilterByCol = (nCol <= static_cast<SCCOL>(nTabEndCol - nGrandTotalCols));
+ bool bFilterByRow = (nRow <= static_cast<SCROW>(nTabEndRow - nGrandTotalRows));
+
+ // column fields
+ for (size_t nColField = 0; nColField < pColFields.size() && bFilterByCol; ++nColField)
+ {
+ if (pColFields[nColField].mnDim == nDataLayoutIndex)
+ // There is no sense including the data layout field for filtering.
+ continue;
+
+ sheet::DataPilotFieldFilter filter;
+ filter.FieldName = pColFields[nColField].maName;
+
+ const uno::Sequence<sheet::MemberResult> rSequence = pColFields[nColField].maResult;
+ const sheet::MemberResult* pArray = rSequence.getConstArray();
+
+ OSL_ENSURE(nDataStartCol + rSequence.getLength() - 1 == nTabEndCol, "ScDPOutput::GetDataFieldCellData: error in geometric assumption");
+
+ tools::Long nItem = nCol - nDataStartCol;
+ // get origin of "continue" fields
+ while ( nItem > 0 && (pArray[nItem].Flags & sheet::MemberResultFlags::CONTINUE) )
+ --nItem;
+
+ filter.MatchValueName = pArray[nItem].Name;
+ rFilters.push_back(filter);
+ }
+
+ // row fields
+ for (size_t nRowField = 0; nRowField < pRowFields.size() && bFilterByRow; ++nRowField)
+ {
+ if (pRowFields[nRowField].mnDim == nDataLayoutIndex)
+ // There is no sense including the data layout field for filtering.
+ continue;
+
+ sheet::DataPilotFieldFilter filter;
+ filter.FieldName = pRowFields[nRowField].maName;
+
+ const uno::Sequence<sheet::MemberResult> rSequence = pRowFields[nRowField].maResult;
+ const sheet::MemberResult* pArray = rSequence.getConstArray();
+
+ OSL_ENSURE(nDataStartRow + rSequence.getLength() - 1 == nTabEndRow, "ScDPOutput::GetDataFieldCellData: error in geometric assumption");
+
+ tools::Long nItem = nRow - nDataStartRow;
+ // get origin of "continue" fields
+ while ( nItem > 0 && (pArray[nItem].Flags & sheet::MemberResultFlags::CONTINUE) )
+ --nItem;
+
+ filter.MatchValueName = pArray[nItem].Name;
+ rFilters.push_back(filter);
+ }
+
+ return true;
+}
+
+namespace {
+
+OUString lcl_GetDataFieldName( std::u16string_view rSourceName, sal_Int16 eFunc )
+{
+ TranslateId pStrId;
+ switch ( eFunc )
+ {
+ case sheet::GeneralFunction2::SUM: pStrId = STR_FUN_TEXT_SUM; break;
+ case sheet::GeneralFunction2::COUNT:
+ case sheet::GeneralFunction2::COUNTNUMS: pStrId = STR_FUN_TEXT_COUNT; break;
+ case sheet::GeneralFunction2::AVERAGE: pStrId = STR_FUN_TEXT_AVG; break;
+ case sheet::GeneralFunction2::MEDIAN: pStrId = STR_FUN_TEXT_MEDIAN; break;
+ case sheet::GeneralFunction2::MAX: pStrId = STR_FUN_TEXT_MAX; break;
+ case sheet::GeneralFunction2::MIN: pStrId = STR_FUN_TEXT_MIN; break;
+ case sheet::GeneralFunction2::PRODUCT: pStrId = STR_FUN_TEXT_PRODUCT; break;
+ case sheet::GeneralFunction2::STDEV:
+ case sheet::GeneralFunction2::STDEVP: pStrId = STR_FUN_TEXT_STDDEV; break;
+ case sheet::GeneralFunction2::VAR:
+ case sheet::GeneralFunction2::VARP: pStrId = STR_FUN_TEXT_VAR; break;
+ case sheet::GeneralFunction2::NONE:
+ case sheet::GeneralFunction2::AUTO: break;
+ default:
+ {
+ assert(false);
+ }
+ }
+ if (!pStrId)
+ return OUString();
+
+ return ScResId(pStrId) + " - " + rSourceName;
+}
+
+}
+
+void ScDPOutput::GetDataDimensionNames(
+ OUString& rSourceName, OUString& rGivenName, const uno::Reference<uno::XInterface>& xDim )
+{
+ uno::Reference<beans::XPropertySet> xDimProp( xDim, uno::UNO_QUERY );
+ uno::Reference<container::XNamed> xDimName( xDim, uno::UNO_QUERY );
+ if ( !(xDimProp.is() && xDimName.is()) )
+ return;
+
+ // Asterisks are added in ScDPSaveData::WriteToSource to create unique names.
+ //TODO: preserve original name there?
+ rSourceName = ScDPUtil::getSourceDimensionName(xDimName->getName());
+
+ // Generate "given name" the same way as in dptabres.
+ //TODO: Should use a stored name when available
+
+ sal_Int16 eFunc = ScUnoHelpFunctions::GetShortProperty(
+ xDimProp, SC_UNO_DP_FUNCTION2,
+ sheet::GeneralFunction2::NONE );
+ rGivenName = lcl_GetDataFieldName( rSourceName, eFunc );
+}
+
+bool ScDPOutput::IsFilterButton( const ScAddress& rPos )
+{
+ SCCOL nCol = rPos.Col();
+ SCROW nRow = rPos.Row();
+ SCTAB nTab = rPos.Tab();
+ if ( nTab != aStartPos.Tab() || !bDoFilter )
+ return false; // wrong sheet or no button at all
+
+ // filter button is at top left
+ return ( nCol == aStartPos.Col() && nRow == aStartPos.Row() );
+}
+
+tools::Long ScDPOutput::GetHeaderDim( const ScAddress& rPos, sheet::DataPilotFieldOrientation& rOrient )
+{
+ SCCOL nCol = rPos.Col();
+ SCROW nRow = rPos.Row();
+ SCTAB nTab = rPos.Tab();
+ if ( nTab != aStartPos.Tab() )
+ return -1; // wrong sheet
+
+ // calculate output positions and sizes
+
+ CalcSizes();
+
+ // test for column header
+
+ if ( nRow == nTabStartRow && nCol >= nDataStartCol && o3tl::make_unsigned(nCol) < nDataStartCol + pColFields.size())
+ {
+ rOrient = sheet::DataPilotFieldOrientation_COLUMN;
+ tools::Long nField = nCol - nDataStartCol;
+ return pColFields[nField].mnDim;
+ }
+
+ // test for row header
+
+ if ( nRow+1 == nDataStartRow && nCol >= nTabStartCol && o3tl::make_unsigned(nCol) < nTabStartCol + pRowFields.size() )
+ {
+ rOrient = sheet::DataPilotFieldOrientation_ROW;
+ tools::Long nField = nCol - nTabStartCol;
+ return pRowFields[nField].mnDim;
+ }
+
+ // test for page field
+
+ SCROW nPageStartRow = aStartPos.Row() + ( bDoFilter ? 1 : 0 );
+ if ( nCol == aStartPos.Col() && nRow >= nPageStartRow && o3tl::make_unsigned(nRow) < nPageStartRow + pPageFields.size() )
+ {
+ rOrient = sheet::DataPilotFieldOrientation_PAGE;
+ tools::Long nField = nRow - nPageStartRow;
+ return pPageFields[nField].mnDim;
+ }
+
+ //TODO: single data field (?)
+
+ rOrient = sheet::DataPilotFieldOrientation_HIDDEN;
+ return -1; // invalid
+}
+
+bool ScDPOutput::GetHeaderDrag( const ScAddress& rPos, bool bMouseLeft, bool bMouseTop,
+ tools::Long nDragDim,
+ tools::Rectangle& rPosRect, sheet::DataPilotFieldOrientation& rOrient, tools::Long& rDimPos )
+{
+ // Rectangle instead of ScRange for rPosRect to allow for negative values
+
+ SCCOL nCol = rPos.Col();
+ SCROW nRow = rPos.Row();
+ SCTAB nTab = rPos.Tab();
+ if ( nTab != aStartPos.Tab() )
+ return false; // wrong sheet
+
+ // calculate output positions and sizes
+
+ CalcSizes();
+
+ // test for column header
+
+ if ( nCol >= nDataStartCol && nCol <= nTabEndCol &&
+ nRow + 1 >= nMemberStartRow && o3tl::make_unsigned(nRow) < nMemberStartRow + pColFields.size())
+ {
+ tools::Long nField = nRow - nMemberStartRow;
+ if (nField < 0)
+ {
+ nField = 0;
+ bMouseTop = true;
+ }
+ //TODO: find start of dimension
+
+ rPosRect = tools::Rectangle( nDataStartCol, nMemberStartRow + nField,
+ nTabEndCol, nMemberStartRow + nField -1 );
+
+ bool bFound = false; // is this within the same orientation?
+ bool bBeforeDrag = false;
+ bool bAfterDrag = false;
+ for (tools::Long nPos=0; o3tl::make_unsigned(nPos)<pColFields.size() && !bFound; nPos++)
+ {
+ if (pColFields[nPos].mnDim == nDragDim)
+ {
+ bFound = true;
+ if ( nField < nPos )
+ bBeforeDrag = true;
+ else if ( nField > nPos )
+ bAfterDrag = true;
+ }
+ }
+
+ if ( bFound )
+ {
+ if (!bBeforeDrag)
+ {
+ rPosRect.AdjustBottom( 1 );
+ if (bAfterDrag)
+ rPosRect.AdjustTop( 1 );
+ }
+ }
+ else
+ {
+ if ( !bMouseTop )
+ {
+ rPosRect.AdjustTop( 1 );
+ rPosRect.AdjustBottom( 1 );
+ ++nField;
+ }
+ }
+
+ rOrient = sheet::DataPilotFieldOrientation_COLUMN;
+ rDimPos = nField; //!...
+ return true;
+ }
+
+ // test for row header
+
+ // special case if no row fields
+ bool bSpecial = ( nRow+1 >= nDataStartRow && nRow <= nTabEndRow &&
+ pRowFields.empty() && nCol == nTabStartCol && bMouseLeft );
+
+ if ( bSpecial || ( nRow+1 >= nDataStartRow && nRow <= nTabEndRow &&
+ nCol + 1 >= nTabStartCol && o3tl::make_unsigned(nCol) < nTabStartCol + pRowFields.size() ) )
+ {
+ tools::Long nField = nCol - nTabStartCol;
+ //TODO: find start of dimension
+
+ rPosRect = tools::Rectangle( nTabStartCol + nField, nDataStartRow - 1,
+ nTabStartCol + nField - 1, nTabEndRow );
+
+ bool bFound = false; // is this within the same orientation?
+ bool bBeforeDrag = false;
+ bool bAfterDrag = false;
+ for (tools::Long nPos=0; o3tl::make_unsigned(nPos)<pRowFields.size() && !bFound; nPos++)
+ {
+ if (pRowFields[nPos].mnDim == nDragDim)
+ {
+ bFound = true;
+ if ( nField < nPos )
+ bBeforeDrag = true;
+ else if ( nField > nPos )
+ bAfterDrag = true;
+ }
+ }
+
+ if ( bFound )
+ {
+ if (!bBeforeDrag)
+ {
+ rPosRect.AdjustRight( 1 );
+ if (bAfterDrag)
+ rPosRect.AdjustLeft( 1 );
+ }
+ }
+ else
+ {
+ if ( !bMouseLeft )
+ {
+ rPosRect.AdjustLeft( 1 );
+ rPosRect.AdjustRight( 1 );
+ ++nField;
+ }
+ }
+
+ rOrient = sheet::DataPilotFieldOrientation_ROW;
+ rDimPos = nField; //!...
+ return true;
+ }
+
+ // test for page fields
+
+ SCROW nPageStartRow = aStartPos.Row() + ( bDoFilter ? 1 : 0 );
+ if ( nCol >= aStartPos.Col() && nCol <= nTabEndCol &&
+ nRow + 1 >= nPageStartRow && o3tl::make_unsigned(nRow) < nPageStartRow + pPageFields.size() )
+ {
+ tools::Long nField = nRow - nPageStartRow;
+ if (nField < 0)
+ {
+ nField = 0;
+ bMouseTop = true;
+ }
+ //TODO: find start of dimension
+
+ rPosRect = tools::Rectangle( aStartPos.Col(), nPageStartRow + nField,
+ nTabEndCol, nPageStartRow + nField - 1 );
+
+ bool bFound = false; // is this within the same orientation?
+ bool bBeforeDrag = false;
+ bool bAfterDrag = false;
+ for (tools::Long nPos=0; o3tl::make_unsigned(nPos)<pPageFields.size() && !bFound; nPos++)
+ {
+ if (pPageFields[nPos].mnDim == nDragDim)
+ {
+ bFound = true;
+ if ( nField < nPos )
+ bBeforeDrag = true;
+ else if ( nField > nPos )
+ bAfterDrag = true;
+ }
+ }
+
+ if ( bFound )
+ {
+ if (!bBeforeDrag)
+ {
+ rPosRect.AdjustBottom( 1 );
+ if (bAfterDrag)
+ rPosRect.AdjustTop( 1 );
+ }
+ }
+ else
+ {
+ if ( !bMouseTop )
+ {
+ rPosRect.AdjustTop( 1 );
+ rPosRect.AdjustBottom( 1 );
+ ++nField;
+ }
+ }
+
+ rOrient = sheet::DataPilotFieldOrientation_PAGE;
+ rDimPos = nField; //!...
+ return true;
+ }
+
+ return false;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sc/source/core/data/dpoutputgeometry.cxx b/sc/source/core/data/dpoutputgeometry.cxx
new file mode 100644
index 000000000..0c5307258
--- /dev/null
+++ b/sc/source/core/data/dpoutputgeometry.cxx
@@ -0,0 +1,255 @@
+/* -*- 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 <dpoutputgeometry.hxx>
+#include <address.hxx>
+
+#include <vector>
+
+using ::std::vector;
+
+ScDPOutputGeometry::ScDPOutputGeometry(const ScRange& rOutRange, bool bShowFilter) :
+ maOutRange(rOutRange),
+ mnRowFields(0),
+ mnColumnFields(0),
+ mnPageFields(0),
+ mnDataFields(0),
+ meDataLayoutType(None),
+ mbShowFilter(bShowFilter),
+ mbHeaderLayout (false),
+ mbCompactMode (false)
+{
+}
+
+void ScDPOutputGeometry::setRowFieldCount(sal_uInt32 nCount)
+{
+ mnRowFields = nCount;
+}
+
+void ScDPOutputGeometry::setColumnFieldCount(sal_uInt32 nCount)
+{
+ mnColumnFields = nCount;
+}
+
+void ScDPOutputGeometry::setPageFieldCount(sal_uInt32 nCount)
+{
+ mnPageFields = nCount;
+}
+
+void ScDPOutputGeometry::setDataFieldCount(sal_uInt32 nCount)
+{
+ mnDataFields = nCount;
+}
+
+void ScDPOutputGeometry::setDataLayoutType(FieldType eType)
+{
+ meDataLayoutType = eType;
+}
+
+void ScDPOutputGeometry::setHeaderLayout(bool bHeaderLayout)
+{
+ mbHeaderLayout = bHeaderLayout;
+}
+
+void ScDPOutputGeometry::setCompactMode(bool bCompactMode)
+{
+ mbCompactMode = bCompactMode;
+}
+
+void ScDPOutputGeometry::getColumnFieldPositions(vector<ScAddress>& rAddrs) const
+{
+ sal_uInt32 nColumnFields, nRowFields;
+ adjustFieldsForDataLayout(nColumnFields, nRowFields);
+
+ vector<ScAddress> aAddrs;
+ if (!nColumnFields)
+ {
+ rAddrs.swap(aAddrs);
+ return;
+ }
+
+ SCROW nCurRow = maOutRange.aStart.Row();
+
+ if (mnPageFields)
+ {
+ SCROW nRowStart = maOutRange.aStart.Row() + int(mbShowFilter);
+ SCROW nRowEnd = nRowStart + static_cast<SCCOL>(mnPageFields-1);
+ nCurRow = nRowEnd + 2;
+ }
+ else if (mbShowFilter)
+ nCurRow += 2;
+
+ SCROW nRow = nCurRow;
+ SCTAB nTab = maOutRange.aStart.Tab();
+ SCCOL nColStart = static_cast<SCCOL>(maOutRange.aStart.Col() + nRowFields);
+ if(mbCompactMode)
+ nColStart = static_cast<SCCOL>(maOutRange.aStart.Col() + 1); // We have only one row in compact mode
+ SCCOL nColEnd = nColStart + static_cast<SCCOL>(nColumnFields-1);
+
+ for (SCCOL nCol = nColStart; nCol <= nColEnd; ++nCol)
+ aAddrs.emplace_back(nCol, nRow, nTab);
+ rAddrs.swap(aAddrs);
+}
+
+void ScDPOutputGeometry::getRowFieldPositions(vector<ScAddress>& rAddrs) const
+{
+ sal_uInt32 nColumnFields, nRowFields;
+ adjustFieldsForDataLayout(nColumnFields, nRowFields);
+
+ vector<ScAddress> aAddrs;
+ if (!nRowFields)
+ {
+ rAddrs.swap(aAddrs);
+ return;
+ }
+
+ SCROW nRow = getRowFieldHeaderRow();
+ SCTAB nTab = maOutRange.aStart.Tab();
+ SCCOL nColStart = maOutRange.aStart.Col();
+ SCCOL nColEnd = nColStart + static_cast<SCCOL>(nRowFields-1);
+
+ if(mbCompactMode)
+ nColEnd = nColStart; // We have only one row in compact mode
+
+ for (SCCOL nCol = nColStart; nCol <= nColEnd; ++nCol)
+ aAddrs.emplace_back(nCol, nRow, nTab);
+ rAddrs.swap(aAddrs);
+}
+
+void ScDPOutputGeometry::getPageFieldPositions(vector<ScAddress>& rAddrs) const
+{
+ vector<ScAddress> aAddrs;
+ if (!mnPageFields)
+ {
+ rAddrs.swap(aAddrs);
+ return;
+ }
+
+ SCTAB nTab = maOutRange.aStart.Tab();
+ SCCOL nCol = maOutRange.aStart.Col();
+
+ SCROW nRowStart = maOutRange.aStart.Row() + int(mbShowFilter);
+ SCROW nRowEnd = nRowStart + static_cast<SCCOL>(mnPageFields-1);
+
+ for (SCROW nRow = nRowStart; nRow <= nRowEnd; ++nRow)
+ aAddrs.emplace_back(nCol, nRow, nTab);
+ rAddrs.swap(aAddrs);
+}
+
+SCROW ScDPOutputGeometry::getRowFieldHeaderRow() const
+{
+ SCROW nCurRow = maOutRange.aStart.Row();
+ sal_uInt32 nColumnFields, nRowFields;
+ adjustFieldsForDataLayout(nColumnFields, nRowFields);
+
+ if (mnPageFields)
+ {
+ SCROW nRowStart = maOutRange.aStart.Row() + int(mbShowFilter);
+ SCROW nRowEnd = nRowStart + static_cast<SCCOL>(mnPageFields-1);
+ nCurRow = nRowEnd + 2;
+ }
+ else if (mbShowFilter)
+ nCurRow += 2;
+
+ if (nColumnFields)
+ nCurRow += static_cast<SCROW>(nColumnFields);
+ else if (nRowFields && mbHeaderLayout)
+ ++nCurRow;
+
+ return nCurRow;
+}
+
+void ScDPOutputGeometry::adjustFieldsForDataLayout(sal_uInt32& rColumnFields, sal_uInt32& rRowFields) const
+{
+ rRowFields = mnRowFields;
+ rColumnFields = mnColumnFields;
+
+ if (mnDataFields >= 2)
+ return;
+
+ // Data layout field can be either row or column field, never page field.
+ switch (meDataLayoutType)
+ {
+ case Column:
+ if (rColumnFields > 0)
+ rColumnFields -= 1;
+ break;
+ case Row:
+ if (rRowFields > 0)
+ rRowFields -= 1;
+ break;
+ default:
+ ;
+ }
+}
+
+std::pair<ScDPOutputGeometry::FieldType, size_t>
+ScDPOutputGeometry::getFieldButtonType(const ScAddress& rPos) const
+{
+ SCROW nCurRow = maOutRange.aStart.Row();
+ sal_uInt32 nColumnFields, nRowFields;
+ adjustFieldsForDataLayout(nColumnFields, nRowFields);
+
+ if (mnPageFields)
+ {
+ SCCOL nCol = maOutRange.aStart.Col();
+ SCROW nRowStart = maOutRange.aStart.Row() + int(mbShowFilter);
+ SCROW nRowEnd = nRowStart + static_cast<SCCOL>(mnPageFields-1);
+ if (rPos.Col() == nCol && nRowStart <= rPos.Row() && rPos.Row() <= nRowEnd)
+ {
+ size_t nPos = static_cast<size_t>(rPos.Row() - nRowStart);
+ return std::pair<FieldType, size_t>(Page, nPos);
+ }
+
+ nCurRow = nRowEnd + 2;
+ }
+ else if (mbShowFilter)
+ nCurRow += 2;
+
+ if (nColumnFields)
+ {
+ SCROW nRow = nCurRow;
+ SCCOL nColStart = static_cast<SCCOL>(maOutRange.aStart.Col() + nRowFields);
+ SCCOL nColEnd = nColStart + static_cast<SCCOL>(nColumnFields-1);
+ if (rPos.Row() == nRow && nColStart <= rPos.Col() && rPos.Col() <= nColEnd)
+ {
+ size_t nPos = static_cast<size_t>(rPos.Col() - nColStart);
+ return std::pair<FieldType, size_t>(Column, nPos);
+ }
+
+ nCurRow += static_cast<SCROW>(nColumnFields);
+ }
+ else if (mbHeaderLayout)
+ ++nCurRow;
+
+ if (nRowFields)
+ {
+ SCCOL nColStart = maOutRange.aStart.Col();
+ SCCOL nColEnd = nColStart + static_cast<SCCOL>(nRowFields-1);
+ if (rPos.Row() == nCurRow && nColStart <= rPos.Col() && rPos.Col() <= nColEnd)
+ {
+ size_t nPos = static_cast<size_t>(rPos.Col() - nColStart);
+ return std::pair<FieldType, size_t>(Row, nPos);
+ }
+ }
+
+ return std::pair<FieldType, size_t>(None, 0);
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sc/source/core/data/dpresfilter.cxx b/sc/source/core/data/dpresfilter.cxx
new file mode 100644
index 000000000..4cef11b76
--- /dev/null
+++ b/sc/source/core/data/dpresfilter.cxx
@@ -0,0 +1,267 @@
+/* -*- 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/.
+ */
+
+#include <dpresfilter.hxx>
+#include <global.hxx>
+
+#include <unotools/charclass.hxx>
+#include <sal/log.hxx>
+#include <o3tl/hash_combine.hxx>
+
+#include <com/sun/star/sheet/DataPilotFieldFilter.hpp>
+#include <com/sun/star/uno/Sequence.hxx>
+
+#include <limits>
+
+using namespace com::sun::star;
+
+ScDPResultFilter::ScDPResultFilter(const OUString& rDimName, bool bDataLayout) :
+ maDimName(rDimName), mbHasValue(false), mbDataLayout(bDataLayout) {}
+
+ScDPResultFilterContext::ScDPResultFilterContext() :
+ mnCol(0), mnRow(0) {}
+
+size_t ScDPResultTree::NamePairHash::operator() (const NamePairType& rPair) const
+{
+ std::size_t seed = 0;
+ o3tl::hash_combine(seed, rPair.first.hashCode());
+ o3tl::hash_combine(seed, rPair.second.hashCode());
+ return seed;
+}
+
+#if DEBUG_PIVOT_TABLE
+void ScDPResultTree::DimensionNode::dump(int nLevel) const
+{
+ string aIndent(nLevel*2, ' ');
+ MembersType::const_iterator it = maChildMembersValueNames.begin(), itEnd = maChildMembersValueNames.end();
+ for (; it != itEnd; ++it)
+ {
+ cout << aIndent << "member: ";
+ const ScDPItemData& rVal = it->first;
+ if (rVal.IsValue())
+ cout << rVal.GetValue();
+ else
+ cout << rVal.GetString();
+ cout << endl;
+
+ it->second->dump(nLevel+1);
+ }
+}
+#endif
+
+ScDPResultTree::MemberNode::MemberNode() {}
+
+ScDPResultTree::MemberNode::~MemberNode() {}
+
+#if DEBUG_PIVOT_TABLE
+void ScDPResultTree::MemberNode::dump(int nLevel) const
+{
+ string aIndent(nLevel*2, ' ');
+ for (const auto& rValue : maValues)
+ cout << aIndent << "value: " << rValue << endl;
+
+ for (const auto& [rName, rxDim] : maChildDimensions)
+ {
+ cout << aIndent << "dimension: " << rName << endl;
+ rxDim->dump(nLevel+1);
+ }
+}
+#endif
+
+ScDPResultTree::ScDPResultTree() : mpRoot(new MemberNode) {}
+ScDPResultTree::~ScDPResultTree()
+{
+}
+
+void ScDPResultTree::add(
+ const std::vector<ScDPResultFilter>& rFilters, double fVal)
+{
+ // TODO: I'll work on the col / row to value node mapping later.
+
+ const OUString* pDimName = nullptr;
+ const OUString* pMemName = nullptr;
+ MemberNode* pMemNode = mpRoot.get();
+
+ for (const ScDPResultFilter& filter : rFilters)
+ {
+ if (filter.mbDataLayout)
+ continue;
+
+ if (maPrimaryDimName.isEmpty())
+ maPrimaryDimName = filter.maDimName;
+
+ // See if this dimension exists.
+ auto& rDims = pMemNode->maChildDimensions;
+ OUString aUpperName = ScGlobal::getCharClass().uppercase(filter.maDimName);
+ auto itDim = rDims.find(aUpperName);
+ if (itDim == rDims.end())
+ {
+ // New dimension. Insert it.
+ auto r = rDims.emplace(aUpperName, DimensionNode());
+ assert(r.second);
+ itDim = r.first;
+ }
+
+ pDimName = &itDim->first;
+
+ // Now, see if this dimension member exists.
+ DimensionNode& rDim = itDim->second;
+ MembersType& rMembersValueNames = rDim.maChildMembersValueNames;
+ aUpperName = ScGlobal::getCharClass().uppercase(filter.maValueName);
+ MembersType::iterator itMem = rMembersValueNames.find(aUpperName);
+ if (itMem == rMembersValueNames.end())
+ {
+ // New member. Insert it.
+ auto pNode = std::make_shared<MemberNode>();
+ std::pair<MembersType::iterator, bool> r =
+ rMembersValueNames.emplace(aUpperName, pNode);
+
+ if (!r.second)
+ // Insertion failed!
+ return;
+
+ itMem = r.first;
+
+ // If the locale independent value string isn't any different it
+ // makes no sense to add it to the separate mapping.
+ if (!filter.maValue.isEmpty() && filter.maValue != filter.maValueName)
+ {
+ MembersType& rMembersValues = rDim.maChildMembersValues;
+ aUpperName = ScGlobal::getCharClass().uppercase(filter.maValue);
+ MembersType::iterator itMemVal = rMembersValues.find(aUpperName);
+ if (itMemVal == rMembersValues.end())
+ {
+ // New member. Insert it.
+ std::pair<MembersType::iterator, bool> it =
+ rMembersValues.emplace(aUpperName, pNode);
+ // If insertion failed do not bail out anymore.
+ SAL_WARN_IF( !it.second, "sc.core", "ScDPResultTree::add - rMembersValues.insert failed");
+ }
+ }
+ }
+
+ pMemName = &itMem->first;
+ pMemNode = itMem->second.get();
+ }
+
+ if (pDimName && pMemName)
+ {
+ NamePairType aNames(
+ ScGlobal::getCharClass().uppercase(*pDimName),
+ ScGlobal::getCharClass().uppercase(*pMemName));
+
+ LeafValuesType::iterator it = maLeafValues.find(aNames);
+ if (it == maLeafValues.end())
+ {
+ // This name pair doesn't exist. Associate a new value for it.
+ maLeafValues.emplace(aNames, fVal);
+ }
+ else
+ {
+ // This name pair already exists. Set the value to NaN.
+ it->second = std::numeric_limits<double>::quiet_NaN();
+ }
+ }
+
+ pMemNode->maValues.push_back(fVal);
+}
+
+void ScDPResultTree::swap(ScDPResultTree& rOther)
+{
+ std::swap(maPrimaryDimName, rOther.maPrimaryDimName);
+ std::swap(mpRoot, rOther.mpRoot);
+ maLeafValues.swap(rOther.maLeafValues);
+}
+
+bool ScDPResultTree::empty() const
+{
+ return mpRoot->maChildDimensions.empty();
+}
+
+void ScDPResultTree::clear()
+{
+ maPrimaryDimName.clear();
+ mpRoot.reset( new MemberNode );
+}
+
+const ScDPResultTree::ValuesType* ScDPResultTree::getResults(
+ const uno::Sequence<sheet::DataPilotFieldFilter>& rFilters) const
+{
+ const MemberNode* pMember = mpRoot.get();
+ for (const sheet::DataPilotFieldFilter& rFilter : rFilters)
+ {
+ auto itDim = pMember->maChildDimensions.find(
+ ScGlobal::getCharClass().uppercase(rFilter.FieldName));
+
+ if (itDim == pMember->maChildDimensions.end())
+ // Specified dimension not found.
+ return nullptr;
+
+ const DimensionNode& rDim = itDim->second;
+ MembersType::const_iterator itMem( rDim.maChildMembersValueNames.find(
+ ScGlobal::getCharClass().uppercase( rFilter.MatchValueName)));
+
+ if (itMem == rDim.maChildMembersValueNames.end())
+ {
+ // Specified member name not found, try locale independent value.
+ itMem = rDim.maChildMembersValues.find( ScGlobal::getCharClass().uppercase( rFilter.MatchValue));
+
+ if (itMem == rDim.maChildMembersValues.end())
+ // Specified member not found.
+ return nullptr;
+ }
+
+ pMember = itMem->second.get();
+ }
+
+ if (pMember->maValues.empty())
+ {
+ // Descend into dimension member children while there is no result and
+ // exactly one dimension field with exactly one member item, for which
+ // no further constraint (filter) has to match.
+ const MemberNode* pFieldMember = pMember;
+ while (pFieldMember->maChildDimensions.size() == 1)
+ {
+ auto itDim( pFieldMember->maChildDimensions.begin());
+ const DimensionNode& rDim = itDim->second;
+ if (rDim.maChildMembersValueNames.size() != 1)
+ break; // while
+ pFieldMember = rDim.maChildMembersValueNames.begin()->second.get();
+ if (!pFieldMember->maValues.empty())
+ return &pFieldMember->maValues;
+ }
+ }
+
+ return &pMember->maValues;
+}
+
+double ScDPResultTree::getLeafResult(const css::sheet::DataPilotFieldFilter& rFilter) const
+{
+ NamePairType aPair(
+ ScGlobal::getCharClass().uppercase(rFilter.FieldName),
+ ScGlobal::getCharClass().uppercase(rFilter.MatchValueName));
+
+ LeafValuesType::const_iterator it = maLeafValues.find(aPair);
+ if (it != maLeafValues.end())
+ // Found!
+ return it->second;
+
+ // Not found. Return an NaN.
+ return std::numeric_limits<double>::quiet_NaN();
+}
+
+#if DEBUG_PIVOT_TABLE
+void ScDPResultTree::dump() const
+{
+ cout << "primary dimension name: " << maPrimaryDimName << endl;
+ mpRoot->dump(0);
+}
+#endif
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sc/source/core/data/dpsave.cxx b/sc/source/core/data/dpsave.cxx
new file mode 100644
index 000000000..16de535bd
--- /dev/null
+++ b/sc/source/core/data/dpsave.cxx
@@ -0,0 +1,1383 @@
+/* -*- 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 <memory>
+#include <dpsave.hxx>
+#include <dpdimsave.hxx>
+#include <miscuno.hxx>
+#include <unonames.hxx>
+#include <dputil.hxx>
+#include <generalfunction.hxx>
+#include <dptabdat.hxx>
+
+#include <sal/types.h>
+#include <sal/log.hxx>
+#include <osl/diagnose.h>
+#include <comphelper/stl_types.hxx>
+#include <unotools/charclass.hxx>
+
+#include <com/sun/star/sheet/XDimensionsSupplier.hpp>
+#include <com/sun/star/sheet/DataPilotFieldAutoShowInfo.hpp>
+#include <com/sun/star/sheet/DataPilotFieldLayoutInfo.hpp>
+#include <com/sun/star/sheet/DataPilotFieldReference.hpp>
+#include <com/sun/star/sheet/DataPilotFieldSortInfo.hpp>
+#include <com/sun/star/sheet/DataPilotFieldSortMode.hpp>
+#include <com/sun/star/sheet/XHierarchiesSupplier.hpp>
+#include <com/sun/star/sheet/XLevelsSupplier.hpp>
+#include <com/sun/star/sheet/XMembersSupplier.hpp>
+#include <com/sun/star/container/XNamed.hpp>
+#include <com/sun/star/util/XCloneable.hpp>
+#include <tools/diagnose_ex.h>
+
+#include <unordered_map>
+#include <algorithm>
+
+using namespace com::sun::star;
+using namespace com::sun::star::sheet;
+using ::std::unique_ptr;
+
+#define SC_DPSAVEMODE_DONTKNOW 2
+
+static void lcl_SetBoolProperty( const uno::Reference<beans::XPropertySet>& xProp,
+ const OUString& rName, bool bValue )
+{
+ //TODO: move to ScUnoHelpFunctions?
+
+ xProp->setPropertyValue( rName, uno::Any( bValue ) );
+}
+
+ScDPSaveMember::ScDPSaveMember(const OUString& rName) :
+ aName( rName ),
+ nVisibleMode( SC_DPSAVEMODE_DONTKNOW ),
+ nShowDetailsMode( SC_DPSAVEMODE_DONTKNOW )
+{
+}
+
+ScDPSaveMember::ScDPSaveMember(const ScDPSaveMember& r) :
+ aName( r.aName ),
+ mpLayoutName( r.mpLayoutName ),
+ nVisibleMode( r.nVisibleMode ),
+ nShowDetailsMode( r.nShowDetailsMode )
+{
+}
+
+ScDPSaveMember::~ScDPSaveMember()
+{
+}
+
+bool ScDPSaveMember::operator== ( const ScDPSaveMember& r ) const
+{
+ return aName == r.aName &&
+ nVisibleMode == r.nVisibleMode &&
+ nShowDetailsMode == r.nShowDetailsMode;
+}
+
+bool ScDPSaveMember::HasIsVisible() const
+{
+ return nVisibleMode != SC_DPSAVEMODE_DONTKNOW;
+}
+
+void ScDPSaveMember::SetIsVisible(bool bSet)
+{
+ nVisibleMode = sal_uInt16(bSet);
+}
+
+bool ScDPSaveMember::HasShowDetails() const
+{
+ return nShowDetailsMode != SC_DPSAVEMODE_DONTKNOW;
+}
+
+void ScDPSaveMember::SetShowDetails(bool bSet)
+{
+ nShowDetailsMode = sal_uInt16(bSet);
+}
+
+void ScDPSaveMember::SetName( const OUString& rNew )
+{
+ // Used only if the source member was renamed (groups).
+ // For UI renaming of members, a layout name must be used.
+
+ aName = rNew;
+}
+
+void ScDPSaveMember::SetLayoutName( const OUString& rName )
+{
+ mpLayoutName = rName;
+}
+
+const std::optional<OUString> & ScDPSaveMember::GetLayoutName() const
+{
+ return mpLayoutName;
+}
+
+void ScDPSaveMember::RemoveLayoutName()
+{
+ mpLayoutName.reset();
+}
+
+void ScDPSaveMember::WriteToSource( const uno::Reference<uno::XInterface>& xMember, sal_Int32 nPosition )
+{
+ uno::Reference<beans::XPropertySet> xMembProp( xMember, uno::UNO_QUERY );
+ OSL_ENSURE( xMembProp.is(), "no properties at member" );
+ if ( !xMembProp.is() )
+ return;
+
+ // exceptions are caught at ScDPSaveData::WriteToSource
+
+ if ( nVisibleMode != SC_DPSAVEMODE_DONTKNOW )
+ lcl_SetBoolProperty( xMembProp,
+ SC_UNO_DP_ISVISIBLE, static_cast<bool>(nVisibleMode) );
+
+ if ( nShowDetailsMode != SC_DPSAVEMODE_DONTKNOW )
+ lcl_SetBoolProperty( xMembProp,
+ SC_UNO_DP_SHOWDETAILS, static_cast<bool>(nShowDetailsMode) );
+
+ if (mpLayoutName)
+ ScUnoHelpFunctions::SetOptionalPropertyValue(xMembProp, SC_UNO_DP_LAYOUTNAME, *mpLayoutName);
+
+ if ( nPosition >= 0 )
+ ScUnoHelpFunctions::SetOptionalPropertyValue(xMembProp, SC_UNO_DP_POSITION, nPosition);
+}
+
+#if DUMP_PIVOT_TABLE
+
+void ScDPSaveMember::Dump(int nIndent) const
+{
+ std::string aIndent(nIndent*4, ' ');
+ cout << aIndent << "* member name: '" << aName << "'" << endl;
+
+ cout << aIndent << " + layout name: ";
+ if (mpLayoutName)
+ cout << "'" << *mpLayoutName << "'";
+ else
+ cout << "(none)";
+ cout << endl;
+
+ cout << aIndent << " + visibility: ";
+ if (nVisibleMode == SC_DPSAVEMODE_DONTKNOW)
+ cout << "(unknown)";
+ else
+ cout << (nVisibleMode ? "visible" : "hidden");
+ cout << endl;
+}
+
+#endif
+
+ScDPSaveDimension::ScDPSaveDimension(const OUString& rName, bool bDataLayout) :
+ aName( rName ),
+ bIsDataLayout( bDataLayout ),
+ bDupFlag( false ),
+ nOrientation( sheet::DataPilotFieldOrientation_HIDDEN ),
+ nFunction( ScGeneralFunction::AUTO ),
+ nUsedHierarchy( -1 ),
+ nShowEmptyMode( SC_DPSAVEMODE_DONTKNOW ),
+ bRepeatItemLabels( false ),
+ bSubTotalDefault( true )
+{
+}
+
+ScDPSaveDimension::ScDPSaveDimension(const ScDPSaveDimension& r) :
+ aName( r.aName ),
+ mpLayoutName( r.mpLayoutName ),
+ mpSubtotalName( r.mpSubtotalName ),
+ bIsDataLayout( r.bIsDataLayout ),
+ bDupFlag( r.bDupFlag ),
+ nOrientation( r.nOrientation ),
+ nFunction( r.nFunction ),
+ nUsedHierarchy( r.nUsedHierarchy ),
+ nShowEmptyMode( r.nShowEmptyMode ),
+ bRepeatItemLabels( r.bRepeatItemLabels ),
+ bSubTotalDefault( r.bSubTotalDefault ),
+ maSubTotalFuncs( r.maSubTotalFuncs )
+{
+ for (const ScDPSaveMember* pMem : r.maMemberList)
+ {
+ const OUString& rName = pMem->GetName();
+ std::unique_ptr<ScDPSaveMember> pNew(new ScDPSaveMember( *pMem ));
+ maMemberList.push_back( pNew.get() );
+ maMemberHash[rName] = std::move(pNew);
+ }
+ if (r.pReferenceValue)
+ pReferenceValue.reset( new sheet::DataPilotFieldReference( *(r.pReferenceValue) ) );
+ if (r.pSortInfo)
+ pSortInfo.reset( new sheet::DataPilotFieldSortInfo( *(r.pSortInfo) ) );
+ if (r.pAutoShowInfo)
+ pAutoShowInfo.reset( new sheet::DataPilotFieldAutoShowInfo( *(r.pAutoShowInfo) ) );
+ if (r.pLayoutInfo)
+ pLayoutInfo.reset(new sheet::DataPilotFieldLayoutInfo( *(r.pLayoutInfo) ));
+}
+
+ScDPSaveDimension::~ScDPSaveDimension()
+{
+ maMemberHash.clear();
+ pReferenceValue.reset();
+ pSortInfo.reset();
+ pAutoShowInfo.reset();
+ pLayoutInfo.reset();
+}
+
+bool ScDPSaveDimension::operator== ( const ScDPSaveDimension& r ) const
+{
+ if ( aName != r.aName ||
+ bIsDataLayout != r.bIsDataLayout ||
+ bDupFlag != r.bDupFlag ||
+ nOrientation != r.nOrientation ||
+ nFunction != r.nFunction ||
+ nUsedHierarchy != r.nUsedHierarchy ||
+ nShowEmptyMode != r.nShowEmptyMode ||
+ bRepeatItemLabels!= r.bRepeatItemLabels||
+ bSubTotalDefault != r.bSubTotalDefault ||
+ maSubTotalFuncs != r.maSubTotalFuncs )
+ return false;
+
+ if (maMemberHash.size() != r.maMemberHash.size() )
+ return false;
+
+ if (!std::equal(maMemberList.begin(), maMemberList.end(), r.maMemberList.begin(), r.maMemberList.end(),
+ [](const ScDPSaveMember* a, const ScDPSaveMember* b) { return *a == *b; }))
+ return false;
+
+ if( pReferenceValue && r.pReferenceValue )
+ {
+ if ( *pReferenceValue != *r.pReferenceValue )
+ {
+ return false;
+ }
+ }
+ else if ( pReferenceValue || r.pReferenceValue )
+ {
+ return false;
+ }
+ if( this->pSortInfo && r.pSortInfo )
+ {
+ if ( *this->pSortInfo != *r.pSortInfo )
+ {
+ return false;
+ }
+ }
+ else if ( this->pSortInfo || r.pSortInfo )
+ {
+ return false;
+ }
+ if( this->pAutoShowInfo && r.pAutoShowInfo )
+ {
+ if ( *this->pAutoShowInfo != *r.pAutoShowInfo )
+ {
+ return false;
+ }
+ }
+ else if ( this->pAutoShowInfo || r.pAutoShowInfo )
+ {
+ return false;
+ }
+
+ return true;
+}
+
+void ScDPSaveDimension::AddMember(std::unique_ptr<ScDPSaveMember> pMember)
+{
+ const OUString & rName = pMember->GetName();
+ auto aExisting = maMemberHash.find( rName );
+ auto tmp = pMember.get();
+ if ( aExisting == maMemberHash.end() )
+ {
+ maMemberHash[rName] = std::move(pMember);
+ }
+ else
+ {
+ maMemberList.erase(std::remove(maMemberList.begin(), maMemberList.end(), aExisting->second.get()), maMemberList.end());
+ aExisting->second = std::move(pMember);
+ }
+ maMemberList.push_back( tmp );
+}
+
+void ScDPSaveDimension::SetName( const OUString& rNew )
+{
+ // Used only if the source dim was renamed (groups).
+ // For UI renaming of dimensions, the layout name must be used.
+
+ aName = rNew;
+}
+
+void ScDPSaveDimension::SetOrientation(css::sheet::DataPilotFieldOrientation nNew)
+{
+ nOrientation = nNew;
+}
+
+void ScDPSaveDimension::SetSubTotals(std::vector<ScGeneralFunction> && rFuncs)
+{
+ maSubTotalFuncs = std::move(rFuncs);
+ bSubTotalDefault = false;
+}
+
+bool ScDPSaveDimension::HasShowEmpty() const
+{
+ return nShowEmptyMode != SC_DPSAVEMODE_DONTKNOW;
+}
+
+void ScDPSaveDimension::SetShowEmpty(bool bSet)
+{
+ nShowEmptyMode = sal_uInt16(bSet);
+}
+
+void ScDPSaveDimension::SetRepeatItemLabels(bool bSet)
+{
+ bRepeatItemLabels = bSet;
+}
+
+void ScDPSaveDimension::SetFunction(ScGeneralFunction nNew)
+{
+ nFunction = nNew;
+}
+
+void ScDPSaveDimension::SetUsedHierarchy(tools::Long nNew)
+{
+ nUsedHierarchy = nNew;
+}
+
+void ScDPSaveDimension::SetSubtotalName(const OUString& rName)
+{
+ mpSubtotalName = rName;
+}
+
+const std::optional<OUString> & ScDPSaveDimension::GetSubtotalName() const
+{
+ return mpSubtotalName;
+}
+
+void ScDPSaveDimension::RemoveSubtotalName()
+{
+ mpSubtotalName.reset();
+}
+
+bool ScDPSaveDimension::IsMemberNameInUse(const OUString& rName) const
+{
+ return std::any_of(maMemberList.begin(), maMemberList.end(), [&rName](const ScDPSaveMember* pMem) {
+ if (rName.equalsIgnoreAsciiCase(pMem->GetName()))
+ return true;
+
+ const std::optional<OUString> & pLayoutName = pMem->GetLayoutName();
+ return pLayoutName && rName.equalsIgnoreAsciiCase(*pLayoutName);
+ });
+}
+
+void ScDPSaveDimension::SetLayoutName(const OUString& rName)
+{
+ mpLayoutName = rName;
+}
+
+const std::optional<OUString> & ScDPSaveDimension::GetLayoutName() const
+{
+ return mpLayoutName;
+}
+
+void ScDPSaveDimension::RemoveLayoutName()
+{
+ mpLayoutName.reset();
+}
+
+void ScDPSaveDimension::SetReferenceValue(const sheet::DataPilotFieldReference* pNew)
+{
+ if (pNew)
+ pReferenceValue.reset( new sheet::DataPilotFieldReference(*pNew) );
+ else
+ pReferenceValue.reset();
+}
+
+void ScDPSaveDimension::SetSortInfo(const sheet::DataPilotFieldSortInfo* pNew)
+{
+ if (pNew)
+ pSortInfo.reset( new sheet::DataPilotFieldSortInfo(*pNew) );
+ else
+ pSortInfo.reset();
+}
+
+void ScDPSaveDimension::SetAutoShowInfo(const sheet::DataPilotFieldAutoShowInfo* pNew)
+{
+ if (pNew)
+ pAutoShowInfo.reset( new sheet::DataPilotFieldAutoShowInfo(*pNew) );
+ else
+ pAutoShowInfo.reset();
+}
+
+void ScDPSaveDimension::SetLayoutInfo(const sheet::DataPilotFieldLayoutInfo* pNew)
+{
+ if (pNew)
+ pLayoutInfo.reset( new sheet::DataPilotFieldLayoutInfo(*pNew) );
+ else
+ pLayoutInfo.reset();
+}
+
+void ScDPSaveDimension::SetCurrentPage( const OUString* pPage )
+{
+ // We use member's visibility attribute to filter by page dimension.
+
+ // pPage == nullptr -> all members visible.
+ for (ScDPSaveMember* pMem : maMemberList)
+ {
+ bool bVisible = !pPage || pMem->GetName() == *pPage;
+ pMem->SetIsVisible(bVisible);
+ }
+}
+
+OUString ScDPSaveDimension::GetCurrentPage() const
+{
+ MemberList::const_iterator it = std::find_if(maMemberList.begin(), maMemberList.end(),
+ [](const ScDPSaveMember* pMem) { return pMem->GetIsVisible(); });
+ if (it != maMemberList.end())
+ return (*it)->GetName();
+
+ return OUString();
+}
+
+ScDPSaveMember* ScDPSaveDimension::GetExistingMemberByName(const OUString& rName)
+{
+ auto res = maMemberHash.find (rName);
+ if (res != maMemberHash.end())
+ return res->second.get();
+ return nullptr;
+}
+
+ScDPSaveMember* ScDPSaveDimension::GetMemberByName(const OUString& rName)
+{
+ auto res = maMemberHash.find (rName);
+ if (res != maMemberHash.end())
+ return res->second.get();
+
+ ScDPSaveMember* pNew = new ScDPSaveMember( rName );
+ maMemberHash[rName] = std::unique_ptr<ScDPSaveMember>(pNew);
+ maMemberList.push_back( pNew );
+ return pNew;
+}
+
+void ScDPSaveDimension::SetMemberPosition( const OUString& rName, sal_Int32 nNewPos )
+{
+ ScDPSaveMember* pMember = GetMemberByName( rName ); // make sure it exists and is in the hash
+
+ maMemberList.erase(std::remove( maMemberList.begin(), maMemberList.end(), pMember), maMemberList.end() );
+
+ maMemberList.insert( maMemberList.begin() + nNewPos, pMember );
+}
+
+void ScDPSaveDimension::WriteToSource( const uno::Reference<uno::XInterface>& xDim )
+{
+ uno::Reference<beans::XPropertySet> xDimProp( xDim, uno::UNO_QUERY );
+ OSL_ENSURE( xDimProp.is(), "no properties at dimension" );
+ if ( xDimProp.is() )
+ {
+ // exceptions are caught at ScDPSaveData::WriteToSource
+
+ sheet::DataPilotFieldOrientation eOrient = nOrientation;
+ xDimProp->setPropertyValue( SC_UNO_DP_ORIENTATION, uno::Any(eOrient) );
+
+ sal_Int16 eFunc = static_cast<sal_Int16>(nFunction);
+ xDimProp->setPropertyValue( SC_UNO_DP_FUNCTION2, uno::Any(eFunc) );
+
+ if ( nUsedHierarchy >= 0 )
+ {
+ xDimProp->setPropertyValue( SC_UNO_DP_USEDHIERARCHY, uno::Any(static_cast<sal_Int32>(nUsedHierarchy)) );
+ }
+
+ if ( pReferenceValue )
+ {
+ ;
+ xDimProp->setPropertyValue( SC_UNO_DP_REFVALUE, uno::Any(*pReferenceValue) );
+ }
+
+ if (mpLayoutName)
+ ScUnoHelpFunctions::SetOptionalPropertyValue(xDimProp, SC_UNO_DP_LAYOUTNAME, *mpLayoutName);
+
+ const std::optional<OUString> & pSubTotalName = GetSubtotalName();
+ if (pSubTotalName)
+ // Custom subtotal name, with '?' being replaced by the visible field name later.
+ ScUnoHelpFunctions::SetOptionalPropertyValue(xDimProp, SC_UNO_DP_FIELD_SUBTOTALNAME, *pSubTotalName);
+ }
+
+ // Level loop outside of maMemberList loop
+ // because SubTotals have to be set independently of known members
+
+ tools::Long nCount = maMemberHash.size();
+
+ tools::Long nHierCount = 0;
+ uno::Reference<container::XIndexAccess> xHiers;
+ uno::Reference<sheet::XHierarchiesSupplier> xHierSupp( xDim, uno::UNO_QUERY );
+ if ( xHierSupp.is() )
+ {
+ uno::Reference<container::XNameAccess> xHiersName = xHierSupp->getHierarchies();
+ xHiers = new ScNameToIndexAccess( xHiersName );
+ nHierCount = xHiers->getCount();
+ }
+
+ bool bHasHiddenMember = false;
+
+ for (tools::Long nHier=0; nHier<nHierCount; nHier++)
+ {
+ tools::Long nLevCount = 0;
+ uno::Reference<container::XIndexAccess> xLevels;
+ uno::Reference<sheet::XLevelsSupplier> xLevSupp(xHiers->getByIndex(nHier), uno::UNO_QUERY);
+ if ( xLevSupp.is() )
+ {
+ uno::Reference<container::XNameAccess> xLevelsName = xLevSupp->getLevels();
+ xLevels = new ScNameToIndexAccess( xLevelsName );
+ nLevCount = xLevels->getCount();
+ }
+
+ for (tools::Long nLev=0; nLev<nLevCount; nLev++)
+ {
+ uno::Reference<uno::XInterface> xLevel(xLevels->getByIndex(nLev), uno::UNO_QUERY);
+ uno::Reference<beans::XPropertySet> xLevProp( xLevel, uno::UNO_QUERY );
+ OSL_ENSURE( xLevProp.is(), "no properties at level" );
+ if ( xLevProp.is() )
+ {
+ if ( !bSubTotalDefault )
+ {
+ uno::Sequence<sal_Int16> aSeq(maSubTotalFuncs.size());
+ for(size_t i = 0; i < maSubTotalFuncs.size(); ++i)
+ aSeq.getArray()[i] = static_cast<sal_Int16>(maSubTotalFuncs[i]);
+ xLevProp->setPropertyValue( SC_UNO_DP_SUBTOTAL2, uno::Any(aSeq) );
+ }
+ if ( nShowEmptyMode != SC_DPSAVEMODE_DONTKNOW )
+ lcl_SetBoolProperty( xLevProp,
+ SC_UNO_DP_SHOWEMPTY, static_cast<bool>(nShowEmptyMode) );
+
+ lcl_SetBoolProperty( xLevProp,
+ SC_UNO_DP_REPEATITEMLABELS, bRepeatItemLabels );
+
+ if ( pSortInfo )
+ ScUnoHelpFunctions::SetOptionalPropertyValue(xLevProp, SC_UNO_DP_SORTING, *pSortInfo);
+
+ if ( pAutoShowInfo )
+ ScUnoHelpFunctions::SetOptionalPropertyValue(xLevProp, SC_UNO_DP_AUTOSHOW, *pAutoShowInfo);
+
+ if ( pLayoutInfo )
+ ScUnoHelpFunctions::SetOptionalPropertyValue(xLevProp, SC_UNO_DP_LAYOUT, *pLayoutInfo);
+
+ // exceptions are caught at ScDPSaveData::WriteToSource
+ }
+
+ if ( nCount > 0 )
+ {
+ uno::Reference<sheet::XMembersSupplier> xMembSupp( xLevel, uno::UNO_QUERY );
+ if ( xMembSupp.is() )
+ {
+ uno::Reference<sheet::XMembersAccess> xMembers = xMembSupp->getMembers();
+ if ( xMembers.is() )
+ {
+ sal_Int32 nPosition = -1; // set position only in manual mode
+ if ( !pSortInfo || pSortInfo->Mode == sheet::DataPilotFieldSortMode::MANUAL )
+ nPosition = 0;
+
+ for (ScDPSaveMember* pMember : maMemberList)
+ {
+ if (!pMember->GetIsVisible())
+ bHasHiddenMember = true;
+ OUString aMemberName = pMember->GetName();
+ if ( xMembers->hasByName( aMemberName ) )
+ {
+ uno::Reference<uno::XInterface> xMemberInt(
+ xMembers->getByName(aMemberName), uno::UNO_QUERY);
+ pMember->WriteToSource( xMemberInt, nPosition );
+
+ if ( nPosition >= 0 )
+ ++nPosition; // increase if initialized
+ }
+ // missing member is no error
+ }
+ }
+ }
+ }
+ }
+ }
+
+ if (xDimProp.is())
+ ScUnoHelpFunctions::SetOptionalPropertyValue(xDimProp, SC_UNO_DP_HAS_HIDDEN_MEMBER, bHasHiddenMember);
+}
+
+void ScDPSaveDimension::UpdateMemberVisibility(const std::unordered_map<OUString, bool>& rData)
+{
+ for (ScDPSaveMember* pMem : maMemberList)
+ {
+ const OUString& rMemName = pMem->GetName();
+ auto itr = rData.find(rMemName);
+ if (itr != rData.end())
+ pMem->SetIsVisible(itr->second);
+ }
+}
+
+bool ScDPSaveDimension::HasInvisibleMember() const
+{
+ return std::any_of(maMemberList.begin(), maMemberList.end(),
+ [](const ScDPSaveMember* pMem) { return !pMem->GetIsVisible(); });
+}
+
+void ScDPSaveDimension::RemoveObsoleteMembers(const MemberSetType& rMembers)
+{
+ MemberList aNew;
+ for (ScDPSaveMember* pMem : maMemberList)
+ {
+ if (rMembers.count(pMem->GetName()))
+ {
+ // This member still exists.
+ aNew.push_back(pMem);
+ }
+ else
+ {
+ maMemberHash.erase(pMem->GetName());
+ }
+ }
+
+ maMemberList.swap(aNew);
+}
+
+#if DUMP_PIVOT_TABLE
+
+void ScDPSaveDimension::Dump(int nIndent) const
+{
+ static const char* pOrientNames[] = { "hidden", "column", "row", "page", "data" };
+ std::string aIndent(nIndent*4, ' ');
+
+ cout << aIndent << "* dimension name: '" << aName << "'" << endl;
+
+ cout << aIndent << " + orientation: ";
+ if (nOrientation <= DataPilotFieldOrientation_DATA)
+ cout << pOrientNames[static_cast<int>(nOrientation)];
+ else
+ cout << "(invalid)";
+ cout << endl;
+
+ cout << aIndent << " + layout name: ";
+ if (mpLayoutName)
+ cout << "'" << *mpLayoutName << "'";
+ else
+ cout << "(none)";
+ cout << endl;
+
+ cout << aIndent << " + subtotal name: ";
+ if (mpSubtotalName)
+ cout << "'" << *mpSubtotalName << "'";
+ else
+ cout << "(none)";
+ cout << endl;
+
+ cout << aIndent << " + is data layout: " << (bIsDataLayout ? "yes" : "no") << endl;
+ cout << aIndent << " + is duplicate: " << (bDupFlag ? "yes" : "no") << endl;
+
+ for (ScDPSaveMember* pMem : maMemberList)
+ {
+ pMem->Dump(nIndent+1);
+ }
+
+ cout << endl; // blank line
+}
+
+#endif
+
+ScDPSaveData::ScDPSaveData() :
+ nColumnGrandMode( SC_DPSAVEMODE_DONTKNOW ),
+ nRowGrandMode( SC_DPSAVEMODE_DONTKNOW ),
+ nIgnoreEmptyMode( SC_DPSAVEMODE_DONTKNOW ),
+ nRepeatEmptyMode( SC_DPSAVEMODE_DONTKNOW ),
+ bFilterButton( true ),
+ bDrillDown( true ),
+ mbDimensionMembersBuilt(false)
+{
+}
+
+ScDPSaveData::ScDPSaveData(const ScDPSaveData& r) :
+ nColumnGrandMode( r.nColumnGrandMode ),
+ nRowGrandMode( r.nRowGrandMode ),
+ nIgnoreEmptyMode( r.nIgnoreEmptyMode ),
+ nRepeatEmptyMode( r.nRepeatEmptyMode ),
+ bFilterButton( r.bFilterButton ),
+ bDrillDown( r.bDrillDown ),
+ mbDimensionMembersBuilt(r.mbDimensionMembersBuilt),
+ mpGrandTotalName(r.mpGrandTotalName)
+{
+ if ( r.pDimensionData )
+ pDimensionData.reset( new ScDPDimensionSaveData( *r.pDimensionData ) );
+
+ for (auto const& it : r.m_DimList)
+ {
+ m_DimList.push_back(std::make_unique<ScDPSaveDimension>(*it));
+ }
+}
+
+ScDPSaveData& ScDPSaveData::operator= ( const ScDPSaveData& r )
+{
+ if ( &r != this )
+ {
+ this->~ScDPSaveData();
+ new( this ) ScDPSaveData ( r );
+ }
+ return *this;
+}
+
+bool ScDPSaveData::operator== ( const ScDPSaveData& r ) const
+{
+ if ( nColumnGrandMode != r.nColumnGrandMode ||
+ nRowGrandMode != r.nRowGrandMode ||
+ nIgnoreEmptyMode != r.nIgnoreEmptyMode ||
+ nRepeatEmptyMode != r.nRepeatEmptyMode ||
+ bFilterButton != r.bFilterButton ||
+ bDrillDown != r.bDrillDown ||
+ mbDimensionMembersBuilt != r.mbDimensionMembersBuilt)
+ return false;
+
+ if ( pDimensionData || r.pDimensionData )
+ if ( !pDimensionData || !r.pDimensionData || !( *pDimensionData == *r.pDimensionData ) )
+ return false;
+
+ if (!(::comphelper::ContainerUniquePtrEquals(m_DimList, r.m_DimList)))
+ return false;
+
+ if (mpGrandTotalName)
+ {
+ if (!r.mpGrandTotalName)
+ return false;
+ if (*mpGrandTotalName != *r.mpGrandTotalName)
+ return false;
+ }
+ else if (r.mpGrandTotalName)
+ return false;
+
+ return true;
+}
+
+ScDPSaveData::~ScDPSaveData()
+{
+}
+
+void ScDPSaveData::SetGrandTotalName(const OUString& rName)
+{
+ mpGrandTotalName = rName;
+}
+
+const std::optional<OUString> & ScDPSaveData::GetGrandTotalName() const
+{
+ return mpGrandTotalName;
+}
+
+namespace {
+
+class DimOrderInserter
+{
+ ScDPSaveData::DimOrderType& mrNames;
+public:
+ explicit DimOrderInserter(ScDPSaveData::DimOrderType& rNames) : mrNames(rNames) {}
+
+ void operator() (const ScDPSaveDimension* pDim)
+ {
+ size_t nRank = mrNames.size();
+ mrNames.emplace(ScGlobal::getCharClass().uppercase(pDim->GetName()), nRank);
+ }
+};
+
+}
+
+const ScDPSaveData::DimOrderType& ScDPSaveData::GetDimensionSortOrder() const
+{
+ if (!mpDimOrder)
+ {
+ mpDimOrder.reset(new DimOrderType);
+ std::vector<const ScDPSaveDimension*> aRowDims, aColDims;
+ GetAllDimensionsByOrientation(sheet::DataPilotFieldOrientation_ROW, aRowDims);
+ GetAllDimensionsByOrientation(sheet::DataPilotFieldOrientation_COLUMN, aColDims);
+
+ std::for_each(aRowDims.begin(), aRowDims.end(), DimOrderInserter(*mpDimOrder));
+ std::for_each(aColDims.begin(), aColDims.end(), DimOrderInserter(*mpDimOrder));
+ }
+ return *mpDimOrder;
+}
+
+void ScDPSaveData::GetAllDimensionsByOrientation(
+ sheet::DataPilotFieldOrientation eOrientation, std::vector<const ScDPSaveDimension*>& rDims) const
+{
+ std::vector<const ScDPSaveDimension*> aDims;
+ for (auto const& it : m_DimList)
+ {
+ const ScDPSaveDimension& rDim = *it;
+ if (rDim.GetOrientation() != eOrientation)
+ continue;
+
+ aDims.push_back(&rDim);
+ }
+
+ rDims.swap(aDims);
+}
+
+void ScDPSaveData::AddDimension(ScDPSaveDimension* pDim)
+{
+ if (!pDim)
+ return;
+
+ CheckDuplicateName(*pDim);
+ m_DimList.push_back(std::unique_ptr<ScDPSaveDimension>(pDim));
+
+ DimensionsChanged();
+}
+
+ScDPSaveDimension* ScDPSaveData::GetDimensionByName(const OUString& rName)
+{
+ for (auto const& iter : m_DimList)
+ {
+ if (iter->GetName() == rName && !iter->IsDataLayout() )
+ return &(*iter);
+ }
+
+ return AppendNewDimension(rName, false);
+}
+
+ScDPSaveDimension* ScDPSaveData::GetExistingDimensionByName(std::u16string_view rName) const
+{
+ for (auto const& iter : m_DimList)
+ {
+ if (iter->GetName() == rName && !iter->IsDataLayout() )
+ return &(*iter);
+ }
+ return nullptr; // don't create new
+}
+
+ScDPSaveDimension* ScDPSaveData::GetNewDimensionByName(const OUString& rName)
+{
+ for (auto const& iter : m_DimList)
+ {
+ if (iter->GetName() == rName && !iter->IsDataLayout() )
+ return DuplicateDimension(rName);
+ }
+
+ return AppendNewDimension(rName, false);
+}
+
+ScDPSaveDimension* ScDPSaveData::GetDataLayoutDimension()
+{
+ ScDPSaveDimension* pDim = GetExistingDataLayoutDimension();
+ if (pDim)
+ return pDim;
+
+ return AppendNewDimension(OUString(), true);
+}
+
+ScDPSaveDimension* ScDPSaveData::GetExistingDataLayoutDimension() const
+{
+ for (auto const& iter : m_DimList)
+ {
+ if ( iter->IsDataLayout() )
+ return &(*iter);
+ }
+ return nullptr;
+}
+
+ScDPSaveDimension* ScDPSaveData::DuplicateDimension(std::u16string_view rName)
+{
+ // always insert new
+
+ ScDPSaveDimension* pOld = GetExistingDimensionByName(rName);
+ if (!pOld)
+ return nullptr;
+
+ ScDPSaveDimension* pNew = new ScDPSaveDimension( *pOld );
+ AddDimension(pNew);
+ return pNew;
+}
+
+void ScDPSaveData::RemoveDimensionByName(const OUString& rName)
+{
+ auto iter = std::find_if(m_DimList.begin(), m_DimList.end(),
+ [&rName](const std::unique_ptr<ScDPSaveDimension>& rxDim) {
+ return rxDim->GetName() == rName && !rxDim->IsDataLayout(); });
+ if (iter != m_DimList.end())
+ {
+ m_DimList.erase(iter);
+ RemoveDuplicateNameCount(rName);
+ DimensionsChanged();
+ }
+}
+
+ScDPSaveDimension& ScDPSaveData::DuplicateDimension( const ScDPSaveDimension& rDim )
+{
+ ScDPSaveDimension* pNew = new ScDPSaveDimension( rDim );
+ AddDimension(pNew);
+ return *pNew;
+}
+
+ScDPSaveDimension* ScDPSaveData::GetInnermostDimension(DataPilotFieldOrientation nOrientation)
+{
+ // return the innermost dimension for the given orientation,
+ // excluding data layout dimension
+
+ auto iter = std::find_if(m_DimList.rbegin(), m_DimList.rend(),
+ [&nOrientation](const std::unique_ptr<ScDPSaveDimension>& rxDim) {
+ return rxDim->GetOrientation() == nOrientation && !rxDim->IsDataLayout(); });
+ if (iter != m_DimList.rend())
+ return iter->get();
+
+ return nullptr;
+}
+
+ScDPSaveDimension* ScDPSaveData::GetFirstDimension(sheet::DataPilotFieldOrientation eOrientation)
+{
+ for (auto const& iter : m_DimList)
+ {
+ if (iter->GetOrientation() == eOrientation && !iter->IsDataLayout())
+ return &(*iter);
+ }
+ return nullptr;
+}
+
+tools::Long ScDPSaveData::GetDataDimensionCount() const
+{
+ tools::Long nDataCount = 0;
+
+ for (auto const& iter : m_DimList)
+ {
+ if (iter->GetOrientation() == sheet::DataPilotFieldOrientation_DATA)
+ ++nDataCount;
+ }
+
+ return nDataCount;
+}
+
+void ScDPSaveData::SetPosition( ScDPSaveDimension* pDim, tools::Long nNew )
+{
+ // position (nNew) is counted within dimensions of the same orientation
+
+ DataPilotFieldOrientation nOrient = pDim->GetOrientation();
+
+ auto it = std::find_if(m_DimList.begin(), m_DimList.end(),
+ [&pDim](const std::unique_ptr<ScDPSaveDimension>& rxDim) { return pDim == rxDim.get(); });
+ if (it != m_DimList.end())
+ {
+ // Tell vector<unique_ptr> to give up ownership of this element.
+ // Don't delete this instance as it is re-inserted into the
+ // container later.
+ it->release();
+ m_DimList.erase(it);
+ }
+
+ auto iterInsert = std::find_if(m_DimList.begin(), m_DimList.end(),
+ [&nOrient, &nNew](const std::unique_ptr<ScDPSaveDimension>& rxDim) {
+ if (rxDim->GetOrientation() == nOrient )
+ --nNew;
+ return nNew <= 0;
+ });
+
+ m_DimList.insert(iterInsert, std::unique_ptr<ScDPSaveDimension>(pDim));
+ DimensionsChanged();
+}
+
+void ScDPSaveData::SetColumnGrand(bool bSet)
+{
+ nColumnGrandMode = sal_uInt16(bSet);
+}
+
+void ScDPSaveData::SetRowGrand(bool bSet)
+{
+ nRowGrandMode = sal_uInt16(bSet);
+}
+
+void ScDPSaveData::SetIgnoreEmptyRows(bool bSet)
+{
+ nIgnoreEmptyMode = sal_uInt16(bSet);
+}
+
+void ScDPSaveData::SetRepeatIfEmpty(bool bSet)
+{
+ nRepeatEmptyMode = sal_uInt16(bSet);
+}
+
+void ScDPSaveData::SetFilterButton(bool bSet)
+{
+ bFilterButton = bSet;
+}
+
+void ScDPSaveData::SetDrillDown(bool bSet)
+{
+ bDrillDown = bSet;
+}
+
+static void lcl_ResetOrient( const uno::Reference<sheet::XDimensionsSupplier>& xSource )
+{
+ uno::Reference<container::XNameAccess> xDimsName = xSource->getDimensions();
+ uno::Reference<container::XIndexAccess> xIntDims = new ScNameToIndexAccess( xDimsName );
+ tools::Long nIntCount = xIntDims->getCount();
+ for (tools::Long nIntDim=0; nIntDim<nIntCount; nIntDim++)
+ {
+ uno::Reference<beans::XPropertySet> xDimProp(xIntDims->getByIndex(nIntDim), uno::UNO_QUERY);
+ if (xDimProp.is())
+ {
+ xDimProp->setPropertyValue( SC_UNO_DP_ORIENTATION, uno::Any(sheet::DataPilotFieldOrientation_HIDDEN) );
+ }
+ }
+}
+
+void ScDPSaveData::WriteToSource( const uno::Reference<sheet::XDimensionsSupplier>& xSource )
+{
+ if (!xSource.is())
+ return;
+
+ // source options must be first!
+
+ uno::Reference<beans::XPropertySet> xSourceProp( xSource, uno::UNO_QUERY );
+ SAL_WARN_IF( !xSourceProp.is(), "sc.core", "no properties at source" );
+ if ( xSourceProp.is() )
+ {
+ // source options are not available for external sources
+ //TODO: use XPropertySetInfo to test for availability?
+
+ try
+ {
+ if ( nIgnoreEmptyMode != SC_DPSAVEMODE_DONTKNOW )
+ lcl_SetBoolProperty( xSourceProp,
+ SC_UNO_DP_IGNOREEMPTY, static_cast<bool>(nIgnoreEmptyMode) );
+ if ( nRepeatEmptyMode != SC_DPSAVEMODE_DONTKNOW )
+ lcl_SetBoolProperty( xSourceProp,
+ SC_UNO_DP_REPEATEMPTY, static_cast<bool>(nRepeatEmptyMode) );
+ }
+ catch(uno::Exception&)
+ {
+ // no error
+ }
+
+ const std::optional<OUString> & pGrandTotalName = GetGrandTotalName();
+ if (pGrandTotalName)
+ ScUnoHelpFunctions::SetOptionalPropertyValue(xSourceProp, SC_UNO_DP_GRANDTOTAL_NAME, *pGrandTotalName);
+ }
+
+ // exceptions in the other calls are errors
+ try
+ {
+ // reset all orientations
+ //TODO: "forgetSettings" or similar at source ?????
+ //TODO: reset all duplicated dimensions, or reuse them below !!!
+ SAL_INFO("sc.core", "ScDPSaveData::WriteToSource");
+
+ lcl_ResetOrient( xSource );
+
+ uno::Reference<container::XNameAccess> xDimsName = xSource->getDimensions();
+ uno::Reference<container::XIndexAccess> xIntDims = new ScNameToIndexAccess( xDimsName );
+ tools::Long nIntCount = xIntDims->getCount();
+
+ for (const auto& rxDim : m_DimList)
+ {
+ OUString aName = rxDim->GetName();
+ OUString aCoreName = ScDPUtil::getSourceDimensionName(aName);
+
+ SAL_INFO("sc.core", aName);
+
+ bool bData = rxDim->IsDataLayout();
+
+ //TODO: getByName for ScDPSource, including DataLayoutDimension !!!!!!!!
+
+ bool bFound = false;
+ for (tools::Long nIntDim=0; nIntDim<nIntCount && !bFound; nIntDim++)
+ {
+ uno::Reference<uno::XInterface> xIntDim(xIntDims->getByIndex(nIntDim),
+ uno::UNO_QUERY);
+ if ( bData )
+ {
+ uno::Reference<beans::XPropertySet> xDimProp( xIntDim, uno::UNO_QUERY );
+ if ( xDimProp.is() )
+ {
+ bFound = ScUnoHelpFunctions::GetBoolProperty( xDimProp,
+ SC_UNO_DP_ISDATALAYOUT );
+ //TODO: error checking -- is "IsDataLayoutDimension" property required??
+ }
+ }
+ else
+ {
+ uno::Reference<container::XNamed> xDimName( xIntDim, uno::UNO_QUERY );
+ if (xDimName.is() && xDimName->getName() == aCoreName)
+ bFound = true;
+ }
+
+ if (bFound)
+ {
+ if (rxDim->GetDupFlag())
+ {
+ uno::Reference<util::XCloneable> xCloneable(xIntDim, uno::UNO_QUERY);
+ SAL_WARN_IF(!xCloneable.is(), "sc.core", "cannot clone dimension");
+ if (xCloneable.is())
+ {
+ uno::Reference<util::XCloneable> xNew = xCloneable->createClone();
+ uno::Reference<container::XNamed> xNewName(xNew, uno::UNO_QUERY);
+ if (xNewName.is())
+ {
+ xNewName->setName(aName);
+ rxDim->WriteToSource(xNew);
+ }
+ }
+ }
+ else
+ rxDim->WriteToSource( xIntDim );
+ }
+ }
+ SAL_WARN_IF(!bFound, "sc.core", "WriteToSource: Dimension not found: " + aName + ".");
+ }
+
+ if ( xSourceProp.is() )
+ {
+ if ( nColumnGrandMode != SC_DPSAVEMODE_DONTKNOW )
+ lcl_SetBoolProperty( xSourceProp,
+ SC_UNO_DP_COLGRAND, static_cast<bool>(nColumnGrandMode) );
+ if ( nRowGrandMode != SC_DPSAVEMODE_DONTKNOW )
+ lcl_SetBoolProperty( xSourceProp,
+ SC_UNO_DP_ROWGRAND, static_cast<bool>(nRowGrandMode) );
+ }
+ }
+ catch(uno::Exception const &)
+ {
+ TOOLS_WARN_EXCEPTION("sc.core", "WriteToSource");
+ }
+}
+
+bool ScDPSaveData::IsEmpty() const
+{
+ for (auto const& iter : m_DimList)
+ {
+ if (iter->GetOrientation() != sheet::DataPilotFieldOrientation_HIDDEN && !iter->IsDataLayout())
+ return false;
+ }
+ return true; // no entries that are not hidden
+}
+
+void ScDPSaveData::RemoveAllGroupDimensions( const OUString& rSrcDimName, std::vector<OUString>* pDeletedNames )
+{
+ if (!pDimensionData)
+ // No group dimensions exist. Nothing to do.
+ return;
+
+ // Remove numeric group dimension (exists once at most). No need to delete
+ // anything in save data (grouping was done inplace in an existing base
+ // dimension).
+ pDimensionData->RemoveNumGroupDimension(rSrcDimName);
+
+ // Remove named group dimension(s). Dimensions have to be removed from
+ // dimension save data and from save data too.
+ const ScDPSaveGroupDimension* pExistingGroup = pDimensionData->GetGroupDimForBase(rSrcDimName);
+ while ( pExistingGroup )
+ {
+ OUString aGroupDimName = pExistingGroup->GetGroupDimName();
+ pDimensionData->RemoveGroupDimension(aGroupDimName); // pExistingGroup is deleted
+
+ // also remove SaveData settings for the dimension that no longer exists
+ RemoveDimensionByName(aGroupDimName);
+
+ if (pDeletedNames)
+ pDeletedNames->push_back(aGroupDimName);
+
+ // see if there are more group dimensions
+ pExistingGroup = pDimensionData->GetGroupDimForBase(rSrcDimName);
+
+ if ( pExistingGroup && pExistingGroup->GetGroupDimName() == aGroupDimName )
+ {
+ // still get the same group dimension?
+ OSL_FAIL("couldn't remove group dimension");
+ pExistingGroup = nullptr; // avoid endless loop
+ }
+ }
+}
+
+ScDPDimensionSaveData* ScDPSaveData::GetDimensionData()
+{
+ if (!pDimensionData)
+ pDimensionData.reset( new ScDPDimensionSaveData );
+ return pDimensionData.get();
+}
+
+void ScDPSaveData::SetDimensionData( const ScDPDimensionSaveData* pNew )
+{
+ if ( pNew )
+ pDimensionData.reset( new ScDPDimensionSaveData( *pNew ) );
+ else
+ pDimensionData.reset();
+}
+
+void ScDPSaveData::BuildAllDimensionMembers(ScDPTableData* pData)
+{
+ if (mbDimensionMembersBuilt)
+ return;
+
+ // First, build a dimension name-to-index map.
+ typedef std::unordered_map<OUString, tools::Long> NameIndexMap;
+ NameIndexMap aMap;
+ tools::Long nColCount = pData->GetColumnCount();
+ for (tools::Long i = 0; i < nColCount; ++i)
+ aMap.emplace(pData->getDimensionName(i), i);
+
+ NameIndexMap::const_iterator itrEnd = aMap.end();
+
+ for (auto const& iter : m_DimList)
+ {
+ const OUString& rDimName = iter->GetName();
+ if (rDimName.isEmpty())
+ // empty dimension name. It must be data layout.
+ continue;
+
+ NameIndexMap::const_iterator itr = aMap.find(rDimName);
+ if (itr == itrEnd)
+ // dimension name not in the data. This should never happen!
+ continue;
+
+ tools::Long nDimIndex = itr->second;
+ const std::vector<SCROW>& rMembers = pData->GetColumnEntries(nDimIndex);
+ size_t nMemberCount = rMembers.size();
+ for (size_t j = 0; j < nMemberCount; ++j)
+ {
+ const ScDPItemData* pMemberData = pData->GetMemberById( nDimIndex, rMembers[j] );
+ OUString aMemName = pData->GetFormattedString(nDimIndex, *pMemberData, false);
+ if (iter->GetExistingMemberByName(aMemName))
+ // this member instance already exists. nothing to do.
+ continue;
+
+ unique_ptr<ScDPSaveMember> pNewMember(new ScDPSaveMember(aMemName));
+ pNewMember->SetIsVisible(true);
+ iter->AddMember(std::move(pNewMember));
+ }
+ }
+
+ mbDimensionMembersBuilt = true;
+}
+
+void ScDPSaveData::SyncAllDimensionMembers(ScDPTableData* pData)
+{
+ typedef std::unordered_map<OUString, tools::Long> NameIndexMap;
+
+ // First, build a dimension name-to-index map.
+ NameIndexMap aMap;
+ tools::Long nColCount = pData->GetColumnCount();
+ for (tools::Long i = 0; i < nColCount; ++i)
+ aMap.emplace(pData->getDimensionName(i), i);
+
+ NameIndexMap::const_iterator itMapEnd = aMap.end();
+
+ for (auto const& it : m_DimList)
+ {
+ const OUString& rDimName = it->GetName();
+ if (rDimName.isEmpty())
+ // empty dimension name. It must be data layout.
+ continue;
+
+ NameIndexMap::const_iterator itMap = aMap.find(rDimName);
+ if (itMap == itMapEnd)
+ // dimension name not in the data. This should never happen!
+ continue;
+
+ ScDPSaveDimension::MemberSetType aMemNames;
+ tools::Long nDimIndex = itMap->second;
+ const std::vector<SCROW>& rMembers = pData->GetColumnEntries(nDimIndex);
+ size_t nMemberCount = rMembers.size();
+ for (size_t j = 0; j < nMemberCount; ++j)
+ {
+ const ScDPItemData* pMemberData = pData->GetMemberById(nDimIndex, rMembers[j]);
+ // ScDPCache::GetItemDataById() (via
+ // ScDPTableData::GetMemberById(),
+ // ScDPGroupTableData::GetMemberById() through
+ // GetCacheTable().getCache()) may return nullptr.
+ if (pMemberData)
+ {
+ OUString aMemName = pData->GetFormattedString(nDimIndex, *pMemberData, false);
+ aMemNames.insert(aMemName);
+ }
+ else
+ {
+ SAL_WARN("sc.core", "No pMemberData for nDimIndex " << nDimIndex << ", rMembers[j] " << rMembers[j]
+ << ", j " << j);
+ }
+ }
+
+ it->RemoveObsoleteMembers(aMemNames);
+ }
+}
+
+bool ScDPSaveData::HasInvisibleMember(std::u16string_view rDimName) const
+{
+ ScDPSaveDimension* pDim = GetExistingDimensionByName(rDimName);
+ if (!pDim)
+ return false;
+
+ return pDim->HasInvisibleMember();
+}
+
+#if DUMP_PIVOT_TABLE
+
+void ScDPSaveData::Dump() const
+{
+ for (auto const& itDim : m_DimList)
+ {
+ const ScDPSaveDimension& rDim = *itDim;
+ rDim.Dump();
+ }
+}
+
+#endif
+
+void ScDPSaveData::CheckDuplicateName(ScDPSaveDimension& rDim)
+{
+ const OUString aName = ScDPUtil::getSourceDimensionName(rDim.GetName());
+ DupNameCountType::iterator it = maDupNameCounts.find(aName);
+ if (it != maDupNameCounts.end())
+ {
+ rDim.SetName(ScDPUtil::createDuplicateDimensionName(aName, ++it->second));
+ rDim.SetDupFlag(true);
+ }
+ else
+ // New name.
+ maDupNameCounts.emplace(aName, 0);
+}
+
+void ScDPSaveData::RemoveDuplicateNameCount(const OUString& rName)
+{
+ OUString aCoreName = rName;
+ if (ScDPUtil::isDuplicateDimension(rName))
+ aCoreName = ScDPUtil::getSourceDimensionName(rName);
+
+ DupNameCountType::iterator it = maDupNameCounts.find(aCoreName);
+ if (it == maDupNameCounts.end())
+ return;
+
+ if (!it->second)
+ {
+ maDupNameCounts.erase(it);
+ return;
+ }
+
+ --it->second;
+}
+
+ScDPSaveDimension* ScDPSaveData::AppendNewDimension(const OUString& rName, bool bDataLayout)
+{
+ if (ScDPUtil::isDuplicateDimension(rName))
+ // This call is for original dimensions only.
+ return nullptr;
+
+ ScDPSaveDimension* pNew = new ScDPSaveDimension(rName, bDataLayout);
+ m_DimList.push_back(std::unique_ptr<ScDPSaveDimension>(pNew));
+ if (!maDupNameCounts.count(rName))
+ maDupNameCounts.emplace(rName, 0);
+
+ DimensionsChanged();
+ return pNew;
+}
+
+void ScDPSaveData::DimensionsChanged()
+{
+ mpDimOrder.reset();
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sc/source/core/data/dpsdbtab.cxx b/sc/source/core/data/dpsdbtab.cxx
new file mode 100644
index 000000000..b56217c27
--- /dev/null
+++ b/sc/source/core/data/dpsdbtab.cxx
@@ -0,0 +1,169 @@
+/* -*- 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 <dpsdbtab.hxx>
+#include <globstr.hrc>
+#include <scresid.hxx>
+#include <dpfilteredcache.hxx>
+#include <document.hxx>
+#include <dpobject.hxx>
+
+#include <com/sun/star/sdb/CommandType.hpp>
+
+using namespace com::sun::star;
+
+using ::std::vector;
+using ::com::sun::star::uno::Sequence;
+using ::com::sun::star::uno::Any;
+
+sal_Int32 ScImportSourceDesc::GetCommandType() const
+{
+ sal_Int32 nSdbType = -1;
+
+ switch ( nType )
+ {
+ case sheet::DataImportMode_SQL: nSdbType = sdb::CommandType::COMMAND; break;
+ case sheet::DataImportMode_TABLE: nSdbType = sdb::CommandType::TABLE; break;
+ case sheet::DataImportMode_QUERY: nSdbType = sdb::CommandType::QUERY; break;
+ default:
+ ;
+ }
+ return nSdbType;
+}
+
+const ScDPCache* ScImportSourceDesc::CreateCache(const ScDPDimensionSaveData* pDimData) const
+{
+ if (!mpDoc)
+ return nullptr;
+
+ sal_Int32 nSdbType = GetCommandType();
+ if (nSdbType < 0)
+ return nullptr;
+
+ ScDPCollection::DBCaches& rCaches = mpDoc->GetDPCollection()->GetDBCaches();
+ return rCaches.getCache(nSdbType, aDBName, aObject, pDimData);
+}
+
+ScDatabaseDPData::ScDatabaseDPData(
+ const ScDocument* pDoc, const ScDPCache& rCache) :
+ ScDPTableData(pDoc),
+ aCacheTable(rCache)
+{
+}
+
+ScDatabaseDPData::~ScDatabaseDPData()
+{
+}
+
+void ScDatabaseDPData::DisposeData()
+{
+ //TODO: use OpenDatabase here?
+ aCacheTable.clear();
+}
+
+sal_Int32 ScDatabaseDPData::GetColumnCount()
+{
+ CreateCacheTable();
+ return GetCacheTable().getColSize();
+}
+
+OUString ScDatabaseDPData::getDimensionName(sal_Int32 nColumn)
+{
+ if (getIsDataLayoutDimension(nColumn))
+ {
+ //TODO: different internal and display names?
+ //return "Data";
+ return ScResId(STR_PIVOT_DATA);
+ }
+
+ CreateCacheTable();
+ return aCacheTable.getFieldName(static_cast<SCCOL>(nColumn));
+}
+
+bool ScDatabaseDPData::getIsDataLayoutDimension(sal_Int32 nColumn)
+{
+ return ( nColumn == GetCacheTable().getColSize());
+}
+
+bool ScDatabaseDPData::IsDateDimension(sal_Int32 /* nDim */)
+{
+ //TODO: later...
+ return false;
+}
+
+void ScDatabaseDPData::SetEmptyFlags( bool /* bIgnoreEmptyRows */, bool /* bRepeatIfEmpty */ )
+{
+ // not used for database data
+ //TODO: disable flags
+}
+
+void ScDatabaseDPData::CreateCacheTable()
+{
+ if (!aCacheTable.empty())
+ // cache table already created.
+ return;
+
+ aCacheTable.fillTable();
+}
+
+void ScDatabaseDPData::FilterCacheTable(std::vector<ScDPFilteredCache::Criterion>&& rCriteria, std::unordered_set<sal_Int32>&& rCatDims)
+{
+ CreateCacheTable();
+ aCacheTable.filterByPageDimension(
+ rCriteria, (IsRepeatIfEmpty() ? std::move(rCatDims) : std::unordered_set<sal_Int32>()));
+}
+
+void ScDatabaseDPData::GetDrillDownData(std::vector<ScDPFilteredCache::Criterion>&& rCriteria, std::unordered_set<sal_Int32>&& rCatDims, Sequence< Sequence<Any> >& rData)
+{
+ CreateCacheTable();
+ sal_Int32 nRowSize = aCacheTable.getRowSize();
+ if (!nRowSize)
+ return;
+
+ aCacheTable.filterTable(
+ rCriteria, rData, IsRepeatIfEmpty() ? std::move(rCatDims) : std::unordered_set<sal_Int32>());
+}
+
+void ScDatabaseDPData::CalcResults(CalcInfo& rInfo, bool bAutoShow)
+{
+ CreateCacheTable();
+ CalcResultsFromCacheTable( aCacheTable, rInfo, bAutoShow);
+}
+
+const ScDPFilteredCache& ScDatabaseDPData::GetCacheTable() const
+{
+ return aCacheTable;
+}
+
+void ScDatabaseDPData::ReloadCacheTable()
+{
+ aCacheTable.clear();
+ CreateCacheTable();
+}
+
+#if DUMP_PIVOT_TABLE
+
+void ScDatabaseDPData::Dump() const
+{
+ // TODO : Implement this.
+}
+
+#endif
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sc/source/core/data/dpshttab.cxx b/sc/source/core/data/dpshttab.cxx
new file mode 100644
index 000000000..a5fd1bd01
--- /dev/null
+++ b/sc/source/core/data/dpshttab.cxx
@@ -0,0 +1,323 @@
+/* -*- 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 <svl/numformat.hxx>
+#include <svl/zforlist.hxx>
+#include <unotools/charclass.hxx>
+
+#include <dpcache.hxx>
+#include <dpshttab.hxx>
+#include <document.hxx>
+#include <dpfilteredcache.hxx>
+#include <dpobject.hxx>
+#include <globstr.hrc>
+#include <scresid.hxx>
+#include <rangenam.hxx>
+#include <queryentry.hxx>
+
+#include <osl/diagnose.h>
+
+#include <vector>
+
+using namespace ::com::sun::star;
+using ::com::sun::star::uno::Any;
+using ::com::sun::star::uno::Sequence;
+using ::std::vector;
+
+ScSheetDPData::ScSheetDPData(const ScDocument* pD, const ScSheetSourceDesc& rDesc, const ScDPCache& rCache) :
+ ScDPTableData(pD),
+ aQuery ( rDesc.GetQueryParam() ),
+ bIgnoreEmptyRows( false ),
+ bRepeatIfEmpty(false),
+ aCacheTable(rCache)
+{
+ SCSIZE nEntryCount( aQuery.GetEntryCount());
+ for (SCSIZE j = 0; j < nEntryCount; ++j)
+ {
+ ScQueryEntry& rEntry = aQuery.GetEntry(j);
+ if (rEntry.bDoQuery)
+ {
+ ScQueryEntry::Item& rItem = rEntry.GetQueryItem();
+ if (rItem.meType == ScQueryEntry::ByString)
+ {
+ sal_uInt32 nIndex = 0;
+ bool bNumber = pD->GetFormatTable()->IsNumberFormat(
+ rItem.maString.getString(), nIndex, rItem.mfVal);
+ rItem.meType = bNumber ? ScQueryEntry::ByValue : ScQueryEntry::ByString;
+ }
+ }
+ }
+}
+
+ScSheetDPData::~ScSheetDPData()
+{
+}
+
+void ScSheetDPData::DisposeData()
+{
+ aCacheTable.clear();
+}
+
+sal_Int32 ScSheetDPData::GetColumnCount()
+{
+ CreateCacheTable();
+ return aCacheTable.getColSize();
+}
+
+OUString ScSheetDPData::getDimensionName(sal_Int32 nColumn)
+{
+ CreateCacheTable();
+ if (getIsDataLayoutDimension(nColumn))
+ {
+ //TODO: different internal and display names?
+ //return "Data";
+ return ScResId(STR_PIVOT_DATA);
+ }
+ else if (nColumn >= aCacheTable.getColSize())
+ {
+ OSL_FAIL("getDimensionName: invalid dimension");
+ return OUString();
+ }
+ else
+ {
+ return aCacheTable.getFieldName(static_cast<SCCOL>(nColumn));
+ }
+}
+
+bool ScSheetDPData::IsDateDimension(sal_Int32 nDim)
+{
+ CreateCacheTable();
+ tools::Long nColCount = aCacheTable.getColSize();
+ if (getIsDataLayoutDimension(nDim))
+ {
+ return false;
+ }
+ else if (nDim >= nColCount)
+ {
+ OSL_FAIL("IsDateDimension: invalid dimension");
+ return false;
+ }
+ else
+ {
+ return GetCacheTable().getCache().IsDateDimension( nDim);
+ }
+}
+
+sal_uInt32 ScSheetDPData::GetNumberFormat(sal_Int32 nDim)
+{
+ CreateCacheTable();
+ if (getIsDataLayoutDimension(nDim))
+ {
+ return 0;
+ }
+ else if (nDim >= GetCacheTable().getColSize())
+ {
+ OSL_FAIL("GetNumberFormat: invalid dimension");
+ return 0;
+ }
+ else
+ {
+ return GetCacheTable().getCache().GetNumberFormat( nDim );
+ }
+}
+sal_uInt32 ScDPTableData::GetNumberFormatByIdx( NfIndexTableOffset eIdx )
+{
+ if( !mpDoc )
+ return 0;
+
+ if ( SvNumberFormatter* pFormatter = mpDoc->GetFormatTable() )
+ return pFormatter->GetFormatIndex( eIdx, LANGUAGE_SYSTEM );
+
+ return 0;
+}
+
+bool ScSheetDPData::getIsDataLayoutDimension(sal_Int32 nColumn)
+{
+ CreateCacheTable();
+ return (nColumn ==static_cast<tools::Long>( aCacheTable.getColSize()));
+}
+
+void ScSheetDPData::SetEmptyFlags( bool bIgnoreEmptyRowsP, bool bRepeatIfEmptyP )
+{
+ bIgnoreEmptyRows = bIgnoreEmptyRowsP;
+ bRepeatIfEmpty = bRepeatIfEmptyP;
+}
+
+bool ScSheetDPData::IsRepeatIfEmpty()
+{
+ return bRepeatIfEmpty;
+}
+
+void ScSheetDPData::CreateCacheTable()
+{
+ // Scan and store the data from the source range.
+ if (!aCacheTable.empty())
+ // already cached.
+ return;
+
+ aCacheTable.fillTable(aQuery, bIgnoreEmptyRows, bRepeatIfEmpty);
+}
+
+void ScSheetDPData::FilterCacheTable(std::vector<ScDPFilteredCache::Criterion>&& rCriteria, std::unordered_set<sal_Int32>&& rCatDims)
+{
+ CreateCacheTable();
+ aCacheTable.filterByPageDimension(
+ rCriteria, (IsRepeatIfEmpty() ? rCatDims : std::unordered_set<sal_Int32>()));
+}
+
+void ScSheetDPData::GetDrillDownData(std::vector<ScDPFilteredCache::Criterion>&& rCriteria, std::unordered_set<sal_Int32>&& rCatDims, Sequence< Sequence<Any> >& rData)
+{
+ CreateCacheTable();
+ sal_Int32 nRowSize = aCacheTable.getRowSize();
+ if (!nRowSize)
+ return;
+
+ aCacheTable.filterTable(
+ rCriteria, rData, IsRepeatIfEmpty() ? rCatDims : std::unordered_set<sal_Int32>());
+}
+
+void ScSheetDPData::CalcResults(CalcInfo& rInfo, bool bAutoShow)
+{
+ CreateCacheTable();
+ CalcResultsFromCacheTable(aCacheTable, rInfo, bAutoShow);
+}
+
+const ScDPFilteredCache& ScSheetDPData::GetCacheTable() const
+{
+ return aCacheTable;
+}
+
+void ScSheetDPData::ReloadCacheTable()
+{
+ aCacheTable.clear();
+ CreateCacheTable();
+}
+
+#if DUMP_PIVOT_TABLE
+
+void ScSheetDPData::Dump() const
+{
+ // TODO : Implement this.
+}
+
+#endif
+
+ScSheetSourceDesc::ScSheetSourceDesc(ScDocument* pDoc) :
+ mpDoc(pDoc) {}
+
+void ScSheetSourceDesc::SetSourceRange(const ScRange& rRange)
+{
+ maSourceRange = rRange;
+ maRangeName.clear(); // overwrite existing range name if any.
+}
+
+const ScRange& ScSheetSourceDesc::GetSourceRange() const
+{
+ if (!maRangeName.isEmpty())
+ {
+ // Obtain the source range from the range name first.
+ maSourceRange = ScRange();
+ ScRangeName* pRangeName = mpDoc->GetRangeName();
+ do
+ {
+ if (!pRangeName)
+ break;
+
+ OUString aUpper = ScGlobal::getCharClass().uppercase(maRangeName);
+ const ScRangeData* pData = pRangeName->findByUpperName(aUpper);
+ if (!pData)
+ break;
+
+ // range name found. Fow now, we only use the first token and
+ // ignore the rest.
+ ScRange aRange;
+ if (!pData->IsReference(aRange))
+ break;
+
+ maSourceRange = aRange;
+ }
+ while (false);
+ }
+ return maSourceRange;
+}
+
+void ScSheetSourceDesc::SetRangeName(const OUString& rName)
+{
+ maRangeName = rName;
+}
+
+bool ScSheetSourceDesc::HasRangeName() const
+{
+ return !maRangeName.isEmpty();
+}
+
+void ScSheetSourceDesc::SetQueryParam(const ScQueryParam& rParam)
+{
+ maQueryParam = rParam;
+}
+
+bool ScSheetSourceDesc::operator== (const ScSheetSourceDesc& rOther) const
+{
+ return maSourceRange == rOther.maSourceRange &&
+ maRangeName == rOther.maRangeName &&
+ maQueryParam == rOther.maQueryParam;
+}
+
+const ScDPCache* ScSheetSourceDesc::CreateCache(const ScDPDimensionSaveData* pDimData) const
+{
+ if (!mpDoc)
+ return nullptr;
+
+ TranslateId pErrId = CheckSourceRange();
+ if (pErrId)
+ {
+ OSL_FAIL( "Error Create Cache" );
+ return nullptr;
+ }
+
+ // All cache instances are managed centrally by ScDPCollection.
+ ScDPCollection* pDPs = mpDoc->GetDPCollection();
+ if (HasRangeName())
+ {
+ // Name-based data source.
+ ScDPCollection::NameCaches& rCaches = pDPs->GetNameCaches();
+ return rCaches.getCache(GetRangeName(), GetSourceRange(), pDimData);
+ }
+
+ ScDPCollection::SheetCaches& rCaches = pDPs->GetSheetCaches();
+ return rCaches.getCache(GetSourceRange(), pDimData);
+}
+
+TranslateId ScSheetSourceDesc::CheckSourceRange() const
+{
+ if (!mpDoc)
+ return STR_ERR_DATAPILOTSOURCE;
+
+ // Make sure the range is valid and sane.
+ const ScRange& rSrcRange = GetSourceRange();
+ if (!rSrcRange.IsValid())
+ return STR_ERR_DATAPILOTSOURCE;
+
+ if (rSrcRange.aStart.Col() > rSrcRange.aEnd.Col() || rSrcRange.aStart.Row() > rSrcRange.aEnd.Row())
+ return STR_ERR_DATAPILOTSOURCE;
+
+ return {};
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sc/source/core/data/dptabdat.cxx b/sc/source/core/data/dptabdat.cxx
new file mode 100644
index 000000000..7b3c80712
--- /dev/null
+++ b/sc/source/core/data/dptabdat.cxx
@@ -0,0 +1,292 @@
+/* -*- 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 <dptabdat.hxx>
+#include <dpcache.hxx>
+#include <dpfilteredcache.hxx>
+#include <dptabres.hxx>
+
+#include <osl/diagnose.h>
+#include <tools/date.hxx>
+
+
+using namespace ::com::sun::star;
+using ::std::vector;
+
+ScDPTableData::CalcInfo::CalcInfo() :
+ pInitState( nullptr ),
+ pColRoot( nullptr ),
+ pRowRoot( nullptr )
+{
+}
+
+ScDPTableData::ScDPTableData(const ScDocument* pDoc) :
+ mpDoc(pDoc)
+{
+ nLastDateVal = nLastHier = nLastLevel = nLastRet = -1; // invalid
+
+ //TODO: reset before new calculation (in case the base date is changed)
+}
+
+ScDPTableData::~ScDPTableData()
+{
+}
+
+OUString ScDPTableData::GetFormattedString(sal_Int32 nDim, const ScDPItemData& rItem, bool bLocaleIndependent) const
+{
+ const ScDPCache& rCache = GetCacheTable().getCache();
+ return rCache.GetFormattedString(nDim, rItem, bLocaleIndependent);
+}
+
+tools::Long ScDPTableData::GetDatePart( tools::Long nDateVal, tools::Long nHierarchy, tools::Long nLevel )
+{
+ if ( nDateVal == nLastDateVal && nHierarchy == nLastHier && nLevel == nLastLevel )
+ return nLastRet;
+
+ Date aDate( 30,12,1899 ); //TODO: get from source data (and cache here)
+ aDate.AddDays( nDateVal);
+
+ tools::Long nRet = 0;
+ switch (nHierarchy)
+ {
+ case SC_DAPI_HIERARCHY_QUARTER:
+ switch (nLevel)
+ {
+ case 0: nRet = aDate.GetYear(); break;
+ case 1: nRet = (aDate.GetMonth()-1) / 3 + 1; break;
+ case 2: nRet = aDate.GetMonth(); break;
+ case 3: nRet = aDate.GetDay(); break;
+ default:
+ OSL_FAIL("GetDatePart: wrong level");
+ }
+ break;
+ case SC_DAPI_HIERARCHY_WEEK:
+ switch (nLevel)
+ {
+ //TODO: use settings for different definitions
+ case 0: nRet = aDate.GetYear(); break; //!...
+ case 1: nRet = aDate.GetWeekOfYear(); break;
+ case 2: nRet = static_cast<tools::Long>(aDate.GetDayOfWeek()); break;
+ default:
+ OSL_FAIL("GetDatePart: wrong level");
+ }
+ break;
+ default:
+ OSL_FAIL("GetDatePart: wrong hierarchy");
+ }
+
+ nLastDateVal = nDateVal;
+ nLastHier = nHierarchy;
+ nLastLevel = nLevel;
+ nLastRet = nRet;
+
+ return nRet;
+}
+
+bool ScDPTableData::IsRepeatIfEmpty()
+{
+ return false;
+}
+
+sal_uInt32 ScDPTableData::GetNumberFormat(sal_Int32)
+{
+ return 0; // default format
+}
+
+bool ScDPTableData::IsBaseForGroup(sal_Int32) const
+{
+ return false; // always false
+}
+
+sal_Int32 ScDPTableData::GetGroupBase(sal_Int32) const
+{
+ return -1; // always none
+}
+
+bool ScDPTableData::IsNumOrDateGroup(sal_Int32) const
+{
+ return false; // always false
+}
+
+bool ScDPTableData::IsInGroup( const ScDPItemData&, sal_Int32,
+ const ScDPItemData&, sal_Int32 ) const
+{
+ OSL_FAIL("IsInGroup shouldn't be called for non-group data");
+ return false;
+}
+
+bool ScDPTableData::HasCommonElement( const ScDPItemData&, sal_Int32,
+ const ScDPItemData&, sal_Int32 ) const
+{
+ OSL_FAIL("HasCommonElement shouldn't be called for non-group data");
+ return false;
+}
+void ScDPTableData::FillRowDataFromCacheTable(sal_Int32 nRow, const ScDPFilteredCache& rCacheTable,
+ const CalcInfo& rInfo, CalcRowData& rData)
+{
+ // column dimensions
+ GetItemData(rCacheTable, nRow, rInfo.aColLevelDims, rData.aColData);
+
+ // row dimensions
+ GetItemData(rCacheTable, nRow, rInfo.aRowLevelDims, rData.aRowData);
+
+ // page dimensions
+ GetItemData(rCacheTable, nRow, rInfo.aPageDims, rData.aPageData);
+
+ tools::Long nCacheColumnCount = rCacheTable.getCache().GetColumnCount();
+ sal_Int32 n = rInfo.aDataSrcCols.size();
+ for (sal_Int32 i = 0; i < n; ++i)
+ {
+ tools::Long nDim = rInfo.aDataSrcCols[i];
+ rData.aValues.emplace_back( );
+ // #i111435# GetItemData needs dimension indexes including groups,
+ // so the index must be checked here (groups aren't useful as data fields).
+ if ( nDim < nCacheColumnCount )
+ {
+ ScDPValue& rVal = rData.aValues.back();
+ rCacheTable.getValue( rVal, static_cast<SCCOL>(nDim), static_cast<SCROW>(nRow));
+ }
+ }
+}
+
+void ScDPTableData::ProcessRowData(CalcInfo& rInfo, const CalcRowData& rData, bool bAutoShow)
+{
+ if (!bAutoShow)
+ {
+ LateInitParams aColParams(rInfo.aColDims, rInfo.aColLevels, false);
+ LateInitParams aRowParams(rInfo.aRowDims, rInfo.aRowLevels, true);
+ // root always init child
+ aColParams.SetInitChild(true);
+ aColParams.SetInitAllChildren( false);
+ aRowParams.SetInitChild(true);
+ aRowParams.SetInitAllChildren( false);
+
+ rInfo.pColRoot->LateInitFrom(aColParams, rData.aColData, 0, *rInfo.pInitState);
+ rInfo.pRowRoot->LateInitFrom(aRowParams, rData.aRowData, 0, *rInfo.pInitState);
+ }
+
+ if ( ( !rInfo.pColRoot->GetChildDimension() || rInfo.pColRoot->GetChildDimension()->IsValidEntry(rData.aColData) ) &&
+ ( !rInfo.pRowRoot->GetChildDimension() || rInfo.pRowRoot->GetChildDimension()->IsValidEntry(rData.aRowData) ) )
+ {
+ //TODO: single process method with ColMembers, RowMembers and data !!!
+ if (rInfo.pColRoot->GetChildDimension())
+ {
+ vector<SCROW> aEmptyData;
+ rInfo.pColRoot->GetChildDimension()->ProcessData(rData.aColData, nullptr, aEmptyData, rData.aValues);
+ }
+
+ rInfo.pRowRoot->ProcessData(rData.aRowData, rInfo.pColRoot->GetChildDimension(),
+ rData.aColData, rData.aValues);
+ }
+}
+
+void ScDPTableData::CalcResultsFromCacheTable(const ScDPFilteredCache& rCacheTable, CalcInfo& rInfo, bool bAutoShow)
+{
+ sal_Int32 nRowSize = rCacheTable.getRowSize();
+ for (sal_Int32 nRow = 0; nRow < nRowSize; ++nRow)
+ {
+ sal_Int32 nLastRow;
+ if (!rCacheTable.isRowActive(nRow, &nLastRow))
+ {
+ nRow = nLastRow;
+ continue;
+ }
+
+ CalcRowData aData;
+ FillRowDataFromCacheTable(nRow, rCacheTable, rInfo, aData);
+ ProcessRowData(rInfo, aData, bAutoShow);
+ }
+}
+
+void ScDPTableData::GetItemData(const ScDPFilteredCache& rCacheTable, sal_Int32 nRow,
+ const vector<sal_Int32>& rDims, vector<SCROW>& rItemData)
+{
+ sal_Int32 nDimSize = rDims.size();
+ rItemData.reserve(rItemData.size() + nDimSize);
+ for (sal_Int32 i = 0; i < nDimSize; ++i)
+ {
+ sal_Int32 nDim = rDims[i];
+
+ if (getIsDataLayoutDimension(nDim))
+ {
+ rItemData.push_back( -1 );
+ continue;
+ }
+
+ nDim = GetSourceDim( nDim );
+ if ( nDim >= rCacheTable.getCache().GetColumnCount() )
+ continue;
+
+ SCROW nId= rCacheTable.getCache().GetItemDataId( static_cast<SCCOL>(nDim), static_cast<SCROW>(nRow), IsRepeatIfEmpty());
+ rItemData.push_back( nId );
+ }
+}
+
+sal_Int32 ScDPTableData::GetMembersCount( sal_Int32 nDim )
+{
+ if ( nDim > MAXCOL )
+ return 0;
+ return GetCacheTable().getFieldEntries( nDim ).size();
+}
+
+const ScDPItemData* ScDPTableData::GetMemberByIndex( sal_Int32 nDim, sal_Int32 nIndex )
+{
+ if ( nIndex >= GetMembersCount( nDim ) )
+ return nullptr;
+
+ const ::std::vector<SCROW>& nMembers = GetCacheTable().getFieldEntries( nDim );
+
+ return GetCacheTable().getCache().GetItemDataById( static_cast<SCCOL>(nDim), static_cast<SCROW>(nMembers[nIndex]) );
+}
+
+const ScDPItemData* ScDPTableData::GetMemberById( sal_Int32 nDim, sal_Int32 nId)
+{
+ return GetCacheTable().getCache().GetItemDataById(nDim, static_cast<SCROW>(nId));
+}
+
+const std::vector< SCROW >& ScDPTableData::GetColumnEntries( sal_Int32 nColumn )
+{
+ return GetCacheTable().getFieldEntries( nColumn );
+}
+
+sal_Int32 ScDPTableData::GetSourceDim( sal_Int32 nDim )
+{
+ return nDim;
+}
+
+sal_Int32 ScDPTableData::Compare( sal_Int32 nDim, sal_Int32 nDataId1, sal_Int32 nDataId2)
+{
+ if ( getIsDataLayoutDimension(nDim) )
+ return 0;
+
+ if ( nDataId1 > nDataId2 )
+ return 1;
+ else if ( nDataId1 == nDataId2 )
+ return 0;
+ else
+ return -1;
+}
+
+#if DUMP_PIVOT_TABLE
+void ScDPTableData::Dump() const
+{
+}
+#endif
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sc/source/core/data/dptabres.cxx b/sc/source/core/data/dptabres.cxx
new file mode 100644
index 000000000..1037eac1c
--- /dev/null
+++ b/sc/source/core/data/dptabres.cxx
@@ -0,0 +1,4117 @@
+/* -*- 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 <dptabres.hxx>
+
+#include <dptabdat.hxx>
+#include <dptabsrc.hxx>
+#include <global.hxx>
+#include <subtotal.hxx>
+#include <globstr.hrc>
+#include <scresid.hxx>
+#include <dpitemdata.hxx>
+#include <generalfunction.hxx>
+
+#include <document.hxx>
+#include <dpresfilter.hxx>
+#include <dputil.hxx>
+
+#include <o3tl/safeint.hxx>
+#include <osl/diagnose.h>
+#include <rtl/math.hxx>
+#include <sal/log.hxx>
+
+#include <math.h>
+#include <float.h>
+#include <algorithm>
+#include <limits>
+#include <memory>
+#include <unordered_map>
+
+#include <com/sun/star/sheet/DataResultFlags.hpp>
+#include <com/sun/star/sheet/MemberResultFlags.hpp>
+#include <com/sun/star/sheet/DataPilotFieldReferenceType.hpp>
+#include <com/sun/star/sheet/DataPilotFieldReferenceItemType.hpp>
+#include <com/sun/star/sheet/DataPilotFieldShowItemsMode.hpp>
+#include <com/sun/star/sheet/DataPilotFieldSortMode.hpp>
+#include <com/sun/star/sheet/GeneralFunction2.hpp>
+
+using namespace com::sun::star;
+using ::std::vector;
+using ::std::pair;
+using ::com::sun::star::uno::Sequence;
+
+namespace {
+
+const TranslateId aFuncStrIds[] = // matching enum ScSubTotalFunc
+{
+ {}, // SUBTOTAL_FUNC_NONE
+ STR_FUN_TEXT_AVG, // SUBTOTAL_FUNC_AVE
+ STR_FUN_TEXT_COUNT, // SUBTOTAL_FUNC_CNT
+ STR_FUN_TEXT_COUNT, // SUBTOTAL_FUNC_CNT2
+ STR_FUN_TEXT_MAX, // SUBTOTAL_FUNC_MAX
+ STR_FUN_TEXT_MIN, // SUBTOTAL_FUNC_MIN
+ STR_FUN_TEXT_PRODUCT, // SUBTOTAL_FUNC_PROD
+ STR_FUN_TEXT_STDDEV, // SUBTOTAL_FUNC_STD
+ STR_FUN_TEXT_STDDEV, // SUBTOTAL_FUNC_STDP
+ STR_FUN_TEXT_SUM, // SUBTOTAL_FUNC_SUM
+ STR_FUN_TEXT_VAR, // SUBTOTAL_FUNC_VAR
+ STR_FUN_TEXT_VAR, // SUBTOTAL_FUNC_VARP
+ STR_FUN_TEXT_MEDIAN, // SUBTOTAL_FUNC_MED
+ {} // SUBTOTAL_FUNC_SELECTION_COUNT - not used for pivot table
+};
+
+bool lcl_SearchMember( const std::vector<std::unique_ptr<ScDPResultMember>>& list, SCROW nOrder, SCROW& rIndex)
+{
+ bool bFound = false;
+ SCROW nLo = 0;
+ SCROW nHi = list.size() - 1;
+ SCROW nIndex;
+ while (nLo <= nHi)
+ {
+ nIndex = (nLo + nHi) / 2;
+ if ( list[nIndex]->GetOrder() < nOrder )
+ nLo = nIndex + 1;
+ else
+ {
+ nHi = nIndex - 1;
+ if ( list[nIndex]->GetOrder() == nOrder )
+ {
+ bFound = true;
+ nLo = nIndex;
+ }
+ }
+ }
+ rIndex = nLo;
+ return bFound;
+}
+
+class FilterStack
+{
+ std::vector<ScDPResultFilter>& mrFilters;
+public:
+ explicit FilterStack(std::vector<ScDPResultFilter>& rFilters) : mrFilters(rFilters) {}
+
+ void pushDimName(const OUString& rName, bool bDataLayout)
+ {
+ mrFilters.emplace_back(rName, bDataLayout);
+ }
+
+ void pushDimValue(const OUString& rValueName, const OUString& rValue)
+ {
+ ScDPResultFilter& rFilter = mrFilters.back();
+ rFilter.maValueName = rValueName;
+ rFilter.maValue = rValue;
+ rFilter.mbHasValue = true;
+ }
+
+ ~FilterStack()
+ {
+ ScDPResultFilter& rFilter = mrFilters.back();
+ if (rFilter.mbHasValue)
+ rFilter.mbHasValue = false;
+ else
+ mrFilters.pop_back();
+ }
+};
+
+// function objects for sorting of the column and row members:
+
+class ScDPRowMembersOrder
+{
+ ScDPResultDimension& rDimension;
+ tools::Long nMeasure;
+ bool bAscending;
+
+public:
+ ScDPRowMembersOrder( ScDPResultDimension& rDim, tools::Long nM, bool bAsc ) :
+ rDimension(rDim),
+ nMeasure(nM),
+ bAscending(bAsc)
+ {}
+
+ bool operator()( sal_Int32 nIndex1, sal_Int32 nIndex2 ) const;
+};
+
+class ScDPColMembersOrder
+{
+ ScDPDataDimension& rDimension;
+ tools::Long nMeasure;
+ bool bAscending;
+
+public:
+ ScDPColMembersOrder( ScDPDataDimension& rDim, tools::Long nM, bool bAsc ) :
+ rDimension(rDim),
+ nMeasure(nM),
+ bAscending(bAsc)
+ {}
+
+ bool operator()( sal_Int32 nIndex1, sal_Int32 nIndex2 ) const;
+};
+
+}
+
+static bool lcl_IsLess( const ScDPDataMember* pDataMember1, const ScDPDataMember* pDataMember2, tools::Long nMeasure, bool bAscending )
+{
+ // members can be NULL if used for rows
+
+ ScDPSubTotalState aEmptyState;
+ const ScDPAggData* pAgg1 = pDataMember1 ? pDataMember1->GetConstAggData( nMeasure, aEmptyState ) : nullptr;
+ const ScDPAggData* pAgg2 = pDataMember2 ? pDataMember2->GetConstAggData( nMeasure, aEmptyState ) : nullptr;
+
+ bool bError1 = pAgg1 && pAgg1->HasError();
+ bool bError2 = pAgg2 && pAgg2->HasError();
+ if ( bError1 )
+ return false; // errors are always sorted at the end
+ else if ( bError2 )
+ return true; // errors are always sorted at the end
+ else
+ {
+ double fVal1 = ( pAgg1 && pAgg1->HasData() ) ? pAgg1->GetResult() : 0.0; // no data is sorted as 0
+ double fVal2 = ( pAgg2 && pAgg2->HasData() ) ? pAgg2->GetResult() : 0.0;
+
+ // compare values
+ // don't have to check approxEqual, as this is the only sort criterion
+
+ return bAscending ? ( fVal1 < fVal2 ) : ( fVal1 > fVal2 );
+ }
+}
+
+static bool lcl_IsEqual( const ScDPDataMember* pDataMember1, const ScDPDataMember* pDataMember2, tools::Long nMeasure )
+{
+ // members can be NULL if used for rows
+
+ ScDPSubTotalState aEmptyState;
+ const ScDPAggData* pAgg1 = pDataMember1 ? pDataMember1->GetConstAggData( nMeasure, aEmptyState ) : nullptr;
+ const ScDPAggData* pAgg2 = pDataMember2 ? pDataMember2->GetConstAggData( nMeasure, aEmptyState ) : nullptr;
+
+ bool bError1 = pAgg1 && pAgg1->HasError();
+ bool bError2 = pAgg2 && pAgg2->HasError();
+ if ( bError1 )
+ {
+ if ( bError2 )
+ return true; // equal
+ else
+ return false;
+ }
+ else if ( bError2 )
+ return false;
+ else
+ {
+ double fVal1 = ( pAgg1 && pAgg1->HasData() ) ? pAgg1->GetResult() : 0.0; // no data is sorted as 0
+ double fVal2 = ( pAgg2 && pAgg2->HasData() ) ? pAgg2->GetResult() : 0.0;
+
+ // compare values
+ // this is used to find equal data at the end of the AutoShow range, so approxEqual must be used
+
+ return rtl::math::approxEqual( fVal1, fVal2 );
+ }
+}
+
+bool ScDPRowMembersOrder::operator()( sal_Int32 nIndex1, sal_Int32 nIndex2 ) const
+{
+ const ScDPResultMember* pMember1 = rDimension.GetMember(nIndex1);
+ const ScDPResultMember* pMember2 = rDimension.GetMember(nIndex2);
+
+// make the hide item to the largest order.
+ if ( !pMember1->IsVisible() || !pMember2->IsVisible() )
+ return pMember1->IsVisible();
+ const ScDPDataMember* pDataMember1 = pMember1->GetDataRoot() ;
+ const ScDPDataMember* pDataMember2 = pMember2->GetDataRoot();
+ // GetDataRoot can be NULL if there was no data.
+ // IsVisible == false can happen after AutoShow.
+ return lcl_IsLess( pDataMember1, pDataMember2, nMeasure, bAscending );
+}
+
+bool ScDPColMembersOrder::operator()( sal_Int32 nIndex1, sal_Int32 nIndex2 ) const
+{
+ const ScDPDataMember* pDataMember1 = rDimension.GetMember(nIndex1);
+ const ScDPDataMember* pDataMember2 = rDimension.GetMember(nIndex2);
+ bool bHide1 = pDataMember1 && !pDataMember1->IsVisible();
+ bool bHide2 = pDataMember2 && !pDataMember2->IsVisible();
+ if ( bHide1 || bHide2 )
+ return !bHide1;
+ return lcl_IsLess( pDataMember1, pDataMember2, nMeasure, bAscending );
+}
+
+ScDPInitState::Member::Member(tools::Long nSrcIndex, SCROW nNameIndex) :
+ mnSrcIndex(nSrcIndex), mnNameIndex(nNameIndex) {}
+
+void ScDPInitState::AddMember( tools::Long nSourceIndex, SCROW nMember )
+{
+ maMembers.emplace_back(nSourceIndex, nMember);
+}
+
+void ScDPInitState::RemoveMember()
+{
+ OSL_ENSURE(!maMembers.empty(), "ScDPInitState::RemoveMember: Attempt to remove member while empty.");
+ if (!maMembers.empty())
+ maMembers.pop_back();
+}
+
+namespace {
+
+#if DUMP_PIVOT_TABLE
+void dumpRow(
+ const OUString& rType, const OUString& rName, const ScDPAggData* pAggData,
+ ScDocument* pDoc, ScAddress& rPos )
+{
+ SCCOL nCol = rPos.Col();
+ SCROW nRow = rPos.Row();
+ SCTAB nTab = rPos.Tab();
+ pDoc->SetString( nCol++, nRow, nTab, rType );
+ pDoc->SetString( nCol++, nRow, nTab, rName );
+ while ( pAggData )
+ {
+ pDoc->SetValue( nCol++, nRow, nTab, pAggData->GetResult() );
+ pAggData = pAggData->GetExistingChild();
+ }
+ rPos.SetRow( nRow + 1 );
+}
+
+void indent( ScDocument* pDoc, SCROW nStartRow, const ScAddress& rPos )
+{
+ SCCOL nCol = rPos.Col();
+ SCTAB nTab = rPos.Tab();
+
+ OUString aString;
+ for (SCROW nRow = nStartRow; nRow < rPos.Row(); nRow++)
+ {
+ aString = pDoc->GetString(nCol, nRow, nTab);
+ if (!aString.isEmpty())
+ {
+ aString = " " + aString;
+ pDoc->SetString( nCol, nRow, nTab, aString );
+ }
+ }
+}
+#endif
+
+}
+
+ScDPRunningTotalState::ScDPRunningTotalState( ScDPResultMember* pColRoot, ScDPResultMember* pRowRoot ) :
+ pColResRoot(pColRoot), pRowResRoot(pRowRoot)
+{
+ // These arrays should never be empty as the terminating value must be present at all times.
+ maColVisible.push_back(-1);
+ maColSorted.push_back(-1);
+ maRowVisible.push_back(-1);
+ maRowSorted.push_back(-1);
+}
+
+void ScDPRunningTotalState::AddColIndex( sal_Int32 nVisible, tools::Long nSorted )
+{
+ maColVisible.back() = nVisible;
+ maColVisible.push_back(-1);
+
+ maColSorted.back() = nSorted;
+ maColSorted.push_back(-1);
+}
+
+void ScDPRunningTotalState::AddRowIndex( sal_Int32 nVisible, tools::Long nSorted )
+{
+ maRowVisible.back() = nVisible;
+ maRowVisible.push_back(-1);
+
+ maRowSorted.back() = nSorted;
+ maRowSorted.push_back(-1);
+}
+
+void ScDPRunningTotalState::RemoveColIndex()
+{
+ OSL_ENSURE(!maColVisible.empty() && !maColSorted.empty(), "ScDPRunningTotalState::RemoveColIndex: array is already empty!");
+ if (maColVisible.size() >= 2)
+ {
+ maColVisible.pop_back();
+ maColVisible.back() = -1;
+ }
+
+ if (maColSorted.size() >= 2)
+ {
+ maColSorted.pop_back();
+ maColSorted.back() = -1;
+ }
+}
+
+void ScDPRunningTotalState::RemoveRowIndex()
+{
+ OSL_ENSURE(!maRowVisible.empty() && !maRowSorted.empty(), "ScDPRunningTotalState::RemoveRowIndex: array is already empty!");
+ if (maRowVisible.size() >= 2)
+ {
+ maRowVisible.pop_back();
+ maRowVisible.back() = -1;
+ }
+
+ if (maRowSorted.size() >= 2)
+ {
+ maRowSorted.pop_back();
+ maRowSorted.back() = -1;
+ }
+}
+
+ScDPRelativePos::ScDPRelativePos( tools::Long nBase, tools::Long nDir ) :
+ nBasePos( nBase ),
+ nDirection( nDir )
+{
+}
+
+void ScDPAggData::Update( const ScDPValue& rNext, ScSubTotalFunc eFunc, const ScDPSubTotalState& rSubState )
+{
+ if (nCount<0) // error?
+ return; // nothing more...
+
+ if (rNext.meType == ScDPValue::Empty)
+ return;
+
+ if ( rSubState.eColForce != SUBTOTAL_FUNC_NONE && rSubState.eRowForce != SUBTOTAL_FUNC_NONE &&
+ rSubState.eColForce != rSubState.eRowForce )
+ return;
+ if ( rSubState.eColForce != SUBTOTAL_FUNC_NONE ) eFunc = rSubState.eColForce;
+ if ( rSubState.eRowForce != SUBTOTAL_FUNC_NONE ) eFunc = rSubState.eRowForce;
+
+ if ( eFunc == SUBTOTAL_FUNC_NONE )
+ return;
+
+ if ( eFunc != SUBTOTAL_FUNC_CNT2 ) // CNT2 counts everything, incl. strings and errors
+ {
+ if (rNext.meType == ScDPValue::Error)
+ {
+ nCount = -1; // -1 for error (not for CNT2)
+ return;
+ }
+ if (rNext.meType == ScDPValue::String)
+ return; // ignore
+ }
+
+ ++nCount; // for all functions
+
+ switch (eFunc)
+ {
+ case SUBTOTAL_FUNC_SUM:
+ case SUBTOTAL_FUNC_AVE:
+ if ( !SubTotal::SafePlus( fVal, rNext.mfValue ) )
+ nCount = -1; // -1 for error
+ break;
+ case SUBTOTAL_FUNC_PROD:
+ if ( nCount == 1 ) // copy first value (fVal is initialized to 0)
+ fVal = rNext.mfValue;
+ else if ( !SubTotal::SafeMult( fVal, rNext.mfValue ) )
+ nCount = -1; // -1 for error
+ break;
+ case SUBTOTAL_FUNC_CNT:
+ case SUBTOTAL_FUNC_CNT2:
+ // nothing more than incrementing nCount
+ break;
+ case SUBTOTAL_FUNC_MAX:
+ if ( nCount == 1 || rNext.mfValue > fVal )
+ fVal = rNext.mfValue;
+ break;
+ case SUBTOTAL_FUNC_MIN:
+ if ( nCount == 1 || rNext.mfValue < fVal )
+ fVal = rNext.mfValue;
+ break;
+ case SUBTOTAL_FUNC_STD:
+ case SUBTOTAL_FUNC_STDP:
+ case SUBTOTAL_FUNC_VAR:
+ case SUBTOTAL_FUNC_VARP:
+ maWelford.update( rNext.mfValue);
+ break;
+ case SUBTOTAL_FUNC_MED:
+ {
+ auto aIter = std::upper_bound(mSortedValues.begin(), mSortedValues.end(), rNext.mfValue);
+ if (aIter == mSortedValues.end())
+ mSortedValues.push_back(rNext.mfValue);
+ else
+ mSortedValues.insert(aIter, rNext.mfValue);
+ }
+ break;
+ default:
+ OSL_FAIL("invalid function");
+ }
+}
+
+void ScDPAggData::Calculate( ScSubTotalFunc eFunc, const ScDPSubTotalState& rSubState )
+{
+ // calculate the original result
+ // (without reference value, used as the basis for reference value calculation)
+
+ // called several times at the cross-section of several subtotals - don't calculate twice then
+ if ( IsCalculated() )
+ return;
+
+ if ( rSubState.eColForce != SUBTOTAL_FUNC_NONE ) eFunc = rSubState.eColForce;
+ if ( rSubState.eRowForce != SUBTOTAL_FUNC_NONE ) eFunc = rSubState.eRowForce;
+
+ if ( eFunc == SUBTOTAL_FUNC_NONE ) // this happens when there is no data dimension
+ {
+ nCount = SC_DPAGG_RESULT_EMPTY; // make sure there's a valid state for HasData etc.
+ return;
+ }
+
+ // check the error conditions for the selected function
+
+ bool bError = false;
+ switch (eFunc)
+ {
+ case SUBTOTAL_FUNC_SUM:
+ case SUBTOTAL_FUNC_PROD:
+ case SUBTOTAL_FUNC_CNT:
+ case SUBTOTAL_FUNC_CNT2:
+ bError = ( nCount < 0 ); // only real errors
+ break;
+
+ case SUBTOTAL_FUNC_AVE:
+ case SUBTOTAL_FUNC_MED:
+ case SUBTOTAL_FUNC_MAX:
+ case SUBTOTAL_FUNC_MIN:
+ bError = ( nCount <= 0 ); // no data is an error
+ break;
+
+ case SUBTOTAL_FUNC_STDP:
+ case SUBTOTAL_FUNC_VARP:
+ bError = ( nCount <= 0 ); // no data is an error
+ assert(bError || nCount == static_cast<sal_Int64>(maWelford.getCount()));
+ break;
+
+ case SUBTOTAL_FUNC_STD:
+ case SUBTOTAL_FUNC_VAR:
+ bError = ( nCount < 2 ); // need at least 2 values
+ assert(bError || nCount == static_cast<sal_Int64>(maWelford.getCount()));
+ break;
+
+ default:
+ OSL_FAIL("invalid function");
+ }
+
+ // calculate the selected function
+
+ double fResult = 0.0;
+ if ( !bError )
+ {
+ switch (eFunc)
+ {
+ case SUBTOTAL_FUNC_MAX:
+ case SUBTOTAL_FUNC_MIN:
+ case SUBTOTAL_FUNC_SUM:
+ case SUBTOTAL_FUNC_PROD:
+ // different error conditions are handled above
+ fResult = fVal;
+ break;
+
+ case SUBTOTAL_FUNC_CNT:
+ case SUBTOTAL_FUNC_CNT2:
+ fResult = nCount;
+ break;
+
+ case SUBTOTAL_FUNC_AVE:
+ if ( nCount > 0 )
+ fResult = fVal / static_cast<double>(nCount);
+ break;
+
+ case SUBTOTAL_FUNC_STD:
+ if ( nCount >= 2 )
+ {
+ fResult = maWelford.getVarianceSample();
+ if (fResult < 0.0)
+ bError = true;
+ else
+ fResult = sqrt( fResult);
+ }
+ break;
+ case SUBTOTAL_FUNC_VAR:
+ if ( nCount >= 2 )
+ fResult = maWelford.getVarianceSample();
+ break;
+ case SUBTOTAL_FUNC_STDP:
+ if ( nCount > 0 )
+ {
+ fResult = maWelford.getVariancePopulation();
+ if (fResult < 0.0)
+ bError = true;
+ else
+ fResult = sqrt( fResult);
+ }
+ break;
+ case SUBTOTAL_FUNC_VARP:
+ if ( nCount > 0 )
+ fResult = maWelford.getVariancePopulation();
+ break;
+ case SUBTOTAL_FUNC_MED:
+ {
+ size_t nSize = mSortedValues.size();
+ if (nSize > 0)
+ {
+ assert(nSize == static_cast<size_t>(nCount));
+ if ((nSize % 2) == 1)
+ fResult = mSortedValues[nSize / 2];
+ else
+ fResult = (mSortedValues[nSize / 2 - 1] + mSortedValues[nSize / 2]) / 2.0;
+ }
+ }
+ break;
+ default:
+ OSL_FAIL("invalid function");
+ }
+ }
+
+ bool bEmpty = ( nCount == 0 ); // no data
+
+ // store the result
+ // Empty is checked first, so empty results are shown empty even for "average" etc.
+ // If these results should be treated as errors in reference value calculations,
+ // a separate state value (EMPTY_ERROR) is needed.
+ // Now, for compatibility, empty "average" results are counted as 0.
+
+ if ( bEmpty )
+ nCount = SC_DPAGG_RESULT_EMPTY;
+ else if ( bError )
+ nCount = SC_DPAGG_RESULT_ERROR;
+ else
+ nCount = SC_DPAGG_RESULT_VALID;
+
+ if ( bEmpty || bError )
+ fResult = 0.0; // default, in case the state is later modified
+
+ fVal = fResult; // used directly from now on
+ fAux = 0.0; // used for running total or original result of reference value
+}
+
+bool ScDPAggData::IsCalculated() const
+{
+ return ( nCount <= SC_DPAGG_RESULT_EMPTY );
+}
+
+double ScDPAggData::GetResult() const
+{
+ assert( IsCalculated() && "ScDPAggData not calculated" );
+
+ return fVal; // use calculated value
+}
+
+bool ScDPAggData::HasError() const
+{
+ assert( IsCalculated() && "ScDPAggData not calculated" );
+
+ return ( nCount == SC_DPAGG_RESULT_ERROR );
+}
+
+bool ScDPAggData::HasData() const
+{
+ assert( IsCalculated() && "ScDPAggData not calculated" );
+
+ return ( nCount != SC_DPAGG_RESULT_EMPTY ); // values or error
+}
+
+void ScDPAggData::SetResult( double fNew )
+{
+ assert( IsCalculated() && "ScDPAggData not calculated" );
+
+ fVal = fNew; // don't reset error flag
+}
+
+void ScDPAggData::SetError()
+{
+ assert( IsCalculated() && "ScDPAggData not calculated" );
+
+ nCount = SC_DPAGG_RESULT_ERROR;
+}
+
+void ScDPAggData::SetEmpty( bool bSet )
+{
+ assert( IsCalculated() && "ScDPAggData not calculated" );
+
+ if ( bSet )
+ nCount = SC_DPAGG_RESULT_EMPTY;
+ else
+ nCount = SC_DPAGG_RESULT_VALID;
+}
+
+double ScDPAggData::GetAuxiliary() const
+{
+ // after Calculate, fAux is used as auxiliary value for running totals and reference values
+ assert( IsCalculated() && "ScDPAggData not calculated" );
+
+ return fAux;
+}
+
+void ScDPAggData::SetAuxiliary( double fNew )
+{
+ // after Calculate, fAux is used as auxiliary value for running totals and reference values
+ assert( IsCalculated() && "ScDPAggData not calculated" );
+
+ fAux = fNew;
+}
+
+ScDPAggData* ScDPAggData::GetChild()
+{
+ if (!pChild)
+ pChild.reset( new ScDPAggData );
+ return pChild.get();
+}
+
+void ScDPAggData::Reset()
+{
+ maWelford = WelfordRunner();
+ fVal = 0.0;
+ fAux = 0.0;
+ nCount = SC_DPAGG_EMPTY;
+ pChild.reset();
+}
+
+#if DUMP_PIVOT_TABLE
+void ScDPAggData::Dump(int nIndent) const
+{
+ std::string aIndent(nIndent*2, ' ');
+ std::cout << aIndent << "* ";
+ if (IsCalculated())
+ std::cout << GetResult();
+ else
+ std::cout << "not calculated";
+
+ std::cout << " [val=" << fVal << "; aux=" << fAux << "; count=" << nCount << "]" << std::endl;
+}
+#endif
+
+ScDPRowTotals::ScDPRowTotals() :
+ bIsInColRoot( false )
+{
+}
+
+ScDPRowTotals::~ScDPRowTotals()
+{
+}
+
+static ScDPAggData* lcl_GetChildTotal( ScDPAggData* pFirst, tools::Long nMeasure )
+{
+ OSL_ENSURE( nMeasure >= 0, "GetColTotal: no measure" );
+
+ ScDPAggData* pAgg = pFirst;
+ tools::Long nSkip = nMeasure;
+
+ // subtotal settings are ignored - column/row totals exist once per measure
+
+ for ( tools::Long nPos=0; nPos<nSkip; nPos++ )
+ pAgg = pAgg->GetChild(); // column total is constructed empty - children need to be created
+
+ if ( !pAgg->IsCalculated() )
+ {
+ // for first use, simulate an empty calculation
+ ScDPSubTotalState aEmptyState;
+ pAgg->Calculate( SUBTOTAL_FUNC_SUM, aEmptyState );
+ }
+
+ return pAgg;
+}
+
+ScDPAggData* ScDPRowTotals::GetRowTotal( tools::Long nMeasure )
+{
+ return lcl_GetChildTotal( &aRowTotal, nMeasure );
+}
+
+ScDPAggData* ScDPRowTotals::GetGrandTotal( tools::Long nMeasure )
+{
+ return lcl_GetChildTotal( &aGrandTotal, nMeasure );
+}
+
+static ScSubTotalFunc lcl_GetForceFunc( const ScDPLevel* pLevel, tools::Long nFuncNo )
+{
+ ScSubTotalFunc eRet = SUBTOTAL_FUNC_NONE;
+ if ( pLevel )
+ {
+ //TODO: direct access via ScDPLevel
+
+ uno::Sequence<sal_Int16> aSeq = pLevel->getSubTotals();
+ tools::Long nSequence = aSeq.getLength();
+ if ( nSequence && aSeq[0] != sheet::GeneralFunction2::AUTO )
+ {
+ // For manual subtotals, "automatic" is added as first function.
+ // ScDPResultMember::GetSubTotalCount adds to the count, here NONE has to be
+ // returned as the first function then.
+
+ --nFuncNo; // keep NONE for first (check below), move the other entries
+ }
+
+ if ( nFuncNo >= 0 && nFuncNo < nSequence )
+ {
+ ScGeneralFunction eUser = static_cast<ScGeneralFunction>(aSeq.getConstArray()[nFuncNo]);
+ if (eUser != ScGeneralFunction::AUTO)
+ eRet = ScDPUtil::toSubTotalFunc(eUser);
+ }
+ }
+ return eRet;
+}
+
+ScDPResultData::ScDPResultData( ScDPSource& rSrc ) :
+ mrSource(rSrc),
+ bLateInit( false ),
+ bDataAtCol( false ),
+ bDataAtRow( false )
+{
+}
+
+ScDPResultData::~ScDPResultData()
+{
+}
+
+void ScDPResultData::SetMeasureData(
+ std::vector<ScSubTotalFunc>& rFunctions, std::vector<sheet::DataPilotFieldReference>& rRefs,
+ std::vector<sheet::DataPilotFieldOrientation>& rRefOrient, std::vector<OUString>& rNames )
+{
+ // We need to have at least one measure data at all times.
+
+ maMeasureFuncs.swap(rFunctions);
+ if (maMeasureFuncs.empty())
+ maMeasureFuncs.push_back(SUBTOTAL_FUNC_NONE);
+
+ maMeasureRefs.swap(rRefs);
+ if (maMeasureRefs.empty())
+ maMeasureRefs.emplace_back(); // default ctor is ok.
+
+ maMeasureRefOrients.swap(rRefOrient);
+ if (maMeasureRefOrients.empty())
+ maMeasureRefOrients.push_back(sheet::DataPilotFieldOrientation_HIDDEN);
+
+ maMeasureNames.swap(rNames);
+ if (maMeasureNames.empty())
+ maMeasureNames.push_back(ScResId(STR_EMPTYDATA));
+}
+
+void ScDPResultData::SetDataLayoutOrientation( sheet::DataPilotFieldOrientation nOrient )
+{
+ bDataAtCol = ( nOrient == sheet::DataPilotFieldOrientation_COLUMN );
+ bDataAtRow = ( nOrient == sheet::DataPilotFieldOrientation_ROW );
+}
+
+void ScDPResultData::SetLateInit( bool bSet )
+{
+ bLateInit = bSet;
+}
+
+tools::Long ScDPResultData::GetColStartMeasure() const
+{
+ if (maMeasureFuncs.size() == 1)
+ return 0;
+
+ return bDataAtCol ? SC_DPMEASURE_ALL : SC_DPMEASURE_ANY;
+}
+
+tools::Long ScDPResultData::GetRowStartMeasure() const
+{
+ if (maMeasureFuncs.size() == 1)
+ return 0;
+
+ return bDataAtRow ? SC_DPMEASURE_ALL : SC_DPMEASURE_ANY;
+}
+
+ScSubTotalFunc ScDPResultData::GetMeasureFunction(tools::Long nMeasure) const
+{
+ OSL_ENSURE(o3tl::make_unsigned(nMeasure) < maMeasureFuncs.size(), "bumm");
+ return maMeasureFuncs[nMeasure];
+}
+
+const sheet::DataPilotFieldReference& ScDPResultData::GetMeasureRefVal(tools::Long nMeasure) const
+{
+ OSL_ENSURE(o3tl::make_unsigned(nMeasure) < maMeasureRefs.size(), "bumm");
+ return maMeasureRefs[nMeasure];
+}
+
+sheet::DataPilotFieldOrientation ScDPResultData::GetMeasureRefOrient(tools::Long nMeasure) const
+{
+ OSL_ENSURE(o3tl::make_unsigned(nMeasure) < maMeasureRefOrients.size(), "bumm");
+ return maMeasureRefOrients[nMeasure];
+}
+
+OUString ScDPResultData::GetMeasureString(tools::Long nMeasure, bool bForce, ScSubTotalFunc eForceFunc, bool& rbTotalResult) const
+{
+ // with bForce==true, return function instead of "result" for single measure
+ // with eForceFunc != SUBTOTAL_FUNC_NONE, always use eForceFunc
+ rbTotalResult = false;
+ if ( nMeasure < 0 || (maMeasureFuncs.size() == 1 && !bForce && eForceFunc == SUBTOTAL_FUNC_NONE) )
+ {
+ // for user-specified subtotal function with all measures,
+ // display only function name
+ assert(unsigned(eForceFunc) < SAL_N_ELEMENTS(aFuncStrIds));
+ if ( eForceFunc != SUBTOTAL_FUNC_NONE )
+ return ScResId(aFuncStrIds[eForceFunc]);
+
+ rbTotalResult = true;
+ return ScResId(STR_TABLE_ERGEBNIS);
+ }
+ else
+ {
+ OSL_ENSURE(o3tl::make_unsigned(nMeasure) < maMeasureFuncs.size(), "bumm");
+ const ScDPDimension* pDataDim = mrSource.GetDataDimension(nMeasure);
+ if (pDataDim)
+ {
+ const std::optional<OUString> & pLayoutName = pDataDim->GetLayoutName();
+ if (pLayoutName)
+ return *pLayoutName;
+ }
+
+ ScSubTotalFunc eFunc = ( eForceFunc == SUBTOTAL_FUNC_NONE ) ?
+ GetMeasureFunction(nMeasure) : eForceFunc;
+
+ return ScDPUtil::getDisplayedMeasureName(maMeasureNames[nMeasure], eFunc);
+ }
+}
+
+OUString ScDPResultData::GetMeasureDimensionName(tools::Long nMeasure) const
+{
+ if ( nMeasure < 0 )
+ {
+ OSL_FAIL("GetMeasureDimensionName: negative");
+ return "***";
+ }
+
+ return mrSource.GetDataDimName(nMeasure);
+}
+
+bool ScDPResultData::IsBaseForGroup( tools::Long nDim ) const
+{
+ return mrSource.GetData()->IsBaseForGroup(nDim);
+}
+
+tools::Long ScDPResultData::GetGroupBase( tools::Long nGroupDim ) const
+{
+ return mrSource.GetData()->GetGroupBase(nGroupDim);
+}
+
+bool ScDPResultData::IsNumOrDateGroup( tools::Long nDim ) const
+{
+ return mrSource.GetData()->IsNumOrDateGroup(nDim);
+}
+
+bool ScDPResultData::IsInGroup( SCROW nGroupDataId, tools::Long nGroupIndex,
+ const ScDPItemData& rBaseData, tools::Long nBaseIndex ) const
+{
+ const ScDPItemData* pGroupData = mrSource.GetItemDataById(nGroupIndex , nGroupDataId);
+ if ( pGroupData )
+ return mrSource.GetData()->IsInGroup(*pGroupData, nGroupIndex, rBaseData, nBaseIndex);
+ else
+ return false;
+}
+
+bool ScDPResultData::HasCommonElement( SCROW nFirstDataId, tools::Long nFirstIndex,
+ const ScDPItemData& rSecondData, tools::Long nSecondIndex ) const
+{
+ const ScDPItemData* pFirstData = mrSource.GetItemDataById(nFirstIndex , nFirstDataId);
+ if ( pFirstData )
+ return mrSource.GetData()->HasCommonElement(*pFirstData, nFirstIndex, rSecondData, nSecondIndex);
+ else
+ return false;
+}
+
+ResultMembers& ScDPResultData::GetDimResultMembers(tools::Long nDim, const ScDPDimension* pDim, ScDPLevel* pLevel) const
+{
+ if (nDim < static_cast<tools::Long>(maDimMembers.size()) && maDimMembers[nDim])
+ return *maDimMembers[nDim];
+
+ if (nDim >= static_cast<tools::Long>(maDimMembers.size()))
+ maDimMembers.resize(nDim+1);
+
+ std::unique_ptr<ResultMembers> pResultMembers(new ResultMembers());
+ // global order is used to initialize aMembers, so it doesn't have to be looked at later
+ const ScMemberSortOrder& rGlobalOrder = pLevel->GetGlobalOrder();
+
+ ScDPMembers* pMembers = pLevel->GetMembersObject();
+ tools::Long nMembCount = pMembers->getCount();
+ for (tools::Long i = 0; i < nMembCount; ++i)
+ {
+ tools::Long nSorted = rGlobalOrder.empty() ? i : rGlobalOrder[i];
+ ScDPMember* pMember = pMembers->getByIndex(nSorted);
+ if (!pResultMembers->FindMember(pMember->GetItemDataId()))
+ {
+ ScDPParentDimData aNew(i, pDim, pLevel, pMember);
+ pResultMembers->InsertMember(aNew);
+ }
+ }
+
+ maDimMembers[nDim] = std::move(pResultMembers);
+ return *maDimMembers[nDim];
+}
+
+ScDPResultMember::ScDPResultMember(
+ const ScDPResultData* pData, const ScDPParentDimData& rParentDimData ) :
+ pResultData( pData ),
+ aParentDimData( rParentDimData ),
+ bHasElements( false ),
+ bForceSubTotal( false ),
+ bHasHiddenDetails( false ),
+ bInitialized( false ),
+ bAutoHidden( false ),
+ nMemberStep( 1 )
+{
+ // pParentLevel/pMemberDesc is 0 for root members
+}
+
+ScDPResultMember::ScDPResultMember(
+ const ScDPResultData* pData, bool bForceSub ) :
+ pResultData( pData ),
+ bHasElements( false ),
+ bForceSubTotal( bForceSub ),
+ bHasHiddenDetails( false ),
+ bInitialized( false ),
+ bAutoHidden( false ),
+ nMemberStep( 1 )
+{
+}
+ScDPResultMember::~ScDPResultMember()
+{
+}
+
+OUString ScDPResultMember::GetName() const
+{
+ const ScDPMember* pMemberDesc = GetDPMember();
+ if (pMemberDesc)
+ return pMemberDesc->GetNameStr( false );
+ else
+ return ScResId(STR_PIVOT_TOTAL); // root member
+}
+
+OUString ScDPResultMember::GetDisplayName( bool bLocaleIndependent ) const
+{
+ const ScDPMember* pDPMember = GetDPMember();
+ if (!pDPMember)
+ return OUString();
+
+ ScDPItemData aItem(pDPMember->FillItemData());
+ if (aParentDimData.mpParentDim)
+ {
+ tools::Long nDim = aParentDimData.mpParentDim->GetDimension();
+ return pResultData->GetSource().GetData()->GetFormattedString(nDim, aItem, bLocaleIndependent);
+ }
+
+ return aItem.GetString();
+}
+
+ScDPItemData ScDPResultMember::FillItemData() const
+{
+ const ScDPMember* pMemberDesc = GetDPMember();
+ if (pMemberDesc)
+ return pMemberDesc->FillItemData();
+ return ScDPItemData(ScResId(STR_PIVOT_TOTAL)); // root member
+}
+
+bool ScDPResultMember::IsNamedItem( SCROW nIndex ) const
+{
+ //TODO: store ScDPMember pointer instead of ScDPMember ???
+ const ScDPMember* pMemberDesc = GetDPMember();
+ if (pMemberDesc)
+ return pMemberDesc->IsNamedItem(nIndex);
+ return false;
+}
+
+bool ScDPResultMember::IsValidEntry( const vector< SCROW >& aMembers ) const
+{
+ if ( !IsValid() )
+ return false;
+
+ const ScDPResultDimension* pChildDim = GetChildDimension();
+ if (pChildDim)
+ {
+ if (aMembers.size() < 2)
+ return false;
+
+ vector<SCROW>::const_iterator itr = aMembers.begin();
+ vector<SCROW> aChildMembers(++itr, aMembers.end());
+ return pChildDim->IsValidEntry(aChildMembers);
+ }
+ else
+ return true;
+}
+
+void ScDPResultMember::InitFrom( const vector<ScDPDimension*>& ppDim, const vector<ScDPLevel*>& ppLev,
+ size_t nPos, ScDPInitState& rInitState ,
+ bool bInitChild )
+{
+ // with LateInit, initialize only those members that have data
+ if ( pResultData->IsLateInit() )
+ return;
+
+ bInitialized = true;
+
+ if (nPos >= ppDim.size())
+ return;
+
+ // skip child dimension if details are not shown
+ if ( GetDPMember() && !GetDPMember()->getShowDetails() )
+ {
+ // Show DataLayout dimension
+ nMemberStep = 1;
+ while ( nPos < ppDim.size() )
+ {
+ if ( ppDim[nPos]->getIsDataLayoutDimension() )
+ {
+ if ( !pChildDimension )
+ pChildDimension.reset( new ScDPResultDimension( pResultData ) );
+ pChildDimension->InitFrom( ppDim, ppLev, nPos, rInitState , false );
+ return;
+ }
+ else
+ { //find next dim
+ nPos ++;
+ nMemberStep ++;
+ }
+ }
+ bHasHiddenDetails = true; // only if there is a next dimension
+ return;
+ }
+
+ if ( bInitChild )
+ {
+ pChildDimension.reset( new ScDPResultDimension( pResultData ) );
+ pChildDimension->InitFrom(ppDim, ppLev, nPos, rInitState);
+ }
+}
+
+void ScDPResultMember::LateInitFrom(
+ LateInitParams& rParams, const vector<SCROW>& pItemData, size_t nPos, ScDPInitState& rInitState)
+{
+ // without LateInit, everything has already been initialized
+ if ( !pResultData->IsLateInit() )
+ return;
+
+ bInitialized = true;
+
+ if ( rParams.IsEnd( nPos ) /*nPos >= ppDim.size()*/)
+ // No next dimension. Bail out.
+ return;
+
+ // skip child dimension if details are not shown
+ if ( GetDPMember() && !GetDPMember()->getShowDetails() )
+ {
+ // Show DataLayout dimension
+ nMemberStep = 1;
+ while ( !rParams.IsEnd( nPos ) )
+ {
+ if ( rParams.GetDim( nPos )->getIsDataLayoutDimension() )
+ {
+ if ( !pChildDimension )
+ pChildDimension.reset( new ScDPResultDimension( pResultData ) );
+
+ // #i111462# reset InitChild flag only for this child dimension's LateInitFrom call,
+ // not for following members of parent dimensions
+ bool bWasInitChild = rParams.GetInitChild();
+ rParams.SetInitChild( false );
+ pChildDimension->LateInitFrom( rParams, pItemData, nPos, rInitState );
+ rParams.SetInitChild( bWasInitChild );
+ return;
+ }
+ else
+ { //find next dim
+ nPos ++;
+ nMemberStep ++;
+ }
+ }
+ bHasHiddenDetails = true; // only if there is a next dimension
+ return;
+ }
+
+ // LateInitFrom is called several times...
+ if ( rParams.GetInitChild() )
+ {
+ if ( !pChildDimension )
+ pChildDimension.reset( new ScDPResultDimension( pResultData ) );
+ pChildDimension->LateInitFrom( rParams, pItemData, nPos, rInitState );
+ }
+}
+
+bool ScDPResultMember::IsSubTotalInTitle(tools::Long nMeasure) const
+{
+ bool bRet = false;
+ if ( pChildDimension && /*pParentLevel*/GetParentLevel() &&
+ /*pParentLevel*/GetParentLevel()->IsOutlineLayout() && /*pParentLevel*/GetParentLevel()->IsSubtotalsAtTop() )
+ {
+ tools::Long nUserSubStart;
+ tools::Long nSubTotals = GetSubTotalCount( &nUserSubStart );
+ nSubTotals -= nUserSubStart; // visible count
+ if ( nSubTotals )
+ {
+ if ( nMeasure == SC_DPMEASURE_ALL )
+ nSubTotals *= pResultData->GetMeasureCount(); // number of subtotals that will be inserted
+
+ // only a single subtotal row will be shown in the outline title row
+ if ( nSubTotals == 1 )
+ bRet = true;
+ }
+ }
+ return bRet;
+}
+
+tools::Long ScDPResultMember::GetSize(tools::Long nMeasure) const
+{
+ if ( !IsVisible() )
+ return 0;
+ const ScDPLevel* pParentLevel = GetParentLevel();
+ tools::Long nExtraSpace = 0;
+ if ( pParentLevel && pParentLevel->IsAddEmpty() )
+ ++nExtraSpace;
+
+ if ( pChildDimension )
+ {
+ // outline layout takes up an extra row for the title only if subtotals aren't shown in that row
+ if ( pParentLevel && pParentLevel->IsOutlineLayout() && !IsSubTotalInTitle( nMeasure ) )
+ ++nExtraSpace;
+
+ tools::Long nSize = pChildDimension->GetSize(nMeasure);
+ tools::Long nUserSubStart;
+ tools::Long nUserSubCount = GetSubTotalCount( &nUserSubStart );
+ nUserSubCount -= nUserSubStart; // for output size, use visible count
+ if ( nUserSubCount )
+ {
+ if ( nMeasure == SC_DPMEASURE_ALL )
+ nSize += pResultData->GetMeasureCount() * nUserSubCount;
+ else
+ nSize += nUserSubCount;
+ }
+ return nSize + nExtraSpace;
+ }
+ else
+ {
+ if ( nMeasure == SC_DPMEASURE_ALL )
+ return pResultData->GetMeasureCount() + nExtraSpace;
+ else
+ return 1 + nExtraSpace;
+ }
+}
+
+bool ScDPResultMember::IsVisible() const
+{
+ if (!bInitialized)
+ return false;
+
+ if (!IsValid())
+ return false;
+
+ if (bHasElements)
+ return true;
+
+ // not initialized -> shouldn't be there at all
+ // (allocated only to preserve ordering)
+ const ScDPLevel* pParentLevel = GetParentLevel();
+
+ return (pParentLevel && pParentLevel->getShowEmpty());
+}
+
+bool ScDPResultMember::IsValid() const
+{
+ // non-Valid members are left out of calculation
+
+ // was member set no invisible at the DataPilotSource?
+ const ScDPMember* pMemberDesc = GetDPMember();
+ if ( pMemberDesc && !pMemberDesc->isVisible() )
+ return false;
+
+ if ( bAutoHidden )
+ return false;
+
+ return true;
+}
+
+tools::Long ScDPResultMember::GetSubTotalCount( tools::Long* pUserSubStart ) const
+{
+ if ( pUserSubStart )
+ *pUserSubStart = 0; // default
+
+ const ScDPLevel* pParentLevel = GetParentLevel();
+
+ if ( bForceSubTotal ) // set if needed for root members
+ return 1; // grand total is always "automatic"
+ else if ( pParentLevel )
+ {
+ //TODO: direct access via ScDPLevel
+
+ uno::Sequence<sal_Int16> aSeq = pParentLevel->getSubTotals();
+ tools::Long nSequence = aSeq.getLength();
+ if ( nSequence && aSeq[0] != sheet::GeneralFunction2::AUTO )
+ {
+ // For manual subtotals, always add "automatic" as first function
+ // (used for calculation, but not for display, needed for sorting, see lcl_GetForceFunc)
+
+ ++nSequence;
+ if ( pUserSubStart )
+ *pUserSubStart = 1; // visible subtotals start at 1
+ }
+ return nSequence;
+ }
+ else
+ return 0;
+}
+
+void ScDPResultMember::ProcessData( const vector< SCROW >& aChildMembers, const ScDPResultDimension* pDataDim,
+ const vector< SCROW >& aDataMembers, const vector<ScDPValue>& aValues )
+{
+ SetHasElements();
+
+ if (pChildDimension)
+ pChildDimension->ProcessData( aChildMembers, pDataDim, aDataMembers, aValues );
+
+ if ( !pDataRoot )
+ {
+ pDataRoot.reset( new ScDPDataMember( pResultData, nullptr ) );
+ if ( pDataDim )
+ pDataRoot->InitFrom( pDataDim ); // recursive
+ }
+
+ ScDPSubTotalState aSubState; // initial state
+
+ tools::Long nUserSubCount = GetSubTotalCount();
+
+ // Calculate at least automatic if no subtotals are selected,
+ // show only own values if there's no child dimension (innermost).
+ if ( !nUserSubCount || !pChildDimension )
+ nUserSubCount = 1;
+
+ const ScDPLevel* pParentLevel = GetParentLevel();
+
+ for (tools::Long nUserPos=0; nUserPos<nUserSubCount; nUserPos++) // including hidden "automatic"
+ {
+ // #i68338# if nUserSubCount is 1 (automatic only), don't set nRowSubTotalFunc
+ if ( pChildDimension && nUserSubCount > 1 )
+ {
+ aSubState.nRowSubTotalFunc = nUserPos;
+ aSubState.eRowForce = lcl_GetForceFunc( pParentLevel, nUserPos );
+ }
+
+ pDataRoot->ProcessData( aDataMembers, aValues, aSubState );
+ }
+}
+
+/**
+ * Parse subtotal string and replace all occurrences of '?' with the caption
+ * string. Do ensure that escaped characters are not translated.
+ */
+static OUString lcl_parseSubtotalName(const OUString& rSubStr, std::u16string_view rCaption)
+{
+ OUStringBuffer aNewStr;
+ sal_Int32 n = rSubStr.getLength();
+ bool bEscaped = false;
+ for (sal_Int32 i = 0; i < n; ++i)
+ {
+ sal_Unicode c = rSubStr[i];
+ if (!bEscaped && c == '\\')
+ {
+ bEscaped = true;
+ continue;
+ }
+
+ if (!bEscaped && c == '?')
+ aNewStr.append(rCaption);
+ else
+ aNewStr.append(c);
+ bEscaped = false;
+ }
+ return aNewStr.makeStringAndClear();
+}
+
+void ScDPResultMember::FillMemberResults(
+ uno::Sequence<sheet::MemberResult>* pSequences, tools::Long& rPos, tools::Long nMeasure, bool bRoot,
+ const OUString* pMemberName, const OUString* pMemberCaption )
+{
+ // IsVisible() test is in ScDPResultDimension::FillMemberResults
+ // (not on data layout dimension)
+
+ if (!pSequences->hasElements())
+ // empty sequence. Bail out.
+ return;
+
+ tools::Long nSize = GetSize(nMeasure);
+ sheet::MemberResult* pArray = pSequences->getArray();
+ OSL_ENSURE( rPos+nSize <= pSequences->getLength(), "bumm" );
+
+ bool bIsNumeric = false;
+ double fValue = std::numeric_limits<double>::quiet_NaN();
+ OUString aName;
+ if ( pMemberName ) // if pMemberName != NULL, use instead of real member name
+ {
+ aName = *pMemberName;
+ }
+ else
+ {
+ ScDPItemData aItemData(FillItemData());
+ if (aParentDimData.mpParentDim)
+ {
+ tools::Long nDim = aParentDimData.mpParentDim->GetDimension();
+ aName = pResultData->GetSource().GetData()->GetFormattedString(nDim, aItemData, false);
+ }
+ else
+ {
+ tools::Long nDim = -1;
+ const ScDPMember* pMem = GetDPMember();
+ if (pMem)
+ nDim = pMem->GetDim();
+ aName = pResultData->GetSource().GetData()->GetFormattedString(nDim, aItemData, false);
+ }
+
+ ScDPItemData::Type eType = aItemData.GetType();
+ bIsNumeric = eType == ScDPItemData::Value || eType == ScDPItemData::GroupValue;
+ // IsValue() is not identical to bIsNumeric, i.e.
+ // ScDPItemData::GroupValue is excluded and not stored in the double,
+ // so even if the item is numeric the Value may be NaN.
+ if (aItemData.IsValue())
+ fValue = aItemData.GetValue();
+ }
+
+ const ScDPDimension* pParentDim = GetParentDim();
+ if ( bIsNumeric && pParentDim && pResultData->IsNumOrDateGroup( pParentDim->GetDimension() ) )
+ {
+ // Numeric group dimensions use numeric entries for proper sorting,
+ // but the group titles must be output as text.
+ bIsNumeric = false;
+ }
+
+ OUString aCaption = aName;
+ const ScDPMember* pMemberDesc = GetDPMember();
+ if (pMemberDesc)
+ {
+ const std::optional<OUString> & pLayoutName = pMemberDesc->GetLayoutName();
+ if (pLayoutName)
+ {
+ aCaption = *pLayoutName;
+ bIsNumeric = false; // layout name is always non-numeric.
+ }
+ }
+
+ if ( pMemberCaption ) // use pMemberCaption if != NULL
+ aCaption = *pMemberCaption;
+ if (aCaption.isEmpty())
+ aCaption = ScResId(STR_EMPTYDATA);
+
+ if (bIsNumeric)
+ pArray[rPos].Flags |= sheet::MemberResultFlags::NUMERIC;
+ else
+ pArray[rPos].Flags &= ~sheet::MemberResultFlags::NUMERIC;
+
+ const ScDPLevel* pParentLevel = GetParentLevel();
+ if ( nSize && !bRoot ) // root is overwritten by first dimension
+ {
+ pArray[rPos].Name = aName;
+ pArray[rPos].Caption = aCaption;
+ pArray[rPos].Flags |= sheet::MemberResultFlags::HASMEMBER;
+ pArray[rPos].Value = fValue;
+
+ // set "continue" flag (removed for subtotals later)
+ for (tools::Long i=1; i<nSize; i++)
+ {
+ pArray[rPos+i].Flags |= sheet::MemberResultFlags::CONTINUE;
+ // tdf#113002 - add numeric flag to recurring data fields
+ if (bIsNumeric)
+ pArray[rPos + i].Flags |= sheet::MemberResultFlags::NUMERIC;
+ }
+
+ if ( pParentLevel && pParentLevel->getRepeatItemLabels() )
+ {
+ tools::Long nSizeNonEmpty = nSize;
+ if ( pParentLevel->IsAddEmpty() )
+ --nSizeNonEmpty;
+ for (tools::Long i=1; i<nSizeNonEmpty; i++)
+ {
+ pArray[rPos+i].Name = aName;
+ pArray[rPos+i].Caption = aCaption;
+ pArray[rPos+i].Flags |= sheet::MemberResultFlags::HASMEMBER;
+ pArray[rPos+i].Value = fValue;
+ }
+ }
+ }
+
+ tools::Long nExtraSpace = 0;
+ if ( pParentLevel && pParentLevel->IsAddEmpty() )
+ ++nExtraSpace;
+
+ bool bTitleLine = false;
+ if ( pParentLevel && pParentLevel->IsOutlineLayout() )
+ bTitleLine = true;
+
+ // if the subtotals are shown at the top (title row) in outline layout,
+ // no extra row for the subtotals is needed
+ bool bSubTotalInTitle = IsSubTotalInTitle( nMeasure );
+
+ bool bHasChild = ( pChildDimension != nullptr );
+ if (bHasChild)
+ {
+ if ( bTitleLine ) // in tabular layout the title is on a separate row
+ ++rPos; // -> fill child dimension one row below
+
+ if (bRoot) // same sequence for root member
+ pChildDimension->FillMemberResults( pSequences, rPos, nMeasure );
+ else
+ pChildDimension->FillMemberResults( pSequences + nMemberStep/*1*/, rPos, nMeasure );
+
+ if ( bTitleLine ) // title row is included in GetSize, so the following
+ --rPos; // positions are calculated with the normal values
+ }
+
+ rPos += nSize;
+
+ tools::Long nUserSubStart;
+ tools::Long nUserSubCount = GetSubTotalCount(&nUserSubStart);
+ if ( !nUserSubCount || !pChildDimension || bSubTotalInTitle )
+ return;
+
+ tools::Long nMemberMeasure = nMeasure;
+ tools::Long nSubSize = pResultData->GetCountForMeasure(nMeasure);
+
+ rPos -= nSubSize * (nUserSubCount - nUserSubStart); // GetSize includes space for SubTotal
+ rPos -= nExtraSpace; // GetSize includes the empty line
+
+ for (tools::Long nUserPos=nUserSubStart; nUserPos<nUserSubCount; nUserPos++)
+ {
+ for ( tools::Long nSubCount=0; nSubCount<nSubSize; nSubCount++ )
+ {
+ if ( nMeasure == SC_DPMEASURE_ALL )
+ nMemberMeasure = nSubCount;
+
+ ScSubTotalFunc eForce = SUBTOTAL_FUNC_NONE;
+ if (bHasChild)
+ eForce = lcl_GetForceFunc( pParentLevel, nUserPos );
+
+ bool bTotalResult = false;
+ OUString aSubStr = aCaption + " " + pResultData->GetMeasureString(nMemberMeasure, false, eForce, bTotalResult);
+
+ if (bTotalResult)
+ {
+ if (pMemberDesc)
+ {
+ // single data field layout.
+ const std::optional<OUString> & pSubtotalName = pParentDim->GetSubtotalName();
+ if (pSubtotalName)
+ aSubStr = lcl_parseSubtotalName(*pSubtotalName, aCaption);
+ pArray[rPos].Flags &= ~sheet::MemberResultFlags::GRANDTOTAL;
+ }
+ else
+ {
+ // root member - subtotal (grand total?) for multi-data field layout.
+ const std::optional<OUString> & pGrandTotalName = pResultData->GetSource().GetGrandTotalName();
+ if (pGrandTotalName)
+ aSubStr = *pGrandTotalName;
+ pArray[rPos].Flags |= sheet::MemberResultFlags::GRANDTOTAL;
+ }
+ }
+
+ fValue = std::numeric_limits<double>::quiet_NaN(); /* TODO: any numeric value to obtain? */
+ pArray[rPos].Name = aName;
+ pArray[rPos].Caption = aSubStr;
+ pArray[rPos].Flags = ( pArray[rPos].Flags |
+ ( sheet::MemberResultFlags::HASMEMBER | sheet::MemberResultFlags::SUBTOTAL) ) &
+ ~sheet::MemberResultFlags::CONTINUE;
+ pArray[rPos].Value = fValue;
+
+ if ( nMeasure == SC_DPMEASURE_ALL )
+ {
+ // data layout dimension is (direct/indirect) child of this.
+ // data layout dimension must have name for all entries.
+
+ uno::Sequence<sheet::MemberResult>* pLayoutSeq = pSequences;
+ if (!bRoot)
+ ++pLayoutSeq;
+ ScDPResultDimension* pLayoutDim = pChildDimension.get();
+ while ( pLayoutDim && !pLayoutDim->IsDataLayout() )
+ {
+ pLayoutDim = pLayoutDim->GetFirstChildDimension();
+ ++pLayoutSeq;
+ }
+ if ( pLayoutDim )
+ {
+ sheet::MemberResult* pLayoutArray = pLayoutSeq->getArray();
+ pLayoutArray[rPos].Name = pResultData->GetMeasureDimensionName(nMemberMeasure);
+ }
+ }
+
+ rPos += 1;
+ }
+ }
+
+ rPos += nExtraSpace; // add again (subtracted above)
+}
+
+void ScDPResultMember::FillDataResults(
+ const ScDPResultMember* pRefMember,
+ ScDPResultFilterContext& rFilterCxt, uno::Sequence<uno::Sequence<sheet::DataResult> >& rSequence,
+ tools::Long nMeasure) const
+{
+ std::unique_ptr<FilterStack> pFilterStack;
+ const ScDPMember* pDPMember = GetDPMember();
+ if (pDPMember)
+ {
+ // Root result has no corresponding DP member. Only take the non-root results.
+ pFilterStack.reset(new FilterStack(rFilterCxt.maFilters));
+ pFilterStack->pushDimValue( GetDisplayName( false), GetDisplayName( true));
+ }
+
+ // IsVisible() test is in ScDPResultDimension::FillDataResults
+ // (not on data layout dimension)
+ const ScDPLevel* pParentLevel = GetParentLevel();
+ sal_Int32 nStartRow = rFilterCxt.mnRow;
+
+ tools::Long nExtraSpace = 0;
+ if ( pParentLevel && pParentLevel->IsAddEmpty() )
+ ++nExtraSpace;
+
+ bool bTitleLine = false;
+ if ( pParentLevel && pParentLevel->IsOutlineLayout() )
+ bTitleLine = true;
+
+ bool bSubTotalInTitle = IsSubTotalInTitle( nMeasure );
+
+ bool bHasChild = ( pChildDimension != nullptr );
+ if (bHasChild)
+ {
+ if ( bTitleLine ) // in tabular layout the title is on a separate row
+ ++rFilterCxt.mnRow; // -> fill child dimension one row below
+
+ sal_Int32 nOldRow = rFilterCxt.mnRow;
+ pChildDimension->FillDataResults(pRefMember, rFilterCxt, rSequence, nMeasure);
+ rFilterCxt.mnRow = nOldRow; // Revert to the original row before the call.
+
+ rFilterCxt.mnRow += GetSize( nMeasure );
+
+ if ( bTitleLine ) // title row is included in GetSize, so the following
+ --rFilterCxt.mnRow; // positions are calculated with the normal values
+ }
+
+ tools::Long nUserSubStart;
+ tools::Long nUserSubCount = GetSubTotalCount(&nUserSubStart);
+ if ( !nUserSubCount && bHasChild )
+ return;
+
+ // Calculate at least automatic if no subtotals are selected,
+ // show only own values if there's no child dimension (innermost).
+ if ( !nUserSubCount || !bHasChild )
+ {
+ nUserSubCount = 1;
+ nUserSubStart = 0;
+ }
+
+ tools::Long nMemberMeasure = nMeasure;
+ tools::Long nSubSize = pResultData->GetCountForMeasure(nMeasure);
+ if (bHasChild)
+ {
+ rFilterCxt.mnRow -= nSubSize * ( nUserSubCount - nUserSubStart ); // GetSize includes space for SubTotal
+ rFilterCxt.mnRow -= nExtraSpace; // GetSize includes the empty line
+ }
+
+ tools::Long nMoveSubTotal = 0;
+ if ( bSubTotalInTitle )
+ {
+ nMoveSubTotal = rFilterCxt.mnRow - nStartRow; // force to first (title) row
+ rFilterCxt.mnRow = nStartRow;
+ }
+
+ if ( pDataRoot )
+ {
+ ScDPSubTotalState aSubState; // initial state
+
+ for (tools::Long nUserPos=nUserSubStart; nUserPos<nUserSubCount; nUserPos++)
+ {
+ if ( bHasChild && nUserSubCount > 1 )
+ {
+ aSubState.nRowSubTotalFunc = nUserPos;
+ aSubState.eRowForce = lcl_GetForceFunc( /*pParentLevel*/GetParentLevel() , nUserPos );
+ }
+
+ for ( tools::Long nSubCount=0; nSubCount<nSubSize; nSubCount++ )
+ {
+ if ( nMeasure == SC_DPMEASURE_ALL )
+ nMemberMeasure = nSubCount;
+ else if ( pResultData->GetColStartMeasure() == SC_DPMEASURE_ALL )
+ nMemberMeasure = SC_DPMEASURE_ALL;
+
+ OSL_ENSURE( rFilterCxt.mnRow < rSequence.getLength(), "bumm" );
+ rFilterCxt.mnCol = 0;
+ if (pRefMember->IsVisible())
+ {
+ uno::Sequence<sheet::DataResult>& rSubSeq = rSequence.getArray()[rFilterCxt.mnRow];
+ pDataRoot->FillDataRow(pRefMember, rFilterCxt, rSubSeq, nMemberMeasure, bHasChild, aSubState);
+ }
+ rFilterCxt.mnRow += 1;
+ }
+ }
+ }
+ else
+ rFilterCxt.mnRow += nSubSize * ( nUserSubCount - nUserSubStart ); // empty rows occur when ShowEmpty is true
+
+ // add extra space again if subtracted from GetSize above,
+ // add to own size if no children
+ rFilterCxt.mnRow += nExtraSpace;
+ rFilterCxt.mnRow += nMoveSubTotal;
+}
+
+void ScDPResultMember::UpdateDataResults( const ScDPResultMember* pRefMember, tools::Long nMeasure ) const
+{
+ // IsVisible() test is in ScDPResultDimension::FillDataResults
+ // (not on data layout dimension)
+
+ bool bHasChild = ( pChildDimension != nullptr );
+
+ tools::Long nUserSubCount = GetSubTotalCount();
+
+ // process subtotals even if not shown
+
+ // Calculate at least automatic if no subtotals are selected,
+ // show only own values if there's no child dimension (innermost).
+ if (!nUserSubCount || !bHasChild)
+ nUserSubCount = 1;
+
+ tools::Long nMemberMeasure = nMeasure;
+ tools::Long nSubSize = pResultData->GetCountForMeasure(nMeasure);
+
+ if (pDataRoot)
+ {
+ ScDPSubTotalState aSubState; // initial state
+
+ for (tools::Long nUserPos = 0; nUserPos < nUserSubCount; ++nUserPos) // including hidden "automatic"
+ {
+ if (bHasChild && nUserSubCount > 1)
+ {
+ aSubState.nRowSubTotalFunc = nUserPos;
+ aSubState.eRowForce = lcl_GetForceFunc(GetParentLevel(), nUserPos);
+ }
+
+ for (tools::Long nSubCount = 0; nSubCount < nSubSize; ++nSubCount)
+ {
+ if (nMeasure == SC_DPMEASURE_ALL)
+ nMemberMeasure = nSubCount;
+ else if (pResultData->GetColStartMeasure() == SC_DPMEASURE_ALL)
+ nMemberMeasure = SC_DPMEASURE_ALL;
+
+ pDataRoot->UpdateDataRow(pRefMember, nMemberMeasure, bHasChild, aSubState);
+ }
+ }
+ }
+
+ if (bHasChild) // child dimension must be processed last, so the column total is known
+ {
+ pChildDimension->UpdateDataResults( pRefMember, nMeasure );
+ }
+}
+
+void ScDPResultMember::SortMembers( ScDPResultMember* pRefMember )
+{
+ bool bHasChild = ( pChildDimension != nullptr );
+ if (bHasChild)
+ pChildDimension->SortMembers( pRefMember ); // sorting is done at the dimension
+
+ if ( IsRoot() && pDataRoot )
+ {
+ // use the row root member to sort columns
+ // sub total count is always 1
+
+ pDataRoot->SortMembers( pRefMember );
+ }
+}
+
+void ScDPResultMember::DoAutoShow( ScDPResultMember* pRefMember )
+{
+ bool bHasChild = ( pChildDimension != nullptr );
+ if (bHasChild)
+ pChildDimension->DoAutoShow( pRefMember ); // sorting is done at the dimension
+
+ if ( IsRoot()&& pDataRoot )
+ {
+ // use the row root member to sort columns
+ // sub total count is always 1
+
+ pDataRoot->DoAutoShow( pRefMember );
+ }
+}
+
+void ScDPResultMember::ResetResults()
+{
+ if (pDataRoot)
+ pDataRoot->ResetResults();
+
+ if (pChildDimension)
+ pChildDimension->ResetResults();
+}
+
+void ScDPResultMember::UpdateRunningTotals( const ScDPResultMember* pRefMember, tools::Long nMeasure,
+ ScDPRunningTotalState& rRunning, ScDPRowTotals& rTotals ) const
+{
+ // IsVisible() test is in ScDPResultDimension::FillDataResults
+ // (not on data layout dimension)
+
+ rTotals.SetInColRoot( IsRoot() );
+
+ bool bHasChild = ( pChildDimension != nullptr );
+
+ tools::Long nUserSubCount = GetSubTotalCount();
+ //if ( nUserSubCount || !bHasChild )
+ {
+ // Calculate at least automatic if no subtotals are selected,
+ // show only own values if there's no child dimension (innermost).
+ if ( !nUserSubCount || !bHasChild )
+ nUserSubCount = 1;
+
+ tools::Long nMemberMeasure = nMeasure;
+ tools::Long nSubSize = pResultData->GetCountForMeasure(nMeasure);
+
+ if ( pDataRoot )
+ {
+ ScDPSubTotalState aSubState; // initial state
+
+ for (tools::Long nUserPos=0; nUserPos<nUserSubCount; nUserPos++) // including hidden "automatic"
+ {
+ if ( bHasChild && nUserSubCount > 1 )
+ {
+ aSubState.nRowSubTotalFunc = nUserPos;
+ aSubState.eRowForce = lcl_GetForceFunc(GetParentLevel(), nUserPos);
+ }
+
+ for ( tools::Long nSubCount=0; nSubCount<nSubSize; nSubCount++ )
+ {
+ if ( nMeasure == SC_DPMEASURE_ALL )
+ nMemberMeasure = nSubCount;
+ else if ( pResultData->GetColStartMeasure() == SC_DPMEASURE_ALL )
+ nMemberMeasure = SC_DPMEASURE_ALL;
+
+ if (pRefMember->IsVisible())
+ pDataRoot->UpdateRunningTotals(
+ pRefMember, nMemberMeasure, bHasChild, aSubState, rRunning, rTotals, *this);
+ }
+ }
+ }
+ }
+
+ if (bHasChild) // child dimension must be processed last, so the column total is known
+ {
+ pChildDimension->UpdateRunningTotals( pRefMember, nMeasure, rRunning, rTotals );
+ }
+}
+
+#if DUMP_PIVOT_TABLE
+void ScDPResultMember::DumpState( const ScDPResultMember* pRefMember, ScDocument* pDoc, ScAddress& rPos ) const
+{
+ dumpRow("ScDPResultMember", GetName(), nullptr, pDoc, rPos);
+ SCROW nStartRow = rPos.Row();
+
+ if (pDataRoot)
+ pDataRoot->DumpState( pRefMember, pDoc, rPos );
+
+ if (pChildDimension)
+ pChildDimension->DumpState( pRefMember, pDoc, rPos );
+
+ indent(pDoc, nStartRow, rPos);
+}
+
+void ScDPResultMember::Dump(int nIndent) const
+{
+ std::string aIndent(nIndent*2, ' ');
+ std::cout << aIndent << "-- result member '" << GetName() << "'" << std::endl;
+
+ std::cout << aIndent << " column totals" << std::endl;
+ for (const ScDPAggData* p = &aColTotal; p; p = p->GetExistingChild())
+ p->Dump(nIndent+1);
+
+ if (pChildDimension)
+ pChildDimension->Dump(nIndent+1);
+
+ if (pDataRoot)
+ {
+ std::cout << aIndent << " data root" << std::endl;
+ pDataRoot->Dump(nIndent+1);
+ }
+}
+#endif
+
+ScDPAggData* ScDPResultMember::GetColTotal( tools::Long nMeasure ) const
+{
+ return lcl_GetChildTotal( const_cast<ScDPAggData*>(&aColTotal), nMeasure );
+}
+
+void ScDPResultMember::FillVisibilityData(ScDPResultVisibilityData& rData) const
+{
+ if (pChildDimension)
+ pChildDimension->FillVisibilityData(rData);
+}
+
+ScDPDataMember::ScDPDataMember( const ScDPResultData* pData, const ScDPResultMember* pRes ) :
+ pResultData( pData ),
+ pResultMember( pRes )
+{
+ // pResultMember is 0 for root members
+}
+
+ScDPDataMember::~ScDPDataMember()
+{
+}
+
+OUString ScDPDataMember::GetName() const
+{
+ if (pResultMember)
+ return pResultMember->GetName();
+ else
+ return OUString();
+}
+
+bool ScDPDataMember::IsVisible() const
+{
+ if (pResultMember)
+ return pResultMember->IsVisible();
+ else
+ return false;
+}
+
+bool ScDPDataMember::IsNamedItem( SCROW nRow ) const
+{
+ if (pResultMember)
+ return pResultMember->IsNamedItem(nRow);
+ else
+ return false;
+}
+
+bool ScDPDataMember::HasHiddenDetails() const
+{
+ if (pResultMember)
+ return pResultMember->HasHiddenDetails();
+ else
+ return false;
+}
+
+void ScDPDataMember::InitFrom( const ScDPResultDimension* pDim )
+{
+ if ( !pChildDimension )
+ pChildDimension.reset( new ScDPDataDimension(pResultData) );
+ pChildDimension->InitFrom(pDim);
+}
+
+const tools::Long SC_SUBTOTALPOS_AUTO = -1; // default
+const tools::Long SC_SUBTOTALPOS_SKIP = -2; // don't use
+
+static tools::Long lcl_GetSubTotalPos( const ScDPSubTotalState& rSubState )
+{
+ if ( rSubState.nColSubTotalFunc >= 0 && rSubState.nRowSubTotalFunc >= 0 &&
+ rSubState.nColSubTotalFunc != rSubState.nRowSubTotalFunc )
+ {
+ // #i68338# don't return the same index for different combinations (leading to repeated updates),
+ // return a "don't use" value instead
+
+ return SC_SUBTOTALPOS_SKIP;
+ }
+
+ tools::Long nRet = SC_SUBTOTALPOS_AUTO;
+ if ( rSubState.nColSubTotalFunc >= 0 ) nRet = rSubState.nColSubTotalFunc;
+ if ( rSubState.nRowSubTotalFunc >= 0 ) nRet = rSubState.nRowSubTotalFunc;
+ return nRet;
+}
+
+void ScDPDataMember::UpdateValues( const vector<ScDPValue>& aValues, const ScDPSubTotalState& rSubState )
+{
+ //TODO: find out how many and which subtotals are used
+
+ ScDPAggData* pAgg = &aAggregate;
+
+ tools::Long nSubPos = lcl_GetSubTotalPos(rSubState);
+ if (nSubPos == SC_SUBTOTALPOS_SKIP)
+ return;
+ if (nSubPos > 0)
+ {
+ tools::Long nSkip = nSubPos * pResultData->GetMeasureCount();
+ for (tools::Long i=0; i<nSkip; i++)
+ pAgg = pAgg->GetChild(); // created if not there
+ }
+
+ size_t nCount = aValues.size();
+ for (size_t nPos = 0; nPos < nCount; ++nPos)
+ {
+ pAgg->Update(aValues[nPos], pResultData->GetMeasureFunction(nPos), rSubState);
+ pAgg = pAgg->GetChild();
+ }
+}
+
+void ScDPDataMember::ProcessData( const vector< SCROW >& aChildMembers, const vector<ScDPValue>& aValues,
+ const ScDPSubTotalState& rSubState )
+{
+ if ( pResultData->IsLateInit() && !pChildDimension && pResultMember && pResultMember->GetChildDimension() )
+ {
+ // if this DataMember doesn't have a child dimension because the ResultMember's
+ // child dimension wasn't there yet during this DataMembers's creation,
+ // create the child dimension now
+ InitFrom( pResultMember->GetChildDimension() );
+ }
+
+ tools::Long nUserSubCount = pResultMember ? pResultMember->GetSubTotalCount() : 0;
+
+ // Calculate at least automatic if no subtotals are selected,
+ // show only own values if there's no child dimension (innermost).
+ if ( !nUserSubCount || !pChildDimension )
+ nUserSubCount = 1;
+
+ ScDPSubTotalState aLocalSubState = rSubState; // keep row state, modify column
+ for (tools::Long nUserPos=0; nUserPos<nUserSubCount; nUserPos++) // including hidden "automatic"
+ {
+ if ( pChildDimension && nUserSubCount > 1 )
+ {
+ const ScDPLevel* pForceLevel = pResultMember ? pResultMember->GetParentLevel() : nullptr;
+ aLocalSubState.nColSubTotalFunc = nUserPos;
+ aLocalSubState.eColForce = lcl_GetForceFunc( pForceLevel, nUserPos );
+ }
+
+ UpdateValues( aValues, aLocalSubState );
+ }
+
+ if (pChildDimension)
+ pChildDimension->ProcessData( aChildMembers, aValues, rSubState ); // with unmodified subtotal state
+}
+
+bool ScDPDataMember::HasData( tools::Long nMeasure, const ScDPSubTotalState& rSubState ) const
+{
+ if ( rSubState.eColForce != SUBTOTAL_FUNC_NONE && rSubState.eRowForce != SUBTOTAL_FUNC_NONE &&
+ rSubState.eColForce != rSubState.eRowForce )
+ return false;
+
+ // HasData can be different between measures!
+
+ const ScDPAggData* pAgg = GetConstAggData( nMeasure, rSubState );
+ if (!pAgg)
+ return false; //TODO: error?
+
+ return pAgg->HasData();
+}
+
+bool ScDPDataMember::HasError( tools::Long nMeasure, const ScDPSubTotalState& rSubState ) const
+{
+ const ScDPAggData* pAgg = GetConstAggData( nMeasure, rSubState );
+ if (!pAgg)
+ return true;
+
+ return pAgg->HasError();
+}
+
+double ScDPDataMember::GetAggregate( tools::Long nMeasure, const ScDPSubTotalState& rSubState ) const
+{
+ const ScDPAggData* pAgg = GetConstAggData( nMeasure, rSubState );
+ if (!pAgg)
+ return DBL_MAX; //TODO: error?
+
+ return pAgg->GetResult();
+}
+
+ScDPAggData* ScDPDataMember::GetAggData( tools::Long nMeasure, const ScDPSubTotalState& rSubState )
+{
+ OSL_ENSURE( nMeasure >= 0, "GetAggData: no measure" );
+
+ ScDPAggData* pAgg = &aAggregate;
+ tools::Long nSkip = nMeasure;
+ tools::Long nSubPos = lcl_GetSubTotalPos(rSubState);
+ if (nSubPos == SC_SUBTOTALPOS_SKIP)
+ return nullptr;
+ if (nSubPos > 0)
+ nSkip += nSubPos * pResultData->GetMeasureCount();
+
+ for ( tools::Long nPos=0; nPos<nSkip; nPos++ )
+ pAgg = pAgg->GetChild(); //TODO: need to create children here?
+
+ return pAgg;
+}
+
+const ScDPAggData* ScDPDataMember::GetConstAggData( tools::Long nMeasure, const ScDPSubTotalState& rSubState ) const
+{
+ OSL_ENSURE( nMeasure >= 0, "GetConstAggData: no measure" );
+
+ const ScDPAggData* pAgg = &aAggregate;
+ tools::Long nSkip = nMeasure;
+ tools::Long nSubPos = lcl_GetSubTotalPos(rSubState);
+ if (nSubPos == SC_SUBTOTALPOS_SKIP)
+ return nullptr;
+ if (nSubPos > 0)
+ nSkip += nSubPos * pResultData->GetMeasureCount();
+
+ for ( tools::Long nPos=0; nPos<nSkip; nPos++ )
+ {
+ pAgg = pAgg->GetExistingChild();
+ if (!pAgg)
+ return nullptr;
+ }
+
+ return pAgg;
+}
+
+void ScDPDataMember::FillDataRow(
+ const ScDPResultMember* pRefMember, ScDPResultFilterContext& rFilterCxt,
+ uno::Sequence<sheet::DataResult>& rSequence, tools::Long nMeasure, bool bIsSubTotalRow,
+ const ScDPSubTotalState& rSubState) const
+{
+ std::unique_ptr<FilterStack> pFilterStack;
+ if (pResultMember)
+ {
+ // Topmost data member (pResultMember=NULL) doesn't need to be handled
+ // since its immediate parent result member is linked to the same
+ // dimension member.
+ pFilterStack.reset(new FilterStack(rFilterCxt.maFilters));
+ pFilterStack->pushDimValue( pResultMember->GetDisplayName( false), pResultMember->GetDisplayName( true));
+ }
+
+ OSL_ENSURE( pRefMember == pResultMember || !pResultMember, "bla" );
+
+ tools::Long nStartCol = rFilterCxt.mnCol;
+
+ const ScDPDataDimension* pDataChild = GetChildDimension();
+ const ScDPResultDimension* pRefChild = pRefMember->GetChildDimension();
+
+ const ScDPLevel* pRefParentLevel = pRefMember->GetParentLevel();
+
+ tools::Long nExtraSpace = 0;
+ if ( pRefParentLevel && pRefParentLevel->IsAddEmpty() )
+ ++nExtraSpace;
+
+ bool bTitleLine = false;
+ if ( pRefParentLevel && pRefParentLevel->IsOutlineLayout() )
+ bTitleLine = true;
+
+ bool bSubTotalInTitle = pRefMember->IsSubTotalInTitle( nMeasure );
+
+ // leave space for children even if the DataMember hasn't been initialized
+ // (pDataChild is null then, this happens when no values for it are in this row)
+ bool bHasChild = ( pRefChild != nullptr );
+
+ if ( bHasChild )
+ {
+ if ( bTitleLine ) // in tabular layout the title is on a separate column
+ ++rFilterCxt.mnCol; // -> fill child dimension one column below
+
+ if ( pDataChild )
+ {
+ tools::Long nOldCol = rFilterCxt.mnCol;
+ pDataChild->FillDataRow(pRefChild, rFilterCxt, rSequence, nMeasure, bIsSubTotalRow, rSubState);
+ rFilterCxt.mnCol = nOldCol; // Revert to the old column value before the call.
+ }
+ rFilterCxt.mnCol += static_cast<sal_uInt16>(pRefMember->GetSize( nMeasure ));
+
+ if ( bTitleLine ) // title column is included in GetSize, so the following
+ --rFilterCxt.mnCol; // positions are calculated with the normal values
+ }
+
+ tools::Long nUserSubStart;
+ tools::Long nUserSubCount = pRefMember->GetSubTotalCount(&nUserSubStart);
+ if ( !nUserSubCount && bHasChild )
+ return;
+
+ // Calculate at least automatic if no subtotals are selected,
+ // show only own values if there's no child dimension (innermost).
+ if ( !nUserSubCount || !bHasChild )
+ {
+ nUserSubCount = 1;
+ nUserSubStart = 0;
+ }
+
+ ScDPSubTotalState aLocalSubState(rSubState); // keep row state, modify column
+
+ tools::Long nMemberMeasure = nMeasure;
+ tools::Long nSubSize = pResultData->GetCountForMeasure(nMeasure);
+ if (bHasChild)
+ {
+ rFilterCxt.mnCol -= nSubSize * ( nUserSubCount - nUserSubStart ); // GetSize includes space for SubTotal
+ rFilterCxt.mnCol -= nExtraSpace; // GetSize includes the empty line
+ }
+
+ tools::Long nMoveSubTotal = 0;
+ if ( bSubTotalInTitle )
+ {
+ nMoveSubTotal = rFilterCxt.mnCol - nStartCol; // force to first (title) column
+ rFilterCxt.mnCol = nStartCol;
+ }
+
+ for (tools::Long nUserPos=nUserSubStart; nUserPos<nUserSubCount; nUserPos++)
+ {
+ if ( pChildDimension && nUserSubCount > 1 )
+ {
+ const ScDPLevel* pForceLevel = pResultMember ? pResultMember->GetParentLevel() : nullptr;
+ aLocalSubState.nColSubTotalFunc = nUserPos;
+ aLocalSubState.eColForce = lcl_GetForceFunc( pForceLevel, nUserPos );
+ }
+
+ for ( tools::Long nSubCount=0; nSubCount<nSubSize; nSubCount++ )
+ {
+ if ( nMeasure == SC_DPMEASURE_ALL )
+ nMemberMeasure = nSubCount;
+
+ OSL_ENSURE( rFilterCxt.mnCol < rSequence.getLength(), "bumm" );
+ sheet::DataResult& rRes = rSequence.getArray()[rFilterCxt.mnCol];
+
+ if ( HasData( nMemberMeasure, aLocalSubState ) )
+ {
+ if ( HasError( nMemberMeasure, aLocalSubState ) )
+ {
+ rRes.Value = 0;
+ rRes.Flags |= sheet::DataResultFlags::ERROR;
+ }
+ else
+ {
+ rRes.Value = GetAggregate( nMemberMeasure, aLocalSubState );
+ rRes.Flags |= sheet::DataResultFlags::HASDATA;
+ }
+ }
+
+ if ( bHasChild || bIsSubTotalRow )
+ rRes.Flags |= sheet::DataResultFlags::SUBTOTAL;
+
+ rFilterCxt.maFilterSet.add(rFilterCxt.maFilters, rRes.Value);
+ rFilterCxt.mnCol += 1;
+ }
+ }
+
+ // add extra space again if subtracted from GetSize above,
+ // add to own size if no children
+ rFilterCxt.mnCol += nExtraSpace;
+ rFilterCxt.mnCol += nMoveSubTotal;
+}
+
+void ScDPDataMember::UpdateDataRow(
+ const ScDPResultMember* pRefMember, tools::Long nMeasure, bool bIsSubTotalRow,
+ const ScDPSubTotalState& rSubState )
+{
+ OSL_ENSURE( pRefMember == pResultMember || !pResultMember, "bla" );
+
+ // Calculate must be called even if not visible (for use as reference value)
+ const ScDPDataDimension* pDataChild = GetChildDimension();
+ const ScDPResultDimension* pRefChild = pRefMember->GetChildDimension();
+
+ // leave space for children even if the DataMember hasn't been initialized
+ // (pDataChild is null then, this happens when no values for it are in this row)
+ bool bHasChild = ( pRefChild != nullptr );
+
+ // process subtotals even if not shown
+ tools::Long nUserSubCount = pRefMember->GetSubTotalCount();
+
+ // Calculate at least automatic if no subtotals are selected,
+ // show only own values if there's no child dimension (innermost).
+ if ( !nUserSubCount || !bHasChild )
+ nUserSubCount = 1;
+
+ ScDPSubTotalState aLocalSubState(rSubState); // keep row state, modify column
+
+ tools::Long nMemberMeasure = nMeasure;
+ tools::Long nSubSize = pResultData->GetCountForMeasure(nMeasure);
+
+ for (tools::Long nUserPos=0; nUserPos<nUserSubCount; nUserPos++) // including hidden "automatic"
+ {
+ if ( pChildDimension && nUserSubCount > 1 )
+ {
+ const ScDPLevel* pForceLevel = pResultMember ? pResultMember->GetParentLevel() : nullptr;
+ aLocalSubState.nColSubTotalFunc = nUserPos;
+ aLocalSubState.eColForce = lcl_GetForceFunc( pForceLevel, nUserPos );
+ }
+
+ for ( tools::Long nSubCount=0; nSubCount<nSubSize; nSubCount++ )
+ {
+ if ( nMeasure == SC_DPMEASURE_ALL )
+ nMemberMeasure = nSubCount;
+
+ // update data...
+ ScDPAggData* pAggData = GetAggData( nMemberMeasure, aLocalSubState );
+ if (pAggData)
+ {
+ //TODO: aLocalSubState?
+ ScSubTotalFunc eFunc = pResultData->GetMeasureFunction( nMemberMeasure );
+ sheet::DataPilotFieldReference aReferenceValue = pResultData->GetMeasureRefVal( nMemberMeasure );
+ sal_Int32 eRefType = aReferenceValue.ReferenceType;
+
+ // calculate the result first - for all members, regardless of reference value
+ pAggData->Calculate( eFunc, aLocalSubState );
+
+ if ( eRefType == sheet::DataPilotFieldReferenceType::ITEM_DIFFERENCE ||
+ eRefType == sheet::DataPilotFieldReferenceType::ITEM_PERCENTAGE ||
+ eRefType == sheet::DataPilotFieldReferenceType::ITEM_PERCENTAGE_DIFFERENCE )
+ {
+ // copy the result into auxiliary value, so differences can be
+ // calculated in any order
+ pAggData->SetAuxiliary( pAggData->GetResult() );
+ }
+ // column/row percentage/index is now in UpdateRunningTotals, so it doesn't disturb sorting
+ }
+ }
+ }
+
+ if ( bHasChild ) // child dimension must be processed last, so the row total is known
+ {
+ if ( pDataChild )
+ pDataChild->UpdateDataRow( pRefChild, nMeasure, bIsSubTotalRow, rSubState );
+ }
+}
+
+void ScDPDataMember::SortMembers( ScDPResultMember* pRefMember )
+{
+ OSL_ENSURE( pRefMember == pResultMember || !pResultMember, "bla" );
+
+ if ( pRefMember->IsVisible() ) //TODO: here or in ScDPDataDimension ???
+ {
+ ScDPDataDimension* pDataChild = GetChildDimension();
+ ScDPResultDimension* pRefChild = pRefMember->GetChildDimension();
+ if ( pRefChild && pDataChild )
+ pDataChild->SortMembers( pRefChild ); // sorting is done at the dimension
+ }
+}
+
+void ScDPDataMember::DoAutoShow( ScDPResultMember* pRefMember )
+{
+ OSL_ENSURE( pRefMember == pResultMember || !pResultMember, "bla" );
+
+ if ( pRefMember->IsVisible() ) //TODO: here or in ScDPDataDimension ???
+ {
+ ScDPDataDimension* pDataChild = GetChildDimension();
+ ScDPResultDimension* pRefChild = pRefMember->GetChildDimension();
+ if ( pRefChild && pDataChild )
+ pDataChild->DoAutoShow( pRefChild ); // sorting is done at the dimension
+ }
+}
+
+void ScDPDataMember::ResetResults()
+{
+ aAggregate.Reset();
+
+ ScDPDataDimension* pDataChild = GetChildDimension();
+ if ( pDataChild )
+ pDataChild->ResetResults();
+}
+
+void ScDPDataMember::UpdateRunningTotals(
+ const ScDPResultMember* pRefMember, tools::Long nMeasure, bool bIsSubTotalRow,
+ const ScDPSubTotalState& rSubState, ScDPRunningTotalState& rRunning,
+ ScDPRowTotals& rTotals, const ScDPResultMember& rRowParent )
+{
+ OSL_ENSURE( pRefMember == pResultMember || !pResultMember, "bla" );
+
+ const ScDPDataDimension* pDataChild = GetChildDimension();
+ const ScDPResultDimension* pRefChild = pRefMember->GetChildDimension();
+
+ bool bIsRoot = ( pResultMember == nullptr || pResultMember->GetParentLevel() == nullptr );
+
+ // leave space for children even if the DataMember hasn't been initialized
+ // (pDataChild is null then, this happens when no values for it are in this row)
+ bool bHasChild = ( pRefChild != nullptr );
+
+ tools::Long nUserSubCount = pRefMember->GetSubTotalCount();
+ {
+ // Calculate at least automatic if no subtotals are selected,
+ // show only own values if there's no child dimension (innermost).
+ if ( !nUserSubCount || !bHasChild )
+ nUserSubCount = 1;
+
+ ScDPSubTotalState aLocalSubState(rSubState); // keep row state, modify column
+
+ tools::Long nMemberMeasure = nMeasure;
+ tools::Long nSubSize = pResultData->GetCountForMeasure(nMeasure);
+
+ for (tools::Long nUserPos=0; nUserPos<nUserSubCount; nUserPos++) // including hidden "automatic"
+ {
+ if ( pChildDimension && nUserSubCount > 1 )
+ {
+ const ScDPLevel* pForceLevel = pResultMember ? pResultMember->GetParentLevel() : nullptr;
+ aLocalSubState.nColSubTotalFunc = nUserPos;
+ aLocalSubState.eColForce = lcl_GetForceFunc( pForceLevel, nUserPos );
+ }
+
+ for ( tools::Long nSubCount=0; nSubCount<nSubSize; nSubCount++ )
+ {
+ if ( nMeasure == SC_DPMEASURE_ALL )
+ nMemberMeasure = nSubCount;
+
+ // update data...
+ ScDPAggData* pAggData = GetAggData( nMemberMeasure, aLocalSubState );
+ if (pAggData)
+ {
+ //TODO: aLocalSubState?
+ sheet::DataPilotFieldReference aReferenceValue = pResultData->GetMeasureRefVal( nMemberMeasure );
+ sal_Int32 eRefType = aReferenceValue.ReferenceType;
+
+ if ( eRefType == sheet::DataPilotFieldReferenceType::RUNNING_TOTAL ||
+ eRefType == sheet::DataPilotFieldReferenceType::ITEM_DIFFERENCE ||
+ eRefType == sheet::DataPilotFieldReferenceType::ITEM_PERCENTAGE ||
+ eRefType == sheet::DataPilotFieldReferenceType::ITEM_PERCENTAGE_DIFFERENCE )
+ {
+ bool bRunningTotal = ( eRefType == sheet::DataPilotFieldReferenceType::RUNNING_TOTAL );
+ bool bRelative =
+ ( aReferenceValue.ReferenceItemType != sheet::DataPilotFieldReferenceItemType::NAMED && !bRunningTotal );
+ tools::Long nRelativeDir = bRelative ?
+ ( ( aReferenceValue.ReferenceItemType == sheet::DataPilotFieldReferenceItemType::PREVIOUS ) ? -1 : 1 ) : 0;
+
+ const ScDPRunningTotalState::IndexArray& rColVisible = rRunning.GetColVisible();
+ const ScDPRunningTotalState::IndexArray& rColSorted = rRunning.GetColSorted();
+ const ScDPRunningTotalState::IndexArray& rRowVisible = rRunning.GetRowVisible();
+ const ScDPRunningTotalState::IndexArray& rRowSorted = rRunning.GetRowSorted();
+
+ OUString aRefFieldName = aReferenceValue.ReferenceField;
+
+ //TODO: aLocalSubState?
+ sheet::DataPilotFieldOrientation nRefOrient = pResultData->GetMeasureRefOrient( nMemberMeasure );
+ bool bRefDimInCol = ( nRefOrient == sheet::DataPilotFieldOrientation_COLUMN );
+ bool bRefDimInRow = ( nRefOrient == sheet::DataPilotFieldOrientation_ROW );
+
+ ScDPResultDimension* pSelectDim = nullptr;
+ sal_Int32 nRowPos = 0;
+ sal_Int32 nColPos = 0;
+
+ // find the reference field in column or row dimensions
+
+ if ( bRefDimInRow ) // look in row dimensions
+ {
+ pSelectDim = rRunning.GetRowResRoot()->GetChildDimension();
+ while ( pSelectDim && pSelectDim->GetName() != aRefFieldName )
+ {
+ tools::Long nIndex = rRowSorted[nRowPos];
+ if ( nIndex >= 0 && nIndex < pSelectDim->GetMemberCount() )
+ pSelectDim = pSelectDim->GetMember(nIndex)->GetChildDimension();
+ else
+ pSelectDim = nullptr;
+ ++nRowPos;
+ }
+ // child dimension of innermost member?
+ if ( pSelectDim && rRowSorted[nRowPos] < 0 )
+ pSelectDim = nullptr;
+ }
+
+ if ( bRefDimInCol ) // look in column dimensions
+ {
+ pSelectDim = rRunning.GetColResRoot()->GetChildDimension();
+ while ( pSelectDim && pSelectDim->GetName() != aRefFieldName )
+ {
+ tools::Long nIndex = rColSorted[nColPos];
+ if ( nIndex >= 0 && nIndex < pSelectDim->GetMemberCount() )
+ pSelectDim = pSelectDim->GetMember(nIndex)->GetChildDimension();
+ else
+ pSelectDim = nullptr;
+ ++nColPos;
+ }
+ // child dimension of innermost member?
+ if ( pSelectDim && rColSorted[nColPos] < 0 )
+ pSelectDim = nullptr;
+ }
+
+ bool bNoDetailsInRef = false;
+ if ( pSelectDim && bRunningTotal )
+ {
+ // Running totals:
+ // If details are hidden for this member in the reference dimension,
+ // don't show or sum up the value. Otherwise, for following members,
+ // the running totals of details and subtotals wouldn't match.
+
+ tools::Long nMyIndex = bRefDimInCol ? rColSorted[nColPos] : rRowSorted[nRowPos];
+ if ( nMyIndex >= 0 && nMyIndex < pSelectDim->GetMemberCount() )
+ {
+ const ScDPResultMember* pMyRefMember = pSelectDim->GetMember(nMyIndex);
+ if ( pMyRefMember && pMyRefMember->HasHiddenDetails() )
+ {
+ pSelectDim = nullptr; // don't calculate
+ bNoDetailsInRef = true; // show error, not empty
+ }
+ }
+ }
+
+ if ( bRelative )
+ {
+ // Difference/Percentage from previous/next:
+ // If details are hidden for this member in the innermost column/row
+ // dimension (the orientation of the reference dimension), show an
+ // error value.
+ // - If the no-details dimension is the reference dimension, its
+ // members will be skipped when finding the previous/next member,
+ // so there must be no results for its members.
+ // - If the no-details dimension is outside of the reference dimension,
+ // no calculation in the reference dimension is possible.
+ // - Otherwise, the error isn't strictly necessary, but shown for
+ // consistency.
+
+ bool bInnerNoDetails = bRefDimInCol ? HasHiddenDetails() :
+ ( !bRefDimInRow || rRowParent.HasHiddenDetails() );
+ if ( bInnerNoDetails )
+ {
+ pSelectDim = nullptr;
+ bNoDetailsInRef = true; // show error, not empty
+ }
+ }
+
+ if ( !bRefDimInCol && !bRefDimInRow ) // invalid dimension specified
+ bNoDetailsInRef = true; // pSelectDim is then already NULL
+
+ // get the member for the reference item and do the calculation
+
+ if ( bRunningTotal )
+ {
+ // running total in (dimension) -> find first existing member
+
+ if ( pSelectDim )
+ {
+ ScDPDataMember* pSelectMember;
+ if ( bRefDimInCol )
+ pSelectMember = ScDPResultDimension::GetColReferenceMember( nullptr, nullptr,
+ nColPos, rRunning );
+ else
+ {
+ const sal_Int32* pRowSorted = rRowSorted.data();
+ const sal_Int32* pColSorted = rColSorted.data();
+ pRowSorted += nRowPos + 1; // including the reference dimension
+ pSelectMember = pSelectDim->GetRowReferenceMember(
+ nullptr, nullptr, pRowSorted, pColSorted);
+ }
+
+ if ( pSelectMember )
+ {
+ // The running total is kept as the auxiliary value in
+ // the first available member for the reference dimension.
+ // Members are visited in final order, so each one's result
+ // can be used and then modified.
+
+ ScDPAggData* pSelectData = pSelectMember->
+ GetAggData( nMemberMeasure, aLocalSubState );
+ if ( pSelectData )
+ {
+ double fTotal = pSelectData->GetAuxiliary();
+ fTotal += pAggData->GetResult();
+ pSelectData->SetAuxiliary( fTotal );
+ pAggData->SetResult( fTotal );
+ pAggData->SetEmpty(false); // always display
+ }
+ }
+ else
+ pAggData->SetError();
+ }
+ else if (bNoDetailsInRef)
+ pAggData->SetError();
+ else
+ pAggData->SetEmpty(true); // empty (dim set to 0 above)
+ }
+ else
+ {
+ // difference/percentage -> find specified member
+
+ if ( pSelectDim )
+ {
+ OUString aRefItemName = aReferenceValue.ReferenceItemName;
+ ScDPRelativePos aRefItemPos( 0, nRelativeDir ); // nBasePos is modified later
+
+ const OUString* pRefName = nullptr;
+ const ScDPRelativePos* pRefPos = nullptr;
+ if ( bRelative )
+ pRefPos = &aRefItemPos;
+ else
+ pRefName = &aRefItemName;
+
+ ScDPDataMember* pSelectMember;
+ if ( bRefDimInCol )
+ {
+ aRefItemPos.nBasePos = rColVisible[nColPos]; // without sort order applied
+ pSelectMember = ScDPResultDimension::GetColReferenceMember( pRefPos, pRefName,
+ nColPos, rRunning );
+ }
+ else
+ {
+ aRefItemPos.nBasePos = rRowVisible[nRowPos]; // without sort order applied
+ const sal_Int32* pRowSorted = rRowSorted.data();
+ const sal_Int32* pColSorted = rColSorted.data();
+ pRowSorted += nRowPos + 1; // including the reference dimension
+ pSelectMember = pSelectDim->GetRowReferenceMember(
+ pRefPos, pRefName, pRowSorted, pColSorted);
+ }
+
+ // difference or perc.difference is empty for the reference item itself
+ if ( pSelectMember == this &&
+ eRefType != sheet::DataPilotFieldReferenceType::ITEM_PERCENTAGE )
+ {
+ pAggData->SetEmpty(true);
+ }
+ else if ( pSelectMember )
+ {
+ const ScDPAggData* pOtherAggData = pSelectMember->
+ GetConstAggData( nMemberMeasure, aLocalSubState );
+ OSL_ENSURE( pOtherAggData, "no agg data" );
+ if ( pOtherAggData )
+ {
+ // Reference member may be visited before or after this one,
+ // so the auxiliary value is used for the original result.
+
+ double fOtherResult = pOtherAggData->GetAuxiliary();
+ double fThisResult = pAggData->GetResult();
+ bool bError = false;
+ switch ( eRefType )
+ {
+ case sheet::DataPilotFieldReferenceType::ITEM_DIFFERENCE:
+ fThisResult = fThisResult - fOtherResult;
+ break;
+ case sheet::DataPilotFieldReferenceType::ITEM_PERCENTAGE:
+ if ( fOtherResult == 0.0 )
+ bError = true;
+ else
+ fThisResult = fThisResult / fOtherResult;
+ break;
+ case sheet::DataPilotFieldReferenceType::ITEM_PERCENTAGE_DIFFERENCE:
+ if ( fOtherResult == 0.0 )
+ bError = true;
+ else
+ fThisResult = ( fThisResult - fOtherResult ) / fOtherResult;
+ break;
+ default:
+ OSL_FAIL("invalid calculation type");
+ }
+ if ( bError )
+ {
+ pAggData->SetError();
+ }
+ else
+ {
+ pAggData->SetResult(fThisResult);
+ pAggData->SetEmpty(false); // always display
+ }
+ //TODO: errors in data?
+ }
+ }
+ else if (bRelative && !bNoDetailsInRef)
+ pAggData->SetEmpty(true); // empty
+ else
+ pAggData->SetError(); // error
+ }
+ else if (bNoDetailsInRef)
+ pAggData->SetError(); // error
+ else
+ pAggData->SetEmpty(true); // empty
+ }
+ }
+ else if ( eRefType == sheet::DataPilotFieldReferenceType::ROW_PERCENTAGE ||
+ eRefType == sheet::DataPilotFieldReferenceType::COLUMN_PERCENTAGE ||
+ eRefType == sheet::DataPilotFieldReferenceType::TOTAL_PERCENTAGE ||
+ eRefType == sheet::DataPilotFieldReferenceType::INDEX )
+ {
+
+ // set total values when they are encountered (always before their use)
+
+ ScDPAggData* pColTotalData = pRefMember->GetColTotal( nMemberMeasure );
+ ScDPAggData* pRowTotalData = rTotals.GetRowTotal( nMemberMeasure );
+ ScDPAggData* pGrandTotalData = rTotals.GetGrandTotal( nMemberMeasure );
+
+ double fTotalValue = pAggData->HasError() ? 0 : pAggData->GetResult();
+
+ if ( bIsRoot && rTotals.IsInColRoot() && pGrandTotalData )
+ pGrandTotalData->SetAuxiliary( fTotalValue );
+
+ if ( bIsRoot && pRowTotalData )
+ pRowTotalData->SetAuxiliary( fTotalValue );
+
+ if ( rTotals.IsInColRoot() && pColTotalData )
+ pColTotalData->SetAuxiliary( fTotalValue );
+
+ // find relation to total values
+
+ switch ( eRefType )
+ {
+ case sheet::DataPilotFieldReferenceType::ROW_PERCENTAGE:
+ case sheet::DataPilotFieldReferenceType::COLUMN_PERCENTAGE:
+ case sheet::DataPilotFieldReferenceType::TOTAL_PERCENTAGE:
+ {
+ double nTotal;
+ if ( eRefType == sheet::DataPilotFieldReferenceType::ROW_PERCENTAGE )
+ nTotal = pRowTotalData ? pRowTotalData->GetAuxiliary() : 0.0;
+ else if ( eRefType == sheet::DataPilotFieldReferenceType::COLUMN_PERCENTAGE )
+ nTotal = pColTotalData ? pColTotalData->GetAuxiliary() : 0.0;
+ else
+ nTotal = pGrandTotalData ? pGrandTotalData->GetAuxiliary() : 0.0;
+
+ if ( nTotal == 0.0 )
+ pAggData->SetError();
+ else
+ pAggData->SetResult( pAggData->GetResult() / nTotal );
+ }
+ break;
+ case sheet::DataPilotFieldReferenceType::INDEX:
+ {
+ double nColTotal = pColTotalData ? pColTotalData->GetAuxiliary() : 0.0;
+ double nRowTotal = pRowTotalData ? pRowTotalData->GetAuxiliary() : 0.0;
+ double nGrandTotal = pGrandTotalData ? pGrandTotalData->GetAuxiliary() : 0.0;
+ if ( nRowTotal == 0.0 || nColTotal == 0.0 )
+ pAggData->SetError();
+ else
+ pAggData->SetResult(
+ ( pAggData->GetResult() * nGrandTotal ) /
+ ( nRowTotal * nColTotal ) );
+ }
+ break;
+ }
+ }
+ }
+ }
+ }
+ }
+
+ if ( bHasChild ) // child dimension must be processed last, so the row total is known
+ {
+ if ( pDataChild )
+ pDataChild->UpdateRunningTotals( pRefChild, nMeasure,
+ bIsSubTotalRow, rSubState, rRunning, rTotals, rRowParent );
+ }
+}
+
+#if DUMP_PIVOT_TABLE
+void ScDPDataMember::DumpState( const ScDPResultMember* pRefMember, ScDocument* pDoc, ScAddress& rPos ) const
+{
+ dumpRow("ScDPDataMember", GetName(), &aAggregate, pDoc, rPos);
+ SCROW nStartRow = rPos.Row();
+
+ const ScDPDataDimension* pDataChild = GetChildDimension();
+ const ScDPResultDimension* pRefChild = pRefMember->GetChildDimension();
+ if ( pDataChild && pRefChild )
+ pDataChild->DumpState( pRefChild, pDoc, rPos );
+
+ indent(pDoc, nStartRow, rPos);
+}
+
+void ScDPDataMember::Dump(int nIndent) const
+{
+ std::string aIndent(nIndent*2, ' ');
+ std::cout << aIndent << "-- data member '"
+ << (pResultMember ? pResultMember->GetName() : OUString()) << "'" << std::endl;
+ for (const ScDPAggData* pAgg = &aAggregate; pAgg; pAgg = pAgg->GetExistingChild())
+ pAgg->Dump(nIndent+1);
+
+ if (pChildDimension)
+ pChildDimension->Dump(nIndent+1);
+}
+#endif
+
+// Helper class to select the members to include in
+// ScDPResultDimension::InitFrom or LateInitFrom if groups are used
+
+namespace {
+
+class ScDPGroupCompare
+{
+private:
+ const ScDPResultData* pResultData;
+ const ScDPInitState& rInitState;
+ tools::Long nDimSource;
+ bool bIncludeAll;
+ bool bIsBase;
+ tools::Long nGroupBase;
+public:
+ ScDPGroupCompare( const ScDPResultData* pData, const ScDPInitState& rState, tools::Long nDimension );
+
+ bool IsIncluded( const ScDPMember& rMember ) { return bIncludeAll || TestIncluded( rMember ); }
+ bool TestIncluded( const ScDPMember& rMember );
+};
+
+}
+
+ScDPGroupCompare::ScDPGroupCompare( const ScDPResultData* pData, const ScDPInitState& rState, tools::Long nDimension ) :
+ pResultData( pData ),
+ rInitState( rState ),
+ nDimSource( nDimension )
+{
+ bIsBase = pResultData->IsBaseForGroup( nDimSource );
+ nGroupBase = pResultData->GetGroupBase( nDimSource ); //TODO: get together in one call?
+
+ // if bIncludeAll is set, TestIncluded doesn't need to be called
+ bIncludeAll = !( bIsBase || nGroupBase >= 0 );
+}
+
+bool ScDPGroupCompare::TestIncluded( const ScDPMember& rMember )
+{
+ bool bInclude = true;
+ if ( bIsBase )
+ {
+ // need to check all previous groups
+ //TODO: get array of groups (or indexes) before loop?
+ ScDPItemData aMemberData(rMember.FillItemData());
+
+ const std::vector<ScDPInitState::Member>& rMemStates = rInitState.GetMembers();
+ bInclude = std::all_of(rMemStates.begin(), rMemStates.end(),
+ [this, &aMemberData](const ScDPInitState::Member& rMem) {
+ return (pResultData->GetGroupBase(rMem.mnSrcIndex) != nDimSource)
+ || pResultData->IsInGroup(rMem.mnNameIndex, rMem.mnSrcIndex, aMemberData, nDimSource);
+ });
+ }
+ else if ( nGroupBase >= 0 )
+ {
+ // base isn't used in preceding fields
+ // -> look for other groups using the same base
+
+ //TODO: get array of groups (or indexes) before loop?
+ ScDPItemData aMemberData(rMember.FillItemData());
+ const std::vector<ScDPInitState::Member>& rMemStates = rInitState.GetMembers();
+ bInclude = std::all_of(rMemStates.begin(), rMemStates.end(),
+ [this, &aMemberData](const ScDPInitState::Member& rMem) {
+ // coverity[copy_paste_error : FALSE] - same base (hierarchy between
+ // the two groups is irrelevant)
+ return (pResultData->GetGroupBase(rMem.mnSrcIndex) != nGroupBase)
+ || pResultData->HasCommonElement(rMem.mnNameIndex, rMem.mnSrcIndex, aMemberData, nDimSource);
+ });
+ }
+
+ return bInclude;
+}
+
+ScDPResultDimension::ScDPResultDimension( const ScDPResultData* pData ) :
+ pResultData( pData ),
+ nSortMeasure( 0 ),
+ bIsDataLayout( false ),
+ bSortByData( false ),
+ bSortAscending( false ),
+ bAutoShow( false ),
+ bAutoTopItems( false ),
+ bInitialized( false ),
+ nAutoMeasure( 0 ),
+ nAutoCount( 0 )
+{
+}
+
+ScDPResultDimension::~ScDPResultDimension()
+{
+}
+
+ScDPResultMember *ScDPResultDimension::FindMember( SCROW iData ) const
+{
+ if( bIsDataLayout )
+ {
+ SAL_WARN_IF(maMemberArray.empty(), "sc.core", "MemberArray is empty");
+ return !maMemberArray.empty() ? maMemberArray[0].get() : nullptr;
+ }
+
+ MemberHash::const_iterator aRes = maMemberHash.find( iData );
+ if( aRes != maMemberHash.end()) {
+ if ( aRes->second->IsNamedItem( iData ) )
+ return aRes->second;
+ OSL_FAIL("problem! hash result is not the same as IsNamedItem");
+ }
+
+ unsigned int i;
+ unsigned int nCount = maMemberArray.size();
+ for( i = 0; i < nCount ; i++ )
+ {
+ ScDPResultMember* pResultMember = maMemberArray[i].get();
+ if ( pResultMember->IsNamedItem( iData ) )
+ return pResultMember;
+ }
+ return nullptr;
+}
+
+void ScDPResultDimension::InitFrom(
+ const vector<ScDPDimension*>& ppDim, const vector<ScDPLevel*>& ppLev,
+ size_t nPos, ScDPInitState& rInitState, bool bInitChild )
+{
+ if (nPos >= ppDim.size() || nPos >= ppLev.size())
+ {
+ bInitialized = true;
+ return;
+ }
+
+ ScDPDimension* pThisDim = ppDim[nPos];
+ ScDPLevel* pThisLevel = ppLev[nPos];
+
+ if (!pThisDim || !pThisLevel)
+ {
+ bInitialized = true;
+ return;
+ }
+
+ bIsDataLayout = pThisDim->getIsDataLayoutDimension(); // member
+ aDimensionName = pThisDim->getName(); // member
+
+ // Check the autoshow setting. If it's enabled, store the settings.
+ const sheet::DataPilotFieldAutoShowInfo& rAutoInfo = pThisLevel->GetAutoShow();
+ if ( rAutoInfo.IsEnabled )
+ {
+ bAutoShow = true;
+ bAutoTopItems = ( rAutoInfo.ShowItemsMode == sheet::DataPilotFieldShowItemsMode::FROM_TOP );
+ nAutoMeasure = pThisLevel->GetAutoMeasure();
+ nAutoCount = rAutoInfo.ItemCount;
+ }
+
+ // Check the sort info, and store the settings if appropriate.
+ const sheet::DataPilotFieldSortInfo& rSortInfo = pThisLevel->GetSortInfo();
+ if ( rSortInfo.Mode == sheet::DataPilotFieldSortMode::DATA )
+ {
+ bSortByData = true;
+ bSortAscending = rSortInfo.IsAscending;
+ nSortMeasure = pThisLevel->GetSortMeasure();
+ }
+
+ // global order is used to initialize aMembers, so it doesn't have to be looked at later
+ const ScMemberSortOrder& rGlobalOrder = pThisLevel->GetGlobalOrder();
+
+ tools::Long nDimSource = pThisDim->GetDimension(); //TODO: check GetSourceDim?
+ ScDPGroupCompare aCompare( pResultData, rInitState, nDimSource );
+
+ // Now, go through all members and initialize them.
+ ScDPMembers* pMembers = pThisLevel->GetMembersObject();
+ tools::Long nMembCount = pMembers->getCount();
+ for ( tools::Long i=0; i<nMembCount; i++ )
+ {
+ tools::Long nSorted = rGlobalOrder.empty() ? i : rGlobalOrder[i];
+
+ ScDPMember* pMember = pMembers->getByIndex(nSorted);
+ if ( aCompare.IsIncluded( *pMember ) )
+ {
+ ScDPParentDimData aData( i, pThisDim, pThisLevel, pMember);
+ ScDPResultMember* pNew = AddMember( aData );
+
+ rInitState.AddMember(nDimSource, pNew->GetDataId());
+ pNew->InitFrom( ppDim, ppLev, nPos+1, rInitState, bInitChild );
+ rInitState.RemoveMember();
+ }
+ }
+ bInitialized = true;
+}
+
+void ScDPResultDimension::LateInitFrom(
+ LateInitParams& rParams, const vector<SCROW>& pItemData, size_t nPos, ScDPInitState& rInitState)
+{
+ if ( rParams.IsEnd( nPos ) )
+ return;
+ if (nPos >= pItemData.size())
+ {
+ SAL_WARN("sc.core", "pos " << nPos << ", but vector size is " << pItemData.size());
+ return;
+ }
+ SCROW rThisData = pItemData[nPos];
+ ScDPDimension* pThisDim = rParams.GetDim( nPos );
+ ScDPLevel* pThisLevel = rParams.GetLevel( nPos );
+
+ if (!pThisDim || !pThisLevel)
+ return;
+
+ tools::Long nDimSource = pThisDim->GetDimension(); //TODO: check GetSourceDim?
+
+ bool bShowEmpty = pThisLevel->getShowEmpty();
+
+ if ( !bInitialized )
+ { // init some values
+ // create all members at the first call (preserve order)
+ bIsDataLayout = pThisDim->getIsDataLayoutDimension();
+ aDimensionName = pThisDim->getName();
+
+ const sheet::DataPilotFieldAutoShowInfo& rAutoInfo = pThisLevel->GetAutoShow();
+ if ( rAutoInfo.IsEnabled )
+ {
+ bAutoShow = true;
+ bAutoTopItems = ( rAutoInfo.ShowItemsMode == sheet::DataPilotFieldShowItemsMode::FROM_TOP );
+ nAutoMeasure = pThisLevel->GetAutoMeasure();
+ nAutoCount = rAutoInfo.ItemCount;
+ }
+
+ const sheet::DataPilotFieldSortInfo& rSortInfo = pThisLevel->GetSortInfo();
+ if ( rSortInfo.Mode == sheet::DataPilotFieldSortMode::DATA )
+ {
+ bSortByData = true;
+ bSortAscending = rSortInfo.IsAscending;
+ nSortMeasure = pThisLevel->GetSortMeasure();
+ }
+ }
+
+ bool bLateInitAllMembers= bIsDataLayout || rParams.GetInitAllChild() || bShowEmpty;
+
+ if ( !bLateInitAllMembers )
+ {
+ ResultMembers& rMembers = pResultData->GetDimResultMembers(nDimSource, pThisDim, pThisLevel);
+ bLateInitAllMembers = rMembers.IsHasHideDetailsMembers();
+
+ SAL_INFO("sc.core", aDimensionName << (rMembers.IsHasHideDetailsMembers() ? " HasHideDetailsMembers" : ""));
+
+ rMembers.SetHasHideDetailsMembers( false );
+ }
+
+ bool bNewAllMembers = (!rParams.IsRow()) || nPos == 0 || bLateInitAllMembers;
+
+ if (bNewAllMembers )
+ {
+ // global order is used to initialize aMembers, so it doesn't have to be looked at later
+ if ( !bInitialized )
+ { //init all members
+ const ScMemberSortOrder& rGlobalOrder = pThisLevel->GetGlobalOrder();
+
+ ScDPGroupCompare aCompare( pResultData, rInitState, nDimSource );
+ ScDPMembers* pMembers = pThisLevel->GetMembersObject();
+ tools::Long nMembCount = pMembers->getCount();
+ for ( tools::Long i=0; i<nMembCount; i++ )
+ {
+ tools::Long nSorted = rGlobalOrder.empty() ? i : rGlobalOrder[i];
+
+ ScDPMember* pMember = pMembers->getByIndex(nSorted);
+ if ( aCompare.IsIncluded( *pMember ) )
+ { // add all members
+ ScDPParentDimData aData( i, pThisDim, pThisLevel, pMember );
+ AddMember( aData );
+ }
+ }
+ bInitialized = true; // don't call again, even if no members were included
+ }
+ // initialize only specific member (or all if "show empty" flag is set)
+ if ( bLateInitAllMembers )
+ {
+ tools::Long nCount = maMemberArray.size();
+ for (tools::Long i=0; i<nCount; i++)
+ {
+ ScDPResultMember* pResultMember = maMemberArray[i].get();
+
+ // check show empty
+ bool bAllChildren = false;
+ if( bShowEmpty )
+ {
+ bAllChildren = !pResultMember->IsNamedItem( rThisData );
+ }
+ rParams.SetInitAllChildren( bAllChildren );
+ rInitState.AddMember( nDimSource, pResultMember->GetDataId() );
+ pResultMember->LateInitFrom( rParams, pItemData, nPos+1, rInitState );
+ rInitState.RemoveMember();
+ }
+ }
+ else
+ {
+ ScDPResultMember* pResultMember = FindMember( rThisData );
+ if( nullptr != pResultMember )
+ {
+ rInitState.AddMember( nDimSource, pResultMember->GetDataId() );
+ pResultMember->LateInitFrom( rParams, pItemData, nPos+1, rInitState );
+ rInitState.RemoveMember();
+ }
+ }
+ }
+ else
+ InitWithMembers( rParams, pItemData, nPos, rInitState );
+}
+
+tools::Long ScDPResultDimension::GetSize(tools::Long nMeasure) const
+{
+ tools::Long nMemberCount = maMemberArray.size();
+ if (!nMemberCount)
+ return 0;
+
+ tools::Long nTotal = 0;
+ if (bIsDataLayout)
+ {
+ OSL_ENSURE(nMeasure == SC_DPMEASURE_ALL || pResultData->GetMeasureCount() == 1,
+ "DataLayout dimension twice?");
+ // repeat first member...
+ nTotal = nMemberCount * maMemberArray[0]->GetSize(0); // all measures have equal size
+ }
+ else
+ {
+ // add all members
+ for (tools::Long nMem=0; nMem<nMemberCount; nMem++)
+ nTotal += maMemberArray[nMem]->GetSize(nMeasure);
+ }
+ return nTotal;
+}
+
+bool ScDPResultDimension::IsValidEntry( const vector< SCROW >& aMembers ) const
+{
+ if (aMembers.empty())
+ return false;
+
+ const ScDPResultMember* pMember = FindMember( aMembers[0] );
+ if ( nullptr != pMember )
+ return pMember->IsValidEntry( aMembers );
+#if OSL_DEBUG_LEVEL > 0
+ SAL_INFO("sc.core", "IsValidEntry: Member not found, DimNam = " << GetName());
+#endif
+ return false;
+}
+
+void ScDPResultDimension::ProcessData( const vector< SCROW >& aMembers,
+ const ScDPResultDimension* pDataDim,
+ const vector< SCROW >& aDataMembers,
+ const vector<ScDPValue>& aValues ) const
+{
+ if (aMembers.empty())
+ return;
+
+ ScDPResultMember* pMember = FindMember( aMembers[0] );
+ if ( nullptr != pMember )
+ {
+ vector<SCROW> aChildMembers;
+ if (aMembers.size() > 1)
+ {
+ vector<SCROW>::const_iterator itr = aMembers.begin();
+ aChildMembers.insert(aChildMembers.begin(), ++itr, aMembers.end());
+ }
+ pMember->ProcessData( aChildMembers, pDataDim, aDataMembers, aValues );
+ return;
+ }
+
+ OSL_FAIL("ProcessData: Member not found");
+}
+
+void ScDPResultDimension::FillMemberResults( uno::Sequence<sheet::MemberResult>* pSequences,
+ tools::Long nStart, tools::Long nMeasure )
+{
+ tools::Long nPos = nStart;
+ tools::Long nCount = maMemberArray.size();
+
+ for (tools::Long i=0; i<nCount; i++)
+ {
+ tools::Long nSorted = aMemberOrder.empty() ? i : aMemberOrder[i];
+
+ ScDPResultMember* pMember = maMemberArray[nSorted].get();
+ // in data layout dimension, use first member with different measures/names
+ if ( bIsDataLayout )
+ {
+ bool bTotalResult = false;
+ OUString aMbrName = pResultData->GetMeasureDimensionName( nSorted );
+ OUString aMbrCapt = pResultData->GetMeasureString( nSorted, false, SUBTOTAL_FUNC_NONE, bTotalResult );
+ maMemberArray[0]->FillMemberResults( pSequences, nPos, nSorted, false, &aMbrName, &aMbrCapt );
+ }
+ else if ( pMember->IsVisible() )
+ {
+ pMember->FillMemberResults( pSequences, nPos, nMeasure, false, nullptr, nullptr );
+ }
+ // nPos is modified
+ }
+}
+
+void ScDPResultDimension::FillDataResults(
+ const ScDPResultMember* pRefMember, ScDPResultFilterContext& rFilterCxt,
+ uno::Sequence< uno::Sequence<sheet::DataResult> >& rSequence, tools::Long nMeasure) const
+{
+ FilterStack aFilterStack(rFilterCxt.maFilters);
+ aFilterStack.pushDimName(GetName(), bIsDataLayout);
+
+ tools::Long nMemberMeasure = nMeasure;
+ tools::Long nCount = maMemberArray.size();
+ for (tools::Long i=0; i<nCount; i++)
+ {
+ tools::Long nSorted = aMemberOrder.empty() ? i : aMemberOrder[i];
+
+ const ScDPResultMember* pMember;
+ if (bIsDataLayout)
+ {
+ OSL_ENSURE(nMeasure == SC_DPMEASURE_ALL || pResultData->GetMeasureCount() == 1,
+ "DataLayout dimension twice?");
+ pMember = maMemberArray[0].get();
+ nMemberMeasure = nSorted;
+ }
+ else
+ pMember = maMemberArray[nSorted].get();
+
+ if ( pMember->IsVisible() )
+ pMember->FillDataResults(pRefMember, rFilterCxt, rSequence, nMemberMeasure);
+ }
+}
+
+void ScDPResultDimension::UpdateDataResults( const ScDPResultMember* pRefMember, tools::Long nMeasure ) const
+{
+ tools::Long nMemberMeasure = nMeasure;
+ tools::Long nCount = maMemberArray.size();
+ for (tools::Long i=0; i<nCount; i++)
+ {
+ const ScDPResultMember* pMember;
+ if (bIsDataLayout)
+ {
+ OSL_ENSURE(nMeasure == SC_DPMEASURE_ALL || pResultData->GetMeasureCount() == 1,
+ "DataLayout dimension twice?");
+ pMember = maMemberArray[0].get();
+ nMemberMeasure = i;
+ }
+ else
+ pMember = maMemberArray[i].get();
+
+ if ( pMember->IsVisible() )
+ pMember->UpdateDataResults( pRefMember, nMemberMeasure );
+ }
+}
+
+void ScDPResultDimension::SortMembers( ScDPResultMember* pRefMember )
+{
+ tools::Long nCount = maMemberArray.size();
+
+ if ( bSortByData )
+ {
+ // sort members
+
+ OSL_ENSURE( aMemberOrder.empty(), "sort twice?" );
+ aMemberOrder.resize( nCount );
+ for (tools::Long nPos=0; nPos<nCount; nPos++)
+ aMemberOrder[nPos] = nPos;
+
+ ScDPRowMembersOrder aComp( *this, nSortMeasure, bSortAscending );
+ ::std::sort( aMemberOrder.begin(), aMemberOrder.end(), aComp );
+ }
+
+ // handle children
+
+ // for data layout, call only once - sorting measure is always taken from settings
+ tools::Long nLoopCount = bIsDataLayout ? std::min<tools::Long>(1, nCount) : nCount;
+ for (tools::Long i=0; i<nLoopCount; i++)
+ {
+ ScDPResultMember* pMember = maMemberArray[i].get();
+ if ( pMember->IsVisible() )
+ pMember->SortMembers( pRefMember );
+ }
+}
+
+void ScDPResultDimension::DoAutoShow( ScDPResultMember* pRefMember )
+{
+ tools::Long nCount = maMemberArray.size();
+
+ // handle children first, before changing the visible state
+
+ // for data layout, call only once - sorting measure is always taken from settings
+ tools::Long nLoopCount = bIsDataLayout ? 1 : nCount;
+ for (tools::Long i=0; i<nLoopCount; i++)
+ {
+ ScDPResultMember* pMember = maMemberArray[i].get();
+ if ( pMember->IsVisible() )
+ pMember->DoAutoShow( pRefMember );
+ }
+
+ if ( !(bAutoShow && nAutoCount > 0 && nAutoCount < nCount) )
+ return;
+
+ // establish temporary order, hide remaining members
+
+ ScMemberSortOrder aAutoOrder;
+ aAutoOrder.resize( nCount );
+ tools::Long nPos;
+ for (nPos=0; nPos<nCount; nPos++)
+ aAutoOrder[nPos] = nPos;
+
+ ScDPRowMembersOrder aComp( *this, nAutoMeasure, !bAutoTopItems );
+ ::std::sort( aAutoOrder.begin(), aAutoOrder.end(), aComp );
+
+ // look for equal values to the last included one
+
+ tools::Long nIncluded = nAutoCount;
+ const ScDPResultMember* pMember1 = maMemberArray[aAutoOrder[nIncluded - 1]].get();
+ const ScDPDataMember* pDataMember1 = pMember1->IsVisible() ? pMember1->GetDataRoot() : nullptr;
+ bool bContinue = true;
+ while ( bContinue )
+ {
+ bContinue = false;
+ if ( nIncluded < nCount )
+ {
+ const ScDPResultMember* pMember2 = maMemberArray[aAutoOrder[nIncluded]].get();
+ const ScDPDataMember* pDataMember2 = pMember2->IsVisible() ? pMember2->GetDataRoot() : nullptr;
+
+ if ( lcl_IsEqual( pDataMember1, pDataMember2, nAutoMeasure ) )
+ {
+ ++nIncluded; // include more members if values are equal
+ bContinue = true;
+ }
+ }
+ }
+
+ // hide the remaining members
+
+ for (nPos = nIncluded; nPos < nCount; nPos++)
+ {
+ ScDPResultMember* pMember = maMemberArray[aAutoOrder[nPos]].get();
+ pMember->SetAutoHidden();
+ }
+}
+
+void ScDPResultDimension::ResetResults()
+{
+ tools::Long nCount = maMemberArray.size();
+ for (tools::Long i=0; i<nCount; i++)
+ {
+ // sort order doesn't matter
+ ScDPResultMember* pMember = maMemberArray[bIsDataLayout ? 0 : i].get();
+ pMember->ResetResults();
+ }
+}
+
+tools::Long ScDPResultDimension::GetSortedIndex( tools::Long nUnsorted ) const
+{
+ return aMemberOrder.empty() ? nUnsorted : aMemberOrder[nUnsorted];
+}
+
+void ScDPResultDimension::UpdateRunningTotals( const ScDPResultMember* pRefMember, tools::Long nMeasure,
+ ScDPRunningTotalState& rRunning, ScDPRowTotals& rTotals ) const
+{
+ const ScDPResultMember* pMember;
+ tools::Long nMemberMeasure = nMeasure;
+ tools::Long nCount = maMemberArray.size();
+ for (tools::Long i=0; i<nCount; i++)
+ {
+ tools::Long nSorted = aMemberOrder.empty() ? i : aMemberOrder[i];
+
+ if (bIsDataLayout)
+ {
+ OSL_ENSURE(nMeasure == SC_DPMEASURE_ALL || pResultData->GetMeasureCount() == 1,
+ "DataLayout dimension twice?");
+ pMember = maMemberArray[0].get();
+ nMemberMeasure = nSorted;
+ }
+ else
+ pMember = maMemberArray[nSorted].get();
+
+ if ( pMember->IsVisible() )
+ {
+ if ( bIsDataLayout )
+ rRunning.AddRowIndex( 0, 0 );
+ else
+ rRunning.AddRowIndex( i, nSorted );
+ pMember->UpdateRunningTotals( pRefMember, nMemberMeasure, rRunning, rTotals );
+ rRunning.RemoveRowIndex();
+ }
+ }
+}
+
+ScDPDataMember* ScDPResultDimension::GetRowReferenceMember(
+ const ScDPRelativePos* pRelativePos, const OUString* pName,
+ const sal_Int32* pRowIndexes, const sal_Int32* pColIndexes ) const
+{
+ // get named, previous/next, or first member of this dimension (first existing if pRelativePos and pName are NULL)
+
+ OSL_ENSURE( pRelativePos == nullptr || pName == nullptr, "can't use position and name" );
+
+ ScDPDataMember* pColMember = nullptr;
+
+ bool bFirstExisting = ( pRelativePos == nullptr && pName == nullptr );
+ tools::Long nMemberCount = maMemberArray.size();
+ tools::Long nMemberIndex = 0; // unsorted
+ tools::Long nDirection = 1; // forward if no relative position is used
+ if ( pRelativePos )
+ {
+ nDirection = pRelativePos->nDirection;
+ nMemberIndex = pRelativePos->nBasePos + nDirection; // bounds are handled below
+
+ OSL_ENSURE( nDirection == 1 || nDirection == -1, "Direction must be 1 or -1" );
+ }
+ else if ( pName )
+ {
+ // search for named member
+
+ const ScDPResultMember* pRowMember = maMemberArray[GetSortedIndex(nMemberIndex)].get();
+
+ //TODO: use ScDPItemData, as in ScDPDimension::IsValidPage?
+ while ( pRowMember && pRowMember->GetName() != *pName )
+ {
+ ++nMemberIndex;
+ if ( nMemberIndex < nMemberCount )
+ pRowMember = maMemberArray[GetSortedIndex(nMemberIndex)].get();
+ else
+ pRowMember = nullptr;
+ }
+ }
+
+ bool bContinue = true;
+ while ( bContinue && nMemberIndex >= 0 && nMemberIndex < nMemberCount )
+ {
+ const ScDPResultMember* pRowMember = maMemberArray[GetSortedIndex(nMemberIndex)].get();
+
+ // get child members by given indexes
+
+ const sal_Int32* pNextRowIndex = pRowIndexes;
+ while ( *pNextRowIndex >= 0 && pRowMember )
+ {
+ const ScDPResultDimension* pRowChild = pRowMember->GetChildDimension();
+ if ( pRowChild && *pNextRowIndex < pRowChild->GetMemberCount() )
+ pRowMember = pRowChild->GetMember( *pNextRowIndex );
+ else
+ pRowMember = nullptr;
+ ++pNextRowIndex;
+ }
+
+ if ( pRowMember && pRelativePos )
+ {
+ // Skip the member if it has hidden details
+ // (because when looking for the details, it is skipped, too).
+ // Also skip if the member is invisible because it has no data,
+ // for consistent ordering.
+ if ( pRowMember->HasHiddenDetails() || !pRowMember->IsVisible() )
+ pRowMember = nullptr;
+ }
+
+ if ( pRowMember )
+ {
+ pColMember = pRowMember->GetDataRoot();
+
+ const sal_Int32* pNextColIndex = pColIndexes;
+ while ( *pNextColIndex >= 0 && pColMember )
+ {
+ ScDPDataDimension* pColChild = pColMember->GetChildDimension();
+ if ( pColChild && *pNextColIndex < pColChild->GetMemberCount() )
+ pColMember = pColChild->GetMember( *pNextColIndex );
+ else
+ pColMember = nullptr;
+ ++pNextColIndex;
+ }
+ }
+
+ // continue searching only if looking for first existing or relative position
+ bContinue = ( pColMember == nullptr && ( bFirstExisting || pRelativePos ) );
+ nMemberIndex += nDirection;
+ }
+
+ return pColMember;
+}
+
+ScDPDataMember* ScDPResultDimension::GetColReferenceMember(
+ const ScDPRelativePos* pRelativePos, const OUString* pName,
+ sal_Int32 nRefDimPos, const ScDPRunningTotalState& rRunning )
+{
+ OSL_ENSURE( pRelativePos == nullptr || pName == nullptr, "can't use position and name" );
+
+ const sal_Int32* pColIndexes = rRunning.GetColSorted().data();
+ const sal_Int32* pRowIndexes = rRunning.GetRowSorted().data();
+
+ // get own row member using all indexes
+
+ const ScDPResultMember* pRowMember = rRunning.GetRowResRoot();
+ ScDPDataMember* pColMember = nullptr;
+
+ const sal_Int32* pNextRowIndex = pRowIndexes;
+ while ( *pNextRowIndex >= 0 && pRowMember )
+ {
+ const ScDPResultDimension* pRowChild = pRowMember->GetChildDimension();
+ if ( pRowChild && *pNextRowIndex < pRowChild->GetMemberCount() )
+ pRowMember = pRowChild->GetMember( *pNextRowIndex );
+ else
+ pRowMember = nullptr;
+ ++pNextRowIndex;
+ }
+
+ // get column (data) members before the reference field
+ //TODO: pass rRowParent from ScDPDataMember::UpdateRunningTotals instead
+
+ if ( pRowMember )
+ {
+ pColMember = pRowMember->GetDataRoot();
+
+ const sal_Int32* pNextColIndex = pColIndexes;
+ sal_Int32 nColSkipped = 0;
+ while ( *pNextColIndex >= 0 && pColMember && nColSkipped < nRefDimPos )
+ {
+ ScDPDataDimension* pColChild = pColMember->GetChildDimension();
+ if ( pColChild && *pNextColIndex < pColChild->GetMemberCount() )
+ pColMember = pColChild->GetMember( *pNextColIndex );
+ else
+ pColMember = nullptr;
+ ++pNextColIndex;
+ ++nColSkipped;
+ }
+ }
+
+ // get column member for the reference field
+
+ if ( pColMember )
+ {
+ ScDPDataDimension* pReferenceDim = pColMember->GetChildDimension();
+ if ( pReferenceDim )
+ {
+ tools::Long nReferenceCount = pReferenceDim->GetMemberCount();
+
+ bool bFirstExisting = ( pRelativePos == nullptr && pName == nullptr );
+ tools::Long nMemberIndex = 0; // unsorted
+ tools::Long nDirection = 1; // forward if no relative position is used
+ pColMember = nullptr; // don't use parent dimension's member if none found
+ if ( pRelativePos )
+ {
+ nDirection = pRelativePos->nDirection;
+ nMemberIndex = pRelativePos->nBasePos + nDirection; // bounds are handled below
+ }
+ else if ( pName )
+ {
+ // search for named member
+
+ pColMember = pReferenceDim->GetMember( pReferenceDim->GetSortedIndex( nMemberIndex ) );
+
+ //TODO: use ScDPItemData, as in ScDPDimension::IsValidPage?
+ while ( pColMember && pColMember->GetName() != *pName )
+ {
+ ++nMemberIndex;
+ if ( nMemberIndex < nReferenceCount )
+ pColMember = pReferenceDim->GetMember( pReferenceDim->GetSortedIndex( nMemberIndex ) );
+ else
+ pColMember = nullptr;
+ }
+ }
+
+ bool bContinue = true;
+ while ( bContinue && nMemberIndex >= 0 && nMemberIndex < nReferenceCount )
+ {
+ pColMember = pReferenceDim->GetMember( pReferenceDim->GetSortedIndex( nMemberIndex ) );
+
+ // get column members below the reference field
+
+ const sal_Int32* pNextColIndex = pColIndexes + nRefDimPos + 1;
+ while ( *pNextColIndex >= 0 && pColMember )
+ {
+ ScDPDataDimension* pColChild = pColMember->GetChildDimension();
+ if ( pColChild && *pNextColIndex < pColChild->GetMemberCount() )
+ pColMember = pColChild->GetMember( *pNextColIndex );
+ else
+ pColMember = nullptr;
+ ++pNextColIndex;
+ }
+
+ if ( pColMember && pRelativePos )
+ {
+ // Skip the member if it has hidden details
+ // (because when looking for the details, it is skipped, too).
+ // Also skip if the member is invisible because it has no data,
+ // for consistent ordering.
+ if ( pColMember->HasHiddenDetails() || !pColMember->IsVisible() )
+ pColMember = nullptr;
+ }
+
+ // continue searching only if looking for first existing or relative position
+ bContinue = ( pColMember == nullptr && ( bFirstExisting || pRelativePos ) );
+ nMemberIndex += nDirection;
+ }
+ }
+ else
+ pColMember = nullptr;
+ }
+
+ return pColMember;
+}
+
+#if DUMP_PIVOT_TABLE
+void ScDPResultDimension::DumpState( const ScDPResultMember* pRefMember, ScDocument* pDoc, ScAddress& rPos ) const
+{
+ OUString aDimName = bIsDataLayout ? OUString("(data layout)") : GetName();
+ dumpRow("ScDPResultDimension", aDimName, nullptr, pDoc, rPos);
+
+ SCROW nStartRow = rPos.Row();
+
+ tools::Long nCount = bIsDataLayout ? 1 : maMemberArray.size();
+ for (tools::Long i=0; i<nCount; i++)
+ {
+ const ScDPResultMember* pMember = maMemberArray[i].get();
+ pMember->DumpState( pRefMember, pDoc, rPos );
+ }
+
+ indent(pDoc, nStartRow, rPos);
+}
+
+void ScDPResultDimension::Dump(int nIndent) const
+{
+ std::string aIndent(nIndent*2, ' ');
+ std::cout << aIndent << "-- dimension '" << GetName() << "'" << std::endl;
+ for (const auto& rxMember : maMemberArray)
+ {
+ const ScDPResultMember* p = rxMember.get();
+ p->Dump(nIndent+1);
+ }
+}
+#endif
+
+tools::Long ScDPResultDimension::GetMemberCount() const
+{
+ return maMemberArray.size();
+}
+
+const ScDPResultMember* ScDPResultDimension::GetMember(tools::Long n) const
+{
+ return maMemberArray[n].get();
+}
+ScDPResultMember* ScDPResultDimension::GetMember(tools::Long n)
+{
+ return maMemberArray[n].get();
+}
+
+ScDPResultDimension* ScDPResultDimension::GetFirstChildDimension() const
+{
+ if ( !maMemberArray.empty() )
+ return maMemberArray[0]->GetChildDimension();
+ else
+ return nullptr;
+}
+
+void ScDPResultDimension::FillVisibilityData(ScDPResultVisibilityData& rData) const
+{
+ if (IsDataLayout())
+ return;
+
+ for (const auto& rxMember : maMemberArray)
+ {
+ ScDPResultMember* pMember = rxMember.get();
+ if (pMember->IsValid())
+ {
+ ScDPItemData aItem(pMember->FillItemData());
+ rData.addVisibleMember(GetName(), aItem);
+ pMember->FillVisibilityData(rData);
+ }
+ }
+}
+
+ScDPDataDimension::ScDPDataDimension( const ScDPResultData* pData ) :
+ pResultData( pData ),
+ pResultDimension( nullptr ),
+ bIsDataLayout( false )
+{
+}
+
+ScDPDataDimension::~ScDPDataDimension()
+{
+}
+
+void ScDPDataDimension::InitFrom( const ScDPResultDimension* pDim )
+{
+ if (!pDim)
+ return;
+
+ pResultDimension = pDim;
+ bIsDataLayout = pDim->IsDataLayout();
+
+ // Go through all result members under the given result dimension, and
+ // create a new data member instance for each result member.
+ tools::Long nCount = pDim->GetMemberCount();
+ for (tools::Long i=0; i<nCount; i++)
+ {
+ const ScDPResultMember* pResMem = pDim->GetMember(i);
+
+ ScDPDataMember* pNew = new ScDPDataMember( pResultData, pResMem );
+ maMembers.emplace_back( pNew);
+
+ if ( !pResultData->IsLateInit() )
+ {
+ // with LateInit, pResMem hasn't necessarily been initialized yet,
+ // so InitFrom for the new result member is called from its ProcessData method
+
+ const ScDPResultDimension* pChildDim = pResMem->GetChildDimension();
+ if ( pChildDim )
+ pNew->InitFrom( pChildDim );
+ }
+ }
+}
+
+void ScDPDataDimension::ProcessData( const vector< SCROW >& aDataMembers, const vector<ScDPValue>& aValues,
+ const ScDPSubTotalState& rSubState )
+{
+ // the ScDPItemData array must contain enough entries for all dimensions - this isn't checked
+
+ tools::Long nCount = maMembers.size();
+ for (tools::Long i=0; i<nCount; i++)
+ {
+ ScDPDataMember* pMember = maMembers[static_cast<sal_uInt16>(i)].get();
+
+ // always first member for data layout dim
+ if ( bIsDataLayout || ( !aDataMembers.empty() && pMember->IsNamedItem(aDataMembers[0]) ) )
+ {
+ vector<SCROW> aChildDataMembers;
+ if (aDataMembers.size() > 1)
+ {
+ vector<SCROW>::const_iterator itr = aDataMembers.begin();
+ aChildDataMembers.insert(aChildDataMembers.begin(), ++itr, aDataMembers.end());
+ }
+ pMember->ProcessData( aChildDataMembers, aValues, rSubState );
+ return;
+ }
+ }
+
+ OSL_FAIL("ProcessData: Member not found");
+}
+
+void ScDPDataDimension::FillDataRow(
+ const ScDPResultDimension* pRefDim, ScDPResultFilterContext& rFilterCxt,
+ uno::Sequence<sheet::DataResult>& rSequence, tools::Long nMeasure, bool bIsSubTotalRow,
+ const ScDPSubTotalState& rSubState) const
+{
+ OUString aDimName;
+ bool bDataLayout = false;
+ if (pResultDimension)
+ {
+ aDimName = pResultDimension->GetName();
+ bDataLayout = pResultDimension->IsDataLayout();
+ }
+
+ FilterStack aFilterStack(rFilterCxt.maFilters);
+ aFilterStack.pushDimName(aDimName, bDataLayout);
+
+ OSL_ENSURE( pRefDim && static_cast<size_t>(pRefDim->GetMemberCount()) == maMembers.size(), "dimensions don't match" );
+ OSL_ENSURE( pRefDim == pResultDimension, "wrong dim" );
+
+ const ScMemberSortOrder& rMemberOrder = pRefDim->GetMemberOrder();
+
+ tools::Long nMemberMeasure = nMeasure;
+ tools::Long nCount = maMembers.size();
+ for (tools::Long i=0; i<nCount; i++)
+ {
+ tools::Long nSorted = rMemberOrder.empty() ? i : rMemberOrder[i];
+
+ tools::Long nMemberPos = nSorted;
+ if (bIsDataLayout)
+ {
+ OSL_ENSURE(nMeasure == SC_DPMEASURE_ALL || pResultData->GetMeasureCount() == 1,
+ "DataLayout dimension twice?");
+ nMemberPos = 0;
+ nMemberMeasure = nSorted;
+ }
+
+ const ScDPResultMember* pRefMember = pRefDim->GetMember(nMemberPos);
+ if ( pRefMember->IsVisible() ) //TODO: here or in ScDPDataMember::FillDataRow ???
+ {
+ const ScDPDataMember* pDataMember = maMembers[static_cast<sal_uInt16>(nMemberPos)].get();
+ pDataMember->FillDataRow(pRefMember, rFilterCxt, rSequence, nMemberMeasure, bIsSubTotalRow, rSubState);
+ }
+ }
+}
+
+void ScDPDataDimension::UpdateDataRow( const ScDPResultDimension* pRefDim,
+ tools::Long nMeasure, bool bIsSubTotalRow,
+ const ScDPSubTotalState& rSubState ) const
+{
+ OSL_ENSURE( pRefDim && static_cast<size_t>(pRefDim->GetMemberCount()) == maMembers.size(), "dimensions don't match" );
+ OSL_ENSURE( pRefDim == pResultDimension, "wrong dim" );
+
+ tools::Long nMemberMeasure = nMeasure;
+ tools::Long nCount = maMembers.size();
+ for (tools::Long i=0; i<nCount; i++)
+ {
+ tools::Long nMemberPos = i;
+ if (bIsDataLayout)
+ {
+ OSL_ENSURE(nMeasure == SC_DPMEASURE_ALL || pResultData->GetMeasureCount() == 1,
+ "DataLayout dimension twice?");
+ nMemberPos = 0;
+ nMemberMeasure = i;
+ }
+
+ // Calculate must be called even if the member is not visible (for use as reference value)
+ const ScDPResultMember* pRefMember = pRefDim->GetMember(nMemberPos);
+ ScDPDataMember* pDataMember = maMembers[static_cast<sal_uInt16>(nMemberPos)].get();
+ pDataMember->UpdateDataRow( pRefMember, nMemberMeasure, bIsSubTotalRow, rSubState );
+ }
+}
+
+void ScDPDataDimension::SortMembers( ScDPResultDimension* pRefDim )
+{
+ tools::Long nCount = maMembers.size();
+
+ if ( pRefDim->IsSortByData() )
+ {
+ // sort members
+
+ ScMemberSortOrder& rMemberOrder = pRefDim->GetMemberOrder();
+ OSL_ENSURE( rMemberOrder.empty(), "sort twice?" );
+ rMemberOrder.resize( nCount );
+ for (tools::Long nPos=0; nPos<nCount; nPos++)
+ rMemberOrder[nPos] = nPos;
+
+ ScDPColMembersOrder aComp( *this, pRefDim->GetSortMeasure(), pRefDim->IsSortAscending() );
+ ::std::sort( rMemberOrder.begin(), rMemberOrder.end(), aComp );
+ }
+
+ // handle children
+
+ OSL_ENSURE( pRefDim && static_cast<size_t>(pRefDim->GetMemberCount()) == maMembers.size(), "dimensions don't match" );
+ OSL_ENSURE( pRefDim == pResultDimension, "wrong dim" );
+
+ // for data layout, call only once - sorting measure is always taken from settings
+ tools::Long nLoopCount = bIsDataLayout ? 1 : nCount;
+ for (tools::Long i=0; i<nLoopCount; i++)
+ {
+ ScDPResultMember* pRefMember = pRefDim->GetMember(i);
+ if ( pRefMember->IsVisible() ) //TODO: here or in ScDPDataMember ???
+ {
+ ScDPDataMember* pDataMember = maMembers[static_cast<sal_uInt16>(i)].get();
+ pDataMember->SortMembers( pRefMember );
+ }
+ }
+}
+
+void ScDPDataDimension::DoAutoShow( ScDPResultDimension* pRefDim )
+{
+ tools::Long nCount = maMembers.size();
+
+ // handle children first, before changing the visible state
+
+ OSL_ENSURE( pRefDim && static_cast<size_t>(pRefDim->GetMemberCount()) == maMembers.size(), "dimensions don't match" );
+ OSL_ENSURE( pRefDim == pResultDimension, "wrong dim" );
+
+ // for data layout, call only once - sorting measure is always taken from settings
+ tools::Long nLoopCount = bIsDataLayout ? 1 : nCount;
+ for (tools::Long i=0; i<nLoopCount; i++)
+ {
+ ScDPResultMember* pRefMember = pRefDim->GetMember(i);
+ if ( pRefMember->IsVisible() ) //TODO: here or in ScDPDataMember ???
+ {
+ ScDPDataMember* pDataMember = maMembers[i].get();
+ pDataMember->DoAutoShow( pRefMember );
+ }
+ }
+
+ if ( !(pRefDim->IsAutoShow() && pRefDim->GetAutoCount() > 0 && pRefDim->GetAutoCount() < nCount) )
+ return;
+
+ // establish temporary order, hide remaining members
+
+ ScMemberSortOrder aAutoOrder;
+ aAutoOrder.resize( nCount );
+ tools::Long nPos;
+ for (nPos=0; nPos<nCount; nPos++)
+ aAutoOrder[nPos] = nPos;
+
+ ScDPColMembersOrder aComp( *this, pRefDim->GetAutoMeasure(), !pRefDim->IsAutoTopItems() );
+ ::std::sort( aAutoOrder.begin(), aAutoOrder.end(), aComp );
+
+ // look for equal values to the last included one
+
+ tools::Long nIncluded = pRefDim->GetAutoCount();
+ ScDPDataMember* pDataMember1 = maMembers[aAutoOrder[nIncluded - 1]].get();
+ if ( !pDataMember1->IsVisible() )
+ pDataMember1 = nullptr;
+ bool bContinue = true;
+ while ( bContinue )
+ {
+ bContinue = false;
+ if ( nIncluded < nCount )
+ {
+ ScDPDataMember* pDataMember2 = maMembers[aAutoOrder[nIncluded]].get();
+ if ( !pDataMember2->IsVisible() )
+ pDataMember2 = nullptr;
+
+ if ( lcl_IsEqual( pDataMember1, pDataMember2, pRefDim->GetAutoMeasure() ) )
+ {
+ ++nIncluded; // include more members if values are equal
+ bContinue = true;
+ }
+ }
+ }
+
+ // hide the remaining members
+
+ for (nPos = nIncluded; nPos < nCount; nPos++)
+ {
+ ScDPResultMember* pMember = pRefDim->GetMember(aAutoOrder[nPos]);
+ pMember->SetAutoHidden();
+ }
+}
+
+void ScDPDataDimension::ResetResults()
+{
+ tools::Long nCount = maMembers.size();
+ for (tools::Long i=0; i<nCount; i++)
+ {
+ // sort order doesn't matter
+
+ tools::Long nMemberPos = bIsDataLayout ? 0 : i;
+ ScDPDataMember* pDataMember = maMembers[nMemberPos].get();
+ pDataMember->ResetResults();
+ }
+}
+
+tools::Long ScDPDataDimension::GetSortedIndex( tools::Long nUnsorted ) const
+{
+ if (!pResultDimension)
+ return nUnsorted;
+
+ const ScMemberSortOrder& rMemberOrder = pResultDimension->GetMemberOrder();
+ return rMemberOrder.empty() ? nUnsorted : rMemberOrder[nUnsorted];
+}
+
+void ScDPDataDimension::UpdateRunningTotals( const ScDPResultDimension* pRefDim,
+ tools::Long nMeasure, bool bIsSubTotalRow,
+ const ScDPSubTotalState& rSubState, ScDPRunningTotalState& rRunning,
+ ScDPRowTotals& rTotals, const ScDPResultMember& rRowParent ) const
+{
+ OSL_ENSURE( pRefDim && static_cast<size_t>(pRefDim->GetMemberCount()) == maMembers.size(), "dimensions don't match" );
+ OSL_ENSURE( pRefDim == pResultDimension, "wrong dim" );
+
+ tools::Long nMemberMeasure = nMeasure;
+ tools::Long nCount = maMembers.size();
+ for (tools::Long i=0; i<nCount; i++)
+ {
+ const ScMemberSortOrder& rMemberOrder = pRefDim->GetMemberOrder();
+ tools::Long nSorted = rMemberOrder.empty() ? i : rMemberOrder[i];
+
+ tools::Long nMemberPos = nSorted;
+ if (bIsDataLayout)
+ {
+ OSL_ENSURE(nMeasure == SC_DPMEASURE_ALL || pResultData->GetMeasureCount() == 1,
+ "DataLayout dimension twice?");
+ nMemberPos = 0;
+ nMemberMeasure = nSorted;
+ }
+
+ const ScDPResultMember* pRefMember = pRefDim->GetMember(nMemberPos);
+ if ( pRefMember->IsVisible() )
+ {
+ if ( bIsDataLayout )
+ rRunning.AddColIndex( 0, 0 );
+ else
+ rRunning.AddColIndex( i, nSorted );
+
+ ScDPDataMember* pDataMember = maMembers[nMemberPos].get();
+ pDataMember->UpdateRunningTotals(
+ pRefMember, nMemberMeasure, bIsSubTotalRow, rSubState, rRunning, rTotals, rRowParent);
+
+ rRunning.RemoveColIndex();
+ }
+ }
+}
+
+#if DUMP_PIVOT_TABLE
+void ScDPDataDimension::DumpState( const ScDPResultDimension* pRefDim, ScDocument* pDoc, ScAddress& rPos ) const
+{
+ OUString aDimName = bIsDataLayout ? OUString("(data layout)") : OUString("(unknown)");
+ dumpRow("ScDPDataDimension", aDimName, nullptr, pDoc, rPos);
+
+ SCROW nStartRow = rPos.Row();
+
+ tools::Long nCount = bIsDataLayout ? 1 : maMembers.size();
+ for (tools::Long i=0; i<nCount; i++)
+ {
+ const ScDPResultMember* pRefMember = pRefDim->GetMember(i);
+ const ScDPDataMember* pDataMember = maMembers[i].get();
+ pDataMember->DumpState( pRefMember, pDoc, rPos );
+ }
+
+ indent(pDoc, nStartRow, rPos);
+}
+
+void ScDPDataDimension::Dump(int nIndent) const
+{
+ std::string aIndent(nIndent*2, ' ');
+ std::cout << aIndent << "-- data dimension '"
+ << (pResultDimension ? pResultDimension->GetName() : OUString()) << "'" << std::endl;
+ for (auto& rxMember : maMembers)
+ rxMember->Dump(nIndent+1);
+}
+#endif
+
+tools::Long ScDPDataDimension::GetMemberCount() const
+{
+ return maMembers.size();
+}
+
+const ScDPDataMember* ScDPDataDimension::GetMember(tools::Long n) const
+{
+ return maMembers[n].get();
+}
+
+ScDPDataMember* ScDPDataDimension::GetMember(tools::Long n)
+{
+ return maMembers[n].get();
+}
+
+ScDPResultVisibilityData::ScDPResultVisibilityData(
+ ScDPSource* pSource) :
+ mpSource(pSource)
+{
+}
+
+ScDPResultVisibilityData::~ScDPResultVisibilityData()
+{
+}
+
+void ScDPResultVisibilityData::addVisibleMember(const OUString& rDimName, const ScDPItemData& rMemberItem)
+{
+ DimMemberType::iterator itr = maDimensions.find(rDimName);
+ if (itr == maDimensions.end())
+ {
+ pair<DimMemberType::iterator, bool> r = maDimensions.emplace(
+ rDimName, VisibleMemberType());
+
+ if (!r.second)
+ // insertion failed.
+ return;
+
+ itr = r.first;
+ }
+ VisibleMemberType& rMem = itr->second;
+ rMem.insert(rMemberItem);
+}
+
+void ScDPResultVisibilityData::fillFieldFilters(vector<ScDPFilteredCache::Criterion>& rFilters) const
+{
+ typedef std::unordered_map<OUString, tools::Long> FieldNameMapType;
+ FieldNameMapType aFieldNames;
+ ScDPTableData* pData = mpSource->GetData();
+ sal_Int32 nColumnCount = pData->GetColumnCount();
+ for (sal_Int32 i = 0; i < nColumnCount; ++i)
+ {
+ aFieldNames.emplace(pData->getDimensionName(i), i);
+ }
+
+ const ScDPDimensions* pDims = mpSource->GetDimensionsObject();
+ for (const auto& [rDimName, rMem] : maDimensions)
+ {
+ ScDPFilteredCache::Criterion aCri;
+ FieldNameMapType::const_iterator itrField = aFieldNames.find(rDimName);
+ if (itrField == aFieldNames.end())
+ // This should never happen!
+ continue;
+
+ tools::Long nDimIndex = itrField->second;
+ aCri.mnFieldIndex = static_cast<sal_Int32>(nDimIndex);
+ aCri.mpFilter = std::make_shared<ScDPFilteredCache::GroupFilter>();
+
+ ScDPFilteredCache::GroupFilter* pGrpFilter =
+ static_cast<ScDPFilteredCache::GroupFilter*>(aCri.mpFilter.get());
+
+ for (const ScDPItemData& rMemItem : rMem)
+ {
+ pGrpFilter->addMatchItem(rMemItem);
+ }
+
+ ScDPDimension* pDim = pDims->getByIndex(nDimIndex);
+ ScDPMembers* pMembers = pDim->GetHierarchiesObject()->getByIndex(0)->
+ GetLevelsObject()->getByIndex(0)->GetMembersObject();
+ if (pGrpFilter->getMatchItemCount() < o3tl::make_unsigned(pMembers->getCount()))
+ rFilters.push_back(aCri);
+ }
+}
+
+size_t ScDPResultVisibilityData::MemberHash::operator() (const ScDPItemData& r) const
+{
+ if (r.IsValue())
+ return static_cast<size_t>(::rtl::math::approxFloor(r.GetValue()));
+ else
+ return r.GetString().hashCode();
+}
+SCROW ScDPResultMember::GetDataId( ) const
+{
+ const ScDPMember* pMemberDesc = GetDPMember();
+ if (pMemberDesc)
+ return pMemberDesc->GetItemDataId();
+ return -1;
+}
+
+ScDPResultMember* ScDPResultDimension::AddMember(const ScDPParentDimData &aData )
+{
+ ScDPResultMember* pMember = new ScDPResultMember( pResultData, aData );
+ SCROW nDataIndex = pMember->GetDataId();
+ maMemberArray.emplace_back( pMember );
+
+ maMemberHash.emplace( nDataIndex, pMember );
+ return pMember;
+}
+
+ScDPResultMember* ScDPResultDimension::InsertMember(const ScDPParentDimData *pMemberData)
+{
+ SCROW nInsert = 0;
+ if ( !lcl_SearchMember( maMemberArray, pMemberData->mnOrder , nInsert ) )
+ {
+ ScDPResultMember* pNew = new ScDPResultMember( pResultData, *pMemberData );
+ maMemberArray.emplace( maMemberArray.begin()+nInsert, pNew );
+
+ SCROW nDataIndex = pMemberData->mpMemberDesc->GetItemDataId();
+ maMemberHash.emplace( nDataIndex, pNew );
+ return pNew;
+ }
+ return maMemberArray[ nInsert ].get();
+}
+
+void ScDPResultDimension::InitWithMembers(
+ LateInitParams& rParams, const std::vector<SCROW>& pItemData, size_t nPos,
+ ScDPInitState& rInitState)
+{
+ if ( rParams.IsEnd( nPos ) )
+ return;
+ ScDPDimension* pThisDim = rParams.GetDim( nPos );
+ ScDPLevel* pThisLevel = rParams.GetLevel( nPos );
+ SCROW nDataID = pItemData[nPos];
+
+ if (!(pThisDim && pThisLevel))
+ return;
+
+ tools::Long nDimSource = pThisDim->GetDimension(); //TODO: check GetSourceDim?
+
+ // create all members at the first call (preserve order)
+ ResultMembers& rMembers = pResultData->GetDimResultMembers(nDimSource, pThisDim, pThisLevel);
+ ScDPGroupCompare aCompare( pResultData, rInitState, nDimSource );
+ // initialize only specific member (or all if "show empty" flag is set)
+ ScDPResultMember* pResultMember = nullptr;
+ if ( bInitialized )
+ pResultMember = FindMember( nDataID );
+ else
+ bInitialized = true;
+
+ if ( pResultMember == nullptr )
+ { //only insert found item
+ const ScDPParentDimData* pMemberData = rMembers.FindMember( nDataID );
+ if ( pMemberData && aCompare.IsIncluded( *( pMemberData->mpMemberDesc ) ) )
+ pResultMember = InsertMember( pMemberData );
+ }
+ if ( pResultMember )
+ {
+ rInitState.AddMember( nDimSource, pResultMember->GetDataId() );
+ pResultMember->LateInitFrom(rParams, pItemData, nPos+1, rInitState);
+ rInitState.RemoveMember();
+ }
+}
+
+ScDPParentDimData::ScDPParentDimData() :
+ mnOrder(-1), mpParentDim(nullptr), mpParentLevel(nullptr), mpMemberDesc(nullptr) {}
+
+ScDPParentDimData::ScDPParentDimData(
+ SCROW nIndex, const ScDPDimension* pDim, const ScDPLevel* pLev, const ScDPMember* pMember) :
+ mnOrder(nIndex), mpParentDim(pDim), mpParentLevel(pLev), mpMemberDesc(pMember) {}
+
+const ScDPParentDimData* ResultMembers::FindMember( SCROW nIndex ) const
+{
+ auto aRes = maMemberHash.find( nIndex );
+ if( aRes != maMemberHash.end()) {
+ if ( aRes->second.mpMemberDesc && aRes->second.mpMemberDesc->GetItemDataId()==nIndex )
+ return &aRes->second;
+ }
+ return nullptr;
+}
+void ResultMembers::InsertMember( const ScDPParentDimData& rNew )
+{
+ if ( !rNew.mpMemberDesc->getShowDetails() )
+ mbHasHideDetailsMember = true;
+ maMemberHash.emplace( rNew.mpMemberDesc->GetItemDataId(), rNew );
+}
+
+ResultMembers::ResultMembers():
+ mbHasHideDetailsMember( false )
+{
+}
+ResultMembers::~ResultMembers()
+{
+}
+
+LateInitParams::LateInitParams(
+ const vector<ScDPDimension*>& ppDim, const vector<ScDPLevel*>& ppLev, bool bRow ) :
+ mppDim( ppDim ),
+ mppLev( ppLev ),
+ mbRow( bRow ),
+ mbInitChild( true ),
+ mbAllChildren( false )
+{
+}
+
+bool LateInitParams::IsEnd( size_t nPos ) const
+{
+ return nPos >= mppDim.size();
+}
+
+void ScDPResultDimension::CheckShowEmpty( bool bShow )
+{
+ tools::Long nCount = maMemberArray.size();
+
+ for (tools::Long i=0; i<nCount; i++)
+ {
+ ScDPResultMember* pMember = maMemberArray.at(i).get();
+ pMember->CheckShowEmpty(bShow);
+ }
+
+}
+
+void ScDPResultMember::CheckShowEmpty( bool bShow )
+{
+ if (bHasElements)
+ {
+ ScDPResultDimension* pChildDim = GetChildDimension();
+ if (pChildDim)
+ pChildDim->CheckShowEmpty();
+ }
+ else if (IsValid() && bInitialized)
+ {
+ bShow = bShow || (GetParentLevel() && GetParentLevel()->getShowEmpty());
+ if (bShow)
+ {
+ SetHasElements();
+ ScDPResultDimension* pChildDim = GetChildDimension();
+ if (pChildDim)
+ pChildDim->CheckShowEmpty(true);
+ }
+ }
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sc/source/core/data/dptabsrc.cxx b/sc/source/core/data/dptabsrc.cxx
new file mode 100644
index 000000000..cab605f69
--- /dev/null
+++ b/sc/source/core/data/dptabsrc.cxx
@@ -0,0 +1,2613 @@
+/* -*- 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 <dptabsrc.hxx>
+
+#include <algorithm>
+#include <vector>
+
+#include <comphelper/sequence.hxx>
+#include <o3tl/any.hxx>
+#include <o3tl/safeint.hxx>
+#include <osl/diagnose.h>
+#include <rtl/math.hxx>
+#include <sal/log.hxx>
+#include <svl/itemprop.hxx>
+#include <vcl/svapp.hxx>
+
+#include <dpcache.hxx>
+#include <dptabres.hxx>
+#include <dptabdat.hxx>
+#include <global.hxx>
+#include <miscuno.hxx>
+#include <unonames.hxx>
+#include <dpitemdata.hxx>
+#include <dputil.hxx>
+#include <dpresfilter.hxx>
+#include <calcmacros.hxx>
+#include <generalfunction.hxx>
+
+#include <com/sun/star/beans/PropertyAttribute.hpp>
+#include <com/sun/star/sheet/DataPilotFieldFilter.hpp>
+#include <com/sun/star/sheet/DataPilotFieldReferenceType.hpp>
+#include <com/sun/star/sheet/DataPilotFieldSortMode.hpp>
+#include <com/sun/star/sheet/DataPilotFieldGroupBy.hpp>
+#include <com/sun/star/sheet/DataPilotFieldAutoShowInfo.hpp>
+#include <com/sun/star/sheet/GeneralFunction2.hpp>
+#include <com/sun/star/sheet/TableFilterField.hpp>
+
+#include <unotools/calendarwrapper.hxx>
+#include <com/sun/star/i18n/CalendarDisplayIndex.hpp>
+
+using namespace com::sun::star;
+using ::std::vector;
+using ::com::sun::star::uno::Sequence;
+using ::com::sun::star::uno::Any;
+using ::com::sun::star::sheet::DataPilotFieldAutoShowInfo;
+
+#define SC_MINCOUNT_LIMIT 1000000
+
+SC_SIMPLE_SERVICE_INFO( ScDPSource, "ScDPSource", "com.sun.star.sheet.DataPilotSource" )
+SC_SIMPLE_SERVICE_INFO( ScDPDimensions, "ScDPDimensions", "com.sun.star.sheet.DataPilotSourceDimensions" )
+SC_SIMPLE_SERVICE_INFO( ScDPDimension, "ScDPDimension", "com.sun.star.sheet.DataPilotSourceDimension" )
+
+// Typos are on purpose here, quote from Eike Rathke (see https://gerrit.libreoffice.org/c/core/+/101116):
+// "The typo is exactly why the SC_SIMPLE_SERVICE_INFO_COMPAT() lists both service names,
+// the old with the typo and the new corrected one. Correcting the typo in the old name
+// will make all extensions fail that use it. This is not to be changed."
+SC_SIMPLE_SERVICE_INFO_COMPAT( ScDPHierarchies, "ScDPHierarchies",
+ "com.sun.star.sheet.DataPilotSourceHierarchies", "com.sun.star.sheet.DataPilotSourceHierarcies" )
+SC_SIMPLE_SERVICE_INFO_COMPAT( ScDPHierarchy, "ScDPHierarchy",
+ "com.sun.star.sheet.DataPilotSourceHierarchy", "com.sun.star.sheet.DataPilotSourceHierarcy" )
+
+SC_SIMPLE_SERVICE_INFO( ScDPLevels, "ScDPLevels", "com.sun.star.sheet.DataPilotSourceLevels" )
+SC_SIMPLE_SERVICE_INFO( ScDPLevel, "ScDPLevel", "com.sun.star.sheet.DataPilotSourceLevel" )
+SC_SIMPLE_SERVICE_INFO( ScDPMembers, "ScDPMembers", "com.sun.star.sheet.DataPilotSourceMembers" )
+SC_SIMPLE_SERVICE_INFO( ScDPMember, "ScDPMember", "com.sun.star.sheet.DataPilotSourceMember" )
+
+// property maps for PropertySetInfo
+// DataDescription / NumberFormat are internal
+
+//TODO: move to a header?
+static bool lcl_GetBoolFromAny( const uno::Any& aAny )
+{
+ auto b = o3tl::tryAccess<bool>(aAny);
+ return b && *b;
+}
+
+ScDPSource::ScDPSource( ScDPTableData* pD ) :
+ pData( pD ),
+ bColumnGrand( true ), // default is true
+ bRowGrand( true ),
+ bIgnoreEmptyRows( false ),
+ bRepeatIfEmpty( false ),
+ nDupCount( 0 ),
+ bResultOverflow( false ),
+ bPageFiltered( false )
+{
+ pData->SetEmptyFlags( bIgnoreEmptyRows, bRepeatIfEmpty );
+}
+
+ScDPSource::~ScDPSource()
+{
+ // free lists
+
+ pColResults.reset();
+ pRowResults.reset();
+
+ pColResRoot.reset();
+ pRowResRoot.reset();
+ pResData.reset();
+}
+
+const std::optional<OUString> & ScDPSource::GetGrandTotalName() const
+{
+ return mpGrandTotalName;
+}
+
+sheet::DataPilotFieldOrientation ScDPSource::GetOrientation(sal_Int32 nColumn)
+{
+ if (std::find(maColDims.begin(), maColDims.end(), nColumn) != maColDims.end())
+ return sheet::DataPilotFieldOrientation_COLUMN;
+
+ if (std::find(maRowDims.begin(), maRowDims.end(), nColumn) != maRowDims.end())
+ return sheet::DataPilotFieldOrientation_ROW;
+
+ if (std::find(maDataDims.begin(), maDataDims.end(), nColumn) != maDataDims.end())
+ return sheet::DataPilotFieldOrientation_DATA;
+
+ if (std::find(maPageDims.begin(), maPageDims.end(), nColumn) != maPageDims.end())
+ return sheet::DataPilotFieldOrientation_PAGE;
+
+ return sheet::DataPilotFieldOrientation_HIDDEN;
+}
+
+sal_Int32 ScDPSource::GetDataDimensionCount() const
+{
+ return maDataDims.size();
+}
+
+ScDPDimension* ScDPSource::GetDataDimension(sal_Int32 nIndex)
+{
+ if (nIndex < 0 || o3tl::make_unsigned(nIndex) >= maDataDims.size())
+ return nullptr;
+
+ sal_Int32 nDimIndex = maDataDims[nIndex];
+ return GetDimensionsObject()->getByIndex(nDimIndex);
+}
+
+OUString ScDPSource::GetDataDimName(sal_Int32 nIndex)
+{
+ OUString aRet;
+ ScDPDimension* pDim = GetDataDimension(nIndex);
+ if (pDim)
+ aRet = pDim->getName();
+ return aRet;
+}
+
+sal_Int32 ScDPSource::GetPosition(sal_Int32 nColumn)
+{
+ std::vector<sal_Int32>::const_iterator it, itBeg = maColDims.begin(), itEnd = maColDims.end();
+ it = std::find(itBeg, itEnd, nColumn);
+ if (it != itEnd)
+ return std::distance(itBeg, it);
+
+ itBeg = maRowDims.begin();
+ itEnd = maRowDims.end();
+ it = std::find(itBeg, itEnd, nColumn);
+ if (it != itEnd)
+ return std::distance(itBeg, it);
+
+ itBeg = maDataDims.begin();
+ itEnd = maDataDims.end();
+ it = std::find(itBeg, itEnd, nColumn);
+ if (it != itEnd)
+ return std::distance(itBeg, it);
+
+ itBeg = maPageDims.begin();
+ itEnd = maPageDims.end();
+ it = std::find(itBeg, itEnd, nColumn);
+ if (it != itEnd)
+ return std::distance(itBeg, it);
+
+ return 0;
+}
+
+namespace {
+
+bool testSubTotal( bool& rAllowed, sal_Int32 nColumn, const std::vector<sal_Int32>& rDims, ScDPSource* pSource )
+{
+ rAllowed = true;
+ std::vector<sal_Int32>::const_iterator it = rDims.begin(), itEnd = rDims.end();
+ for (; it != itEnd; ++it)
+ {
+ if (*it != nColumn)
+ continue;
+
+ if ( pSource->IsDataLayoutDimension(nColumn) )
+ {
+ // no subtotals for data layout dim, no matter where
+ rAllowed = false;
+ return true;
+ }
+
+ // no subtotals if no other dim but data layout follows
+ ++it;
+ if (it != itEnd && pSource->IsDataLayoutDimension(*it))
+ ++it;
+ if (it == itEnd)
+ rAllowed = false;
+
+ return true; // found
+ }
+
+ return false;
+}
+
+void removeDim( sal_Int32 nRemove, std::vector<sal_Int32>& rDims )
+{
+ std::vector<sal_Int32>::iterator it = std::find(rDims.begin(), rDims.end(), nRemove);
+ if (it != rDims.end())
+ rDims.erase(it);
+}
+
+}
+
+bool ScDPSource::SubTotalAllowed(sal_Int32 nColumn)
+{
+ //TODO: cache this at ScDPResultData
+ bool bAllowed = true;
+ if ( testSubTotal(bAllowed, nColumn, maColDims, this) )
+ return bAllowed;
+ if ( testSubTotal(bAllowed, nColumn, maRowDims, this) )
+ return bAllowed;
+ return bAllowed;
+}
+
+void ScDPSource::SetOrientation(sal_Int32 nColumn, sheet::DataPilotFieldOrientation nNew)
+{
+ //TODO: change to no-op if new orientation is equal to old?
+
+ // remove from old list
+ removeDim(nColumn, maColDims);
+ removeDim(nColumn, maRowDims);
+ removeDim(nColumn, maDataDims);
+ removeDim(nColumn, maPageDims);
+
+ // add to new list
+ switch (nNew)
+ {
+ case sheet::DataPilotFieldOrientation_COLUMN:
+ maColDims.push_back(nColumn);
+ break;
+ case sheet::DataPilotFieldOrientation_ROW:
+ maRowDims.push_back(nColumn);
+ break;
+ case sheet::DataPilotFieldOrientation_DATA:
+ maDataDims.push_back(nColumn);
+ break;
+ case sheet::DataPilotFieldOrientation_PAGE:
+ maPageDims.push_back(nColumn);
+ break;
+ // DataPilot Migration - Cache&&Performance
+ case sheet::DataPilotFieldOrientation_HIDDEN:
+ break;
+ default:
+ OSL_FAIL( "ScDPSource::SetOrientation: unexpected orientation" );
+ break;
+ }
+}
+
+bool ScDPSource::IsDataLayoutDimension(sal_Int32 nDim)
+{
+ return nDim == pData->GetColumnCount();
+}
+
+sheet::DataPilotFieldOrientation ScDPSource::GetDataLayoutOrientation()
+{
+ return GetOrientation(pData->GetColumnCount());
+}
+
+bool ScDPSource::IsDateDimension(sal_Int32 nDim)
+{
+ return pData->IsDateDimension(nDim);
+}
+
+ScDPDimensions* ScDPSource::GetDimensionsObject()
+{
+ if (!pDimensions.is())
+ {
+ pDimensions = new ScDPDimensions(this);
+ }
+ return pDimensions.get();
+}
+
+uno::Reference<container::XNameAccess> SAL_CALL ScDPSource::getDimensions()
+{
+ return GetDimensionsObject();
+}
+
+void ScDPSource::SetDupCount( tools::Long nNew )
+{
+ nDupCount = nNew;
+}
+
+ScDPDimension* ScDPSource::AddDuplicated(std::u16string_view rNewName)
+{
+ OSL_ENSURE( pDimensions.is(), "AddDuplicated without dimensions?" );
+
+ // re-use
+
+ tools::Long nOldDimCount = pDimensions->getCount();
+ for (tools::Long i=0; i<nOldDimCount; i++)
+ {
+ ScDPDimension* pDim = pDimensions->getByIndex(i);
+ if (pDim && pDim->getName() == rNewName)
+ {
+ //TODO: test if pDim is a duplicate of source
+ return pDim;
+ }
+ }
+
+ SetDupCount( nDupCount + 1 );
+ pDimensions->CountChanged(); // uses nDupCount
+
+ return pDimensions->getByIndex( pDimensions->getCount() - 1 );
+}
+
+sal_Int32 ScDPSource::GetSourceDim(sal_Int32 nDim)
+{
+ // original source dimension or data layout dimension?
+ if ( nDim <= pData->GetColumnCount() )
+ return nDim;
+
+ if ( nDim < pDimensions->getCount() )
+ {
+ ScDPDimension* pDimObj = pDimensions->getByIndex( nDim );
+ if ( pDimObj )
+ {
+ tools::Long nSource = pDimObj->GetSourceDim();
+ if ( nSource >= 0 )
+ return nSource;
+ }
+ }
+
+ OSL_FAIL("GetSourceDim: wrong dim");
+ return nDim;
+}
+
+uno::Sequence< uno::Sequence<sheet::DataResult> > SAL_CALL ScDPSource::getResults()
+{
+ CreateRes_Impl(); // create pColResRoot and pRowResRoot
+
+ if ( bResultOverflow ) // set in CreateRes_Impl
+ {
+ // no results available
+ throw uno::RuntimeException();
+ }
+
+ sal_Int32 nColCount = pColResRoot->GetSize(pResData->GetColStartMeasure());
+ sal_Int32 nRowCount = pRowResRoot->GetSize(pResData->GetRowStartMeasure());
+
+ // allocate full sequence
+ //TODO: leave out empty rows???
+
+ uno::Sequence< uno::Sequence<sheet::DataResult> > aSeq( nRowCount );
+ uno::Sequence<sheet::DataResult>* pRowAry = aSeq.getArray();
+ for (sal_Int32 nRow = 0; nRow < nRowCount; nRow++)
+ {
+ uno::Sequence<sheet::DataResult> aColSeq( nColCount );
+ // use default values of DataResult
+ pRowAry[nRow] = aColSeq;
+ }
+
+ ScDPResultFilterContext aFilterCxt;
+ pRowResRoot->FillDataResults(
+ pColResRoot.get(), aFilterCxt, aSeq, pResData->GetRowStartMeasure());
+
+ maResFilterSet.swap(aFilterCxt.maFilterSet); // Keep this data for GETPIVOTDATA.
+
+ return aSeq;
+}
+
+uno::Sequence<double> ScDPSource::getFilteredResults(
+ const uno::Sequence<sheet::DataPilotFieldFilter>& aFilters )
+{
+ if (maResFilterSet.empty())
+ getResults(); // Build result tree first.
+
+ // Get result values from the tree.
+ const ScDPResultTree::ValuesType* pVals = maResFilterSet.getResults(aFilters);
+ if (pVals && !pVals->empty())
+ {
+ return comphelper::containerToSequence(*pVals);
+ }
+
+ if (aFilters.getLength() == 1)
+ {
+ // Try to get result from the leaf nodes.
+ double fVal = maResFilterSet.getLeafResult(aFilters[0]);
+ if (!std::isnan(fVal))
+ {
+ return { fVal };
+ }
+ }
+
+ return uno::Sequence<double>();
+}
+
+void SAL_CALL ScDPSource::refresh()
+{
+ disposeData();
+}
+
+void SAL_CALL ScDPSource::addRefreshListener( const uno::Reference<util::XRefreshListener >& )
+{
+ OSL_FAIL("not implemented"); //TODO: exception?
+}
+
+void SAL_CALL ScDPSource::removeRefreshListener( const uno::Reference<util::XRefreshListener >& )
+{
+ OSL_FAIL("not implemented"); //TODO: exception?
+}
+
+Sequence< Sequence<Any> > SAL_CALL ScDPSource::getDrillDownData(const Sequence<sheet::DataPilotFieldFilter>& aFilters)
+{
+ sal_Int32 nColumnCount = GetData()->GetColumnCount();
+
+ vector<ScDPFilteredCache::Criterion> aFilterCriteria;
+ for (const sheet::DataPilotFieldFilter& rFilter : aFilters)
+ {
+ const OUString& aFieldName = rFilter.FieldName;
+ for (sal_Int32 nCol = 0; nCol < nColumnCount; ++nCol)
+ {
+ if (aFieldName == pData->getDimensionName(nCol))
+ {
+ ScDPDimension* pDim = GetDimensionsObject()->getByIndex( nCol );
+ ScDPMembers* pMembers = pDim->GetHierarchiesObject()->getByIndex(0)->
+ GetLevelsObject()->getByIndex(0)->GetMembersObject();
+ sal_Int32 nIndex = pMembers->GetIndexFromName( rFilter.MatchValueName );
+ if ( nIndex >= 0 )
+ {
+ ScDPItemData aItem(pMembers->getByIndex(nIndex)->FillItemData());
+ aFilterCriteria.emplace_back( );
+ aFilterCriteria.back().mnFieldIndex = nCol;
+ aFilterCriteria.back().mpFilter =
+ std::make_shared<ScDPFilteredCache::SingleFilter>(aItem);
+ }
+ }
+ }
+ }
+
+ // Take into account the visibilities of field members.
+ ScDPResultVisibilityData aResVisData(this);
+ pRowResRoot->FillVisibilityData(aResVisData);
+ pColResRoot->FillVisibilityData(aResVisData);
+ aResVisData.fillFieldFilters(aFilterCriteria);
+
+ Sequence< Sequence<Any> > aTabData;
+ std::unordered_set<sal_Int32> aCatDims;
+ GetCategoryDimensionIndices(aCatDims);
+ pData->GetDrillDownData(std::move(aFilterCriteria), std::move(aCatDims), aTabData);
+ return aTabData;
+}
+
+OUString ScDPSource::getDataDescription()
+{
+ CreateRes_Impl(); // create pResData
+
+ OUString aRet;
+ if ( pResData->GetMeasureCount() == 1 )
+ {
+ bool bTotalResult = false;
+ aRet = pResData->GetMeasureString(0, true, SUBTOTAL_FUNC_NONE, bTotalResult);
+ }
+
+ // empty for more than one measure
+
+ return aRet;
+}
+
+void ScDPSource::setIgnoreEmptyRows(bool bSet)
+{
+ bIgnoreEmptyRows = bSet;
+ pData->SetEmptyFlags( bIgnoreEmptyRows, bRepeatIfEmpty );
+}
+
+void ScDPSource::setRepeatIfEmpty(bool bSet)
+{
+ bRepeatIfEmpty = bSet;
+ pData->SetEmptyFlags( bIgnoreEmptyRows, bRepeatIfEmpty );
+}
+
+void ScDPSource::disposeData()
+{
+ maResFilterSet.clear();
+
+ if ( pResData )
+ {
+ // reset all data...
+
+ pColResRoot.reset();
+ pRowResRoot.reset();
+ pResData.reset();
+ pColResults.reset();
+ pRowResults.reset();
+ aColLevelList.clear();
+ aRowLevelList.clear();
+ }
+
+ pDimensions.clear(); // settings have to be applied (from SaveData) again!
+ SetDupCount( 0 );
+
+ maColDims.clear();
+ maRowDims.clear();
+ maDataDims.clear();
+ maPageDims.clear();
+
+ pData->DisposeData(); // cached entries etc.
+ bPageFiltered = false;
+ bResultOverflow = false;
+}
+
+static tools::Long lcl_CountMinMembers(const vector<ScDPDimension*>& ppDim, const vector<ScDPLevel*>& ppLevel, tools::Long nLevels )
+{
+ // Calculate the product of the member count for those consecutive levels that
+ // have the "show all" flag, one following level, and the data layout dimension.
+
+ tools::Long nTotal = 1;
+ tools::Long nDataCount = 1;
+ bool bWasShowAll = true;
+ tools::Long nPos = nLevels;
+ while ( nPos > 0 )
+ {
+ --nPos;
+
+ if ( nPos+1 < nLevels && ppDim[nPos] == ppDim[nPos+1] )
+ {
+ OSL_FAIL("lcl_CountMinMembers: multiple levels from one dimension not implemented");
+ return 0;
+ }
+
+ bool bDo = false;
+ if ( ppDim[nPos]->getIsDataLayoutDimension() )
+ {
+ // data layout dim doesn't interfere with "show all" flags
+ nDataCount = ppLevel[nPos]->GetMembersObject()->getCount();
+ if ( nDataCount == 0 )
+ nDataCount = 1;
+ }
+ else if ( bWasShowAll ) // "show all" set for all following levels?
+ {
+ bDo = true;
+ if ( !ppLevel[nPos]->getShowEmpty() )
+ {
+ // this level is counted, following ones are not
+ bWasShowAll = false;
+ }
+ }
+ if ( bDo )
+ {
+ tools::Long nThisCount = ppLevel[nPos]->GetMembersObject()->getMinMembers();
+ if ( nThisCount == 0 )
+ {
+ nTotal = 1; // empty level -> start counting from here
+ //TODO: start with visible elements in this level?
+ }
+ else
+ {
+ if ( nTotal >= LONG_MAX / nThisCount )
+ return LONG_MAX; // overflow
+ nTotal *= nThisCount;
+ }
+ }
+ }
+
+ // always include data layout dim, even after restarting
+ if ( nTotal >= LONG_MAX / nDataCount )
+ return LONG_MAX; // overflow
+ nTotal *= nDataCount;
+
+ return nTotal;
+}
+
+void ScDPSource::FillCalcInfo(bool bIsRow, ScDPTableData::CalcInfo& rInfo, bool &rHasAutoShow)
+{
+ const std::vector<sal_Int32>& rDims = bIsRow ? maRowDims : maColDims;
+ for (const auto& rDimIndex : rDims)
+ {
+ ScDPDimension* pDim = GetDimensionsObject()->getByIndex(rDimIndex);
+ tools::Long nHierarchy = ScDPDimension::getUsedHierarchy();
+ if ( nHierarchy >= ScDPHierarchies::getCount() )
+ nHierarchy = 0;
+ ScDPLevels* pLevels = pDim->GetHierarchiesObject()->getByIndex(nHierarchy)->GetLevelsObject();
+ sal_Int32 nCount = pLevels->getCount();
+
+ //TODO: Test
+ if (pDim->getIsDataLayoutDimension() && maDataDims.size() < 2)
+ nCount = 0;
+ //TODO: Test
+
+ for (sal_Int32 j = 0; j < nCount; ++j)
+ {
+ ScDPLevel* pLevel = pLevels->getByIndex(j);
+ pLevel->EvaluateSortOrder();
+
+ // no layout flags for column fields, only for row fields
+ pLevel->SetEnableLayout( bIsRow );
+
+ if ( pLevel->GetAutoShow().IsEnabled )
+ rHasAutoShow = true;
+
+ if (bIsRow)
+ {
+ rInfo.aRowLevelDims.push_back(rDimIndex);
+ rInfo.aRowDims.push_back(pDim);
+ rInfo.aRowLevels.push_back(pLevel);
+ }
+ else
+ {
+ rInfo.aColLevelDims.push_back(rDimIndex);
+ rInfo.aColDims.push_back(pDim);
+ rInfo.aColLevels.push_back(pLevel);
+ }
+
+ pLevel->GetMembersObject(); // initialize for groups
+ }
+ }
+}
+
+namespace {
+
+class CategoryDimInserter
+{
+ ScDPSource& mrSource;
+ std::unordered_set<sal_Int32>& mrCatDims;
+public:
+ CategoryDimInserter(ScDPSource& rSource, std::unordered_set<sal_Int32>& rCatDims) :
+ mrSource(rSource),
+ mrCatDims(rCatDims) {}
+
+ void operator() (tools::Long nDim)
+ {
+ if (!mrSource.IsDataLayoutDimension(nDim))
+ mrCatDims.insert(nDim);
+ }
+};
+
+}
+
+void ScDPSource::GetCategoryDimensionIndices(std::unordered_set<sal_Int32>& rCatDims)
+{
+ std::unordered_set<sal_Int32> aCatDims;
+
+ CategoryDimInserter aInserter(*this, aCatDims);
+ std::for_each(maColDims.begin(), maColDims.end(), aInserter);
+ std::for_each(maRowDims.begin(), maRowDims.end(), aInserter);
+ std::for_each(maPageDims.begin(), maPageDims.end(), aInserter);
+
+ rCatDims.swap(aCatDims);
+}
+
+void ScDPSource::FilterCacheByPageDimensions()
+{
+ // #i117661# Repeated calls to ScDPFilteredCache::filterByPageDimension
+ // are invalid because rows are only hidden, never shown again. If
+ // FilterCacheByPageDimensions is called again, the cache table must
+ // be re-initialized. Currently, CreateRes_Impl always uses a fresh cache
+ // because ScDBDocFunc::DataPilotUpdate calls InvalidateData.
+
+ if (bPageFiltered)
+ {
+ SAL_WARN( "sc.core","tried to apply page field filters several times");
+
+ pData->DisposeData();
+ pData->CreateCacheTable(); // re-initialize the cache table
+ bPageFiltered = false;
+ }
+
+ // filter table by page dimensions.
+ vector<ScDPFilteredCache::Criterion> aCriteria;
+ for (const auto& rDimIndex : maPageDims)
+ {
+ ScDPDimension* pDim = GetDimensionsObject()->getByIndex(rDimIndex);
+ tools::Long nField = pDim->GetDimension();
+
+ ScDPMembers* pMems = pDim->GetHierarchiesObject()->getByIndex(0)->
+ GetLevelsObject()->getByIndex(0)->GetMembersObject();
+
+ tools::Long nMemCount = pMems->getCount();
+ ScDPFilteredCache::Criterion aFilter;
+ aFilter.mnFieldIndex = static_cast<sal_Int32>(nField);
+ aFilter.mpFilter = std::make_shared<ScDPFilteredCache::GroupFilter>();
+ ScDPFilteredCache::GroupFilter* pGrpFilter =
+ static_cast<ScDPFilteredCache::GroupFilter*>(aFilter.mpFilter.get());
+ for (tools::Long j = 0; j < nMemCount; ++j)
+ {
+ ScDPMember* pMem = pMems->getByIndex(j);
+ if (pMem->isVisible())
+ {
+ ScDPItemData aData(pMem->FillItemData());
+ pGrpFilter->addMatchItem(aData);
+ }
+ }
+ if (pGrpFilter->getMatchItemCount() < o3tl::make_unsigned(nMemCount))
+ // there is at least one invisible item. Add this filter criterion to the mix.
+ aCriteria.push_back(aFilter);
+
+ if (!pDim->HasSelectedPage())
+ continue;
+
+ const ScDPItemData& rData = pDim->GetSelectedData();
+ aCriteria.emplace_back();
+ ScDPFilteredCache::Criterion& r = aCriteria.back();
+ r.mnFieldIndex = static_cast<sal_Int32>(nField);
+ r.mpFilter = std::make_shared<ScDPFilteredCache::SingleFilter>(rData);
+ }
+ if (!aCriteria.empty())
+ {
+ std::unordered_set<sal_Int32> aCatDims;
+ GetCategoryDimensionIndices(aCatDims);
+ pData->FilterCacheTable(std::move(aCriteria), std::move(aCatDims));
+ bPageFiltered = true;
+ }
+}
+
+void ScDPSource::CreateRes_Impl()
+{
+ if (pResData)
+ return;
+
+ sheet::DataPilotFieldOrientation nDataOrient = GetDataLayoutOrientation();
+ if (maDataDims.size() > 1 && ( nDataOrient != sheet::DataPilotFieldOrientation_COLUMN &&
+ nDataOrient != sheet::DataPilotFieldOrientation_ROW ) )
+ {
+ // if more than one data dimension, data layout orientation must be set
+ SetOrientation( pData->GetColumnCount(), sheet::DataPilotFieldOrientation_ROW );
+ nDataOrient = sheet::DataPilotFieldOrientation_ROW;
+ }
+
+ // TODO: Aggregate pDataNames, pDataRefValues, nDataRefOrient, and
+ // eDataFunctions into a structure and use vector instead of static
+ // or pointer arrays.
+ vector<OUString> aDataNames;
+ vector<sheet::DataPilotFieldReference> aDataRefValues;
+ vector<ScSubTotalFunc> aDataFunctions;
+ vector<sheet::DataPilotFieldOrientation> aDataRefOrient;
+
+ ScDPTableData::CalcInfo aInfo;
+
+ // LateInit (initialize only those rows/children that are used) can be used unless
+ // any data dimension needs reference values from column/row dimensions
+ bool bLateInit = true;
+
+ // Go through all data dimensions (i.e. fields) and build their meta data
+ // so that they can be passed on to ScDPResultData instance later.
+ // TODO: aggregate all of data dimension info into a structure.
+ for (const tools::Long nDimIndex : maDataDims)
+ {
+ // Get function for each data field.
+ ScDPDimension* pDim = GetDimensionsObject()->getByIndex(nDimIndex);
+ ScGeneralFunction eUser = pDim->getFunction();
+ if (eUser == ScGeneralFunction::AUTO)
+ {
+ //TODO: test for numeric data
+ eUser = ScGeneralFunction::SUM;
+ }
+
+ // Map UNO's enum to internal enum ScSubTotalFunc.
+ aDataFunctions.push_back(ScDPUtil::toSubTotalFunc(eUser));
+
+ // Get reference field/item information.
+ aDataRefValues.push_back(pDim->GetReferenceValue());
+ sheet::DataPilotFieldOrientation nDataRefOrient = sheet::DataPilotFieldOrientation_HIDDEN; // default if not used
+ sal_Int32 eRefType = aDataRefValues.back().ReferenceType;
+ if ( eRefType == sheet::DataPilotFieldReferenceType::ITEM_DIFFERENCE ||
+ eRefType == sheet::DataPilotFieldReferenceType::ITEM_PERCENTAGE ||
+ eRefType == sheet::DataPilotFieldReferenceType::ITEM_PERCENTAGE_DIFFERENCE ||
+ eRefType == sheet::DataPilotFieldReferenceType::RUNNING_TOTAL )
+ {
+ sal_Int32 nColumn = comphelper::findValue(
+ GetDimensionsObject()->getElementNames(), aDataRefValues.back().ReferenceField);
+ if ( nColumn >= 0 )
+ {
+ nDataRefOrient = GetOrientation(nColumn);
+ // need fully initialized results to find reference values
+ // (both in column or row dimensions), so updated values or
+ // differences to 0 can be displayed even for empty results.
+ bLateInit = false;
+ }
+ }
+
+ aDataRefOrient.push_back(nDataRefOrient);
+
+ aDataNames.push_back(pDim->getName());
+
+ //TODO: modify user visible strings as in ScDPResultData::GetMeasureString instead!
+
+ aDataNames.back() = ScDPUtil::getSourceDimensionName(aDataNames.back());
+
+ //TODO: if the name is overridden by user, a flag must be set
+ //TODO: so the user defined name replaces the function string and field name.
+
+ //TODO: the complete name (function and field) must be stored at the dimension
+
+ tools::Long nSource = pDim->GetSourceDim();
+ if (nSource >= 0)
+ aInfo.aDataSrcCols.push_back(nSource);
+ else
+ aInfo.aDataSrcCols.push_back(nDimIndex);
+ }
+
+ pResData.reset( new ScDPResultData(*this) );
+ pResData->SetMeasureData(aDataFunctions, aDataRefValues, aDataRefOrient, aDataNames);
+ pResData->SetDataLayoutOrientation(nDataOrient);
+ pResData->SetLateInit( bLateInit );
+
+ bool bHasAutoShow = false;
+
+ ScDPInitState aInitState;
+
+ // Page field selections restrict the members shown in related fields
+ // (both in column and row fields). aInitState is filled with the page
+ // field selections, they are kept across the data iterator loop.
+
+ for (const auto& rDimIndex : maPageDims)
+ {
+ ScDPDimension* pDim = GetDimensionsObject()->getByIndex(rDimIndex);
+ if ( pDim->HasSelectedPage() )
+ aInitState.AddMember(rDimIndex, GetCache()->GetIdByItemData(rDimIndex, pDim->GetSelectedData()));
+ }
+
+ // Show grand total columns only when the option is set *and* there is at
+ // least one column field. Same for the grand total rows.
+ sheet::DataPilotFieldOrientation nDataLayoutOrient = GetDataLayoutOrientation();
+ tools::Long nColDimCount2 = maColDims.size() - (nDataLayoutOrient == sheet::DataPilotFieldOrientation_COLUMN ? 1 : 0);
+ tools::Long nRowDimCount2 = maRowDims.size() - (nDataLayoutOrient == sheet::DataPilotFieldOrientation_ROW ? 1 : 0);
+ bool bShowColGrand = bColumnGrand && nColDimCount2 > 0;
+ bool bShowRowGrand = bRowGrand && nRowDimCount2 > 0;
+ pColResRoot.reset( new ScDPResultMember(pResData.get(), bShowColGrand) );
+ pRowResRoot.reset( new ScDPResultMember(pResData.get(), bShowRowGrand) );
+
+ FillCalcInfo(false, aInfo, bHasAutoShow);
+ tools::Long nColLevelCount = aInfo.aColLevels.size();
+
+ pColResRoot->InitFrom( aInfo.aColDims, aInfo.aColLevels, 0, aInitState );
+ pColResRoot->SetHasElements();
+
+ FillCalcInfo(true, aInfo, bHasAutoShow);
+ tools::Long nRowLevelCount = aInfo.aRowLevels.size();
+
+ if ( nRowLevelCount > 0 )
+ {
+ // disable layout flags for the innermost row field (level)
+ aInfo.aRowLevels[nRowLevelCount-1]->SetEnableLayout( false );
+ }
+
+ pRowResRoot->InitFrom( aInfo.aRowDims, aInfo.aRowLevels, 0, aInitState );
+ pRowResRoot->SetHasElements();
+
+ // initialize members object also for all page dimensions (needed for numeric groups)
+ for (const auto& rDimIndex : maPageDims)
+ {
+ ScDPDimension* pDim = GetDimensionsObject()->getByIndex(rDimIndex);
+ tools::Long nHierarchy = ScDPDimension::getUsedHierarchy();
+ if ( nHierarchy >= ScDPHierarchies::getCount() )
+ nHierarchy = 0;
+
+ ScDPLevels* pLevels = pDim->GetHierarchiesObject()->getByIndex(nHierarchy)->GetLevelsObject();
+ tools::Long nCount = pLevels->getCount();
+ for (tools::Long j=0; j<nCount; j++)
+ pLevels->getByIndex(j)->GetMembersObject(); // initialize for groups
+ }
+
+ // pre-check: calculate minimum number of result columns / rows from
+ // levels that have the "show all" flag set
+
+ tools::Long nMinColMembers = lcl_CountMinMembers( aInfo.aColDims, aInfo.aColLevels, nColLevelCount );
+ tools::Long nMinRowMembers = lcl_CountMinMembers( aInfo.aRowDims, aInfo.aRowLevels, nRowLevelCount );
+
+ if ( nMinColMembers > MAXCOLCOUNT/*SC_MINCOUNT_LIMIT*/ || nMinRowMembers > SC_MINCOUNT_LIMIT )
+ {
+ // resulting table is too big -> abort before calculating
+ // (this relies on late init, so no members are allocated in InitFrom above)
+
+ bResultOverflow = true;
+ return;
+ }
+
+ FilterCacheByPageDimensions();
+
+ aInfo.aPageDims = maPageDims;
+ aInfo.pInitState = &aInitState;
+ aInfo.pColRoot = pColResRoot.get();
+ aInfo.pRowRoot = pRowResRoot.get();
+ pData->CalcResults(aInfo, false);
+
+ pColResRoot->CheckShowEmpty();
+ pRowResRoot->CheckShowEmpty();
+
+ // With all data processed, calculate the final results:
+
+ // UpdateDataResults calculates all original results from the collected values,
+ // and stores them as reference values if needed.
+ pRowResRoot->UpdateDataResults( pColResRoot.get(), pResData->GetRowStartMeasure() );
+
+ if ( bHasAutoShow ) // do the double calculation only if AutoShow is used
+ {
+ // Find the desired members and set bAutoHidden flag for the others
+ pRowResRoot->DoAutoShow( pColResRoot.get() );
+
+ // Reset all results to empty, so they can be built again with data for the
+ // desired members only.
+ pColResRoot->ResetResults();
+ pRowResRoot->ResetResults();
+ pData->CalcResults(aInfo, true);
+
+ // Call UpdateDataResults again, with the new (limited) values.
+ pRowResRoot->UpdateDataResults( pColResRoot.get(), pResData->GetRowStartMeasure() );
+ }
+
+ // SortMembers does the sorting by a result dimension, using the original results,
+ // but not running totals etc.
+ pRowResRoot->SortMembers( pColResRoot.get() );
+
+ // UpdateRunningTotals calculates running totals along column/row dimensions,
+ // differences from other members (named or relative), and column/row percentages
+ // or index values.
+ // Running totals and relative differences need to be done using the sorted values.
+ // Column/row percentages and index values must be done after sorting, because the
+ // results may no longer be in the right order (row total for percentage of row is
+ // always 1).
+ ScDPRunningTotalState aRunning( pColResRoot.get(), pRowResRoot.get() );
+ ScDPRowTotals aTotals;
+ pRowResRoot->UpdateRunningTotals( pColResRoot.get(), pResData->GetRowStartMeasure(), aRunning, aTotals );
+
+#if DUMP_PIVOT_TABLE
+ DumpResults();
+#endif
+}
+
+void ScDPSource::FillLevelList( sheet::DataPilotFieldOrientation nOrientation, std::vector<ScDPLevel*> &rList )
+{
+ rList.clear();
+
+ std::vector<sal_Int32>* pDimIndex = nullptr;
+ switch (nOrientation)
+ {
+ case sheet::DataPilotFieldOrientation_COLUMN:
+ pDimIndex = &maColDims;
+ break;
+ case sheet::DataPilotFieldOrientation_ROW:
+ pDimIndex = &maRowDims;
+ break;
+ case sheet::DataPilotFieldOrientation_DATA:
+ pDimIndex = &maDataDims;
+ break;
+ case sheet::DataPilotFieldOrientation_PAGE:
+ pDimIndex = &maPageDims;
+ break;
+ default:
+ OSL_FAIL( "ScDPSource::FillLevelList: unexpected orientation" );
+ break;
+ }
+ if (!pDimIndex)
+ {
+ OSL_FAIL("invalid orientation");
+ return;
+ }
+
+ ScDPDimensions* pDims = GetDimensionsObject();
+ for (const auto& rIndex : *pDimIndex)
+ {
+ ScDPDimension* pDim = pDims->getByIndex(rIndex);
+ OSL_ENSURE( pDim->getOrientation() == nOrientation, "orientations are wrong" );
+
+ ScDPHierarchies* pHiers = pDim->GetHierarchiesObject();
+ sal_Int32 nHierarchy = ScDPDimension::getUsedHierarchy();
+ if ( nHierarchy >= ScDPHierarchies::getCount() )
+ nHierarchy = 0;
+ ScDPHierarchy* pHier = pHiers->getByIndex(nHierarchy);
+ ScDPLevels* pLevels = pHier->GetLevelsObject();
+ sal_Int32 nLevCount = pLevels->getCount();
+ for (sal_Int32 nLev=0; nLev<nLevCount; nLev++)
+ {
+ ScDPLevel* pLevel = pLevels->getByIndex(nLev);
+ rList.push_back(pLevel);
+ }
+ }
+}
+
+void ScDPSource::FillMemberResults()
+{
+ if ( pColResults || pRowResults )
+ return;
+
+ CreateRes_Impl();
+
+ if ( bResultOverflow ) // set in CreateRes_Impl
+ {
+ // no results available -> abort (leave empty)
+ // exception is thrown in ScDPSource::getResults
+ return;
+ }
+
+ FillLevelList( sheet::DataPilotFieldOrientation_COLUMN, aColLevelList );
+ sal_Int32 nColLevelCount = aColLevelList.size();
+ if (nColLevelCount)
+ {
+ tools::Long nColDimSize = pColResRoot->GetSize(pResData->GetColStartMeasure());
+ pColResults.reset(new uno::Sequence<sheet::MemberResult>[nColLevelCount]);
+ for (tools::Long i=0; i<nColLevelCount; i++)
+ pColResults[i].realloc(nColDimSize);
+
+ tools::Long nPos = 0;
+ pColResRoot->FillMemberResults( pColResults.get(), nPos, pResData->GetColStartMeasure(),
+ true, nullptr, nullptr );
+ }
+
+ FillLevelList( sheet::DataPilotFieldOrientation_ROW, aRowLevelList );
+ tools::Long nRowLevelCount = aRowLevelList.size();
+ if (nRowLevelCount)
+ {
+ tools::Long nRowDimSize = pRowResRoot->GetSize(pResData->GetRowStartMeasure());
+ pRowResults.reset( new uno::Sequence<sheet::MemberResult>[nRowLevelCount] );
+ for (tools::Long i=0; i<nRowLevelCount; i++)
+ pRowResults[i].realloc(nRowDimSize);
+
+ tools::Long nPos = 0;
+ pRowResRoot->FillMemberResults( pRowResults.get(), nPos, pResData->GetRowStartMeasure(),
+ true, nullptr, nullptr );
+ }
+}
+
+const uno::Sequence<sheet::MemberResult>* ScDPSource::GetMemberResults( const ScDPLevel* pLevel )
+{
+ FillMemberResults();
+
+ sal_Int32 i = 0;
+ sal_Int32 nColCount = aColLevelList.size();
+ for (i=0; i<nColCount; i++)
+ {
+ ScDPLevel* pColLevel = aColLevelList[i];
+ if ( pColLevel == pLevel )
+ return &pColResults[i];
+ }
+ sal_Int32 nRowCount = aRowLevelList.size();
+ for (i=0; i<nRowCount; i++)
+ {
+ ScDPLevel* pRowLevel = aRowLevelList[i];
+ if ( pRowLevel == pLevel )
+ return &pRowResults[i];
+ }
+ return nullptr;
+}
+
+// XPropertySet
+
+uno::Reference<beans::XPropertySetInfo> SAL_CALL ScDPSource::getPropertySetInfo()
+{
+ using beans::PropertyAttribute::READONLY;
+
+ static const SfxItemPropertyMapEntry aDPSourceMap_Impl[] =
+ {
+ { SC_UNO_DP_COLGRAND, 0, cppu::UnoType<bool>::get(), 0, 0 },
+ { SC_UNO_DP_DATADESC, 0, cppu::UnoType<OUString>::get(), beans::PropertyAttribute::READONLY, 0 },
+ { SC_UNO_DP_IGNOREEMPTY, 0, cppu::UnoType<bool>::get(), 0, 0 }, // for sheet data only
+ { SC_UNO_DP_REPEATEMPTY, 0, cppu::UnoType<bool>::get(), 0, 0 }, // for sheet data only
+ { SC_UNO_DP_ROWGRAND, 0, cppu::UnoType<bool>::get(), 0, 0 },
+ { SC_UNO_DP_ROWFIELDCOUNT, 0, cppu::UnoType<sal_Int32>::get(), READONLY, 0 },
+ { SC_UNO_DP_COLUMNFIELDCOUNT, 0, cppu::UnoType<sal_Int32>::get(), READONLY, 0 },
+ { SC_UNO_DP_DATAFIELDCOUNT, 0, cppu::UnoType<sal_Int32>::get(), READONLY, 0 },
+ { SC_UNO_DP_GRANDTOTAL_NAME, 0, cppu::UnoType<OUString>::get(), 0, 0 },
+ { u"", 0, css::uno::Type(), 0, 0 }
+ };
+ static uno::Reference<beans::XPropertySetInfo> aRef =
+ new SfxItemPropertySetInfo( aDPSourceMap_Impl );
+ return aRef;
+}
+
+void SAL_CALL ScDPSource::setPropertyValue( const OUString& aPropertyName, const uno::Any& aValue )
+{
+ if (aPropertyName == SC_UNO_DP_COLGRAND)
+ bColumnGrand = lcl_GetBoolFromAny(aValue);
+ else if (aPropertyName == SC_UNO_DP_ROWGRAND)
+ bRowGrand = lcl_GetBoolFromAny(aValue);
+ else if (aPropertyName == SC_UNO_DP_IGNOREEMPTY)
+ setIgnoreEmptyRows( lcl_GetBoolFromAny( aValue ) );
+ else if (aPropertyName == SC_UNO_DP_REPEATEMPTY)
+ setRepeatIfEmpty( lcl_GetBoolFromAny( aValue ) );
+ else if (aPropertyName == SC_UNO_DP_GRANDTOTAL_NAME)
+ {
+ OUString aName;
+ if (aValue >>= aName)
+ mpGrandTotalName = aName;
+ }
+ else
+ {
+ OSL_FAIL("unknown property");
+ //TODO: THROW( UnknownPropertyException() );
+ }
+}
+
+uno::Any SAL_CALL ScDPSource::getPropertyValue( const OUString& aPropertyName )
+{
+ uno::Any aRet;
+ if ( aPropertyName == SC_UNO_DP_COLGRAND )
+ aRet <<= bColumnGrand;
+ else if ( aPropertyName == SC_UNO_DP_ROWGRAND )
+ aRet <<= bRowGrand;
+ else if ( aPropertyName == SC_UNO_DP_IGNOREEMPTY )
+ aRet <<= bIgnoreEmptyRows;
+ else if ( aPropertyName == SC_UNO_DP_REPEATEMPTY )
+ aRet <<= bRepeatIfEmpty;
+ else if ( aPropertyName == SC_UNO_DP_DATADESC ) // read-only
+ aRet <<= getDataDescription();
+ else if ( aPropertyName == SC_UNO_DP_ROWFIELDCOUNT ) // read-only
+ aRet <<= static_cast<sal_Int32>(maRowDims.size());
+ else if ( aPropertyName == SC_UNO_DP_COLUMNFIELDCOUNT ) // read-only
+ aRet <<= static_cast<sal_Int32>(maColDims.size());
+ else if ( aPropertyName == SC_UNO_DP_DATAFIELDCOUNT ) // read-only
+ aRet <<= static_cast<sal_Int32>(maDataDims.size());
+ else if (aPropertyName == SC_UNO_DP_GRANDTOTAL_NAME)
+ {
+ if (mpGrandTotalName)
+ aRet <<= *mpGrandTotalName;
+ }
+ else
+ {
+ OSL_FAIL("unknown property");
+ //TODO: THROW( UnknownPropertyException() );
+ }
+ return aRet;
+}
+
+#if DUMP_PIVOT_TABLE
+void ScDPSource::DumpResults() const
+{
+ std::cout << "+++++ column root" << std::endl;
+ pColResRoot->Dump(1);
+ std::cout << "+++++ row root" << std::endl;
+ pRowResRoot->Dump(1);
+}
+#endif
+
+SC_IMPL_DUMMY_PROPERTY_LISTENER( ScDPSource )
+
+ScDPDimensions::ScDPDimensions( ScDPSource* pSrc ) :
+ pSource( pSrc )
+{
+ //TODO: hold pSource
+
+ // include data layout dimension and duplicated dimensions
+ nDimCount = pSource->GetData()->GetColumnCount() + 1 + pSource->GetDupCount();
+}
+
+ScDPDimensions::~ScDPDimensions()
+{
+ //TODO: release pSource
+}
+
+void ScDPDimensions::CountChanged()
+{
+ // include data layout dimension and duplicated dimensions
+ sal_Int32 nNewCount = pSource->GetData()->GetColumnCount() + 1 + pSource->GetDupCount();
+ if ( ppDims )
+ {
+ sal_Int32 i;
+ sal_Int32 nCopy = std::min( nNewCount, nDimCount );
+ rtl::Reference<ScDPDimension>* ppNew = new rtl::Reference<ScDPDimension>[nNewCount];
+
+ for (i=0; i<nCopy; i++) // copy existing dims
+ ppNew[i] = ppDims[i];
+ for (i=nCopy; i<nNewCount; i++) // clear additional pointers
+ ppNew[i] = nullptr;
+
+ ppDims.reset( ppNew );
+ }
+ nDimCount = nNewCount;
+}
+
+// very simple XNameAccess implementation using getCount/getByIndex
+
+uno::Any SAL_CALL ScDPDimensions::getByName( const OUString& aName )
+{
+ sal_Int32 nCount = getCount();
+ for (sal_Int32 i=0; i<nCount; i++)
+ if ( getByIndex(i)->getName() == aName )
+ {
+ uno::Reference<container::XNamed> xNamed = getByIndex(i);
+ uno::Any aRet;
+ aRet <<= xNamed;
+ return aRet;
+ }
+
+ throw container::NoSuchElementException();
+// return uno::Any();
+}
+
+uno::Sequence<OUString> SAL_CALL ScDPDimensions::getElementNames()
+{
+ tools::Long nCount = getCount();
+ uno::Sequence<OUString> aSeq(nCount);
+ OUString* pArr = aSeq.getArray();
+ for (tools::Long i=0; i<nCount; i++)
+ pArr[i] = getByIndex(i)->getName();
+ return aSeq;
+}
+
+sal_Bool SAL_CALL ScDPDimensions::hasByName( const OUString& aName )
+{
+ tools::Long nCount = getCount();
+ for (tools::Long i=0; i<nCount; i++)
+ if ( getByIndex(i)->getName() == aName )
+ return true;
+ return false;
+}
+
+uno::Type SAL_CALL ScDPDimensions::getElementType()
+{
+ return cppu::UnoType<container::XNamed>::get();
+}
+
+sal_Bool SAL_CALL ScDPDimensions::hasElements()
+{
+ return ( getCount() > 0 );
+}
+
+// end of XNameAccess implementation
+
+tools::Long ScDPDimensions::getCount() const
+{
+ // in tabular data, every column of source data is a dimension
+
+ return nDimCount;
+}
+
+ScDPDimension* ScDPDimensions::getByIndex(tools::Long nIndex) const
+{
+ if ( nIndex >= 0 && nIndex < nDimCount )
+ {
+ if ( !ppDims )
+ {
+ const_cast<ScDPDimensions*>(this)->ppDims.reset(new rtl::Reference<ScDPDimension>[nDimCount] );
+ for (tools::Long i=0; i<nDimCount; i++)
+ ppDims[i] = nullptr;
+ }
+ if ( !ppDims[nIndex].is() )
+ {
+ ppDims[nIndex] = new ScDPDimension( pSource, nIndex );
+ }
+
+ return ppDims[nIndex].get();
+ }
+
+ return nullptr; //TODO: exception?
+}
+
+ScDPDimension::ScDPDimension( ScDPSource* pSrc, tools::Long nD ) :
+ pSource( pSrc ),
+ nDim( nD ),
+ nFunction( ScGeneralFunction::SUM ), // sum is default
+ nSourceDim( -1 ),
+ bHasSelectedPage( false ),
+ mbHasHiddenMember(false)
+{
+ //TODO: hold pSource
+}
+
+ScDPDimension::~ScDPDimension()
+{
+ //TODO: release pSource
+}
+
+ScDPHierarchies* ScDPDimension::GetHierarchiesObject()
+{
+ if (!mxHierarchies.is())
+ {
+ mxHierarchies = new ScDPHierarchies( pSource, nDim );
+ }
+ return mxHierarchies.get();
+}
+
+const std::optional<OUString> & ScDPDimension::GetLayoutName() const
+{
+ return mpLayoutName;
+}
+
+const std::optional<OUString> & ScDPDimension::GetSubtotalName() const
+{
+ return mpSubtotalName;
+}
+
+uno::Reference<container::XNameAccess> SAL_CALL ScDPDimension::getHierarchies()
+{
+ return GetHierarchiesObject();
+}
+
+OUString SAL_CALL ScDPDimension::getName()
+{
+ if (!aName.isEmpty())
+ return aName;
+ else
+ return pSource->GetData()->getDimensionName( nDim );
+}
+
+void SAL_CALL ScDPDimension::setName( const OUString& rNewName )
+{
+ // used after cloning
+ aName = rNewName;
+}
+
+sheet::DataPilotFieldOrientation ScDPDimension::getOrientation() const
+{
+ return pSource->GetOrientation( nDim );
+}
+
+bool ScDPDimension::getIsDataLayoutDimension() const
+{
+ return pSource->GetData()->getIsDataLayoutDimension( nDim );
+}
+
+void ScDPDimension::setFunction(ScGeneralFunction nNew)
+{
+ nFunction = nNew;
+}
+
+ScDPDimension* ScDPDimension::CreateCloneObject()
+{
+ OSL_ENSURE( nSourceDim < 0, "recursive duplicate - not implemented" );
+
+ //TODO: set new name here, or temporary name ???
+ OUString aNewName = aName;
+
+ ScDPDimension* pNew = pSource->AddDuplicated( aNewName );
+
+ pNew->aName = aNewName; //TODO: here or in source?
+ pNew->nSourceDim = nDim; //TODO: recursive?
+
+ return pNew;
+}
+
+uno::Reference<util::XCloneable> SAL_CALL ScDPDimension::createClone()
+{
+ return CreateCloneObject();
+}
+
+const ScDPItemData& ScDPDimension::GetSelectedData()
+{
+ if ( !pSelectedData )
+ {
+ // find the named member to initialize pSelectedData from it, with name and value
+
+ tools::Long nLevel = 0;
+
+ tools::Long nHierarchy = getUsedHierarchy();
+ if ( nHierarchy >= ScDPHierarchies::getCount() )
+ nHierarchy = 0;
+ ScDPLevels* pLevels = GetHierarchiesObject()->getByIndex(nHierarchy)->GetLevelsObject();
+ tools::Long nLevCount = pLevels->getCount();
+ if ( nLevel < nLevCount )
+ {
+ ScDPMembers* pMembers = pLevels->getByIndex(nLevel)->GetMembersObject();
+
+ //TODO: merge with ScDPMembers::getByName
+ tools::Long nCount = pMembers->getCount();
+ for (tools::Long i=0; i<nCount && !pSelectedData; i++)
+ {
+ ScDPMember* pMember = pMembers->getByIndex(i);
+ if (aSelectedPage == pMember->GetNameStr(false))
+ {
+ pSelectedData.reset( new ScDPItemData(pMember->FillItemData()) );
+ }
+ }
+ }
+
+ if ( !pSelectedData )
+ pSelectedData.reset( new ScDPItemData(aSelectedPage) ); // default - name only
+ }
+
+ return *pSelectedData;
+}
+
+// XPropertySet
+
+uno::Reference<beans::XPropertySetInfo> SAL_CALL ScDPDimension::getPropertySetInfo()
+{
+ static const SfxItemPropertyMapEntry aDPDimensionMap_Impl[] =
+ {
+ { SC_UNO_DP_FILTER, 0, cppu::UnoType<uno::Sequence<sheet::TableFilterField>>::get(), 0, 0 },
+ { SC_UNO_DP_FLAGS, 0, cppu::UnoType<sal_Int32>::get(), beans::PropertyAttribute::READONLY, 0 },
+ { SC_UNO_DP_FUNCTION, 0, cppu::UnoType<sheet::GeneralFunction>::get(), 0, 0 },
+ { SC_UNO_DP_FUNCTION2, 0, cppu::UnoType<sal_Int16>::get(), 0, 0 },
+ { SC_UNO_DP_ISDATALAYOUT, 0, cppu::UnoType<bool>::get(), beans::PropertyAttribute::READONLY, 0 },
+ { SC_UNO_DP_NUMBERFO, 0, cppu::UnoType<sal_Int32>::get(), beans::PropertyAttribute::READONLY, 0 },
+ { SC_UNO_DP_ORIENTATION, 0, cppu::UnoType<sheet::DataPilotFieldOrientation>::get(), 0, 0 },
+ { SC_UNO_DP_ORIGINAL, 0, cppu::UnoType<container::XNamed>::get(), beans::PropertyAttribute::READONLY, 0 },
+ { SC_UNO_DP_ORIGINAL_POS, 0, cppu::UnoType<sal_Int32>::get(), 0, 0 },
+ { SC_UNO_DP_POSITION, 0, cppu::UnoType<sal_Int32>::get(), 0, 0 },
+ { SC_UNO_DP_REFVALUE, 0, cppu::UnoType<sheet::DataPilotFieldReference>::get(), 0, 0 },
+ { SC_UNO_DP_USEDHIERARCHY, 0, cppu::UnoType<sal_Int32>::get(), 0, 0 },
+ { SC_UNO_DP_LAYOUTNAME, 0, cppu::UnoType<OUString>::get(), 0, 0 },
+ { SC_UNO_DP_FIELD_SUBTOTALNAME, 0, cppu::UnoType<OUString>::get(), 0, 0 },
+ { SC_UNO_DP_HAS_HIDDEN_MEMBER, 0, cppu::UnoType<bool>::get(), 0, 0 },
+ { u"", 0, css::uno::Type(), 0, 0 }
+ };
+ static uno::Reference<beans::XPropertySetInfo> aRef =
+ new SfxItemPropertySetInfo( aDPDimensionMap_Impl );
+ return aRef;
+}
+
+void SAL_CALL ScDPDimension::setPropertyValue( const OUString& aPropertyName, const uno::Any& aValue )
+{
+ if ( aPropertyName == SC_UNO_DP_USEDHIERARCHY )
+ {
+ // #i52547# don't use the incomplete date hierarchy implementation - ignore the call
+ }
+ else if ( aPropertyName == SC_UNO_DP_ORIENTATION )
+ {
+ sheet::DataPilotFieldOrientation eEnum;
+ if (aValue >>= eEnum)
+ pSource->SetOrientation( nDim, eEnum );
+ }
+ else if ( aPropertyName == SC_UNO_DP_FUNCTION )
+ {
+ sheet::GeneralFunction eEnum;
+ if (aValue >>= eEnum)
+ setFunction( static_cast<ScGeneralFunction>(eEnum) );
+ }
+ else if ( aPropertyName == SC_UNO_DP_FUNCTION2 )
+ {
+ sal_Int16 eEnum;
+ if (aValue >>= eEnum)
+ setFunction( static_cast<ScGeneralFunction>(eEnum) );
+ }
+ else if ( aPropertyName == SC_UNO_DP_REFVALUE )
+ aValue >>= aReferenceValue;
+ else if ( aPropertyName == SC_UNO_DP_FILTER )
+ {
+ bool bDone = false;
+ uno::Sequence<sheet::TableFilterField> aSeq;
+ if (aValue >>= aSeq)
+ {
+ sal_Int32 nLength = aSeq.getLength();
+ if ( nLength == 0 )
+ {
+ aSelectedPage.clear();
+ bHasSelectedPage = false;
+ bDone = true;
+ }
+ else if ( nLength == 1 )
+ {
+ const sheet::TableFilterField& rField = aSeq[0];
+ if ( rField.Field == 0 && rField.Operator == sheet::FilterOperator_EQUAL && !rField.IsNumeric )
+ {
+ aSelectedPage = rField.StringValue;
+ bHasSelectedPage = true;
+ bDone = true;
+ }
+ }
+ }
+ if ( !bDone )
+ {
+ OSL_FAIL("Filter property is not a single string");
+ throw lang::IllegalArgumentException();
+ }
+ pSelectedData.reset(); // invalid after changing aSelectedPage
+ }
+ else if (aPropertyName == SC_UNO_DP_LAYOUTNAME)
+ {
+ OUString aTmpName;
+ if (aValue >>= aTmpName)
+ mpLayoutName = aTmpName;
+ }
+ else if (aPropertyName == SC_UNO_DP_FIELD_SUBTOTALNAME)
+ {
+ OUString aTmpName;
+ if (aValue >>= aTmpName)
+ mpSubtotalName = aTmpName;
+ }
+ else if (aPropertyName == SC_UNO_DP_HAS_HIDDEN_MEMBER)
+ {
+ bool b = false;
+ aValue >>= b;
+ mbHasHiddenMember = b;
+ }
+ else
+ {
+ OSL_FAIL("unknown property");
+ //TODO: THROW( UnknownPropertyException() );
+ }
+}
+
+uno::Any SAL_CALL ScDPDimension::getPropertyValue( const OUString& aPropertyName )
+{
+ uno::Any aRet;
+ if ( aPropertyName == SC_UNO_DP_POSITION )
+ aRet <<= pSource->GetPosition( nDim );
+ else if ( aPropertyName == SC_UNO_DP_USEDHIERARCHY )
+ aRet <<= static_cast<sal_Int32>(getUsedHierarchy());
+ else if ( aPropertyName == SC_UNO_DP_ORIENTATION )
+ {
+ sheet::DataPilotFieldOrientation eVal = getOrientation();
+ aRet <<= eVal;
+ }
+ else if ( aPropertyName == SC_UNO_DP_FUNCTION )
+ {
+ ScGeneralFunction nVal = getFunction();
+ if (nVal == ScGeneralFunction::MEDIAN)
+ nVal = ScGeneralFunction::NONE;
+ const int nValAsInt = static_cast<int>(nVal);
+ assert(nValAsInt >= int(css::sheet::GeneralFunction_NONE) &&
+ nValAsInt <= int(css::sheet::GeneralFunction_VARP));
+ aRet <<= static_cast<sheet::GeneralFunction>(nValAsInt);
+ }
+ else if ( aPropertyName == SC_UNO_DP_FUNCTION2 )
+ {
+ ScGeneralFunction eVal = getFunction();
+ aRet <<= static_cast<sal_Int16>(eVal);
+ }
+ else if ( aPropertyName == SC_UNO_DP_REFVALUE )
+ aRet <<= aReferenceValue;
+ else if ( aPropertyName == SC_UNO_DP_ISDATALAYOUT ) // read-only properties
+ aRet <<= getIsDataLayoutDimension();
+ else if ( aPropertyName == SC_UNO_DP_NUMBERFO )
+ {
+ sal_Int32 nFormat = 0;
+ ScGeneralFunction eFunc = getFunction();
+ // #i63745# don't use source format for "count"
+ if ( eFunc != ScGeneralFunction::COUNT && eFunc != ScGeneralFunction::COUNTNUMS )
+ nFormat = pSource->GetData()->GetNumberFormat( ( nSourceDim >= 0 ) ? nSourceDim : nDim );
+
+ switch ( aReferenceValue.ReferenceType )
+ {
+ case sheet::DataPilotFieldReferenceType::ITEM_PERCENTAGE:
+ case sheet::DataPilotFieldReferenceType::ITEM_PERCENTAGE_DIFFERENCE:
+ case sheet::DataPilotFieldReferenceType::ROW_PERCENTAGE:
+ case sheet::DataPilotFieldReferenceType::COLUMN_PERCENTAGE:
+ case sheet::DataPilotFieldReferenceType::TOTAL_PERCENTAGE:
+ nFormat = pSource->GetData()->GetNumberFormatByIdx( NF_PERCENT_DEC2 );
+ break;
+ case sheet::DataPilotFieldReferenceType::INDEX:
+ nFormat = pSource->GetData()->GetNumberFormatByIdx( NF_NUMBER_SYSTEM );
+ break;
+ default:
+ break;
+ }
+
+ aRet <<= nFormat;
+ }
+ else if ( aPropertyName == SC_UNO_DP_ORIGINAL )
+ {
+ uno::Reference<container::XNamed> xOriginal;
+ if (nSourceDim >= 0)
+ xOriginal = pSource->GetDimensionsObject()->getByIndex(nSourceDim);
+ aRet <<= xOriginal;
+ }
+ else if (aPropertyName == SC_UNO_DP_ORIGINAL_POS)
+ {
+ aRet <<= nSourceDim;
+ }
+ else if ( aPropertyName == SC_UNO_DP_FILTER )
+ {
+ if ( bHasSelectedPage )
+ {
+ // single filter field: first field equal to selected string
+ sheet::TableFilterField aField( sheet::FilterConnection_AND, 0,
+ sheet::FilterOperator_EQUAL, false, 0.0, aSelectedPage );
+ aRet <<= uno::Sequence<sheet::TableFilterField>( &aField, 1 );
+ }
+ else
+ aRet <<= uno::Sequence<sheet::TableFilterField>(0);
+ }
+ else if (aPropertyName == SC_UNO_DP_LAYOUTNAME)
+ aRet <<= mpLayoutName ? *mpLayoutName : OUString();
+ else if (aPropertyName == SC_UNO_DP_FIELD_SUBTOTALNAME)
+ aRet <<= mpSubtotalName ? *mpSubtotalName : OUString();
+ else if (aPropertyName == SC_UNO_DP_HAS_HIDDEN_MEMBER)
+ aRet <<= mbHasHiddenMember;
+ else if (aPropertyName == SC_UNO_DP_FLAGS)
+ {
+ aRet <<= sal_Int32(0); // tabular data: all orientations are possible
+ }
+ else
+ {
+ OSL_FAIL("unknown property");
+ //TODO: THROW( UnknownPropertyException() );
+ }
+ return aRet;
+}
+
+SC_IMPL_DUMMY_PROPERTY_LISTENER( ScDPDimension )
+
+ScDPHierarchies::ScDPHierarchies( ScDPSource* pSrc, tools::Long nD ) :
+ pSource( pSrc ),
+ nDim( nD )
+{
+ //TODO: hold pSource
+}
+
+ScDPHierarchies::~ScDPHierarchies()
+{
+ //TODO: release pSource
+}
+
+// very simple XNameAccess implementation using getCount/getByIndex
+
+uno::Any SAL_CALL ScDPHierarchies::getByName( const OUString& aName )
+{
+ tools::Long nCount = getCount();
+ for (tools::Long i=0; i<nCount; i++)
+ if ( getByIndex(i)->getName() == aName )
+ {
+ uno::Reference<container::XNamed> xNamed = getByIndex(i);
+ uno::Any aRet;
+ aRet <<= xNamed;
+ return aRet;
+ }
+
+ throw container::NoSuchElementException();
+}
+
+uno::Sequence<OUString> SAL_CALL ScDPHierarchies::getElementNames()
+{
+ tools::Long nCount = getCount();
+ uno::Sequence<OUString> aSeq(nCount);
+ OUString* pArr = aSeq.getArray();
+ for (tools::Long i=0; i<nCount; i++)
+ pArr[i] = getByIndex(i)->getName();
+ return aSeq;
+}
+
+sal_Bool SAL_CALL ScDPHierarchies::hasByName( const OUString& aName )
+{
+ tools::Long nCount = getCount();
+ for (tools::Long i=0; i<nCount; i++)
+ if ( getByIndex(i)->getName() == aName )
+ return true;
+ return false;
+}
+
+uno::Type SAL_CALL ScDPHierarchies::getElementType()
+{
+ return cppu::UnoType<container::XNamed>::get();
+}
+
+sal_Bool SAL_CALL ScDPHierarchies::hasElements()
+{
+ return ( getCount() > 0 );
+}
+
+// end of XNameAccess implementation
+
+sal_Int32 ScDPHierarchies::getCount()
+{
+ return nHierCount;
+}
+
+ScDPHierarchy* ScDPHierarchies::getByIndex(tools::Long nIndex) const
+{
+ // pass hierarchy index to new object in case the implementation
+ // will be extended to more than one hierarchy
+
+ if ( nIndex >= 0 && nIndex < nHierCount )
+ {
+ if ( !ppHiers )
+ {
+ const_cast<ScDPHierarchies*>(this)->ppHiers.reset( new rtl::Reference<ScDPHierarchy>[nHierCount] );
+ for (sal_Int32 i=0; i<nHierCount; i++)
+ ppHiers[i] = nullptr;
+ }
+ if ( !ppHiers[nIndex].is() )
+ {
+ ppHiers[nIndex] = new ScDPHierarchy( pSource, nDim, nIndex );
+ }
+
+ return ppHiers[nIndex].get();
+ }
+
+ return nullptr; //TODO: exception?
+}
+
+ScDPHierarchy::ScDPHierarchy( ScDPSource* pSrc, sal_Int32 nD, sal_Int32 nH ) :
+ pSource( pSrc ),
+ nDim( nD ),
+ nHier( nH )
+{
+ //TODO: hold pSource
+}
+
+ScDPHierarchy::~ScDPHierarchy()
+{
+ //TODO: release pSource
+}
+
+ScDPLevels* ScDPHierarchy::GetLevelsObject()
+{
+ if (!mxLevels.is())
+ {
+ mxLevels = new ScDPLevels( pSource, nDim, nHier );
+ }
+ return mxLevels.get();
+}
+
+uno::Reference<container::XNameAccess> SAL_CALL ScDPHierarchy::getLevels()
+{
+ return GetLevelsObject();
+}
+
+OUString SAL_CALL ScDPHierarchy::getName()
+{
+ OUString aRet; //TODO: globstr-ID !!!!
+ switch (nHier)
+ {
+ case SC_DAPI_HIERARCHY_FLAT:
+ aRet = "flat";
+ break; //TODO: name ???????
+ case SC_DAPI_HIERARCHY_QUARTER:
+ aRet = "Quarter";
+ break; //TODO: name ???????
+ case SC_DAPI_HIERARCHY_WEEK:
+ aRet = "Week";
+ break; //TODO: name ???????
+ default:
+ OSL_FAIL( "ScDPHierarchy::getName: unexpected hierarchy" );
+ break;
+ }
+ return aRet;
+}
+
+void SAL_CALL ScDPHierarchy::setName( const OUString& /* rNewName */ )
+{
+ OSL_FAIL("not implemented"); //TODO: exception?
+}
+
+ScDPLevels::ScDPLevels( ScDPSource* pSrc, sal_Int32 nD, sal_Int32 nH ) :
+ pSource( pSrc ),
+ nDim( nD ),
+ nHier( nH )
+{
+ //TODO: hold pSource
+
+ // text columns have only one level
+
+ tools::Long nSrcDim = pSource->GetSourceDim( nDim );
+ if ( pSource->IsDateDimension( nSrcDim ) )
+ {
+ switch ( nHier )
+ {
+ case SC_DAPI_HIERARCHY_FLAT: nLevCount = SC_DAPI_FLAT_LEVELS; break;
+ case SC_DAPI_HIERARCHY_QUARTER: nLevCount = SC_DAPI_QUARTER_LEVELS; break;
+ case SC_DAPI_HIERARCHY_WEEK: nLevCount = SC_DAPI_WEEK_LEVELS; break;
+ default:
+ OSL_FAIL("wrong hierarchy");
+ nLevCount = 0;
+ }
+ }
+ else
+ nLevCount = 1;
+}
+
+ScDPLevels::~ScDPLevels()
+{
+ //TODO: release pSource
+}
+
+// very simple XNameAccess implementation using getCount/getByIndex
+
+uno::Any SAL_CALL ScDPLevels::getByName( const OUString& aName )
+{
+ tools::Long nCount = getCount();
+ for (tools::Long i=0; i<nCount; i++)
+ if ( getByIndex(i)->getName() == aName )
+ {
+ uno::Reference<container::XNamed> xNamed = getByIndex(i);
+ uno::Any aRet;
+ aRet <<= xNamed;
+ return aRet;
+ }
+
+ throw container::NoSuchElementException();
+}
+
+uno::Sequence<OUString> SAL_CALL ScDPLevels::getElementNames()
+{
+ tools::Long nCount = getCount();
+ uno::Sequence<OUString> aSeq(nCount);
+ OUString* pArr = aSeq.getArray();
+ for (tools::Long i=0; i<nCount; i++)
+ pArr[i] = getByIndex(i)->getName();
+ return aSeq;
+}
+
+sal_Bool SAL_CALL ScDPLevels::hasByName( const OUString& aName )
+{
+ tools::Long nCount = getCount();
+ for (tools::Long i=0; i<nCount; i++)
+ if ( getByIndex(i)->getName() == aName )
+ return true;
+ return false;
+}
+
+uno::Type SAL_CALL ScDPLevels::getElementType()
+{
+ return cppu::UnoType<container::XNamed>::get();
+}
+
+sal_Bool SAL_CALL ScDPLevels::hasElements()
+{
+ return ( getCount() > 0 );
+}
+
+// end of XNameAccess implementation
+
+sal_Int32 ScDPLevels::getCount() const
+{
+ return nLevCount;
+}
+
+ScDPLevel* ScDPLevels::getByIndex(sal_Int32 nIndex) const
+{
+ if ( nIndex >= 0 && nIndex < nLevCount )
+ {
+ if ( !ppLevs )
+ {
+ const_cast<ScDPLevels*>(this)->ppLevs.reset(new rtl::Reference<ScDPLevel>[nLevCount] );
+ for (tools::Long i=0; i<nLevCount; i++)
+ ppLevs[i] = nullptr;
+ }
+ if ( !ppLevs[nIndex].is() )
+ {
+ ppLevs[nIndex] = new ScDPLevel( pSource, nDim, nHier, nIndex );
+ }
+
+ return ppLevs[nIndex].get();
+ }
+
+ return nullptr; //TODO: exception?
+}
+
+namespace {
+
+class ScDPGlobalMembersOrder
+{
+ ScDPLevel& rLevel;
+ bool bAscending;
+
+public:
+ ScDPGlobalMembersOrder( ScDPLevel& rLev, bool bAsc ) :
+ rLevel(rLev),
+ bAscending(bAsc)
+ {}
+
+ bool operator()( sal_Int32 nIndex1, sal_Int32 nIndex2 ) const;
+};
+
+}
+
+bool ScDPGlobalMembersOrder::operator()( sal_Int32 nIndex1, sal_Int32 nIndex2 ) const
+{
+ sal_Int32 nCompare = 0;
+ // seems that some ::std::sort() implementations pass the same index twice
+ if( nIndex1 != nIndex2 )
+ {
+ ScDPMembers* pMembers = rLevel.GetMembersObject();
+ ScDPMember* pMember1 = pMembers->getByIndex(nIndex1);
+ ScDPMember* pMember2 = pMembers->getByIndex(nIndex2);
+ nCompare = pMember1->Compare( *pMember2 );
+ }
+ return bAscending ? (nCompare < 0) : (nCompare > 0);
+}
+
+ScDPLevel::ScDPLevel( ScDPSource* pSrc, sal_Int32 nD, sal_Int32 nH, sal_Int32 nL ) :
+ pSource( pSrc ),
+ nDim( nD ),
+ nHier( nH ),
+ nLev( nL ),
+ aSortInfo( OUString(), true, sheet::DataPilotFieldSortMode::NAME ), // default: sort by name
+ nSortMeasure( 0 ),
+ nAutoMeasure( 0 ),
+ bShowEmpty( false ),
+ bEnableLayout( false ),
+ bRepeatItemLabels( false )
+{
+ //TODO: hold pSource
+ // aSubTotals is empty
+}
+
+ScDPLevel::~ScDPLevel()
+{
+ //TODO: release pSource
+}
+
+void ScDPLevel::EvaluateSortOrder()
+{
+ switch (aSortInfo.Mode)
+ {
+ case sheet::DataPilotFieldSortMode::DATA:
+ {
+ // find index of measure (index among data dimensions)
+
+ tools::Long nMeasureCount = pSource->GetDataDimensionCount();
+ for (tools::Long nMeasure=0; nMeasure<nMeasureCount; nMeasure++)
+ {
+ if (pSource->GetDataDimName(nMeasure) == aSortInfo.Field)
+ {
+ nSortMeasure = nMeasure;
+ break;
+ }
+ }
+
+ //TODO: error if not found?
+ }
+ break;
+ case sheet::DataPilotFieldSortMode::MANUAL:
+ case sheet::DataPilotFieldSortMode::NAME:
+ {
+ ScDPMembers* pLocalMembers = GetMembersObject();
+ tools::Long nCount = pLocalMembers->getCount();
+
+ aGlobalOrder.resize( nCount );
+ for (tools::Long nPos=0; nPos<nCount; nPos++)
+ aGlobalOrder[nPos] = nPos;
+
+ // allow manual or name (manual is always ascending)
+ bool bAscending = ( aSortInfo.Mode == sheet::DataPilotFieldSortMode::MANUAL || aSortInfo.IsAscending );
+ ScDPGlobalMembersOrder aComp( *this, bAscending );
+ ::std::sort( aGlobalOrder.begin(), aGlobalOrder.end(), aComp );
+ }
+ break;
+ }
+
+ if ( !aAutoShowInfo.IsEnabled )
+ return;
+
+ // find index of measure (index among data dimensions)
+
+ tools::Long nMeasureCount = pSource->GetDataDimensionCount();
+ for (tools::Long nMeasure=0; nMeasure<nMeasureCount; nMeasure++)
+ {
+ if (pSource->GetDataDimName(nMeasure) == aAutoShowInfo.DataField)
+ {
+ nAutoMeasure = nMeasure;
+ break;
+ }
+ }
+
+ //TODO: error if not found?
+}
+
+void ScDPLevel::SetEnableLayout(bool bSet)
+{
+ bEnableLayout = bSet;
+}
+
+ScDPMembers* ScDPLevel::GetMembersObject()
+{
+ if (!mxMembers.is())
+ {
+ mxMembers = new ScDPMembers( pSource, nDim, nHier, nLev );
+ }
+ return mxMembers.get();
+}
+
+uno::Reference<sheet::XMembersAccess> SAL_CALL ScDPLevel::getMembers()
+{
+ return GetMembersObject();
+}
+
+uno::Sequence<sheet::MemberResult> SAL_CALL ScDPLevel::getResults()
+{
+ const uno::Sequence<sheet::MemberResult>* pRes = pSource->GetMemberResults( this );
+ if (pRes)
+ return *pRes;
+
+ return {}; //TODO: Error?
+}
+
+OUString SAL_CALL ScDPLevel::getName()
+{
+ tools::Long nSrcDim = pSource->GetSourceDim( nDim );
+ if ( pSource->IsDateDimension( nSrcDim ) )
+ {
+ OUString aRet; //TODO: globstr-ID !!!!
+
+ if ( nHier == SC_DAPI_HIERARCHY_QUARTER )
+ {
+ switch ( nLev )
+ {
+ case SC_DAPI_LEVEL_YEAR:
+ aRet = "Year";
+ break;
+ case SC_DAPI_LEVEL_QUARTER:
+ aRet = "Quarter";
+ break;
+ case SC_DAPI_LEVEL_MONTH:
+ aRet = "Month";
+ break;
+ case SC_DAPI_LEVEL_DAY:
+ aRet = "Day";
+ break;
+ default:
+ OSL_FAIL( "ScDPLevel::getName: unexpected level" );
+ break;
+ }
+ }
+ else if ( nHier == SC_DAPI_HIERARCHY_WEEK )
+ {
+ switch ( nLev )
+ {
+ case SC_DAPI_LEVEL_YEAR:
+ aRet = "Year";
+ break;
+ case SC_DAPI_LEVEL_WEEK:
+ aRet = "Week";
+ break;
+ case SC_DAPI_LEVEL_WEEKDAY:
+ aRet = "Weekday";
+ break;
+ default:
+ OSL_FAIL( "ScDPLevel::getName: unexpected level" );
+ break;
+ }
+ }
+ if (!aRet.isEmpty())
+ return aRet;
+ }
+
+ ScDPDimension* pDim = pSource->GetDimensionsObject()->getByIndex(nSrcDim);
+ if (!pDim)
+ return OUString();
+
+ return pDim->getName();
+}
+
+void SAL_CALL ScDPLevel::setName( const OUString& /* rNewName */ )
+{
+ OSL_FAIL("not implemented"); //TODO: exception?
+}
+
+uno::Sequence<sal_Int16> ScDPLevel::getSubTotals() const
+{
+ //TODO: separate functions for settings and evaluation?
+
+ tools::Long nSrcDim = pSource->GetSourceDim( nDim );
+ if ( !pSource->SubTotalAllowed( nSrcDim ) )
+ return {};
+
+ return aSubTotals;
+}
+
+// XPropertySet
+
+uno::Reference<beans::XPropertySetInfo> SAL_CALL ScDPLevel::getPropertySetInfo()
+{
+ static const SfxItemPropertyMapEntry aDPLevelMap_Impl[] =
+ {
+ //TODO: change type of AutoShow/Layout/Sorting to API struct when available
+ { SC_UNO_DP_AUTOSHOW, 0, cppu::UnoType<sheet::DataPilotFieldAutoShowInfo>::get(), 0, 0 },
+ { SC_UNO_DP_LAYOUT, 0, cppu::UnoType<sheet::DataPilotFieldLayoutInfo>::get(), 0, 0 },
+ { SC_UNO_DP_SHOWEMPTY, 0, cppu::UnoType<bool>::get(), 0, 0 },
+ { SC_UNO_DP_REPEATITEMLABELS, 0, cppu::UnoType<bool>::get(), 0, 0 },
+ { SC_UNO_DP_SORTING, 0, cppu::UnoType<sheet::DataPilotFieldSortInfo>::get(), 0, 0 },
+ { SC_UNO_DP_SUBTOTAL, 0, cppu::UnoType<uno::Sequence<sheet::GeneralFunction>>::get(), 0, 0 },
+ { SC_UNO_DP_SUBTOTAL2, 0, cppu::UnoType<uno::Sequence<sal_Int16>>::get(), 0, 0 },
+ { u"", 0, css::uno::Type(), 0, 0 }
+ };
+ static uno::Reference<beans::XPropertySetInfo> aRef =
+ new SfxItemPropertySetInfo( aDPLevelMap_Impl );
+ return aRef;
+}
+
+void SAL_CALL ScDPLevel::setPropertyValue( const OUString& aPropertyName, const uno::Any& aValue )
+{
+ if ( aPropertyName == SC_UNO_DP_SHOWEMPTY )
+ bShowEmpty = lcl_GetBoolFromAny(aValue);
+ else if ( aPropertyName == SC_UNO_DP_REPEATITEMLABELS )
+ bRepeatItemLabels = lcl_GetBoolFromAny(aValue);
+ else if ( aPropertyName == SC_UNO_DP_SUBTOTAL )
+ {
+ uno::Sequence<sheet::GeneralFunction> aSeq;
+ aValue >>= aSeq;
+ aSubTotals.realloc(aSeq.getLength());
+ std::transform(std::cbegin(aSeq), std::cend(aSeq), aSubTotals.getArray(),
+ [](const sheet::GeneralFunction& rFunc) -> sal_Int16 {
+ return static_cast<sal_Int16>(rFunc); });
+ }
+ else if ( aPropertyName == SC_UNO_DP_SUBTOTAL2 )
+ aValue >>= aSubTotals;
+ else if ( aPropertyName == SC_UNO_DP_SORTING )
+ aValue >>= aSortInfo;
+ else if ( aPropertyName == SC_UNO_DP_AUTOSHOW )
+ aValue >>= aAutoShowInfo;
+ else if ( aPropertyName == SC_UNO_DP_LAYOUT )
+ aValue >>= aLayoutInfo;
+ else
+ {
+ OSL_FAIL("unknown property");
+ }
+}
+
+uno::Any SAL_CALL ScDPLevel::getPropertyValue( const OUString& aPropertyName )
+{
+ uno::Any aRet;
+ if ( aPropertyName == SC_UNO_DP_SHOWEMPTY )
+ aRet <<= bShowEmpty;
+ else if ( aPropertyName == SC_UNO_DP_REPEATITEMLABELS )
+ aRet <<= bRepeatItemLabels;
+ else if ( aPropertyName == SC_UNO_DP_SUBTOTAL )
+ {
+ const uno::Sequence<sal_Int16> aSeq = getSubTotals();
+ uno::Sequence<sheet::GeneralFunction> aNewSeq(aSeq.getLength());
+ std::transform(aSeq.begin(), aSeq.end(), aNewSeq.getArray(),
+ [](const sal_Int16 nFunc) -> sheet::GeneralFunction {
+ if (nFunc == sheet::GeneralFunction2::MEDIAN)
+ return sheet::GeneralFunction_NONE;
+ return static_cast<sheet::GeneralFunction>(nFunc);
+ });
+
+ aRet <<= aNewSeq;
+ }
+ else if ( aPropertyName == SC_UNO_DP_SUBTOTAL2 )
+ {
+ uno::Sequence<sal_Int16> aSeq = getSubTotals(); //TODO: avoid extra copy?
+ aRet <<= aSeq;
+ }
+ else if ( aPropertyName == SC_UNO_DP_SORTING )
+ aRet <<= aSortInfo;
+ else if ( aPropertyName == SC_UNO_DP_AUTOSHOW )
+ aRet <<= aAutoShowInfo;
+ else if ( aPropertyName == SC_UNO_DP_LAYOUT )
+ aRet <<= aLayoutInfo;
+ else if (aPropertyName == SC_UNO_DP_LAYOUTNAME)
+ {
+ // read only property
+ tools::Long nSrcDim = pSource->GetSourceDim(nDim);
+ ScDPDimension* pDim = pSource->GetDimensionsObject()->getByIndex(nSrcDim);
+ if (!pDim)
+ return aRet;
+
+ const std::optional<OUString> & pLayoutName = pDim->GetLayoutName();
+ if (!pLayoutName)
+ return aRet;
+
+ aRet <<= *pLayoutName;
+ }
+ else
+ {
+ OSL_FAIL("unknown property");
+ }
+ return aRet;
+}
+
+SC_IMPL_DUMMY_PROPERTY_LISTENER( ScDPLevel )
+
+ScDPMembers::ScDPMembers( ScDPSource* pSrc, sal_Int32 nD, sal_Int32 nH, sal_Int32 nL ) :
+ pSource( pSrc ),
+ nDim( nD ),
+ nHier( nH ),
+ nLev( nL )
+{
+ //TODO: hold pSource
+
+ tools::Long nSrcDim = pSource->GetSourceDim( nDim );
+ if ( pSource->IsDataLayoutDimension(nSrcDim) )
+ nMbrCount = pSource->GetDataDimensionCount();
+ else if ( nHier != SC_DAPI_HIERARCHY_FLAT && pSource->IsDateDimension( nSrcDim ) )
+ {
+ nMbrCount = 0;
+ if ( nHier == SC_DAPI_HIERARCHY_QUARTER )
+ {
+ switch (nLev)
+ {
+ case SC_DAPI_LEVEL_YEAR:
+ {
+ const ScDPItemData* pLastNumData = nullptr;
+ for ( SCROW n = 0; n < static_cast<SCROW>(pSource->GetData()->GetColumnEntries(nDim).size()); n-- )
+ {
+ const ScDPItemData* pData = GetSrcItemDataByIndex( n );
+ if ( pData && pData->HasStringData() )
+ break;
+ else
+ pLastNumData = pData;
+ }
+
+ if ( pLastNumData )
+ {
+ const ScDPItemData* pFirstData = GetSrcItemDataByIndex( 0 );
+ double fFirstVal = pFirstData->GetValue();
+ double fLastVal = pLastNumData->GetValue();
+
+ tools::Long nFirstYear = pSource->GetData()->GetDatePart(
+ static_cast<tools::Long>(::rtl::math::approxFloor( fFirstVal )),
+ nHier, nLev );
+ tools::Long nLastYear = pSource->GetData()->GetDatePart(
+ static_cast<tools::Long>(::rtl::math::approxFloor( fLastVal )),
+ nHier, nLev );
+
+ nMbrCount = nLastYear + 1 - nFirstYear;
+ }
+ else
+ nMbrCount = 0; // no values
+ }
+ break;
+ case SC_DAPI_LEVEL_QUARTER: nMbrCount = 4; break;
+ case SC_DAPI_LEVEL_MONTH: nMbrCount = 12; break;
+ case SC_DAPI_LEVEL_DAY: nMbrCount = 31; break;
+ default:
+ OSL_FAIL( "ScDPMembers::ScDPMembers: unexpected level" );
+ break;
+ }
+ }
+ else if ( nHier == SC_DAPI_HIERARCHY_WEEK )
+ {
+ switch (nLev)
+ {
+ case SC_DAPI_LEVEL_YEAR: nMbrCount = 1; break; //TODO: get years from source
+ case SC_DAPI_LEVEL_WEEK: nMbrCount = 53; break;
+ case SC_DAPI_LEVEL_WEEKDAY: nMbrCount = 7; break;
+ default:
+ OSL_FAIL( "ScDPMembers::ScDPMembers: unexpected level" );
+ break;
+ }
+ }
+ }
+ else
+ nMbrCount = pSource->GetData()->GetMembersCount( nSrcDim );
+}
+
+ScDPMembers::~ScDPMembers()
+{
+}
+
+// XNameAccess implementation using getCount/getByIndex
+
+sal_Int32 ScDPMembers::GetIndexFromName( const OUString& rName ) const
+{
+ if ( aHashMap.empty() )
+ {
+ // store the index for each name
+
+ sal_Int32 nCount = getCount();
+ for (sal_Int32 i=0; i<nCount; i++)
+ aHashMap[ getByIndex(i)->getName() ] = i;
+ }
+
+ ScDPMembersHashMap::const_iterator aIter = aHashMap.find( rName );
+ if ( aIter != aHashMap.end() )
+ return aIter->second; // found index
+ else
+ return -1; // not found
+}
+
+uno::Any SAL_CALL ScDPMembers::getByName( const OUString& aName )
+{
+ sal_Int32 nIndex = GetIndexFromName( aName );
+ if ( nIndex >= 0 )
+ {
+ uno::Reference<container::XNamed> xNamed = getByIndex(nIndex);
+ uno::Any aRet;
+ aRet <<= xNamed;
+ return aRet;
+ }
+
+ throw container::NoSuchElementException();
+}
+
+uno::Sequence<OUString> SAL_CALL ScDPMembers::getElementNames()
+{
+ return getElementNames( false );
+}
+
+sal_Bool SAL_CALL ScDPMembers::hasByName( const OUString& aName )
+{
+ return ( GetIndexFromName( aName ) >= 0 );
+}
+
+uno::Type SAL_CALL ScDPMembers::getElementType()
+{
+ return cppu::UnoType<container::XNamed>::get();
+}
+
+sal_Bool SAL_CALL ScDPMembers::hasElements()
+{
+ return ( getCount() > 0 );
+}
+
+// end of XNameAccess implementation
+
+// XMembersAccess implementation
+
+uno::Sequence<OUString> SAL_CALL ScDPMembers::getLocaleIndependentElementNames()
+{
+ return getElementNames( true );
+}
+
+// end of XMembersAccess implementation
+
+uno::Sequence<OUString> ScDPMembers::getElementNames( bool bLocaleIndependent ) const
+{
+ // Return list of names in sorted order,
+ // so it's displayed in that order in the field options dialog.
+ // Sorting is done at the level object (parent of this).
+
+ ScDPLevel* pLevel = pSource->GetDimensionsObject()->getByIndex(nDim)->
+ GetHierarchiesObject()->getByIndex(nHier)->GetLevelsObject()->getByIndex(nLev);
+ pLevel->EvaluateSortOrder();
+ const std::vector<sal_Int32>& rGlobalOrder = pLevel->GetGlobalOrder();
+ bool bSort = !rGlobalOrder.empty();
+
+ tools::Long nCount = getCount();
+ uno::Sequence<OUString> aSeq(nCount);
+ OUString* pArr = aSeq.getArray();
+ for (tools::Long i=0; i<nCount; i++)
+ pArr[i] = getByIndex(bSort ? rGlobalOrder[i] : i)->GetNameStr( bLocaleIndependent);
+ return aSeq;
+}
+
+sal_Int32 ScDPMembers::getMinMembers() const
+{
+ // used in lcl_CountMinMembers
+
+ sal_Int32 nVisCount = 0;
+ if (!maMembers.empty())
+ {
+ nVisCount = std::count_if(maMembers.begin(), maMembers.end(), [](const rtl::Reference<ScDPMember>& pMbr) {
+ // count only visible with details (default is true for both)
+ return !pMbr || (pMbr->isVisible() && pMbr->getShowDetails()); });
+ }
+ else
+ nVisCount = nMbrCount; // default for all
+
+ return nVisCount;
+}
+
+ScDPMember* ScDPMembers::getByIndex(sal_Int32 nIndex) const
+{
+ // result of GetColumnEntries must not change between ScDPMembers ctor
+ // and all calls to getByIndex
+
+ if ( nIndex >= 0 && nIndex < nMbrCount )
+ {
+ if (maMembers.empty())
+ maMembers.resize(nMbrCount);
+
+ if (!maMembers[nIndex])
+ {
+ rtl::Reference<ScDPMember> pNew;
+ sal_Int32 nSrcDim = pSource->GetSourceDim( nDim );
+ if ( pSource->IsDataLayoutDimension(nSrcDim) )
+ {
+ // empty name (never shown, not used for lookup)
+ pNew.set(new ScDPMember(pSource, nDim, nHier, nLev, 0));
+ }
+ else if ( nHier != SC_DAPI_HIERARCHY_FLAT && pSource->IsDateDimension( nSrcDim ) )
+ {
+ sal_Int32 nGroupBy = 0;
+ sal_Int32 nVal = 0;
+ OUString aName;
+
+ if ( nLev == SC_DAPI_LEVEL_YEAR ) // YEAR is in both hierarchies
+ {
+ //TODO: cache year range here!
+
+ double fFirstVal = pSource->GetData()->GetMemberByIndex( nSrcDim, 0 )->GetValue();
+ tools::Long nFirstYear = pSource->GetData()->GetDatePart(
+ static_cast<tools::Long>(::rtl::math::approxFloor( fFirstVal )),
+ nHier, nLev );
+
+ nVal = nFirstYear + nIndex;
+ }
+ else if ( nHier == SC_DAPI_HIERARCHY_WEEK && nLev == SC_DAPI_LEVEL_WEEKDAY )
+ {
+ nVal = nIndex; // DayOfWeek is 0-based
+ aName = ScGlobal::GetCalendar().getDisplayName(
+ css::i18n::CalendarDisplayIndex::DAY,
+ sal::static_int_cast<sal_Int16>(nVal), 0 );
+ }
+ else if ( nHier == SC_DAPI_HIERARCHY_QUARTER && nLev == SC_DAPI_LEVEL_MONTH )
+ {
+ nVal = nIndex; // Month is 0-based
+ aName = ScGlobal::GetCalendar().getDisplayName(
+ css::i18n::CalendarDisplayIndex::MONTH,
+ sal::static_int_cast<sal_Int16>(nVal), 0 );
+ }
+ else
+ nVal = nIndex + 1; // Quarter, Day, Week are 1-based
+
+ switch (nLev)
+ {
+ case SC_DAPI_LEVEL_YEAR:
+ nGroupBy = sheet::DataPilotFieldGroupBy::YEARS;
+ break;
+ case SC_DAPI_LEVEL_QUARTER:
+ case SC_DAPI_LEVEL_WEEK:
+ nGroupBy = sheet::DataPilotFieldGroupBy::QUARTERS;
+ break;
+ case SC_DAPI_LEVEL_MONTH:
+ case SC_DAPI_LEVEL_WEEKDAY:
+ nGroupBy = sheet::DataPilotFieldGroupBy::MONTHS;
+ break;
+ case SC_DAPI_LEVEL_DAY:
+ nGroupBy = sheet::DataPilotFieldGroupBy::DAYS;
+ break;
+ default:
+ ;
+ }
+ if (aName.isEmpty())
+ aName = OUString::number(nVal);
+
+ ScDPItemData aData(nGroupBy, nVal);
+ SCROW nId = pSource->GetCache()->GetIdByItemData(nDim, aData);
+ pNew.set(new ScDPMember(pSource, nDim, nHier, nLev, nId));
+ }
+ else
+ {
+ const std::vector<SCROW>& memberIndexs = pSource->GetData()->GetColumnEntries(nSrcDim);
+ pNew.set(new ScDPMember(pSource, nDim, nHier, nLev, memberIndexs[nIndex]));
+ }
+ maMembers[nIndex] = pNew;
+ }
+
+ return maMembers[nIndex].get();
+ }
+
+ return nullptr; //TODO: exception?
+}
+
+ScDPMember::ScDPMember(
+ ScDPSource* pSrc, sal_Int32 nD, sal_Int32 nH, sal_Int32 nL, SCROW nIndex) :
+ pSource( pSrc ),
+ nDim( nD ),
+ nHier( nH ),
+ nLev( nL ),
+ mnDataId( nIndex ),
+ nPosition( -1 ),
+ bVisible( true ),
+ bShowDet( true )
+{
+ //TODO: hold pSource
+}
+
+ScDPMember::~ScDPMember()
+{
+ //TODO: release pSource
+}
+
+bool ScDPMember::IsNamedItem(SCROW nIndex) const
+{
+ sal_Int32 nSrcDim = pSource->GetSourceDim( nDim );
+ if ( nHier != SC_DAPI_HIERARCHY_FLAT && pSource->IsDateDimension( nSrcDim ) )
+ {
+ const ScDPItemData* pData = pSource->GetCache()->GetItemDataById(nDim, nIndex);
+ if (pData->IsValue())
+ {
+ tools::Long nComp = pSource->GetData()->GetDatePart(
+ static_cast<tools::Long>(::rtl::math::approxFloor( pData->GetValue() )),
+ nHier, nLev );
+ // fValue is converted from integer, so simple comparison works
+ const ScDPItemData* pData2 = GetItemData();
+ return pData2 && nComp == pData2->GetValue();
+ }
+ }
+
+ return nIndex == mnDataId;
+}
+
+sal_Int32 ScDPMember::Compare( const ScDPMember& rOther ) const
+{
+ if ( nPosition >= 0 )
+ {
+ if ( rOther.nPosition >= 0 )
+ {
+ OSL_ENSURE( nPosition != rOther.nPosition, "same position for two members" );
+ return ( nPosition < rOther.nPosition ) ? -1 : 1;
+ }
+ else
+ {
+ // only this has a position - members with specified positions come before those without
+ return -1;
+ }
+ }
+ else if ( rOther.nPosition >= 0 )
+ {
+ // only rOther has a position
+ return 1;
+ }
+
+ // no positions set - compare names
+ return pSource->GetData()->Compare( pSource->GetSourceDim(nDim),mnDataId,rOther.GetItemDataId());
+}
+
+ScDPItemData ScDPMember::FillItemData() const
+{
+ //TODO: handle date hierarchy...
+
+ const ScDPItemData* pData = GetItemData();
+ return (pData ? *pData : ScDPItemData());
+}
+
+const std::optional<OUString> & ScDPMember::GetLayoutName() const
+{
+ return mpLayoutName;
+}
+
+OUString ScDPMember::GetNameStr( bool bLocaleIndependent ) const
+{
+ const ScDPItemData* pData = GetItemData();
+ if (pData)
+ return pSource->GetData()->GetFormattedString(nDim, *pData, bLocaleIndependent);
+ return OUString();
+}
+
+OUString SAL_CALL ScDPMember::getName()
+{
+ return GetNameStr( false );
+}
+
+void SAL_CALL ScDPMember::setName( const OUString& /* rNewName */ )
+{
+ OSL_FAIL("not implemented"); //TODO: exception?
+}
+
+// XPropertySet
+
+uno::Reference<beans::XPropertySetInfo> SAL_CALL ScDPMember::getPropertySetInfo()
+{
+ static const SfxItemPropertyMapEntry aDPMemberMap_Impl[] =
+ {
+ { SC_UNO_DP_ISVISIBLE, 0, cppu::UnoType<bool>::get(), 0, 0 },
+ { SC_UNO_DP_POSITION, 0, cppu::UnoType<sal_Int32>::get(), 0, 0 },
+ { SC_UNO_DP_SHOWDETAILS, 0, cppu::UnoType<bool>::get(), 0, 0 },
+ { SC_UNO_DP_LAYOUTNAME, 0, cppu::UnoType<OUString>::get(), 0, 0 },
+ { u"", 0, css::uno::Type(), 0, 0 }
+ };
+ static uno::Reference<beans::XPropertySetInfo> aRef =
+ new SfxItemPropertySetInfo( aDPMemberMap_Impl );
+ return aRef;
+}
+
+void SAL_CALL ScDPMember::setPropertyValue( const OUString& aPropertyName, const uno::Any& aValue )
+{
+ if ( aPropertyName == SC_UNO_DP_ISVISIBLE )
+ bVisible = lcl_GetBoolFromAny(aValue);
+ else if ( aPropertyName == SC_UNO_DP_SHOWDETAILS )
+ bShowDet = lcl_GetBoolFromAny(aValue);
+ else if ( aPropertyName == SC_UNO_DP_POSITION )
+ aValue >>= nPosition;
+ else if (aPropertyName == SC_UNO_DP_LAYOUTNAME)
+ {
+ OUString aName;
+ if (aValue >>= aName)
+ mpLayoutName = aName;
+ }
+ else
+ {
+ OSL_FAIL("unknown property");
+ }
+}
+
+uno::Any SAL_CALL ScDPMember::getPropertyValue( const OUString& aPropertyName )
+{
+ uno::Any aRet;
+ if ( aPropertyName == SC_UNO_DP_ISVISIBLE )
+ aRet <<= bVisible;
+ else if ( aPropertyName == SC_UNO_DP_SHOWDETAILS )
+ aRet <<= bShowDet;
+ else if ( aPropertyName == SC_UNO_DP_POSITION )
+ aRet <<= nPosition;
+ else if (aPropertyName == SC_UNO_DP_LAYOUTNAME)
+ aRet <<= mpLayoutName ? *mpLayoutName : OUString();
+ else
+ {
+ OSL_FAIL("unknown property");
+ }
+ return aRet;
+}
+
+SC_IMPL_DUMMY_PROPERTY_LISTENER( ScDPMember )
+
+const ScDPCache* ScDPSource::GetCache()
+{
+ OSL_ENSURE( GetData() , "empty ScDPTableData pointer");
+ return ( GetData()!=nullptr ) ? &GetData()->GetCacheTable().getCache() : nullptr ;
+}
+
+const ScDPItemData* ScDPMember::GetItemData() const
+{
+ const ScDPItemData* pData = pSource->GetItemDataById(nDim, mnDataId);
+ SAL_WARN_IF( !pData, "sc.core", "ScDPMember::GetItemData: what data? nDim " << nDim << ", mnDataId " << mnDataId);
+ return pData;
+}
+
+const ScDPItemData* ScDPSource::GetItemDataById(sal_Int32 nDim, sal_Int32 nId)
+{
+ return GetData()->GetMemberById(nDim, nId);
+}
+
+const ScDPItemData* ScDPMembers::GetSrcItemDataByIndex(SCROW nIndex)
+{
+ const std::vector< SCROW >& memberIds = pSource->GetData()->GetColumnEntries( nDim );
+ if ( nIndex < 0 || o3tl::make_unsigned(nIndex) >= memberIds.size() )
+ return nullptr;
+ SCROW nId = memberIds[ nIndex ];
+ return pSource->GetItemDataById( nDim, nId );
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sc/source/core/data/dputil.cxx b/sc/source/core/data/dputil.cxx
new file mode 100644
index 000000000..651d55093
--- /dev/null
+++ b/sc/source/core/data/dputil.cxx
@@ -0,0 +1,420 @@
+/* -*- 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/.
+ */
+
+#include <dputil.hxx>
+#include <dpitemdata.hxx>
+#include <dpnumgroupinfo.hxx>
+#include <globstr.hrc>
+#include <scresid.hxx>
+#include <generalfunction.hxx>
+
+#include <comphelper/string.hxx>
+#include <unotools/localedatawrapper.hxx>
+#include <unotools/calendarwrapper.hxx>
+#include <svl/numformat.hxx>
+#include <svl/zforlist.hxx>
+#include <rtl/math.hxx>
+#include <o3tl/string_view.hxx>
+#include <osl/diagnose.h>
+
+#include <com/sun/star/sheet/DataPilotFieldGroupBy.hpp>
+#include <com/sun/star/i18n/CalendarDisplayIndex.hpp>
+
+using namespace com::sun::star;
+
+namespace {
+
+const sal_uInt16 SC_DP_LEAPYEAR = 1648; // arbitrary leap year for date calculations
+
+OUString getTwoDigitString(sal_Int32 nValue)
+{
+ OUString aRet = OUString::number( nValue );
+ if ( aRet.getLength() < 2 )
+ aRet = "0" + aRet;
+ return aRet;
+}
+
+void appendDateStr(OUStringBuffer& rBuffer, double fValue, SvNumberFormatter* pFormatter)
+{
+ sal_uInt32 nFormat = pFormatter->GetStandardFormat( SvNumFormatType::DATE, ScGlobal::eLnge );
+ OUString aString;
+ pFormatter->GetInputLineString(fValue, nFormat, aString);
+ rBuffer.append(aString);
+}
+
+OUString getSpecialDateName(double fValue, bool bFirst, SvNumberFormatter* pFormatter)
+{
+ OUStringBuffer aBuffer;
+ aBuffer.append( bFirst ? '<' : '>' );
+ appendDateStr(aBuffer, fValue, pFormatter);
+ return aBuffer.makeStringAndClear();
+}
+
+}
+
+bool ScDPUtil::isDuplicateDimension(std::u16string_view rName)
+{
+ return o3tl::ends_with(rName, u"*");
+}
+
+OUString ScDPUtil::getSourceDimensionName(std::u16string_view rName)
+{
+ return OUString(comphelper::string::stripEnd(rName, '*'));
+}
+
+sal_uInt8 ScDPUtil::getDuplicateIndex(const OUString& rName)
+{
+ // Count all trailing '*'s.
+
+ sal_Int32 n = rName.getLength();
+ if (!n)
+ return 0;
+
+ sal_uInt8 nDupCount = 0;
+ const sal_Unicode* p = rName.getStr();
+ const sal_Unicode* pStart = p;
+ p += n-1; // Set it to the last char.
+ for (; p != pStart; --p, ++nDupCount)
+ {
+ if (*p != '*')
+ break;
+ }
+
+ return nDupCount;
+}
+
+OUString ScDPUtil::createDuplicateDimensionName(const OUString& rOriginal, size_t nDupCount)
+{
+ if (!nDupCount)
+ return rOriginal;
+
+ OUStringBuffer aBuf(rOriginal);
+ for (size_t i = 0; i < nDupCount; ++i)
+ aBuf.append('*');
+
+ return aBuf.makeStringAndClear();
+}
+
+OUString ScDPUtil::getDateGroupName(
+ sal_Int32 nDatePart, sal_Int32 nValue, SvNumberFormatter* pFormatter,
+ double fStart, double fEnd)
+{
+ if (nValue == ScDPItemData::DateFirst)
+ return getSpecialDateName(fStart, true, pFormatter);
+ if (nValue == ScDPItemData::DateLast)
+ return getSpecialDateName(fEnd, false, pFormatter);
+
+ switch ( nDatePart )
+ {
+ case sheet::DataPilotFieldGroupBy::YEARS:
+ return OUString::number(nValue);
+ case sheet::DataPilotFieldGroupBy::QUARTERS:
+ return ScGlobal::getLocaleData().getQuarterAbbreviation(sal_Int16(nValue-1)); // nValue is 1-based
+ case css::sheet::DataPilotFieldGroupBy::MONTHS:
+ return ScGlobal::GetCalendar().getDisplayName(
+ i18n::CalendarDisplayIndex::MONTH, sal_Int16(nValue-1), 0); // 0-based, get short name
+ case sheet::DataPilotFieldGroupBy::DAYS:
+ {
+ Date aDate(1, 1, SC_DP_LEAPYEAR);
+ aDate.AddDays(nValue - 1); // nValue is 1-based
+ tools::Long nDays = aDate - pFormatter->GetNullDate();
+
+ const sal_uInt32 nFormat = pFormatter->GetFormatIndex(NF_DATE_SYS_DDMMM, ScGlobal::eLnge);
+ const Color* pColor;
+ OUString aStr;
+ pFormatter->GetOutputString(nDays, nFormat, aStr, &pColor);
+ return aStr;
+ }
+ case sheet::DataPilotFieldGroupBy::HOURS:
+ {
+ //TODO: allow am/pm format?
+ return getTwoDigitString(nValue);
+ }
+ break;
+ case sheet::DataPilotFieldGroupBy::MINUTES:
+ case sheet::DataPilotFieldGroupBy::SECONDS:
+ {
+ return ScGlobal::getLocaleData().getTimeSep() + getTwoDigitString(nValue);
+ }
+ break;
+ default:
+ OSL_FAIL("invalid date part");
+ }
+
+ return "FIXME: unhandled value";
+}
+
+double ScDPUtil::getNumGroupStartValue(double fValue, const ScDPNumGroupInfo& rInfo)
+{
+ if (fValue < rInfo.mfStart && !rtl::math::approxEqual(fValue, rInfo.mfStart))
+ return -std::numeric_limits<double>::infinity();
+
+ if (fValue > rInfo.mfEnd && !rtl::math::approxEqual(fValue, rInfo.mfEnd))
+ return std::numeric_limits<double>::infinity();
+
+ double fDiff = fValue - rInfo.mfStart;
+ double fDiv = rtl::math::approxFloor( fDiff / rInfo.mfStep );
+ double fGroupStart = rInfo.mfStart + fDiv * rInfo.mfStep;
+
+ if (rtl::math::approxEqual(fGroupStart, rInfo.mfEnd) &&
+ !rtl::math::approxEqual(fGroupStart, rInfo.mfStart))
+ {
+ if (!rInfo.mbDateValues)
+ {
+ // A group that would consist only of the end value is not
+ // created, instead the value is included in the last group
+ // before. So the previous group is used if the calculated group
+ // start value is the selected end value.
+
+ fDiv -= 1.0;
+ return rInfo.mfStart + fDiv * rInfo.mfStep;
+ }
+
+ // For date values, the end value is instead treated as above the
+ // limit if it would be a group of its own.
+
+ return rInfo.mfEnd + rInfo.mfStep;
+ }
+
+ return fGroupStart;
+}
+
+namespace {
+
+void lcl_AppendDateStr( OUStringBuffer& rBuffer, double fValue, SvNumberFormatter* pFormatter )
+{
+ sal_uInt32 nFormat = pFormatter->GetStandardFormat( SvNumFormatType::DATE, ScGlobal::eLnge );
+ OUString aString;
+ pFormatter->GetInputLineString( fValue, nFormat, aString );
+ rBuffer.append( aString );
+}
+
+OUString lcl_GetSpecialNumGroupName( double fValue, bool bFirst, sal_Unicode cDecSeparator,
+ bool bDateValues, SvNumberFormatter* pFormatter )
+{
+ OSL_ENSURE( cDecSeparator != 0, "cDecSeparator not initialized" );
+
+ OUStringBuffer aBuffer;
+ aBuffer.append( bFirst ? '<' : '>' );
+ if ( bDateValues )
+ lcl_AppendDateStr( aBuffer, fValue, pFormatter );
+ else
+ rtl::math::doubleToUStringBuffer( aBuffer, fValue, rtl_math_StringFormat_Automatic,
+ rtl_math_DecimalPlaces_Max, cDecSeparator, true );
+ return aBuffer.makeStringAndClear();
+}
+
+OUString lcl_GetNumGroupName(
+ double fStartValue, const ScDPNumGroupInfo& rInfo, sal_Unicode cDecSep,
+ SvNumberFormatter* pFormatter)
+{
+ OSL_ENSURE( cDecSep != 0, "cDecSeparator not initialized" );
+
+ double fStep = rInfo.mfStep;
+ double fEndValue = fStartValue + fStep;
+ if (rInfo.mbIntegerOnly && (rInfo.mbDateValues || !rtl::math::approxEqual(fEndValue, rInfo.mfEnd)))
+ {
+ // The second number of the group label is
+ // (first number + size - 1) if there are only integer numbers,
+ // (first number + size) if any non-integer numbers are involved.
+ // Exception: The last group (containing the end value) is always
+ // shown as including the end value (but not for dates).
+
+ fEndValue -= 1.0;
+ }
+
+ if ( fEndValue > rInfo.mfEnd && !rInfo.mbAutoEnd )
+ {
+ // limit the last group to the end value
+
+ fEndValue = rInfo.mfEnd;
+ }
+
+ OUStringBuffer aBuffer;
+ if ( rInfo.mbDateValues )
+ {
+ lcl_AppendDateStr( aBuffer, fStartValue, pFormatter );
+ aBuffer.append( " - " ); // with spaces
+ lcl_AppendDateStr( aBuffer, fEndValue, pFormatter );
+ }
+ else
+ {
+ rtl::math::doubleToUStringBuffer( aBuffer, fStartValue, rtl_math_StringFormat_Automatic,
+ rtl_math_DecimalPlaces_Max, cDecSep, true );
+ aBuffer.append( '-' );
+ rtl::math::doubleToUStringBuffer( aBuffer, fEndValue, rtl_math_StringFormat_Automatic,
+ rtl_math_DecimalPlaces_Max, cDecSep, true );
+ }
+
+ return aBuffer.makeStringAndClear();
+}
+
+}
+
+OUString ScDPUtil::getNumGroupName(
+ double fValue, const ScDPNumGroupInfo& rInfo, sal_Unicode cDecSep, SvNumberFormatter* pFormatter)
+{
+ if ( fValue < rInfo.mfStart && !rtl::math::approxEqual( fValue, rInfo.mfStart ) )
+ return lcl_GetSpecialNumGroupName( rInfo.mfStart, true, cDecSep, rInfo.mbDateValues, pFormatter );
+
+ if ( fValue > rInfo.mfEnd && !rtl::math::approxEqual( fValue, rInfo.mfEnd ) )
+ return lcl_GetSpecialNumGroupName( rInfo.mfEnd, false, cDecSep, rInfo.mbDateValues, pFormatter );
+
+ double fDiff = fValue - rInfo.mfStart;
+ double fDiv = rtl::math::approxFloor( fDiff / rInfo.mfStep );
+ double fGroupStart = rInfo.mfStart + fDiv * rInfo.mfStep;
+
+ if ( rtl::math::approxEqual( fGroupStart, rInfo.mfEnd ) &&
+ !rtl::math::approxEqual( fGroupStart, rInfo.mfStart ) )
+ {
+ if (rInfo.mbDateValues)
+ {
+ // For date values, the end value is instead treated as above the limit
+ // if it would be a group of its own.
+ return lcl_GetSpecialNumGroupName( rInfo.mfEnd, false, cDecSep, rInfo.mbDateValues, pFormatter );
+ }
+ }
+
+ return lcl_GetNumGroupName(fGroupStart, rInfo, cDecSep, pFormatter);
+}
+
+sal_Int32 ScDPUtil::getDatePartValue(
+ double fValue, const ScDPNumGroupInfo* pInfo, sal_Int32 nDatePart,
+ const SvNumberFormatter* pFormatter)
+{
+ // Start and end are inclusive
+ // (End date without a time value is included, with a time value it's not)
+
+ if (pInfo)
+ {
+ if (fValue < pInfo->mfStart && !rtl::math::approxEqual(fValue, pInfo->mfStart))
+ return ScDPItemData::DateFirst;
+ if (fValue > pInfo->mfEnd && !rtl::math::approxEqual(fValue, pInfo->mfEnd))
+ return ScDPItemData::DateLast;
+ }
+
+ sal_Int32 nResult = 0;
+
+ if (nDatePart == sheet::DataPilotFieldGroupBy::HOURS ||
+ nDatePart == sheet::DataPilotFieldGroupBy::MINUTES ||
+ nDatePart == sheet::DataPilotFieldGroupBy::SECONDS)
+ {
+ // handle time
+ // (do as in the cell functions, ScInterpreter::ScGetHour() etc.)
+
+ sal_uInt16 nHour, nMinute, nSecond;
+ double fFractionOfSecond;
+ tools::Time::GetClock( fValue, nHour, nMinute, nSecond, fFractionOfSecond, 0);
+
+ switch (nDatePart)
+ {
+ case sheet::DataPilotFieldGroupBy::HOURS:
+ nResult = nHour;
+ break;
+ case sheet::DataPilotFieldGroupBy::MINUTES:
+ nResult = nMinute;
+ break;
+ case sheet::DataPilotFieldGroupBy::SECONDS:
+ nResult = nSecond;
+ break;
+ }
+ }
+ else
+ {
+ Date aDate = pFormatter->GetNullDate();
+ aDate.AddDays(::rtl::math::approxFloor(fValue));
+
+ switch ( nDatePart )
+ {
+ case css::sheet::DataPilotFieldGroupBy::YEARS:
+ nResult = aDate.GetYear();
+ break;
+ case css::sheet::DataPilotFieldGroupBy::QUARTERS:
+ nResult = 1 + (aDate.GetMonth() - 1) / 3; // 1..4
+ break;
+ case css::sheet::DataPilotFieldGroupBy::MONTHS:
+ nResult = aDate.GetMonth(); // 1..12
+ break;
+ case css::sheet::DataPilotFieldGroupBy::DAYS:
+ {
+ Date aYearStart(1, 1, aDate.GetYear());
+ nResult = (aDate - aYearStart) + 1; // Jan 01 has value 1
+ if (nResult >= 60 && !aDate.IsLeapYear())
+ {
+ // days are counted from 1 to 366 - if not from a leap year, adjust
+ ++nResult;
+ }
+ }
+ break;
+ default:
+ OSL_FAIL("invalid date part");
+ }
+ }
+
+ return nResult;
+}
+
+namespace {
+
+const TranslateId aFuncStrIds[] = {
+ {}, // SUBTOTAL_FUNC_NONE
+ STR_FUN_TEXT_AVG, // SUBTOTAL_FUNC_AVE
+ STR_FUN_TEXT_COUNT, // SUBTOTAL_FUNC_CNT
+ STR_FUN_TEXT_COUNT, // SUBTOTAL_FUNC_CNT2
+ STR_FUN_TEXT_MAX, // SUBTOTAL_FUNC_MAX
+ STR_FUN_TEXT_MIN, // SUBTOTAL_FUNC_MIN
+ STR_FUN_TEXT_PRODUCT, // SUBTOTAL_FUNC_PROD
+ STR_FUN_TEXT_STDDEV, // SUBTOTAL_FUNC_STD
+ STR_FUN_TEXT_STDDEV, // SUBTOTAL_FUNC_STDP
+ STR_FUN_TEXT_SUM, // SUBTOTAL_FUNC_SUM
+ STR_FUN_TEXT_VAR, // SUBTOTAL_FUNC_VAR
+ STR_FUN_TEXT_VAR, // SUBTOTAL_FUNC_VARP
+ STR_FUN_TEXT_MEDIAN, // SUBTOTAL_FUNC_MED
+ {} // SUBTOTAL_FUNC_SELECTION_COUNT - not used for pivot table
+};
+
+}
+
+OUString ScDPUtil::getDisplayedMeasureName(const OUString& rName, ScSubTotalFunc eFunc)
+{
+ assert(unsigned(eFunc) < SAL_N_ELEMENTS(aFuncStrIds));
+ TranslateId pId = aFuncStrIds[eFunc];
+ if (!pId)
+ return rName;
+ return ScResId(pId) + // function name
+ " - " +
+ rName; // field name
+}
+
+ScSubTotalFunc ScDPUtil::toSubTotalFunc(ScGeneralFunction eGenFunc)
+{
+ ScSubTotalFunc eSubTotal = SUBTOTAL_FUNC_NONE;
+ switch (eGenFunc)
+ {
+ case ScGeneralFunction::NONE: eSubTotal = SUBTOTAL_FUNC_NONE; break;
+ case ScGeneralFunction::SUM: eSubTotal = SUBTOTAL_FUNC_SUM; break;
+ case ScGeneralFunction::COUNT: eSubTotal = SUBTOTAL_FUNC_CNT2; break;
+ case ScGeneralFunction::AVERAGE: eSubTotal = SUBTOTAL_FUNC_AVE; break;
+ case ScGeneralFunction::MEDIAN: eSubTotal = SUBTOTAL_FUNC_MED; break;
+ case ScGeneralFunction::MAX: eSubTotal = SUBTOTAL_FUNC_MAX; break;
+ case ScGeneralFunction::MIN: eSubTotal = SUBTOTAL_FUNC_MIN; break;
+ case ScGeneralFunction::PRODUCT: eSubTotal = SUBTOTAL_FUNC_PROD; break;
+ case ScGeneralFunction::COUNTNUMS: eSubTotal = SUBTOTAL_FUNC_CNT; break;
+ case ScGeneralFunction::STDEV: eSubTotal = SUBTOTAL_FUNC_STD; break;
+ case ScGeneralFunction::STDEVP: eSubTotal = SUBTOTAL_FUNC_STDP; break;
+ case ScGeneralFunction::VAR: eSubTotal = SUBTOTAL_FUNC_VAR; break;
+ case ScGeneralFunction::VARP: eSubTotal = SUBTOTAL_FUNC_VARP; break;
+ case ScGeneralFunction::AUTO: eSubTotal = SUBTOTAL_FUNC_NONE; break;
+ default:
+ assert(false);
+ }
+ return eSubTotal;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sc/source/core/data/drawpage.cxx b/sc/source/core/data/drawpage.cxx
new file mode 100644
index 000000000..6b6f029fb
--- /dev/null
+++ b/sc/source/core/data/drawpage.cxx
@@ -0,0 +1,51 @@
+/* -*- 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 <drawpage.hxx>
+#include <drwlayer.hxx>
+#include <pageuno.hxx>
+
+ScDrawPage::ScDrawPage(ScDrawLayer& rNewModel, bool bMasterPage)
+: FmFormPage(rNewModel, bMasterPage)
+{
+ SetSize( Size( SAL_MAX_INT32, SAL_MAX_INT32 ) );
+ // largest size supported by sal_Int32 SdrPage::mnWidth/Height
+}
+
+ScDrawPage::~ScDrawPage()
+{
+}
+
+rtl::Reference<SdrPage> ScDrawPage::CloneSdrPage(SdrModel& rTargetModel) const
+{
+ ScDrawLayer& rScDrawLayer(static_cast< ScDrawLayer& >(rTargetModel));
+ rtl::Reference<ScDrawPage> pClonedScDrawPage(
+ new ScDrawPage(
+ rScDrawLayer,
+ IsMasterPage()));
+ pClonedScDrawPage->FmFormPage::lateInit(*this);
+ return pClonedScDrawPage;
+}
+
+css::uno::Reference< css::uno::XInterface > ScDrawPage::createUnoPage()
+{
+ return static_cast<cppu::OWeakObject*>( new ScPageObj( this ) );
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sc/source/core/data/drwlayer.cxx b/sc/source/core/data/drwlayer.cxx
new file mode 100644
index 000000000..3f41a017e
--- /dev/null
+++ b/sc/source/core/data/drwlayer.cxx
@@ -0,0 +1,2676 @@
+/* -*- 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 <com/sun/star/uno/Reference.hxx>
+#include <com/sun/star/chart/XChartDocument.hpp>
+#include <com/sun/star/chart2/XChartDocument.hpp>
+#include <com/sun/star/embed/XClassifiedObject.hpp>
+#include <com/sun/star/embed/XEmbeddedObject.hpp>
+
+#include <scitems.hxx>
+#include <editeng/eeitem.hxx>
+#include <editeng/frmdiritem.hxx>
+#include <sot/exchange.hxx>
+#include <svx/objfac3d.hxx>
+#include <svx/xtable.hxx>
+#include <svx/svdoutl.hxx>
+#include <svx/svditer.hxx>
+#include <svx/svdlayer.hxx>
+#include <svx/svdoashp.hxx>
+#include <svx/svdobj.hxx>
+#include <svx/svdocapt.hxx>
+#include <svx/svdomeas.hxx>
+#include <svx/svdoole2.hxx>
+#include <svx/svdopath.hxx>
+#include <svx/svdundo.hxx>
+#include <svx/sdsxyitm.hxx>
+#include <svx/svxids.hrc>
+#include <i18nlangtag/mslangid.hxx>
+#include <editeng/unolingu.hxx>
+#include <svx/drawitem.hxx>
+#include <editeng/fhgtitem.hxx>
+#include <editeng/scriptspaceitem.hxx>
+#include <sfx2/objsh.hxx>
+#include <svl/itempool.hxx>
+#include <vcl/canvastools.hxx>
+#include <vcl/svapp.hxx>
+#include <vcl/settings.hxx>
+#include <tools/globname.hxx>
+#include <tools/UnitConversion.hxx>
+#include <osl/diagnose.h>
+
+#include <basegfx/polygon/b2dpolygon.hxx>
+#include <basegfx/polygon/b2dpolygontools.hxx>
+
+#include <drwlayer.hxx>
+#include <drawpage.hxx>
+#include <global.hxx>
+#include <document.hxx>
+#include <userdat.hxx>
+#include <markdata.hxx>
+#include <globstr.hrc>
+#include <scresid.hxx>
+#include <scmod.hxx>
+#include <postit.hxx>
+#include <attrib.hxx>
+#include <charthelper.hxx>
+#include <table.hxx>
+#include <basegfx/matrix/b2dhommatrix.hxx>
+
+#include <memory>
+#include <algorithm>
+#include <cstdlib>
+
+namespace com::sun::star::embed { class XEmbeddedObject; }
+
+#define DET_ARROW_OFFSET 1000
+
+using namespace ::com::sun::star;
+
+static E3dObjFactory* pF3d = nullptr;
+static sal_uInt16 nInst = 0;
+
+SfxObjectShell* ScDrawLayer::pGlobalDrawPersist = nullptr;
+
+bool bDrawIsInUndo = false; //TODO: Member
+
+ScUndoObjData::ScUndoObjData( SdrObject* pObjP, const ScAddress& rOS, const ScAddress& rOE,
+ const ScAddress& rNS, const ScAddress& rNE ) :
+ SdrUndoObj( *pObjP ),
+ aOldStt( rOS ),
+ aOldEnd( rOE ),
+ aNewStt( rNS ),
+ aNewEnd( rNE )
+{
+}
+
+ScUndoObjData::~ScUndoObjData()
+{
+}
+
+void ScUndoObjData::Undo()
+{
+ ScDrawObjData* pData = ScDrawLayer::GetObjData( pObj );
+ OSL_ENSURE(pData,"ScUndoObjData: Data missing");
+ if (pData)
+ {
+ pData->maStart = aOldStt;
+ pData->maEnd = aOldEnd;
+ }
+
+ // Undo also an untransformed anchor
+ pData = ScDrawLayer::GetNonRotatedObjData( pObj );
+ if (pData)
+ {
+ pData->maStart = aOldStt;
+ pData->maEnd = aOldEnd;
+ }
+}
+
+void ScUndoObjData::Redo()
+{
+ ScDrawObjData* pData = ScDrawLayer::GetObjData( pObj );
+ OSL_ENSURE(pData,"ScUndoObjData: Data missing");
+ if (pData)
+ {
+ pData->maStart = aNewStt;
+ pData->maEnd = aNewEnd;
+ }
+
+ // Redo also an untransformed anchor
+ pData = ScDrawLayer::GetNonRotatedObjData( pObj );
+ if (pData)
+ {
+ pData->maStart = aNewStt;
+ pData->maEnd = aNewEnd;
+ }
+}
+
+ScUndoAnchorData::ScUndoAnchorData( SdrObject* pObjP, ScDocument* pDoc, SCTAB nTab ) :
+ SdrUndoObj( *pObjP ),
+ mpDoc( pDoc ),
+ mnTab( nTab )
+{
+ mbWasCellAnchored = ScDrawLayer::IsCellAnchored( *pObjP );
+ mbWasResizeWithCell = ScDrawLayer::IsResizeWithCell( *pObjP );
+}
+
+ScUndoAnchorData::~ScUndoAnchorData()
+{
+}
+
+void ScUndoAnchorData::Undo()
+{
+ // Trigger Object Change
+ if (pObj->IsInserted() && pObj->getSdrPageFromSdrObject())
+ {
+ SdrHint aHint(SdrHintKind::ObjectChange, *pObj);
+ pObj->getSdrModelFromSdrObject().Broadcast(aHint);
+ }
+
+ if (mbWasCellAnchored)
+ ScDrawLayer::SetCellAnchoredFromPosition(*pObj, *mpDoc, mnTab, mbWasResizeWithCell);
+ else
+ ScDrawLayer::SetPageAnchored( *pObj );
+}
+
+void ScUndoAnchorData::Redo()
+{
+ if (mbWasCellAnchored)
+ ScDrawLayer::SetPageAnchored( *pObj );
+ else
+ ScDrawLayer::SetCellAnchoredFromPosition(*pObj, *mpDoc, mnTab, mbWasResizeWithCell);
+
+ // Trigger Object Change
+ if (pObj->IsInserted() && pObj->getSdrPageFromSdrObject())
+ {
+ SdrHint aHint(SdrHintKind::ObjectChange, *pObj);
+ pObj->getSdrModelFromSdrObject().Broadcast(aHint);
+ }
+}
+
+ScTabDeletedHint::ScTabDeletedHint( SCTAB nTabNo ) :
+ nTab( nTabNo )
+{
+}
+
+ScTabDeletedHint::~ScTabDeletedHint()
+{
+}
+
+ScTabSizeChangedHint::ScTabSizeChangedHint( SCTAB nTabNo ) :
+ nTab( nTabNo )
+{
+}
+
+ScTabSizeChangedHint::~ScTabSizeChangedHint()
+{
+}
+
+#define MAXMM 10000000
+
+
+static void lcl_ReverseTwipsToMM( tools::Rectangle& rRect )
+{
+ rRect = o3tl::convert(rRect, o3tl::Length::mm100, o3tl::Length::twip);
+}
+
+static ScRange lcl_getClipRangeFromClipDoc(ScDocument* pClipDoc, SCTAB nClipTab)
+{
+ if (!pClipDoc)
+ return ScRange();
+
+ SCCOL nClipStartX;
+ SCROW nClipStartY;
+ SCCOL nClipEndX;
+ SCROW nClipEndY;
+ pClipDoc->GetClipStart(nClipStartX, nClipStartY);
+ pClipDoc->GetClipArea(nClipEndX, nClipEndY, true);
+ nClipEndX = nClipEndX + nClipStartX;
+ nClipEndY += nClipStartY; // GetClipArea returns the difference
+
+ return ScRange(nClipStartX, nClipStartY, nClipTab, nClipEndX, nClipEndY, nClipTab);
+}
+
+ScDrawLayer::ScDrawLayer( ScDocument* pDocument, const OUString& rName ) :
+ FmFormModel(
+ nullptr,
+ pGlobalDrawPersist ? pGlobalDrawPersist : (pDocument ? pDocument->GetDocumentShell() : nullptr)),
+ aName( rName ),
+ pDoc( pDocument ),
+ bRecording( false ),
+ bAdjustEnabled( true ),
+ bHyphenatorSet( false )
+{
+ SetVOCInvalidationIsReliable(true);
+
+ pGlobalDrawPersist = nullptr; // Only use once
+
+ SfxObjectShell* pObjSh = pDocument ? pDocument->GetDocumentShell() : nullptr;
+ XColorListRef pXCol = XColorList::GetStdColorList();
+ if ( pObjSh )
+ {
+ SetObjectShell( pObjSh );
+
+ // set color table
+ const SvxColorListItem* pColItem = pObjSh->GetItem( SID_COLOR_TABLE );
+ if ( pColItem )
+ pXCol = pColItem->GetColorList();
+ }
+ SetPropertyList( static_cast<XPropertyList *> (pXCol.get()) );
+
+ SetSwapGraphics();
+
+ SetScaleUnit(MapUnit::Map100thMM);
+ SfxItemPool& rPool = GetItemPool();
+ rPool.SetDefaultMetric(MapUnit::Map100thMM);
+ SvxFrameDirectionItem aModeItem( SvxFrameDirection::Environment, EE_PARA_WRITINGDIR );
+ rPool.SetPoolDefaultItem( aModeItem );
+
+ // #i33700#
+ // Set shadow distance defaults as PoolDefaultItems. Details see bug.
+ rPool.SetPoolDefaultItem(makeSdrShadowXDistItem(300));
+ rPool.SetPoolDefaultItem(makeSdrShadowYDistItem(300));
+
+ // default for script spacing depends on locale, see SdDrawDocument ctor in sd
+ LanguageType eOfficeLanguage = Application::GetSettings().GetLanguageTag().getLanguageType();
+ if (MsLangId::isKorean(eOfficeLanguage) || eOfficeLanguage == LANGUAGE_JAPANESE)
+ {
+ // secondary is edit engine pool
+ rPool.GetSecondaryPool()->SetPoolDefaultItem( SvxScriptSpaceItem( false, EE_PARA_ASIANCJKSPACING ) );
+ }
+
+ rPool.FreezeIdRanges(); // the pool is also used directly
+
+ SdrLayerAdmin& rAdmin = GetLayerAdmin();
+ rAdmin.NewLayer("vorne", SC_LAYER_FRONT.get());
+ rAdmin.NewLayer("hinten", SC_LAYER_BACK.get());
+ rAdmin.NewLayer("intern", SC_LAYER_INTERN.get());
+ // tdf#140252 use same name as in ctor of SdrLayerAdmin
+ rAdmin.NewLayer(rAdmin.GetControlLayerName(), SC_LAYER_CONTROLS.get());
+ rAdmin.NewLayer("hidden", SC_LAYER_HIDDEN.get());
+
+ // Set link for URL-Fields
+ ScModule* pScMod = SC_MOD();
+ Outliner& rOutliner = GetDrawOutliner();
+ rOutliner.SetCalcFieldValueHdl( LINK( pScMod, ScModule, CalcFieldValueHdl ) );
+
+ Outliner& rHitOutliner = GetHitTestOutliner();
+ rHitOutliner.SetCalcFieldValueHdl( LINK( pScMod, ScModule, CalcFieldValueHdl ) );
+
+ // set FontHeight pool defaults without changing static SdrEngineDefaults
+ SfxItemPool* pOutlinerPool = rOutliner.GetEditTextObjectPool();
+ if ( pOutlinerPool )
+ {
+ m_pItemPool->SetPoolDefaultItem(SvxFontHeightItem( 423, 100, EE_CHAR_FONTHEIGHT )); // 12Pt
+ m_pItemPool->SetPoolDefaultItem(SvxFontHeightItem( 423, 100, EE_CHAR_FONTHEIGHT_CJK )); // 12Pt
+ m_pItemPool->SetPoolDefaultItem(SvxFontHeightItem( 423, 100, EE_CHAR_FONTHEIGHT_CTL )); // 12Pt
+ }
+ SfxItemPool* pHitOutlinerPool = rHitOutliner.GetEditTextObjectPool();
+ if ( pHitOutlinerPool )
+ {
+ pHitOutlinerPool->SetPoolDefaultItem(SvxFontHeightItem( 423, 100, EE_CHAR_FONTHEIGHT )); // 12Pt
+ pHitOutlinerPool->SetPoolDefaultItem(SvxFontHeightItem( 423, 100, EE_CHAR_FONTHEIGHT_CJK )); // 12Pt
+ pHitOutlinerPool->SetPoolDefaultItem(SvxFontHeightItem( 423, 100, EE_CHAR_FONTHEIGHT_CTL )); // 12Pt
+ }
+
+ // initial undo mode as in Calc document
+ if( pDoc )
+ EnableUndo( pDoc->IsUndoEnabled() );
+
+ // URL-Buttons have no handler anymore, all is done by themselves
+
+ if( !nInst++ )
+ {
+ pF3d = new E3dObjFactory;
+ }
+}
+
+ScDrawLayer::~ScDrawLayer()
+{
+ Broadcast(SdrHint(SdrHintKind::ModelCleared));
+
+ ClearModel(true);
+
+ pUndoGroup.reset();
+ if( !--nInst )
+ {
+ delete pF3d;
+ pF3d = nullptr;
+ }
+}
+
+void ScDrawLayer::UseHyphenator()
+{
+ if (!bHyphenatorSet)
+ {
+ css::uno::Reference< css::linguistic2::XHyphenator >
+ xHyphenator = LinguMgr::GetHyphenator();
+
+ GetDrawOutliner().SetHyphenator( xHyphenator );
+ GetHitTestOutliner().SetHyphenator( xHyphenator );
+
+ bHyphenatorSet = true;
+ }
+}
+
+rtl::Reference<SdrPage> ScDrawLayer::AllocPage(bool bMasterPage)
+{
+ return new ScDrawPage(*this, bMasterPage);
+}
+
+bool ScDrawLayer::HasObjects() const
+{
+ bool bFound = false;
+
+ sal_uInt16 nCount = GetPageCount();
+ for (sal_uInt16 i=0; i<nCount && !bFound; i++)
+ if (GetPage(i)->GetObjCount())
+ bFound = true;
+
+ return bFound;
+}
+
+SdrModel* ScDrawLayer::AllocModel() const
+{
+ // Allocated model (for clipboard etc) must not have a pointer
+ // to the original model's document, pass NULL as document:
+
+ return new ScDrawLayer( nullptr, aName );
+}
+
+bool ScDrawLayer::ScAddPage( SCTAB nTab )
+{
+ if (bDrawIsInUndo)
+ return false; // not inserted
+
+ rtl::Reference<ScDrawPage> pPage = static_cast<ScDrawPage*>(AllocPage( false ).get());
+ InsertPage(pPage.get(), static_cast<sal_uInt16>(nTab));
+ if (bRecording)
+ AddCalcUndo(std::make_unique<SdrUndoNewPage>(*pPage));
+
+ ResetTab(nTab, pDoc->GetTableCount()-1);
+ return true; // inserted
+}
+
+void ScDrawLayer::ScRemovePage( SCTAB nTab )
+{
+ if (bDrawIsInUndo)
+ return;
+
+ Broadcast( ScTabDeletedHint( nTab ) );
+ if (bRecording)
+ {
+ SdrPage* pPage = GetPage(static_cast<sal_uInt16>(nTab));
+ AddCalcUndo(std::make_unique<SdrUndoDelPage>(*pPage)); // Undo-Action becomes the page owner
+ RemovePage( static_cast<sal_uInt16>(nTab) ); // just deliver, not deleting
+ }
+ else
+ DeletePage( static_cast<sal_uInt16>(nTab) ); // just get rid of it
+
+ ResetTab(nTab, pDoc->GetTableCount()-1);
+}
+
+void ScDrawLayer::ScRenamePage( SCTAB nTab, const OUString& rNewName )
+{
+ ScDrawPage* pPage = static_cast<ScDrawPage*>( GetPage(static_cast<sal_uInt16>(nTab)) );
+ if (pPage)
+ pPage->SetName(rNewName);
+}
+
+void ScDrawLayer::ScMovePage( sal_uInt16 nOldPos, sal_uInt16 nNewPos )
+{
+ MovePage( nOldPos, nNewPos );
+ sal_uInt16 nMinPos = std::min(nOldPos, nNewPos);
+ ResetTab(nMinPos, pDoc->GetTableCount()-1);
+}
+
+void ScDrawLayer::ScCopyPage( sal_uInt16 nOldPos, sal_uInt16 nNewPos )
+{
+ if (bDrawIsInUndo)
+ return;
+
+ SdrPage* pOldPage = GetPage(nOldPos);
+ SdrPage* pNewPage = GetPage(nNewPos);
+
+ // Copying
+
+ if (pOldPage && pNewPage)
+ {
+ SCTAB nOldTab = static_cast<SCTAB>(nOldPos);
+ SCTAB nNewTab = static_cast<SCTAB>(nNewPos);
+
+ SdrObjListIter aIter( pOldPage, SdrIterMode::Flat );
+ SdrObject* pOldObject = aIter.Next();
+ while (pOldObject)
+ {
+ ScDrawObjData* pOldData = GetObjData(pOldObject);
+ if (pOldData)
+ {
+ pOldData->maStart.SetTab(nOldTab);
+ pOldData->maEnd.SetTab(nOldTab);
+ }
+
+ // Clone to target SdrModel
+ SdrObject* pNewObject(pOldObject->CloneSdrObject(*this));
+ pNewObject->NbcMove(Size(0,0));
+ pNewPage->InsertObject( pNewObject );
+ ScDrawObjData* pNewData = GetObjData(pNewObject);
+ if (pNewData)
+ {
+ pNewData->maStart.SetTab(nNewTab);
+ pNewData->maEnd.SetTab(nNewTab);
+ }
+
+ if (bRecording)
+ AddCalcUndo( std::make_unique<SdrUndoInsertObj>( *pNewObject ) );
+
+ pOldObject = aIter.Next();
+ }
+ }
+
+ ResetTab(static_cast<SCTAB>(nNewPos), pDoc->GetTableCount()-1);
+}
+
+void ScDrawLayer::ResetTab( SCTAB nStart, SCTAB nEnd )
+{
+ SCTAB nPageSize = static_cast<SCTAB>(GetPageCount());
+ if (nPageSize < 0)
+ // No drawing pages exist.
+ return;
+
+ if (nEnd >= nPageSize)
+ // Avoid iterating beyond the last existing page.
+ nEnd = nPageSize - 1;
+
+ for (SCTAB i = nStart; i <= nEnd; ++i)
+ {
+ SdrPage* pPage = GetPage(static_cast<sal_uInt16>(i));
+ if (!pPage)
+ continue;
+
+ SdrObjListIter aIter(pPage, SdrIterMode::Flat);
+ for (SdrObject* pObj = aIter.Next(); pObj; pObj = aIter.Next())
+ {
+ ScDrawObjData* pData = GetObjData(pObj);
+ if (!pData)
+ continue;
+
+ pData->maStart.SetTab(i);
+ pData->maEnd.SetTab(i);
+ }
+ }
+}
+
+static bool IsInBlock( const ScAddress& rPos, SCCOL nCol1,SCROW nRow1, SCCOL nCol2,SCROW nRow2 )
+{
+ return rPos.Col() >= nCol1 && rPos.Col() <= nCol2 &&
+ rPos.Row() >= nRow1 && rPos.Row() <= nRow2;
+}
+
+void ScDrawLayer::MoveCells( SCTAB nTab, SCCOL nCol1,SCROW nRow1, SCCOL nCol2,SCROW nRow2,
+ SCCOL nDx,SCROW nDy, bool bUpdateNoteCaptionPos )
+{
+ SdrPage* pPage = GetPage(static_cast<sal_uInt16>(nTab));
+ OSL_ENSURE(pPage,"Page not found");
+ if (!pPage)
+ return;
+
+ bool bNegativePage = pDoc && pDoc->IsNegativePage( nTab );
+
+ const size_t nCount = pPage->GetObjCount();
+ for ( size_t i = 0; i < nCount; ++i )
+ {
+ SdrObject* pObj = pPage->GetObj( i );
+ ScDrawObjData* pData = GetObjDataTab( pObj, nTab );
+ if( pData )
+ {
+ const ScAddress aOldStt = pData->maStart;
+ const ScAddress aOldEnd = pData->maEnd;
+ bool bChange = false;
+ if ( aOldStt.IsValid() && IsInBlock( aOldStt, nCol1,nRow1, nCol2,nRow2 ) )
+ {
+ pData->maStart.IncCol( nDx );
+ pData->maStart.IncRow( nDy );
+ bChange = true;
+ }
+ if ( aOldEnd.IsValid() && IsInBlock( aOldEnd, nCol1,nRow1, nCol2,nRow2 ) )
+ {
+ pData->maEnd.IncCol( nDx );
+ pData->maEnd.IncRow( nDy );
+ bChange = true;
+ }
+ if (bChange)
+ {
+ if ( dynamic_cast<const SdrRectObj*>( pObj) != nullptr && pData->maStart.IsValid() && pData->maEnd.IsValid() )
+ pData->maStart.PutInOrder( pData->maEnd );
+
+ // Update also an untransformed anchor that's what we stored ( and still do ) to xml
+ ScDrawObjData* pNoRotatedAnchor = GetNonRotatedObjData( pObj );
+ if ( pNoRotatedAnchor )
+ {
+ const ScAddress aOldSttNoRotatedAnchor = pNoRotatedAnchor->maStart;
+ const ScAddress aOldEndNoRotatedAnchor = pNoRotatedAnchor->maEnd;
+ if ( aOldSttNoRotatedAnchor.IsValid() && IsInBlock( aOldSttNoRotatedAnchor, nCol1,nRow1, nCol2,nRow2 ) )
+ {
+ pNoRotatedAnchor->maStart.IncCol(nDx);
+ pNoRotatedAnchor->maStart.IncRow(nDy);
+ }
+ if ( aOldEndNoRotatedAnchor.IsValid() && IsInBlock( aOldEndNoRotatedAnchor, nCol1,nRow1, nCol2,nRow2 ) )
+ {
+ pNoRotatedAnchor->maEnd.IncCol(nDx);
+ pNoRotatedAnchor->maEnd.IncRow(nDy);
+ }
+ }
+
+ AddCalcUndo( std::make_unique<ScUndoObjData>( pObj, aOldStt, aOldEnd, pData->maStart, pData->maEnd ) );
+ RecalcPos( pObj, *pData, bNegativePage, bUpdateNoteCaptionPos );
+ }
+ }
+ }
+}
+
+void ScDrawLayer::SetPageSize(sal_uInt16 nPageNo, const Size& rSize, bool bUpdateNoteCaptionPos,
+ const ScObjectHandling eObjectHandling)
+{
+ SdrPage* pPage = GetPage(nPageNo);
+ if (!pPage)
+ return;
+
+ if ( rSize != pPage->GetSize() )
+ {
+ pPage->SetSize( rSize );
+ Broadcast( ScTabSizeChangedHint( static_cast<SCTAB>(nPageNo) ) ); // SetWorkArea() on the views
+ }
+
+ // Do not call RecalcPos while loading, because row height is not finished, when SetPageSize
+ // is called first time. Instead the objects are initialized from ScXMLImport::endDocument() and
+ // RecalcPos is called from there.
+ if (!pDoc || pDoc->IsImportingXML())
+ return;
+
+ // Implement Detective lines (adjust to new heights / widths)
+ // even if size is still the same
+ // (individual rows/columns can have been changed))
+
+ bool bNegativePage = pDoc && pDoc->IsNegativePage( static_cast<SCTAB>(nPageNo) );
+
+ // Disable mass broadcasts from drawing objects' position changes.
+ bool bWasLocked = isLocked();
+ setLock(true);
+
+ const size_t nCount = pPage->GetObjCount();
+ for ( size_t i = 0; i < nCount; ++i )
+ {
+ SdrObject* pObj = pPage->GetObj( i );
+ ScDrawObjData* pData = GetObjDataTab( pObj, static_cast<SCTAB>(nPageNo) );
+ if( pData ) // cell anchored
+ {
+ if (pData->meType == ScDrawObjData::DrawingObject
+ || pData->meType == ScDrawObjData::ValidationCircle)
+ {
+ switch (eObjectHandling)
+ {
+ case ScObjectHandling::RecalcPosMode:
+ RecalcPos(pObj, *pData, bNegativePage, bUpdateNoteCaptionPos);
+ break;
+ case ScObjectHandling::MoveRTLMode:
+ MoveRTL(pObj);
+ break;
+ case ScObjectHandling::MirrorRTLMode:
+ MirrorRTL(pObj);
+ break;
+ }
+ }
+ else // DetectiveArrow and CellNote
+ RecalcPos(pObj, *pData, bNegativePage, bUpdateNoteCaptionPos);
+ }
+ else // page anchored
+ {
+ switch (eObjectHandling)
+ {
+ case ScObjectHandling::MoveRTLMode:
+ MoveRTL(pObj);
+ break;
+ case ScObjectHandling::MirrorRTLMode:
+ MirrorRTL(pObj);
+ break;
+ case ScObjectHandling::RecalcPosMode: // does not occur for page anchored shapes
+ break;
+ }
+ }
+ }
+
+ setLock(bWasLocked);
+}
+
+namespace
+{
+ //Can't have a zero width dimension
+ tools::Rectangle lcl_makeSafeRectangle(const tools::Rectangle &rNew)
+ {
+ tools::Rectangle aRect = rNew;
+ if (aRect.Bottom() == aRect.Top())
+ aRect.SetBottom( aRect.Top()+1 );
+ if (aRect.Right() == aRect.Left())
+ aRect.SetRight( aRect.Left()+1 );
+ return aRect;
+ }
+
+ Point lcl_calcAvailableDiff(const ScDocument &rDoc, SCCOL nCol, SCROW nRow, SCTAB nTab, const Point &aWantedDiff)
+ {
+ Point aAvailableDiff(aWantedDiff);
+ tools::Long nHeight = o3tl::convert(rDoc.GetRowHeight( nRow, nTab ), o3tl::Length::twip, o3tl::Length::mm100);
+ tools::Long nWidth = o3tl::convert(rDoc.GetColWidth( nCol, nTab ), o3tl::Length::twip, o3tl::Length::mm100);
+ if (aAvailableDiff.Y() > nHeight)
+ aAvailableDiff.setY( nHeight );
+ if (aAvailableDiff.X() > nWidth)
+ aAvailableDiff.setX( nWidth );
+ return aAvailableDiff;
+ }
+
+ tools::Rectangle lcl_UpdateCalcPoly(basegfx::B2DPolygon &rCalcPoly, int nWhichPoint, const Point &rPos)
+ {
+ rCalcPoly.setB2DPoint(nWhichPoint, basegfx::B2DPoint(rPos.X(), rPos.Y()));
+ basegfx::B2DRange aRange(basegfx::utils::getRange(rCalcPoly));
+ return tools::Rectangle(static_cast<tools::Long>(aRange.getMinX()), static_cast<tools::Long>(aRange.getMinY()),
+ static_cast<tools::Long>(aRange.getMaxX()), static_cast<tools::Long>(aRange.getMaxY()));
+ }
+
+bool lcl_AreRectanglesApproxEqual(const tools::Rectangle& rRectA, const tools::Rectangle& rRectB)
+{
+ // Twips <-> Hmm conversions introduce +-1 differences although there are no real changes in the object.
+ // Therefore test with == is not appropriate in some cases.
+ if (std::abs(rRectA.Left() - rRectB.Left()) > 1)
+ return false;
+ if (std::abs(rRectA.Top() - rRectB.Top()) > 1)
+ return false;
+ if (std::abs(rRectA.Right() - rRectB.Right()) > 1)
+ return false;
+ if (std::abs(rRectA.Bottom() - rRectB.Bottom()) > 1)
+ return false;
+ return true;
+}
+
+bool lcl_NeedsMirrorYCorrection(const SdrObject* pObj)
+{
+ return pObj->GetObjIdentifier() == SdrObjKind::CustomShape
+ && static_cast<const SdrObjCustomShape*>(pObj)->IsMirroredY();
+}
+
+void lcl_SetLogicRectFromAnchor(SdrObject* pObj, const ScDrawObjData& rAnchor, const ScDocument* pDoc)
+{
+ // This is only used during initialization. At that time, shape handling is always LTR. No need
+ // to consider negative page.
+ if (!pObj || !pDoc || !rAnchor.maEnd.IsValid() || !rAnchor.maStart.IsValid())
+ return;
+
+ // In case of a vertical mirrored custom shape, LibreOffice uses internally an additional 180deg
+ // in aGeo.nRotationAngle and in turn has a different logic rectangle position. We remove flip,
+ // set the logic rectangle, and apply flip again. You cannot simple use a 180deg-rotated
+ // rectangle, because custom shape mirroring is internally applied after the other
+ // transformations.
+ const bool bNeedsMirrorYCorrection = lcl_NeedsMirrorYCorrection(pObj); // remember state
+ if (bNeedsMirrorYCorrection)
+ {
+ const tools::Rectangle aRect(pObj->GetSnapRect());
+ const Point aLeft(aRect.Left(), (aRect.Top() + aRect.Bottom()) >> 1);
+ const Point aRight(aLeft.X() + 1000, aLeft.Y());
+ pObj->NbcMirror(aLeft, aRight);
+ }
+
+ // Build full sized logic rectangle from start and end given in anchor.
+ const tools::Rectangle aStartCellRect(
+ pDoc->GetMMRect(rAnchor.maStart.Col(), rAnchor.maStart.Row(), rAnchor.maStart.Col(),
+ rAnchor.maStart.Row(), rAnchor.maStart.Tab(), false /*bHiddenAsZero*/));
+ Point aStartPoint(aStartCellRect.Left(), aStartCellRect.Top());
+ aStartPoint.AdjustX(rAnchor.maStartOffset.getX());
+ aStartPoint.AdjustY(rAnchor.maStartOffset.getY());
+
+ const tools::Rectangle aEndCellRect(
+ pDoc->GetMMRect(rAnchor.maEnd.Col(), rAnchor.maEnd.Row(), rAnchor.maEnd.Col(),
+ rAnchor.maEnd.Row(), rAnchor.maEnd.Tab(), false /*bHiddenAsZero*/));
+ Point aEndPoint(aEndCellRect.Left(), aEndCellRect.Top());
+ aEndPoint.AdjustX(rAnchor.maEndOffset.getX());
+ aEndPoint.AdjustY(rAnchor.maEndOffset.getY());
+
+ // Set this as new, full sized logical rectangle
+ tools::Rectangle aNewRectangle(aStartPoint, aEndPoint);
+ aNewRectangle.Justify();
+ if (!lcl_AreRectanglesApproxEqual(pObj->GetLogicRect(), aNewRectangle))
+ pObj->NbcSetLogicRect(lcl_makeSafeRectangle(aNewRectangle));
+
+ // The shape has the correct logical rectangle now. Reapply the above removed mirroring.
+ if (bNeedsMirrorYCorrection)
+ {
+ const tools::Rectangle aRect(pObj->GetSnapRect());
+ const Point aLeft(aRect.Left(), (aRect.Top() + aRect.Bottom()) >> 1);
+ const Point aRight(aLeft.X() + 1000, aLeft.Y());
+ pObj->NbcMirror(aLeft, aRight);
+ }
+}
+
+} // namespace
+
+void ScDrawLayer::ResizeLastRectFromAnchor(const SdrObject* pObj, ScDrawObjData& rData,
+ bool bNegativePage, bool bCanResize)
+{
+ tools::Rectangle aRect = pObj->GetSnapRect();
+ SCCOL nCol1 = rData.maStart.Col();
+ SCROW nRow1 = rData.maStart.Row();
+ SCTAB nTab1 = rData.maStart.Tab();
+ SCCOL nCol2 = rData.maEnd.Col();
+ SCROW nRow2 = rData.maEnd.Row();
+ SCTAB nTab2 = rData.maEnd.Tab();
+ Point aPos(pDoc->GetColOffset(nCol1, nTab1, /*bHiddenAsZero*/true),
+ pDoc->GetRowOffset(nRow1, nTab1, /*bHiddenAsZero*/true));
+ aPos.setX(convertTwipToMm100(aPos.X()));
+ aPos.setY(convertTwipToMm100(aPos.Y()));
+ aPos += lcl_calcAvailableDiff(*pDoc, nCol1, nRow1, nTab1, rData.maStartOffset);
+
+ // this sets the needed changed position (translation)
+ aRect.SetPos(aPos);
+
+ if (bCanResize)
+ {
+ // all this stuff is additional stuff to evtl. not only translate the
+ // range (Rectangle), but also check for and evtl. do corrections for it's size
+ const tools::Rectangle aLastCellRect(rData.getLastCellRect());
+
+ // If the row was hidden before, or we don't have a valid cell rect, calculate the
+ // new rect based on the end point.
+ // Also when the end point is set, we need to consider it.
+ if (rData.mbWasInHiddenRow || aLastCellRect.IsEmpty() || nRow1 != nRow2 || nCol1 != nCol2)
+ {
+ Point aEnd(pDoc->GetColOffset(nCol2, nTab2, /*bHiddenAsZero*/true),
+ pDoc->GetRowOffset(nRow2, nTab2, /*bHiddenAsZero*/true));
+ aEnd.setX(convertTwipToMm100(aEnd.X()));
+ aEnd.setY(convertTwipToMm100(aEnd.Y()));
+ aEnd += lcl_calcAvailableDiff(*pDoc, nCol2, nRow2, nTab2, rData.maEndOffset);
+
+ aRect = tools::Rectangle(aPos, aEnd);
+ }
+ else if (!aLastCellRect.IsEmpty())
+ {
+ // We calculate based on the last cell rect to be able to scale the image
+ // as much as the cell was scaled.
+ // Still, we keep the image in its current cell (to keep start anchor == end anchor)
+ const tools::Rectangle aCurrentCellRect(GetCellRect(*GetDocument(), rData.maStart, true));
+ tools::Long nCurrentWidth(aCurrentCellRect.GetWidth());
+ tools::Long nCurrentHeight(aCurrentCellRect.GetHeight());
+ const tools::Long nLastWidth(aLastCellRect.GetWidth());
+ const tools::Long nLastHeight(aLastCellRect.GetHeight());
+
+ // tdf#116931 Avoid and correct nifty numerical problems with the integer
+ // based and converted values (GetCellRect uses multiplies with HMM_PER_TWIPS)
+ if(nCurrentWidth + 1 == nLastWidth || nCurrentWidth == nLastWidth + 1)
+ {
+ nCurrentWidth = nLastWidth;
+ }
+
+ if(nCurrentHeight + 1 == nLastHeight || nCurrentHeight == nLastHeight + 1)
+ {
+ nCurrentHeight = nLastHeight;
+ }
+
+ // get initial ScalingFactors
+ double fWidthFactor(nCurrentWidth == nLastWidth || 0 == nLastWidth
+ ? 1.0
+ : static_cast<double>(nCurrentWidth) / static_cast<double>(nLastWidth));
+ double fHeightFactor(nCurrentHeight == nLastHeight || 0 == nLastHeight
+ ? 1.0
+ : static_cast<double>(nCurrentHeight) / static_cast<double>(nLastHeight));
+
+ // check if we grow or shrink - and at all
+ const bool bIsGrowing(nCurrentWidth > nLastWidth || nCurrentHeight > nLastHeight);
+ const bool bIsShrinking(nCurrentWidth < nLastWidth || nCurrentHeight < nLastHeight);
+ const bool bIsSizeChanged(bIsGrowing || bIsShrinking);
+
+ // handle AspectRatio, only needed if size does change
+ if(bIsSizeChanged && pObj->shouldKeepAspectRatio())
+ {
+ tools::Rectangle aRectIncludingOffset = aRect;
+ aRectIncludingOffset.setWidth(aRect.GetWidth() + rData.maStartOffset.X());
+ aRectIncludingOffset.setHeight(aRect.GetHeight() + rData.maStartOffset.Y());
+ tools::Long nWidth = aRectIncludingOffset.GetWidth();
+ assert(nWidth && "div-by-zero");
+ double fMaxWidthFactor = static_cast<double>(nCurrentWidth)
+ / static_cast<double>(nWidth);
+ tools::Long nHeight = aRectIncludingOffset.GetHeight();
+ assert(nHeight && "div-by-zero");
+ double fMaxHeightFactor = static_cast<double>(nCurrentHeight)
+ / static_cast<double>(nHeight);
+ double fMaxFactor = std::min(fMaxHeightFactor, fMaxWidthFactor);
+
+ if(bIsGrowing) // cell is growing larger
+ {
+ // To actually grow the image, we need to take the max
+ fWidthFactor = std::max(fWidthFactor, fHeightFactor);
+ }
+ else if(bIsShrinking) // cell is growing smaller, take the min
+ {
+ fWidthFactor = std::min(fWidthFactor, fHeightFactor);
+ }
+
+ // We don't want the image to become larger than the current cell
+ fWidthFactor = fHeightFactor = std::min(fWidthFactor, fMaxFactor);
+ }
+
+ if(bIsSizeChanged)
+ {
+ // tdf#116931 re-organized scaling (if needed)
+ // Check if we need to scale at all. Always scale on growing.
+ bool bNeedToScale(bIsGrowing);
+
+ if(!bNeedToScale && bIsShrinking)
+ {
+ // Check if original still fits into space. Do *not* forget to
+ // compare with evtl. numerically corrected aCurrentCellRect
+ const bool bFitsInX(aRect.Right() <= aCurrentCellRect.Left() + nCurrentWidth);
+ const bool bFitsInY(aRect.Bottom() <= aCurrentCellRect.Top() + nCurrentHeight);
+
+ // If the image still fits in the smaller cell, don't resize it at all
+ bNeedToScale = (!bFitsInX || !bFitsInY);
+ }
+
+ if(bNeedToScale)
+ {
+ // tdf#116931 use transformations now. Translation is already applied
+ // (see aRect.SetPos above), so only scale needs to be applied - relative
+ // to *new* CellRect (which is aCurrentCellRect).
+ // Prepare scale relative to top-left of aCurrentCellRect
+ basegfx::B2DHomMatrix aChange;
+
+ aChange.translate(-aCurrentCellRect.Left(), -aCurrentCellRect.Top());
+ aChange.scale(fWidthFactor, fHeightFactor);
+ aChange.translate(aCurrentCellRect.Left(), aCurrentCellRect.Top());
+
+ // create B2DRange and transform by prepared scale
+ basegfx::B2DRange aNewRange = vcl::unotools::b2DRectangleFromRectangle(aRect);
+
+ aNewRange.transform(aChange);
+
+ // apply to aRect
+ aRect = tools::Rectangle(
+ basegfx::fround(aNewRange.getMinX()), basegfx::fround(aNewRange.getMinY()),
+ basegfx::fround(aNewRange.getMaxX()), basegfx::fround(aNewRange.getMaxY()));
+ }
+ }
+ }
+ }
+
+ if (bNegativePage)
+ MirrorRectRTL(aRect);
+
+ rData.setShapeRect(GetDocument(), lcl_makeSafeRectangle(aRect), pObj->IsVisible());
+}
+
+void ScDrawLayer::InitializeCellAnchoredObj(SdrObject* pObj, ScDrawObjData& rData)
+{
+ // This is called from ScXMLImport::endDocument()
+ if (!pDoc || !pObj)
+ return;
+ if (!rData.getShapeRect().IsEmpty())
+ return; // already initialized, should not happen
+ if (rData.meType == ScDrawObjData::CellNote || rData.meType == ScDrawObjData::ValidationCircle
+ || rData.meType == ScDrawObjData::DetectiveArrow)
+ return; // handled in RecalcPos
+
+ // Prevent multiple broadcasts during the series of changes.
+ bool bWasLocked = pObj->getSdrModelFromSdrObject().isLocked();
+ pObj->getSdrModelFromSdrObject().setLock(true);
+
+ // rNoRotatedAnchor refers in its start and end addresses and its start and end offsets to
+ // the logic rectangle of the object. The values are so, as if no hidden columns and rows
+ // exists and if it is a LTR sheet. These values are directly used for XML in ODF file.
+ ScDrawObjData& rNoRotatedAnchor = *GetNonRotatedObjData(pObj, true /*bCreate*/);
+
+ // From XML import, rData contains temporarily the anchor information as they are given in
+ // XML. Copy it to rNoRotatedAnchor, where it belongs. rData will later contain the anchor
+ // of the transformed object as visible on screen.
+ rNoRotatedAnchor.maStart = rData.maStart;
+ rNoRotatedAnchor.maEnd = rData.maEnd;
+ rNoRotatedAnchor.maStartOffset = rData.maStartOffset;
+ rNoRotatedAnchor.maEndOffset = rData.maEndOffset;
+
+ SCCOL nCol1 = rNoRotatedAnchor.maStart.Col();
+ SCROW nRow1 = rNoRotatedAnchor.maStart.Row();
+ SCTAB nTab1 = rNoRotatedAnchor.maStart.Tab(); // Used as parameter several times
+
+ // Object has coordinates relative to left/top of containing cell in XML. Change object to
+ // absolute coordinates as internally used.
+ const tools::Rectangle aRect(
+ pDoc->GetMMRect(nCol1, nRow1, nCol1, nRow1, nTab1, false /*bHiddenAsZero*/));
+ const Size aShift(aRect.Left(), aRect.Top());
+ pObj->NbcMove(aShift);
+
+ const ScAnchorType aAnchorType = ScDrawLayer::GetAnchorType(*pObj);
+ if (aAnchorType == SCA_CELL_RESIZE)
+ {
+ if (pObj->GetObjIdentifier() == SdrObjKind::Line)
+ {
+ // Horizontal lines might have wrong start and end anchor because of erroneously applied
+ // 180deg rotation (tdf#137446). Other lines have wrong end anchor. Coordinates in
+ // object are correct. Use them for recreating the anchor.
+ const basegfx::B2DPolygon aPoly(
+ static_cast<SdrPathObj*>(pObj)->GetPathPoly().getB2DPolygon(0));
+ const basegfx::B2DPoint aB2DPoint0(aPoly.getB2DPoint(0));
+ const basegfx::B2DPoint aB2DPoint1(aPoly.getB2DPoint(1));
+ const Point aPointLT(FRound(std::min(aB2DPoint0.getX(), aB2DPoint1.getX())),
+ FRound(std::min(aB2DPoint0.getY(), aB2DPoint1.getY())));
+ const Point aPointRB(FRound(std::max(aB2DPoint0.getX(), aB2DPoint1.getX())),
+ FRound(std::max(aB2DPoint0.getY(), aB2DPoint1.getY())));
+ const tools::Rectangle aObjRect(aPointLT, aPointRB);
+ GetCellAnchorFromPosition(aObjRect, rNoRotatedAnchor, *pDoc, nTab1,
+ false /*bHiddenAsZero*/);
+ }
+ else if (pObj->GetObjIdentifier() == SdrObjKind::Measure)
+ {
+ // Measure lines might have got wrong start and end anchor from XML import. Recreate
+ // anchor from start and end point.
+ SdrMeasureObj* pMeasureObj = static_cast<SdrMeasureObj*>(pObj);
+ // tdf#137576. The logic rectangle has likely no current values here, but only the
+ // 1cm x 1cm default size. The call of TakeUnrotatedSnapRect is currently (LO 7.2)
+ // the only way to force a recalc of the logic rectangle.
+ tools::Rectangle aObjRect;
+ pMeasureObj->TakeUnrotatedSnapRect(aObjRect);
+ GetCellAnchorFromPosition(aObjRect, rNoRotatedAnchor, *pDoc, rData.maStart.Tab(),
+ false /*bHiddenAsZero*/);
+ }
+ else
+ {
+ // In case there are hidden rows or cols, versions 7.0 and earlier have written width and
+ // height in file so that hidden row or col are count as zero. XML import bases the
+ // logical rectangle of the object on it. Shapes have at least wrong size, when row or col
+ // are shown. We try to regenerate the logic rectangle as far as possible from the anchor.
+ // ODF specifies anyway, that the size has to be ignored, if end cell attributes exist.
+ lcl_SetLogicRectFromAnchor(pObj, rNoRotatedAnchor, pDoc);
+ }
+ }
+ else // aAnchorType == SCA_CELL, other types will not occur here.
+ {
+ // XML has no end cell address in this case. We generate it from position.
+ UpdateCellAnchorFromPositionEnd(*pObj, rNoRotatedAnchor, *pDoc, nTab1,
+ true /*bUseLogicRect*/);
+ }
+
+ // Make sure maShapeRect of rNoRotatedAnchor is not empty. Method ScDrawView::Notify()
+ // needs it to detect a change in object geometry. For example a 180deg rotation effects only
+ // logic rect.
+ rNoRotatedAnchor.setShapeRect(GetDocument(), pObj->GetLogicRect(), true);
+
+ // Start and end addresses and offsets in rData refer to the actual snap rectangle of the
+ // shape. We initialize them here based on the "full" sized object. Adaptation to reduced size
+ // (by hidden row/col) is done later in RecalcPos.
+ GetCellAnchorFromPosition(pObj->GetSnapRect(), rData, *pDoc, nTab1, false /*bHiddenAsZero*/);
+
+ // As of ODF 1.3 strict there is no attribute to store whether an object is hidden. So a "visible"
+ // object might actually be hidden by being in hidden row or column. We detect it here.
+ // Note, that visibility by hidden row or column refers to the snap rectangle.
+ if (pObj->IsVisible()
+ && (pDoc->RowHidden(rData.maStart.Row(), rData.maStart.Tab())
+ || pDoc->ColHidden(rData.maStart.Col(), rData.maStart.Tab())))
+ pObj->SetVisible(false);
+
+ // Set visibility. ToDo: Really used?
+ rNoRotatedAnchor.setShapeRect(GetDocument(), pObj->GetLogicRect(), pObj->IsVisible());
+
+ // And set maShapeRect in rData. It stores not only the current rectangles, but currently,
+ // existence of maShapeRect is the flag for initialization is done.
+ rData.setShapeRect(GetDocument(), pObj->GetSnapRect(), pObj->IsVisible());
+
+ pObj->getSdrModelFromSdrObject().setLock(bWasLocked);
+}
+
+void ScDrawLayer::RecalcPos( SdrObject* pObj, ScDrawObjData& rData, bool bNegativePage, bool bUpdateNoteCaptionPos )
+{
+ OSL_ENSURE( pDoc, "ScDrawLayer::RecalcPos - missing document" );
+ if( !pDoc )
+ return;
+
+ if (rData.meType == ScDrawObjData::CellNote)
+ {
+ OSL_ENSURE( rData.maStart.IsValid(), "ScDrawLayer::RecalcPos - invalid position for cell note" );
+ /* #i109372# On insert/remove rows/columns/cells: Updating the caption
+ position must not be done, if the cell containing the note has not
+ been moved yet in the document. The calling code now passes an
+ additional boolean stating if the cells are already moved. */
+ /* tdf #152081 Do not change hidden objects. That would produce zero height
+ or width and loss of caption.*/
+ if (bUpdateNoteCaptionPos && pObj->IsVisible())
+ {
+ /* When inside an undo action, there may be pending note captions
+ where cell note is already deleted (thus document cannot find
+ the note object anymore). The caption will be deleted later
+ with drawing undo. */
+ if( ScPostIt* pNote = pDoc->GetNote( rData.maStart ) )
+ pNote->UpdateCaptionPos( rData.maStart );
+ }
+ return;
+ }
+
+ bool bValid1 = rData.maStart.IsValid();
+ SCCOL nCol1 = rData.maStart.Col();
+ SCROW nRow1 = rData.maStart.Row();
+ SCTAB nTab1 = rData.maStart.Tab();
+ bool bValid2 = rData.maEnd.IsValid();
+ SCCOL nCol2 = rData.maEnd.Col();
+ SCROW nRow2 = rData.maEnd.Row();
+ SCTAB nTab2 = rData.maEnd.Tab();
+
+ if (rData.meType == ScDrawObjData::ValidationCircle)
+ {
+ // Validation circle for detective.
+ rData.setShapeRect(GetDocument(), pObj->GetLogicRect());
+
+ // rData.maStart should contain the address of the be validated cell.
+ tools::Rectangle aRect = GetCellRect(*GetDocument(), rData.maStart, true);
+ aRect.AdjustLeft( -250 );
+ aRect.AdjustRight(250 );
+ aRect.AdjustTop( -70 );
+ aRect.AdjustBottom(70 );
+ if ( bNegativePage )
+ MirrorRectRTL( aRect );
+
+ if ( pObj->GetLogicRect() != aRect )
+ {
+ if (bRecording)
+ AddCalcUndo( std::make_unique<SdrUndoGeoObj>( *pObj ) );
+ rData.setShapeRect(GetDocument(), lcl_makeSafeRectangle(aRect));
+ // maStart has the meaning of "to be validated cell" in a validation circle. For usual
+ // drawing objects it has the meaning "left/top of logic/snap rect". Because the rectangle
+ // is expanded above, SetLogicRect() will set maStart to one cell left and one cell above
+ // of the to be validated cell. We need to backup the old value and restore it.
+ ScAddress aBackup(rData.maStart);
+ pObj->SetLogicRect(rData.getShapeRect());
+ rData.maStart = aBackup;
+ }
+ }
+ else if (rData.meType == ScDrawObjData::DetectiveArrow)
+ {
+ rData.setShapeRect(GetDocument(), pObj->GetLogicRect());
+ basegfx::B2DPolygon aCalcPoly;
+ Point aOrigStartPos(pObj->GetPoint(0));
+ Point aOrigEndPos(pObj->GetPoint(1));
+ aCalcPoly.append(basegfx::B2DPoint(aOrigStartPos.X(), aOrigStartPos.Y()));
+ aCalcPoly.append(basegfx::B2DPoint(aOrigEndPos.X(), aOrigEndPos.Y()));
+ //TODO: do not create multiple Undos for one object (last one can be omitted then)
+
+ SCCOL nLastCol;
+ SCROW nLastRow;
+ if( bValid1 )
+ {
+ Point aPos( pDoc->GetColOffset( nCol1, nTab1 ), pDoc->GetRowOffset( nRow1, nTab1 ) );
+ if (!pDoc->ColHidden(nCol1, nTab1, nullptr, &nLastCol))
+ aPos.AdjustX(pDoc->GetColWidth( nCol1, nTab1 ) / 4 );
+ if (!pDoc->RowHidden(nRow1, nTab1, nullptr, &nLastRow))
+ aPos.AdjustY(pDoc->GetRowHeight( nRow1, nTab1 ) / 2 );
+ aPos.setX(convertTwipToMm100(aPos.X()));
+ aPos.setY(convertTwipToMm100(aPos.Y()));
+ Point aStartPos = aPos;
+ if ( bNegativePage )
+ aStartPos.setX( -aStartPos.X() ); // don't modify aPos - used below
+ if ( pObj->GetPoint( 0 ) != aStartPos )
+ {
+ if (bRecording)
+ AddCalcUndo( std::make_unique<SdrUndoGeoObj>( *pObj ) );
+
+ rData.setShapeRect(GetDocument(), lcl_UpdateCalcPoly(aCalcPoly, 0, aStartPos));
+ pObj->SetPoint( aStartPos, 0 );
+ }
+
+ if( !bValid2 )
+ {
+ Point aEndPos( aPos.X() + DET_ARROW_OFFSET, aPos.Y() - DET_ARROW_OFFSET );
+ if (aEndPos.Y() < 0)
+ aEndPos.AdjustY(2 * DET_ARROW_OFFSET);
+ if ( bNegativePage )
+ aEndPos.setX( -aEndPos.X() );
+ if ( pObj->GetPoint( 1 ) != aEndPos )
+ {
+ if (bRecording)
+ AddCalcUndo( std::make_unique<SdrUndoGeoObj>( *pObj ) );
+
+ rData.setShapeRect(GetDocument(), lcl_UpdateCalcPoly(aCalcPoly, 1, aEndPos));
+ pObj->SetPoint( aEndPos, 1 );
+ }
+ }
+ }
+ if( bValid2 )
+ {
+ Point aPos( pDoc->GetColOffset( nCol2, nTab2 ), pDoc->GetRowOffset( nRow2, nTab2 ) );
+ if (!pDoc->ColHidden(nCol2, nTab2, nullptr, &nLastCol))
+ aPos.AdjustX(pDoc->GetColWidth( nCol2, nTab2 ) / 4 );
+ if (!pDoc->RowHidden(nRow2, nTab2, nullptr, &nLastRow))
+ aPos.AdjustY(pDoc->GetRowHeight( nRow2, nTab2 ) / 2 );
+ aPos.setX(convertTwipToMm100(aPos.X()));
+ aPos.setY(convertTwipToMm100(aPos.Y()));
+ Point aEndPos = aPos;
+ if ( bNegativePage )
+ aEndPos.setX( -aEndPos.X() ); // don't modify aPos - used below
+ if ( pObj->GetPoint( 1 ) != aEndPos )
+ {
+ if (bRecording)
+ AddCalcUndo( std::make_unique<SdrUndoGeoObj>( *pObj ) );
+
+ rData.setShapeRect(GetDocument(), lcl_UpdateCalcPoly(aCalcPoly, 1, aEndPos));
+ pObj->SetPoint( aEndPos, 1 );
+ }
+
+ if( !bValid1 )
+ {
+ Point aStartPos( aPos.X() - DET_ARROW_OFFSET, aPos.Y() - DET_ARROW_OFFSET );
+ if (aStartPos.X() < 0)
+ aStartPos.AdjustX(2 * DET_ARROW_OFFSET);
+ if (aStartPos.Y() < 0)
+ aStartPos.AdjustY(2 * DET_ARROW_OFFSET);
+ if ( bNegativePage )
+ aStartPos.setX( -aStartPos.X() );
+ if ( pObj->GetPoint( 0 ) != aStartPos )
+ {
+ if (bRecording)
+ AddCalcUndo( std::make_unique<SdrUndoGeoObj>( *pObj ) );
+
+ rData.setShapeRect(GetDocument(), lcl_UpdateCalcPoly(aCalcPoly, 0, aStartPos));
+ pObj->SetPoint( aStartPos, 0 );
+ }
+ }
+ }
+ } // end ScDrawObjData::DetectiveArrow
+ else // start ScDrawObjData::DrawingObject
+ {
+ // Do not change hidden objects. That would produce zero height or width and loss of offsets.
+ if (!pObj->IsVisible())
+ return;
+
+ // Prevent multiple broadcasts during the series of changes.
+ bool bWasLocked = pObj->getSdrModelFromSdrObject().isLocked();
+ pObj->getSdrModelFromSdrObject().setLock(true);
+
+ bool bCanResize = bValid2 && !pObj->IsResizeProtect() && rData.mbResizeWithCell;
+
+ // update anchor with snap rect
+ ResizeLastRectFromAnchor( pObj, rData, bNegativePage, bCanResize );
+
+ ScDrawObjData& rNoRotatedAnchor = *GetNonRotatedObjData( pObj, true /*bCreate*/);
+
+ if( bCanResize )
+ {
+ tools::Rectangle aNew = rData.getShapeRect();
+ tools::Rectangle aOld(pObj->GetSnapRect());
+ if (!lcl_AreRectanglesApproxEqual(aNew, aOld))
+ {
+ if (bRecording)
+ AddCalcUndo( std::make_unique<SdrUndoGeoObj>( *pObj ) );
+
+ // ToDo: Implement NbcSetSnapRect of SdrMeasureObj. Then this can be removed.
+ tools::Long nOldWidth = aOld.GetWidth();
+ tools::Long nOldHeight = aOld.GetHeight();
+ if (pObj->IsPolyObj() && nOldWidth && nOldHeight)
+ {
+ // Polyline objects need special treatment.
+ Size aSizeMove(aNew.Left()-aOld.Left(), aNew.Top()-aOld.Top());
+ pObj->NbcMove(aSizeMove);
+
+ double fXFrac = static_cast<double>(aNew.GetWidth()) / static_cast<double>(nOldWidth);
+ double fYFrac = static_cast<double>(aNew.GetHeight()) / static_cast<double>(nOldHeight);
+ pObj->NbcResize(aNew.TopLeft(), Fraction(fXFrac), Fraction(fYFrac));
+ }
+
+ rData.setShapeRect(GetDocument(), lcl_makeSafeRectangle(rData.getShapeRect()), pObj->IsVisible());
+ if (pObj->GetObjIdentifier() == SdrObjKind::CustomShape)
+ pObj->AdjustToMaxRect(rData.getShapeRect());
+ else
+ pObj->SetSnapRect(rData.getShapeRect());
+
+ // The shape rectangle in the 'unrotated' anchor needs to be updated to the changed
+ // object geometry. It is used in adjustAnchoredPosition() in ScDrawView::Notify().
+ rNoRotatedAnchor.setShapeRect(pDoc, pObj->GetLogicRect(), pObj->IsVisible());
+ }
+ }
+ else
+ {
+ const Point aPos(rData.getShapeRect().TopLeft());
+ if ( pObj->GetRelativePos() != aPos )
+ {
+ if (bRecording)
+ AddCalcUndo( std::make_unique<SdrUndoGeoObj>( *pObj ) );
+ pObj->SetRelativePos( aPos );
+ rNoRotatedAnchor.setShapeRect(pDoc, pObj->GetLogicRect(), pObj->IsVisible());
+ }
+ }
+ /*
+ * If we were not allowed resize the object, then the end cell anchor
+ * is possibly incorrect now, and if the object has no end-cell (e.g.
+ * missing in original .xml) we are also forced to generate one
+ */
+ bool bEndAnchorIsBad = !bValid2 || pObj->IsResizeProtect();
+ if (bEndAnchorIsBad)
+ {
+ // update 'rotated' anchor
+ ScDrawLayer::UpdateCellAnchorFromPositionEnd(*pObj, rData, *pDoc, nTab1, false);
+ // update 'unrotated' anchor
+ ScDrawLayer::UpdateCellAnchorFromPositionEnd(*pObj, rNoRotatedAnchor, *pDoc, nTab1 );
+ }
+
+ // End prevent multiple broadcasts during the series of changes.
+ pObj->getSdrModelFromSdrObject().setLock(bWasLocked);
+ if (!bWasLocked)
+ pObj->BroadcastObjectChange();
+ } // end ScDrawObjData::DrawingObject
+}
+
+bool ScDrawLayer::GetPrintArea( ScRange& rRange, bool bSetHor, bool bSetVer ) const
+{
+ OSL_ENSURE( pDoc, "ScDrawLayer::GetPrintArea without document" );
+ if ( !pDoc )
+ return false;
+
+ SCTAB nTab = rRange.aStart.Tab();
+ OSL_ENSURE( rRange.aEnd.Tab() == nTab, "GetPrintArea: Tab differ" );
+
+ bool bNegativePage = pDoc->IsNegativePage( nTab );
+
+ bool bAny = false;
+ tools::Long nEndX = 0;
+ tools::Long nEndY = 0;
+ tools::Long nStartX = LONG_MAX;
+ tools::Long nStartY = LONG_MAX;
+
+ // Calculate borders
+
+ if (!bSetHor)
+ {
+ nStartX = 0;
+ SCCOL nStartCol = rRange.aStart.Col();
+ SCCOL i;
+ for (i=0; i<nStartCol; i++)
+ nStartX +=pDoc->GetColWidth(i,nTab);
+ nEndX = nStartX;
+ SCCOL nEndCol = rRange.aEnd.Col();
+ for (i=nStartCol; i<=nEndCol; i++)
+ nEndX += pDoc->GetColWidth(i,nTab);
+ nStartX = convertTwipToMm100(nStartX);
+ nEndX = convertTwipToMm100(nEndX);
+ }
+ if (!bSetVer)
+ {
+ nStartY = pDoc->GetRowHeight( 0, rRange.aStart.Row()-1, nTab);
+ nEndY = nStartY + pDoc->GetRowHeight( rRange.aStart.Row(),
+ rRange.aEnd.Row(), nTab);
+ nStartY = convertTwipToMm100(nStartY);
+ nEndY = convertTwipToMm100(nEndY);
+ }
+
+ if ( bNegativePage )
+ {
+ nStartX = -nStartX; // positions are negative, swap start/end so the same comparisons work
+ nEndX = -nEndX;
+ ::std::swap( nStartX, nEndX );
+ }
+
+ const SdrPage* pPage = GetPage(static_cast<sal_uInt16>(nTab));
+ OSL_ENSURE(pPage,"Page not found");
+ if (pPage)
+ {
+ SdrObjListIter aIter( pPage, SdrIterMode::Flat );
+ SdrObject* pObject = aIter.Next();
+ while (pObject)
+ {
+ //TODO: test Flags (hidden?)
+
+ tools::Rectangle aObjRect = pObject->GetCurrentBoundRect();
+ bool bFit = true;
+ if ( !bSetHor && ( aObjRect.Right() < nStartX || aObjRect.Left() > nEndX ) )
+ bFit = false;
+ if ( !bSetVer && ( aObjRect.Bottom() < nStartY || aObjRect.Top() > nEndY ) )
+ bFit = false;
+ // #i104716# don't include hidden note objects
+ if ( bFit && pObject->GetLayer() != SC_LAYER_HIDDEN )
+ {
+ if (bSetHor)
+ {
+ if (aObjRect.Left() < nStartX) nStartX = aObjRect.Left();
+ if (aObjRect.Right() > nEndX) nEndX = aObjRect.Right();
+ }
+ if (bSetVer)
+ {
+ if (aObjRect.Top() < nStartY) nStartY = aObjRect.Top();
+ if (aObjRect.Bottom() > nEndY) nEndY = aObjRect.Bottom();
+ }
+ bAny = true;
+ }
+
+ pObject = aIter.Next();
+ }
+ }
+
+ if ( bNegativePage )
+ {
+ nStartX = -nStartX; // reverse transformation, so the same cell address calculation works
+ nEndX = -nEndX;
+ ::std::swap( nStartX, nEndX );
+ }
+
+ if (bAny)
+ {
+ OSL_ENSURE( nStartX<=nEndX && nStartY<=nEndY, "Start/End wrong in ScDrawLayer::GetPrintArea" );
+
+ if (bSetHor)
+ {
+ nStartX = o3tl::toTwips(nStartX, o3tl::Length::mm100);
+ nEndX = o3tl::toTwips(nEndX, o3tl::Length::mm100);
+ tools::Long nWidth;
+
+ nWidth = 0;
+ rRange.aStart.SetCol( 0 );
+ if (nWidth <= nStartX)
+ {
+ for (SCCOL nCol : pDoc->GetColumnsRange(nTab, 0, pDoc->MaxCol()))
+ {
+ nWidth += pDoc->GetColWidth(nCol,nTab);
+ if (nWidth > nStartX)
+ {
+ rRange.aStart.SetCol( nCol );
+ break;
+ }
+ }
+ }
+
+ nWidth = 0;
+ rRange.aEnd.SetCol( 0 );
+ if (nWidth <= nEndX)
+ {
+ for (SCCOL nCol : pDoc->GetColumnsRange(nTab, 0, pDoc->MaxCol())) //TODO: start at Start
+ {
+ nWidth += pDoc->GetColWidth(nCol,nTab);
+ if (nWidth > nEndX)
+ {
+ rRange.aEnd.SetCol( nCol );
+ break;
+ }
+ }
+ }
+ }
+
+ if (bSetVer)
+ {
+ nStartY = o3tl::toTwips(nStartY, o3tl::Length::mm100);
+ nEndY = o3tl::toTwips(nEndY, o3tl::Length::mm100);
+ SCROW nRow = pDoc->GetRowForHeight( nTab, nStartY);
+ rRange.aStart.SetRow( nRow>0 ? (nRow-1) : 0);
+ nRow = pDoc->GetRowForHeight( nTab, nEndY);
+ rRange.aEnd.SetRow( nRow == pDoc->MaxRow() ? pDoc->MaxRow() :
+ (nRow>0 ? (nRow-1) : 0));
+ }
+ }
+ else
+ {
+ if (bSetHor)
+ {
+ rRange.aStart.SetCol(0);
+ rRange.aEnd.SetCol(0);
+ }
+ if (bSetVer)
+ {
+ rRange.aStart.SetRow(0);
+ rRange.aEnd.SetRow(0);
+ }
+ }
+ return bAny;
+}
+
+void ScDrawLayer::AddCalcUndo( std::unique_ptr<SdrUndoAction> pUndo )
+{
+ if (bRecording)
+ {
+ if (!pUndoGroup)
+ pUndoGroup.reset(new SdrUndoGroup(*this));
+
+ pUndoGroup->AddAction( std::move(pUndo) );
+ }
+}
+
+void ScDrawLayer::BeginCalcUndo(bool bDisableTextEditUsesCommonUndoManager)
+{
+ SetDisableTextEditUsesCommonUndoManager(bDisableTextEditUsesCommonUndoManager);
+ pUndoGroup.reset();
+ bRecording = true;
+}
+
+std::unique_ptr<SdrUndoGroup> ScDrawLayer::GetCalcUndo()
+{
+ std::unique_ptr<SdrUndoGroup> pRet = std::move(pUndoGroup);
+ bRecording = false;
+ SetDisableTextEditUsesCommonUndoManager(false);
+ return pRet;
+}
+
+void ScDrawLayer::MoveArea( SCTAB nTab, SCCOL nCol1,SCROW nRow1, SCCOL nCol2,SCROW nRow2,
+ SCCOL nDx,SCROW nDy, bool bInsDel, bool bUpdateNoteCaptionPos )
+{
+ OSL_ENSURE( pDoc, "ScDrawLayer::MoveArea without document" );
+ if ( !pDoc )
+ return;
+
+ if (!bAdjustEnabled)
+ return;
+
+ bool bNegativePage = pDoc->IsNegativePage( nTab );
+
+ tools::Rectangle aRect = pDoc->GetMMRect( nCol1, nRow1, nCol2, nRow2, nTab );
+ lcl_ReverseTwipsToMM( aRect );
+ //TODO: use twips directly?
+
+ Point aMove;
+
+ if (nDx > 0)
+ for (SCCOL s=0; s<nDx; s++)
+ aMove.AdjustX(pDoc->GetColWidth(s+nCol1,nTab) );
+ else
+ for (SCCOL s=-1; s>=nDx; s--)
+ aMove.AdjustX( -(pDoc->GetColWidth(s+nCol1,nTab)) );
+ if (nDy > 0)
+ aMove.AdjustY(pDoc->GetRowHeight( nRow1, nRow1+nDy-1, nTab) );
+ else
+ aMove.AdjustY( -sal_Int16(pDoc->GetRowHeight( nRow1+nDy, nRow1-1, nTab)) );
+
+ if ( bNegativePage )
+ aMove.setX( -aMove.X() );
+
+ Point aTopLeft = aRect.TopLeft(); // Beginning when zoomed out
+ if (bInsDel)
+ {
+ if ( aMove.X() != 0 && nDx < 0 ) // nDx counts cells, sign is independent of RTL
+ aTopLeft.AdjustX(aMove.X() );
+ if ( aMove.Y() < 0 )
+ aTopLeft.AdjustY(aMove.Y() );
+ }
+
+ // Detectiv arrows: Adjust cell position
+
+ MoveCells( nTab, nCol1,nRow1, nCol2,nRow2, nDx,nDy, bUpdateNoteCaptionPos );
+}
+
+bool ScDrawLayer::HasObjectsInRows( SCTAB nTab, SCROW nStartRow, SCROW nEndRow )
+{
+ OSL_ENSURE( pDoc, "ScDrawLayer::HasObjectsInRows without document" );
+ if ( !pDoc )
+ return false;
+
+ SdrPage* pPage = GetPage(static_cast<sal_uInt16>(nTab));
+ OSL_ENSURE(pPage,"Page not found");
+ if (!pPage)
+ return false;
+
+ // for an empty page, there's no need to calculate the row heights
+ if (!pPage->GetObjCount())
+ return false;
+
+ tools::Rectangle aTestRect;
+
+ aTestRect.AdjustTop(pDoc->GetRowHeight( 0, nStartRow-1, nTab) );
+
+ if (nEndRow==pDoc->MaxRow())
+ aTestRect.SetBottom( MAXMM );
+ else
+ {
+ aTestRect.SetBottom( aTestRect.Top() );
+ aTestRect.AdjustBottom(pDoc->GetRowHeight( nStartRow, nEndRow, nTab) );
+ aTestRect.SetBottom(convertTwipToMm100(aTestRect.Bottom()));
+ }
+
+ aTestRect.SetTop(convertTwipToMm100(aTestRect.Top()));
+
+ aTestRect.SetLeft( 0 );
+ aTestRect.SetRight( MAXMM );
+
+ bool bNegativePage = pDoc->IsNegativePage( nTab );
+ if ( bNegativePage )
+ MirrorRectRTL( aTestRect );
+
+ bool bFound = false;
+
+ tools::Rectangle aObjRect;
+ SdrObjListIter aIter( pPage );
+ SdrObject* pObject = aIter.Next();
+ while ( pObject && !bFound )
+ {
+ aObjRect = pObject->GetSnapRect(); //TODO: GetLogicRect ?
+ if (aTestRect.Contains(aObjRect.TopLeft()) || aTestRect.Contains(aObjRect.BottomLeft()))
+ bFound = true;
+
+ pObject = aIter.Next();
+ }
+
+ return bFound;
+}
+
+void ScDrawLayer::DeleteObjectsInArea( SCTAB nTab, SCCOL nCol1,SCROW nRow1,
+ SCCOL nCol2,SCROW nRow2, bool bAnchored )
+{
+ OSL_ENSURE( pDoc, "ScDrawLayer::DeleteObjectsInArea without document" );
+ if ( !pDoc )
+ return;
+
+ SdrPage* pPage = GetPage(static_cast<sal_uInt16>(nTab));
+ OSL_ENSURE(pPage,"Page ?");
+ if (!pPage)
+ return;
+
+ pPage->RecalcObjOrdNums();
+
+ const size_t nObjCount = pPage->GetObjCount();
+ if (!nObjCount)
+ return;
+
+ tools::Rectangle aDelRect = pDoc->GetMMRect( nCol1, nRow1, nCol2, nRow2, nTab );
+ tools::Rectangle aDelCircle = aDelRect;
+ aDelCircle.AdjustLeft(-250);
+ aDelCircle.AdjustRight(250);
+ aDelCircle.AdjustTop(-70);
+ aDelCircle.AdjustBottom(70);
+
+ std::vector<SdrObject*> ppObj;
+ ppObj.reserve(nObjCount);
+
+ SdrObjListIter aIter( pPage, SdrIterMode::Flat );
+ SdrObject* pObject = aIter.Next();
+ while (pObject)
+ {
+ // do not delete note caption, they are always handled by the cell note
+ // TODO: detective objects are still deleted, is this desired?
+ if (!IsNoteCaption( pObject ))
+ {
+ tools::Rectangle aObjRect;
+ ScDrawObjData* pObjData = ScDrawLayer::GetObjData(pObject);
+ if (pObjData && pObjData->meType == ScDrawObjData::ValidationCircle)
+ {
+ aObjRect = pObject->GetLogicRect();
+ if(aDelCircle.Contains(aObjRect))
+ ppObj.push_back(pObject);
+ }
+ else
+ {
+ aObjRect = pObject->GetCurrentBoundRect();
+ if (aDelRect.Contains(aObjRect))
+ {
+ if (bAnchored)
+ {
+ ScAnchorType aAnchorType = ScDrawLayer::GetAnchorType(*pObject);
+ if (aAnchorType == SCA_CELL || aAnchorType == SCA_CELL_RESIZE)
+ ppObj.push_back(pObject);
+ }
+ else
+ ppObj.push_back(pObject);
+ }
+ }
+ }
+
+ pObject = aIter.Next();
+ }
+
+ if (bRecording)
+ for (auto p : ppObj)
+ AddCalcUndo(std::make_unique<SdrUndoDelObj>(*p));
+
+ for (auto p : ppObj)
+ pPage->RemoveObject(p->GetOrdNum());
+}
+
+void ScDrawLayer::DeleteObjectsInSelection( const ScMarkData& rMark )
+{
+ OSL_ENSURE( pDoc, "ScDrawLayer::DeleteObjectsInSelection without document" );
+ if ( !pDoc )
+ return;
+
+ if ( !rMark.IsMultiMarked() )
+ return;
+
+ const ScRange& aMarkRange = rMark.GetMultiMarkArea();
+
+ SCTAB nTabCount = pDoc->GetTableCount();
+ for (const SCTAB nTab : rMark)
+ {
+ if (nTab >= nTabCount)
+ break;
+
+ SdrPage* pPage = GetPage(static_cast<sal_uInt16>(nTab));
+ if (pPage)
+ {
+ pPage->RecalcObjOrdNums();
+ const size_t nObjCount = pPage->GetObjCount();
+ if (nObjCount)
+ {
+ // Rectangle around the whole selection
+ tools::Rectangle aMarkBound = pDoc->GetMMRect(
+ aMarkRange.aStart.Col(), aMarkRange.aStart.Row(),
+ aMarkRange.aEnd.Col(), aMarkRange.aEnd.Row(), nTab );
+
+ std::vector<SdrObject*> ppObj;
+ ppObj.reserve(nObjCount);
+
+ SdrObjListIter aIter( pPage, SdrIterMode::Flat );
+ SdrObject* pObject = aIter.Next();
+ while (pObject)
+ {
+ // do not delete note caption, they are always handled by the cell note
+ // TODO: detective objects are still deleted, is this desired?
+ if (!IsNoteCaption( pObject ))
+ {
+ tools::Rectangle aObjRect = pObject->GetCurrentBoundRect();
+ ScRange aRange = pDoc->GetRange(nTab, aObjRect);
+ bool bObjectInMarkArea =
+ aMarkBound.Contains(aObjRect) && rMark.IsAllMarked(aRange);
+ const ScDrawObjData* pObjData = ScDrawLayer::GetObjData(pObject);
+ ScAnchorType aAnchorType = ScDrawLayer::GetAnchorType(*pObject);
+ bool bObjectAnchoredToMarkedCell
+ = ((aAnchorType == SCA_CELL || aAnchorType == SCA_CELL_RESIZE)
+ && pObjData && pObjData->maStart.IsValid()
+ && rMark.IsCellMarked(pObjData->maStart.Col(),
+ pObjData->maStart.Row()));
+ if (bObjectInMarkArea || bObjectAnchoredToMarkedCell)
+ {
+ ppObj.push_back(pObject);
+ }
+ }
+
+ pObject = aIter.Next();
+ }
+
+ // Delete objects (backwards)
+
+ if (bRecording)
+ for (auto p : ppObj)
+ AddCalcUndo(std::make_unique<SdrUndoDelObj>(*p));
+
+ for (auto p : ppObj)
+ pPage->RemoveObject(p->GetOrdNum());
+ }
+ }
+ else
+ {
+ OSL_FAIL("pPage?");
+ }
+ }
+}
+
+void ScDrawLayer::CopyToClip( ScDocument* pClipDoc, SCTAB nTab, const tools::Rectangle& rRange )
+{
+ // copy everything in the specified range into the same page (sheet) in the clipboard doc
+
+ SdrPage* pSrcPage = GetPage(static_cast<sal_uInt16>(nTab));
+ if (!pSrcPage)
+ return;
+
+ ScDrawLayer* pDestModel = nullptr;
+ SdrPage* pDestPage = nullptr;
+
+ SdrObjListIter aIter( pSrcPage, SdrIterMode::Flat );
+ SdrObject* pOldObject = aIter.Next();
+ while (pOldObject)
+ {
+ tools::Rectangle aObjRect = pOldObject->GetCurrentBoundRect();
+
+ bool bObjectInArea = rRange.Contains(aObjRect);
+ const ScDrawObjData* pObjData = ScDrawLayer::GetObjData(pOldObject);
+ if (pObjData)
+ {
+ ScRange aClipRange = lcl_getClipRangeFromClipDoc(pClipDoc, nTab);
+ bObjectInArea = bObjectInArea || aClipRange.Contains(pObjData->maStart);
+ }
+
+ // do not copy internal objects (detective) and note captions
+ if (bObjectInArea && pOldObject->GetLayer() != SC_LAYER_INTERN
+ && !IsNoteCaption(pOldObject))
+ {
+ if ( !pDestModel )
+ {
+ pDestModel = pClipDoc->GetDrawLayer(); // does the document already have a drawing layer?
+ if ( !pDestModel )
+ {
+ // allocate drawing layer in clipboard document only if there are objects to copy
+
+ pClipDoc->InitDrawLayer(); //TODO: create contiguous pages
+ pDestModel = pClipDoc->GetDrawLayer();
+ }
+ if (pDestModel)
+ pDestPage = pDestModel->GetPage( static_cast<sal_uInt16>(nTab) );
+ }
+
+ OSL_ENSURE( pDestPage, "no page" );
+ if (pDestPage)
+ {
+ // Clone to target SdrModel
+ SdrObject* pNewObject(pOldObject->CloneSdrObject(*pDestModel));
+
+ uno::Reference< chart2::XChartDocument > xOldChart( ScChartHelper::GetChartFromSdrObject( pOldObject ) );
+ if(!xOldChart.is())//#i110034# do not move charts as they lose all their data references otherwise
+ pNewObject->NbcMove(Size(0,0));
+ pDestPage->InsertObject( pNewObject );
+
+ // no undo needed in clipboard document
+ // charts are not updated
+ }
+ }
+
+ pOldObject = aIter.Next();
+ }
+}
+
+static bool lcl_IsAllInRange( const ::std::vector< ScRangeList >& rRangesVector, const ScRange& rClipRange )
+{
+ // check if every range of rRangesVector is completely in rClipRange
+
+ for( const ScRangeList& rRanges : rRangesVector )
+ {
+ for ( size_t i = 0, nCount = rRanges.size(); i < nCount; i++ )
+ {
+ const ScRange & rRange = rRanges[ i ];
+ if ( !rClipRange.Contains( rRange ) )
+ {
+ return false; // at least one range is not valid
+ }
+ }
+ }
+
+ return true; // everything is fine
+}
+
+static bool lcl_MoveRanges( ::std::vector< ScRangeList >& rRangesVector, const ScRange& rSourceRange,
+ const ScAddress& rDestPos, const ScDocument& rDoc )
+{
+ bool bChanged = false;
+
+ ScRange aErrorRange( ScAddress::UNINITIALIZED );
+ for( ScRangeList& rRanges : rRangesVector )
+ {
+ for ( size_t i = 0, nCount = rRanges.size(); i < nCount; i++ )
+ {
+ ScRange & rRange = rRanges[ i ];
+ if ( rSourceRange.Contains( rRange ) )
+ {
+ SCCOL nDiffX = rDestPos.Col() - rSourceRange.aStart.Col();
+ SCROW nDiffY = rDestPos.Row() - rSourceRange.aStart.Row();
+ SCTAB nDiffZ = rDestPos.Tab() - rSourceRange.aStart.Tab();
+ if (!rRange.Move( nDiffX, nDiffY, nDiffZ, aErrorRange, rDoc ))
+ {
+ assert(!"can't move range");
+ }
+ bChanged = true;
+ }
+ }
+ }
+
+ return bChanged;
+}
+
+void ScDrawLayer::CopyFromClip( ScDrawLayer* pClipModel, SCTAB nSourceTab, const tools::Rectangle& rSourceRange,
+ const ScAddress& rDestPos, const tools::Rectangle& rDestRange )
+{
+ OSL_ENSURE( pDoc, "ScDrawLayer::CopyFromClip without document" );
+ if ( !pDoc )
+ return;
+
+ if (!pClipModel)
+ return;
+
+ if (bDrawIsInUndo) //TODO: can this happen?
+ {
+ OSL_FAIL("CopyFromClip, bDrawIsInUndo");
+ return;
+ }
+
+ bool bMirrorObj = ( rSourceRange.Left() < 0 && rSourceRange.Right() < 0 &&
+ rDestRange.Left() > 0 && rDestRange.Right() > 0 ) ||
+ ( rSourceRange.Left() > 0 && rSourceRange.Right() > 0 &&
+ rDestRange.Left() < 0 && rDestRange.Right() < 0 );
+ tools::Rectangle aMirroredSource = rSourceRange;
+ if ( bMirrorObj )
+ MirrorRectRTL( aMirroredSource );
+
+ SCTAB nDestTab = rDestPos.Tab();
+
+ SdrPage* pSrcPage = pClipModel->GetPage(static_cast<sal_uInt16>(nSourceTab));
+ SdrPage* pDestPage = GetPage(static_cast<sal_uInt16>(nDestTab));
+ OSL_ENSURE( pSrcPage && pDestPage, "draw page missing" );
+ if ( !pSrcPage || !pDestPage )
+ return;
+
+ SdrObjListIter aIter( pSrcPage, SdrIterMode::Flat );
+ SdrObject* pOldObject = aIter.Next();
+
+ ScDocument* pClipDoc = pClipModel->GetDocument();
+ // a clipboard document and its source share the same document item pool,
+ // so the pointers can be compared to see if this is copy&paste within
+ // the same document
+ bool bSameDoc = pDoc && pClipDoc && pDoc->GetPool() == pClipDoc->GetPool();
+ bool bDestClip = pDoc && pDoc->IsClipboard();
+
+ //#i110034# charts need correct sheet names for xml range conversion during load
+ //so the target sheet name is temporarily renamed (if we have any SdrObjects)
+ OUString aDestTabName;
+ bool bRestoreDestTabName = false;
+ if( pOldObject && !bSameDoc && !bDestClip )
+ {
+ if( pDoc && pClipDoc )
+ {
+ OUString aSourceTabName;
+ if( pClipDoc->GetName( nSourceTab, aSourceTabName )
+ && pDoc->GetName( nDestTab, aDestTabName ) )
+ {
+ if( aSourceTabName != aDestTabName &&
+ pDoc->ValidNewTabName(aSourceTabName) )
+ {
+ bRestoreDestTabName = pDoc->RenameTab( nDestTab, aSourceTabName );
+ }
+ }
+ }
+ }
+
+ // first mirror, then move
+ Size aMove( rDestRange.Left() - aMirroredSource.Left(), rDestRange.Top() - aMirroredSource.Top() );
+
+ tools::Long nDestWidth = rDestRange.GetWidth();
+ tools::Long nDestHeight = rDestRange.GetHeight();
+ tools::Long nSourceWidth = rSourceRange.GetWidth();
+ tools::Long nSourceHeight = rSourceRange.GetHeight();
+
+ tools::Long nWidthDiff = nDestWidth - nSourceWidth;
+ tools::Long nHeightDiff = nDestHeight - nSourceHeight;
+
+ Fraction aHorFract(1,1);
+ Fraction aVerFract(1,1);
+ bool bResize = false;
+ // sizes can differ by 1 from twips->1/100mm conversion for equal cell sizes,
+ // don't resize to empty size when pasting into hidden columns or rows
+ if ( std::abs(nWidthDiff) > 1 && nDestWidth > 1 && nSourceWidth > 1 )
+ {
+ aHorFract = Fraction( nDestWidth, nSourceWidth );
+ bResize = true;
+ }
+ if ( std::abs(nHeightDiff) > 1 && nDestHeight > 1 && nSourceHeight > 1 )
+ {
+ aVerFract = Fraction( nDestHeight, nSourceHeight );
+ bResize = true;
+ }
+ Point aRefPos = rDestRange.TopLeft(); // for resizing (after moving)
+
+ while (pOldObject)
+ {
+ tools::Rectangle aObjRect = pOldObject->GetCurrentBoundRect();
+ // do not copy internal objects (detective) and note captions
+
+ SCTAB nClipTab = bRestoreDestTabName ? nDestTab : nSourceTab;
+ ScRange aClipRange = lcl_getClipRangeFromClipDoc(pClipDoc, nClipTab);
+
+ bool bObjectInArea = rSourceRange.Contains(aObjRect);
+ const ScDrawObjData* pObjData = ScDrawLayer::GetObjData(pOldObject);
+ if (pObjData) // Consider images anchored to the copied cell
+ bObjectInArea = bObjectInArea || aClipRange.Contains(pObjData->maStart);
+ if (bObjectInArea && (pOldObject->GetLayer() != SC_LAYER_INTERN)
+ && !IsNoteCaption(pOldObject))
+ {
+ // Clone to target SdrModel
+ SdrObject* pNewObject(pOldObject->CloneSdrObject(*this));
+
+ if ( bMirrorObj )
+ MirrorRTL( pNewObject ); // first mirror, then move
+
+ pNewObject->NbcMove( aMove );
+ if ( bResize )
+ pNewObject->NbcResize( aRefPos, aHorFract, aVerFract );
+
+ pDestPage->InsertObject( pNewObject );
+ if (bRecording)
+ AddCalcUndo( std::make_unique<SdrUndoInsertObj>( *pNewObject ) );
+
+ //#i110034# handle chart data references (after InsertObject)
+
+ if ( pNewObject->GetObjIdentifier() == SdrObjKind::OLE2 )
+ {
+ uno::Reference< embed::XEmbeddedObject > xIPObj = static_cast<SdrOle2Obj*>(pNewObject)->GetObjRef();
+ uno::Reference< embed::XClassifiedObject > xClassified = xIPObj;
+ SvGlobalName aObjectClassName;
+ if ( xClassified.is() )
+ {
+ try {
+ aObjectClassName = SvGlobalName( xClassified->getClassID() );
+ } catch( uno::Exception& )
+ {
+ // TODO: handle error?
+ }
+ }
+
+ if ( xIPObj.is() && SotExchange::IsChart( aObjectClassName ) )
+ {
+ uno::Reference< chart2::XChartDocument > xNewChart( ScChartHelper::GetChartFromSdrObject( pNewObject ) );
+ if( xNewChart.is() && !xNewChart->hasInternalDataProvider() )
+ {
+ OUString aChartName = static_cast<SdrOle2Obj*>(pNewObject)->GetPersistName();
+ ::std::vector< ScRangeList > aRangesVector;
+ pDoc->GetChartRanges( aChartName, aRangesVector, *pDoc );
+ if( !aRangesVector.empty() )
+ {
+ bool bInSourceRange = false;
+ if ( pClipDoc )
+ {
+ bInSourceRange = lcl_IsAllInRange( aRangesVector, aClipRange );
+ }
+
+ // always lose references when pasting into a clipboard document (transpose)
+ if ( ( bInSourceRange || bSameDoc ) && !bDestClip )
+ {
+ if ( bInSourceRange )
+ {
+ if ( rDestPos != aClipRange.aStart )
+ {
+ // update the data ranges to the new (copied) position
+ if ( lcl_MoveRanges( aRangesVector, aClipRange, rDestPos, *pDoc ) )
+ pDoc->SetChartRanges( aChartName, aRangesVector );
+ }
+ }
+ else
+ {
+ // leave the ranges unchanged
+ }
+ }
+ else
+ {
+ // pasting into a new document without the complete source data
+ // -> break connection to source data and switch to own data
+
+ uno::Reference< chart::XChartDocument > xOldChartDoc( ScChartHelper::GetChartFromSdrObject( pOldObject ), uno::UNO_QUERY );
+ uno::Reference< chart::XChartDocument > xNewChartDoc( xNewChart, uno::UNO_QUERY );
+ if( xOldChartDoc.is() && xNewChartDoc.is() )
+ xNewChartDoc->attachData( xOldChartDoc->getData() );
+
+ // (see ScDocument::UpdateChartListenerCollection, PastingDrawFromOtherDoc)
+ }
+ }
+ }
+ }
+ }
+ }
+
+ pOldObject = aIter.Next();
+ }
+
+ if( bRestoreDestTabName )
+ pDoc->RenameTab( nDestTab, aDestTabName );
+}
+
+void ScDrawLayer::MirrorRTL( SdrObject* pObj )
+{
+ OSL_ENSURE( pDoc, "ScDrawLayer::MirrorRTL - missing document" );
+ if( !pDoc )
+ return;
+
+ SdrObjKind nIdent = pObj->GetObjIdentifier();
+
+ // don't mirror OLE or graphics, otherwise ask the object
+ // if it can be mirrored
+ bool bCanMirror = ( nIdent != SdrObjKind::Graphic && nIdent != SdrObjKind::OLE2 );
+ if (bCanMirror)
+ {
+ SdrObjTransformInfoRec aInfo;
+ pObj->TakeObjInfo( aInfo );
+ bCanMirror = aInfo.bMirror90Allowed;
+ }
+
+ if (bCanMirror)
+ {
+ ScDrawObjData* pData = GetObjData(pObj);
+ if (pData) // cell anchored
+ {
+ // Remember values from positive side.
+ const tools::Rectangle aOldSnapRect = pObj->GetSnapRect();
+ const tools::Rectangle aOldLogicRect = pObj->GetLogicRect();
+ // Generate noRotate anchor if missing.
+ ScDrawObjData* pNoRotatedAnchor = GetNonRotatedObjData(pObj);
+ if (!pNoRotatedAnchor)
+ {
+ ScDrawObjData aNoRotateAnchor;
+ const tools::Rectangle aLogicRect(pObj->GetLogicRect());
+ GetCellAnchorFromPosition(aLogicRect, aNoRotateAnchor,
+ *pDoc, pData->maStart.Tab());
+ aNoRotateAnchor.mbResizeWithCell = pData->mbResizeWithCell;
+ SetNonRotatedAnchor(*pObj, aNoRotateAnchor);
+ pNoRotatedAnchor = GetNonRotatedObjData(pObj);
+ assert(pNoRotatedAnchor);
+ }
+ // Mirror object at vertical axis
+ Point aRef1( 0, 0 );
+ Point aRef2( 0, 1 );
+ if (bRecording)
+ AddCalcUndo( std::make_unique<SdrUndoGeoObj>( *pObj ) );
+ pObj->Mirror( aRef1, aRef2 );
+
+ // Adapt offsets in pNoRotatedAnchor so, that object will be moved to current position in
+ // save and reload.
+ const tools::Long nInverseShift = aOldSnapRect.Left() + aOldSnapRect.Right();
+ const Point aLogicLT = pObj->GetLogicRect().TopLeft();
+ const Point aMirroredLogicLT = aLogicLT + Point(nInverseShift, 0);
+ const Point aOffsetDiff = aMirroredLogicLT - aOldLogicRect.TopLeft();
+ // new Offsets
+ pNoRotatedAnchor->maStartOffset += aOffsetDiff;
+ pNoRotatedAnchor->maEndOffset += aOffsetDiff;
+ }
+ else // page anchored
+ {
+ Point aRef1( 0, 0 );
+ Point aRef2( 0, 1 );
+ if (bRecording)
+ AddCalcUndo( std::make_unique<SdrUndoGeoObj>( *pObj ) );
+ pObj->Mirror( aRef1, aRef2 );
+ }
+ }
+ else
+ {
+ // Move instead of mirroring:
+ // New start position is negative of old end position
+ // -> move by sum of start and end position
+ tools::Rectangle aObjRect = pObj->GetSnapRect();
+ Size aMoveSize( -(aObjRect.Left() + aObjRect.Right()), 0 );
+ if (bRecording)
+ AddCalcUndo( std::make_unique<SdrUndoMoveObj>( *pObj, aMoveSize ) );
+ pObj->Move( aMoveSize );
+ }
+
+ // for cell anchored objects adapt rectangles in anchors
+ ScDrawObjData* pData = GetObjData(pObj);
+ if (pData)
+ {
+ pData->setShapeRect(GetDocument(), pObj->GetSnapRect(), pObj->IsVisible());
+ ScDrawObjData* pNoRotatedAnchor = GetNonRotatedObjData(pObj, true /*bCreate*/);
+ pNoRotatedAnchor->setShapeRect(GetDocument(), pObj->GetLogicRect(), pObj->IsVisible());
+ }
+}
+
+void ScDrawLayer::MoveRTL(SdrObject* pObj)
+{
+ tools::Rectangle aObjRect = pObj->GetSnapRect();
+ Size aMoveSize( -(aObjRect.Left() + aObjRect.Right()), 0 );
+ if (bRecording)
+ AddCalcUndo( std::make_unique<SdrUndoMoveObj>( *pObj, aMoveSize ) );
+ pObj->Move( aMoveSize );
+
+ // for cell anchored objects adapt rectangles in anchors
+ ScDrawObjData* pData = GetObjData(pObj);
+ if (pData)
+ {
+ pData->setShapeRect(GetDocument(), pObj->GetSnapRect(), pObj->IsVisible());
+ ScDrawObjData* pNoRotatedAnchor = GetNonRotatedObjData(pObj, true /*bCreate*/);
+ pNoRotatedAnchor->setShapeRect(GetDocument(), pObj->GetLogicRect(), pObj->IsVisible());
+ }
+}
+
+void ScDrawLayer::MirrorRectRTL( tools::Rectangle& rRect )
+{
+ // mirror and swap left/right
+ tools::Long nTemp = rRect.Left();
+ rRect.SetLeft( -rRect.Right() );
+ rRect.SetRight( -nTemp );
+}
+
+tools::Rectangle ScDrawLayer::GetCellRect( const ScDocument& rDoc, const ScAddress& rPos, bool bMergedCell )
+{
+ tools::Rectangle aCellRect;
+ OSL_ENSURE( rDoc.ValidColRowTab( rPos.Col(), rPos.Row(), rPos.Tab() ), "ScDrawLayer::GetCellRect - invalid cell address" );
+ if( rDoc.ValidColRowTab( rPos.Col(), rPos.Row(), rPos.Tab() ) )
+ {
+ // find top left position of passed cell address
+ Point aTopLeft;
+ for( SCCOL nCol = 0; nCol < rPos.Col(); ++nCol )
+ aTopLeft.AdjustX(rDoc.GetColWidth( nCol, rPos.Tab() ) );
+ if( rPos.Row() > 0 )
+ aTopLeft.AdjustY(rDoc.GetRowHeight( 0, rPos.Row() - 1, rPos.Tab() ) );
+
+ // find bottom-right position of passed cell address
+ ScAddress aEndPos = rPos;
+ if( bMergedCell )
+ {
+ const ScMergeAttr* pMerge = rDoc.GetAttr( rPos, ATTR_MERGE );
+ if( pMerge->GetColMerge() > 1 )
+ aEndPos.IncCol( pMerge->GetColMerge() - 1 );
+ if( pMerge->GetRowMerge() > 1 )
+ aEndPos.IncRow( pMerge->GetRowMerge() - 1 );
+ }
+ Point aBotRight = aTopLeft;
+ for( SCCOL nCol = rPos.Col(); nCol <= aEndPos.Col(); ++nCol )
+ aBotRight.AdjustX(rDoc.GetColWidth( nCol, rPos.Tab() ) );
+ aBotRight.AdjustY(rDoc.GetRowHeight( rPos.Row(), aEndPos.Row(), rPos.Tab() ) );
+
+ // twips -> 1/100 mm
+ aTopLeft = o3tl::convert(aTopLeft, o3tl::Length::twip, o3tl::Length::mm100);
+ aBotRight = o3tl::convert(aBotRight, o3tl::Length::twip, o3tl::Length::mm100);
+
+ aCellRect = tools::Rectangle( aTopLeft, aBotRight );
+ if( rDoc.IsNegativePage( rPos.Tab() ) )
+ MirrorRectRTL( aCellRect );
+ }
+ return aCellRect;
+}
+
+OUString ScDrawLayer::GetVisibleName( const SdrObject* pObj )
+{
+ OUString aName = pObj->GetName();
+ if ( pObj->GetObjIdentifier() == SdrObjKind::OLE2 )
+ {
+ // For OLE, the user defined name (GetName) is used
+ // if it's not empty (accepting possibly duplicate names),
+ // otherwise the persist name is used so every object appears
+ // in the Navigator at all.
+
+ if ( aName.isEmpty() )
+ aName = static_cast<const SdrOle2Obj*>(pObj)->GetPersistName();
+ }
+ return aName;
+}
+
+static bool IsNamedObject( const SdrObject* pObj, std::u16string_view rName )
+{
+ // sal_True if rName is the object's Name or PersistName
+ // (used to find a named object)
+
+ return ( pObj->GetName() == rName ||
+ ( pObj->GetObjIdentifier() == SdrObjKind::OLE2 &&
+ static_cast<const SdrOle2Obj*>(pObj)->GetPersistName() == rName ) );
+}
+
+SdrObject* ScDrawLayer::GetNamedObject( std::u16string_view rName, SdrObjKind nId, SCTAB& rFoundTab ) const
+{
+ sal_uInt16 nTabCount = GetPageCount();
+ for (sal_uInt16 nTab=0; nTab<nTabCount; nTab++)
+ {
+ const SdrPage* pPage = GetPage(nTab);
+ OSL_ENSURE(pPage,"Page ?");
+ if (pPage)
+ {
+ SdrObjListIter aIter( pPage, SdrIterMode::DeepWithGroups );
+ SdrObject* pObject = aIter.Next();
+ while (pObject)
+ {
+ if ( nId == SdrObjKind::NONE || pObject->GetObjIdentifier() == nId )
+ if ( IsNamedObject( pObject, rName ) )
+ {
+ rFoundTab = static_cast<SCTAB>(nTab);
+ return pObject;
+ }
+
+ pObject = aIter.Next();
+ }
+ }
+ }
+
+ return nullptr;
+}
+
+OUString ScDrawLayer::GetNewGraphicName( tools::Long* pnCounter ) const
+{
+ OUString aBase = ScResId(STR_GRAPHICNAME) + " ";
+
+ bool bThere = true;
+ OUString aGraphicName;
+ SCTAB nDummy;
+ tools::Long nId = pnCounter ? *pnCounter : 0;
+ while (bThere)
+ {
+ ++nId;
+ aGraphicName = aBase + OUString::number( nId );
+ bThere = ( GetNamedObject( aGraphicName, SdrObjKind::NONE, nDummy ) != nullptr );
+ }
+
+ if ( pnCounter )
+ *pnCounter = nId;
+
+ return aGraphicName;
+}
+
+void ScDrawLayer::EnsureGraphicNames()
+{
+ // make sure all graphic objects have names (after Excel import etc.)
+
+ sal_uInt16 nTabCount = GetPageCount();
+ for (sal_uInt16 nTab=0; nTab<nTabCount; nTab++)
+ {
+ SdrPage* pPage = GetPage(nTab);
+ OSL_ENSURE(pPage,"Page ?");
+ if (pPage)
+ {
+ SdrObjListIter aIter( pPage, SdrIterMode::DeepWithGroups );
+ SdrObject* pObject = aIter.Next();
+
+ /* The index passed to GetNewGraphicName() will be set to
+ the used index in each call. This prevents the repeated search
+ for all names from 1 to current index. */
+ tools::Long nCounter = 0;
+
+ while (pObject)
+ {
+ if ( pObject->GetObjIdentifier() == SdrObjKind::Graphic && pObject->GetName().isEmpty())
+ pObject->SetName( GetNewGraphicName( &nCounter ) );
+
+ pObject = aIter.Next();
+ }
+ }
+ }
+}
+
+namespace
+{
+ SdrObjUserData* GetFirstUserDataOfType(const SdrObject *pObj, sal_uInt16 nId)
+ {
+ sal_uInt16 nCount = pObj ? pObj->GetUserDataCount() : 0;
+ for( sal_uInt16 i = 0; i < nCount; i++ )
+ {
+ SdrObjUserData* pData = pObj->GetUserData( i );
+ if( pData && pData->GetInventor() == SdrInventor::ScOrSwDraw && pData->GetId() == nId )
+ return pData;
+ }
+ return nullptr;
+ }
+
+ void DeleteFirstUserDataOfType(SdrObject *pObj, sal_uInt16 nId)
+ {
+ sal_uInt16 nCount = pObj ? pObj->GetUserDataCount() : 0;
+ for( sal_uInt16 i = nCount; i > 0; i-- )
+ {
+ SdrObjUserData* pData = pObj->GetUserData( i-1 );
+ if( pData && pData->GetInventor() == SdrInventor::ScOrSwDraw && pData->GetId() == nId )
+ pObj->DeleteUserData(i-1);
+ }
+ }
+}
+
+void ScDrawLayer::SetNonRotatedAnchor(SdrObject& rObj, const ScDrawObjData& rAnchor)
+{
+ ScDrawObjData* pAnchor = GetNonRotatedObjData( &rObj, true );
+ pAnchor->maStart = rAnchor.maStart;
+ pAnchor->maEnd = rAnchor.maEnd;
+ pAnchor->maStartOffset = rAnchor.maStartOffset;
+ pAnchor->maEndOffset = rAnchor.maEndOffset;
+ pAnchor->mbResizeWithCell = rAnchor.mbResizeWithCell;
+}
+
+void ScDrawLayer::SetCellAnchored( SdrObject &rObj, const ScDrawObjData &rAnchor )
+{
+ ScDrawObjData* pAnchor = GetObjData( &rObj, true );
+ pAnchor->maStart = rAnchor.maStart;
+ pAnchor->maEnd = rAnchor.maEnd;
+ pAnchor->maStartOffset = rAnchor.maStartOffset;
+ pAnchor->maEndOffset = rAnchor.maEndOffset;
+ pAnchor->mbResizeWithCell = rAnchor.mbResizeWithCell;
+}
+
+void ScDrawLayer::SetCellAnchoredFromPosition( SdrObject &rObj, const ScDocument &rDoc, SCTAB nTab,
+ bool bResizeWithCell )
+{
+ if (!rObj.IsVisible())
+ return;
+ ScDrawObjData aAnchor;
+ // set anchor in terms of the visual ( SnapRect )
+ // object ( e.g. for when object is rotated )
+ const tools::Rectangle aObjRect(rObj.GetSnapRect());
+ GetCellAnchorFromPosition(
+ aObjRect,
+ aAnchor,
+ rDoc,
+ nTab);
+
+ aAnchor.mbResizeWithCell = bResizeWithCell;
+ SetCellAnchored( rObj, aAnchor );
+
+ // absolutely necessary to set flag, ScDrawLayer::RecalcPos expects it.
+ if ( ScDrawObjData* pAnchor = GetObjData( &rObj ) )
+ {
+ pAnchor->setShapeRect(&rDoc, rObj.GetSnapRect());
+ }
+
+ // - keep also an anchor in terms of the Logic ( untransformed ) object
+ // because that's what we stored ( and still do ) to xml
+
+ // Vertical flipped custom shapes need special treatment, see comment in
+ // lcl_SetLogicRectFromAnchor
+ tools::Rectangle aObjRect2;
+ if (lcl_NeedsMirrorYCorrection(&rObj))
+ {
+ const tools::Rectangle aRect(rObj.GetSnapRect());
+ const Point aLeft(aRect.Left(), (aRect.Top() + aRect.Bottom()) >> 1);
+ const Point aRight(aLeft.X() + 1000, aLeft.Y());
+ rObj.NbcMirror(aLeft, aRight);
+ aObjRect2 = rObj.GetLogicRect();
+ rObj.NbcMirror(aLeft, aRight);
+ }
+ else if (rObj.GetObjIdentifier() == SdrObjKind::Measure)
+ {
+ // tdf#137576. A SdrMeasureObj might have a wrong logic rect here. TakeUnrotatedSnapRect
+ // calculates the current unrotated snap rectangle, sets logic rectangle and returns it.
+ static_cast<SdrMeasureObj&>(rObj).TakeUnrotatedSnapRect(aObjRect2);
+ }
+ else
+ aObjRect2 = rObj.GetLogicRect();
+
+ // Values in XML are so as if it is a LTR sheet. The object is shifted to negative page on loading
+ // so that the snap rectangle appears mirrored. For transformed objects the shifted logic rectangle
+ // is not the mirrored LTR rectangle. We calculate the mirrored LTR rectangle here.
+ if (rDoc.IsNegativePage(nTab))
+ {
+ const tools::Rectangle aSnapRect(rObj.GetSnapRect());
+ aObjRect2.Move(Size(-aSnapRect.Left() - aSnapRect.Right(), 0));
+ MirrorRectRTL(aObjRect2);
+ }
+
+ ScDrawObjData aNoRotatedAnchor;
+ GetCellAnchorFromPosition(
+ aObjRect2,
+ aNoRotatedAnchor,
+ rDoc,
+ nTab);
+
+ aNoRotatedAnchor.mbResizeWithCell = bResizeWithCell;
+ SetNonRotatedAnchor( rObj, aNoRotatedAnchor);
+ // And update maShapeRect. It is used in adjustAnchoredPosition() in ScDrawView::Notify().
+ if (ScDrawObjData* pAnchor = GetNonRotatedObjData(&rObj))
+ {
+ pAnchor->setShapeRect(&rDoc, rObj.GetLogicRect());
+ }
+}
+
+void ScDrawLayer::GetCellAnchorFromPosition(
+ const tools::Rectangle &rObjRect,
+ ScDrawObjData &rAnchor,
+ const ScDocument &rDoc,
+ SCTAB nTab,
+ bool bHiddenAsZero)
+{
+ ScRange aRange = rDoc.GetRange( nTab, rObjRect, bHiddenAsZero );
+
+ tools::Rectangle aCellRect;
+
+ rAnchor.maStart = aRange.aStart;
+ aCellRect = rDoc.GetMMRect( aRange.aStart.Col(), aRange.aStart.Row(),
+ aRange.aStart.Col(), aRange.aStart.Row(), aRange.aStart.Tab(), bHiddenAsZero );
+ rAnchor.maStartOffset.setY( rObjRect.Top()-aCellRect.Top() );
+ if (!rDoc.IsNegativePage(nTab))
+ rAnchor.maStartOffset.setX( rObjRect.Left()-aCellRect.Left() );
+ else
+ rAnchor.maStartOffset.setX( aCellRect.Right()-rObjRect.Right() );
+
+ rAnchor.maEnd = aRange.aEnd;
+ aCellRect = rDoc.GetMMRect( aRange.aEnd.Col(), aRange.aEnd.Row(),
+ aRange.aEnd.Col(), aRange.aEnd.Row(), aRange.aEnd.Tab(), bHiddenAsZero );
+ if (!rObjRect.IsEmpty())
+ rAnchor.maEndOffset.setY( rObjRect.Bottom()-aCellRect.Top() );
+ if (!rDoc.IsNegativePage(nTab))
+ {
+ if (!rObjRect.IsEmpty())
+ rAnchor.maEndOffset.setX( rObjRect.Right()-aCellRect.Left() );
+ }
+ else
+ rAnchor.maEndOffset.setX( aCellRect.Right()-rObjRect.Left() );
+}
+
+void ScDrawLayer::UpdateCellAnchorFromPositionEnd( const SdrObject &rObj, ScDrawObjData &rAnchor, const ScDocument &rDoc, SCTAB nTab, bool bUseLogicRect )
+{
+ tools::Rectangle aObjRect(bUseLogicRect ? rObj.GetLogicRect() : rObj.GetSnapRect());
+ ScRange aRange = rDoc.GetRange( nTab, aObjRect );
+
+ ScDrawObjData* pAnchor = &rAnchor;
+ pAnchor->maEnd = aRange.aEnd;
+
+ tools::Rectangle aCellRect = rDoc.GetMMRect( aRange.aEnd.Col(), aRange.aEnd.Row(),
+ aRange.aEnd.Col(), aRange.aEnd.Row(), aRange.aEnd.Tab() );
+ pAnchor->maEndOffset.setY( aObjRect.Bottom()-aCellRect.Top() );
+ if (!rDoc.IsNegativePage(nTab))
+ pAnchor->maEndOffset.setX( aObjRect.Right()-aCellRect.Left() );
+ else
+ pAnchor->maEndOffset.setX( aCellRect.Right()-aObjRect.Left() );
+}
+
+bool ScDrawLayer::IsCellAnchored( const SdrObject& rObj )
+{
+ // Cell anchored object always has a user data, to store the anchor cell
+ // info. If it doesn't then it's page-anchored.
+ return GetFirstUserDataOfType(&rObj, SC_UD_OBJDATA) != nullptr;
+}
+
+bool ScDrawLayer::IsResizeWithCell( const SdrObject& rObj )
+{
+ // Cell anchored object always has a user data, to store the anchor cell
+ // info. If it doesn't then it's page-anchored.
+ ScDrawObjData* pDrawObjData = GetObjData(const_cast<SdrObject*>(&rObj));
+ if (!pDrawObjData)
+ return false;
+
+ return pDrawObjData->mbResizeWithCell;
+}
+
+void ScDrawLayer::SetPageAnchored( SdrObject &rObj )
+{
+ DeleteFirstUserDataOfType(&rObj, SC_UD_OBJDATA);
+ DeleteFirstUserDataOfType(&rObj, SC_UD_OBJDATA);
+}
+
+ScAnchorType ScDrawLayer::GetAnchorType( const SdrObject &rObj )
+{
+ //If this object has a cell anchor associated with it
+ //then it's cell-anchored, otherwise it's page-anchored
+ const ScDrawObjData* pObjData = ScDrawLayer::GetObjData(const_cast<SdrObject*>(&rObj));
+
+ // When there is no cell anchor, it is page anchored.
+ if (!pObjData)
+ return SCA_PAGE;
+
+ // It's cell-anchored, check if the object resizes with the cell
+ if (pObjData->mbResizeWithCell)
+ return SCA_CELL_RESIZE;
+
+ return SCA_CELL;
+}
+
+std::vector<SdrObject*>
+ScDrawLayer::GetObjectsAnchoredToRows(SCTAB nTab, SCROW nStartRow, SCROW nEndRow)
+{
+ SdrPage* pPage = GetPage(static_cast<sal_uInt16>(nTab));
+ if (!pPage || pPage->GetObjCount() < 1)
+ return std::vector<SdrObject*>();
+
+ std::vector<SdrObject*> aObjects;
+ SdrObjListIter aIter( pPage, SdrIterMode::Flat );
+ SdrObject* pObject = aIter.Next();
+ ScRange aRange( 0, nStartRow, nTab, pDoc->MaxCol(), nEndRow, nTab);
+ while (pObject)
+ {
+ ScDrawObjData* pObjData = GetObjData(pObject);
+ if (pObjData && aRange.Contains(pObjData->maStart))
+ aObjects.push_back(pObject);
+ pObject = aIter.Next();
+ }
+ return aObjects;
+}
+
+std::map<SCROW, std::vector<SdrObject*>>
+ScDrawLayer::GetObjectsAnchoredToRange(SCTAB nTab, SCCOL nCol, SCROW nStartRow, SCROW nEndRow)
+{
+ SdrPage* pPage = GetPage(static_cast<sal_uInt16>(nTab));
+ if (!pPage || pPage->GetObjCount() < 1)
+ return std::map<SCROW, std::vector<SdrObject*>>();
+
+ std::map<SCROW, std::vector<SdrObject*>> aRowObjects;
+ SdrObjListIter aIter( pPage, SdrIterMode::Flat );
+ SdrObject* pObject = aIter.Next();
+ ScRange aRange( nCol, nStartRow, nTab, nCol, nEndRow, nTab);
+ while (pObject)
+ {
+ if (!dynamic_cast<SdrCaptionObj*>(pObject)) // Caption objects are handled differently
+ {
+ ScDrawObjData* pObjData = GetObjData(pObject);
+ if (pObjData && aRange.Contains(pObjData->maStart))
+ aRowObjects[pObjData->maStart.Row()].push_back(pObject);
+ }
+ pObject = aIter.Next();
+ }
+ return aRowObjects;
+}
+
+bool ScDrawLayer::HasObjectsAnchoredInRange(const ScRange& rRange)
+{
+ // This only works for one table at a time
+ assert(rRange.aStart.Tab() == rRange.aEnd.Tab());
+
+ SdrPage* pPage = GetPage(static_cast<sal_uInt16>(rRange.aStart.Tab()));
+ if (!pPage || pPage->GetObjCount() < 1)
+ return false;
+
+ SdrObjListIter aIter( pPage, SdrIterMode::Flat );
+ SdrObject* pObject = aIter.Next();
+ while (pObject)
+ {
+ if (!dynamic_cast<SdrCaptionObj*>(pObject)) // Caption objects are handled differently
+ {
+ ScDrawObjData* pObjData = GetObjData(pObject);
+ if (pObjData && rRange.Contains(pObjData->maStart)) // Object is in given range
+ return true;
+ }
+ pObject = aIter.Next();
+ }
+ return false;
+}
+
+std::vector<SdrObject*> ScDrawLayer::GetObjectsAnchoredToCols(SCTAB nTab, SCCOL nStartCol,
+ SCCOL nEndCol)
+{
+ SdrPage* pPage = GetPage(static_cast<sal_uInt16>(nTab));
+ if (!pPage || pPage->GetObjCount() < 1)
+ return std::vector<SdrObject*>();
+
+ std::vector<SdrObject*> aObjects;
+ SdrObjListIter aIter(pPage, SdrIterMode::Flat);
+ SdrObject* pObject = aIter.Next();
+ ScRange aRange(nStartCol, 0, nTab, nEndCol, pDoc->MaxRow(), nTab);
+ while (pObject)
+ {
+ ScDrawObjData* pObjData = GetObjData(pObject);
+ if (pObjData && aRange.Contains(pObjData->maStart))
+ aObjects.push_back(pObject);
+ pObject = aIter.Next();
+ }
+ return aObjects;
+}
+
+void ScDrawLayer::MoveObject(SdrObject* pObject, const ScAddress& rNewPosition)
+{
+ // Get anchor data
+ ScDrawObjData* pObjData = GetObjData(pObject, false);
+ if (!pObjData)
+ return;
+ const ScAddress aOldStart = pObjData->maStart;
+ const ScAddress aOldEnd = pObjData->maEnd;
+
+ // Set start address
+ pObjData->maStart = rNewPosition;
+
+ // Set end address
+ const SCCOL nObjectColSpan = aOldEnd.Col() - aOldStart.Col();
+ const SCROW nObjectRowSpan = aOldEnd.Row() - aOldStart.Row();
+ ScAddress aNewEnd = rNewPosition;
+ aNewEnd.IncRow(nObjectRowSpan);
+ aNewEnd.IncCol(nObjectColSpan);
+ pObjData->maEnd = aNewEnd;
+
+ // Update draw object according to new anchor
+ RecalcPos(pObject, *pObjData, false, false);
+}
+
+ScDrawObjData* ScDrawLayer::GetNonRotatedObjData( SdrObject* pObj, bool bCreate )
+{
+ sal_uInt16 nCount = pObj ? pObj->GetUserDataCount() : 0;
+ sal_uInt16 nFound = 0;
+ for( sal_uInt16 i = 0; i < nCount; i++ )
+ {
+ SdrObjUserData* pData = pObj->GetUserData( i );
+ if( pData && pData->GetInventor() == SdrInventor::ScOrSwDraw && pData->GetId() == SC_UD_OBJDATA && ++nFound == 2 )
+ return static_cast<ScDrawObjData*>(pData);
+ }
+ if( pObj && bCreate )
+ {
+ ScDrawObjData* pData = new ScDrawObjData;
+ pObj->AppendUserData(std::unique_ptr<SdrObjUserData>(pData));
+ return pData;
+ }
+ return nullptr;
+}
+
+ScDrawObjData* ScDrawLayer::GetObjData( SdrObject* pObj, bool bCreate )
+{
+ if (SdrObjUserData *pData = GetFirstUserDataOfType(pObj, SC_UD_OBJDATA))
+ return static_cast<ScDrawObjData*>(pData);
+
+ if( pObj && bCreate )
+ {
+ ScDrawObjData* pData = new ScDrawObjData;
+ pObj->AppendUserData(std::unique_ptr<SdrObjUserData>(pData));
+ return pData;
+ }
+ return nullptr;
+}
+
+ScDrawObjData* ScDrawLayer::GetObjDataTab( SdrObject* pObj, SCTAB nTab )
+{
+ ScDrawObjData* pData = GetObjData( pObj );
+ if ( pData )
+ {
+ if ( pData->maStart.IsValid() )
+ pData->maStart.SetTab( nTab );
+ if ( pData->maEnd.IsValid() )
+ pData->maEnd.SetTab( nTab );
+ }
+ return pData;
+}
+
+bool ScDrawLayer::IsNoteCaption( SdrObject* pObj )
+{
+ ScDrawObjData* pData = pObj ? GetObjData( pObj ) : nullptr;
+ return pData && pData->meType == ScDrawObjData::CellNote;
+}
+
+ScDrawObjData* ScDrawLayer::GetNoteCaptionData( SdrObject* pObj, SCTAB nTab )
+{
+ ScDrawObjData* pData = pObj ? GetObjDataTab( pObj, nTab ) : nullptr;
+ return (pData && pData->meType == ScDrawObjData::CellNote) ? pData : nullptr;
+}
+
+ScMacroInfo* ScDrawLayer::GetMacroInfo( SdrObject* pObj, bool bCreate )
+{
+ if (SdrObjUserData *pData = GetFirstUserDataOfType(pObj, SC_UD_MACRODATA))
+ return static_cast<ScMacroInfo*>(pData);
+
+ if ( bCreate )
+ {
+ ScMacroInfo* pData = new ScMacroInfo;
+ pObj->AppendUserData(std::unique_ptr<SdrObjUserData>(pData));
+ return pData;
+ }
+ return nullptr;
+}
+
+void ScDrawLayer::SetGlobalDrawPersist(SfxObjectShell* pPersist)
+{
+ OSL_ENSURE(!pGlobalDrawPersist,"Multiple SetGlobalDrawPersist");
+ pGlobalDrawPersist = pPersist;
+}
+
+void ScDrawLayer::SetChanged( bool bFlg /* = true */ )
+{
+ if ( bFlg && pDoc )
+ pDoc->SetChartListenerCollectionNeedsUpdate( true );
+ FmFormModel::SetChanged( bFlg );
+}
+
+css::uno::Reference< css::uno::XInterface > ScDrawLayer::createUnoModel()
+{
+ css::uno::Reference< css::uno::XInterface > xRet;
+ if( pDoc && pDoc->GetDocumentShell() )
+ xRet = pDoc->GetDocumentShell()->GetModel();
+
+ return xRet;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sc/source/core/data/edittextiterator.cxx b/sc/source/core/data/edittextiterator.cxx
new file mode 100644
index 000000000..bdf7c9934
--- /dev/null
+++ b/sc/source/core/data/edittextiterator.cxx
@@ -0,0 +1,97 @@
+/* -*- 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/.
+ */
+
+#include <edittextiterator.hxx>
+#include <document.hxx>
+#include <table.hxx>
+#include <column.hxx>
+
+namespace sc {
+
+EditTextIterator::EditTextIterator( const ScDocument& rDoc, SCTAB nTab ) :
+ mrTable(*rDoc.maTabs.at(nTab)),
+ mnCol(0),
+ mpCells(nullptr),
+ miEnd(maPos.first)
+{
+ init();
+}
+
+void EditTextIterator::init()
+{
+ mnCol = 0;
+ if (mnCol >= mrTable.aCol.size())
+ mnCol = -1;
+
+ if (mnCol != -1)
+ {
+ mpCells = &mrTable.aCol[mnCol].maCells;
+ maPos = mpCells->position(0);
+ miEnd = mpCells->end();
+ }
+}
+
+const EditTextObject* EditTextIterator::seek()
+{
+ while (maPos.first->type != sc::element_type_edittext)
+ {
+ incBlock();
+ if (maPos.first == miEnd)
+ {
+ // Move to the next column.
+ ++mnCol;
+ if (mnCol >= mrTable.aCol.size())
+ // No more columns.
+ return nullptr;
+
+ mpCells = &mrTable.aCol[mnCol].maCells;
+ maPos = mpCells->position(0);
+ miEnd = mpCells->end();
+ }
+ }
+
+ // We are on the right block type.
+ return sc::edittext_block::at(*maPos.first->data, maPos.second);
+}
+
+void EditTextIterator::incBlock()
+{
+ ++maPos.first;
+ maPos.second = 0;
+}
+
+const EditTextObject* EditTextIterator::first()
+{
+ init();
+ if (mnCol == -1)
+ return nullptr;
+ return seek();
+}
+
+const EditTextObject* EditTextIterator::next()
+{
+ if (mnCol == -1)
+ return nullptr;
+
+ if (maPos.first == miEnd)
+ return nullptr;
+
+ // increment position by one
+ if (maPos.second + 1 < maPos.first->size)
+ // Increment within the block.
+ ++maPos.second;
+ else
+ incBlock();
+
+ return seek();
+}
+
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sc/source/core/data/fillinfo.cxx b/sc/source/core/data/fillinfo.cxx
new file mode 100644
index 000000000..a6fa1b318
--- /dev/null
+++ b/sc/source/core/data/fillinfo.cxx
@@ -0,0 +1,1064 @@
+/* -*- 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 <scitems.hxx>
+#include <editeng/boxitem.hxx>
+#include <editeng/lineitem.hxx>
+#include <editeng/shaditem.hxx>
+#include <editeng/brushitem.hxx>
+#include <svx/framelink.hxx>
+#include <osl/diagnose.h>
+
+#include <fillinfo.hxx>
+#include <document.hxx>
+#include <table.hxx>
+#include <attrib.hxx>
+#include <attarray.hxx>
+#include <markarr.hxx>
+#include <markdata.hxx>
+#include <patattr.hxx>
+#include <poolhelp.hxx>
+#include <docpool.hxx>
+#include <conditio.hxx>
+#include <stlpool.hxx>
+#include <cellvalue.hxx>
+#include <mtvcellfunc.hxx>
+
+#include <algorithm>
+#include <limits>
+#include <vector>
+#include <memory>
+
+// Similar as in output.cxx
+
+static void lcl_GetMergeRange( SCCOL nX, SCROW nY, SCSIZE nArrY,
+ const ScDocument* pDoc, RowInfo* pRowInfo,
+ SCCOL nX1, SCROW nY1, SCTAB nTab,
+ SCCOL& rStartX, SCROW& rStartY, SCCOL& rEndX, SCROW& rEndY )
+{
+ ScCellInfo* pInfo = &pRowInfo[nArrY].cellInfo(nX);
+
+ rStartX = nX;
+ rStartY = nY;
+ bool bHOver = pInfo->bHOverlapped;
+ bool bVOver = pInfo->bVOverlapped;
+ SCCOL nLastCol;
+ SCROW nLastRow;
+
+ while (bHOver) // nY constant
+ {
+ --rStartX;
+ if (rStartX >= nX1 && !pDoc->ColHidden(rStartX, nTab, nullptr, &nLastCol))
+ {
+ bHOver = pRowInfo[nArrY].cellInfo(rStartX).bHOverlapped;
+ bVOver = pRowInfo[nArrY].cellInfo(rStartX).bVOverlapped;
+ }
+ else
+ {
+ ScMF nOverlap = pDoc->GetAttr( rStartX, rStartY, nTab, ATTR_MERGE_FLAG )->GetValue();
+ bHOver = bool(nOverlap & ScMF::Hor);
+ bVOver = bool(nOverlap & ScMF::Ver);
+ }
+ }
+
+ while (bVOver)
+ {
+ --rStartY;
+
+ if (nArrY>0)
+ --nArrY; // local copy !
+
+ if (rStartX >= nX1 && rStartY >= nY1 &&
+ !pDoc->ColHidden(rStartX, nTab, nullptr, &nLastCol) &&
+ !pDoc->RowHidden(rStartY, nTab, nullptr, &nLastRow) &&
+ pRowInfo[nArrY].nRowNo == rStartY)
+ {
+ bVOver = pRowInfo[nArrY].cellInfo(rStartX).bVOverlapped;
+ }
+ else
+ {
+ ScMF nOverlap = pDoc->GetAttr(
+ rStartX, rStartY, nTab, ATTR_MERGE_FLAG )->GetValue();
+ bVOver = bool(nOverlap & ScMF::Ver);
+ }
+ }
+
+ const ScMergeAttr* pMerge;
+ if (rStartX >= nX1 && rStartY >= nY1 &&
+ !pDoc->ColHidden(rStartX, nTab, nullptr, &nLastCol) &&
+ !pDoc->RowHidden(rStartY, nTab, nullptr, &nLastRow) &&
+ pRowInfo[nArrY].nRowNo == rStartY)
+ {
+ pMerge = &pRowInfo[nArrY].cellInfo(rStartX).pPatternAttr->
+ GetItem(ATTR_MERGE);
+ }
+ else
+ pMerge = pDoc->GetAttr(rStartX,rStartY,nTab,ATTR_MERGE);
+
+ rEndX = rStartX + pMerge->GetColMerge() - 1;
+ rEndY = rStartY + pMerge->GetRowMerge() - 1;
+}
+
+namespace {
+
+class RowInfoFiller
+{
+ ScDocument& mrDoc;
+ SCTAB mnTab;
+ RowInfo* mpRowInfo;
+ SCCOL mnCol;
+ SCSIZE mnArrY;
+ SCCOL mnStartCol;
+ SCROW mnHiddenEndRow;
+ bool mbHiddenRow;
+
+ bool isHidden(size_t nRow)
+ {
+ SCROW nThisRow = static_cast<SCROW>(nRow);
+ if (nThisRow > mnHiddenEndRow)
+ mbHiddenRow = mrDoc.RowHidden(nThisRow, mnTab, nullptr, &mnHiddenEndRow);
+ return mbHiddenRow;
+ }
+
+ void alignArray(size_t nRow)
+ {
+ while (mpRowInfo[mnArrY].nRowNo < static_cast<SCROW>(nRow))
+ ++mnArrY;
+ }
+
+ void setInfo(size_t nRow, const ScRefCellValue& rCell)
+ {
+ alignArray(nRow);
+
+ RowInfo& rThisRowInfo = mpRowInfo[mnArrY];
+ if(mnCol >= mnStartCol-1)
+ rThisRowInfo.cellInfo(mnCol).maCell = rCell;
+ rThisRowInfo.basicCellInfo(mnCol).bEmptyCellText = false;
+ ++mnArrY;
+ }
+
+public:
+ RowInfoFiller(ScDocument& rDoc, SCTAB nTab, RowInfo* pRowInfo, SCCOL nCol, SCSIZE nArrY, SCCOL nStartCol) :
+ mrDoc(rDoc), mnTab(nTab), mpRowInfo(pRowInfo), mnCol(nCol), mnArrY(nArrY), mnStartCol(nStartCol),
+ mnHiddenEndRow(-1), mbHiddenRow(false) {}
+
+ void operator() (size_t nRow, double fVal)
+ {
+ if (!isHidden(nRow))
+ setInfo(nRow, ScRefCellValue(fVal));
+ }
+
+ void operator() (size_t nRow, const svl::SharedString& rStr)
+ {
+ if (!isHidden(nRow))
+ setInfo(nRow, ScRefCellValue(&rStr));
+ }
+
+ void operator() (size_t nRow, const EditTextObject* p)
+ {
+ if (!isHidden(nRow))
+ setInfo(nRow, ScRefCellValue(p));
+ }
+
+ void operator() (size_t nRow, const ScFormulaCell* p)
+ {
+ if (!isHidden(nRow))
+ setInfo(nRow, ScRefCellValue(const_cast<ScFormulaCell*>(p)));
+ }
+};
+
+bool isRotateItemUsed(const ScDocumentPool *pPool)
+{
+ return pPool->GetItemCount2( ATTR_ROTATE_VALUE ) > 0;
+}
+
+void initRowInfo(const ScDocument* pDoc, RowInfo* pRowInfo, const SCSIZE nMaxRow,
+ double fRowScale, SCROW nRow1, SCTAB nTab, SCROW& rYExtra, SCSIZE& rArrRow, SCROW& rRow2)
+{
+ sal_uInt16 nDocHeight = ScGlobal::nStdRowHeight;
+ SCROW nDocHeightEndRow = -1;
+ for (SCROW nSignedY=nRow1-1; nSignedY<=rYExtra; nSignedY++)
+ {
+ SCROW nY;
+ if (nSignedY >= 0)
+ nY = nSignedY;
+ else
+ nY = pDoc->MaxRow()+1; // invalid
+
+ if (nY > nDocHeightEndRow)
+ {
+ if (pDoc->ValidRow(nY))
+ nDocHeight = pDoc->GetRowHeight( nY, nTab, nullptr, &nDocHeightEndRow );
+ else
+ nDocHeight = ScGlobal::nStdRowHeight;
+ }
+
+ if ( rArrRow==0 || nDocHeight || nY > pDoc->MaxRow() )
+ {
+ RowInfo* pThisRowInfo = &pRowInfo[rArrRow];
+ // pThisRowInfo->pCellInfo is set below using allocCellInfo()
+
+ sal_uInt16 nHeight = static_cast<sal_uInt16>(
+ std::clamp(
+ nDocHeight * fRowScale, 1.0, double(std::numeric_limits<sal_uInt16>::max())));
+
+ pThisRowInfo->nRowNo = nY; //TODO: case < 0 ?
+ pThisRowInfo->nHeight = nHeight;
+ pThisRowInfo->bEmptyBack = true;
+ pThisRowInfo->bChanged = true;
+ pThisRowInfo->bAutoFilter = false;
+ pThisRowInfo->bPivotButton = false;
+ pThisRowInfo->nRotMaxCol = SC_ROTMAX_NONE;
+
+ ++rArrRow;
+ if (rArrRow >= nMaxRow)
+ {
+ OSL_FAIL("FillInfo: Range too big" );
+ rYExtra = nSignedY; // End
+ rRow2 = rYExtra - 1; // Adjust range
+ }
+ }
+ else
+ if (nSignedY == rYExtra) // hidden additional line?
+ ++rYExtra;
+ }
+}
+
+void initCellInfo(RowInfo* pRowInfo, SCSIZE nArrCount, SCCOL nStartCol, SCCOL nRotMax,
+ const SvxShadowItem* pDefShadow)
+{
+ for (SCSIZE nArrRow = 0; nArrRow < nArrCount; ++nArrRow)
+ {
+ RowInfo& rThisRowInfo = pRowInfo[nArrRow];
+ // A lot of memory (and performance allocating and initializing it) can
+ // be saved if we do not allocate CellInfo for columns before nStartCol.
+ // But code in ScOutputData::SetCellRotation(), ScOutputData::DrawRotatedFrame()
+ // and ScOutputData::SetCellRotations() accesses those. That depends on
+ // nRotMaxCol being set to something else than none, and the value is already
+ // initialized here. So allocate all those cells starting from column 0 only if needed.
+ SCCOL nMinCol = rThisRowInfo.nRotMaxCol != SC_ROTMAX_NONE ? 0 : nStartCol;
+ rThisRowInfo.allocCellInfo( nMinCol, nRotMax + 1 );
+
+ for (SCCOL nCol = nMinCol-1; nCol <= nRotMax+1; ++nCol) // Preassign cell info
+ {
+ ScCellInfo& rInfo = rThisRowInfo.cellInfo(nCol);
+ rInfo.pShadowAttr = pDefShadow;
+ }
+ }
+}
+
+void initColWidths(RowInfo* pRowInfo, const ScDocument* pDoc, double fColScale, SCTAB nTab, SCCOL nCol2, SCCOL nRotMax)
+{
+ for (SCCOL nCol=nCol2+2; nCol<=nRotMax+1; nCol++) // Add remaining widths
+ {
+ if ( pDoc->ValidCol(nCol) )
+ {
+ if (!pDoc->ColHidden(nCol, nTab))
+ {
+ sal_uInt16 nThisWidth = static_cast<sal_uInt16>(pDoc->GetColWidth( nCol, nTab ) * fColScale);
+ if (!nThisWidth)
+ nThisWidth = 1;
+
+ pRowInfo[0].basicCellInfo(nCol).nWidth = nThisWidth;
+ }
+ }
+ }
+}
+
+bool handleConditionalFormat(ScConditionalFormatList& rCondFormList, const ScCondFormatIndexes& rCondFormats,
+ ScCellInfo* pInfo, ScTableInfo* pTableInfo, ScStyleSheetPool* pStlPool,
+ const ScAddress& rAddr, bool& bHidden, bool& bHideFormula, bool bTabProtect)
+{
+ bool bFound = false;
+ bool bAnyCondition = false;
+ for(const auto& rCondFormat : rCondFormats)
+ {
+ ScConditionalFormat* pCondForm = rCondFormList.GetFormat(rCondFormat);
+ if(!pCondForm)
+ continue;
+
+ ScCondFormatData aData = pCondForm->GetData(
+ pInfo->maCell, rAddr);
+ if (!aData.aStyleName.isEmpty())
+ {
+ SfxStyleSheetBase* pStyleSheet =
+ pStlPool->Find( aData.aStyleName, SfxStyleFamily::Para );
+ if ( pStyleSheet )
+ {
+ //TODO: cache Style-Sets !!!
+ pInfo->pConditionSet = &pStyleSheet->GetItemSet();
+ bAnyCondition = true;
+
+ // TODO: moggi: looks like there is a bug around bHidden and bHideFormula
+ // They are normally for the whole pattern and not for a single cell
+ // we need to check already here for protected cells
+ const ScProtectionAttr* pProtAttr;
+ if ( bTabProtect && (pProtAttr = pInfo->pConditionSet->GetItemIfSet( ATTR_PROTECTION )) )
+ {
+ bHidden = pProtAttr->GetHideCell();
+ bHideFormula = pProtAttr->GetHideFormula();
+
+ }
+ bFound = true;
+
+ }
+ // if style is not there, treat like no condition
+ }
+
+ if(aData.mxColorScale)
+ {
+ pInfo->mxColorScale = aData.mxColorScale;
+ bFound = true;
+ }
+
+ if(aData.pDataBar)
+ {
+ pInfo->pDataBar = aData.pDataBar.get();
+ pTableInfo->addDataBarInfo(std::move(aData.pDataBar));
+ bFound = true;
+ }
+
+ if(aData.pIconSet)
+ {
+ pInfo->pIconSet = aData.pIconSet.get();
+ pTableInfo->addIconSetInfo(std::move(aData.pIconSet));
+ bFound = true;
+ }
+
+ if (bFound)
+ break;
+ }
+
+ return bAnyCondition;
+}
+
+}
+
+void ScDocument::FillInfo(
+ ScTableInfo& rTabInfo, SCCOL nCol1, SCROW nRow1, SCCOL nCol2, SCROW nRow2,
+ SCTAB nTab, double fColScale, double fRowScale, bool bPageMode, bool bFormulaMode,
+ const ScMarkData* pMarkData )
+{
+ OSL_ENSURE( maTabs[nTab], "Table does not exist" );
+
+ bool bLayoutRTL = IsLayoutRTL( nTab );
+
+ ScDocumentPool* pPool = mxPoolHelper->GetDocPool();
+ ScStyleSheetPool* pStlPool = mxPoolHelper->GetStylePool();
+
+ RowInfo* pRowInfo = rTabInfo.mpRowInfo.get();
+
+ const SvxBrushItem* pDefBackground =
+ &pPool->GetDefaultItem( ATTR_BACKGROUND );
+ const ScMergeAttr* pDefMerge =
+ &pPool->GetDefaultItem( ATTR_MERGE );
+ const SvxShadowItem* pDefShadow =
+ &pPool->GetDefaultItem( ATTR_SHADOW );
+
+ SCSIZE nArrRow;
+ SCSIZE nArrCount;
+ bool bAnyMerged = false;
+ bool bAnyShadow = false;
+ bool bAnyCondition = false;
+ bool bAnyPreview = false;
+
+ bool bTabProtect = IsTabProtected(nTab);
+
+ // first only the entries for the entire column
+
+ nArrRow=0;
+ SCROW nYExtra = nRow2+1;
+ initRowInfo(this, pRowInfo, rTabInfo.mnArrCapacity, fRowScale, nRow1,
+ nTab, nYExtra, nArrRow, nRow2);
+ nArrCount = nArrRow; // incl. Dummys
+
+ // Rotated text...
+
+ // Is Attribute really used in document?
+ bool bAnyItem = isRotateItemUsed(pPool);
+
+ SCCOL nRotMax = nCol2;
+ if ( bAnyItem && HasAttrib( 0, nRow1, nTab, MaxCol(), nRow2+1, nTab,
+ HasAttrFlags::Rotate | HasAttrFlags::Conditional ) )
+ {
+ //TODO: check Conditionals also for HasAttrFlags::Rotate ????
+
+ OSL_ENSURE( nArrCount>2, "nArrCount too small" );
+ FindMaxRotCol( nTab, &pRowInfo[1], nArrCount-1, nCol1, nCol2 );
+ // FindMaxRotCol sets nRotMaxCol
+
+ for (nArrRow=0; nArrRow<nArrCount; nArrRow++)
+ if (pRowInfo[nArrRow].nRotMaxCol != SC_ROTMAX_NONE && pRowInfo[nArrRow].nRotMaxCol > nRotMax)
+ nRotMax = pRowInfo[nArrRow].nRotMaxCol;
+ }
+
+ // Allocate cell information only after the test rotation
+ // to nRotMax due to nRotateDir Flag
+ initCellInfo(pRowInfo, nArrCount, nCol1, nRotMax, pDefShadow);
+
+ initColWidths(pRowInfo, this, fColScale, nTab, nCol2, nRotMax);
+
+ ScConditionalFormatList* pCondFormList = GetCondFormList(nTab);
+ if (pCondFormList)
+ pCondFormList->startRendering();
+
+ SCCOL nLastHiddenCheckedCol = -2;
+ bool bColHidden = false;
+ for (SCCOL nCol=-1; nCol<=nCol2+1; nCol++) // collect basic info also for all previous cols, and left & right + 1
+ {
+ if (ValidCol(nCol))
+ {
+ // #i58049#, #i57939# Hidden columns must be skipped here, or their attributes
+ // will disturb the output
+
+ if (nCol > nLastHiddenCheckedCol)
+ bColHidden = ColHidden(nCol, nTab, nullptr, &nLastHiddenCheckedCol);
+ // TODO: Optimize this loop.
+ if (!bColHidden)
+ {
+ sal_uInt16 nColWidth = GetColWidth( nCol, nTab, false ); // false=no need to check for hidden, checked above
+ sal_uInt16 nThisWidth = static_cast<sal_uInt16>(std::clamp(nColWidth * fColScale, 1.0, double(std::numeric_limits<sal_uInt16>::max())));
+
+ pRowInfo[0].basicCellInfo(nCol).nWidth = nThisWidth; //TODO: this should be enough
+
+ const ScAttrArray* pThisAttrArr; // Attribute
+ if (nCol < maTabs[nTab]->GetAllocatedColumnsCount())
+ {
+ ScColumn* pThisCol = &maTabs[nTab]->aCol[nCol]; // Column data
+
+ nArrRow = 1;
+ // Iterate between rows nY1 and nY2 and pick up non-empty
+ // cells that are not hidden.
+ RowInfoFiller aFunc(*this, nTab, pRowInfo, nCol, nArrRow, nCol1);
+ sc::ParseAllNonEmpty(pThisCol->maCells.begin(), pThisCol->maCells, nRow1, nRow2,
+ aFunc);
+
+ pThisAttrArr = pThisCol->pAttrArray.get();
+ }
+ else
+ pThisAttrArr = &maTabs[nTab]->aDefaultColData.AttrArray();
+
+ if (nCol+1 >= nCol1) // Attribute/Blockmark from nX1-1
+ {
+ nArrRow = 0;
+
+ SCROW nCurRow=nRow1; // single rows
+ if (nCurRow>0)
+ --nCurRow; // 1 more on top
+ else
+ nArrRow = 1;
+
+ SCROW nThisRow;
+ SCSIZE nIndex;
+ if ( pThisAttrArr->Count() )
+ (void) pThisAttrArr->Search( nCurRow, nIndex );
+ else
+ nIndex = 0;
+
+ do
+ {
+ const ScPatternAttr* pPattern = nullptr;
+ if ( pThisAttrArr->Count() )
+ {
+ nThisRow = pThisAttrArr->mvData[nIndex].nEndRow; // End of range
+ pPattern = pThisAttrArr->mvData[nIndex].pPattern;
+ }
+ else
+ {
+ nThisRow = MaxRow();
+ pPattern = GetDefPattern();
+ }
+
+ const SvxBrushItem* pBackground = &pPattern->GetItem(ATTR_BACKGROUND);
+ const SvxBoxItem* pLinesAttr = &pPattern->GetItem(ATTR_BORDER);
+
+ const SvxLineItem* pTLBRLine = &pPattern->GetItem( ATTR_BORDER_TLBR );
+ const SvxLineItem* pBLTRLine = &pPattern->GetItem( ATTR_BORDER_BLTR );
+
+ const SvxShadowItem* pShadowAttr = &pPattern->GetItem(ATTR_SHADOW);
+ if (pShadowAttr != pDefShadow)
+ bAnyShadow = true;
+
+ const ScMergeAttr* pMergeAttr = &pPattern->GetItem(ATTR_MERGE);
+ bool bMerged = ( pMergeAttr != pDefMerge && *pMergeAttr != *pDefMerge );
+ ScMF nOverlap = pPattern->GetItemSet().
+ Get(ATTR_MERGE_FLAG).GetValue();
+ bool bHOverlapped(nOverlap & ScMF::Hor);
+ bool bVOverlapped(nOverlap & ScMF::Ver);
+ bool bAutoFilter(nOverlap & ScMF::Auto);
+ bool bPivotButton(nOverlap & ScMF::Button);
+ bool bScenario(nOverlap & ScMF::Scenario);
+ bool bPivotPopupButton(nOverlap & ScMF::ButtonPopup);
+ bool bFilterActive(nOverlap & ScMF::HiddenMember);
+ if (bMerged||bHOverlapped||bVOverlapped)
+ bAnyMerged = true; // internal
+
+ bool bHidden, bHideFormula;
+ if (bTabProtect)
+ {
+ const ScProtectionAttr& rProtAttr = pPattern->GetItem(ATTR_PROTECTION);
+ bHidden = rProtAttr.GetHideCell();
+ bHideFormula = rProtAttr.GetHideFormula();
+ }
+ else
+ bHidden = bHideFormula = false;
+
+ const ScCondFormatIndexes& rCondFormats = pPattern->GetItem(ATTR_CONDITIONAL).GetCondFormatData();
+ bool bContainsCondFormat = !rCondFormats.empty();
+
+ do
+ {
+ SCROW nLastHiddenRow = -1;
+ bool bRowHidden = RowHidden(nCurRow, nTab, nullptr, &nLastHiddenRow);
+ if ( nArrRow==0 || !bRowHidden )
+ {
+ if ( GetPreviewCellStyle( nCol, nCurRow, nTab ) != nullptr )
+ bAnyPreview = true;
+ RowInfo* pThisRowInfo = &pRowInfo[nArrRow];
+ if (pBackground != pDefBackground) // Column background == Default ?
+ pThisRowInfo->bEmptyBack = false;
+ if (bContainsCondFormat)
+ pThisRowInfo->bEmptyBack = false;
+ if (bAutoFilter)
+ pThisRowInfo->bAutoFilter = true;
+ if (bPivotButton || bPivotPopupButton)
+ pThisRowInfo->bPivotButton = true;
+
+ ScCellInfo* pInfo = &pThisRowInfo->cellInfo(nCol);
+ ScBasicCellInfo* pBasicInfo = &pThisRowInfo->basicCellInfo(nCol);
+ pInfo->pBackground = pBackground;
+ pInfo->pPatternAttr = pPattern;
+ pInfo->bMerged = bMerged;
+ pInfo->bHOverlapped = bHOverlapped;
+ pInfo->bVOverlapped = bVOverlapped;
+ pInfo->bAutoFilter = bAutoFilter;
+ pInfo->bPivotButton = bPivotButton;
+ pInfo->bPivotPopupButton = bPivotPopupButton;
+ pInfo->bFilterActive = bFilterActive;
+ pInfo->pLinesAttr = pLinesAttr;
+ pInfo->mpTLBRLine = pTLBRLine;
+ pInfo->mpBLTRLine = pBLTRLine;
+ pInfo->pShadowAttr = pShadowAttr;
+ // nWidth is no longer set individually
+
+ if (bScenario)
+ {
+ pInfo->pBackground = ScGlobal::GetButtonBrushItem();
+ pThisRowInfo->bEmptyBack = false;
+ }
+
+ if (bContainsCondFormat && pCondFormList)
+ {
+ bAnyCondition |= handleConditionalFormat(*pCondFormList, rCondFormats,
+ pInfo, &rTabInfo, pStlPool, ScAddress(nCol, nCurRow, nTab),
+ bHidden, bHideFormula, bTabProtect);
+ }
+
+ if (bHidden || (bFormulaMode && bHideFormula && pInfo->maCell.meType == CELLTYPE_FORMULA))
+ pBasicInfo->bEmptyCellText = true;
+
+ ++nArrRow;
+ }
+ else if (nLastHiddenRow >= 0)
+ {
+ nCurRow = nLastHiddenRow;
+ if (nCurRow > nThisRow)
+ nCurRow = nThisRow;
+ }
+ ++nCurRow;
+ }
+ while (nCurRow <= nThisRow && nCurRow <= nYExtra);
+ ++nIndex;
+ }
+ while ( nIndex < pThisAttrArr->Count() && nThisRow < nYExtra );
+
+ if (pMarkData && pMarkData->IsMultiMarked())
+ {
+ // Block marks
+ ScMarkArray aThisMarkArr(pMarkData->GetMarkArray( nCol ));
+ nArrRow = 1;
+ nCurRow = nRow1; // single rows
+
+ if ( aThisMarkArr.Search( nRow1, nIndex ) )
+ {
+ do
+ {
+ nThisRow=aThisMarkArr.mvData[nIndex].nRow; // End of range
+
+ do
+ {
+ if ( !RowHidden( nCurRow,nTab ) )
+ {
+ ++nArrRow;
+ }
+ ++nCurRow;
+ }
+ while (nCurRow <= nThisRow && nCurRow <= nRow2);
+ ++nIndex;
+ }
+ while ( nIndex < aThisMarkArr.mvData.size() && nThisRow < nRow2 );
+ }
+ }
+ }
+ else // columns in front
+ {
+ for (nArrRow=1; nArrRow+1<nArrCount; nArrRow++)
+ pRowInfo[nArrRow].basicCellInfo(nCol).nWidth = nThisWidth; //TODO: or check only 0 ??
+ }
+ }
+ }
+ else
+ pRowInfo[0].basicCellInfo(nCol).nWidth = STD_COL_WIDTH;
+ // STD_COL_WIDTH farthest to the left and right is needed for DrawExtraShadow
+ }
+
+ if (pCondFormList)
+ pCondFormList->endRendering();
+
+ // evaluate conditional formatting
+ std::vector< std::unique_ptr<ScPatternAttr> > aAltPatterns;
+ // favour preview over condition
+ if (bAnyCondition || bAnyPreview)
+ {
+ for (nArrRow=0; nArrRow<nArrCount; nArrRow++)
+ {
+ for (SCCOL nCol=nCol1-1; nCol<=nCol2+1; nCol++) // 1 more left and right
+ {
+ ScCellInfo* pInfo = &pRowInfo[nArrRow].cellInfo(nCol);
+ ScPatternAttr* pModifiedPatt = nullptr;
+
+ if ( ValidCol(nCol) && pRowInfo[nArrRow].nRowNo <= MaxRow() )
+ {
+ if ( ScStyleSheet* pPreviewStyle = GetPreviewCellStyle( nCol, pRowInfo[nArrRow].nRowNo, nTab ) )
+ {
+ aAltPatterns.push_back( std::make_unique<ScPatternAttr>( *pInfo->pPatternAttr ) );
+ pModifiedPatt = aAltPatterns.back().get();
+ pModifiedPatt->SetStyleSheet( pPreviewStyle );
+ }
+ }
+ // favour preview over condition
+ const SfxItemSet* pCondSet = pModifiedPatt ? &pModifiedPatt->GetItemSet() : pInfo->pConditionSet;
+
+ if (pCondSet)
+ {
+ // Background
+ if ( const SvxBrushItem* pItem = pCondSet->GetItemIfSet( ATTR_BACKGROUND ) )
+ {
+ pInfo->pBackground = pItem;
+ pRowInfo[nArrRow].bEmptyBack = false;
+ }
+
+ // Border
+ if ( const SvxBoxItem* pItem = pCondSet->GetItemIfSet( ATTR_BORDER ) )
+ pInfo->pLinesAttr = pItem;
+
+ if ( const SvxLineItem* pItem = pCondSet->GetItemIfSet( ATTR_BORDER_TLBR ) )
+ pInfo->mpTLBRLine = pItem;
+ if ( const SvxLineItem* pItem = pCondSet->GetItemIfSet( ATTR_BORDER_BLTR ) )
+ pInfo->mpBLTRLine = pItem;
+
+ // Shadow
+ if ( const SvxShadowItem* pItem = pCondSet->GetItemIfSet( ATTR_SHADOW ) )
+ {
+ pInfo->pShadowAttr = pItem;
+ bAnyShadow = true;
+ }
+ }
+ if( bAnyCondition && pInfo->mxColorScale)
+ {
+ pRowInfo[nArrRow].bEmptyBack = false;
+ pInfo->pBackground = &pPool->Put(SvxBrushItem(*pInfo->mxColorScale, ATTR_BACKGROUND));
+ }
+ }
+ }
+ }
+
+ // End conditional formatting
+
+ // Adjust data from merged cells
+
+ if (bAnyMerged)
+ {
+ for (nArrRow=0; nArrRow<nArrCount; nArrRow++)
+ {
+ RowInfo* pThisRowInfo = &pRowInfo[nArrRow];
+ SCROW nSignedY = nArrRow ? pThisRowInfo->nRowNo : nRow1-1;
+
+ for (SCCOL nCol=nCol1-1; nCol<=nCol2+1; nCol++) // 1 more left and right
+ {
+ ScCellInfo* pInfo = &pThisRowInfo->cellInfo(nCol);
+
+ if (pInfo->bMerged || pInfo->bHOverlapped || pInfo->bVOverlapped)
+ {
+ SCCOL nStartX;
+ SCROW nStartY;
+ SCCOL nEndX;
+ SCROW nEndY;
+ lcl_GetMergeRange( nCol,nSignedY, nArrRow, this,pRowInfo, nCol1,nRow1,nTab,
+ nStartX,nStartY, nEndX,nEndY );
+ const ScPatternAttr* pStartPattern = GetPattern( nStartX,nStartY,nTab );
+ const SfxItemSet* pStartCond = GetCondResult( nStartX,nStartY,nTab );
+
+ // Copy Background (or in output.cxx)
+
+ const SvxBrushItem* pBrushItem = nullptr;
+ if ( !pStartCond ||
+ !(pBrushItem = pStartCond->GetItemIfSet(ATTR_BACKGROUND)) )
+ pBrushItem = &pStartPattern->GetItem(ATTR_BACKGROUND);
+ pInfo->pBackground = pBrushItem;
+ pRowInfo[nArrRow].bEmptyBack = false;
+
+ // Shadow
+
+ const SvxShadowItem* pShadowItem = nullptr;
+ if ( !pStartCond ||
+ !(pShadowItem = pStartCond->GetItemIfSet(ATTR_SHADOW)) )
+ pShadowItem = &pStartPattern->GetItem(ATTR_SHADOW);
+ pInfo->pShadowAttr = pShadowItem;
+ if (pInfo->pShadowAttr != pDefShadow)
+ bAnyShadow = true;
+ }
+ }
+ }
+ }
+
+ if (bAnyShadow) // distribute Shadow
+ {
+ for (nArrRow=0; nArrRow<nArrCount; nArrRow++)
+ {
+ bool bTop = ( nArrRow == 0 );
+ bool bBottom = ( nArrRow+1 == nArrCount );
+
+ for (SCCOL nCol=nCol1-1; nCol<=nCol2+1; nCol++) // 1 more left and right
+ {
+ bool bLeft = ( nCol == nCol1-1 );
+ bool bRight = ( nCol == nCol2+1 );
+
+ ScCellInfo* pInfo = &pRowInfo[nArrRow].cellInfo(nCol);
+ const SvxShadowItem* pThisAttr = pInfo->pShadowAttr;
+ SvxShadowLocation eLoc = pThisAttr ? pThisAttr->GetLocation() : SvxShadowLocation::NONE;
+ if (eLoc != SvxShadowLocation::NONE)
+ {
+ // or test on != eLoc
+
+ SCCOL nDxPos = 1;
+ SCCOL nDxNeg = -1;
+
+ while ( nCol+nDxPos < nCol2+1 && pRowInfo[0].basicCellInfo(nCol+nDxPos).nWidth == 0 )
+ ++nDxPos;
+ while ( nCol+nDxNeg > nCol1-1 && pRowInfo[0].basicCellInfo(nCol+nDxNeg).nWidth == 0 )
+ --nDxNeg;
+
+ bool bLeftDiff = !bLeft &&
+ pRowInfo[nArrRow].cellInfo(nCol+nDxNeg).pShadowAttr->GetLocation() == SvxShadowLocation::NONE;
+ bool bRightDiff = !bRight &&
+ pRowInfo[nArrRow].cellInfo(nCol+nDxPos).pShadowAttr->GetLocation() == SvxShadowLocation::NONE;
+ bool bTopDiff = !bTop &&
+ pRowInfo[nArrRow-1].cellInfo(nCol).pShadowAttr->GetLocation() == SvxShadowLocation::NONE;
+ bool bBottomDiff = !bBottom &&
+ pRowInfo[nArrRow+1].cellInfo(nCol).pShadowAttr->GetLocation() == SvxShadowLocation::NONE;
+
+ if ( bLayoutRTL )
+ {
+ switch (eLoc)
+ {
+ case SvxShadowLocation::BottomRight: eLoc = SvxShadowLocation::BottomLeft; break;
+ case SvxShadowLocation::BottomLeft: eLoc = SvxShadowLocation::BottomRight; break;
+ case SvxShadowLocation::TopRight: eLoc = SvxShadowLocation::TopLeft; break;
+ case SvxShadowLocation::TopLeft: eLoc = SvxShadowLocation::TopRight; break;
+ default:
+ {
+ // added to avoid warnings
+ }
+ }
+ }
+
+ switch (eLoc)
+ {
+ case SvxShadowLocation::BottomRight:
+ if (bBottomDiff)
+ {
+ pRowInfo[nArrRow+1].cellInfo(nCol).pHShadowOrigin = pThisAttr;
+ pRowInfo[nArrRow+1].cellInfo(nCol).eHShadowPart =
+ bLeftDiff ? SC_SHADOW_HSTART : SC_SHADOW_HORIZ;
+ }
+ if (bRightDiff)
+ {
+ pRowInfo[nArrRow].cellInfo(nCol+1).pVShadowOrigin = pThisAttr;
+ pRowInfo[nArrRow].cellInfo(nCol+1).eVShadowPart =
+ bTopDiff ? SC_SHADOW_VSTART : SC_SHADOW_VERT;
+ }
+ if (bBottomDiff && bRightDiff)
+ {
+ pRowInfo[nArrRow+1].cellInfo(nCol+1).pHShadowOrigin = pThisAttr;
+ pRowInfo[nArrRow+1].cellInfo(nCol+1).eHShadowPart = SC_SHADOW_CORNER;
+ }
+ break;
+
+ case SvxShadowLocation::BottomLeft:
+ if (bBottomDiff)
+ {
+ pRowInfo[nArrRow+1].cellInfo(nCol).pHShadowOrigin = pThisAttr;
+ pRowInfo[nArrRow+1].cellInfo(nCol).eHShadowPart =
+ bRightDiff ? SC_SHADOW_HSTART : SC_SHADOW_HORIZ;
+ }
+ if (bLeftDiff)
+ {
+ pRowInfo[nArrRow].cellInfo(nCol-1).pVShadowOrigin = pThisAttr;
+ pRowInfo[nArrRow].cellInfo(nCol-1).eVShadowPart =
+ bTopDiff ? SC_SHADOW_VSTART : SC_SHADOW_VERT;
+ }
+ if (bBottomDiff && bLeftDiff)
+ {
+ pRowInfo[nArrRow+1].cellInfo(nCol-1).pHShadowOrigin = pThisAttr;
+ pRowInfo[nArrRow+1].cellInfo(nCol-1).eHShadowPart = SC_SHADOW_CORNER;
+ }
+ break;
+
+ case SvxShadowLocation::TopRight:
+ if (bTopDiff)
+ {
+ pRowInfo[nArrRow-1].cellInfo(nCol).pHShadowOrigin = pThisAttr;
+ pRowInfo[nArrRow-1].cellInfo(nCol).eHShadowPart =
+ bLeftDiff ? SC_SHADOW_HSTART : SC_SHADOW_HORIZ;
+ }
+ if (bRightDiff)
+ {
+ pRowInfo[nArrRow].cellInfo(nCol+1).pVShadowOrigin = pThisAttr;
+ pRowInfo[nArrRow].cellInfo(nCol+1).eVShadowPart =
+ bBottomDiff ? SC_SHADOW_VSTART : SC_SHADOW_VERT;
+ }
+ if (bTopDiff && bRightDiff)
+ {
+ pRowInfo[nArrRow-1].cellInfo(nCol+1).pHShadowOrigin = pThisAttr;
+ pRowInfo[nArrRow-1].cellInfo(nCol+1).eHShadowPart = SC_SHADOW_CORNER;
+ }
+ break;
+
+ case SvxShadowLocation::TopLeft:
+ if (bTopDiff)
+ {
+ pRowInfo[nArrRow-1].cellInfo(nCol).pHShadowOrigin = pThisAttr;
+ pRowInfo[nArrRow-1].cellInfo(nCol).eHShadowPart =
+ bRightDiff ? SC_SHADOW_HSTART : SC_SHADOW_HORIZ;
+ }
+ if (bLeftDiff)
+ {
+ pRowInfo[nArrRow].cellInfo(nCol-1).pVShadowOrigin = pThisAttr;
+ pRowInfo[nArrRow].cellInfo(nCol-1).eVShadowPart =
+ bBottomDiff ? SC_SHADOW_VSTART : SC_SHADOW_VERT;
+ }
+ if (bTopDiff && bLeftDiff)
+ {
+ pRowInfo[nArrRow-1].cellInfo(nCol-1).pHShadowOrigin = pThisAttr;
+ pRowInfo[nArrRow-1].cellInfo(nCol-1).eHShadowPart = SC_SHADOW_CORNER;
+ }
+ break;
+
+ default:
+ OSL_FAIL("wrong Shadow-Enum");
+ }
+ }
+ }
+ }
+ }
+
+ rTabInfo.mnArrCount = sal::static_int_cast<sal_uInt16>(nArrCount);
+ rTabInfo.mbPageMode = bPageMode;
+
+ // *** create the frame border array ***
+
+ // RowInfo structs are filled in the range [ 0 , nArrCount-1 ],
+ // each RowInfo contains ScCellInfo structs in the range [ nCol1-1 , nCol2+1 ]
+ // and ScBasicCellInfo structs in the range [ -1, nCol2+1 ]
+
+ size_t nColCount = nCol2 - nCol1 + 1 + 2;
+ size_t nRowCount = nArrCount;
+
+ svx::frame::Array& rArray = rTabInfo.maArray;
+ rArray.Initialize( nColCount, nRowCount );
+
+ for( size_t nRow = 0; nRow < nRowCount; ++nRow )
+ {
+ sal_uInt16 nCellInfoY = static_cast< sal_uInt16 >( nRow );
+ RowInfo& rThisRowInfo = pRowInfo[ nCellInfoY ];
+
+ for( SCCOL nCol = nCol1 - 1; nCol <= nCol2 + 1; ++nCol ) // 1 more left and right
+ {
+ const ScCellInfo& rInfo = rThisRowInfo.cellInfo( nCol );
+ const SvxBoxItem* pBox = rInfo.pLinesAttr;
+ const SvxLineItem* pTLBR = rInfo.mpTLBRLine;
+ const SvxLineItem* pBLTR = rInfo.mpBLTRLine;
+
+ size_t colToIndex = -(nCol1 - 1);
+ // These are rArray indexes (0-based), not really rows/columns.
+ size_t nX = nCol + colToIndex;
+ size_t nFirstCol = nX;
+ size_t nFirstRow = nRow;
+
+ // *** merged cells *** -------------------------------------------
+
+ if( !rArray.IsMerged( nX, nRow ) && (rInfo.bMerged || rInfo.bHOverlapped || rInfo.bVOverlapped) )
+ {
+ // *** insert merged range in svx::frame::Array ***
+
+ /* #i69369# top-left cell of a merged range may be located in
+ a hidden column or row. Use lcl_GetMergeRange() to find the
+ complete merged range, then calculate dimensions and
+ document position of the visible range. */
+
+ // note: document rows must be looked up in RowInfo structs
+
+ // current column and row in document coordinates
+ SCCOL nCurrDocCol = nCol;
+ SCROW nCurrDocRow = static_cast< SCROW >( (nCellInfoY > 0) ? rThisRowInfo.nRowNo : (nRow1 - 1) );
+
+ // find entire merged range in document, returns signed document coordinates
+ SCCOL nFirstRealDocColS, nLastRealDocColS;
+ SCROW nFirstRealDocRowS, nLastRealDocRowS;
+ lcl_GetMergeRange( nCurrDocCol, nCurrDocRow,
+ nCellInfoY, this, pRowInfo, nCol1,nRow1,nTab,
+ nFirstRealDocColS, nFirstRealDocRowS, nLastRealDocColS, nLastRealDocRowS );
+
+ // *complete* merged range in document coordinates
+ SCCOL nFirstRealDocCol = nFirstRealDocColS;
+ SCROW nFirstRealDocRow = nFirstRealDocRowS;
+ SCCOL nLastRealDocCol = nLastRealDocColS;
+ SCROW nLastRealDocRow = nLastRealDocRowS;
+
+ // first visible column (nCol1-1 is first processed document column)
+ SCCOL nFirstDocCol = (nCol1 > 0) ? ::std::max< SCCOL >( nFirstRealDocCol, nCol1 - 1 ) : nFirstRealDocCol;
+ nFirstCol = nFirstDocCol + colToIndex;
+
+ // last visible column (nCol2+1 is last processed document column)
+ SCCOL nLastDocCol = (nCol2 < MaxCol()) ? ::std::min< SCCOL >( nLastRealDocCol, nCol2 + 1 ) : nLastRealDocCol;
+ size_t nLastCol = nLastDocCol + colToIndex;
+
+ // first visible row
+ sal_uInt16 nFirstCellInfoY = nCellInfoY;
+ while( ((nFirstCellInfoY > 1) && (pRowInfo[ nFirstCellInfoY - 1 ].nRowNo >= nFirstRealDocRow)) ||
+ ((nFirstCellInfoY == 1) && (static_cast< SCROW >( nRow1 - 1 ) >= nFirstRealDocRow)) )
+ --nFirstCellInfoY;
+ SCROW nFirstDocRow = (nFirstCellInfoY > 0) ? pRowInfo[ nFirstCellInfoY ].nRowNo : static_cast< SCROW >( nRow1 - 1 );
+ nFirstRow = static_cast< size_t >( nFirstCellInfoY );
+
+ // last visible row
+ sal_uInt16 nLastCellInfoY = nCellInfoY;
+ while( (sal::static_int_cast<SCSIZE>(nLastCellInfoY + 1) < nArrCount) &&
+ (pRowInfo[ nLastCellInfoY + 1 ].nRowNo <= nLastRealDocRow) )
+ ++nLastCellInfoY;
+ SCROW nLastDocRow = (nLastCellInfoY > 0) ? pRowInfo[ nLastCellInfoY ].nRowNo : static_cast< SCROW >( nRow1 - 1 );
+ size_t nLastRow = static_cast< size_t >( nLastCellInfoY );
+
+ // insert merged range
+ rArray.SetMergedRange( nFirstCol, nFirstRow, nLastCol, nLastRow );
+
+ // *** find additional size not included in svx::frame::Array ***
+
+ // additional space before first column
+ if( nFirstCol == 0 )
+ {
+ tools::Long nSize = 0;
+ for( SCCOL nDocCol = nFirstRealDocCol; nDocCol < nFirstDocCol; ++nDocCol )
+ nSize += std::max( tools::Long(GetColWidth( nDocCol, nTab ) * fColScale), tools::Long(1) );
+ rArray.SetAddMergedLeftSize( nX, nRow, nSize );
+ }
+ // additional space after last column
+ if( nLastCol + 1 == nColCount )
+ {
+ tools::Long nSize = 0;
+ for( SCCOL nDocCol = nLastDocCol + 1; nDocCol <= nLastRealDocCol; ++nDocCol )
+ nSize += std::max( tools::Long(GetColWidth( nDocCol, nTab ) * fColScale), tools::Long(1) );
+ rArray.SetAddMergedRightSize( nX, nRow, nSize );
+ }
+ // additional space above first row
+ if( nFirstRow == 0 )
+ {
+ tools::Long nSize = 0;
+ for( SCROW nDocRow = nFirstRealDocRow; nDocRow < nFirstDocRow; ++nDocRow )
+ nSize += std::max( tools::Long(GetRowHeight( nDocRow, nTab ) * fRowScale), tools::Long(1) );
+ rArray.SetAddMergedTopSize( nX, nRow, nSize );
+ }
+ // additional space beyond last row
+ if( nLastRow + 1 == nRowCount )
+ {
+ tools::Long nSize = 0;
+ for( SCROW nDocRow = nLastDocRow + 1; nDocRow <= nLastRealDocRow; ++nDocRow )
+ nSize += std::max( tools::Long(GetRowHeight( nDocRow, nTab ) * fRowScale), tools::Long(1) );
+ rArray.SetAddMergedBottomSize( nX, nRow, nSize );
+ }
+
+ // *** use line attributes from real origin cell ***
+
+ if( (nFirstRealDocCol != nCurrDocCol) || (nFirstRealDocRow != nCurrDocRow) )
+ {
+ if( const ScPatternAttr* pPattern = GetPattern( nFirstRealDocCol, nFirstRealDocRow, nTab ) )
+ {
+ const SfxItemSet* pCond = GetCondResult( nFirstRealDocCol, nFirstRealDocRow, nTab );
+ pBox = &pPattern->GetItem( ATTR_BORDER, pCond );
+ pTLBR = &pPattern->GetItem( ATTR_BORDER_TLBR, pCond );
+ pBLTR = &pPattern->GetItem( ATTR_BORDER_BLTR, pCond );
+ }
+ else
+ {
+ pBox = nullptr;
+ pTLBR = pBLTR = nullptr;
+ }
+ }
+ }
+
+ // *** borders *** ------------------------------------------------
+
+ if( pBox )
+ {
+ rArray.SetCellStyleLeft( nFirstCol, nFirstRow, svx::frame::Style( pBox->GetLeft(), fColScale ) );
+ rArray.SetCellStyleRight( nFirstCol, nFirstRow, svx::frame::Style( pBox->GetRight(), fColScale ) );
+ rArray.SetCellStyleTop( nFirstCol, nFirstRow, svx::frame::Style( pBox->GetTop(), fRowScale ) );
+ rArray.SetCellStyleBottom( nFirstCol, nFirstRow, svx::frame::Style( pBox->GetBottom(), fRowScale ) );
+ }
+
+ if( pTLBR )
+ rArray.SetCellStyleTLBR( nFirstCol, nFirstRow, svx::frame::Style( pTLBR->GetLine(), fRowScale ) );
+ if( pBLTR )
+ rArray.SetCellStyleBLTR( nFirstCol, nFirstRow, svx::frame::Style( pBLTR->GetLine(), fRowScale ) );
+ }
+ }
+
+ /* Mirror the entire frame array. */
+ if( bLayoutRTL )
+ rArray.MirrorSelfX();
+}
+
+ScTableInfo::ScTableInfo(const SCSIZE capacity)
+ : mpRowInfo(new RowInfo[capacity])
+ , mnArrCount(0)
+ , mnArrCapacity(capacity)
+ , mbPageMode(false)
+{
+ memset(static_cast<void*>(mpRowInfo.get()), 0, mnArrCapacity * sizeof(RowInfo));
+}
+
+ScTableInfo::~ScTableInfo()
+{
+ for( SCSIZE nIdx = 0; nIdx < mnArrCapacity; ++nIdx )
+ mpRowInfo[ nIdx ].freeCellInfo();
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sc/source/core/data/formulacell.cxx b/sc/source/core/data/formulacell.cxx
new file mode 100644
index 000000000..f2d840cb9
--- /dev/null
+++ b/sc/source/core/data/formulacell.cxx
@@ -0,0 +1,5571 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4; fill-column: 100 -*- */
+/*
+ * 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 <config_feature_opencl.h>
+
+#include <sal/config.h>
+#include <sal/log.hxx>
+#include <osl/diagnose.h>
+
+#include <cassert>
+#include <cstdlib>
+
+#include <formulacell.hxx>
+#include <grouptokenconverter.hxx>
+
+#include <compiler.hxx>
+#include <document.hxx>
+#include <cellvalue.hxx>
+#include <interpre.hxx>
+#include <macromgr.hxx>
+#include <refupdat.hxx>
+#include <recursionhelper.hxx>
+#include <docoptio.hxx>
+#include <rangenam.hxx>
+#include <rangelst.hxx>
+#include <dbdata.hxx>
+#include <progress.hxx>
+#include <scmatrix.hxx>
+#include <rechead.hxx>
+#include <scitems.hxx>
+#include <validat.hxx>
+#include <editutil.hxx>
+#include <chgtrack.hxx>
+#include <tokenarray.hxx>
+
+#include <comphelper/threadpool.hxx>
+#include <editeng/editobj.hxx>
+#include <formula/errorcodes.hxx>
+#include <svl/intitem.hxx>
+#include <svl/numformat.hxx>
+#include <formulagroup.hxx>
+#include <listenercontext.hxx>
+#include <types.hxx>
+#include <scopetools.hxx>
+#include <refupdatecontext.hxx>
+#include <tokenstringcontext.hxx>
+#include <refhint.hxx>
+#include <listenerquery.hxx>
+#include <listenerqueryids.hxx>
+#include <grouparealistener.hxx>
+#include <formulalogger.hxx>
+#include <com/sun/star/sheet/FormulaLanguage.hpp>
+
+#if HAVE_FEATURE_OPENCL
+#include <opencl/openclwrapper.hxx>
+#endif
+
+#include <memory>
+#include <map>
+
+using namespace formula;
+
+#define DEBUG_CALCULATION 0
+#if DEBUG_CALCULATION
+static bool bDebugCalculationActive = false; // Set to true for global active init,
+static ScAddress aDebugCalculationTriggerAddress(1,2,0); // or on cell Sheet1.B3, whatever you like
+
+struct DebugCalculationEntry
+{
+ ScAddress maPos;
+ OUString maResult;
+ const ScDocument& mrDoc;
+ sal_uInt32 mnGroup;
+ sal_uInt16 mnRecursion;
+
+ DebugCalculationEntry( const ScAddress& rPos, ScDocument& rDoc, sal_uInt32 nGroup ) :
+ maPos(rPos),
+ mrDoc(rDoc),
+ mnGroup(nGroup),
+ mnRecursion(rDoc.GetRecursionHelper().GetRecursionCount())
+ {
+ }
+};
+
+/** Debug/dump formula cell calculation chain.
+ Either, somewhere set aDC.mbActive=true, or
+ aDC.maTrigger=ScAddress(col,row,tab) of interest from where to start.
+ This does not work for deep recursion > MAXRECURSION, the results are
+ somewhat... funny... ;)
+ */
+static struct DebugCalculation
+{
+ std::vector< DebugCalculationEntry > mvPos;
+ std::vector< DebugCalculationEntry > mvResults;
+ ScAddress maTrigger;
+ sal_uInt32 mnGroup;
+ bool mbActive;
+ bool mbSwitchOff;
+ bool mbPrint;
+ bool mbPrintResults;
+
+ DebugCalculation() : mnGroup(0), mbActive(bDebugCalculationActive), mbSwitchOff(false),
+ mbPrint(true), mbPrintResults(false) {}
+
+ /** Print chain in encountered dependency order. */
+ void print() const
+ {
+ for (auto const& it : mvPos)
+ {
+ OUString aStr( it.maPos.Format( ScRefFlags::VALID | ScRefFlags::TAB_3D, &it.mrDoc) +
+ " [" + OUString::number( it.mnRecursion) + "," + OUString::number( it.mnGroup) + "]");
+ fprintf( stderr, "%s -> ", aStr.toUtf8().getStr());
+ }
+ fprintf( stderr, "%s", "END\n");
+ }
+
+ /** Print chain results. */
+ void printResults() const
+ {
+ for (auto const& it : mvResults)
+ {
+ OUString aStr( it.maPos.Format( ScRefFlags::VALID | ScRefFlags::TAB_3D, &it.mrDoc));
+ aStr += " (" + it.maResult + ")";
+ fprintf( stderr, "%s, ", aStr.toUtf8().getStr());
+ }
+ fprintf( stderr, "%s", "END\n");
+ }
+
+ void storeResult( const svl::SharedString& rStr )
+ {
+ if (mbActive && !mvPos.empty())
+ mvPos.back().maResult = "\"" + rStr.getString() + "\"";
+ }
+
+ void storeResult( const double& fVal )
+ {
+ if (mbActive && !mvPos.empty())
+ mvPos.back().maResult = rtl::math::doubleToUString( fVal, rtl_math_StringFormat_G, 2, '.', true);
+ }
+
+ void storeResultError( FormulaError nErr )
+ {
+ if (mbActive && !mvPos.empty())
+ mvPos.back().maResult = "Err:" + OUString::number( int( nErr ));
+ }
+
+ void enterGroup()
+ {
+ ++mnGroup;
+ }
+
+ void leaveGroup()
+ {
+ --mnGroup;
+ }
+
+} aDC;
+
+struct DebugCalculationStacker
+{
+ DebugCalculationStacker( const ScAddress& rPos, ScDocument& rDoc )
+ {
+ if (!aDC.mbActive && rPos == aDC.maTrigger)
+ aDC.mbActive = aDC.mbSwitchOff = true;
+ if (aDC.mbActive)
+ {
+ aDC.mvPos.push_back( DebugCalculationEntry( rPos, rDoc, aDC.mnGroup));
+ aDC.mbPrint = true;
+ }
+ }
+
+ ~DebugCalculationStacker()
+ {
+ if (aDC.mbActive)
+ {
+ if (!aDC.mvPos.empty())
+ {
+ if (aDC.mbPrint)
+ {
+ aDC.print();
+ aDC.mbPrint = false;
+ }
+ if (aDC.mbPrintResults)
+ {
+ // Store results until final result is available, reversing order.
+ aDC.mvResults.push_back( aDC.mvPos.back());
+ }
+ aDC.mvPos.pop_back();
+ if (aDC.mbPrintResults && aDC.mvPos.empty())
+ {
+ aDC.printResults();
+ std::vector< DebugCalculationEntry >().swap( aDC.mvResults);
+ }
+ if (aDC.mbSwitchOff && aDC.mvPos.empty())
+ aDC.mbActive = false;
+ }
+ }
+ }
+};
+#endif
+
+namespace {
+
+// More or less arbitrary, of course all recursions must fit into available
+// stack space (which is what on all systems we don't know yet?). Choosing a
+// lower value may be better than trying a much higher value that also isn't
+// sufficient but temporarily leads to high memory consumption. On the other
+// hand, if the value fits all recursions, execution is quicker as no resumes
+// are necessary. Could be made a configurable option.
+// Allow for a year's calendar (366).
+const sal_uInt16 MAXRECURSION = 400;
+
+typedef SCCOLROW(*DimensionSelector)(const ScDocument&, const ScAddress&, const ScSingleRefData&);
+
+SCCOLROW lcl_GetCol(const ScDocument& rDoc, const ScAddress& rPos, const ScSingleRefData& rData)
+{
+ return rData.toAbs(rDoc, rPos).Col();
+}
+
+SCCOLROW lcl_GetRow(const ScDocument& rDoc, const ScAddress& rPos, const ScSingleRefData& rData)
+{
+ return rData.toAbs(rDoc, rPos).Row();
+}
+
+SCCOLROW lcl_GetTab(const ScDocument& rDoc, const ScAddress& rPos, const ScSingleRefData& rData)
+{
+ return rData.toAbs(rDoc, rPos).Tab();
+}
+
+/** Check if both references span the same range in selected dimension.
+ */
+bool
+lcl_checkRangeDimension(
+ const ScDocument& rDoc,
+ const ScAddress& rPos, const SingleDoubleRefProvider& rRef1, const SingleDoubleRefProvider& rRef2,
+ const DimensionSelector aWhich)
+{
+ return aWhich(rDoc, rPos, rRef1.Ref1) == aWhich(rDoc, rPos, rRef2.Ref1) &&
+ aWhich(rDoc, rPos, rRef1.Ref2) == aWhich(rDoc, rPos, rRef2.Ref2);
+}
+
+bool
+lcl_checkRangeDimensions(
+ const ScDocument& rDoc,
+ const ScAddress& rPos, const SingleDoubleRefProvider& rRef1, const SingleDoubleRefProvider& rRef2,
+ bool& bCol, bool& bRow, bool& bTab)
+{
+ const bool bSameCols(lcl_checkRangeDimension(rDoc, rPos, rRef1, rRef2, lcl_GetCol));
+ const bool bSameRows(lcl_checkRangeDimension(rDoc, rPos, rRef1, rRef2, lcl_GetRow));
+ const bool bSameTabs(lcl_checkRangeDimension(rDoc, rPos, rRef1, rRef2, lcl_GetTab));
+
+ // Test if exactly two dimensions are equal
+ if (int(bSameCols) + int(bSameRows) + int(bSameTabs) == 2)
+ {
+ bCol = !bSameCols;
+ bRow = !bSameRows;
+ bTab = !bSameTabs;
+ return true;
+ }
+ return false;
+}
+
+/** Check if references in given reference list can possibly
+ form a range. To do that, two of their dimensions must be the same.
+ */
+bool
+lcl_checkRangeDimensions(
+ const ScDocument& rDoc, const ScAddress& rPos,
+ const std::vector<formula::FormulaToken*>::const_iterator& rBegin,
+ const std::vector<formula::FormulaToken*>::const_iterator& rEnd,
+ bool& bCol, bool& bRow, bool& bTab)
+{
+ std::vector<formula::FormulaToken*>::const_iterator aCur(rBegin);
+ ++aCur;
+ const SingleDoubleRefProvider aRef(**rBegin);
+ bool bOk(false);
+ {
+ const SingleDoubleRefProvider aRefCur(**aCur);
+ bOk = lcl_checkRangeDimensions(rDoc, rPos, aRef, aRefCur, bCol, bRow, bTab);
+ }
+ while (bOk && aCur != rEnd)
+ {
+ const SingleDoubleRefProvider aRefCur(**aCur);
+ bool bColTmp(false);
+ bool bRowTmp(false);
+ bool bTabTmp(false);
+ bOk = lcl_checkRangeDimensions(rDoc, rPos, aRef, aRefCur, bColTmp, bRowTmp, bTabTmp);
+ bOk = bOk && (bCol == bColTmp && bRow == bRowTmp && bTab == bTabTmp);
+ ++aCur;
+ }
+
+ return bOk && aCur == rEnd;
+}
+
+class LessByReference
+{
+ const ScDocument& mrDoc;
+ ScAddress maPos;
+ DimensionSelector maFunc;
+public:
+ LessByReference(const ScDocument& rDoc, const ScAddress& rPos, const DimensionSelector& rFunc) :
+ mrDoc(rDoc), maPos(rPos), maFunc(rFunc) {}
+
+ bool operator() (const formula::FormulaToken* pRef1, const formula::FormulaToken* pRef2)
+ {
+ const SingleDoubleRefProvider aRef1(*pRef1);
+ const SingleDoubleRefProvider aRef2(*pRef2);
+ return maFunc(mrDoc, maPos, aRef1.Ref1) < maFunc(mrDoc, maPos, aRef2.Ref1);
+ }
+};
+
+/**
+ * Returns true if range denoted by token p2 starts immediately after range
+ * denoted by token p1. Dimension, in which the comparison takes place, is
+ * given by maFunc.
+ */
+class AdjacentByReference
+{
+ const ScDocument& mrDoc;
+ ScAddress maPos;
+ DimensionSelector maFunc;
+public:
+ AdjacentByReference(const ScDocument& rDoc, const ScAddress& rPos, DimensionSelector aFunc) :
+ mrDoc(rDoc), maPos(rPos), maFunc(aFunc) {}
+
+ bool operator() (const formula::FormulaToken* p1, const formula::FormulaToken* p2)
+ {
+ const SingleDoubleRefProvider aRef1(*p1);
+ const SingleDoubleRefProvider aRef2(*p2);
+ return maFunc(mrDoc, maPos, aRef2.Ref1) - maFunc(mrDoc, maPos, aRef1.Ref2) == 1;
+ }
+};
+
+bool
+lcl_checkIfAdjacent(
+ const ScDocument& rDoc,
+ const ScAddress& rPos, const std::vector<formula::FormulaToken*>& rReferences, const DimensionSelector aWhich)
+{
+ auto aBegin(rReferences.cbegin());
+ auto aEnd(rReferences.cend());
+ auto aBegin1(aBegin);
+ ++aBegin1;
+ --aEnd;
+ return std::equal(aBegin, aEnd, aBegin1, AdjacentByReference(rDoc, rPos, aWhich));
+}
+
+void
+lcl_fillRangeFromRefList(
+ const ScDocument& rDoc,
+ const ScAddress& aPos, const std::vector<formula::FormulaToken*>& rReferences, ScRange& rRange)
+{
+ const ScSingleRefData aStart(
+ SingleDoubleRefProvider(*rReferences.front()).Ref1);
+ rRange.aStart = aStart.toAbs(rDoc, aPos);
+ const ScSingleRefData aEnd(
+ SingleDoubleRefProvider(*rReferences.back()).Ref2);
+ rRange.aEnd = aEnd.toAbs(rDoc, aPos);
+}
+
+bool
+lcl_refListFormsOneRange(
+ const ScDocument& rDoc,
+ const ScAddress& rPos, std::vector<formula::FormulaToken*>& rReferences,
+ ScRange& rRange)
+{
+ if (rReferences.size() == 1)
+ {
+ lcl_fillRangeFromRefList(rDoc, rPos, rReferences, rRange);
+ return true;
+ }
+
+ bool bCell(false);
+ bool bRow(false);
+ bool bTab(false);
+ if (lcl_checkRangeDimensions(rDoc, rPos, rReferences.begin(), rReferences.end(), bCell, bRow, bTab))
+ {
+ DimensionSelector aWhich;
+ if (bCell)
+ {
+ aWhich = lcl_GetCol;
+ }
+ else if (bRow)
+ {
+ aWhich = lcl_GetRow;
+ }
+ else if (bTab)
+ {
+ aWhich = lcl_GetTab;
+ }
+ else
+ {
+ OSL_FAIL( "lcl_checkRangeDimensions shouldn't allow that!");
+ aWhich = lcl_GetRow; // initialize to avoid warning
+ }
+
+ // Sort the references by start of range
+ std::sort(rReferences.begin(), rReferences.end(), LessByReference(rDoc, rPos, aWhich));
+ if (lcl_checkIfAdjacent(rDoc, rPos, rReferences, aWhich))
+ {
+ lcl_fillRangeFromRefList(rDoc, rPos, rReferences, rRange);
+ return true;
+ }
+ }
+ return false;
+}
+
+bool lcl_isReference(const FormulaToken& rToken)
+{
+ return
+ rToken.GetType() == svSingleRef ||
+ rToken.GetType() == svDoubleRef;
+}
+
+void adjustRangeName(formula::FormulaToken* pToken, ScDocument& rNewDoc, const ScDocument& rOldDoc,
+ const ScAddress& rNewPos, const ScAddress& rOldPos, bool bGlobalNamesToLocal)
+{
+ ScRangeData* pRangeData = nullptr;
+ SCTAB nSheet = pToken->GetSheet();
+ sal_uInt16 nIndex = pToken->GetIndex();
+ if (!rOldDoc.CopyAdjustRangeName( nSheet, nIndex, pRangeData, rNewDoc, rNewPos, rOldPos, bGlobalNamesToLocal, true))
+ return; // nothing to do
+
+ if (!pRangeData)
+ {
+ // If this happened we have a real problem.
+ pToken->SetIndex(0);
+ assert(!"inserting the range name should not fail");
+ return;
+ }
+
+ pToken->SetIndex(nIndex);
+ pToken->SetSheet(nSheet);
+}
+
+void adjustDBRange(formula::FormulaToken* pToken, ScDocument& rNewDoc, const ScDocument& rOldDoc)
+{
+ ScDBCollection* pOldDBCollection = rOldDoc.GetDBCollection();
+ if (!pOldDBCollection)
+ return;//strange error case, don't do anything
+ ScDBCollection::NamedDBs& aOldNamedDBs = pOldDBCollection->getNamedDBs();
+ ScDBData* pDBData = aOldNamedDBs.findByIndex(pToken->GetIndex());
+ if (!pDBData)
+ return; //invalid index
+ OUString aDBName = pDBData->GetUpperName();
+
+ //search in new document
+ ScDBCollection* pNewDBCollection = rNewDoc.GetDBCollection();
+ if (!pNewDBCollection)
+ {
+ rNewDoc.SetDBCollection(std::unique_ptr<ScDBCollection>(new ScDBCollection(rNewDoc)));
+ pNewDBCollection = rNewDoc.GetDBCollection();
+ }
+ ScDBCollection::NamedDBs& aNewNamedDBs = pNewDBCollection->getNamedDBs();
+ ScDBData* pNewDBData = aNewNamedDBs.findByUpperName(aDBName);
+ if (!pNewDBData)
+ {
+ pNewDBData = new ScDBData(*pDBData);
+ bool ins = aNewNamedDBs.insert(std::unique_ptr<ScDBData>(pNewDBData));
+ assert(ins); (void)ins;
+ }
+ pToken->SetIndex(pNewDBData->GetIndex());
+}
+
+}
+
+bool AreaListenerKey::operator < ( const AreaListenerKey& r ) const
+{
+ if (maRange.aStart.Tab() != r.maRange.aStart.Tab())
+ return maRange.aStart.Tab() < r.maRange.aStart.Tab();
+ if (maRange.aStart.Col() != r.maRange.aStart.Col())
+ return maRange.aStart.Col() < r.maRange.aStart.Col();
+ if (maRange.aStart.Row() != r.maRange.aStart.Row())
+ return maRange.aStart.Row() < r.maRange.aStart.Row();
+ if (maRange.aEnd.Tab() != r.maRange.aEnd.Tab())
+ return maRange.aEnd.Tab() < r.maRange.aEnd.Tab();
+ if (maRange.aEnd.Col() != r.maRange.aEnd.Col())
+ return maRange.aEnd.Col() < r.maRange.aEnd.Col();
+ if (maRange.aEnd.Row() != r.maRange.aEnd.Row())
+ return maRange.aEnd.Row() < r.maRange.aEnd.Row();
+ if (mbStartFixed != r.mbStartFixed)
+ return r.mbStartFixed;
+ if (mbEndFixed != r.mbEndFixed)
+ return r.mbEndFixed;
+
+ return false;
+}
+
+ScFormulaCellGroup::ScFormulaCellGroup() :
+ mnRefCount(0),
+ mpTopCell(nullptr),
+ mnLength(0),
+ mnWeight(0),
+ mnFormatType(SvNumFormatType::NUMBER),
+ mbInvariant(false),
+ mbSubTotal(false),
+ mbPartOfCycle(false),
+ meCalcState(sc::GroupCalcEnabled)
+{
+}
+
+ScFormulaCellGroup::~ScFormulaCellGroup()
+{
+}
+
+void ScFormulaCellGroup::setCode( const ScTokenArray& rCode )
+{
+ mpCode = rCode.CloneValue();
+ mbInvariant = mpCode->IsInvariant();
+ mpCode->GenHash();
+}
+
+void ScFormulaCellGroup::compileCode(
+ ScDocument& rDoc, const ScAddress& rPos, FormulaGrammar::Grammar eGram )
+{
+ if (!mpCode)
+ return;
+
+ if (mpCode->GetLen() && mpCode->GetCodeError() == FormulaError::NONE && !mpCode->GetCodeLen())
+ {
+ bool bMatrixFormula = mpTopCell->GetMatrixFlag() != ScMatrixMode::NONE;
+ ScCompiler aComp(rDoc, rPos, *mpCode, eGram, true, bMatrixFormula);
+ mbSubTotal = aComp.CompileTokenArray();
+ mnFormatType = aComp.GetNumFormatType();
+ }
+ else
+ {
+ mbSubTotal = mpCode->HasOpCodeRPN( ocSubTotal ) || mpCode->HasOpCodeRPN( ocAggregate );
+ }
+}
+
+sc::FormulaGroupAreaListener* ScFormulaCellGroup::getAreaListener(
+ ScFormulaCell** ppTopCell, const ScRange& rRange, bool bStartFixed, bool bEndFixed )
+{
+ AreaListenerKey aKey(rRange, bStartFixed, bEndFixed);
+
+ AreaListenersType::iterator it = m_AreaListeners.lower_bound(aKey);
+ if (it == m_AreaListeners.end() || m_AreaListeners.key_comp()(aKey, it->first))
+ {
+ // Insert a new one.
+ it = m_AreaListeners.insert(
+ it, std::make_pair(aKey, std::make_unique<sc::FormulaGroupAreaListener>(
+ rRange, (*ppTopCell)->GetDocument(), (*ppTopCell)->aPos, mnLength, bStartFixed, bEndFixed)));
+ }
+
+ return it->second.get();
+}
+
+void ScFormulaCellGroup::endAllGroupListening( ScDocument& rDoc )
+{
+ for (const auto& rEntry : m_AreaListeners)
+ {
+ sc::FormulaGroupAreaListener *const pListener = rEntry.second.get();
+ ScRange aListenRange = pListener->getListeningRange();
+ // This "always listen" special range is never grouped.
+ bool bGroupListening = (aListenRange != BCA_LISTEN_ALWAYS);
+ rDoc.EndListeningArea(aListenRange, bGroupListening, pListener);
+ }
+
+ m_AreaListeners.clear();
+}
+
+ScFormulaCell::ScFormulaCell( ScDocument& rDoc, const ScAddress& rPos ) :
+ bDirty(false),
+ bTableOpDirty(false),
+ bChanged(false),
+ bRunning(false),
+ bCompile(false),
+ bSubTotal(false),
+ bIsIterCell(false),
+ bInChangeTrack(false),
+ bNeedListening(false),
+ mbNeedsNumberFormat(false),
+ mbAllowNumberFormatChange(false),
+ mbPostponedDirty(false),
+ mbIsExtRef(false),
+ mbSeenInPath(false),
+ mbFreeFlying(false),
+ cMatrixFlag(ScMatrixMode::NONE),
+ nSeenInIteration(0),
+ nFormatType(SvNumFormatType::NUMBER),
+ eTempGrammar(formula::FormulaGrammar::GRAM_DEFAULT),
+ pCode(new ScTokenArray(rDoc)),
+ rDocument(rDoc),
+ pPrevious(nullptr),
+ pNext(nullptr),
+ pPreviousTrack(nullptr),
+ pNextTrack(nullptr),
+ aPos(rPos)
+{
+}
+
+ScFormulaCell::ScFormulaCell( ScDocument& rDoc, const ScAddress& rPos,
+ const OUString& rFormula,
+ const FormulaGrammar::Grammar eGrammar,
+ ScMatrixMode cMatInd ) :
+ bDirty( true ), // -> Because of the use of the Auto Pilot Function was: cMatInd != 0
+ bTableOpDirty( false ),
+ bChanged( false ),
+ bRunning( false ),
+ bCompile( false ),
+ bSubTotal( false ),
+ bIsIterCell( false ),
+ bInChangeTrack( false ),
+ bNeedListening( false ),
+ mbNeedsNumberFormat( false ),
+ mbAllowNumberFormatChange(false),
+ mbPostponedDirty(false),
+ mbIsExtRef(false),
+ mbSeenInPath(false),
+ mbFreeFlying(false),
+ cMatrixFlag ( cMatInd ),
+ nSeenInIteration(0),
+ nFormatType ( SvNumFormatType::NUMBER ),
+ eTempGrammar( eGrammar),
+ pCode( nullptr ),
+ rDocument( rDoc ),
+ pPrevious(nullptr),
+ pNext(nullptr),
+ pPreviousTrack(nullptr),
+ pNextTrack(nullptr),
+ aPos(rPos)
+{
+ Compile( rFormula, true, eGrammar ); // bNoListening, Insert does that
+ if (!pCode)
+ // We need to have a non-NULL token array instance at all times.
+ pCode = new ScTokenArray(rDoc);
+}
+
+ScFormulaCell::ScFormulaCell(
+ ScDocument& rDoc, const ScAddress& rPos, std::unique_ptr<ScTokenArray> pArray,
+ const FormulaGrammar::Grammar eGrammar, ScMatrixMode cMatInd ) :
+ bDirty( true ),
+ bTableOpDirty( false ),
+ bChanged( false ),
+ bRunning( false ),
+ bCompile( false ),
+ bSubTotal( false ),
+ bIsIterCell( false ),
+ bInChangeTrack( false ),
+ bNeedListening( false ),
+ mbNeedsNumberFormat( false ),
+ mbAllowNumberFormatChange(false),
+ mbPostponedDirty(false),
+ mbIsExtRef(false),
+ mbSeenInPath(false),
+ mbFreeFlying(false),
+ cMatrixFlag ( cMatInd ),
+ nSeenInIteration(0),
+ nFormatType ( SvNumFormatType::NUMBER ),
+ eTempGrammar( eGrammar),
+ pCode(pArray.release()),
+ rDocument( rDoc ),
+ pPrevious(nullptr),
+ pNext(nullptr),
+ pPreviousTrack(nullptr),
+ pNextTrack(nullptr),
+ aPos(rPos)
+{
+ assert(pCode); // Never pass a NULL pointer here.
+
+ pCode->Finalize(); // Reduce memory usage if needed.
+
+ // Generate RPN token array.
+ if (pCode->GetLen() && pCode->GetCodeError() == FormulaError::NONE && !pCode->GetCodeLen())
+ {
+ ScCompiler aComp(rDocument, aPos, *pCode, eTempGrammar, true, cMatrixFlag != ScMatrixMode::NONE);
+ bSubTotal = aComp.CompileTokenArray();
+ nFormatType = aComp.GetNumFormatType();
+ }
+ else
+ {
+ if ( pCode->HasOpCodeRPN( ocSubTotal ) || pCode->HasOpCodeRPN( ocAggregate ) )
+ bSubTotal = true;
+ }
+
+ if (bSubTotal)
+ rDocument.AddSubTotalCell(this);
+
+ pCode->GenHash();
+}
+
+ScFormulaCell::ScFormulaCell(
+ ScDocument& rDoc, const ScAddress& rPos, const ScTokenArray& rArray,
+ const FormulaGrammar::Grammar eGrammar, ScMatrixMode cMatInd ) :
+ bDirty( true ),
+ bTableOpDirty( false ),
+ bChanged( false ),
+ bRunning( false ),
+ bCompile( false ),
+ bSubTotal( false ),
+ bIsIterCell( false ),
+ bInChangeTrack( false ),
+ bNeedListening( false ),
+ mbNeedsNumberFormat( false ),
+ mbAllowNumberFormatChange(false),
+ mbPostponedDirty(false),
+ mbIsExtRef(false),
+ mbSeenInPath(false),
+ mbFreeFlying(false),
+ cMatrixFlag ( cMatInd ),
+ nSeenInIteration(0),
+ nFormatType ( SvNumFormatType::NUMBER ),
+ eTempGrammar( eGrammar),
+ pCode(new ScTokenArray(rArray)), // also implicitly does Finalize() on the array
+ rDocument( rDoc ),
+ pPrevious(nullptr),
+ pNext(nullptr),
+ pPreviousTrack(nullptr),
+ pNextTrack(nullptr),
+ aPos(rPos)
+{
+ // RPN array generation
+ if( pCode->GetLen() && pCode->GetCodeError() == FormulaError::NONE && !pCode->GetCodeLen() )
+ {
+ ScCompiler aComp( rDocument, aPos, *pCode, eTempGrammar, true, cMatrixFlag != ScMatrixMode::NONE );
+ bSubTotal = aComp.CompileTokenArray();
+ nFormatType = aComp.GetNumFormatType();
+ }
+ else
+ {
+ if ( pCode->HasOpCodeRPN( ocSubTotal ) || pCode->HasOpCodeRPN( ocAggregate ) )
+ bSubTotal = true;
+ }
+
+ if (bSubTotal)
+ rDocument.AddSubTotalCell(this);
+
+ pCode->GenHash();
+}
+
+ScFormulaCell::ScFormulaCell(
+ ScDocument& rDoc, const ScAddress& rPos, const ScFormulaCellGroupRef& xGroup,
+ const FormulaGrammar::Grammar eGrammar, ScMatrixMode cInd ) :
+ mxGroup(xGroup),
+ bDirty(true),
+ bTableOpDirty( false ),
+ bChanged( false ),
+ bRunning( false ),
+ bCompile( false ),
+ bSubTotal(xGroup->mbSubTotal),
+ bIsIterCell( false ),
+ bInChangeTrack( false ),
+ bNeedListening( false ),
+ mbNeedsNumberFormat( false ),
+ mbAllowNumberFormatChange(false),
+ mbPostponedDirty(false),
+ mbIsExtRef(false),
+ mbSeenInPath(false),
+ mbFreeFlying(false),
+ cMatrixFlag ( cInd ),
+ nSeenInIteration(0),
+ nFormatType(xGroup->mnFormatType),
+ eTempGrammar( eGrammar),
+ pCode(xGroup->mpCode ? &*xGroup->mpCode : new ScTokenArray(rDoc)),
+ rDocument( rDoc ),
+ pPrevious(nullptr),
+ pNext(nullptr),
+ pPreviousTrack(nullptr),
+ pNextTrack(nullptr),
+ aPos(rPos)
+{
+ if (bSubTotal)
+ rDocument.AddSubTotalCell(this);
+}
+
+ScFormulaCell::ScFormulaCell(const ScFormulaCell& rCell, ScDocument& rDoc, const ScAddress& rPos, ScCloneFlags nCloneFlags) :
+ bDirty( rCell.bDirty ),
+ bTableOpDirty( false ),
+ bChanged( rCell.bChanged ),
+ bRunning( false ),
+ bCompile( rCell.bCompile ),
+ bSubTotal( rCell.bSubTotal ),
+ bIsIterCell( false ),
+ bInChangeTrack( false ),
+ bNeedListening( false ),
+ mbNeedsNumberFormat( rCell.mbNeedsNumberFormat ),
+ mbAllowNumberFormatChange(false),
+ mbPostponedDirty(false),
+ mbIsExtRef(false),
+ mbSeenInPath(false),
+ mbFreeFlying(false),
+ cMatrixFlag ( rCell.cMatrixFlag ),
+ nSeenInIteration(0),
+ nFormatType( rCell.nFormatType ),
+ aResult( rCell.aResult ),
+ eTempGrammar( rCell.eTempGrammar),
+ rDocument( rDoc ),
+ pPrevious(nullptr),
+ pNext(nullptr),
+ pPreviousTrack(nullptr),
+ pNextTrack(nullptr),
+ aPos(rPos)
+{
+ pCode = rCell.pCode->Clone().release();
+
+ // set back any errors and recompile
+ // not in the Clipboard - it must keep the received error flag
+ // Special Length=0: as bad cells are generated, then they are also retained
+ if ( pCode->GetCodeError() != FormulaError::NONE && !rDocument.IsClipboard() && pCode->GetLen() )
+ {
+ pCode->SetCodeError( FormulaError::NONE );
+ bCompile = true;
+ }
+ // Compile ColRowNames on URM_MOVE/URM_COPY _after_ UpdateReference !
+ bool bCompileLater = false;
+ bool bClipMode = rCell.rDocument.IsClipboard();
+
+ //update ScNameTokens
+ if (!rDocument.IsClipOrUndo() || rDoc.IsUndo())
+ {
+ if (!rDocument.IsClipboardSource() || aPos.Tab() != rCell.aPos.Tab())
+ {
+ bool bGlobalNamesToLocal = ((nCloneFlags & ScCloneFlags::NamesToLocal) != ScCloneFlags::Default);
+ formula::FormulaToken* pToken = nullptr;
+ formula::FormulaTokenArrayPlainIterator aIter(*pCode);
+ while((pToken = aIter.GetNextName())!= nullptr)
+ {
+ OpCode eOpCode = pToken->GetOpCode();
+ if (eOpCode == ocName)
+ adjustRangeName(pToken, rDoc, rCell.rDocument, aPos, rCell.aPos, bGlobalNamesToLocal);
+ else if (eOpCode == ocDBArea || eOpCode == ocTableRef)
+ adjustDBRange(pToken, rDoc, rCell.rDocument);
+ }
+ }
+
+ bool bCopyBetweenDocs = rDocument.GetPool() != rCell.rDocument.GetPool();
+ if (bCopyBetweenDocs && !(nCloneFlags & ScCloneFlags::NoMakeAbsExternal))
+ {
+ pCode->ReadjustAbsolute3DReferences(rCell.rDocument, rDoc, rCell.aPos);
+ }
+
+ pCode->AdjustAbsoluteRefs( rCell.rDocument, rCell.aPos, aPos, bCopyBetweenDocs );
+ }
+
+ if (!rDocument.IsClipOrUndo())
+ {
+ if (&rDocument.GetSharedStringPool() != &rCell.rDocument.GetSharedStringPool())
+ pCode->ReinternStrings( rDocument.GetSharedStringPool());
+ pCode->AdjustReferenceOnCopy( aPos);
+ }
+
+ if( !bCompile )
+ { // Name references with references and ColRowNames
+ formula::FormulaTokenArrayPlainIterator aIter(*pCode);
+ for (;;)
+ {
+ formula::FormulaToken* t = aIter.GetNextReferenceOrName();
+ if (!t || bCompile)
+ break;
+ if ( t->IsExternalRef() )
+ {
+ // External name, cell, and area references.
+ bCompile = true;
+ }
+ else if ( t->GetType() == svIndex )
+ {
+ const ScRangeData* pRangeData = rDoc.FindRangeNameBySheetAndIndex( t->GetSheet(), t->GetIndex());
+ if( pRangeData )
+ {
+ if( pRangeData->HasReferences() )
+ bCompile = true;
+ }
+ else
+ bCompile = true; // invalid reference!
+ }
+ else if ( t->GetOpCode() == ocColRowName )
+ {
+ bCompile = true; // new lookup needed
+ bCompileLater = bClipMode;
+ }
+ }
+ }
+ if( bCompile )
+ {
+ if ( !bCompileLater && bClipMode )
+ {
+ // Merging ranges needs the actual positions after UpdateReference.
+ // ColRowNames and TableRefs need new lookup after positions are
+ // adjusted.
+ bCompileLater = pCode->HasOpCode( ocRange) || pCode->HasOpCode( ocColRowName) ||
+ pCode->HasOpCode( ocTableRef);
+ }
+ if ( !bCompileLater )
+ {
+ // bNoListening, not at all if in Clipboard/Undo,
+ // and not from Clipboard either, instead after Insert(Clone) and UpdateReference.
+ CompileTokenArray( true );
+ }
+ }
+
+ if( nCloneFlags & ScCloneFlags::StartListening )
+ StartListeningTo( rDoc );
+
+ if (bSubTotal)
+ rDocument.AddSubTotalCell(this);
+}
+
+ScFormulaCell::~ScFormulaCell()
+{
+ rDocument.RemoveFromFormulaTrack( this );
+ rDocument.RemoveFromFormulaTree( this );
+ rDocument.RemoveSubTotalCell(this);
+ if (pCode->HasOpCode(ocMacro))
+ rDocument.GetMacroManager()->RemoveDependentCell(this);
+
+ if (rDocument.HasExternalRefManager())
+ rDocument.GetExternalRefManager()->removeRefCell(this);
+
+ if (!mxGroup || !mxGroup->mpCode)
+ // Formula token is not shared.
+ delete pCode;
+
+ if (mxGroup && mxGroup->mpTopCell == this)
+ mxGroup->mpTopCell = nullptr;
+}
+
+ScFormulaCell* ScFormulaCell::Clone() const
+{
+ return new ScFormulaCell(*this, rDocument, aPos);
+}
+
+ScFormulaCell* ScFormulaCell::Clone( const ScAddress& rPos ) const
+{
+ return new ScFormulaCell(*this, rDocument, rPos, ScCloneFlags::Default);
+}
+
+size_t ScFormulaCell::GetHash() const
+{
+ return pCode->GetHash();
+}
+
+OUString ScFormulaCell::GetFormula( const FormulaGrammar::Grammar eGrammar, const ScInterpreterContext* pContext ) const
+{
+ if( pCode->GetCodeError() != FormulaError::NONE && !pCode->GetLen() )
+ {
+ return ScGlobal::GetErrorString(pCode->GetCodeError());
+ }
+ OUStringBuffer buffer;
+ if( cMatrixFlag == ScMatrixMode::Reference )
+ {
+ // Reference to another cell that contains a matrix formula.
+ formula::FormulaTokenArrayPlainIterator aIter(*pCode);
+ formula::FormulaToken* p = aIter.GetNextReferenceRPN();
+ if( p )
+ {
+ /* FIXME: original GetFormula() code obtained
+ * pCell only if (!IsInChangeTrack()),
+ * GetEnglishFormula() omitted that test.
+ * Can we live without in all cases? */
+ ScFormulaCell* pCell = nullptr;
+ ScSingleRefData& rRef = *p->GetSingleRef();
+ ScAddress aAbs = rRef.toAbs(rDocument, aPos);
+ if (rDocument.ValidAddress(aAbs))
+ pCell = rDocument.GetFormulaCell(aAbs);
+
+ if (pCell)
+ {
+ return pCell->GetFormula( eGrammar, pContext );
+ }
+ else
+ {
+ ScCompiler aComp( rDocument, aPos, *pCode, eGrammar, false, false, pContext );
+ aComp.CreateStringFromTokenArray( buffer );
+ }
+ }
+ else
+ {
+ OSL_FAIL("ScFormulaCell::GetFormula: not a matrix");
+ }
+ }
+ else
+ {
+ ScCompiler aComp( rDocument, aPos, *pCode, eGrammar, false, false, pContext );
+ aComp.CreateStringFromTokenArray( buffer );
+ }
+
+ buffer.insert( 0, '=');
+ if( cMatrixFlag != ScMatrixMode::NONE )
+ {
+ buffer.insert( 0, '{');
+ buffer.append( '}');
+ }
+ return buffer.makeStringAndClear();
+}
+
+OUString ScFormulaCell::GetFormula( sc::CompileFormulaContext& rCxt, const ScInterpreterContext* pContext ) const
+{
+ OUStringBuffer aBuf;
+ if (pCode->GetCodeError() != FormulaError::NONE && !pCode->GetLen())
+ {
+ ScTokenArray aCode(rCxt.getDoc());
+ aCode.AddToken( FormulaErrorToken( pCode->GetCodeError()));
+ ScCompiler aComp(rCxt, aPos, aCode, false, false, pContext);
+ aComp.CreateStringFromTokenArray(aBuf);
+ return aBuf.makeStringAndClear();
+ }
+ else if( cMatrixFlag == ScMatrixMode::Reference )
+ {
+ // Reference to another cell that contains a matrix formula.
+ formula::FormulaTokenArrayPlainIterator aIter(*pCode);
+ formula::FormulaToken* p = aIter.GetNextReferenceRPN();
+ if( p )
+ {
+ /* FIXME: original GetFormula() code obtained
+ * pCell only if (!IsInChangeTrack()),
+ * GetEnglishFormula() omitted that test.
+ * Can we live without in all cases? */
+ ScFormulaCell* pCell = nullptr;
+ ScSingleRefData& rRef = *p->GetSingleRef();
+ ScAddress aAbs = rRef.toAbs(rDocument, aPos);
+ if (rDocument.ValidAddress(aAbs))
+ pCell = rDocument.GetFormulaCell(aAbs);
+
+ if (pCell)
+ {
+ return pCell->GetFormula(rCxt);
+ }
+ else
+ {
+ ScCompiler aComp(rCxt, aPos, *pCode, false, false, pContext);
+ aComp.CreateStringFromTokenArray(aBuf);
+ }
+ }
+ else
+ {
+ OSL_FAIL("ScFormulaCell::GetFormula: not a matrix");
+ }
+ }
+ else
+ {
+ ScCompiler aComp(rCxt, aPos, *pCode, false, false, pContext);
+ aComp.CreateStringFromTokenArray(aBuf);
+ }
+
+ aBuf.insert( 0, '=');
+ if( cMatrixFlag != ScMatrixMode::NONE )
+ {
+ aBuf.insert( 0, '{');
+ aBuf.append( '}');
+ }
+
+ return aBuf.makeStringAndClear();
+}
+
+void ScFormulaCell::GetResultDimensions( SCSIZE& rCols, SCSIZE& rRows )
+{
+ MaybeInterpret();
+
+ if (pCode->GetCodeError() == FormulaError::NONE && aResult.GetType() == svMatrixCell)
+ {
+ const ScMatrix* pMat = aResult.GetToken()->GetMatrix();
+ if (pMat)
+ {
+ pMat->GetDimensions( rCols, rRows );
+ if (pCode->IsHyperLink())
+ {
+ // Row 2 element is the URL that is not to be displayed and the
+ // result dimension not to be extended.
+ assert(rRows == 2);
+ rRows = 1;
+ }
+ return;
+ }
+ }
+ rCols = 0;
+ rRows = 0;
+}
+
+void ScFormulaCell::ResetDirty() { bDirty = bTableOpDirty = mbPostponedDirty = false; }
+void ScFormulaCell::SetNeedsListening( bool bVar ) { bNeedListening = bVar; }
+
+void ScFormulaCell::SetNeedsDirty( bool bVar )
+{
+ mbPostponedDirty = bVar;
+}
+
+void ScFormulaCell::SetNeedNumberFormat( bool bVal )
+{
+ mbNeedsNumberFormat = mbAllowNumberFormatChange = bVal;
+}
+
+void ScFormulaCell::Compile( const OUString& rFormula, bool bNoListening,
+ const FormulaGrammar::Grammar eGrammar )
+{
+ if ( rDocument.IsClipOrUndo() )
+ return;
+ bool bWasInFormulaTree = rDocument.IsInFormulaTree( this );
+ if ( bWasInFormulaTree )
+ rDocument.RemoveFromFormulaTree( this );
+ // pCode may not deleted for queries, but must be empty
+ if ( pCode )
+ pCode->Clear();
+ ScTokenArray* pCodeOld = pCode;
+ ScCompiler aComp( rDocument, aPos, eGrammar);
+ pCode = aComp.CompileString( rFormula ).release();
+ assert(!mxGroup);
+ delete pCodeOld;
+ if( pCode->GetCodeError() == FormulaError::NONE )
+ {
+ if ( !pCode->GetLen() && !aResult.GetHybridFormula().isEmpty() && rFormula == aResult.GetHybridFormula() )
+ { // not recursive CompileTokenArray/Compile/CompileTokenArray
+ if ( rFormula[0] == '=' )
+ pCode->AddBad( rFormula.copy(1) );
+ else
+ pCode->AddBad( rFormula );
+ }
+ bCompile = true;
+ CompileTokenArray( bNoListening );
+ }
+ else
+ bChanged = true;
+
+ if ( bWasInFormulaTree )
+ rDocument.PutInFormulaTree( this );
+}
+
+void ScFormulaCell::Compile(
+ sc::CompileFormulaContext& rCxt, const OUString& rFormula, bool bNoListening )
+{
+ if ( rDocument.IsClipOrUndo() )
+ return;
+ bool bWasInFormulaTree = rDocument.IsInFormulaTree( this );
+ if ( bWasInFormulaTree )
+ rDocument.RemoveFromFormulaTree( this );
+ // pCode may not deleted for queries, but must be empty
+ if ( pCode )
+ pCode->Clear();
+ ScTokenArray* pCodeOld = pCode;
+ ScCompiler aComp(rCxt, aPos);
+ pCode = aComp.CompileString( rFormula ).release();
+ assert(!mxGroup);
+ delete pCodeOld;
+ if( pCode->GetCodeError() == FormulaError::NONE )
+ {
+ if ( !pCode->GetLen() && !aResult.GetHybridFormula().isEmpty() && rFormula == aResult.GetHybridFormula() )
+ { // not recursive CompileTokenArray/Compile/CompileTokenArray
+ if ( rFormula[0] == '=' )
+ pCode->AddBad( rFormula.copy(1) );
+ else
+ pCode->AddBad( rFormula );
+ }
+ bCompile = true;
+ CompileTokenArray(rCxt, bNoListening);
+ }
+ else
+ bChanged = true;
+
+ if ( bWasInFormulaTree )
+ rDocument.PutInFormulaTree( this );
+}
+
+void ScFormulaCell::CompileTokenArray( bool bNoListening )
+{
+ // Not already compiled?
+ if( !pCode->GetLen() && !aResult.GetHybridFormula().isEmpty() )
+ {
+ Compile( aResult.GetHybridFormula(), bNoListening, eTempGrammar);
+ }
+ else if( bCompile && !rDocument.IsClipOrUndo() && pCode->GetCodeError() == FormulaError::NONE )
+ {
+ // RPN length may get changed
+ bool bWasInFormulaTree = rDocument.IsInFormulaTree( this );
+ if ( bWasInFormulaTree )
+ rDocument.RemoveFromFormulaTree( this );
+
+ // Loading from within filter? No listening yet!
+ if( rDocument.IsInsertingFromOtherDoc() )
+ bNoListening = true;
+
+ if( !bNoListening && pCode->GetCodeLen() )
+ EndListeningTo( rDocument );
+ ScCompiler aComp(rDocument, aPos, *pCode, rDocument.GetGrammar(), true, cMatrixFlag != ScMatrixMode::NONE);
+ bSubTotal = aComp.CompileTokenArray();
+ if( pCode->GetCodeError() == FormulaError::NONE )
+ {
+ nFormatType = aComp.GetNumFormatType();
+ bChanged = true;
+ aResult.SetToken( nullptr);
+ bCompile = false;
+ if ( !bNoListening )
+ StartListeningTo( rDocument );
+ }
+ if ( bWasInFormulaTree )
+ rDocument.PutInFormulaTree( this );
+
+ if (bSubTotal)
+ rDocument.AddSubTotalCell(this);
+ }
+}
+
+void ScFormulaCell::CompileTokenArray( sc::CompileFormulaContext& rCxt, bool bNoListening )
+{
+ // Not already compiled?
+ if( !pCode->GetLen() && !aResult.GetHybridFormula().isEmpty() )
+ {
+ rCxt.setGrammar(eTempGrammar);
+ Compile(rCxt, aResult.GetHybridFormula(), bNoListening);
+ }
+ else if( bCompile && !rDocument.IsClipOrUndo() && pCode->GetCodeError() == FormulaError::NONE)
+ {
+ // RPN length may get changed
+ bool bWasInFormulaTree = rDocument.IsInFormulaTree( this );
+ if ( bWasInFormulaTree )
+ rDocument.RemoveFromFormulaTree( this );
+
+ // Loading from within filter? No listening yet!
+ if( rDocument.IsInsertingFromOtherDoc() )
+ bNoListening = true;
+
+ if( !bNoListening && pCode->GetCodeLen() )
+ EndListeningTo( rDocument );
+ ScCompiler aComp(rCxt, aPos, *pCode, true, cMatrixFlag != ScMatrixMode::NONE);
+ bSubTotal = aComp.CompileTokenArray();
+ if( pCode->GetCodeError() == FormulaError::NONE )
+ {
+ nFormatType = aComp.GetNumFormatType();
+ bChanged = true;
+ aResult.SetToken( nullptr);
+ bCompile = false;
+ if ( !bNoListening )
+ StartListeningTo( rDocument );
+ }
+ if ( bWasInFormulaTree )
+ rDocument.PutInFormulaTree( this );
+
+ if (bSubTotal)
+ rDocument.AddSubTotalCell(this);
+ }
+}
+
+void ScFormulaCell::CompileXML( sc::CompileFormulaContext& rCxt, ScProgress& rProgress )
+{
+ if ( cMatrixFlag == ScMatrixMode::Reference )
+ { // is already token code via ScDocFunc::EnterMatrix, ScDocument::InsertMatrixFormula
+ // just establish listeners
+ StartListeningTo( rDocument );
+ return ;
+ }
+
+ // Error constant formula cell stays as is.
+ if (!pCode->GetLen() && pCode->GetCodeError() != FormulaError::NONE)
+ return;
+
+ // Compilation changes RPN count, remove and reinsert to FormulaTree if it
+ // was in to update its count.
+ bool bWasInFormulaTree = rDocument.IsInFormulaTree( this);
+ if (bWasInFormulaTree)
+ rDocument.RemoveFromFormulaTree( this);
+ rCxt.setGrammar(eTempGrammar);
+ ScCompiler aComp(rCxt, aPos, *pCode, true, cMatrixFlag != ScMatrixMode::NONE);
+ OUString aFormula, aFormulaNmsp;
+ aComp.CreateStringFromXMLTokenArray( aFormula, aFormulaNmsp );
+ rDocument.DecXMLImportedFormulaCount( aFormula.getLength() );
+ rProgress.SetStateCountDownOnPercent( rDocument.GetXMLImportedFormulaCount() );
+ // pCode may not deleted for queries, but must be empty
+ pCode->Clear();
+
+ bool bDoCompile = true;
+
+ if ( !mxGroup && aFormulaNmsp.isEmpty() ) // optimization
+ {
+ ScAddress aPreviousCell( aPos );
+ aPreviousCell.IncRow( -1 );
+ ScFormulaCell *pPreviousCell = rDocument.GetFormulaCell( aPreviousCell );
+ if (pPreviousCell && pPreviousCell->GetCode()->IsShareable())
+ {
+ // Build formula string using the tokens from the previous cell,
+ // but use the current cell position.
+ ScCompiler aBackComp( rCxt, aPos, *(pPreviousCell->pCode) );
+ OUStringBuffer aShouldBeBuf;
+ aBackComp.CreateStringFromTokenArray( aShouldBeBuf );
+
+ // The initial '=' is optional in ODFF.
+ const sal_Int32 nLeadingEqual = (aFormula.getLength() > 0 && aFormula[0] == '=') ? 1 : 0;
+ OUString aShouldBe = aShouldBeBuf.makeStringAndClear();
+ if (aFormula.getLength() == aShouldBe.getLength() + nLeadingEqual &&
+ aFormula.match( aShouldBe, nLeadingEqual))
+ {
+ // Put them in the same formula group.
+ ScFormulaCellGroupRef xGroup = pPreviousCell->GetCellGroup();
+ if (!xGroup) // Last cell is not grouped yet. Start a new group.
+ xGroup = pPreviousCell->CreateCellGroup(1, false);
+ ++xGroup->mnLength;
+ SetCellGroup( xGroup );
+
+ // Do setup here based on previous cell.
+
+ nFormatType = pPreviousCell->nFormatType;
+ bSubTotal = pPreviousCell->bSubTotal;
+ bChanged = true;
+ bCompile = false;
+
+ if (bSubTotal)
+ rDocument.AddSubTotalCell(this);
+
+ bDoCompile = false;
+ pCode = pPreviousCell->pCode;
+ if (pPreviousCell->mbIsExtRef)
+ rDocument.GetExternalRefManager()->insertRefCellFromTemplate( pPreviousCell, this );
+ }
+ }
+ }
+
+ if (bDoCompile)
+ {
+ ScTokenArray* pCodeOld = pCode;
+ pCode = aComp.CompileString( aFormula, aFormulaNmsp ).release();
+ assert(!mxGroup);
+ delete pCodeOld;
+
+ if( pCode->GetCodeError() == FormulaError::NONE )
+ {
+ if ( !pCode->GetLen() )
+ {
+ if ( !aFormula.isEmpty() && aFormula[0] == '=' )
+ pCode->AddBad( aFormula.copy( 1 ) );
+ else
+ pCode->AddBad( aFormula );
+ }
+ bSubTotal = aComp.CompileTokenArray();
+ if( pCode->GetCodeError() == FormulaError::NONE )
+ {
+ nFormatType = aComp.GetNumFormatType();
+ bChanged = true;
+ bCompile = false;
+ }
+
+ if (bSubTotal)
+ rDocument.AddSubTotalCell(this);
+ }
+ else
+ bChanged = true;
+ }
+
+ // After loading, it must be known if ocDde/ocWebservice is in any formula
+ // (for external links warning, CompileXML is called at the end of loading XML file)
+ rDocument.CheckLinkFormulaNeedingCheck(*pCode);
+
+ //volatile cells must be added here for import
+ if( !pCode->IsRecalcModeNormal() || pCode->IsRecalcModeForced())
+ {
+ // During load, only those cells that are marked explicitly dirty get
+ // recalculated. So we need to set it dirty here.
+ SetDirtyVar();
+ rDocument.AppendToFormulaTrack(this);
+ // Do not call TrackFormulas() here, not all listeners may have been
+ // established, postponed until ScDocument::CompileXML() finishes.
+ }
+ else if (bWasInFormulaTree)
+ rDocument.PutInFormulaTree(this);
+}
+
+void ScFormulaCell::CalcAfterLoad( sc::CompileFormulaContext& rCxt, bool bStartListening )
+{
+ bool bNewCompiled = false;
+ // If a Calc 1.0-doc is read, we have a result, but no token array
+ if( !pCode->GetLen() && !aResult.GetHybridFormula().isEmpty() )
+ {
+ rCxt.setGrammar(eTempGrammar);
+ Compile(rCxt, aResult.GetHybridFormula(), true);
+ aResult.SetToken( nullptr);
+ bDirty = true;
+ bNewCompiled = true;
+ }
+ // The RPN array is not created when a Calc 3.0-Doc has been read as the Range Names exist until now.
+ if( pCode->GetLen() && !pCode->GetCodeLen() && pCode->GetCodeError() == FormulaError::NONE )
+ {
+ ScCompiler aComp(rCxt, aPos, *pCode, true, cMatrixFlag != ScMatrixMode::NONE);
+ bSubTotal = aComp.CompileTokenArray();
+ nFormatType = aComp.GetNumFormatType();
+ bDirty = true;
+ bCompile = false;
+ bNewCompiled = true;
+
+ if (bSubTotal)
+ rDocument.AddSubTotalCell(this);
+ }
+
+ // On OS/2 with broken FPU exception, we can somehow store /0 without Err503. Later on in
+ // the BLC Lib NumberFormatter crashes when doing a fabs (NAN) (# 32739 #).
+ // We iron this out here for all systems, such that we also have an Err503 here.
+ if ( aResult.IsValue() && !std::isfinite( aResult.GetDouble() ) )
+ {
+ OSL_FAIL("Formula cell INFINITY!!! Where does this document come from?");
+ aResult.SetResultError( FormulaError::IllegalFPOperation );
+ bDirty = true;
+ }
+
+ // DoubleRefs for binary operators were always a Matrix before version v5.0.
+ // Now this is only the case when in an array formula, otherwise it's an implicit intersection
+ if ( ScDocument::GetSrcVersion() < SC_MATRIX_DOUBLEREF &&
+ GetMatrixFlag() == ScMatrixMode::NONE && pCode->HasMatrixDoubleRefOps() )
+ {
+ cMatrixFlag = ScMatrixMode::Formula;
+ SetMatColsRows( 1, 1);
+ }
+
+ // Do the cells need to be calculated? After Load cells can contain an error code, and then start
+ // the listener and Recalculate (if needed) if not ScRecalcMode::NORMAL
+ if( !bNewCompiled || pCode->GetCodeError() == FormulaError::NONE )
+ {
+ if (bStartListening)
+ StartListeningTo(rDocument);
+
+ if( !pCode->IsRecalcModeNormal() )
+ bDirty = true;
+ }
+ if ( pCode->IsRecalcModeAlways() )
+ { // random(), today(), now() always stay in the FormulaTree, so that they are calculated
+ // for each F9
+ bDirty = true;
+ }
+ // No SetDirty yet, as no all Listeners are known yet (only in SetDirtyAfterLoad)
+}
+
+bool ScFormulaCell::MarkUsedExternalReferences()
+{
+ return pCode && rDocument.MarkUsedExternalReferences(*pCode, aPos);
+}
+
+namespace {
+class RecursionCounter
+{
+ ScRecursionHelper& rRec;
+ bool bStackedInIteration;
+#if defined DBG_UTIL && !defined NDEBUG
+ const ScFormulaCell* cell;
+#endif
+public:
+ RecursionCounter( ScRecursionHelper& r, ScFormulaCell* p )
+ : rRec(r)
+#if defined DBG_UTIL && !defined NDEBUG
+ , cell(p)
+#endif
+ {
+ bStackedInIteration = rRec.IsDoingIteration();
+ if (bStackedInIteration)
+ rRec.GetRecursionInIterationStack().push( p);
+ rRec.IncRecursionCount();
+ }
+ ~RecursionCounter()
+ {
+ rRec.DecRecursionCount();
+ if (bStackedInIteration)
+ {
+#if defined DBG_UTIL && !defined NDEBUG
+ assert(rRec.GetRecursionInIterationStack().top() == cell);
+#endif
+ rRec.GetRecursionInIterationStack().pop();
+ }
+ }
+};
+
+// Forced calculation: OpenCL and threads require formula groups, so force even single cells to be a "group".
+// Remove the group again at the end, since there are some places throughout the code
+// that do not handle well groups with just 1 cell. Remove the groups only when the recursion level
+// reaches 0 again (groups contain some info such as disabling threading because of cycles, so removing
+// a group immediately would remove the info), for this reason affected cells are stored in the recursion
+// helper.
+struct TemporaryCellGroupMaker
+{
+ TemporaryCellGroupMaker( ScFormulaCell* cell, bool enable )
+ : mCell( cell )
+ , mEnabled( enable )
+ {
+ if( mEnabled && mCell->GetCellGroup() == nullptr )
+ {
+ mCell->CreateCellGroup( 1, false );
+ mCell->GetDocument().GetRecursionHelper().AddTemporaryGroupCell( mCell );
+ }
+ }
+ ~TemporaryCellGroupMaker() COVERITY_NOEXCEPT_FALSE
+ {
+ if( mEnabled )
+ mCell->GetDocument().GetRecursionHelper().CleanTemporaryGroupCells();
+ }
+ ScFormulaCell* mCell;
+ const bool mEnabled;
+};
+
+} // namespace
+
+bool ScFormulaCell::Interpret(SCROW nStartOffset, SCROW nEndOffset)
+{
+ ScRecursionHelper& rRecursionHelper = rDocument.GetRecursionHelper();
+ bool bGroupInterpreted = false;
+
+ // The result would possibly depend on a cell without a valid value, bail out
+ // the entire dependency computation.
+ if (rRecursionHelper.IsAbortingDependencyComputation())
+ return false;
+
+ if ((mxGroup && !rRecursionHelper.CheckFGIndependence(mxGroup.get())) || !rRecursionHelper.AreGroupsIndependent())
+ return bGroupInterpreted;
+
+ static ForceCalculationType forceType = ScCalcConfig::getForceCalculationType();
+ TemporaryCellGroupMaker cellGroupMaker( this, forceType != ForceCalculationNone && forceType != ForceCalculationCore );
+
+ ScFormulaCell* pTopCell = mxGroup ? mxGroup->mpTopCell : this;
+
+ if (pTopCell->mbSeenInPath && rRecursionHelper.GetDepComputeLevel() &&
+ rRecursionHelper.AnyCycleMemberInDependencyEvalMode(pTopCell))
+ {
+ // This call arose from a dependency calculation and we just found a cycle.
+ // This will mark all elements in the cycle as parts-of-cycle.
+ ScFormulaGroupCycleCheckGuard aCycleCheckGuard(rRecursionHelper, pTopCell);
+ // Reaching here does not necessarily mean a circular reference, so don't set Err:522 here yet.
+ // If there is a genuine circular reference, it will be marked so when all groups
+ // in the cycle get out of dependency evaluation mode.
+ // But returning without calculation a new value means other cells depending
+ // on this one would use a possibly invalid value, so ensure the dependency
+ // computation is aborted without resetting the dirty flag of any cell.
+ rRecursionHelper.AbortDependencyComputation();
+ return bGroupInterpreted;
+ }
+
+#if DEBUG_CALCULATION
+ static bool bDebugCalculationInit = true;
+ if (bDebugCalculationInit)
+ {
+ aDC.maTrigger = aDebugCalculationTriggerAddress;
+ aDC.mbPrintResults = true;
+ bDebugCalculationInit = false;
+ }
+ DebugCalculationStacker aDebugEntry(aPos, rDocument);
+#endif
+
+ if (!IsDirtyOrInTableOpDirty() || rRecursionHelper.IsInReturn())
+ return bGroupInterpreted; // no double/triple processing
+
+ //FIXME:
+ // If the call originates from a Reschedule in DdeLink update, leave dirty
+ // Better: Do a Dde Link Update without Reschedule or do it completely asynchronously!
+ if ( rDocument.IsInDdeLinkUpdate() )
+ return bGroupInterpreted;
+
+ if (bRunning)
+ {
+ if (!rDocument.GetDocOptions().IsIter())
+ {
+ aResult.SetResultError( FormulaError::CircularReference );
+ return bGroupInterpreted;
+ }
+
+ if (aResult.GetResultError() == FormulaError::CircularReference)
+ aResult.SetResultError( FormulaError::NONE );
+
+ // Start or add to iteration list.
+ if (!rRecursionHelper.IsDoingIteration() ||
+ !rRecursionHelper.GetRecursionInIterationStack().top()->bIsIterCell)
+ rRecursionHelper.SetInIterationReturn( true);
+
+ return bGroupInterpreted;
+ }
+ // no multiple interprets for GetErrCode, IsValue, GetValue and
+ // different entry point recursions. Would also lead to premature
+ // convergence in iterations.
+ if (rRecursionHelper.GetIteration() && nSeenInIteration ==
+ rRecursionHelper.GetIteration())
+ return bGroupInterpreted;
+
+ bool bOldRunning = bRunning;
+ if (rRecursionHelper.GetRecursionCount() > MAXRECURSION)
+ {
+ bRunning = true;
+ rRecursionHelper.SetInRecursionReturn( true);
+ }
+ else
+ {
+ rDocument.IncInterpretLevel();
+
+#if DEBUG_CALCULATION
+ aDC.enterGroup();
+#endif
+ bool bPartOfCycleBefore = mxGroup && mxGroup->mbPartOfCycle;
+ bGroupInterpreted = InterpretFormulaGroup(nStartOffset, nEndOffset);
+ bool bPartOfCycleAfter = mxGroup && mxGroup->mbPartOfCycle;
+
+#if DEBUG_CALCULATION
+ aDC.leaveGroup();
+#endif
+ if (!bGroupInterpreted)
+ {
+ // This call resulted from a dependency calculation for a multigroup-threading attempt,
+ // but found dependency among the groups.
+ if (!rRecursionHelper.AreGroupsIndependent())
+ {
+ rDocument.DecInterpretLevel();
+ return bGroupInterpreted;
+ }
+ // Dependency calc inside InterpretFormulaGroup() failed due to
+ // detection of a cycle and there are parent FG's in the cycle.
+ // Skip InterpretTail() in such cases, only run InterpretTail for the "cycle-starting" FG
+ if (!bPartOfCycleBefore && bPartOfCycleAfter && rRecursionHelper.AnyParentFGInCycle())
+ {
+ rDocument.DecInterpretLevel();
+ return bGroupInterpreted;
+ }
+
+ ScFormulaGroupCycleCheckGuard aCycleCheckGuard(rRecursionHelper, this);
+ ScInterpreterContextGetterGuard aContextGetterGuard(rDocument, rDocument.GetFormatTable());
+ InterpretTail( *aContextGetterGuard.GetInterpreterContext(), SCITP_NORMAL);
+ }
+
+ rDocument.DecInterpretLevel();
+ }
+
+ // While leaving a recursion or iteration stack, insert its cells to the
+ // recursion list in reverse order.
+ if (rRecursionHelper.IsInReturn())
+ {
+ bool bFreeFlyingInserted = false;
+ if (rRecursionHelper.GetRecursionCount() > 0 || !rRecursionHelper.IsDoingRecursion())
+ {
+ rRecursionHelper.Insert( this, bOldRunning, aResult);
+ bFreeFlyingInserted = mbFreeFlying;
+ }
+ bool bIterationFromRecursion = false;
+ bool bResumeIteration = false;
+ do
+ {
+ if ((rRecursionHelper.IsInIterationReturn() &&
+ rRecursionHelper.GetRecursionCount() == 0 &&
+ !rRecursionHelper.IsDoingIteration()) ||
+ bIterationFromRecursion || bResumeIteration)
+ {
+ bool & rDone = rRecursionHelper.GetConvergingReference();
+ rDone = false;
+ if (!bIterationFromRecursion && bResumeIteration)
+ {
+ bResumeIteration = false;
+ // Resuming iteration expands the range.
+ ScFormulaRecursionList::const_iterator aOldStart(
+ rRecursionHelper.GetLastIterationStart());
+ rRecursionHelper.ResumeIteration();
+ // Mark new cells being in iteration.
+ for (ScFormulaRecursionList::const_iterator aIter(
+ rRecursionHelper.GetIterationStart()); aIter !=
+ aOldStart; ++aIter)
+ {
+ ScFormulaCell* pIterCell = (*aIter).pCell;
+ pIterCell->bIsIterCell = true;
+ }
+ // Mark older cells dirty again, in case they converted
+ // without accounting for all remaining cells in the circle
+ // that weren't touched so far, e.g. conditional. Restore
+ // backupped result.
+ sal_uInt16 nIteration = rRecursionHelper.GetIteration();
+ for (ScFormulaRecursionList::const_iterator aIter(
+ aOldStart); aIter !=
+ rRecursionHelper.GetIterationEnd(); ++aIter)
+ {
+ ScFormulaCell* pIterCell = (*aIter).pCell;
+ if (pIterCell->nSeenInIteration == nIteration)
+ {
+ if (!pIterCell->bDirty || aIter == aOldStart)
+ {
+ pIterCell->aResult = (*aIter).aPreviousResult;
+ }
+ --pIterCell->nSeenInIteration;
+ }
+ pIterCell->bDirty = true;
+ }
+ }
+ else
+ {
+ bResumeIteration = false;
+ // Close circle once. If 'this' is self-referencing only
+ // (e.g. counter or self-adder) then it is already
+ // implicitly closed.
+ /* TODO: does this even make sense anymore? The last cell
+ * added above with rRecursionHelper.Insert() should always
+ * be 'this', shouldn't it? */
+ if (rRecursionHelper.GetList().size() > 1)
+ {
+ ScFormulaCell* pLastCell = rRecursionHelper.GetList().back().pCell;
+ if (pLastCell != this)
+ {
+ rDocument.IncInterpretLevel();
+ ScInterpreterContextGetterGuard aContextGetterGuard(rDocument, rDocument.GetFormatTable());
+ pLastCell->InterpretTail(
+ *aContextGetterGuard.GetInterpreterContext(), SCITP_CLOSE_ITERATION_CIRCLE);
+ rDocument.DecInterpretLevel();
+ }
+ }
+ // Start at 1, init things.
+ rRecursionHelper.StartIteration();
+ // Mark all cells being in iteration. Reset results to
+ // original values, formula cells have been interpreted
+ // already, discard that step.
+ for (ScFormulaRecursionList::const_iterator aIter(
+ rRecursionHelper.GetIterationStart()); aIter !=
+ rRecursionHelper.GetIterationEnd(); ++aIter)
+ {
+ ScFormulaCell* pIterCell = (*aIter).pCell;
+ pIterCell->aResult = (*aIter).aPreviousResult;
+ pIterCell->bIsIterCell = true;
+ }
+ }
+ bIterationFromRecursion = false;
+ sal_uInt16 nIterMax = rDocument.GetDocOptions().GetIterCount();
+ for ( ; rRecursionHelper.GetIteration() <= nIterMax && !rDone;
+ rRecursionHelper.IncIteration())
+ {
+ rDone = false;
+ bool bFirst = true;
+ for ( ScFormulaRecursionList::iterator aIter(
+ rRecursionHelper.GetIterationStart()); aIter !=
+ rRecursionHelper.GetIterationEnd() &&
+ !rRecursionHelper.IsInReturn(); ++aIter)
+ {
+ ScFormulaCell* pIterCell = (*aIter).pCell;
+ if (pIterCell->IsDirtyOrInTableOpDirty() &&
+ rRecursionHelper.GetIteration() !=
+ pIterCell->GetSeenInIteration())
+ {
+ (*aIter).aPreviousResult = pIterCell->aResult;
+ rDocument.IncInterpretLevel();
+ ScInterpreterContextGetterGuard aContextGetterGuard(rDocument, rDocument.GetFormatTable());
+ pIterCell->InterpretTail( *aContextGetterGuard.GetInterpreterContext(), SCITP_FROM_ITERATION);
+ rDocument.DecInterpretLevel();
+ }
+ if (bFirst)
+ {
+ rDone = !pIterCell->IsDirtyOrInTableOpDirty();
+ bFirst = false;
+ }
+ else if (rDone)
+ {
+ rDone = !pIterCell->IsDirtyOrInTableOpDirty();
+ }
+ }
+ if (rRecursionHelper.IsInReturn())
+ {
+ bResumeIteration = true;
+ break; // for
+ // Don't increment iteration.
+ }
+ }
+ if (!bResumeIteration)
+ {
+ if (rDone)
+ {
+ for (ScFormulaRecursionList::const_iterator aIter(
+ rRecursionHelper.GetIterationStart());
+ aIter != rRecursionHelper.GetIterationEnd();
+ ++aIter)
+ {
+ ScFormulaCell* pIterCell = (*aIter).pCell;
+ pIterCell->bIsIterCell = false;
+ pIterCell->nSeenInIteration = 0;
+ pIterCell->bRunning = (*aIter).bOldRunning;
+ }
+ }
+ else
+ {
+ for (ScFormulaRecursionList::const_iterator aIter(
+ rRecursionHelper.GetIterationStart());
+ aIter != rRecursionHelper.GetIterationEnd();
+ ++aIter)
+ {
+ ScFormulaCell* pIterCell = (*aIter).pCell;
+ pIterCell->bIsIterCell = false;
+ pIterCell->nSeenInIteration = 0;
+ pIterCell->bRunning = (*aIter).bOldRunning;
+ pIterCell->ResetDirty();
+ // The difference to Excel is that Excel does not
+ // produce an error for non-convergence thus a
+ // delta of 0.001 still works to execute the
+ // maximum number of iterations and display the
+ // results no matter if the result anywhere reached
+ // near delta, but also never indicates whether the
+ // result actually makes sense in case of
+ // non-counter context. Calc does check the delta
+ // in every case. If we wanted to support what
+ // Excel does then add another option "indicate
+ // non-convergence error" (default on) and execute
+ // the following block only if set.
+#if 1
+ // If one cell didn't converge, all cells of this
+ // circular dependency don't, no matter whether
+ // single cells did.
+ pIterCell->aResult.SetResultError( FormulaError::NoConvergence);
+ pIterCell->bChanged = true;
+#endif
+ }
+ }
+ // End this iteration and remove entries.
+ rRecursionHelper.EndIteration();
+ bResumeIteration = rRecursionHelper.IsDoingIteration();
+ }
+ }
+ if (rRecursionHelper.IsInRecursionReturn() &&
+ rRecursionHelper.GetRecursionCount() == 0 &&
+ !rRecursionHelper.IsDoingRecursion())
+ {
+ bIterationFromRecursion = false;
+ // Iterate over cells known so far, start with the last cell
+ // encountered, inserting new cells if another recursion limit
+ // is reached. Repeat until solved.
+ rRecursionHelper.SetDoingRecursion( true);
+ do
+ {
+ rRecursionHelper.SetInRecursionReturn( false);
+ for (ScFormulaRecursionList::const_iterator aIter(
+ rRecursionHelper.GetIterationStart());
+ !rRecursionHelper.IsInReturn() && aIter !=
+ rRecursionHelper.GetIterationEnd(); ++aIter)
+ {
+ ScFormulaCell* pCell = (*aIter).pCell;
+ if (pCell->IsDirtyOrInTableOpDirty())
+ {
+ rDocument.IncInterpretLevel();
+ ScInterpreterContextGetterGuard aContextGetterGuard(rDocument, rDocument.GetFormatTable());
+ pCell->InterpretTail( *aContextGetterGuard.GetInterpreterContext(), SCITP_NORMAL);
+ rDocument.DecInterpretLevel();
+ if (!pCell->IsDirtyOrInTableOpDirty() && !pCell->IsIterCell())
+ pCell->bRunning = (*aIter).bOldRunning;
+ }
+ }
+ } while (rRecursionHelper.IsInRecursionReturn());
+ rRecursionHelper.SetDoingRecursion( false);
+ if (rRecursionHelper.IsInIterationReturn())
+ {
+ if (!bResumeIteration)
+ bIterationFromRecursion = true;
+ }
+ else if (bResumeIteration ||
+ rRecursionHelper.IsDoingIteration())
+ rRecursionHelper.GetList().erase(
+ rRecursionHelper.GetIterationStart(),
+ rRecursionHelper.GetLastIterationStart());
+ else
+ rRecursionHelper.Clear();
+ }
+ } while (bIterationFromRecursion || bResumeIteration);
+
+ if (bFreeFlyingInserted)
+ {
+ // Remove this from recursion list, it may get deleted.
+ // It additionally also should mean that the recursion/iteration
+ // ends here as it must had been triggered by this free-flying
+ // out-of-sheets cell
+ /* TODO: replace by a simple rRecursionHelper.EndIteration() call
+ * if the assertions hold. */
+ const bool bOnlyThis = (rRecursionHelper.GetList().size() == 1);
+ assert(bOnlyThis);
+ rRecursionHelper.GetList().remove_if([this](const ScFormulaRecursionEntry& r){return r.pCell == this;});
+ if (bOnlyThis)
+ {
+ assert(rRecursionHelper.GetList().empty());
+ if (rRecursionHelper.GetList().empty())
+ rRecursionHelper.EndIteration();
+ }
+ }
+ }
+
+#if DEBUG_CALCULATION
+ FormulaError nErr = aResult.GetResultError();
+ if (nErr != FormulaError::NONE)
+ aDC.storeResultError( nErr);
+ else if (aResult.IsValue())
+ aDC.storeResult( aResult.GetDouble());
+ else
+ aDC.storeResult( aResult.GetString());
+#endif
+
+ return bGroupInterpreted;
+}
+
+void ScFormulaCell::InterpretTail( ScInterpreterContext& rContext, ScInterpretTailParameter eTailParam )
+{
+ RecursionCounter aRecursionCounter( rDocument.GetRecursionHelper(), this);
+ // TODO If this cell is not an iteration cell, add it to the list of iteration cells?
+ if(bIsIterCell)
+ nSeenInIteration = rDocument.GetRecursionHelper().GetIteration();
+ if( !pCode->GetCodeLen() && pCode->GetCodeError() == FormulaError::NONE )
+ {
+ // #i11719# no RPN and no error and no token code but result string present
+ // => interpretation of this cell during name-compilation and unknown names
+ // => can't exchange underlying code array in CompileTokenArray() /
+ // Compile() because interpreter's token iterator would crash or pCode
+ // would be deleted twice if this cell was interpreted during
+ // compilation.
+ // This should only be a temporary condition and, since we set an
+ // error, if ran into it again we'd bump into the dirty-clearing
+ // condition further down.
+ if ( !pCode->GetLen() && !aResult.GetHybridFormula().isEmpty() )
+ {
+ pCode->SetCodeError( FormulaError::NoCode );
+ // This is worth an assertion; if encountered in daily work
+ // documents we might need another solution. Or just confirm correctness.
+ return;
+ }
+ CompileTokenArray();
+ }
+
+ if( pCode->GetCodeLen() )
+ {
+ std::unique_ptr<ScInterpreter> pScopedInterpreter;
+ ScInterpreter* pInterpreter;
+ if (rContext.pInterpreter)
+ {
+ pInterpreter = rContext.pInterpreter;
+ pInterpreter->Init(this, aPos, *pCode);
+ }
+ else
+ {
+ pScopedInterpreter.reset(new ScInterpreter( this, rDocument, rContext, aPos, *pCode ));
+ pInterpreter = pScopedInterpreter.get();
+ }
+
+ FormulaError nOldErrCode = aResult.GetResultError();
+ if ( nSeenInIteration == 0 )
+ { // Only the first time
+ // With bChanged=false, if a newly compiled cell has a result of
+ // 0.0, no change is detected and the cell will not be repainted.
+ // bChanged = false;
+ aResult.SetResultError( FormulaError::NONE );
+ }
+
+ switch ( aResult.GetResultError() )
+ {
+ case FormulaError::CircularReference : // will be determined again if so
+ aResult.SetResultError( FormulaError::NONE );
+ break;
+ default: break;
+ }
+
+ bool bOldRunning = bRunning;
+ bRunning = true;
+ pInterpreter->Interpret();
+ if (rDocument.GetRecursionHelper().IsInReturn() && eTailParam != SCITP_CLOSE_ITERATION_CIRCLE)
+ {
+ if (nSeenInIteration > 0)
+ --nSeenInIteration; // retry when iteration is resumed
+
+ if ( aResult.GetType() == formula::svUnknown )
+ aResult.SetToken( pInterpreter->GetResultToken().get() );
+
+ return;
+ }
+ bRunning = bOldRunning;
+
+ // The result may be invalid or depend on another invalid result, just abort
+ // without updating the cell value. Since the dirty flag will not be reset,
+ // the proper value will be computed later.
+ if(rDocument.GetRecursionHelper().IsAbortingDependencyComputation())
+ return;
+
+ // #i102616# For single-sheet saving consider only content changes, not format type,
+ // because format type isn't set on loading (might be changed later)
+ bool bContentChanged = false;
+
+ // Do not create a HyperLink() cell if the formula results in an error.
+ if( pInterpreter->GetError() != FormulaError::NONE && pCode->IsHyperLink())
+ pCode->SetHyperLink(false);
+
+ if( pInterpreter->GetError() != FormulaError::NONE && pInterpreter->GetError() != FormulaError::CircularReference)
+ {
+ bChanged = true;
+
+ if (pInterpreter->GetError() == FormulaError::RetryCircular)
+ {
+ // Array formula matrix calculation corner case. Keep dirty
+ // state, do not remove from formula tree or anything else, but
+ // store FormulaError::CircularReference in case this cell does not get
+ // recalculated.
+ aResult.SetResultError( FormulaError::CircularReference);
+ return;
+ }
+
+ ResetDirty();
+ }
+
+ if (eTailParam == SCITP_FROM_ITERATION && IsDirtyOrInTableOpDirty())
+ {
+ bool bIsValue = aResult.IsValue(); // the previous type
+ // Did it converge?
+ if ((bIsValue && pInterpreter->GetResultType() == svDouble && fabs(
+ pInterpreter->GetNumResult() - aResult.GetDouble()) <=
+ rDocument.GetDocOptions().GetIterEps()) ||
+ (!bIsValue && pInterpreter->GetResultType() == svString &&
+ pInterpreter->GetStringResult() == aResult.GetString()))
+ {
+ // A convergence in the first iteration doesn't necessarily
+ // mean that it's done, it may be as not all related cells
+ // of a circle changed their values yet. If the set really
+ // converges it will do so also during the next iteration. This
+ // fixes situations like of #i44115#. If this wasn't wanted an
+ // initial "uncalculated" value would be needed for all cells
+ // of a circular dependency => graph needed before calculation.
+ if (nSeenInIteration > 1 ||
+ rDocument.GetDocOptions().GetIterCount() == 1)
+ {
+ ResetDirty();
+ }
+ }
+ }
+
+ // New error code?
+ if( pInterpreter->GetError() != nOldErrCode )
+ {
+ bChanged = true;
+ // bContentChanged only has to be set if the file content would be changed
+ if ( aResult.GetCellResultType() != svUnknown )
+ bContentChanged = true;
+ }
+
+ ScFormulaResult aNewResult( pInterpreter->GetResultToken().get());
+
+ // For IF() and other jumps or changed formatted source data the result
+ // format may change for different runs, e.g. =IF(B1,B1) with first
+ // B1:0 boolean FALSE next B1:23 numeric 23, we don't want the 23
+ // displayed as TRUE. Do not force a general format though if
+ // mbNeedsNumberFormat is set (because there was a general format..).
+ // Note that nFormatType may be out of sync here if a format was
+ // applied or cleared after the last run, but obtaining the current
+ // format always just to check would be expensive. There may be
+ // cases where the format should be changed but is not. If that turns
+ // out to be a real problem then obtain the current format type after
+ // the initial check when needed.
+ bool bForceNumberFormat = (mbAllowNumberFormatChange && !mbNeedsNumberFormat &&
+ !SvNumberFormatter::IsCompatible( nFormatType, pInterpreter->GetRetFormatType()));
+
+ // We have some requirements additionally to IsCompatible().
+ // * Do not apply a NumberFormat::LOGICAL if the result value is not
+ // 1.0 or 0.0
+ // * Do not override an already set numeric number format if the result
+ // is of type NumberFormat::LOGICAL, it could be user applied.
+ // On the other hand, for an empty jump path instead of FALSE an
+ // unexpected for example 0% could be displayed. YMMV.
+ // * Never override a non-standard number format that indicates user
+ // applied.
+ // * NumberFormat::TEXT does not force a change.
+ if (bForceNumberFormat)
+ {
+ sal_uInt32 nOldFormatIndex = NUMBERFORMAT_ENTRY_NOT_FOUND;
+ const SvNumFormatType nRetType = pInterpreter->GetRetFormatType();
+ if (nRetType == SvNumFormatType::LOGICAL)
+ {
+ double fVal = aNewResult.GetDouble();
+ if (fVal != 1.0 && fVal != 0.0)
+ bForceNumberFormat = false;
+ else
+ {
+ nOldFormatIndex = rDocument.GetNumberFormat( rContext, aPos);
+ nFormatType = rContext.GetFormatTable()->GetType( nOldFormatIndex);
+ switch (nFormatType)
+ {
+ case SvNumFormatType::PERCENT:
+ case SvNumFormatType::CURRENCY:
+ case SvNumFormatType::SCIENTIFIC:
+ case SvNumFormatType::FRACTION:
+ bForceNumberFormat = false;
+ break;
+ case SvNumFormatType::NUMBER:
+ if ((nOldFormatIndex % SV_COUNTRY_LANGUAGE_OFFSET) != 0)
+ bForceNumberFormat = false;
+ break;
+ default: break;
+ }
+ }
+ }
+ else if (nRetType == SvNumFormatType::TEXT)
+ {
+ bForceNumberFormat = false;
+ }
+ if (bForceNumberFormat)
+ {
+ if (nOldFormatIndex == NUMBERFORMAT_ENTRY_NOT_FOUND)
+ {
+ nOldFormatIndex = rDocument.GetNumberFormat( rContext, aPos);
+ nFormatType = rContext.GetFormatTable()->GetType( nOldFormatIndex);
+ }
+ if (nOldFormatIndex !=
+ ScGlobal::GetStandardFormat( *rContext.GetFormatTable(), nOldFormatIndex, nFormatType))
+ bForceNumberFormat = false;
+ }
+ }
+
+ if( mbNeedsNumberFormat || bForceNumberFormat )
+ {
+ bool bSetFormat = true;
+ const SvNumFormatType nOldFormatType = nFormatType;
+ nFormatType = pInterpreter->GetRetFormatType();
+ sal_uInt32 nFormatIndex = pInterpreter->GetRetFormatIndex();
+
+ if (nFormatType == SvNumFormatType::TEXT)
+ {
+ // Don't set text format as hard format.
+ bSetFormat = false;
+ }
+ else if (nFormatType == SvNumFormatType::LOGICAL && cMatrixFlag != ScMatrixMode::NONE)
+ {
+ // In a matrix range do not set an (inherited) logical format
+ // as hard format if the value does not represent a strict TRUE
+ // or FALSE value. But do set for a top left error value so
+ // following matrix cells can inherit for non-error values.
+ // This solves a problem with IF() expressions in array context
+ // where incidentally the top left element results in logical
+ // type but some others don't. It still doesn't solve the
+ // reverse case though, where top left is not logical type but
+ // some other elements should be. We'd need to transport type
+ // or format information on arrays.
+ StackVar eNewCellResultType = aNewResult.GetCellResultType();
+ if (eNewCellResultType != svError || cMatrixFlag == ScMatrixMode::Reference)
+ {
+ if (eNewCellResultType != svDouble)
+ {
+ bSetFormat = false;
+ nFormatType = nOldFormatType; // that? or number?
+ }
+ else
+ {
+ double fVal = aNewResult.GetDouble();
+ if (fVal != 1.0 && fVal != 0.0)
+ {
+ bSetFormat = false;
+ nFormatType = SvNumFormatType::NUMBER;
+ }
+ }
+ }
+ }
+
+ if (bSetFormat && (bForceNumberFormat || ((nFormatIndex % SV_COUNTRY_LANGUAGE_OFFSET) == 0)))
+ nFormatIndex = ScGlobal::GetStandardFormat(*rContext.GetFormatTable(),
+ nFormatIndex, nFormatType);
+
+ // Do not replace a General format (which was the reason why
+ // mbNeedsNumberFormat was set) with a General format.
+ // 1. setting a format has quite some overhead in the
+ // ScPatternAttr/ScAttrArray handling, even if identical.
+ // 2. the General formats may be of different locales.
+ // XXX if mbNeedsNumberFormat was set even if the current format
+ // was not General then we'd have to obtain the current format here
+ // and check at least the types.
+ const bool bSetNumberFormat = bSetFormat && (bForceNumberFormat || ((nFormatIndex % SV_COUNTRY_LANGUAGE_OFFSET) != 0));
+ if (bSetNumberFormat && !rDocument.IsInLayoutStrings())
+ {
+ // set number format explicitly
+ if (!rDocument.IsThreadedGroupCalcInProgress())
+ rDocument.SetNumberFormat( aPos, nFormatIndex );
+ else
+ {
+ // SetNumberFormat() is not thread-safe (modifies ScAttrArray), delay the work
+ // to the main thread. Since thread calculations operate on formula groups,
+ // it's enough to store just the row.
+ DelayedSetNumberFormat data = { aPos.Col(), aPos.Row(), nFormatIndex };
+ rContext.maDelayedSetNumberFormat.push_back( data );
+ }
+ bChanged = true;
+ }
+
+ // Currently (2019-05-10) nothing else can cope with a duration
+ // format type, change to time as it was before.
+ if (nFormatType == SvNumFormatType::DURATION)
+ nFormatType = SvNumFormatType::TIME;
+
+ mbNeedsNumberFormat = false;
+ }
+
+ // In case of changes just obtain the result, no temporary and
+ // comparison needed anymore.
+ if (bChanged)
+ {
+ // #i102616# Compare anyway if the sheet is still marked unchanged for single-sheet saving
+ // Also handle special cases of initial results after loading.
+ if ( !bContentChanged && rDocument.IsStreamValid(aPos.Tab()) )
+ {
+ StackVar eOld = aResult.GetCellResultType();
+ StackVar eNew = aNewResult.GetCellResultType();
+ if ( eOld == svUnknown && ( eNew == svError || ( eNew == svDouble && aNewResult.GetDouble() == 0.0 ) ) )
+ {
+ // ScXMLTableRowCellContext::EndElement doesn't call SetFormulaResultDouble for 0
+ // -> no change
+ }
+ else
+ {
+ if ( eOld == svHybridCell ) // string result from SetFormulaResultString?
+ eOld = svString; // ScHybridCellToken has a valid GetString method
+
+ // #i106045# use approxEqual to compare with stored value
+ bContentChanged = (eOld != eNew ||
+ (eNew == svDouble && !rtl::math::approxEqual( aResult.GetDouble(), aNewResult.GetDouble() )) ||
+ (eNew == svString && aResult.GetString() != aNewResult.GetString()));
+ }
+ }
+
+ aResult.SetToken( pInterpreter->GetResultToken().get() );
+ }
+ else
+ {
+ StackVar eOld = aResult.GetCellResultType();
+ StackVar eNew = aNewResult.GetCellResultType();
+ bChanged = (eOld != eNew ||
+ (eNew == svDouble && aResult.GetDouble() != aNewResult.GetDouble()) ||
+ (eNew == svString && aResult.GetString() != aNewResult.GetString()));
+
+ // #i102616# handle special cases of initial results after loading
+ // (only if the sheet is still marked unchanged)
+ if ( bChanged && !bContentChanged && rDocument.IsStreamValid(aPos.Tab()) )
+ {
+ if ((eOld == svUnknown && (eNew == svError || (eNew == svDouble && aNewResult.GetDouble() == 0.0))) ||
+ ((eOld == svHybridCell) &&
+ eNew == svString && aResult.GetString() == aNewResult.GetString()) ||
+ (eOld == svDouble && eNew == svDouble &&
+ rtl::math::approxEqual( aResult.GetDouble(), aNewResult.GetDouble())))
+ {
+ // no change, see above
+ }
+ else
+ bContentChanged = true;
+ }
+
+ aResult.Assign( aNewResult);
+ }
+
+ // Precision as shown?
+ if ( aResult.IsValue() && pInterpreter->GetError() == FormulaError::NONE
+ && rDocument.GetDocOptions().IsCalcAsShown()
+ && nFormatType != SvNumFormatType::DATE
+ && nFormatType != SvNumFormatType::TIME
+ && nFormatType != SvNumFormatType::DATETIME )
+ {
+ sal_uInt32 nFormat = rDocument.GetNumberFormat( rContext, aPos );
+ aResult.SetDouble( rDocument.RoundValueAsShown(
+ aResult.GetDouble(), nFormat, &rContext));
+ }
+ if (eTailParam == SCITP_NORMAL)
+ {
+ ResetDirty();
+ }
+ if( aResult.GetMatrix() )
+ {
+ // If the formula wasn't entered as a matrix formula, live on with
+ // the upper left corner and let reference counting delete the matrix.
+ if( cMatrixFlag != ScMatrixMode::Formula && !pCode->IsHyperLink() )
+ aResult.SetToken( aResult.GetCellResultToken().get());
+ }
+ if ( aResult.IsValue() && !std::isfinite( aResult.GetDouble() ) )
+ {
+ // Coded double error may occur via filter import.
+ FormulaError nErr = GetDoubleErrorValue( aResult.GetDouble());
+ aResult.SetResultError( nErr);
+ bChanged = bContentChanged = true;
+ }
+
+ if (bContentChanged && rDocument.IsStreamValid(aPos.Tab()))
+ {
+ // pass bIgnoreLock=true, because even if called from pending row height update,
+ // a changed result must still reset the stream flag
+ rDocument.SetStreamValid(aPos.Tab(), false, true);
+ }
+ if ( !rDocument.IsThreadedGroupCalcInProgress() && !pCode->IsRecalcModeAlways() )
+ rDocument.RemoveFromFormulaTree( this );
+
+ // FORCED cells also immediately tested for validity (start macro possibly)
+
+ if ( pCode->IsRecalcModeForced() )
+ {
+ sal_uLong nValidation = rDocument.GetAttr(
+ aPos.Col(), aPos.Row(), aPos.Tab(), ATTR_VALIDDATA )->GetValue();
+ if ( nValidation )
+ {
+ const ScValidationData* pData = rDocument.GetValidationEntry( nValidation );
+ ScRefCellValue aTmpCell(this);
+ if ( pData && !pData->IsDataValid(aTmpCell, aPos))
+ pData->DoCalcError( this );
+ }
+ }
+
+ // Reschedule slows the whole thing down considerably, thus only execute on percent change
+ if (!rDocument.IsThreadedGroupCalcInProgress())
+ {
+ ScProgress *pProgress = ScProgress::GetInterpretProgress();
+ if (pProgress && pProgress->Enabled())
+ {
+ pProgress->SetStateCountDownOnPercent(
+ rDocument.GetFormulaCodeInTree()/MIN_NO_CODES_PER_PROGRESS_UPDATE );
+ }
+
+ switch (pInterpreter->GetVolatileType())
+ {
+ case ScInterpreter::VOLATILE:
+ // Volatile via built-in volatile functions. No actions needed.
+ break;
+ case ScInterpreter::VOLATILE_MACRO:
+ // The formula contains a volatile macro.
+ pCode->SetExclusiveRecalcModeAlways();
+ rDocument.PutInFormulaTree(this);
+ StartListeningTo(rDocument);
+ break;
+ case ScInterpreter::NOT_VOLATILE:
+ if (pCode->IsRecalcModeAlways())
+ {
+ // The formula was previously volatile, but no more.
+ EndListeningTo(rDocument);
+ pCode->SetExclusiveRecalcModeNormal();
+ }
+ else
+ {
+ // non-volatile formula. End listening to the area in case
+ // it's listening due to macro module change.
+ rDocument.EndListeningArea(BCA_LISTEN_ALWAYS, false, this);
+ }
+ rDocument.RemoveFromFormulaTree(this);
+ break;
+ default:
+ ;
+ }
+ }
+ }
+ else
+ {
+ // Cells with compiler errors should not be marked dirty forever
+ OSL_ENSURE( pCode->GetCodeError() != FormulaError::NONE, "no RPN code and no errors ?!?!" );
+ ResetDirty();
+ }
+}
+
+void ScFormulaCell::HandleStuffAfterParallelCalculation(ScInterpreter* pInterpreter)
+{
+ if( !pCode->GetCodeLen() )
+ return;
+
+ if ( !pCode->IsRecalcModeAlways() )
+ rDocument.RemoveFromFormulaTree( this );
+
+ std::unique_ptr<ScInterpreter> pScopedInterpreter;
+ if (pInterpreter)
+ pInterpreter->Init(this, aPos, *pCode);
+ else
+ {
+ pScopedInterpreter.reset(new ScInterpreter( this, rDocument, rDocument.GetNonThreadedContext(), aPos, *pCode ));
+ pInterpreter = pScopedInterpreter.get();
+ }
+
+ switch (pInterpreter->GetVolatileType())
+ {
+ case ScInterpreter::VOLATILE_MACRO:
+ // The formula contains a volatile macro.
+ pCode->SetExclusiveRecalcModeAlways();
+ rDocument.PutInFormulaTree(this);
+ StartListeningTo(rDocument);
+ break;
+ case ScInterpreter::NOT_VOLATILE:
+ if (pCode->IsRecalcModeAlways())
+ {
+ // The formula was previously volatile, but no more.
+ EndListeningTo(rDocument);
+ pCode->SetExclusiveRecalcModeNormal();
+ }
+ else
+ {
+ // non-volatile formula. End listening to the area in case
+ // it's listening due to macro module change.
+ rDocument.EndListeningArea(BCA_LISTEN_ALWAYS, false, this);
+ }
+ rDocument.RemoveFromFormulaTree(this);
+ break;
+ default:
+ ;
+ }
+}
+
+void ScFormulaCell::SetCompile( bool bVal )
+{
+ bCompile = bVal;
+}
+
+void ScFormulaCell::SetMatColsRows( SCCOL nCols, SCROW nRows )
+{
+ ScMatrixFormulaCellToken* pMat = aResult.GetMatrixFormulaCellTokenNonConst();
+ if (pMat)
+ pMat->SetMatColsRows( nCols, nRows );
+ else if (nCols || nRows)
+ {
+ aResult.SetToken( new ScMatrixFormulaCellToken( nCols, nRows));
+ // Setting the new token actually forces an empty result at this top
+ // left cell, so have that recalculated.
+ SetDirty();
+ }
+}
+
+void ScFormulaCell::GetMatColsRows( SCCOL & nCols, SCROW & nRows ) const
+{
+ const ScMatrixFormulaCellToken* pMat = aResult.GetMatrixFormulaCellToken();
+ if (pMat)
+ pMat->GetMatColsRows( nCols, nRows);
+ else
+ {
+ nCols = 0;
+ nRows = 0;
+ }
+}
+
+void ScFormulaCell::SetInChangeTrack( bool bVal )
+{
+ bInChangeTrack = bVal;
+}
+
+void ScFormulaCell::Notify( const SfxHint& rHint )
+{
+ if (rDocument.IsInDtorClear())
+ return;
+
+ const SfxHintId nHint = rHint.GetId();
+ if (nHint == SfxHintId::ScReference)
+ {
+ const sc::RefHint& rRefHint = static_cast<const sc::RefHint&>(rHint);
+
+ switch (rRefHint.getType())
+ {
+ case sc::RefHint::ColumnReordered:
+ {
+ const sc::RefColReorderHint& rRefColReorder =
+ static_cast<const sc::RefColReorderHint&>(rRefHint);
+ if (!IsShared() || IsSharedTop())
+ pCode->MoveReferenceColReorder(
+ aPos, rRefColReorder.getTab(),
+ rRefColReorder.getStartRow(),
+ rRefColReorder.getEndRow(),
+ rRefColReorder.getColMap());
+ }
+ break;
+ case sc::RefHint::RowReordered:
+ {
+ const sc::RefRowReorderHint& rRefRowReorder =
+ static_cast<const sc::RefRowReorderHint&>(rRefHint);
+ if (!IsShared() || IsSharedTop())
+ pCode->MoveReferenceRowReorder(
+ aPos, rRefRowReorder.getTab(),
+ rRefRowReorder.getStartColumn(),
+ rRefRowReorder.getEndColumn(),
+ rRefRowReorder.getRowMap());
+ }
+ break;
+ case sc::RefHint::StartListening:
+ {
+ StartListeningTo(rDocument);
+ }
+ break;
+ case sc::RefHint::StopListening:
+ {
+ EndListeningTo(rDocument);
+ }
+ break;
+ default:
+ ;
+ }
+
+ return;
+ }
+
+ if ( rDocument.GetHardRecalcState() != ScDocument::HardRecalcState::OFF )
+ return;
+
+ if (!(nHint == SfxHintId::ScDataChanged || nHint == SfxHintId::ScTableOpDirty || (bSubTotal && nHint == SfxHintId::ScHiddenRowsChanged)))
+ return;
+
+ bool bForceTrack = false;
+ if ( nHint == SfxHintId::ScTableOpDirty )
+ {
+ bForceTrack = !bTableOpDirty;
+ if ( !bTableOpDirty )
+ {
+ rDocument.AddTableOpFormulaCell( this );
+ bTableOpDirty = true;
+ }
+ }
+ else
+ {
+ bForceTrack = !bDirty;
+ SetDirtyVar();
+ }
+ // Don't remove from FormulaTree to put in FormulaTrack to
+ // put in FormulaTree again and again, only if necessary.
+ // Any other means except ScRecalcMode::ALWAYS by which a cell could
+ // be in FormulaTree if it would notify other cells through
+ // FormulaTrack which weren't in FormulaTrack/FormulaTree before?!?
+ // Yes. The new TableOpDirty made it necessary to have a
+ // forced mode where formulas may still be in FormulaTree from
+ // TableOpDirty but have to notify dependents for normal dirty.
+ if ( (bForceTrack || !rDocument.IsInFormulaTree( this )
+ || pCode->IsRecalcModeAlways())
+ && !rDocument.IsInFormulaTrack( this ) )
+ rDocument.AppendToFormulaTrack( this );
+}
+
+void ScFormulaCell::Query( SvtListener::QueryBase& rQuery ) const
+{
+ switch (rQuery.getId())
+ {
+ case SC_LISTENER_QUERY_FORMULA_GROUP_POS:
+ {
+ sc::RefQueryFormulaGroup& rRefQuery =
+ static_cast<sc::RefQueryFormulaGroup&>(rQuery);
+ if (IsShared())
+ rRefQuery.add(aPos);
+ }
+ break;
+ default:
+ ;
+ }
+}
+
+void ScFormulaCell::SetDirty( bool bDirtyFlag )
+{
+ if (IsInChangeTrack())
+ return;
+
+ if ( rDocument.GetHardRecalcState() != ScDocument::HardRecalcState::OFF )
+ {
+ SetDirtyVar();
+ rDocument.SetStreamValid(aPos.Tab(), false);
+ return;
+ }
+
+ // Avoid multiple formula tracking in Load() and in CompileAll()
+ // after CopyScenario() and CopyBlockFromClip().
+ // If unconditional formula tracking is needed, set bDirty=false
+ // before calling SetDirty(), for example in CompileTokenArray().
+ if ( !bDirty || mbPostponedDirty || !rDocument.IsInFormulaTree( this ) )
+ {
+ if( bDirtyFlag )
+ SetDirtyVar();
+ rDocument.AppendToFormulaTrack( this );
+
+ // While loading a document listeners have not been established yet.
+ // Tracking would remove this cell from the FormulaTrack and add it to
+ // the FormulaTree, once in there it would be assumed that its
+ // dependents already had been tracked and it would be skipped on a
+ // subsequent notify. Postpone tracking until all listeners are set.
+ if (!rDocument.IsImportingXML())
+ rDocument.TrackFormulas();
+ }
+
+ rDocument.SetStreamValid(aPos.Tab(), false);
+}
+
+void ScFormulaCell::SetDirtyVar()
+{
+ bDirty = true;
+ mbPostponedDirty = false;
+ if (mxGroup && mxGroup->meCalcState == sc::GroupCalcRunning)
+ {
+ mxGroup->meCalcState = sc::GroupCalcEnabled;
+ mxGroup->mbPartOfCycle = false;
+ }
+
+ // mark the sheet of this cell to be calculated
+ //#FIXME do we need to revert this remnant of old fake vba events? rDocument.AddCalculateTable( aPos.Tab() );
+}
+
+void ScFormulaCell::SetDirtyAfterLoad()
+{
+ bDirty = true;
+ if ( rDocument.GetHardRecalcState() == ScDocument::HardRecalcState::OFF )
+ rDocument.PutInFormulaTree( this );
+}
+
+void ScFormulaCell::ResetTableOpDirtyVar()
+{
+ bTableOpDirty = false;
+}
+
+void ScFormulaCell::SetTableOpDirty()
+{
+ if ( IsInChangeTrack() )
+ return;
+
+ if ( rDocument.GetHardRecalcState() != ScDocument::HardRecalcState::OFF )
+ bTableOpDirty = true;
+ else
+ {
+ if ( !bTableOpDirty || !rDocument.IsInFormulaTree( this ) )
+ {
+ if ( !bTableOpDirty )
+ {
+ rDocument.AddTableOpFormulaCell( this );
+ bTableOpDirty = true;
+ }
+ rDocument.AppendToFormulaTrack( this );
+ rDocument.TrackFormulas( SfxHintId::ScTableOpDirty );
+ }
+ }
+}
+
+void ScFormulaCell::SetResultDouble( double n )
+{
+ aResult.SetDouble(n);
+}
+
+void ScFormulaCell::SetResultToken( const formula::FormulaToken* pToken )
+{
+ aResult.SetToken(pToken);
+}
+
+const svl::SharedString & ScFormulaCell::GetResultString() const
+{
+ return aResult.GetString();
+}
+
+bool ScFormulaCell::HasHybridStringResult() const
+{
+ return aResult.GetType() == formula::svHybridCell && !aResult.GetString().isEmpty();
+}
+
+void ScFormulaCell::SetResultMatrix( SCCOL nCols, SCROW nRows, const ScConstMatrixRef& pMat, const formula::FormulaToken* pUL )
+{
+ aResult.SetMatrix(nCols, nRows, pMat, pUL);
+}
+
+void ScFormulaCell::SetErrCode( FormulaError n )
+{
+ /* FIXME: check the numerous places where ScTokenArray::GetCodeError() is
+ * used whether it is solely for transport of a simple result error and get
+ * rid of that abuse. */
+ pCode->SetCodeError( n );
+ // Hard set errors are transported as result type value per convention,
+ // e.g. via clipboard. ScFormulaResult::IsValue() and
+ // ScFormulaResult::GetDouble() handle that.
+ aResult.SetResultError( n );
+}
+
+void ScFormulaCell::SetResultError( FormulaError n )
+{
+ aResult.SetResultError( n );
+}
+
+void ScFormulaCell::AddRecalcMode( ScRecalcMode nBits )
+{
+ if ( (nBits & ScRecalcMode::EMask) != ScRecalcMode::NORMAL )
+ SetDirtyVar();
+ if ( nBits & ScRecalcMode::ONLOAD_ONCE )
+ { // OnLoadOnce is used only to set Dirty after filter import.
+ nBits = (nBits & ~ScRecalcMode::EMask) | ScRecalcMode::NORMAL;
+ }
+ pCode->AddRecalcMode( nBits );
+}
+
+void ScFormulaCell::SetHybridDouble( double n )
+{
+ aResult.SetHybridDouble( n);
+}
+
+void ScFormulaCell::SetHybridString( const svl::SharedString& r )
+{
+ aResult.SetHybridString( r);
+}
+
+void ScFormulaCell::SetHybridEmptyDisplayedAsString()
+{
+ aResult.SetHybridEmptyDisplayedAsString();
+}
+
+void ScFormulaCell::SetHybridFormula( const OUString& r,
+ const formula::FormulaGrammar::Grammar eGrammar )
+{
+ aResult.SetHybridFormula( r); eTempGrammar = eGrammar;
+}
+
+OUString ScFormulaCell::GetHybridFormula() const
+{
+ return aResult.GetHybridFormula();
+}
+
+// Dynamically create the URLField on a mouse-over action on a hyperlink() cell.
+void ScFormulaCell::GetURLResult( OUString& rURL, OUString& rCellText )
+{
+ OUString aCellString;
+
+ const Color* pColor;
+
+ // Cell Text uses the Cell format while the URL uses
+ // the default format for the type.
+ const sal_uInt32 nCellFormat = rDocument.GetNumberFormat( aPos );
+ SvNumberFormatter* pFormatter = rDocument.GetFormatTable();
+
+ const sal_uInt32 nURLFormat = ScGlobal::GetStandardFormat( *pFormatter, nCellFormat, SvNumFormatType::NUMBER);
+
+ if ( IsValue() )
+ {
+ double fValue = GetValue();
+ pFormatter->GetOutputString( fValue, nCellFormat, rCellText, &pColor );
+ }
+ else
+ {
+ aCellString = GetString().getString();
+ pFormatter->GetOutputString( aCellString, nCellFormat, rCellText, &pColor );
+ }
+ ScConstMatrixRef xMat( aResult.GetMatrix());
+ if (xMat)
+ {
+ // determine if the matrix result is a string or value.
+ if (!xMat->IsValue(0, 1))
+ rURL = xMat->GetString(0, 1).getString();
+ else
+ pFormatter->GetOutputString(
+ xMat->GetDouble(0, 1), nURLFormat, rURL, &pColor);
+ }
+
+ if(rURL.isEmpty())
+ {
+ if(IsValue())
+ pFormatter->GetOutputString( GetValue(), nURLFormat, rURL, &pColor );
+ else
+ pFormatter->GetOutputString( aCellString, nURLFormat, rURL, &pColor );
+ }
+}
+
+bool ScFormulaCell::IsMultilineResult()
+{
+ if (!IsValue())
+ return aResult.IsMultiline();
+ return false;
+}
+
+bool ScFormulaCell::IsHyperLinkCell() const
+{
+ return pCode && pCode->IsHyperLink();
+}
+
+std::unique_ptr<EditTextObject> ScFormulaCell::CreateURLObject()
+{
+ OUString aCellText;
+ OUString aURL;
+ GetURLResult( aURL, aCellText );
+
+ return ScEditUtil::CreateURLObjectFromURL( rDocument, aURL, aCellText );
+}
+
+bool ScFormulaCell::IsEmpty()
+{
+ MaybeInterpret();
+ return aResult.GetCellResultType() == formula::svEmptyCell;
+}
+
+bool ScFormulaCell::IsEmptyDisplayedAsString()
+{
+ MaybeInterpret();
+ return aResult.IsEmptyDisplayedAsString();
+}
+
+bool ScFormulaCell::IsValue()
+{
+ MaybeInterpret();
+ return aResult.IsValue();
+}
+
+bool ScFormulaCell::IsValueNoError()
+{
+ MaybeInterpret();
+ if (pCode->GetCodeError() != FormulaError::NONE)
+ return false;
+
+ return aResult.IsValueNoError();
+}
+
+bool ScFormulaCell::IsValueNoError() const
+{
+ if (NeedsInterpret())
+ // false if the cell is dirty & needs to be interpreted.
+ return false;
+
+ if (pCode->GetCodeError() != FormulaError::NONE)
+ return false;
+
+ return aResult.IsValueNoError();
+}
+
+double ScFormulaCell::GetValue()
+{
+ MaybeInterpret();
+ return GetRawValue();
+}
+
+const svl::SharedString & ScFormulaCell::GetString()
+{
+ MaybeInterpret();
+ return GetRawString();
+}
+
+double ScFormulaCell::GetRawValue() const
+{
+ if ((pCode->GetCodeError() == FormulaError::NONE) &&
+ aResult.GetResultError() == FormulaError::NONE)
+ return aResult.GetDouble();
+ return 0.0;
+}
+
+const svl::SharedString & ScFormulaCell::GetRawString() const
+{
+ if ((pCode->GetCodeError() == FormulaError::NONE) &&
+ aResult.GetResultError() == FormulaError::NONE)
+ return aResult.GetString();
+
+ return svl::SharedString::getEmptyString();
+}
+
+const ScMatrix* ScFormulaCell::GetMatrix()
+{
+ if ( rDocument.GetAutoCalc() )
+ {
+ if( IsDirtyOrInTableOpDirty()
+ // Was stored !bDirty but an accompanying matrix cell was bDirty?
+ || (!bDirty && cMatrixFlag == ScMatrixMode::Formula && !aResult.GetMatrix()))
+ Interpret();
+ }
+ return aResult.GetMatrix().get();
+}
+
+bool ScFormulaCell::GetMatrixOrigin( const ScDocument& rDoc, ScAddress& rPos ) const
+{
+ switch ( cMatrixFlag )
+ {
+ case ScMatrixMode::Formula :
+ rPos = aPos;
+ return true;
+ case ScMatrixMode::Reference :
+ {
+ formula::FormulaTokenArrayPlainIterator aIter(*pCode);
+ formula::FormulaToken* t = aIter.GetNextReferenceRPN();
+ if( t )
+ {
+ ScSingleRefData& rRef = *t->GetSingleRef();
+ ScAddress aAbs = rRef.toAbs(rDoc, aPos);
+ if (rDoc.ValidAddress(aAbs))
+ {
+ rPos = aAbs;
+ return true;
+ }
+ }
+ }
+ break;
+ default: break;
+ }
+ return false;
+}
+
+sc::MatrixEdge ScFormulaCell::GetMatrixEdge( const ScDocument& rDoc, ScAddress& rOrgPos ) const
+{
+ switch ( cMatrixFlag )
+ {
+ case ScMatrixMode::Formula :
+ case ScMatrixMode::Reference :
+ {
+ static thread_local SCCOL nC;
+ static thread_local SCROW nR;
+ ScAddress aOrg;
+ if ( !GetMatrixOrigin( rDoc, aOrg ) )
+ return sc::MatrixEdge::Nothing;
+ if ( aOrg != rOrgPos )
+ { // First time or a different matrix than last time.
+ rOrgPos = aOrg;
+ const ScFormulaCell* pFCell;
+ if ( cMatrixFlag == ScMatrixMode::Reference )
+ pFCell = rDocument.GetFormulaCell(aOrg);
+ else
+ pFCell = this; // this ScMatrixMode::Formula
+ // There's only one this, don't compare pFCell==this.
+ if (pFCell && pFCell->cMatrixFlag == ScMatrixMode::Formula)
+ {
+ pFCell->GetMatColsRows( nC, nR );
+ if ( nC == 0 || nR == 0 )
+ {
+ // No ScMatrixFormulaCellToken available yet, calculate new.
+ nC = 1;
+ nR = 1;
+ ScAddress aTmpOrg;
+ ScFormulaCell* pCell;
+ ScAddress aAdr( aOrg );
+ aAdr.IncCol();
+ bool bCont = true;
+ do
+ {
+ pCell = rDocument.GetFormulaCell(aAdr);
+ if (pCell && pCell->cMatrixFlag == ScMatrixMode::Reference &&
+ pCell->GetMatrixOrigin(rDocument, aTmpOrg) && aTmpOrg == aOrg)
+ {
+ nC++;
+ aAdr.IncCol();
+ }
+ else
+ bCont = false;
+ } while ( bCont );
+ aAdr = aOrg;
+ aAdr.IncRow();
+ bCont = true;
+ do
+ {
+ pCell = rDocument.GetFormulaCell(aAdr);
+ if (pCell && pCell->cMatrixFlag == ScMatrixMode::Reference &&
+ pCell->GetMatrixOrigin(rDocument, aTmpOrg) && aTmpOrg == aOrg)
+ {
+ nR++;
+ aAdr.IncRow();
+ }
+ else
+ bCont = false;
+ } while ( bCont );
+
+ const_cast<ScFormulaCell*>(pFCell)->SetMatColsRows(nC, nR);
+ }
+ }
+ else
+ {
+#if OSL_DEBUG_LEVEL > 0
+ SAL_WARN( "sc", "broken Matrix, no MatFormula at origin, Pos: "
+ << aPos.Format(ScRefFlags::COL_VALID | ScRefFlags::ROW_VALID, &rDocument)
+ << ", MatOrg: "
+ << aOrg.Format(ScRefFlags::COL_VALID | ScRefFlags::ROW_VALID, &rDocument) );
+#endif
+ return sc::MatrixEdge::Nothing;
+ }
+ }
+ // here we are, healthy and clean, somewhere in between
+ SCCOL dC = aPos.Col() - aOrg.Col();
+ SCROW dR = aPos.Row() - aOrg.Row();
+ sc::MatrixEdge nEdges = sc::MatrixEdge::Nothing;
+ if ( dC >= 0 && dR >= 0 && dC < nC && dR < nR )
+ {
+ if ( dC == 0 )
+ nEdges |= sc::MatrixEdge::Left;
+ if ( dC+1 == nC )
+ nEdges |= sc::MatrixEdge::Right;
+ if ( dR == 0 )
+ nEdges |= sc::MatrixEdge::Top;
+ if ( dR+1 == nR )
+ nEdges |= sc::MatrixEdge::Bottom;
+ if ( nEdges == sc::MatrixEdge::Nothing )
+ nEdges = sc::MatrixEdge::Inside;
+ }
+ else
+ {
+ SAL_WARN( "sc", "broken Matrix, Pos: "
+ << aPos.Format(ScRefFlags::COL_VALID | ScRefFlags::ROW_VALID, &rDocument)
+ << ", MatOrg: "
+ << aOrg.Format(ScRefFlags::COL_VALID | ScRefFlags::ROW_VALID, &rDocument)
+ << ", MatCols: " << static_cast<sal_Int32>( nC )
+ << ", MatRows: " << static_cast<sal_Int32>( nR )
+ << ", DiffCols: " << static_cast<sal_Int32>( dC )
+ << ", DiffRows: " << static_cast<sal_Int32>( dR ));
+ }
+ return nEdges;
+ }
+ default:
+ return sc::MatrixEdge::Nothing;
+ }
+}
+
+FormulaError ScFormulaCell::GetErrCode()
+{
+ MaybeInterpret();
+
+ /* FIXME: If ScTokenArray::SetCodeError() was really only for code errors
+ * and not also abused for signaling other error conditions we could bail
+ * out even before attempting to interpret broken code. */
+ FormulaError nErr = pCode->GetCodeError();
+ if (nErr != FormulaError::NONE)
+ return nErr;
+ return aResult.GetResultError();
+}
+
+FormulaError ScFormulaCell::GetRawError() const
+{
+ FormulaError nErr = pCode->GetCodeError();
+ if (nErr != FormulaError::NONE)
+ return nErr;
+ return aResult.GetResultError();
+}
+
+bool ScFormulaCell::GetErrorOrValue( FormulaError& rErr, double& rVal )
+{
+ MaybeInterpret();
+
+ rErr = pCode->GetCodeError();
+ if (rErr != FormulaError::NONE)
+ return true;
+
+ return aResult.GetErrorOrDouble(rErr, rVal);
+}
+
+sc::FormulaResultValue ScFormulaCell::GetResult()
+{
+ MaybeInterpret();
+
+ FormulaError nErr = pCode->GetCodeError();
+ if (nErr != FormulaError::NONE)
+ return sc::FormulaResultValue(nErr);
+
+ return aResult.GetResult();
+}
+
+sc::FormulaResultValue ScFormulaCell::GetResult() const
+{
+ FormulaError nErr = pCode->GetCodeError();
+ if (nErr != FormulaError::NONE)
+ return sc::FormulaResultValue(nErr);
+
+ return aResult.GetResult();
+}
+
+bool ScFormulaCell::HasOneReference( ScRange& r ) const
+{
+ formula::FormulaTokenArrayPlainIterator aIter(*pCode);
+ formula::FormulaToken* p = aIter.GetNextReferenceRPN();
+ if( p && !aIter.GetNextReferenceRPN() ) // only one!
+ {
+ SingleDoubleRefProvider aProv( *p );
+ r.aStart = aProv.Ref1.toAbs(rDocument, aPos);
+ r.aEnd = aProv.Ref2.toAbs(rDocument, aPos);
+ return true;
+ }
+ else
+ return false;
+}
+
+bool
+ScFormulaCell::HasRefListExpressibleAsOneReference(ScRange& rRange) const
+{
+ /* If there appears just one reference in the formula, it's the same
+ as HasOneReference(). If there are more of them, they can denote
+ one range if they are (sole) arguments of one function.
+ Union of these references must form one range and their
+ intersection must be empty set.
+ */
+
+ // Detect the simple case of exactly one reference in advance without all
+ // overhead.
+ // #i107741# Doing so actually makes outlines using SUBTOTAL(x;reference)
+ // work again, where the function does not have only references.
+ if (HasOneReference( rRange))
+ return true;
+
+ // Get first reference, if any
+ formula::FormulaTokenArrayPlainIterator aIter(*pCode);
+ formula::FormulaToken* const pFirstReference(aIter.GetNextReferenceRPN());
+ if (pFirstReference)
+ {
+ // Collect all consecutive references, starting by the one
+ // already found
+ std::vector<formula::FormulaToken*> aReferences { pFirstReference };
+ FormulaToken* pToken(aIter.NextRPN());
+ FormulaToken* pFunction(nullptr);
+ while (pToken)
+ {
+ if (lcl_isReference(*pToken))
+ {
+ aReferences.push_back(pToken);
+ pToken = aIter.NextRPN();
+ }
+ else
+ {
+ if (pToken->IsFunction())
+ {
+ pFunction = pToken;
+ }
+ break;
+ }
+ }
+ if (pFunction && !aIter.GetNextReferenceRPN()
+ && (pFunction->GetParamCount() == aReferences.size()))
+ {
+ return lcl_refListFormsOneRange(rDocument, aPos, aReferences, rRange);
+ }
+ }
+ return false;
+}
+
+ScFormulaCell::RelNameRef ScFormulaCell::HasRelNameReference() const
+{
+ RelNameRef eRelNameRef = RelNameRef::NONE;
+ formula::FormulaTokenArrayPlainIterator aIter(*pCode);
+ formula::FormulaToken* t;
+ while ( ( t = aIter.GetNextReferenceRPN() ) != nullptr )
+ {
+ switch (t->GetType())
+ {
+ case formula::svSingleRef:
+ if (t->GetSingleRef()->IsRelName() && eRelNameRef == RelNameRef::NONE)
+ eRelNameRef = RelNameRef::SINGLE;
+ break;
+ case formula::svDoubleRef:
+ if (t->GetDoubleRef()->Ref1.IsRelName() || t->GetDoubleRef()->Ref2.IsRelName())
+ // May originate from individual cell names, in which case
+ // it needs recompilation.
+ return RelNameRef::DOUBLE;
+ /* TODO: have an extra flag at ScComplexRefData if range was
+ * extended? or too cumbersome? might narrow recompilation to
+ * only needed cases.
+ * */
+ break;
+ default:
+ ; // nothing
+ }
+ }
+ return eRelNameRef;
+}
+
+bool ScFormulaCell::UpdatePosOnShift( const sc::RefUpdateContext& rCxt )
+{
+ if (rCxt.meMode != URM_INSDEL)
+ // Just in case...
+ return false;
+
+ if (!rCxt.mnColDelta && !rCxt.mnRowDelta && !rCxt.mnTabDelta)
+ // No movement.
+ return false;
+
+ if (!rCxt.maRange.Contains(aPos))
+ return false;
+
+ // This formula cell itself is being shifted during cell range
+ // insertion or deletion. Update its position.
+ ScAddress aErrorPos( ScAddress::UNINITIALIZED );
+ if (!aPos.Move(rCxt.mnColDelta, rCxt.mnRowDelta, rCxt.mnTabDelta, aErrorPos, rCxt.mrDoc))
+ {
+ assert(!"can't move ScFormulaCell");
+ }
+
+ return true;
+}
+
+namespace {
+
+/**
+ * Check if we need to re-compile column or row names.
+ */
+bool checkCompileColRowName(
+ const sc::RefUpdateContext& rCxt, ScDocument& rDoc, const ScTokenArray& rCode,
+ const ScAddress& aOldPos, const ScAddress& aPos, bool bValChanged)
+{
+ switch (rCxt.meMode)
+ {
+ case URM_INSDEL:
+ {
+ if (rCxt.mnColDelta <= 0 && rCxt.mnRowDelta <= 0)
+ return false;
+
+ formula::FormulaTokenArrayPlainIterator aIter(rCode);
+ formula::FormulaToken* t;
+ ScRangePairList* pColList = rDoc.GetColNameRanges();
+ ScRangePairList* pRowList = rDoc.GetRowNameRanges();
+ while ((t = aIter.GetNextColRowName()) != nullptr)
+ {
+ ScSingleRefData& rRef = *t->GetSingleRef();
+ if (rCxt.mnRowDelta > 0 && rRef.IsColRel())
+ { // ColName
+ ScAddress aAdr = rRef.toAbs(rDoc, aPos);
+ ScRangePair* pR = pColList->Find( aAdr );
+ if ( pR )
+ { // defined
+ if (pR->GetRange(1).aStart.Row() == rCxt.maRange.aStart.Row())
+ return true;
+ }
+ else
+ { // on the fly
+ if (aAdr.Row() + 1 == rCxt.maRange.aStart.Row())
+ return true;
+ }
+ }
+ if (rCxt.mnColDelta > 0 && rRef.IsRowRel())
+ { // RowName
+ ScAddress aAdr = rRef.toAbs(rDoc, aPos);
+ ScRangePair* pR = pRowList->Find( aAdr );
+ if ( pR )
+ { // defined
+ if ( pR->GetRange(1).aStart.Col() == rCxt.maRange.aStart.Col())
+ return true;
+ }
+ else
+ { // on the fly
+ if (aAdr.Col() + 1 == rCxt.maRange.aStart.Col())
+ return true;
+ }
+ }
+ }
+ }
+ break;
+ case URM_MOVE:
+ { // Recompile for Move/D&D when ColRowName was moved or this Cell
+ // points to one and was moved.
+ bool bMoved = (aPos != aOldPos);
+ if (bMoved)
+ return true;
+
+ formula::FormulaTokenArrayPlainIterator aIter(rCode);
+ const formula::FormulaToken* t = aIter.GetNextColRowName();
+ for (; t; t = aIter.GetNextColRowName())
+ {
+ const ScSingleRefData& rRef = *t->GetSingleRef();
+ ScAddress aAbs = rRef.toAbs(rDoc, aPos);
+ if (rDoc.ValidAddress(aAbs))
+ {
+ if (rCxt.maRange.Contains(aAbs))
+ return true;
+ }
+ }
+ }
+ break;
+ case URM_COPY:
+ return bValChanged;
+ default:
+ ;
+ }
+
+ return false;
+}
+
+void setOldCodeToUndo(
+ ScDocument& rUndoDoc, const ScAddress& aUndoPos, const ScTokenArray* pOldCode, FormulaGrammar::Grammar eTempGrammar, ScMatrixMode cMatrixFlag)
+{
+ // Copy the cell to aUndoPos, which is its current position in the document,
+ // so this works when UpdateReference is called before moving the cells
+ // (InsertCells/DeleteCells - aPos is changed above) as well as when UpdateReference
+ // is called after moving the cells (MoveBlock/PasteFromClip - aOldPos is changed).
+
+ // If there is already a formula cell in the undo document, don't overwrite it,
+ // the first (oldest) is the important cell.
+ if (rUndoDoc.GetCellType(aUndoPos) == CELLTYPE_FORMULA)
+ return;
+
+ ScFormulaCell* pFCell =
+ new ScFormulaCell(
+ rUndoDoc, aUndoPos, pOldCode ? *pOldCode : ScTokenArray(rUndoDoc), eTempGrammar, cMatrixFlag);
+
+ pFCell->SetResultToken(nullptr); // to recognize it as changed later (Cut/Paste!)
+ rUndoDoc.SetFormulaCell(aUndoPos, pFCell);
+}
+
+}
+
+bool ScFormulaCell::UpdateReferenceOnShift(
+ const sc::RefUpdateContext& rCxt, ScDocument* pUndoDoc, const ScAddress* pUndoCellPos )
+{
+ if (rCxt.meMode != URM_INSDEL)
+ // Just in case...
+ return false;
+
+ bool bCellStateChanged = false;
+ ScAddress aUndoPos( aPos ); // position for undo cell in pUndoDoc
+ if ( pUndoCellPos )
+ aUndoPos = *pUndoCellPos;
+ ScAddress aOldPos( aPos );
+ bCellStateChanged = UpdatePosOnShift(rCxt);
+
+ // Check presence of any references or column row names.
+ bool bHasRefs = pCode->HasReferences();
+ bool bHasColRowNames = false;
+ if (!bHasRefs)
+ {
+ bHasColRowNames = (formula::FormulaTokenArrayPlainIterator(*pCode).GetNextColRowName() != nullptr);
+ bHasRefs = bHasColRowNames;
+ }
+ bool bOnRefMove = pCode->IsRecalcModeOnRefMove();
+
+ if (!bHasRefs && !bOnRefMove)
+ // This formula cell contains no references, nor needs recalculating
+ // on reference update. Bail out.
+ return bCellStateChanged;
+
+ std::unique_ptr<ScTokenArray> pOldCode;
+ if (pUndoDoc)
+ pOldCode = pCode->Clone();
+
+ bool bValChanged = false;
+ bool bRefModified = false;
+ bool bRecompile = bCompile;
+
+ if (bHasRefs)
+ {
+ // Update cell or range references.
+ sc::RefUpdateResult aRes = pCode->AdjustReferenceOnShift(rCxt, aOldPos);
+ bRefModified = aRes.mbReferenceModified;
+ bValChanged = aRes.mbValueChanged;
+ if (aRes.mbNameModified)
+ bRecompile = true;
+ }
+
+ if (bValChanged || bRefModified)
+ bCellStateChanged = true;
+
+ if (bOnRefMove)
+ // Cell may reference itself, e.g. ocColumn, ocRow without parameter
+ bOnRefMove = (bValChanged || (aPos != aOldPos) || bRefModified);
+
+ bool bNewListening = false;
+ bool bInDeleteUndo = false;
+
+ if (bHasRefs)
+ {
+ // Upon Insert ColRowNames have to be recompiled in case the
+ // insertion occurs right in front of the range.
+ if (bHasColRowNames && !bRecompile)
+ bRecompile = checkCompileColRowName(rCxt, rDocument, *pCode, aOldPos, aPos, bValChanged);
+
+ ScChangeTrack* pChangeTrack = rDocument.GetChangeTrack();
+ bInDeleteUndo = (pChangeTrack && pChangeTrack->IsInDeleteUndo());
+
+ // RelNameRefs are always moved
+ bool bHasRelName = false;
+ if (!bRecompile)
+ {
+ RelNameRef eRelNameRef = HasRelNameReference();
+ bHasRelName = (eRelNameRef != RelNameRef::NONE);
+ bRecompile = (eRelNameRef == RelNameRef::DOUBLE);
+ }
+ // Reference changed and new listening needed?
+ // Except in Insert/Delete without specialities.
+ bNewListening = (bRefModified || bRecompile
+ || (bValChanged && bInDeleteUndo) || bHasRelName);
+
+ if ( bNewListening )
+ EndListeningTo(rDocument, pOldCode.get(), aOldPos);
+ }
+
+ // NeedDirty for changes except for Copy and Move/Insert without RelNames
+ bool bNeedDirty = (bValChanged || bRecompile || bOnRefMove);
+
+ if (pUndoDoc && (bValChanged || bOnRefMove))
+ setOldCodeToUndo(*pUndoDoc, aUndoPos, pOldCode.get(), eTempGrammar, cMatrixFlag);
+
+ bCompile |= bRecompile;
+ if (bCompile)
+ {
+ CompileTokenArray( bNewListening ); // no Listening
+ bNeedDirty = true;
+ }
+
+ if ( !bInDeleteUndo )
+ { // In ChangeTrack Delete-Reject listeners are established in
+ // InsertCol/InsertRow
+ if ( bNewListening )
+ {
+ // Inserts/Deletes re-establish listeners after all
+ // UpdateReference calls.
+ // All replaced shared formula listeners have to be
+ // established after an Insert or Delete. Do nothing here.
+ SetNeedsListening( true);
+ }
+ }
+
+ if (bNeedDirty)
+ { // Cut off references, invalid or similar?
+ // Postpone SetDirty() until all listeners have been re-established in
+ // Inserts/Deletes.
+ mbPostponedDirty = true;
+ }
+
+ return bCellStateChanged;
+}
+
+bool ScFormulaCell::UpdateReferenceOnMove(
+ const sc::RefUpdateContext& rCxt, ScDocument* pUndoDoc, const ScAddress* pUndoCellPos )
+{
+ if (rCxt.meMode != URM_MOVE)
+ return false;
+
+ ScAddress aUndoPos( aPos ); // position for undo cell in pUndoDoc
+ if ( pUndoCellPos )
+ aUndoPos = *pUndoCellPos;
+ ScAddress aOldPos( aPos );
+
+ bool bCellInMoveTarget = rCxt.maRange.Contains(aPos);
+
+ if ( bCellInMoveTarget )
+ {
+ // The cell is being moved or copied to a new position. I guess the
+ // position has been updated prior to this call? Determine
+ // its original position before the move which will be used to adjust
+ // relative references later.
+ aOldPos.Set(aPos.Col() - rCxt.mnColDelta, aPos.Row() - rCxt.mnRowDelta, aPos.Tab() - rCxt.mnTabDelta);
+ }
+
+ // Check presence of any references or column row names.
+ bool bHasRefs = pCode->HasReferences();
+ bool bHasColRowNames = false;
+ if (!bHasRefs)
+ {
+ bHasColRowNames = (formula::FormulaTokenArrayPlainIterator(*pCode).GetNextColRowName() != nullptr);
+ bHasRefs = bHasColRowNames;
+ }
+ bool bOnRefMove = pCode->IsRecalcModeOnRefMove();
+
+ if (!bHasRefs && !bOnRefMove)
+ // This formula cell contains no references, nor needs recalculating
+ // on reference update. Bail out.
+ return false;
+
+ bool bCellStateChanged = false;
+ std::unique_ptr<ScTokenArray> pOldCode;
+ if (pUndoDoc)
+ pOldCode = pCode->Clone();
+
+ bool bValChanged = false;
+ bool bRefModified = false;
+
+ if (bHasRefs)
+ {
+ // Update cell or range references.
+ sc::RefUpdateResult aRes = pCode->AdjustReferenceOnMove(rCxt, aOldPos, aPos);
+ bRefModified = aRes.mbReferenceModified || aRes.mbNameModified;
+ bValChanged = aRes.mbValueChanged;
+ if (aRes.mbNameModified)
+ // Re-compile to get the RPN token regenerated to reflect updated names.
+ bCompile = true;
+ }
+
+ if (bValChanged || bRefModified)
+ bCellStateChanged = true;
+
+ if (bOnRefMove)
+ // Cell may reference itself, e.g. ocColumn, ocRow without parameter
+ bOnRefMove = (bValChanged || (aPos != aOldPos));
+
+ bool bColRowNameCompile = false;
+ bool bHasRelName = false;
+ bool bNewListening = false;
+ bool bInDeleteUndo = false;
+
+ if (bHasRefs)
+ {
+ // Upon Insert ColRowNames have to be recompiled in case the
+ // insertion occurs right in front of the range.
+ if (bHasColRowNames)
+ bColRowNameCompile = checkCompileColRowName(rCxt, rDocument, *pCode, aOldPos, aPos, bValChanged);
+
+ ScChangeTrack* pChangeTrack = rDocument.GetChangeTrack();
+ bInDeleteUndo = (pChangeTrack && pChangeTrack->IsInDeleteUndo());
+
+ // RelNameRefs are always moved
+ RelNameRef eRelNameRef = HasRelNameReference();
+ bHasRelName = (eRelNameRef != RelNameRef::NONE);
+ bCompile |= (eRelNameRef == RelNameRef::DOUBLE);
+ // Reference changed and new listening needed?
+ // Except in Insert/Delete without specialties.
+ bNewListening = (bRefModified || bColRowNameCompile
+ || bValChanged || bHasRelName)
+ // #i36299# Don't duplicate action during cut&paste / drag&drop
+ // on a cell in the range moved, start/end listeners is done
+ // via ScDocument::DeleteArea() and ScDocument::CopyFromClip().
+ && !(rDocument.IsInsertingFromOtherDoc() && rCxt.maRange.Contains(aPos));
+
+ if ( bNewListening )
+ EndListeningTo(rDocument, pOldCode.get(), aOldPos);
+ }
+
+ bool bNeedDirty = false;
+ // NeedDirty for changes except for Copy and Move/Insert without RelNames
+ if ( bRefModified || bColRowNameCompile ||
+ (bValChanged && bHasRelName ) || bOnRefMove)
+ bNeedDirty = true;
+
+ if (pUndoDoc && !bCellInMoveTarget && (bValChanged || bRefModified || bOnRefMove))
+ setOldCodeToUndo(*pUndoDoc, aUndoPos, pOldCode.get(), eTempGrammar, cMatrixFlag);
+
+ bValChanged = false;
+
+ bCompile = (bCompile || bValChanged || bColRowNameCompile);
+ if ( bCompile )
+ {
+ CompileTokenArray( bNewListening ); // no Listening
+ bNeedDirty = true;
+ }
+
+ if ( !bInDeleteUndo )
+ { // In ChangeTrack Delete-Reject listeners are established in
+ // InsertCol/InsertRow
+ if ( bNewListening )
+ {
+ StartListeningTo( rDocument );
+ }
+ }
+
+ if (bNeedDirty)
+ { // Cut off references, invalid or similar?
+ sc::AutoCalcSwitch aACSwitch(rDocument, false);
+ SetDirty();
+ }
+
+ return bCellStateChanged;
+}
+
+bool ScFormulaCell::UpdateReferenceOnCopy(
+ const sc::RefUpdateContext& rCxt, ScDocument* pUndoDoc, const ScAddress* pUndoCellPos )
+{
+ if (rCxt.meMode != URM_COPY)
+ return false;
+
+ ScAddress aUndoPos( aPos ); // position for undo cell in pUndoDoc
+ if ( pUndoCellPos )
+ aUndoPos = *pUndoCellPos;
+ ScAddress aOldPos( aPos );
+
+ if (rCxt.maRange.Contains(aPos))
+ {
+ // The cell is being moved or copied to a new position. I guess the
+ // position has been updated prior to this call? Determine
+ // its original position before the move which will be used to adjust
+ // relative references later.
+ aOldPos.Set(aPos.Col() - rCxt.mnColDelta, aPos.Row() - rCxt.mnRowDelta, aPos.Tab() - rCxt.mnTabDelta);
+ }
+
+ // Check presence of any references or column row names.
+ bool bHasRefs = pCode->HasReferences();
+ bool bHasColRowNames = (formula::FormulaTokenArrayPlainIterator(*pCode).GetNextColRowName() != nullptr);
+ bHasRefs = bHasRefs || bHasColRowNames;
+ bool bOnRefMove = pCode->IsRecalcModeOnRefMove();
+
+ if (!bHasRefs && !bOnRefMove)
+ // This formula cell contains no references, nor needs recalculating
+ // on reference update. Bail out.
+ return false;
+
+ std::unique_ptr<ScTokenArray> pOldCode;
+ if (pUndoDoc)
+ pOldCode = pCode->Clone();
+
+ if (bOnRefMove)
+ // Cell may reference itself, e.g. ocColumn, ocRow without parameter
+ bOnRefMove = (aPos != aOldPos);
+
+ bool bNeedDirty = bOnRefMove;
+
+ if (pUndoDoc && bOnRefMove)
+ setOldCodeToUndo(*pUndoDoc, aUndoPos, pOldCode.get(), eTempGrammar, cMatrixFlag);
+
+ if (bCompile)
+ {
+ CompileTokenArray(); // no Listening
+ bNeedDirty = true;
+ }
+
+ if (bNeedDirty)
+ { // Cut off references, invalid or similar?
+ sc::AutoCalcSwitch aACSwitch(rDocument, false);
+ SetDirty();
+ }
+
+ return false;
+}
+
+bool ScFormulaCell::UpdateReference(
+ const sc::RefUpdateContext& rCxt, ScDocument* pUndoDoc, const ScAddress* pUndoCellPos )
+{
+ if (rDocument.IsClipOrUndo())
+ return false;
+
+ if (mxGroup && mxGroup->mpTopCell != this)
+ {
+ // This is not a top cell of a formula group. Don't update references.
+
+ switch (rCxt.meMode)
+ {
+ case URM_INSDEL:
+ return UpdatePosOnShift(rCxt);
+ default:
+ ;
+ }
+ return false;
+ }
+
+ switch (rCxt.meMode)
+ {
+ case URM_INSDEL:
+ return UpdateReferenceOnShift(rCxt, pUndoDoc, pUndoCellPos);
+ case URM_MOVE:
+ return UpdateReferenceOnMove(rCxt, pUndoDoc, pUndoCellPos);
+ case URM_COPY:
+ return UpdateReferenceOnCopy(rCxt, pUndoDoc, pUndoCellPos);
+ default:
+ ;
+ }
+
+ return false;
+}
+
+void ScFormulaCell::UpdateInsertTab( const sc::RefUpdateInsertTabContext& rCxt )
+{
+ // Adjust tokens only when it's not grouped or grouped top cell.
+ bool bAdjustCode = !mxGroup || mxGroup->mpTopCell == this;
+ bool bPosChanged = (rCxt.mnInsertPos <= aPos.Tab());
+ if (rDocument.IsClipOrUndo() || !pCode->HasReferences())
+ {
+ if (bPosChanged)
+ aPos.IncTab(rCxt.mnSheets);
+
+ return;
+ }
+
+ EndListeningTo( rDocument );
+ ScAddress aOldPos = aPos;
+ // IncTab _after_ EndListeningTo and _before_ Compiler UpdateInsertTab!
+ if (bPosChanged)
+ aPos.IncTab(rCxt.mnSheets);
+
+ if (!bAdjustCode)
+ return;
+
+ sc::RefUpdateResult aRes = pCode->AdjustReferenceOnInsertedTab(rCxt, aOldPos);
+ if (aRes.mbNameModified)
+ // Re-compile after new sheet(s) have been inserted.
+ bCompile = true;
+
+ // no StartListeningTo because the new sheets have not been inserted yet.
+}
+
+void ScFormulaCell::UpdateDeleteTab( const sc::RefUpdateDeleteTabContext& rCxt )
+{
+ // Adjust tokens only when it's not grouped or grouped top cell.
+ bool bAdjustCode = !mxGroup || mxGroup->mpTopCell == this;
+ bool bPosChanged = (aPos.Tab() >= rCxt.mnDeletePos + rCxt.mnSheets);
+ if (rDocument.IsClipOrUndo() || !pCode->HasReferences())
+ {
+ if (bPosChanged)
+ aPos.IncTab(-1*rCxt.mnSheets);
+ return;
+ }
+
+ EndListeningTo( rDocument );
+ // IncTab _after_ EndListeningTo and _before_ Compiler UpdateDeleteTab!
+ ScAddress aOldPos = aPos;
+ if (bPosChanged)
+ aPos.IncTab(-1*rCxt.mnSheets);
+
+ if (!bAdjustCode)
+ return;
+
+ sc::RefUpdateResult aRes = pCode->AdjustReferenceOnDeletedTab(rCxt, aOldPos);
+ if (aRes.mbNameModified)
+ // Re-compile after sheet(s) have been deleted.
+ bCompile = true;
+}
+
+void ScFormulaCell::UpdateMoveTab( const sc::RefUpdateMoveTabContext& rCxt, SCTAB nTabNo )
+{
+ // Adjust tokens only when it's not grouped or grouped top cell.
+ bool bAdjustCode = !mxGroup || mxGroup->mpTopCell == this;
+
+ if (!pCode->HasReferences() || rDocument.IsClipOrUndo())
+ {
+ aPos.SetTab(nTabNo);
+ return;
+ }
+
+ EndListeningTo(rDocument);
+ ScAddress aOldPos = aPos;
+ // SetTab _after_ EndListeningTo and _before_ Compiler UpdateMoveTab !
+ aPos.SetTab(nTabNo);
+
+ // no StartListeningTo because pTab[nTab] not yet correct!
+
+ if (!bAdjustCode)
+ return;
+
+ sc::RefUpdateResult aRes = pCode->AdjustReferenceOnMovedTab(rCxt, aOldPos);
+ if (aRes.mbNameModified)
+ // Re-compile after sheet(s) have been deleted.
+ bCompile = true;
+}
+
+void ScFormulaCell::UpdateInsertTabAbs(SCTAB nTable)
+{
+ if (rDocument.IsClipOrUndo())
+ return;
+
+ bool bAdjustCode = !mxGroup || mxGroup->mpTopCell == this;
+ if (!bAdjustCode)
+ return;
+
+ formula::FormulaTokenArrayPlainIterator aIter(*pCode);
+ formula::FormulaToken* p = aIter.GetNextReferenceRPN();
+ while (p)
+ {
+ ScSingleRefData& rRef1 = *p->GetSingleRef();
+ if (!rRef1.IsTabRel() && nTable <= rRef1.Tab())
+ rRef1.IncTab(1);
+ if (p->GetType() == formula::svDoubleRef)
+ {
+ ScSingleRefData& rRef2 = p->GetDoubleRef()->Ref2;
+ if (!rRef2.IsTabRel() && nTable <= rRef2.Tab())
+ rRef2.IncTab(1);
+ }
+ p = aIter.GetNextReferenceRPN();
+ }
+}
+
+bool ScFormulaCell::TestTabRefAbs(SCTAB nTable)
+{
+ if (rDocument.IsClipOrUndo())
+ return false;
+
+ bool bAdjustCode = !mxGroup || mxGroup->mpTopCell == this;
+ if (!bAdjustCode)
+ return false;
+
+ bool bRet = false;
+ formula::FormulaTokenArrayPlainIterator aIter(*pCode);
+ formula::FormulaToken* p = aIter.GetNextReferenceRPN();
+ while (p)
+ {
+ ScSingleRefData& rRef1 = *p->GetSingleRef();
+ if (!rRef1.IsTabRel())
+ {
+ if (nTable != rRef1.Tab())
+ bRet = true;
+ else if (nTable != aPos.Tab())
+ rRef1.SetAbsTab(aPos.Tab());
+ }
+ if (p->GetType() == formula::svDoubleRef)
+ {
+ ScSingleRefData& rRef2 = p->GetDoubleRef()->Ref2;
+ if (!rRef2.IsTabRel())
+ {
+ if(nTable != rRef2.Tab())
+ bRet = true;
+ else if (nTable != aPos.Tab())
+ rRef2.SetAbsTab(aPos.Tab());
+ }
+ }
+ p = aIter.GetNextReferenceRPN();
+ }
+ return bRet;
+}
+
+void ScFormulaCell::UpdateCompile( bool bForceIfNameInUse )
+{
+ if ( bForceIfNameInUse && !bCompile )
+ bCompile = pCode->HasNameOrColRowName();
+ if ( bCompile )
+ pCode->SetCodeError( FormulaError::NONE ); // make sure it will really be compiled
+ CompileTokenArray();
+}
+
+static void lcl_TransposeReference(ScSingleRefData& rRef)
+{
+ // References to or over filtered rows are not adjusted
+ // analog to the normal (non-transposed) case
+ SCCOLROW nTemp = rRef.Col();
+ rRef.SetRelCol(rRef.Row());
+ rRef.SetRelRow(nTemp);
+}
+
+// Reference transposition is only called in Clipboard Document
+void ScFormulaCell::TransposeReference()
+{
+ bool bFound = false;
+ formula::FormulaTokenArrayPlainIterator aIter(*pCode);
+ formula::FormulaToken* t;
+ while ( ( t = aIter.GetNextReference() ) != nullptr )
+ {
+ ScSingleRefData& rRef1 = *t->GetSingleRef();
+ if ( rRef1.IsColRel() && rRef1.IsRowRel() )
+ {
+ bool bDouble = (t->GetType() == formula::svDoubleRef);
+ ScSingleRefData& rRef2 = (bDouble ? t->GetDoubleRef()->Ref2 : rRef1);
+ if ( !bDouble || (rRef2.IsColRel() && rRef2.IsRowRel()) )
+ {
+ lcl_TransposeReference(rRef1);
+
+ if ( bDouble )
+ lcl_TransposeReference(rRef2);
+
+ bFound = true;
+ }
+ }
+ }
+
+ if (bFound)
+ bCompile = true;
+}
+
+void ScFormulaCell::UpdateTranspose( const ScRange& rSource, const ScAddress& rDest,
+ ScDocument* pUndoDoc )
+{
+ EndListeningTo( rDocument );
+
+ ScAddress aOldPos = aPos;
+ bool bPosChanged = false; // Whether this cell has been moved
+
+ // Dest range is transposed
+ ScRange aDestRange( rDest, ScAddress(
+ static_cast<SCCOL>(rDest.Col() + rSource.aEnd.Row() - rSource.aStart.Row()),
+ static_cast<SCROW>(rDest.Row() + rSource.aEnd.Col() - rSource.aStart.Col()),
+ rDest.Tab() + rSource.aEnd.Tab() - rSource.aStart.Tab() ) );
+
+ // cell within range
+ if ( aDestRange.Contains( aOldPos ) )
+ {
+ // References of these cells were not changed by ScTokenArray::AdjustReferenceOnMove()
+ // Count back Positions
+ SCCOL nRelPosX = aOldPos.Col();
+ SCROW nRelPosY = aOldPos.Row();
+ SCTAB nRelPosZ = aOldPos.Tab();
+ ScRefUpdate::DoTranspose( nRelPosX, nRelPosY, nRelPosZ, rDocument, aDestRange, rSource.aStart );
+ aOldPos.Set( nRelPosX, nRelPosY, nRelPosZ );
+ bPosChanged = true;
+ }
+
+ std::unique_ptr<ScTokenArray> pOld;
+ if (pUndoDoc)
+ pOld = pCode->Clone();
+ bool bRefChanged = false;
+
+ formula::FormulaTokenArrayPlainIterator aIter(*pCode);
+ formula::FormulaToken* t;
+ while( (t = aIter.GetNextReferenceOrName()) != nullptr )
+ {
+ if( t->GetOpCode() == ocName )
+ {
+ const ScRangeData* pName = rDocument.FindRangeNameBySheetAndIndex( t->GetSheet(), t->GetIndex());
+ if (pName && pName->IsModified())
+ bRefChanged = true;
+ }
+ else if( t->GetType() != svIndex )
+ {
+ SingleDoubleRefModifier aMod(*t);
+ ScComplexRefData& rRef = aMod.Ref();
+ ScRange aAbs = rRef.toAbs(rDocument, aOldPos);
+ bool bMod = (ScRefUpdate::UpdateTranspose(rDocument, rSource, rDest, aAbs) != UR_NOTHING || bPosChanged);
+ if (bMod)
+ {
+ rRef.SetRange(rDocument.GetSheetLimits(), aAbs, aPos); // based on the new anchor position.
+ bRefChanged = true;
+
+ // Absolute sheet reference => set 3D flag.
+ // More than one sheet referenced => has to have both 3D flags.
+ // If end part has 3D flag => start part must have it too.
+ // The same behavior as in ScTokenArray::AdjustReferenceOnMove() is used for 3D-Flags.
+ rRef.Ref2.SetFlag3D(aAbs.aStart.Tab() != aAbs.aEnd.Tab() || !rRef.Ref2.IsTabRel());
+ rRef.Ref1.SetFlag3D(
+ (rSource.aStart.Tab() != rDest.Tab() && !bPosChanged)
+ || !rRef.Ref1.IsTabRel() || rRef.Ref2.IsFlag3D());
+ }
+ }
+ }
+
+ if (bRefChanged)
+ {
+ if (pUndoDoc)
+ {
+ // Similar to setOldCodeToUndo(), but it cannot be used due to the check
+ // pUndoDoc->GetCellType(aPos) == CELLTYPE_FORMULA
+ ScFormulaCell* pFCell = new ScFormulaCell(
+ *pUndoDoc, aPos, pOld ? *pOld : ScTokenArray(*pUndoDoc), eTempGrammar, cMatrixFlag);
+
+ pFCell->aResult.SetToken( nullptr); // to recognize it as changed later (Cut/Paste!)
+ pUndoDoc->SetFormulaCell(aPos, pFCell);
+ }
+
+ bCompile = true;
+ CompileTokenArray(); // also call StartListeningTo
+ SetDirty();
+ }
+ else
+ StartListeningTo( rDocument ); // Listener as previous
+}
+
+void ScFormulaCell::UpdateGrow( const ScRange& rArea, SCCOL nGrowX, SCROW nGrowY )
+{
+ EndListeningTo( rDocument );
+
+ bool bRefChanged = false;
+
+ formula::FormulaTokenArrayPlainIterator aIter(*pCode);
+ formula::FormulaToken* t;
+
+ while( (t = aIter.GetNextReferenceOrName()) != nullptr )
+ {
+ if( t->GetOpCode() == ocName )
+ {
+ const ScRangeData* pName = rDocument.FindRangeNameBySheetAndIndex( t->GetSheet(), t->GetIndex());
+ if (pName && pName->IsModified())
+ bRefChanged = true;
+ }
+ else if( t->GetType() != svIndex )
+ {
+ SingleDoubleRefModifier aMod(*t);
+ ScComplexRefData& rRef = aMod.Ref();
+ ScRange aAbs = rRef.toAbs(rDocument, aPos);
+ bool bMod = (ScRefUpdate::UpdateGrow(rArea, nGrowX, nGrowY, aAbs) != UR_NOTHING);
+ if (bMod)
+ {
+ rRef.SetRange(rDocument.GetSheetLimits(), aAbs, aPos);
+ bRefChanged = true;
+ }
+ }
+ }
+
+ if (bRefChanged)
+ {
+ bCompile = true;
+ CompileTokenArray(); // Also call StartListeningTo
+ SetDirty();
+ }
+ else
+ StartListeningTo( rDocument ); // Listener as previous
+}
+
+// See also ScDocument::FindRangeNamesReferencingSheet()
+static void lcl_FindRangeNamesInUse(sc::UpdatedRangeNames& rIndexes, const ScTokenArray* pCode, const ScDocument& rDoc,
+ int nRecursion)
+{
+ FormulaTokenArrayPlainIterator aIter(*pCode);
+ for (FormulaToken* p = aIter.First(); p; p = aIter.Next())
+ {
+ if (p->GetOpCode() == ocName)
+ {
+ sal_uInt16 nTokenIndex = p->GetIndex();
+ SCTAB nTab = p->GetSheet();
+ rIndexes.setUpdatedName( nTab, nTokenIndex);
+
+ if (nRecursion < 126) // whatever... 42*3
+ {
+ ScRangeData* pSubName = rDoc.FindRangeNameBySheetAndIndex( nTab, nTokenIndex);
+ if (pSubName)
+ lcl_FindRangeNamesInUse(rIndexes, pSubName->GetCode(), rDoc, nRecursion+1);
+ }
+ }
+ }
+}
+
+void ScFormulaCell::FindRangeNamesInUse(sc::UpdatedRangeNames& rIndexes) const
+{
+ lcl_FindRangeNamesInUse( rIndexes, pCode, rDocument, 0);
+}
+
+void ScFormulaCell::SetChanged(bool b)
+{
+ bChanged = b;
+}
+
+void ScFormulaCell::SetCode( std::unique_ptr<ScTokenArray> pNew )
+{
+ assert(!mxGroup); // Don't call this if it's shared.
+ delete pCode;
+ pCode = pNew.release(); // takes ownership.
+}
+
+void ScFormulaCell::SetRunning( bool bVal )
+{
+ bRunning = bVal;
+}
+
+void ScFormulaCell::CompileDBFormula( sc::CompileFormulaContext& rCxt )
+{
+ FormulaTokenArrayPlainIterator aIter(*pCode);
+ for( FormulaToken* p = aIter.First(); p; p = aIter.Next() )
+ {
+ OpCode eOp = p->GetOpCode();
+ if ( eOp == ocDBArea || eOp == ocTableRef )
+ {
+ bCompile = true;
+ CompileTokenArray(rCxt);
+ SetDirty();
+ break;
+ }
+ }
+}
+
+void ScFormulaCell::CompileColRowNameFormula( sc::CompileFormulaContext& rCxt )
+{
+ FormulaTokenArrayPlainIterator aIter(*pCode);
+ for ( FormulaToken* p = aIter.First(); p; p = aIter.Next() )
+ {
+ if ( p->GetOpCode() == ocColRowName )
+ {
+ bCompile = true;
+ CompileTokenArray(rCxt);
+ SetDirty();
+ break;
+ }
+ }
+}
+
+void ScFormulaCell::SetPrevious( ScFormulaCell* pF ) { pPrevious = pF; }
+void ScFormulaCell::SetNext( ScFormulaCell* pF ) { pNext = pF; }
+void ScFormulaCell::SetPreviousTrack( ScFormulaCell* pF ) { pPreviousTrack = pF; }
+void ScFormulaCell::SetNextTrack( ScFormulaCell* pF ) { pNextTrack = pF; }
+
+ScFormulaCellGroupRef ScFormulaCell::CreateCellGroup( SCROW nLen, bool bInvariant )
+{
+ if (mxGroup)
+ {
+ // You can't create a new group if the cell is already a part of a group.
+ // Is this a sign of some inconsistent or incorrect data structures? Or normal?
+ SAL_INFO("sc.opencl", "You can't create a new group if the cell is already a part of a group");
+ return ScFormulaCellGroupRef();
+ }
+
+ mxGroup.reset(new ScFormulaCellGroup);
+ mxGroup->mpTopCell = this;
+ mxGroup->mbInvariant = bInvariant;
+ mxGroup->mnLength = nLen;
+ mxGroup->mpCode = std::move(*pCode); // Move this to the shared location.
+ delete pCode;
+ pCode = &*mxGroup->mpCode;
+ return mxGroup;
+}
+
+void ScFormulaCell::SetCellGroup( const ScFormulaCellGroupRef &xRef )
+{
+ if (!xRef)
+ {
+ // Make this cell a non-grouped cell.
+ if (mxGroup)
+ pCode = mxGroup->mpCode->Clone().release();
+
+ mxGroup = xRef;
+ return;
+ }
+
+ // Group object has shared token array.
+ if (!mxGroup)
+ // Currently not shared. Delete the existing token array first.
+ delete pCode;
+
+ mxGroup = xRef;
+ pCode = &*mxGroup->mpCode;
+ mxGroup->mnWeight = 0; // invalidate
+}
+
+ScFormulaCell::CompareState ScFormulaCell::CompareByTokenArray( const ScFormulaCell& rOther ) const
+{
+ // no Matrix formulae yet.
+ if ( GetMatrixFlag() != ScMatrixMode::NONE )
+ return NotEqual;
+
+ // are these formulas at all similar ?
+ if ( GetHash() != rOther.GetHash() )
+ return NotEqual;
+
+ if (!pCode->IsShareable() || !rOther.pCode->IsShareable())
+ return NotEqual;
+
+ FormulaToken **pThis = pCode->GetCode();
+ sal_uInt16 nThisLen = pCode->GetCodeLen();
+ FormulaToken **pOther = rOther.pCode->GetCode();
+ sal_uInt16 nOtherLen = rOther.pCode->GetCodeLen();
+
+ if ( !pThis || !pOther )
+ {
+ // Error: no compiled code for cells !"
+ return NotEqual;
+ }
+
+ if ( nThisLen != nOtherLen )
+ return NotEqual;
+
+ // No tokens can be an error cell so check error code, otherwise we could
+ // end up with a series of equal error values instead of individual error
+ // values. Also if for any reason different errors are set even if all
+ // tokens are equal, the cells are not equal.
+ if (pCode->GetCodeError() != rOther.pCode->GetCodeError())
+ return NotEqual;
+
+ bool bInvariant = true;
+
+ // check we are basically the same function
+ for ( sal_uInt16 i = 0; i < nThisLen; i++ )
+ {
+ formula::FormulaToken *pThisTok = pThis[i];
+ formula::FormulaToken *pOtherTok = pOther[i];
+
+ if ( pThisTok->GetType() != pOtherTok->GetType() ||
+ pThisTok->GetOpCode() != pOtherTok->GetOpCode() ||
+ pThisTok->GetParamCount() != pOtherTok->GetParamCount() )
+ {
+ // Incompatible type, op-code or param counts.
+ return NotEqual;
+ }
+
+ switch (pThisTok->GetType())
+ {
+ case formula::svMatrix:
+ case formula::svExternalSingleRef:
+ case formula::svExternalDoubleRef:
+ // Ignoring matrix and external references for now.
+ return NotEqual;
+
+ case formula::svSingleRef:
+ {
+ // Single cell reference.
+ const ScSingleRefData& rRef = *pThisTok->GetSingleRef();
+ if (rRef != *pOtherTok->GetSingleRef())
+ return NotEqual;
+
+ if (rRef.IsRowRel())
+ bInvariant = false;
+ }
+ break;
+ case formula::svDoubleRef:
+ {
+ // Range reference.
+ const ScSingleRefData& rRef1 = *pThisTok->GetSingleRef();
+ const ScSingleRefData& rRef2 = *pThisTok->GetSingleRef2();
+ if (rRef1 != *pOtherTok->GetSingleRef())
+ return NotEqual;
+
+ if (rRef2 != *pOtherTok->GetSingleRef2())
+ return NotEqual;
+
+ if (rRef1.IsRowRel())
+ bInvariant = false;
+
+ if (rRef2.IsRowRel())
+ bInvariant = false;
+ }
+ break;
+ case formula::svDouble:
+ {
+ if(!rtl::math::approxEqual(pThisTok->GetDouble(), pOtherTok->GetDouble()))
+ return NotEqual;
+ }
+ break;
+ case formula::svString:
+ {
+ if(pThisTok->GetString() != pOtherTok->GetString())
+ return NotEqual;
+ }
+ break;
+ case formula::svIndex:
+ {
+ if(pThisTok->GetIndex() != pOtherTok->GetIndex() || pThisTok->GetSheet() != pOtherTok->GetSheet())
+ return NotEqual;
+ }
+ break;
+ case formula::svByte:
+ {
+ if(pThisTok->GetByte() != pOtherTok->GetByte())
+ return NotEqual;
+ }
+ break;
+ case formula::svExternal:
+ {
+ if (pThisTok->GetExternal() != pOtherTok->GetExternal())
+ return NotEqual;
+
+ if (pThisTok->GetByte() != pOtherTok->GetByte())
+ return NotEqual;
+ }
+ break;
+ case formula::svError:
+ {
+ if (pThisTok->GetError() != pOtherTok->GetError())
+ return NotEqual;
+ }
+ break;
+ default:
+ ;
+ }
+ }
+
+ // If still the same, check lexical names as different names may result in
+ // identical RPN code.
+
+ pThis = pCode->GetArray();
+ nThisLen = pCode->GetLen();
+ pOther = rOther.pCode->GetArray();
+ nOtherLen = rOther.pCode->GetLen();
+
+ if ( !pThis || !pOther )
+ {
+ // Error: no code for cells !"
+ return NotEqual;
+ }
+
+ if ( nThisLen != nOtherLen )
+ return NotEqual;
+
+ for ( sal_uInt16 i = 0; i < nThisLen; i++ )
+ {
+ formula::FormulaToken *pThisTok = pThis[i];
+ formula::FormulaToken *pOtherTok = pOther[i];
+
+ if ( pThisTok->GetType() != pOtherTok->GetType() ||
+ pThisTok->GetOpCode() != pOtherTok->GetOpCode() ||
+ pThisTok->GetParamCount() != pOtherTok->GetParamCount() )
+ {
+ // Incompatible type, op-code or param counts.
+ return NotEqual;
+ }
+
+ switch (pThisTok->GetType())
+ {
+ // ScCompiler::HandleIIOpCode() may optimize some refs only in RPN code,
+ // resulting in identical RPN references that could lead to creating
+ // a formula group from formulas that should not be merged into a group,
+ // so check also the formula itself.
+ case formula::svSingleRef:
+ {
+ // Single cell reference.
+ const ScSingleRefData& rRef = *pThisTok->GetSingleRef();
+ if (rRef != *pOtherTok->GetSingleRef())
+ return NotEqual;
+
+ if (rRef.IsRowRel())
+ bInvariant = false;
+ }
+ break;
+ case formula::svDoubleRef:
+ {
+ // Range reference.
+ const ScSingleRefData& rRef1 = *pThisTok->GetSingleRef();
+ const ScSingleRefData& rRef2 = *pThisTok->GetSingleRef2();
+ if (rRef1 != *pOtherTok->GetSingleRef())
+ return NotEqual;
+
+ if (rRef2 != *pOtherTok->GetSingleRef2())
+ return NotEqual;
+
+ if (rRef1.IsRowRel())
+ bInvariant = false;
+
+ if (rRef2.IsRowRel())
+ bInvariant = false;
+ }
+ break;
+ // All index tokens are names. Different categories already had
+ // different OpCode values.
+ case formula::svIndex:
+ {
+ if (pThisTok->GetIndex() != pOtherTok->GetIndex())
+ return NotEqual;
+ switch (pThisTok->GetOpCode())
+ {
+ case ocTableRef:
+ // nothing, sheet value assumed as -1, silence
+ // ScTableRefToken::GetSheet() SAL_WARN about
+ // unhandled
+ ;
+ break;
+ default: // ocName, ocDBArea
+ if (pThisTok->GetSheet() != pOtherTok->GetSheet())
+ return NotEqual;
+ }
+ }
+ break;
+ default:
+ ;
+ }
+ }
+
+ return bInvariant ? EqualInvariant : EqualRelativeRef;
+}
+
+namespace {
+
+// Split N into optimally equal-sized pieces, each not larger than K.
+// Return value P is number of pieces. A returns the number of pieces
+// one larger than N/P, 0..P-1.
+
+int splitup(int N, int K, int& A)
+{
+ assert(N > 0);
+ assert(K > 0);
+
+ A = 0;
+
+ if (N <= K)
+ return 1;
+
+ const int ideal_num_parts = N / K;
+ if (ideal_num_parts * K == N)
+ return ideal_num_parts;
+
+ const int num_parts = ideal_num_parts + 1;
+ const int nominal_part_size = N / num_parts;
+
+ A = N - num_parts * nominal_part_size;
+
+ return num_parts;
+}
+
+struct ScDependantsCalculator
+{
+ ScDocument& mrDoc;
+ const ScTokenArray& mrCode;
+ const ScFormulaCellGroupRef& mxGroup;
+ const SCROW mnLen;
+ const ScAddress& mrPos;
+ const bool mFromFirstRow;
+ const SCROW mnStartOffset;
+ const SCROW mnEndOffset;
+ const SCROW mnSpanLen;
+
+ ScDependantsCalculator(ScDocument& rDoc, const ScTokenArray& rCode, const ScFormulaCell& rCell,
+ const ScAddress& rPos, bool fromFirstRow, SCROW nStartOffset, SCROW nEndOffset) :
+ mrDoc(rDoc),
+ mrCode(rCode),
+ mxGroup(rCell.GetCellGroup()),
+ mnLen(mxGroup->mnLength),
+ mrPos(rPos),
+ // ScColumn::FetchVectorRefArray() always fetches data from row 0, even if the data is used
+ // only from further rows. This data fetching could also lead to Interpret() calls, so
+ // in OpenCL mode the formula in practice depends on those cells too.
+ mFromFirstRow(fromFirstRow),
+ mnStartOffset(nStartOffset),
+ mnEndOffset(nEndOffset),
+ mnSpanLen(nEndOffset - nStartOffset + 1)
+ {
+ }
+
+ // FIXME: copy-pasted from ScGroupTokenConverter. factor out somewhere else
+ // (note already modified a bit, mFromFirstRow)
+
+ // I think what this function does is to check whether the relative row reference nRelRow points
+ // to a row that is inside the range of rows covered by the formula group.
+
+ bool isSelfReferenceRelative(const ScAddress& rRefPos, SCROW nRelRow)
+ {
+ if (rRefPos.Col() != mrPos.Col() || rRefPos.Tab() != mrPos.Tab())
+ return false;
+
+ SCROW nEndRow = mrPos.Row() + mnLen - 1;
+
+ if (nRelRow <= 0)
+ {
+ SCROW nTest = nEndRow;
+ nTest += nRelRow;
+ if (nTest >= mrPos.Row())
+ return true;
+ }
+ else
+ {
+ SCROW nTest = mrPos.Row(); // top row.
+ nTest += nRelRow;
+ if (nTest <= nEndRow)
+ return true;
+ // If pointing below the formula, it's always included if going from first row.
+ if (mFromFirstRow)
+ return true;
+ }
+
+ return false;
+ }
+
+ // FIXME: another copy-paste
+
+ // And this correspondingly checks whether an absolute row is inside the range of rows covered
+ // by the formula group.
+
+ bool isSelfReferenceAbsolute(const ScAddress& rRefPos)
+ {
+ if (rRefPos.Col() != mrPos.Col() || rRefPos.Tab() != mrPos.Tab())
+ return false;
+
+ SCROW nEndRow = mrPos.Row() + mnLen - 1;
+
+ if (rRefPos.Row() < mrPos.Row())
+ return false;
+
+ // If pointing below the formula, it's always included if going from first row.
+ if (rRefPos.Row() > nEndRow && !mFromFirstRow)
+ return false;
+
+ return true;
+ }
+
+ // Checks if the doubleref engulfs all of formula group cells
+ // Note : does not check if there is a partial overlap, that can be done by calling
+ // isSelfReference[Absolute|Relative]() on both the start and end of the double ref
+ bool isDoubleRefSpanGroupRange(const ScRange& rAbs, bool bIsRef1RowRel, bool bIsRef2RowRel)
+ {
+ if (rAbs.aStart.Col() > mrPos.Col() || rAbs.aEnd.Col() < mrPos.Col()
+ || rAbs.aStart.Tab() > mrPos.Tab() || rAbs.aEnd.Tab() < mrPos.Tab())
+ {
+ return false;
+ }
+
+ SCROW nStartRow = mrPos.Row();
+ SCROW nEndRow = nStartRow + mnLen - 1;
+ SCROW nRefStartRow = rAbs.aStart.Row();
+ SCROW nRefEndRow = rAbs.aEnd.Row();
+
+ if (bIsRef1RowRel && bIsRef2RowRel &&
+ ((nRefStartRow <= nStartRow && nRefEndRow >= nEndRow) ||
+ ((nRefStartRow + mnLen - 1) <= nStartRow &&
+ (nRefEndRow + mnLen - 1) >= nEndRow)))
+ return true;
+
+ if (!bIsRef1RowRel && nRefStartRow <= nStartRow &&
+ (nRefEndRow >= nEndRow || (nRefEndRow + mnLen - 1) >= nEndRow))
+ return true;
+
+ if (!bIsRef2RowRel &&
+ nRefStartRow <= nStartRow && nRefEndRow >= nEndRow)
+ return true;
+
+ // If going from first row, the referenced range must be entirely above the formula,
+ // otherwise the formula would be included.
+ if (mFromFirstRow && nRefEndRow >= nStartRow)
+ return true;
+
+ return false;
+ }
+
+ // FIXME: another copy-paste
+ SCROW trimLength(SCTAB nTab, SCCOL nCol1, SCCOL nCol2, SCROW nRow, SCROW nRowLen)
+ {
+ SCROW nLastRow = nRow + nRowLen - 1; // current last row.
+ nLastRow = mrDoc.GetLastDataRow(nTab, nCol1, nCol2, nLastRow);
+ if (nLastRow < (nRow + nRowLen - 1))
+ {
+ // This can end up negative! Was that the original intent, or
+ // is it accidental? Was it not like that originally but the
+ // surrounding conditions changed?
+ nRowLen = nLastRow - nRow + 1;
+ // Anyway, let's assume it doesn't make sense to return a
+ // negative or zero value here.
+ if (nRowLen <= 0)
+ nRowLen = 1;
+ }
+ else if (nLastRow == 0)
+ // Column is empty.
+ nRowLen = 1;
+
+ return nRowLen;
+ }
+
+ bool DoIt()
+ {
+ // Partially from ScGroupTokenConverter::convert in sc/source/core/data/grouptokenconverter.cxx
+
+ ScRangeList aRangeList;
+
+ // Self references should be checked by considering the entire formula-group not just the provided span.
+ bool bHasSelfReferences = false;
+ bool bInDocShellRecalc = mrDoc.IsInDocShellRecalc();
+
+ FormulaToken** pRPNArray = mrCode.GetCode();
+ sal_uInt16 nCodeLen = mrCode.GetCodeLen();
+ for (sal_Int32 nTokenIdx = nCodeLen-1; nTokenIdx >= 0; --nTokenIdx)
+ {
+ auto p = pRPNArray[nTokenIdx];
+ if (!bInDocShellRecalc)
+ {
+ // The dependency evaluator evaluates all arguments of IF/IFS/SWITCH irrespective
+ // of the result of the condition expression.
+ // This is a perf problem if we *don't* intent on recalc'ing all dirty cells
+ // in the document. So lets disable threading and stop dependency evaluation if
+ // the call did not originate from ScDocShell::DoRecalc()/ScDocShell::DoHardRecalc()
+ // for formulae with IF/IFS/SWITCH
+ OpCode nOpCode = p->GetOpCode();
+ if (nOpCode == ocIf || nOpCode == ocIfs_MS || nOpCode == ocSwitch_MS)
+ return false;
+ }
+
+ switch (p->GetType())
+ {
+ case svSingleRef:
+ {
+ ScSingleRefData aRef = *p->GetSingleRef(); // =Sheet1!A1
+ if( aRef.IsDeleted())
+ return false;
+ ScAddress aRefPos = aRef.toAbs(mrDoc, mrPos);
+
+ if (!mrDoc.TableExists(aRefPos.Tab()))
+ return false; // or true?
+
+ if (aRef.IsRowRel())
+ {
+ if (isSelfReferenceRelative(aRefPos, aRef.Row()))
+ {
+ bHasSelfReferences = true;
+ continue;
+ }
+
+ // Trim data array length to actual data range.
+ SCROW nTrimLen = trimLength(aRefPos.Tab(), aRefPos.Col(), aRefPos.Col(), aRefPos.Row() + mnStartOffset, mnSpanLen);
+
+ aRangeList.Join(ScRange(aRefPos.Col(), aRefPos.Row() + mnStartOffset, aRefPos.Tab(),
+ aRefPos.Col(), aRefPos.Row() + mnStartOffset + nTrimLen - 1, aRefPos.Tab()));
+ }
+ else
+ {
+ if (isSelfReferenceAbsolute(aRefPos))
+ {
+ bHasSelfReferences = true;
+ continue;
+ }
+
+ aRangeList.Join(ScRange(aRefPos.Col(), aRefPos.Row(), aRefPos.Tab()));
+ }
+ }
+ break;
+ case svDoubleRef:
+ {
+ ScComplexRefData aRef = *p->GetDoubleRef();
+ if( aRef.IsDeleted())
+ return false;
+ ScRange aAbs = aRef.toAbs(mrDoc, mrPos);
+
+ // Multiple sheet
+ if (aRef.Ref1.Tab() != aRef.Ref2.Tab())
+ return false;
+
+ bool bIsRef1RowRel = aRef.Ref1.IsRowRel();
+ // Check for self reference.
+ if (bIsRef1RowRel)
+ {
+ if (isSelfReferenceRelative(aAbs.aStart, aRef.Ref1.Row()))
+ {
+ bHasSelfReferences = true;
+ continue;
+ }
+ }
+ else if (isSelfReferenceAbsolute(aAbs.aStart))
+ {
+ bHasSelfReferences = true;
+ continue;
+ }
+
+ bool bIsRef2RowRel = aRef.Ref2.IsRowRel();
+ if (bIsRef2RowRel)
+ {
+ if (isSelfReferenceRelative(aAbs.aEnd, aRef.Ref2.Row()))
+ {
+ bHasSelfReferences = true;
+ continue;
+ }
+ }
+ else if (isSelfReferenceAbsolute(aAbs.aEnd))
+ {
+ bHasSelfReferences = true;
+ continue;
+ }
+
+ if (isDoubleRefSpanGroupRange(aAbs, bIsRef1RowRel, bIsRef2RowRel))
+ {
+ bHasSelfReferences = true;
+ continue;
+ }
+
+ // The first row that will be referenced through the doubleref.
+ SCROW nFirstRefRow = bIsRef1RowRel ? aAbs.aStart.Row() + mnStartOffset : aAbs.aStart.Row();
+ // The last row that will be referenced through the doubleref.
+ SCROW nLastRefRow = bIsRef2RowRel ? aAbs.aEnd.Row() + mnEndOffset : aAbs.aEnd.Row();
+ // Number of rows to be evaluated from nFirstRefRow.
+ SCROW nArrayLength = nLastRefRow - nFirstRefRow + 1;
+ assert(nArrayLength > 0);
+
+ // Trim trailing empty rows.
+ nArrayLength = trimLength(aAbs.aStart.Tab(), aAbs.aStart.Col(), aAbs.aEnd.Col(), nFirstRefRow, nArrayLength);
+
+ aRangeList.Join(ScRange(aAbs.aStart.Col(), nFirstRefRow, aAbs.aStart.Tab(),
+ aAbs.aEnd.Col(), nFirstRefRow + nArrayLength - 1, aAbs.aEnd.Tab()));
+ }
+ break;
+ default:
+ break;
+ }
+ }
+
+ // Compute dependencies irrespective of the presence of any self references.
+ // These dependencies would get computed via InterpretTail anyway when we disable group calc, so lets do it now.
+ // The advantage is that the FG's get marked for cycles early if present, and can avoid lots of complications.
+ for (size_t i = 0; i < aRangeList.size(); ++i)
+ {
+ const ScRange & rRange = aRangeList[i];
+ assert(rRange.aStart.Tab() == rRange.aEnd.Tab());
+ for (auto nCol = rRange.aStart.Col(); nCol <= rRange.aEnd.Col(); nCol++)
+ {
+ SCROW nStartRow = rRange.aStart.Row();
+ SCROW nLength = rRange.aEnd.Row() - rRange.aStart.Row() + 1;
+ if( mFromFirstRow )
+ { // include also all previous rows
+ nLength += nStartRow;
+ nStartRow = 0;
+ }
+ if (!mrDoc.HandleRefArrayForParallelism(ScAddress(nCol, nStartRow, rRange.aStart.Tab()),
+ nLength, mxGroup))
+ return false;
+ }
+ }
+
+ if (bHasSelfReferences)
+ mxGroup->mbPartOfCycle = true;
+
+ return !bHasSelfReferences;
+ }
+};
+
+} // anonymous namespace
+
+bool ScFormulaCell::InterpretFormulaGroup(SCROW nStartOffset, SCROW nEndOffset)
+{
+ if (!mxGroup || !pCode)
+ return false;
+
+ auto aScope = sc::FormulaLogger::get().enterGroup(rDocument, *this);
+ ScRecursionHelper& rRecursionHelper = rDocument.GetRecursionHelper();
+
+ if (mxGroup->mbPartOfCycle)
+ {
+ aScope.addMessage("This formula-group is part of a cycle");
+ return false;
+ }
+
+ if (mxGroup->meCalcState == sc::GroupCalcDisabled)
+ {
+ static constexpr OUStringLiteral MESSAGE = u"group calc disabled";
+ aScope.addMessage(MESSAGE);
+ return false;
+ }
+
+ // Use SC_FORCE_CALCULATION=opencl/threads to force calculation e.g. for unittests
+ static ForceCalculationType forceType = ScCalcConfig::getForceCalculationType();
+ if (forceType == ForceCalculationCore
+ || ( GetWeight() < ScInterpreter::GetGlobalConfig().mnOpenCLMinimumFormulaGroupSize
+ && forceType != ForceCalculationOpenCL
+ && forceType != ForceCalculationThreads))
+ {
+ mxGroup->meCalcState = sc::GroupCalcDisabled;
+ aScope.addGroupSizeThresholdMessage(*this);
+ return false;
+ }
+
+ if (cMatrixFlag != ScMatrixMode::NONE)
+ {
+ mxGroup->meCalcState = sc::GroupCalcDisabled;
+ aScope.addMessage("matrix skipped");
+ return false;
+ }
+
+ if( forceType != ForceCalculationNone )
+ {
+ // ScConditionEntry::Interpret() creates a temporary cell and interprets it
+ // without it actually being in the document at the specified position.
+ // That would confuse opencl/threading code, as they refer to the cell group
+ // also using the position. This is normally not triggered (single cells
+ // are normally not in a cell group), but if forced, check for this explicitly.
+ if( rDocument.GetFormulaCell( aPos ) != this )
+ {
+ mxGroup->meCalcState = sc::GroupCalcDisabled;
+ aScope.addMessage("cell not in document");
+ return false;
+ }
+ }
+
+ // Get rid of -1's in offsets (defaults) or any invalid offsets.
+ SCROW nMaxOffset = mxGroup->mnLength - 1;
+ nStartOffset = nStartOffset < 0 ? 0 : std::min(nStartOffset, nMaxOffset);
+ nEndOffset = nEndOffset < 0 ? nMaxOffset : std::min(nEndOffset, nMaxOffset);
+
+ if (nEndOffset < nStartOffset)
+ {
+ nStartOffset = 0;
+ nEndOffset = nMaxOffset;
+ }
+
+ if (nEndOffset == nStartOffset)
+ return false; // Do not use threads for a single row.
+
+ // Guard against endless recursion of Interpret() calls, for this to work
+ // ScFormulaCell::InterpretFormulaGroup() must never be called through
+ // anything else than ScFormulaCell::Interpret(), same as
+ // ScFormulaCell::InterpretTail()
+ RecursionCounter aRecursionCounter( rRecursionHelper, this);
+
+ bool bDependencyComputed = false;
+ bool bDependencyCheckFailed = false;
+
+ // Preference order: First try OpenCL, then threading.
+ // TODO: Do formula-group span computation for OCL too if nStartOffset/nEndOffset are non default.
+ if( InterpretFormulaGroupOpenCL(aScope, bDependencyComputed, bDependencyCheckFailed))
+ return true;
+
+ if( InterpretFormulaGroupThreading(aScope, bDependencyComputed, bDependencyCheckFailed, nStartOffset, nEndOffset))
+ return true;
+
+ return false;
+}
+
+bool ScFormulaCell::CheckComputeDependencies(sc::FormulaLogger::GroupScope& rScope, bool fromFirstRow,
+ SCROW nStartOffset, SCROW nEndOffset,
+ bool bCalcDependencyOnly)
+{
+ ScRecursionHelper& rRecursionHelper = rDocument.GetRecursionHelper();
+ // iterate over code in the formula ...
+ // ensure all input is pre-calculated -
+ // to avoid writing during the calculation
+ if (bCalcDependencyOnly)
+ {
+ // Lets not use "ScFormulaGroupDependencyComputeGuard" here as there is no corresponding
+ // "ScFormulaGroupCycleCheckGuard" for this formula-group.
+ // (We can only reach here from a multi-group dependency evaluation attempt).
+ // (These two have to be in pairs always for any given formula-group)
+ ScDependantsCalculator aCalculator(rDocument, *pCode, *this, mxGroup->mpTopCell->aPos, fromFirstRow, nStartOffset, nEndOffset);
+ return aCalculator.DoIt();
+ }
+
+ bool bOKToParallelize = false;
+ {
+ ScFormulaGroupCycleCheckGuard aCycleCheckGuard(rRecursionHelper, this);
+ if (mxGroup->mbPartOfCycle)
+ {
+ mxGroup->meCalcState = sc::GroupCalcDisabled;
+ rScope.addMessage("found circular formula-group dependencies");
+ return false;
+ }
+
+ ScFormulaGroupDependencyComputeGuard aDepComputeGuard(rRecursionHelper);
+ ScDependantsCalculator aCalculator(rDocument, *pCode, *this, mxGroup->mpTopCell->aPos, fromFirstRow, nStartOffset, nEndOffset);
+ bOKToParallelize = aCalculator.DoIt();
+
+ }
+
+ if (rRecursionHelper.IsInRecursionReturn())
+ {
+ mxGroup->meCalcState = sc::GroupCalcDisabled;
+ rScope.addMessage("Recursion limit reached, cannot thread this formula group now");
+ return false;
+ }
+
+ if (mxGroup->mbPartOfCycle)
+ {
+ mxGroup->meCalcState = sc::GroupCalcDisabled;
+ rScope.addMessage("found circular formula-group dependencies");
+ return false;
+ }
+
+ if (!rRecursionHelper.AreGroupsIndependent())
+ {
+ // This call resulted from a dependency calculation for a multigroup-threading attempt,
+ // but found dependency among the groups.
+ rScope.addMessage("multi-group-dependency failed");
+ return false;
+ }
+
+ if (!bOKToParallelize)
+ {
+ mxGroup->meCalcState = sc::GroupCalcDisabled;
+ rScope.addMessage("could not do new dependencies calculation thing");
+ return false;
+ }
+
+ return true;
+}
+
+static SCCOL lcl_probeLeftOrRightFGs(const ScFormulaCellGroupRef& xGroup, const ScDocument& rDoc,
+ o3tl::sorted_vector<ScFormulaCellGroup*>& rFGSet,
+ std::map<SCCOL, ScFormulaCell*>& rFGMap, bool bLeft)
+{
+ const SCROW nLen = xGroup->mnLength;
+ const sal_Int32 nWt = xGroup->mnWeight;
+ ScAddress aAddr(xGroup->mpTopCell->aPos);
+
+ SCCOL nColRet = aAddr.Col();
+
+ const SCCOL nMaxCol = rDoc.GetAllocatedColumnsCount(aAddr.Tab()) - 1;
+ if (bLeft)
+ --nColRet;
+ else
+ ++nColRet;
+
+ while (nColRet >= 0 && nColRet <= nMaxCol)
+ {
+ aAddr.SetCol(nColRet);
+ const ScFormulaCell* pCell = rDoc.GetFormulaCell(aAddr);
+ if (!pCell)
+ break;
+
+ if (!pCell->NeedsInterpret())
+ break;
+
+ const ScFormulaCellGroupRef& xNGroup = pCell->GetCellGroup();
+ if (!xNGroup)
+ break;
+
+ if (!pCell->GetCode()->IsEnabledForThreading())
+ break;
+
+ if (xNGroup->mpTopCell->aPos.Row() != aAddr.Row())
+ break;
+
+ const SCROW nNLen = xNGroup->mnLength;
+ const sal_Int32 nNWt = pCell->GetWeight();
+ if (nNLen != nLen || nNWt != nWt)
+ break;
+
+ rFGSet.insert(xNGroup.get());
+ rFGMap[nColRet] = xNGroup->mpTopCell;
+
+ if (bLeft)
+ --nColRet;
+ else
+ ++nColRet;
+ }
+
+ if (bLeft)
+ ++nColRet;
+ else
+ --nColRet;
+
+ return nColRet;
+}
+
+// To be called only from InterpretFormulaGroup().
+bool ScFormulaCell::InterpretFormulaGroupThreading(sc::FormulaLogger::GroupScope& aScope,
+ bool& bDependencyComputed,
+ bool& bDependencyCheckFailed,
+ SCROW nStartOffset,
+ SCROW nEndOffset)
+{
+ static const bool bThreadingProhibited = std::getenv("SC_NO_THREADED_CALCULATION");
+ if (!bDependencyCheckFailed && !bThreadingProhibited &&
+ pCode->IsEnabledForThreading() &&
+ ScCalcConfig::isThreadingEnabled())
+ {
+ if(!bDependencyComputed && !CheckComputeDependencies(aScope, false, nStartOffset, nEndOffset))
+ {
+ bDependencyComputed = true;
+ bDependencyCheckFailed = true;
+ return false;
+ }
+
+ bDependencyComputed = true;
+
+ // Then do the threaded calculation
+
+ class Executor : public comphelper::ThreadTask
+ {
+ private:
+ const unsigned mnThisThread;
+ const unsigned mnThreadsTotal;
+ ScDocument* mpDocument;
+ ScInterpreterContext* mpContext;
+ const ScAddress& mrTopPos;
+ SCCOL mnStartCol;
+ SCCOL mnEndCol;
+ SCROW mnStartOffset;
+ SCROW mnEndOffset;
+
+ public:
+ Executor(const std::shared_ptr<comphelper::ThreadTaskTag>& rTag,
+ unsigned nThisThread,
+ unsigned nThreadsTotal,
+ ScDocument* pDocument2,
+ ScInterpreterContext* pContext,
+ const ScAddress& rTopPos,
+ SCCOL nStartCol,
+ SCCOL nEndCol,
+ SCROW nStartOff,
+ SCROW nEndOff) :
+ comphelper::ThreadTask(rTag),
+ mnThisThread(nThisThread),
+ mnThreadsTotal(nThreadsTotal),
+ mpDocument(pDocument2),
+ mpContext(pContext),
+ mrTopPos(rTopPos),
+ mnStartCol(nStartCol),
+ mnEndCol(nEndCol),
+ mnStartOffset(nStartOff),
+ mnEndOffset(nEndOff)
+ {
+ }
+
+ virtual void doWork() override
+ {
+ ScRange aCalcRange(mnStartCol, mrTopPos.Row() + mnStartOffset, mrTopPos.Tab(),
+ mnEndCol, mrTopPos.Row() + mnEndOffset, mrTopPos.Tab());
+ mpDocument->CalculateInColumnInThread(*mpContext, aCalcRange, mnThisThread, mnThreadsTotal);
+ }
+
+ };
+
+ SvNumberFormatter* pNonThreadedFormatter = rDocument.GetNonThreadedContext().GetFormatTable();
+
+ comphelper::ThreadPool& rThreadPool(comphelper::ThreadPool::getSharedOptimalPool());
+ sal_Int32 nThreadCount = rThreadPool.getWorkerCount();
+
+ SAL_INFO("sc.threaded", "Running " << nThreadCount << " threads");
+
+ o3tl::sorted_vector<ScFormulaCellGroup*> aFGSet;
+ std::map<SCCOL, ScFormulaCell*> aFGMap;
+ aFGSet.insert(mxGroup.get());
+
+ ScRecursionHelper& rRecursionHelper = rDocument.GetRecursionHelper();
+ SCCOL nColStart = aPos.Col();
+ SCCOL nColEnd = nColStart;
+ if (!rRecursionHelper.HasFormulaGroupSet() && rDocument.IsInDocShellRecalc())
+ {
+ nColStart = lcl_probeLeftOrRightFGs(mxGroup, rDocument, aFGSet, aFGMap, true);
+ nColEnd = lcl_probeLeftOrRightFGs(mxGroup, rDocument, aFGSet, aFGMap, false);
+ }
+
+ if (nColStart != nColEnd)
+ {
+ ScCheckIndependentFGGuard aGuard(rRecursionHelper, &aFGSet);
+ for (SCCOL nCurrCol = nColStart; nCurrCol <= nColEnd; ++nCurrCol)
+ {
+ if (nCurrCol == aPos.Col())
+ continue;
+
+ bool bFGOK = aFGMap[nCurrCol]->CheckComputeDependencies(aScope, false, nStartOffset, nEndOffset, true);
+ if (!bFGOK || !aGuard.AreGroupsIndependent())
+ {
+ nColEnd = nColStart = aPos.Col();
+ break;
+ }
+ }
+ }
+
+ std::vector<std::unique_ptr<ScInterpreter>> aInterpreters(nThreadCount);
+ {
+ assert(!rDocument.IsThreadedGroupCalcInProgress());
+ rDocument.SetThreadedGroupCalcInProgress(true);
+
+ ScMutationDisable aGuard(rDocument, ScMutationGuardFlags::CORE);
+
+ // Start nThreadCount new threads
+ std::shared_ptr<comphelper::ThreadTaskTag> aTag = comphelper::ThreadPool::createThreadTaskTag();
+ ScThreadedInterpreterContextGetterGuard aContextGetterGuard(nThreadCount, rDocument, pNonThreadedFormatter);
+ ScInterpreterContext* context = nullptr;
+
+ for (int i = 0; i < nThreadCount; ++i)
+ {
+ context = aContextGetterGuard.GetInterpreterContextForThreadIdx(i);
+ assert(!context->pInterpreter);
+ aInterpreters[i].reset(new ScInterpreter(this, rDocument, *context, mxGroup->mpTopCell->aPos, *pCode, true));
+ context->pInterpreter = aInterpreters[i].get();
+ rDocument.SetupContextFromNonThreadedContext(*context, i);
+ rThreadPool.pushTask(std::make_unique<Executor>(aTag, i, nThreadCount, &rDocument, context, mxGroup->mpTopCell->aPos,
+ nColStart, nColEnd, nStartOffset, nEndOffset));
+ }
+
+ SAL_INFO("sc.threaded", "Waiting for threads to finish work");
+ // Do not join the threads here. They will get joined in ScDocument destructor
+ // if they don't get joined from elsewhere before (via ThreadPool::waitUntilDone).
+ rThreadPool.waitUntilDone(aTag, false);
+
+ rDocument.SetThreadedGroupCalcInProgress(false);
+
+ for (int i = 0; i < nThreadCount; ++i)
+ {
+ context = aContextGetterGuard.GetInterpreterContextForThreadIdx(i);
+ // This is intentionally done in this main thread in order to avoid locking.
+ rDocument.MergeContextBackIntoNonThreadedContext(*context, i);
+ context->pInterpreter = nullptr;
+ }
+
+ SAL_INFO("sc.threaded", "Done");
+ }
+
+ ScAddress aStartPos(mxGroup->mpTopCell->aPos);
+ SCROW nSpanLen = nEndOffset - nStartOffset + 1;
+ aStartPos.SetRow(aStartPos.Row() + nStartOffset);
+ // Reuse one of the previously allocated interpreter objects here.
+ rDocument.HandleStuffAfterParallelCalculation(nColStart, nColEnd, aStartPos.Row(), nSpanLen,
+ aStartPos.Tab(), aInterpreters[0].get());
+
+ return true;
+ }
+
+ return false;
+}
+
+// To be called only from InterpretFormulaGroup().
+bool ScFormulaCell::InterpretFormulaGroupOpenCL(sc::FormulaLogger::GroupScope& aScope,
+ bool& bDependencyComputed,
+ bool& bDependencyCheckFailed)
+{
+ bool bCanVectorize = pCode->IsEnabledForOpenCL();
+ switch (pCode->GetVectorState())
+ {
+ case FormulaVectorEnabled:
+ case FormulaVectorCheckReference:
+ break;
+
+ // Not good.
+ case FormulaVectorDisabledByOpCode:
+ aScope.addMessage("group calc disabled due to vector state (non-vector-supporting opcode)");
+ break;
+ case FormulaVectorDisabledByStackVariable:
+ aScope.addMessage("group calc disabled due to vector state (non-vector-supporting stack variable)");
+ break;
+ case FormulaVectorDisabledNotInSubSet:
+ aScope.addMessage("group calc disabled due to vector state (opcode not in subset)");
+ break;
+ case FormulaVectorDisabled:
+ case FormulaVectorUnknown:
+ default:
+ aScope.addMessage("group calc disabled due to vector state (unknown)");
+ return false;
+ }
+
+ if (!bCanVectorize)
+ return false;
+
+ if (!ScCalcConfig::isOpenCLEnabled())
+ {
+ aScope.addMessage("opencl not enabled");
+ return false;
+ }
+
+ // TableOp does tricks with using a cell with different values, just bail out.
+ if(rDocument.IsInInterpreterTableOp())
+ return false;
+
+ if (bDependencyCheckFailed)
+ return false;
+
+ if(!bDependencyComputed && !CheckComputeDependencies(aScope, true, 0, mxGroup->mnLength - 1))
+ {
+ bDependencyComputed = true;
+ bDependencyCheckFailed = true;
+ return false;
+ }
+
+ bDependencyComputed = true;
+
+ // TODO : Disable invariant formula group interpretation for now in order
+ // to get implicit intersection to work.
+ if (mxGroup->mbInvariant && false)
+ return InterpretInvariantFormulaGroup();
+
+ int nMaxGroupLength = INT_MAX;
+
+#ifdef _WIN32
+ // Heuristic: Certain old low-end OpenCL implementations don't
+ // work for us with too large group lengths. 1000 was determined
+ // empirically to be a good compromise.
+ if (openclwrapper::gpuEnv.mbNeedsTDRAvoidance)
+ nMaxGroupLength = 1000;
+#endif
+
+ if (std::getenv("SC_MAX_GROUP_LENGTH"))
+ nMaxGroupLength = std::atoi(std::getenv("SC_MAX_GROUP_LENGTH"));
+
+ int nNumOnePlus;
+ const int nNumParts = splitup(GetSharedLength(), nMaxGroupLength, nNumOnePlus);
+
+ int nOffset = 0;
+ int nCurChunkSize;
+ ScAddress aOrigPos = mxGroup->mpTopCell->aPos;
+ for (int i = 0; i < nNumParts; i++, nOffset += nCurChunkSize)
+ {
+ nCurChunkSize = GetSharedLength()/nNumParts + (i < nNumOnePlus ? 1 : 0);
+
+ ScFormulaCellGroupRef xGroup;
+
+ if (nNumParts == 1)
+ xGroup = mxGroup;
+ else
+ {
+ // Ugly hack
+ xGroup = new ScFormulaCellGroup();
+ xGroup->mpTopCell = mxGroup->mpTopCell;
+ xGroup->mpTopCell->aPos = aOrigPos;
+ xGroup->mpTopCell->aPos.IncRow(nOffset);
+ xGroup->mbInvariant = mxGroup->mbInvariant;
+ xGroup->mnLength = nCurChunkSize;
+ xGroup->mpCode = std::move(mxGroup->mpCode); // temporarily transfer
+ }
+
+ ScTokenArray aCode(rDocument);
+ ScGroupTokenConverter aConverter(aCode, rDocument, *this, xGroup->mpTopCell->aPos);
+ // TODO avoid this extra compilation
+ ScCompiler aComp( rDocument, xGroup->mpTopCell->aPos, *pCode, formula::FormulaGrammar::GRAM_UNSPECIFIED, true, cMatrixFlag != ScMatrixMode::NONE );
+ aComp.CompileTokenArray();
+ if (aComp.HasUnhandledPossibleImplicitIntersections() || !aConverter.convert(*pCode, aScope))
+ {
+ if(aComp.HasUnhandledPossibleImplicitIntersections())
+ {
+ SAL_INFO("sc.opencl", "group " << xGroup->mpTopCell->aPos << " has unhandled implicit intersections, disabling");
+#ifdef DBG_UTIL
+ for( const OpCode opcode : aComp.UnhandledPossibleImplicitIntersectionsOpCodes())
+ {
+ SAL_INFO("sc.opencl", "unhandled implicit intersection opcode "
+ << formula::FormulaCompiler().GetOpCodeMap(com::sun::star::sheet::FormulaLanguage::ENGLISH)->getSymbol(opcode)
+ << "(" << int(opcode) << ")");
+ }
+#endif
+ }
+ else
+ SAL_INFO("sc.opencl", "conversion of group " << xGroup->mpTopCell->aPos << " failed, disabling");
+
+ mxGroup->meCalcState = sc::GroupCalcDisabled;
+
+ // Undo the hack above
+ if (nNumParts > 1)
+ {
+ mxGroup->mpTopCell->aPos = aOrigPos;
+ xGroup->mpTopCell = nullptr;
+ mxGroup->mpCode = std::move(xGroup->mpCode);
+ }
+
+ aScope.addMessage("group token conversion failed");
+ return false;
+ }
+
+ // The converted code does not have RPN tokens yet. The interpreter will
+ // generate them.
+ xGroup->meCalcState = mxGroup->meCalcState = sc::GroupCalcRunning;
+ sc::FormulaGroupInterpreter *pInterpreter = sc::FormulaGroupInterpreter::getStatic();
+
+ if (pInterpreter == nullptr ||
+ !pInterpreter->interpret(rDocument, xGroup->mpTopCell->aPos, xGroup, aCode))
+ {
+ SAL_INFO("sc.opencl", "interpreting group " << mxGroup->mpTopCell->aPos
+ << " (state " << static_cast<int>(mxGroup->meCalcState) << ") failed, disabling");
+ mxGroup->meCalcState = sc::GroupCalcDisabled;
+
+ // Undo the hack above
+ if (nNumParts > 1)
+ {
+ mxGroup->mpTopCell->aPos = aOrigPos;
+ xGroup->mpTopCell = nullptr;
+ mxGroup->mpCode = std::move(xGroup->mpCode);
+ }
+
+ aScope.addMessage("group interpretation unsuccessful");
+ return false;
+ }
+
+ aScope.setCalcComplete();
+
+ if (nNumParts > 1)
+ {
+ xGroup->mpTopCell = nullptr;
+ mxGroup->mpCode = std::move(xGroup->mpCode);
+ }
+ }
+
+ if (nNumParts > 1)
+ mxGroup->mpTopCell->aPos = aOrigPos;
+ mxGroup->meCalcState = sc::GroupCalcEnabled;
+ return true;
+}
+
+bool ScFormulaCell::InterpretInvariantFormulaGroup()
+{
+ if (pCode->GetVectorState() == FormulaVectorCheckReference)
+ {
+ // An invariant group should only have absolute row references, and no
+ // external references are allowed.
+
+ ScTokenArray aCode(rDocument);
+ FormulaTokenArrayPlainIterator aIter(*pCode);
+ for (const formula::FormulaToken* p = aIter.First(); p; p = aIter.Next())
+ {
+ switch (p->GetType())
+ {
+ case svSingleRef:
+ {
+ ScSingleRefData aRef = *p->GetSingleRef();
+ ScAddress aRefPos = aRef.toAbs(rDocument, aPos);
+ formula::FormulaTokenRef pNewToken = rDocument.ResolveStaticReference(aRefPos);
+ if (!pNewToken)
+ return false;
+
+ aCode.AddToken(*pNewToken);
+ }
+ break;
+ case svDoubleRef:
+ {
+ ScComplexRefData aRef = *p->GetDoubleRef();
+ ScRange aRefRange = aRef.toAbs(rDocument, aPos);
+ formula::FormulaTokenRef pNewToken = rDocument.ResolveStaticReference(aRefRange);
+ if (!pNewToken)
+ return false;
+
+ aCode.AddToken(*pNewToken);
+ }
+ break;
+ default:
+ aCode.AddToken(*p);
+ }
+ }
+
+ ScCompiler aComp(rDocument, aPos, aCode, rDocument.GetGrammar(), true, cMatrixFlag != ScMatrixMode::NONE);
+ aComp.CompileTokenArray(); // Create RPN token array.
+ ScInterpreter aInterpreter(this, rDocument, rDocument.GetNonThreadedContext(), aPos, aCode);
+ aInterpreter.Interpret();
+ aResult.SetToken(aInterpreter.GetResultToken().get());
+ }
+ else
+ {
+ // Formula contains no references.
+ ScInterpreter aInterpreter(this, rDocument, rDocument.GetNonThreadedContext(), aPos, *pCode);
+ aInterpreter.Interpret();
+ aResult.SetToken(aInterpreter.GetResultToken().get());
+ }
+
+ for ( sal_Int32 i = 0; i < mxGroup->mnLength; i++ )
+ {
+ ScAddress aTmpPos = aPos;
+ aTmpPos.SetRow(mxGroup->mpTopCell->aPos.Row() + i);
+ ScFormulaCell* pCell = rDocument.GetFormulaCell(aTmpPos);
+ if (!pCell)
+ {
+ SAL_WARN("sc.core.formulacell", "GetFormulaCell not found");
+ continue;
+ }
+
+ // FIXME: this set of horrors is unclear to me ... certainly
+ // the above GetCell is profoundly nasty & slow ...
+ // Ensure the cell truly has a result:
+ pCell->aResult = aResult;
+ pCell->ResetDirty();
+ pCell->SetChanged(true);
+ }
+
+ return true;
+}
+
+namespace {
+
+void startListeningArea(
+ ScFormulaCell* pCell, ScDocument& rDoc, const ScAddress& rPos, const formula::FormulaToken& rToken)
+{
+ const ScSingleRefData& rRef1 = *rToken.GetSingleRef();
+ const ScSingleRefData& rRef2 = *rToken.GetSingleRef2();
+ ScAddress aCell1 = rRef1.toAbs(rDoc, rPos);
+ ScAddress aCell2 = rRef2.toAbs(rDoc, rPos);
+ if (!(aCell1.IsValid() && aCell2.IsValid()))
+ return;
+
+ if (rToken.GetOpCode() == ocColRowNameAuto)
+ { // automagically
+ if ( rRef1.IsColRel() )
+ { // ColName
+ aCell2.SetRow(rDoc.MaxRow());
+ }
+ else
+ { // RowName
+ aCell2.SetCol(rDoc.MaxCol());
+ }
+ }
+ rDoc.StartListeningArea(ScRange(aCell1, aCell2), false, pCell);
+}
+
+}
+
+void ScFormulaCell::StartListeningTo( ScDocument& rDoc )
+{
+ if (mxGroup)
+ mxGroup->endAllGroupListening(rDoc);
+
+ if (rDoc.IsClipOrUndo() || rDoc.GetNoListening() || IsInChangeTrack())
+ return;
+
+ rDoc.SetDetectiveDirty(true); // It has changed something
+
+ ScTokenArray* pArr = GetCode();
+ if( pArr->IsRecalcModeAlways() )
+ {
+ rDoc.StartListeningArea(BCA_LISTEN_ALWAYS, false, this);
+ SetNeedsListening( false);
+ return;
+ }
+
+ formula::FormulaTokenArrayPlainIterator aIter(*pArr);
+ formula::FormulaToken* t;
+ while ( ( t = aIter.GetNextReferenceRPN() ) != nullptr )
+ {
+ switch (t->GetType())
+ {
+ case svSingleRef:
+ {
+ ScAddress aCell = t->GetSingleRef()->toAbs(rDocument, aPos);
+ if (aCell.IsValid())
+ rDoc.StartListeningCell(aCell, this);
+ }
+ break;
+ case svDoubleRef:
+ startListeningArea(this, rDoc, aPos, *t);
+ break;
+ default:
+ ; // nothing
+ }
+ }
+ SetNeedsListening( false);
+}
+
+void ScFormulaCell::StartListeningTo( sc::StartListeningContext& rCxt )
+{
+ ScDocument& rDoc = rCxt.getDoc();
+
+ if (mxGroup)
+ mxGroup->endAllGroupListening(rDoc);
+
+ if (rDoc.IsClipOrUndo() || rDoc.GetNoListening() || IsInChangeTrack())
+ return;
+
+ rDoc.SetDetectiveDirty(true); // It has changed something
+
+ ScTokenArray* pArr = GetCode();
+ if( pArr->IsRecalcModeAlways() )
+ {
+ rDoc.StartListeningArea(BCA_LISTEN_ALWAYS, false, this);
+ SetNeedsListening( false);
+ return;
+ }
+
+ formula::FormulaTokenArrayPlainIterator aIter(*pArr);
+ formula::FormulaToken* t;
+ while ( ( t = aIter.GetNextReferenceRPN() ) != nullptr )
+ {
+ switch (t->GetType())
+ {
+ case svSingleRef:
+ {
+ ScAddress aCell = t->GetSingleRef()->toAbs(rDocument, aPos);
+ if (aCell.IsValid())
+ rDoc.StartListeningCell(rCxt, aCell, *this);
+ }
+ break;
+ case svDoubleRef:
+ startListeningArea(this, rDoc, aPos, *t);
+ break;
+ default:
+ ; // nothing
+ }
+ }
+ SetNeedsListening( false);
+}
+
+namespace {
+
+void endListeningArea(
+ ScFormulaCell* pCell, ScDocument& rDoc, const ScAddress& rPos, const formula::FormulaToken& rToken)
+{
+ const ScSingleRefData& rRef1 = *rToken.GetSingleRef();
+ const ScSingleRefData& rRef2 = *rToken.GetSingleRef2();
+ ScAddress aCell1 = rRef1.toAbs(rDoc, rPos);
+ ScAddress aCell2 = rRef2.toAbs(rDoc, rPos);
+ if (!(aCell1.IsValid() && aCell2.IsValid()))
+ return;
+
+ if (rToken.GetOpCode() == ocColRowNameAuto)
+ { // automagically
+ if ( rRef1.IsColRel() )
+ { // ColName
+ aCell2.SetRow(rDoc.MaxRow());
+ }
+ else
+ { // RowName
+ aCell2.SetCol(rDoc.MaxCol());
+ }
+ }
+
+ rDoc.EndListeningArea(ScRange(aCell1, aCell2), false, pCell);
+}
+
+}
+
+void ScFormulaCell::EndListeningTo( ScDocument& rDoc, ScTokenArray* pArr,
+ ScAddress aCellPos )
+{
+ if (mxGroup)
+ mxGroup->endAllGroupListening(rDoc);
+
+ if (rDoc.IsClipOrUndo() || IsInChangeTrack())
+ return;
+
+ if (!HasBroadcaster())
+ return;
+
+ rDoc.SetDetectiveDirty(true); // It has changed something
+
+ if ( GetCode()->IsRecalcModeAlways() )
+ {
+ rDoc.EndListeningArea(BCA_LISTEN_ALWAYS, false, this);
+ return;
+ }
+
+ if (!pArr)
+ {
+ pArr = GetCode();
+ aCellPos = aPos;
+ }
+ formula::FormulaTokenArrayPlainIterator aIter(*pArr);
+ formula::FormulaToken* t;
+ while ( ( t = aIter.GetNextReferenceRPN() ) != nullptr )
+ {
+ switch (t->GetType())
+ {
+ case svSingleRef:
+ {
+ ScAddress aCell = t->GetSingleRef()->toAbs(rDocument, aCellPos);
+ if (aCell.IsValid())
+ rDoc.EndListeningCell(aCell, this);
+ }
+ break;
+ case svDoubleRef:
+ endListeningArea(this, rDoc, aCellPos, *t);
+ break;
+ default:
+ ; // nothing
+ }
+ }
+}
+
+void ScFormulaCell::EndListeningTo( sc::EndListeningContext& rCxt )
+{
+ if (mxGroup)
+ mxGroup->endAllGroupListening(rCxt.getDoc());
+
+ if (rCxt.getDoc().IsClipOrUndo() || IsInChangeTrack())
+ return;
+
+ if (!HasBroadcaster())
+ return;
+
+ ScDocument& rDoc = rCxt.getDoc();
+ rDoc.SetDetectiveDirty(true); // It has changed something
+
+ ScTokenArray* pArr = rCxt.getOldCode();
+ ScAddress aCellPos = rCxt.getOldPosition(aPos);
+ if (!pArr)
+ pArr = pCode;
+
+ if (pArr->IsRecalcModeAlways())
+ {
+ rDoc.EndListeningArea(BCA_LISTEN_ALWAYS, false, this);
+ return;
+ }
+
+ formula::FormulaTokenArrayPlainIterator aIter(*pArr);
+ formula::FormulaToken* t;
+ while ( ( t = aIter.GetNextReferenceRPN() ) != nullptr )
+ {
+ switch (t->GetType())
+ {
+ case svSingleRef:
+ {
+ ScAddress aCell = t->GetSingleRef()->toAbs(rDocument, aCellPos);
+ if (aCell.IsValid())
+ rDoc.EndListeningCell(rCxt, aCell, *this);
+ }
+ break;
+ case svDoubleRef:
+ endListeningArea(this, rDoc, aCellPos, *t);
+ break;
+ default:
+ ; // nothing
+ }
+ }
+}
+
+bool ScFormulaCell::IsShared() const
+{
+ return bool(mxGroup);
+}
+
+bool ScFormulaCell::IsSharedTop() const
+{
+ if (!mxGroup)
+ return false;
+
+ return mxGroup->mpTopCell == this;
+}
+
+SCROW ScFormulaCell::GetSharedTopRow() const
+{
+ return mxGroup ? mxGroup->mpTopCell->aPos.Row() : -1;
+}
+
+SCROW ScFormulaCell::GetSharedLength() const
+{
+ return mxGroup ? mxGroup->mnLength : 0;
+}
+
+sal_Int32 ScFormulaCell::GetWeight() const
+{
+ if (!mxGroup)
+ return 1;
+
+ if (mxGroup->mnWeight > 0)
+ return mxGroup->mnWeight;
+
+ double nSharedCodeWeight = GetSharedCode()->GetWeight();
+ double nResult = nSharedCodeWeight * GetSharedLength();
+ if (nResult < SAL_MAX_INT32)
+ mxGroup->mnWeight = nResult;
+ else
+ mxGroup->mnWeight = SAL_MAX_INT32;
+
+ return mxGroup->mnWeight;
+}
+
+ScTokenArray* ScFormulaCell::GetSharedCode()
+{
+ return mxGroup ? &*mxGroup->mpCode : nullptr;
+}
+
+const ScTokenArray* ScFormulaCell::GetSharedCode() const
+{
+ return mxGroup ? &*mxGroup->mpCode : nullptr;
+}
+
+void ScFormulaCell::SyncSharedCode()
+{
+ if (!mxGroup)
+ // Not a shared formula cell.
+ return;
+
+ pCode = &*mxGroup->mpCode;
+}
+
+#if DUMP_COLUMN_STORAGE
+
+void ScFormulaCell::Dump() const
+{
+ cout << "-- formula cell (" << aPos.Format(ScRefFlags::VALID | ScRefFlags::TAB_3D, &rDocument) << ")" << endl;
+ cout << " * shared: " << (mxGroup ? "true" : "false") << endl;
+ if (mxGroup)
+ {
+ cout << " * shared length: " << mxGroup->mnLength << endl;
+ cout << " * shared calc state: " << mxGroup->meCalcState << endl;
+ }
+
+ sc::TokenStringContext aCxt(rDocument, rDocument.GetGrammar());
+ cout << " * code: " << pCode->CreateString(aCxt, aPos) << endl;
+
+ FormulaError nErrCode = pCode->GetCodeError();
+ cout << " * code error: ";
+ if (nErrCode == FormulaError::NONE)
+ cout << "(none)";
+ else
+ {
+ OUString aStr = ScGlobal::GetErrorString(nErrCode);
+ cout << " * code error: " << aStr << " (" << int(nErrCode) << ")";
+ }
+ cout << endl;
+
+ cout << " * result: ";
+ sc::FormulaResultValue aRV = aResult.GetResult();
+ switch (aRV.meType)
+ {
+ case sc::FormulaResultValue::Value:
+ cout << aRV.mfValue << " (value)";
+ break;
+ case sc::FormulaResultValue::String:
+ cout << aRV.maString.getString() << " (string)";
+ break;
+ case sc::FormulaResultValue::Error:
+ cout << ScGlobal::GetErrorString(aRV.mnError) << " (error: " << int(aRV.mnError) << ")";
+ break;
+ case sc::FormulaResultValue::Invalid:
+ cout << "(invalid)";
+ break;
+ default:
+ ;
+ }
+ cout << endl;
+}
+
+#endif
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sc/source/core/data/formulaiter.cxx b/sc/source/core/data/formulaiter.cxx
new file mode 100644
index 000000000..d7f183b2a
--- /dev/null
+++ b/sc/source/core/data/formulaiter.cxx
@@ -0,0 +1,77 @@
+/* -*- 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 <formulaiter.hxx>
+
+#include <formulacell.hxx>
+#include <tokenarray.hxx>
+#include <formula/token.hxx>
+#include <token.hxx>
+
+using namespace formula;
+
+ScDetectiveRefIter::ScDetectiveRefIter( const ScDocument& rDoc, ScFormulaCell* pCell ) :
+ mrDoc(rDoc),
+ maIter(*pCell->GetCode()),
+ aPos(pCell->aPos)
+{
+}
+
+static bool lcl_ScDetectiveRefIter_SkipRef( const ScDocument& rDoc, formula::FormulaToken* p, const ScAddress& rPos )
+{
+ ScSingleRefData& rRef1 = *p->GetSingleRef();
+ ScAddress aAbs1 = rRef1.toAbs(rDoc, rPos);
+ if (!rDoc.ValidAddress(aAbs1))
+ return true;
+ if ( p->GetType() == svDoubleRef || p->GetType() == svExternalDoubleRef )
+ {
+ ScSingleRefData& rRef2 = p->GetDoubleRef()->Ref2;
+ ScAddress aAbs2 = rRef2.toAbs(rDoc, rPos);
+ if (!rDoc.ValidAddress(aAbs2))
+ return true;
+ }
+ return false;
+}
+
+bool ScDetectiveRefIter::GetNextRef( ScRange& rRange )
+{
+ bool bRet = false;
+ formula::FormulaToken* p = GetNextRefToken();
+ if( p )
+ {
+ SingleDoubleRefProvider aProv( *p );
+ rRange.aStart = aProv.Ref1.toAbs(mrDoc, aPos);
+ rRange.aEnd = aProv.Ref2.toAbs(mrDoc, aPos);
+ bRet = true;
+ }
+
+ return bRet;
+}
+
+formula::FormulaToken* ScDetectiveRefIter::GetNextRefToken()
+{
+ formula::FormulaToken* p = maIter.GetNextReferenceRPN();
+ while (p && lcl_ScDetectiveRefIter_SkipRef(mrDoc, p, aPos))
+ {
+ p = maIter.GetNextReferenceRPN();
+ }
+ return p;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sc/source/core/data/funcdesc.cxx b/sc/source/core/data/funcdesc.cxx
new file mode 100644
index 000000000..060cda7a5
--- /dev/null
+++ b/sc/source/core/data/funcdesc.cxx
@@ -0,0 +1,1267 @@
+/* -*- 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 <funcdesc.hxx>
+
+#include <addincol.hxx>
+#include <appoptio.hxx>
+#include <callform.hxx>
+#include <compiler.hxx>
+#include <compiler.hrc>
+#include <global.hxx>
+#include <scfuncs.hrc>
+#include <scmod.hxx>
+#include <scresid.hxx>
+#include <helpids.h>
+
+#include <rtl/ustring.hxx>
+#include <rtl/ustrbuf.hxx>
+#include <unotools/collatorwrapper.hxx>
+#include <formula/funcvarargs.h>
+#include <osl/diagnose.h>
+
+#include <memory>
+
+namespace {
+
+struct ScFuncDescCore
+{
+ /*
+ * An opcode from include/formula/compiler.hxx
+ */
+ sal_uInt16 nOpCode;
+ /*
+ * Pointer to list of strings
+ */
+ const TranslateId* pResource;
+ /*
+ * Count of list of strings
+ */
+ size_t nResourceLen;
+ /*
+ * 16-bit value:
+ *
+ * Bit 1: boolean flag whether function is suppressed. Usually 0. This
+ * may be used to add UI string resources before UI freeze if
+ * implementation isn't ready yet without displaying them in the
+ * function wizard, most recent used list and other UI elements. Also
+ * not available via API then.
+ *
+ * Bit 2: boolean flag whether function is hidden in the Function
+ * Wizard unless used in an expression.
+ */
+ sal_uInt16 nFunctionFlags;
+ /*
+ * Function group (text, math, ...), one of ID_FUNCTION_GRP_...
+ */
+ sal_uInt16 nCategory;
+ /*
+ * Help ID, HID_FUNC_...
+ */
+ const char* pHelpId;
+ /*
+ * Number of parameters. VAR_ARGS if variable number, or
+ * VAR_ARGS+number if number of fixed parameters and variable
+ * arguments following. Or PAIRED_VAR_ARGS if variable number of
+ * paired parameters, or PAIRED_VAR_ARGS+number if number of fixed
+ * parameters and variable paired arguments following.
+ */
+ sal_uInt16 nArgs;
+ /*
+ * For every parameter:
+ * Boolean flag whether the parameter is optional.
+ */
+ sal_uInt8 aOptionalArgs[7];
+ /*
+ * Limited number of maximum (variable) parameters, or 0 if no specific
+ * limit other than the general VAR_ARGS-1 value.
+ */
+ sal_uInt16 nVarArgsLimit;
+};
+
+}
+
+static void ScFuncRes(const ScFuncDescCore &rEntry, ScFuncDesc*, bool& rbSuppressed);
+
+ScFuncDesc::ScFuncDesc() :
+ pDefArgFlags (nullptr),
+ nFIndex (0),
+ nCategory (0),
+ nArgCount (0),
+ nVarArgsStart (0),
+ nVarArgsLimit (0),
+ bIncomplete (false),
+ mbHidden (false)
+{}
+
+ScFuncDesc::~ScFuncDesc()
+{
+ Clear();
+}
+
+void ScFuncDesc::Clear()
+{
+ sal_uInt16 nArgs = nArgCount;
+ if (nArgs >= PAIRED_VAR_ARGS)
+ nArgs -= PAIRED_VAR_ARGS - 2;
+ else if (nArgs >= VAR_ARGS)
+ nArgs -= VAR_ARGS - 1;
+ if (nArgs)
+ {
+ delete [] pDefArgFlags;
+ }
+ nArgCount = 0;
+ nVarArgsStart = 0;
+ nVarArgsLimit = 0;
+ maDefArgNames.clear();
+ maDefArgDescs.clear();
+ pDefArgFlags = nullptr;
+
+ mxFuncName.reset();
+ mxFuncDesc.reset();
+
+ nFIndex = 0;
+ nCategory = 0;
+ sHelpId.clear();
+ bIncomplete = false;
+ mbHidden = false;
+}
+
+OUString ScFuncDesc::GetParamList() const
+{
+ OUString sep(ScCompiler::GetNativeSymbol(ocSep));
+
+ OUStringBuffer aSig;
+
+ if ( nArgCount > 0 )
+ {
+ if ( nArgCount < VAR_ARGS )
+ {
+ sal_uInt16 nLastSuppressed = nArgCount;
+ sal_uInt16 nLastAdded = nArgCount;
+ for ( sal_uInt16 i=0; i<nArgCount; i++ )
+ {
+ nLastAdded = i;
+ aSig.append(maDefArgNames[i]);
+ if ( i != nArgCount-1 )
+ {
+ aSig.append(sep);
+ aSig.append( " " );
+ }
+ }
+ // If only suppressed parameters follow the last added parameter,
+ // remove one "; "
+ if (nLastSuppressed < nArgCount && nLastAdded < nLastSuppressed &&
+ aSig.getLength() >= 2)
+ aSig.setLength(aSig.getLength() - 2);
+ }
+ else if ( nArgCount < PAIRED_VAR_ARGS)
+ {
+ for ( sal_uInt16 nArg = 0; nArg < nVarArgsStart; nArg++ )
+ {
+ aSig.append(maDefArgNames[nArg]);
+ aSig.append(sep);
+ aSig.append( " " );
+ }
+ /* NOTE: Currently there are no suppressed var args parameters. If
+ * there were, we'd have to cope with it here and above for the fix
+ * parameters. For now parameters are always added, so no special
+ * treatment of a trailing "; " necessary. */
+ aSig.append(maDefArgNames[nVarArgsStart]);
+ aSig.append('1');
+ aSig.append(sep);
+ aSig.append(' ');
+ aSig.append(maDefArgNames[nVarArgsStart]);
+ aSig.append('2');
+ aSig.append(sep);
+ aSig.append(" ... ");
+ }
+ else
+ {
+ for ( sal_uInt16 nArg = 0; nArg < nVarArgsStart; nArg++ )
+ {
+ aSig.append(maDefArgNames[nArg]);
+ aSig.append(sep);
+ aSig.append( " " );
+ }
+
+ aSig.append(maDefArgNames[nVarArgsStart]);
+ aSig.append('1');
+ aSig.append(sep);
+ aSig.append(maDefArgNames[nVarArgsStart+1]);
+ aSig.append('1');
+ aSig.append(sep);
+ aSig.append( " " );
+ aSig.append(maDefArgNames[nVarArgsStart]);
+ aSig.append('2');
+ aSig.append(sep);
+ aSig.append(maDefArgNames[nVarArgsStart+1]);
+ aSig.append('2');
+ aSig.append(sep);
+ aSig.append( " ... " );
+ }
+ }
+
+ return aSig.makeStringAndClear();
+}
+
+OUString ScFuncDesc::getSignature() const
+{
+ OUStringBuffer aSig;
+
+ if(mxFuncName)
+ {
+ aSig.append(*mxFuncName);
+
+ OUString aParamList = GetParamList();
+ if( !aParamList.isEmpty() )
+ {
+ aSig.append( "( " );
+ aSig.append(aParamList);
+ // U+00A0 (NBSP) prevents automatic line break
+ aSig.append( u'\x00A0' );
+ aSig.append( ")" );
+ }
+ else
+ aSig.append( "()" );
+ }
+ return aSig.makeStringAndClear();
+}
+
+OUString ScFuncDesc::getFormula( const ::std::vector< OUString >& _aArguments ) const
+{
+ OUString sep = ScCompiler::GetNativeSymbol(ocSep);
+
+ OUStringBuffer aFormula;
+
+ if(mxFuncName)
+ {
+ aFormula.append( *mxFuncName );
+
+ aFormula.append( "(" );
+ if ( nArgCount > 0 && !_aArguments.empty() && !_aArguments[0].isEmpty())
+ {
+ ::std::vector< OUString >::const_iterator aIter = _aArguments.begin();
+ ::std::vector< OUString >::const_iterator aEnd = _aArguments.end();
+
+ aFormula.append( *aIter );
+ ++aIter;
+ while( aIter != aEnd && !aIter->isEmpty() )
+ {
+ aFormula.append( sep );
+ aFormula.append( *aIter );
+ ++aIter;
+ }
+ }
+
+ aFormula.append( ")" );
+ }
+ return aFormula.makeStringAndClear();
+}
+
+sal_uInt16 ScFuncDesc::GetSuppressedArgCount() const
+{
+ return nArgCount;
+}
+
+OUString ScFuncDesc::getFunctionName() const
+{
+ OUString sRet;
+ if ( mxFuncName )
+ sRet = *mxFuncName;
+ return sRet;
+}
+
+const formula::IFunctionCategory* ScFuncDesc::getCategory() const
+{
+ return ScGlobal::GetStarCalcFunctionMgr()->getCategory(nCategory - 1);
+}
+
+OUString ScFuncDesc::getDescription() const
+{
+ OUString sRet;
+ if ( mxFuncDesc )
+ sRet = *mxFuncDesc;
+ return sRet;
+}
+
+sal_Int32 ScFuncDesc::getSuppressedArgumentCount() const
+{
+ return GetSuppressedArgCount();
+}
+
+void ScFuncDesc::fillVisibleArgumentMapping(::std::vector<sal_uInt16>& _rArguments) const
+{
+ _rArguments.resize( nArgCount);
+ sal_uInt16 value = 0;
+ for (auto & argument : _rArguments)
+ argument = value++;
+
+ sal_uInt16 nArgs = nArgCount;
+ if (nArgs >= PAIRED_VAR_ARGS)
+ nArgs -= PAIRED_VAR_ARGS - 2;
+ else if (nArgs >= VAR_ARGS)
+ nArgs -= VAR_ARGS - 1;
+ for (sal_uInt16 i=0; i < nArgs; ++i)
+ {
+ _rArguments.push_back(i);
+ }
+}
+
+void ScFuncDesc::initArgumentInfo() const
+{
+ // get the full argument description
+ // (add-in has to be instantiated to get the type information)
+
+ if ( !(bIncomplete && mxFuncName) )
+ return;
+
+ ScUnoAddInCollection& rAddIns = *ScGlobal::GetAddInCollection();
+ OUString aIntName(rAddIns.FindFunction( *mxFuncName, true )); // pFuncName is upper-case
+
+ if ( !aIntName.isEmpty() )
+ {
+ // GetFuncData with bComplete=true loads the component and updates
+ // the global function list if needed.
+
+ rAddIns.GetFuncData( aIntName, true );
+ }
+
+ if ( bIncomplete )
+ {
+ OSL_FAIL( "couldn't initialize add-in function" );
+ const_cast<ScFuncDesc*>(this)->bIncomplete = false; // even if there was an error, don't try again
+ }
+}
+
+OString ScFuncDesc::getHelpId() const
+{
+ return sHelpId;
+}
+
+bool ScFuncDesc::isHidden() const
+{
+ return mbHidden;
+}
+
+sal_uInt32 ScFuncDesc::getParameterCount() const
+{
+ return nArgCount;
+}
+
+sal_uInt32 ScFuncDesc::getVarArgsStart() const
+{
+ return nVarArgsStart;
+}
+
+sal_uInt32 ScFuncDesc::getVarArgsLimit() const
+{
+ return nVarArgsLimit;
+}
+
+OUString ScFuncDesc::getParameterName(sal_uInt32 _nPos) const
+{
+ return maDefArgNames[_nPos];
+}
+
+OUString ScFuncDesc::getParameterDescription(sal_uInt32 _nPos) const
+{
+ return maDefArgDescs[_nPos];
+}
+
+bool ScFuncDesc::isParameterOptional(sal_uInt32 _nPos) const
+{
+ return pDefArgFlags[_nPos].bOptional;
+}
+
+bool ScFuncDesc::compareByName(const ScFuncDesc* a, const ScFuncDesc* b)
+{
+ return (ScGlobal::GetCaseCollator().compareString(*a->mxFuncName, *b->mxFuncName ) < 0);
+}
+
+#define ENTRY(CODE) CODE, SAL_N_ELEMENTS(CODE)
+
+ScFunctionList::ScFunctionList( bool bEnglishFunctionNames )
+ : mbEnglishFunctionNames( bEnglishFunctionNames )
+{
+ sal_Int32 nMaxFuncNameLen = 0; // Length of longest function name
+
+ // See ScFuncDescCore definition for format details.
+ // This list must be sorted in order of the opcode, dbgutil builds enable _GLIBCXX_DEBUG
+ // which will concept check that the list is sorted on first use to ensure this holds
+ static const ScFuncDescCore aDescs[] =
+ {
+ { SC_OPCODE_IF, ENTRY(SC_OPCODE_IF_ARY), 0, ID_FUNCTION_GRP_LOGIC, HID_FUNC_WENN, 3, { 0, 1, 1 }, 0 },
+ { SC_OPCODE_IF_ERROR, ENTRY(SC_OPCODE_IF_ERROR_ARY), 0, ID_FUNCTION_GRP_LOGIC, HID_FUNC_IFERROR, 2, { 0, 0 }, 0 },
+ { SC_OPCODE_IF_NA, ENTRY(SC_OPCODE_IF_NA_ARY), 0, ID_FUNCTION_GRP_LOGIC, HID_FUNC_IFNA, 2, { 0, 0 }, 0 },
+ { SC_OPCODE_CHOOSE, ENTRY(SC_OPCODE_CHOOSE_ARY), 0, ID_FUNCTION_GRP_TABLE, HID_FUNC_WAHL, VAR_ARGS+1, { 0, 0 }, 31 },
+ { SC_OPCODE_AND, ENTRY(SC_OPCODE_AND_ARY), 0, ID_FUNCTION_GRP_LOGIC, HID_FUNC_UND, VAR_ARGS, { 0 }, 0 },
+ { SC_OPCODE_OR, ENTRY(SC_OPCODE_OR_ARY), 0, ID_FUNCTION_GRP_LOGIC, HID_FUNC_ODER, VAR_ARGS, { 0 }, 0 },
+ { SC_OPCODE_PI, ENTRY(SC_OPCODE_PI_ARY), 0, ID_FUNCTION_GRP_MATH, HID_FUNC_PI, 0, { }, 0 },
+ { SC_OPCODE_RANDOM, ENTRY(SC_OPCODE_RANDOM_ARY), 0, ID_FUNCTION_GRP_MATH, HID_FUNC_ZUFALLSZAHL, 0, { }, 0 },
+ { SC_OPCODE_TRUE, ENTRY(SC_OPCODE_TRUE_ARY), 0, ID_FUNCTION_GRP_LOGIC, HID_FUNC_WAHR, 0, { }, 0 },
+ { SC_OPCODE_FALSE, ENTRY(SC_OPCODE_FALSE_ARY), 0, ID_FUNCTION_GRP_LOGIC, HID_FUNC_FALSCH, 0, { }, 0 },
+ { SC_OPCODE_GET_ACT_DATE, ENTRY(SC_OPCODE_GET_ACT_DATE_ARY), 0, ID_FUNCTION_GRP_DATETIME, HID_FUNC_HEUTE, 0, { }, 0 },
+ { SC_OPCODE_GET_ACT_TIME, ENTRY(SC_OPCODE_GET_ACT_TIME_ARY), 0, ID_FUNCTION_GRP_DATETIME, HID_FUNC_JETZT, 0, { }, 0 },
+ { SC_OPCODE_NO_VALUE, ENTRY(SC_OPCODE_NO_VALUE_ARY), 0, ID_FUNCTION_GRP_INFO, HID_FUNC_NV, 0, { }, 0 },
+ { SC_OPCODE_CURRENT, ENTRY(SC_OPCODE_CURRENT_ARY), 0, ID_FUNCTION_GRP_INFO, HID_FUNC_AKTUELL, 0, { }, 0 },
+ { SC_OPCODE_RANDOM_NV, ENTRY(SC_OPCODE_RANDOM_NV_ARY), 0, ID_FUNCTION_GRP_MATH, HID_FUNC_RAND_NV, 0, { }, 0 },
+ { SC_OPCODE_DEG, ENTRY(SC_OPCODE_DEG_ARY), 0, ID_FUNCTION_GRP_MATH, HID_FUNC_DEG, 1, { 0 }, 0 },
+ { SC_OPCODE_RAD, ENTRY(SC_OPCODE_RAD_ARY), 0, ID_FUNCTION_GRP_MATH, HID_FUNC_RAD, 1, { 0 }, 0 },
+ { SC_OPCODE_SIN, ENTRY(SC_OPCODE_SIN_ARY), 0, ID_FUNCTION_GRP_MATH, HID_FUNC_SIN, 1, { 0 }, 0 },
+ { SC_OPCODE_COS, ENTRY(SC_OPCODE_COS_ARY), 0, ID_FUNCTION_GRP_MATH, HID_FUNC_COS, 1, { 0 }, 0 },
+ { SC_OPCODE_TAN, ENTRY(SC_OPCODE_TAN_ARY), 0, ID_FUNCTION_GRP_MATH, HID_FUNC_TAN, 1, { 0 }, 0 },
+ { SC_OPCODE_COT, ENTRY(SC_OPCODE_COT_ARY), 0, ID_FUNCTION_GRP_MATH, HID_FUNC_COT, 1, { 0 }, 0 },
+ { SC_OPCODE_ARC_SIN, ENTRY(SC_OPCODE_ARC_SIN_ARY), 0, ID_FUNCTION_GRP_MATH, HID_FUNC_ARCSIN, 1, { 0 }, 0 },
+ { SC_OPCODE_ARC_COS, ENTRY(SC_OPCODE_ARC_COS_ARY), 0, ID_FUNCTION_GRP_MATH, HID_FUNC_ARCCOS, 1, { 0 }, 0 },
+ { SC_OPCODE_ARC_TAN, ENTRY(SC_OPCODE_ARC_TAN_ARY), 0, ID_FUNCTION_GRP_MATH, HID_FUNC_ARCTAN, 1, { 0 }, 0 },
+ { SC_OPCODE_ARC_COT, ENTRY(SC_OPCODE_ARC_COT_ARY), 0, ID_FUNCTION_GRP_MATH, HID_FUNC_ARCCOT, 1, { 0 }, 0 },
+ { SC_OPCODE_SIN_HYP, ENTRY(SC_OPCODE_SIN_HYP_ARY), 0, ID_FUNCTION_GRP_MATH, HID_FUNC_SINHYP, 1, { 0 }, 0 },
+ { SC_OPCODE_COS_HYP, ENTRY(SC_OPCODE_COS_HYP_ARY), 0, ID_FUNCTION_GRP_MATH, HID_FUNC_COSHYP, 1, { 0 }, 0 },
+ { SC_OPCODE_TAN_HYP, ENTRY(SC_OPCODE_TAN_HYP_ARY), 0, ID_FUNCTION_GRP_MATH, HID_FUNC_TANHYP, 1, { 0 }, 0 },
+ { SC_OPCODE_COT_HYP, ENTRY(SC_OPCODE_COT_HYP_ARY), 0, ID_FUNCTION_GRP_MATH, HID_FUNC_COTHYP, 1, { 0 }, 0 },
+ { SC_OPCODE_ARC_SIN_HYP, ENTRY(SC_OPCODE_ARC_SIN_HYP_ARY), 0, ID_FUNCTION_GRP_MATH, HID_FUNC_ARSINHYP, 1, { 0 }, 0 },
+ { SC_OPCODE_ARC_COS_HYP, ENTRY(SC_OPCODE_ARC_COS_HYP_ARY), 0, ID_FUNCTION_GRP_MATH, HID_FUNC_ARCOSHYP, 1, { 0 }, 0 },
+ { SC_OPCODE_ARC_TAN_HYP, ENTRY(SC_OPCODE_ARC_TAN_HYP_ARY), 0, ID_FUNCTION_GRP_MATH, HID_FUNC_ARTANHYP, 1, { 0 }, 0 },
+ { SC_OPCODE_ARC_COT_HYP, ENTRY(SC_OPCODE_ARC_COT_HYP_ARY), 0, ID_FUNCTION_GRP_MATH, HID_FUNC_ARCOTHYP, 1, { 0 }, 0 },
+ { SC_OPCODE_COSECANT, ENTRY(SC_OPCODE_COSECANT_ARY), 0, ID_FUNCTION_GRP_MATH, HID_FUNC_COSECANT, 1, { 0 }, 0 },
+ { SC_OPCODE_SECANT, ENTRY(SC_OPCODE_SECANT_ARY), 0, ID_FUNCTION_GRP_MATH, HID_FUNC_SECANT, 1, { 0 }, 0 },
+ { SC_OPCODE_COSECANT_HYP, ENTRY(SC_OPCODE_COSECANT_HYP_ARY), 0, ID_FUNCTION_GRP_MATH, HID_FUNC_COSECANTHYP, 1, { 0 }, 0 },
+ { SC_OPCODE_SECANT_HYP, ENTRY(SC_OPCODE_SECANT_HYP_ARY), 0, ID_FUNCTION_GRP_MATH, HID_FUNC_SECANTHYP, 1, { 0 }, 0 },
+ { SC_OPCODE_EXP, ENTRY(SC_OPCODE_EXP_ARY), 0, ID_FUNCTION_GRP_MATH, HID_FUNC_EXP, 1, { 0 }, 0 },
+ { SC_OPCODE_LN, ENTRY(SC_OPCODE_LN_ARY), 0, ID_FUNCTION_GRP_MATH, HID_FUNC_LN, 1, { 0 }, 0 },
+ { SC_OPCODE_SQRT, ENTRY(SC_OPCODE_SQRT_ARY), 0, ID_FUNCTION_GRP_MATH, HID_FUNC_WURZEL, 1, { 0 }, 0 },
+ { SC_OPCODE_FACT, ENTRY(SC_OPCODE_FACT_ARY), 0, ID_FUNCTION_GRP_MATH, HID_FUNC_FAKULTAET, 1, { 0 }, 0 },
+ { SC_OPCODE_GET_YEAR, ENTRY(SC_OPCODE_GET_YEAR_ARY), 0, ID_FUNCTION_GRP_DATETIME, HID_FUNC_JAHR, 1, { 0 }, 0 },
+ { SC_OPCODE_GET_MONTH, ENTRY(SC_OPCODE_GET_MONTH_ARY), 0, ID_FUNCTION_GRP_DATETIME, HID_FUNC_MONAT, 1, { 0 }, 0 },
+ { SC_OPCODE_GET_DAY, ENTRY(SC_OPCODE_GET_DAY_ARY), 0, ID_FUNCTION_GRP_DATETIME, HID_FUNC_TAG, 1, { 0 }, 0 },
+ { SC_OPCODE_GET_HOUR, ENTRY(SC_OPCODE_GET_HOUR_ARY), 0, ID_FUNCTION_GRP_DATETIME, HID_FUNC_STUNDE, 1, { 0 }, 0 },
+ { SC_OPCODE_GET_MIN, ENTRY(SC_OPCODE_GET_MIN_ARY), 0, ID_FUNCTION_GRP_DATETIME, HID_FUNC_MINUTE, 1, { 0 }, 0 },
+ { SC_OPCODE_GET_SEC, ENTRY(SC_OPCODE_GET_SEC_ARY), 0, ID_FUNCTION_GRP_DATETIME, HID_FUNC_SEKUNDE, 1, { 0 }, 0 },
+ { SC_OPCODE_PLUS_MINUS, ENTRY(SC_OPCODE_PLUS_MINUS_ARY), 0, ID_FUNCTION_GRP_MATH, HID_FUNC_VORZEICHEN, 1, { 0 }, 0 },
+ { SC_OPCODE_ABS, ENTRY(SC_OPCODE_ABS_ARY), 0, ID_FUNCTION_GRP_MATH, HID_FUNC_ABS, 1, { 0 }, 0 },
+ { SC_OPCODE_INT, ENTRY(SC_OPCODE_INT_ARY), 0, ID_FUNCTION_GRP_MATH, HID_FUNC_GANZZAHL, 1, { 0 }, 0 },
+ { SC_OPCODE_PHI, ENTRY(SC_OPCODE_PHI_ARY), 0, ID_FUNCTION_GRP_STATISTIC, HID_FUNC_PHI, 1, { 0 }, 0 },
+ { SC_OPCODE_GAUSS, ENTRY(SC_OPCODE_GAUSS_ARY), 0, ID_FUNCTION_GRP_STATISTIC, HID_FUNC_GAUSS, 1, { 0 }, 0 },
+ { SC_OPCODE_IS_EMPTY, ENTRY(SC_OPCODE_IS_EMPTY_ARY), 0, ID_FUNCTION_GRP_INFO, HID_FUNC_ISTLEER, 1, { 0 }, 0 },
+ { SC_OPCODE_IS_STRING, ENTRY(SC_OPCODE_IS_STRING_ARY), 0, ID_FUNCTION_GRP_INFO, HID_FUNC_ISTTEXT, 1, { 0 }, 0 },
+ { SC_OPCODE_IS_NON_STRING, ENTRY(SC_OPCODE_IS_NON_STRING_ARY), 0, ID_FUNCTION_GRP_INFO, HID_FUNC_ISTKTEXT, 1, { 0 }, 0 },
+ { SC_OPCODE_IS_LOGICAL, ENTRY(SC_OPCODE_IS_LOGICAL_ARY), 0, ID_FUNCTION_GRP_INFO, HID_FUNC_ISTLOG, 1, { 0 }, 0 },
+ { SC_OPCODE_TYPE, ENTRY(SC_OPCODE_TYPE_ARY), 0, ID_FUNCTION_GRP_INFO, HID_FUNC_TYP, 1, { 0 }, 0 },
+ { SC_OPCODE_IS_REF, ENTRY(SC_OPCODE_IS_REF_ARY), 0, ID_FUNCTION_GRP_INFO, HID_FUNC_ISTBEZUG, 1, { 0 }, 0 },
+ { SC_OPCODE_IS_VALUE, ENTRY(SC_OPCODE_IS_VALUE_ARY), 0, ID_FUNCTION_GRP_INFO, HID_FUNC_ISTZAHL, 1, { 0 }, 0 },
+ { SC_OPCODE_IS_FORMULA, ENTRY(SC_OPCODE_IS_FORMULA_ARY), 0, ID_FUNCTION_GRP_INFO, HID_FUNC_ISTFORMEL, 1, { 0 }, 0 },
+ { SC_OPCODE_IS_NV, ENTRY(SC_OPCODE_IS_NV_ARY), 0, ID_FUNCTION_GRP_INFO, HID_FUNC_ISTNV, 1, { 0 }, 0 },
+ { SC_OPCODE_IS_ERR, ENTRY(SC_OPCODE_IS_ERR_ARY), 0, ID_FUNCTION_GRP_INFO, HID_FUNC_ISTFEHL, 1, { 0 }, 0 },
+ { SC_OPCODE_IS_ERROR, ENTRY(SC_OPCODE_IS_ERROR_ARY), 0, ID_FUNCTION_GRP_INFO, HID_FUNC_ISTFEHLER, 1, { 0 }, 0 },
+ { SC_OPCODE_IS_EVEN, ENTRY(SC_OPCODE_IS_EVEN_ARY), 0, ID_FUNCTION_GRP_INFO, HID_FUNC_ISTGERADE, 1, { 0 }, 0 },
+ { SC_OPCODE_IS_ODD, ENTRY(SC_OPCODE_IS_ODD_ARY), 0, ID_FUNCTION_GRP_INFO, HID_FUNC_ISTUNGERADE, 1, { 0 }, 0 },
+ { SC_OPCODE_N, ENTRY(SC_OPCODE_N_ARY), 0, ID_FUNCTION_GRP_INFO, HID_FUNC_N, 1, { 0 }, 0 },
+ { SC_OPCODE_GET_DATE_VALUE, ENTRY(SC_OPCODE_GET_DATE_VALUE_ARY), 0, ID_FUNCTION_GRP_DATETIME, HID_FUNC_DATWERT, 1, { 0 }, 0 },
+ { SC_OPCODE_GET_TIME_VALUE, ENTRY(SC_OPCODE_GET_TIME_VALUE_ARY), 0, ID_FUNCTION_GRP_DATETIME, HID_FUNC_ZEITWERT, 1, { 0 }, 0 },
+ { SC_OPCODE_CODE, ENTRY(SC_OPCODE_CODE_ARY), 0, ID_FUNCTION_GRP_TEXT, HID_FUNC_CODE, 1, { 0 }, 0 },
+ { SC_OPCODE_TRIM, ENTRY(SC_OPCODE_TRIM_ARY), 0, ID_FUNCTION_GRP_TEXT, HID_FUNC_GLAETTEN, 1, { 0 }, 0 },
+ { SC_OPCODE_UPPER, ENTRY(SC_OPCODE_UPPER_ARY), 0, ID_FUNCTION_GRP_TEXT, HID_FUNC_GROSS, 1, { 0 }, 0 },
+ { SC_OPCODE_PROPER, ENTRY(SC_OPCODE_PROPER_ARY), 0, ID_FUNCTION_GRP_TEXT, HID_FUNC_GROSS2, 1, { 0 }, 0 },
+ { SC_OPCODE_LOWER, ENTRY(SC_OPCODE_LOWER_ARY), 0, ID_FUNCTION_GRP_TEXT, HID_FUNC_KLEIN, 1, { 0 }, 0 },
+ { SC_OPCODE_LEN, ENTRY(SC_OPCODE_LEN_ARY), 0, ID_FUNCTION_GRP_TEXT, HID_FUNC_LAENGE, 1, { 0 }, 0 },
+ { SC_OPCODE_T, ENTRY(SC_OPCODE_T_ARY), 0, ID_FUNCTION_GRP_TEXT, HID_FUNC_T, 1, { 0 }, 0 },
+ { SC_OPCODE_VALUE, ENTRY(SC_OPCODE_VALUE_ARY), 0, ID_FUNCTION_GRP_TEXT, HID_FUNC_WERT, 1, { 0 }, 0 },
+ { SC_OPCODE_CLEAN, ENTRY(SC_OPCODE_CLEAN_ARY), 0, ID_FUNCTION_GRP_TEXT, HID_FUNC_SAEUBERN, 1, { 0 }, 0 },
+ { SC_OPCODE_CHAR, ENTRY(SC_OPCODE_CHAR_ARY), 0, ID_FUNCTION_GRP_TEXT, HID_FUNC_ZEICHEN, 1, { 0 }, 0 },
+ { SC_OPCODE_LOG10, ENTRY(SC_OPCODE_LOG10_ARY), 0, ID_FUNCTION_GRP_MATH, HID_FUNC_LOG10, 1, { 0 }, 0 },
+ { SC_OPCODE_EVEN, ENTRY(SC_OPCODE_EVEN_ARY), 0, ID_FUNCTION_GRP_MATH, HID_FUNC_GERADE, 1, { 0 }, 0 },
+ { SC_OPCODE_ODD, ENTRY(SC_OPCODE_ODD_ARY), 0, ID_FUNCTION_GRP_MATH, HID_FUNC_UNGERADE, 1, { 0 }, 0 },
+ { SC_OPCODE_STD_NORM_DIST, ENTRY(SC_OPCODE_STD_NORM_DIST_ARY), 0, ID_FUNCTION_GRP_STATISTIC, HID_FUNC_STANDNORMVERT, 1, { 0 }, 0 },
+ { SC_OPCODE_FISHER, ENTRY(SC_OPCODE_FISHER_ARY), 0, ID_FUNCTION_GRP_STATISTIC, HID_FUNC_FISHER, 1, { 0 }, 0 },
+ { SC_OPCODE_FISHER_INV, ENTRY(SC_OPCODE_FISHER_INV_ARY), 0, ID_FUNCTION_GRP_STATISTIC, HID_FUNC_FISHERINV, 1, { 0 }, 0 },
+ { SC_OPCODE_S_NORM_INV, ENTRY(SC_OPCODE_S_NORM_INV_ARY), 0, ID_FUNCTION_GRP_STATISTIC, HID_FUNC_STANDNORMINV, 1, { 0 }, 0 },
+ { SC_OPCODE_GAMMA_LN, ENTRY(SC_OPCODE_GAMMA_LN_ARY), 0, ID_FUNCTION_GRP_STATISTIC, HID_FUNC_GAMMALN, 1, { 0 }, 0 },
+ { SC_OPCODE_ERROR_TYPE, ENTRY(SC_OPCODE_ERROR_TYPE_ARY), 0, ID_FUNCTION_GRP_TABLE, HID_FUNC_FEHLERTYP, 1, { 0 }, 0 },
+ { SC_OPCODE_FORMULA, ENTRY(SC_OPCODE_FORMULA_ARY), 0, ID_FUNCTION_GRP_INFO, HID_FUNC_FORMEL, 1, { 0 }, 0 },
+ { SC_OPCODE_ARABIC, ENTRY(SC_OPCODE_ARABIC_ARY), 0, ID_FUNCTION_GRP_TEXT, HID_FUNC_ARABISCH, 1, { 0 }, 0 },
+ { SC_OPCODE_INFO, ENTRY(SC_OPCODE_INFO_ARY), 0, ID_FUNCTION_GRP_INFO, HID_FUNC_INFO, 1, { 0 }, 0 },
+ { SC_OPCODE_BAHTTEXT, ENTRY(SC_OPCODE_BAHTTEXT_ARY), 0, ID_FUNCTION_GRP_TEXT, HID_FUNC_BAHTTEXT, 1, { 0 }, 0 },
+ { SC_OPCODE_JIS, ENTRY(SC_OPCODE_JIS_ARY), 0, ID_FUNCTION_GRP_TEXT, HID_FUNC_JIS, 1, { 0 }, 0 },
+ { SC_OPCODE_ASC, ENTRY(SC_OPCODE_ASC_ARY), 0, ID_FUNCTION_GRP_TEXT, HID_FUNC_ASC, 1, { 0 }, 0 },
+ { SC_OPCODE_UNICODE, ENTRY(SC_OPCODE_UNICODE_ARY), 0, ID_FUNCTION_GRP_TEXT, HID_FUNC_UNICODE, 1, { 0 }, 0 },
+ { SC_OPCODE_UNICHAR, ENTRY(SC_OPCODE_UNICHAR_ARY), 0, ID_FUNCTION_GRP_TEXT, HID_FUNC_UNICHAR, 1, { 0 }, 0 },
+ { SC_OPCODE_GAMMA, ENTRY(SC_OPCODE_GAMMA_ARY), 0, ID_FUNCTION_GRP_STATISTIC, HID_FUNC_GAMMA, 1, { 0 }, 0 },
+ { SC_OPCODE_GAMMA_LN_MS, ENTRY(SC_OPCODE_GAMMA_LN_MS_ARY), 0, ID_FUNCTION_GRP_STATISTIC, HID_FUNC_GAMMALN_MS, 1, { 0 }, 0 },
+ { SC_OPCODE_ERF_MS, ENTRY(SC_OPCODE_ERF_MS_ARY), 0, ID_FUNCTION_GRP_STATISTIC, HID_FUNC_ERF_MS, 1, { 0 }, 0 },
+ { SC_OPCODE_ERFC_MS, ENTRY(SC_OPCODE_ERFC_MS_ARY), 0, ID_FUNCTION_GRP_STATISTIC, HID_FUNC_ERFC_MS, 1, { 0 }, 0 },
+ { SC_OPCODE_ERROR_TYPE_ODF, ENTRY(SC_OPCODE_ERROR_TYPE_ODF_ARY), 0, ID_FUNCTION_GRP_TABLE, HID_FUNC_ERROR_TYPE_ODF, 1, { 0 }, 0 },
+ { SC_OPCODE_ENCODEURL, ENTRY(SC_OPCODE_ENCODEURL_ARY), 0, ID_FUNCTION_GRP_TEXT, HID_FUNC_ENCODEURL, 1, { 0 }, 0 },
+ { SC_OPCODE_ISOWEEKNUM, ENTRY(SC_OPCODE_ISOWEEKNUM_ARY), 0, ID_FUNCTION_GRP_DATETIME, HID_FUNC_ISOWEEKNUM, 1, { 0 }, 0 },
+ { SC_OPCODE_NOT, ENTRY(SC_OPCODE_NOT_ARY), 0, ID_FUNCTION_GRP_LOGIC, HID_FUNC_NICHT, 1, { 0 }, 0 },
+ { SC_OPCODE_ARC_TAN_2, ENTRY(SC_OPCODE_ARC_TAN_2_ARY), 0, ID_FUNCTION_GRP_MATH, HID_FUNC_ARCTAN2, 2, { 0, 0 }, 0 },
+ { SC_OPCODE_CEIL, ENTRY(SC_OPCODE_CEIL_ARY), 0, ID_FUNCTION_GRP_MATH, HID_FUNC_OBERGRENZE, 3, { 0, 1, 1 }, 0 },
+ { SC_OPCODE_FLOOR, ENTRY(SC_OPCODE_FLOOR_ARY), 0, ID_FUNCTION_GRP_MATH, HID_FUNC_UNTERGRENZE, 3, { 0, 1, 1 }, 0 },
+ { SC_OPCODE_ROUND, ENTRY(SC_OPCODE_ROUND_ARY), 0, ID_FUNCTION_GRP_MATH, HID_FUNC_RUNDEN, 2, { 0, 1 }, 0 },
+ { SC_OPCODE_ROUND_UP, ENTRY(SC_OPCODE_ROUND_UP_ARY), 0, ID_FUNCTION_GRP_MATH, HID_FUNC_AUFRUNDEN, 2, { 0, 1 }, 0 },
+ { SC_OPCODE_ROUND_DOWN, ENTRY(SC_OPCODE_ROUND_DOWN_ARY), 0, ID_FUNCTION_GRP_MATH, HID_FUNC_ABRUNDEN, 2, { 0, 1 }, 0 },
+ { SC_OPCODE_TRUNC, ENTRY(SC_OPCODE_TRUNC_ARY), 0, ID_FUNCTION_GRP_MATH, HID_FUNC_KUERZEN, 2, { 0, 0 }, 0 },
+ { SC_OPCODE_LOG, ENTRY(SC_OPCODE_LOG_ARY), 0, ID_FUNCTION_GRP_MATH, HID_FUNC_LOG, 2, { 0, 1 }, 0 },
+ { SC_OPCODE_POWER, ENTRY(SC_OPCODE_POWER_ARY), 0, ID_FUNCTION_GRP_MATH, HID_FUNC_POTENZ, 2, { 0, 0 }, 0 },
+ { SC_OPCODE_GCD, ENTRY(SC_OPCODE_GCD_ARY), 0, ID_FUNCTION_GRP_MATH, HID_FUNC_GGT, VAR_ARGS, { 0 }, 0 },
+ { SC_OPCODE_LCM, ENTRY(SC_OPCODE_LCM_ARY), 0, ID_FUNCTION_GRP_MATH, HID_FUNC_KGV, VAR_ARGS, { 0 }, 0 },
+ { SC_OPCODE_MOD, ENTRY(SC_OPCODE_MOD_ARY), 0, ID_FUNCTION_GRP_MATH, HID_FUNC_REST, 2, { 0, 0 }, 0 },
+ { SC_OPCODE_SUM_PRODUCT, ENTRY(SC_OPCODE_SUM_PRODUCT_ARY), 0, ID_FUNCTION_GRP_MATRIX, HID_FUNC_SUMMENPRODUKT, VAR_ARGS, { 0 }, 0 },
+ { SC_OPCODE_SUM_SQ, ENTRY(SC_OPCODE_SUM_SQ_ARY), 0, ID_FUNCTION_GRP_MATH, HID_FUNC_QUADRATESUMME, VAR_ARGS, { 0 }, 0 },
+ { SC_OPCODE_SUM_X2MY2, ENTRY(SC_OPCODE_SUM_X2MY2_ARY), 0, ID_FUNCTION_GRP_MATRIX, HID_FUNC_SUMMEX2MY2, 2, { 0, 0 }, 0 },
+ { SC_OPCODE_SUM_X2DY2, ENTRY(SC_OPCODE_SUM_X2DY2_ARY), 0, ID_FUNCTION_GRP_MATRIX, HID_FUNC_SUMMEX2PY2, 2, { 0, 0 }, 0 },
+ { SC_OPCODE_SUM_XMY2, ENTRY(SC_OPCODE_SUM_XMY2_ARY), 0, ID_FUNCTION_GRP_MATRIX, HID_FUNC_SUMMEXMY2, 2, { 0, 0 }, 0 },
+ { SC_OPCODE_GET_DATE, ENTRY(SC_OPCODE_GET_DATE_ARY), 0, ID_FUNCTION_GRP_DATETIME, HID_FUNC_DATUM, 3, { 0, 0, 0 }, 0 },
+ { SC_OPCODE_GET_TIME, ENTRY(SC_OPCODE_GET_TIME_ARY), 0, ID_FUNCTION_GRP_DATETIME, HID_FUNC_ZEIT, 3, { 0, 0, 0 }, 0 },
+ { SC_OPCODE_GET_DIFF_DATE, ENTRY(SC_OPCODE_GET_DIFF_DATE_ARY), 0, ID_FUNCTION_GRP_DATETIME, HID_FUNC_TAGE, 2, { 0, 0 }, 0 },
+ { SC_OPCODE_GET_DIFF_DATE_360, ENTRY(SC_OPCODE_GET_DIFF_DATE_360_ARY), 0, ID_FUNCTION_GRP_DATETIME, HID_FUNC_TAGE360, 3, { 0, 0, 1 }, 0 },
+ { SC_OPCODE_MIN, ENTRY(SC_OPCODE_MIN_ARY), 0, ID_FUNCTION_GRP_STATISTIC, HID_FUNC_MIN, VAR_ARGS, { 0 }, 0 },
+ { SC_OPCODE_MAX, ENTRY(SC_OPCODE_MAX_ARY), 0, ID_FUNCTION_GRP_STATISTIC, HID_FUNC_MAX, VAR_ARGS, { 0 }, 0 },
+ { SC_OPCODE_SUM, ENTRY(SC_OPCODE_SUM_ARY), 0, ID_FUNCTION_GRP_MATH, HID_FUNC_SUMME, VAR_ARGS, { 0 }, 0 },
+ { SC_OPCODE_PRODUCT, ENTRY(SC_OPCODE_PRODUCT_ARY), 0, ID_FUNCTION_GRP_MATH, HID_FUNC_PRODUKT, VAR_ARGS, { 0 }, 0 },
+ { SC_OPCODE_AVERAGE, ENTRY(SC_OPCODE_AVERAGE_ARY), 0, ID_FUNCTION_GRP_STATISTIC, HID_FUNC_MITTELWERT, VAR_ARGS, { 0 }, 0 },
+ { SC_OPCODE_COUNT, ENTRY(SC_OPCODE_COUNT_ARY), 0, ID_FUNCTION_GRP_STATISTIC, HID_FUNC_ANZAHL, VAR_ARGS, { 0 }, 0 },
+ { SC_OPCODE_COUNT_2, ENTRY(SC_OPCODE_COUNT_2_ARY), 0, ID_FUNCTION_GRP_STATISTIC, HID_FUNC_ANZAHL2, VAR_ARGS, { 0 }, 0 },
+ { SC_OPCODE_NPV, ENTRY(SC_OPCODE_NPV_ARY), 0, ID_FUNCTION_GRP_FINANCIAL, HID_FUNC_NBW, VAR_ARGS+1, { 0, 0 }, 0 },
+ { SC_OPCODE_IRR, ENTRY(SC_OPCODE_IRR_ARY), 0, ID_FUNCTION_GRP_FINANCIAL, HID_FUNC_IKV, 2, { 0, 1 }, 0 },
+ { SC_OPCODE_VAR, ENTRY(SC_OPCODE_VAR_ARY), 0, ID_FUNCTION_GRP_STATISTIC, HID_FUNC_VARIANZ, VAR_ARGS, { 0 }, 0 },
+ { SC_OPCODE_VAR_P, ENTRY(SC_OPCODE_VAR_P_ARY), 0, ID_FUNCTION_GRP_STATISTIC, HID_FUNC_VARIANZEN, VAR_ARGS, { 0 }, 0 },
+ { SC_OPCODE_ST_DEV, ENTRY(SC_OPCODE_ST_DEV_ARY), 0, ID_FUNCTION_GRP_STATISTIC, HID_FUNC_STABW, VAR_ARGS, { 0 }, 0 },
+ { SC_OPCODE_ST_DEV_P, ENTRY(SC_OPCODE_ST_DEV_P_ARY), 0, ID_FUNCTION_GRP_STATISTIC, HID_FUNC_STABWN, VAR_ARGS, { 0 }, 0 },
+ { SC_OPCODE_B, ENTRY(SC_OPCODE_B_ARY), 0, ID_FUNCTION_GRP_STATISTIC, HID_FUNC_B, 4, { 0, 0, 0, 1 }, 0 },
+ { SC_OPCODE_NORM_DIST, ENTRY(SC_OPCODE_NORM_DIST_ARY), 0, ID_FUNCTION_GRP_STATISTIC, HID_FUNC_NORMVERT, 4, { 0, 0, 0, 1 }, 0 },
+ { SC_OPCODE_EXP_DIST, ENTRY(SC_OPCODE_EXP_DIST_ARY), 0, ID_FUNCTION_GRP_STATISTIC, HID_FUNC_EXPONVERT, 3, { 0, 0, 0 }, 0 },
+ { SC_OPCODE_BINOM_DIST, ENTRY(SC_OPCODE_BINOM_DIST_ARY), 0, ID_FUNCTION_GRP_STATISTIC, HID_FUNC_BINOMVERT, 4, { 0, 0, 0, 0 }, 0 },
+ { SC_OPCODE_POISSON_DIST, ENTRY(SC_OPCODE_POISSON_DIST_ARY), 0, ID_FUNCTION_GRP_STATISTIC, HID_FUNC_POISSON, 3, { 0, 0, 1 }, 0 },
+ { SC_OPCODE_COMBIN, ENTRY(SC_OPCODE_COMBIN_ARY), 0, ID_FUNCTION_GRP_MATH, HID_FUNC_KOMBINATIONEN, 2, { 0, 0 }, 0 },
+ { SC_OPCODE_COMBIN_A, ENTRY(SC_OPCODE_COMBIN_A_ARY), 0, ID_FUNCTION_GRP_MATH, HID_FUNC_KOMBINATIONEN2, 2, { 0, 0 }, 0 },
+ { SC_OPCODE_PERMUT, ENTRY(SC_OPCODE_PERMUT_ARY), 0, ID_FUNCTION_GRP_STATISTIC, HID_FUNC_VARIATIONEN, 2, { 0, 0 }, 0 },
+ { SC_OPCODE_PERMUTATION_A, ENTRY(SC_OPCODE_PERMUTATION_A_ARY), 0, ID_FUNCTION_GRP_STATISTIC, HID_FUNC_VARIATIONEN2, 2, { 0, 0 }, 0 },
+ { SC_OPCODE_PV, ENTRY(SC_OPCODE_PV_ARY), 0, ID_FUNCTION_GRP_FINANCIAL, HID_FUNC_BW, 5, { 0, 0, 0, 1, 1 }, 0 },
+ { SC_OPCODE_SYD, ENTRY(SC_OPCODE_SYD_ARY), 0, ID_FUNCTION_GRP_FINANCIAL, HID_FUNC_DIA, 4, { 0, 0, 0, 0 }, 0 },
+ { SC_OPCODE_DDB, ENTRY(SC_OPCODE_DDB_ARY), 0, ID_FUNCTION_GRP_FINANCIAL, HID_FUNC_GDA, 5, { 0, 0, 0, 0, 1 }, 0 },
+ { SC_OPCODE_DB, ENTRY(SC_OPCODE_DB_ARY), 0, ID_FUNCTION_GRP_FINANCIAL, HID_FUNC_GDA2, 5, { 0, 0, 0, 0, 1 }, 0 },
+ { SC_OPCODE_VBD , ENTRY(SC_OPCODE_VBD_ARY), 0, ID_FUNCTION_GRP_FINANCIAL, HID_FUNC_VDB, 7, { 0, 0, 0, 0, 0, 1, 1 }, 0 },
+ { SC_OPCODE_PDURATION, ENTRY(SC_OPCODE_PDURATION_ARY), 0, ID_FUNCTION_GRP_FINANCIAL, HID_FUNC_LAUFZEIT, 3, { 0, 0, 0 }, 0 },
+ { SC_OPCODE_SLN, ENTRY(SC_OPCODE_SLN_ARY), 0, ID_FUNCTION_GRP_FINANCIAL, HID_FUNC_LIA, 3, { 0, 0, 0 }, 0 },
+ { SC_OPCODE_PMT, ENTRY(SC_OPCODE_PMT_ARY), 0, ID_FUNCTION_GRP_FINANCIAL, HID_FUNC_RMZ, 5, { 0, 0, 0, 1, 1 }, 0 },
+ { SC_OPCODE_COLUMNS, ENTRY(SC_OPCODE_COLUMNS_ARY), 0, ID_FUNCTION_GRP_TABLE, HID_FUNC_SPALTEN, 1, { 0 }, 0 },
+ { SC_OPCODE_ROWS, ENTRY(SC_OPCODE_ROWS_ARY), 0, ID_FUNCTION_GRP_TABLE, HID_FUNC_ZEILEN, 1, { 0 }, 0 },
+ { SC_OPCODE_COLUMN, ENTRY(SC_OPCODE_COLUMN_ARY), 0, ID_FUNCTION_GRP_TABLE, HID_FUNC_SPALTE, 1, { 1 }, 0 },
+ { SC_OPCODE_ROW, ENTRY(SC_OPCODE_ROW_ARY), 0, ID_FUNCTION_GRP_TABLE, HID_FUNC_ZEILE, 1, { 1 }, 0 },
+ { SC_OPCODE_RRI, ENTRY(SC_OPCODE_RRI_ARY), 0, ID_FUNCTION_GRP_FINANCIAL, HID_FUNC_ZGZ, 3, { 0, 0, 0 }, 0 },
+ { SC_OPCODE_FV, ENTRY(SC_OPCODE_FV_ARY), 0, ID_FUNCTION_GRP_FINANCIAL, HID_FUNC_ZW, 5, { 0, 0, 0, 1, 1 }, 0 },
+ { SC_OPCODE_NPER, ENTRY(SC_OPCODE_NPER_ARY), 0, ID_FUNCTION_GRP_FINANCIAL, HID_FUNC_ZZR, 5, { 0, 0, 0, 1, 1 }, 0 },
+ { SC_OPCODE_RATE, ENTRY(SC_OPCODE_RATE_ARY), 0, ID_FUNCTION_GRP_FINANCIAL, HID_FUNC_ZINS, 6, { 0, 0, 0, 1, 1, 1 }, 0 },
+ { SC_OPCODE_IPMT, ENTRY(SC_OPCODE_IPMT_ARY), 0, ID_FUNCTION_GRP_FINANCIAL, HID_FUNC_ZINSZ, 6, { 0, 0, 0, 0, 1, 1 }, 0 },
+ { SC_OPCODE_PPMT, ENTRY(SC_OPCODE_PPMT_ARY), 0, ID_FUNCTION_GRP_FINANCIAL, HID_FUNC_KAPZ, 6, { 0, 0, 0, 0, 1, 1 }, 0 },
+ { SC_OPCODE_CUM_IPMT, ENTRY(SC_OPCODE_CUM_IPMT_ARY), 0, ID_FUNCTION_GRP_FINANCIAL, HID_FUNC_KUMZINSZ, 6, { 0, 0, 0, 0, 0, 0 }, 0 },
+ { SC_OPCODE_CUM_PRINC, ENTRY(SC_OPCODE_CUM_PRINC_ARY), 0, ID_FUNCTION_GRP_FINANCIAL, HID_FUNC_KUMKAPITAL, 6, { 0, 0, 0, 0, 0, 0 }, 0 },
+ { SC_OPCODE_EFFECT, ENTRY(SC_OPCODE_EFFECT_ARY), 0, ID_FUNCTION_GRP_FINANCIAL, HID_FUNC_EFFEKTIV, 2, { 0, 0 }, 0 },
+ { SC_OPCODE_NOMINAL, ENTRY(SC_OPCODE_NOMINAL_ARY), 0, ID_FUNCTION_GRP_FINANCIAL, HID_FUNC_NOMINAL, 2, { 0, 0 }, 0 },
+ { SC_OPCODE_SUB_TOTAL, ENTRY(SC_OPCODE_SUB_TOTAL_ARY), 0, ID_FUNCTION_GRP_MATH, HID_FUNC_TEILERGEBNIS, 2, { 0, 0 }, 0 },
+ { SC_OPCODE_DB_SUM, ENTRY(SC_OPCODE_DB_SUM_ARY), 0, ID_FUNCTION_GRP_DATABASE, HID_FUNC_DBSUMME, 3, { 0, 0, 0 }, 0 },
+ { SC_OPCODE_DB_COUNT, ENTRY(SC_OPCODE_DB_COUNT_ARY), 0, ID_FUNCTION_GRP_DATABASE, HID_FUNC_DBANZAHL, 3, { 0, 1, 0 }, 0 },
+ { SC_OPCODE_DB_COUNT_2, ENTRY(SC_OPCODE_DB_COUNT_2_ARY), 0, ID_FUNCTION_GRP_DATABASE, HID_FUNC_DBANZAHL2, 3, { 0, 1, 0 }, 0 },
+ { SC_OPCODE_DB_AVERAGE, ENTRY(SC_OPCODE_DB_AVERAGE_ARY), 0, ID_FUNCTION_GRP_DATABASE, HID_FUNC_DBMITTELWERT, 3, { 0, 0, 0 }, 0 },
+ { SC_OPCODE_DB_GET, ENTRY(SC_OPCODE_DB_GET_ARY), 0, ID_FUNCTION_GRP_DATABASE, HID_FUNC_DBAUSZUG, 3, { 0, 0, 0 }, 0 },
+ { SC_OPCODE_DB_MAX, ENTRY(SC_OPCODE_DB_MAX_ARY), 0, ID_FUNCTION_GRP_DATABASE, HID_FUNC_DBMAX, 3, { 0, 0, 0 }, 0 },
+ { SC_OPCODE_DB_MIN, ENTRY(SC_OPCODE_DB_MIN_ARY), 0, ID_FUNCTION_GRP_DATABASE, HID_FUNC_DBMIN, 3, { 0, 0, 0 }, 0 },
+ { SC_OPCODE_DB_PRODUCT, ENTRY(SC_OPCODE_DB_PRODUCT_ARY), 0, ID_FUNCTION_GRP_DATABASE, HID_FUNC_DBPRODUKT, 3, { 0, 0, 0 }, 0 },
+ { SC_OPCODE_DB_STD_DEV, ENTRY(SC_OPCODE_DB_STD_DEV_ARY), 0, ID_FUNCTION_GRP_DATABASE, HID_FUNC_DBSTDABW, 3, { 0, 0, 0 }, 0 },
+ { SC_OPCODE_DB_STD_DEV_P, ENTRY(SC_OPCODE_DB_STD_DEV_P_ARY), 0, ID_FUNCTION_GRP_DATABASE, HID_FUNC_DBSTDABWN, 3, { 0, 0, 0 }, 0 },
+ { SC_OPCODE_DB_VAR, ENTRY(SC_OPCODE_DB_VAR_ARY), 0, ID_FUNCTION_GRP_DATABASE, HID_FUNC_DBVARIANZ, 3, { 0, 0, 0 }, 0 },
+ { SC_OPCODE_DB_VAR_P, ENTRY(SC_OPCODE_DB_VAR_P_ARY), 0, ID_FUNCTION_GRP_DATABASE, HID_FUNC_DBVARIANZEN, 3, { 0, 0, 0 }, 0 },
+ { SC_OPCODE_INDIRECT, ENTRY(SC_OPCODE_INDIRECT_ARY), 0, ID_FUNCTION_GRP_TABLE, HID_FUNC_INDIREKT, 2, { 0, 1 }, 0 },
+ { SC_OPCODE_ADDRESS, ENTRY(SC_OPCODE_ADDRESS_ARY), 0, ID_FUNCTION_GRP_TABLE, HID_FUNC_ADRESSE, 5, { 0, 0, 1, 1, 1 }, 0 },
+ { SC_OPCODE_MATCH, ENTRY(SC_OPCODE_MATCH_ARY), 0, ID_FUNCTION_GRP_TABLE, HID_FUNC_VERGLEICH, 3, { 0, 0, 1 }, 0 },
+ { SC_OPCODE_COUNT_EMPTY_CELLS, ENTRY(SC_OPCODE_COUNT_EMPTY_CELLS_ARY), 0, ID_FUNCTION_GRP_STATISTIC, HID_FUNC_ANZAHLLEEREZELLEN, 1, { 0 }, 0 },
+ { SC_OPCODE_COUNT_IF, ENTRY(SC_OPCODE_COUNT_IF_ARY), 0, ID_FUNCTION_GRP_STATISTIC, HID_FUNC_ZAEHLENWENN, 2, { 0, 0 }, 0 },
+ { SC_OPCODE_SUM_IF, ENTRY(SC_OPCODE_SUM_IF_ARY), 0, ID_FUNCTION_GRP_MATH, HID_FUNC_SUMMEWENN, 3, { 0, 0, 1 }, 0 },
+ { SC_OPCODE_LOOKUP, ENTRY(SC_OPCODE_LOOKUP_ARY), 0, ID_FUNCTION_GRP_TABLE, HID_FUNC_VERWEIS, 3, { 0, 0, 1 }, 0 },
+ { SC_OPCODE_V_LOOKUP, ENTRY(SC_OPCODE_V_LOOKUP_ARY), 0, ID_FUNCTION_GRP_TABLE, HID_FUNC_SVERWEIS, 4, { 0, 0, 0, 1 }, 0 },
+ { SC_OPCODE_H_LOOKUP, ENTRY(SC_OPCODE_H_LOOKUP_ARY), 0, ID_FUNCTION_GRP_TABLE, HID_FUNC_WVERWEIS, 4, { 0, 0, 0, 1 }, 0 },
+ { SC_OPCODE_OFFSET, ENTRY(SC_OPCODE_OFFSET_ARY), 0, ID_FUNCTION_GRP_TABLE, HID_FUNC_VERSCHIEBUNG, 5, { 0, 0, 0, 1, 1 }, 0 },
+ { SC_OPCODE_INDEX, ENTRY(SC_OPCODE_INDEX_ARY), 0, ID_FUNCTION_GRP_TABLE, HID_FUNC_INDEX, 4, { 0, 1, 1, 1 }, 0 },
+ { SC_OPCODE_AREAS, ENTRY(SC_OPCODE_AREAS_ARY), 0, ID_FUNCTION_GRP_TABLE, HID_FUNC_BEREICHE, 1, { 0 }, 0 },
+ { SC_OPCODE_CURRENCY, ENTRY(SC_OPCODE_CURRENCY_ARY), 0, ID_FUNCTION_GRP_TEXT, HID_FUNC_DM, 2, { 0, 1 }, 0 },
+ { SC_OPCODE_REPLACE, ENTRY(SC_OPCODE_REPLACE_ARY), 0, ID_FUNCTION_GRP_TEXT, HID_FUNC_ERSETZEN, 4, { 0, 0, 0, 0 }, 0 },
+ { SC_OPCODE_FIXED, ENTRY(SC_OPCODE_FIXED_ARY), 0, ID_FUNCTION_GRP_TEXT, HID_FUNC_FEST, 3, { 0, 1, 1 }, 0 },
+ { SC_OPCODE_FIND, ENTRY(SC_OPCODE_FIND_ARY), 0, ID_FUNCTION_GRP_TEXT, HID_FUNC_FINDEN, 3, { 0, 0, 1 }, 0 },
+ { SC_OPCODE_EXACT, ENTRY(SC_OPCODE_EXACT_ARY), 0, ID_FUNCTION_GRP_TEXT, HID_FUNC_IDENTISCH, 2, { 0, 0 }, 0 },
+ { SC_OPCODE_LEFT, ENTRY(SC_OPCODE_LEFT_ARY), 0, ID_FUNCTION_GRP_TEXT, HID_FUNC_LINKS, 2, { 0, 1 }, 0 },
+ { SC_OPCODE_RIGHT, ENTRY(SC_OPCODE_RIGHT_ARY), 0, ID_FUNCTION_GRP_TEXT, HID_FUNC_RECHTS, 2, { 0, 1 }, 0 },
+ { SC_OPCODE_SEARCH, ENTRY(SC_OPCODE_SEARCH_ARY), 0, ID_FUNCTION_GRP_TEXT, HID_FUNC_SUCHEN, 3, { 0, 0, 1 }, 0 },
+ { SC_OPCODE_MID, ENTRY(SC_OPCODE_MID_ARY), 0, ID_FUNCTION_GRP_TEXT, HID_FUNC_TEIL, 3, { 0, 0, 0 }, 0 },
+ { SC_OPCODE_TEXT, ENTRY(SC_OPCODE_TEXT_ARY), 0, ID_FUNCTION_GRP_TEXT, HID_FUNC_TEXT, 2, { 0, 0 }, 0 },
+ { SC_OPCODE_SUBSTITUTE, ENTRY(SC_OPCODE_SUBSTITUTE_ARY), 0, ID_FUNCTION_GRP_TEXT, HID_FUNC_WECHSELN, 4, { 0, 0, 0, 1 }, 0 },
+ { SC_OPCODE_REPT, ENTRY(SC_OPCODE_REPT_ARY), 0, ID_FUNCTION_GRP_TEXT, HID_FUNC_WIEDERHOLEN, 2, { 0, 0 }, 0 },
+ { SC_OPCODE_CONCAT, ENTRY(SC_OPCODE_CONCAT_ARY), 0, ID_FUNCTION_GRP_TEXT, HID_FUNC_VERKETTEN, VAR_ARGS, { 0 }, 0 },
+ { SC_OPCODE_MAT_DET, ENTRY(SC_OPCODE_MAT_DET_ARY), 0, ID_FUNCTION_GRP_MATRIX, HID_FUNC_MDET, 1, { 0 }, 0 },
+ { SC_OPCODE_MAT_INV, ENTRY(SC_OPCODE_MAT_INV_ARY), 0, ID_FUNCTION_GRP_MATRIX, HID_FUNC_MINV, 1, { 0 }, 0 },
+ { SC_OPCODE_MAT_MULT, ENTRY(SC_OPCODE_MAT_MULT_ARY), 0, ID_FUNCTION_GRP_MATRIX, HID_FUNC_MMULT, 2, { 0, 0 }, 0 },
+ { SC_OPCODE_MAT_TRANS, ENTRY(SC_OPCODE_MAT_TRANS_ARY), 0, ID_FUNCTION_GRP_MATRIX, HID_FUNC_MTRANS, 1, { 0 }, 0 },
+ { SC_OPCODE_MATRIX_UNIT, ENTRY(SC_OPCODE_MATRIX_UNIT_ARY), 0, ID_FUNCTION_GRP_MATRIX, HID_FUNC_EINHEITSMATRIX, 1, { 0 }, 0 },
+ { SC_OPCODE_HYP_GEOM_DIST, ENTRY(SC_OPCODE_HYP_GEOM_DIST_ARY), 0, ID_FUNCTION_GRP_STATISTIC, HID_FUNC_HYPGEOMVERT, 5, { 0, 0, 0, 0, 1 }, 0 },
+ { SC_OPCODE_LOG_NORM_DIST, ENTRY(SC_OPCODE_LOG_NORM_DIST_ARY), 0, ID_FUNCTION_GRP_STATISTIC, HID_FUNC_LOGNORMVERT, 4, { 0, 1, 1, 1 }, 0 },
+ { SC_OPCODE_T_DIST, ENTRY(SC_OPCODE_T_DIST_ARY), 0, ID_FUNCTION_GRP_STATISTIC, HID_FUNC_TVERT, 3, { 0, 0, 0 }, 0 },
+ { SC_OPCODE_F_DIST, ENTRY(SC_OPCODE_F_DIST_ARY), 0, ID_FUNCTION_GRP_STATISTIC, HID_FUNC_FVERT, 3, { 0, 0, 0 }, 0 },
+ { SC_OPCODE_CHI_DIST, ENTRY(SC_OPCODE_CHI_DIST_ARY), 0, ID_FUNCTION_GRP_STATISTIC, HID_FUNC_CHIVERT, 2, { 0, 0 }, 0 },
+ { SC_OPCODE_WEIBULL, ENTRY(SC_OPCODE_WEIBULL_ARY), 0, ID_FUNCTION_GRP_STATISTIC, HID_FUNC_WEIBULL, 4, { 0, 0, 0, 0 }, 0 },
+ { SC_OPCODE_NEG_BINOM_VERT, ENTRY(SC_OPCODE_NEG_BINOM_VERT_ARY), 0, ID_FUNCTION_GRP_STATISTIC, HID_FUNC_NEGBINOMVERT, 3, { 0, 0, 0 }, 0 },
+ { SC_OPCODE_CRIT_BINOM, ENTRY(SC_OPCODE_CRIT_BINOM_ARY), 0, ID_FUNCTION_GRP_STATISTIC, HID_FUNC_KRITBINOM, 3, { 0, 0, 0 }, 0 },
+ { SC_OPCODE_KURT, ENTRY(SC_OPCODE_KURT_ARY), 0, ID_FUNCTION_GRP_STATISTIC, HID_FUNC_KURT, VAR_ARGS, { 0 }, 0 },
+ { SC_OPCODE_HAR_MEAN, ENTRY(SC_OPCODE_HAR_MEAN_ARY), 0, ID_FUNCTION_GRP_STATISTIC, HID_FUNC_HARMITTEL, VAR_ARGS, { 0 }, 0 },
+ { SC_OPCODE_GEO_MEAN, ENTRY(SC_OPCODE_GEO_MEAN_ARY), 0, ID_FUNCTION_GRP_STATISTIC, HID_FUNC_GEOMITTEL, VAR_ARGS, { 0 }, 0 },
+ { SC_OPCODE_STANDARD, ENTRY(SC_OPCODE_STANDARD_ARY), 0, ID_FUNCTION_GRP_STATISTIC, HID_FUNC_STANDARDISIERUNG, 3, { 0, 0, 0 }, 0 },
+ { SC_OPCODE_AVE_DEV, ENTRY(SC_OPCODE_AVE_DEV_ARY), 0, ID_FUNCTION_GRP_STATISTIC, HID_FUNC_MITTELABW, VAR_ARGS, { 0 }, 0 },
+ { SC_OPCODE_SKEW, ENTRY(SC_OPCODE_SKEW_ARY), 0, ID_FUNCTION_GRP_STATISTIC, HID_FUNC_SCHIEFE, VAR_ARGS, { 0 }, 0 },
+ { SC_OPCODE_DEV_SQ, ENTRY(SC_OPCODE_DEV_SQ_ARY), 0, ID_FUNCTION_GRP_STATISTIC, HID_FUNC_SUMQUADABW, VAR_ARGS, { 0 }, 0 },
+ { SC_OPCODE_MEDIAN, ENTRY(SC_OPCODE_MEDIAN_ARY), 0, ID_FUNCTION_GRP_STATISTIC, HID_FUNC_MEDIAN, VAR_ARGS, { 0 }, 0 },
+ { SC_OPCODE_MODAL_VALUE, ENTRY(SC_OPCODE_MODAL_VALUE_ARY), 0, ID_FUNCTION_GRP_STATISTIC, HID_FUNC_MODALWERT, VAR_ARGS, { 0 }, 0 },
+ { SC_OPCODE_Z_TEST, ENTRY(SC_OPCODE_Z_TEST_ARY), 0, ID_FUNCTION_GRP_STATISTIC, HID_FUNC_GTEST, 3, { 0, 0, 1 }, 0 },
+ { SC_OPCODE_T_TEST, ENTRY(SC_OPCODE_T_TEST_ARY), 0, ID_FUNCTION_GRP_STATISTIC, HID_FUNC_TTEST, 4, { 0, 0, 0, 0 }, 0 },
+ { SC_OPCODE_RANK, ENTRY(SC_OPCODE_RANK_ARY), 0, ID_FUNCTION_GRP_STATISTIC, HID_FUNC_RANG, 3, { 0, 0, 1 }, 0 },
+ { SC_OPCODE_PERCENTILE, ENTRY(SC_OPCODE_PERCENTILE_ARY), 0, ID_FUNCTION_GRP_STATISTIC, HID_FUNC_QUANTIL, 2, { 0, 0 }, 0 },
+ { SC_OPCODE_PERCENT_RANK, ENTRY(SC_OPCODE_PERCENT_RANK_ARY), 0, ID_FUNCTION_GRP_STATISTIC, HID_FUNC_QUANTILSRANG, 3, { 0, 0, 1 }, 0 },
+ { SC_OPCODE_LARGE, ENTRY(SC_OPCODE_LARGE_ARY), 0, ID_FUNCTION_GRP_STATISTIC, HID_FUNC_KGROESSTE, 2, { 0, 0 }, 0 },
+ { SC_OPCODE_SMALL, ENTRY(SC_OPCODE_SMALL_ARY), 0, ID_FUNCTION_GRP_STATISTIC, HID_FUNC_KKLEINSTE, 2, { 0, 0 }, 0 },
+ { SC_OPCODE_FREQUENCY, ENTRY(SC_OPCODE_FREQUENCY_ARY), 0, ID_FUNCTION_GRP_MATRIX, HID_FUNC_HAEUFIGKEIT, 2, { 0, 0 }, 0 },
+ { SC_OPCODE_QUARTILE, ENTRY(SC_OPCODE_QUARTILE_ARY), 0, ID_FUNCTION_GRP_STATISTIC, HID_FUNC_QUARTILE, 2, { 0, 0 }, 0 },
+ { SC_OPCODE_NORM_INV, ENTRY(SC_OPCODE_NORM_INV_ARY), 0, ID_FUNCTION_GRP_STATISTIC, HID_FUNC_NORMINV, 3, { 0, 0, 0 }, 0 },
+ { SC_OPCODE_CONFIDENCE, ENTRY(SC_OPCODE_CONFIDENCE_ARY), 0, ID_FUNCTION_GRP_STATISTIC, HID_FUNC_KONFIDENZ, 3, { 0, 0, 0 }, 0 },
+ { SC_OPCODE_F_TEST, ENTRY(SC_OPCODE_F_TEST_ARY), 0, ID_FUNCTION_GRP_STATISTIC, HID_FUNC_FTEST, 2, { 0, 0 }, 0 },
+ { SC_OPCODE_TRIM_MEAN, ENTRY(SC_OPCODE_TRIM_MEAN_ARY), 0, ID_FUNCTION_GRP_STATISTIC, HID_FUNC_GESTUTZTMITTEL, 2, { 0, 0 }, 0 },
+ { SC_OPCODE_PROB, ENTRY(SC_OPCODE_PROB_ARY), 0, ID_FUNCTION_GRP_STATISTIC, HID_FUNC_WAHRSCHBEREICH, 4, { 0, 0, 0, 1 }, 0 },
+ { SC_OPCODE_CORREL, ENTRY(SC_OPCODE_CORREL_ARY), 0, ID_FUNCTION_GRP_STATISTIC, HID_FUNC_KORREL, 2, { 0, 0 }, 0 },
+ { SC_OPCODE_COVAR, ENTRY(SC_OPCODE_COVAR_ARY), 0, ID_FUNCTION_GRP_STATISTIC, HID_FUNC_KOVAR, 2, { 0, 0 }, 0 },
+ { SC_OPCODE_PEARSON, ENTRY(SC_OPCODE_PEARSON_ARY), 0, ID_FUNCTION_GRP_STATISTIC, HID_FUNC_PEARSON, 2, { 0, 0 }, 0 },
+ { SC_OPCODE_RSQ, ENTRY(SC_OPCODE_RSQ_ARY), 0, ID_FUNCTION_GRP_STATISTIC, HID_FUNC_BESTIMMTHEITSMASS, 2, { 0, 0 }, 0 },
+ { SC_OPCODE_STEYX, ENTRY(SC_OPCODE_STEYX_ARY), 0, ID_FUNCTION_GRP_STATISTIC, HID_FUNC_STFEHLERYX, 2, { 0, 0 }, 0 },
+ { SC_OPCODE_SLOPE, ENTRY(SC_OPCODE_SLOPE_ARY), 0, ID_FUNCTION_GRP_STATISTIC, HID_FUNC_STEIGUNG, 2, { 0, 0 }, 0 },
+ { SC_OPCODE_INTERCEPT, ENTRY(SC_OPCODE_INTERCEPT_ARY), 0, ID_FUNCTION_GRP_STATISTIC, HID_FUNC_ACHSENABSCHNITT, 2, { 0, 0 }, 0 },
+ { SC_OPCODE_TREND, ENTRY(SC_OPCODE_TREND_ARY), 0, ID_FUNCTION_GRP_MATRIX, HID_FUNC_TREND, 4, { 0, 1, 1, 1 }, 0 },
+ { SC_OPCODE_GROWTH, ENTRY(SC_OPCODE_GROWTH_ARY), 0, ID_FUNCTION_GRP_MATRIX, HID_FUNC_VARIATION, 4, { 0, 1, 1, 1 }, 0 },
+ { SC_OPCODE_LINEST, ENTRY(SC_OPCODE_LINEST_ARY), 0, ID_FUNCTION_GRP_MATRIX, HID_FUNC_RGP, 4, { 0, 1, 1, 1 }, 0 },
+ { SC_OPCODE_LOGEST, ENTRY(SC_OPCODE_LOGEST_ARY), 0, ID_FUNCTION_GRP_MATRIX, HID_FUNC_RKP, 4, { 0, 1, 1, 1 }, 0 },
+ { SC_OPCODE_FORECAST, ENTRY(SC_OPCODE_FORECAST_ARY), 0, ID_FUNCTION_GRP_STATISTIC, HID_FUNC_SCHAETZER, 3, { 0, 0, 0 }, 0 },
+ { SC_OPCODE_CHI_INV, ENTRY(SC_OPCODE_CHI_INV_ARY), 0, ID_FUNCTION_GRP_STATISTIC, HID_FUNC_CHIINV, 2, { 0, 0 }, 0 },
+ { SC_OPCODE_GAMMA_DIST, ENTRY(SC_OPCODE_GAMMA_DIST_ARY), 0, ID_FUNCTION_GRP_STATISTIC, HID_FUNC_GAMMAVERT, 4, { 0, 0, 0, 1 }, 0 },
+ { SC_OPCODE_GAMMA_INV, ENTRY(SC_OPCODE_GAMMA_INV_ARY), 0, ID_FUNCTION_GRP_STATISTIC, HID_FUNC_GAMMAINV, 3, { 0, 0, 0 }, 0 },
+ { SC_OPCODE_T_INV, ENTRY(SC_OPCODE_T_INV_ARY), 0, ID_FUNCTION_GRP_STATISTIC, HID_FUNC_TINV, 2, { 0, 0 }, 0 },
+ { SC_OPCODE_F_INV, ENTRY(SC_OPCODE_F_INV_ARY), 0, ID_FUNCTION_GRP_STATISTIC, HID_FUNC_FINV, 3, { 0, 0, 0 }, 0 },
+ { SC_OPCODE_CHI_TEST, ENTRY(SC_OPCODE_CHI_TEST_ARY), 0, ID_FUNCTION_GRP_STATISTIC, HID_FUNC_CHITEST, 2, { 0, 0 }, 0 },
+ { SC_OPCODE_LOG_INV, ENTRY(SC_OPCODE_LOG_INV_ARY), 0, ID_FUNCTION_GRP_STATISTIC, HID_FUNC_LOGINV, 3, { 0, 1, 1 }, 0 },
+ { SC_OPCODE_BETA_DIST, ENTRY(SC_OPCODE_BETA_DIST_ARY), 0, ID_FUNCTION_GRP_STATISTIC, HID_FUNC_BETAVERT, 6, { 0, 0, 0, 1, 1, 1 }, 0 },
+ { SC_OPCODE_BETA_INV, ENTRY(SC_OPCODE_BETA_INV_ARY), 0, ID_FUNCTION_GRP_STATISTIC, HID_FUNC_BETAINV, 5, { 0, 0, 0, 1, 1 }, 0 },
+ { SC_OPCODE_WEEK, ENTRY(SC_OPCODE_WEEK_ARY), 0, ID_FUNCTION_GRP_DATETIME, HID_FUNC_KALENDERWOCHE, 2, { 0, 1 }, 0 },
+ { SC_OPCODE_GET_DAY_OF_WEEK, ENTRY(SC_OPCODE_GET_DAY_OF_WEEK_ARY), 0, ID_FUNCTION_GRP_DATETIME, HID_FUNC_WOCHENTAG, 2, { 0, 1 }, 0 },
+ { SC_OPCODE_STYLE, ENTRY(SC_OPCODE_STYLE_ARY), 0, ID_FUNCTION_GRP_TABLE, HID_FUNC_VORLAGE, 3, { 0, 1, 1 }, 0 },
+ { SC_OPCODE_DDE, ENTRY(SC_OPCODE_DDE_ARY), 0, ID_FUNCTION_GRP_TABLE, HID_FUNC_DDE, 4, { 0, 0, 0, 1 }, 0 },
+ { SC_OPCODE_BASE, ENTRY(SC_OPCODE_BASE_ARY), 0, ID_FUNCTION_GRP_TEXT, HID_FUNC_BASIS, 3, { 0, 0, 1 }, 0 },
+ { SC_OPCODE_SHEET, ENTRY(SC_OPCODE_SHEET_ARY), 0, ID_FUNCTION_GRP_TABLE, HID_FUNC_TABELLE, 1, { 1 }, 0 },
+ { SC_OPCODE_SHEETS, ENTRY(SC_OPCODE_SHEETS_ARY), 0, ID_FUNCTION_GRP_TABLE, HID_FUNC_TABELLEN, 1, { 1 }, 0 },
+ { SC_OPCODE_MIN_A, ENTRY(SC_OPCODE_MIN_A_ARY), 0, ID_FUNCTION_GRP_STATISTIC, HID_FUNC_MINA, VAR_ARGS, { 0 }, 0 },
+ { SC_OPCODE_MAX_A, ENTRY(SC_OPCODE_MAX_A_ARY), 0, ID_FUNCTION_GRP_STATISTIC, HID_FUNC_MAXA, VAR_ARGS, { 0 }, 0 },
+ { SC_OPCODE_AVERAGE_A, ENTRY(SC_OPCODE_AVERAGE_A_ARY), 0, ID_FUNCTION_GRP_STATISTIC, HID_FUNC_MITTELWERTA, VAR_ARGS, { 0 }, 0 },
+ { SC_OPCODE_ST_DEV_A, ENTRY(SC_OPCODE_ST_DEV_A_ARY), 0, ID_FUNCTION_GRP_STATISTIC, HID_FUNC_STABWA, VAR_ARGS, { 0 }, 0 },
+ { SC_OPCODE_ST_DEV_P_A, ENTRY(SC_OPCODE_ST_DEV_P_A_ARY), 0, ID_FUNCTION_GRP_STATISTIC, HID_FUNC_STABWNA, VAR_ARGS, { 0 }, 0 },
+ { SC_OPCODE_VAR_A, ENTRY(SC_OPCODE_VAR_A_ARY), 0, ID_FUNCTION_GRP_STATISTIC, HID_FUNC_VARIANZA, VAR_ARGS, { 0 }, 0 },
+ { SC_OPCODE_VAR_P_A, ENTRY(SC_OPCODE_VAR_P_A_ARY), 0, ID_FUNCTION_GRP_STATISTIC, HID_FUNC_VARIANZENA, VAR_ARGS, { 0 }, 0 },
+ { SC_OPCODE_EASTERSUNDAY, ENTRY(SC_OPCODE_EASTERSUNDAY_ARY), 0, ID_FUNCTION_GRP_DATETIME, HID_FUNC_OSTERSONNTAG, 1, { 0 }, 0 },
+ { SC_OPCODE_DECIMAL, ENTRY(SC_OPCODE_DECIMAL_ARY), 0, ID_FUNCTION_GRP_TEXT, HID_FUNC_DEZIMAL, 2, { 0, 0 }, 0 },
+ { SC_OPCODE_CONVERT_OOO, ENTRY(SC_OPCODE_CONVERT_OOO_ARY), 0, ID_FUNCTION_GRP_MATH, HID_FUNC_UMRECHNEN, 3, { 0, 0, 0 }, 0 },
+ { SC_OPCODE_ROMAN, ENTRY(SC_OPCODE_ROMAN_ARY), 0, ID_FUNCTION_GRP_TEXT, HID_FUNC_ROEMISCH, 2, { 0, 1 }, 0 },
+ { SC_OPCODE_MIRR, ENTRY(SC_OPCODE_MIRR_ARY), 0, ID_FUNCTION_GRP_FINANCIAL, HID_FUNC_QIKV, 3, { 0, 0, 0 }, 0 },
+ { SC_OPCODE_CELL, ENTRY(SC_OPCODE_CELL_ARY), 0, ID_FUNCTION_GRP_INFO, HID_FUNC_ZELLE, 2, { 0, 1 }, 0 },
+ { SC_OPCODE_ISPMT, ENTRY(SC_OPCODE_ISPMT_ARY), 0, ID_FUNCTION_GRP_FINANCIAL, HID_FUNC_ISPMT, 4, { 0, 0, 0, 0 }, 0 },
+ { SC_OPCODE_HYPERLINK, ENTRY(SC_OPCODE_HYPERLINK_ARY), 0, ID_FUNCTION_GRP_TABLE, HID_FUNC_HYPERLINK, 2, { 0, 1 }, 0 },
+ { SC_OPCODE_GET_PIVOT_DATA, ENTRY(SC_OPCODE_GET_PIVOT_DATA_ARY), 0, ID_FUNCTION_GRP_TABLE, HID_FUNC_GETPIVOTDATA, VAR_ARGS+2, { 0, 0, 1 }, 0 },
+ { SC_OPCODE_EUROCONVERT, ENTRY(SC_OPCODE_EUROCONVERT_ARY), 0, ID_FUNCTION_GRP_MATH, HID_FUNC_EUROCONVERT, 5, { 0, 0, 0, 1, 1 }, 0 },
+ { SC_OPCODE_NUMBERVALUE, ENTRY(SC_OPCODE_NUMBERVALUE_ARY), 0, ID_FUNCTION_GRP_TEXT, HID_FUNC_NUMBERVALUE, 3, { 0, 1, 1 }, 0 },
+ { SC_OPCODE_CHISQ_DIST, ENTRY(SC_OPCODE_CHISQ_DIST_ARY), 0, ID_FUNCTION_GRP_STATISTIC, HID_FUNC_CHISQDIST, 3, { 0, 0, 1 }, 0 },
+ { SC_OPCODE_CHISQ_INV, ENTRY(SC_OPCODE_CHISQ_INV_ARY), 0, ID_FUNCTION_GRP_STATISTIC, HID_FUNC_CHISQINV, 2, { 0, 0 }, 0 },
+ { SC_OPCODE_BITAND, ENTRY(SC_OPCODE_BITAND_ARY), 0, ID_FUNCTION_GRP_MATH, HID_FUNC_BITAND, 2, { 0, 0 }, 0 },
+ { SC_OPCODE_BITOR, ENTRY(SC_OPCODE_BITOR_ARY), 0, ID_FUNCTION_GRP_MATH, HID_FUNC_BITOR, 2, { 0, 0 }, 0 },
+ { SC_OPCODE_BITXOR, ENTRY(SC_OPCODE_BITXOR_ARY), 0, ID_FUNCTION_GRP_MATH, HID_FUNC_BITXOR, 2, { 0, 0 }, 0 },
+ { SC_OPCODE_BITRSHIFT, ENTRY(SC_OPCODE_BITRSHIFT_ARY), 0, ID_FUNCTION_GRP_MATH, HID_FUNC_BITRSHIFT, 2, { 0, 0 }, 0 },
+ { SC_OPCODE_BITLSHIFT, ENTRY(SC_OPCODE_BITLSHIFT_ARY), 0, ID_FUNCTION_GRP_MATH, HID_FUNC_BITLSHIFT, 2, { 0, 0 }, 0 },
+ { SC_OPCODE_GET_DATEDIF, ENTRY(SC_OPCODE_GET_DATEDIF_ARY), 0, ID_FUNCTION_GRP_DATETIME, HID_FUNC_DATEDIF, 3, { 0, 0, 0 }, 0 },
+ { SC_OPCODE_XOR, ENTRY(SC_OPCODE_XOR_ARY), 0, ID_FUNCTION_GRP_LOGIC, HID_FUNC_XOR, VAR_ARGS, { 0 }, 0 },
+ { SC_OPCODE_AVERAGE_IF, ENTRY(SC_OPCODE_AVERAGE_IF_ARY), 0, ID_FUNCTION_GRP_STATISTIC, HID_FUNC_AVERAGEIF, 3, { 0, 0, 1 }, 0 },
+ { SC_OPCODE_SUM_IFS, ENTRY(SC_OPCODE_SUM_IFS_ARY), 0, ID_FUNCTION_GRP_MATH, HID_FUNC_SUMIFS, PAIRED_VAR_ARGS+1, { 0, 0, 0 }, 0 },
+ { SC_OPCODE_AVERAGE_IFS, ENTRY(SC_OPCODE_AVERAGE_IFS_ARY), 0, ID_FUNCTION_GRP_STATISTIC, HID_FUNC_AVERAGEIFS, PAIRED_VAR_ARGS+1, { 0, 0, 0 }, 0 },
+ { SC_OPCODE_COUNT_IFS, ENTRY(SC_OPCODE_COUNT_IFS_ARY), 0, ID_FUNCTION_GRP_STATISTIC, HID_FUNC_COUNTIFS, PAIRED_VAR_ARGS, { 0, 0 }, 0 },
+ { SC_OPCODE_SKEWP, ENTRY(SC_OPCODE_SKEWP_ARY), 0, ID_FUNCTION_GRP_STATISTIC, HID_FUNC_SKEWP, VAR_ARGS, { 0 }, 0 },
+ { SC_OPCODE_LENB, ENTRY(SC_OPCODE_LENB_ARY), 0, ID_FUNCTION_GRP_TEXT, HID_FUNC_LENB, 1, { 0 }, 0 },
+ { SC_OPCODE_RIGHTB, ENTRY(SC_OPCODE_RIGHTB_ARY), 0, ID_FUNCTION_GRP_TEXT, HID_FUNC_RIGHTB, 2, { 0, 1 }, 0 },
+ { SC_OPCODE_LEFTB, ENTRY(SC_OPCODE_LEFTB_ARY), 0, ID_FUNCTION_GRP_TEXT, HID_FUNC_LEFTB, 2, { 0, 1 }, 0 },
+ { SC_OPCODE_MIDB, ENTRY(SC_OPCODE_MIDB_ARY), 0, ID_FUNCTION_GRP_TEXT, HID_FUNC_MIDB, 3, { 0, 0, 0 }, 0 },
+ { SC_OPCODE_FILTERXML, ENTRY(SC_OPCODE_FILTERXML_ARY), 0, ID_FUNCTION_GRP_TEXT, HID_FUNC_FILTERXML, 2, { 0, 0 }, 0 },
+ { SC_OPCODE_WEBSERVICE, ENTRY(SC_OPCODE_WEBSERVICE_ARY), 0, ID_FUNCTION_GRP_TEXT, HID_FUNC_WEBSERVICE, 1, { 0, 0 }, 0 },
+ { SC_OPCODE_COVARIANCE_S, ENTRY(SC_OPCODE_COVARIANCE_S_ARY), 0, ID_FUNCTION_GRP_STATISTIC, HID_FUNC_COVARIANCE_S, 2, { 0, 0 }, 0 },
+ { SC_OPCODE_COVARIANCE_P, ENTRY(SC_OPCODE_COVARIANCE_P_ARY), 0, ID_FUNCTION_GRP_STATISTIC, HID_FUNC_COVARIANCE_P, 2, { 0, 0 }, 0 },
+ { SC_OPCODE_ST_DEV_P_MS, ENTRY(SC_OPCODE_ST_DEV_P_MS_ARY), 0, ID_FUNCTION_GRP_STATISTIC, HID_FUNC_ST_DEV_P_MS, VAR_ARGS, { 0 }, 0 },
+ { SC_OPCODE_ST_DEV_S, ENTRY(SC_OPCODE_ST_DEV_S_ARY), 0, ID_FUNCTION_GRP_STATISTIC, HID_FUNC_ST_DEV_S, VAR_ARGS, { 0 }, 0 },
+ { SC_OPCODE_VAR_P_MS, ENTRY(SC_OPCODE_VAR_P_MS_ARY), 0, ID_FUNCTION_GRP_STATISTIC, HID_FUNC_VAR_P_MS, VAR_ARGS, { 0 }, 0 },
+ { SC_OPCODE_VAR_S, ENTRY(SC_OPCODE_VAR_S_ARY), 0, ID_FUNCTION_GRP_STATISTIC, HID_FUNC_VAR_S, VAR_ARGS, { 0 }, 0 },
+ { SC_OPCODE_BETA_DIST_MS, ENTRY(SC_OPCODE_BETA_DIST_MS_ARY), 0, ID_FUNCTION_GRP_STATISTIC, HID_FUNC_BETADIST_MS, 6, { 0, 0, 0, 0, 1, 1 }, 0 },
+ { SC_OPCODE_BETA_INV_MS, ENTRY(SC_OPCODE_BETA_INV_MS_ARY), 0, ID_FUNCTION_GRP_STATISTIC, HID_FUNC_BETAINV_MS, 5, { 0, 0, 0, 1, 1 }, 0 },
+ { SC_OPCODE_BINOM_DIST_MS, ENTRY(SC_OPCODE_BINOM_DIST_MS_ARY), 0, ID_FUNCTION_GRP_STATISTIC, HID_FUNC_BINOM_DIST_MS, 4, { 0, 0, 0, 0 }, 0 },
+ { SC_OPCODE_BINOM_INV, ENTRY(SC_OPCODE_BINOM_INV_ARY), 0, ID_FUNCTION_GRP_STATISTIC, HID_FUNC_BINOM_INV_MS, 3, { 0, 0, 0 }, 0 },
+ { SC_OPCODE_CHI_DIST_MS, ENTRY(SC_OPCODE_CHI_DIST_MS_ARY), 0, ID_FUNCTION_GRP_STATISTIC, HID_FUNC_CHIVERT_MS, 2, { 0, 0 }, 0 },
+ { SC_OPCODE_CHI_INV_MS, ENTRY(SC_OPCODE_CHI_INV_MS_ARY), 0, ID_FUNCTION_GRP_STATISTIC, HID_FUNC_CHIINV_MS, 2, { 0, 0 }, 0 },
+ { SC_OPCODE_CHI_TEST_MS, ENTRY(SC_OPCODE_CHI_TEST_MS_ARY), 0, ID_FUNCTION_GRP_STATISTIC, HID_FUNC_CHITEST_MS, 2, { 0, 0 }, 0 },
+ { SC_OPCODE_CHISQ_DIST_MS, ENTRY(SC_OPCODE_CHISQ_DIST_MS_ARY), 0, ID_FUNCTION_GRP_STATISTIC, HID_FUNC_CHISQDIST_MS, 3, { 0, 0, 0 }, 0 },
+ { SC_OPCODE_CHISQ_INV_MS, ENTRY(SC_OPCODE_CHISQ_INV_MS_ARY), 0, ID_FUNCTION_GRP_STATISTIC, HID_FUNC_CHISQINV_MS, 2, { 0, 0 }, 0 },
+ { SC_OPCODE_CONFIDENCE_N, ENTRY(SC_OPCODE_CONFIDENCE_N_ARY), 0, ID_FUNCTION_GRP_STATISTIC, HID_FUNC_CONFIDENCE_N, 3, { 0, 0, 0 }, 0 },
+ { SC_OPCODE_CONFIDENCE_T, ENTRY(SC_OPCODE_CONFIDENCE_T_ARY), 0, ID_FUNCTION_GRP_STATISTIC, HID_FUNC_CONFIDENCE_T, 3, { 0, 0, 0 }, 0 },
+ { SC_OPCODE_F_DIST_LT, ENTRY(SC_OPCODE_F_DIST_LT_ARY), 0, ID_FUNCTION_GRP_STATISTIC, HID_FUNC_F_DIST_LT, 4, { 0, 0, 0, 1 }, 0 },
+ { SC_OPCODE_F_DIST_RT, ENTRY(SC_OPCODE_F_DIST_RT_ARY), 0, ID_FUNCTION_GRP_STATISTIC, HID_FUNC_F_DIST_RT, 3, { 0, 0, 0 }, 0 },
+ { SC_OPCODE_F_INV_LT, ENTRY(SC_OPCODE_F_INV_LT_ARY), 0, ID_FUNCTION_GRP_STATISTIC, HID_FUNC_F_INV_LT, 3, { 0, 0, 0 }, 0 },
+ { SC_OPCODE_F_INV_RT, ENTRY(SC_OPCODE_F_INV_RT_ARY), 0, ID_FUNCTION_GRP_STATISTIC, HID_FUNC_F_INV_RT, 3, { 0, 0, 0 }, 0 },
+ { SC_OPCODE_F_TEST_MS, ENTRY(SC_OPCODE_F_TEST_MS_ARY), 0, ID_FUNCTION_GRP_STATISTIC, HID_FUNC_F_TEST_MS, 2, { 0, 0 }, 0 },
+ { SC_OPCODE_EXP_DIST_MS, ENTRY(SC_OPCODE_EXP_DIST_MS_ARY), 0, ID_FUNCTION_GRP_STATISTIC, HID_FUNC_EXP_DIST_MS, 3, { 0, 0, 0 }, 0 },
+ { SC_OPCODE_HYP_GEOM_DIST_MS, ENTRY(SC_OPCODE_HYP_GEOM_DIST_MS_ARY), 0, ID_FUNCTION_GRP_STATISTIC, HID_FUNC_HYP_GEOM_DIST_MS, 5, { 0, 0, 0, 0, 0 }, 0 },
+ { SC_OPCODE_POISSON_DIST_MS, ENTRY(SC_OPCODE_POISSON_DIST_MS_ARY), 0, ID_FUNCTION_GRP_STATISTIC, HID_FUNC_POISSON_DIST_MS, 3, { 0, 0, 0 }, 0 },
+ { SC_OPCODE_WEIBULL_MS, ENTRY(SC_OPCODE_WEIBULL_MS_ARY), 0, ID_FUNCTION_GRP_STATISTIC, HID_FUNC_WEIBULL_DIST_MS, 4, { 0, 0, 0, 0 }, 0 },
+ { SC_OPCODE_GAMMA_DIST_MS, ENTRY(SC_OPCODE_GAMMA_DIST_MS_ARY), 0, ID_FUNCTION_GRP_STATISTIC, HID_FUNC_GAMMADIST_MS, 4, { 0, 0, 0, 0 }, 0 },
+ { SC_OPCODE_GAMMA_INV_MS, ENTRY(SC_OPCODE_GAMMA_INV_MS_ARY), 0, ID_FUNCTION_GRP_STATISTIC, HID_FUNC_GAMMAINV_MS, 3, { 0, 0, 0 }, 0 },
+ { SC_OPCODE_LOG_NORM_DIST_MS, ENTRY(SC_OPCODE_LOG_NORM_DIST_MS_ARY), 0, ID_FUNCTION_GRP_STATISTIC, HID_FUNC_LOGNORMDIST_MS, 4, { 0, 0, 0, 0 }, 0 },
+ { SC_OPCODE_LOG_INV_MS, ENTRY(SC_OPCODE_LOG_INV_MS_ARY), 0, ID_FUNCTION_GRP_STATISTIC, HID_FUNC_LOGINV, 3, { 0, 0, 0 }, 0 },
+ { SC_OPCODE_NORM_DIST_MS, ENTRY(SC_OPCODE_NORM_DIST_MS_ARY), 0, ID_FUNCTION_GRP_STATISTIC, HID_FUNC_NORMDIST_MS, 4, { 0, 0, 0, 0 }, 0 },
+ { SC_OPCODE_NORM_INV_MS, ENTRY(SC_OPCODE_NORM_INV_MS_ARY), 0, ID_FUNCTION_GRP_STATISTIC, HID_FUNC_NORMINV_MS, 3, { 0, 0, 0 }, 0 },
+ { SC_OPCODE_STD_NORM_DIST_MS, ENTRY(SC_OPCODE_STD_NORM_DIST_MS_ARY), 0, ID_FUNCTION_GRP_STATISTIC, HID_FUNC_STD_NORMDIST_MS, 2, { 0, 0 }, 0 },
+ { SC_OPCODE_S_NORM_INV_MS, ENTRY(SC_OPCODE_S_NORM_INV_MS_ARY), 0, ID_FUNCTION_GRP_STATISTIC, HID_FUNC_STD_NORMINV_MS, 1, { 0 }, 0 },
+ { SC_OPCODE_T_DIST_MS, ENTRY(SC_OPCODE_T_DIST_MS_ARY), 0, ID_FUNCTION_GRP_STATISTIC, HID_FUNC_TDIST_MS, 3, { 0, 0, 0 }, 0 },
+ { SC_OPCODE_T_DIST_RT, ENTRY(SC_OPCODE_T_DIST_RT_ARY), 0, ID_FUNCTION_GRP_STATISTIC, HID_FUNC_TDIST_RT, 2, { 0, 0 }, 0 },
+ { SC_OPCODE_T_DIST_2T, ENTRY(SC_OPCODE_T_DIST_2T_ARY), 0, ID_FUNCTION_GRP_STATISTIC, HID_FUNC_TDIST_2T, 2, { 0, 0 }, 0 },
+ { SC_OPCODE_T_INV_2T, ENTRY(SC_OPCODE_T_INV_2T_ARY), 0, ID_FUNCTION_GRP_STATISTIC, HID_FUNC_TINV_2T, 2, { 0, 0 }, 0 },
+ { SC_OPCODE_T_INV_MS, ENTRY(SC_OPCODE_T_INV_MS_ARY), 0, ID_FUNCTION_GRP_STATISTIC, HID_FUNC_TINV_MS, 2, { 0, 0 }, 0 },
+ { SC_OPCODE_T_TEST_MS, ENTRY(SC_OPCODE_T_TEST_MS_ARY), 0, ID_FUNCTION_GRP_STATISTIC, HID_FUNC_TTEST_MS, 4, { 0, 0, 0, 0 }, 0 },
+ { SC_OPCODE_PERCENTILE_INC, ENTRY(SC_OPCODE_PERCENTILE_INC_ARY), 0, ID_FUNCTION_GRP_STATISTIC, HID_FUNC_PERCENTILE_INC, 2, { 0, 0 }, 0 },
+ { SC_OPCODE_PERCENT_RANK_INC, ENTRY(SC_OPCODE_PERCENT_RANK_INC_ARY), 0, ID_FUNCTION_GRP_STATISTIC, HID_FUNC_PERCENTRANK_INC, 3, { 0, 0, 1 }, 0 },
+ { SC_OPCODE_QUARTILE_INC, ENTRY(SC_OPCODE_QUARTILE_INC_ARY), 0, ID_FUNCTION_GRP_STATISTIC, HID_FUNC_QUARTILE_INC, 2, { 0, 0 }, 0 },
+ { SC_OPCODE_RANK_EQ, ENTRY(SC_OPCODE_RANK_EQ_ARY), 0, ID_FUNCTION_GRP_STATISTIC, HID_FUNC_RANK_EQ, 3, { 0, 0, 1 }, 0 },
+ { SC_OPCODE_PERCENTILE_EXC, ENTRY(SC_OPCODE_PERCENTILE_EXC_ARY), 0, ID_FUNCTION_GRP_STATISTIC, HID_FUNC_PERCENTILE_EXC, 2, { 0, 0 }, 0 },
+ { SC_OPCODE_PERCENT_RANK_EXC, ENTRY(SC_OPCODE_PERCENT_RANK_EXC_ARY), 0, ID_FUNCTION_GRP_STATISTIC, HID_FUNC_PERCENTRANK_EXC, 3, { 0, 0, 1 }, 0 },
+ { SC_OPCODE_QUARTILE_EXC, ENTRY(SC_OPCODE_QUARTILE_EXC_ARY), 0, ID_FUNCTION_GRP_STATISTIC, HID_FUNC_QUARTILE_EXC, 2, { 0, 0 }, 0 },
+ { SC_OPCODE_RANK_AVG, ENTRY(SC_OPCODE_RANK_AVG_ARY), 0, ID_FUNCTION_GRP_STATISTIC, HID_FUNC_RANK_AVG, 3, { 0, 0, 1 }, 0 },
+ { SC_OPCODE_MODAL_VALUE_MS, ENTRY(SC_OPCODE_MODAL_VALUE_MS_ARY), 0, ID_FUNCTION_GRP_STATISTIC, HID_FUNC_MODAL_VALUE_MS, VAR_ARGS, { 0 }, 0 },
+ { SC_OPCODE_MODAL_VALUE_MULTI, ENTRY(SC_OPCODE_MODAL_VALUE_MULTI_ARY), 0, ID_FUNCTION_GRP_STATISTIC, HID_FUNC_MODAL_VALUE_MULTI, VAR_ARGS, { 0 }, 0 },
+ { SC_OPCODE_NEG_BINOM_DIST_MS, ENTRY(SC_OPCODE_NEG_BINOM_DIST_MS_ARY), 0, ID_FUNCTION_GRP_STATISTIC, HID_FUNC_NEGBINOMDIST_MS, 4, { 0, 0, 0, 0 }, 0 },
+ { SC_OPCODE_Z_TEST_MS, ENTRY(SC_OPCODE_Z_TEST_MS_ARY), 0, ID_FUNCTION_GRP_STATISTIC, HID_FUNC_Z_TEST_MS, 3, { 0, 0, 1 }, 0 },
+ { SC_OPCODE_CEIL_MS, ENTRY(SC_OPCODE_CEIL_MS_ARY), 0, ID_FUNCTION_GRP_MATH, HID_FUNC_CEIL_MS, 2, { 0, 0 }, 0 },
+ { SC_OPCODE_CEIL_ISO, ENTRY(SC_OPCODE_CEIL_ISO_ARY), 0, ID_FUNCTION_GRP_MATH, HID_FUNC_CEIL_ISO, 2, { 0, 1 }, 0 },
+ { SC_OPCODE_FLOOR_MS, ENTRY(SC_OPCODE_FLOOR_MS_ARY), 0, ID_FUNCTION_GRP_MATH, HID_FUNC_FLOOR_MS, 2, { 0, 0 }, 0 },
+ { SC_OPCODE_NETWORKDAYS_MS, ENTRY(SC_OPCODE_NETWORKDAYS_MS_ARY), 0, ID_FUNCTION_GRP_DATETIME, HID_FUNC_NETWORKDAYS_MS, 4, { 0, 0, 1, 1 }, 0 },
+ { SC_OPCODE_WORKDAY_MS, ENTRY(SC_OPCODE_WORKDAY_MS_ARY), 0, ID_FUNCTION_GRP_DATETIME, HID_FUNC_WORKDAY_MS, 4, { 0, 0, 1, 1 }, 0 },
+ { SC_OPCODE_AGGREGATE, ENTRY(SC_OPCODE_AGGREGATE_ARY), 0, ID_FUNCTION_GRP_MATH, HID_FUNC_AGGREGATE, VAR_ARGS+3, { 0, 0, 0, 1 }, 0 },
+ { SC_OPCODE_COLOR, ENTRY(SC_OPCODE_COLOR_ARY), 0, ID_FUNCTION_GRP_MATH, HID_FUNC_COLOR, 4, { 0, 0, 0, 1 }, 0 },
+ { SC_OPCODE_CEIL_MATH, ENTRY(SC_OPCODE_CEIL_MATH_ARY), 0, ID_FUNCTION_GRP_MATH, HID_FUNC_CEIL_MATH, 3, { 0, 1, 1 }, 0 },
+ { SC_OPCODE_CEIL_PRECISE, ENTRY(SC_OPCODE_CEIL_PRECISE_ARY), 0, ID_FUNCTION_GRP_MATH, HID_FUNC_CEIL_PRECISE, 2, { 0, 1 }, 0 },
+ { SC_OPCODE_NETWORKDAYS, ENTRY(SC_OPCODE_NETWORKDAYS_ARY), 0, ID_FUNCTION_GRP_DATETIME, HID_FUNC_NETWORKDAYS, 4, { 0, 0, 1, 1 }, 0 },
+ { SC_OPCODE_FLOOR_MATH, ENTRY(SC_OPCODE_FLOOR_MATH_ARY), 0, ID_FUNCTION_GRP_MATH, HID_FUNC_FLOOR_MATH, 3, { 0, 1, 1 }, 0 },
+ { SC_OPCODE_FLOOR_PRECISE, ENTRY(SC_OPCODE_FLOOR_PRECISE_ARY), 0, ID_FUNCTION_GRP_MATH, HID_FUNC_FLOOR_PRECISE, 2, { 0, 1 }, 0 },
+ { SC_OPCODE_RAWSUBTRACT, ENTRY(SC_OPCODE_RAWSUBTRACT_ARY), 0, ID_FUNCTION_GRP_MATH, HID_FUNC_RAWSUBTRACT, VAR_ARGS+2, { 0, 0, 1 }, 0 },
+ { SC_OPCODE_WEEKNUM_OOO, ENTRY(SC_OPCODE_WEEKNUM_OOO_ARY), 2, ID_FUNCTION_GRP_DATETIME, HID_FUNC_WEEKNUM_OOO, 2, { 0, 0 }, 0 },
+ { SC_OPCODE_FORECAST_ETS_ADD, ENTRY(SC_OPCODE_FORECAST_ETS_ADD_ARY), 0, ID_FUNCTION_GRP_STATISTIC, HID_FUNC_FORECAST_ETS_ADD, 6, { 0, 0, 0, 1, 1, 1 }, 0 },
+ { SC_OPCODE_FORECAST_ETS_SEA, ENTRY(SC_OPCODE_FORECAST_ETS_SEA_ARY), 0, ID_FUNCTION_GRP_STATISTIC, HID_FUNC_FORECAST_ETS_SEA, 4, { 0, 0, 1, 1 }, 0 },
+ { SC_OPCODE_FORECAST_ETS_MUL, ENTRY(SC_OPCODE_FORECAST_ETS_MUL_ARY), 0, ID_FUNCTION_GRP_STATISTIC, HID_FUNC_FORECAST_ETS_MUL, 6, { 0, 0, 0, 1, 1, 1 }, 0 },
+ { SC_OPCODE_FORECAST_ETS_PIA, ENTRY(SC_OPCODE_FORECAST_ETS_PIA_ARY), 0, ID_FUNCTION_GRP_STATISTIC, HID_FUNC_FORECAST_ETS_PIA, 7, { 0, 0, 0, 1, 1, 1, 1 }, 0 },
+ { SC_OPCODE_FORECAST_ETS_PIM, ENTRY(SC_OPCODE_FORECAST_ETS_PIM_ARY), 0, ID_FUNCTION_GRP_STATISTIC, HID_FUNC_FORECAST_ETS_PIM, 7, { 0, 0, 0, 1, 1, 1, 1 }, 0 },
+ { SC_OPCODE_FORECAST_ETS_STA, ENTRY(SC_OPCODE_FORECAST_ETS_STA_ARY), 0, ID_FUNCTION_GRP_STATISTIC, HID_FUNC_FORECAST_ETS_STA, 6, { 0, 0, 0, 1, 1, 1 }, 0 },
+ { SC_OPCODE_FORECAST_ETS_STM, ENTRY(SC_OPCODE_FORECAST_ETS_STM_ARY), 0, ID_FUNCTION_GRP_STATISTIC, HID_FUNC_FORECAST_ETS_STM, 6, { 0, 0, 0, 1, 1, 1 }, 0 },
+ { SC_OPCODE_FORECAST_LIN, ENTRY(SC_OPCODE_FORECAST_LIN_ARY), 0, ID_FUNCTION_GRP_STATISTIC, HID_FUNC_FORECAST_LIN, 3, { 0, 0, 0 }, 0 },
+ { SC_OPCODE_CONCAT_MS, ENTRY(SC_OPCODE_CONCAT_MS_ARY), 0, ID_FUNCTION_GRP_TEXT, HID_FUNC_CONCAT_MS, VAR_ARGS, { 0 }, 0 },
+ { SC_OPCODE_TEXTJOIN_MS, ENTRY(SC_OPCODE_TEXTJOIN_MS_ARY), 0, ID_FUNCTION_GRP_TEXT, HID_FUNC_TEXTJOIN_MS, VAR_ARGS + 2, { 0, 0, 0 }, 0 },
+ { SC_OPCODE_IFS_MS, ENTRY(SC_OPCODE_IFS_MS_ARY), 0, ID_FUNCTION_GRP_LOGIC, HID_FUNC_IFS_MS, PAIRED_VAR_ARGS, { 0, 0 }, 0 },
+ { SC_OPCODE_SWITCH_MS, ENTRY(SC_OPCODE_SWITCH_MS_ARY), 0, ID_FUNCTION_GRP_LOGIC, HID_FUNC_SWITCH_MS, PAIRED_VAR_ARGS + 1, { 0, 0, 0 }, 0 },
+ { SC_OPCODE_MINIFS_MS, ENTRY(SC_OPCODE_MINIFS_MS_ARY), 0, ID_FUNCTION_GRP_STATISTIC, HID_FUNC_MINIFS_MS, PAIRED_VAR_ARGS + 1, { 0, 0, 0 }, 0 },
+ { SC_OPCODE_MAXIFS_MS, ENTRY(SC_OPCODE_MAXIFS_MS_ARY), 0, ID_FUNCTION_GRP_STATISTIC, HID_FUNC_MAXIFS_MS, PAIRED_VAR_ARGS + 1, { 0, 0, 0 }, 0 },
+ { SC_OPCODE_ROUNDSIG, ENTRY(SC_OPCODE_ROUNDSIG_ARY), 0, ID_FUNCTION_GRP_MATH, HID_FUNC_ROUNDSIG, 2, { 0, 0 }, 0 },
+ { SC_OPCODE_REPLACEB, ENTRY(SC_OPCODE_REPLACEB_ARY), 0, ID_FUNCTION_GRP_TEXT, HID_FUNC_REPLACEB, 4, { 0, 0, 0, 0 }, 0 },
+ { SC_OPCODE_FINDB, ENTRY(SC_OPCODE_FINDB_ARY), 0, ID_FUNCTION_GRP_TEXT, HID_FUNC_FINDB, 3, { 0, 0, 1 }, 0 },
+ { SC_OPCODE_SEARCHB, ENTRY(SC_OPCODE_SEARCHB_ARY), 0, ID_FUNCTION_GRP_TEXT, HID_FUNC_SEARCHB, 3, { 0, 0, 1 }, 0 },
+ { SC_OPCODE_REGEX, ENTRY(SC_OPCODE_REGEX_ARY), 0, ID_FUNCTION_GRP_TEXT, HID_FUNC_REGEX, 4, { 0, 0, 1, 1 }, 0 },
+ { SC_OPCODE_FOURIER, ENTRY(SC_OPCODE_FOURIER_ARY), 0, ID_FUNCTION_GRP_MATRIX, HID_FUNC_FOURIER, 5, { 0, 0, 1, 1, 1 }, 0 },
+ { SC_OPCODE_RANDBETWEEN_NV, ENTRY(SC_OPCODE_RANDBETWEEN_NV_ARY), 0, ID_FUNCTION_GRP_MATH, HID_FUNC_RANDBETWEEN_NV, 2, { 0, 0 }, 0 }
+ };
+
+ ScFuncDesc* pDesc = nullptr;
+ sal_Int32 nStrLen = 0;
+ ::std::vector<const ScFuncDesc*> tmpFuncVector;
+
+ // Browse for all possible OpCodes. This is not the fastest method, but
+ // otherwise the sub resources within the resource blocks and the
+ // resource blocks themselves would had to be ordered according to
+ // OpCodes, which is utopian...
+ ScFuncDescCore const * pDescsEnd = aDescs + SAL_N_ELEMENTS(aDescs);
+ for (sal_uInt16 i = 0; i <= SC_OPCODE_LAST_OPCODE_ID; ++i)
+ {
+ const ScFuncDescCore *pEntry = std::lower_bound(aDescs, pDescsEnd, i,
+ [](const ScFuncDescCore &rItem, sal_uInt16 key)
+ {
+ return rItem.nOpCode < key;
+ }
+ );
+
+ // Opcode Resource available?
+ if (pEntry != pDescsEnd && pEntry->nOpCode == i && pEntry->pResource)
+ {
+ pDesc = new ScFuncDesc;
+ bool bSuppressed = false;
+
+ ScFuncRes(*pEntry, pDesc, bSuppressed);
+ // Instead of dealing with this exceptional case at 1001 places
+ // we simply don't add an entirely suppressed function to the
+ // list and delete it.
+ if (bSuppressed)
+ delete pDesc;
+ else
+ {
+ pDesc->nFIndex = i;
+ tmpFuncVector.push_back(pDesc);
+
+ nStrLen = pDesc->mxFuncName->getLength();
+ if (nStrLen > nMaxFuncNameLen)
+ nMaxFuncNameLen = nStrLen;
+ }
+ }
+ }
+
+ // legacy binary AddIn functions
+
+ sal_uInt16 nNextId = SC_OPCODE_LAST_OPCODE_ID + 1; // FuncID for AddIn functions
+
+ // Interpretation of AddIn list
+ OUString aDefArgNameValue = "value";
+ OUString aDefArgNameString = "string";
+ OUString aDefArgNameValues = "values";
+ OUString aDefArgNameStrings = "strings";
+ OUString aDefArgNameCells = "cells";
+ OUString aDefArgNameNone = "none";
+ OUString aDefArgDescValue = "a value";
+ OUString aDefArgDescString = "a string";
+ OUString aDefArgDescValues = "array of values";
+ OUString aDefArgDescStrings = "array of strings";
+ OUString aDefArgDescCells = "range of cells";
+ OUString aDefArgDescNone = "none";
+
+ OUString aArgName, aArgDesc;
+ const LegacyFuncCollection& rLegacyFuncColl = *ScGlobal::GetLegacyFuncCollection();
+ for (auto const& legacyFunc : rLegacyFuncColl)
+ {
+ const LegacyFuncData *const pLegacyFuncData = legacyFunc.second.get();
+ pDesc = new ScFuncDesc;
+ sal_uInt16 nArgs = pLegacyFuncData->GetParamCount() - 1;
+ pLegacyFuncData->getParamDesc( aArgName, aArgDesc, 0 );
+ pDesc->nFIndex = nNextId++; // ??? OpCode vergeben
+ pDesc->nCategory = ID_FUNCTION_GRP_ADDINS;
+ pDesc->mxFuncName = pLegacyFuncData->GetInternalName().toAsciiUpperCase();
+ pDesc->mxFuncDesc = aArgDesc + "\n"
+ "( AddIn: " + pLegacyFuncData->GetModuleName() + " )";
+ pDesc->nArgCount = nArgs;
+ if (nArgs)
+ {
+ pDesc->maDefArgNames.clear();
+ pDesc->maDefArgNames.resize(nArgs);
+ pDesc->maDefArgDescs.clear();
+ pDesc->maDefArgDescs.resize(nArgs);
+ pDesc->pDefArgFlags = new ScFuncDesc::ParameterFlags[nArgs];
+ for (sal_uInt16 j = 0; j < nArgs; ++j)
+ {
+ pDesc->pDefArgFlags[j].bOptional = false;
+ pLegacyFuncData->getParamDesc( aArgName, aArgDesc, j+1 );
+ if ( !aArgName.isEmpty() )
+ pDesc->maDefArgNames[j] = aArgName;
+ else
+ {
+ switch (pLegacyFuncData->GetParamType(j+1))
+ {
+ case ParamType::PTR_DOUBLE:
+ pDesc->maDefArgNames[j] = aDefArgNameValue;
+ break;
+ case ParamType::PTR_STRING:
+ pDesc->maDefArgNames[j] = aDefArgNameString;
+ break;
+ case ParamType::PTR_DOUBLE_ARR:
+ pDesc->maDefArgNames[j] = aDefArgNameValues;
+ break;
+ case ParamType::PTR_STRING_ARR:
+ pDesc->maDefArgNames[j] = aDefArgNameStrings;
+ break;
+ case ParamType::PTR_CELL_ARR:
+ pDesc->maDefArgNames[j] = aDefArgNameCells;
+ break;
+ default:
+ pDesc->maDefArgNames[j] = aDefArgNameNone;
+ break;
+ }
+ }
+ if ( !aArgDesc.isEmpty() )
+ pDesc->maDefArgDescs[j] = aArgDesc;
+ else
+ {
+ switch (pLegacyFuncData->GetParamType(j+1))
+ {
+ case ParamType::PTR_DOUBLE:
+ pDesc->maDefArgDescs[j] = aDefArgDescValue;
+ break;
+ case ParamType::PTR_STRING:
+ pDesc->maDefArgDescs[j] = aDefArgDescString;
+ break;
+ case ParamType::PTR_DOUBLE_ARR:
+ pDesc->maDefArgDescs[j] = aDefArgDescValues;
+ break;
+ case ParamType::PTR_STRING_ARR:
+ pDesc->maDefArgDescs[j] = aDefArgDescStrings;
+ break;
+ case ParamType::PTR_CELL_ARR:
+ pDesc->maDefArgDescs[j] = aDefArgDescCells;
+ break;
+ default:
+ pDesc->maDefArgDescs[j] = aDefArgDescNone;
+ break;
+ }
+ }
+ }
+ }
+
+ tmpFuncVector.push_back(pDesc);
+ nStrLen = pDesc->mxFuncName->getLength();
+ if ( nStrLen > nMaxFuncNameLen)
+ nMaxFuncNameLen = nStrLen;
+ }
+
+ // StarOne AddIns
+
+ ScUnoAddInCollection* pUnoAddIns = ScGlobal::GetAddInCollection();
+ tools::Long nUnoCount = pUnoAddIns->GetFuncCount();
+ for (tools::Long nFunc=0; nFunc<nUnoCount; nFunc++)
+ {
+ pDesc = new ScFuncDesc;
+ pDesc->nFIndex = nNextId++;
+
+ if ( pUnoAddIns->FillFunctionDesc( nFunc, *pDesc, mbEnglishFunctionNames ) )
+ {
+ tmpFuncVector.push_back(pDesc);
+ nStrLen = pDesc->mxFuncName->getLength();
+ if (nStrLen > nMaxFuncNameLen)
+ nMaxFuncNameLen = nStrLen;
+ }
+ else
+ delete pDesc;
+ }
+
+ aFunctionList.swap(tmpFuncVector);
+
+ //Initialize iterator
+ aFunctionListIter = aFunctionList.end();
+}
+
+ScFunctionList::~ScFunctionList()
+{
+ const ScFuncDesc* pDesc = First();
+ while (pDesc)
+ {
+ delete pDesc;
+ pDesc = Next();
+ }
+}
+
+const ScFuncDesc* ScFunctionList::First()
+{
+ const ScFuncDesc* pDesc = nullptr;
+ aFunctionListIter = aFunctionList.begin();
+ if(aFunctionListIter != aFunctionList.end())
+ pDesc = *aFunctionListIter;
+
+ return pDesc;
+}
+
+const ScFuncDesc* ScFunctionList::Next()
+{
+ const ScFuncDesc* pDesc = nullptr;
+ if(aFunctionListIter != aFunctionList.end())
+ {
+ if((++aFunctionListIter) != aFunctionList.end())
+ pDesc = *aFunctionListIter;
+ }
+ return pDesc;
+}
+
+const ScFuncDesc* ScFunctionList::GetFunction( sal_uInt32 nIndex ) const
+{
+ const ScFuncDesc* pDesc = nullptr;
+ if(nIndex < aFunctionList.size())
+ pDesc = aFunctionList.at(nIndex);
+
+ return pDesc;
+}
+
+
+sal_uInt32 ScFunctionCategory::getCount() const
+{
+ return m_rCategory.size();
+}
+
+OUString ScFunctionCategory::getName() const
+{
+ if ( m_sName.isEmpty() )
+ m_sName = ScFunctionMgr::GetCategoryName(m_nCategory);
+ return m_sName;
+}
+
+const formula::IFunctionDescription* ScFunctionCategory::getFunction(sal_uInt32 _nPos) const
+{
+ const ScFuncDesc* pDesc = nullptr;
+ if(_nPos < m_rCategory.size())
+ pDesc = m_rCategory.at(_nPos);
+ return pDesc;
+}
+
+sal_uInt32 ScFunctionCategory::getNumber() const
+{
+ return m_nCategory;
+}
+
+
+ScFunctionMgr::ScFunctionMgr()
+{
+ ScFunctionList* pFuncList /**< list of all calc functions */
+ = ScGlobal::GetStarCalcFunctionList();
+
+ OSL_ENSURE( pFuncList, "Functionlist not found." );
+ sal_uInt32 catCount[MAX_FUNCCAT] = {0};
+
+ aCatLists[0].reserve(pFuncList->GetCount());
+
+ // Retrieve all functions, store in cumulative ("All") category, and count
+ // number of functions in each category
+ for(const ScFuncDesc* pDesc = pFuncList->First(); pDesc; pDesc = pFuncList->Next())
+ {
+ OSL_ENSURE((pDesc->nCategory) < MAX_FUNCCAT, "Unknown category");
+ if ((pDesc->nCategory) < MAX_FUNCCAT)
+ ++catCount[pDesc->nCategory];
+ aCatLists[0].push_back(pDesc);
+ }
+
+ // Sort functions in cumulative category by name
+ ::std::sort(aCatLists[0].begin(), aCatLists[0].end(), ScFuncDesc::compareByName);
+
+ // Allocate correct amount of space for categories
+ for (sal_uInt16 i = 1; i < MAX_FUNCCAT; ++i)
+ {
+ aCatLists[i].reserve(catCount[i]);
+ }
+
+ // Fill categories with the corresponding functions (still sorted by name)
+ for (auto const& elemList : aCatLists[0])
+ {
+ if ((elemList->nCategory) < MAX_FUNCCAT)
+ aCatLists[elemList->nCategory].push_back(elemList);
+ }
+
+ // Initialize iterators
+ pCurCatListIter = aCatLists[0].end();
+ pCurCatListEnd = aCatLists[0].end();
+}
+
+ScFunctionMgr::~ScFunctionMgr()
+{
+}
+
+
+const ScFuncDesc* ScFunctionMgr::Get( sal_uInt16 nFIndex ) const
+{
+ const ScFuncDesc* pDesc;
+ for (pDesc = First(); pDesc; pDesc = Next())
+ if (pDesc->nFIndex == nFIndex)
+ break;
+ return pDesc;
+}
+
+const ScFuncDesc* ScFunctionMgr::First( sal_uInt16 nCategory ) const
+{
+ OSL_ENSURE( nCategory < MAX_FUNCCAT, "Unknown category" );
+ const ScFuncDesc* pDesc = nullptr;
+ if ( nCategory < MAX_FUNCCAT )
+ {
+ pCurCatListIter = aCatLists[nCategory].begin();
+ pCurCatListEnd = aCatLists[nCategory].end();
+ pDesc = *pCurCatListIter;
+ }
+ else
+ {
+ pCurCatListIter = aCatLists[0].end();
+ pCurCatListEnd = aCatLists[0].end();
+ }
+ return pDesc;
+}
+
+const ScFuncDesc* ScFunctionMgr::Next() const
+{
+ const ScFuncDesc* pDesc = nullptr;
+ if ( pCurCatListIter != pCurCatListEnd )
+ {
+ if ( (++pCurCatListIter) != pCurCatListEnd )
+ {
+ pDesc = *pCurCatListIter;
+ }
+ }
+ return pDesc;
+}
+
+sal_uInt32 ScFunctionMgr::getCount() const
+{
+ return MAX_FUNCCAT - 1;
+}
+
+const formula::IFunctionCategory* ScFunctionMgr::getCategory(sal_uInt32 nCategory) const
+{
+ if ( nCategory < (MAX_FUNCCAT-1) )
+ {
+ if (m_aCategories.find(nCategory) == m_aCategories.end())
+ m_aCategories[nCategory] = std::make_shared<ScFunctionCategory>(aCatLists[nCategory+1],nCategory); // aCatLists[0] is "all"
+ return m_aCategories[nCategory].get();
+ }
+ return nullptr;
+}
+
+void ScFunctionMgr::fillLastRecentlyUsedFunctions(::std::vector< const formula::IFunctionDescription*>& _rLastRUFunctions) const
+{
+ const ScAppOptions& rAppOpt = SC_MOD()->GetAppOptions();
+ sal_uInt16 nLRUFuncCount = std::min( rAppOpt.GetLRUFuncListCount(), sal_uInt16(LRU_MAX) );
+ sal_uInt16* pLRUListIds = rAppOpt.GetLRUFuncList();
+ _rLastRUFunctions.clear();
+
+ if ( pLRUListIds )
+ {
+ for (sal_uInt16 i = 0; i < nLRUFuncCount; ++i)
+ {
+ _rLastRUFunctions.push_back( Get( pLRUListIds[i] ) );
+ }
+ }
+}
+
+OUString ScFunctionMgr::GetCategoryName(sal_uInt32 _nCategoryNumber )
+{
+ if (_nCategoryNumber >= SC_FUNCGROUP_COUNT)
+ {
+ OSL_FAIL("Invalid category number!");
+ return OUString();
+ }
+
+ return ScResId(RID_FUNCTION_CATEGORIES[_nCategoryNumber]);
+}
+
+sal_Unicode ScFunctionMgr::getSingleToken(const formula::IFunctionManager::EToken _eToken) const
+{
+ switch(_eToken)
+ {
+ case eOk:
+ return ScCompiler::GetNativeSymbolChar(ocOpen);
+ case eClose:
+ return ScCompiler::GetNativeSymbolChar(ocClose);
+ case eSep:
+ return ScCompiler::GetNativeSymbolChar(ocSep);
+ case eArrayOpen:
+ return ScCompiler::GetNativeSymbolChar(ocArrayOpen);
+ case eArrayClose:
+ return ScCompiler::GetNativeSymbolChar(ocArrayClose);
+ }
+ return 0;
+}
+
+static void ScFuncRes(const ScFuncDescCore &rEntry, ScFuncDesc* pDesc, bool& rbSuppressed)
+{
+ const sal_uInt16 nOpCode = rEntry.nOpCode;
+ sal_uInt16 nFunctionFlags = rEntry.nFunctionFlags;
+ // Bit 1: entirely suppressed
+ // Bit 2: hidden unless used
+ rbSuppressed = ((nFunctionFlags & 1) != 0);
+ pDesc->mbHidden = ((nFunctionFlags & 2) != 0);
+ pDesc->nCategory = rEntry.nCategory;
+ pDesc->sHelpId = rEntry.pHelpId;
+ pDesc->nArgCount = rEntry.nArgs;
+ sal_uInt16 nArgs = pDesc->nArgCount;
+ sal_uInt16 nVarArgsSet = 0;
+ if (nArgs >= PAIRED_VAR_ARGS)
+ {
+ nVarArgsSet = 2;
+ nArgs -= PAIRED_VAR_ARGS - nVarArgsSet;
+ }
+ else if (nArgs >= VAR_ARGS)
+ {
+ nVarArgsSet = 1;
+ nArgs -= VAR_ARGS - nVarArgsSet;
+ }
+ assert(nArgs <= SAL_N_ELEMENTS(rEntry.aOptionalArgs));
+ if (nArgs)
+ {
+ pDesc->nVarArgsStart = nArgs - nVarArgsSet;
+ pDesc->nVarArgsLimit = rEntry.nVarArgsLimit;
+ pDesc->pDefArgFlags = new ScFuncDesc::ParameterFlags[nArgs];
+ for (sal_uInt16 i = 0; i < nArgs; ++i)
+ {
+ pDesc->pDefArgFlags[i].bOptional = static_cast<bool>(rEntry.aOptionalArgs[i]);
+ }
+ }
+
+ pDesc->mxFuncName = ScCompiler::GetNativeSymbol(static_cast<OpCode>(nOpCode));
+ pDesc->mxFuncDesc = ScResId(rEntry.pResource[0]);
+
+ if (!nArgs)
+ return;
+
+ pDesc->maDefArgNames.clear();
+ pDesc->maDefArgNames.resize(nArgs);
+ pDesc->maDefArgDescs.clear();
+ pDesc->maDefArgDescs.resize(nArgs);
+ for (sal_uInt16 i = 0; i < nArgs; ++i)
+ {
+ size_t nIndex = (i * 2) + 1;
+ if (nIndex < rEntry.nResourceLen)
+ pDesc->maDefArgNames[i] = ScResId(rEntry.pResource[nIndex]);
+ if (nIndex + 1 < rEntry.nResourceLen)
+ pDesc->maDefArgDescs[i] = ScResId(rEntry.pResource[nIndex + 1]);
+ // If empty and variable number of arguments and last parameter and
+ // parameter is optional and the previous is not optional, repeat
+ // previous parameter name and description.
+ if ((pDesc->maDefArgNames[i].isEmpty() || pDesc->maDefArgDescs[i].isEmpty()) &&
+ nVarArgsSet > 0 && i > nVarArgsSet && (i == nArgs-1 || i == nArgs-2) &&
+ pDesc->pDefArgFlags[i].bOptional)
+ {
+ sal_uInt16 nPrev = i - nVarArgsSet;
+ if (!pDesc->pDefArgFlags[nPrev].bOptional)
+ {
+ if (pDesc->maDefArgNames[i].isEmpty())
+ pDesc->maDefArgNames[i] = pDesc->maDefArgNames[nPrev];
+ if (pDesc->maDefArgDescs[i].isEmpty())
+ pDesc->maDefArgDescs[i] = pDesc->maDefArgDescs[nPrev];
+ // This also means that variable arguments start one
+ // parameter set earlier.
+ pDesc->nVarArgsStart -= nVarArgsSet;
+ }
+ }
+ }
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sc/source/core/data/global.cxx b/sc/source/core/data/global.cxx
new file mode 100644
index 000000000..a7b63ce1e
--- /dev/null
+++ b/sc/source/core/data/global.cxx
@@ -0,0 +1,1160 @@
+/* -*- 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 <scitems.hxx>
+#include <svx/algitem.hxx>
+#include <editeng/brushitem.hxx>
+#include <editeng/editobj.hxx>
+#include <svl/itempool.hxx>
+#include <svl/srchitem.hxx>
+#include <editeng/langitem.hxx>
+#include <o3tl/string_view.hxx>
+#include <o3tl/unit_conversion.hxx>
+#include <sfx2/docfile.hxx>
+#include <sfx2/dispatch.hxx>
+#include <sfx2/objsh.hxx>
+#include <sfx2/sfxsids.hrc>
+#include <sfx2/viewfrm.hxx>
+#include <sfx2/viewsh.hxx>
+#include <svl/intitem.hxx>
+#include <svl/numformat.hxx>
+#include <svl/stritem.hxx>
+#include <svl/zforlist.hxx>
+#include <svl/zformat.hxx>
+#include <vcl/keycodes.hxx>
+#include <vcl/virdev.hxx>
+#include <vcl/settings.hxx>
+#include <vcl/svapp.hxx>
+#include <unotools/charclass.hxx>
+#include <unotools/securityoptions.hxx>
+#include <osl/diagnose.h>
+
+#include <i18nlangtag/mslangid.hxx>
+#include <comphelper/doublecheckedinit.hxx>
+#include <comphelper/processfactory.hxx>
+#include <comphelper/string.hxx>
+#include <unotools/calendarwrapper.hxx>
+#include <unotools/collatorwrapper.hxx>
+#include <unotools/syslocale.hxx>
+#include <unotools/transliterationwrapper.hxx>
+
+#include <comphelper/lok.hxx>
+
+#include <global.hxx>
+#include <scresid.hxx>
+#include <autoform.hxx>
+#include <patattr.hxx>
+#include <addincol.hxx>
+#include <adiasync.hxx>
+#include <userlist.hxx>
+#include <interpre.hxx>
+#include <unitconv.hxx>
+#include <compiler.hxx>
+#include <parclass.hxx>
+#include <funcdesc.hxx>
+#include <globstr.hrc>
+#include <strings.hrc>
+#include <scmod.hxx>
+#include <editutil.hxx>
+#include <docsh.hxx>
+#include <sharedstringpoolpurge.hxx>
+#include <formulaopt.hxx>
+
+tools::SvRef<ScDocShell> ScGlobal::xDrawClipDocShellRef;
+std::unique_ptr<SvxSearchItem> ScGlobal::xSearchItem;
+std::unique_ptr<ScAutoFormat> ScGlobal::xAutoFormat;
+std::atomic<LegacyFuncCollection*> ScGlobal::pLegacyFuncCollection(nullptr);
+std::atomic<ScUnoAddInCollection*> ScGlobal::pAddInCollection(nullptr);
+std::unique_ptr<ScUserList> ScGlobal::xUserList;
+LanguageType ScGlobal::eLnge = LANGUAGE_SYSTEM;
+std::atomic<css::lang::Locale*> ScGlobal::pLocale(nullptr);
+std::optional<SvtSysLocale> ScGlobal::oSysLocale;
+std::optional<CalendarWrapper> ScGlobal::oCalendar;
+std::atomic<CollatorWrapper*> ScGlobal::pCollator(nullptr);
+std::atomic<CollatorWrapper*> ScGlobal::pCaseCollator(nullptr);
+std::atomic<::utl::TransliterationWrapper*> ScGlobal::pTransliteration(nullptr);
+std::atomic<::utl::TransliterationWrapper*> ScGlobal::pCaseTransliteration(nullptr);
+css::uno::Reference< css::i18n::XOrdinalSuffix> ScGlobal::xOrdinalSuffix;
+OUString ScGlobal::aStrClipDocName;
+
+std::unique_ptr<SvxBrushItem> ScGlobal::xEmptyBrushItem;
+std::unique_ptr<SvxBrushItem> ScGlobal::xButtonBrushItem;
+
+std::unique_ptr<ScFunctionList> ScGlobal::xStarCalcFunctionList;
+std::unique_ptr<ScFunctionMgr> ScGlobal::xStarCalcFunctionMgr;
+
+std::atomic<ScUnitConverter*> ScGlobal::pUnitConverter(nullptr);
+std::unique_ptr<SvNumberFormatter> ScGlobal::xEnglishFormatter;
+std::unique_ptr<ScFieldEditEngine> ScGlobal::xFieldEditEngine;
+std::atomic<sc::SharedStringPoolPurge*> ScGlobal::pSharedStringPoolPurge;
+
+double ScGlobal::nScreenPPTX = 96.0;
+double ScGlobal::nScreenPPTY = 96.0;
+
+sal_uInt16 ScGlobal::nDefFontHeight = 225;
+sal_uInt16 ScGlobal::nStdRowHeight = 256;
+
+tools::Long ScGlobal::nLastRowHeightExtra = 0;
+tools::Long ScGlobal::nLastColWidthExtra = STD_EXTRA_WIDTH;
+
+SfxViewShell* pScActiveViewShell = nullptr; //FIXME: Make this a member
+sal_uInt16 nScClickMouseModifier = 0; //FIXME: This too
+sal_uInt16 nScFillModeMouseModifier = 0; //FIXME: And this
+
+bool ScGlobal::bThreadedGroupCalcInProgress = false;
+
+InputHandlerFunctionNames ScGlobal::maInputHandlerFunctionNames;
+
+
+// Static functions
+
+bool ScGlobal::HasAttrChanged( const SfxItemSet& rNewAttrs,
+ const SfxItemSet& rOldAttrs,
+ const sal_uInt16 nWhich )
+{
+ bool bInvalidate = false;
+ const SfxPoolItem* pNewItem = nullptr;
+ const SfxItemState eNewState = rNewAttrs.GetItemState( nWhich, true, &pNewItem );
+ const SfxPoolItem* pOldItem = nullptr;
+ const SfxItemState eOldState = rOldAttrs.GetItemState( nWhich, true, &pOldItem );
+
+ if ( eNewState == eOldState )
+ {
+ // Both Items set
+ // PoolItems, meaning comparing pointers is valid
+ if ( SfxItemState::SET == eOldState )
+ bInvalidate = (pNewItem != pOldItem);
+ }
+ else
+ {
+ // Contains a Default Item
+ // PoolItems, meaning Item comparison necessary
+ if (!pOldItem)
+ pOldItem = &rOldAttrs.GetPool()->GetDefaultItem( nWhich );
+
+ if (!pNewItem)
+ pNewItem = &rNewAttrs.GetPool()->GetDefaultItem( nWhich );
+
+ bInvalidate = (*pNewItem != *pOldItem);
+ }
+
+ return bInvalidate;
+}
+
+sal_uInt32 ScGlobal::GetStandardFormat( SvNumberFormatter& rFormatter,
+ sal_uInt32 nFormat, SvNumFormatType nType )
+{
+ const SvNumberformat* pFormat = rFormatter.GetEntry( nFormat );
+ if ( pFormat )
+ return rFormatter.GetStandardFormat( nFormat, nType, pFormat->GetLanguage() );
+ return rFormatter.GetStandardFormat( nType, eLnge );
+}
+
+sal_uInt16 ScGlobal::GetStandardRowHeight()
+{
+ return nStdRowHeight;
+}
+
+SvNumberFormatter* ScGlobal::GetEnglishFormatter()
+{
+ assert(!bThreadedGroupCalcInProgress);
+ if ( !xEnglishFormatter )
+ {
+ xEnglishFormatter.reset( new SvNumberFormatter(
+ ::comphelper::getProcessComponentContext(), LANGUAGE_ENGLISH_US ) );
+ xEnglishFormatter->SetEvalDateFormat( NF_EVALDATEFORMAT_INTL_FORMAT );
+ }
+ return xEnglishFormatter.get();
+}
+
+bool ScGlobal::CheckWidthInvalidate( bool& bNumFormatChanged,
+ const SfxItemSet& rNewAttrs,
+ const SfxItemSet& rOldAttrs )
+{
+ std::optional<bool> equal = ScPatternAttr::FastEqualPatternSets( rNewAttrs, rOldAttrs );
+ if( equal.has_value() && equal )
+ {
+ bNumFormatChanged = false;
+ return false;
+ }
+ // Check whether attribute changes in rNewAttrs compared to rOldAttrs render
+ // the text width at a cell invalid
+ bNumFormatChanged =
+ HasAttrChanged( rNewAttrs, rOldAttrs, ATTR_VALUE_FORMAT );
+ return ( bNumFormatChanged
+ || HasAttrChanged( rNewAttrs, rOldAttrs, ATTR_LANGUAGE_FORMAT )
+ || HasAttrChanged( rNewAttrs, rOldAttrs, ATTR_FONT )
+ || HasAttrChanged( rNewAttrs, rOldAttrs, ATTR_CJK_FONT )
+ || HasAttrChanged( rNewAttrs, rOldAttrs, ATTR_CTL_FONT )
+ || HasAttrChanged( rNewAttrs, rOldAttrs, ATTR_FONT_HEIGHT )
+ || HasAttrChanged( rNewAttrs, rOldAttrs, ATTR_CJK_FONT_HEIGHT )
+ || HasAttrChanged( rNewAttrs, rOldAttrs, ATTR_CTL_FONT_HEIGHT )
+ || HasAttrChanged( rNewAttrs, rOldAttrs, ATTR_FONT_WEIGHT )
+ || HasAttrChanged( rNewAttrs, rOldAttrs, ATTR_CJK_FONT_WEIGHT )
+ || HasAttrChanged( rNewAttrs, rOldAttrs, ATTR_CTL_FONT_WEIGHT )
+ || HasAttrChanged( rNewAttrs, rOldAttrs, ATTR_FONT_POSTURE )
+ || HasAttrChanged( rNewAttrs, rOldAttrs, ATTR_CJK_FONT_POSTURE )
+ || HasAttrChanged( rNewAttrs, rOldAttrs, ATTR_CTL_FONT_POSTURE )
+ || HasAttrChanged( rNewAttrs, rOldAttrs, ATTR_FONT_UNDERLINE )
+ || HasAttrChanged( rNewAttrs, rOldAttrs, ATTR_FONT_OVERLINE )
+ || HasAttrChanged( rNewAttrs, rOldAttrs, ATTR_FONT_CROSSEDOUT )
+ || HasAttrChanged( rNewAttrs, rOldAttrs, ATTR_FONT_CONTOUR )
+ || HasAttrChanged( rNewAttrs, rOldAttrs, ATTR_FONT_SHADOWED )
+ || HasAttrChanged( rNewAttrs, rOldAttrs, ATTR_STACKED )
+ || HasAttrChanged( rNewAttrs, rOldAttrs, ATTR_ROTATE_VALUE )
+ || HasAttrChanged( rNewAttrs, rOldAttrs, ATTR_ROTATE_MODE )
+ || HasAttrChanged( rNewAttrs, rOldAttrs, ATTR_LINEBREAK )
+ || HasAttrChanged( rNewAttrs, rOldAttrs, ATTR_MARGIN )
+ );
+}
+
+const SvxSearchItem& ScGlobal::GetSearchItem()
+{
+ assert(!bThreadedGroupCalcInProgress);
+ if (!xSearchItem)
+ {
+ xSearchItem.reset(new SvxSearchItem( SID_SEARCH_ITEM ));
+ xSearchItem->SetAppFlag( SvxSearchApp::CALC );
+ }
+ return *xSearchItem;
+}
+
+void ScGlobal::SetSearchItem( const SvxSearchItem& rNew )
+{
+ assert(!bThreadedGroupCalcInProgress);
+ // FIXME: An assignment operator would be nice here
+ xSearchItem.reset(rNew.Clone());
+
+ xSearchItem->SetWhich( SID_SEARCH_ITEM );
+ xSearchItem->SetAppFlag( SvxSearchApp::CALC );
+}
+
+void ScGlobal::ClearAutoFormat()
+{
+ assert(!bThreadedGroupCalcInProgress);
+ if (xAutoFormat)
+ {
+ // When modified via StarOne then only the SaveLater flag is set and no saving is done.
+ // If the flag is set then save now.
+ if (xAutoFormat->IsSaveLater())
+ xAutoFormat->Save();
+ xAutoFormat.reset();
+ }
+}
+
+ScAutoFormat* ScGlobal::GetAutoFormat()
+{
+ return xAutoFormat.get();
+}
+
+ScAutoFormat* ScGlobal::GetOrCreateAutoFormat()
+{
+ assert(!bThreadedGroupCalcInProgress);
+ if ( !xAutoFormat )
+ {
+ xAutoFormat.reset(new ScAutoFormat);
+ xAutoFormat->Load();
+ }
+
+ return xAutoFormat.get();
+}
+
+LegacyFuncCollection* ScGlobal::GetLegacyFuncCollection()
+{
+ return comphelper::doubleCheckedInit( pLegacyFuncCollection, []() { return new LegacyFuncCollection(); });
+}
+
+ScUnoAddInCollection* ScGlobal::GetAddInCollection()
+{
+ return comphelper::doubleCheckedInit( pAddInCollection, []() { return new ScUnoAddInCollection(); });
+}
+
+ScUserList* ScGlobal::GetUserList()
+{
+ assert(!bThreadedGroupCalcInProgress);
+ // Hack: Load Cfg item at the App
+ global_InitAppOptions();
+
+ if (!xUserList)
+ xUserList.reset(new ScUserList());
+ return xUserList.get();
+}
+
+void ScGlobal::SetUserList( const ScUserList* pNewList )
+{
+ assert(!bThreadedGroupCalcInProgress);
+ if ( pNewList )
+ {
+ if ( !xUserList )
+ xUserList.reset( new ScUserList( *pNewList ) );
+ else
+ *xUserList = *pNewList;
+ }
+ else
+ {
+ xUserList.reset();
+ }
+}
+
+OUString ScGlobal::GetErrorString(FormulaError nErr)
+{
+ TranslateId pErrNumber;
+ switch (nErr)
+ {
+ case FormulaError::NoRef:
+ pErrNumber = STR_NO_REF_TABLE;
+ break;
+ case FormulaError::NoAddin:
+ pErrNumber = STR_NO_ADDIN;
+ break;
+ case FormulaError::NoMacro:
+ pErrNumber = STR_NO_MACRO;
+ break;
+ case FormulaError::NotAvailable:
+ return ScCompiler::GetNativeSymbol(ocErrNA);
+ case FormulaError::NoName:
+ return ScCompiler::GetNativeSymbol(ocErrName);
+ case FormulaError::NoValue:
+ return ScCompiler::GetNativeSymbol(ocErrValue);
+ case FormulaError::NoCode:
+ return ScCompiler::GetNativeSymbol(ocErrNull);
+ case FormulaError::DivisionByZero:
+ return ScCompiler::GetNativeSymbol(ocErrDivZero);
+ case FormulaError::IllegalFPOperation:
+ return ScCompiler::GetNativeSymbol(ocErrNum);
+ default:
+ return ScResId(STR_ERROR_STR) + OUString::number( static_cast<int>(nErr) );
+ }
+ return ScResId(pErrNumber);
+}
+
+OUString ScGlobal::GetLongErrorString(FormulaError nErr)
+{
+ TranslateId pErrNumber;
+ switch (nErr)
+ {
+ case FormulaError::NONE:
+ return OUString();
+ case FormulaError::IllegalArgument:
+ pErrNumber = STR_LONG_ERR_ILL_ARG;
+ break;
+ case FormulaError::IllegalFPOperation:
+ pErrNumber = STR_LONG_ERR_ILL_FPO;
+ break;
+ case FormulaError::IllegalChar:
+ pErrNumber = STR_LONG_ERR_ILL_CHAR;
+ break;
+ case FormulaError::IllegalParameter:
+ pErrNumber = STR_LONG_ERR_ILL_PAR;
+ break;
+ case FormulaError::Pair:
+ case FormulaError::PairExpected:
+ pErrNumber = STR_LONG_ERR_PAIR;
+ break;
+ case FormulaError::OperatorExpected:
+ pErrNumber = STR_LONG_ERR_OP_EXP;
+ break;
+ case FormulaError::VariableExpected:
+ case FormulaError::ParameterExpected:
+ pErrNumber = STR_LONG_ERR_VAR_EXP;
+ break;
+ case FormulaError::CodeOverflow:
+ pErrNumber = STR_LONG_ERR_CODE_OVF;
+ break;
+ case FormulaError::StringOverflow:
+ pErrNumber = STR_LONG_ERR_STR_OVF;
+ break;
+ case FormulaError::StackOverflow:
+ pErrNumber = STR_LONG_ERR_STACK_OVF;
+ break;
+ case FormulaError::MatrixSize:
+ pErrNumber = STR_LONG_ERR_MATRIX_SIZE;
+ break;
+ case FormulaError::UnknownState:
+ case FormulaError::UnknownVariable:
+ case FormulaError::UnknownOpCode:
+ case FormulaError::UnknownStackVariable:
+ case FormulaError::UnknownToken:
+ case FormulaError::NoCode:
+ pErrNumber = STR_LONG_ERR_SYNTAX;
+ break;
+ case FormulaError::CircularReference:
+ pErrNumber = STR_LONG_ERR_CIRC_REF;
+ break;
+ case FormulaError::NoConvergence:
+ pErrNumber = STR_LONG_ERR_NO_CONV;
+ break;
+ case FormulaError::NoRef:
+ pErrNumber = STR_LONG_ERR_NO_REF;
+ break;
+ case FormulaError::NoName:
+ pErrNumber = STR_LONG_ERR_NO_NAME;
+ break;
+ case FormulaError::NoAddin:
+ pErrNumber = STR_LONG_ERR_NO_ADDIN;
+ break;
+ case FormulaError::NoMacro:
+ pErrNumber = STR_LONG_ERR_NO_MACRO;
+ break;
+ case FormulaError::DivisionByZero:
+ pErrNumber = STR_LONG_ERR_DIV_ZERO;
+ break;
+ case FormulaError::NestedArray:
+ pErrNumber = STR_ERR_LONG_NESTED_ARRAY;
+ break;
+ case FormulaError::BadArrayContent:
+ pErrNumber = STR_ERR_LONG_BAD_ARRAY_CONTENT;
+ break;
+ case FormulaError::LinkFormulaNeedingCheck:
+ pErrNumber = STR_ERR_LONG_LINK_FORMULA_NEEDING_CHECK;
+ break;
+ case FormulaError::NoValue:
+ pErrNumber = STR_LONG_ERR_NO_VALUE;
+ break;
+ case FormulaError::NotAvailable:
+ pErrNumber = STR_LONG_ERR_NV;
+ break;
+ default:
+ return ScResId(STR_ERROR_STR) + OUString::number( static_cast<int>(nErr) );
+ }
+ return ScResId(pErrNumber);
+}
+
+SvxBrushItem* ScGlobal::GetButtonBrushItem()
+{
+ assert(!bThreadedGroupCalcInProgress);
+ xButtonBrushItem->SetColor( Application::GetSettings().GetStyleSettings().GetFaceColor() );
+ return xButtonBrushItem.get();
+}
+
+void ScGlobal::Init()
+{
+ // The default language for number formats (ScGlobal::eLnge) must
+ // always be LANGUAGE_SYSTEM
+ // FIXME: So remove this variable?
+ eLnge = LANGUAGE_SYSTEM;
+
+ oSysLocale.emplace();
+
+ xEmptyBrushItem = std::make_unique<SvxBrushItem>( COL_TRANSPARENT, ATTR_BACKGROUND );
+ xButtonBrushItem = std::make_unique<SvxBrushItem>( Color(), ATTR_BACKGROUND );
+
+ InitPPT();
+ //ScCompiler::InitSymbolsNative();
+ // ScParameterClassification _after_ Compiler, needs function resources if
+ // arguments are to be merged in, which in turn need strings of function
+ // names from the compiler.
+ ScParameterClassification::Init();
+
+ InitAddIns();
+
+ aStrClipDocName = ScResId( SCSTR_NONAME ) + "1";
+
+ // ScDocumentPool::InitVersionMaps() has been called earlier already
+}
+
+void ScGlobal::InitPPT()
+{
+ OutputDevice* pDev = Application::GetDefaultDevice();
+
+ if (comphelper::LibreOfficeKit::isActive())
+ {
+ // LOK: the below limited precision is not enough for RowColumnHeader.
+ nScreenPPTX = o3tl::convert<double>(pDev->GetDPIX(), o3tl::Length::twip, o3tl::Length::in);
+ nScreenPPTY = o3tl::convert<double>(pDev->GetDPIY(), o3tl::Length::twip, o3tl::Length::in);
+ }
+ else
+ {
+ // Avoid cumulative placement errors by intentionally limiting
+ // precision.
+ Point aPix1000 = pDev->LogicToPixel(Point(1000, 1000), MapMode(MapUnit::MapTwip));
+ nScreenPPTX = aPix1000.X() / 1000.0;
+ nScreenPPTY = aPix1000.Y() / 1000.0;
+ }
+}
+
+const OUString& ScGlobal::GetClipDocName()
+{
+ return aStrClipDocName;
+}
+
+void ScGlobal::SetClipDocName( const OUString& rNew )
+{
+ assert(!bThreadedGroupCalcInProgress);
+ aStrClipDocName = rNew;
+}
+
+void ScGlobal::InitTextHeight(const SfxItemPool* pPool)
+{
+ if (!pPool)
+ {
+ OSL_FAIL("ScGlobal::InitTextHeight: No Pool");
+ return;
+ }
+
+ const ScPatternAttr& rPattern = pPool->GetDefaultItem(ATTR_PATTERN);
+
+ OutputDevice* pDefaultDev = Application::GetDefaultDevice();
+ ScopedVclPtrInstance< VirtualDevice > pVirtWindow( *pDefaultDev );
+ pVirtWindow->SetMapMode(MapMode(MapUnit::MapPixel));
+ vcl::Font aDefFont;
+ rPattern.GetFont(aDefFont, SC_AUTOCOL_BLACK, pVirtWindow); // Font color doesn't matter here
+ pVirtWindow->SetFont(aDefFont);
+ sal_uInt16 nTest = static_cast<sal_uInt16>(
+ pVirtWindow->PixelToLogic(Size(0, pVirtWindow->GetTextHeight()), MapMode(MapUnit::MapTwip)).Height());
+
+ if (nTest > nDefFontHeight)
+ nDefFontHeight = nTest;
+
+ const SvxMarginItem& rMargin = rPattern.GetItem(ATTR_MARGIN);
+
+ nTest = static_cast<sal_uInt16>(nDefFontHeight + rMargin.GetTopMargin()
+ + rMargin.GetBottomMargin() - STD_ROWHEIGHT_DIFF);
+
+ if (nTest > nStdRowHeight)
+ nStdRowHeight = nTest;
+}
+
+void ScGlobal::Clear()
+{
+ // Destroy asyncs _before_ ExitExternalFunc!
+ theAddInAsyncTbl.clear();
+ ExitExternalFunc();
+ ClearAutoFormat();
+ xSearchItem.reset();
+ delete pLegacyFuncCollection.exchange(nullptr);
+ delete pAddInCollection.exchange(nullptr);
+ xUserList.reset();
+ xStarCalcFunctionList.reset(); // Destroy before ResMgr!
+ xStarCalcFunctionMgr.reset();
+ ScParameterClassification::Exit();
+ ScCompiler::DeInit();
+ ScInterpreter::GlobalExit(); // Delete static Stack
+
+ xEmptyBrushItem.reset();
+ xButtonBrushItem.reset();
+ xEnglishFormatter.reset();
+ delete pCaseTransliteration.exchange(nullptr);
+ delete pTransliteration.exchange(nullptr);
+ delete pCaseCollator.exchange(nullptr);
+ delete pCollator.exchange(nullptr);
+ oCalendar.reset();
+ oSysLocale.reset();
+ delete pLocale.exchange(nullptr);
+
+ delete pUnitConverter.exchange(nullptr);
+ xFieldEditEngine.reset();
+ delete pSharedStringPoolPurge.exchange(nullptr);
+
+ xDrawClipDocShellRef.clear();
+}
+
+rtl_TextEncoding ScGlobal::GetCharsetValue( std::u16string_view rCharSet )
+{
+ // new TextEncoding values
+ if ( CharClass::isAsciiNumeric( rCharSet ) )
+ {
+ sal_Int32 nVal = o3tl::toInt32(rCharSet);
+ if ( nVal == RTL_TEXTENCODING_DONTKNOW )
+ return osl_getThreadTextEncoding();
+ return static_cast<rtl_TextEncoding>(nVal);
+ }
+ // old CharSet values for compatibility
+ else if (o3tl::equalsIgnoreAsciiCase(rCharSet, u"ANSI") ) return RTL_TEXTENCODING_MS_1252;
+ else if (o3tl::equalsIgnoreAsciiCase(rCharSet, u"MAC") ) return RTL_TEXTENCODING_APPLE_ROMAN;
+ else if (o3tl::equalsIgnoreAsciiCase(rCharSet, u"IBMPC") ) return RTL_TEXTENCODING_IBM_850;
+ else if (o3tl::equalsIgnoreAsciiCase(rCharSet, u"IBMPC_437")) return RTL_TEXTENCODING_IBM_437;
+ else if (o3tl::equalsIgnoreAsciiCase(rCharSet, u"IBMPC_850")) return RTL_TEXTENCODING_IBM_850;
+ else if (o3tl::equalsIgnoreAsciiCase(rCharSet, u"IBMPC_860")) return RTL_TEXTENCODING_IBM_860;
+ else if (o3tl::equalsIgnoreAsciiCase(rCharSet, u"IBMPC_861")) return RTL_TEXTENCODING_IBM_861;
+ else if (o3tl::equalsIgnoreAsciiCase(rCharSet, u"IBMPC_863")) return RTL_TEXTENCODING_IBM_863;
+ else if (o3tl::equalsIgnoreAsciiCase(rCharSet, u"IBMPC_865")) return RTL_TEXTENCODING_IBM_865;
+ // Some wrong "help" on the net mentions UTF8 and even unoconv uses it,
+ // which worked accidentally if the system encoding is UTF-8 anyway, so
+ // support it ;) but only when reading.
+ else if (o3tl::equalsIgnoreAsciiCase(rCharSet, u"UTF8")) return RTL_TEXTENCODING_UTF8;
+ else if (o3tl::equalsIgnoreAsciiCase(rCharSet, u"UTF-8")) return RTL_TEXTENCODING_UTF8;
+ else return osl_getThreadTextEncoding();
+}
+
+OUString ScGlobal::GetCharsetString( rtl_TextEncoding eVal )
+{
+ const char* pChar;
+ switch ( eVal )
+ {
+ // old CharSet strings for compatibility
+ case RTL_TEXTENCODING_MS_1252: pChar = "ANSI"; break;
+ case RTL_TEXTENCODING_APPLE_ROMAN: pChar = "MAC"; break;
+ // IBMPC == IBMPC_850
+ case RTL_TEXTENCODING_IBM_437: pChar = "IBMPC_437"; break;
+ case RTL_TEXTENCODING_IBM_850: pChar = "IBMPC_850"; break;
+ case RTL_TEXTENCODING_IBM_860: pChar = "IBMPC_860"; break;
+ case RTL_TEXTENCODING_IBM_861: pChar = "IBMPC_861"; break;
+ case RTL_TEXTENCODING_IBM_863: pChar = "IBMPC_863"; break;
+ case RTL_TEXTENCODING_IBM_865: pChar = "IBMPC_865"; break;
+ case RTL_TEXTENCODING_DONTKNOW: pChar = "SYSTEM"; break;
+ // new string of TextEncoding value
+ default:
+ return OUString::number( eVal );
+ }
+ return OUString::createFromAscii(pChar);
+}
+
+bool ScGlobal::HasStarCalcFunctionList()
+{
+ return bool(xStarCalcFunctionList);
+}
+
+ScFunctionList* ScGlobal::GetStarCalcFunctionList()
+{
+ assert(!bThreadedGroupCalcInProgress);
+ if ( !xStarCalcFunctionList )
+ xStarCalcFunctionList.reset( new ScFunctionList( SC_MOD()->GetFormulaOptions().GetUseEnglishFuncName()));
+
+ return xStarCalcFunctionList.get();
+}
+
+ScFunctionMgr* ScGlobal::GetStarCalcFunctionMgr()
+{
+ assert(!bThreadedGroupCalcInProgress);
+ if ( !xStarCalcFunctionMgr )
+ xStarCalcFunctionMgr.reset(new ScFunctionMgr);
+
+ return xStarCalcFunctionMgr.get();
+}
+
+void ScGlobal::ResetFunctionList()
+{
+ // FunctionMgr has pointers into FunctionList, must also be updated
+ xStarCalcFunctionMgr.reset();
+ xStarCalcFunctionList.reset();
+ // Building new names also needs InputHandler data to be refreshed.
+ maInputHandlerFunctionNames = InputHandlerFunctionNames();
+}
+
+const InputHandlerFunctionNames& ScGlobal::GetInputHandlerFunctionNames()
+{
+ if (maInputHandlerFunctionNames.maFunctionData.empty())
+ {
+ const OUString aParenthesesReplacement( cParenthesesReplacement);
+ const ScFunctionList* pFuncList = GetStarCalcFunctionList();
+ const sal_uInt32 nListCount = pFuncList->GetCount();
+ const CharClass* pCharClass = (pFuncList->IsEnglishFunctionNames()
+ ? ScCompiler::GetCharClassEnglish()
+ : ScCompiler::GetCharClassLocalized());
+ for (sal_uInt32 i=0; i < nListCount; ++i)
+ {
+ const ScFuncDesc* pDesc = pFuncList->GetFunction( i );
+ if ( pDesc->mxFuncName )
+ {
+ OUString aFuncName(pCharClass->uppercase(*(pDesc->mxFuncName)));
+ // fdo#75264 fill maFormulaChar with all characters used in formula names
+ for (sal_Int32 j = 0; j < aFuncName.getLength(); j++)
+ maInputHandlerFunctionNames.maFunctionChar.insert(aFuncName[j]);
+ maInputHandlerFunctionNames.maFunctionData.insert(
+ ScTypedStrData(*(pDesc->mxFuncName) + aParenthesesReplacement, 0.0, 0.0,
+ ScTypedStrData::Standard));
+ pDesc->initArgumentInfo();
+ OUString aEntry = pDesc->getSignature();
+ maInputHandlerFunctionNames.maFunctionDataPara.insert(
+ ScTypedStrData(aEntry, 0.0, 0.0, ScTypedStrData::Standard));
+ }
+ }
+ }
+ return maInputHandlerFunctionNames;
+}
+
+ScUnitConverter* ScGlobal::GetUnitConverter()
+{
+ return comphelper::doubleCheckedInit( pUnitConverter,
+ []() { return new ScUnitConverter; });
+}
+
+const sal_Unicode* ScGlobal::UnicodeStrChr( const sal_Unicode* pStr,
+ sal_Unicode c )
+{
+ if ( !pStr )
+ return nullptr;
+ while ( *pStr )
+ {
+ if ( *pStr == c )
+ return pStr;
+ pStr++;
+ }
+ return nullptr;
+}
+
+OUString ScGlobal::addToken(std::u16string_view rTokenList, std::u16string_view rToken,
+ sal_Unicode cSep, sal_Int32 nSepCount, bool bForceSep)
+{
+ OUStringBuffer aBuf(rTokenList);
+ if( bForceSep || (!rToken.empty() && !rTokenList.empty()) )
+ comphelper::string::padToLength(aBuf, aBuf.getLength() + nSepCount, cSep);
+ aBuf.append(rToken);
+ return aBuf.makeStringAndClear();
+}
+
+bool ScGlobal::IsQuoted( const OUString& rString, sal_Unicode cQuote )
+{
+ return (rString.getLength() >= 2) && (rString[0] == cQuote) && (rString[ rString.getLength() - 1 ] == cQuote);
+}
+
+void ScGlobal::AddQuotes( OUString& rString, sal_Unicode cQuote, bool bEscapeEmbedded )
+{
+ if (bEscapeEmbedded)
+ {
+ sal_Unicode pQ[3];
+ pQ[0] = pQ[1] = cQuote;
+ pQ[2] = 0;
+ OUString aQuotes( pQ );
+ rString = rString.replaceAll( OUStringChar(cQuote), aQuotes);
+ }
+ rString = OUStringChar( cQuote ) + rString + OUStringChar( cQuote );
+}
+
+void ScGlobal::EraseQuotes( OUString& rString, sal_Unicode cQuote, bool bUnescapeEmbedded )
+{
+ if ( IsQuoted( rString, cQuote ) )
+ {
+ rString = rString.copy( 1, rString.getLength() - 2 );
+ if (bUnescapeEmbedded)
+ {
+ sal_Unicode pQ[3];
+ pQ[0] = pQ[1] = cQuote;
+ pQ[2] = 0;
+ OUString aQuotes( pQ );
+ rString = rString.replaceAll( aQuotes, OUStringChar(cQuote));
+ }
+ }
+}
+
+sal_Int32 ScGlobal::FindUnquoted( const OUString& rString, sal_Unicode cChar, sal_Int32 nStart )
+{
+ assert(nStart >= 0);
+ const sal_Unicode cQuote = '\'';
+ const sal_Unicode* const pStart = rString.getStr();
+ const sal_Unicode* const pStop = pStart + rString.getLength();
+ const sal_Unicode* p = pStart + nStart;
+ bool bQuoted = false;
+ while (p < pStop)
+ {
+ if (*p == cChar && !bQuoted)
+ return sal::static_int_cast< sal_Int32 >( p - pStart );
+ else if (*p == cQuote)
+ {
+ if (!bQuoted)
+ bQuoted = true;
+ else if (p < pStop-1 && *(p+1) == cQuote)
+ ++p;
+ else
+ bQuoted = false;
+ }
+ ++p;
+ }
+ return -1;
+}
+
+const sal_Unicode* ScGlobal::FindUnquoted( const sal_Unicode* pString, sal_Unicode cChar )
+{
+ sal_Unicode cQuote = '\'';
+ const sal_Unicode* p = pString;
+ bool bQuoted = false;
+ while (*p)
+ {
+ if (*p == cChar && !bQuoted)
+ return p;
+ else if (*p == cQuote)
+ {
+ if (!bQuoted)
+ bQuoted = true;
+ else if (*(p+1) == cQuote)
+ ++p;
+ else
+ bQuoted = false;
+ }
+ ++p;
+ }
+ return nullptr;
+}
+
+bool ScGlobal::EETextObjEqual( const EditTextObject* pObj1,
+ const EditTextObject* pObj2 )
+{
+ if ( pObj1 == pObj2 ) // Both empty or the same object
+ return true;
+
+ if ( pObj1 && pObj2 )
+ return pObj1->Equals( *pObj2);
+
+ return false;
+}
+
+void ScGlobal::OpenURL(const OUString& rURL, const OUString& rTarget, bool bIgnoreSettings)
+{
+ // OpenURL is always called in the GridWindow by mouse clicks in some way or another.
+ // That's why pScActiveViewShell and nScClickMouseModifier are correct.
+
+ // Fragments pointing into the current document should be always opened.
+ if (!bIgnoreSettings && !(ShouldOpenURL() || rURL.startsWith("#")))
+ return;
+
+ SfxViewFrame* pViewFrm = SfxViewFrame::Current();
+ if (!pViewFrm)
+ return;
+
+ OUString aUrlName( rURL );
+ SfxViewFrame* pFrame = nullptr;
+ const SfxObjectShell* pObjShell = nullptr;
+ OUString aReferName;
+ if ( pScActiveViewShell )
+ {
+ pFrame = pScActiveViewShell->GetViewFrame();
+ pObjShell = pFrame->GetObjectShell();
+ const SfxMedium* pMed = pObjShell->GetMedium();
+ if (pMed)
+ aReferName = pMed->GetName();
+ }
+
+ // Don't fiddle with fragments pointing into current document.
+ // Also don't mess around with a vnd.sun.star.script or service or other
+ // internal "URI".
+ if (!aUrlName.startsWith("#")
+ && !aUrlName.startsWithIgnoreAsciiCase("vnd.sun.star.script:")
+ && !aUrlName.startsWithIgnoreAsciiCase("macro:")
+ && !aUrlName.startsWithIgnoreAsciiCase("slot:")
+ && !aUrlName.startsWithIgnoreAsciiCase("service:")
+ && !aUrlName.startsWithIgnoreAsciiCase(".uno:"))
+ {
+ // Any relative reference would fail with "not an absolute URL"
+ // error, try to construct an absolute URI with the path relative
+ // to the current document's path or work path, as usual for all
+ // external references.
+ // This then also, as ScGlobal::GetAbsDocName() uses
+ // INetURLObject::smartRel2Abs(), supports "\\" UNC path names as
+ // smb:// Samba shares and DOS path separators converted to proper
+ // file:// URI.
+ const OUString aNewUrlName( ScGlobal::GetAbsDocName( aUrlName, pObjShell));
+ if (!aNewUrlName.isEmpty())
+ aUrlName = aNewUrlName;
+ }
+
+ SfxStringItem aUrl( SID_FILE_NAME, aUrlName );
+ SfxStringItem aTarget( SID_TARGETNAME, rTarget );
+ if ( nScClickMouseModifier & KEY_SHIFT ) // control-click -> into new window
+ aTarget.SetValue("_blank");
+
+ SfxFrameItem aFrm( SID_DOCFRAME, pFrame );
+ SfxStringItem aReferer( SID_REFERER, aReferName );
+
+ SfxBoolItem aNewView( SID_OPEN_NEW_VIEW, false );
+ SfxBoolItem aBrowsing( SID_BROWSE, true );
+
+ // No SID_SILENT anymore
+ pViewFrm->GetDispatcher()->ExecuteList(SID_OPENDOC,
+ SfxCallMode::ASYNCHRON | SfxCallMode::RECORD,
+ { &aUrl, &aTarget, &aFrm, &aReferer, &aNewView, &aBrowsing });
+}
+
+bool ScGlobal::ShouldOpenURL()
+{
+ bool bCtrlClickHappened = (nScClickMouseModifier & KEY_MOD1);
+ bool bCtrlClickSecOption = SvtSecurityOptions::IsOptionSet( SvtSecurityOptions::EOption::CtrlClickHyperlink );
+ if( bCtrlClickHappened && ! bCtrlClickSecOption )
+ {
+ // return since ctrl+click happened when the
+ // ctrl+click security option was disabled, link should not open
+ return false;
+ }
+ else if( ! bCtrlClickHappened && bCtrlClickSecOption )
+ {
+ // ctrl+click did not happen; only click happened maybe with some
+ // other key combo. and security option is set, so return
+ return false;
+ }
+ return true;
+}
+
+bool ScGlobal::IsSystemRTL()
+{
+ return MsLangId::isRightToLeft( Application::GetSettings().GetLanguageTag().getLanguageType() );
+}
+
+SvtScriptType ScGlobal::GetDefaultScriptType()
+{
+ // Used when text contains only WEAK characters.
+ // Script type of office language is used then (same as GetEditDefaultLanguage,
+ // to get consistent behavior of text in simple cells and EditEngine,
+ // also same as GetAppLanguage() in Writer)
+ return SvtLanguageOptions::GetScriptTypeOfLanguage( Application::GetSettings().GetLanguageTag().getLanguageType() );
+}
+
+LanguageType ScGlobal::GetEditDefaultLanguage()
+{
+ // Used for EditEngine::SetDefaultLanguage
+ return Application::GetSettings().GetLanguageTag().getLanguageType();
+}
+
+sal_uInt16 ScGlobal::GetScriptedWhichID( SvtScriptType nScriptType, sal_uInt16 nWhich )
+{
+ switch ( nScriptType )
+ {
+ case SvtScriptType::LATIN:
+ case SvtScriptType::ASIAN:
+ case SvtScriptType::COMPLEX:
+ break; // take exact matches
+ default: // prefer one, first COMPLEX, then ASIAN
+ if ( nScriptType & SvtScriptType::COMPLEX )
+ nScriptType = SvtScriptType::COMPLEX;
+ else if ( nScriptType & SvtScriptType::ASIAN )
+ nScriptType = SvtScriptType::ASIAN;
+ }
+ switch ( nScriptType )
+ {
+ case SvtScriptType::COMPLEX:
+ {
+ switch ( nWhich )
+ {
+ case ATTR_FONT:
+ case ATTR_CJK_FONT:
+ nWhich = ATTR_CTL_FONT;
+ break;
+ case ATTR_FONT_HEIGHT:
+ case ATTR_CJK_FONT_HEIGHT:
+ nWhich = ATTR_CTL_FONT_HEIGHT;
+ break;
+ case ATTR_FONT_WEIGHT:
+ case ATTR_CJK_FONT_WEIGHT:
+ nWhich = ATTR_CTL_FONT_WEIGHT;
+ break;
+ case ATTR_FONT_POSTURE:
+ case ATTR_CJK_FONT_POSTURE:
+ nWhich = ATTR_CTL_FONT_POSTURE;
+ break;
+ }
+ }
+ break;
+ case SvtScriptType::ASIAN:
+ {
+ switch ( nWhich )
+ {
+ case ATTR_FONT:
+ case ATTR_CTL_FONT:
+ nWhich = ATTR_CJK_FONT;
+ break;
+ case ATTR_FONT_HEIGHT:
+ case ATTR_CTL_FONT_HEIGHT:
+ nWhich = ATTR_CJK_FONT_HEIGHT;
+ break;
+ case ATTR_FONT_WEIGHT:
+ case ATTR_CTL_FONT_WEIGHT:
+ nWhich = ATTR_CJK_FONT_WEIGHT;
+ break;
+ case ATTR_FONT_POSTURE:
+ case ATTR_CTL_FONT_POSTURE:
+ nWhich = ATTR_CJK_FONT_POSTURE;
+ break;
+ }
+ }
+ break;
+ default:
+ {
+ switch ( nWhich )
+ {
+ case ATTR_CTL_FONT:
+ case ATTR_CJK_FONT:
+ nWhich = ATTR_FONT;
+ break;
+ case ATTR_CTL_FONT_HEIGHT:
+ case ATTR_CJK_FONT_HEIGHT:
+ nWhich = ATTR_FONT_HEIGHT;
+ break;
+ case ATTR_CTL_FONT_WEIGHT:
+ case ATTR_CJK_FONT_WEIGHT:
+ nWhich = ATTR_FONT_WEIGHT;
+ break;
+ case ATTR_CTL_FONT_POSTURE:
+ case ATTR_CJK_FONT_POSTURE:
+ nWhich = ATTR_FONT_POSTURE;
+ break;
+ }
+ }
+ }
+ return nWhich;
+}
+
+void ScGlobal::AddLanguage( SfxItemSet& rSet, const SvNumberFormatter& rFormatter )
+{
+ OSL_ENSURE( rSet.GetItemState( ATTR_LANGUAGE_FORMAT, false ) == SfxItemState::DEFAULT,
+ "ScGlobal::AddLanguage - language already added");
+
+ const SfxUInt32Item* pHardItem = rSet.GetItemIfSet( ATTR_VALUE_FORMAT, false );
+ if ( !pHardItem )
+ return;
+
+ const SvNumberformat* pHardFormat = rFormatter.GetEntry(
+ pHardItem->GetValue() );
+
+ sal_uInt32 nParentFmt = 0; // Pool default
+ const SfxItemSet* pParent = rSet.GetParent();
+ if ( pParent )
+ nParentFmt = pParent->Get( ATTR_VALUE_FORMAT ).GetValue();
+ const SvNumberformat* pParFormat = rFormatter.GetEntry( nParentFmt );
+
+ if ( pHardFormat && pParFormat &&
+ (pHardFormat->GetLanguage() != pParFormat->GetLanguage()) )
+ rSet.Put( SvxLanguageItem( pHardFormat->GetLanguage(), ATTR_LANGUAGE_FORMAT ) );
+}
+
+utl::TransliterationWrapper& ScGlobal::GetTransliteration()
+{
+ return *comphelper::doubleCheckedInit( pTransliteration,
+ []()
+ {
+ const LanguageType eOfficeLanguage = Application::GetSettings().GetLanguageTag().getLanguageType();
+ ::utl::TransliterationWrapper* p = new ::utl::TransliterationWrapper(
+ ::comphelper::getProcessComponentContext(), TransliterationFlags::IGNORE_CASE );
+ p->loadModuleIfNeeded( eOfficeLanguage );
+ return p;
+ });
+}
+::utl::TransliterationWrapper& ScGlobal::GetCaseTransliteration()
+{
+ return *comphelper::doubleCheckedInit( pCaseTransliteration,
+ []()
+ {
+ const LanguageType eOfficeLanguage = Application::GetSettings().GetLanguageTag().getLanguageType();
+ ::utl::TransliterationWrapper* p = new ::utl::TransliterationWrapper(
+ ::comphelper::getProcessComponentContext(), TransliterationFlags::NONE );
+ p->loadModuleIfNeeded( eOfficeLanguage );
+ return p;
+ });
+}
+utl::TransliterationWrapper& ScGlobal::GetTransliteration(bool bCaseSensitive)
+{
+ return bCaseSensitive ? GetCaseTransliteration() : GetTransliteration();
+}
+
+const LocaleDataWrapper& ScGlobal::getLocaleData()
+{
+ OSL_ENSURE(
+ oSysLocale,
+ "ScGlobal::getLocaleDataPtr() called before ScGlobal::Init()");
+
+ return oSysLocale->GetLocaleData();
+}
+
+const CharClass& ScGlobal::getCharClass()
+{
+ OSL_ENSURE(
+ oSysLocale,
+ "ScGlobal::getCharClassPtr() called before ScGlobal::Init()");
+
+ return oSysLocale->GetCharClass();
+}
+
+CalendarWrapper& ScGlobal::GetCalendar()
+{
+ assert(!bThreadedGroupCalcInProgress);
+ if ( !oCalendar )
+ {
+ oCalendar.emplace( ::comphelper::getProcessComponentContext() );
+ oCalendar->loadDefaultCalendar( GetLocale() );
+ }
+ return *oCalendar;
+}
+
+namespace {
+
+struct GetMutex {
+ osl::Mutex * operator ()() {
+ static osl::Mutex m;
+ return &m;
+ }
+};
+
+}
+
+CollatorWrapper& ScGlobal::GetCollator()
+{
+ return *comphelper::doubleCheckedInit( pCollator,
+ []()
+ {
+ CollatorWrapper* p = new CollatorWrapper( ::comphelper::getProcessComponentContext() );
+ p->loadDefaultCollator( GetLocale(), SC_COLLATOR_IGNORES );
+ return p;
+ },
+ GetMutex());
+}
+CollatorWrapper& ScGlobal::GetCaseCollator()
+{
+ return *comphelper::doubleCheckedInit( pCaseCollator,
+ []()
+ {
+ CollatorWrapper* p = new CollatorWrapper( ::comphelper::getProcessComponentContext() );
+ p->loadDefaultCollator( GetLocale(), 0 );
+ return p;
+ },
+ GetMutex());
+}
+CollatorWrapper& ScGlobal::GetCollator(bool bCaseSensitive)
+{
+ return bCaseSensitive ? GetCaseCollator() : GetCollator();
+}
+css::lang::Locale& ScGlobal::GetLocale()
+{
+ return *comphelper::doubleCheckedInit( pLocale,
+ []() { return new css::lang::Locale( Application::GetSettings().GetLanguageTag().getLocale()); });
+}
+
+ScFieldEditEngine& ScGlobal::GetStaticFieldEditEngine()
+{
+ assert(!bThreadedGroupCalcInProgress);
+ if (!xFieldEditEngine)
+ {
+ // Creating a ScFieldEditEngine with pDocument=NULL leads to document
+ // specific fields not being resolvable! See
+ // ScFieldEditEngine::CalcFieldValue(). pEnginePool=NULL lets
+ // EditEngine internally create and delete a default pool.
+ xFieldEditEngine.reset(new ScFieldEditEngine( nullptr, nullptr));
+ }
+ return *xFieldEditEngine;
+}
+
+sc::SharedStringPoolPurge& ScGlobal::GetSharedStringPoolPurge()
+{
+ return *comphelper::doubleCheckedInit( pSharedStringPoolPurge,
+ []() { return new sc::SharedStringPoolPurge; });
+}
+
+OUString ScGlobal::ReplaceOrAppend( const OUString& rString,
+ std::u16string_view rPlaceholder, const OUString& rReplacement )
+{
+ if (rString.isEmpty())
+ return rReplacement;
+ sal_Int32 nFound = rString.indexOf( rPlaceholder);
+ if (nFound < 0)
+ {
+ if (rString[rString.getLength()-1] == ' ')
+ return rString + rReplacement;
+ return rString + " " + rReplacement;
+ }
+ return rString.replaceFirst( rPlaceholder, rReplacement, &nFound);
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sc/source/core/data/global2.cxx b/sc/source/core/data/global2.cxx
new file mode 100644
index 000000000..ab731b2a9
--- /dev/null
+++ b/sc/source/core/data/global2.cxx
@@ -0,0 +1,598 @@
+/* -*- 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 <sfx2/docfile.hxx>
+#include <sfx2/objsh.hxx>
+#include <unotools/configmgr.hxx>
+#include <unotools/pathoptions.hxx>
+#include <tools/urlobj.hxx>
+#include <svl/numformat.hxx>
+#include <svl/zforlist.hxx>
+#include <formula/errorcodes.hxx>
+#include <sal/log.hxx>
+#include <rtl/character.hxx>
+#include <rtl/math.hxx>
+#include <o3tl/string_view.hxx>
+
+#include <global.hxx>
+#include <rangeutl.hxx>
+#include <compiler.hxx>
+#include <paramisc.hxx>
+#include <calcconfig.hxx>
+
+// struct ScImportParam:
+
+ScImportParam::ScImportParam() :
+ nCol1(0),
+ nRow1(0),
+ nCol2(0),
+ nRow2(0),
+ bImport(false),
+ bNative(false),
+ bSql(true),
+ nType(ScDbTable)
+{
+}
+
+ScImportParam::ScImportParam( const ScImportParam& r ) :
+ nCol1 (r.nCol1),
+ nRow1 (r.nRow1),
+ nCol2 (r.nCol2),
+ nRow2 (r.nRow2),
+ bImport (r.bImport),
+ aDBName (r.aDBName),
+ aStatement (r.aStatement),
+ bNative (r.bNative),
+ bSql (r.bSql),
+ nType (r.nType)
+{
+}
+
+ScImportParam::~ScImportParam()
+{
+}
+
+ScImportParam& ScImportParam::operator=( const ScImportParam& r )
+{
+ nCol1 = r.nCol1;
+ nRow1 = r.nRow1;
+ nCol2 = r.nCol2;
+ nRow2 = r.nRow2;
+ bImport = r.bImport;
+ aDBName = r.aDBName;
+ aStatement = r.aStatement;
+ bNative = r.bNative;
+ bSql = r.bSql;
+ nType = r.nType;
+
+ return *this;
+}
+
+bool ScImportParam::operator==( const ScImportParam& rOther ) const
+{
+ return( nCol1 == rOther.nCol1 &&
+ nRow1 == rOther.nRow1 &&
+ nCol2 == rOther.nCol2 &&
+ nRow2 == rOther.nRow2 &&
+ bImport == rOther.bImport &&
+ aDBName == rOther.aDBName &&
+ aStatement == rOther.aStatement &&
+ bNative == rOther.bNative &&
+ bSql == rOther.bSql &&
+ nType == rOther.nType );
+
+ //TODO: are nQuerySh and pConnection equal ?
+}
+
+// struct ScConsolidateParam:
+
+ScConsolidateParam::ScConsolidateParam()
+{
+ Clear();
+}
+
+ScConsolidateParam::ScConsolidateParam( const ScConsolidateParam& r )
+{
+ operator=(r);
+}
+
+ScConsolidateParam::~ScConsolidateParam()
+{
+}
+
+void ScConsolidateParam::ClearDataAreas()
+{
+ pDataAreas.reset();
+ nDataAreaCount = 0;
+}
+
+void ScConsolidateParam::Clear()
+{
+ ClearDataAreas();
+
+ nCol = 0;
+ nRow = 0;
+ nTab = 0;
+ bByCol = bByRow = bReferenceData = false;
+ eFunction = SUBTOTAL_FUNC_SUM;
+}
+
+ScConsolidateParam& ScConsolidateParam::operator=( const ScConsolidateParam& r )
+{
+ if (this != &r)
+ {
+ nCol = r.nCol;
+ nRow = r.nRow;
+ nTab = r.nTab;
+ bByCol = r.bByCol;
+ bByRow = r.bByRow;
+ bReferenceData = r.bReferenceData;
+ eFunction = r.eFunction;
+ nDataAreaCount = r.nDataAreaCount;
+ if ( r.nDataAreaCount > 0 )
+ {
+ nDataAreaCount = r.nDataAreaCount;
+ pDataAreas.reset( new ScArea[nDataAreaCount] );
+ for ( sal_uInt16 i=0; i<nDataAreaCount; i++ )
+ pDataAreas[i] = r.pDataAreas[i];
+ }
+ else
+ pDataAreas.reset();
+ }
+ return *this;
+}
+
+bool ScConsolidateParam::operator==( const ScConsolidateParam& r ) const
+{
+ bool bEqual = (nCol == r.nCol)
+ && (nRow == r.nRow)
+ && (nTab == r.nTab)
+ && (bByCol == r.bByCol)
+ && (bByRow == r.bByRow)
+ && (bReferenceData == r.bReferenceData)
+ && (nDataAreaCount == r.nDataAreaCount)
+ && (eFunction == r.eFunction);
+
+ if ( nDataAreaCount == 0 )
+ bEqual = bEqual && (pDataAreas == nullptr) && (r.pDataAreas == nullptr);
+ else
+ bEqual = bEqual && (pDataAreas != nullptr) && (r.pDataAreas != nullptr);
+
+ if ( bEqual && (nDataAreaCount > 0) )
+ for ( sal_uInt16 i=0; i<nDataAreaCount && bEqual; i++ )
+ bEqual = pDataAreas[i] == r.pDataAreas[i];
+
+ return bEqual;
+}
+
+void ScConsolidateParam::SetAreas( std::unique_ptr<ScArea[]> pAreas, sal_uInt16 nCount )
+{
+ pDataAreas = std::move(pAreas);
+ nDataAreaCount = nCount;
+}
+
+// struct ScSolveParam
+
+ScSolveParam::ScSolveParam()
+{
+}
+
+ScSolveParam::ScSolveParam( const ScSolveParam& r )
+ : aRefFormulaCell ( r.aRefFormulaCell ),
+ aRefVariableCell( r.aRefVariableCell ),
+ pStrTargetVal ( r.pStrTargetVal )
+{
+}
+
+ScSolveParam::ScSolveParam( const ScAddress& rFormulaCell,
+ const ScAddress& rVariableCell,
+ const OUString& rTargetValStr )
+ : aRefFormulaCell ( rFormulaCell ),
+ aRefVariableCell( rVariableCell ),
+ pStrTargetVal ( rTargetValStr )
+{
+}
+
+ScSolveParam::~ScSolveParam()
+{
+}
+
+ScSolveParam& ScSolveParam::operator=( const ScSolveParam& r )
+{
+ aRefFormulaCell = r.aRefFormulaCell;
+ aRefVariableCell = r.aRefVariableCell;
+ pStrTargetVal = r.pStrTargetVal;
+ return *this;
+}
+
+bool ScSolveParam::operator==( const ScSolveParam& r ) const
+{
+ bool bEqual = (aRefFormulaCell == r.aRefFormulaCell)
+ && (aRefVariableCell == r.aRefVariableCell);
+
+ if ( bEqual )
+ {
+ if ( !pStrTargetVal && !r.pStrTargetVal )
+ bEqual = true;
+ else if ( !pStrTargetVal || !r.pStrTargetVal )
+ bEqual = false;
+ else
+ bEqual = ( *pStrTargetVal == *(r.pStrTargetVal) );
+ }
+
+ return bEqual;
+}
+
+// struct ScTabOpParam
+
+ScTabOpParam::ScTabOpParam() : meMode(Column) {}
+
+ScTabOpParam::ScTabOpParam( const ScTabOpParam& r )
+ : aRefFormulaCell ( r.aRefFormulaCell ),
+ aRefFormulaEnd ( r.aRefFormulaEnd ),
+ aRefRowCell ( r.aRefRowCell ),
+ aRefColCell ( r.aRefColCell ),
+ meMode(r.meMode)
+{
+}
+
+ScTabOpParam::ScTabOpParam( const ScRefAddress& rFormulaCell,
+ const ScRefAddress& rFormulaEnd,
+ const ScRefAddress& rRowCell,
+ const ScRefAddress& rColCell,
+ Mode eMode )
+ : aRefFormulaCell ( rFormulaCell ),
+ aRefFormulaEnd ( rFormulaEnd ),
+ aRefRowCell ( rRowCell ),
+ aRefColCell ( rColCell ),
+ meMode(eMode)
+{
+}
+
+ScTabOpParam& ScTabOpParam::operator=( const ScTabOpParam& r )
+{
+ aRefFormulaCell = r.aRefFormulaCell;
+ aRefFormulaEnd = r.aRefFormulaEnd;
+ aRefRowCell = r.aRefRowCell;
+ aRefColCell = r.aRefColCell;
+ meMode = r.meMode;
+ return *this;
+}
+
+bool ScTabOpParam::operator==( const ScTabOpParam& r ) const
+{
+ return ( (aRefFormulaCell == r.aRefFormulaCell)
+ && (aRefFormulaEnd == r.aRefFormulaEnd)
+ && (aRefRowCell == r.aRefRowCell)
+ && (aRefColCell == r.aRefColCell)
+ && (meMode == r.meMode) );
+}
+
+OUString ScGlobal::GetAbsDocName( const OUString& rFileName,
+ const SfxObjectShell* pShell )
+{
+ OUString aAbsName;
+ if (!pShell || !pShell->HasName())
+ { // maybe relative to document path working directory
+ INetURLObject aObj;
+ if (!utl::ConfigManager::IsFuzzing())
+ {
+ aObj.SetSmartURL(SvtPathOptions().GetWorkPath());
+ aObj.setFinalSlash(); // it IS a path
+ }
+ else
+ aObj.SetSmartURL(u"file:///tmp/document");
+ bool bWasAbs = true;
+ aAbsName = aObj.smartRel2Abs( rFileName, bWasAbs ).GetMainURL(INetURLObject::DecodeMechanism::NONE);
+ // returned string must be encoded because it's used directly to create SfxMedium
+ }
+ else
+ {
+ const SfxMedium* pMedium = pShell->GetMedium();
+ if ( pMedium )
+ {
+ bool bWasAbs = true;
+ aAbsName = pMedium->GetURLObject().smartRel2Abs( rFileName, bWasAbs ).GetMainURL(INetURLObject::DecodeMechanism::NONE);
+ }
+ else
+ { // This can't happen, but ...
+ // just to be sure to have the same encoding
+ INetURLObject aObj;
+ aObj.SetSmartURL( aAbsName );
+ aAbsName = aObj.GetMainURL(INetURLObject::DecodeMechanism::NONE);
+ }
+ }
+ return aAbsName;
+}
+
+OUString ScGlobal::GetDocTabName( std::u16string_view rFileName,
+ std::u16string_view rTabName )
+{
+ OUString aDocTab = OUString::Concat("'") + rFileName;
+ sal_Int32 nPos = 1;
+ while( (nPos = aDocTab.indexOf( '\'', nPos )) != -1 )
+ { // escape Quotes
+ aDocTab = aDocTab.replaceAt( nPos, 0, u"\\" );
+ nPos += 2;
+ }
+ aDocTab += "'" + OUStringChar(SC_COMPILER_FILE_TAB_SEP) + rTabName;
+ // "'Doc'#Tab"
+ return aDocTab;
+}
+
+namespace
+{
+bool isEmptyString( const OUString& rStr )
+{
+ if (rStr.isEmpty())
+ return true;
+ else if (rStr[0] == ' ')
+ {
+ const sal_Unicode* p = rStr.getStr() + 1;
+ const sal_Unicode* const pStop = p - 1 + rStr.getLength();
+ while (p < pStop && *p == ' ')
+ ++p;
+ if (p == pStop)
+ return true;
+ }
+ return false;
+}
+}
+
+double ScGlobal::ConvertStringToValue( const OUString& rStr, const ScCalcConfig& rConfig,
+ FormulaError & rError, FormulaError nStringNoValueError,
+ SvNumberFormatter* pFormatter, SvNumFormatType & rCurFmtType )
+{
+ // We keep ScCalcConfig::StringConversion::LOCALE default until
+ // we provide a friendly way to convert string numbers into numbers in the UI.
+
+ double fValue = 0.0;
+ if (nStringNoValueError == FormulaError::CellNoValue)
+ {
+ // Requested that all strings result in 0, error handled by caller.
+ rError = nStringNoValueError;
+ return fValue;
+ }
+
+ switch (rConfig.meStringConversion)
+ {
+ case ScCalcConfig::StringConversion::ILLEGAL:
+ rError = nStringNoValueError;
+ return fValue;
+ case ScCalcConfig::StringConversion::ZERO:
+ return fValue;
+ case ScCalcConfig::StringConversion::LOCALE:
+ {
+ if (rConfig.mbEmptyStringAsZero)
+ {
+ // The number scanner does not accept empty strings or strings
+ // containing only spaces, be on par in these cases with what was
+ // accepted in OOo and is in AOO (see also the
+ // StringConversion::UNAMBIGUOUS branch) and convert to 0 to prevent
+ // interoperability nightmares.
+
+ if (isEmptyString( rStr))
+ return fValue;
+ }
+
+ if (!pFormatter)
+ goto Label_fallback_to_unambiguous;
+
+ sal_uInt32 nFIndex = 0;
+ if (!pFormatter->IsNumberFormat(rStr, nFIndex, fValue))
+ {
+ rError = nStringNoValueError;
+ fValue = 0.0;
+ }
+ return fValue;
+ }
+ break;
+ case ScCalcConfig::StringConversion::UNAMBIGUOUS:
+Label_fallback_to_unambiguous:
+ {
+ if (!rConfig.mbEmptyStringAsZero)
+ {
+ if (isEmptyString( rStr))
+ {
+ rError = nStringNoValueError;
+ return fValue;
+ }
+ }
+ }
+ // continue below, pulled from switch case for better readability
+ break;
+ }
+
+ rtl_math_ConversionStatus eStatus;
+ sal_Int32 nParseEnd;
+ // Decimal and group separator 0 => only integer and possibly exponent,
+ // stops at first non-digit non-sign.
+ fValue = ::rtl::math::stringToDouble( rStr, 0, 0, &eStatus, &nParseEnd);
+ sal_Int32 nLen = rStr.getLength();
+ if (eStatus == rtl_math_ConversionStatus_Ok && nParseEnd < nLen)
+ {
+ // Not at string end, check for trailing blanks or switch to date or
+ // time parsing or bail out.
+ const sal_Unicode* const pStart = rStr.getStr();
+ const sal_Unicode* p = pStart + nParseEnd;
+ const sal_Unicode* const pStop = pStart + nLen;
+ switch (*p++)
+ {
+ case ' ':
+ while (p < pStop && *p == ' ')
+ ++p;
+ if (p < pStop)
+ rError = nStringNoValueError;
+ break;
+ case '-':
+ case ':':
+ {
+ bool bDate = (*(p-1) == '-');
+ enum State { year = 0, month, day, hour, minute, second, fraction, done, blank, stop };
+ sal_Int32 nUnit[done] = {0,0,0,0,0,0,0};
+ const sal_Int32 nLimit[done] = {0,12,31,0,59,59,0};
+ State eState = (bDate ? month : minute);
+ rCurFmtType = (bDate ? SvNumFormatType::DATE : SvNumFormatType::TIME);
+ nUnit[eState-1] = o3tl::toInt32(rStr.subView( 0, nParseEnd));
+ const sal_Unicode* pLastStart = p;
+ // Ensure there's no preceding sign. Negative dates
+ // currently aren't handled correctly. Also discard
+ // +CCYY-MM-DD
+ p = pStart;
+ while (p < pStop && *p == ' ')
+ ++p;
+ if (p < pStop && !rtl::isAsciiDigit(*p))
+ rError = nStringNoValueError;
+ p = pLastStart;
+ while (p < pStop && rError == FormulaError::NONE && eState < blank)
+ {
+ if (eState == minute)
+ rCurFmtType |= SvNumFormatType::TIME;
+ if (rtl::isAsciiDigit(*p))
+ {
+ // Maximum 2 digits per unit, except fractions.
+ if (p - pLastStart >= 2 && eState != fraction)
+ rError = nStringNoValueError;
+ }
+ else if (p > pLastStart)
+ {
+ // We had at least one digit.
+ if (eState < done)
+ {
+ nUnit[eState] = o3tl::toInt32(rStr.subView( pLastStart - pStart, p - pLastStart));
+ if (nLimit[eState] && nLimit[eState] < nUnit[eState])
+ rError = nStringNoValueError;
+ }
+ pLastStart = p + 1; // hypothetical next start
+ // Delimiters must match, a trailing delimiter
+ // yields an invalid date/time.
+ switch (eState)
+ {
+ case month:
+ // Month must be followed by separator and
+ // day, no trailing blanks.
+ if (*p != '-' || (p+1 == pStop))
+ rError = nStringNoValueError;
+ break;
+ case day:
+ if ((*p != 'T' || (p+1 == pStop)) && *p != ' ')
+ rError = nStringNoValueError;
+ // Take one blank as a valid delimiter
+ // between date and time.
+ break;
+ case hour:
+ // Hour must be followed by separator and
+ // minute, no trailing blanks.
+ if (*p != ':' || (p+1 == pStop))
+ rError = nStringNoValueError;
+ break;
+ case minute:
+ if ((*p != ':' || (p+1 == pStop)) && *p != ' ')
+ rError = nStringNoValueError;
+ if (*p == ' ')
+ eState = done;
+ break;
+ case second:
+ if (((*p != ',' && *p != '.') || (p+1 == pStop)) && *p != ' ')
+ rError = nStringNoValueError;
+ if (*p == ' ')
+ eState = done;
+ break;
+ case fraction:
+ eState = done;
+ break;
+ default:
+ rError = nStringNoValueError;
+ break;
+ }
+ eState = static_cast<State>(eState + 1);
+ }
+ else
+ rError = nStringNoValueError;
+ ++p;
+ }
+ if (eState == blank)
+ {
+ while (p < pStop && *p == ' ')
+ ++p;
+ if (p < pStop)
+ rError = nStringNoValueError;
+ eState = stop;
+ }
+
+ // Month without day, or hour without minute.
+ if (eState == month || (eState == day && p <= pLastStart) ||
+ eState == hour || (eState == minute && p <= pLastStart))
+ rError = nStringNoValueError;
+
+ if (rError == FormulaError::NONE)
+ {
+ // Catch the very last unit at end of string.
+ if (p > pLastStart && eState < done)
+ {
+ nUnit[eState] = o3tl::toInt32(rStr.subView( pLastStart - pStart, p - pLastStart));
+ if (nLimit[eState] && nLimit[eState] < nUnit[eState])
+ rError = nStringNoValueError;
+ }
+ if (bDate && nUnit[hour] > 23)
+ rError = nStringNoValueError;
+ if (rError == FormulaError::NONE)
+ {
+ if (bDate && nUnit[day] == 0)
+ nUnit[day] = 1;
+ double fFraction = (nUnit[fraction] <= 0 ? 0.0 :
+ ::rtl::math::pow10Exp( nUnit[fraction],
+ static_cast<int>( -ceil( log10( static_cast<double>( nUnit[fraction]))))));
+ if (!bDate)
+ fValue = 0.0;
+ else
+ {
+ Date aDate(
+ sal::static_int_cast<sal_Int16>(nUnit[day]),
+ sal::static_int_cast<sal_Int16>(nUnit[month]),
+ sal::static_int_cast<sal_Int16>(nUnit[year]));
+ if (!aDate.IsValidDate())
+ rError = nStringNoValueError;
+ else
+ {
+ if (pFormatter)
+ fValue = aDate - pFormatter->GetNullDate();
+ else
+ {
+ SAL_WARN("sc.core","ScGlobal::ConvertStringToValue - fixed null date");
+ static Date aDefaultNullDate( 30, 12, 1899);
+ fValue = aDate - aDefaultNullDate;
+ }
+ }
+ }
+ fValue += ((nUnit[hour] * 3600) + (nUnit[minute] * 60) + nUnit[second] + fFraction) / 86400.0;
+ }
+ }
+ }
+ break;
+ default:
+ rError = nStringNoValueError;
+ }
+ if (rError != FormulaError::NONE)
+ fValue = 0.0;
+ }
+ return fValue;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sc/source/core/data/globalx.cxx b/sc/source/core/data/globalx.cxx
new file mode 100644
index 000000000..f08869cb1
--- /dev/null
+++ b/sc/source/core/data/globalx.cxx
@@ -0,0 +1,142 @@
+/* -*- 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 <callform.hxx>
+#include <global.hxx>
+#include <osl/diagnose.h>
+#include <osl/file.hxx>
+#include <tools/urlobj.hxx>
+#include <tools/diagnose_ex.h>
+#include <ucbhelper/content.hxx>
+
+#include <unotools/pathoptions.hxx>
+
+#include <com/sun/star/sdbc/XResultSet.hpp>
+#include <com/sun/star/ucb/XContentAccess.hpp>
+
+#include <com/sun/star/i18n/OrdinalSuffix.hpp>
+#include <comphelper/processfactory.hxx>
+#include <unotools/configmgr.hxx>
+#include <unotools/localedatawrapper.hxx>
+
+namespace com::sun::star::ucb { class XCommandEnvironment; }
+
+using namespace ::com::sun::star;
+using namespace ::com::sun::star::uno;
+using namespace ::com::sun::star::ucb;
+
+void ScGlobal::InitAddIns()
+{
+ if (utl::ConfigManager::IsFuzzing())
+ return;
+
+ // multi paths separated by semicolons
+ SvtPathOptions aPathOpt;
+ const OUString& aMultiPath = aPathOpt.GetAddinPath();
+ if (aMultiPath.isEmpty())
+ return;
+
+ sal_Int32 nIdx {0};
+ do
+ {
+ OUString aPath = aMultiPath.getToken(0, ';', nIdx);
+ if (aPath.isEmpty())
+ continue;
+
+ OUString aUrl;
+ if ( osl::FileBase::getFileURLFromSystemPath( aPath, aUrl ) == osl::FileBase::E_None )
+ aPath = aUrl;
+
+ INetURLObject aObj;
+ aObj.SetSmartURL( aPath );
+ aObj.setFinalSlash();
+ try
+ {
+ ::ucbhelper::Content aCnt( aObj.GetMainURL(INetURLObject::DecodeMechanism::NONE),
+ Reference< XCommandEnvironment >(),
+ comphelper::getProcessComponentContext() );
+ Reference< sdbc::XResultSet > xResultSet;
+ Sequence< OUString > aProps;
+ try
+ {
+ xResultSet = aCnt.createCursor(
+ aProps, ::ucbhelper::INCLUDE_DOCUMENTS_ONLY );
+ }
+ catch ( Exception& )
+ {
+ // ucb may throw different exceptions on failure now
+ // no assertion if AddIn directory doesn't exist
+ }
+
+ if ( xResultSet.is() )
+ {
+ Reference< XContentAccess > xContentAccess( xResultSet, UNO_QUERY );
+ try
+ {
+ if ( xResultSet->first() )
+ {
+ do
+ {
+ OUString aId = xContentAccess->queryContentIdentifierString();
+ InitExternalFunc( aId );
+ }
+ while ( xResultSet->next() );
+ }
+ }
+ catch ( Exception& )
+ {
+ TOOLS_WARN_EXCEPTION( "sc", "" );
+ }
+ }
+ }
+ catch ( Exception& )
+ {
+ TOOLS_WARN_EXCEPTION( "sc", "" );
+ }
+ catch ( ... )
+ {
+ OSL_FAIL( "unexpected exception caught!" );
+ }
+ }
+ while (nIdx>0);
+}
+
+OUString ScGlobal::GetOrdinalSuffix( sal_Int32 nNumber)
+{
+ try
+ {
+ if (!xOrdinalSuffix.is())
+ {
+ xOrdinalSuffix = i18n::OrdinalSuffix::create( ::comphelper::getProcessComponentContext() );
+ }
+ uno::Sequence< OUString > aSuffixes = xOrdinalSuffix->getOrdinalSuffix( nNumber,
+ ScGlobal::getLocaleData().getLanguageTag().getLocale());
+ if ( aSuffixes.hasElements() )
+ return aSuffixes[0];
+ else
+ return OUString();
+ }
+ catch ( Exception& )
+ {
+ TOOLS_WARN_EXCEPTION( "sc", "GetOrdinalSuffix: exception caught during init" );
+ }
+ return OUString();
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sc/source/core/data/grouptokenconverter.cxx b/sc/source/core/data/grouptokenconverter.cxx
new file mode 100644
index 000000000..07fefbccb
--- /dev/null
+++ b/sc/source/core/data/grouptokenconverter.cxx
@@ -0,0 +1,316 @@
+/* -*- 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/.
+ */
+
+#include <grouptokenconverter.hxx>
+#include <document.hxx>
+#include <formulacell.hxx>
+#include <tokenarray.hxx>
+#include <refdata.hxx>
+
+#include <formula/token.hxx>
+#include <formula/vectortoken.hxx>
+
+using namespace formula;
+
+bool ScGroupTokenConverter::isSelfReferenceRelative(const ScAddress& rRefPos, SCROW nRelRow)
+{
+ if (rRefPos.Col() != mrPos.Col() || rRefPos.Tab() != mrPos.Tab())
+ return false;
+
+ SCROW nLen = mrCell.GetCellGroup()->mnLength;
+ SCROW nEndRow = mrPos.Row() + nLen - 1;
+
+ if (nRelRow < 0)
+ {
+ SCROW nTest = nEndRow;
+ nTest += nRelRow;
+ if (nTest >= mrPos.Row())
+ return true;
+ }
+ else if (nRelRow > 0)
+ {
+ SCROW nTest = mrPos.Row(); // top row.
+ nTest += nRelRow;
+ if (nTest <= nEndRow)
+ return true;
+ }
+
+ return false;
+}
+
+bool ScGroupTokenConverter::isSelfReferenceAbsolute(const ScAddress& rRefPos)
+{
+ if (rRefPos.Col() != mrPos.Col() || rRefPos.Tab() != mrPos.Tab())
+ return false;
+
+ SCROW nLen = mrCell.GetCellGroup()->mnLength;
+ SCROW nEndRow = mrPos.Row() + nLen - 1;
+
+ if (rRefPos.Row() < mrPos.Row())
+ return false;
+
+ if (rRefPos.Row() > nEndRow)
+ return false;
+
+ return true;
+}
+
+SCROW ScGroupTokenConverter::trimLength(SCTAB nTab, SCCOL nCol1, SCCOL nCol2, SCROW nRow, SCROW nRowLen)
+{
+ SCROW nLastRow = nRow + nRowLen - 1; // current last row.
+ nLastRow = mrDoc.GetLastDataRow(nTab, nCol1, nCol2, nLastRow);
+ if (nLastRow < (nRow + nRowLen - 1))
+ {
+ // This can end up negative! Was that the original intent, or
+ // is it accidental? Was it not like that originally but the
+ // surrounding conditions changed?
+ nRowLen = nLastRow - nRow + 1;
+ // Anyway, let's assume it doesn't make sense to return a
+ // negative value here. But should we then return 0 or 1? In
+ // the "Column is empty" case below, we return 1, why!? And,
+ // at the callsites there are tests for a zero value returned
+ // from this function (but not for a negative one).
+ if (nRowLen < 0)
+ nRowLen = 0;
+ }
+ else if (nLastRow == 0)
+ // Column is empty.
+ nRowLen = 1;
+
+ return nRowLen;
+}
+
+ScGroupTokenConverter::ScGroupTokenConverter(
+ ScTokenArray& rGroupTokens, ScDocument& rDoc, const ScFormulaCell& rCell, const ScAddress& rPos) :
+ mrGroupTokens(rGroupTokens),
+ mrDoc(rDoc),
+ mrCell(rCell),
+ mrPos(rPos)
+{
+}
+
+bool ScGroupTokenConverter::convert( const ScTokenArray& rCode, sc::FormulaLogger::GroupScope& rScope )
+{
+#if 0
+ { // debug to start with:
+ ScCompiler aComp( &mrDoc, mrPos, rCode, formula::FormulaGrammar::GRAM_NATIVE_XL_R1C1);
+ OUStringBuffer aAsString;
+ aComp.CreateStringFromTokenArray(aAsString);
+ }
+#endif
+
+ const SCROW nLen = mrCell.GetCellGroup()->mnLength;
+ formula::FormulaTokenArrayPlainIterator aIter(rCode);
+ for (const formula::FormulaToken* p = aIter.First(); p; p = aIter.Next())
+ {
+ // A reference can be either absolute or relative. If it's absolute,
+ // convert it to a static value token. If relative, convert it to a
+ // vector reference token. Note: we only care about relative vs
+ // absolute reference state for row directions.
+
+ switch (p->GetType())
+ {
+ case svSingleRef:
+ {
+ ScSingleRefData aRef = *p->GetSingleRef();
+ if( aRef.IsDeleted())
+ return false;
+ ScAddress aRefPos = aRef.toAbs(mrDoc, mrPos);
+ if (aRef.IsRowRel())
+ {
+ if (isSelfReferenceRelative(aRefPos, aRef.Row()))
+ return false;
+
+ // Trim data array length to actual data range.
+ SCROW nTrimLen = trimLength(aRefPos.Tab(), aRefPos.Col(), aRefPos.Col(), aRefPos.Row(), nLen);
+ // Fetch double array guarantees that the length of the
+ // returned array equals or greater than the requested
+ // length.
+
+ formula::VectorRefArray aArray;
+ if (nTrimLen)
+ {
+#ifdef DBG_UTIL
+ // All the necessary Interpret() calls for all the cells
+ // should have been already handled by ScDependantsCalculator
+ // calling HandleRefArrayForParallelism(), and that handling also checks
+ // for cycles etc. Recursively calling Interpret() from here (which shouldn't
+ // happen) could lead to unhandled problems.
+ // Also, because of caching FetchVectorRefArray() fetches values for all rows
+ // up to the maximum one, so check those too.
+ mrDoc.AssertNoInterpretNeeded(
+ ScAddress(aRefPos.Col(), 0, aRefPos.Tab()), nTrimLen + aRefPos.Row());
+#endif
+ aArray = mrDoc.FetchVectorRefArray(aRefPos, nTrimLen);
+ }
+
+ if (!aArray.isValid())
+ return false;
+
+ formula::SingleVectorRefToken aTok(aArray, nTrimLen);
+ mrGroupTokens.AddToken(aTok);
+ rScope.addRefMessage(mrPos, aRefPos, nLen, aArray);
+
+ if (nTrimLen && !mxFormulaGroupContext)
+ {
+ //tdf#98880 if the SingleVectorRefToken relies on the
+ //underlying storage provided by the Document
+ //FormulaGroupContext, take a reference to it here to
+ //ensure that backing storage exists for our lifetime
+ mxFormulaGroupContext = mrDoc.GetFormulaGroupContext();
+ }
+ }
+ else
+ {
+ // Absolute row reference.
+ if (isSelfReferenceAbsolute(aRefPos))
+ return false;
+
+ formula::FormulaTokenRef pNewToken = mrDoc.ResolveStaticReference(aRefPos);
+ if (!pNewToken)
+ return false;
+
+ mrGroupTokens.AddToken(*pNewToken);
+ rScope.addRefMessage(mrPos, aRefPos, *pNewToken);
+ }
+ }
+ break;
+ case svDoubleRef:
+ {
+ // This code may break in case of implicit intersection, leading to unnecessarily large
+ // matrix operations and possibly incorrect results (=C:C/D:D). That is handled by
+ // having ScCompiler check that there are no possible implicit intersections.
+ // Additionally some functions such as INDEX() and OFFSET() require a reference,
+ // that is handled by denylisting those opcodes in ScTokenArray::CheckToken().
+
+ ScComplexRefData aRef = *p->GetDoubleRef();
+ if( aRef.IsDeleted())
+ return false;
+ ScRange aAbs = aRef.toAbs(mrDoc, mrPos);
+
+ // Multiple sheets not handled by vector/matrix.
+ if (aRef.Ref1.Tab() != aRef.Ref2.Tab())
+ return false;
+
+ // Check for self reference.
+ if (aRef.Ref1.IsRowRel())
+ {
+ if (isSelfReferenceRelative(aAbs.aStart, aRef.Ref1.Row()))
+ return false;
+ }
+ else if (isSelfReferenceAbsolute(aAbs.aStart))
+ return false;
+
+ if (aRef.Ref2.IsRowRel())
+ {
+ if (isSelfReferenceRelative(aAbs.aEnd, aRef.Ref2.Row()))
+ return false;
+ }
+ else if (isSelfReferenceAbsolute(aAbs.aEnd))
+ return false;
+
+ // Row reference is relative.
+ bool bAbsFirst = !aRef.Ref1.IsRowRel();
+ bool bAbsLast = !aRef.Ref2.IsRowRel();
+ ScAddress aRefPos = aAbs.aStart;
+ size_t nCols = aAbs.aEnd.Col() - aAbs.aStart.Col() + 1;
+ std::vector<formula::VectorRefArray> aArrays;
+ aArrays.reserve(nCols);
+ SCROW nRefRowSize = aAbs.aEnd.Row() - aAbs.aStart.Row() + 1;
+ SCROW nArrayLength = nRefRowSize;
+ if (!bAbsLast)
+ {
+ // range end position is relative. Extend the array length.
+ SCROW nLastRefRowOffset = aAbs.aEnd.Row() - mrPos.Row();
+ SCROW nLastRefRow = mrPos.Row() + nLen - 1 + nLastRefRowOffset;
+ SCROW nNewLength = nLastRefRow - aAbs.aStart.Row() + 1;
+ if (nNewLength > nArrayLength)
+ nArrayLength = nNewLength;
+ }
+
+ // Trim trailing empty rows.
+ SCROW nRequestedLength = nArrayLength; // keep the original length.
+ nArrayLength = trimLength(aRefPos.Tab(), aAbs.aStart.Col(), aAbs.aEnd.Col(), aRefPos.Row(), nArrayLength);
+
+ for (SCCOL i = aAbs.aStart.Col(); i <= aAbs.aEnd.Col(); ++i)
+ {
+ aRefPos.SetCol(i);
+ formula::VectorRefArray aArray;
+ if (nArrayLength)
+ {
+#ifdef DBG_UTIL
+ mrDoc.AssertNoInterpretNeeded(
+ ScAddress(aRefPos.Col(), 0, aRefPos.Tab()), nArrayLength + aRefPos.Row());
+#endif
+ aArray = mrDoc.FetchVectorRefArray(aRefPos, nArrayLength);
+ }
+
+ if (!aArray.isValid())
+ return false;
+
+ aArrays.push_back(aArray);
+ }
+
+ std::vector<formula::VectorRefArray> aArraysTmp = aArrays;
+ formula::DoubleVectorRefToken aTok( std::move(aArraysTmp), nArrayLength, nRefRowSize, bAbsFirst, bAbsLast );
+ mrGroupTokens.AddToken(aTok);
+ rScope.addRefMessage(mrPos, aAbs.aStart, nRequestedLength, aArrays);
+
+ if (nArrayLength && !aArrays.empty() && !mxFormulaGroupContext)
+ {
+ //tdf#98880 if the DoubleVectorRefToken relies on the
+ //underlying storage provided by the Document
+ //FormulaGroupContext, take a reference to it here to
+ //ensure that backing storage exists for our lifetime
+ mxFormulaGroupContext = mrDoc.GetFormulaGroupContext();
+ }
+ }
+ break;
+ case svIndex:
+ {
+ if (p->GetOpCode() != ocName)
+ {
+ // May be DB-range or TableRef
+ mrGroupTokens.AddToken(*p);
+ break;
+ }
+
+ // Named range.
+ ScRangeName* pNames = mrDoc.GetRangeName();
+ if (!pNames)
+ // This should never fail.
+ return false;
+
+ ScRangeData* pRange = pNames->findByIndex(p->GetIndex());
+ if (!pRange)
+ // No named range exists by that index.
+ return false;
+
+ ScTokenArray* pNamedTokens = pRange->GetCode();
+ if (!pNamedTokens)
+ // This named range is empty.
+ return false;
+
+ mrGroupTokens.AddOpCode(ocOpen);
+
+ if (!convert(*pNamedTokens, rScope))
+ return false;
+
+ mrGroupTokens.AddOpCode(ocClose);
+ }
+ break;
+ default:
+ mrGroupTokens.AddToken(*p);
+ }
+ }
+
+ return true;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sc/source/core/data/listenercontext.cxx b/sc/source/core/data/listenercontext.cxx
new file mode 100644
index 000000000..dac2380bf
--- /dev/null
+++ b/sc/source/core/data/listenercontext.cxx
@@ -0,0 +1,95 @@
+/* -*- 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/.
+ */
+
+#include <listenercontext.hxx>
+#include <document.hxx>
+#include <mtvelements.hxx>
+
+namespace sc {
+
+StartListeningContext::StartListeningContext(ScDocument& rDoc) :
+ mrDoc(rDoc), mpSet(std::make_shared<ColumnBlockPositionSet>(rDoc)) {}
+
+StartListeningContext::StartListeningContext(
+ ScDocument& rDoc, const std::shared_ptr<ColumnBlockPositionSet>& pSet) :
+ mrDoc(rDoc), mpSet(pSet) {}
+
+void StartListeningContext::setColumnSet( const std::shared_ptr<const ColumnSet>& rpColSet )
+{
+ mpColSet = rpColSet;
+}
+
+const std::shared_ptr<const ColumnSet>& StartListeningContext::getColumnSet() const
+{
+ return mpColSet;
+}
+
+ColumnBlockPosition* StartListeningContext::getBlockPosition(SCTAB nTab, SCCOL nCol)
+{
+ return mpSet->getBlockPosition(nTab, nCol);
+}
+
+EndListeningContext::EndListeningContext(ScDocument& rDoc, ScTokenArray* pOldCode) :
+ mrDoc(rDoc), mpPosSet(std::make_shared<ColumnBlockPositionSet>(rDoc)),
+ mpOldCode(pOldCode), maPosDelta(0,0,0) {}
+
+EndListeningContext::EndListeningContext(
+ ScDocument& rDoc, const std::shared_ptr<ColumnBlockPositionSet>& pSet, ScTokenArray* pOldCode) :
+ mrDoc(rDoc), mpPosSet(pSet),
+ mpOldCode(pOldCode), maPosDelta(0,0,0) {}
+
+void EndListeningContext::setPositionDelta( const ScAddress& rDelta )
+{
+ maPosDelta = rDelta;
+}
+
+ScAddress EndListeningContext::getOldPosition( const ScAddress& rPos ) const
+{
+ ScAddress aOldPos = rPos;
+ aOldPos.IncCol(maPosDelta.Col());
+ aOldPos.IncRow(maPosDelta.Row());
+ aOldPos.IncTab(maPosDelta.Tab());
+ return aOldPos;
+}
+
+ColumnBlockPosition* EndListeningContext::getBlockPosition(SCTAB nTab, SCCOL nCol)
+{
+ return mpPosSet->getBlockPosition(nTab, nCol);
+}
+
+void EndListeningContext::addEmptyBroadcasterPosition(SCTAB nTab, SCCOL nCol, SCROW nRow)
+{
+ maSet.set(mrDoc, nTab, nCol, nRow, true);
+}
+
+void EndListeningContext::purgeEmptyBroadcasters()
+{
+ PurgeListenerAction aAction(mrDoc);
+ maSet.executeAction(mrDoc, aAction);
+}
+
+PurgeListenerAction::PurgeListenerAction(ScDocument& rDoc) :
+ mrDoc(rDoc), mpBlockPos(new ColumnBlockPosition) {}
+
+void PurgeListenerAction::startColumn( SCTAB nTab, SCCOL nCol )
+{
+ mrDoc.InitColumnBlockPosition(*mpBlockPos, nTab, nCol);
+}
+
+void PurgeListenerAction::execute( const ScAddress& rPos, SCROW nLength, bool bVal )
+{
+ if (bVal)
+ {
+ mrDoc.DeleteBroadcasters(*mpBlockPos, rPos, nLength);
+ }
+};
+
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sc/source/core/data/markarr.cxx b/sc/source/core/data/markarr.cxx
new file mode 100644
index 000000000..9b93c32f2
--- /dev/null
+++ b/sc/source/core/data/markarr.cxx
@@ -0,0 +1,462 @@
+/* -*- 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 <markarr.hxx>
+#include <address.hxx>
+#include <sheetlimits.hxx>
+#include <vector>
+
+ScMarkArray::ScMarkArray(const ScSheetLimits& rLimits) :
+ mrSheetLimits(rLimits)
+{
+ Reset(false);
+}
+
+// Move constructor
+ScMarkArray::ScMarkArray( ScMarkArray&& rOther ) noexcept
+ : mrSheetLimits(rOther.mrSheetLimits)
+{
+ operator=(std::move(rOther));
+}
+
+// Copy constructor
+ScMarkArray::ScMarkArray( const ScMarkArray & rOther )
+ : mrSheetLimits(rOther.mrSheetLimits)
+{
+ operator=(rOther);
+}
+
+void ScMarkArray::Reset( bool bMarked, SCSIZE nNeeded )
+{
+ // always create pData here
+ // (or have separate method to ensure pData)
+
+ assert(nNeeded);
+ mvData.resize(1);
+ mvData.reserve(nNeeded);
+ mvData[0].nRow = mrSheetLimits.mnMaxRow;
+ mvData[0].bMarked = bMarked;
+}
+
+// Iterative implementation of Binary Search
+bool ScMarkArray::Search( SCROW nRow, SCSIZE& nIndex ) const
+{
+ assert(mvData.size() > 0);
+ SCSIZE nHi = mvData.size() - 1;
+ SCSIZE nLo = 0;
+
+ while ( nLo <= nHi )
+ {
+ SCSIZE i = (nLo + nHi) / 2;
+
+ if (mvData[i].nRow < nRow)
+ {
+ // If [nRow] greater, ignore left half
+ nLo = i + 1;
+ }
+ else if ((i > 0) && (mvData[i - 1].nRow >= nRow))
+ {
+ // If [nRow] is smaller, ignore right half
+ nHi = i - 1;
+ }
+ else
+ {
+ // found
+ nIndex=i;
+ return true;
+ }
+ }
+
+ // not found
+ nIndex=0;
+ return false;
+}
+
+bool ScMarkArray::GetMark( SCROW nRow ) const
+{
+ SCSIZE i;
+ if (Search( nRow, i ))
+ return mvData[i].bMarked;
+ else
+ return false;
+
+}
+
+void ScMarkArray::SetMarkArea( SCROW nStartRow, SCROW nEndRow, bool bMarked )
+{
+ if (!(mrSheetLimits.ValidRow(nStartRow) && mrSheetLimits.ValidRow(nEndRow)))
+ return;
+
+ if ((nStartRow == 0) && (nEndRow == mrSheetLimits.mnMaxRow))
+ {
+ Reset(bMarked);
+ }
+ else
+ {
+ SCSIZE ni; // number of entries in beginning
+ SCSIZE nInsert; // insert position (mnMaxRow+1 := no insert)
+ bool bCombined = false;
+ bool bSplit = false;
+ if ( nStartRow > 0 )
+ {
+ // skip beginning
+ SCSIZE nIndex;
+ Search( nStartRow, nIndex );
+ ni = nIndex;
+
+ nInsert = mrSheetLimits.GetMaxRowCount();
+ if ( mvData[ni].bMarked != bMarked )
+ {
+ if ( ni == 0 || (mvData[ni-1].nRow < nStartRow - 1) )
+ { // may be a split or a simple insert or just a shrink,
+ // row adjustment is done further down
+ if ( mvData[ni].nRow > nEndRow )
+ bSplit = true;
+ ni++;
+ nInsert = ni;
+ }
+ else if ( ni > 0 && mvData[ni-1].nRow == nStartRow - 1 )
+ nInsert = ni;
+ }
+ if ( ni > 0 && mvData[ni-1].bMarked == bMarked )
+ { // combine
+ mvData[ni-1].nRow = nEndRow;
+ nInsert = mrSheetLimits.GetMaxRowCount();
+ bCombined = true;
+ }
+ }
+ else
+ {
+ nInsert = 0;
+ ni = 0;
+ }
+
+ SCSIZE nj = ni; // stop position of range to replace
+ while ( nj < mvData.size() && mvData[nj].nRow <= nEndRow )
+ nj++;
+ if ( !bSplit )
+ {
+ if ( nj < mvData.size() && mvData[nj].bMarked == bMarked )
+ { // combine
+ if ( ni > 0 )
+ {
+ if ( mvData[ni-1].bMarked == bMarked )
+ { // adjacent entries
+ mvData[ni-1].nRow = mvData[nj].nRow;
+ nj++;
+ }
+ else if ( ni == nInsert )
+ mvData[ni-1].nRow = nStartRow - 1; // shrink
+ }
+ nInsert = mrSheetLimits.GetMaxRowCount();
+ bCombined = true;
+ }
+ else if ( ni > 0 && ni == nInsert )
+ mvData[ni-1].nRow = nStartRow - 1; // shrink
+ }
+ if ( ni < nj )
+ { // remove middle entries
+ if ( !bCombined )
+ { // replace one entry
+ mvData[ni].nRow = nEndRow;
+ mvData[ni].bMarked = bMarked;
+ ni++;
+ nInsert = mrSheetLimits.GetMaxRowCount();
+ }
+ if ( ni < nj )
+ { // remove entries
+ mvData.erase(mvData.begin() + ni, mvData.begin() + nj);
+ }
+ }
+
+ if ( nInsert < sal::static_int_cast<SCSIZE>(mrSheetLimits.GetMaxRowCount()) )
+ { // insert or append new entry
+ if ( nInsert <= mvData.size() )
+ {
+ if ( !bSplit )
+ mvData.insert(mvData.begin() + nInsert, { nEndRow, bMarked });
+ else
+ {
+ mvData.insert(mvData.begin() + nInsert, 2, { nEndRow, bMarked });
+ mvData[nInsert+1] = mvData[nInsert-1];
+ }
+ }
+ else
+ mvData.push_back(ScMarkEntry{ nEndRow, bMarked });
+ if ( nInsert )
+ mvData[nInsert-1].nRow = nStartRow - 1;
+ }
+ }
+}
+
+/**
+ optimised init-from-range-list. Specifically this is optimised for cases
+ where we have very large data columns with lots and lots of ranges.
+*/
+void ScMarkArray::Set( std::vector<ScMarkEntry> && rMarkEntries )
+{
+ mvData = std::move(rMarkEntries);
+}
+
+bool ScMarkArray::IsAllMarked( SCROW nStartRow, SCROW nEndRow ) const
+{
+ SCSIZE nStartIndex;
+ SCSIZE nEndIndex;
+
+ if (Search( nStartRow, nStartIndex ))
+ if (mvData[nStartIndex].bMarked)
+ if (Search( nEndRow, nEndIndex ))
+ if (nEndIndex==nStartIndex)
+ return true;
+
+ return false;
+}
+
+bool ScMarkArray::HasOneMark( SCROW& rStartRow, SCROW& rEndRow ) const
+{
+ bool bRet = false;
+ if ( mvData.size() == 1 )
+ {
+ if ( mvData[0].bMarked )
+ {
+ rStartRow = 0;
+ rEndRow = mrSheetLimits.mnMaxRow;
+ bRet = true;
+ }
+ }
+ else if ( mvData.size() == 2 )
+ {
+ if ( mvData[0].bMarked )
+ {
+ rStartRow = 0;
+ rEndRow = mvData[0].nRow;
+ }
+ else
+ {
+ rStartRow = mvData[0].nRow + 1;
+ rEndRow = mrSheetLimits.mnMaxRow;
+ }
+ bRet = true;
+ }
+ else if ( mvData.size() == 3 )
+ {
+ if ( mvData[1].bMarked )
+ {
+ rStartRow = mvData[0].nRow + 1;
+ rEndRow = mvData[1].nRow;
+ bRet = true;
+ }
+ }
+ return bRet;
+}
+
+bool ScMarkArray::operator==( const ScMarkArray& rOther ) const
+{
+ return mvData == rOther.mvData;
+}
+
+ScMarkArray& ScMarkArray::operator=( const ScMarkArray& rOther )
+{
+ mvData = rOther.mvData;
+ return *this;
+}
+
+ScMarkArray& ScMarkArray::operator=(ScMarkArray&& rOther) noexcept
+{
+ mvData = std::move(rOther.mvData);
+ return *this;
+}
+
+SCROW ScMarkArray::GetNextMarked( SCROW nRow, bool bUp ) const
+{
+ SCROW nRet = nRow;
+ if (mrSheetLimits.ValidRow(nRow))
+ {
+ SCSIZE nIndex;
+ Search(nRow, nIndex);
+ if (!mvData[nIndex].bMarked)
+ {
+ if (bUp)
+ {
+ if (nIndex>0)
+ nRet = mvData[nIndex-1].nRow;
+ else
+ nRet = -1;
+ }
+ else
+ nRet = mvData[nIndex].nRow + 1;
+ }
+ }
+ return nRet;
+}
+
+SCROW ScMarkArray::GetMarkEnd( SCROW nRow, bool bUp ) const
+{
+ SCROW nRet;
+ SCSIZE nIndex;
+ Search(nRow, nIndex);
+ assert( mvData[nIndex].bMarked && "GetMarkEnd without bMarked" );
+ if (bUp)
+ {
+ if (nIndex>0)
+ nRet = mvData[nIndex-1].nRow + 1;
+ else
+ nRet = 0;
+ }
+ else
+ nRet = mvData[nIndex].nRow;
+
+ return nRet;
+}
+
+void ScMarkArray::Shift(SCROW nStartRow, tools::Long nOffset)
+{
+ if (nOffset == 0 || nStartRow > mrSheetLimits.mnMaxRow)
+ return;
+
+ for (size_t i=0; i < mvData.size(); ++i)
+ {
+ auto& rEntry = mvData[i];
+
+ if (rEntry.nRow < nStartRow)
+ continue;
+ rEntry.nRow += nOffset;
+ if (rEntry.nRow < 0)
+ {
+ rEntry.nRow = 0;
+ }
+ else if (rEntry.nRow > mrSheetLimits.mnMaxRow)
+ {
+ rEntry.nRow = mrSheetLimits.mnMaxRow;
+ }
+ }
+}
+
+void ScMarkArray::Intersect(const ScMarkArray& rOther)
+{
+ size_t i = 0;
+ size_t j = 0;
+
+ std::vector<ScMarkEntry> aEntryArray;
+ aEntryArray.reserve(std::max(mvData.size(), rOther.mvData.size()));
+
+ while (i < mvData.size() && j < rOther.mvData.size())
+ {
+ const auto& rEntry = mvData[i];
+ const auto& rOtherEntry = rOther.mvData[j];
+
+ if (rEntry.bMarked != rOtherEntry.bMarked)
+ {
+ if (!rOtherEntry.bMarked)
+ {
+ aEntryArray.push_back(rOther.mvData[j++]);
+ while (i < mvData.size() && mvData[i].nRow <= rOtherEntry.nRow)
+ ++i;
+ }
+ else // rEntry not marked
+ {
+ aEntryArray.push_back(mvData[i++]);
+ while (j < rOther.mvData.size() && rOther.mvData[j].nRow <= rEntry.nRow)
+ ++j;
+ }
+ }
+ else // rEntry.bMarked == rOtherEntry.bMarked
+ {
+ if (rEntry.bMarked) // both marked
+ {
+ if (rEntry.nRow <= rOtherEntry.nRow)
+ {
+ aEntryArray.push_back(mvData[i++]); // upper row
+ if (rEntry.nRow == rOtherEntry.nRow)
+ ++j;
+ }
+ else
+ {
+ aEntryArray.push_back(rOther.mvData[j++]); // upper row
+ }
+ }
+ else // both not marked
+ {
+ if (rEntry.nRow <= rOtherEntry.nRow)
+ {
+ aEntryArray.push_back(rOther.mvData[j++]); // lower row
+ while (i < mvData.size() && mvData[i].nRow <= rOtherEntry.nRow)
+ ++i;
+ }
+ else
+ {
+ aEntryArray.push_back(mvData[i++]); // lower row
+ while (j < rOther.mvData.size() && rOther.mvData[j].nRow <= rEntry.nRow)
+ ++j;
+ }
+ }
+ }
+ }
+
+ assert((i == mvData.size() || j == rOther.mvData.size()) && "Unexpected case.");
+
+ if (i == mvData.size())
+ {
+ aEntryArray.insert(aEntryArray.end(), rOther.mvData.begin() + j, rOther.mvData.end());
+ }
+ else // j == rOther.nCount
+ {
+ aEntryArray.insert(aEntryArray.end(), mvData.begin() + i, mvData.end());
+ }
+
+ mvData = std::move(aEntryArray);
+}
+
+
+// -------------- Iterator ----------------------------------------------
+
+ScMarkArrayIter::ScMarkArrayIter( const ScMarkArray* pNewArray ) :
+ pArray( pNewArray ),
+ nPos( 0 )
+{
+}
+
+void ScMarkArrayIter::reset( const ScMarkArray* pNewArray )
+{
+ pArray = pNewArray;
+ nPos = 0;
+}
+
+bool ScMarkArrayIter::Next( SCROW& rTop, SCROW& rBottom )
+{
+ if (!pArray)
+ return false;
+ if ( nPos >= pArray->mvData.size() )
+ return false;
+ while (!pArray->mvData[nPos].bMarked)
+ {
+ ++nPos;
+ if ( nPos >= pArray->mvData.size() )
+ return false;
+ }
+ rBottom = pArray->mvData[nPos].nRow;
+ if (nPos==0)
+ rTop = 0;
+ else
+ rTop = pArray->mvData[nPos-1].nRow + 1;
+ ++nPos;
+ return true;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sc/source/core/data/markdata.cxx b/sc/source/core/data/markdata.cxx
new file mode 100644
index 000000000..54379a554
--- /dev/null
+++ b/sc/source/core/data/markdata.cxx
@@ -0,0 +1,950 @@
+/* -*- 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 <memory>
+#include <markdata.hxx>
+#include <markarr.hxx>
+#include <markmulti.hxx>
+#include <rangelst.hxx>
+#include <segmenttree.hxx>
+#include <sheetlimits.hxx>
+#include <document.hxx>
+#include <columnspanset.hxx>
+#include <fstalgorithm.hxx>
+#include <unordered_map>
+
+#include <osl/diagnose.h>
+
+#include <mdds/flat_segment_tree.hpp>
+#include <cassert>
+
+
+ScMarkData::ScMarkData(const ScSheetLimits& rSheetLimits) :
+ aMultiSel(rSheetLimits),
+ mrSheetLimits(rSheetLimits)
+{
+ ResetMark();
+}
+
+ScMarkData& ScMarkData::operator=(const ScMarkData& rOther)
+{
+ maTabMarked = rOther.maTabMarked;
+ aMarkRange = rOther.aMarkRange;
+ aMultiRange = rOther.aMultiRange;
+ aMultiSel = rOther.aMultiSel;
+ aTopEnvelope = rOther.aTopEnvelope;
+ aBottomEnvelope = rOther.aBottomEnvelope;
+ aLeftEnvelope = rOther.aLeftEnvelope;
+ aRightEnvelope = rOther.aRightEnvelope;
+ bMarked = rOther.bMarked;
+ bMultiMarked = rOther.bMultiMarked;
+ bMarking = rOther.bMarking;
+ bMarkIsNeg = rOther.bMarkIsNeg;
+ return *this;
+}
+
+ScMarkData& ScMarkData::operator=(ScMarkData&& rOther)
+{
+ maTabMarked = std::move(rOther.maTabMarked);
+ aMarkRange = std::move(rOther.aMarkRange);
+ aMultiRange = std::move(rOther.aMultiRange);
+ aMultiSel = std::move(rOther.aMultiSel);
+ aTopEnvelope = std::move(rOther.aTopEnvelope);
+ aBottomEnvelope = std::move(rOther.aBottomEnvelope);
+ aLeftEnvelope = std::move(rOther.aLeftEnvelope);
+ aRightEnvelope = std::move(rOther.aRightEnvelope);
+ bMarked = rOther.bMarked;
+ bMultiMarked = rOther.bMultiMarked;
+ bMarking = rOther.bMarking;
+ bMarkIsNeg = rOther.bMarkIsNeg;
+ return *this;
+}
+
+
+void ScMarkData::ResetMark()
+{
+ aMultiSel.Clear();
+
+ bMarked = bMultiMarked = false;
+ bMarking = bMarkIsNeg = false;
+ aTopEnvelope.RemoveAll();
+ aBottomEnvelope.RemoveAll();
+ aLeftEnvelope.RemoveAll();
+ aRightEnvelope.RemoveAll();
+}
+
+void ScMarkData::SetMarkArea( const ScRange& rRange )
+{
+ aMarkRange = rRange;
+ aMarkRange.PutInOrder();
+ if ( !bMarked )
+ {
+ // Upon creation of a document ScFormatShell GetTextAttrState
+ // may query (default) attributes although no sheet is marked yet.
+ // => mark that one.
+ if ( !GetSelectCount() )
+ maTabMarked.insert( aMarkRange.aStart.Tab() );
+ bMarked = true;
+ }
+}
+
+void ScMarkData::SetMultiMarkArea( const ScRange& rRange, bool bMark, bool bSetupMulti )
+{
+ if ( aMultiSel.IsEmpty() )
+ {
+ // if simple mark range is set, copy to multi marks
+ if ( bMarked && !bMarkIsNeg && !bSetupMulti )
+ {
+ bMarked = false;
+ SCCOL nStartCol = aMarkRange.aStart.Col();
+ SCCOL nEndCol = aMarkRange.aEnd.Col();
+ PutInOrder( nStartCol, nEndCol );
+ SetMultiMarkArea( aMarkRange, true, true );
+ }
+ }
+
+ SCCOL nStartCol = rRange.aStart.Col();
+ SCROW nStartRow = rRange.aStart.Row();
+ SCCOL nEndCol = rRange.aEnd.Col();
+ SCROW nEndRow = rRange.aEnd.Row();
+ PutInOrder( nStartRow, nEndRow );
+ PutInOrder( nStartCol, nEndCol );
+
+ aMultiSel.SetMarkArea( nStartCol, nEndCol, nStartRow, nEndRow, bMark );
+
+ if ( bMultiMarked ) // Update aMultiRange
+ {
+ if ( nStartCol < aMultiRange.aStart.Col() )
+ aMultiRange.aStart.SetCol( nStartCol );
+ if ( nStartRow < aMultiRange.aStart.Row() )
+ aMultiRange.aStart.SetRow( nStartRow );
+ if ( nEndCol > aMultiRange.aEnd.Col() )
+ aMultiRange.aEnd.SetCol( nEndCol );
+ if ( nEndRow > aMultiRange.aEnd.Row() )
+ aMultiRange.aEnd.SetRow( nEndRow );
+ }
+ else
+ {
+ aMultiRange = rRange; // new
+ bMultiMarked = true;
+ }
+}
+
+void ScMarkData::SetAreaTab( SCTAB nTab )
+{
+ aMarkRange.aStart.SetTab(nTab);
+ aMarkRange.aEnd.SetTab(nTab);
+ aMultiRange.aStart.SetTab(nTab);
+ aMultiRange.aEnd.SetTab(nTab);
+}
+
+void ScMarkData::SelectTable( SCTAB nTab, bool bNew )
+{
+ if ( bNew )
+ {
+ maTabMarked.insert( nTab );
+ }
+ else
+ {
+ maTabMarked.erase( nTab );
+ }
+}
+
+bool ScMarkData::GetTableSelect( SCTAB nTab ) const
+{
+ return (maTabMarked.find( nTab ) != maTabMarked.end());
+}
+
+void ScMarkData::SelectOneTable( SCTAB nTab )
+{
+ maTabMarked.clear();
+ maTabMarked.insert( nTab );
+}
+
+SCTAB ScMarkData::GetSelectCount() const
+{
+ return static_cast<SCTAB> ( maTabMarked.size() );
+}
+
+SCTAB ScMarkData::GetFirstSelected() const
+{
+ if (!maTabMarked.empty())
+ return (*maTabMarked.begin());
+
+ OSL_FAIL("GetFirstSelected: nothing selected");
+ return 0;
+}
+
+SCTAB ScMarkData::GetLastSelected() const
+{
+ if (!maTabMarked.empty())
+ return (*maTabMarked.rbegin());
+
+ OSL_FAIL("GetLastSelected: nothing selected");
+ return 0;
+}
+
+void ScMarkData::SetSelectedTabs(const MarkedTabsType& rTabs)
+{
+ MarkedTabsType aTabs(rTabs.begin(), rTabs.end());
+ maTabMarked.swap(aTabs);
+}
+
+void ScMarkData::MarkToMulti()
+{
+ if ( bMarked && !bMarking )
+ {
+ SetMultiMarkArea( aMarkRange, !bMarkIsNeg );
+ bMarked = false;
+
+ // check if all multi mark ranges have been removed
+ if ( bMarkIsNeg && !HasAnyMultiMarks() )
+ ResetMark();
+ }
+}
+
+void ScMarkData::MarkToSimple()
+{
+ if ( bMarking )
+ return;
+
+ if ( bMultiMarked && bMarked )
+ MarkToMulti(); // may result in bMarked and bMultiMarked reset
+
+ if ( !bMultiMarked )
+ return;
+
+ ScRange aNew = aMultiRange;
+
+ bool bOk = false;
+ SCCOL nStartCol = aNew.aStart.Col();
+ SCCOL nEndCol = aNew.aEnd.Col();
+
+ while ( nStartCol < nEndCol && !aMultiSel.HasMarks( nStartCol ) )
+ ++nStartCol;
+ while ( nStartCol < nEndCol && !aMultiSel.HasMarks( nEndCol ) )
+ --nEndCol;
+
+ // Rows are only taken from MarkArray
+ SCROW nStartRow, nEndRow;
+ if ( aMultiSel.HasOneMark( nStartCol, nStartRow, nEndRow ) )
+ {
+ bOk = true;
+ SCROW nCmpStart, nCmpEnd;
+ for (SCCOL nCol=nStartCol+1; nCol<=nEndCol && bOk; nCol++)
+ if ( !aMultiSel.HasOneMark( nCol, nCmpStart, nCmpEnd )
+ || nCmpStart != nStartRow || nCmpEnd != nEndRow )
+ bOk = false;
+ }
+
+ if (bOk)
+ {
+ aNew.aStart.SetCol(nStartCol);
+ aNew.aStart.SetRow(nStartRow);
+ aNew.aEnd.SetCol(nEndCol);
+ aNew.aEnd.SetRow(nEndRow);
+
+ ResetMark();
+ aMarkRange = aNew;
+ bMarked = true;
+ bMarkIsNeg = false;
+ }
+}
+
+bool ScMarkData::IsCellMarked( SCCOL nCol, SCROW nRow, bool bNoSimple ) const
+{
+ if ( bMarked && !bNoSimple && !bMarkIsNeg )
+ if ( aMarkRange.aStart.Col() <= nCol && aMarkRange.aEnd.Col() >= nCol &&
+ aMarkRange.aStart.Row() <= nRow && aMarkRange.aEnd.Row() >= nRow )
+ return true;
+
+ if (bMultiMarked)
+ {
+ //TODO: test here for negative Marking ?
+
+ return aMultiSel.GetMark( nCol, nRow );
+ }
+
+ return false;
+}
+
+bool ScMarkData::IsColumnMarked( SCCOL nCol ) const
+{
+ // bMarkIsNeg meanwhile also for columns heads
+ //TODO: GetMarkColumnRanges for completely marked column
+
+ if ( bMarked && !bMarkIsNeg &&
+ aMarkRange.aStart.Col() <= nCol && aMarkRange.aEnd.Col() >= nCol &&
+ aMarkRange.aStart.Row() == 0 && aMarkRange.aEnd.Row() == mrSheetLimits.mnMaxRow )
+ return true;
+
+ if ( bMultiMarked && aMultiSel.IsAllMarked( nCol, 0, mrSheetLimits.mnMaxRow ) )
+ return true;
+
+ return false;
+}
+
+bool ScMarkData::IsRowMarked( SCROW nRow ) const
+{
+ // bMarkIsNeg meanwhile also for row heads
+ //TODO: GetMarkRowRanges for completely marked rows
+
+ if ( bMarked && !bMarkIsNeg &&
+ aMarkRange.aStart.Col() == 0 && aMarkRange.aEnd.Col() == mrSheetLimits.mnMaxCol &&
+ aMarkRange.aStart.Row() <= nRow && aMarkRange.aEnd.Row() >= nRow )
+ return true;
+
+ if ( bMultiMarked )
+ return aMultiSel.IsRowMarked( nRow );
+
+ return false;
+}
+
+void ScMarkData::MarkFromRangeList( const ScRangeList& rList, bool bReset )
+{
+ if (bReset)
+ {
+ maTabMarked.clear();
+ ResetMark();
+ }
+
+ size_t nCount = rList.size();
+ if ( nCount == 1 && !bMarked && !bMultiMarked )
+ {
+ const ScRange& rRange = rList[ 0 ];
+ SetMarkArea( rRange );
+ SelectTable( rRange.aStart.Tab(), true );
+ }
+ else
+ {
+ for (size_t i=0; i < nCount; i++)
+ {
+ const ScRange& rRange = rList[ i ];
+ SetMultiMarkArea( rRange );
+ SelectTable( rRange.aStart.Tab(), true );
+ }
+ }
+}
+
+/**
+ Optimise the case of constructing from a range list, speeds up import.
+*/
+ScMarkData::ScMarkData(const ScSheetLimits& rLimits, const ScRangeList& rList)
+ : aMultiSel(rLimits),
+ mrSheetLimits(rLimits)
+{
+ ResetMark();
+
+ for (const ScRange& rRange : rList)
+ maTabMarked.insert( rRange.aStart.Tab() );
+
+ if (rList.size() > 1)
+ {
+ bMultiMarked = true;
+ aMultiRange = rList.Combine();
+
+ aMultiSel.Set( rList );
+ }
+ else if (rList.size() == 1)
+ {
+ const ScRange& rRange = rList[ 0 ];
+ SetMarkArea( rRange );
+ }
+}
+
+
+void ScMarkData::FillRangeListWithMarks( ScRangeList* pList, bool bClear, SCTAB nForTab ) const
+{
+ if (!pList)
+ return;
+
+ if (bClear)
+ pList->RemoveAll();
+
+ //TODO: for multiple selected tables enter multiple ranges !!!
+
+ if ( bMultiMarked )
+ {
+ SCTAB nTab = (nForTab < 0 ? aMultiRange.aStart.Tab() : nForTab);
+
+ SCCOL nStartCol = aMultiRange.aStart.Col();
+ SCCOL nEndCol = aMultiRange.aEnd.Col();
+ for (SCCOL nCol=nStartCol; nCol<=nEndCol; nCol++)
+ {
+ if (aMultiSel.HasMarks( nCol ))
+ {
+ // Feeding column-wise fragments to ScRangeList::Join() is a
+ // huge bottleneck, speed this up for multiple columns
+ // consisting of identical row sets by building a column span
+ // first. This is usually the case for filtered data, for
+ // example.
+ SCCOL nToCol = nCol+1;
+ for ( ; nToCol <= nEndCol; ++nToCol)
+ {
+ if (!aMultiSel.HasEqualRowsMarked(nCol, nToCol))
+ break;
+ }
+ --nToCol;
+ ScRange aRange( nCol, 0, nTab, nToCol, 0, nTab );
+ SCROW nTop, nBottom;
+ ScMultiSelIter aMultiIter( aMultiSel, nCol );
+ while ( aMultiIter.Next( nTop, nBottom ) )
+ {
+ aRange.aStart.SetRow( nTop );
+ aRange.aEnd.SetRow( nBottom );
+ pList->Join( aRange );
+ }
+ nCol = nToCol;
+ }
+ }
+ }
+
+ if ( bMarked )
+ {
+ if (nForTab < 0)
+ pList->push_back( aMarkRange );
+ else
+ {
+ ScRange aRange( aMarkRange );
+ aRange.aStart.SetTab( nForTab );
+ aRange.aEnd.SetTab( nForTab );
+ pList->push_back( aRange );
+ }
+ }
+}
+
+void ScMarkData::ExtendRangeListTables( ScRangeList* pList ) const
+{
+ if (!pList)
+ return;
+
+ ScRangeList aOldList(*pList);
+ pList->RemoveAll(); //TODO: or skip the existing below
+
+ for (const auto& rTab : maTabMarked)
+ for ( size_t i=0, nCount = aOldList.size(); i<nCount; i++)
+ {
+ ScRange aRange = aOldList[ i ];
+ aRange.aStart.SetTab(rTab);
+ aRange.aEnd.SetTab(rTab);
+ pList->push_back( aRange );
+ }
+}
+
+ScRangeList ScMarkData::GetMarkedRanges() const
+{
+ ScRangeList aRet;
+ FillRangeListWithMarks(&aRet, false);
+ return aRet;
+}
+
+ScRangeList ScMarkData::GetMarkedRangesForTab( SCTAB nTab ) const
+{
+ ScRangeList aRet;
+ FillRangeListWithMarks(&aRet, false, nTab);
+ return aRet;
+}
+
+std::vector<sc::ColRowSpan> ScMarkData::GetMarkedRowSpans() const
+{
+ typedef mdds::flat_segment_tree<SCCOLROW, bool> SpansType;
+
+ ScRangeList aRanges = GetMarkedRanges();
+ SpansType aSpans(0, mrSheetLimits.mnMaxRow+1, false);
+ SpansType::const_iterator itPos = aSpans.begin();
+
+ for (size_t i = 0, n = aRanges.size(); i < n; ++i)
+ {
+ const ScRange& r = aRanges[i];
+ itPos = aSpans.insert(itPos, r.aStart.Row(), r.aEnd.Row()+1, true).first;
+ }
+
+ return sc::toSpanArray<SCCOLROW,sc::ColRowSpan>(aSpans);
+}
+
+std::vector<sc::ColRowSpan> ScMarkData::GetMarkedColSpans() const
+{
+
+ if (bMultiMarked)
+ {
+ SCCOL nStartCol = aMultiRange.aStart.Col();
+ SCCOL nEndCol = aMultiRange.aEnd.Col();
+ if (bMarked)
+ {
+ // Use segment tree to merge marked with multi marked.
+ typedef mdds::flat_segment_tree<SCCOLROW, bool> SpansType;
+ SpansType aSpans(0, mrSheetLimits.mnMaxCol+1, false);
+ SpansType::const_iterator itPos = aSpans.begin();
+ do
+ {
+ if (aMultiSel.GetRowSelArray().HasMarks())
+ {
+ itPos = aSpans.insert(itPos, nStartCol, nEndCol+1, true).first;
+ break; // do; all columns marked
+ }
+
+ /* XXX if it turns out that span insert is too slow for lots of
+ * subsequent columns we could gather each span first and then
+ * insert. */
+ for (SCCOL nCol = nStartCol; nCol <= nEndCol; ++nCol)
+ {
+ const ScMarkArray* pMultiArray = aMultiSel.GetMultiSelArray( nCol );
+ if (pMultiArray && pMultiArray->HasMarks())
+ itPos = aSpans.insert(itPos, nCol, nCol+1, true).first;
+ }
+ }
+ while(false);
+
+ // Merge marked.
+ aSpans.insert(itPos, aMarkRange.aStart.Col(), aMarkRange.aEnd.Col()+1, true);
+
+ return sc::toSpanArray<SCCOLROW,sc::ColRowSpan>(aSpans);
+ }
+ else
+ {
+ // A plain vector is sufficient, avoid segment tree and conversion
+ // to vector overhead.
+ std::vector<sc::ColRowSpan> aVec;
+ if (aMultiSel.GetRowSelArray().HasMarks())
+ {
+ aVec.emplace_back( nStartCol, nEndCol);
+ return aVec; // all columns marked
+ }
+ sc::ColRowSpan aSpan( -1, -1);
+ for (SCCOL nCol = nStartCol; nCol <= nEndCol; ++nCol)
+ {
+ const ScMarkArray* pMultiArray = aMultiSel.GetMultiSelArray( nCol );
+ if (pMultiArray && pMultiArray->HasMarks())
+ {
+ if (aSpan.mnStart == -1)
+ aSpan.mnStart = nCol;
+ aSpan.mnEnd = nCol;
+ }
+ else
+ {
+ // Add span gathered so far, if any.
+ if (aSpan.mnStart != -1)
+ {
+ aVec.push_back( aSpan);
+ aSpan.mnStart = -1;
+ }
+ }
+ }
+ // Add last span, if any.
+ if (aSpan.mnStart != -1)
+ aVec.push_back( aSpan);
+ return aVec;
+ }
+ }
+
+ // Only reached if not multi marked.
+ std::vector<sc::ColRowSpan> aVec;
+ if (bMarked)
+ {
+ aVec.emplace_back( aMarkRange.aStart.Col(), aMarkRange.aEnd.Col());
+ }
+ return aVec;
+}
+
+bool ScMarkData::IsAllMarked( const ScRange& rRange ) const
+{
+ SCCOL nStartCol = rRange.aStart.Col();
+ SCROW nStartRow = rRange.aStart.Row();
+ SCCOL nEndCol = rRange.aEnd.Col();
+ SCROW nEndRow = rRange.aEnd.Row();
+
+ if ( !bMultiMarked )
+ {
+ if ( bMarked && !bMarkIsNeg &&
+ aMarkRange.aStart.Col() <= nStartCol && aMarkRange.aEnd.Col() >= nEndCol &&
+ aMarkRange.aStart.Row() <= nStartRow && aMarkRange.aEnd.Row() >= nEndRow )
+ return true;
+ return false;
+ }
+
+ bool bOk = true;
+
+ if ( nStartCol == 0 && nEndCol == mrSheetLimits.mnMaxCol )
+ return aMultiSel.IsRowRangeMarked( nStartRow, nEndRow );
+
+ for (SCCOL nCol=nStartCol; nCol<=nEndCol && bOk; nCol++)
+ if ( !aMultiSel.IsAllMarked( nCol, nStartRow, nEndRow ) )
+ bOk = false;
+
+ return bOk;
+}
+
+SCCOL ScMarkData::GetStartOfEqualColumns( SCCOL nLastCol, SCCOL nMinCol ) const
+{
+ if( !bMultiMarked )
+ {
+ if ( bMarked && !bMarkIsNeg )
+ {
+ if( aMarkRange.aEnd.Col() >= nMinCol && aMarkRange.aStart.Col() < nLastCol )
+ return aMarkRange.aEnd.Col() + 1;
+ if( aMarkRange.aEnd.Col() >= nLastCol && aMarkRange.aStart.Col() <= nMinCol )
+ return aMarkRange.aStart.Col();
+ }
+ return nMinCol;
+ }
+ return aMultiSel.GetStartOfEqualColumns( nLastCol, nMinCol );
+}
+
+SCROW ScMarkData::GetNextMarked( SCCOL nCol, SCROW nRow, bool bUp ) const
+{
+ if ( !bMultiMarked )
+ return nRow;
+
+ return aMultiSel.GetNextMarked( nCol, nRow, bUp );
+}
+
+bool ScMarkData::HasMultiMarks( SCCOL nCol ) const
+{
+ if ( !bMultiMarked )
+ return false;
+
+ return aMultiSel.HasMarks( nCol );
+}
+
+bool ScMarkData::HasAnyMultiMarks() const
+{
+ if ( !bMultiMarked )
+ return false;
+
+ return aMultiSel.HasAnyMarks();
+}
+
+void ScMarkData::InsertTab( SCTAB nTab )
+{
+ std::set<SCTAB> tabMarked;
+ for (const auto& rTab : maTabMarked)
+ {
+ if (rTab < nTab)
+ tabMarked.insert(rTab);
+ else
+ tabMarked.insert(rTab + 1);
+ }
+ maTabMarked.swap(tabMarked);
+}
+
+void ScMarkData::DeleteTab( SCTAB nTab )
+{
+ std::set<SCTAB> tabMarked;
+ for (const auto& rTab : maTabMarked)
+ {
+ if (rTab < nTab)
+ tabMarked.insert(rTab);
+ else if (rTab > nTab)
+ tabMarked.insert(rTab - 1);
+ }
+ maTabMarked.swap(tabMarked);
+}
+
+void ScMarkData::ShiftCols(const ScDocument& rDoc, SCCOL nStartCol, sal_Int32 nColOffset)
+{
+ if (bMarked)
+ {
+ aMarkRange.IncColIfNotLessThan(rDoc, nStartCol, nColOffset);
+ }
+ else if (bMultiMarked)
+ {
+ aMultiSel.ShiftCols(nStartCol, nColOffset);
+ aMultiRange.IncColIfNotLessThan(rDoc, nStartCol, nColOffset);
+ }
+}
+
+void ScMarkData::ShiftRows(const ScDocument& rDoc, SCROW nStartRow, sal_Int32 nRowOffset)
+{
+ if (bMarked)
+ {
+ aMarkRange.IncRowIfNotLessThan(rDoc, nStartRow, nRowOffset);
+ }
+ else if (bMultiMarked)
+ {
+ aMultiSel.ShiftRows(nStartRow, nRowOffset);
+ aMultiRange.IncRowIfNotLessThan(rDoc, nStartRow, nRowOffset);
+ }
+}
+
+static void lcl_AddRanges(ScRange& rRangeDest, const ScRange& rNewRange )
+{
+ SCCOL nStartCol = rNewRange.aStart.Col();
+ SCROW nStartRow = rNewRange.aStart.Row();
+ SCCOL nEndCol = rNewRange.aEnd.Col();
+ SCROW nEndRow = rNewRange.aEnd.Row();
+ PutInOrder( nStartRow, nEndRow );
+ PutInOrder( nStartCol, nEndCol );
+ if ( nStartCol < rRangeDest.aStart.Col() )
+ rRangeDest.aStart.SetCol( nStartCol );
+ if ( nStartRow < rRangeDest.aStart.Row() )
+ rRangeDest.aStart.SetRow( nStartRow );
+ if ( nEndCol > rRangeDest.aEnd.Col() )
+ rRangeDest.aEnd.SetCol( nEndCol );
+ if ( nEndRow > rRangeDest.aEnd.Row() )
+ rRangeDest.aEnd.SetRow( nEndRow );
+}
+
+void ScMarkData::GetSelectionCover( ScRange& rRange )
+{
+ if( bMultiMarked )
+ {
+ rRange = aMultiRange;
+ SCCOL nStartCol = aMultiRange.aStart.Col(), nEndCol = aMultiRange.aEnd.Col();
+ PutInOrder( nStartCol, nEndCol );
+ nStartCol = ( nStartCol == 0 ) ? nStartCol : nStartCol - 1;
+ nEndCol = ( nEndCol == mrSheetLimits.mnMaxCol ) ? nEndCol : nEndCol + 1;
+ std::unique_ptr<ScFlatBoolRowSegments> pPrevColMarkedRows;
+ std::unique_ptr<ScFlatBoolRowSegments> pCurColMarkedRows;
+ std::unordered_map<SCROW,ScFlatBoolColSegments> aRowToColSegmentsInTopEnvelope;
+ std::unordered_map<SCROW,ScFlatBoolColSegments> aRowToColSegmentsInBottomEnvelope;
+ ScFlatBoolRowSegments aNoRowsMarked(mrSheetLimits.mnMaxRow);
+ aNoRowsMarked.setFalse( 0, mrSheetLimits.mnMaxRow );
+
+ bool bPrevColUnMarked = false;
+
+ for ( SCCOL nCol=nStartCol; nCol <= nEndCol; nCol++ )
+ {
+ SCROW nTop, nBottom;
+ bool bCurColUnMarked = !aMultiSel.HasMarks( nCol );
+ if ( !bCurColUnMarked )
+ {
+ pCurColMarkedRows.reset( new ScFlatBoolRowSegments(mrSheetLimits.mnMaxRow) );
+ pCurColMarkedRows->setFalse( 0, mrSheetLimits.mnMaxRow );
+ ScMultiSelIter aMultiIter( aMultiSel, nCol );
+ ScFlatBoolRowSegments::ForwardIterator aPrevItr(
+ pPrevColMarkedRows ? *pPrevColMarkedRows
+ : aNoRowsMarked); // For finding left envelope
+ ScFlatBoolRowSegments::ForwardIterator aPrevItr1(
+ pPrevColMarkedRows ? *pPrevColMarkedRows
+ : aNoRowsMarked); // For finding right envelope
+ SCROW nTopPrev = 0, nBottomPrev = 0; // For right envelope
+ while ( aMultiIter.Next( nTop, nBottom ) )
+ {
+ pCurColMarkedRows->setTrue( nTop, nBottom );
+ if( bPrevColUnMarked && ( nCol > nStartCol ))
+ {
+ ScRange aAddRange(nCol - 1, nTop, aMultiRange.aStart.Tab(),
+ nCol - 1, nBottom, aMultiRange.aStart.Tab());
+ lcl_AddRanges( rRange, aAddRange ); // Left envelope
+ aLeftEnvelope.push_back( aAddRange );
+ }
+ else if( nCol > nStartCol )
+ {
+ SCROW nTop1 = nTop, nBottom1 = nTop;
+ while( nTop1 <= nBottom && nBottom1 <= nBottom )
+ {
+ bool bRangeMarked = false;
+ const bool bHasValue = aPrevItr.getValue( nTop1, bRangeMarked );
+ assert(bHasValue); (void)bHasValue;
+ if( bRangeMarked )
+ {
+ nTop1 = aPrevItr.getLastPos() + 1;
+ nBottom1 = nTop1;
+ }
+ else
+ {
+ nBottom1 = aPrevItr.getLastPos();
+ if( nBottom1 > nBottom )
+ nBottom1 = nBottom;
+ ScRange aAddRange( nCol - 1, nTop1, aMultiRange.aStart.Tab(),
+ nCol - 1, nBottom1, aMultiRange.aStart.Tab() );
+ lcl_AddRanges( rRange, aAddRange ); // Left envelope
+ aLeftEnvelope.push_back( aAddRange );
+ nTop1 = ++nBottom1;
+ }
+ }
+ while( nTopPrev <= nBottom && nBottomPrev <= nBottom )
+ {
+ bool bRangeMarked;
+ const bool bHasValue = aPrevItr1.getValue( nTopPrev, bRangeMarked );
+ assert(bHasValue); (void)bHasValue;
+ if( bRangeMarked )
+ {
+ nBottomPrev = aPrevItr1.getLastPos();
+ if( nTopPrev < nTop )
+ {
+ if( nBottomPrev >= nTop )
+ {
+ nBottomPrev = nTop - 1;
+ ScRange aAddRange( nCol, nTopPrev, aMultiRange.aStart.Tab(),
+ nCol, nBottomPrev, aMultiRange.aStart.Tab());
+ lcl_AddRanges( rRange, aAddRange ); // Right envelope
+ aRightEnvelope.push_back( aAddRange );
+ nTopPrev = nBottomPrev = (nBottom + 1);
+ }
+ else
+ {
+ ScRange aAddRange( nCol, nTopPrev, aMultiRange.aStart.Tab(),
+ nCol, nBottomPrev, aMultiRange.aStart.Tab());
+ lcl_AddRanges( rRange, aAddRange ); // Right envelope
+ aRightEnvelope.push_back( aAddRange );
+ nTopPrev = ++nBottomPrev;
+ }
+ }
+ else
+ nTopPrev = nBottomPrev = ( nBottom + 1 );
+ }
+ else
+ {
+ nBottomPrev = aPrevItr1.getLastPos();
+ nTopPrev = ++nBottomPrev;
+ }
+ }
+ }
+ if( nTop )
+ {
+ ScRange aAddRange( nCol, nTop - 1, aMultiRange.aStart.Tab(),
+ nCol, nTop - 1, aMultiRange.aStart.Tab());
+ lcl_AddRanges( rRange, aAddRange ); // Top envelope
+ auto it = aRowToColSegmentsInTopEnvelope.find(nTop - 1);
+ if (it == aRowToColSegmentsInTopEnvelope.end())
+ it = aRowToColSegmentsInTopEnvelope.emplace(nTop - 1, ScFlatBoolColSegments(mrSheetLimits.mnMaxCol)).first;
+ it->second.setTrue( nCol, nCol );
+ }
+ if( nBottom < mrSheetLimits.mnMaxRow )
+ {
+ ScRange aAddRange(nCol, nBottom + 1, aMultiRange.aStart.Tab(),
+ nCol, nBottom + 1, aMultiRange.aStart.Tab());
+ lcl_AddRanges( rRange, aAddRange ); // Bottom envelope
+ auto it = aRowToColSegmentsInBottomEnvelope.find(nBottom + 1);
+ if (it == aRowToColSegmentsInBottomEnvelope.end())
+ it = aRowToColSegmentsInBottomEnvelope.emplace(nBottom + 1, ScFlatBoolColSegments(mrSheetLimits.mnMaxCol)).first;
+ it->second.setTrue( nCol, nCol );
+ }
+ }
+
+ while( nTopPrev <= mrSheetLimits.mnMaxRow && nBottomPrev <= mrSheetLimits.mnMaxRow && ( nCol > nStartCol ) )
+ {
+ bool bRangeMarked;
+ const bool bHasValue = aPrevItr1.getValue( nTopPrev, bRangeMarked );
+ assert(bHasValue); (void)bHasValue;
+ if( bRangeMarked )
+ {
+ nBottomPrev = aPrevItr1.getLastPos();
+ ScRange aAddRange(nCol, nTopPrev, aMultiRange.aStart.Tab(),
+ nCol, nBottomPrev, aMultiRange.aStart.Tab());
+ lcl_AddRanges( rRange, aAddRange ); // Right envelope
+ aRightEnvelope.push_back( aAddRange );
+ nTopPrev = ++nBottomPrev;
+ }
+ else
+ {
+ nBottomPrev = aPrevItr1.getLastPos();
+ nTopPrev = ++nBottomPrev;
+ }
+ }
+ }
+ else if( nCol > nStartCol )
+ {
+ bPrevColUnMarked = true;
+ SCROW nTopPrev = 0, nBottomPrev = 0;
+ bool bRangeMarked = false;
+ ScFlatBoolRowSegments::ForwardIterator aPrevItr(
+ pPrevColMarkedRows ? *pPrevColMarkedRows : aNoRowsMarked);
+ while( nTopPrev <= mrSheetLimits.mnMaxRow && nBottomPrev <= mrSheetLimits.mnMaxRow )
+ {
+ const bool bHasValue = aPrevItr.getValue(nTopPrev, bRangeMarked);
+ assert(bHasValue); (void)bHasValue;
+ if( bRangeMarked )
+ {
+ nBottomPrev = aPrevItr.getLastPos();
+ ScRange aAddRange(nCol, nTopPrev, aMultiRange.aStart.Tab(),
+ nCol, nBottomPrev, aMultiRange.aStart.Tab());
+ lcl_AddRanges( rRange, aAddRange ); // Right envelope
+ aRightEnvelope.push_back( aAddRange );
+ nTopPrev = ++nBottomPrev;
+ }
+ else
+ {
+ nBottomPrev = aPrevItr.getLastPos();
+ nTopPrev = ++nBottomPrev;
+ }
+ }
+ }
+ if ( bCurColUnMarked )
+ pPrevColMarkedRows.reset();
+ else
+ pPrevColMarkedRows = std::move( pCurColMarkedRows );
+ }
+ for( auto& rKV : aRowToColSegmentsInTopEnvelope )
+ {
+ SCCOL nStart = nStartCol;
+ ScFlatBoolColSegments::RangeData aRange;
+ while( nStart <= nEndCol )
+ {
+ if( !rKV.second.getRangeData( nStart, aRange ) )
+ break;
+ if( aRange.mbValue ) // is marked
+ aTopEnvelope.push_back( ScRange( aRange.mnCol1, rKV.first, aMultiRange.aStart.Tab(),
+ aRange.mnCol2, rKV.first, aMultiRange.aStart.Tab() ) );
+ nStart = aRange.mnCol2 + 1;
+ }
+ }
+ for( auto& rKV : aRowToColSegmentsInBottomEnvelope )
+ {
+ SCCOL nStart = nStartCol;
+ ScFlatBoolColSegments::RangeData aRange;
+ while( nStart <= nEndCol )
+ {
+ if( !rKV.second.getRangeData( nStart, aRange ) )
+ break;
+ if( aRange.mbValue ) // is marked
+ aBottomEnvelope.push_back( ScRange( aRange.mnCol1, rKV.first, aMultiRange.aStart.Tab(),
+ aRange.mnCol2, rKV.first, aMultiRange.aStart.Tab() ) );
+ nStart = aRange.mnCol2 + 1;
+ }
+ }
+ }
+ else if( bMarked )
+ {
+ aMarkRange.PutInOrder();
+ SCROW nRow1, nRow2, nRow1New, nRow2New;
+ SCCOL nCol1, nCol2, nCol1New, nCol2New;
+ SCTAB nTab1, nTab2;
+ aMarkRange.GetVars( nCol1, nRow1, nTab1, nCol2, nRow2, nTab2 );
+ nCol1New = nCol1;
+ nCol2New = nCol2;
+ nRow1New = nRow1;
+ nRow2New = nRow2;
+ // Each envelope will have zero or more ranges for single rectangle selection.
+ if( nCol1 > 0 )
+ {
+ aLeftEnvelope.push_back( ScRange( nCol1 - 1, nRow1, nTab1, nCol1 - 1, nRow2, nTab2 ) );
+ --nCol1New;
+ }
+ if( nRow1 > 0 )
+ {
+ aTopEnvelope.push_back( ScRange( nCol1, nRow1 - 1, nTab1, nCol2, nRow1 - 1, nTab2 ) );
+ --nRow1New;
+ }
+ if( nCol2 < mrSheetLimits.mnMaxCol )
+ {
+ aRightEnvelope.push_back( ScRange( nCol2 + 1, nRow1, nTab1, nCol2 + 1, nRow2, nTab2 ) );
+ ++nCol2New;
+ }
+ if( nRow2 < mrSheetLimits.mnMaxRow )
+ {
+ aBottomEnvelope.push_back( ScRange( nCol1, nRow2 + 1, nTab1, nCol2, nRow2 + 1, nTab2 ) );
+ ++nRow2New;
+ }
+ rRange = ScRange( nCol1New, nRow1New, nTab1, nCol2New, nRow2New, nTab2 );
+ }
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sc/source/core/data/markmulti.cxx b/sc/source/core/data/markmulti.cxx
new file mode 100644
index 000000000..4c92f5f25
--- /dev/null
+++ b/sc/source/core/data/markmulti.cxx
@@ -0,0 +1,485 @@
+/* -*- 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 <markmulti.hxx>
+#include <markarr.hxx>
+#include <rangelst.hxx>
+#include <segmenttree.hxx>
+#include <sheetlimits.hxx>
+
+#include <o3tl/safeint.hxx>
+
+#include <algorithm>
+
+ScMultiSel::ScMultiSel(const ScSheetLimits& rSheetLimits)
+ : aRowSel(rSheetLimits), mrSheetLimits(rSheetLimits)
+{
+}
+
+ScMultiSel& ScMultiSel::operator=(const ScMultiSel& rOther)
+{
+ aMultiSelContainer = rOther.aMultiSelContainer;
+ aRowSel = rOther.aRowSel;
+ return *this;
+}
+
+ScMultiSel& ScMultiSel::operator=(ScMultiSel&& rOther)
+{
+ aMultiSelContainer = std::move(rOther.aMultiSelContainer);
+ aRowSel = std::move(rOther.aRowSel);
+ return *this;
+}
+
+
+void ScMultiSel::Clear()
+{
+ aMultiSelContainer.clear();
+ aRowSel.Reset();
+}
+
+SCCOL ScMultiSel::GetMultiSelectionCount() const
+{
+ SCCOL nCount = 0;
+ for (const auto & i : aMultiSelContainer)
+ if (i.HasMarks())
+ ++nCount;
+ return nCount;
+}
+
+bool ScMultiSel::HasMarks( SCCOL nCol ) const
+{
+ if ( aRowSel.HasMarks() )
+ return true;
+ return nCol < static_cast<SCCOL>(aMultiSelContainer.size()) && aMultiSelContainer[nCol].HasMarks();
+}
+
+bool ScMultiSel::HasOneMark( SCCOL nCol, SCROW& rStartRow, SCROW& rEndRow ) const
+{
+ SCROW nRow1 = -1, nRow2 = -1, nRow3 = -1, nRow4 = -1;
+ bool aResult1 = aRowSel.HasOneMark( nRow1, nRow2 );
+ bool aResult2 = nCol < static_cast<SCCOL>(aMultiSelContainer.size())
+ && aMultiSelContainer[nCol].HasOneMark( nRow3, nRow4 );
+
+ if ( aResult1 || aResult2 )
+ {
+ if ( aResult1 && aResult2 )
+ {
+ if ( ( nRow2 + 1 ) < nRow3 )
+ return false;
+ if ( ( nRow4 + 1 ) < nRow1 )
+ return false;
+
+ auto aRows = std::minmax( { nRow1, nRow2, nRow3, nRow4 } );
+ rStartRow = aRows.first;
+ rEndRow = aRows.second;
+ return true;
+ }
+ if ( aResult1 )
+ {
+ rStartRow = nRow1;
+ rEndRow = nRow2;
+ return true;
+ }
+
+ rStartRow = nRow3;
+ rEndRow = nRow4;
+ return true;
+ }
+
+ return false;
+}
+
+bool ScMultiSel::GetMark( SCCOL nCol, SCROW nRow ) const
+{
+ if ( aRowSel.GetMark( nRow ) )
+ return true;
+ return nCol < static_cast<SCCOL>(aMultiSelContainer.size()) && aMultiSelContainer[nCol].GetMark(nRow);
+}
+
+bool ScMultiSel::IsAllMarked( SCCOL nCol, SCROW nStartRow, SCROW nEndRow ) const
+{
+ bool bHasMarks1 = aRowSel.HasMarks();
+ bool bHasMarks2 = nCol < static_cast<SCCOL>(aMultiSelContainer.size()) && aMultiSelContainer[nCol].HasMarks();
+
+ if ( !bHasMarks1 && !bHasMarks2 )
+ return false;
+
+ if ( bHasMarks1 && bHasMarks2 )
+ {
+ if ( aRowSel.IsAllMarked( nStartRow, nEndRow ) ||
+ aMultiSelContainer[nCol].IsAllMarked( nStartRow, nEndRow ) )
+ return true;
+ ScMultiSelIter aMultiIter( *this, nCol );
+ ScFlatBoolRowSegments::RangeData aRowRange;
+ bool bRet = aMultiIter.GetRangeData( nStartRow, aRowRange );
+ return bRet && aRowRange.mbValue && aRowRange.mnRow2 >= nEndRow;
+ }
+
+ if ( bHasMarks1 )
+ return aRowSel.IsAllMarked( nStartRow, nEndRow );
+
+ return aMultiSelContainer[nCol].IsAllMarked( nStartRow, nEndRow );
+}
+
+bool ScMultiSel::HasEqualRowsMarked( SCCOL nCol1, SCCOL nCol2 ) const
+{
+ bool bCol1Exists = nCol1 < static_cast<SCCOL>(aMultiSelContainer.size());
+ bool bCol2Exists = nCol2 < static_cast<SCCOL>(aMultiSelContainer.size());
+ if ( bCol1Exists || bCol2Exists )
+ {
+ if ( bCol1Exists && bCol2Exists )
+ return aMultiSelContainer[nCol1] == aMultiSelContainer[nCol2];
+ else if ( bCol1Exists )
+ return !aMultiSelContainer[nCol1].HasMarks();
+ else
+ return !aMultiSelContainer[nCol2].HasMarks();
+ }
+
+ return true;
+}
+
+SCCOL ScMultiSel::GetStartOfEqualColumns( SCCOL nLastCol, SCCOL nMinCol ) const
+{
+ if( nMinCol > nLastCol )
+ return nMinCol;
+ if( nLastCol >= static_cast<SCCOL>(aMultiSelContainer.size()))
+ {
+ if( nMinCol >= static_cast<SCCOL>(aMultiSelContainer.size()))
+ return nMinCol;
+ SCCOL nCol = static_cast<SCCOL>(aMultiSelContainer.size()) - 1;
+ while( nCol >= nMinCol && aMultiSelContainer[nCol] == aRowSel )
+ --nCol;
+ return nCol + 1;
+ }
+ SCCOL nCol = nLastCol - 1;
+ while( nCol >= nMinCol && aMultiSelContainer[nCol] == aMultiSelContainer[nLastCol] )
+ --nCol;
+ return nCol + 1;
+}
+
+SCROW ScMultiSel::GetNextMarked( SCCOL nCol, SCROW nRow, bool bUp ) const
+{
+ if ( nCol >= static_cast<SCCOL>(aMultiSelContainer.size()) || !aMultiSelContainer[nCol].HasMarks() )
+ return aRowSel.GetNextMarked( nRow, bUp );
+
+ SCROW nRow1, nRow2;
+ nRow1 = aRowSel.GetNextMarked( nRow, bUp );
+ nRow2 = aMultiSelContainer[nCol].GetNextMarked( nRow, bUp );
+ if ( nRow1 == nRow2 )
+ return nRow1;
+ if ( nRow1 == -1 )
+ return nRow2;
+ if ( nRow2 == -1 )
+ return nRow1;
+
+ PutInOrder( nRow1, nRow2 );
+ return ( bUp ? nRow2 : nRow1 );
+}
+
+void ScMultiSel::MarkAllCols( SCROW nStartRow, SCROW nEndRow )
+{
+ aMultiSelContainer.resize(mrSheetLimits.mnMaxCol+1, ScMarkArray(mrSheetLimits));
+ for ( SCCOL nCol = mrSheetLimits.mnMaxCol; nCol >= 0; --nCol )
+ {
+ aMultiSelContainer[nCol].SetMarkArea( nStartRow, nEndRow, true );
+ }
+}
+
+void ScMultiSel::SetMarkArea( SCCOL nStartCol, SCCOL nEndCol, SCROW nStartRow, SCROW nEndRow, bool bMark )
+{
+ if ( nStartCol == 0 && nEndCol == mrSheetLimits.mnMaxCol )
+ {
+ aRowSel.SetMarkArea( nStartRow, nEndRow, bMark );
+ if ( !bMark )
+ {
+ // Remove any per column marks for the row range.
+ for ( auto& aIter : aMultiSelContainer )
+ if ( aIter.HasMarks() )
+ aIter.SetMarkArea( nStartRow, nEndRow, false );
+ }
+ return;
+ }
+
+ // Bad case - we need to extend aMultiSelContainer size to MAXCOL
+ // and move row marks from aRowSel to aMultiSelContainer
+ if ( !bMark && aRowSel.HasMarks() )
+ {
+ SCROW nBeg, nLast = nEndRow;
+ if ( aRowSel.GetMark( nStartRow ) )
+ {
+ nBeg = nStartRow;
+ nLast = aRowSel.GetMarkEnd( nStartRow, false );
+ }
+ else
+ {
+ nBeg = aRowSel.GetNextMarked( nStartRow, false );
+ if ( nBeg != mrSheetLimits.GetMaxRowCount() )
+ nLast = aRowSel.GetMarkEnd( nBeg, false );
+ }
+
+ if ( nBeg != mrSheetLimits.GetMaxRowCount() && nLast >= nEndRow && nBeg <= nEndRow )
+ MarkAllCols( nBeg, nEndRow );
+ else
+ {
+ while ( nBeg != mrSheetLimits.GetMaxRowCount() && nLast < nEndRow )
+ {
+ MarkAllCols( nBeg, nLast );
+ nBeg = aRowSel.GetNextMarked( nLast + 1, false );
+ if ( nBeg != mrSheetLimits.GetMaxRowCount() )
+ nLast = aRowSel.GetMarkEnd( nBeg, false );
+ }
+ if ( nBeg != mrSheetLimits.GetMaxRowCount() && nLast >= nEndRow && nBeg <= nEndRow )
+ MarkAllCols( nBeg, nEndRow );
+ }
+
+ aRowSel.SetMarkArea( nStartRow, nEndRow, false );
+ }
+
+ if (nEndCol >= static_cast<SCCOL>(aMultiSelContainer.size()))
+ aMultiSelContainer.resize(nEndCol+1, ScMarkArray(mrSheetLimits));
+ for ( SCCOL nColIter = nEndCol; nColIter >= nStartCol; --nColIter )
+ aMultiSelContainer[nColIter].SetMarkArea( nStartRow, nEndRow, bMark );
+}
+
+/**
+ optimised init-from-range-list. Specifically this is optimised for cases
+ where we have very large data columns with lots and lots of ranges.
+*/
+void ScMultiSel::Set( ScRangeList const & rList )
+{
+ Clear();
+ if (rList.size() == 0)
+ return;
+
+ // sort by row to make the combining/merging faster
+ auto aNewList = rList;
+ std::sort(aNewList.begin(), aNewList.end(),
+ [](const ScRange& lhs, const ScRange& rhs)
+ {
+ return lhs.aStart.Row() < rhs.aStart.Row();
+ });
+
+ std::vector<std::vector<ScMarkEntry>> aMarkEntriesPerCol(mrSheetLimits.mnMaxCol+1);
+
+ SCCOL nMaxCol = -1;
+ for (const ScRange& rRange : aNewList)
+ {
+ SCCOL nStartCol = rRange.aStart.Col();
+ SCROW nStartRow = rRange.aStart.Row();
+ SCCOL nEndCol = rRange.aEnd.Col();
+ SCROW nEndRow = rRange.aEnd.Row();
+ assert( nEndRow >= nStartRow && "this method assumes the input data has ranges with endrow>=startrow");
+ assert( nEndCol >= nStartCol && "this method assumes the input data has ranges with endcol>=startcol");
+ if ( nStartCol == 0 && nEndCol == mrSheetLimits.mnMaxCol )
+ aRowSel.SetMarkArea( nStartRow, nEndRow, /*bMark*/true );
+ else
+ {
+ for ( SCCOL nCol = nStartCol; nCol <= nEndCol; ++nCol )
+ {
+ auto & rMarkEntries = aMarkEntriesPerCol[nCol];
+ int nEntries = rMarkEntries.size();
+ if (nEntries > 1 && nStartRow >= rMarkEntries[nEntries-2].nRow+1
+ && nStartRow <= rMarkEntries[nEntries-1].nRow+1)
+ {
+ // overlaps or directly adjacent previous range
+ rMarkEntries.back().nRow = std::max(nEndRow, rMarkEntries.back().nRow);
+ }
+ else
+ {
+ // new range
+ if (nStartRow > 0)
+ rMarkEntries.emplace_back(ScMarkEntry{nStartRow-1, false});
+ rMarkEntries.emplace_back(ScMarkEntry{nEndRow, true});
+ }
+ }
+ nMaxCol = std::max(nMaxCol, nEndCol);
+ }
+ }
+
+ aMultiSelContainer.resize(nMaxCol+1, ScMarkArray(mrSheetLimits));
+ for (SCCOL nCol = 0; nCol<=nMaxCol; ++nCol)
+ if (!aMarkEntriesPerCol[nCol].empty())
+ aMultiSelContainer[nCol].Set( std::move(aMarkEntriesPerCol[nCol]) );
+}
+
+bool ScMultiSel::IsRowMarked( SCROW nRow ) const
+{
+ return aRowSel.GetMark( nRow );
+}
+
+bool ScMultiSel::IsRowRangeMarked( SCROW nStartRow, SCROW nEndRow ) const
+{
+ if ( !aRowSel.GetMark( nStartRow ) )
+ return false;
+ SCROW nLast = aRowSel.GetMarkEnd( nStartRow, false );
+ return ( nLast >= nEndRow );
+}
+
+ScMarkArray ScMultiSel::GetMarkArray( SCCOL nCol ) const
+{
+ ScMultiSelIter aMultiIter( *this, nCol );
+ ScMarkArray aMarkArray(mrSheetLimits);
+ SCROW nTop, nBottom;
+ while( aMultiIter.Next( nTop, nBottom ) )
+ aMarkArray.SetMarkArea( nTop, nBottom, true );
+ return aMarkArray;
+}
+
+bool ScMultiSel::HasAnyMarks() const
+{
+ if ( aRowSel.HasMarks() )
+ return true;
+ for ( const auto& aPair : aMultiSelContainer )
+ if ( aPair.HasMarks() )
+ return true;
+ return false;
+}
+
+void ScMultiSel::ShiftCols(SCCOL nStartCol, sal_Int32 nColOffset)
+{
+ if (nStartCol > mrSheetLimits.mnMaxCol)
+ return;
+
+ ScMultiSel aNewMultiSel(*this);
+ Clear();
+
+ if (nColOffset < 0)
+ {
+ // columns that would be moved on the left of nStartCol must be removed
+ const SCCOL nEndPos = std::min<SCCOL>(aNewMultiSel.aMultiSelContainer.size(), nStartCol - nColOffset);
+ for (SCCOL nSearchPos = nStartCol; nSearchPos < nEndPos; ++nSearchPos)
+ aNewMultiSel.aMultiSelContainer[nSearchPos].Reset();
+ }
+
+ SCCOL nCol = 0;
+ for (const auto& aSourceArray : aNewMultiSel.aMultiSelContainer)
+ {
+ SCCOL nDestCol = nCol;
+ if (nDestCol >= nStartCol)
+ {
+ nDestCol += nColOffset;
+ if (nDestCol < 0)
+ nDestCol = 0;
+ else if (nDestCol > mrSheetLimits.mnMaxCol)
+ nDestCol = mrSheetLimits.mnMaxCol;
+ }
+ if (nDestCol >= static_cast<SCCOL>(aMultiSelContainer.size()))
+ aMultiSelContainer.resize(nDestCol, ScMarkArray(mrSheetLimits));
+ aMultiSelContainer[nDestCol] = aSourceArray;
+ ++nCol;
+ }
+ aRowSel = aNewMultiSel.aRowSel;
+
+ if (!(nColOffset > 0 && nStartCol > 0 && o3tl::make_unsigned(nStartCol) < aNewMultiSel.aMultiSelContainer.size()))
+ return;
+
+ // insert nColOffset new columns, and select their cells if they are selected
+ // both in the old column at nStartPos and in the previous column
+ auto& rPrevPos = aNewMultiSel.aMultiSelContainer[nStartCol - 1];
+ auto& rStartPos = aNewMultiSel.aMultiSelContainer[nStartCol];
+ auto& rNewCol = aMultiSelContainer[nStartCol];
+ rNewCol = rStartPos;
+ rNewCol.Intersect(rPrevPos);
+ if (nStartCol + nColOffset >= static_cast<SCCOL>(aNewMultiSel.aMultiSelContainer.size()))
+ aNewMultiSel.aMultiSelContainer.resize(nStartCol + nColOffset, ScMarkArray(mrSheetLimits));
+ for (tools::Long i = 1; i < nColOffset; ++i)
+ aMultiSelContainer[nStartCol + i] = rNewCol;
+}
+
+void ScMultiSel::ShiftRows(SCROW nStartRow, sal_Int32 nRowOffset)
+{
+ for (auto& aPair: aMultiSelContainer)
+ aPair.Shift(nStartRow, nRowOffset);
+ aRowSel.Shift(nStartRow, nRowOffset);
+}
+
+const ScMarkArray* ScMultiSel::GetMultiSelArray( SCCOL nCol ) const
+{
+ if (nCol >= static_cast<SCCOL>(aMultiSelContainer.size()))
+ return nullptr;
+ return &aMultiSelContainer[nCol];
+}
+
+ScMultiSelIter::ScMultiSelIter( const ScMultiSel& rMultiSel, SCCOL nCol ) :
+ aMarkArrayIter(nullptr),
+ nNextSegmentStart(0)
+{
+ bool bHasMarks1 = rMultiSel.aRowSel.HasMarks();
+ bool bHasMarks2 = nCol < static_cast<SCCOL>(rMultiSel.aMultiSelContainer.size())
+ && rMultiSel.aMultiSelContainer[nCol].HasMarks();
+
+ if (bHasMarks1 && bHasMarks2)
+ {
+ pRowSegs.reset( new ScFlatBoolRowSegments(rMultiSel.mrSheetLimits.mnMaxRow) );
+ pRowSegs->setFalse( 0, rMultiSel.mrSheetLimits.mnMaxRow );
+ {
+ ScMarkArrayIter aMarkIter( &rMultiSel.aRowSel );
+ SCROW nTop, nBottom;
+ while ( aMarkIter.Next( nTop, nBottom ) )
+ pRowSegs->setTrue( nTop, nBottom );
+ }
+
+ {
+ ScMarkArrayIter aMarkIter( &rMultiSel.aMultiSelContainer[nCol] );
+ SCROW nTop, nBottom;
+ while ( aMarkIter.Next( nTop, nBottom ) )
+ pRowSegs->setTrue( nTop, nBottom );
+ }
+ }
+ else if (bHasMarks1)
+ {
+ aMarkArrayIter.reset( &rMultiSel.aRowSel);
+ }
+ else if (bHasMarks2)
+ {
+ aMarkArrayIter.reset( &rMultiSel.aMultiSelContainer[nCol]);
+ }
+}
+
+bool ScMultiSelIter::Next( SCROW& rTop, SCROW& rBottom )
+{
+ if (pRowSegs)
+ {
+ ScFlatBoolRowSegments::RangeData aRowRange;
+ bool bRet = pRowSegs->getRangeData( nNextSegmentStart, aRowRange );
+ if ( bRet && !aRowRange.mbValue )
+ {
+ nNextSegmentStart = aRowRange.mnRow2 + 1;
+ bRet = pRowSegs->getRangeData( nNextSegmentStart, aRowRange );
+ }
+ if ( bRet )
+ {
+ rTop = aRowRange.mnRow1;
+ rBottom = aRowRange.mnRow2;
+ nNextSegmentStart = rBottom + 1;
+ }
+ return bRet;
+ }
+
+ return aMarkArrayIter.Next( rTop, rBottom);
+}
+
+bool ScMultiSelIter::GetRangeData( SCROW nRow, ScFlatBoolRowSegments::RangeData& rRowRange ) const
+{
+ assert(pRowSegs);
+ return pRowSegs->getRangeData( nRow, rRowRange);
+}
+
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sc/source/core/data/mtvcellfunc.cxx b/sc/source/core/data/mtvcellfunc.cxx
new file mode 100644
index 000000000..98f6998cc
--- /dev/null
+++ b/sc/source/core/data/mtvcellfunc.cxx
@@ -0,0 +1,31 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * 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/.
+ */
+
+#include <mtvcellfunc.hxx>
+
+namespace sc {
+
+CellStoreType::iterator ProcessFormula(
+ const CellStoreType::iterator& it, CellStoreType& rStore, SCROW nRow1, SCROW nRow2,
+ std::function<void(size_t,ScFormulaCell*)> aFuncElem )
+{
+ using FuncType = std::function<void(size_t,ScFormulaCell*)>;
+ using ElseFuncType = std::function<void(mdds::mtv::element_t, size_t, size_t)>;
+
+ // empty function for handling the 'else' part.
+ static ElseFuncType aFuncElse =
+ [](mdds::mtv::element_t,size_t,size_t) {};
+
+ return ProcessElements1<
+ CellStoreType, formula_block,
+ FuncType, ElseFuncType>(
+ it, rStore, nRow1, nRow2, aFuncElem, aFuncElse);
+}
+
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sc/source/core/data/mtvelements.cxx b/sc/source/core/data/mtvelements.cxx
new file mode 100644
index 000000000..2c2c8daa6
--- /dev/null
+++ b/sc/source/core/data/mtvelements.cxx
@@ -0,0 +1,198 @@
+/* -*- 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/.
+ */
+
+#include <mtvelements.hxx>
+#include <document.hxx>
+#include <cellvalue.hxx>
+#include <column.hxx>
+#include <table.hxx>
+
+#include <sstream>
+
+namespace sc {
+
+CellStoreEvent::CellStoreEvent() : mpCol(nullptr) {}
+
+CellStoreEvent::CellStoreEvent(ScColumn* pCol) : mpCol(pCol) {}
+
+void CellStoreEvent::element_block_acquired(const mdds::mtv::base_element_block* block)
+{
+ if (!mpCol)
+ return;
+
+ switch (mdds::mtv::get_block_type(*block))
+ {
+ case sc::element_type_formula:
+ ++mpCol->mnBlkCountFormula;
+ break;
+ default:
+ ;
+ }
+}
+
+void CellStoreEvent::element_block_released(const mdds::mtv::base_element_block* block)
+{
+ if (!mpCol)
+ return;
+
+ switch (mdds::mtv::get_block_type(*block))
+ {
+ case sc::element_type_formula:
+ --mpCol->mnBlkCountFormula;
+ break;
+ default:
+ ;
+ }
+}
+
+void CellStoreEvent::stop()
+{
+ mpCol = nullptr;
+}
+
+void CellStoreEvent::swap(CellStoreEvent& other)
+{
+ std::swap(mpCol, other.mpCol);
+}
+
+const ScColumn* CellStoreEvent::getColumn() const
+{
+ return mpCol;
+}
+
+ColumnBlockPositionSet::ColumnBlockPositionSet(ScDocument& rDoc) : mrDoc(rDoc) {}
+
+ColumnBlockPosition* ColumnBlockPositionSet::getBlockPosition(SCTAB nTab, SCCOL nCol)
+{
+ std::scoped_lock aGuard(maMtxTables);
+
+ TablesType::iterator itTab = maTables.find(nTab);
+ if (itTab == maTables.end())
+ {
+ std::pair<TablesType::iterator,bool> r =
+ maTables.emplace(nTab, ColumnsType());
+ if (!r.second)
+ // insertion failed.
+ return nullptr;
+
+ itTab = r.first;
+ }
+
+ ColumnsType& rCols = itTab->second;
+
+ ColumnsType::iterator it = rCols.find(nCol);
+ if (it != rCols.end())
+ // Block position for this column has already been fetched.
+ return &it->second;
+
+ std::pair<ColumnsType::iterator,bool> r =
+ rCols.emplace(nCol, ColumnBlockPosition());
+
+ if (!r.second)
+ // insertion failed.
+ return nullptr;
+
+ it = r.first;
+
+ if (!mrDoc.InitColumnBlockPosition(it->second, nTab, nCol))
+ return nullptr;
+
+ return &it->second;
+}
+
+void ColumnBlockPositionSet::clear()
+{
+ std::scoped_lock aGuard(maMtxTables);
+ maTables.clear();
+}
+
+struct TableColumnBlockPositionSet::Impl
+{
+ typedef std::unordered_map<SCCOL, ColumnBlockPosition> ColumnsType;
+
+ ScTable* mpTab;
+ ColumnsType maColumns;
+
+ Impl() : mpTab(nullptr) {}
+};
+
+TableColumnBlockPositionSet::TableColumnBlockPositionSet( ScDocument& rDoc, SCTAB nTab ) :
+ mpImpl(std::make_unique<Impl>())
+{
+ mpImpl->mpTab = rDoc.FetchTable(nTab);
+
+ if (!mpImpl->mpTab)
+ {
+ std::ostringstream os;
+ os << "Passed table index " << nTab << " is invalid.";
+ throw std::invalid_argument(os.str());
+ }
+}
+
+TableColumnBlockPositionSet::TableColumnBlockPositionSet( TableColumnBlockPositionSet&& rOther ) noexcept :
+ mpImpl(std::move(rOther.mpImpl)) {}
+
+TableColumnBlockPositionSet::~TableColumnBlockPositionSet() {}
+
+ColumnBlockPosition* TableColumnBlockPositionSet::getBlockPosition( SCCOL nCol )
+{
+ using ColumnsType = Impl::ColumnsType;
+
+ ColumnsType::iterator it = mpImpl->maColumns.find(nCol);
+
+ if (it != mpImpl->maColumns.end())
+ // Block position for this column has already been fetched.
+ return &it->second;
+
+ std::pair<ColumnsType::iterator,bool> r =
+ mpImpl->maColumns.emplace(nCol, ColumnBlockPosition());
+
+ if (!r.second)
+ // insertion failed.
+ return nullptr;
+
+ it = r.first;
+
+ if (!mpImpl->mpTab->InitColumnBlockPosition(it->second, nCol))
+ return nullptr;
+
+ return &it->second;
+}
+
+void TableColumnBlockPositionSet::invalidate()
+{
+ mpImpl->maColumns.clear();
+}
+
+ScRefCellValue toRefCell( const sc::CellStoreType::const_iterator& itPos, size_t nOffset )
+{
+ switch (itPos->type)
+ {
+ case sc::element_type_numeric:
+ // Numeric cell
+ return ScRefCellValue(sc::numeric_block::at(*itPos->data, nOffset));
+ case sc::element_type_string:
+ // String cell
+ return ScRefCellValue(&sc::string_block::at(*itPos->data, nOffset));
+ case sc::element_type_edittext:
+ // Edit cell
+ return ScRefCellValue(sc::edittext_block::at(*itPos->data, nOffset));
+ case sc::element_type_formula:
+ // Formula cell
+ return ScRefCellValue(sc::formula_block::at(*itPos->data, nOffset));
+ default:
+ ;
+ }
+
+ return ScRefCellValue();
+}
+
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sc/source/core/data/olinetab.cxx b/sc/source/core/data/olinetab.cxx
new file mode 100644
index 000000000..13fc17a7b
--- /dev/null
+++ b/sc/source/core/data/olinetab.cxx
@@ -0,0 +1,873 @@
+/* -*- 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 <olinetab.hxx>
+#include <address.hxx>
+#include <table.hxx>
+
+#include <osl/diagnose.h>
+
+ScOutlineEntry::ScOutlineEntry( SCCOLROW nNewStart, SCCOLROW nNewSize, bool bNewHidden ) :
+ nStart ( nNewStart ),
+ nSize ( nNewSize ),
+ bHidden ( bNewHidden ),
+ bVisible( true )
+{
+}
+
+ScOutlineEntry::ScOutlineEntry( const ScOutlineEntry& rEntry ) :
+ nStart ( rEntry.nStart ),
+ nSize ( rEntry.nSize ),
+ bHidden ( rEntry.bHidden ),
+ bVisible( rEntry.bVisible )
+{
+}
+
+SCCOLROW ScOutlineEntry::GetEnd() const
+{
+ return nStart+nSize-1;
+}
+
+void ScOutlineEntry::Move( SCCOLROW nDelta )
+{
+ SCCOLROW nNewPos = nStart + nDelta;
+ if (nNewPos<0)
+ {
+ OSL_FAIL("OutlineEntry < 0");
+ nNewPos = 0;
+ }
+ nStart = nNewPos;
+}
+
+void ScOutlineEntry::SetSize( SCSIZE nNewSize )
+{
+ if (nNewSize>0)
+ nSize = nNewSize;
+ else
+ {
+ OSL_FAIL("ScOutlineEntry Size == 0");
+ }
+}
+
+void ScOutlineEntry::SetPosSize( SCCOLROW nNewPos, SCSIZE nNewSize )
+{
+ nStart = nNewPos;
+ SetSize( nNewSize );
+}
+
+void ScOutlineEntry::SetHidden( bool bNewHidden )
+{
+ bHidden = bNewHidden;
+}
+
+void ScOutlineEntry::SetVisible( bool bNewVisible )
+{
+ bVisible = bNewVisible;
+}
+
+OString ScOutlineEntry::dumpAsString() const
+{
+ const char* const pSep = ":";
+ return OString::number(nStart) + pSep + OString::number(nSize) +
+ pSep + (bHidden ? "1" : "0") + pSep + (bVisible ? "1" : "0");
+}
+
+ScOutlineCollection::ScOutlineCollection() {}
+
+size_t ScOutlineCollection::size() const
+{
+ return m_Entries.size();
+}
+
+void ScOutlineCollection::clear()
+{
+ m_Entries.clear();
+}
+
+void ScOutlineCollection::insert(ScOutlineEntry const& rEntry)
+{
+ SCCOLROW nStart = rEntry.GetStart();
+ m_Entries.insert(std::make_pair(nStart, rEntry));
+}
+
+ScOutlineCollection::iterator ScOutlineCollection::begin()
+{
+ return m_Entries.begin();
+}
+
+ScOutlineCollection::iterator ScOutlineCollection::end()
+{
+ return m_Entries.end();
+}
+
+ScOutlineCollection::const_iterator ScOutlineCollection::begin() const
+{
+ return m_Entries.begin();
+}
+
+ScOutlineCollection::const_iterator ScOutlineCollection::end() const
+{
+ return m_Entries.end();
+}
+
+ScOutlineCollection::iterator ScOutlineCollection::erase(const iterator& pos)
+{
+ return m_Entries.erase(pos);
+}
+
+bool ScOutlineCollection::empty() const
+{
+ return m_Entries.empty();
+}
+
+ScOutlineCollection::iterator ScOutlineCollection::FindStart(SCCOLROW nMinStart)
+{
+ return m_Entries.lower_bound(nMinStart);
+}
+
+OString ScOutlineCollection::dumpAsString() const
+{
+ OString aOutput;
+ const char* const pGroupEntrySep = ",";
+ for (const auto& rKeyValuePair : m_Entries)
+ aOutput += rKeyValuePair.second.dumpAsString() + pGroupEntrySep;
+
+ return aOutput;
+}
+
+ScOutlineArray::ScOutlineArray() :
+ nDepth(0) {}
+
+ScOutlineArray::ScOutlineArray( const ScOutlineArray& rArray ) :
+ nDepth( rArray.nDepth )
+{
+ for (size_t nLevel = 0; nLevel < nDepth; ++nLevel)
+ {
+ const ScOutlineCollection& rColl = rArray.aCollections[nLevel];
+ for (const auto& rEntry : rColl)
+ {
+ const ScOutlineEntry *const pEntry = &rEntry.second;
+ aCollections[nLevel].insert(*pEntry);
+ }
+ }
+}
+
+void ScOutlineArray::FindEntry(
+ SCCOLROW nSearchPos, size_t& rFindLevel, size_t& rFindIndex,
+ size_t nMaxLevel )
+{
+ rFindLevel = rFindIndex = 0;
+
+ if (nMaxLevel > nDepth)
+ nMaxLevel = nDepth;
+
+ for (size_t nLevel = 0; nLevel < nMaxLevel; ++nLevel) //TODO: Search backwards?
+ {
+ ScOutlineCollection* pCollect = &aCollections[nLevel];
+ size_t nIndex = 0;
+ for (auto& rEntry : *pCollect)
+ {
+ ScOutlineEntry *const pEntry = &rEntry.second;
+ if (pEntry->GetStart() <= nSearchPos && pEntry->GetEnd() >= nSearchPos)
+ {
+ rFindLevel = nLevel + 1; // Next Level (for insertion)
+ rFindIndex = nIndex;
+ }
+ ++nIndex;
+ }
+ }
+}
+
+bool ScOutlineArray::Insert(
+ SCCOLROW nStartCol, SCCOLROW nEndCol, bool& rSizeChanged, bool bHidden )
+{
+ rSizeChanged = false;
+
+ size_t nStartLevel, nEndLevel, nStartIndex, nEndIndex;
+ bool bFound = false;
+
+ bool bCont;
+ sal_uInt16 nFindMax;
+ FindEntry( nStartCol, nStartLevel, nStartIndex ); // nLevel = new Level (old+1)
+ FindEntry( nEndCol, nEndLevel, nEndIndex );
+ nFindMax = std::max(nStartLevel,nEndLevel);
+ do
+ {
+ bCont = false;
+
+ if (nStartLevel == nEndLevel && nStartIndex == nEndIndex && nStartLevel < SC_OL_MAXDEPTH)
+ bFound = true;
+
+ if (!bFound && nFindMax>0)
+ {
+ --nFindMax;
+ if (nStartLevel)
+ {
+ ScOutlineCollection::const_iterator it = aCollections[nStartLevel-1].begin();
+ std::advance(it, nStartIndex);
+ if (it->second.GetStart() == nStartCol)
+ FindEntry(nStartCol, nStartLevel, nStartIndex, nFindMax);
+ }
+
+ if (nEndLevel)
+ {
+ ScOutlineCollection::const_iterator it = aCollections[nEndLevel-1].begin();
+ std::advance(it, nEndIndex);
+ if (it->second.GetEnd() == nEndCol)
+ FindEntry(nEndCol, nEndLevel, nEndIndex, nFindMax);
+ }
+ bCont = true;
+ }
+ }
+ while ( !bFound && bCont );
+
+ if (!bFound)
+ return false;
+
+ size_t nLevel = nStartLevel;
+
+ // Move the ones underneath
+ bool bNeedSize = false;
+ if (nDepth > 0)
+ {
+ for (size_t nMoveLevel = nDepth-1; nMoveLevel >= nLevel; --nMoveLevel)
+ {
+ ScOutlineCollection& rColl = aCollections[nMoveLevel];
+ ScOutlineCollection::iterator it = rColl.begin(), itEnd = rColl.end();
+ while (it != itEnd)
+ {
+ ScOutlineEntry *const pEntry = &it->second;
+ SCCOLROW nEntryStart = pEntry->GetStart();
+ if (nEntryStart >= nStartCol && nEntryStart <= nEndCol)
+ {
+ if (nMoveLevel >= SC_OL_MAXDEPTH - 1)
+ {
+ rSizeChanged = false; // No more room
+ return false;
+ }
+ aCollections[nMoveLevel+1].insert(*pEntry);
+ it = rColl.erase(it);
+ if (nMoveLevel == nDepth - 1)
+ bNeedSize = true;
+ }
+ else
+ ++it;
+ }
+ if (nMoveLevel == 0)
+ break;
+ }
+ }
+
+ if (bNeedSize)
+ {
+ ++nDepth;
+ rSizeChanged = true;
+ }
+
+ if (nDepth <= nLevel)
+ {
+ nDepth = nLevel+1;
+ rSizeChanged = true;
+ }
+
+ ScOutlineEntry aNewEntry(nStartCol, nEndCol+1-nStartCol, bHidden);
+ aNewEntry.SetVisible( true );
+ aCollections[nLevel].insert(aNewEntry);
+
+ return true;
+}
+
+bool ScOutlineArray::FindTouchedLevel(
+ SCCOLROW nBlockStart, SCCOLROW nBlockEnd, size_t& rFindLevel) const
+{
+ bool bFound = false;
+ rFindLevel = 0;
+
+ for (size_t nLevel = 0; nLevel < nDepth; ++nLevel)
+ {
+ const ScOutlineCollection* pCollect = &aCollections[nLevel];
+ for (const auto& rEntry : *pCollect)
+ {
+ const ScOutlineEntry *const pEntry = &rEntry.second;
+ SCCOLROW nStart = pEntry->GetStart();
+ SCCOLROW nEnd = pEntry->GetEnd();
+
+ if ( ( nBlockStart>=nStart && nBlockStart<=nEnd ) ||
+ ( nBlockEnd >=nStart && nBlockEnd <=nEnd ) )
+ {
+ rFindLevel = nLevel; // Actual Level
+ bFound = true;
+ }
+ }
+ }
+
+ return bFound;
+}
+
+void ScOutlineArray::PromoteSub(SCCOLROW nStartPos, SCCOLROW nEndPos, size_t nStartLevel)
+{
+ if (nStartLevel==0)
+ {
+ OSL_FAIL("PromoteSub with Level 0");
+ return;
+ }
+
+ for (size_t nLevel = nStartLevel; nLevel < nDepth; ++nLevel)
+ {
+ ScOutlineCollection& rColl = aCollections[nLevel];
+ ScOutlineCollection::iterator it = rColl.begin(), itEnd = rColl.end();
+ while (it != itEnd)
+ {
+ ScOutlineEntry *const pEntry = &it->second;
+ SCCOLROW nStart = pEntry->GetStart();
+ SCCOLROW nEnd = pEntry->GetEnd();
+ if (nStart >= nStartPos && nEnd <= nEndPos)
+ {
+ aCollections[nLevel-1].insert(*pEntry);
+
+ it = rColl.erase(it);
+ }
+ else
+ ++it;
+ }
+
+ it = rColl.begin();
+ itEnd = rColl.end();
+
+ while (it != itEnd)
+ {
+ ScOutlineEntry *const pEntry = &it->second;
+ SCCOLROW nStart = pEntry->GetStart();
+ SCCOLROW nEnd = pEntry->GetEnd();
+ if (nStart >= nStartPos && nEnd <= nEndPos)
+ {
+ aCollections[nLevel-1].insert(*pEntry);
+
+ it = rColl.erase(it);
+ }
+ else
+ ++it;
+ }
+ }
+}
+
+/**
+ * Adapt nDepth for empty Levels
+ */
+bool ScOutlineArray::DecDepth()
+{
+ bool bChanged = false;
+ bool bCont;
+ do
+ {
+ bCont = false;
+ if (nDepth)
+ {
+ if (aCollections[nDepth-1].empty())
+ {
+ --nDepth;
+ bChanged = true;
+ bCont = true;
+ }
+ }
+ }
+ while (bCont);
+
+ return bChanged;
+}
+
+bool ScOutlineArray::Remove( SCCOLROW nBlockStart, SCCOLROW nBlockEnd, bool& rSizeChanged )
+{
+ size_t nLevel;
+ FindTouchedLevel( nBlockStart, nBlockEnd, nLevel );
+
+ ScOutlineCollection* pCollect = &aCollections[nLevel];
+ ScOutlineCollection::iterator it = pCollect->begin(), itEnd = pCollect->end();
+ bool bAny = false;
+ while (it != itEnd)
+ {
+ ScOutlineEntry *const pEntry = &it->second;
+ SCCOLROW nStart = pEntry->GetStart();
+ SCCOLROW nEnd = pEntry->GetEnd();
+ if (nBlockStart <= nEnd && nBlockEnd >= nStart)
+ {
+ // Overlaps
+ pCollect->erase(it);
+ PromoteSub( nStart, nEnd, nLevel+1 );
+ itEnd = pCollect->end();
+ it = pCollect->FindStart( nEnd+1 );
+ bAny = true;
+ }
+ else
+ ++it;
+ }
+
+ if (bAny) // Adapt Depth
+ if (DecDepth())
+ rSizeChanged = true;
+
+ return bAny;
+}
+
+ScOutlineEntry* ScOutlineArray::GetEntry(size_t nLevel, size_t nIndex)
+{
+ if (nLevel >= nDepth)
+ return nullptr;
+
+ ScOutlineCollection& rColl = aCollections[nLevel];
+ if (nIndex >= rColl.size())
+ return nullptr;
+
+ ScOutlineCollection::iterator it = rColl.begin();
+ std::advance(it, nIndex);
+ return &it->second;
+}
+
+const ScOutlineEntry* ScOutlineArray::GetEntry(size_t nLevel, size_t nIndex) const
+{
+ if (nLevel >= nDepth)
+ return nullptr;
+
+ const ScOutlineCollection& rColl = aCollections[nLevel];
+ if (nIndex >= rColl.size())
+ return nullptr;
+
+ ScOutlineCollection::const_iterator it = rColl.begin();
+ std::advance(it, nIndex);
+ return &it->second;
+}
+
+size_t ScOutlineArray::GetCount(size_t nLevel) const
+{
+ if (nLevel >= nDepth)
+ return 0;
+
+ return aCollections[nLevel].size();
+}
+
+const ScOutlineEntry* ScOutlineArray::GetEntryByPos(size_t nLevel, SCCOLROW nPos) const
+{
+ if (nLevel >= nDepth)
+ return nullptr;
+
+ const ScOutlineCollection& rColl = aCollections[nLevel];
+ ScOutlineCollection::const_iterator it = std::find_if(rColl.begin(), rColl.end(),
+ [&nPos](const auto& rEntry) {
+ const ScOutlineEntry *const pEntry = &rEntry.second;
+ return pEntry->GetStart() <= nPos && nPos <= pEntry->GetEnd();
+ });
+ if (it != rColl.end())
+ return &it->second;
+
+ return nullptr;
+}
+
+bool ScOutlineArray::GetEntryIndex(size_t nLevel, SCCOLROW nPos, size_t& rnIndex) const
+{
+ if (nLevel >= nDepth)
+ return false;
+
+ // Found entry contains passed position
+ const ScOutlineCollection& rColl = aCollections[nLevel];
+ ScOutlineCollection::const_iterator it = std::find_if(rColl.begin(), rColl.end(),
+ [&nPos](const auto& rEntry) {
+ const ScOutlineEntry *const p = &rEntry.second;
+ return p->GetStart() <= nPos && nPos <= p->GetEnd();
+ });
+ if (it != rColl.end())
+ {
+ rnIndex = std::distance(rColl.begin(), it);
+ return true;
+ }
+ return false;
+}
+
+bool ScOutlineArray::GetEntryIndexInRange(
+ size_t nLevel, SCCOLROW nBlockStart, SCCOLROW nBlockEnd, size_t& rnIndex) const
+{
+ if (nLevel >= nDepth)
+ return false;
+
+ // Found entry will be completely inside of passed range
+ const ScOutlineCollection& rColl = aCollections[nLevel];
+ ScOutlineCollection::const_iterator it = std::find_if(rColl.begin(), rColl.end(),
+ [&nBlockStart, &nBlockEnd](const auto& rEntry) {
+ const ScOutlineEntry *const p = &rEntry.second;
+ return nBlockStart <= p->GetStart() && p->GetEnd() <= nBlockEnd;
+ });
+ if (it != rColl.end())
+ {
+ rnIndex = std::distance(rColl.begin(), it);
+ return true;
+ }
+ return false;
+}
+
+void ScOutlineArray::SetVisibleBelow(
+ size_t nLevel, size_t nEntry, bool bValue, bool bSkipHidden)
+{
+ const ScOutlineEntry* pEntry = GetEntry( nLevel, nEntry );
+ if (!pEntry)
+ return;
+
+ SCCOLROW nStart = pEntry->GetStart();
+ SCCOLROW nEnd = pEntry->GetEnd();
+
+ for (size_t nSubLevel = nLevel+1; nSubLevel < nDepth; ++nSubLevel)
+ {
+ ScOutlineCollection& rColl = aCollections[nSubLevel];
+ size_t nPos = 0;
+ for (auto& rEntry : rColl)
+ {
+ ScOutlineEntry *const p = &rEntry.second;
+ if (p->GetStart() >= nStart && p->GetEnd() <= nEnd)
+ {
+ p->SetVisible(bValue);
+ if (bSkipHidden && !p->IsHidden())
+ {
+ SetVisibleBelow(nSubLevel, nPos, bValue, true);
+ }
+ }
+ ++nPos;
+ }
+
+ if (bSkipHidden)
+ nSubLevel = nDepth; // Bail out
+ }
+}
+
+void ScOutlineArray::GetRange(SCCOLROW& rStart, SCCOLROW& rEnd) const
+{
+ const ScOutlineCollection& rColl = aCollections[0];
+ if (!rColl.empty())
+ {
+ ScOutlineCollection::const_iterator it = rColl.begin();
+ rStart = it->second.GetStart();
+ std::advance(it, rColl.size()-1);
+ rEnd = it->second.GetEnd();
+ }
+ else
+ rStart = rEnd = 0;
+}
+
+void ScOutlineArray::ExtendBlock(size_t nLevel, SCCOLROW& rBlkStart, SCCOLROW& rBlkEnd)
+{
+ if (nLevel >= nDepth)
+ return;
+
+ const ScOutlineCollection& rColl = aCollections[nLevel];
+ for (const auto& rEntry : rColl)
+ {
+ const ScOutlineEntry *const pEntry = &rEntry.second;
+ SCCOLROW nStart = pEntry->GetStart();
+ SCCOLROW nEnd = pEntry->GetEnd();
+
+ if (rBlkStart <= nEnd && rBlkEnd >= nStart)
+ {
+ if (nStart < rBlkStart)
+ rBlkStart = nStart;
+ if (nEnd > rBlkEnd)
+ rBlkEnd = nEnd;
+ }
+ }
+}
+
+bool ScOutlineArray::TestInsertSpace(SCSIZE nSize, SCCOLROW nMaxVal) const
+{
+ const ScOutlineCollection& rColl = aCollections[0];
+ if (rColl.empty())
+ return true;
+
+ ScOutlineCollection::const_iterator it = rColl.begin();
+ std::advance(it, rColl.size()-1);
+ SCCOLROW nEnd = it->second.GetEnd();
+ return sal::static_int_cast<SCCOLROW>(nEnd+nSize) <= nMaxVal;
+}
+
+void ScOutlineArray::InsertSpace(SCCOLROW nStartPos, SCSIZE nSize)
+{
+ ScSubOutlineIterator aIter( this );
+ ScOutlineEntry* pEntry;
+ while ((pEntry = aIter.GetNext()) != nullptr)
+ {
+ if ( pEntry->GetStart() >= nStartPos )
+ pEntry->Move(static_cast<SCCOLROW>(nSize));
+ else
+ {
+ SCCOLROW nEnd = pEntry->GetEnd();
+ // Always expand if inserted within the group
+ // When inserting at the end, only if the group is not hidden
+ if ( nEnd >= nStartPos || ( nEnd+1 >= nStartPos && !pEntry->IsHidden() ) )
+ {
+ SCSIZE nEntrySize = pEntry->GetSize();
+ nEntrySize += nSize;
+ pEntry->SetSize( nEntrySize );
+ }
+ }
+ }
+}
+
+bool ScOutlineArray::DeleteSpace(SCCOLROW nStartPos, SCSIZE nSize)
+{
+ SCCOLROW nEndPos = nStartPos + nSize - 1;
+ bool bNeedSave = false; // Do we need the original one for Undo?
+ bool bChanged = false; // For Level test
+
+ ScSubOutlineIterator aIter( this );
+ ScOutlineEntry* pEntry;
+ while((pEntry=aIter.GetNext())!=nullptr)
+ {
+ SCCOLROW nEntryStart = pEntry->GetStart();
+ SCCOLROW nEntryEnd = pEntry->GetEnd();
+ SCSIZE nEntrySize = pEntry->GetSize();
+
+ if ( nEntryEnd >= nStartPos )
+ {
+ if ( nEntryStart > nEndPos ) // Right
+ pEntry->Move(-static_cast<SCCOLROW>(nSize));
+ else if ( nEntryStart < nStartPos && nEntryEnd >= nEndPos ) // Outside
+ pEntry->SetSize( nEntrySize-nSize );
+ else
+ {
+ bNeedSave = true;
+ if ( nEntryStart >= nStartPos && nEntryEnd <= nEndPos ) // Inside
+ {
+ aIter.DeleteLast();
+ bChanged = true;
+ }
+ else if ( nEntryStart >= nStartPos ) // Top right
+ pEntry->SetPosSize( nStartPos, static_cast<SCSIZE>(nEntryEnd-nEndPos) );
+ else // Top left
+ pEntry->SetSize( static_cast<SCSIZE>(nStartPos-nEntryStart) );
+ }
+ }
+ }
+
+ if (bChanged)
+ DecDepth();
+
+ return bNeedSave;
+}
+
+bool ScOutlineArray::ManualAction(
+ SCCOLROW nStartPos, SCCOLROW nEndPos, bool bShow, const ScTable& rTable, bool bCol)
+{
+ bool bModified = false;
+ ScSubOutlineIterator aIter( this );
+ ScOutlineEntry* pEntry;
+ while((pEntry=aIter.GetNext())!=nullptr)
+ {
+ SCCOLROW nEntryStart = pEntry->GetStart();
+ SCCOLROW nEntryEnd = pEntry->GetEnd();
+
+ if (nEntryEnd>=nStartPos && nEntryStart<=nEndPos)
+ {
+ if ( pEntry->IsHidden() == bShow )
+ {
+ // #i12341# hide if all columns/rows are hidden, show if at least one
+ // is visible
+ SCCOLROW nEnd = rTable.LastHiddenColRow(nEntryStart, bCol);
+ bool bAllHidden = (nEntryEnd <= nEnd && nEnd <
+ ::std::numeric_limits<SCCOLROW>::max());
+
+ bool bToggle = ( bShow != bAllHidden );
+ if ( bToggle )
+ {
+ pEntry->SetHidden( !bShow );
+ SetVisibleBelow( aIter.LastLevel(), aIter.LastEntry(), bShow, bShow );
+ bModified = true;
+ }
+ }
+ }
+ }
+ return bModified;
+}
+
+void ScOutlineArray::RemoveAll()
+{
+ for (size_t nLevel = 0; nLevel < nDepth; ++nLevel)
+ aCollections[nLevel].clear();
+
+ nDepth = 0;
+}
+
+void ScOutlineArray::finalizeImport(const ScTable& rTable)
+{
+ ScSubOutlineIterator aIter( this );
+ ScOutlineEntry* pEntry;
+ while((pEntry=aIter.GetNext())!=nullptr)
+ {
+
+ if (!pEntry->IsHidden())
+ continue;
+
+ SCCOLROW nEntryStart = pEntry->GetStart();
+ SCCOLROW nEntryEnd = pEntry->GetEnd();
+ SCCOLROW nEnd = rTable.LastHiddenColRow(nEntryStart, false/*bCol*/);
+ bool bAllHidden = (nEntryEnd <= nEnd && nEnd <
+ ::std::numeric_limits<SCCOLROW>::max());
+
+ pEntry->SetHidden(bAllHidden);
+ SetVisibleBelow(aIter.LastLevel(), aIter.LastEntry(), !bAllHidden, !bAllHidden);
+ }
+}
+
+OString ScOutlineArray::dumpAsString() const
+{
+ OString aOutput;
+ const char* const pLevelSep = " ";
+ for (const auto& rCollection : aCollections)
+ {
+ if (rCollection.empty())
+ continue;
+ aOutput += rCollection.dumpAsString() + pLevelSep;
+ }
+
+ return aOutput;
+}
+
+ScOutlineTable::ScOutlineTable()
+{
+}
+
+ScOutlineTable::ScOutlineTable( const ScOutlineTable& rOutline ) :
+ aColOutline( rOutline.aColOutline ),
+ aRowOutline( rOutline.aRowOutline )
+{
+}
+
+bool ScOutlineTable::TestInsertCol( SCSIZE nSize )
+{
+ return aColOutline.TestInsertSpace( nSize, MAXCOL );
+}
+
+void ScOutlineTable::InsertCol( SCCOL nStartCol, SCSIZE nSize )
+{
+ aColOutline.InsertSpace( nStartCol, nSize );
+}
+
+bool ScOutlineTable::DeleteCol( SCCOL nStartCol, SCSIZE nSize )
+{
+ return aColOutline.DeleteSpace( nStartCol, nSize );
+}
+
+bool ScOutlineTable::TestInsertRow( SCSIZE nSize )
+{
+ return aRowOutline.TestInsertSpace( nSize, MAXROW );
+}
+
+void ScOutlineTable::InsertRow( SCROW nStartRow, SCSIZE nSize )
+{
+ aRowOutline.InsertSpace( nStartRow, nSize );
+}
+
+bool ScOutlineTable::DeleteRow( SCROW nStartRow, SCSIZE nSize )
+{
+ return aRowOutline.DeleteSpace( nStartRow, nSize );
+}
+
+ScSubOutlineIterator::ScSubOutlineIterator( ScOutlineArray* pOutlineArray ) :
+ pArray( pOutlineArray ),
+ nStart( 0 ),
+ nEnd( SCCOLROW_MAX ), // Iterate over all of them
+ nSubLevel( 0 ),
+ nSubEntry( 0 )
+{
+ nDepth = pArray->nDepth;
+}
+
+ScSubOutlineIterator::ScSubOutlineIterator(
+ ScOutlineArray* pOutlineArray, size_t nLevel, size_t nEntry ) :
+ pArray( pOutlineArray )
+{
+ const ScOutlineCollection& rColl = pArray->aCollections[nLevel];
+ ScOutlineCollection::const_iterator it = rColl.begin();
+ std::advance(it, nEntry);
+ const ScOutlineEntry* pEntry = &it->second;
+ nStart = pEntry->GetStart();
+ nEnd = pEntry->GetEnd();
+ nSubLevel = nLevel + 1;
+ nSubEntry = 0;
+ nDepth = pArray->nDepth;
+}
+
+ScOutlineEntry* ScSubOutlineIterator::GetNext()
+{
+ ScOutlineEntry* pEntry = nullptr;
+ bool bFound = false;
+ do
+ {
+ if (nSubLevel >= nDepth)
+ return nullptr;
+
+ ScOutlineCollection& rColl = pArray->aCollections[nSubLevel];
+ if (nSubEntry < rColl.size())
+ {
+ ScOutlineCollection::iterator it = rColl.begin();
+ std::advance(it, nSubEntry);
+ pEntry = &it->second;
+
+ if (pEntry->GetStart() >= nStart && pEntry->GetEnd() <= nEnd)
+ bFound = true;
+
+ ++nSubEntry;
+ }
+ else
+ {
+ // Go to the next sub-level
+ nSubEntry = 0;
+ ++nSubLevel;
+ }
+ }
+ while (!bFound);
+ return pEntry; // nSubLevel valid, if pEntry != 0
+}
+
+size_t ScSubOutlineIterator::LastEntry() const
+{
+ if (nSubEntry == 0)
+ {
+ OSL_FAIL("ScSubOutlineIterator::LastEntry before GetNext");
+ return 0;
+ }
+ return nSubEntry-1;
+}
+
+void ScSubOutlineIterator::DeleteLast()
+{
+ if (nSubLevel >= nDepth)
+ {
+ OSL_FAIL("ScSubOutlineIterator::DeleteLast after End");
+ return;
+ }
+ if (nSubEntry == 0)
+ {
+ OSL_FAIL("ScSubOutlineIterator::DeleteLast before GetNext");
+ return;
+ }
+
+ --nSubEntry;
+ ScOutlineCollection& rColl = pArray->aCollections[nSubLevel];
+ assert(nSubEntry < rColl.size());
+ ScOutlineCollection::iterator it = rColl.begin();
+ std::advance(it, nSubEntry);
+ rColl.erase(it);
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sc/source/core/data/pagepar.cxx b/sc/source/core/data/pagepar.cxx
new file mode 100644
index 000000000..7e996fc64
--- /dev/null
+++ b/sc/source/core/data/pagepar.cxx
@@ -0,0 +1,56 @@
+/* -*- 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 <pagepar.hxx>
+
+// struct ScPageTableParam:
+
+ScPageTableParam::ScPageTableParam()
+{
+ Reset();
+}
+
+void ScPageTableParam::Reset()
+{
+ bCellContent = true;
+ bNotes=bGrid=bHeaders=bDrawings=
+ bLeftRight=bScaleAll=bScaleTo=bScalePageNum=
+ bFormulas=bNullVals=bSkipEmpty=bForceBreaks = false;
+ bTopDown=bScaleNone=bCharts=bObjects = true;
+ nScaleAll = 100;
+ nScalePageNum = nScaleWidth = nScaleHeight = 0;
+ nFirstPageNo = 1;
+}
+
+// struct ScPageAreaParam:
+
+ScPageAreaParam::ScPageAreaParam()
+{
+ Reset();
+}
+
+void ScPageAreaParam::Reset()
+{
+ bPrintArea = bRepeatRow = bRepeatCol = false;
+
+ aPrintArea = ScRange();
+}
+
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sc/source/core/data/patattr.cxx b/sc/source/core/data/patattr.cxx
new file mode 100644
index 000000000..8a88e9449
--- /dev/null
+++ b/sc/source/core/data/patattr.cxx
@@ -0,0 +1,1422 @@
+/* -*- 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 <memory>
+#include <utility>
+#include <scitems.hxx>
+#include <editeng/adjustitem.hxx>
+#include <editeng/boxitem.hxx>
+#include <editeng/lineitem.hxx>
+#include <editeng/brushitem.hxx>
+#include <editeng/charreliefitem.hxx>
+#include <editeng/contouritem.hxx>
+#include <svtools/colorcfg.hxx>
+#include <editeng/colritem.hxx>
+#include <editeng/crossedoutitem.hxx>
+#include <editeng/eeitem.hxx>
+#include <editeng/emphasismarkitem.hxx>
+#include <editeng/fhgtitem.hxx>
+#include <editeng/fontitem.hxx>
+#include <editeng/forbiddenruleitem.hxx>
+#include <editeng/frmdiritem.hxx>
+#include <editeng/langitem.hxx>
+#include <editeng/postitem.hxx>
+#include <svx/rotmodit.hxx>
+#include <editeng/scriptspaceitem.hxx>
+#include <editeng/shaditem.hxx>
+#include <editeng/shdditem.hxx>
+#include <editeng/udlnitem.hxx>
+#include <editeng/wghtitem.hxx>
+#include <editeng/wrlmitem.hxx>
+#include <editeng/justifyitem.hxx>
+#include <svl/intitem.hxx>
+#include <svl/numformat.hxx>
+#include <svl/zforlist.hxx>
+#include <vcl/outdev.hxx>
+#include <tools/fract.hxx>
+#include <tools/UnitConversion.hxx>
+#include <osl/diagnose.h>
+
+#include <attrib.hxx>
+#include <patattr.hxx>
+#include <docpool.hxx>
+#include <stlsheet.hxx>
+#include <stlpool.hxx>
+#include <document.hxx>
+#include <globstr.hrc>
+#include <scresid.hxx>
+#include <validat.hxx>
+#include <scmod.hxx>
+#include <fillinfo.hxx>
+#include <boost/functional/hash.hpp>
+
+ScPatternAttr::ScPatternAttr( SfxItemSet&& pItemSet, const OUString& rStyleName )
+ : SfxSetItem ( ATTR_PATTERN, std::move(pItemSet) ),
+ pName ( rStyleName ),
+ pStyle ( nullptr ),
+ mnKey(0)
+{
+}
+
+ScPatternAttr::ScPatternAttr( SfxItemSet&& pItemSet )
+ : SfxSetItem ( ATTR_PATTERN, std::move(pItemSet) ),
+ pStyle ( nullptr ),
+ mnKey(0)
+{
+}
+
+ScPatternAttr::ScPatternAttr( SfxItemPool* pItemPool )
+ : SfxSetItem ( ATTR_PATTERN, SfxItemSetFixed<ATTR_PATTERN_START, ATTR_PATTERN_END>( *pItemPool ) ),
+ pStyle ( nullptr ),
+ mnKey(0)
+{
+}
+
+ScPatternAttr::ScPatternAttr( const ScPatternAttr& rPatternAttr )
+ : SfxSetItem ( rPatternAttr ),
+ pName ( rPatternAttr.pName ),
+ pStyle ( rPatternAttr.pStyle ),
+ mnKey(rPatternAttr.mnKey)
+{
+}
+
+ScPatternAttr* ScPatternAttr::Clone( SfxItemPool *pPool ) const
+{
+ ScPatternAttr* pPattern = new ScPatternAttr( GetItemSet().CloneAsValue(true, pPool) );
+
+ pPattern->pStyle = pStyle;
+ pPattern->pName = pName;
+
+ return pPattern;
+}
+
+static bool StrCmp( const OUString* pStr1, const OUString* pStr2 )
+{
+ if (pStr1 == pStr2)
+ return true;
+ if (pStr1 && !pStr2)
+ return false;
+ if (!pStr1 && pStr2)
+ return false;
+ return *pStr1 == *pStr2;
+}
+
+constexpr size_t compareSize = ATTR_PATTERN_END - ATTR_PATTERN_START + 1;
+
+std::optional<bool> ScPatternAttr::FastEqualPatternSets( const SfxItemSet& rSet1, const SfxItemSet& rSet2 )
+{
+ // #i62090# The SfxItemSet in the SfxSetItem base class always has the same ranges
+ // (single range from ATTR_PATTERN_START to ATTR_PATTERN_END), and the items are pooled,
+ // so it's enough to compare just the pointers (Count just because it's even faster).
+
+ if ( rSet1.Count() != rSet2.Count() )
+ return { false };
+
+ // Actually test_tdf133629 from UITest_calc_tests9 somehow manages to have
+ // a different range (and I don't understand enough why), so better be safe and compare fully.
+ if( rSet1.TotalCount() != compareSize || rSet2.TotalCount() != compareSize )
+ return std::nullopt;
+
+ SfxPoolItem const ** pItems1 = rSet1.GetItems_Impl(); // inline method of SfxItemSet
+ SfxPoolItem const ** pItems2 = rSet2.GetItems_Impl();
+
+ return { memcmp( pItems1, pItems2, compareSize * sizeof(pItems1[0]) ) == 0 };
+}
+
+static bool EqualPatternSets( const SfxItemSet& rSet1, const SfxItemSet& rSet2 )
+{
+ std::optional<bool> equal = ScPatternAttr::FastEqualPatternSets( rSet1, rSet2 );
+ if(equal.has_value())
+ return *equal;
+ return rSet1 == rSet2;
+}
+
+bool ScPatternAttr::operator==( const SfxPoolItem& rCmp ) const
+{
+ // #i62090# Use quick comparison between ScPatternAttr's ItemSets
+
+ if (!SfxPoolItem::operator==(rCmp) )
+ return false;
+ if (!mxHashCode)
+ CalcHashCode();
+ auto const & rOther = static_cast<const ScPatternAttr&>(rCmp);
+ if (!rOther.mxHashCode)
+ rOther.CalcHashCode();
+ if (*mxHashCode != *rOther.mxHashCode)
+ return false;
+ return EqualPatternSets( GetItemSet(), rOther.GetItemSet() ) &&
+ StrCmp( GetStyleName(), rOther.GetStyleName() );
+}
+
+SfxPoolItem::lookup_iterator ScPatternAttr::Lookup(lookup_iterator begin, lookup_iterator end ) const
+{
+ if( !mxHashCode )
+ CalcHashCode();
+ if( *mxHashCode != 0 )
+ {
+ for( auto it = begin; it != end; ++it)
+ {
+ const ScPatternAttr* other = static_cast<const ScPatternAttr*>(*it);
+ if( !other->mxHashCode )
+ other->CalcHashCode();
+ if (*mxHashCode == *other->mxHashCode
+ && EqualPatternSets( GetItemSet(), other->GetItemSet())
+ && StrCmp( GetStyleName(), other->GetStyleName()))
+ {
+ return it;
+ }
+ }
+ }
+ return end;
+}
+
+SvxCellOrientation ScPatternAttr::GetCellOrientation( const SfxItemSet& rItemSet, const SfxItemSet* pCondSet )
+{
+ SvxCellOrientation eOrient = SvxCellOrientation::Standard;
+
+ if( GetItem( ATTR_STACKED, rItemSet, pCondSet ).GetValue() )
+ {
+ eOrient = SvxCellOrientation::Stacked;
+ }
+ else
+ {
+ Degree100 nAngle = GetItem( ATTR_ROTATE_VALUE, rItemSet, pCondSet ).GetValue();
+ if( nAngle == 9000_deg100 )
+ eOrient = SvxCellOrientation::BottomUp;
+ else if( nAngle == 27000_deg100 )
+ eOrient = SvxCellOrientation::TopBottom;
+ }
+
+ return eOrient;
+}
+
+SvxCellOrientation ScPatternAttr::GetCellOrientation( const SfxItemSet* pCondSet ) const
+{
+ return GetCellOrientation( GetItemSet(), pCondSet );
+}
+
+namespace {
+
+void getFontIDsByScriptType(SvtScriptType nScript,
+ TypedWhichId<SvxFontItem>& nFontId,
+ TypedWhichId<SvxFontHeightItem>& nHeightId,
+ TypedWhichId<SvxWeightItem>& nWeightId,
+ TypedWhichId<SvxPostureItem>& nPostureId,
+ TypedWhichId<SvxLanguageItem>& nLangId)
+{
+ if ( nScript == SvtScriptType::ASIAN )
+ {
+ nFontId = ATTR_CJK_FONT;
+ nHeightId = ATTR_CJK_FONT_HEIGHT;
+ nWeightId = ATTR_CJK_FONT_WEIGHT;
+ nPostureId = ATTR_CJK_FONT_POSTURE;
+ nLangId = ATTR_CJK_FONT_LANGUAGE;
+ }
+ else if ( nScript == SvtScriptType::COMPLEX )
+ {
+ nFontId = ATTR_CTL_FONT;
+ nHeightId = ATTR_CTL_FONT_HEIGHT;
+ nWeightId = ATTR_CTL_FONT_WEIGHT;
+ nPostureId = ATTR_CTL_FONT_POSTURE;
+ nLangId = ATTR_CTL_FONT_LANGUAGE;
+ }
+ else
+ {
+ nFontId = ATTR_FONT;
+ nHeightId = ATTR_FONT_HEIGHT;
+ nWeightId = ATTR_FONT_WEIGHT;
+ nPostureId = ATTR_FONT_POSTURE;
+ nLangId = ATTR_FONT_LANGUAGE;
+ }
+}
+
+}
+
+void ScPatternAttr::GetFont(
+ vcl::Font& rFont, const SfxItemSet& rItemSet, ScAutoFontColorMode eAutoMode,
+ const OutputDevice* pOutDev, const Fraction* pScale,
+ const SfxItemSet* pCondSet, SvtScriptType nScript,
+ const Color* pBackConfigColor, const Color* pTextConfigColor )
+{
+ // Read items
+
+ const SvxFontItem* pFontAttr;
+ sal_uInt32 nFontHeight;
+ FontWeight eWeight;
+ FontItalic eItalic;
+ FontLineStyle eUnder;
+ FontLineStyle eOver;
+ bool bWordLine;
+ FontStrikeout eStrike;
+ bool bOutline;
+ bool bShadow;
+ FontEmphasisMark eEmphasis;
+ FontRelief eRelief;
+ Color aColor;
+ LanguageType eLang;
+
+ TypedWhichId<SvxFontItem> nFontId(0);
+ TypedWhichId<SvxFontHeightItem> nHeightId(0);
+ TypedWhichId<SvxWeightItem> nWeightId(0);
+ TypedWhichId<SvxPostureItem> nPostureId(0);
+ TypedWhichId<SvxLanguageItem> nLangId(0);
+ getFontIDsByScriptType(nScript, nFontId, nHeightId, nWeightId, nPostureId, nLangId);
+
+ if ( pCondSet )
+ {
+ pFontAttr = pCondSet->GetItemIfSet( nFontId );
+ if ( !pFontAttr )
+ pFontAttr = &rItemSet.Get( nFontId );
+
+ const SvxFontHeightItem* pFontHeightItem = pCondSet->GetItemIfSet( nHeightId );
+ if ( !pFontHeightItem )
+ pFontHeightItem = &rItemSet.Get( nHeightId );
+ nFontHeight = pFontHeightItem->GetHeight();
+
+ const SvxWeightItem* pFontHWeightItem = pCondSet->GetItemIfSet( nWeightId );
+ if ( !pFontHWeightItem )
+ pFontHWeightItem = &rItemSet.Get( nWeightId );
+ eWeight = pFontHWeightItem->GetValue();
+
+ const SvxPostureItem* pPostureItem = pCondSet->GetItemIfSet( nPostureId );
+ if ( !pPostureItem )
+ pPostureItem = &rItemSet.Get( nPostureId );
+ eItalic = pPostureItem->GetValue();
+
+ const SvxUnderlineItem* pUnderlineItem = pCondSet->GetItemIfSet( ATTR_FONT_UNDERLINE );
+ if ( !pUnderlineItem )
+ pUnderlineItem = &rItemSet.Get( ATTR_FONT_UNDERLINE );
+ eUnder = pUnderlineItem->GetValue();
+
+ const SvxOverlineItem* pOverlineItem = pCondSet->GetItemIfSet( ATTR_FONT_OVERLINE );
+ if ( !pOverlineItem )
+ pOverlineItem = &rItemSet.Get( ATTR_FONT_OVERLINE );
+ eOver = pOverlineItem->GetValue();
+
+ const SvxWordLineModeItem* pWordlineItem = pCondSet->GetItemIfSet( ATTR_FONT_WORDLINE );
+ if ( !pWordlineItem )
+ pWordlineItem = &rItemSet.Get( ATTR_FONT_WORDLINE );
+ bWordLine = pWordlineItem->GetValue();
+
+ const SvxCrossedOutItem* pCrossedOutItem = pCondSet->GetItemIfSet( ATTR_FONT_CROSSEDOUT );
+ if ( !pCrossedOutItem )
+ pCrossedOutItem = &rItemSet.Get( ATTR_FONT_CROSSEDOUT );
+ eStrike = pCrossedOutItem->GetValue();
+
+ const SvxContourItem* pContourItem = pCondSet->GetItemIfSet( ATTR_FONT_CONTOUR );
+ if ( !pContourItem )
+ pContourItem = &rItemSet.Get( ATTR_FONT_CONTOUR );
+ bOutline = pContourItem->GetValue();
+
+ const SvxShadowedItem* pShadowedItem = pCondSet->GetItemIfSet( ATTR_FONT_SHADOWED );
+ if ( !pShadowedItem )
+ pShadowedItem = &rItemSet.Get( ATTR_FONT_SHADOWED );
+ bShadow = pShadowedItem->GetValue();
+
+ const SvxEmphasisMarkItem* pEmphasisMarkItem = pCondSet->GetItemIfSet( ATTR_FONT_EMPHASISMARK );
+ if ( !pEmphasisMarkItem )
+ pEmphasisMarkItem = &rItemSet.Get( ATTR_FONT_EMPHASISMARK );
+ eEmphasis = pEmphasisMarkItem->GetEmphasisMark();
+
+ const SvxCharReliefItem* pCharReliefItem = pCondSet->GetItemIfSet( ATTR_FONT_RELIEF );
+ if ( !pCharReliefItem )
+ pCharReliefItem = &rItemSet.Get( ATTR_FONT_RELIEF );
+ eRelief = pCharReliefItem->GetValue();
+
+ const SvxColorItem* pColorItem = pCondSet->GetItemIfSet( ATTR_FONT_COLOR );
+ if ( !pColorItem )
+ pColorItem = &rItemSet.Get( ATTR_FONT_COLOR );
+ aColor = pColorItem->GetValue();
+
+ const SvxLanguageItem* pLanguageItem = pCondSet->GetItemIfSet( nLangId );
+ if ( !pLanguageItem )
+ pLanguageItem = &rItemSet.Get( nLangId );
+ eLang = pLanguageItem->GetLanguage();
+ }
+ else // Everything from rItemSet
+ {
+ pFontAttr = &rItemSet.Get( nFontId );
+ nFontHeight = rItemSet.Get( nHeightId ).GetHeight();
+ eWeight = rItemSet.Get( nWeightId ).GetValue();
+ eItalic = rItemSet.Get( nPostureId ).GetValue();
+ eUnder = rItemSet.Get( ATTR_FONT_UNDERLINE ).GetValue();
+ eOver = rItemSet.Get( ATTR_FONT_OVERLINE ).GetValue();
+ bWordLine = rItemSet.Get( ATTR_FONT_WORDLINE ).GetValue();
+ eStrike = rItemSet.Get( ATTR_FONT_CROSSEDOUT ).GetValue();
+ bOutline = rItemSet.Get( ATTR_FONT_CONTOUR ).GetValue();
+ bShadow = rItemSet.Get( ATTR_FONT_SHADOWED ).GetValue();
+ eEmphasis = rItemSet.Get( ATTR_FONT_EMPHASISMARK ).GetEmphasisMark();
+ eRelief = rItemSet.Get( ATTR_FONT_RELIEF ).GetValue();
+ aColor = rItemSet.Get( ATTR_FONT_COLOR ).GetValue();
+ // for graphite language features
+ eLang = rItemSet.Get( nLangId ).GetLanguage();
+ }
+ OSL_ENSURE(pFontAttr,"Oops?");
+
+ // Evaluate
+
+ // FontItem:
+
+ if (rFont.GetFamilyName() != pFontAttr->GetFamilyName())
+ rFont.SetFamilyName( pFontAttr->GetFamilyName() );
+ if (rFont.GetStyleName() != pFontAttr->GetStyleName())
+ rFont.SetStyleName( pFontAttr->GetStyleName() );
+
+ rFont.SetFamily( pFontAttr->GetFamily() );
+ rFont.SetCharSet( pFontAttr->GetCharSet() );
+ rFont.SetPitch( pFontAttr->GetPitch() );
+
+ rFont.SetLanguage(eLang);
+
+ // Size
+
+ if ( pOutDev != nullptr )
+ {
+ Size aEffSize;
+ Fraction aFraction( 1,1 );
+ if (pScale)
+ aFraction = *pScale;
+ Size aSize( 0, static_cast<tools::Long>(nFontHeight) );
+ MapMode aDestMode = pOutDev->GetMapMode();
+ MapMode aSrcMode( MapUnit::MapTwip, Point(), aFraction, aFraction );
+ if (aDestMode.GetMapUnit() == MapUnit::MapPixel && pOutDev->GetDPIX() > 0)
+ aEffSize = pOutDev->LogicToPixel( aSize, aSrcMode );
+ else
+ {
+ Fraction aFractOne(1,1);
+ aDestMode.SetScaleX( aFractOne );
+ aDestMode.SetScaleY( aFractOne );
+ aEffSize = OutputDevice::LogicToLogic( aSize, aSrcMode, aDestMode );
+ }
+ rFont.SetFontSize( aEffSize );
+ }
+ else /* if pOutDev != NULL */
+ {
+ rFont.SetFontSize( Size( 0, static_cast<tools::Long>(nFontHeight) ) );
+ }
+
+ // determine effective font color
+
+ if ( ( aColor == COL_AUTO && eAutoMode != SC_AUTOCOL_RAW ) ||
+ eAutoMode == SC_AUTOCOL_IGNOREFONT || eAutoMode == SC_AUTOCOL_IGNOREALL )
+ {
+ if ( eAutoMode == SC_AUTOCOL_BLACK )
+ aColor = COL_BLACK;
+ else
+ {
+ // get background color from conditional or own set
+ Color aBackColor;
+ if ( pCondSet )
+ {
+ const SvxBrushItem* pItem = pCondSet->GetItemIfSet( ATTR_BACKGROUND );
+ if ( !pItem )
+ pItem = &rItemSet.Get( ATTR_BACKGROUND );
+ aBackColor = pItem->GetColor();
+ }
+ else
+ aBackColor = rItemSet.Get( ATTR_BACKGROUND ).GetColor();
+
+ // if background color attribute is transparent, use window color for brightness comparisons
+ if ( aBackColor == COL_TRANSPARENT ||
+ eAutoMode == SC_AUTOCOL_IGNOREBACK || eAutoMode == SC_AUTOCOL_IGNOREALL )
+ {
+ if ( eAutoMode == SC_AUTOCOL_PRINT )
+ aBackColor = COL_WHITE;
+ else if ( pBackConfigColor )
+ {
+ // pBackConfigColor can be used to avoid repeated lookup of the configured color
+ aBackColor = *pBackConfigColor;
+ }
+ else
+ aBackColor = SC_MOD()->GetColorConfig().GetColorValue(svtools::DOCCOLOR).nColor;
+ }
+
+ // get system text color for comparison
+ Color aSysTextColor;
+ if ( eAutoMode == SC_AUTOCOL_PRINT )
+ aSysTextColor = COL_BLACK;
+ else if ( pTextConfigColor )
+ {
+ // pTextConfigColor can be used to avoid repeated lookup of the configured color
+ aSysTextColor = *pTextConfigColor;
+ }
+ else
+ aSysTextColor = SC_MOD()->GetColorConfig().GetColorValue(svtools::FONTCOLOR).nColor;
+
+ // select the resulting color
+ if ( aBackColor.IsDark() && aSysTextColor.IsDark() )
+ {
+ // use white instead of dark on dark
+ aColor = COL_WHITE;
+ }
+ else if ( aBackColor.IsBright() && aSysTextColor.IsBright() )
+ {
+ // use black instead of bright on bright
+ aColor = COL_BLACK;
+ }
+ else
+ {
+ // use aSysTextColor (black for SC_AUTOCOL_PRINT, from style settings otherwise)
+ aColor = aSysTextColor;
+ }
+ }
+ }
+
+ // set font effects
+ rFont.SetWeight( eWeight );
+ rFont.SetItalic( eItalic );
+ rFont.SetUnderline( eUnder );
+ rFont.SetOverline( eOver );
+ rFont.SetWordLineMode( bWordLine );
+ rFont.SetStrikeout( eStrike );
+ rFont.SetOutline( bOutline );
+ rFont.SetShadow( bShadow );
+ rFont.SetEmphasisMark( eEmphasis );
+ rFont.SetRelief( eRelief );
+ rFont.SetColor( aColor );
+ rFont.SetTransparent( true );
+}
+
+void ScPatternAttr::GetFont(
+ vcl::Font& rFont, ScAutoFontColorMode eAutoMode,
+ const OutputDevice* pOutDev, const Fraction* pScale,
+ const SfxItemSet* pCondSet, SvtScriptType nScript,
+ const Color* pBackConfigColor, const Color* pTextConfigColor ) const
+{
+ GetFont( rFont, GetItemSet(), eAutoMode, pOutDev, pScale, pCondSet, nScript, pBackConfigColor, pTextConfigColor );
+}
+
+ScDxfFont ScPatternAttr::GetDxfFont(const SfxItemSet& rItemSet, SvtScriptType nScript)
+{
+ TypedWhichId<SvxFontItem> nFontId(0);
+ TypedWhichId<SvxFontHeightItem> nHeightId(0);
+ TypedWhichId<SvxWeightItem> nWeightId(0);
+ TypedWhichId<SvxPostureItem> nPostureId(0);
+ TypedWhichId<SvxLanguageItem> nLangId(0);
+ getFontIDsByScriptType(nScript, nFontId, nHeightId, nWeightId, nPostureId, nLangId);
+
+ ScDxfFont aReturn;
+
+ if ( const SvxFontItem* pItem = rItemSet.GetItemIfSet( nFontId ) )
+ {
+ aReturn.pFontAttr = pItem;
+ }
+
+ if ( const SvxFontHeightItem* pItem = rItemSet.GetItemIfSet( nHeightId ) )
+ {
+ aReturn.nFontHeight = pItem->GetHeight();
+ }
+
+ if ( const SvxWeightItem* pItem = rItemSet.GetItemIfSet( nWeightId ) )
+ {
+ aReturn.eWeight = pItem->GetValue();
+ }
+
+ if ( const SvxPostureItem* pItem = rItemSet.GetItemIfSet( nPostureId ) )
+ {
+ aReturn.eItalic = pItem->GetValue();
+ }
+
+ if ( const SvxUnderlineItem* pItem = rItemSet.GetItemIfSet( ATTR_FONT_UNDERLINE ) )
+ {
+ pItem = &rItemSet.Get( ATTR_FONT_UNDERLINE );
+ aReturn.eUnder = pItem->GetValue();
+ }
+
+ if ( const SvxOverlineItem* pItem = rItemSet.GetItemIfSet( ATTR_FONT_OVERLINE ) )
+ {
+ aReturn.eOver = pItem->GetValue();
+ }
+
+ if ( const SvxWordLineModeItem* pItem = rItemSet.GetItemIfSet( ATTR_FONT_WORDLINE ) )
+ {
+ aReturn.bWordLine = pItem->GetValue();
+ }
+
+ if ( const SvxCrossedOutItem* pItem = rItemSet.GetItemIfSet( ATTR_FONT_CROSSEDOUT ) )
+ {
+ pItem = &rItemSet.Get( ATTR_FONT_CROSSEDOUT );
+ aReturn.eStrike = pItem->GetValue();
+ }
+
+ if ( const SvxContourItem* pItem = rItemSet.GetItemIfSet( ATTR_FONT_CONTOUR ) )
+ {
+ aReturn.bOutline = pItem->GetValue();
+ }
+
+ if ( const SvxShadowedItem* pItem = rItemSet.GetItemIfSet( ATTR_FONT_SHADOWED ) )
+ {
+ pItem = &rItemSet.Get( ATTR_FONT_SHADOWED );
+ aReturn.bShadow = pItem->GetValue();
+ }
+
+ if ( const SvxEmphasisMarkItem* pItem = rItemSet.GetItemIfSet( ATTR_FONT_EMPHASISMARK ) )
+ {
+ aReturn.eEmphasis = pItem->GetEmphasisMark();
+ }
+
+ if ( const SvxCharReliefItem* pItem = rItemSet.GetItemIfSet( ATTR_FONT_RELIEF ) )
+ {
+ aReturn.eRelief = pItem->GetValue();
+ }
+
+ if ( const SvxColorItem* pItem = rItemSet.GetItemIfSet( ATTR_FONT_COLOR ) )
+ {
+ aReturn.aColor = pItem->GetValue();
+ }
+
+ if ( const SvxLanguageItem* pItem = rItemSet.GetItemIfSet( nLangId ) )
+ {
+ aReturn.eLang = pItem->GetLanguage();
+ }
+
+ return aReturn;
+}
+
+template <class T>
+static void lcl_populate( std::unique_ptr<T>& rxItem, TypedWhichId<T> nWhich, const SfxItemSet& rSrcSet, const SfxItemSet* pCondSet )
+{
+ const T* pItem = pCondSet->GetItemIfSet( nWhich );
+ if ( !pItem )
+ pItem = &rSrcSet.Get( nWhich );
+ rxItem.reset(pItem->Clone());
+}
+
+void ScPatternAttr::FillToEditItemSet( SfxItemSet& rEditSet, const SfxItemSet& rSrcSet, const SfxItemSet* pCondSet )
+{
+ // Read Items
+
+ std::unique_ptr<SvxColorItem> aColorItem(std::make_unique<SvxColorItem>(EE_CHAR_COLOR)); // use item as-is
+ std::unique_ptr<SvxFontItem> aFontItem(std::make_unique<SvxFontItem>(EE_CHAR_FONTINFO)); // use item as-is
+ std::unique_ptr<SvxFontItem> aCjkFontItem(std::make_unique<SvxFontItem>(EE_CHAR_FONTINFO_CJK)); // use item as-is
+ std::unique_ptr<SvxFontItem> aCtlFontItem(std::make_unique<SvxFontItem>(EE_CHAR_FONTINFO_CTL)); // use item as-is
+ tools::Long nTHeight, nCjkTHeight, nCtlTHeight; // Twips
+ FontWeight eWeight, eCjkWeight, eCtlWeight;
+ std::unique_ptr<SvxUnderlineItem> aUnderlineItem(std::make_unique<SvxUnderlineItem>(LINESTYLE_NONE, EE_CHAR_UNDERLINE));
+ std::unique_ptr<SvxOverlineItem> aOverlineItem(std::make_unique<SvxOverlineItem>(LINESTYLE_NONE, EE_CHAR_OVERLINE));
+ bool bWordLine;
+ FontStrikeout eStrike;
+ FontItalic eItalic, eCjkItalic, eCtlItalic;
+ bool bOutline;
+ bool bShadow;
+ bool bForbidden;
+ FontEmphasisMark eEmphasis;
+ FontRelief eRelief;
+ LanguageType eLang, eCjkLang, eCtlLang;
+ bool bHyphenate;
+ SvxFrameDirection eDirection;
+
+ //TODO: additional parameter to control if language is needed?
+
+ if ( pCondSet )
+ {
+ lcl_populate(aColorItem, ATTR_FONT_COLOR, rSrcSet, pCondSet);
+ lcl_populate(aFontItem, ATTR_FONT, rSrcSet, pCondSet);
+ lcl_populate(aCjkFontItem, ATTR_CJK_FONT, rSrcSet, pCondSet);
+ lcl_populate(aCtlFontItem, ATTR_CTL_FONT, rSrcSet, pCondSet);
+
+ const SvxFontHeightItem* pFontHeightItem = pCondSet->GetItemIfSet( ATTR_FONT_HEIGHT );
+ if (!pFontHeightItem)
+ pFontHeightItem = &rSrcSet.Get( ATTR_FONT_HEIGHT );
+ nTHeight = pFontHeightItem->GetHeight();
+ pFontHeightItem = pCondSet->GetItemIfSet( ATTR_CJK_FONT_HEIGHT );
+ if ( !pFontHeightItem )
+ pFontHeightItem = &rSrcSet.Get( ATTR_CJK_FONT_HEIGHT );
+ nCjkTHeight = pFontHeightItem->GetHeight();
+ pFontHeightItem = pCondSet->GetItemIfSet( ATTR_CTL_FONT_HEIGHT );
+ if ( !pFontHeightItem )
+ pFontHeightItem = &rSrcSet.Get( ATTR_CTL_FONT_HEIGHT );
+ nCtlTHeight = pFontHeightItem->GetHeight();
+
+ const SvxWeightItem* pWeightItem = pCondSet->GetItemIfSet( ATTR_FONT_WEIGHT );
+ if ( !pWeightItem )
+ pWeightItem = &rSrcSet.Get( ATTR_FONT_WEIGHT );
+ eWeight = pWeightItem->GetValue();
+ pWeightItem = pCondSet->GetItemIfSet( ATTR_CJK_FONT_WEIGHT );
+ if ( !pWeightItem )
+ pWeightItem = &rSrcSet.Get( ATTR_CJK_FONT_WEIGHT );
+ eCjkWeight = pWeightItem->GetValue();
+ pWeightItem = pCondSet->GetItemIfSet( ATTR_CTL_FONT_WEIGHT );
+ if ( !pWeightItem )
+ pWeightItem = &rSrcSet.Get( ATTR_CTL_FONT_WEIGHT );
+ eCtlWeight = pWeightItem->GetValue();
+
+ const SvxPostureItem* pPostureItem = pCondSet->GetItemIfSet( ATTR_FONT_POSTURE );
+ if ( !pPostureItem )
+ pPostureItem = &rSrcSet.Get( ATTR_FONT_POSTURE );
+ eItalic = pPostureItem->GetValue();
+ pPostureItem = pCondSet->GetItemIfSet( ATTR_CJK_FONT_POSTURE );
+ if ( !pPostureItem )
+ pPostureItem = &rSrcSet.Get( ATTR_CJK_FONT_POSTURE );
+ eCjkItalic = pPostureItem->GetValue();
+ pPostureItem = pCondSet->GetItemIfSet( ATTR_CTL_FONT_POSTURE );
+ if ( !pPostureItem )
+ pPostureItem = &rSrcSet.Get( ATTR_CTL_FONT_POSTURE );
+ eCtlItalic = pPostureItem->GetValue();
+
+ lcl_populate(aUnderlineItem, ATTR_FONT_UNDERLINE, rSrcSet, pCondSet);
+ lcl_populate(aOverlineItem, ATTR_FONT_OVERLINE, rSrcSet, pCondSet);
+
+ const SvxWordLineModeItem* pWordLineModeItem = pCondSet->GetItemIfSet( ATTR_FONT_WORDLINE );
+ if ( !pWordLineModeItem )
+ pWordLineModeItem = &rSrcSet.Get( ATTR_FONT_WORDLINE );
+ bWordLine = pWordLineModeItem->GetValue();
+
+ const SvxCrossedOutItem* pCrossedOutItem = pCondSet->GetItemIfSet( ATTR_FONT_CROSSEDOUT );
+ if ( !pCrossedOutItem )
+ pCrossedOutItem = &rSrcSet.Get( ATTR_FONT_CROSSEDOUT );
+ eStrike = pCrossedOutItem->GetValue();
+
+ const SvxContourItem* pContourItem = pCondSet->GetItemIfSet( ATTR_FONT_CONTOUR );
+ if ( !pContourItem )
+ pContourItem = &rSrcSet.Get( ATTR_FONT_CONTOUR );
+ bOutline = pContourItem->GetValue();
+
+ const SvxShadowedItem* pShadowedItem = pCondSet->GetItemIfSet( ATTR_FONT_SHADOWED );
+ if ( !pShadowedItem )
+ pShadowedItem = &rSrcSet.Get( ATTR_FONT_SHADOWED );
+ bShadow = pShadowedItem->GetValue();
+
+ const SvxForbiddenRuleItem* pForbiddenRuleItem = pCondSet->GetItemIfSet( ATTR_FORBIDDEN_RULES );
+ if ( !pForbiddenRuleItem )
+ pForbiddenRuleItem = &rSrcSet.Get( ATTR_FORBIDDEN_RULES );
+ bForbidden = pForbiddenRuleItem->GetValue();
+
+ const SvxEmphasisMarkItem* pEmphasisMarkItem = pCondSet->GetItemIfSet( ATTR_FONT_EMPHASISMARK );
+ if ( !pEmphasisMarkItem )
+ pEmphasisMarkItem = &rSrcSet.Get( ATTR_FONT_EMPHASISMARK );
+ eEmphasis = pEmphasisMarkItem->GetEmphasisMark();
+ const SvxCharReliefItem* pCharReliefItem = pCondSet->GetItemIfSet( ATTR_FONT_RELIEF );
+ if ( !pCharReliefItem )
+ pCharReliefItem = &rSrcSet.Get( ATTR_FONT_RELIEF );
+ eRelief = pCharReliefItem->GetValue();
+
+ const SvxLanguageItem* pLanguageItem = pCondSet->GetItemIfSet( ATTR_FONT_LANGUAGE );
+ if ( !pLanguageItem )
+ pLanguageItem = &rSrcSet.Get( ATTR_FONT_LANGUAGE );
+ eLang = pLanguageItem->GetLanguage();
+ pLanguageItem = pCondSet->GetItemIfSet( ATTR_CJK_FONT_LANGUAGE );
+ if ( !pLanguageItem )
+ pLanguageItem = &rSrcSet.Get( ATTR_CJK_FONT_LANGUAGE );
+ eCjkLang = pLanguageItem->GetLanguage();
+ pLanguageItem = pCondSet->GetItemIfSet( ATTR_CTL_FONT_LANGUAGE );
+ if ( !pLanguageItem )
+ pLanguageItem = &rSrcSet.Get( ATTR_CTL_FONT_LANGUAGE );
+ eCtlLang = pLanguageItem->GetLanguage();
+
+ const ScHyphenateCell* pHyphenateCell = pCondSet->GetItemIfSet( ATTR_HYPHENATE );
+ if ( !pHyphenateCell )
+ pHyphenateCell = &rSrcSet.Get( ATTR_HYPHENATE );
+ bHyphenate = pHyphenateCell->GetValue();
+
+ const SvxFrameDirectionItem* pFrameDirectionItem = pCondSet->GetItemIfSet( ATTR_WRITINGDIR );
+ if ( !pFrameDirectionItem )
+ pFrameDirectionItem = &rSrcSet.Get( ATTR_WRITINGDIR );
+ eDirection = pFrameDirectionItem->GetValue();
+ }
+ else // Everything directly from Pattern
+ {
+ aColorItem.reset(rSrcSet.Get(ATTR_FONT_COLOR).Clone());
+ aFontItem.reset(rSrcSet.Get(ATTR_FONT).Clone());
+ aCjkFontItem.reset(rSrcSet.Get(ATTR_CJK_FONT).Clone());
+ aCtlFontItem.reset(rSrcSet.Get(ATTR_CTL_FONT).Clone());
+ nTHeight = rSrcSet.Get( ATTR_FONT_HEIGHT ).GetHeight();
+ nCjkTHeight = rSrcSet.Get( ATTR_CJK_FONT_HEIGHT ).GetHeight();
+ nCtlTHeight = rSrcSet.Get( ATTR_CTL_FONT_HEIGHT ).GetHeight();
+ eWeight = rSrcSet.Get( ATTR_FONT_WEIGHT ).GetValue();
+ eCjkWeight = rSrcSet.Get( ATTR_CJK_FONT_WEIGHT ).GetValue();
+ eCtlWeight = rSrcSet.Get( ATTR_CTL_FONT_WEIGHT ).GetValue();
+ eItalic = rSrcSet.Get( ATTR_FONT_POSTURE ).GetValue();
+ eCjkItalic = rSrcSet.Get( ATTR_CJK_FONT_POSTURE ).GetValue();
+ eCtlItalic = rSrcSet.Get( ATTR_CTL_FONT_POSTURE ).GetValue();
+ aUnderlineItem.reset(rSrcSet.Get(ATTR_FONT_UNDERLINE).Clone());
+ aOverlineItem.reset(rSrcSet.Get(ATTR_FONT_OVERLINE).Clone());
+ bWordLine = rSrcSet.Get( ATTR_FONT_WORDLINE ).GetValue();
+ eStrike = rSrcSet.Get( ATTR_FONT_CROSSEDOUT ).GetValue();
+ bOutline = rSrcSet.Get( ATTR_FONT_CONTOUR ).GetValue();
+ bShadow = rSrcSet.Get( ATTR_FONT_SHADOWED ).GetValue();
+ bForbidden = rSrcSet.Get( ATTR_FORBIDDEN_RULES ).GetValue();
+ eEmphasis = rSrcSet.Get( ATTR_FONT_EMPHASISMARK ).GetEmphasisMark();
+ eRelief = rSrcSet.Get( ATTR_FONT_RELIEF ).GetValue();
+ eLang = rSrcSet.Get( ATTR_FONT_LANGUAGE ).GetLanguage();
+ eCjkLang = rSrcSet.Get( ATTR_CJK_FONT_LANGUAGE ).GetLanguage();
+ eCtlLang = rSrcSet.Get( ATTR_CTL_FONT_LANGUAGE ).GetLanguage();
+ bHyphenate = rSrcSet.Get( ATTR_HYPHENATE ).GetValue();
+ eDirection = rSrcSet.Get( ATTR_WRITINGDIR ).GetValue();
+ }
+
+ // Expect to be compatible to LogicToLogic, ie. 2540/1440 = 127/72, and round
+
+ tools::Long nHeight = convertTwipToMm100(nTHeight);
+ tools::Long nCjkHeight = convertTwipToMm100(nCjkTHeight);
+ tools::Long nCtlHeight = convertTwipToMm100(nCtlTHeight);
+
+ // put items into EditEngine ItemSet
+
+ if ( aColorItem->GetValue() == COL_AUTO )
+ {
+ // When cell attributes are converted to EditEngine paragraph attributes,
+ // don't create a hard item for automatic color, because that would be converted
+ // to black when the item's Store method is used in CreateTransferable/WriteBin.
+ // COL_AUTO is the EditEngine's pool default, so ClearItem will result in automatic
+ // color, too, without having to store the item.
+ rEditSet.ClearItem( EE_CHAR_COLOR );
+ }
+ else
+ {
+ // tdf#125054 adapt WhichID
+ rEditSet.Put( std::move(aColorItem), EE_CHAR_COLOR );
+ }
+
+ // tdf#125054 adapt WhichID
+ rEditSet.Put( std::move(aFontItem), EE_CHAR_FONTINFO );
+ rEditSet.Put( std::move(aCjkFontItem), EE_CHAR_FONTINFO_CJK );
+ rEditSet.Put( std::move(aCtlFontItem), EE_CHAR_FONTINFO_CTL );
+
+ rEditSet.Put( SvxFontHeightItem( nHeight, 100, EE_CHAR_FONTHEIGHT ) );
+ rEditSet.Put( SvxFontHeightItem( nCjkHeight, 100, EE_CHAR_FONTHEIGHT_CJK ) );
+ rEditSet.Put( SvxFontHeightItem( nCtlHeight, 100, EE_CHAR_FONTHEIGHT_CTL ) );
+ rEditSet.Put( SvxWeightItem ( eWeight, EE_CHAR_WEIGHT ) );
+ rEditSet.Put( SvxWeightItem ( eCjkWeight, EE_CHAR_WEIGHT_CJK ) );
+ rEditSet.Put( SvxWeightItem ( eCtlWeight, EE_CHAR_WEIGHT_CTL ) );
+
+ // tdf#125054 adapt WhichID
+ rEditSet.Put( std::move(aUnderlineItem), EE_CHAR_UNDERLINE );
+ rEditSet.Put( std::move(aOverlineItem), EE_CHAR_OVERLINE );
+
+ rEditSet.Put( SvxWordLineModeItem( bWordLine, EE_CHAR_WLM ) );
+ rEditSet.Put( SvxCrossedOutItem( eStrike, EE_CHAR_STRIKEOUT ) );
+ rEditSet.Put( SvxPostureItem ( eItalic, EE_CHAR_ITALIC ) );
+ rEditSet.Put( SvxPostureItem ( eCjkItalic, EE_CHAR_ITALIC_CJK ) );
+ rEditSet.Put( SvxPostureItem ( eCtlItalic, EE_CHAR_ITALIC_CTL ) );
+ rEditSet.Put( SvxContourItem ( bOutline, EE_CHAR_OUTLINE ) );
+ rEditSet.Put( SvxShadowedItem ( bShadow, EE_CHAR_SHADOW ) );
+ rEditSet.Put( SvxForbiddenRuleItem(bForbidden, EE_PARA_FORBIDDENRULES) );
+ rEditSet.Put( SvxEmphasisMarkItem( eEmphasis, EE_CHAR_EMPHASISMARK ) );
+ rEditSet.Put( SvxCharReliefItem( eRelief, EE_CHAR_RELIEF ) );
+ rEditSet.Put( SvxLanguageItem ( eLang, EE_CHAR_LANGUAGE ) );
+ rEditSet.Put( SvxLanguageItem ( eCjkLang, EE_CHAR_LANGUAGE_CJK ) );
+ rEditSet.Put( SvxLanguageItem ( eCtlLang, EE_CHAR_LANGUAGE_CTL ) );
+ rEditSet.Put( SfxBoolItem ( EE_PARA_HYPHENATE, bHyphenate ) );
+ rEditSet.Put( SvxFrameDirectionItem( eDirection, EE_PARA_WRITINGDIR ) );
+
+ // Script spacing is always off.
+ // The cell attribute isn't used here as long as there is no UI to set it
+ // (don't evaluate attributes that can't be changed).
+ // If a locale-dependent default is needed, it has to go into the cell
+ // style, like the fonts.
+ rEditSet.Put( SvxScriptSpaceItem( false, EE_PARA_ASIANCJKSPACING ) );
+}
+
+void ScPatternAttr::FillEditItemSet( SfxItemSet* pEditSet, const SfxItemSet* pCondSet ) const
+{
+ if( pEditSet )
+ FillToEditItemSet( *pEditSet, GetItemSet(), pCondSet );
+}
+
+void ScPatternAttr::GetFromEditItemSet( SfxItemSet& rDestSet, const SfxItemSet& rEditSet )
+{
+ if (const SvxColorItem* pItem = rEditSet.GetItemIfSet(EE_CHAR_COLOR))
+ rDestSet.Put( *pItem, ATTR_FONT_COLOR );
+
+ if (const SvxFontItem* pItem = rEditSet.GetItemIfSet(EE_CHAR_FONTINFO))
+ rDestSet.Put( *pItem, ATTR_FONT );
+ if (const SvxFontItem* pItem = rEditSet.GetItemIfSet(EE_CHAR_FONTINFO_CJK))
+ rDestSet.Put( *pItem, ATTR_CJK_FONT );
+ if (const SvxFontItem* pItem = rEditSet.GetItemIfSet(EE_CHAR_FONTINFO_CTL))
+ rDestSet.Put( *pItem, ATTR_CTL_FONT );
+
+ if (const SvxFontHeightItem* pItem = rEditSet.GetItemIfSet(EE_CHAR_FONTHEIGHT))
+ rDestSet.Put( SvxFontHeightItem(o3tl::toTwips(pItem->GetHeight(), o3tl::Length::mm100),
+ 100, ATTR_FONT_HEIGHT ) );
+ if (const SvxFontHeightItem* pItem = rEditSet.GetItemIfSet(EE_CHAR_FONTHEIGHT_CJK))
+ rDestSet.Put( SvxFontHeightItem(o3tl::toTwips(pItem->GetHeight(), o3tl::Length::mm100),
+ 100, ATTR_CJK_FONT_HEIGHT ) );
+ if (const SvxFontHeightItem* pItem = rEditSet.GetItemIfSet(EE_CHAR_FONTHEIGHT_CTL))
+ rDestSet.Put( SvxFontHeightItem(o3tl::toTwips(pItem->GetHeight(), o3tl::Length::mm100),
+ 100, ATTR_CTL_FONT_HEIGHT ) );
+
+ if (const SvxWeightItem* pItem = rEditSet.GetItemIfSet(EE_CHAR_WEIGHT))
+ rDestSet.Put( SvxWeightItem( pItem->GetValue(),
+ ATTR_FONT_WEIGHT) );
+ if (const SvxWeightItem* pItem = rEditSet.GetItemIfSet(EE_CHAR_WEIGHT_CJK))
+ rDestSet.Put( SvxWeightItem( pItem->GetValue(),
+ ATTR_CJK_FONT_WEIGHT) );
+ if (const SvxWeightItem* pItem = rEditSet.GetItemIfSet(EE_CHAR_WEIGHT_CTL))
+ rDestSet.Put( SvxWeightItem( pItem->GetValue(),
+ ATTR_CTL_FONT_WEIGHT) );
+
+ // SvxTextLineItem contains enum and color
+ if (const SvxUnderlineItem* pItem = rEditSet.GetItemIfSet(EE_CHAR_UNDERLINE))
+ rDestSet.Put( *pItem, ATTR_FONT_UNDERLINE );
+ if (const SvxOverlineItem* pItem = rEditSet.GetItemIfSet(EE_CHAR_OVERLINE))
+ rDestSet.Put( *pItem, ATTR_FONT_OVERLINE );
+ if (const SvxWordLineModeItem* pItem = rEditSet.GetItemIfSet(EE_CHAR_WLM))
+ rDestSet.Put( SvxWordLineModeItem( pItem->GetValue(),
+ ATTR_FONT_WORDLINE) );
+
+ if (const SvxCrossedOutItem* pItem = rEditSet.GetItemIfSet(EE_CHAR_STRIKEOUT))
+ rDestSet.Put( SvxCrossedOutItem( pItem->GetValue(),
+ ATTR_FONT_CROSSEDOUT) );
+
+ if (const SvxPostureItem* pItem = rEditSet.GetItemIfSet(EE_CHAR_ITALIC))
+ rDestSet.Put( SvxPostureItem( pItem->GetValue(),
+ ATTR_FONT_POSTURE) );
+ if (const SvxPostureItem* pItem = rEditSet.GetItemIfSet(EE_CHAR_ITALIC_CJK))
+ rDestSet.Put( SvxPostureItem( pItem->GetValue(),
+ ATTR_CJK_FONT_POSTURE) );
+ if (const SvxPostureItem* pItem = rEditSet.GetItemIfSet(EE_CHAR_ITALIC_CTL))
+ rDestSet.Put( SvxPostureItem( pItem->GetValue(),
+ ATTR_CTL_FONT_POSTURE) );
+
+ if (const SvxContourItem* pItem = rEditSet.GetItemIfSet(EE_CHAR_OUTLINE))
+ rDestSet.Put( SvxContourItem( pItem->GetValue(),
+ ATTR_FONT_CONTOUR) );
+ if (const SvxShadowedItem* pItem = rEditSet.GetItemIfSet(EE_CHAR_SHADOW))
+ rDestSet.Put( SvxShadowedItem( pItem->GetValue(),
+ ATTR_FONT_SHADOWED) );
+ if (const SvxEmphasisMarkItem* pItem = rEditSet.GetItemIfSet(EE_CHAR_EMPHASISMARK))
+ rDestSet.Put( SvxEmphasisMarkItem( pItem->GetEmphasisMark(),
+ ATTR_FONT_EMPHASISMARK) );
+ if (const SvxCharReliefItem* pItem = rEditSet.GetItemIfSet(EE_CHAR_RELIEF))
+ rDestSet.Put( SvxCharReliefItem( pItem->GetValue(),
+ ATTR_FONT_RELIEF) );
+
+ if (const SvxLanguageItem* pItem = rEditSet.GetItemIfSet(EE_CHAR_LANGUAGE))
+ rDestSet.Put( SvxLanguageItem(pItem->GetValue(), ATTR_FONT_LANGUAGE) );
+ if (const SvxLanguageItem* pItem = rEditSet.GetItemIfSet(EE_CHAR_LANGUAGE_CJK))
+ rDestSet.Put( SvxLanguageItem(pItem->GetValue(), ATTR_CJK_FONT_LANGUAGE) );
+ if (const SvxLanguageItem* pItem = rEditSet.GetItemIfSet(EE_CHAR_LANGUAGE_CTL))
+ rDestSet.Put( SvxLanguageItem(pItem->GetValue(), ATTR_CTL_FONT_LANGUAGE) );
+
+ const SvxAdjustItem* pAdjustItem = rEditSet.GetItemIfSet(EE_PARA_JUST);
+
+ if (!pAdjustItem)
+ return;
+
+ SvxCellHorJustify eVal;
+ switch ( pAdjustItem->GetAdjust() )
+ {
+ case SvxAdjust::Left:
+ // EditEngine Default is always set in the GetAttribs() ItemSet !
+ // whether left or right, is decided in text / number
+ eVal = SvxCellHorJustify::Standard;
+ break;
+ case SvxAdjust::Right:
+ eVal = SvxCellHorJustify::Right;
+ break;
+ case SvxAdjust::Block:
+ eVal = SvxCellHorJustify::Block;
+ break;
+ case SvxAdjust::Center:
+ eVal = SvxCellHorJustify::Center;
+ break;
+ case SvxAdjust::BlockLine:
+ eVal = SvxCellHorJustify::Block;
+ break;
+ case SvxAdjust::End:
+ eVal = SvxCellHorJustify::Right;
+ break;
+ default:
+ eVal = SvxCellHorJustify::Standard;
+ }
+ if ( eVal != SvxCellHorJustify::Standard )
+ rDestSet.Put( SvxHorJustifyItem( eVal, ATTR_HOR_JUSTIFY) );
+}
+
+void ScPatternAttr::GetFromEditItemSet( const SfxItemSet* pEditSet )
+{
+ if( !pEditSet )
+ return;
+ GetFromEditItemSet( GetItemSet(), *pEditSet );
+ mxHashCode.reset();
+}
+
+void ScPatternAttr::FillEditParaItems( SfxItemSet* pEditSet ) const
+{
+ // already there in GetFromEditItemSet, but not in FillEditItemSet
+ // Default horizontal alignment is always implemented as left
+
+ const SfxItemSet& rMySet = GetItemSet();
+
+ SvxCellHorJustify eHorJust = rMySet.Get(ATTR_HOR_JUSTIFY).GetValue();
+
+ SvxAdjust eSvxAdjust;
+ switch (eHorJust)
+ {
+ case SvxCellHorJustify::Right: eSvxAdjust = SvxAdjust::Right; break;
+ case SvxCellHorJustify::Center: eSvxAdjust = SvxAdjust::Center; break;
+ case SvxCellHorJustify::Block: eSvxAdjust = SvxAdjust::Block; break;
+ default: eSvxAdjust = SvxAdjust::Left; break;
+ }
+ pEditSet->Put( SvxAdjustItem( eSvxAdjust, EE_PARA_JUST ) );
+}
+
+void ScPatternAttr::DeleteUnchanged( const ScPatternAttr* pOldAttrs )
+{
+ SfxItemSet& rThisSet = GetItemSet();
+ const SfxItemSet& rOldSet = pOldAttrs->GetItemSet();
+
+ const SfxPoolItem* pThisItem;
+ const SfxPoolItem* pOldItem;
+
+ for ( sal_uInt16 nSubWhich=ATTR_PATTERN_START; nSubWhich<=ATTR_PATTERN_END; nSubWhich++ )
+ {
+ // only items that are set are interesting
+ if ( rThisSet.GetItemState( nSubWhich, false, &pThisItem ) == SfxItemState::SET )
+ {
+ SfxItemState eOldState = rOldSet.GetItemState( nSubWhich, true, &pOldItem );
+ if ( eOldState == SfxItemState::SET )
+ {
+ // item is set in OldAttrs (or its parent) -> compare pointers
+ if ( pThisItem == pOldItem )
+ {
+ rThisSet.ClearItem( nSubWhich );
+ mxHashCode.reset();
+ }
+ }
+ else if ( eOldState != SfxItemState::DONTCARE )
+ {
+ // not set in OldAttrs -> compare item value to default item
+ if ( *pThisItem == rThisSet.GetPool()->GetDefaultItem( nSubWhich ) )
+ {
+ rThisSet.ClearItem( nSubWhich );
+ mxHashCode.reset();
+ }
+ }
+ }
+ }
+}
+
+bool ScPatternAttr::HasItemsSet( const sal_uInt16* pWhich ) const
+{
+ const SfxItemSet& rSet = GetItemSet();
+ for (sal_uInt16 i=0; pWhich[i]; i++)
+ if ( rSet.GetItemState( pWhich[i], false ) == SfxItemState::SET )
+ return true;
+ return false;
+}
+
+void ScPatternAttr::ClearItems( const sal_uInt16* pWhich )
+{
+ SfxItemSet& rSet = GetItemSet();
+ for (sal_uInt16 i=0; pWhich[i]; i++)
+ rSet.ClearItem(pWhich[i]);
+ mxHashCode.reset();
+}
+
+static SfxStyleSheetBase* lcl_CopyStyleToPool
+ (
+ SfxStyleSheetBase* pSrcStyle,
+ SfxStyleSheetBasePool* pSrcPool,
+ SfxStyleSheetBasePool* pDestPool,
+ const SvNumberFormatterIndexTable* pFormatExchangeList
+ )
+{
+ if ( !pSrcStyle || !pDestPool || !pSrcPool )
+ {
+ OSL_FAIL( "CopyStyleToPool: Invalid Arguments :-/" );
+ return nullptr;
+ }
+
+ const OUString aStrSrcStyle = pSrcStyle->GetName();
+ const SfxStyleFamily eFamily = pSrcStyle->GetFamily();
+ SfxStyleSheetBase* pDestStyle = pDestPool->Find( aStrSrcStyle, eFamily );
+
+ if ( !pDestStyle )
+ {
+ const OUString aStrParent = pSrcStyle->GetParent();
+ const SfxItemSet& rSrcSet = pSrcStyle->GetItemSet();
+
+ pDestStyle = &pDestPool->Make( aStrSrcStyle, eFamily, SfxStyleSearchBits::UserDefined );
+ SfxItemSet& rDestSet = pDestStyle->GetItemSet();
+ rDestSet.Put( rSrcSet );
+
+ // number format exchange list has to be handled here, too
+ // (only called for cell styles)
+
+ const SfxUInt32Item* pSrcItem;
+ if ( pFormatExchangeList &&
+ (pSrcItem = rSrcSet.GetItemIfSet( ATTR_VALUE_FORMAT, false )) )
+ {
+ sal_uLong nOldFormat = pSrcItem->GetValue();
+ SvNumberFormatterIndexTable::const_iterator it = pFormatExchangeList->find(nOldFormat);
+ if (it != pFormatExchangeList->end())
+ {
+ sal_uInt32 nNewFormat = it->second;
+ rDestSet.Put( SfxUInt32Item( ATTR_VALUE_FORMAT, nNewFormat ) );
+ }
+ }
+
+ // if necessary create derivative Styles, if not available:
+
+ if ( ScResId(STR_STYLENAME_STANDARD) != aStrParent &&
+ aStrSrcStyle != aStrParent &&
+ !pDestPool->Find( aStrParent, eFamily ) )
+ {
+ lcl_CopyStyleToPool( pSrcPool->Find( aStrParent, eFamily ),
+ pSrcPool, pDestPool, pFormatExchangeList );
+ }
+
+ pDestStyle->SetParent( aStrParent );
+ }
+
+ return pDestStyle;
+}
+
+ScPatternAttr* ScPatternAttr::PutInPool( ScDocument* pDestDoc, ScDocument* pSrcDoc ) const
+{
+ const SfxItemSet* pSrcSet = &GetItemSet();
+
+ ScPatternAttr aDestPattern( pDestDoc->GetPool() );
+ SfxItemSet* pDestSet = &aDestPattern.GetItemSet();
+
+ // Copy cell pattern style to other document:
+
+ if ( pDestDoc != pSrcDoc )
+ {
+ OSL_ENSURE( pStyle, "Missing Pattern-Style! :-/" );
+
+ // if pattern in DestDoc is available, use this, otherwise copy
+ // parent style to style or create if necessary and attach DestDoc
+
+ SfxStyleSheetBase* pStyleCpy = lcl_CopyStyleToPool( pStyle,
+ pSrcDoc->GetStyleSheetPool(),
+ pDestDoc->GetStyleSheetPool(),
+ pDestDoc->GetFormatExchangeList() );
+
+ aDestPattern.SetStyleSheet( static_cast<ScStyleSheet*>(pStyleCpy) );
+ }
+
+ for ( sal_uInt16 nAttrId = ATTR_PATTERN_START; nAttrId <= ATTR_PATTERN_END; nAttrId++ )
+ {
+ const SfxPoolItem* pSrcItem;
+ SfxItemState eItemState = pSrcSet->GetItemState( nAttrId, false, &pSrcItem );
+ if (eItemState==SfxItemState::SET)
+ {
+ std::unique_ptr<SfxPoolItem> pNewItem;
+
+ if ( nAttrId == ATTR_VALIDDATA )
+ {
+ // Copy validity to the new document
+
+ sal_uLong nNewIndex = 0;
+ ScValidationDataList* pSrcList = pSrcDoc->GetValidationList();
+ if ( pSrcList )
+ {
+ sal_uLong nOldIndex = static_cast<const SfxUInt32Item*>(pSrcItem)->GetValue();
+ const ScValidationData* pOldData = pSrcList->GetData( nOldIndex );
+ if ( pOldData )
+ nNewIndex = pDestDoc->AddValidationEntry( *pOldData );
+ }
+ pNewItem.reset(new SfxUInt32Item( ATTR_VALIDDATA, nNewIndex ));
+ }
+ else if ( nAttrId == ATTR_VALUE_FORMAT && pDestDoc->GetFormatExchangeList() )
+ {
+ // Number format to Exchange List
+
+ sal_uLong nOldFormat = static_cast<const SfxUInt32Item*>(pSrcItem)->GetValue();
+ SvNumberFormatterIndexTable::const_iterator it = pDestDoc->GetFormatExchangeList()->find(nOldFormat);
+ if (it != pDestDoc->GetFormatExchangeList()->end())
+ {
+ sal_uInt32 nNewFormat = it->second;
+ pNewItem.reset(new SfxUInt32Item( ATTR_VALUE_FORMAT, nNewFormat ));
+ }
+ }
+
+ if ( pNewItem )
+ {
+ pDestSet->Put(std::move(pNewItem));
+ }
+ else
+ pDestSet->Put(*pSrcItem);
+ }
+ }
+
+ ScPatternAttr* pPatternAttr = const_cast<ScPatternAttr*>( &pDestDoc->GetPool()->Put(aDestPattern) );
+ return pPatternAttr;
+}
+
+bool ScPatternAttr::IsVisible() const
+{
+ const SfxItemSet& rSet = GetItemSet();
+
+ if ( const SvxBrushItem* pItem = rSet.GetItemIfSet( ATTR_BACKGROUND ) )
+ if ( pItem->GetColor() != COL_TRANSPARENT )
+ return true;
+
+ if ( const SvxBoxItem* pBoxItem = rSet.GetItemIfSet( ATTR_BORDER ) )
+ {
+ if ( pBoxItem->GetTop() || pBoxItem->GetBottom() ||
+ pBoxItem->GetLeft() || pBoxItem->GetRight() )
+ return true;
+ }
+
+ if ( const SvxLineItem* pItem = rSet.GetItemIfSet( ATTR_BORDER_TLBR ) )
+ if( pItem->GetLine() )
+ return true;
+
+ if ( const SvxLineItem* pItem = rSet.GetItemIfSet( ATTR_BORDER_BLTR ) )
+ if( pItem->GetLine() )
+ return true;
+
+ if ( const SvxShadowItem* pItem = rSet.GetItemIfSet( ATTR_SHADOW ) )
+ if ( pItem->GetLocation() != SvxShadowLocation::NONE )
+ return true;
+
+ return false;
+}
+
+static bool OneEqual( const SfxItemSet& rSet1, const SfxItemSet& rSet2, sal_uInt16 nId )
+{
+ const SfxPoolItem* pItem1 = &rSet1.Get(nId);
+ const SfxPoolItem* pItem2 = &rSet2.Get(nId);
+ return ( pItem1 == pItem2 || *pItem1 == *pItem2 );
+}
+
+bool ScPatternAttr::IsVisibleEqual( const ScPatternAttr& rOther ) const
+{
+ const SfxItemSet& rThisSet = GetItemSet();
+ const SfxItemSet& rOtherSet = rOther.GetItemSet();
+
+ return OneEqual( rThisSet, rOtherSet, ATTR_BACKGROUND ) &&
+ OneEqual( rThisSet, rOtherSet, ATTR_BORDER ) &&
+ OneEqual( rThisSet, rOtherSet, ATTR_BORDER_TLBR ) &&
+ OneEqual( rThisSet, rOtherSet, ATTR_BORDER_BLTR ) &&
+ OneEqual( rThisSet, rOtherSet, ATTR_SHADOW );
+
+ //TODO: also here only check really visible values !!!
+}
+
+const OUString* ScPatternAttr::GetStyleName() const
+{
+ return pName ? &*pName : ( pStyle ? &pStyle->GetName() : nullptr );
+}
+
+void ScPatternAttr::SetStyleSheet( ScStyleSheet* pNewStyle, bool bClearDirectFormat )
+{
+ if (pNewStyle)
+ {
+ SfxItemSet& rPatternSet = GetItemSet();
+ const SfxItemSet& rStyleSet = pNewStyle->GetItemSet();
+
+ if (bClearDirectFormat)
+ {
+ for (sal_uInt16 i=ATTR_PATTERN_START; i<=ATTR_PATTERN_END; i++)
+ {
+ if (rStyleSet.GetItemState(i) == SfxItemState::SET)
+ rPatternSet.ClearItem(i);
+ }
+ }
+ rPatternSet.SetParent(&pNewStyle->GetItemSet());
+ pStyle = pNewStyle;
+ pName.reset();
+ }
+ else
+ {
+ OSL_FAIL( "ScPatternAttr::SetStyleSheet( NULL ) :-|" );
+ GetItemSet().SetParent(nullptr);
+ pStyle = nullptr;
+ }
+ mxHashCode.reset();
+}
+
+void ScPatternAttr::UpdateStyleSheet(const ScDocument& rDoc)
+{
+ if (pName)
+ {
+ pStyle = static_cast<ScStyleSheet*>(rDoc.GetStyleSheetPool()->Find(*pName, SfxStyleFamily::Para));
+
+ // use Standard if Style is not found,
+ // to avoid empty display in Toolbox-Controller
+ // Assumes that "Standard" is always the 1st entry!
+ if (!pStyle)
+ {
+ std::unique_ptr<SfxStyleSheetIterator> pIter = rDoc.GetStyleSheetPool()->CreateIterator(SfxStyleFamily::Para);
+ pStyle = dynamic_cast< ScStyleSheet* >(pIter->First());
+ }
+
+ if (pStyle)
+ {
+ GetItemSet().SetParent(&pStyle->GetItemSet());
+ pName.reset();
+ }
+ }
+ else
+ pStyle = nullptr;
+ mxHashCode.reset();
+}
+
+void ScPatternAttr::StyleToName()
+{
+ // Style was deleted, remember name:
+
+ if ( pStyle )
+ {
+ pName = pStyle->GetName();
+ pStyle = nullptr;
+ GetItemSet().SetParent( nullptr );
+ mxHashCode.reset();
+ }
+}
+
+bool ScPatternAttr::IsSymbolFont() const
+{
+ if( const SvxFontItem* pItem = GetItemSet().GetItemIfSet( ATTR_FONT ) )
+ return pItem->GetCharSet() == RTL_TEXTENCODING_SYMBOL;
+ else
+ return false;
+}
+
+namespace {
+
+sal_uInt32 getNumberFormatKey(const SfxItemSet& rSet)
+{
+ return rSet.Get(ATTR_VALUE_FORMAT).GetValue();
+}
+
+LanguageType getLanguageType(const SfxItemSet& rSet)
+{
+ return rSet.Get(ATTR_LANGUAGE_FORMAT).GetLanguage();
+}
+
+}
+
+sal_uInt32 ScPatternAttr::GetNumberFormat( SvNumberFormatter* pFormatter ) const
+{
+ sal_uInt32 nFormat = getNumberFormatKey(GetItemSet());
+ LanguageType eLang = getLanguageType(GetItemSet());
+ if ( nFormat < SV_COUNTRY_LANGUAGE_OFFSET && eLang == LANGUAGE_SYSTEM )
+ ; // it remains as it is
+ else if ( pFormatter )
+ nFormat = pFormatter->GetFormatForLanguageIfBuiltIn( nFormat, eLang );
+ return nFormat;
+}
+
+// the same if conditional formatting is in play:
+
+sal_uInt32 ScPatternAttr::GetNumberFormat( SvNumberFormatter* pFormatter,
+ const SfxItemSet* pCondSet ) const
+{
+ assert(pFormatter);
+ if (!pCondSet)
+ return GetNumberFormat(pFormatter);
+
+ // Conditional format takes precedence over style and even hard format.
+
+ sal_uInt32 nFormat;
+ LanguageType eLang;
+ if (pCondSet->GetItemState(ATTR_VALUE_FORMAT) == SfxItemState::SET )
+ {
+ nFormat = getNumberFormatKey(*pCondSet);
+ if (pCondSet->GetItemState(ATTR_LANGUAGE_FORMAT) == SfxItemState::SET)
+ eLang = getLanguageType(*pCondSet);
+ else
+ eLang = getLanguageType(GetItemSet());
+ }
+ else
+ {
+ nFormat = getNumberFormatKey(GetItemSet());
+ eLang = getLanguageType(GetItemSet());
+ }
+
+ return pFormatter->GetFormatForLanguageIfBuiltIn(nFormat, eLang);
+}
+
+const SfxPoolItem& ScPatternAttr::GetItem( sal_uInt16 nWhich, const SfxItemSet& rItemSet, const SfxItemSet* pCondSet )
+{
+ const SfxPoolItem* pCondItem;
+ if ( pCondSet && pCondSet->GetItemState( nWhich, true, &pCondItem ) == SfxItemState::SET )
+ return *pCondItem;
+ return rItemSet.Get(nWhich);
+}
+
+const SfxPoolItem& ScPatternAttr::GetItem( sal_uInt16 nSubWhich, const SfxItemSet* pCondSet ) const
+{
+ return GetItem( nSubWhich, GetItemSet(), pCondSet );
+}
+
+// GetRotateVal is tested before ATTR_ORIENTATION
+
+Degree100 ScPatternAttr::GetRotateVal( const SfxItemSet* pCondSet ) const
+{
+ Degree100 nAttrRotate(0);
+ if ( GetCellOrientation() == SvxCellOrientation::Standard )
+ {
+ bool bRepeat = ( GetItem(ATTR_HOR_JUSTIFY, pCondSet).
+ GetValue() == SvxCellHorJustify::Repeat );
+ // ignore orientation/rotation if "repeat" is active
+ if ( !bRepeat )
+ nAttrRotate = GetItem( ATTR_ROTATE_VALUE, pCondSet ).GetValue();
+ }
+ return nAttrRotate;
+}
+
+ScRotateDir ScPatternAttr::GetRotateDir( const SfxItemSet* pCondSet ) const
+{
+ ScRotateDir nRet = ScRotateDir::NONE;
+
+ Degree100 nAttrRotate = GetRotateVal( pCondSet );
+ if ( nAttrRotate )
+ {
+ SvxRotateMode eRotMode = GetItem(ATTR_ROTATE_MODE, pCondSet).GetValue();
+
+ if ( eRotMode == SVX_ROTATE_MODE_STANDARD || nAttrRotate == 18000_deg100 )
+ nRet = ScRotateDir::Standard;
+ else if ( eRotMode == SVX_ROTATE_MODE_CENTER )
+ nRet = ScRotateDir::Center;
+ else if ( eRotMode == SVX_ROTATE_MODE_TOP || eRotMode == SVX_ROTATE_MODE_BOTTOM )
+ {
+ Degree100 nRot180 = nAttrRotate % 18000_deg100; // 1/100 degrees
+ if ( nRot180 == 9000_deg100 )
+ nRet = ScRotateDir::Center;
+ else if ( ( eRotMode == SVX_ROTATE_MODE_TOP && nRot180 < 9000_deg100 ) ||
+ ( eRotMode == SVX_ROTATE_MODE_BOTTOM && nRot180 > 9000_deg100 ) )
+ nRet = ScRotateDir::Left;
+ else
+ nRet = ScRotateDir::Right;
+ }
+ }
+
+ return nRet;
+}
+
+void ScPatternAttr::SetKey(sal_uInt64 nKey)
+{
+ mnKey = nKey;
+}
+
+sal_uInt64 ScPatternAttr::GetKey() const
+{
+ return mnKey;
+}
+
+void ScPatternAttr::CalcHashCode() const
+{
+ auto const & rSet = GetItemSet();
+ if( rSet.TotalCount() != compareSize ) // see EqualPatternSets()
+ {
+ mxHashCode = 0; // invalid
+ return;
+ }
+ mxHashCode = 1; // Set up seed so that an empty pattern does not have an (invalid) hash of 0.
+ boost::hash_range(*mxHashCode, rSet.GetItems_Impl(), rSet.GetItems_Impl() + compareSize);
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sc/source/core/data/pivot2.cxx b/sc/source/core/data/pivot2.cxx
new file mode 100644
index 000000000..7544e6e95
--- /dev/null
+++ b/sc/source/core/data/pivot2.cxx
@@ -0,0 +1,161 @@
+/* -*- 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 <pivot.hxx>
+
+#if DEBUG_PIVOT_TABLE
+using std::cout;
+using std::endl;
+#endif
+
+
+
+// ScDPName
+
+ScDPName::ScDPName() : mnDupCount(0)
+{}
+
+ScDPName::ScDPName(const OUString& rName, const OUString& rLayoutName, sal_uInt8 nDupCount) :
+ maName(rName), maLayoutName(rLayoutName), mnDupCount(nDupCount)
+{}
+
+// ScDPLabelData
+
+ScDPLabelData::Member::Member() :
+ mbVisible(true),
+ mbShowDetails(true)
+{}
+
+OUString const & ScDPLabelData::Member::getDisplayName() const
+{
+ if (!maLayoutName.isEmpty())
+ return maLayoutName;
+
+ return maName;
+}
+
+ScDPLabelData::ScDPLabelData() :
+ mnCol(-1),
+ mnOriginalDim(-1),
+ mnFuncMask(PivotFunc::NONE),
+ mnUsedHier(0),
+ mnFlags(0),
+ mnDupCount(0),
+ mbShowAll(false),
+ mbIsValue(false),
+ mbDataLayout(false),
+ mbRepeatItemLabels(false)
+{}
+
+OUString const & ScDPLabelData::getDisplayName() const
+{
+ if (!maLayoutName.isEmpty())
+ return maLayoutName;
+
+ return maName;
+}
+
+// ScPivotField
+
+ScPivotField::ScPivotField(SCCOL nNewCol) :
+ mnOriginalDim(-1),
+ nFuncMask(PivotFunc::NONE),
+ nCol(nNewCol),
+ mnDupCount(0)
+{}
+
+tools::Long ScPivotField::getOriginalDim() const
+{
+ return mnOriginalDim >= 0 ? mnOriginalDim : static_cast<tools::Long>(nCol);
+}
+
+// ScPivotParam
+
+ScPivotParam::ScPivotParam() :
+ nCol(0), nRow(0), nTab(0),
+ bIgnoreEmptyRows(false), bDetectCategories(false),
+ bMakeTotalCol(true), bMakeTotalRow(true)
+{}
+
+ScPivotParam::ScPivotParam( const ScPivotParam& r )
+ : nCol( r.nCol ), nRow( r.nRow ), nTab( r.nTab ),
+ maPageFields(r.maPageFields),
+ maColFields(r.maColFields),
+ maRowFields(r.maRowFields),
+ maDataFields(r.maDataFields),
+ bIgnoreEmptyRows(r.bIgnoreEmptyRows),
+ bDetectCategories(r.bDetectCategories),
+ bMakeTotalCol(r.bMakeTotalCol),
+ bMakeTotalRow(r.bMakeTotalRow)
+{
+ SetLabelData(r.maLabelArray);
+}
+
+ScPivotParam::~ScPivotParam()
+{}
+
+void ScPivotParam::SetLabelData(const ScDPLabelDataVector& rVector)
+{
+ ScDPLabelDataVector aNewArray;
+ aNewArray.reserve(rVector.size());
+ for (const auto& rxData : rVector)
+ {
+ aNewArray.push_back(std::make_unique<ScDPLabelData>(*rxData));
+ }
+ maLabelArray.swap(aNewArray);
+}
+
+ScPivotParam& ScPivotParam::operator=( const ScPivotParam& rPivotParam )
+{
+ nCol = rPivotParam.nCol;
+ nRow = rPivotParam.nRow;
+ nTab = rPivotParam.nTab;
+ bIgnoreEmptyRows = rPivotParam.bIgnoreEmptyRows;
+ bDetectCategories = rPivotParam.bDetectCategories;
+ bMakeTotalCol = rPivotParam.bMakeTotalCol;
+ bMakeTotalRow = rPivotParam.bMakeTotalRow;
+
+ maPageFields = rPivotParam.maPageFields;
+ maColFields = rPivotParam.maColFields;
+ maRowFields = rPivotParam.maRowFields;
+ maDataFields = rPivotParam.maDataFields;
+
+ SetLabelData(rPivotParam.maLabelArray);
+ return *this;
+}
+
+// ScPivotFuncData
+
+ScPivotFuncData::ScPivotFuncData( SCCOL nCol, PivotFunc nFuncMask ) :
+ mnOriginalDim(-1),
+ mnFuncMask(nFuncMask),
+ mnCol( nCol ),
+ mnDupCount(0)
+{}
+
+#if DEBUG_PIVOT_TABLE
+void ScPivotFuncData::Dump() const
+{
+ cout << "ScPivotFuncData: (col=" << mnCol << ", original dim=" << mnOriginalDim
+ << ", func mask=" << static_cast<int>(mnFuncMask) << ", duplicate count=" << static_cast<int>(mnDupCount)
+ << ")" << endl;
+}
+#endif
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sc/source/core/data/poolhelp.cxx b/sc/source/core/data/poolhelp.cxx
new file mode 100644
index 000000000..6ed855fc7
--- /dev/null
+++ b/sc/source/core/data/poolhelp.cxx
@@ -0,0 +1,118 @@
+/* -*- 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 <comphelper/processfactory.hxx>
+#include <svl/numformat.hxx>
+#include <svl/zforlist.hxx>
+#include <editeng/editeng.hxx>
+
+#include <poolhelp.hxx>
+#include <document.hxx>
+#include <docpool.hxx>
+#include <stlpool.hxx>
+
+ScPoolHelper::ScPoolHelper( ScDocument& rSourceDoc )
+ : pDocPool(new ScDocumentPool)
+ , m_rSourceDoc(rSourceDoc)
+{
+ pDocPool->FreezeIdRanges();
+
+ mxStylePool = new ScStyleSheetPool( *pDocPool, &rSourceDoc );
+}
+
+ScPoolHelper::~ScPoolHelper()
+{
+ pEnginePool.clear();
+ pEditPool.clear();
+ pFormTable.reset();
+ mxStylePool.clear();
+ pDocPool.clear();
+}
+
+SfxItemPool* ScPoolHelper::GetEditPool() const
+{
+ if ( !pEditPool )
+ {
+ pEditPool = EditEngine::CreatePool();
+ pEditPool->SetDefaultMetric( MapUnit::Map100thMM );
+ pEditPool->FreezeIdRanges();
+ }
+ return pEditPool.get();
+}
+
+SfxItemPool* ScPoolHelper::GetEnginePool() const
+{
+ if ( !pEnginePool )
+ {
+ pEnginePool = EditEngine::CreatePool();
+ pEnginePool->SetDefaultMetric( MapUnit::Map100thMM );
+ pEnginePool->FreezeIdRanges();
+ } // ifg ( pEnginePool )
+ return pEnginePool.get();
+}
+SvNumberFormatter* ScPoolHelper::GetFormTable() const
+{
+ if (!pFormTable)
+ pFormTable = CreateNumberFormatter();
+ return pFormTable.get();
+}
+
+void ScPoolHelper::SetFormTableOpt(const ScDocOptions& rOpt)
+{
+ aOpt = rOpt;
+ // #i105512# if the number formatter exists, update its settings
+ if (pFormTable)
+ {
+ sal_uInt16 d,m;
+ sal_Int16 y;
+ aOpt.GetDate( d,m,y );
+ pFormTable->ChangeNullDate( d,m,y );
+ pFormTable->ChangeStandardPrec( aOpt.GetStdPrecision() );
+ pFormTable->SetYear2000( aOpt.GetYear2000() );
+ }
+}
+
+std::unique_ptr<SvNumberFormatter> ScPoolHelper::CreateNumberFormatter() const
+{
+ std::unique_ptr<SvNumberFormatter> p;
+ {
+ std::scoped_lock aGuard(maMtxCreateNumFormatter);
+ p.reset(new SvNumberFormatter(comphelper::getProcessComponentContext(), LANGUAGE_SYSTEM));
+ }
+ p->SetColorLink( LINK(&m_rSourceDoc, ScDocument, GetUserDefinedColor) );
+ p->SetEvalDateFormat(NF_EVALDATEFORMAT_INTL_FORMAT);
+
+ sal_uInt16 d,m;
+ sal_Int16 y;
+ aOpt.GetDate(d, m, y);
+ p->ChangeNullDate(d, m, y);
+ p->ChangeStandardPrec(aOpt.GetStdPrecision());
+ p->SetYear2000(aOpt.GetYear2000());
+ return p;
+}
+
+void ScPoolHelper::SourceDocumentGone()
+{
+ // reset all pointers to the source document
+ mxStylePool->SetDocument( nullptr );
+ if ( pFormTable )
+ pFormTable->SetColorLink( Link<sal_uInt16,Color*>() );
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sc/source/core/data/postit.cxx b/sc/source/core/data/postit.cxx
new file mode 100644
index 000000000..cd8acfdce
--- /dev/null
+++ b/sc/source/core/data/postit.cxx
@@ -0,0 +1,1306 @@
+/* -*- 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 <memory>
+#include <postit.hxx>
+
+#include <rtl/ustrbuf.hxx>
+#include <sal/log.hxx>
+#include <unotools/useroptions.hxx>
+#include <svx/svdpage.hxx>
+#include <svx/svdocapt.hxx>
+#include <editeng/outlobj.hxx>
+#include <editeng/editobj.hxx>
+#include <basegfx/polygon/b2dpolygon.hxx>
+#include <osl/diagnose.h>
+#include <comphelper/lok.hxx>
+
+#include <scitems.hxx>
+#include <svx/xfillit0.hxx>
+#include <svx/xlnstit.hxx>
+#include <svx/xlnstwit.hxx>
+#include <svx/xlnstcit.hxx>
+#include <svx/sxcecitm.hxx>
+#include <svx/xflclit.hxx>
+#include <svx/sdshitm.hxx>
+#include <svx/sdsxyitm.hxx>
+#include <svx/sdtditm.hxx>
+#include <svx/sdtagitm.hxx>
+#include <svx/sdtmfitm.hxx>
+#include <tools/gen.hxx>
+
+#include <document.hxx>
+#include <docpool.hxx>
+#include <patattr.hxx>
+#include <drwlayer.hxx>
+#include <userdat.hxx>
+#include <detfunc.hxx>
+#include <editutil.hxx>
+
+using namespace com::sun::star;
+
+namespace {
+
+const tools::Long SC_NOTECAPTION_WIDTH = 2900; /// Default width of note caption textbox.
+const tools::Long SC_NOTECAPTION_MAXWIDTH_TEMP = 12000; /// Maximum width of temporary note caption textbox.
+const tools::Long SC_NOTECAPTION_HEIGHT = 1800; /// Default height of note caption textbox.
+const tools::Long SC_NOTECAPTION_CELLDIST = 600; /// Default distance of note captions to border of anchor cell.
+const tools::Long SC_NOTECAPTION_OFFSET_Y = -1500; /// Default Y offset of note captions to top border of anchor cell.
+const tools::Long SC_NOTECAPTION_OFFSET_X = 1500; /// Default X offset of note captions to left border of anchor cell.
+const tools::Long SC_NOTECAPTION_BORDERDIST_TEMP = 100; /// Distance of temporary note captions to visible sheet area.
+
+/** Static helper functions for caption objects. */
+class ScCaptionUtil
+{
+public:
+ /** Moves the caption object to the correct layer according to passed visibility. */
+ static void SetCaptionLayer( SdrCaptionObj& rCaption, bool bShown );
+ /** Sets basic caption settings required for note caption objects. */
+ static void SetBasicCaptionSettings( SdrCaptionObj& rCaption, bool bShown );
+ /** Stores the cell position of the note in the user data area of the caption. */
+ static void SetCaptionUserData( SdrCaptionObj& rCaption, const ScAddress& rPos );
+ /** Sets all default formatting attributes to the caption object. */
+ static void SetDefaultItems( SdrCaptionObj& rCaption, ScDocument& rDoc, const SfxItemSet* pExtraItemSet );
+};
+
+void ScCaptionUtil::SetCaptionLayer( SdrCaptionObj& rCaption, bool bShown )
+{
+ SdrLayerID nLayer = bShown ? SC_LAYER_INTERN : SC_LAYER_HIDDEN;
+ if( nLayer != rCaption.GetLayer() )
+ rCaption.SetLayer( nLayer );
+}
+
+void ScCaptionUtil::SetBasicCaptionSettings( SdrCaptionObj& rCaption, bool bShown )
+{
+ SetCaptionLayer( rCaption, bShown );
+ rCaption.SetFixedTail();
+ rCaption.SetSpecialTextBoxShadow();
+}
+
+void ScCaptionUtil::SetCaptionUserData( SdrCaptionObj& rCaption, const ScAddress& rPos )
+{
+ // pass true to ScDrawLayer::GetObjData() to create the object data entry
+ ScDrawObjData* pObjData = ScDrawLayer::GetObjData( &rCaption, true );
+ OSL_ENSURE( pObjData, "ScCaptionUtil::SetCaptionUserData - missing drawing object user data" );
+ pObjData->maStart = rPos;
+ pObjData->meType = ScDrawObjData::CellNote;
+}
+
+void ScCaptionUtil::SetDefaultItems( SdrCaptionObj& rCaption, ScDocument& rDoc, const SfxItemSet* pExtraItemSet )
+{
+ SfxItemSet aItemSet = rCaption.GetMergedItemSet();
+
+ // caption tail arrow
+ ::basegfx::B2DPolygon aTriangle;
+ aTriangle.append( ::basegfx::B2DPoint( 10.0, 0.0 ) );
+ aTriangle.append( ::basegfx::B2DPoint( 0.0, 30.0 ) );
+ aTriangle.append( ::basegfx::B2DPoint( 20.0, 30.0 ) );
+ aTriangle.setClosed( true );
+ /* Line ends are now created with an empty name. The
+ checkForUniqueItem() method then finds a unique name for the item's
+ value. */
+ aItemSet.Put( XLineStartItem( OUString(), ::basegfx::B2DPolyPolygon( aTriangle ) ) );
+ aItemSet.Put( XLineStartWidthItem( 200 ) );
+ aItemSet.Put( XLineStartCenterItem( false ) );
+ aItemSet.Put( XFillStyleItem( drawing::FillStyle_SOLID ) );
+ aItemSet.Put( XFillColorItem( OUString(), ScDetectiveFunc::GetCommentColor() ) );
+ aItemSet.Put( SdrCaptionEscDirItem( SdrCaptionEscDir::BestFit ) );
+
+ // shadow
+ /* SdrShadowItem has sal_False, instead the shadow is set for the
+ rectangle only with SetSpecialTextBoxShadow() when the object is
+ created (item must be set to adjust objects from older files). */
+ aItemSet.Put( makeSdrShadowItem( false ) );
+ aItemSet.Put( makeSdrShadowXDistItem( 100 ) );
+ aItemSet.Put( makeSdrShadowYDistItem( 100 ) );
+
+ // text attributes
+ aItemSet.Put( makeSdrTextLeftDistItem( 100 ) );
+ aItemSet.Put( makeSdrTextRightDistItem( 100 ) );
+ aItemSet.Put( makeSdrTextUpperDistItem( 100 ) );
+ aItemSet.Put( makeSdrTextLowerDistItem( 100 ) );
+ aItemSet.Put( makeSdrTextAutoGrowWidthItem( false ) );
+ aItemSet.Put( makeSdrTextAutoGrowHeightItem( true ) );
+ // use the default cell style to be able to modify the caption font
+ const ScPatternAttr& rDefPattern = rDoc.GetPool()->GetDefaultItem( ATTR_PATTERN );
+ rDefPattern.FillEditItemSet( &aItemSet );
+
+ if (pExtraItemSet)
+ {
+ /* Updates caption item set according to the passed item set while removing shadow items. */
+
+ aItemSet.Put(*pExtraItemSet);
+ // reset shadow items
+ aItemSet.Put( makeSdrShadowItem( false ) );
+ aItemSet.Put( makeSdrShadowXDistItem( 100 ) );
+ aItemSet.Put( makeSdrShadowYDistItem( 100 ) );
+ }
+
+ rCaption.SetMergedItemSet( aItemSet );
+
+ if (pExtraItemSet)
+ rCaption.SetSpecialTextBoxShadow();
+}
+
+/** Helper for creation and manipulation of caption drawing objects independent
+ from cell annotations. */
+class ScCaptionCreator
+{
+public:
+ /** Create a new caption. The caption will not be inserted into the document. */
+ explicit ScCaptionCreator( ScDocument& rDoc, const ScAddress& rPos, bool bTailFront );
+ /** Manipulate an existing caption. */
+ explicit ScCaptionCreator( ScDocument& rDoc, const ScAddress& rPos, const ScCaptionPtr& xCaption );
+
+ /** Returns the drawing layer page of the sheet contained in maPos. */
+ SdrPage* GetDrawPage();
+ /** Returns the caption drawing object. */
+ ScCaptionPtr & GetCaption() { return mxCaption; }
+
+ /** Moves the caption inside the passed rectangle. Uses page area if 0 is passed. */
+ void FitCaptionToRect( const tools::Rectangle* pVisRect = nullptr );
+ /** Places the caption inside the passed rectangle, tries to keep the cell rectangle uncovered. Uses page area if 0 is passed. */
+ void AutoPlaceCaption( const tools::Rectangle* pVisRect = nullptr );
+ /** Updates caption tail and textbox according to current cell position. Uses page area if 0 is passed. */
+ void UpdateCaptionPos();
+
+protected:
+ /** Helper constructor for derived classes. */
+ explicit ScCaptionCreator( ScDocument& rDoc, const ScAddress& rPos );
+
+ /** Calculates the caption tail position according to current cell position. */
+ Point CalcTailPos( bool bTailFront );
+ /** Implements creation of the caption object. The caption will not be inserted into the document. */
+ void CreateCaption( bool bShown, bool bTailFront );
+
+private:
+ /** Initializes all members. */
+ void Initialize();
+ /** Returns the passed rectangle if existing, page rectangle otherwise. */
+ const tools::Rectangle& GetVisRect( const tools::Rectangle* pVisRect ) const { return pVisRect ? *pVisRect : maPageRect; }
+
+private:
+ ScDocument& mrDoc;
+ ScAddress maPos;
+ ScCaptionPtr mxCaption;
+ tools::Rectangle maPageRect;
+ tools::Rectangle maCellRect;
+ bool mbNegPage;
+};
+
+ScCaptionCreator::ScCaptionCreator( ScDocument& rDoc, const ScAddress& rPos, bool bTailFront ) :
+ mrDoc( rDoc ),
+ maPos( rPos )
+{
+ Initialize();
+ CreateCaption( true/*bShown*/, bTailFront );
+}
+
+ScCaptionCreator::ScCaptionCreator( ScDocument& rDoc, const ScAddress& rPos, const ScCaptionPtr& xCaption ) :
+ mrDoc( rDoc ),
+ maPos( rPos ),
+ mxCaption( xCaption )
+{
+ Initialize();
+}
+
+ScCaptionCreator::ScCaptionCreator( ScDocument& rDoc, const ScAddress& rPos ) :
+ mrDoc( rDoc ),
+ maPos( rPos )
+{
+ Initialize();
+}
+
+SdrPage* ScCaptionCreator::GetDrawPage()
+{
+ ScDrawLayer* pDrawLayer = mrDoc.GetDrawLayer();
+ return pDrawLayer ? pDrawLayer->GetPage( static_cast< sal_uInt16 >( maPos.Tab() ) ) : nullptr;
+}
+
+void ScCaptionCreator::FitCaptionToRect( const tools::Rectangle* pVisRect )
+{
+ const tools::Rectangle& rVisRect = GetVisRect( pVisRect );
+
+ // tail position
+ Point aTailPos = mxCaption->GetTailPos();
+ aTailPos.setX( ::std::clamp( aTailPos.X(), rVisRect.Left(), rVisRect.Right() ) );
+ aTailPos.setY( ::std::clamp( aTailPos.Y(), rVisRect.Top(), rVisRect.Bottom() ) );
+ mxCaption->SetTailPos( aTailPos );
+
+ // caption rectangle
+ tools::Rectangle aCaptRect = mxCaption->GetLogicRect();
+ Point aCaptPos = aCaptRect.TopLeft();
+ // move textbox inside right border of visible area
+ aCaptPos.setX( ::std::min< tools::Long >( aCaptPos.X(), rVisRect.Right() - aCaptRect.GetWidth() ) );
+ // move textbox inside left border of visible area (this may move it outside on right side again)
+ aCaptPos.setX( ::std::max< tools::Long >( aCaptPos.X(), rVisRect.Left() ) );
+ // move textbox inside bottom border of visible area
+ aCaptPos.setY( ::std::min< tools::Long >( aCaptPos.Y(), rVisRect.Bottom() - aCaptRect.GetHeight() ) );
+ // move textbox inside top border of visible area (this may move it outside on bottom side again)
+ aCaptPos.setY( ::std::max< tools::Long >( aCaptPos.Y(), rVisRect.Top() ) );
+ // update caption
+ aCaptRect.SetPos( aCaptPos );
+ mxCaption->SetLogicRect( aCaptRect );
+}
+
+void ScCaptionCreator::AutoPlaceCaption( const tools::Rectangle* pVisRect )
+{
+ const tools::Rectangle& rVisRect = GetVisRect( pVisRect );
+
+ // caption rectangle
+ tools::Rectangle aCaptRect = mxCaption->GetLogicRect();
+ tools::Long nWidth = aCaptRect.GetWidth();
+ tools::Long nHeight = aCaptRect.GetHeight();
+
+ // n***Space contains available space between border of visible area and cell
+ tools::Long nLeftSpace = maCellRect.Left() - rVisRect.Left() + 1;
+ tools::Long nRightSpace = rVisRect.Right() - maCellRect.Right() + 1;
+ tools::Long nTopSpace = maCellRect.Top() - rVisRect.Top() + 1;
+ tools::Long nBottomSpace = rVisRect.Bottom() - maCellRect.Bottom() + 1;
+
+ // nNeeded*** contains textbox dimensions plus needed distances to cell or border of visible area
+ tools::Long nNeededSpaceX = nWidth + SC_NOTECAPTION_CELLDIST;
+ tools::Long nNeededSpaceY = nHeight + SC_NOTECAPTION_CELLDIST;
+
+ // bFitsWidth*** == true means width of textbox fits into horizontal free space of visible area
+ bool bFitsWidthLeft = nNeededSpaceX <= nLeftSpace; // text box width fits into the width left of cell
+ bool bFitsWidthRight = nNeededSpaceX <= nRightSpace; // text box width fits into the width right of cell
+ bool bFitsWidth = nWidth <= rVisRect.GetWidth(); // text box width fits into width of visible area
+
+ // bFitsHeight*** == true means height of textbox fits into vertical free space of visible area
+ bool bFitsHeightTop = nNeededSpaceY <= nTopSpace; // text box height fits into the height above cell
+ bool bFitsHeightBottom = nNeededSpaceY <= nBottomSpace; // text box height fits into the height below cell
+ bool bFitsHeight = nHeight <= rVisRect.GetHeight(); // text box height fits into height of visible area
+
+ // bFits*** == true means the textbox fits completely into free space of visible area
+ bool bFitsLeft = bFitsWidthLeft && bFitsHeight;
+ bool bFitsRight = bFitsWidthRight && bFitsHeight;
+ bool bFitsTop = bFitsWidth && bFitsHeightTop;
+ bool bFitsBottom = bFitsWidth && bFitsHeightBottom;
+
+ Point aCaptPos;
+ // use left/right placement if possible, or if top/bottom placement not possible
+ if( bFitsLeft || bFitsRight || (!bFitsTop && !bFitsBottom) )
+ {
+ // prefer left in RTL sheet and right in LTR sheets
+ bool bPreferLeft = bFitsLeft && (mbNegPage || !bFitsRight);
+ bool bPreferRight = bFitsRight && (!mbNegPage || !bFitsLeft);
+ // move to left, if left is preferred, or if neither left nor right fit and there is more space to the left
+ if( bPreferLeft || (!bPreferRight && (nLeftSpace > nRightSpace)) )
+ aCaptPos.setX( maCellRect.Left() - SC_NOTECAPTION_CELLDIST - nWidth );
+ else // to right
+ aCaptPos.setX( maCellRect.Right() + SC_NOTECAPTION_CELLDIST );
+ // Y position according to top cell border
+ aCaptPos.setY( maCellRect.Top() + SC_NOTECAPTION_OFFSET_Y );
+ }
+ else // top or bottom placement
+ {
+ // X position
+ aCaptPos.setX( maCellRect.Left() + SC_NOTECAPTION_OFFSET_X );
+ // top placement, if possible
+ if( bFitsTop )
+ aCaptPos.setY( maCellRect.Top() - SC_NOTECAPTION_CELLDIST - nHeight );
+ else // bottom placement
+ aCaptPos.setY( maCellRect.Bottom() + SC_NOTECAPTION_CELLDIST );
+ }
+
+ // update textbox position in note caption object
+ aCaptRect.SetPos( aCaptPos );
+ mxCaption->SetLogicRect( aCaptRect );
+ FitCaptionToRect( pVisRect );
+}
+
+void ScCaptionCreator::UpdateCaptionPos()
+{
+ ScDrawLayer* pDrawLayer = mrDoc.GetDrawLayer();
+
+ // update caption position
+ const Point& rOldTailPos = mxCaption->GetTailPos();
+ Point aTailPos = CalcTailPos( false );
+ if( rOldTailPos != aTailPos )
+ {
+ // create drawing undo action
+ if( pDrawLayer && pDrawLayer->IsRecording() )
+ pDrawLayer->AddCalcUndo( std::make_unique<SdrUndoGeoObj>( *mxCaption ) );
+ // calculate new caption rectangle (#i98141# handle LTR<->RTL switch correctly)
+ tools::Rectangle aCaptRect = mxCaption->GetLogicRect();
+ tools::Long nDiffX = (rOldTailPos.X() >= 0) ? (aCaptRect.Left() - rOldTailPos.X()) : (rOldTailPos.X() - aCaptRect.Right());
+ if( mbNegPage ) nDiffX = -nDiffX - aCaptRect.GetWidth();
+ tools::Long nDiffY = aCaptRect.Top() - rOldTailPos.Y();
+ aCaptRect.SetPos( aTailPos + Point( nDiffX, nDiffY ) );
+ // set new tail position and caption rectangle
+ mxCaption->SetTailPos( aTailPos );
+ mxCaption->SetLogicRect( aCaptRect );
+ // fit caption into draw page
+ FitCaptionToRect();
+ }
+
+ // update cell position in caption user data
+ ScDrawObjData* pCaptData = ScDrawLayer::GetNoteCaptionData( mxCaption.get(), maPos.Tab() );
+ if( pCaptData && (maPos != pCaptData->maStart) )
+ {
+ // create drawing undo action
+ if( pDrawLayer && pDrawLayer->IsRecording() )
+ pDrawLayer->AddCalcUndo( std::make_unique<ScUndoObjData>( mxCaption.get(), pCaptData->maStart, pCaptData->maEnd, maPos, pCaptData->maEnd ) );
+ // set new position
+ pCaptData->maStart = maPos;
+ }
+}
+
+Point ScCaptionCreator::CalcTailPos( bool bTailFront )
+{
+ // tail position
+ bool bTailLeft = bTailFront != mbNegPage;
+ Point aTailPos = bTailLeft ? maCellRect.TopLeft() : maCellRect.TopRight();
+ // move caption point 1/10 mm inside cell
+ if( bTailLeft ) aTailPos.AdjustX(10 ); else aTailPos.AdjustX( -10 );
+ aTailPos.AdjustY(10);
+ return aTailPos;
+}
+
+void ScCaptionCreator::CreateCaption( bool bShown, bool bTailFront )
+{
+ // create the caption drawing object
+ tools::Rectangle aTextRect( Point( 0 , 0 ), Size( SC_NOTECAPTION_WIDTH, SC_NOTECAPTION_HEIGHT ) );
+ Point aTailPos = CalcTailPos( bTailFront );
+ mxCaption.reset(
+ new SdrCaptionObj(
+ *mrDoc.GetDrawLayer(), // TTTT should ret a ref?
+ aTextRect,
+ aTailPos));
+ // basic caption settings
+ ScCaptionUtil::SetBasicCaptionSettings( *mxCaption, bShown );
+}
+
+void ScCaptionCreator::Initialize()
+{
+ maCellRect = ScDrawLayer::GetCellRect( mrDoc, maPos, true );
+ mbNegPage = mrDoc.IsNegativePage( maPos.Tab() );
+ if( SdrPage* pDrawPage = GetDrawPage() )
+ {
+ maPageRect = tools::Rectangle( Point( 0, 0 ), pDrawPage->GetSize() );
+ /* #i98141# SdrPage::GetSize() returns negative width in RTL mode.
+ The call to Rectangle::Adjust() orders left/right coordinate
+ accordingly. */
+ maPageRect.Justify();
+ }
+}
+
+/** Helper for creation of permanent caption drawing objects for cell notes. */
+class ScNoteCaptionCreator : public ScCaptionCreator
+{
+public:
+ /** Create a new caption object and inserts it into the document. */
+ explicit ScNoteCaptionCreator( ScDocument& rDoc, const ScAddress& rPos, ScNoteData& rNoteData );
+ /** Manipulate an existing caption. */
+ explicit ScNoteCaptionCreator( ScDocument& rDoc, const ScAddress& rPos, ScCaptionPtr& xCaption, bool bShown );
+};
+
+ScNoteCaptionCreator::ScNoteCaptionCreator( ScDocument& rDoc, const ScAddress& rPos, ScNoteData& rNoteData ) :
+ ScCaptionCreator( rDoc, rPos ) // use helper c'tor that does not create the caption yet
+{
+ SdrPage* pDrawPage = GetDrawPage();
+ OSL_ENSURE( pDrawPage, "ScNoteCaptionCreator::ScNoteCaptionCreator - no drawing page" );
+ if( !pDrawPage )
+ return;
+
+ // create the caption drawing object
+ CreateCaption( rNoteData.mbShown, false );
+ rNoteData.mxCaption = GetCaption();
+ OSL_ENSURE( rNoteData.mxCaption, "ScNoteCaptionCreator::ScNoteCaptionCreator - missing caption object" );
+ if( rNoteData.mxCaption )
+ {
+ // store note position in user data of caption object
+ ScCaptionUtil::SetCaptionUserData( *rNoteData.mxCaption, rPos );
+ // insert object into draw page
+ pDrawPage->InsertObject( rNoteData.mxCaption.get() );
+ }
+}
+
+ScNoteCaptionCreator::ScNoteCaptionCreator( ScDocument& rDoc, const ScAddress& rPos, ScCaptionPtr& xCaption, bool bShown ) :
+ ScCaptionCreator( rDoc, rPos, xCaption )
+{
+ SdrPage* pDrawPage = GetDrawPage();
+ OSL_ENSURE( pDrawPage, "ScNoteCaptionCreator::ScNoteCaptionCreator - no drawing page" );
+ OSL_ENSURE( xCaption->getSdrPageFromSdrObject() == pDrawPage, "ScNoteCaptionCreator::ScNoteCaptionCreator - wrong drawing page in caption" );
+ if( pDrawPage && (xCaption->getSdrPageFromSdrObject() == pDrawPage) )
+ {
+ // store note position in user data of caption object
+ ScCaptionUtil::SetCaptionUserData( *xCaption, rPos );
+ // basic caption settings
+ ScCaptionUtil::SetBasicCaptionSettings( *xCaption, bShown );
+ // set correct tail position
+ xCaption->SetTailPos( CalcTailPos( false ) );
+ }
+}
+
+} // namespace
+
+ScCaptionPtr::ScCaptionPtr() :
+ mpHead(nullptr), mpNext(nullptr), mpCaption(nullptr), mbNotOwner(false)
+{
+}
+
+ScCaptionPtr::ScCaptionPtr( SdrCaptionObj* p ) :
+ mpHead(nullptr), mpNext(nullptr), mpCaption(p), mbNotOwner(false)
+{
+ if (p)
+ {
+ newHead();
+ }
+}
+
+ScCaptionPtr::ScCaptionPtr( const ScCaptionPtr& r ) :
+ mpHead(r.mpHead), mpCaption(r.mpCaption), mbNotOwner(false)
+{
+ if (r.mpCaption)
+ {
+ assert(r.mpHead);
+ r.incRef();
+ // Insert into list.
+ mpNext = r.mpNext;
+ r.mpNext = this;
+ }
+ else
+ {
+ assert(!r.mpHead);
+ mpNext = nullptr;
+ }
+}
+
+ScCaptionPtr::ScCaptionPtr(ScCaptionPtr&& r) noexcept
+ : mpHead(r.mpHead), mpNext(r.mpNext), mpCaption(r.mpCaption), mbNotOwner(false)
+{
+ r.replaceInList( this );
+ r.mpCaption = nullptr;
+ r.mbNotOwner = false;
+}
+
+ScCaptionPtr& ScCaptionPtr::operator=(ScCaptionPtr&& r) noexcept
+{
+ assert(this != &r);
+
+ mpHead = r.mpHead;
+ mpNext = r.mpNext;
+ mpCaption = r.mpCaption;
+ mbNotOwner = r.mbNotOwner;
+
+ r.replaceInList( this );
+ r.mpCaption = nullptr;
+ r.mbNotOwner = false;
+
+ return *this;
+}
+
+ScCaptionPtr& ScCaptionPtr::operator=( const ScCaptionPtr& r )
+{
+ if (this == &r)
+ return *this;
+
+ if (mpCaption == r.mpCaption)
+ {
+ // Two lists for the same caption is bad.
+ assert(!mpCaption || mpHead == r.mpHead);
+ assert(!mpCaption); // assigning same caption pointer within same list is weird
+ // Nullptr captions are not inserted to the list, so nothing to do here
+ // if both are.
+ return *this;
+ }
+
+ // Let's find some weird usage.
+ // Assigning without head doesn't make sense unless it is a nullptr caption.
+ assert(r.mpHead || !r.mpCaption);
+ // A nullptr caption must not be in a list and thus not have a head.
+ assert(!r.mpHead || r.mpCaption);
+ // Same captions were caught above, so here different heads must be present.
+ assert(r.mpHead != mpHead);
+
+ r.incRef();
+ decRefAndDestroy();
+ removeFromList();
+
+ mpCaption = r.mpCaption;
+ mbNotOwner = r.mbNotOwner;
+ // That head is this' master.
+ mpHead = r.mpHead;
+ // Insert into list.
+ mpNext = r.mpNext;
+ r.mpNext = this;
+
+ return *this;
+}
+
+void ScCaptionPtr::setNotOwner()
+{
+ mbNotOwner = true;
+}
+
+ScCaptionPtr::Head::Head( ScCaptionPtr* p ) :
+ mpFirst(p), mnRefs(1)
+{
+}
+
+void ScCaptionPtr::newHead()
+{
+ assert(!mpHead);
+ mpHead = new Head(this);
+}
+
+void ScCaptionPtr::replaceInList(ScCaptionPtr* pNew) noexcept
+{
+ if (!mpHead && !mpNext)
+ return;
+
+ assert(mpHead);
+ assert(mpCaption == pNew->mpCaption);
+
+ ScCaptionPtr* pThat = mpHead->mpFirst;
+ while (pThat && pThat != this && pThat->mpNext != this)
+ {
+ pThat = pThat->mpNext;
+ }
+ if (pThat && pThat != this)
+ {
+ assert(pThat->mpNext == this);
+ pThat->mpNext = pNew;
+ }
+ pNew->mpNext = mpNext;
+ if (mpHead->mpFirst == this)
+ mpHead->mpFirst = pNew;
+
+ mpHead = nullptr;
+ mpNext = nullptr;
+}
+
+void ScCaptionPtr::removeFromList()
+{
+ if (!mpHead && !mpNext && !mpCaption)
+ return;
+
+#if OSL_DEBUG_LEVEL > 0
+ oslInterlockedCount nCount = 0;
+#endif
+ ScCaptionPtr* pThat = (mpHead ? mpHead->mpFirst : nullptr);
+ while (pThat && pThat != this && pThat->mpNext != this)
+ {
+ // Use the walk to check consistency on the fly.
+ assert(pThat->mpHead == mpHead); // all belong to the same
+ assert(pThat->mpHead || !pThat->mpNext); // next without head is bad
+ assert(pThat->mpCaption == mpCaption);
+ pThat = pThat->mpNext;
+#if OSL_DEBUG_LEVEL > 0
+ ++nCount;
+#endif
+ }
+ assert(pThat || !mpHead); // not found only if this was standalone
+ if (pThat)
+ {
+ if (pThat != this)
+ {
+#if OSL_DEBUG_LEVEL > 0
+ // The while loop above was not executed, and for this
+ // (pThat->mpNext) the loop below won't either.
+ ++nCount;
+#endif
+ pThat->mpNext = mpNext;
+ }
+#if OSL_DEBUG_LEVEL > 0
+ do
+ {
+ assert(pThat->mpHead == mpHead); // all belong to the same
+ assert(pThat->mpHead || !pThat->mpNext); // next without head is bad
+ assert(pThat->mpCaption == mpCaption);
+ ++nCount;
+ }
+ while ((pThat = pThat->mpNext) != nullptr);
+#endif
+ }
+#if OSL_DEBUG_LEVEL > 0
+ // If part of a list then refs were already decremented.
+ assert(nCount == (mpHead ? mpHead->mnRefs + 1 : 0));
+#endif
+ if (mpHead && mpHead->mpFirst == this)
+ {
+ if (mpNext)
+ mpHead->mpFirst = mpNext;
+ else
+ {
+ // The only one destroys also head.
+ assert(mpHead->mnRefs == 0); // cough
+ delete mpHead; // DEAD now
+ }
+ }
+ mpHead = nullptr;
+ mpNext = nullptr;
+}
+
+void ScCaptionPtr::reset( SdrCaptionObj* p )
+{
+ assert(!p || p != mpCaption);
+#if OSL_DEBUG_LEVEL > 0
+ if (p)
+ {
+ // Check if we end up with a duplicated management in this list.
+ ScCaptionPtr* pThat = (mpHead ? mpHead->mpFirst : nullptr);
+ while (pThat)
+ {
+ assert(pThat->mpCaption != p);
+ pThat = pThat->mpNext;
+ }
+ }
+#endif
+ decRefAndDestroy();
+ removeFromList();
+ mpCaption = p;
+ mbNotOwner = false;
+ if (p)
+ {
+ newHead();
+ }
+}
+
+ScCaptionPtr::~ScCaptionPtr()
+{
+ decRefAndDestroy();
+ removeFromList();
+}
+
+oslInterlockedCount ScCaptionPtr::getRefs() const
+{
+ return mpHead ? mpHead->mnRefs : 0;
+}
+
+void ScCaptionPtr::incRef() const
+{
+ if (mpHead)
+ osl_atomic_increment(&mpHead->mnRefs);
+}
+
+bool ScCaptionPtr::decRef() const
+{
+ return mpHead && mpHead->mnRefs > 0 && !osl_atomic_decrement(&mpHead->mnRefs);
+}
+
+void ScCaptionPtr::decRefAndDestroy()
+{
+ if (!decRef())
+ return;
+
+ assert(mpHead->mpFirst == this); // this must be one and only one
+ assert(!mpNext); // this must be one and only one
+ assert(mpCaption);
+
+#if 0
+ // Quick workaround for when there are still cases where the caption
+ // pointer is dangling
+ mpCaption = nullptr;
+ mbNotOwner = false;
+#else
+ // Destroying Draw Undo and some other delete the SdrObject, don't
+ // attempt that twice.
+ if (mbNotOwner)
+ {
+ mpCaption = nullptr;
+ mbNotOwner = false;
+ }
+ else
+ {
+ removeFromDrawPageAndFree( true ); // ignoring Undo
+ if (mpCaption)
+ {
+ // There's no draw page associated so removeFromDrawPageAndFree()
+ // didn't do anything, but still we want to delete the caption
+ // object. release()/dissolve() also resets mpCaption.
+ SdrObject* pObj = release();
+ SdrObject::Free( pObj );
+ }
+ }
+#endif
+ delete mpHead;
+ mpHead = nullptr;
+}
+
+void ScCaptionPtr::insertToDrawPage( SdrPage& rDrawPage )
+{
+ assert(mpHead && mpCaption);
+
+ rDrawPage.InsertObject( mpCaption );
+}
+
+void ScCaptionPtr::removeFromDrawPage( SdrPage& rDrawPage )
+{
+ assert(mpHead && mpCaption);
+ SdrObject* pObj = rDrawPage.RemoveObject( mpCaption->GetOrdNum() );
+ assert(pObj == mpCaption); (void)pObj;
+}
+
+void ScCaptionPtr::removeFromDrawPageAndFree( bool bIgnoreUndo )
+{
+ assert(mpHead && mpCaption);
+ SdrPage* pDrawPage(mpCaption->getSdrPageFromSdrObject());
+ SAL_WARN_IF( !pDrawPage, "sc.core", "ScCaptionPtr::removeFromDrawPageAndFree - object without drawing page");
+ if (!pDrawPage)
+ return;
+
+ pDrawPage->RecalcObjOrdNums();
+ bool bRecording = false;
+ if(!bIgnoreUndo)
+ {
+ ScDrawLayer* pDrawLayer(dynamic_cast< ScDrawLayer* >(&mpCaption->getSdrModelFromSdrObject()));
+ SAL_WARN_IF( !pDrawLayer, "sc.core", "ScCaptionPtr::removeFromDrawPageAndFree - object without drawing layer");
+ // create drawing undo action (before removing the object to have valid draw page in undo action)
+ bRecording = (pDrawLayer && pDrawLayer->IsRecording());
+ if (bRecording)
+ pDrawLayer->AddCalcUndo( std::make_unique<SdrUndoDelObj>( *mpCaption ));
+ }
+ // remove the object from the drawing page, delete if undo is disabled
+ removeFromDrawPage( *pDrawPage );
+ // If called from outside mnRefs must be 1 to delete. If called from
+ // decRefAndDestroy() mnRefs is already 0.
+ if (!bRecording && getRefs() <= 1)
+ {
+ SdrObject* pObj = release();
+ SdrObject::Free( pObj );
+ }
+}
+
+SdrCaptionObj* ScCaptionPtr::release()
+{
+ SdrCaptionObj* pTmp = mpCaption;
+ dissolve();
+ return pTmp;
+}
+
+void ScCaptionPtr::forget()
+{
+ decRef();
+ removeFromList();
+ mpCaption = nullptr;
+ mbNotOwner = false;
+}
+
+void ScCaptionPtr::dissolve()
+{
+ ScCaptionPtr::Head* pHead = mpHead;
+ ScCaptionPtr* pThat = (mpHead ? mpHead->mpFirst : this);
+ while (pThat)
+ {
+ assert(!pThat->mpNext || pThat->mpHead); // next without head is bad
+ assert(pThat->mpHead == pHead); // same head required within one list
+ ScCaptionPtr* p = pThat->mpNext;
+ pThat->clear();
+ pThat = p;
+ }
+ assert(!mpHead && !mpNext && !mpCaption); // should had been cleared during list walk
+ delete pHead;
+}
+
+void ScCaptionPtr::clear()
+{
+ mpHead = nullptr;
+ mpNext = nullptr;
+ mpCaption = nullptr;
+ mbNotOwner = false;
+}
+
+struct ScCaptionInitData
+{
+ std::optional< SfxItemSet > moItemSet; /// Caption object formatting.
+ std::optional< OutlinerParaObject > mxOutlinerObj; /// Text object with all text portion formatting.
+ OUString maSimpleText; /// Simple text without formatting.
+ Point maCaptionOffset; /// Caption position relative to cell corner.
+ Size maCaptionSize; /// Size of the caption object.
+ bool mbDefaultPosSize; /// True = use default position and size for caption.
+
+ explicit ScCaptionInitData();
+};
+
+ScCaptionInitData::ScCaptionInitData() :
+ mbDefaultPosSize( true )
+{
+}
+
+ScNoteData::ScNoteData( bool bShown ) :
+ mbShown( bShown )
+{
+}
+
+sal_uInt32 ScPostIt::mnLastPostItId = 1;
+
+ScPostIt::ScPostIt( ScDocument& rDoc, const ScAddress& rPos, sal_uInt32 nPostItId ) :
+ mrDoc( rDoc ),
+ maNoteData( false )
+{
+ mnPostItId = nPostItId == 0 ? mnLastPostItId++ : nPostItId;
+ AutoStamp();
+ CreateCaption( rPos );
+}
+
+ScPostIt::ScPostIt( ScDocument& rDoc, const ScAddress& rPos, const ScPostIt& rNote, sal_uInt32 nPostItId ) :
+ mrDoc( rDoc ),
+ maNoteData( rNote.maNoteData )
+{
+ mnPostItId = nPostItId == 0 ? mnLastPostItId++ : nPostItId;
+ maNoteData.mxCaption.reset(nullptr);
+ CreateCaption( rPos, rNote.maNoteData.mxCaption.get() );
+}
+
+ScPostIt::ScPostIt( ScDocument& rDoc, const ScAddress& rPos, const ScNoteData& rNoteData, bool bAlwaysCreateCaption, sal_uInt32 nPostItId ) :
+ mrDoc( rDoc ),
+ maNoteData( rNoteData )
+{
+ mnPostItId = nPostItId == 0 ? mnLastPostItId++ : nPostItId;
+ if( bAlwaysCreateCaption || maNoteData.mbShown )
+ CreateCaptionFromInitData( rPos );
+}
+
+ScPostIt::~ScPostIt()
+{
+ RemoveCaption();
+}
+
+std::unique_ptr<ScPostIt> ScPostIt::Clone( const ScAddress& rOwnPos, ScDocument& rDestDoc, const ScAddress& rDestPos, bool bCloneCaption ) const
+{
+ CreateCaptionFromInitData( rOwnPos );
+ sal_uInt32 nPostItId = comphelper::LibreOfficeKit::isActive() ? 0 : mnPostItId;
+ return bCloneCaption ? std::make_unique<ScPostIt>( rDestDoc, rDestPos, *this, nPostItId ) : std::make_unique<ScPostIt>( rDestDoc, rDestPos, maNoteData, false, mnPostItId );
+}
+
+void ScPostIt::SetDate( const OUString& rDate )
+{
+ maNoteData.maDate = rDate;
+}
+
+void ScPostIt::SetAuthor( const OUString& rAuthor )
+{
+ maNoteData.maAuthor = rAuthor;
+}
+
+void ScPostIt::AutoStamp()
+{
+ maNoteData.maDate = ScGlobal::getLocaleData().getDate( Date( Date::SYSTEM ) );
+ maNoteData.maAuthor = SvtUserOptions().GetID();
+}
+
+const OutlinerParaObject* ScPostIt::GetOutlinerObject() const
+{
+ if( maNoteData.mxCaption )
+ return maNoteData.mxCaption->GetOutlinerParaObject();
+ if( maNoteData.mxInitData && maNoteData.mxInitData->mxOutlinerObj )
+ return &*maNoteData.mxInitData->mxOutlinerObj;
+ return nullptr;
+}
+
+const EditTextObject* ScPostIt::GetEditTextObject() const
+{
+ const OutlinerParaObject* pOPO = GetOutlinerObject();
+ return pOPO ? &pOPO->GetTextObject() : nullptr;
+}
+
+OUString ScPostIt::GetText() const
+{
+ if( const EditTextObject* pEditObj = GetEditTextObject() )
+ {
+ OUStringBuffer aBuffer;
+ ScNoteEditEngine& rEngine = mrDoc.GetNoteEngine();
+ rEngine.SetTextCurrentDefaults(*pEditObj);
+ sal_Int32 nParaCount = rEngine.GetParagraphCount();
+ for( sal_Int32 nPara = 0; nPara < nParaCount; ++nPara )
+ {
+ if( nPara > 0 )
+ aBuffer.append( '\n' );
+ aBuffer.append(rEngine.GetText(nPara));
+ }
+ return aBuffer.makeStringAndClear();
+ }
+ if( maNoteData.mxInitData )
+ return maNoteData.mxInitData->maSimpleText;
+ return OUString();
+}
+
+bool ScPostIt::HasMultiLineText() const
+{
+ if( const EditTextObject* pEditObj = GetEditTextObject() )
+ return pEditObj->GetParagraphCount() > 1;
+ if( maNoteData.mxInitData )
+ return maNoteData.mxInitData->maSimpleText.indexOf( '\n' ) >= 0;
+ return false;
+}
+
+void ScPostIt::SetText( const ScAddress& rPos, const OUString& rText )
+{
+ CreateCaptionFromInitData( rPos );
+ if( maNoteData.mxCaption )
+ maNoteData.mxCaption->SetText( rText );
+}
+
+SdrCaptionObj* ScPostIt::GetOrCreateCaption( const ScAddress& rPos ) const
+{
+ CreateCaptionFromInitData( rPos );
+ return maNoteData.mxCaption.get();
+}
+
+void ScPostIt::ForgetCaption( bool bPreserveData )
+{
+ if (bPreserveData)
+ {
+ // Used in clipboard when the originating document is destructed to be
+ // able to paste into another document. Caption size and relative
+ // position are not preserved but default created when pasted. Also the
+ // MergedItemSet can not be carried over or it had to be adapted to
+ // defaults and pool. At least preserve the text and outline object if
+ // possible.
+ ScCaptionInitData* pInitData = new ScCaptionInitData;
+ const OutlinerParaObject* pOPO = GetOutlinerObject();
+ if (pOPO)
+ pInitData->mxOutlinerObj = *pOPO;
+ pInitData->maSimpleText = GetText();
+
+ maNoteData.mxInitData.reset(pInitData);
+ maNoteData.mxCaption.forget();
+ }
+ else
+ {
+ /* This function is used in undo actions to give up the responsibility for
+ the caption object which is handled by separate drawing undo actions. */
+ maNoteData.mxCaption.forget();
+ maNoteData.mxInitData.reset();
+ }
+}
+
+void ScPostIt::ShowCaption( const ScAddress& rPos, bool bShow )
+{
+ CreateCaptionFromInitData( rPos );
+ // no separate drawing undo needed, handled completely inside ScUndoShowHideNote
+ maNoteData.mbShown = bShow;
+ if( maNoteData.mxCaption )
+ ScCaptionUtil::SetCaptionLayer( *maNoteData.mxCaption, bShow );
+}
+
+void ScPostIt::ShowCaptionTemp( const ScAddress& rPos, bool bShow )
+{
+ CreateCaptionFromInitData( rPos );
+ if( maNoteData.mxCaption )
+ ScCaptionUtil::SetCaptionLayer( *maNoteData.mxCaption, maNoteData.mbShown || bShow );
+}
+
+void ScPostIt::UpdateCaptionPos( const ScAddress& rPos )
+{
+ CreateCaptionFromInitData( rPos );
+ if( maNoteData.mxCaption )
+ {
+ ScCaptionCreator aCreator( mrDoc, rPos, maNoteData.mxCaption );
+ aCreator.UpdateCaptionPos();
+ }
+}
+
+// private --------------------------------------------------------------------
+
+void ScPostIt::CreateCaptionFromInitData( const ScAddress& rPos ) const
+{
+ // Captions are not created in Undo documents and only rarely in Clipboard,
+ // but otherwise we need caption or initial data.
+ assert((maNoteData.mxCaption || maNoteData.mxInitData) || mrDoc.IsUndo() || mrDoc.IsClipboard());
+ if( !maNoteData.mxInitData )
+ return;
+
+
+ /* This function is called from ScPostIt::Clone() when copying cells
+ to the clipboard/undo document, and when copying cells from the
+ clipboard/undo document. The former should always be called first,
+ so if called in a clipboard/undo document, the caption should have
+ been created already. However, for clipboard in case the
+ originating document was destructed a new caption has to be
+ created. */
+ OSL_ENSURE( !mrDoc.IsUndo() && (!mrDoc.IsClipboard() || !maNoteData.mxCaption),
+ "ScPostIt::CreateCaptionFromInitData - note caption should not be created in undo/clip documents" );
+
+ // going to forget the initial caption data struct when this method returns
+ auto xInitData = std::move(maNoteData.mxInitData);
+
+ /* #i104915# Never try to create notes in Undo document, leads to
+ crash due to missing document members (e.g. row height array). */
+ if( maNoteData.mxCaption || mrDoc.IsUndo() )
+ return;
+
+ if (mrDoc.IsClipboard())
+ mrDoc.InitDrawLayer(); // ensure there is a drawing layer
+
+ // ScNoteCaptionCreator c'tor creates the caption and inserts it into the document and maNoteData
+ ScNoteCaptionCreator aCreator( mrDoc, rPos, maNoteData );
+ if( !maNoteData.mxCaption )
+ return;
+
+ // Prevent triple change broadcasts of the same object.
+ bool bWasLocked = maNoteData.mxCaption->getSdrModelFromSdrObject().isLocked();
+ maNoteData.mxCaption->getSdrModelFromSdrObject().setLock(true);
+
+ // transfer ownership of outliner object to caption, or set simple text
+ OSL_ENSURE( xInitData->mxOutlinerObj || !xInitData->maSimpleText.isEmpty(),
+ "ScPostIt::CreateCaptionFromInitData - need either outliner para object or simple text" );
+ if (xInitData->mxOutlinerObj)
+ maNoteData.mxCaption->SetOutlinerParaObject( std::move(xInitData->mxOutlinerObj) );
+ else
+ maNoteData.mxCaption->SetText( xInitData->maSimpleText );
+
+ // copy all items or set default items; reset shadow items
+ ScCaptionUtil::SetDefaultItems( *maNoteData.mxCaption, mrDoc, xInitData->moItemSet ? &*xInitData->moItemSet : nullptr );
+
+ // set position and size of the caption object
+ if( xInitData->mbDefaultPosSize )
+ {
+ // set other items and fit caption size to text
+ maNoteData.mxCaption->SetMergedItem( makeSdrTextMinFrameWidthItem( SC_NOTECAPTION_WIDTH ) );
+ maNoteData.mxCaption->SetMergedItem( makeSdrTextMaxFrameWidthItem( SC_NOTECAPTION_MAXWIDTH_TEMP ) );
+ maNoteData.mxCaption->AdjustTextFrameWidthAndHeight();
+ aCreator.AutoPlaceCaption();
+ }
+ else
+ {
+ tools::Rectangle aCellRect = ScDrawLayer::GetCellRect( mrDoc, rPos, true );
+ bool bNegPage = mrDoc.IsNegativePage( rPos.Tab() );
+ tools::Long nPosX = bNegPage ? (aCellRect.Left() - xInitData->maCaptionOffset.X()) : (aCellRect.Right() + xInitData->maCaptionOffset.X());
+ tools::Long nPosY = aCellRect.Top() + xInitData->maCaptionOffset.Y();
+ tools::Rectangle aCaptRect( Point( nPosX, nPosY ), xInitData->maCaptionSize );
+ maNoteData.mxCaption->SetLogicRect( aCaptRect );
+ aCreator.FitCaptionToRect();
+ }
+
+ // End prevent triple change broadcasts of the same object.
+ maNoteData.mxCaption->getSdrModelFromSdrObject().setLock(bWasLocked);
+ maNoteData.mxCaption->BroadcastObjectChange();
+}
+
+void ScPostIt::CreateCaption( const ScAddress& rPos, const SdrCaptionObj* pCaption )
+{
+ OSL_ENSURE( !maNoteData.mxCaption, "ScPostIt::CreateCaption - unexpected caption object found" );
+ maNoteData.mxCaption.reset(nullptr);
+
+ /* #i104915# Never try to create notes in Undo document, leads to
+ crash due to missing document members (e.g. row height array). */
+ OSL_ENSURE( !mrDoc.IsUndo(), "ScPostIt::CreateCaption - note caption should not be created in undo documents" );
+ if( mrDoc.IsUndo() )
+ return;
+
+ // drawing layer may be missing, if a note is copied into a clipboard document
+ if( mrDoc.IsClipboard() )
+ mrDoc.InitDrawLayer();
+
+ // ScNoteCaptionCreator c'tor creates the caption and inserts it into the document and maNoteData
+ ScNoteCaptionCreator aCreator( mrDoc, rPos, maNoteData );
+ if( !maNoteData.mxCaption )
+ return;
+
+ // clone settings of passed caption
+ if( pCaption )
+ {
+ // copy edit text object (object must be inserted into page already)
+ if( OutlinerParaObject* pOPO = pCaption->GetOutlinerParaObject() )
+ maNoteData.mxCaption->SetOutlinerParaObject( *pOPO );
+ // copy formatting items (after text has been copied to apply font formatting)
+ maNoteData.mxCaption->SetMergedItemSetAndBroadcast( pCaption->GetMergedItemSet() );
+ // move textbox position relative to new cell, copy textbox size
+ tools::Rectangle aCaptRect = pCaption->GetLogicRect();
+ Point aDist = maNoteData.mxCaption->GetTailPos() - pCaption->GetTailPos();
+ aCaptRect.Move( aDist.X(), aDist.Y() );
+ maNoteData.mxCaption->SetLogicRect( aCaptRect );
+ aCreator.FitCaptionToRect();
+ }
+ else
+ {
+ // set default formatting and default position
+ ScCaptionUtil::SetDefaultItems( *maNoteData.mxCaption, mrDoc, nullptr );
+ aCreator.AutoPlaceCaption();
+ }
+
+ // create undo action
+ if( ScDrawLayer* pDrawLayer = mrDoc.GetDrawLayer() )
+ if( pDrawLayer->IsRecording() )
+ pDrawLayer->AddCalcUndo( std::make_unique<SdrUndoNewObj>( *maNoteData.mxCaption ) );
+}
+
+void ScPostIt::RemoveCaption()
+{
+ if (!maNoteData.mxCaption)
+ return;
+
+ /* Remove caption object only, if this note is its owner (e.g. notes in
+ undo documents refer to captions in original document, do not remove
+ them from drawing layer here). */
+ // TTTT maybe no longer needed - can that still happen?
+ ScDrawLayer* pDrawLayer = mrDoc.GetDrawLayer();
+ if (pDrawLayer == &maNoteData.mxCaption->getSdrModelFromSdrObject())
+ maNoteData.mxCaption.removeFromDrawPageAndFree();
+
+ SAL_INFO("sc.core","ScPostIt::RemoveCaption - refs: " << maNoteData.mxCaption.getRefs() <<
+ " IsUndo: " << mrDoc.IsUndo() << " IsClip: " << mrDoc.IsClipboard() <<
+ " Dtor: " << mrDoc.IsInDtorClear());
+
+ // Forget the caption object if removeFromDrawPageAndFree() did not free it.
+ if (maNoteData.mxCaption)
+ {
+ SAL_INFO("sc.core","ScPostIt::RemoveCaption - forgetting one ref");
+ maNoteData.mxCaption.forget();
+ }
+}
+
+ScCaptionPtr ScNoteUtil::CreateTempCaption(
+ ScDocument& rDoc, const ScAddress& rPos, SdrPage& rDrawPage,
+ std::u16string_view rUserText, const tools::Rectangle& rVisRect, bool bTailFront )
+{
+ OUStringBuffer aBuffer( rUserText );
+ // add plain text of invisible (!) cell note (no formatting etc.)
+ SdrCaptionObj* pNoteCaption = nullptr;
+ const ScPostIt* pNote = rDoc.GetNote( rPos );
+ if( pNote && !pNote->IsCaptionShown() )
+ {
+ if( !aBuffer.isEmpty() )
+ aBuffer.append( "\n--------\n" + pNote->GetText() );
+ pNoteCaption = pNote->GetOrCreateCaption( rPos );
+ }
+
+ // create a caption if any text exists
+ if( !pNoteCaption && aBuffer.isEmpty() )
+ return ScCaptionPtr();
+
+ // prepare visible rectangle (add default distance to all borders)
+ tools::Rectangle aVisRect(
+ rVisRect.Left() + SC_NOTECAPTION_BORDERDIST_TEMP,
+ rVisRect.Top() + SC_NOTECAPTION_BORDERDIST_TEMP,
+ rVisRect.Right() - SC_NOTECAPTION_BORDERDIST_TEMP,
+ rVisRect.Bottom() - SC_NOTECAPTION_BORDERDIST_TEMP );
+
+ // create the caption object
+ ScCaptionCreator aCreator( rDoc, rPos, bTailFront );
+
+ // insert caption into page (needed to set caption text)
+ aCreator.GetCaption().insertToDrawPage( rDrawPage );
+
+ SdrCaptionObj* pCaption = aCreator.GetCaption().get(); // just for ease of use
+
+ // clone the edit text object, unless user text is present, then set this text
+ if( pNoteCaption && rUserText.empty() )
+ {
+ if( OutlinerParaObject* pOPO = pNoteCaption->GetOutlinerParaObject() )
+ pCaption->SetOutlinerParaObject( *pOPO );
+ // set formatting (must be done after setting text) and resize the box to fit the text
+ pCaption->SetMergedItemSetAndBroadcast( pNoteCaption->GetMergedItemSet() );
+ tools::Rectangle aCaptRect( pCaption->GetLogicRect().TopLeft(), pNoteCaption->GetLogicRect().GetSize() );
+ pCaption->SetLogicRect( aCaptRect );
+ }
+ else
+ {
+ // if pNoteCaption is null, then aBuffer contains some text
+ pCaption->SetText( aBuffer.makeStringAndClear() );
+ ScCaptionUtil::SetDefaultItems( *pCaption, rDoc, nullptr );
+ // adjust caption size to text size
+ tools::Long nMaxWidth = ::std::min< tools::Long >( aVisRect.GetWidth() * 2 / 3, SC_NOTECAPTION_MAXWIDTH_TEMP );
+ pCaption->SetMergedItem( makeSdrTextAutoGrowWidthItem( true ) );
+ pCaption->SetMergedItem( makeSdrTextMinFrameWidthItem( SC_NOTECAPTION_WIDTH ) );
+ pCaption->SetMergedItem( makeSdrTextMaxFrameWidthItem( nMaxWidth ) );
+ pCaption->SetMergedItem( makeSdrTextAutoGrowHeightItem( true ) );
+ pCaption->AdjustTextFrameWidthAndHeight();
+ }
+
+ // move caption into visible area
+ aCreator.AutoPlaceCaption( &aVisRect );
+
+ // XXX Note it is already inserted to the draw page.
+ return aCreator.GetCaption();
+}
+
+ScPostIt* ScNoteUtil::CreateNoteFromCaption(
+ ScDocument& rDoc, const ScAddress& rPos, SdrCaptionObj* pCaption )
+{
+ ScNoteData aNoteData( true/*bShown*/ );
+ aNoteData.mxCaption.reset( pCaption );
+ ScPostIt* pNote = new ScPostIt( rDoc, rPos, aNoteData, false );
+ pNote->AutoStamp();
+
+ rDoc.SetNote(rPos, std::unique_ptr<ScPostIt>(pNote));
+
+ // ScNoteCaptionCreator c'tor updates the caption object to be part of a note
+ ScNoteCaptionCreator aCreator( rDoc, rPos, aNoteData.mxCaption, true/*bShown*/ );
+
+ return pNote;
+}
+
+ScPostIt* ScNoteUtil::CreateNoteFromObjectData(
+ ScDocument& rDoc, const ScAddress& rPos, SfxItemSet&& rItemSet,
+ const OutlinerParaObject& rOutlinerObj, const tools::Rectangle& rCaptionRect,
+ bool bShown )
+{
+ ScNoteData aNoteData( bShown );
+ aNoteData.mxInitData = std::make_shared<ScCaptionInitData>();
+ ScCaptionInitData& rInitData = *aNoteData.mxInitData;
+ rInitData.moItemSet.emplace(std::move(rItemSet));
+ rInitData.mxOutlinerObj = rOutlinerObj;
+
+ // convert absolute caption position to relative position
+ rInitData.mbDefaultPosSize = rCaptionRect.IsEmpty();
+ if( !rInitData.mbDefaultPosSize )
+ {
+ tools::Rectangle aCellRect = ScDrawLayer::GetCellRect( rDoc, rPos, true );
+ bool bNegPage = rDoc.IsNegativePage( rPos.Tab() );
+ rInitData.maCaptionOffset.setX( bNegPage ? (aCellRect.Left() - rCaptionRect.Right()) : (rCaptionRect.Left() - aCellRect.Right()) );
+ rInitData.maCaptionOffset.setY( rCaptionRect.Top() - aCellRect.Top() );
+ rInitData.maCaptionSize = rCaptionRect.GetSize();
+ }
+
+ /* Create the note and insert it into the document. If the note is
+ visible, the caption object will be created automatically. */
+ ScPostIt* pNote = new ScPostIt( rDoc, rPos, aNoteData, /*bAlwaysCreateCaption*/false, 0/*nPostItId*/ );
+ pNote->AutoStamp();
+
+ rDoc.SetNote(rPos, std::unique_ptr<ScPostIt>(pNote));
+
+ return pNote;
+}
+
+ScPostIt* ScNoteUtil::CreateNoteFromString(
+ ScDocument& rDoc, const ScAddress& rPos, const OUString& rNoteText,
+ bool bShown, bool bAlwaysCreateCaption, sal_uInt32 nPostItId )
+{
+ ScPostIt* pNote = nullptr;
+ if( !rNoteText.isEmpty() )
+ {
+ ScNoteData aNoteData( bShown );
+ aNoteData.mxInitData = std::make_shared<ScCaptionInitData>();
+ ScCaptionInitData& rInitData = *aNoteData.mxInitData;
+ rInitData.maSimpleText = rNoteText;
+ rInitData.mbDefaultPosSize = true;
+
+ /* Create the note and insert it into the document. If the note is
+ visible, the caption object will be created automatically. */
+ pNote = new ScPostIt( rDoc, rPos, aNoteData, bAlwaysCreateCaption, nPostItId );
+ pNote->AutoStamp();
+ //insert takes ownership
+ rDoc.SetNote(rPos, std::unique_ptr<ScPostIt>(pNote));
+ }
+ return pNote;
+}
+
+namespace sc {
+
+NoteEntry::NoteEntry( const ScAddress& rPos, const ScPostIt* pNote ) :
+ maPos(rPos), mpNote(pNote) {}
+
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sc/source/core/data/queryevaluator.cxx b/sc/source/core/data/queryevaluator.cxx
new file mode 100644
index 000000000..0cf27a36e
--- /dev/null
+++ b/sc/source/core/data/queryevaluator.cxx
@@ -0,0 +1,961 @@
+/* -*- 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 <queryevaluator.hxx>
+
+#include <cellform.hxx>
+#include <cellvalue.hxx>
+#include <colorscale.hxx>
+#include <document.hxx>
+#include <docoptio.hxx>
+#include <queryparam.hxx>
+#include <scitems.hxx>
+#include <table.hxx>
+
+#include <editeng/brushitem.hxx>
+#include <editeng/colritem.hxx>
+#include <svl/numformat.hxx>
+#include <svl/sharedstringpool.hxx>
+#include <svl/zformat.hxx>
+#include <unotools/collatorwrapper.hxx>
+
+bool ScQueryEvaluator::isPartialTextMatchOp(ScQueryOp eOp)
+{
+ switch (eOp)
+ {
+ // these operators can only be used with textural comparisons.
+ case SC_CONTAINS:
+ case SC_DOES_NOT_CONTAIN:
+ case SC_BEGINS_WITH:
+ case SC_ENDS_WITH:
+ case SC_DOES_NOT_BEGIN_WITH:
+ case SC_DOES_NOT_END_WITH:
+ return true;
+ default:;
+ }
+ return false;
+}
+
+bool ScQueryEvaluator::isTextMatchOp(ScQueryOp eOp)
+{
+ if (isPartialTextMatchOp(eOp))
+ return true;
+
+ switch (eOp)
+ {
+ // these operators can be used for either textural or value comparison.
+ case SC_EQUAL:
+ case SC_NOT_EQUAL:
+ return true;
+ default:;
+ }
+ return false;
+}
+
+bool ScQueryEvaluator::isMatchWholeCellHelper(bool docMatchWholeCell, ScQueryOp eOp)
+{
+ bool bMatchWholeCell = docMatchWholeCell;
+ if (isPartialTextMatchOp(eOp))
+ // may have to do partial textural comparison.
+ bMatchWholeCell = false;
+ return bMatchWholeCell;
+}
+
+bool ScQueryEvaluator::isMatchWholeCell(ScQueryOp eOp) const
+{
+ return isMatchWholeCellHelper(mbMatchWholeCell, eOp);
+}
+
+bool ScQueryEvaluator::isMatchWholeCell(const ScDocument& rDoc, ScQueryOp eOp)
+{
+ return isMatchWholeCellHelper(rDoc.GetDocOptions().IsMatchWholeCell(), eOp);
+}
+
+void ScQueryEvaluator::setupTransliteratorIfNeeded()
+{
+ if (!mpTransliteration)
+ mpTransliteration = &ScGlobal::GetTransliteration(mrParam.bCaseSens);
+}
+
+void ScQueryEvaluator::setupCollatorIfNeeded()
+{
+ if (!mpCollator)
+ mpCollator = &ScGlobal::GetCollator(mrParam.bCaseSens);
+}
+
+ScQueryEvaluator::ScQueryEvaluator(ScDocument& rDoc, const ScTable& rTab,
+ const ScQueryParam& rParam, const ScInterpreterContext* pContext,
+ bool* pTestEqualCondition)
+ : mrDoc(rDoc)
+ , mrStrPool(rDoc.GetSharedStringPool())
+ , mrTab(rTab)
+ , mrParam(rParam)
+ , mpTestEqualCondition(pTestEqualCondition)
+ , mpTransliteration(nullptr)
+ , mpCollator(nullptr)
+ , mbMatchWholeCell(rDoc.GetDocOptions().IsMatchWholeCell())
+ , mbCaseSensitive(rParam.bCaseSens)
+ , mpContext(pContext)
+ , mnEntryCount(mrParam.GetEntryCount())
+{
+ if (mnEntryCount <= nFixedBools)
+ {
+ mpPasst = &maBool[0];
+ mpTest = &maTest[0];
+ }
+ else
+ {
+ mpBoolDynamic.reset(new bool[mnEntryCount]);
+ mpTestDynamic.reset(new bool[mnEntryCount]);
+ mpPasst = mpBoolDynamic.get();
+ mpTest = mpTestDynamic.get();
+ }
+}
+
+bool ScQueryEvaluator::isRealWildOrRegExp(const ScQueryEntry& rEntry) const
+{
+ if (mrParam.eSearchType == utl::SearchParam::SearchType::Normal)
+ return false;
+
+ return isTextMatchOp(rEntry.eOp);
+}
+
+bool ScQueryEvaluator::isTestWildOrRegExp(const ScQueryEntry& rEntry) const
+{
+ if (!mpTestEqualCondition)
+ return false;
+
+ if (mrParam.eSearchType == utl::SearchParam::SearchType::Normal)
+ return false;
+
+ return (rEntry.eOp == SC_LESS_EQUAL || rEntry.eOp == SC_GREATER_EQUAL);
+}
+
+bool ScQueryEvaluator::isQueryByValue(ScQueryOp eOp, ScQueryEntry::QueryType eType,
+ const ScRefCellValue& rCell)
+{
+ if (eType == ScQueryEntry::ByString || isPartialTextMatchOp(eOp))
+ return false;
+
+ return isQueryByValueForCell(rCell);
+}
+
+bool ScQueryEvaluator::isQueryByValueForCell(const ScRefCellValue& rCell)
+{
+ if (rCell.meType == CELLTYPE_FORMULA && rCell.mpFormula->GetErrCode() != FormulaError::NONE)
+ // Error values are compared as string.
+ return false;
+
+ return rCell.hasNumeric();
+}
+
+bool ScQueryEvaluator::isQueryByString(ScQueryOp eOp, ScQueryEntry::QueryType eType,
+ const ScRefCellValue& rCell)
+{
+ if (isTextMatchOp(eOp))
+ return true;
+
+ if (eType != ScQueryEntry::ByString)
+ return false;
+
+ return rCell.hasString();
+}
+
+sal_uInt32 ScQueryEvaluator::getNumFmt(SCCOL nCol, SCROW nRow)
+{
+ sal_uInt32 nNumFmt
+ = (mpContext ? mrTab.GetNumberFormat(*mpContext, ScAddress(nCol, nRow, mrTab.GetTab()))
+ : mrTab.GetNumberFormat(nCol, nRow));
+ if (nNumFmt && (nNumFmt % SV_COUNTRY_LANGUAGE_OFFSET) == 0)
+ // Any General of any locale is irrelevant for rounding.
+ nNumFmt = 0;
+ return nNumFmt;
+}
+
+std::pair<bool, bool> ScQueryEvaluator::compareByValue(const ScRefCellValue& rCell, SCCOL nCol,
+ SCROW nRow, const ScQueryEntry& rEntry,
+ const ScQueryEntry::Item& rItem)
+{
+ bool bOk = false;
+ bool bTestEqual = false;
+ double nCellVal;
+ double fQueryVal = rItem.mfVal;
+ // Defer all number format detection to as late as possible as it's a
+ // bottle neck, even if that complicates the code. Also do not
+ // unnecessarily call ScDocument::RoundValueAsShown() for the same
+ // reason.
+ sal_uInt32 nNumFmt = NUMBERFORMAT_ENTRY_NOT_FOUND;
+
+ switch (rCell.meType)
+ {
+ case CELLTYPE_VALUE:
+ nCellVal = rCell.mfValue;
+ break;
+ case CELLTYPE_FORMULA:
+ nCellVal = rCell.mpFormula->GetValue();
+ break;
+ default:
+ nCellVal = 0.0;
+ }
+ if (rItem.mbRoundForFilter && nCellVal != 0.0)
+ {
+ nNumFmt = getNumFmt(nCol, nRow);
+ if (nNumFmt)
+ {
+ switch (rCell.meType)
+ {
+ case CELLTYPE_VALUE:
+ case CELLTYPE_FORMULA:
+ nCellVal = mrDoc.RoundValueAsShown(nCellVal, nNumFmt, mpContext);
+ break;
+ default:
+ assert(!"can't be");
+ }
+ }
+ }
+
+ /* NOTE: lcl_PrepareQuery() prepares a filter query such that if a
+ * date+time format was queried rEntry.bQueryByDate is not set. In
+ * case other queries wanted to use this mechanism they should do
+ * the same, in other words only if rEntry.nVal is an integer value
+ * rEntry.bQueryByDate should be true and the time fraction be
+ * stripped here. */
+
+ if (rItem.meType == ScQueryEntry::ByDate)
+ {
+ if (nNumFmt == NUMBERFORMAT_ENTRY_NOT_FOUND)
+ nNumFmt = getNumFmt(nCol, nRow);
+ if (nNumFmt)
+ {
+ SvNumberFormatter* pFormatter
+ = mpContext ? mpContext->GetFormatTable() : mrDoc.GetFormatTable();
+ const SvNumberformat* pEntry = pFormatter->GetEntry(nNumFmt);
+ if (pEntry)
+ {
+ SvNumFormatType nNumFmtType = pEntry->GetType();
+ /* NOTE: Omitting the check for absence of
+ * css::util::NumberFormat::TIME would include also date+time formatted
+ * values of the same day. That may be desired in some
+ * cases, querying all time values of a day, but confusing
+ * in other cases. A user can always setup a standard
+ * filter query for x >= date AND x < date+1 */
+ if ((nNumFmtType & SvNumFormatType::DATE) && !(nNumFmtType & SvNumFormatType::TIME))
+ {
+ // The format is of date type. Strip off the time
+ // element.
+ nCellVal = ::rtl::math::approxFloor(nCellVal);
+ }
+ }
+ }
+ }
+
+ switch (rEntry.eOp)
+ {
+ case SC_EQUAL:
+ bOk = ::rtl::math::approxEqual(nCellVal, fQueryVal);
+ break;
+ case SC_LESS:
+ bOk = (nCellVal < fQueryVal) && !::rtl::math::approxEqual(nCellVal, fQueryVal);
+ break;
+ case SC_GREATER:
+ bOk = (nCellVal > fQueryVal) && !::rtl::math::approxEqual(nCellVal, fQueryVal);
+ break;
+ case SC_LESS_EQUAL:
+ bOk = (nCellVal < fQueryVal) || ::rtl::math::approxEqual(nCellVal, fQueryVal);
+ if (bOk && mpTestEqualCondition)
+ bTestEqual = ::rtl::math::approxEqual(nCellVal, fQueryVal);
+ break;
+ case SC_GREATER_EQUAL:
+ bOk = (nCellVal > fQueryVal) || ::rtl::math::approxEqual(nCellVal, fQueryVal);
+ if (bOk && mpTestEqualCondition)
+ bTestEqual = ::rtl::math::approxEqual(nCellVal, fQueryVal);
+ break;
+ case SC_NOT_EQUAL:
+ bOk = !::rtl::math::approxEqual(nCellVal, fQueryVal);
+ break;
+ default:
+ assert(false);
+ break;
+ }
+
+ return std::pair<bool, bool>(bOk, bTestEqual);
+}
+
+OUString ScQueryEvaluator::getCellString(const ScRefCellValue& rCell, SCROW nRow, SCCOL nCol,
+ const svl::SharedString** sharedString)
+{
+ if (rCell.meType == CELLTYPE_FORMULA && rCell.mpFormula->GetErrCode() != FormulaError::NONE)
+ {
+ // Error cell is evaluated as string (for now).
+ const FormulaError error = rCell.mpFormula->GetErrCode();
+ auto it = mCachedSharedErrorStrings.find(error);
+ if (it == mCachedSharedErrorStrings.end())
+ {
+ svl::SharedString str = mrStrPool.intern(ScGlobal::GetErrorString(error));
+ auto pos = mCachedSharedErrorStrings.insert({ error, str });
+ assert(pos.second); // inserted
+ it = pos.first;
+ }
+ *sharedString = &it->second;
+ return OUString();
+ }
+ else if (rCell.meType == CELLTYPE_STRING)
+ {
+ *sharedString = rCell.mpString;
+ return OUString();
+ }
+ else
+ {
+ sal_uInt32 nFormat
+ = mpContext ? mrTab.GetNumberFormat(*mpContext, ScAddress(nCol, nRow, mrTab.GetTab()))
+ : mrTab.GetNumberFormat(nCol, nRow);
+ SvNumberFormatter* pFormatter
+ = mpContext ? mpContext->GetFormatTable() : mrDoc.GetFormatTable();
+ return ScCellFormat::GetInputString(rCell, nFormat, *pFormatter, mrDoc, sharedString, true);
+ }
+}
+
+bool ScQueryEvaluator::isFastCompareByString(const ScQueryEntry& rEntry) const
+{
+ // If this is true, then there's a fast path in compareByString() which
+ // can be selected using the template argument to get fast code
+ // that will not check the same conditions every time. This makes a difference
+ // in fast lookups that search for an exact value (case sensitive or not).
+ const bool bRealWildOrRegExp = isRealWildOrRegExp(rEntry);
+ const bool bTestWildOrRegExp = isTestWildOrRegExp(rEntry);
+ // SC_EQUAL is part of isTextMatchOp(rEntry)
+ return rEntry.eOp == SC_EQUAL && !bRealWildOrRegExp && !bTestWildOrRegExp
+ && isMatchWholeCell(rEntry.eOp);
+}
+
+// The value is placed inside one parameter: [pValueSource1] or [pValueSource2] but never in both.
+// For the template argument see isFastCompareByString().
+template <bool bFast>
+std::pair<bool, bool> ScQueryEvaluator::compareByString(const ScQueryEntry& rEntry,
+ const ScQueryEntry::Item& rItem,
+ const svl::SharedString* pValueSource1,
+ const OUString* pValueSource2)
+{
+ bool bOk = false;
+ bool bTestEqual = false;
+ bool bMatchWholeCell;
+ if (bFast)
+ bMatchWholeCell = true;
+ else
+ bMatchWholeCell = isMatchWholeCell(rEntry.eOp);
+ const bool bRealWildOrRegExp = !bFast && isRealWildOrRegExp(rEntry);
+ const bool bTestWildOrRegExp = !bFast && isTestWildOrRegExp(rEntry);
+
+ assert(!bFast || pValueSource1 != nullptr); // shared string for fast path
+ // [pValueSource1] or [pValueSource2] but never both of them or none of them
+ assert((pValueSource1 != nullptr) != (pValueSource2 != nullptr));
+
+ if (!bFast && (bRealWildOrRegExp || bTestWildOrRegExp))
+ {
+ const OUString& rValue = pValueSource1 ? pValueSource1->getString() : *pValueSource2;
+
+ sal_Int32 nStart = 0;
+ sal_Int32 nEnd = rValue.getLength();
+
+ // from 614 on, nEnd is behind the found text
+ bool bMatch = false;
+ if (rEntry.eOp == SC_ENDS_WITH || rEntry.eOp == SC_DOES_NOT_END_WITH)
+ {
+ nEnd = 0;
+ nStart = rValue.getLength();
+ bMatch
+ = rEntry.GetSearchTextPtr(mrParam.eSearchType, mrParam.bCaseSens, bMatchWholeCell)
+ ->SearchBackward(rValue, &nStart, &nEnd);
+ }
+ else
+ {
+ bMatch
+ = rEntry.GetSearchTextPtr(mrParam.eSearchType, mrParam.bCaseSens, bMatchWholeCell)
+ ->SearchForward(rValue, &nStart, &nEnd);
+ }
+ if (bMatch && bMatchWholeCell && (nStart != 0 || nEnd != rValue.getLength()))
+ bMatch = false; // RegExp must match entire cell string
+ if (bRealWildOrRegExp)
+ {
+ switch (rEntry.eOp)
+ {
+ case SC_EQUAL:
+ case SC_CONTAINS:
+ bOk = bMatch;
+ break;
+ case SC_NOT_EQUAL:
+ case SC_DOES_NOT_CONTAIN:
+ bOk = !bMatch;
+ break;
+ case SC_BEGINS_WITH:
+ bOk = (bMatch && (nStart == 0));
+ break;
+ case SC_DOES_NOT_BEGIN_WITH:
+ bOk = !(bMatch && (nStart == 0));
+ break;
+ case SC_ENDS_WITH:
+ bOk = (bMatch && (nEnd == rValue.getLength()));
+ break;
+ case SC_DOES_NOT_END_WITH:
+ bOk = !(bMatch && (nEnd == rValue.getLength()));
+ break;
+ default:
+ assert(false);
+ break;
+ }
+ }
+ else
+ bTestEqual = bMatch;
+ }
+ if (bFast || !bRealWildOrRegExp)
+ {
+ // Simple string matching i.e. no regexp match.
+ if (bFast || isTextMatchOp(rEntry.eOp))
+ {
+ // Check this even with bFast.
+ if (rItem.meType != ScQueryEntry::ByString && rItem.maString.isEmpty())
+ {
+ // #i18374# When used from functions (match, countif, sumif, vlookup, hlookup, lookup),
+ // the query value is assigned directly, and the string is empty. In that case,
+ // don't find any string (isEqual would find empty string results in formula cells).
+ bOk = false;
+ if (rEntry.eOp == SC_NOT_EQUAL)
+ bOk = !bOk;
+ }
+ else if (bFast || bMatchWholeCell)
+ {
+ if (bFast || pValueSource1)
+ {
+ // Fast string equality check by comparing string identifiers.
+ // This is the bFast path, all conditions should lead here on bFast == true.
+ if (mrParam.bCaseSens)
+ {
+ bOk = pValueSource1->getData() == rItem.maString.getData();
+ }
+ else
+ {
+ bOk = pValueSource1->getDataIgnoreCase()
+ == rItem.maString.getDataIgnoreCase();
+ }
+ }
+ else // if (pValueSource2)
+ {
+ if (mrParam.bCaseSens)
+ {
+ bOk = (*pValueSource2 == rItem.maString.getString());
+ }
+ else
+ {
+ // fallback
+ const svl::SharedString rSource2(mrStrPool.intern(*pValueSource2));
+ // Fast string equality check by comparing string identifiers.
+ bOk = rSource2.getDataIgnoreCase() == rItem.maString.getDataIgnoreCase();
+ }
+ }
+
+ if (!bFast && rEntry.eOp == SC_NOT_EQUAL)
+ bOk = !bOk;
+ }
+ else
+ {
+ // Where do we find a match (if at all)
+ sal_Int32 nStrPos;
+
+ if (!mbCaseSensitive)
+ { // Common case for vlookup etc.
+ const svl::SharedString rSource(
+ pValueSource1 ? *pValueSource1 : mrStrPool.intern(*pValueSource2));
+
+ const rtl_uString* pQuer = rItem.maString.getDataIgnoreCase();
+ const rtl_uString* pCellStr = rSource.getDataIgnoreCase();
+
+ assert(pCellStr != nullptr);
+ if (pQuer == nullptr)
+ pQuer = svl::SharedString::getEmptyString().getDataIgnoreCase();
+
+ const sal_Int32 nIndex
+ = (rEntry.eOp == SC_ENDS_WITH || rEntry.eOp == SC_DOES_NOT_END_WITH)
+ ? (pCellStr->length - pQuer->length)
+ : 0;
+
+ if (nIndex < 0)
+ nStrPos = -1;
+ else
+ { // OUString::indexOf
+ nStrPos = rtl_ustr_indexOfStr_WithLength(pCellStr->buffer + nIndex,
+ pCellStr->length - nIndex,
+ pQuer->buffer, pQuer->length);
+
+ if (nStrPos >= 0)
+ nStrPos += nIndex;
+ }
+ }
+ else
+ {
+ const OUString& rValue
+ = pValueSource1 ? pValueSource1->getString() : *pValueSource2;
+ const OUString aQueryStr = rItem.maString.getString();
+ const LanguageType nLang
+ = ScGlobal::oSysLocale->GetLanguageTag().getLanguageType();
+ setupTransliteratorIfNeeded();
+ const OUString aCell(mpTransliteration->transliterate(
+ rValue, nLang, 0, rValue.getLength(), nullptr));
+
+ const OUString aQuer(mpTransliteration->transliterate(
+ aQueryStr, nLang, 0, aQueryStr.getLength(), nullptr));
+
+ const sal_Int32 nIndex
+ = (rEntry.eOp == SC_ENDS_WITH || rEntry.eOp == SC_DOES_NOT_END_WITH)
+ ? (aCell.getLength() - aQuer.getLength())
+ : 0;
+ nStrPos = ((nIndex < 0) ? -1 : aCell.indexOf(aQuer, nIndex));
+ }
+ switch (rEntry.eOp)
+ {
+ case SC_EQUAL:
+ case SC_CONTAINS:
+ bOk = (nStrPos != -1);
+ break;
+ case SC_NOT_EQUAL:
+ case SC_DOES_NOT_CONTAIN:
+ bOk = (nStrPos == -1);
+ break;
+ case SC_BEGINS_WITH:
+ bOk = (nStrPos == 0);
+ break;
+ case SC_DOES_NOT_BEGIN_WITH:
+ bOk = (nStrPos != 0);
+ break;
+ case SC_ENDS_WITH:
+ bOk = (nStrPos >= 0);
+ break;
+ case SC_DOES_NOT_END_WITH:
+ bOk = (nStrPos < 0);
+ break;
+ default:
+ assert(false);
+ break;
+ }
+ }
+ }
+ else
+ { // use collator here because data was probably sorted
+ const OUString& rValue = pValueSource1 ? pValueSource1->getString() : *pValueSource2;
+ setupCollatorIfNeeded();
+ sal_Int32 nCompare = mpCollator->compareString(rValue, rItem.maString.getString());
+ switch (rEntry.eOp)
+ {
+ case SC_LESS:
+ bOk = (nCompare < 0);
+ break;
+ case SC_GREATER:
+ bOk = (nCompare > 0);
+ break;
+ case SC_LESS_EQUAL:
+ bOk = (nCompare <= 0);
+ if (bOk && mpTestEqualCondition && !bTestEqual)
+ bTestEqual = (nCompare == 0);
+ break;
+ case SC_GREATER_EQUAL:
+ bOk = (nCompare >= 0);
+ if (bOk && mpTestEqualCondition && !bTestEqual)
+ bTestEqual = (nCompare == 0);
+ break;
+ default:
+ assert(false);
+ break;
+ }
+ }
+ }
+
+ return std::pair<bool, bool>(bOk, bTestEqual);
+}
+
+std::pair<bool, bool> ScQueryEvaluator::compareByTextColor(SCCOL nCol, SCROW nRow,
+ const ScQueryEntry::Item& rItem)
+{
+ ScAddress aPos(nCol, nRow, mrTab.GetTab());
+ Color color;
+ bool bHasConditionalColor = false;
+ // Text color can be set via conditional formatting - check that first
+ const ScPatternAttr* pPattern = mrDoc.GetPattern(nCol, nRow, mrTab.GetTab());
+ if (pPattern)
+ {
+ if (!pPattern->GetItem(ATTR_CONDITIONAL).GetCondFormatData().empty())
+ {
+ const SfxItemSet* pCondSet = mrDoc.GetCondResult(nCol, nRow, mrTab.GetTab());
+ const SvxColorItem* pColor = &pPattern->GetItem(ATTR_FONT_COLOR, pCondSet);
+ color = pColor->GetValue();
+ bHasConditionalColor = true;
+ }
+ }
+
+ if (!bHasConditionalColor)
+ {
+ const SvxColorItem* pColor = mrDoc.GetAttr(aPos, ATTR_FONT_COLOR);
+ color = pColor->GetValue();
+ }
+
+ bool bMatch = rItem.maColor == color;
+ return std::pair<bool, bool>(bMatch, false);
+}
+
+std::pair<bool, bool> ScQueryEvaluator::compareByBackgroundColor(SCCOL nCol, SCROW nRow,
+ const ScQueryEntry::Item& rItem)
+{
+ ScAddress aPos(nCol, nRow, mrTab.GetTab());
+ Color color;
+
+ // Background color can be set via conditional formatting - check that first
+ bool bHasConditionalColor = false;
+ const ScPatternAttr* pPattern = mrDoc.GetPattern(nCol, nRow, mrTab.GetTab());
+ if (pPattern)
+ {
+ if (!pPattern->GetItem(ATTR_CONDITIONAL).GetCondFormatData().empty())
+ {
+ const SfxItemSet* pCondSet = mrDoc.GetCondResult(nCol, nRow, mrTab.GetTab());
+ const SvxBrushItem* pBackgroundColor = &pPattern->GetItem(ATTR_BACKGROUND, pCondSet);
+ color = pBackgroundColor->GetColor();
+ bHasConditionalColor = true;
+ }
+ }
+
+ ScConditionalFormat* pCondFormat = mrDoc.GetCondFormat(nCol, nRow, mrTab.GetTab());
+ if (pCondFormat)
+ {
+ for (size_t i = 0; i < pCondFormat->size(); i++)
+ {
+ auto aEntry = pCondFormat->GetEntry(i);
+ if (aEntry->GetType() == ScFormatEntry::Type::Colorscale)
+ {
+ const ScColorScaleFormat* pColFormat
+ = static_cast<const ScColorScaleFormat*>(aEntry);
+ color = *(pColFormat->GetColor(aPos));
+ bHasConditionalColor = true;
+ }
+ }
+ }
+
+ if (!bHasConditionalColor)
+ {
+ const SvxBrushItem* pBrush = mrDoc.GetAttr(aPos, ATTR_BACKGROUND);
+ color = pBrush->GetColor();
+ }
+
+ bool bMatch = rItem.maColor == color;
+ return std::pair<bool, bool>(bMatch, false);
+}
+
+// To be called only if both isQueryByValue() and isQueryByString()
+// returned false and range lookup is wanted! In range lookup comparison
+// numbers are less than strings. Nothing else is compared.
+std::pair<bool, bool> ScQueryEvaluator::compareByRangeLookup(const ScRefCellValue& rCell,
+ const ScQueryEntry& rEntry,
+ const ScQueryEntry::Item& rItem)
+{
+ bool bTestEqual = false;
+
+ if (rItem.meType == ScQueryEntry::ByString && rEntry.eOp != SC_LESS
+ && rEntry.eOp != SC_LESS_EQUAL)
+ return std::pair<bool, bool>(false, bTestEqual);
+
+ if (rItem.meType != ScQueryEntry::ByString && rEntry.eOp != SC_GREATER
+ && rEntry.eOp != SC_GREATER_EQUAL)
+ return std::pair<bool, bool>(false, bTestEqual);
+
+ if (rItem.meType == ScQueryEntry::ByString)
+ {
+ if (rCell.meType == CELLTYPE_FORMULA && rCell.mpFormula->GetErrCode() != FormulaError::NONE)
+ // Error values are compared as string.
+ return std::pair<bool, bool>(false, bTestEqual);
+
+ return std::pair<bool, bool>(rCell.hasNumeric(), bTestEqual);
+ }
+
+ return std::pair<bool, bool>(!rCell.hasNumeric(), bTestEqual);
+}
+
+std::pair<bool, bool> ScQueryEvaluator::processEntry(SCROW nRow, SCCOL nCol, ScRefCellValue& aCell,
+ const ScQueryEntry& rEntry, size_t nEntryIndex)
+{
+ std::pair<bool, bool> aRes(false, false);
+ const ScQueryEntry::QueryItemsType& rItems = rEntry.GetQueryItems();
+ if (rItems.size() == 1 && rItems.front().meType == ScQueryEntry::ByEmpty)
+ {
+ if (rEntry.IsQueryByEmpty())
+ aRes.first = aCell.isEmpty();
+ else
+ {
+ assert(rEntry.IsQueryByNonEmpty());
+ aRes.first = !aCell.isEmpty();
+ }
+ return aRes;
+ }
+ if (rEntry.eOp == SC_EQUAL && rItems.size() >= 10)
+ {
+ // If there are many items to query for (autofilter does this), then try to search
+ // efficiently in those items. So first search all the items of the relevant type,
+ // If that does not find anything, fall back to the generic code.
+ double value = 0;
+ bool valid = true;
+ // For ScQueryEntry::ByValue check that the cell either is a value or is a formula
+ // that has a value and is not an error (those are compared as strings). This
+ // is basically simplified isQueryByValue().
+ if (aCell.meType == CELLTYPE_VALUE)
+ value = aCell.mfValue;
+ else if (aCell.meType == CELLTYPE_FORMULA
+ && aCell.mpFormula->GetErrCode() != FormulaError::NONE
+ && aCell.mpFormula->IsValue())
+ {
+ value = aCell.mpFormula->GetValue();
+ }
+ else
+ valid = false;
+ if (valid)
+ {
+ if (rItems.size() >= 100)
+ {
+ // Sort, cache and binary search for the value in items.
+ // Don't bother comparing approximately.
+ if (mCachedSortedItemValues.size() <= nEntryIndex)
+ {
+ mCachedSortedItemValues.resize(nEntryIndex + 1);
+ auto& values = mCachedSortedItemValues[nEntryIndex];
+ values.reserve(rItems.size());
+ for (const auto& rItem : rItems)
+ if (rItem.meType == ScQueryEntry::ByValue)
+ values.push_back(rItem.mfVal);
+ std::sort(values.begin(), values.end());
+ }
+ auto& values = mCachedSortedItemValues[nEntryIndex];
+ auto it = std::lower_bound(values.begin(), values.end(), value);
+ if (it != values.end() && *it == value)
+ return std::make_pair(true, true);
+ }
+ else
+ {
+ for (const auto& rItem : rItems)
+ {
+ // For speed don't bother comparing approximately here, usually there either
+ // will be an exact match or it wouldn't match anyway.
+ if (rItem.meType == ScQueryEntry::ByValue && value == rItem.mfVal)
+ {
+ return std::make_pair(true, true);
+ }
+ }
+ }
+ }
+ }
+ const svl::SharedString* cellSharedString = nullptr;
+ OUString cellString;
+ bool cellStringSet = false;
+ const bool bFastCompareByString = isFastCompareByString(rEntry);
+ if (rEntry.eOp == SC_EQUAL && rItems.size() >= 10 && bFastCompareByString)
+ {
+ // The same as above but for strings. Try to optimize the case when
+ // it's a svl::SharedString comparison. That happens when SC_EQUAL is used
+ // and simple matching is used, see compareByString()
+ if (!cellStringSet)
+ {
+ cellString = getCellString(aCell, nRow, rEntry.nField, &cellSharedString);
+ cellStringSet = true;
+ }
+ // Allow also checking ScQueryEntry::ByValue if the cell is not numeric,
+ // as in that case isQueryByNumeric() would be false and isQueryByString() would
+ // be true because of SC_EQUAL making isTextMatchOp() true.
+ bool compareByValue = !isQueryByValueForCell(aCell);
+ // For ScQueryEntry::ByString check that the cell is represented by a shared string,
+ // which means it's either a string cell or a formula error. This is not as
+ // generous as isQueryByString() but it should be enough and better be safe.
+ if (cellSharedString != nullptr)
+ {
+ if (rItems.size() >= 100)
+ {
+ // Sort, cache and binary search for the string in items.
+ // Since each SharedString is identified by pointer value,
+ // sorting by pointer value is enough.
+ if (mCachedSortedItemStrings.size() <= nEntryIndex)
+ {
+ mCachedSortedItemStrings.resize(nEntryIndex + 1);
+ auto& values = mCachedSortedItemStrings[nEntryIndex];
+ values.reserve(rItems.size());
+ for (const auto& rItem : rItems)
+ {
+ if (rItem.meType == ScQueryEntry::ByString
+ || (compareByValue && rItem.meType == ScQueryEntry::ByValue))
+ {
+ values.push_back(mrParam.bCaseSens
+ ? rItem.maString.getData()
+ : rItem.maString.getDataIgnoreCase());
+ }
+ }
+ std::sort(values.begin(), values.end());
+ }
+ auto& values = mCachedSortedItemStrings[nEntryIndex];
+ const rtl_uString* string = mrParam.bCaseSens
+ ? cellSharedString->getData()
+ : cellSharedString->getDataIgnoreCase();
+ auto it = std::lower_bound(values.begin(), values.end(), string);
+ if (it != values.end() && *it == string)
+ return std::make_pair(true, true);
+ }
+ else
+ {
+ for (const auto& rItem : rItems)
+ {
+ if ((rItem.meType == ScQueryEntry::ByString
+ || (compareByValue && rItem.meType == ScQueryEntry::ByValue))
+ && (mrParam.bCaseSens
+ ? cellSharedString->getData() == rItem.maString.getData()
+ : cellSharedString->getDataIgnoreCase()
+ == rItem.maString.getDataIgnoreCase()))
+ {
+ return std::make_pair(true, true);
+ }
+ }
+ }
+ }
+ }
+ // Generic handling.
+ for (const auto& rItem : rItems)
+ {
+ if (rItem.meType == ScQueryEntry::ByTextColor)
+ {
+ std::pair<bool, bool> aThisRes = compareByTextColor(nCol, nRow, rItem);
+ aRes.first |= aThisRes.first;
+ aRes.second |= aThisRes.second;
+ }
+ else if (rItem.meType == ScQueryEntry::ByBackgroundColor)
+ {
+ std::pair<bool, bool> aThisRes = compareByBackgroundColor(nCol, nRow, rItem);
+ aRes.first |= aThisRes.first;
+ aRes.second |= aThisRes.second;
+ }
+ else if (isQueryByValue(rEntry.eOp, rItem.meType, aCell))
+ {
+ std::pair<bool, bool> aThisRes = compareByValue(aCell, nCol, nRow, rEntry, rItem);
+ aRes.first |= aThisRes.first;
+ aRes.second |= aThisRes.second;
+ }
+ else if (isQueryByString(rEntry.eOp, rItem.meType, aCell))
+ {
+ if (!cellStringSet)
+ {
+ cellString = getCellString(aCell, nRow, rEntry.nField, &cellSharedString);
+ cellStringSet = true;
+ }
+ std::pair<bool, bool> aThisRes;
+ if (cellSharedString && bFastCompareByString) // fast
+ aThisRes = compareByString<true>(rEntry, rItem, cellSharedString, nullptr);
+ else if (cellSharedString)
+ aThisRes = compareByString(rEntry, rItem, cellSharedString, nullptr);
+ else
+ aThisRes = compareByString(rEntry, rItem, nullptr, &cellString);
+ aRes.first |= aThisRes.first;
+ aRes.second |= aThisRes.second;
+ }
+ else if (mrParam.mbRangeLookup)
+ {
+ std::pair<bool, bool> aThisRes = compareByRangeLookup(aCell, rEntry, rItem);
+ aRes.first |= aThisRes.first;
+ aRes.second |= aThisRes.second;
+ }
+
+ if (aRes.first && (aRes.second || mpTestEqualCondition == nullptr))
+ break;
+ }
+ return aRes;
+}
+
+bool ScQueryEvaluator::ValidQuery(SCROW nRow, const ScRefCellValue* pCell,
+ sc::TableColumnBlockPositionSet* pBlockPos)
+{
+ if (!mrParam.GetEntry(0).bDoQuery)
+ return true;
+
+ tools::Long nPos = -1;
+ ScQueryParam::const_iterator it, itBeg = mrParam.begin(), itEnd = mrParam.end();
+ for (it = itBeg; it != itEnd && it->bDoQuery; ++it)
+ {
+ const ScQueryEntry& rEntry = *it;
+
+ // Short-circuit the test at the end of the loop - if this is SC_AND
+ // and the previous value is false, this value will not be needed.
+ // Disable this if pbTestEqualCondition is present as that one may get set
+ // even if the result is false (that also means pTest doesn't need to be
+ // handled here).
+ if (rEntry.eConnect == SC_AND && mpTestEqualCondition == nullptr && nPos != -1
+ && !mpPasst[nPos])
+ {
+ continue;
+ }
+
+ SCCOL nCol = static_cast<SCCOL>(rEntry.nField);
+
+ // We can only handle one single direct query passed as a known pCell,
+ // subsequent queries have to obtain the cell.
+ ScRefCellValue aCell;
+ if (pCell && it == itBeg)
+ aCell = *pCell;
+ else if (pBlockPos)
+ { // hinted mdds access
+ aCell = const_cast<ScTable&>(mrTab).GetCellValue(
+ nCol, *pBlockPos->getBlockPosition(nCol), nRow);
+ }
+ else
+ aCell = mrTab.GetCellValue(nCol, nRow);
+
+ std::pair<bool, bool> aRes = processEntry(nRow, nCol, aCell, rEntry, it - itBeg);
+
+ if (nPos == -1)
+ {
+ nPos++;
+ mpPasst[nPos] = aRes.first;
+ mpTest[nPos] = aRes.second;
+ }
+ else
+ {
+ if (rEntry.eConnect == SC_AND)
+ {
+ mpPasst[nPos] = mpPasst[nPos] && aRes.first;
+ mpTest[nPos] = mpTest[nPos] && aRes.second;
+ }
+ else
+ {
+ nPos++;
+ mpPasst[nPos] = aRes.first;
+ mpTest[nPos] = aRes.second;
+ }
+ }
+ }
+
+ for (tools::Long j = 1; j <= nPos; j++)
+ {
+ mpPasst[0] = mpPasst[0] || mpPasst[j];
+ mpTest[0] = mpTest[0] || mpTest[j];
+ }
+
+ bool bRet = mpPasst[0];
+ if (mpTestEqualCondition)
+ *mpTestEqualCondition = mpTest[0];
+
+ return bRet;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sc/source/core/data/queryiter.cxx b/sc/source/core/data/queryiter.cxx
new file mode 100644
index 000000000..6718c1870
--- /dev/null
+++ b/sc/source/core/data/queryiter.cxx
@@ -0,0 +1,1476 @@
+/* -*- 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 <queryiter.hxx>
+
+#include <comphelper/flagguard.hxx>
+#include <o3tl/safeint.hxx>
+#include <svl/numformat.hxx>
+#include <svl/zforlist.hxx>
+
+#include <global.hxx>
+#include <dociter.hxx>
+#include <document.hxx>
+#include <table.hxx>
+#include <column.hxx>
+#include <formulacell.hxx>
+#include <attarray.hxx>
+#include <patattr.hxx>
+#include <docoptio.hxx>
+#include <cellform.hxx>
+#include <segmenttree.hxx>
+#include <progress.hxx>
+#include <queryparam.hxx>
+#include <queryentry.hxx>
+#include <globstr.hrc>
+#include <scresid.hxx>
+#include <cellvalue.hxx>
+#include <scmatrix.hxx>
+#include <rowheightcontext.hxx>
+#include <queryevaluator.hxx>
+#include <rangecache.hxx>
+#include <refdata.hxx>
+
+#include <o3tl/safeint.hxx>
+#include <tools/fract.hxx>
+#include <editeng/editobj.hxx>
+#include <svl/sharedstring.hxx>
+#include <unotools/collatorwrapper.hxx>
+#include <osl/diagnose.h>
+#include <sal/log.hxx>
+
+#include <algorithm>
+#include <limits>
+#include <vector>
+
+template< ScQueryCellIteratorAccess accessType, ScQueryCellIteratorType queryType >
+ScQueryCellIteratorBase< accessType, queryType >::ScQueryCellIteratorBase(ScDocument& rDocument,
+ ScInterpreterContext& rContext, SCTAB nTable, const ScQueryParam& rParam, bool bMod )
+ : AccessBase( rDocument, rContext, rParam )
+ , nStopOnMismatch( nStopOnMismatchDisabled )
+ , nTestEqualCondition( nTestEqualConditionDisabled )
+ , bAdvanceQuery( false )
+ , bIgnoreMismatchOnLeadingStrings( false )
+{
+ nTab = nTable;
+ nCol = maParam.nCol1;
+ nRow = maParam.nRow1;
+ SCSIZE i;
+ if (!bMod) // Or else it's already inserted
+ return;
+
+ SCSIZE nCount = maParam.GetEntryCount();
+ for (i = 0; (i < nCount) && (maParam.GetEntry(i).bDoQuery); ++i)
+ {
+ ScQueryEntry& rEntry = maParam.GetEntry(i);
+ ScQueryEntry::Item& rItem = rEntry.GetQueryItem();
+ sal_uInt32 nIndex = 0;
+ bool bNumber = mrContext.GetFormatTable()->IsNumberFormat(
+ rItem.maString.getString(), nIndex, rItem.mfVal);
+ rItem.meType = bNumber ? ScQueryEntry::ByValue : ScQueryEntry::ByString;
+ }
+}
+
+template< ScQueryCellIteratorAccess accessType, ScQueryCellIteratorType queryType >
+void ScQueryCellIteratorBase< accessType, queryType >::PerformQuery()
+{
+ assert(nTab < rDoc.GetTableCount() && "index out of bounds, FIX IT");
+ const ScQueryEntry& rEntry = maParam.GetEntry(0);
+ const ScQueryEntry::Item& rItem = rEntry.GetQueryItem();
+
+ const bool bSingleQueryItem = rEntry.GetQueryItems().size() == 1;
+ SCCOLROW nFirstQueryField = rEntry.nField;
+ bool bAllStringIgnore = bIgnoreMismatchOnLeadingStrings &&
+ rItem.meType != ScQueryEntry::ByString;
+ bool bFirstStringIgnore = bIgnoreMismatchOnLeadingStrings &&
+ !maParam.bHasHeader && rItem.meType == ScQueryEntry::ByString &&
+ ((maParam.bByRow && nRow == maParam.nRow1) ||
+ (!maParam.bByRow && nCol == maParam.nCol1));
+ bool bTestEqualCondition = false;
+ ScQueryEvaluator queryEvaluator(rDoc, *rDoc.maTabs[nTab], maParam, &mrContext,
+ (nTestEqualCondition ? &bTestEqualCondition : nullptr));
+ if( queryType == ScQueryCellIteratorType::CountIf )
+ {
+ // These are not used for COUNTIF, so should not be set, make the compiler
+ // explicitly aware of it so that the relevant parts are optimized away.
+ assert( !bAllStringIgnore );
+ assert( !bIgnoreMismatchOnLeadingStrings );
+ assert( nStopOnMismatch == nStopOnMismatchDisabled );
+ assert( nTestEqualCondition == nTestEqualConditionDisabled );
+ bAllStringIgnore = false;
+ bIgnoreMismatchOnLeadingStrings = false;
+ nStopOnMismatch = nStopOnMismatchDisabled;
+ nTestEqualCondition = nTestEqualConditionDisabled;
+ // This one is always set.
+ assert( bAdvanceQuery );
+ bAdvanceQuery = true;
+ }
+
+ ScColumn* pCol = &(rDoc.maTabs[nTab])->aCol[nCol];
+ while (true)
+ {
+ bool bNextColumn = maCurPos.first == pCol->maCells.end();
+ if (!bNextColumn)
+ {
+ if (nRow > maParam.nRow2)
+ bNextColumn = true;
+ }
+
+ if (bNextColumn)
+ {
+ do
+ {
+ ++nCol;
+ if (nCol > maParam.nCol2 || nCol >= rDoc.maTabs[nTab]->GetAllocatedColumnsCount())
+ return;
+ if ( bAdvanceQuery )
+ {
+ AdvanceQueryParamEntryField();
+ nFirstQueryField = rEntry.nField;
+ }
+ pCol = &(rDoc.maTabs[nTab])->aCol[nCol];
+ }
+ while (!rItem.mbMatchEmpty && pCol->IsEmptyData());
+
+ InitPos();
+
+ bFirstStringIgnore = bIgnoreMismatchOnLeadingStrings &&
+ !maParam.bHasHeader && rItem.meType == ScQueryEntry::ByString &&
+ maParam.bByRow;
+ }
+
+ if (maCurPos.first->type == sc::element_type_empty)
+ {
+ if (rItem.mbMatchEmpty && bSingleQueryItem)
+ {
+ // This shortcut, instead of determining if any SC_OR query
+ // exists or this query is SC_AND'ed (which wouldn't make
+ // sense, but..) and evaluating them in ValidQuery(), is
+ // possible only because the interpreter is the only caller
+ // that sets mbMatchEmpty and there is only one item in those
+ // cases.
+ // XXX this would have to be reworked if other filters used it
+ // in different manners and evaluation would have to be done in
+ // ValidQuery().
+ if(HandleItemFound())
+ return;
+ IncPos();
+ continue;
+ }
+ else
+ {
+ IncBlock();
+ continue;
+ }
+ }
+
+ ScRefCellValue aCell = sc::toRefCell(maCurPos.first, maCurPos.second);
+
+ if (bAllStringIgnore && aCell.hasString())
+ IncPos();
+ else
+ {
+ if ( queryEvaluator.ValidQuery( nRow,
+ (nCol == static_cast<SCCOL>(nFirstQueryField) ? &aCell : nullptr)))
+ {
+ if ( nTestEqualCondition && bTestEqualCondition )
+ nTestEqualCondition |= nTestEqualConditionMatched;
+ if ( aCell.isEmpty())
+ return;
+ if( HandleItemFound())
+ return;
+ IncPos();
+ continue;
+ }
+ else if ( nStopOnMismatch )
+ {
+ // Yes, even a mismatch may have a fulfilled equal
+ // condition if regular expressions were involved and
+ // SC_LESS_EQUAL or SC_GREATER_EQUAL were queried.
+ if ( nTestEqualCondition && bTestEqualCondition )
+ {
+ nTestEqualCondition |= nTestEqualConditionMatched;
+ nStopOnMismatch |= nStopOnMismatchOccurred;
+ return;
+ }
+ bool bStop;
+ if (bFirstStringIgnore)
+ {
+ if (aCell.hasString())
+ {
+ IncPos();
+ bStop = false;
+ }
+ else
+ bStop = true;
+ }
+ else
+ bStop = true;
+ if (bStop)
+ {
+ nStopOnMismatch |= nStopOnMismatchOccurred;
+ return;
+ }
+ }
+ else
+ IncPos();
+ }
+ bFirstStringIgnore = false;
+ }
+}
+
+template< ScQueryCellIteratorAccess accessType, ScQueryCellIteratorType queryType >
+void ScQueryCellIteratorBase< accessType, queryType >::InitPos()
+{
+ if constexpr( accessType != ScQueryCellIteratorAccess::SortedCache )
+ AccessBase::InitPos();
+ else
+ {
+ // This should be all in AccessBase::InitPos(), but that one can't call
+ // BinarySearch(), so do it this way instead.
+ AccessBase::InitPosStart();
+ ScQueryOp& op = maParam.GetEntry(0).eOp;
+ SCROW beforeRow = -1;
+ SCROW lastRow = -1;
+ if( op == SC_EQUAL )
+ {
+ if( BinarySearch( nCol ))
+ {
+ // BinarySearch() searches for the last item that matches. Now we
+ // also need to find the first item where to start. Find the last
+ // non-matching position using SC_LESS and the start position
+ // is the one after it.
+ lastRow = nRow;
+ ScQueryOp saveOp = op;
+ op = SC_LESS;
+ if( BinarySearch( nCol, true ))
+ beforeRow = nRow;
+ // If BinarySearch() returns false, there was no match, which means
+ // there's no value smaller. In that case BinarySearch() has set
+ // the position to the first row in the range.
+ op = saveOp; // back to SC_EQUAL
+ }
+ else if( maParam.GetEntry(0).GetQueryItem().mbMatchEmpty
+ && rDoc.IsEmptyData(nCol, maParam.nRow1, nCol, maParam.nRow2, nTab))
+ {
+ // BinarySearch() returns false in case it's all empty data,
+ // handle that specially.
+ beforeRow = -1;
+ lastRow = maParam.nRow2;
+ }
+ }
+ else
+ { // The range is from the start up to and including the last matching.
+ if( BinarySearch( nCol ))
+ lastRow = nRow;
+ }
+ AccessBase::InitPosFinish( beforeRow, lastRow );
+ }
+}
+
+template< ScQueryCellIteratorAccess accessType, ScQueryCellIteratorType queryType >
+void ScQueryCellIteratorBase< accessType, queryType >::AdvanceQueryParamEntryField()
+{
+ SCSIZE nEntries = maParam.GetEntryCount();
+ for ( SCSIZE j = 0; j < nEntries; j++ )
+ {
+ ScQueryEntry& rEntry = maParam.GetEntry( j );
+ if ( rEntry.bDoQuery )
+ {
+ if ( rEntry.nField < rDoc.MaxCol() )
+ rEntry.nField++;
+ else
+ {
+ assert(!"AdvanceQueryParamEntryField: ++rEntry.nField > MAXCOL");
+ }
+ }
+ else
+ break; // for
+ }
+}
+
+namespace {
+
+template<typename Iter>
+void incBlock(std::pair<Iter, size_t>& rPos)
+{
+ // Move to the next block.
+ ++rPos.first;
+ rPos.second = 0;
+}
+
+template<typename Iter>
+void decBlock(std::pair<Iter, size_t>& rPos)
+{
+ // Move to the last element of the previous block.
+ --rPos.first;
+ rPos.second = rPos.first->size - 1;
+}
+
+}
+
+template< ScQueryCellIteratorAccess accessType, ScQueryCellIteratorType queryType >
+bool ScQueryCellIteratorBase< accessType, queryType >::BinarySearch( SCCOL col, bool forEqual )
+{
+ assert(maParam.GetEntry(0).bDoQuery && !maParam.GetEntry(1).bDoQuery
+ && maParam.GetEntry(0).GetQueryItems().size() == 1 );
+ assert(maParam.eSearchType == utl::SearchParam::SearchType::Normal);
+ assert(maParam.GetEntry(0).GetQueryItem().meType == ScQueryEntry::ByString
+ || maParam.GetEntry(0).GetQueryItem().meType == ScQueryEntry::ByValue);
+ assert(maParam.bByRow);
+ assert(maParam.GetEntry(0).eOp == SC_LESS || maParam.GetEntry(0).eOp == SC_LESS_EQUAL
+ || maParam.GetEntry(0).eOp == SC_GREATER || maParam.GetEntry(0).eOp == SC_GREATER_EQUAL
+ || maParam.GetEntry(0).eOp == SC_EQUAL);
+
+ // TODO: This will be extremely slow with mdds::multi_type_vector.
+
+ assert(nTab < rDoc.GetTableCount() && "index out of bounds, FIX IT");
+ nCol = col;
+ nRow = maParam.nRow1;
+
+ if (nCol >= rDoc.maTabs[nTab]->GetAllocatedColumnsCount())
+ return false;
+
+ ScColumn* pCol = &(rDoc.maTabs[nTab])->aCol[nCol];
+ if (pCol->IsEmptyData())
+ return false;
+
+ CollatorWrapper& rCollator = ScGlobal::GetCollator(maParam.bCaseSens);
+ SvNumberFormatter& rFormatter = *(mrContext.GetFormatTable());
+ const ScQueryEntry& rEntry = maParam.GetEntry(0);
+ const ScQueryEntry::Item& rItem = rEntry.GetQueryItem();
+ bool bAscending = rEntry.eOp == SC_LESS || rEntry.eOp == SC_LESS_EQUAL || rEntry.eOp == SC_EQUAL;
+ bool bByString = rItem.meType == ScQueryEntry::ByString;
+ bool bForceStr = bByString && ( rEntry.eOp == SC_EQUAL || forEqual );
+ bool bAllStringIgnore = bIgnoreMismatchOnLeadingStrings && !bByString;
+ bool bFirstStringIgnore = bIgnoreMismatchOnLeadingStrings &&
+ !maParam.bHasHeader && bByString;
+
+ if (maParam.bHasHeader)
+ ++nRow;
+
+ if (bFirstStringIgnore)
+ {
+ sc::CellStoreType::const_position_type aPos = pCol->maCells.position(nRow);
+ if (aPos.first->type == sc::element_type_string || aPos.first->type == sc::element_type_edittext)
+ {
+ ScRefCellValue aCell = sc::toRefCell(aPos.first, aPos.second);
+ sal_uInt32 nFormat = pCol->GetNumberFormat(mrContext, nRow);
+ OUString aCellStr = ScCellFormat::GetInputString(aCell, nFormat, rFormatter, rDoc);
+ sal_Int32 nTmp = rCollator.compareString(aCellStr, rEntry.GetQueryItem().maString.getString());
+ if ((rEntry.eOp == SC_LESS_EQUAL && nTmp > 0) ||
+ (rEntry.eOp == SC_GREATER_EQUAL && nTmp < 0) ||
+ (rEntry.eOp == SC_EQUAL && nTmp != 0) ||
+ (rEntry.eOp == SC_LESS && nTmp >= 0) ||
+ (rEntry.eOp == SC_GREATER && nTmp <= 0))
+ ++nRow;
+ }
+ }
+
+ // Skip leading empty block, if any.
+ sc::CellStoreType::const_position_type startPos = pCol->maCells.position(nRow);
+ if (startPos.first->type == sc::element_type_empty)
+ incBlock(startPos);
+ if(bAllStringIgnore)
+ {
+ // Skip all leading string or empty blocks.
+ while (startPos.first != pCol->maCells.end()
+ && (startPos.first->type == sc::element_type_string ||
+ startPos.first->type == sc::element_type_edittext ||
+ startPos.first->type == sc::element_type_empty))
+ {
+ incBlock(startPos);
+ }
+ }
+ if(startPos.first == pCol->maCells.end())
+ return false;
+ nRow = startPos.first->position + startPos.second;
+ if (nRow > maParam.nRow2)
+ return false;
+
+ auto aIndexer = MakeBinarySearchIndexer(pCol->maCells, nRow, maParam.nRow2);
+ if (!aIndexer.isValid())
+ return false;
+
+ size_t nLo = aIndexer.getLowIndex();
+ size_t nHi = aIndexer.getHighIndex();
+ BinarySearchCellType aCellData;
+
+ // Bookkeeping values for breaking up the binary search in case the data
+ // range isn't strictly sorted.
+ size_t nLastInRange = nLo;
+ double fLastInRangeValue = bAscending ?
+ -(::std::numeric_limits<double>::max()) :
+ ::std::numeric_limits<double>::max();
+ OUString aLastInRangeString;
+ if (!bAscending)
+ aLastInRangeString = OUString(u'\xFFFF');
+
+ aCellData = aIndexer.getCell(nLastInRange);
+ ScRefCellValue aCell = aCellData.first;
+ if (bForceStr || aCell.hasString())
+ {
+ sal_uInt32 nFormat = pCol->GetNumberFormat(mrContext, aCellData.second);
+ OUString aStr = ScCellFormat::GetInputString(aCell, nFormat, rFormatter, rDoc);
+ aLastInRangeString = aStr;
+ }
+ else
+ {
+ switch (aCell.meType)
+ {
+ case CELLTYPE_VALUE :
+ fLastInRangeValue = aCell.mfValue;
+ break;
+ case CELLTYPE_FORMULA :
+ fLastInRangeValue = aCell.mpFormula->GetValue();
+ break;
+ default:
+ {
+ // added to avoid warnings
+ }
+ }
+ }
+
+ sal_Int32 nRes = 0;
+ std::optional<size_t> found;
+ bool bDone = false;
+ bool orderBroken = false;
+ while (nLo <= nHi && !bDone)
+ {
+ size_t nMid = (nLo+nHi)/2;
+ size_t i = nMid;
+
+ aCellData = aIndexer.getCell(i);
+ aCell = aCellData.first;
+ bool bStr = bForceStr || aCell.hasString();
+ nRes = 0;
+
+ // compares are content<query:-1, content>query:1
+ // Cell value comparison similar to ScTable::ValidQuery()
+ if (!bStr && !bByString)
+ {
+ double nCellVal;
+ switch (aCell.meType)
+ {
+ case CELLTYPE_VALUE :
+ case CELLTYPE_FORMULA :
+ nCellVal = aCell.getValue();
+ break;
+ default:
+ nCellVal = 0.0;
+ }
+ if ((nCellVal < rItem.mfVal) && !::rtl::math::approxEqual(
+ nCellVal, rItem.mfVal))
+ {
+ nRes = -1;
+ if (bAscending)
+ {
+ if (fLastInRangeValue <= nCellVal)
+ {
+ fLastInRangeValue = nCellVal;
+ nLastInRange = i;
+ }
+ else if (fLastInRangeValue >= nCellVal)
+ {
+ // not strictly sorted, continue with GetThis()
+ orderBroken = true;
+ bDone = true;
+ }
+ }
+ }
+ else if ((nCellVal > rItem.mfVal) && !::rtl::math::approxEqual(
+ nCellVal, rItem.mfVal))
+ {
+ nRes = 1;
+ if (!bAscending)
+ {
+ if (fLastInRangeValue >= nCellVal)
+ {
+ fLastInRangeValue = nCellVal;
+ nLastInRange = i;
+ }
+ else if (fLastInRangeValue <= nCellVal)
+ {
+ // not strictly sorted, continue with GetThis()
+ orderBroken = true;
+ bDone = true;
+ }
+ }
+ }
+ }
+ else if (bStr && bByString)
+ {
+ sal_uInt32 nFormat = pCol->GetNumberFormat(mrContext, aCellData.second);
+ OUString aCellStr = ScCellFormat::GetInputString(aCell, nFormat, rFormatter, rDoc);
+
+ nRes = rCollator.compareString(aCellStr, rEntry.GetQueryItem().maString.getString());
+ if (nRes < 0 && bAscending)
+ {
+ sal_Int32 nTmp = rCollator.compareString( aLastInRangeString,
+ aCellStr);
+ if (nTmp <= 0)
+ {
+ aLastInRangeString = aCellStr;
+ nLastInRange = i;
+ }
+ else if (nTmp > 0)
+ {
+ // not strictly sorted, continue with GetThis()
+ orderBroken = true;
+ bDone = true;
+ }
+ }
+ else if (nRes > 0 && !bAscending)
+ {
+ sal_Int32 nTmp = rCollator.compareString( aLastInRangeString,
+ aCellStr);
+ if (nTmp >= 0)
+ {
+ aLastInRangeString = aCellStr;
+ nLastInRange = i;
+ }
+ else if (nTmp < 0)
+ {
+ // not strictly sorted, continue with GetThis()
+ orderBroken = true;
+ bDone = true;
+ }
+ }
+ }
+ else if (!bStr && bByString)
+ {
+ nRes = -1; // numeric < string
+ if (bAscending)
+ nLastInRange = i;
+ }
+ else // if (bStr && !bByString)
+ {
+ nRes = 1; // string > numeric
+ if (!bAscending)
+ nLastInRange = i;
+ }
+ if (nRes < 0)
+ {
+ if (bAscending)
+ nLo = nMid + 1;
+ else // assumed to be SC_GREATER_EQUAL
+ {
+ if (nMid > 0)
+ nHi = nMid - 1;
+ else
+ bDone = true;
+ }
+ }
+ else if (nRes > 0)
+ {
+ if (bAscending)
+ {
+ if (nMid > 0)
+ nHi = nMid - 1;
+ else
+ bDone = true;
+ }
+ else // assumed to be SC_GREATER_EQUAL
+ nLo = nMid + 1;
+ }
+ else
+ {
+ if(rEntry.eOp == SC_LESS_EQUAL || rEntry.eOp == SC_GREATER_EQUAL || rEntry.eOp == SC_EQUAL)
+ {
+ found = i;
+ nLastInRange = i;
+ // But keep searching to find the last matching one.
+ nLo = nMid + 1;
+ }
+ else if (bAscending)
+ {
+ if (nMid > 0)
+ nHi = nMid - 1;
+ else
+ bDone = true;
+ }
+ else
+ {
+ if (nMid > 0)
+ nHi = nMid - 1;
+ else
+ bDone = true;
+ }
+ }
+ }
+
+ bool isInRange;
+ if (orderBroken)
+ {
+ // Reset position to the first row in range and force caller
+ // to search from start.
+ nLo = aIndexer.getLowIndex();
+ isInRange = false;
+ }
+ else if (found)
+ {
+ nLo = *found;
+ isInRange = true;
+ }
+ else
+ {
+ // Not nothing was found and the search position is at the start,
+ // then the possible match would need to be before the data range.
+ // In that case return false to force the caller to search from the start
+ // and detect this.
+ isInRange = nLo != aIndexer.getLowIndex();
+ // If nothing was found, that is either because there is no value
+ // that would match exactly, or the data range is not properly sorted
+ // and we failed to detect (doing so reliably would require a linear scan).
+ // Set the position to the last one that was in matching range (i.e. before
+ // where the exact match would be), and leave sorting it out to GetThis()
+ // or whatever the caller uses.
+ nLo = nLastInRange;
+ }
+
+ aCellData = aIndexer.getCell(nLo);
+ if (nLo <= nHi && aCellData.second <= maParam.nRow2)
+ {
+ nRow = aCellData.second;
+ maCurPos = aIndexer.getPosition(nLo);
+ return isInRange;
+ }
+ else
+ {
+ nRow = maParam.nRow2 + 1;
+ // Set current position to the last possible row.
+ maCurPos.first = pCol->maCells.end();
+ --maCurPos.first;
+ maCurPos.second = maCurPos.first->size - 1;
+ return false;
+ }
+}
+
+
+template< ScQueryCellIteratorAccess accessType >
+bool ScQueryCellIterator< accessType >::FindEqualOrSortedLastInRange( SCCOL& nFoundCol,
+ SCROW& nFoundRow )
+{
+ // Set and automatically reset mpParam->mbRangeLookup when returning.
+ comphelper::FlagRestorationGuard aRangeLookupResetter( maParam.mbRangeLookup, true );
+
+ nFoundCol = rDoc.MaxCol()+1;
+ nFoundRow = rDoc.MaxRow()+1;
+ SetStopOnMismatch( true ); // assume sorted keys
+ SetTestEqualCondition( true );
+ bIgnoreMismatchOnLeadingStrings = true;
+ bool bLiteral = maParam.eSearchType == utl::SearchParam::SearchType::Normal &&
+ maParam.GetEntry(0).GetQueryItem().meType == ScQueryEntry::ByString;
+ bool bBinary = maParam.bByRow &&
+ (bLiteral || maParam.GetEntry(0).GetQueryItem().meType == ScQueryEntry::ByValue) &&
+ (maParam.GetEntry(0).eOp == SC_LESS_EQUAL || maParam.GetEntry(0).eOp == SC_GREATER_EQUAL);
+ bool bFound = false;
+ if (bBinary)
+ {
+ if (BinarySearch( maParam.nCol1 ))
+ {
+ // BinarySearch() already positions correctly and only needs real
+ // query comparisons afterwards, skip the verification check below.
+ maParam.mbRangeLookup = false;
+ bFound = GetThis();
+ }
+ else // Not sorted properly, or before the range (in which case GetFirst() will be simple).
+ bFound = GetFirst();
+ }
+ else
+ {
+ bFound = GetFirst();
+ }
+ if (bFound)
+ {
+ // First equal entry or last smaller than (greater than) entry.
+ PositionType aPosSave;
+ bool bNext = false;
+ do
+ {
+ nFoundCol = GetCol();
+ nFoundRow = GetRow();
+ aPosSave = maCurPos;
+ if (IsEqualConditionFulfilled())
+ break;
+ bNext = GetNext();
+ }
+ while (bNext);
+
+ // There may be no pNext but equal condition fulfilled if regular
+ // expressions are involved. Keep the found entry and proceed.
+ if (!bNext && !IsEqualConditionFulfilled())
+ {
+ // Step back to last in range and adjust position markers for
+ // GetNumberFormat() or similar.
+ SCCOL nColDiff = nCol - nFoundCol;
+ nCol = nFoundCol;
+ nRow = nFoundRow;
+ maCurPos = aPosSave;
+ if (maParam.mbRangeLookup)
+ {
+ // Verify that the found entry does not only fulfill the range
+ // lookup but also the real query, i.e. not numeric was found
+ // if query is ByString and vice versa.
+ maParam.mbRangeLookup = false;
+ // Step back the last field advance if GetNext() did one.
+ if (bAdvanceQuery && nColDiff)
+ {
+ SCSIZE nEntries = maParam.GetEntryCount();
+ for (SCSIZE j=0; j < nEntries; ++j)
+ {
+ ScQueryEntry& rEntry = maParam.GetEntry( j );
+ if (rEntry.bDoQuery)
+ {
+ if (rEntry.nField - nColDiff >= 0)
+ rEntry.nField -= nColDiff;
+ else
+ {
+ assert(!"FindEqualOrSortedLastInRange: rEntry.nField -= nColDiff < 0");
+ }
+ }
+ else
+ break; // for
+ }
+ }
+ // Check it.
+ if (!GetThis())
+ {
+ nFoundCol = rDoc.MaxCol()+1;
+ nFoundRow = rDoc.MaxRow()+1;
+ }
+ }
+ }
+ }
+ if ( IsEqualConditionFulfilled() )
+ {
+ // Position on last equal entry.
+ SCSIZE nEntries = maParam.GetEntryCount();
+ for ( SCSIZE j = 0; j < nEntries; j++ )
+ {
+ ScQueryEntry& rEntry = maParam.GetEntry( j );
+ if ( rEntry.bDoQuery )
+ {
+ switch ( rEntry.eOp )
+ {
+ case SC_LESS_EQUAL :
+ case SC_GREATER_EQUAL :
+ rEntry.eOp = SC_EQUAL;
+ break;
+ default:
+ {
+ // added to avoid warnings
+ }
+ }
+ }
+ else
+ break; // for
+ }
+ PositionType aPosSave;
+ bIgnoreMismatchOnLeadingStrings = false;
+ SetTestEqualCondition( false );
+ do
+ {
+ nFoundCol = GetCol();
+ nFoundRow = GetRow();
+ aPosSave = maCurPos;
+ } while (GetNext());
+
+ // Step back conditions are the same as above
+ nCol = nFoundCol;
+ nRow = nFoundRow;
+ maCurPos = aPosSave;
+ return true;
+ }
+ if ( (maParam.eSearchType != utl::SearchParam::SearchType::Normal) &&
+ StoppedOnMismatch() )
+ {
+ // Assume found entry to be the last value less than respectively
+ // greater than the query. But keep on searching for an equal match.
+ SCSIZE nEntries = maParam.GetEntryCount();
+ for ( SCSIZE j = 0; j < nEntries; j++ )
+ {
+ ScQueryEntry& rEntry = maParam.GetEntry( j );
+ if ( rEntry.bDoQuery )
+ {
+ switch ( rEntry.eOp )
+ {
+ case SC_LESS_EQUAL :
+ case SC_GREATER_EQUAL :
+ rEntry.eOp = SC_EQUAL;
+ break;
+ default:
+ {
+ // added to avoid warnings
+ }
+ }
+ }
+ else
+ break; // for
+ }
+ SetStopOnMismatch( false );
+ SetTestEqualCondition( false );
+ if (GetNext())
+ {
+ // Last of a consecutive area, avoid searching the entire parameter
+ // range as it is a real performance bottleneck in case of regular
+ // expressions.
+ PositionType aPosSave;
+ do
+ {
+ nFoundCol = GetCol();
+ nFoundRow = GetRow();
+ aPosSave = maCurPos;
+ SetStopOnMismatch( true );
+ } while (GetNext());
+ nCol = nFoundCol;
+ nRow = nFoundRow;
+ maCurPos = aPosSave;
+ }
+ }
+ return (nFoundCol <= rDoc.MaxCol()) && (nFoundRow <= rDoc.MaxRow());
+}
+
+// Direct linear cell access using mdds.
+
+ScQueryCellIteratorAccessSpecific< ScQueryCellIteratorAccess::Direct >
+ ::ScQueryCellIteratorAccessSpecific( ScDocument& rDocument,
+ ScInterpreterContext& rContext, const ScQueryParam& rParam )
+ : maParam( rParam )
+ , rDoc( rDocument )
+ , mrContext( rContext )
+{
+ // coverity[uninit_member] - this just contains data, subclass will initialize some of it
+}
+
+void ScQueryCellIteratorAccessSpecific< ScQueryCellIteratorAccess::Direct >::InitPos()
+{
+ nRow = maParam.nRow1;
+ if (maParam.bHasHeader && maParam.bByRow)
+ ++nRow;
+ const ScColumn& rCol = rDoc.maTabs[nTab]->CreateColumnIfNotExists(nCol);
+ maCurPos = rCol.maCells.position(nRow);
+}
+
+void ScQueryCellIteratorAccessSpecific< ScQueryCellIteratorAccess::Direct >::IncPos()
+{
+ if (maCurPos.second + 1 < maCurPos.first->size)
+ {
+ // Move within the same block.
+ ++maCurPos.second;
+ ++nRow;
+ }
+ else
+ // Move to the next block.
+ IncBlock();
+}
+
+void ScQueryCellIteratorAccessSpecific< ScQueryCellIteratorAccess::Direct >::IncBlock()
+{
+ ++maCurPos.first;
+ maCurPos.second = 0;
+
+ nRow = maCurPos.first->position;
+}
+
+/**
+ * This class sequentially indexes non-empty cells in order, from the top of
+ * the block where the start row position is, to the bottom of the block
+ * where the end row position is. It skips all empty blocks that may be
+ * present in between.
+ *
+ * The index value is an offset from the first element of the first block
+ * disregarding all empty cell blocks.
+ */
+class ScQueryCellIteratorAccessSpecific< ScQueryCellIteratorAccess::Direct >::NonEmptyCellIndexer
+{
+ typedef std::map<size_t, sc::CellStoreType::const_iterator> BlockMapType;
+
+ BlockMapType maBlockMap;
+
+ const sc::CellStoreType& mrCells;
+
+ size_t mnLowIndex;
+ size_t mnHighIndex;
+
+ bool mbValid;
+
+public:
+ /**
+ * @param rCells cell storage container
+ * @param nStartRow logical start row position
+ * @param nEndRow logical end row position, inclusive.
+ */
+ NonEmptyCellIndexer(
+ const sc::CellStoreType& rCells, SCROW nStartRow, SCROW nEndRow) :
+ mrCells(rCells), mnLowIndex(0), mnHighIndex(0), mbValid(true)
+ {
+ // Find the low position.
+
+ sc::CellStoreType::const_position_type aLoPos = mrCells.position(nStartRow);
+ assert(aLoPos.first->type != sc::element_type_empty);
+ assert(aLoPos.first != rCells.end());
+
+ SCROW nFirstRow = aLoPos.first->position;
+ SCROW nLastRow = aLoPos.first->position + aLoPos.first->size - 1;
+
+ if (nFirstRow > nEndRow)
+ {
+ // Both start and end row positions are within the leading skipped
+ // blocks.
+ mbValid = false;
+ return;
+ }
+
+ // Calculate the index of the low position.
+ if (nFirstRow < nStartRow)
+ mnLowIndex = nStartRow - nFirstRow;
+ else
+ {
+ // Start row is within the skipped block(s). Set it to the first
+ // element of the low block.
+ mnLowIndex = 0;
+ }
+
+ if (nEndRow < nLastRow)
+ {
+ assert(nEndRow >= nFirstRow);
+ mnHighIndex = nEndRow - nFirstRow;
+
+ maBlockMap.emplace(aLoPos.first->size, aLoPos.first);
+ return;
+ }
+
+ // Find the high position.
+
+ sc::CellStoreType::const_position_type aHiPos = mrCells.position(aLoPos.first, nEndRow);
+ if (aHiPos.first->type == sc::element_type_empty)
+ {
+ // Move to the last position of the previous block.
+ decBlock(aHiPos);
+
+ // Check the row position of the end of the previous block, and make sure it's valid.
+ SCROW nBlockEndRow = aHiPos.first->position + aHiPos.first->size - 1;
+ if (nBlockEndRow < nStartRow)
+ {
+ mbValid = false;
+ return;
+ }
+ }
+
+ // Tag the start and end blocks, and all blocks in between in order
+ // but skip all empty blocks.
+
+ size_t nPos = 0;
+ sc::CellStoreType::const_iterator itBlk = aLoPos.first;
+ while (itBlk != aHiPos.first)
+ {
+ if (itBlk->type == sc::element_type_empty)
+ {
+ ++itBlk;
+ continue;
+ }
+
+ nPos += itBlk->size;
+ maBlockMap.emplace(nPos, itBlk);
+ ++itBlk;
+
+ if (itBlk->type == sc::element_type_empty)
+ ++itBlk;
+
+ assert(itBlk != mrCells.end());
+ }
+
+ assert(itBlk == aHiPos.first);
+ nPos += itBlk->size;
+ maBlockMap.emplace(nPos, itBlk);
+
+ // Calculate the high index.
+ BlockMapType::const_reverse_iterator ri = maBlockMap.rbegin();
+ mnHighIndex = ri->first;
+ mnHighIndex -= ri->second->size;
+ mnHighIndex += aHiPos.second;
+ }
+
+ sc::CellStoreType::const_position_type getPosition( size_t nIndex ) const
+ {
+ assert(mbValid);
+ assert(mnLowIndex <= nIndex);
+ assert(nIndex <= mnHighIndex);
+
+ sc::CellStoreType::const_position_type aRet(mrCells.end(), 0);
+
+ BlockMapType::const_iterator it = maBlockMap.upper_bound(nIndex);
+ if (it == maBlockMap.end())
+ return aRet;
+
+ sc::CellStoreType::const_iterator itBlk = it->second;
+ size_t nBlkIndex = it->first - itBlk->size; // index of the first element of the block.
+ assert(nBlkIndex <= nIndex);
+ assert(nIndex < it->first);
+
+ size_t nOffset = nIndex - nBlkIndex;
+ aRet.first = itBlk;
+ aRet.second = nOffset;
+ return aRet;
+ }
+
+ BinarySearchCellType getCell( size_t nIndex ) const
+ {
+ BinarySearchCellType aRet;
+ aRet.second = -1;
+
+ sc::CellStoreType::const_position_type aPos = getPosition(nIndex);
+ if (aPos.first == mrCells.end())
+ return aRet;
+
+ aRet.first = sc::toRefCell(aPos.first, aPos.second);
+ aRet.second = aPos.first->position + aPos.second;
+ return aRet;
+ }
+
+ size_t getLowIndex() const { return mnLowIndex; }
+
+ size_t getHighIndex() const { return mnHighIndex; }
+
+ bool isValid() const { return mbValid; }
+};
+
+ScQueryCellIteratorAccessSpecific< ScQueryCellIteratorAccess::Direct >::NonEmptyCellIndexer
+ScQueryCellIteratorAccessSpecific< ScQueryCellIteratorAccess::Direct >::MakeBinarySearchIndexer(
+ const sc::CellStoreType& rCells, SCROW nStartRow, SCROW nEndRow )
+{
+ return NonEmptyCellIndexer(rCells, nStartRow, nEndRow);
+}
+
+// Sorted access using ScSortedRangeCache.
+
+ScQueryCellIteratorAccessSpecific< ScQueryCellIteratorAccess::SortedCache >
+ ::ScQueryCellIteratorAccessSpecific( ScDocument& rDocument,
+ ScInterpreterContext& rContext, const ScQueryParam& rParam )
+ : maParam( rParam )
+ , rDoc( rDocument )
+ , mrContext( rContext )
+{
+ // coverity[uninit_member] - this just contains data, subclass will initialize some of it
+}
+
+void ScQueryCellIteratorAccessSpecific< ScQueryCellIteratorAccess::SortedCache >::SetSortedRangeCache(
+ const ScSortedRangeCache& cache)
+{
+ sortedCache = &cache;
+}
+
+// The idea in iterating using the sorted cache is that the iteration is instead done
+// over indexes of the sorted cache (which is a stable sort of the cell contents) in the range
+// that fits the query condition and then that is mapped to rows. This will result in iterating
+// over only matching rows in their sorted order (and for equal rows in their row order).
+void ScQueryCellIteratorAccessSpecific< ScQueryCellIteratorAccess::SortedCache >::InitPosStart()
+{
+ ScRange aSortedRangeRange( nCol, maParam.nRow1, nTab, nCol, maParam.nRow2, nTab );
+ // We want all matching values first in the sort order,
+ SetSortedRangeCache( rDoc.GetSortedRangeCache( aSortedRangeRange, maParam, &mrContext ));
+ // InitPosFinish() needs to be called after this, ScQueryCellIteratorBase::InitPos()
+ // will handle that
+}
+
+void ScQueryCellIteratorAccessSpecific< ScQueryCellIteratorAccess::SortedCache >::InitPosFinish(
+ SCROW beforeRow, SCROW lastRow )
+{
+ pColumn = &rDoc.maTabs[nTab]->CreateColumnIfNotExists(nCol);
+ if(lastRow >= 0)
+ {
+ sortedCachePos = beforeRow >= 0 ? sortedCache->indexForRow(beforeRow) + 1 : 0;
+ sortedCachePosLast = sortedCache->indexForRow(lastRow);
+ if(sortedCachePos <= sortedCachePosLast)
+ {
+ nRow = sortedCache->rowForIndex(sortedCachePos);
+ maCurPos = pColumn->maCells.position(nRow);
+ return;
+ }
+ }
+ // No rows, set to end.
+ sortedCachePos = sortedCachePosLast = 0;
+ maCurPos.first = pColumn->maCells.end();
+ maCurPos.second = 0;
+}
+
+template<bool fast>
+bool ScQueryCellIteratorAccessSpecific< ScQueryCellIteratorAccess::SortedCache >::IncPosImpl()
+{
+ if(sortedCachePos < sortedCachePosLast)
+ {
+ ++sortedCachePos;
+ nRow = sortedCache->rowForIndex(sortedCachePos);
+#ifndef DBG_UTIL
+ if constexpr (!fast)
+#endif
+ {
+ // Avoid mdds position() call if row is in the same block.
+ if(maCurPos.first != pColumn->maCells.end() && o3tl::make_unsigned(nRow) >= maCurPos.first->position
+ && o3tl::make_unsigned(nRow) < maCurPos.first->position + maCurPos.first->size)
+ maCurPos.second = nRow - maCurPos.first->position;
+ else
+ maCurPos = pColumn->maCells.position(nRow);
+ }
+ return true;
+ }
+ else
+ {
+ // This will make PerformQuery() go to next column.
+ // Necessary even in fast mode, as GetNext() will call GetThis() in this case.
+ maCurPos.first = pColumn->maCells.end();
+ maCurPos.second = 0;
+ return false;
+ }
+}
+
+// Helper that allows binary search of unsorted cells using ScSortedRangeCache.
+// Rows in the given range are kept in a sorted vector and that vector is binary-searched.
+class ScQueryCellIteratorAccessSpecific< ScQueryCellIteratorAccess::SortedCache >::SortedCacheIndexer
+{
+ std::vector<SCROW> mSortedRowsCopy;
+ const std::vector<SCROW>& mSortedRows;
+ const sc::CellStoreType& mCells;
+ size_t mLowIndex;
+ size_t mHighIndex;
+ bool mValid;
+
+ const std::vector<SCROW>& makeSortedRows( const ScSortedRangeCache* cache, SCROW startRow, SCROW endRow )
+ {
+ // Keep a reference to rows from the cache if equal, otherwise make a copy.
+ if(startRow == cache->getRange().aStart.Row() && endRow == cache->getRange().aEnd.Row())
+ return cache->sortedRows();
+ else
+ {
+ mSortedRowsCopy.reserve( cache->sortedRows().size());
+ for( SCROW row : cache->sortedRows())
+ if( row >= startRow && row <= endRow )
+ mSortedRowsCopy.emplace_back( row );
+ return mSortedRowsCopy;
+ }
+ }
+
+public:
+ SortedCacheIndexer( const sc::CellStoreType& cells, SCROW startRow, SCROW endRow,
+ const ScSortedRangeCache* cache )
+ : mSortedRows( makeSortedRows( cache, startRow, endRow ))
+ , mCells( cells )
+ , mValid( false )
+ {
+ if(mSortedRows.empty())
+ {
+ // coverity[uninit_member] - these are initialized only if valid
+ return;
+ }
+ mLowIndex = 0;
+ mHighIndex = mSortedRows.size() - 1;
+ mValid = true;
+ }
+
+ sc::CellStoreType::const_position_type getPosition( size_t nIndex ) const
+ {
+ // TODO optimize?
+ SCROW row = mSortedRows[ nIndex ];
+ return mCells.position(row);
+ }
+
+ BinarySearchCellType getCell( size_t nIndex ) const
+ {
+ BinarySearchCellType aRet;
+ aRet.second = -1;
+
+ sc::CellStoreType::const_position_type aPos = getPosition(nIndex);
+ if (aPos.first == mCells.end())
+ return aRet;
+
+ aRet.first = sc::toRefCell(aPos.first, aPos.second);
+ aRet.second = aPos.first->position + aPos.second;
+ return aRet;
+ }
+
+ size_t getLowIndex() const { return mLowIndex; }
+
+ size_t getHighIndex() const { return mHighIndex; }
+
+ bool isValid() const { return mValid; }
+};
+
+ScQueryCellIteratorAccessSpecific< ScQueryCellIteratorAccess::SortedCache >::SortedCacheIndexer
+ScQueryCellIteratorAccessSpecific< ScQueryCellIteratorAccess::SortedCache >::MakeBinarySearchIndexer(
+ const sc::CellStoreType& rCells, SCROW nStartRow, SCROW nEndRow)
+{
+ return SortedCacheIndexer(rCells, nStartRow, nEndRow, sortedCache);
+}
+
+static bool CanBeUsedForSorterCache(ScDocument& /*rDoc*/, const ScQueryParam& /*rParam*/,
+ SCTAB /*nTab*/, const ScFormulaCell* /*cell*/, const ScComplexRefData* /*refData*/,
+ ScInterpreterContext& /*context*/)
+{
+#if 1
+ /* TODO: tdf#151958 broken by string query of binary search on sorted
+ * cache, use the direct query instead for releases and fix SortedCache
+ * implementation after. Not only COUNTIF() is broken, but also COUNTIFS(),
+ * and maybe lcl_LookupQuery() for VLOOKUP() etc. as well. Just disable
+ * this for now.
+ * Can't just return false because below would be unreachable code. Can't
+ * just #if/#else/#endif either because parameters would be unused. Crap
+ * this and comment out parameter names. */
+ return false;
+#else
+ if(!rParam.GetEntry(0).bDoQuery || rParam.GetEntry(1).bDoQuery
+ || rParam.GetEntry(0).GetQueryItems().size() != 1 )
+ return false;
+ if(rParam.eSearchType != utl::SearchParam::SearchType::Normal)
+ return false;
+ if(rParam.GetEntry(0).GetQueryItem().meType != ScQueryEntry::ByValue
+ && rParam.GetEntry(0).GetQueryItem().meType != ScQueryEntry::ByString)
+ return false;
+ if(!rParam.bByRow)
+ return false;
+ if(rParam.bHasHeader)
+ return false;
+ if(rParam.mbRangeLookup)
+ return false;
+ if(rParam.GetEntry(0).GetQueryItem().meType == ScQueryEntry::ByString
+ && !ScQueryEvaluator::isMatchWholeCell(rDoc, rParam.GetEntry(0).eOp))
+ return false; // substring matching cannot be sorted
+ if(rParam.GetEntry(0).eOp != SC_LESS && rParam.GetEntry(0).eOp != SC_LESS_EQUAL
+ && rParam.GetEntry(0).eOp != SC_GREATER && rParam.GetEntry(0).eOp != SC_GREATER_EQUAL
+ && rParam.GetEntry(0).eOp != SC_EQUAL)
+ return false;
+ // For unittests allow inefficient caching, in order for the code to be checked.
+ static bool inUnitTest = getenv("LO_TESTNAME") != nullptr;
+ if(refData == nullptr || refData->Ref1.IsRowRel() || refData->Ref2.IsRowRel())
+ {
+ // If this is not a range, then a cache is not worth it. If rows are relative, then each
+ // computation will use a different area, so the cache wouldn't be reused. Tab/cols are
+ // not a problem, because formula group computations are done for the same tab/col.
+ if(!inUnitTest)
+ return false;
+ }
+ if(rParam.nRow2 - rParam.nRow1 < 10)
+ {
+ if(!inUnitTest)
+ return false;
+ }
+ if( !cell )
+ return false;
+ if( !cell->GetCellGroup() || cell->GetCellGroup()->mnLength < 10 )
+ {
+ if(!inUnitTest)
+ return false;
+ }
+ // Check that all the relevant caches would be valid (may not be the case when mixing
+ // numeric and string cells for ByValue lookups).
+ for(SCCOL col : rDoc.GetAllocatedColumnsRange(nTab, rParam.nCol1, rParam.nCol2))
+ {
+ ScRange aSortedRangeRange( col, rParam.nRow1, nTab, col, rParam.nRow2, nTab);
+ if( aSortedRangeRange.Contains( cell->aPos ))
+ return false; // self-referencing, can't create cache
+ ScSortedRangeCache& cache = rDoc.GetSortedRangeCache( aSortedRangeRange, rParam, &context );
+ if(!cache.isValid())
+ return false;
+ }
+ return true;
+#endif
+}
+
+// Generic query implementation.
+
+bool ScQueryCellIteratorTypeSpecific< ScQueryCellIteratorType::Generic >::HandleItemFound()
+{
+ getThisResult = true;
+ return true; // Return from PerformQuery().
+}
+
+template< ScQueryCellIteratorAccess accessType >
+bool ScQueryCellIterator< accessType >::GetThis()
+{
+ getThisResult = false;
+ PerformQuery();
+ return getThisResult;
+}
+
+template< ScQueryCellIteratorAccess accessType >
+bool ScQueryCellIterator< accessType >::GetFirst()
+{
+ assert(nTab < rDoc.GetTableCount() && "index out of bounds, FIX IT");
+ nCol = maParam.nCol1;
+ InitPos();
+ return GetThis();
+}
+
+template< ScQueryCellIteratorAccess accessType >
+bool ScQueryCellIterator< accessType >::GetNext()
+{
+ IncPos();
+ if ( nStopOnMismatch )
+ nStopOnMismatch = nStopOnMismatchEnabled;
+ if ( nTestEqualCondition )
+ nTestEqualCondition = nTestEqualConditionEnabled;
+ return GetThis();
+}
+
+template<>
+bool ScQueryCellIterator< ScQueryCellIteratorAccess::SortedCache >::GetNext()
+{
+ assert( !nStopOnMismatch );
+ assert( !nTestEqualCondition );
+ // When searching using sorted cache, we should always find cells that match,
+ // because InitPos()/IncPos() select only such rows, so skip GetThis() (and thus
+ // the somewhat expensive PerformQuery) as long as we're not at the end
+ // of a column. As an optimization IncPosFast() returns true if not at the end,
+ // in which case in non-DBG_UTIL mode it doesn't even bother to set maCurPos.
+ if( IncPosFast())
+ {
+#ifdef DBG_UTIL
+ assert(GetThis());
+#endif
+ return true;
+ }
+ return GetThis();
+}
+
+bool ScQueryCellIteratorSortedCache::CanBeUsed(ScDocument& rDoc, const ScQueryParam& rParam,
+ SCTAB nTab, const ScFormulaCell* cell, const ScComplexRefData* refData,
+ ScInterpreterContext& context)
+{
+ return CanBeUsedForSorterCache(rDoc, rParam, nTab, cell, refData, context);
+}
+
+// Countifs implementation.
+
+bool ScQueryCellIteratorTypeSpecific< ScQueryCellIteratorType::CountIf >::HandleItemFound()
+{
+ ++countIfCount;
+ return false; // Continue searching.
+}
+
+template< ScQueryCellIteratorAccess accessType >
+sal_uInt64 ScCountIfCellIterator< accessType >::GetCount()
+{
+ // Keep Entry.nField in iterator on column change
+ SetAdvanceQueryParamEntryField( true );
+ assert(nTab < rDoc.GetTableCount() && "try to access index out of bounds, FIX IT");
+ maParam.nCol1 = rDoc.ClampToAllocatedColumns(nTab, maParam.nCol1);
+ maParam.nCol2 = rDoc.ClampToAllocatedColumns(nTab, maParam.nCol2);
+ nCol = maParam.nCol1;
+ InitPos();
+ countIfCount = 0;
+ PerformQuery();
+ return countIfCount;
+}
+
+
+bool ScCountIfCellIteratorSortedCache::CanBeUsed(ScDocument& rDoc, const ScQueryParam& rParam,
+ SCTAB nTab, const ScFormulaCell* cell, const ScComplexRefData* refData,
+ ScInterpreterContext& context)
+{
+ return CanBeUsedForSorterCache(rDoc, rParam, nTab, cell, refData, context);
+}
+
+template<>
+sal_uInt64 ScCountIfCellIterator< ScQueryCellIteratorAccess::SortedCache >::GetCount()
+{
+ // Keep Entry.nField in iterator on column change
+ SetAdvanceQueryParamEntryField( true );
+ assert(nTab < rDoc.GetTableCount() && "try to access index out of bounds, FIX IT");
+ sal_uInt64 count = 0;
+ // Each column must be sorted separately.
+ for(SCCOL col : rDoc.GetAllocatedColumnsRange(nTab, maParam.nCol1, maParam.nCol2))
+ {
+ nCol = col;
+ nRow = maParam.nRow1;
+ ScRange aSortedRangeRange( col, maParam.nRow1, nTab, col, maParam.nRow2, nTab);
+ ScQueryOp& op = maParam.GetEntry(0).eOp;
+ SetSortedRangeCache( rDoc.GetSortedRangeCache( aSortedRangeRange, maParam, &mrContext ));
+ if( op == SC_EQUAL )
+ {
+ // BinarySearch() searches for the last item that matches. Therefore first
+ // find the last non-matching position using SC_LESS and then find the last
+ // matching position using SC_EQUAL.
+ ScQueryOp saveOp = op;
+ op = SC_LESS;
+ if( BinarySearch( nCol, true ))
+ {
+ op = saveOp; // back to SC_EQUAL
+ size_t lastNonMatching = sortedCache->indexForRow(nRow);
+ if( BinarySearch( nCol ))
+ {
+ size_t lastMatching = sortedCache->indexForRow(nRow);
+ assert(lastMatching >= lastNonMatching);
+ count += lastMatching - lastNonMatching;
+ }
+ else
+ {
+ // BinarySearch() should at least find the same result as the SC_LESS
+ // call, so this should not happen.
+ assert(false);
+ }
+ }
+ else
+ {
+ // BinarySearch() returning false means that all values are larger,
+ // so try to find matching ones and count those up to and including
+ // the found one.
+ op = saveOp; // back to SC_EQUAL
+ if( BinarySearch( nCol ))
+ {
+ size_t lastMatching = sortedCache->indexForRow(nRow) + 1;
+ count += lastMatching;
+ }
+ else if( maParam.GetEntry(0).GetQueryItem().mbMatchEmpty
+ && rDoc.IsEmptyData(col, maParam.nRow1, col, maParam.nRow2, nTab))
+ {
+ // BinarySearch() returns false in case it's all empty data,
+ // handle that specially.
+ count += maParam.nRow2 - maParam.nRow1 + 1;
+ }
+ }
+ }
+ else
+ {
+ // BinarySearch() searches for the last item that matches. Therefore everything
+ // up to and including the found row matches the condition.
+ if( BinarySearch( nCol ))
+ count += sortedCache->indexForRow(nRow) + 1;
+ }
+ }
+ if( maParam.GetEntry(0).GetQueryItem().mbMatchEmpty
+ && maParam.nCol2 >= rDoc.GetAllocatedColumnsCount( nTab ))
+ {
+ count += (maParam.nCol2 - rDoc.GetAllocatedColumnsCount( nTab ))
+ * ( maParam.nRow2 - maParam.nRow1 + 1 );
+ }
+ return count;
+}
+
+template class ScQueryCellIterator< ScQueryCellIteratorAccess::Direct >;
+template class ScQueryCellIterator< ScQueryCellIteratorAccess::SortedCache >;
+template class ScCountIfCellIterator< ScQueryCellIteratorAccess::Direct >;
+template class ScCountIfCellIterator< ScQueryCellIteratorAccess::SortedCache >;
+
+// gcc for some reason needs these too
+template class ScQueryCellIteratorBase< ScQueryCellIteratorAccess::Direct, ScQueryCellIteratorType::Generic >;
+template class ScQueryCellIteratorBase< ScQueryCellIteratorAccess::SortedCache, ScQueryCellIteratorType::Generic >;
+template class ScQueryCellIteratorBase< ScQueryCellIteratorAccess::Direct, ScQueryCellIteratorType::CountIf >;
+template class ScQueryCellIteratorBase< ScQueryCellIteratorAccess::SortedCache, ScQueryCellIteratorType::CountIf >;
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sc/source/core/data/refupdatecontext.cxx b/sc/source/core/data/refupdatecontext.cxx
new file mode 100644
index 000000000..8faf1f105
--- /dev/null
+++ b/sc/source/core/data/refupdatecontext.cxx
@@ -0,0 +1,139 @@
+/* -*- 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/.
+ */
+
+#include <refupdatecontext.hxx>
+#include <algorithm>
+#include <clipparam.hxx>
+#include <mtvelements.hxx>
+
+namespace sc {
+
+void UpdatedRangeNames::setUpdatedName(SCTAB nTab, sal_uInt16 nIndex)
+{
+ // Map anything <-1 to global names. Unless we really want to come up with
+ // some classification there...
+ if (nTab < -1)
+ nTab = -1;
+
+ UpdatedNamesType::iterator it = maUpdatedNames.find(nTab);
+ if (it == maUpdatedNames.end())
+ {
+ // Insert a new container for this sheet index.
+ NameIndicesType aIndices;
+ std::pair<UpdatedNamesType::iterator,bool> r =
+ maUpdatedNames.emplace( nTab, aIndices);
+
+ if (!r.second)
+ // Insertion failed for whatever reason.
+ return;
+
+ it = r.first;
+ }
+
+ NameIndicesType& rIndices = it->second;
+ rIndices.insert(nIndex);
+}
+
+bool UpdatedRangeNames::isNameUpdated(SCTAB nTab, sal_uInt16 nIndex) const
+{
+ UpdatedNamesType::const_iterator it = maUpdatedNames.find(nTab);
+ if (it == maUpdatedNames.end())
+ return false;
+
+ const NameIndicesType& rIndices = it->second;
+ return rIndices.count(nIndex) > 0;
+}
+
+UpdatedRangeNames::NameIndicesType UpdatedRangeNames::getUpdatedNames(SCTAB nTab) const
+{
+ UpdatedNamesType::const_iterator it = maUpdatedNames.find(nTab);
+ if (it == maUpdatedNames.end())
+ return NameIndicesType();
+ return it->second;
+}
+
+bool UpdatedRangeNames::isEmpty(SCTAB nTab) const
+{
+ UpdatedNamesType::const_iterator it = maUpdatedNames.find(nTab);
+ return it == maUpdatedNames.end();
+}
+
+RefUpdateContext::RefUpdateContext(ScDocument& rDoc, ScDocument* pClipdoc)
+ : mrDoc(rDoc)
+ , meMode(URM_INSDEL)
+ , mbTransposed(pClipdoc != nullptr && pClipdoc->GetClipParam().isTransposed())
+ , mnColDelta(0)
+ , mnRowDelta(0)
+ , mnTabDelta(0)
+ , mpBlockPos(nullptr)
+{
+ assert((pClipdoc == nullptr || pClipdoc->IsClipboard()) && "only nullptr or clipdoc allowed");
+}
+
+bool RefUpdateContext::isInserted() const
+{
+ return (meMode == URM_INSDEL) && (mnColDelta > 0 || mnRowDelta > 0 || mnTabDelta > 0);
+}
+
+bool RefUpdateContext::isDeleted() const
+{
+ return (meMode == URM_INSDEL) && (mnColDelta < 0 || mnRowDelta < 0 || mnTabDelta < 0);
+}
+
+void RefUpdateContext::setBlockPositionReference( ColumnBlockPositionSet* blockPos )
+{
+ mpBlockPos = blockPos;
+}
+
+ColumnBlockPosition* RefUpdateContext::getBlockPosition(SCTAB nTab, SCCOL nCol)
+{
+ return mpBlockPos ? mpBlockPos->getBlockPosition(nTab, nCol) : nullptr;
+}
+
+RefUpdateResult::RefUpdateResult() : mbValueChanged(false), mbReferenceModified(false), mbNameModified(false) {}
+
+RefUpdateInsertTabContext::RefUpdateInsertTabContext(ScDocument& rDoc, SCTAB nInsertPos, SCTAB nSheets) :
+ mrDoc(rDoc), mnInsertPos(nInsertPos), mnSheets(nSheets) {}
+
+RefUpdateDeleteTabContext::RefUpdateDeleteTabContext(ScDocument& rDoc, SCTAB nDeletePos, SCTAB nSheets) :
+ mrDoc(rDoc), mnDeletePos(nDeletePos), mnSheets(nSheets) {}
+
+RefUpdateMoveTabContext::RefUpdateMoveTabContext(ScDocument& rDoc, SCTAB nOldPos, SCTAB nNewPos) :
+ mrDoc(rDoc), mnOldPos(nOldPos), mnNewPos(nNewPos) {}
+
+SCTAB RefUpdateMoveTabContext::getNewTab(SCTAB nOldTab) const
+{
+ // Sheets below the lower bound or above the upper bound will not change.
+ SCTAB nLowerBound = std::min(mnOldPos, mnNewPos);
+ SCTAB nUpperBound = std::max(mnOldPos, mnNewPos);
+
+ if (nOldTab < nLowerBound || nUpperBound < nOldTab)
+ // Outside the boundary. Nothing to adjust.
+ return nOldTab;
+
+ if (nOldTab == mnOldPos)
+ return mnNewPos;
+
+ // It's somewhere in between.
+ if (mnOldPos < mnNewPos)
+ {
+ // Moving a sheet to the right. The rest of the sheets shifts to the left.
+ return nOldTab - 1;
+ }
+
+ // Moving a sheet to the left. The rest of the sheets shifts to the right.
+ return nOldTab + 1;
+}
+
+SetFormulaDirtyContext::SetFormulaDirtyContext() :
+ mnTabDeletedStart(-1), mnTabDeletedEnd(-1), mbClearTabDeletedFlag(false) {}
+
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sc/source/core/data/rowheightcontext.cxx b/sc/source/core/data/rowheightcontext.cxx
new file mode 100644
index 000000000..c5f615c66
--- /dev/null
+++ b/sc/source/core/data/rowheightcontext.cxx
@@ -0,0 +1,38 @@
+/* -*- 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/.
+ */
+
+#include <rowheightcontext.hxx>
+
+namespace sc {
+
+RowHeightContext::RowHeightContext(SCROW nMaxRow,
+ double fPPTX, double fPPTY, const Fraction& rZoomX, const Fraction& rZoomY,
+ OutputDevice* pOutDev ) :
+ maHeights(nMaxRow, 0),
+ mfPPTX(fPPTX), mfPPTY(fPPTY),
+ maZoomX(rZoomX), maZoomY(rZoomY),
+ mpOutDev(pOutDev),
+ mnExtraHeight(0),
+ mbForceAutoSize(false) {}
+
+RowHeightContext::~RowHeightContext() {}
+
+void RowHeightContext::setExtraHeight( sal_uInt16 nH )
+{
+ mnExtraHeight = nH;
+}
+
+void RowHeightContext::setForceAutoSize( bool b )
+{
+ mbForceAutoSize = b;
+}
+
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sc/source/core/data/segmenttree.cxx b/sc/source/core/data/segmenttree.cxx
new file mode 100644
index 000000000..aa10d3254
--- /dev/null
+++ b/sc/source/core/data/segmenttree.cxx
@@ -0,0 +1,711 @@
+/* -*- 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 <segmenttree.hxx>
+#include <o3tl/safeint.hxx>
+#include <mdds/flat_segment_tree.hpp>
+#include <sal/log.hxx>
+#include <algorithm>
+#include <limits>
+#include <string_view>
+#include <global.hxx>
+
+using ::std::numeric_limits;
+
+namespace {
+
+template<typename ValueType_, typename ExtValueType_ = ValueType_>
+class ScFlatSegmentsImpl
+{
+public:
+ typedef ValueType_ ValueType;
+ typedef ExtValueType_ ExtValueType;
+
+ struct RangeData
+ {
+ SCCOLROW mnPos1;
+ SCCOLROW mnPos2;
+ ValueType mnValue;
+ };
+
+ ScFlatSegmentsImpl(SCCOLROW nMax, ValueType nDefault);
+ ScFlatSegmentsImpl(const ScFlatSegmentsImpl& r);
+
+ bool setValue(SCCOLROW nPos1, SCCOLROW nPos2, ValueType nValue);
+ void setValueIf(SCCOLROW nPos1, SCCOLROW nPos2, ValueType nValue, const std::function<bool(ValueType)>& rPredicate);
+ ValueType getValue(SCCOLROW nPos);
+ sal_uInt64 getSumValue(SCCOLROW nPos1, SCCOLROW nPos2);
+ bool getRangeData(SCCOLROW nPos, RangeData& rData);
+ bool getRangeDataLeaf(SCCOLROW nPos, RangeData& rData);
+ void removeSegment(SCCOLROW nPos1, SCCOLROW nPos2);
+ void insertSegment(SCCOLROW nPos, SCCOLROW nSize, bool bSkipStartBoundary);
+
+ SCCOLROW findLastTrue(ValueType nValue) const;
+
+ // range iteration
+ bool getFirst(RangeData& rData);
+ bool getNext(RangeData& rData);
+
+ void enableTreeSearch(bool b)
+ {
+ mbTreeSearchEnabled = b;
+ }
+
+ void makeReady();
+
+private:
+ typedef ::mdds::flat_segment_tree<SCCOLROW, ValueType> fst_type;
+ fst_type maSegments;
+ typename fst_type::const_iterator maItr;
+
+ bool mbTreeSearchEnabled:1;
+};
+
+}
+
+template<typename ValueType_, typename ExtValueType_>
+ScFlatSegmentsImpl<ValueType_, ExtValueType_>::ScFlatSegmentsImpl(SCCOLROW nMax, ValueType nDefault) :
+ maSegments(0, nMax+1, nDefault),
+ mbTreeSearchEnabled(true)
+{
+}
+
+template<typename ValueType_, typename ExtValueType_>
+ScFlatSegmentsImpl<ValueType_, ExtValueType_>::ScFlatSegmentsImpl(const ScFlatSegmentsImpl<ValueType_, ExtValueType_>& r) :
+ maSegments(r.maSegments),
+ mbTreeSearchEnabled(r.mbTreeSearchEnabled)
+{
+}
+
+template<typename ValueType_, typename ExtValueType_>
+bool ScFlatSegmentsImpl<ValueType_, ExtValueType_>::setValue(SCCOLROW nPos1, SCCOLROW nPos2, ValueType nValue)
+{
+ ::std::pair<typename fst_type::const_iterator, bool> ret;
+ ret = maSegments.insert(maItr, nPos1, nPos2+1, nValue);
+ maItr = ret.first;
+ return ret.second;
+}
+
+template<typename ValueType_, typename ExtValueType_>
+void ScFlatSegmentsImpl<ValueType_, ExtValueType_>::setValueIf(SCCOLROW nPos1, SCCOLROW nPos2,
+ ValueType nValue, const std::function<bool(ValueType)>& rPredicate)
+{
+ SCCOLROW nCurrentStartRow = nPos1;
+ while (nCurrentStartRow <= nPos2)
+ {
+ RangeData aRangeData;
+ getRangeData(nCurrentStartRow, aRangeData);
+ if (rPredicate(aRangeData.mnValue))
+ {
+ // set value from current iteration point on, til end of range.
+ // Note that aRangeData may well contain much lower values for nPos1
+ setValue(nCurrentStartRow, std::min<SCCOLROW>(nPos2, aRangeData.mnPos2), nValue);
+ }
+
+ // even if nPos2 is bigger than nPos2 this should terminate the loop
+ nCurrentStartRow = aRangeData.mnPos2 + 1;
+ }
+}
+
+template<typename ValueType_, typename ExtValueType_>
+typename ScFlatSegmentsImpl<ValueType_, ExtValueType_>::ValueType ScFlatSegmentsImpl<ValueType_, ExtValueType_>::getValue(SCCOLROW nPos)
+{
+ ValueType nValue = 0;
+ if (!mbTreeSearchEnabled)
+ {
+ maSegments.search(nPos, nValue);
+ return nValue;
+ }
+
+ if (!maSegments.is_tree_valid())
+ {
+ assert(!ScGlobal::bThreadedGroupCalcInProgress);
+ maSegments.build_tree();
+ }
+
+ maSegments.search_tree(nPos, nValue);
+ return nValue;
+}
+
+template<typename ValueType_, typename ExtValueType_>
+sal_uInt64 ScFlatSegmentsImpl<ValueType_, ExtValueType_>::getSumValue(SCCOLROW nPos1, SCCOLROW nPos2)
+{
+ if (mbTreeSearchEnabled)
+ {
+
+ if (!maSegments.is_tree_valid())
+ {
+ assert(!ScGlobal::bThreadedGroupCalcInProgress);
+ maSegments.build_tree();
+ }
+
+ RangeData aData;
+ auto [it, found] = maSegments.search_tree(nPos1, aData.mnValue, &aData.mnPos1, &aData.mnPos2);
+ if (!found)
+ return 0;
+ aData.mnPos2 = aData.mnPos2-1; // end point is not inclusive.
+
+ sal_uInt64 nValue = 0;
+
+ SCROW nCurPos = nPos1;
+ SCROW nEndPos = aData.mnPos2;
+ while (nEndPos <= nPos2)
+ {
+ sal_uInt64 nRes;
+ if (o3tl::checked_multiply<sal_uInt64>(aData.mnValue, nEndPos - nCurPos + 1, nRes))
+ {
+ SAL_WARN("sc.core", "row height overflow");
+ nRes = SAL_MAX_INT64;
+ }
+ nValue = o3tl::saturating_add(nValue, nRes);
+ nCurPos = nEndPos + 1;
+ auto itPair = maSegments.search(it, nCurPos, aData.mnValue, &aData.mnPos1, &aData.mnPos2);
+ if (!itPair.second)
+ break;
+ it = itPair.first;
+ aData.mnPos2 = aData.mnPos2-1; // end point is not inclusive.
+ nEndPos = aData.mnPos2;
+ }
+ if (nCurPos <= nPos2)
+ {
+ nEndPos = ::std::min(nEndPos, nPos2);
+ sal_uInt64 nRes;
+ if (o3tl::checked_multiply<sal_uInt64>(aData.mnValue, nEndPos - nCurPos + 1, nRes))
+ {
+ SAL_WARN("sc.core", "row height overflow");
+ nRes = SAL_MAX_INT64;
+ }
+ nValue = o3tl::saturating_add(nValue, nRes);
+ }
+ return nValue;
+ }
+ else
+ {
+ RangeData aData;
+ if (!getRangeDataLeaf(nPos1, aData))
+ return 0;
+
+ sal_uInt64 nValue = 0;
+
+ SCROW nCurPos = nPos1;
+ SCROW nEndPos = aData.mnPos2;
+ while (nEndPos <= nPos2)
+ {
+ sal_uInt64 nRes;
+ if (o3tl::checked_multiply<sal_uInt64>(aData.mnValue, nEndPos - nCurPos + 1, nRes))
+ {
+ SAL_WARN("sc.core", "row height overflow");
+ nRes = SAL_MAX_INT64;
+ }
+ nValue = o3tl::saturating_add(nValue, nRes);
+ nCurPos = nEndPos + 1;
+ if (!getRangeDataLeaf(nCurPos, aData))
+ break;
+
+ nEndPos = aData.mnPos2;
+ }
+ if (nCurPos <= nPos2)
+ {
+ nEndPos = ::std::min(nEndPos, nPos2);
+ sal_uInt64 nRes;
+ if (o3tl::checked_multiply<sal_uInt64>(aData.mnValue, nEndPos - nCurPos + 1, nRes))
+ {
+ SAL_WARN("sc.core", "row height overflow");
+ nRes = SAL_MAX_INT64;
+ }
+ nValue = o3tl::saturating_add(nValue, nRes);
+ }
+ return nValue;
+ }
+}
+
+template<typename ValueType_, typename ExtValueType_>
+bool ScFlatSegmentsImpl<ValueType_, ExtValueType_>::getRangeData(SCCOLROW nPos, RangeData& rData)
+{
+ if (!mbTreeSearchEnabled)
+ return getRangeDataLeaf(nPos, rData);
+
+ if (!maSegments.is_tree_valid())
+ {
+ assert(!ScGlobal::bThreadedGroupCalcInProgress);
+ maSegments.build_tree();
+ }
+
+ auto [it,found] = maSegments.search_tree(nPos, rData.mnValue, &rData.mnPos1, &rData.mnPos2);
+ if (!found)
+ return false;
+ maItr = it; // cache the iterator to speed up ForwardIterator.
+ rData.mnPos2 = rData.mnPos2-1; // end point is not inclusive.
+ return true;
+}
+
+template<typename ValueType_, typename ExtValueType_>
+bool ScFlatSegmentsImpl<ValueType_, ExtValueType_>::getRangeDataLeaf(SCCOLROW nPos, RangeData& rData)
+{
+ // Conduct leaf-node only search. Faster when searching between range insertion.
+ const ::std::pair<typename fst_type::const_iterator, bool> &ret =
+ maSegments.search(maItr, nPos, rData.mnValue, &rData.mnPos1, &rData.mnPos2);
+
+ if (!ret.second)
+ return false;
+
+ maItr = ret.first;
+
+ rData.mnPos2 = rData.mnPos2-1; // end point is not inclusive.
+ return true;
+}
+
+template<typename ValueType_, typename ExtValueType_>
+void ScFlatSegmentsImpl<ValueType_, ExtValueType_>::removeSegment(SCCOLROW nPos1, SCCOLROW nPos2)
+{
+ maSegments.shift_left(nPos1, nPos2);
+ maItr = maSegments.begin();
+}
+
+template<typename ValueType_, typename ExtValueType_>
+void ScFlatSegmentsImpl<ValueType_, ExtValueType_>::insertSegment(SCCOLROW nPos, SCCOLROW nSize, bool bSkipStartBoundary)
+{
+ maSegments.shift_right(nPos, nSize, bSkipStartBoundary);
+ maItr = maSegments.begin();
+}
+
+template<typename ValueType_, typename ExtValueType_>
+SCCOLROW ScFlatSegmentsImpl<ValueType_, ExtValueType_>::findLastTrue(ValueType nValue) const
+{
+ SCCOLROW nPos = numeric_limits<SCCOLROW>::max(); // position not found.
+ typename fst_type::const_reverse_iterator itr = maSegments.rbegin(), itrEnd = maSegments.rend();
+ // Note that when searching in reverse direction, we need to skip the first
+ // node, since the right-most leaf node does not store a valid value.
+ for (++itr; itr != itrEnd; ++itr)
+ {
+ if (itr->second != nValue)
+ {
+ nPos = (--itr)->first - 1;
+ break;
+ }
+ }
+ return nPos;
+}
+
+template<typename ValueType_, typename ExtValueType_>
+bool ScFlatSegmentsImpl<ValueType_, ExtValueType_>::getFirst(RangeData& rData)
+{
+ maItr = maSegments.begin();
+ return getNext(rData);
+}
+
+template<typename ValueType_, typename ExtValueType_>
+bool ScFlatSegmentsImpl<ValueType_, ExtValueType_>::getNext(RangeData& rData)
+{
+ typename fst_type::const_iterator itrEnd = maSegments.end();
+ if (maItr == itrEnd)
+ return false;
+
+ rData.mnPos1 = maItr->first;
+ rData.mnValue = maItr->second;
+
+ ++maItr;
+ if (maItr == itrEnd)
+ return false;
+
+ rData.mnPos2 = maItr->first - 1;
+ return true;
+}
+
+template<typename ValueType_, typename ExtValueType_>
+void ScFlatSegmentsImpl<ValueType_, ExtValueType_>::makeReady()
+{
+ assert(!ScGlobal::bThreadedGroupCalcInProgress);
+ if (!maSegments.is_tree_valid())
+ maSegments.build_tree();
+}
+
+class ScFlatUInt16SegmentsImpl : public ScFlatSegmentsImpl<sal_uInt16, sal_uInt32>
+{
+public:
+ explicit ScFlatUInt16SegmentsImpl(SCCOLROW nMax, sal_uInt16 nDefault) :
+ ScFlatSegmentsImpl<sal_uInt16, sal_uInt32>(nMax, nDefault)
+ {
+ }
+};
+
+class ScFlatBoolSegmentsImpl : public ScFlatSegmentsImpl<bool>
+{
+public:
+ explicit ScFlatBoolSegmentsImpl(SCCOLROW nMax) :
+ ScFlatSegmentsImpl<bool>(nMax, false)
+ {
+ }
+
+ bool setTrue(SCCOLROW nPos1, SCCOLROW nPos2);
+ bool setFalse(SCCOLROW nPos1, SCCOLROW nPos2);
+};
+
+bool ScFlatBoolSegmentsImpl::setTrue(SCCOLROW nPos1, SCCOLROW nPos2)
+{
+ return setValue(nPos1, nPos2, true);
+}
+
+bool ScFlatBoolSegmentsImpl::setFalse(SCCOLROW nPos1, SCCOLROW nPos2)
+{
+ return setValue(nPos1, nPos2, false);
+}
+
+ScFlatBoolRowSegments::ForwardIterator::ForwardIterator(ScFlatBoolRowSegments& rSegs) :
+ mrSegs(rSegs), mnCurPos(0), mnLastPos(-1), mbCurValue(false)
+{
+}
+
+bool ScFlatBoolRowSegments::ForwardIterator::getValue(SCROW nPos, bool& rVal)
+{
+ if (nPos >= mnCurPos)
+ // It can only go in a forward direction.
+ mnCurPos = nPos;
+
+ if (mnCurPos > mnLastPos)
+ {
+ // position not in the current segment. Update the current value.
+ ScFlatBoolRowSegments::RangeData aData;
+ if (!mrSegs.getRangeData(mnCurPos, aData))
+ return false;
+
+ mbCurValue = aData.mbValue;
+ mnLastPos = aData.mnRow2;
+ }
+
+ rVal = mbCurValue;
+ return true;
+}
+
+ScFlatBoolRowSegments::RangeIterator::RangeIterator(ScFlatBoolRowSegments const & rSegs) :
+ mrSegs(rSegs)
+{
+}
+
+bool ScFlatBoolRowSegments::RangeIterator::getFirst(RangeData& rRange)
+{
+ ScFlatBoolSegmentsImpl::RangeData aData;
+ if (!mrSegs.mpImpl->getFirst(aData))
+ return false;
+
+ rRange.mnRow1 = static_cast<SCROW>(aData.mnPos1);
+ rRange.mnRow2 = static_cast<SCROW>(aData.mnPos2);
+ rRange.mbValue = static_cast<bool>(aData.mnValue);
+ return true;
+}
+
+bool ScFlatBoolRowSegments::RangeIterator::getNext(RangeData& rRange)
+{
+ ScFlatBoolSegmentsImpl::RangeData aData;
+ if (!mrSegs.mpImpl->getNext(aData))
+ return false;
+
+ rRange.mnRow1 = static_cast<SCROW>(aData.mnPos1);
+ rRange.mnRow2 = static_cast<SCROW>(aData.mnPos2);
+ rRange.mbValue = static_cast<bool>(aData.mnValue);
+ return true;
+}
+
+ScFlatBoolRowSegments::ScFlatBoolRowSegments(SCROW nMaxRow) :
+ mpImpl(new ScFlatBoolSegmentsImpl(nMaxRow))
+{
+}
+
+ScFlatBoolRowSegments::ScFlatBoolRowSegments(const ScFlatBoolRowSegments& r) :
+ mpImpl(new ScFlatBoolSegmentsImpl(*r.mpImpl))
+{
+}
+
+ScFlatBoolRowSegments::~ScFlatBoolRowSegments()
+{
+}
+
+bool ScFlatBoolRowSegments::setTrue(SCROW nRow1, SCROW nRow2)
+{
+ return mpImpl->setTrue(static_cast<SCCOLROW>(nRow1), static_cast<SCCOLROW>(nRow2));
+}
+
+bool ScFlatBoolRowSegments::setFalse(SCROW nRow1, SCROW nRow2)
+{
+ return mpImpl->setFalse(static_cast<SCCOLROW>(nRow1), static_cast<SCCOLROW>(nRow2));
+}
+
+bool ScFlatBoolRowSegments::getRangeData(SCROW nRow, RangeData& rData) const
+{
+ ScFlatBoolSegmentsImpl::RangeData aData;
+ if (!mpImpl->getRangeData(static_cast<SCCOLROW>(nRow), aData))
+ return false;
+
+ rData.mbValue = aData.mnValue;
+ rData.mnRow1 = static_cast<SCROW>(aData.mnPos1);
+ rData.mnRow2 = static_cast<SCROW>(aData.mnPos2);
+ return true;
+}
+
+bool ScFlatBoolRowSegments::getRangeDataLeaf(SCROW nRow, RangeData& rData)
+{
+ ScFlatBoolSegmentsImpl::RangeData aData;
+ if (!mpImpl->getRangeDataLeaf(static_cast<SCCOLROW>(nRow), aData))
+ return false;
+
+ rData.mbValue = aData.mnValue;
+ rData.mnRow1 = static_cast<SCROW>(aData.mnPos1);
+ rData.mnRow2 = static_cast<SCROW>(aData.mnPos2);
+ return true;
+}
+
+void ScFlatBoolRowSegments::removeSegment(SCROW nRow1, SCROW nRow2)
+{
+ mpImpl->removeSegment(static_cast<SCCOLROW>(nRow1), static_cast<SCCOLROW>(nRow2));
+}
+
+void ScFlatBoolRowSegments::insertSegment(SCROW nRow, SCROW nSize)
+{
+ mpImpl->insertSegment(static_cast<SCCOLROW>(nRow), static_cast<SCCOLROW>(nSize), true/*bSkipStartBoundary*/);
+}
+
+SCROW ScFlatBoolRowSegments::findLastTrue() const
+{
+ return mpImpl->findLastTrue(false);
+}
+
+void ScFlatBoolRowSegments::makeReady()
+{
+ mpImpl->makeReady();
+}
+
+OString ScFlatBoolRowSegments::dumpAsString()
+{
+ OString aOutput;
+ OString aSegment;
+ RangeData aRange;
+ SCROW nRow = 0;
+ while (getRangeData(nRow, aRange))
+ {
+ if (!nRow)
+ aSegment = (aRange.mbValue ? std::string_view("1") : std::string_view("0")) + OString::Concat(":");
+ else
+ aSegment.clear();
+
+ aSegment += OString::number(aRange.mnRow2) + " ";
+ aOutput += aSegment;
+ nRow = aRange.mnRow2 + 1;
+ }
+
+ return aOutput;
+}
+
+ScFlatBoolColSegments::ScFlatBoolColSegments(SCCOL nMaxCol) :
+ mpImpl(new ScFlatBoolSegmentsImpl(nMaxCol))
+{
+}
+
+ScFlatBoolColSegments::ScFlatBoolColSegments(const ScFlatBoolColSegments& r) :
+ mpImpl(new ScFlatBoolSegmentsImpl(*r.mpImpl))
+{
+}
+
+ScFlatBoolColSegments::~ScFlatBoolColSegments()
+{
+}
+
+bool ScFlatBoolColSegments::setTrue(SCCOL nCol1, SCCOL nCol2)
+{
+ return mpImpl->setTrue(static_cast<SCCOLROW>(nCol1), static_cast<SCCOLROW>(nCol2));
+}
+
+bool ScFlatBoolColSegments::setFalse(SCCOL nCol1, SCCOL nCol2)
+{
+ return mpImpl->setFalse(static_cast<SCCOLROW>(nCol1), static_cast<SCCOLROW>(nCol2));
+}
+
+bool ScFlatBoolColSegments::getRangeData(SCCOL nCol, RangeData& rData)
+{
+ ScFlatBoolSegmentsImpl::RangeData aData;
+ if (!mpImpl->getRangeData(static_cast<SCCOLROW>(nCol), aData))
+ return false;
+
+ rData.mbValue = aData.mnValue;
+ rData.mnCol1 = static_cast<SCCOL>(aData.mnPos1);
+ rData.mnCol2 = static_cast<SCCOL>(aData.mnPos2);
+ return true;
+}
+
+void ScFlatBoolColSegments::removeSegment(SCCOL nCol1, SCCOL nCol2)
+{
+ mpImpl->removeSegment(static_cast<SCCOLROW>(nCol1), static_cast<SCCOLROW>(nCol2));
+}
+
+void ScFlatBoolColSegments::insertSegment(SCCOL nCol, SCCOL nSize)
+{
+ mpImpl->insertSegment(static_cast<SCCOLROW>(nCol), static_cast<SCCOLROW>(nSize), true/*bSkipStartBoundary*/);
+}
+
+void ScFlatBoolColSegments::makeReady()
+{
+ mpImpl->makeReady();
+}
+
+OString ScFlatBoolColSegments::dumpAsString()
+{
+ OString aOutput;
+ OString aSegment;
+ RangeData aRange;
+ SCCOL nCol = 0;
+ while (getRangeData(nCol, aRange))
+ {
+ if (!nCol)
+ aSegment = (aRange.mbValue ? OString::Concat("1") : OString::Concat("0")) + OString::Concat(":");
+ else
+ aSegment.clear();
+
+ aSegment += OString::number(aRange.mnCol2) + " ";
+ aOutput += aSegment;
+ nCol = aRange.mnCol2 + 1;
+ }
+
+ return aOutput;
+}
+
+ScFlatUInt16RowSegments::ForwardIterator::ForwardIterator(ScFlatUInt16RowSegments& rSegs) :
+ mrSegs(rSegs), mnCurPos(0), mnLastPos(-1), mnCurValue(0)
+{
+}
+
+bool ScFlatUInt16RowSegments::ForwardIterator::getValue(SCROW nPos, sal_uInt16& rVal)
+{
+ if (nPos >= mnCurPos)
+ // It can only go in a forward direction.
+ mnCurPos = nPos;
+
+ if (mnCurPos > mnLastPos)
+ {
+ // position not in the current segment. Update the current value.
+ ScFlatUInt16SegmentsImpl::RangeData aData;
+ if (mnLastPos == -1)
+ {
+ // first time in this method, use the tree search based method
+ if (!mrSegs.mpImpl->getRangeData(mnCurPos, aData))
+ return false;
+ }
+ else
+ {
+ // but on subsequent calls, use the leaf method, which is faster
+ // because we have a cached iterator.
+ if (!mrSegs.mpImpl->getRangeDataLeaf(mnCurPos, aData))
+ return false;
+ }
+
+ mnCurValue = aData.mnValue;
+ mnLastPos = aData.mnPos2;
+ }
+
+ rVal = mnCurValue;
+ return true;
+}
+
+ScFlatUInt16RowSegments::ScFlatUInt16RowSegments(SCROW nMaxRow, sal_uInt16 nDefault) :
+ mpImpl(new ScFlatUInt16SegmentsImpl(nMaxRow, nDefault))
+{
+}
+
+ScFlatUInt16RowSegments::ScFlatUInt16RowSegments(const ScFlatUInt16RowSegments& r) :
+ mpImpl(new ScFlatUInt16SegmentsImpl(*r.mpImpl))
+{
+}
+
+ScFlatUInt16RowSegments::~ScFlatUInt16RowSegments()
+{
+}
+
+void ScFlatUInt16RowSegments::setValue(SCROW nRow1, SCROW nRow2, sal_uInt16 nValue)
+{
+ mpImpl->setValue(static_cast<SCCOLROW>(nRow1), static_cast<SCCOLROW>(nRow2), nValue);
+}
+
+sal_uInt16 ScFlatUInt16RowSegments::getValue(SCROW nRow)
+{
+ return mpImpl->getValue(static_cast<SCCOLROW>(nRow));
+}
+
+sal_uInt64 ScFlatUInt16RowSegments::getSumValue(SCROW nRow1, SCROW nRow2)
+{
+ return mpImpl->getSumValue(static_cast<SCCOLROW>(nRow1), static_cast<SCCOLROW>(nRow2));
+}
+
+bool ScFlatUInt16RowSegments::getRangeData(SCROW nRow, RangeData& rData)
+{
+ ScFlatUInt16SegmentsImpl::RangeData aData;
+ if (!mpImpl->getRangeData(static_cast<SCCOLROW>(nRow), aData))
+ return false;
+
+ rData.mnRow1 = aData.mnPos1;
+ rData.mnRow2 = aData.mnPos2;
+ rData.mnValue = aData.mnValue;
+ return true;
+}
+
+void ScFlatUInt16RowSegments::removeSegment(SCROW nRow1, SCROW nRow2)
+{
+ mpImpl->removeSegment(static_cast<SCCOLROW>(nRow1), static_cast<SCCOLROW>(nRow2));
+}
+
+void ScFlatUInt16RowSegments::insertSegment(SCROW nRow, SCROW nSize)
+{
+ mpImpl->insertSegment(static_cast<SCCOLROW>(nRow), static_cast<SCCOLROW>(nSize), false/*bSkipStartBoundary*/);
+}
+
+SCROW ScFlatUInt16RowSegments::findLastTrue(sal_uInt16 nValue) const
+{
+ return mpImpl->findLastTrue(nValue);
+}
+
+void ScFlatUInt16RowSegments::enableTreeSearch(bool bEnable)
+{
+ mpImpl->enableTreeSearch(bEnable);
+}
+
+void ScFlatUInt16RowSegments::setValueIf(SCROW nRow1, SCROW nRow2, sal_uInt16 nValue, const std::function<bool(sal_uInt16)>& rPredicate)
+{
+ mpImpl->setValueIf(static_cast<SCCOLROW>(nRow1), static_cast<SCCOLROW>(nRow2), nValue, rPredicate);
+}
+
+void ScFlatUInt16RowSegments::makeReady()
+{
+ mpImpl->makeReady();
+}
+
+OString ScFlatUInt16RowSegments::dumpAsString()
+{
+ OString aOutput;
+ OString aSegment;
+ RangeData aRange;
+ SCROW nRow = 0;
+ while (getRangeData(nRow, aRange))
+ {
+ aSegment = OString::number(aRange.mnValue) + ":" +
+ OString::number(aRange.mnRow2) + " ";
+ aOutput += aSegment;
+ nRow = aRange.mnRow2 + 1;
+ }
+
+ return aOutput;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sc/source/core/data/sheetevents.cxx b/sc/source/core/data/sheetevents.cxx
new file mode 100644
index 000000000..7d2152226
--- /dev/null
+++ b/sc/source/core/data/sheetevents.cxx
@@ -0,0 +1,123 @@
+/* -*- 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 <sheetevents.hxx>
+#include <com/sun/star/script/vba/VBAEventId.hpp>
+#include <optional>
+
+OUString ScSheetEvents::GetEventName(ScSheetEventId nEvent)
+{
+ static const char* aEventNames[] =
+ {
+ "OnFocus", // SC_SHEETEVENT_FOCUS
+ "OnUnfocus", // SC_SHEETEVENT_UNFOCUS
+ "OnSelect", // SC_SHEETEVENT_SELECT
+ "OnDoubleClick", // SC_SHEETEVENT_DOUBLECLICK
+ "OnRightClick", // SC_SHEETEVENT_RIGHTCLICK
+ "OnChange", // SC_SHEETEVENT_CHANGE
+ "OnCalculate" // SC_SHEETEVENT_CALCULATE
+ };
+ return OUString::createFromAscii(aEventNames[static_cast<int>(nEvent)]);
+}
+
+sal_Int32 ScSheetEvents::GetVbaSheetEventId(ScSheetEventId nEvent)
+{
+ using namespace ::com::sun::star::script::vba::VBAEventId;
+
+ static const sal_Int32 nVbaEventIds[] =
+ {
+ WORKSHEET_ACTIVATE, // SC_SHEETEVENT_FOCUS
+ WORKSHEET_DEACTIVATE, // SC_SHEETEVENT_UNFOCUS
+ WORKSHEET_SELECTIONCHANGE, // SC_SHEETEVENT_SELECT
+ WORKSHEET_BEFOREDOUBLECLICK, // SC_SHEETEVENT_DOUBLECLICK
+ WORKSHEET_BEFORERIGHTCLICK, // SC_SHEETEVENT_RIGHTCLICK
+ WORKSHEET_CHANGE, // SC_SHEETEVENT_CHANGE
+ WORKSHEET_CALCULATE // SC_SHEETEVENT_CALCULATE
+ };
+ return nVbaEventIds[static_cast<int>(nEvent)];
+}
+
+const int COUNT = static_cast<int>(ScSheetEventId::COUNT);
+
+sal_Int32 ScSheetEvents::GetVbaDocumentEventId(ScSheetEventId nEvent)
+{
+ using namespace ::com::sun::star::script::vba::VBAEventId;
+ sal_Int32 nSheetEventId = GetVbaSheetEventId(nEvent);
+ return (nSheetEventId != NO_EVENT) ? (nSheetEventId + USERDEFINED_START) : NO_EVENT;
+}
+
+ScSheetEvents::ScSheetEvents()
+{
+}
+
+ScSheetEvents::~ScSheetEvents()
+{
+ Clear();
+}
+
+void ScSheetEvents::Clear()
+{
+ mpScriptNames.reset();
+}
+
+ScSheetEvents::ScSheetEvents(const ScSheetEvents& rOther)
+{
+ *this = rOther;
+}
+
+ScSheetEvents& ScSheetEvents::operator=(const ScSheetEvents& rOther)
+{
+ if (this != &rOther)
+ {
+ Clear();
+ if (rOther.mpScriptNames)
+ {
+ mpScriptNames.reset( new std::optional<OUString>[COUNT] );
+ for (sal_Int32 nEvent=0; nEvent<COUNT; ++nEvent)
+ mpScriptNames[nEvent] = rOther.mpScriptNames[nEvent];
+ }
+ }
+ return *this;
+}
+
+const OUString* ScSheetEvents::GetScript(ScSheetEventId nEvent) const
+{
+ if (mpScriptNames)
+ {
+ std::optional<OUString> const & r = mpScriptNames[static_cast<int>(nEvent)];
+ if (r)
+ return &*r;
+ }
+ return nullptr;
+}
+
+void ScSheetEvents::SetScript(ScSheetEventId eEvent, const OUString* pNew)
+{
+ int nEvent = static_cast<int>(eEvent);
+ if (!mpScriptNames)
+ {
+ mpScriptNames.reset( new std::optional<OUString>[COUNT] );
+ }
+ if (pNew)
+ mpScriptNames[nEvent] = *pNew;
+ else
+ mpScriptNames[nEvent].reset();
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sc/source/core/data/simpleformulacalc.cxx b/sc/source/core/data/simpleformulacalc.cxx
new file mode 100644
index 000000000..102373d5a
--- /dev/null
+++ b/sc/source/core/data/simpleformulacalc.cxx
@@ -0,0 +1,155 @@
+/* -*- 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/.
+ */
+
+#include <memory>
+#include <simpleformulacalc.hxx>
+#include <document.hxx>
+#include <tokenarray.hxx>
+#include <interpre.hxx>
+#include <compiler.hxx>
+#include <sfx2/linkmgr.hxx>
+
+#define DISPLAY_LEN 66
+
+ScSimpleFormulaCalculator::ScSimpleFormulaCalculator( ScDocument& rDoc, const ScAddress& rAddr,
+ const OUString& rFormula, bool bMatrixFormula, formula::FormulaGrammar::Grammar eGram )
+ : mnFormatType(SvNumFormatType::ALL)
+ , mbCalculated(false)
+ , maAddr(rAddr)
+ , mrDoc(rDoc)
+ , maGram(eGram)
+ , mbMatrixResult(false)
+ , mbLimitString(false)
+ , mbMatrixFormula(bMatrixFormula)
+{
+ // compile already here
+ ScCompiler aComp(mrDoc, maAddr, eGram, true, bMatrixFormula);
+ mpCode = aComp.CompileString(rFormula);
+ if(mpCode->GetCodeError() == FormulaError::NONE && mpCode->GetLen())
+ aComp.CompileTokenArray();
+}
+
+ScSimpleFormulaCalculator::~ScSimpleFormulaCalculator()
+{
+}
+
+void ScSimpleFormulaCalculator::Calculate()
+{
+ if(mbCalculated)
+ return;
+
+ mbCalculated = true;
+
+ ScInterpreter aInt(mrDoc.GetFormulaCell( maAddr ), mrDoc, mrDoc.GetNonThreadedContext(), maAddr, *mpCode);
+ if (mbMatrixFormula)
+ aInt.AssertFormulaMatrix();
+
+ sfx2::LinkManager aNewLinkMgr( mrDoc.GetDocumentShell() );
+ aInt.SetLinkManager( &aNewLinkMgr );
+
+ formula::StackVar aIntType = aInt.Interpret();
+ if ( aIntType == formula::svMatrixCell )
+ {
+ ScCompiler aComp(mrDoc, maAddr, maGram);
+ OUStringBuffer aStr;
+ aComp.CreateStringFromToken(aStr, aInt.GetResultToken().get());
+
+ mbMatrixResult = true;
+
+ if (mbLimitString)
+ {
+ const sal_Unicode cCol = ScCompiler::GetNativeSymbol(ocArrayColSep)[0];
+ const sal_Unicode cRow = ScCompiler::GetNativeSymbol(ocArrayRowSep)[0];
+ const sal_Int32 n = aStr.getLength();
+ for (sal_Int32 i = DISPLAY_LEN; i < n; ++i)
+ {
+ const sal_Unicode c = aStr[i];
+ if (c == cCol || c == cRow)
+ {
+ aStr.truncate(i+1);
+ aStr.append("...");
+ break;
+ }
+ }
+ }
+
+ maMatrixFormulaResult = aStr.makeStringAndClear();
+ }
+ mnFormatType = aInt.GetRetFormatType();
+ maResult.SetToken(aInt.GetResultToken().get());
+}
+
+bool ScSimpleFormulaCalculator::IsValue()
+{
+ Calculate();
+
+ if (mbMatrixResult)
+ return false;
+
+ return maResult.IsValue();
+}
+
+bool ScSimpleFormulaCalculator::IsMatrix()
+{
+ Calculate();
+
+ return mbMatrixResult;
+}
+
+FormulaError ScSimpleFormulaCalculator::GetErrCode()
+{
+ Calculate();
+
+ FormulaError nErr = mpCode->GetCodeError();
+ if (nErr != FormulaError::NONE)
+ return nErr;
+ return maResult.GetResultError();
+}
+
+double ScSimpleFormulaCalculator::GetValue()
+{
+ Calculate();
+
+ if ((mpCode->GetCodeError() == FormulaError::NONE) &&
+ maResult.GetResultError() == FormulaError::NONE)
+ return maResult.GetDouble();
+
+ return 0.0;
+}
+
+svl::SharedString ScSimpleFormulaCalculator::GetString()
+{
+ Calculate();
+
+ if (mbMatrixResult)
+ return svl::SharedString( maMatrixFormulaResult); // string not interned
+
+ if ((mpCode->GetCodeError() == FormulaError::NONE) &&
+ maResult.GetResultError() == FormulaError::NONE)
+ return maResult.GetString();
+
+ return svl::SharedString::getEmptyString();
+}
+
+bool ScSimpleFormulaCalculator::HasColRowName() const
+{
+ return formula::FormulaTokenArrayPlainIterator(*mpCode).GetNextColRowName() != nullptr;
+}
+
+ScTokenArray* ScSimpleFormulaCalculator::GetCode()
+{
+ return mpCode.get();
+}
+
+void ScSimpleFormulaCalculator::SetLimitString(bool bLimitString)
+{
+ mbLimitString = bLimitString;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sc/source/core/data/sortparam.cxx b/sc/source/core/data/sortparam.cxx
new file mode 100644
index 000000000..cb369baae
--- /dev/null
+++ b/sc/source/core/data/sortparam.cxx
@@ -0,0 +1,306 @@
+/* -*- 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 <sortparam.hxx>
+#include <global.hxx>
+#include <address.hxx>
+#include <queryparam.hxx>
+#include <subtotalparam.hxx>
+
+#include <osl/diagnose.h>
+
+#include <algorithm>
+
+ScSortParam::ScSortParam()
+{
+ Clear();
+}
+
+ScSortParam::ScSortParam( const ScSortParam& r ) :
+ nCol1(r.nCol1),nRow1(r.nRow1),nCol2(r.nCol2),nRow2(r.nRow2),
+ aDataAreaExtras(r.aDataAreaExtras),
+ nUserIndex(r.nUserIndex),
+ bHasHeader(r.bHasHeader),bByRow(r.bByRow),bCaseSens(r.bCaseSens),
+ bNaturalSort(r.bNaturalSort),
+ bUserDef(r.bUserDef),
+ bInplace(r.bInplace),
+ nDestTab(r.nDestTab),nDestCol(r.nDestCol),nDestRow(r.nDestRow),
+ maKeyState( r.maKeyState ),
+ aCollatorLocale( r.aCollatorLocale ), aCollatorAlgorithm( r.aCollatorAlgorithm ),
+ nCompatHeader( r.nCompatHeader )
+{
+}
+
+ScSortParam::~ScSortParam() {}
+
+void ScSortParam::Clear()
+{
+ ScSortKeyState aKeyState;
+
+ nCol1=nCol2=nDestCol = 0;
+ nRow1=nRow2=nDestRow = 0;
+ aDataAreaExtras = ScDataAreaExtras();
+ aDataAreaExtras.mbCellDrawObjects = true;
+ aDataAreaExtras.mbCellFormats = true;
+ nCompatHeader = 2;
+ nDestTab = 0;
+ nUserIndex = 0;
+ bHasHeader=bCaseSens=bUserDef=bNaturalSort = false;
+ bByRow = bInplace = true;
+ aCollatorLocale = css::lang::Locale();
+ aCollatorAlgorithm.clear();
+
+ aKeyState.bDoSort = false;
+ aKeyState.nField = 0;
+ aKeyState.bAscending = true;
+
+ // Initialize to default size
+ maKeyState.assign( DEFSORT, aKeyState );
+}
+
+ScSortParam& ScSortParam::operator=( const ScSortParam& r )
+{
+ nCol1 = r.nCol1;
+ nRow1 = r.nRow1;
+ nCol2 = r.nCol2;
+ nRow2 = r.nRow2;
+ aDataAreaExtras = r.aDataAreaExtras;
+ nUserIndex = r.nUserIndex;
+ bHasHeader = r.bHasHeader;
+ bByRow = r.bByRow;
+ bCaseSens = r.bCaseSens;
+ bNaturalSort = r.bNaturalSort;
+ bUserDef = r.bUserDef;
+ bInplace = r.bInplace;
+ nDestTab = r.nDestTab;
+ nDestCol = r.nDestCol;
+ nDestRow = r.nDestRow;
+ maKeyState = r.maKeyState;
+ aCollatorLocale = r.aCollatorLocale;
+ aCollatorAlgorithm = r.aCollatorAlgorithm;
+ nCompatHeader = r.nCompatHeader;
+
+ return *this;
+}
+
+bool ScSortParam::operator==( const ScSortParam& rOther ) const
+{
+ bool bEqual = false;
+ // Number of Sorts the same?
+ sal_uInt16 nLast = 0;
+ sal_uInt16 nOtherLast = 0;
+ sal_uInt16 nSortSize = GetSortKeyCount();
+
+ if ( !maKeyState.empty() )
+ {
+ while ( maKeyState[nLast++].bDoSort && nLast < nSortSize ) ;
+ nLast--;
+ }
+
+ if ( !rOther.maKeyState.empty() )
+ {
+ while ( rOther.maKeyState[nOtherLast++].bDoSort && nOtherLast < nSortSize ) ;
+ nOtherLast--;
+ }
+
+ if ( (nLast == nOtherLast)
+ && (nCol1 == rOther.nCol1)
+ && (nRow1 == rOther.nRow1)
+ && (nCol2 == rOther.nCol2)
+ && (nRow2 == rOther.nRow2)
+ && (aDataAreaExtras == rOther.aDataAreaExtras)
+ && (bHasHeader == rOther.bHasHeader)
+ && (bByRow == rOther.bByRow)
+ && (bCaseSens == rOther.bCaseSens)
+ && (bNaturalSort == rOther.bNaturalSort)
+ && (bUserDef == rOther.bUserDef)
+ && (nUserIndex == rOther.nUserIndex)
+ && (bInplace == rOther.bInplace)
+ && (nDestTab == rOther.nDestTab)
+ && (nDestCol == rOther.nDestCol)
+ && (nDestRow == rOther.nDestRow)
+ && (aCollatorLocale.Language == rOther.aCollatorLocale.Language)
+ && (aCollatorLocale.Country == rOther.aCollatorLocale.Country)
+ && (aCollatorLocale.Variant == rOther.aCollatorLocale.Variant)
+ && (aCollatorAlgorithm == rOther.aCollatorAlgorithm)
+ && ( !maKeyState.empty() || !rOther.maKeyState.empty() )
+ )
+ {
+ bEqual = true;
+ for ( sal_uInt16 i=0; i<=nLast && bEqual; i++ )
+ bEqual = ( maKeyState[i].nField == rOther.maKeyState[i].nField ) &&
+ ( maKeyState[i].bAscending == rOther.maKeyState[i].bAscending );
+ }
+ if ( maKeyState.empty() && rOther.maKeyState.empty() )
+ bEqual = true;
+
+ return bEqual;
+}
+
+ScSortParam::ScSortParam( const ScSubTotalParam& rSub, const ScSortParam& rOld ) :
+ nCol1(rSub.nCol1),nRow1(rSub.nRow1),nCol2(rSub.nCol2),nRow2(rSub.nRow2),
+ aDataAreaExtras(rOld.aDataAreaExtras),
+ nUserIndex(rSub.nUserIndex),
+ bHasHeader(true),bByRow(true),bCaseSens(rSub.bCaseSens),bNaturalSort(rOld.bNaturalSort),
+ bUserDef(rSub.bUserDef),
+ bInplace(true),
+ nDestTab(0),nDestCol(0),nDestRow(0),
+ aCollatorLocale( rOld.aCollatorLocale ), aCollatorAlgorithm( rOld.aCollatorAlgorithm ),
+ nCompatHeader( rOld.nCompatHeader )
+{
+ aDataAreaExtras.mbCellFormats = rSub.bIncludePattern;
+ aDataAreaExtras.resetArea();
+
+ sal_uInt16 i;
+
+ // first the groups from the partial results
+ if (rSub.bDoSort)
+ for (i=0; i<MAXSUBTOTAL; i++)
+ if (rSub.bGroupActive[i])
+ {
+ ScSortKeyState key;
+ key.bDoSort = true;
+ key.nField = rSub.nField[i];
+ key.bAscending = rSub.bAscending;
+ maKeyState.push_back(key);
+ }
+
+ // then the old settings
+ for (i=0; i < rOld.GetSortKeyCount(); i++)
+ if (rOld.maKeyState[i].bDoSort)
+ {
+ SCCOLROW nThisField = rOld.maKeyState[i].nField;
+ bool bDouble = false;
+ for (sal_uInt16 j = 0; j < GetSortKeyCount(); j++)
+ if ( maKeyState[j].nField == nThisField )
+ bDouble = true;
+ if (!bDouble) // do not enter a field twice
+ {
+ ScSortKeyState key;
+ key.bDoSort = true;
+ key.nField = nThisField;
+ key.bAscending = rOld.maKeyState[i].bAscending;
+ maKeyState.push_back(key);
+ }
+ }
+}
+
+ScSortParam::ScSortParam( const ScQueryParam& rParam, SCCOL nCol ) :
+ nCol1(nCol),nRow1(rParam.nRow1),nCol2(nCol),nRow2(rParam.nRow2),nUserIndex(0),
+ bHasHeader(rParam.bHasHeader),bByRow(true),bCaseSens(rParam.bCaseSens),
+ bNaturalSort(false),
+//TODO: what about Locale and Algorithm?
+ bUserDef(false),
+ bInplace(true),
+ nDestTab(0),nDestCol(0),nDestRow(0), nCompatHeader(2)
+{
+ aDataAreaExtras.mbCellDrawObjects = true;
+
+ ScSortKeyState aKeyState;
+ aKeyState.bDoSort = true;
+ aKeyState.nField = nCol;
+ aKeyState.bAscending = true;
+
+ maKeyState.push_back( aKeyState );
+
+ // Set the rest
+ aKeyState.bDoSort = false;
+ aKeyState.nField = 0;
+
+ for (sal_uInt16 i=1; i<GetSortKeyCount(); i++)
+ maKeyState.push_back( aKeyState );
+}
+
+void ScSortParam::MoveToDest()
+{
+ if (!bInplace)
+ {
+ SCCOL nDifX = nDestCol - nCol1;
+ SCROW nDifY = nDestRow - nRow1;
+
+ nCol1 = sal::static_int_cast<SCCOL>( nCol1 + nDifX );
+ nRow1 = sal::static_int_cast<SCROW>( nRow1 + nDifY );
+ nCol2 = sal::static_int_cast<SCCOL>( nCol2 + nDifX );
+ nRow2 = sal::static_int_cast<SCROW>( nRow2 + nDifY );
+ for (sal_uInt16 i=0; i<GetSortKeyCount(); i++)
+ if (bByRow)
+ maKeyState[i].nField += nDifX;
+ else
+ maKeyState[i].nField += nDifY;
+
+ bInplace = true;
+ }
+ else
+ {
+ OSL_FAIL("MoveToDest, bInplace == TRUE");
+ }
+}
+
+namespace sc {
+
+namespace {
+
+struct ReorderIndex
+{
+ struct LessByPos2
+ {
+ bool operator() ( const ReorderIndex& r1, const ReorderIndex& r2 ) const
+ {
+ return r1.mnPos2 < r2.mnPos2;
+ }
+ };
+
+ SCCOLROW mnPos1;
+ SCCOLROW mnPos2;
+
+ ReorderIndex( SCCOLROW nPos1, SCCOLROW nPos2 ) : mnPos1(nPos1), mnPos2(nPos2) {}
+};
+
+}
+
+void ReorderParam::reverse()
+{
+ SCCOLROW nStart;
+ if (mbByRow)
+ nStart = maSortRange.aStart.Row();
+ else
+ nStart = maSortRange.aStart.Col();
+
+ size_t n = maOrderIndices.size();
+ std::vector<ReorderIndex> aBucket;
+ aBucket.reserve(n);
+ for (size_t i = 0; i < n; ++i)
+ {
+ SCCOLROW nPos1 = i + nStart;
+ SCCOLROW nPos2 = maOrderIndices[i];
+ aBucket.emplace_back(nPos1, nPos2);
+ }
+
+ std::sort(aBucket.begin(), aBucket.end(), ReorderIndex::LessByPos2());
+ std::vector<SCCOLROW> aNew;
+ aNew.reserve(n);
+ for (size_t i = 0; i < n; ++i)
+ aNew.push_back(aBucket[i].mnPos1);
+
+ maOrderIndices.swap(aNew);
+}
+
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sc/source/core/data/stlpool.cxx b/sc/source/core/data/stlpool.cxx
new file mode 100644
index 000000000..8f554896f
--- /dev/null
+++ b/sc/source/core/data/stlpool.cxx
@@ -0,0 +1,447 @@
+/* -*- 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 <memory>
+#include <scitems.hxx>
+#include <editeng/eeitem.hxx>
+#include <i18nlangtag/mslangid.hxx>
+#include <editeng/borderline.hxx>
+#include <editeng/boxitem.hxx>
+#include <editeng/brushitem.hxx>
+#include <editeng/editdata.hxx>
+#include <editeng/editeng.hxx>
+#include <editeng/editobj.hxx>
+#include <editeng/flditem.hxx>
+#include <editeng/fontitem.hxx>
+#include <svx/pageitem.hxx>
+#include <svl/itemset.hxx>
+#include <svl/zforlist.hxx>
+#include <svl/IndexedStyleSheets.hxx>
+#include <unotools/charclass.hxx>
+#include <vcl/outdev.hxx>
+#include <vcl/svapp.hxx>
+#include <vcl/settings.hxx>
+#include <osl/diagnose.h>
+
+#include <sc.hrc>
+#include <attrib.hxx>
+#include <global.hxx>
+#include <globstr.hrc>
+#include <scresid.hxx>
+#include <document.hxx>
+#include <docpool.hxx>
+#include <stlpool.hxx>
+#include <stlsheet.hxx>
+#include <editutil.hxx>
+#include <stylehelper.hxx>
+
+ScStyleSheetPool::ScStyleSheetPool( const SfxItemPool& rPoolP,
+ ScDocument* pDocument )
+ : SfxStyleSheetPool( rPoolP ),
+ pActualStyleSheet( nullptr ),
+ pDoc( pDocument ),
+ bHasStandardStyles( false )
+{
+}
+
+ScStyleSheetPool::~ScStyleSheetPool()
+{
+}
+
+void ScStyleSheetPool::SetDocument( ScDocument* pDocument )
+{
+ pDoc = pDocument;
+}
+
+SfxStyleSheetBase& ScStyleSheetPool::Make( const OUString& rName,
+ SfxStyleFamily eFam, SfxStyleSearchBits mask)
+{
+ if ( rName == STRING_STANDARD && Find( rName, eFam ) != nullptr )
+ {
+ // When updating styles from a template, Office 5.1 sometimes created
+ // files with multiple default styles.
+ // Create new styles in that case:
+
+ //TODO: only when loading?
+
+ OSL_FAIL("renaming additional default style");
+ sal_uInt32 nCount = GetIndexedStyleSheets().GetNumberOfStyleSheets();
+ for ( sal_uInt32 nAdd = 1; nAdd <= nCount; nAdd++ )
+ {
+ OUString aNewName = ScResId(STR_STYLENAME_STANDARD) + OUString::number( nAdd );
+ if ( Find( aNewName, eFam ) == nullptr )
+ return SfxStyleSheetPool::Make(aNewName, eFam, mask);
+ }
+ }
+
+ // Core uses translated names for both naming and display.
+ // This for all three, loading standard builtin styles from styles.xml
+ // configuration, loading documents and updating from templates.
+ return SfxStyleSheetPool::Make( ScStyleNameConversion::ProgrammaticToDisplayName( rName, eFam), eFam, mask);
+}
+
+rtl::Reference<SfxStyleSheetBase> ScStyleSheetPool::Create( const OUString& rName,
+ SfxStyleFamily eFamily,
+ SfxStyleSearchBits nMaskP )
+{
+ rtl::Reference<ScStyleSheet> pSheet = new ScStyleSheet( rName, *this, eFamily, nMaskP );
+ if ( eFamily == SfxStyleFamily::Para && ScResId(STR_STYLENAME_STANDARD) != rName )
+ pSheet->SetParent( ScResId(STR_STYLENAME_STANDARD) );
+
+ return pSheet;
+}
+
+rtl::Reference<SfxStyleSheetBase> ScStyleSheetPool::Create( const SfxStyleSheetBase& rStyle )
+{
+ OSL_ENSURE( rStyle.isScStyleSheet(), "Invalid StyleSheet-class! :-/" );
+ return new ScStyleSheet( static_cast<const ScStyleSheet&>(rStyle) );
+}
+
+void ScStyleSheetPool::Remove( SfxStyleSheetBase* pStyle )
+{
+ if ( pStyle )
+ {
+ OSL_ENSURE( SfxStyleSearchBits::UserDefined & pStyle->GetMask(),
+ "SfxStyleSearchBits::UserDefined not set!" );
+
+ static_cast<ScDocumentPool&>(rPool).StyleDeleted(static_cast<ScStyleSheet*>(pStyle));
+ SfxStyleSheetPool::Remove(pStyle);
+ }
+}
+
+void ScStyleSheetPool::CopyStyleFrom( ScStyleSheetPool* pSrcPool,
+ const OUString& rName, SfxStyleFamily eFamily )
+{
+ // this is the Dest-Pool
+
+ SfxStyleSheetBase* pStyleSheet = pSrcPool->Find( rName, eFamily );
+ if (!pStyleSheet)
+ return;
+
+ const SfxItemSet& rSourceSet = pStyleSheet->GetItemSet();
+ SfxStyleSheetBase* pDestSheet = Find( rName, eFamily );
+ if (!pDestSheet)
+ pDestSheet = &Make( rName, eFamily );
+ SfxItemSet& rDestSet = pDestSheet->GetItemSet();
+ rDestSet.PutExtended( rSourceSet, SfxItemState::DONTCARE, SfxItemState::DEFAULT );
+
+ if ( eFamily == SfxStyleFamily::Page )
+ {
+ // Set-Items
+
+ if ( const SvxSetItem* pSetItem = rSourceSet.GetItemIfSet( ATTR_PAGE_HEADERSET, false ) )
+ {
+ const SfxItemSet& rSrcSub = pSetItem->GetItemSet();
+ SfxItemSet aDestSub( *rDestSet.GetPool(), rSrcSub.GetRanges() );
+ aDestSub.PutExtended( rSrcSub, SfxItemState::DONTCARE, SfxItemState::DEFAULT );
+ }
+ if ( const SvxSetItem* pSetItem = rSourceSet.GetItemIfSet( ATTR_PAGE_FOOTERSET, false ) )
+ {
+ const SfxItemSet& rSrcSub = pSetItem->GetItemSet();
+ SfxItemSet aDestSub( *rDestSet.GetPool(), rSrcSub.GetRanges() );
+ aDestSub.PutExtended( rSrcSub, SfxItemState::DONTCARE, SfxItemState::DEFAULT );
+ rDestSet.Put( SvxSetItem( ATTR_PAGE_FOOTERSET, aDestSub ) );
+ }
+ }
+ else // cell styles
+ {
+ // number format exchange list has to be handled here, too
+
+ const SfxUInt32Item* pItem;
+ if ( pDoc && pDoc->GetFormatExchangeList() &&
+ (pItem = rSourceSet.GetItemIfSet( ATTR_VALUE_FORMAT, false )) )
+ {
+ sal_uLong nOldFormat = pItem->GetValue();
+ SvNumberFormatterIndexTable::const_iterator it = pDoc->GetFormatExchangeList()->find(nOldFormat);
+ if (it != pDoc->GetFormatExchangeList()->end())
+ {
+ sal_uInt32 nNewFormat = it->second;
+ rDestSet.Put( SfxUInt32Item( ATTR_VALUE_FORMAT, nNewFormat ) );
+ }
+ }
+ }
+}
+
+// Standard templates
+
+void ScStyleSheetPool::CopyStdStylesFrom( ScStyleSheetPool* pSrcPool )
+{
+ // Copy Default styles
+
+ CopyStyleFrom( pSrcPool, ScResId(STR_STYLENAME_STANDARD), SfxStyleFamily::Para );
+ CopyStyleFrom( pSrcPool, ScResId(STR_STYLENAME_STANDARD), SfxStyleFamily::Page );
+ CopyStyleFrom( pSrcPool, ScResId(STR_STYLENAME_REPORT), SfxStyleFamily::Page );
+}
+
+static void lcl_CheckFont( SfxItemSet& rSet, LanguageType eLang, DefaultFontType nFontType, sal_uInt16 nItemId )
+{
+ if ( eLang != LANGUAGE_NONE && eLang != LANGUAGE_DONTKNOW && eLang != LANGUAGE_SYSTEM )
+ {
+ vcl::Font aDefFont = OutputDevice::GetDefaultFont( nFontType, eLang, GetDefaultFontFlags::OnlyOne );
+ SvxFontItem aNewItem( aDefFont.GetFamilyType(), aDefFont.GetFamilyName(), aDefFont.GetStyleName(),
+ aDefFont.GetPitch(), aDefFont.GetCharSet(), nItemId );
+ if ( aNewItem != rSet.Get( nItemId ) )
+ {
+ // put item into style's ItemSet only if different from (static) default
+ rSet.Put( aNewItem );
+ }
+ }
+}
+
+void ScStyleSheetPool::CreateStandardStyles()
+{
+ // Add new entries even for CopyStdStylesFrom
+
+ Color aColBlack ( COL_BLACK );
+ OUString aStr;
+ sal_Int32 nStrLen;
+ const OUString aHelpFile;//which text???
+ SfxItemSet* pSet = nullptr;
+ SfxItemSet* pHFSet = nullptr;
+ ScEditEngineDefaulter aEdEngine( EditEngine::CreatePool().get(), true );
+ aEdEngine.SetUpdateLayout( false );
+ std::unique_ptr<EditTextObject> pEmptyTxtObj = aEdEngine.CreateTextObject();
+ std::unique_ptr<EditTextObject> pTxtObj;
+ ScPageHFItem aHeaderItem( ATTR_PAGE_HEADERRIGHT );
+ ScPageHFItem aFooterItem( ATTR_PAGE_FOOTERRIGHT );
+ ScStyleSheet* pSheet = nullptr;
+ ::editeng::SvxBorderLine aBorderLine ( &aColBlack, SvxBorderLineWidth::Medium );
+ SvxBoxItem aBoxItem ( ATTR_BORDER );
+ SvxBoxInfoItem aBoxInfoItem ( ATTR_BORDER_INNER );
+
+ OUString aStrStandard = ScResId(STR_STYLENAME_STANDARD);
+
+ // Cell format templates:
+
+ // 1. Standard
+
+ pSheet = static_cast<ScStyleSheet*>( &Make( aStrStandard, SfxStyleFamily::Para, SfxStyleSearchBits::ScStandard ) );
+ pSheet->SetHelpId( aHelpFile, HID_SC_SHEET_CELL_STD );
+
+ // if default fonts for the document's languages are different from the pool default,
+ // put them into the default style
+ // (not as pool defaults, because pool defaults can't be changed by the user)
+ // the document languages must be set before creating the default styles!
+
+ pSet = &pSheet->GetItemSet();
+ LanguageType eLatin, eCjk, eCtl;
+ pDoc->GetLanguage( eLatin, eCjk, eCtl );
+
+ // If the UI language is Korean, the default Latin font has to
+ // be queried for Korean, too (the Latin language from the document can't be Korean).
+ // This is the same logic as in SwDocShell::InitNew.
+ LanguageType eUiLanguage = Application::GetSettings().GetUILanguageTag().getLanguageType();
+ if (MsLangId::isKorean(eUiLanguage))
+ eLatin = eUiLanguage;
+
+ lcl_CheckFont( *pSet, eLatin, DefaultFontType::LATIN_SPREADSHEET, ATTR_FONT );
+ lcl_CheckFont( *pSet, eCjk, DefaultFontType::CJK_SPREADSHEET, ATTR_CJK_FONT );
+ lcl_CheckFont( *pSet, eCtl, DefaultFontType::CTL_SPREADSHEET, ATTR_CTL_FONT );
+
+ // #i55300# default CTL font size for Thai has to be larger
+ // #i59408# The 15 point size causes problems with row heights, so no different
+ // size is used for Thai in Calc for now.
+// if ( eCtl == LANGUAGE_THAI )
+// pSet->Put( SvxFontHeightItem( 300, 100, ATTR_CTL_FONT_HEIGHT ) ); // 15 pt
+
+ // Page format template:
+
+ // 1. Standard
+
+ pSheet = static_cast<ScStyleSheet*>( &Make( aStrStandard,
+ SfxStyleFamily::Page,
+ SfxStyleSearchBits::ScStandard ) );
+
+ pSet = &pSheet->GetItemSet();
+ pSheet->SetHelpId( aHelpFile, HID_SC_SHEET_PAGE_STD );
+
+ // distance to header/footer for the sheet
+ SvxSetItem aHFSetItem = pSet->Get( ATTR_PAGE_HEADERSET );
+ aHFSetItem.SetWhich(ATTR_PAGE_HEADERSET);
+ pSet->Put( aHFSetItem );
+ aHFSetItem.SetWhich(ATTR_PAGE_FOOTERSET);
+ pSet->Put( aHFSetItem );
+
+ // Header:
+ // [empty][\sheet\][empty]
+
+ aEdEngine.SetTextCurrentDefaults(OUString());
+ aEdEngine.QuickInsertField( SvxFieldItem(SvxTableField(), EE_FEATURE_FIELD), ESelection() );
+ pTxtObj = aEdEngine.CreateTextObject();
+ aHeaderItem.SetLeftArea ( *pEmptyTxtObj );
+ aHeaderItem.SetCenterArea( *pTxtObj );
+ aHeaderItem.SetRightArea ( *pEmptyTxtObj );
+ pSet->Put( aHeaderItem );
+
+ // Footer:
+ // [empty][Page \STR_PAGE\][empty]
+
+ aStr = ScResId( STR_PAGE ) + " ";
+ aEdEngine.SetTextCurrentDefaults( aStr );
+ nStrLen = aStr.getLength();
+ aEdEngine.QuickInsertField( SvxFieldItem(SvxPageField(), EE_FEATURE_FIELD), ESelection(0,nStrLen,0,nStrLen) );
+ pTxtObj = aEdEngine.CreateTextObject();
+ aFooterItem.SetLeftArea ( *pEmptyTxtObj );
+ aFooterItem.SetCenterArea( *pTxtObj );
+ aFooterItem.SetRightArea ( *pEmptyTxtObj );
+ pSet->Put( aFooterItem );
+
+ // 2. Report
+
+ pSheet = static_cast<ScStyleSheet*>( &Make( ScResId( STR_STYLENAME_REPORT ),
+ SfxStyleFamily::Page,
+ SfxStyleSearchBits::ScStandard ) );
+ pSet = &pSheet->GetItemSet();
+ pSheet->SetHelpId( aHelpFile, HID_SC_SHEET_PAGE_REP );
+
+ // Background and border
+ aBoxItem.SetLine( &aBorderLine, SvxBoxItemLine::TOP );
+ aBoxItem.SetLine( &aBorderLine, SvxBoxItemLine::BOTTOM );
+ aBoxItem.SetLine( &aBorderLine, SvxBoxItemLine::LEFT );
+ aBoxItem.SetLine( &aBorderLine, SvxBoxItemLine::RIGHT );
+ aBoxItem.SetAllDistances( 10 ); // 0.2mm
+ aBoxInfoItem.SetValid( SvxBoxInfoItemValidFlags::TOP );
+ aBoxInfoItem.SetValid( SvxBoxInfoItemValidFlags::BOTTOM );
+ aBoxInfoItem.SetValid( SvxBoxInfoItemValidFlags::LEFT );
+ aBoxInfoItem.SetValid( SvxBoxInfoItemValidFlags::RIGHT );
+ aBoxInfoItem.SetValid( SvxBoxInfoItemValidFlags::DISTANCE );
+ aBoxInfoItem.SetTable( false );
+ aBoxInfoItem.SetDist ( true );
+
+ SvxSetItem aHFSetItem2 = pSet->Get( ATTR_PAGE_HEADERSET );
+ pHFSet = &(aHFSetItem2.GetItemSet());
+
+ pHFSet->Put( SvxBrushItem( COL_LIGHTGRAY, ATTR_BACKGROUND ) );
+ pHFSet->Put( aBoxItem );
+ pHFSet->Put( aBoxInfoItem );
+ aHFSetItem2.SetWhich(ATTR_PAGE_HEADERSET);
+ pSet->Put( aHFSetItem2 );
+ aHFSetItem2.SetWhich(ATTR_PAGE_FOOTERSET);
+ pSet->Put( aHFSetItem2 );
+
+ // Footer:
+ // [\TABLE\ (\DATA\)][empty][\DATE\, \TIME\]
+
+ aStr = " ()";
+ aEdEngine.SetTextCurrentDefaults( aStr );
+ aEdEngine.QuickInsertField( SvxFieldItem(SvxFileField(), EE_FEATURE_FIELD), ESelection(0,2,0,2) );
+ aEdEngine.QuickInsertField( SvxFieldItem(SvxTableField(), EE_FEATURE_FIELD), ESelection() );
+ pTxtObj = aEdEngine.CreateTextObject();
+ aHeaderItem.SetLeftArea( *pTxtObj );
+ aHeaderItem.SetCenterArea( *pEmptyTxtObj );
+ aStr = ", ";
+ aEdEngine.SetTextCurrentDefaults( aStr );
+ aEdEngine.QuickInsertField( SvxFieldItem(SvxTimeField(), EE_FEATURE_FIELD), ESelection(0,2,0,2) );
+ aEdEngine.QuickInsertField( SvxFieldItem(SvxDateField(Date( Date::SYSTEM ),SvxDateType::Var), EE_FEATURE_FIELD),
+ ESelection() );
+ pTxtObj = aEdEngine.CreateTextObject();
+ aHeaderItem.SetRightArea( *pTxtObj );
+ pSet->Put( aHeaderItem );
+
+ // Footer:
+ // [empty][Page: \PAGE\ / \PAGE\][empty]
+
+ aStr = ScResId( STR_PAGE ) + " ";
+ nStrLen = aStr.getLength();
+ aStr += " / ";
+ sal_Int32 nStrLen2 = aStr.getLength();
+ aEdEngine.SetTextCurrentDefaults( aStr );
+ aEdEngine.QuickInsertField( SvxFieldItem(SvxPagesField(), EE_FEATURE_FIELD), ESelection(0,nStrLen2,0,nStrLen2) );
+ aEdEngine.QuickInsertField( SvxFieldItem(SvxPageField(), EE_FEATURE_FIELD), ESelection(0,nStrLen,0,nStrLen) );
+ pTxtObj = aEdEngine.CreateTextObject();
+ aFooterItem.SetLeftArea ( *pEmptyTxtObj );
+ aFooterItem.SetCenterArea( *pTxtObj );
+ aFooterItem.SetRightArea ( *pEmptyTxtObj );
+ pSet->Put( aFooterItem );
+
+ bHasStandardStyles = true;
+}
+
+namespace {
+
+struct CaseInsensitiveNamePredicate : svl::StyleSheetPredicate
+{
+ CaseInsensitiveNamePredicate(const OUString& rName, SfxStyleFamily eFam)
+ : mUppercaseName(ScGlobal::getCharClass().uppercase(rName)), mFamily(eFam)
+ {
+ }
+
+ bool
+ Check(const SfxStyleSheetBase& rStyleSheet) override
+ {
+ if (rStyleSheet.GetFamily() == mFamily)
+ {
+ OUString aUpName = ScGlobal::getCharClass().uppercase(rStyleSheet.GetName());
+ if (mUppercaseName == aUpName)
+ {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ OUString mUppercaseName;
+ SfxStyleFamily mFamily;
+};
+
+}
+
+// Functor object to find all style sheets of a family which match a given name caseinsensitively
+ScStyleSheet* ScStyleSheetPool::FindCaseIns( const OUString& rName, SfxStyleFamily eFam )
+{
+ CaseInsensitiveNamePredicate aPredicate(rName, eFam);
+ std::vector<sal_Int32> aFoundPositions = GetIndexedStyleSheets().FindPositionsByPredicate(aPredicate);
+
+ ScStyleSheet* first = nullptr; // first case insensitive match found
+ for (const auto& rPos : aFoundPositions)
+ {
+ SfxStyleSheetBase *pFound = GetStyleSheetByPositionInIndex(rPos);
+ // we do not know what kind of sheets we have.
+ if (pFound->isScStyleSheet())
+ {
+ if (pFound->GetName() == rName) // exact case sensitive match
+ return static_cast<ScStyleSheet*>(pFound);
+ if (!first)
+ first = static_cast<ScStyleSheet*>(pFound);
+ }
+ }
+ return first;
+}
+
+ScStyleSheet* ScStyleSheetPool::FindAutoStyle(const OUString& rName)
+{
+ ScStyleSheet* pStyleSheet = FindCaseIns(rName, SfxStyleFamily::Para);
+ if (!pStyleSheet)
+ if (auto pFound = Find(ScResId(STR_STYLENAME_STANDARD), SfxStyleFamily::Para))
+ if (pFound->isScStyleSheet()) // we do not know what kind of sheets we have
+ pStyleSheet = static_cast<ScStyleSheet*>(pFound);
+ return pStyleSheet;
+}
+
+void ScStyleSheetPool::setAllParaStandard()
+{
+ SfxStyleSheetBase* pSheet = First(SfxStyleFamily::Para);
+ while (pSheet)
+ {
+ pSheet->SetMask(SfxStyleSearchBits::ScStandard);
+ pSheet = Next();
+ }
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sc/source/core/data/stlsheet.cxx b/sc/source/core/data/stlsheet.cxx
new file mode 100644
index 000000000..fdf09e7cd
--- /dev/null
+++ b/sc/source/core/data/stlsheet.cxx
@@ -0,0 +1,303 @@
+/* -*- 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 <document.hxx>
+#include <stlsheet.hxx>
+#include <stlpool.hxx>
+
+#include <scitems.hxx>
+#include <editeng/boxitem.hxx>
+#include <editeng/frmdiritem.hxx>
+#include <editeng/lrspitem.hxx>
+#include <svx/pageitem.hxx>
+#include <editeng/paperinf.hxx>
+#include <editeng/shaditem.hxx>
+#include <editeng/sizeitem.hxx>
+#include <editeng/ulspitem.hxx>
+#include <editeng/xmlcnitm.hxx>
+#include <svl/itempool.hxx>
+#include <svl/itemset.hxx>
+#include <svl/numformat.hxx>
+#include <svl/hint.hxx>
+#include <o3tl/unit_conversion.hxx>
+#include <attrib.hxx>
+
+#include <globstr.hrc>
+#include <scresid.hxx>
+#include <sc.hrc>
+
+constexpr auto TWO_CM = o3tl::convert(2, o3tl::Length::cm, o3tl::Length::twip); // 1134
+constexpr auto HFDIST_CM = o3tl::convert(250, o3tl::Length::mm100, o3tl::Length::twip); // 142
+
+ScStyleSheet::ScStyleSheet( const OUString& rName,
+ const ScStyleSheetPool& rPoolP,
+ SfxStyleFamily eFamily,
+ SfxStyleSearchBits nMaskP )
+
+ : SfxStyleSheet ( rName, rPoolP, eFamily, nMaskP )
+ , eUsage( Usage::UNKNOWN )
+{
+}
+
+ScStyleSheet::ScStyleSheet( const ScStyleSheet& rStyle )
+ : SfxStyleSheet ( rStyle )
+ , eUsage( Usage::UNKNOWN )
+{
+}
+
+ScStyleSheet::~ScStyleSheet()
+{
+}
+
+bool ScStyleSheet::HasFollowSupport() const
+{
+ return false;
+}
+
+bool ScStyleSheet::HasParentSupport () const
+{
+ bool bHasParentSupport = false;
+
+ switch ( GetFamily() )
+ {
+ case SfxStyleFamily::Para: bHasParentSupport = true; break;
+ case SfxStyleFamily::Page: bHasParentSupport = false; break;
+ default:
+ {
+ // added to avoid warnings
+ }
+ }
+
+ return bHasParentSupport;
+}
+
+bool ScStyleSheet::SetParent( const OUString& rParentName )
+{
+ bool bResult = false;
+ OUString aEffName = rParentName;
+ SfxStyleSheetBase* pStyle = m_pPool->Find( aEffName, nFamily );
+ if (!pStyle)
+ {
+ std::unique_ptr<SfxStyleSheetIterator> pIter = m_pPool->CreateIterator(nFamily);
+ pStyle = pIter->First();
+ if (pStyle)
+ aEffName = pStyle->GetName();
+ }
+
+ if ( pStyle && aEffName != GetName() )
+ {
+ bResult = SfxStyleSheet::SetParent( aEffName );
+ if (bResult)
+ {
+ SfxItemSet& rParentSet = pStyle->GetItemSet();
+ GetItemSet().SetParent( &rParentSet );
+
+ // #i113491# Drag&Drop in the stylist's hierarchical view doesn't execute a slot,
+ // so the repaint has to come from here (after modifying the ItemSet).
+ // RepaintRange checks the document's IsVisible flag and locked repaints.
+ ScDocument* pDoc = static_cast<ScStyleSheetPool*>(GetPool())->GetDocument();
+ if (pDoc)
+ pDoc->RepaintRange( ScRange( 0,0,0, pDoc->MaxCol(),pDoc->MaxRow(),MAXTAB ) );
+ }
+ }
+
+ return bResult;
+}
+
+void ScStyleSheet::ResetParent()
+{
+ GetItemSet().SetParent(nullptr);
+}
+
+SfxItemSet& ScStyleSheet::GetItemSet()
+{
+ if ( !pSet )
+ {
+ switch ( GetFamily() )
+ {
+ case SfxStyleFamily::Page:
+ {
+ // Page templates should not be derivable,
+ // therefore suitable values are set at this point.
+ // (== Standard page template)
+
+ SfxItemPool& rItemPool = GetPool()->GetPool();
+ pSet = new SfxItemSetFixed<
+ ATTR_USERDEF, ATTR_USERDEF,
+ ATTR_WRITINGDIR, ATTR_WRITINGDIR,
+ ATTR_BACKGROUND, ATTR_BACKGROUND,
+ ATTR_BORDER, ATTR_SHADOW,
+ ATTR_LRSPACE, ATTR_PAGE_SCALETO>(rItemPool);
+
+ // If being loaded also the set is then filled in from the file,
+ // so the defaults do not need to be set.
+ // GetPrinter would then also create a new printer,
+ // because the stored Printer is not loaded yet!
+
+ ScDocument* pDoc = static_cast<ScStyleSheetPool*>(GetPool())->GetDocument();
+ if ( pDoc )
+ {
+ // Setting reasonable default values:
+ SvxPageItem aPageItem( ATTR_PAGE );
+ SvxSizeItem aPaperSizeItem( ATTR_PAGE_SIZE, SvxPaperInfo::GetDefaultPaperSize() );
+
+ SvxSetItem aHFSetItem(
+ rItemPool.GetDefaultItem(ATTR_PAGE_HEADERSET) );
+
+ SfxItemSet& rHFSet = aHFSetItem.GetItemSet();
+ SvxSizeItem aHFSizeItem( // 0,5 cm + distance
+ ATTR_PAGE_SIZE,
+ Size( 0, o3tl::convert(500, o3tl::Length::mm100, o3tl::Length::twip) + HFDIST_CM ) );
+
+ SvxULSpaceItem aHFDistItem ( HFDIST_CM,// nUp
+ HFDIST_CM,// nLow
+ ATTR_ULSPACE );
+
+ SvxLRSpaceItem aLRSpaceItem( TWO_CM, // nLeft
+ TWO_CM, // nRight
+ TWO_CM, // nTLeft
+ 0, // nFirstLineOffset
+ ATTR_LRSPACE );
+ SvxULSpaceItem aULSpaceItem( TWO_CM, // nUp
+ TWO_CM, // nLow
+ ATTR_ULSPACE );
+ SvxBoxInfoItem aBoxInfoItem( ATTR_BORDER_INNER );
+
+ aBoxInfoItem.SetTable( false );
+ aBoxInfoItem.SetDist( true );
+ aBoxInfoItem.SetValid( SvxBoxInfoItemValidFlags::DISTANCE );
+
+ aPageItem.SetLandscape( false );
+
+ rHFSet.Put( aBoxInfoItem );
+ rHFSet.Put( aHFSizeItem );
+ rHFSet.Put( aHFDistItem );
+ rHFSet.Put( SvxLRSpaceItem( 0,0,0,0, ATTR_LRSPACE ) ); // Set border to Null
+
+ aHFSetItem.SetWhich(ATTR_PAGE_HEADERSET);
+ pSet->Put( aHFSetItem );
+ aHFSetItem.SetWhich(ATTR_PAGE_FOOTERSET);
+ pSet->Put( aHFSetItem );
+ pSet->Put( aBoxInfoItem ); // Do not overwrite PoolDefault
+ // due to format templates
+
+
+ // Writing direction: not as pool default because the default for cells
+ // must remain SvxFrameDirection::Environment, and each page style's setting is
+ // supposed to be saved in the file format.
+ // The page default depends on the system language.
+ SvxFrameDirection eDirection = ScGlobal::IsSystemRTL() ?
+ SvxFrameDirection::Horizontal_RL_TB : SvxFrameDirection::Horizontal_LR_TB;
+ pSet->Put( SvxFrameDirectionItem( eDirection, ATTR_WRITINGDIR ) );
+
+ rItemPool.SetPoolDefaultItem( aPageItem );
+ rItemPool.SetPoolDefaultItem( aPaperSizeItem );
+ rItemPool.SetPoolDefaultItem( aLRSpaceItem );
+ rItemPool.SetPoolDefaultItem( aULSpaceItem );
+ rItemPool.SetPoolDefaultItem( SfxUInt16Item( ATTR_PAGE_SCALE, 100 ) );
+ ScPageScaleToItem aScaleToItem;
+ rItemPool.SetPoolDefaultItem( aScaleToItem );
+ rItemPool.SetPoolDefaultItem( SfxUInt16Item( ATTR_PAGE_SCALETOPAGES, 0 ) );
+ }
+ }
+ break;
+
+ case SfxStyleFamily::Para:
+ default:
+ pSet = new SfxItemSetFixed<ATTR_PATTERN_START, ATTR_PATTERN_END>( GetPool()->GetPool() );
+ break;
+ }
+ bMySet = true;
+ }
+ if ( nHelpId == HID_SC_SHEET_CELL_ERG1 )
+ {
+ if ( !pSet->Count() )
+ {
+ // Hack to work around that when this code is called from
+ // ~ScStyleSheetPool -> ~SfxStyleSheetPool, GetPool() is no longer
+ // an ScStyleSheetPool:
+ ScStyleSheetPool * pool = dynamic_cast<ScStyleSheetPool *>(
+ GetPool());
+ if (pool != nullptr) {
+ ScDocument* pDoc = pool->GetDocument();
+ if ( pDoc )
+ {
+ sal_uInt32 nNumFmt = pDoc->GetFormatTable()->GetStandardFormat( SvNumFormatType::CURRENCY,ScGlobal::eLnge );
+ pSet->Put( SfxUInt32Item( ATTR_VALUE_FORMAT, nNumFmt ) );
+ }
+ }
+ }
+ }
+
+ return *pSet;
+}
+
+bool ScStyleSheet::IsUsed() const
+{
+ switch (GetFamily())
+ {
+ case SfxStyleFamily::Para:
+ {
+ // Always query the document to let it decide if a rescan is necessary,
+ // and store the state.
+ ScDocument* pDoc = static_cast<ScStyleSheetPool*>(m_pPool)->GetDocument();
+ if ( pDoc && pDoc->IsStyleSheetUsed( *this ) )
+ eUsage = Usage::USED;
+ else
+ eUsage = Usage::NOTUSED;
+ return eUsage == Usage::USED;
+ }
+ case SfxStyleFamily::Page:
+ {
+ // tdf#108188 - verify that the page style is actually used
+ ScDocument* pDoc = static_cast<ScStyleSheetPool*>(m_pPool)->GetDocument();
+ if (pDoc && pDoc->IsPageStyleInUse(GetName(), nullptr))
+ eUsage = Usage::USED;
+ else
+ eUsage = Usage::NOTUSED;
+ return eUsage == Usage::USED;
+ }
+ default:
+ return true;
+ }
+}
+
+void ScStyleSheet::Notify( SfxBroadcaster&, const SfxHint& rHint )
+{
+ if ( rHint.GetId() == SfxHintId::Dying )
+ GetItemSet().SetParent( nullptr );
+}
+
+// Avoid creating a Style "Standard" if this is not the Standard-Name;
+// otherwise two styles would have the same name when storing.
+// (on loading the style is created directly per Make with the name; making this query
+// not applicable)
+//TODO: If at any time during loading SetName is called, a flag has to be set/checked for loading
+//TODO: The whole check has to be removed if for a new file version the name transformation is dropped.
+
+bool ScStyleSheet::SetName(const OUString& rNew, bool bReindexNow)
+{
+ OUString aFileStdName = STRING_STANDARD;
+ if ( rNew == aFileStdName && aFileStdName != ScResId(STR_STYLENAME_STANDARD) )
+ return false;
+ else
+ return SfxStyleSheet::SetName(rNew, bReindexNow);
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sc/source/core/data/subtotalparam.cxx b/sc/source/core/data/subtotalparam.cxx
new file mode 100644
index 000000000..e8f329542
--- /dev/null
+++ b/sc/source/core/data/subtotalparam.cxx
@@ -0,0 +1,199 @@
+/* -*- 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/.
+ */
+
+#include <subtotalparam.hxx>
+
+#include <osl/diagnose.h>
+
+ScSubTotalParam::ScSubTotalParam()
+{
+ for ( sal_uInt16 i=0; i<MAXSUBTOTAL; i++ )
+ {
+ nSubTotals[i] = 0;
+ pSubTotals[i] = nullptr;
+ pFunctions[i] = nullptr;
+ }
+
+ Clear();
+}
+
+ScSubTotalParam::ScSubTotalParam( const ScSubTotalParam& r ) :
+ nCol1(r.nCol1),nRow1(r.nRow1),nCol2(r.nCol2),nRow2(r.nRow2),nUserIndex(r.nUserIndex),
+ bRemoveOnly(r.bRemoveOnly),bReplace(r.bReplace),bPagebreak(r.bPagebreak),bCaseSens(r.bCaseSens),
+ bDoSort(r.bDoSort),bAscending(r.bAscending),bUserDef(r.bUserDef),
+ bIncludePattern(r.bIncludePattern)
+{
+ for (sal_uInt16 i=0; i<MAXSUBTOTAL; i++)
+ {
+ bGroupActive[i] = r.bGroupActive[i];
+ nField[i] = r.nField[i];
+
+ if ( (r.nSubTotals[i] > 0) && r.pSubTotals[i] && r.pFunctions[i] )
+ {
+ nSubTotals[i] = r.nSubTotals[i];
+ pSubTotals[i].reset(new SCCOL [r.nSubTotals[i]]);
+ pFunctions[i].reset(new ScSubTotalFunc [r.nSubTotals[i]]);
+
+ for (SCCOL j=0; j<r.nSubTotals[i]; j++)
+ {
+ pSubTotals[i][j] = r.pSubTotals[i][j];
+ pFunctions[i][j] = r.pFunctions[i][j];
+ }
+ }
+ else
+ {
+ nSubTotals[i] = 0;
+ }
+ }
+}
+
+void ScSubTotalParam::Clear()
+{
+ nCol1=nCol2= 0;
+ nRow1=nRow2 = 0;
+ nUserIndex = 0;
+ bPagebreak=bCaseSens=bUserDef=bIncludePattern=bRemoveOnly = false;
+ bAscending=bReplace=bDoSort = true;
+
+ for (sal_uInt16 i=0; i<MAXSUBTOTAL; i++)
+ {
+ bGroupActive[i] = false;
+ nField[i] = 0;
+
+ if ( (nSubTotals[i] > 0) && pSubTotals[i] && pFunctions[i] )
+ {
+ for ( SCCOL j=0; j<nSubTotals[i]; j++ ) {
+ pSubTotals[i][j] = 0;
+ pFunctions[i][j] = SUBTOTAL_FUNC_NONE;
+ }
+ }
+ }
+}
+
+ScSubTotalParam& ScSubTotalParam::operator=( const ScSubTotalParam& r )
+{
+ if(this == &r)
+ return *this;
+
+ nCol1 = r.nCol1;
+ nRow1 = r.nRow1;
+ nCol2 = r.nCol2;
+ nRow2 = r.nRow2;
+ bRemoveOnly = r.bRemoveOnly;
+ bReplace = r.bReplace;
+ bPagebreak = r.bPagebreak;
+ bCaseSens = r.bCaseSens;
+ bDoSort = r.bDoSort;
+ bAscending = r.bAscending;
+ bUserDef = r.bUserDef;
+ nUserIndex = r.nUserIndex;
+ bIncludePattern = r.bIncludePattern;
+
+ for (sal_uInt16 i=0; i<MAXSUBTOTAL; i++)
+ {
+ bGroupActive[i] = r.bGroupActive[i];
+ nField[i] = r.nField[i];
+ nSubTotals[i] = r.nSubTotals[i];
+
+ pSubTotals[i].reset();
+ pFunctions[i].reset();
+
+ if ( r.nSubTotals[i] > 0 )
+ {
+ pSubTotals[i].reset(new SCCOL [r.nSubTotals[i]]);
+ pFunctions[i].reset(new ScSubTotalFunc [r.nSubTotals[i]]);
+
+ for (SCCOL j=0; j<r.nSubTotals[i]; j++)
+ {
+ pSubTotals[i][j] = r.pSubTotals[i][j];
+ pFunctions[i][j] = r.pFunctions[i][j];
+ }
+ }
+ else
+ {
+ nSubTotals[i] = 0;
+ }
+ }
+
+ return *this;
+}
+
+bool ScSubTotalParam::operator==( const ScSubTotalParam& rOther ) const
+{
+ bool bEqual = (nCol1 == rOther.nCol1)
+ && (nRow1 == rOther.nRow1)
+ && (nCol2 == rOther.nCol2)
+ && (nRow2 == rOther.nRow2)
+ && (nUserIndex == rOther.nUserIndex)
+ && (bRemoveOnly == rOther.bRemoveOnly)
+ && (bReplace == rOther.bReplace)
+ && (bPagebreak == rOther.bPagebreak)
+ && (bDoSort == rOther.bDoSort)
+ && (bCaseSens == rOther.bCaseSens)
+ && (bAscending == rOther.bAscending)
+ && (bUserDef == rOther.bUserDef)
+ && (bIncludePattern== rOther.bIncludePattern);
+
+ if ( bEqual )
+ {
+ bEqual = true;
+ for ( sal_uInt16 i=0; i<MAXSUBTOTAL && bEqual; i++ )
+ {
+ bEqual = (bGroupActive[i] == rOther.bGroupActive[i])
+ && (nField[i] == rOther.nField[i])
+ && (nSubTotals[i] == rOther.nSubTotals[i]);
+
+ if ( bEqual && (nSubTotals[i] > 0) )
+ {
+ for (SCCOL j=0; (j<nSubTotals[i]) && bEqual; j++)
+ {
+ bEqual = bEqual
+ && (pSubTotals[i][j] == rOther.pSubTotals[i][j])
+ && (pFunctions[i][j] == rOther.pFunctions[i][j]);
+ }
+ }
+ }
+ }
+
+ return bEqual;
+}
+
+void ScSubTotalParam::SetSubTotals( sal_uInt16 nGroup,
+ const SCCOL* ptrSubTotals,
+ const ScSubTotalFunc* ptrFunctions,
+ sal_uInt16 nCount )
+{
+ OSL_ENSURE( (nGroup <= MAXSUBTOTAL),
+ "ScSubTotalParam::SetSubTotals(): nGroup > MAXSUBTOTAL!" );
+ OSL_ENSURE( ptrSubTotals,
+ "ScSubTotalParam::SetSubTotals(): ptrSubTotals == NULL!" );
+ OSL_ENSURE( ptrFunctions,
+ "ScSubTotalParam::SetSubTotals(): ptrFunctions == NULL!" );
+ OSL_ENSURE( (nCount > 0),
+ "ScSubTotalParam::SetSubTotals(): nCount <= 0!" );
+
+ if ( !(ptrSubTotals && ptrFunctions && (nCount > 0) && (nGroup <= MAXSUBTOTAL)) )
+ return;
+
+ // 0 is interpreted as 1, otherwise decrementing the array index
+ if (nGroup != 0)
+ nGroup--;
+
+ pSubTotals[nGroup].reset(new SCCOL[nCount]);
+ pFunctions[nGroup].reset(new ScSubTotalFunc[nCount]);
+ nSubTotals[nGroup] = static_cast<SCCOL>(nCount);
+
+ for ( sal_uInt16 i=0; i<nCount; i++ )
+ {
+ pSubTotals[nGroup][i] = ptrSubTotals[i];
+ pFunctions[nGroup][i] = ptrFunctions[i];
+ }
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sc/source/core/data/tabbgcolor.cxx b/sc/source/core/data/tabbgcolor.cxx
new file mode 100644
index 000000000..5b14ff830
--- /dev/null
+++ b/sc/source/core/data/tabbgcolor.cxx
@@ -0,0 +1,36 @@
+/* -*- 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 <tabbgcolor.hxx>
+
+ScUndoTabColorInfo::ScUndoTabColorInfo(SCTAB nTab) :
+ mnTabId(nTab),
+ maOldTabBgColor(COL_AUTO),
+ maNewTabBgColor(COL_AUTO)
+{
+}
+
+ScUndoTabColorInfo::ScUndoTabColorInfo(const ScUndoTabColorInfo& r) :
+ mnTabId(r.mnTabId),
+ maOldTabBgColor(r.maOldTabBgColor),
+ maNewTabBgColor(r.maNewTabBgColor)
+{
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sc/source/core/data/table1.cxx b/sc/source/core/data/table1.cxx
new file mode 100644
index 000000000..b177bd55b
--- /dev/null
+++ b/sc/source/core/data/table1.cxx
@@ -0,0 +1,2729 @@
+/* -*- 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 <scitems.hxx>
+#include <editeng/justifyitem.hxx>
+#include <o3tl/safeint.hxx>
+#include <o3tl/unit_conversion.hxx>
+#include <unotools/textsearch.hxx>
+#include <unotools/charclass.hxx>
+#include <osl/diagnose.h>
+
+#include <patattr.hxx>
+#include <table.hxx>
+#include <document.hxx>
+#include <drwlayer.hxx>
+#include <olinetab.hxx>
+#include <global.hxx>
+#include <globstr.hrc>
+#include <scresid.hxx>
+#include <refupdat.hxx>
+#include <markdata.hxx>
+#include <progress.hxx>
+#include <prnsave.hxx>
+#include <tabprotection.hxx>
+#include <sheetevents.hxx>
+#include <segmenttree.hxx>
+#include <dbdata.hxx>
+#include <conditio.hxx>
+#include <globalnames.hxx>
+#include <cellvalue.hxx>
+#include <scmatrix.hxx>
+#include <refupdatecontext.hxx>
+#include <rowheightcontext.hxx>
+#include <compressedarray.hxx>
+#include <vcl/svapp.hxx>
+
+#include <formula/vectortoken.hxx>
+#include <token.hxx>
+
+#include <vector>
+#include <memory>
+
+using ::std::vector;
+
+namespace {
+
+ScProgress* GetProgressBar(
+ SCSIZE nCount, SCSIZE nTotalCount, ScProgress* pOuterProgress, const ScDocument* pDoc)
+{
+ if (nTotalCount < 1000)
+ {
+ // if the total number of rows is less than 1000, don't even bother
+ // with the progress bar because drawing progress bar can be very
+ // expensive especially in GTK.
+ return nullptr;
+ }
+
+ if (pOuterProgress)
+ return pOuterProgress;
+
+ if (nCount > 1)
+ return new ScProgress(
+ pDoc->GetDocumentShell(), ScResId(STR_PROGRESS_HEIGHTING), nTotalCount, true);
+
+ return nullptr;
+}
+
+void GetOptimalHeightsInColumn(
+ sc::RowHeightContext& rCxt, ScColContainer& rCol, SCROW nStartRow, SCROW nEndRow,
+ ScProgress* pProgress, sal_uLong nProgressStart )
+{
+ assert(nStartRow <= nEndRow);
+
+ // first, one time over the whole range
+ // (with the last column in the hope that they most likely still are
+ // on standard format)
+
+
+ rCol.back().GetOptimalHeight(rCxt, nStartRow, nEndRow, 0, 0);
+
+ // from there search for the standard height that is in use in the lower part
+
+ RowHeightsArray& rHeights = rCxt.getHeightArray();
+ sal_uInt16 nMinHeight = rHeights.GetValue(nEndRow);
+ SCSIZE nPos = nEndRow - 1;
+ while ( nPos )
+ {
+ auto aRangeData = rHeights.GetRangeData(nPos-1);
+ if (aRangeData.maValue < nMinHeight)
+ break;
+ nPos = std::max<SCSIZE>(0, aRangeData.mnRow1);
+ }
+
+ const SCROW nMinStart = nPos;
+
+ sal_uInt64 nWeightedCount = nProgressStart + rCol.back().GetWeightedCount(nStartRow, nEndRow);
+ const SCCOL maxCol = rCol.size() - 1; // last col done already above
+ for (SCCOL nCol=0; nCol<maxCol; nCol++)
+ {
+ rCol[nCol].GetOptimalHeight(rCxt, nStartRow, nEndRow, nMinHeight, nMinStart);
+
+ if (pProgress)
+ {
+ nWeightedCount += rCol[nCol].GetWeightedCount(nStartRow, nEndRow);
+ pProgress->SetState( nWeightedCount );
+ }
+ }
+}
+
+struct OptimalHeightsFuncObjBase
+{
+ virtual ~OptimalHeightsFuncObjBase() {}
+ virtual bool operator() (SCROW nStartRow, SCROW nEndRow, sal_uInt16 nHeight, bool bApi) = 0;
+};
+
+struct SetRowHeightOnlyFunc : public OptimalHeightsFuncObjBase
+{
+ ScTable* mpTab;
+ explicit SetRowHeightOnlyFunc(ScTable* pTab) :
+ mpTab(pTab)
+ {}
+
+ virtual bool operator() (SCROW nStartRow, SCROW nEndRow, sal_uInt16 nHeight, bool /* bApi */) override
+ {
+ mpTab->SetRowHeightOnly(nStartRow, nEndRow, nHeight);
+ return false;
+ }
+};
+
+struct SetRowHeightRangeFunc : public OptimalHeightsFuncObjBase
+{
+ ScTable* mpTab;
+ double mnPPTY;
+
+ SetRowHeightRangeFunc(ScTable* pTab, double nPPTY) :
+ mpTab(pTab),
+ mnPPTY(nPPTY)
+ {}
+
+ virtual bool operator() (SCROW nStartRow, SCROW nEndRow, sal_uInt16 nHeight, bool bApi) override
+ {
+ return mpTab->SetRowHeightRange(nStartRow, nEndRow, nHeight, mnPPTY, bApi);
+ }
+};
+
+bool SetOptimalHeightsToRows(
+ sc::RowHeightContext& rCxt,
+ OptimalHeightsFuncObjBase& rFuncObj,
+ ScBitMaskCompressedArray<SCROW, CRFlags>* pRowFlags, SCROW nStartRow, SCROW nEndRow,
+ bool bApi )
+{
+ bool bChanged = false;
+ SCROW nRngStart = 0;
+ SCROW nRngEnd = 0;
+ sal_uInt16 nLast = 0;
+ sal_uInt16 nExtraHeight = rCxt.getExtraHeight();
+ for (SCSIZE i = nStartRow; i <= o3tl::make_unsigned(nEndRow); i++)
+ {
+ size_t nIndex;
+ SCROW nRegionEndRow;
+ CRFlags nRowFlag = pRowFlags->GetValue( i, nIndex, nRegionEndRow );
+ if ( nRegionEndRow > nEndRow )
+ nRegionEndRow = nEndRow;
+ SCSIZE nMoreRows = nRegionEndRow - i; // additional equal rows after first
+
+ bool bAutoSize = !(nRowFlag & CRFlags::ManualSize);
+ if (bAutoSize || rCxt.isForceAutoSize())
+ {
+ if (nExtraHeight)
+ {
+ if (bAutoSize)
+ pRowFlags->SetValue( i, nRegionEndRow, nRowFlag | CRFlags::ManualSize);
+ }
+ else if (!bAutoSize)
+ pRowFlags->SetValue( i, nRegionEndRow, nRowFlag & ~CRFlags::ManualSize);
+
+ for (SCSIZE nInner = i; nInner <= i + nMoreRows; ++nInner)
+ {
+ if (nLast)
+ {
+ SCROW nRangeRowEnd;
+ size_t nTmp;
+ sal_uInt16 nRangeValue = rCxt.getHeightArray().GetValue(nInner, nTmp, nRangeRowEnd);
+ if (nRangeValue + nExtraHeight == nLast)
+ {
+ nRngEnd = std::min<SCSIZE>(i + nMoreRows, nRangeRowEnd);
+ nInner = nRangeRowEnd;
+ }
+ else
+ {
+ bChanged |= rFuncObj(nRngStart, nRngEnd, nLast, bApi);
+ nLast = 0;
+ }
+ }
+ if (!nLast)
+ {
+ nLast = rCxt.getHeightArray().GetValue(nInner) + rCxt.getExtraHeight();
+ nRngStart = nInner;
+ nRngEnd = nInner;
+ }
+ }
+ }
+ else
+ {
+ if (nLast)
+ bChanged |= rFuncObj(nRngStart, nRngEnd, nLast, bApi);
+ nLast = 0;
+ }
+ i += nMoreRows; // already handled - skip
+ }
+ if (nLast)
+ bChanged |= rFuncObj(nRngStart, nRngEnd, nLast, bApi);
+
+ return bChanged;
+}
+
+}
+
+ScTable::ScTable( ScDocument& rDoc, SCTAB nNewTab, const OUString& rNewName,
+ bool bColInfo, bool bRowInfo ) :
+ aCol( rDoc.GetSheetLimits(), INITIALCOLCOUNT ),
+ aName( rNewName ),
+ aCodeName( rNewName ),
+ nLinkRefreshDelay( 0 ),
+ nLinkMode( ScLinkMode::NONE ),
+ aPageStyle( ScResId(STR_STYLENAME_STANDARD) ),
+ nRepeatStartX( SCCOL_REPEAT_NONE ),
+ nRepeatEndX( SCCOL_REPEAT_NONE ),
+ nRepeatStartY( SCROW_REPEAT_NONE ),
+ nRepeatEndY( SCROW_REPEAT_NONE ),
+ mpRowHeights( static_cast<ScFlatUInt16RowSegments*>(nullptr) ),
+ mpHiddenCols(new ScFlatBoolColSegments(rDoc.MaxCol())),
+ mpHiddenRows(new ScFlatBoolRowSegments(rDoc.MaxRow())),
+ mpFilteredCols(new ScFlatBoolColSegments(rDoc.MaxCol())),
+ mpFilteredRows(new ScFlatBoolRowSegments(rDoc.MaxRow())),
+ nTableAreaX( 0 ),
+ nTableAreaY( 0 ),
+ nTableAreaVisibleX( 0 ),
+ nTableAreaVisibleY( 0 ),
+ nTab( nNewTab ),
+ rDocument( rDoc ),
+ pSortCollator( nullptr ),
+ nLockCount( 0 ),
+ aScenarioColor( COL_LIGHTGRAY ),
+ aTabBgColor( COL_AUTO ),
+ nScenarioFlags(ScScenarioFlags::NONE),
+ mpCondFormatList( new ScConditionalFormatList() ),
+ maLOKFreezeCell(-1, -1, nNewTab),
+ bScenario(false),
+ bLayoutRTL(false),
+ bLoadingRTL(false),
+ bPageSizeValid(false),
+ bTableAreaValid(false),
+ bTableAreaVisibleValid(false),
+ bVisible(true),
+ bPendingRowHeights(false),
+ bCalcNotification(false),
+ bGlobalKeepQuery(false),
+ bPrintEntireSheet(true),
+ bActiveScenario(false),
+ mbPageBreaksValid(false),
+ mbForceBreaks(false),
+ bStreamValid(false)
+{
+ aDefaultColData.InitAttrArray(new ScAttrArray(static_cast<SCCOL>(-1), nNewTab, rDoc, nullptr));
+ if (bColInfo)
+ {
+ mpColWidth.reset( new ScCompressedArray<SCCOL, sal_uInt16>( rDocument.MaxCol()+1, STD_COL_WIDTH ) );
+ mpColFlags.reset( new ScBitMaskCompressedArray<SCCOL, CRFlags>( rDocument.MaxCol()+1, CRFlags::NONE ) );
+ }
+
+ if (bRowInfo)
+ {
+ mpRowHeights.reset(new ScFlatUInt16RowSegments(rDocument.MaxRow(), ScGlobal::nStdRowHeight));
+ pRowFlags.reset(new ScBitMaskCompressedArray<SCROW, CRFlags>( rDocument.MaxRow(), CRFlags::NONE));
+ }
+
+ if ( rDocument.IsDocVisible() )
+ {
+ // when a sheet is added to a visible document,
+ // initialize its RTL flag from the system locale
+ bLayoutRTL = ScGlobal::IsSystemRTL();
+ }
+
+ ScDrawLayer* pDrawLayer = rDocument.GetDrawLayer();
+ if (pDrawLayer)
+ {
+ if ( pDrawLayer->ScAddPage( nTab ) ) // sal_False (not inserted) during Undo
+ {
+ pDrawLayer->ScRenamePage( nTab, aName );
+ sal_uLong const nx = o3tl::convert((rDocument.MaxCol()+1) * STD_COL_WIDTH, o3tl::Length::twip, o3tl::Length::mm100);
+ sal_uLong ny = o3tl::convert((rDocument.MaxRow()+1) * ScGlobal::nStdRowHeight, o3tl::Length::twip, o3tl::Length::mm10);
+ pDrawLayer->SetPageSize( static_cast<sal_uInt16>(nTab), Size( nx, ny ), false );
+ }
+ }
+
+ for (SCCOL k=0; k < aCol.size(); k++)
+ aCol[k].Init( k, nTab, rDocument, true );
+}
+
+ScTable::~ScTable() COVERITY_NOEXCEPT_FALSE
+{
+ if (!rDocument.IsInDtorClear())
+ {
+ for (SCCOL nCol = 0; nCol < aCol.size(); ++nCol)
+ {
+ aCol[nCol].FreeNotes();
+ }
+ // In the dtor, don't delete the pages in the wrong order.
+ // (or else nTab does not reflect the page number!)
+ // In ScDocument::Clear is afterwards used from Clear at the Draw Layer to delete everything.
+
+ ScDrawLayer* pDrawLayer = rDocument.GetDrawLayer();
+ if (pDrawLayer)
+ pDrawLayer->ScRemovePage( nTab );
+ }
+
+ pRowFlags.reset();
+ pSheetEvents.reset();
+ pOutlineTable.reset();
+ pSearchText.reset();
+ moRepeatColRange.reset();
+ moRepeatRowRange.reset();
+ pScenarioRanges.reset();
+ mpRangeName.reset();
+ pDBDataNoName.reset();
+ DestroySortCollator();
+}
+
+sal_Int64 ScTable::GetHashCode() const
+{
+ return sal::static_int_cast<sal_Int64>(reinterpret_cast<sal_IntPtr>(this));
+}
+
+void ScTable::SetName( const OUString& rNewName )
+{
+ aName = rNewName;
+ aUpperName.clear(); // invalidated if the name is changed
+
+ // SetStreamValid is handled in ScDocument::RenameTab
+}
+
+const OUString& ScTable::GetUpperName() const
+{
+ if (aUpperName.isEmpty() && !aName.isEmpty())
+ aUpperName = ScGlobal::getCharClass().uppercase(aName);
+ return aUpperName;
+}
+
+void ScTable::SetVisible( bool bVis )
+{
+ if (bVisible != bVis)
+ SetStreamValid(false);
+
+ bVisible = bVis;
+}
+
+void ScTable::SetStreamValid( bool bSet, bool bIgnoreLock )
+{
+ if (!bStreamValid && !bSet)
+ return; // shortcut
+ if ( bIgnoreLock || !rDocument.IsStreamValidLocked() )
+ bStreamValid = bSet;
+}
+
+void ScTable::SetPendingRowHeights( bool bSet )
+{
+ bPendingRowHeights = bSet;
+}
+
+void ScTable::SetLayoutRTL( bool bSet )
+{
+ bLayoutRTL = bSet;
+}
+
+void ScTable::SetLoadingRTL( bool bSet )
+{
+ bLoadingRTL = bSet;
+}
+
+void ScTable::SetTabBgColor(const Color& rColor)
+{
+ if (aTabBgColor != rColor)
+ {
+ // The tab color has changed. Set this table 'modified'.
+ aTabBgColor = rColor;
+ SetStreamValid(false);
+ }
+}
+
+void ScTable::SetScenario( bool bFlag )
+{
+ bScenario = bFlag;
+}
+
+void ScTable::SetLink( ScLinkMode nMode,
+ const OUString& rDoc, const OUString& rFlt, const OUString& rOpt,
+ const OUString& rTab, sal_uLong nRefreshDelay )
+{
+ nLinkMode = nMode;
+ aLinkDoc = rDoc; // File
+ aLinkFlt = rFlt; // Filter
+ aLinkOpt = rOpt; // Filter options
+ aLinkTab = rTab; // Sheet name in source file
+ nLinkRefreshDelay = nRefreshDelay; // refresh delay in seconds, 0==off
+
+ SetStreamValid(false);
+}
+
+sal_uInt16 ScTable::GetOptimalColWidth( SCCOL nCol, OutputDevice* pDev,
+ double nPPTX, double nPPTY,
+ const Fraction& rZoomX, const Fraction& rZoomY,
+ bool bFormula, const ScMarkData* pMarkData,
+ const ScColWidthParam* pParam )
+{
+ if ( nCol >= aCol.size() )
+ return ( STD_COL_WIDTH - STD_EXTRA_WIDTH );
+
+ return aCol[nCol].GetOptimalColWidth( pDev, nPPTX, nPPTY, rZoomX, rZoomY,
+ bFormula, STD_COL_WIDTH - STD_EXTRA_WIDTH, pMarkData, pParam );
+}
+
+tools::Long ScTable::GetNeededSize( SCCOL nCol, SCROW nRow,
+ OutputDevice* pDev,
+ double nPPTX, double nPPTY,
+ const Fraction& rZoomX, const Fraction& rZoomY,
+ bool bWidth, bool bTotalSize, bool bInPrintTwips )
+{
+ if ( nCol >= aCol.size() )
+ return 0;
+
+ ScNeededSizeOptions aOptions;
+ aOptions.bSkipMerged = false; // count merged cells
+ aOptions.bTotalSize = bTotalSize;
+
+ return aCol[nCol].GetNeededSize
+ ( nRow, pDev, nPPTX, nPPTY, rZoomX, rZoomY, bWidth, aOptions, nullptr, bInPrintTwips );
+}
+
+bool ScTable::SetOptimalHeight(
+ sc::RowHeightContext& rCxt, SCROW nStartRow, SCROW nEndRow, bool bApi,
+ ScProgress* pOuterProgress, sal_uInt64 nProgressStart )
+{
+ assert(nStartRow <= nEndRow);
+
+ OSL_ENSURE( rCxt.getExtraHeight() == 0 || rCxt.isForceAutoSize(),
+ "automatic OptimalHeight with Extra" );
+
+ if ( rDocument.IsAdjustHeightLocked() )
+ {
+ return false;
+ }
+
+ SCSIZE nCount = static_cast<SCSIZE>(nEndRow-nStartRow+1);
+
+ ScProgress* pProgress = GetProgressBar(nCount, GetWeightedCount(), pOuterProgress, &rDocument);
+
+ mpRowHeights->enableTreeSearch(false);
+
+ GetOptimalHeightsInColumn(rCxt, aCol, nStartRow, nEndRow, pProgress, nProgressStart);
+
+ SetRowHeightRangeFunc aFunc(this, rCxt.getPPTY());
+ bool bChanged = SetOptimalHeightsToRows(rCxt, aFunc, pRowFlags.get(), nStartRow, nEndRow, bApi);
+
+ if ( pProgress != pOuterProgress )
+ delete pProgress;
+
+ mpRowHeights->enableTreeSearch(true);
+
+ return bChanged;
+}
+
+void ScTable::SetOptimalHeightOnly(
+ sc::RowHeightContext& rCxt, SCROW nStartRow, SCROW nEndRow,
+ ScProgress* pOuterProgress, sal_uInt64 nProgressStart )
+{
+ OSL_ENSURE( rCxt.getExtraHeight() == 0 || rCxt.isForceAutoSize(),
+ "automatic OptimalHeight with Extra" );
+
+ if ( rDocument.IsAdjustHeightLocked() )
+ return;
+
+ SCSIZE nCount = static_cast<SCSIZE>(nEndRow-nStartRow+1);
+
+ ScProgress* pProgress = GetProgressBar(nCount, GetWeightedCount(), pOuterProgress, &rDocument);
+
+ GetOptimalHeightsInColumn(rCxt, aCol, nStartRow, nEndRow, pProgress, nProgressStart);
+
+ SetRowHeightOnlyFunc aFunc(this);
+
+ SetOptimalHeightsToRows(rCxt, aFunc, pRowFlags.get(), nStartRow, nEndRow, true);
+
+ if ( pProgress != pOuterProgress )
+ delete pProgress;
+}
+
+bool ScTable::GetCellArea( SCCOL& rEndCol, SCROW& rEndRow ) const
+{
+ bool bFound = false;
+ SCCOL nMaxX = 0;
+ SCROW nMaxY = 0;
+ for (SCCOL i=0; i<aCol.size(); i++)
+ {
+ if (!aCol[i].IsEmptyData())
+ {
+ bFound = true;
+ nMaxX = i;
+ SCROW nRow = aCol[i].GetLastDataPos();
+ if (nRow > nMaxY)
+ nMaxY = nRow;
+ }
+ if ( aCol[i].HasCellNotes() )
+ {
+ SCROW maxNoteRow = aCol[i].GetCellNotesMaxRow();
+ if (maxNoteRow >= nMaxY)
+ {
+ bFound = true;
+ nMaxY = maxNoteRow;
+ }
+ if (i>nMaxX)
+ {
+ bFound = true;
+ nMaxX = i;
+ }
+ }
+ if (aCol[i].HasSparklines())
+ {
+ SCROW maxSparklineRow = aCol[i].GetSparklinesMaxRow();
+ if (maxSparklineRow >= nMaxY)
+ {
+ bFound = true;
+ nMaxY = maxSparklineRow;
+ }
+ if (i > nMaxX)
+ {
+ bFound = true;
+ nMaxX = i;
+ }
+ }
+ }
+
+ rEndCol = nMaxX;
+ rEndRow = nMaxY;
+ return bFound;
+}
+
+bool ScTable::GetTableArea( SCCOL& rEndCol, SCROW& rEndRow, bool bCalcHiddens) const
+{
+ bool bRet = true; //TODO: remember?
+ if (bCalcHiddens)
+ {
+ if (!bTableAreaValid)
+ {
+ bRet = GetPrintArea(nTableAreaX, nTableAreaY, true, bCalcHiddens);
+ bTableAreaValid = true;
+ }
+ rEndCol = nTableAreaX;
+ rEndRow = nTableAreaY;
+ }
+ else
+ {
+ if (!bTableAreaVisibleValid)
+ {
+ bRet = GetPrintArea(nTableAreaVisibleX, nTableAreaVisibleY, true, bCalcHiddens);
+ bTableAreaVisibleValid = true;
+ }
+ rEndCol = nTableAreaVisibleX;
+ rEndRow = nTableAreaVisibleY;
+ }
+ return bRet;
+}
+
+const SCCOL SC_COLUMNS_STOP = 30;
+
+bool ScTable::GetPrintArea( SCCOL& rEndCol, SCROW& rEndRow, bool bNotes, bool bCalcHiddens ) const
+{
+ bool bFound = false;
+ SCCOL nMaxX = 0;
+ SCROW nMaxY = 0;
+ SCCOL i;
+
+ for (i=0; i<aCol.size(); i++) // Test data
+ {
+ if (bCalcHiddens || !rDocument.ColHidden(i, nTab))
+ {
+ if (!aCol[i].IsEmptyData())
+ {
+ bFound = true;
+ if (i>nMaxX)
+ nMaxX = i;
+ SCROW nColY = aCol[i].GetLastDataPos();
+ if (nColY > nMaxY)
+ nMaxY = nColY;
+ }
+ if (bNotes && aCol[i].HasCellNotes() )
+ {
+ SCROW maxNoteRow = aCol[i].GetCellNotesMaxRow();
+ if (maxNoteRow >= nMaxY)
+ {
+ bFound = true;
+ nMaxY = maxNoteRow;
+ }
+ if (i>nMaxX)
+ {
+ bFound = true;
+ nMaxX = i;
+ }
+ }
+ if (aCol[i].HasSparklines())
+ {
+ SCROW maxSparklineRow = aCol[i].GetSparklinesMaxRow();
+ if (maxSparklineRow >= nMaxY)
+ {
+ bFound = true;
+ nMaxY = maxSparklineRow;
+ }
+ if (i > nMaxX)
+ {
+ bFound = true;
+ nMaxX = i;
+ }
+ }
+ }
+ }
+
+ SCCOL nMaxDataX = nMaxX;
+
+ for (i=0; i<aCol.size(); i++) // Test attribute
+ {
+ if (bCalcHiddens || !rDocument.ColHidden(i, nTab))
+ {
+ SCROW nLastRow;
+ if (aCol[i].GetLastVisibleAttr( nLastRow ))
+ {
+ bFound = true;
+ nMaxX = i;
+ if (nLastRow > nMaxY)
+ nMaxY = nLastRow;
+ }
+ }
+ }
+
+ if (nMaxX == rDocument.MaxCol()) // omit attribute at the right
+ {
+ --nMaxX;
+ while ( nMaxX>0 && aCol[nMaxX].IsVisibleAttrEqual(aCol[nMaxX+1], 0, rDocument.MaxRow()) )
+ --nMaxX;
+ }
+
+ if ( nMaxX < nMaxDataX )
+ {
+ nMaxX = nMaxDataX;
+ }
+ else if ( nMaxX > nMaxDataX )
+ {
+ SCCOL nAttrStartX = nMaxDataX + 1;
+ while ( nAttrStartX < (aCol.size()-1) )
+ {
+ SCCOL nAttrEndX = nAttrStartX;
+ while ( nAttrEndX < (aCol.size()-1) && aCol[nAttrStartX].IsVisibleAttrEqual(aCol[nAttrEndX+1], 0, rDocument.MaxRow()) )
+ ++nAttrEndX;
+ if ( nAttrEndX + 1 - nAttrStartX >= SC_COLUMNS_STOP )
+ {
+ // found equally-formatted columns behind data -> stop before these columns
+ nMaxX = nAttrStartX - 1;
+
+ // also don't include default-formatted columns before that
+ SCROW nDummyRow;
+ while ( nMaxX > nMaxDataX && !aCol[nMaxX].GetLastVisibleAttr( nDummyRow ) )
+ --nMaxX;
+ break;
+ }
+ nAttrStartX = nAttrEndX + 1;
+ }
+ }
+
+ rEndCol = nMaxX;
+ rEndRow = nMaxY;
+ return bFound;
+}
+
+bool ScTable::GetPrintAreaHor( SCROW nStartRow, SCROW nEndRow,
+ SCCOL& rEndCol ) const
+{
+ bool bFound = false;
+ SCCOL nMaxX = 0;
+ SCCOL i;
+
+ for (i=0; i<aCol.size(); i++) // Test attribute
+ {
+ if (aCol[i].HasVisibleAttrIn( nStartRow, nEndRow ))
+ {
+ bFound = true;
+ nMaxX = i;
+ }
+ }
+
+ if (nMaxX == rDocument.MaxCol()) // omit attribute at the right
+ {
+ --nMaxX;
+ while ( nMaxX>0 && aCol[nMaxX].IsVisibleAttrEqual(aCol[nMaxX+1], nStartRow, nEndRow) )
+ --nMaxX;
+ }
+
+ for (i=0; i<aCol.size(); i++) // test the data
+ {
+ if (!aCol[i].IsEmptyData( nStartRow, nEndRow )) //TODO: bNotes ??????
+ {
+ bFound = true;
+ if (i > nMaxX)
+ nMaxX = i;
+ }
+ else if (aCol[i].HasSparklines())
+ {
+ if (i > nMaxX)
+ {
+ bFound = true;
+ nMaxX = i;
+ }
+ }
+ }
+
+ rEndCol = nMaxX;
+ return bFound;
+}
+
+bool ScTable::GetPrintAreaVer( SCCOL nStartCol, SCCOL nEndCol,
+ SCROW& rEndRow, bool bNotes ) const
+{
+ nStartCol = std::min<SCCOL>( nStartCol, aCol.size()-1 );
+ nEndCol = std::min<SCCOL>( nEndCol, aCol.size()-1 );
+ bool bFound = false;
+ SCROW nMaxY = 0;
+ SCCOL i;
+
+ for (i=nStartCol; i<=nEndCol; i++) // Test attribute
+ {
+ SCROW nLastRow;
+ if (aCol[i].GetLastVisibleAttr( nLastRow ))
+ {
+ bFound = true;
+ if (nLastRow > nMaxY)
+ nMaxY = nLastRow;
+ }
+ }
+
+ for (i=nStartCol; i<=nEndCol; i++) // Test data
+ {
+ if (!aCol[i].IsEmptyData())
+ {
+ bFound = true;
+ SCROW nColY = aCol[i].GetLastDataPos();
+ if (nColY > nMaxY)
+ nMaxY = nColY;
+ }
+ if (bNotes && aCol[i].HasCellNotes() )
+ {
+ SCROW maxNoteRow =aCol[i].GetCellNotesMaxRow();
+ if (maxNoteRow > nMaxY)
+ {
+ bFound = true;
+ nMaxY = maxNoteRow;
+ }
+ }
+ if (aCol[i].HasSparklines())
+ {
+ SCROW maxNoteRow = aCol[i].GetSparklinesMaxRow();
+ if (maxNoteRow > nMaxY)
+ {
+ bFound = true;
+ nMaxY = maxNoteRow;
+ }
+ }
+ }
+
+ rEndRow = nMaxY;
+ return bFound;
+}
+
+bool ScTable::GetDataStart( SCCOL& rStartCol, SCROW& rStartRow ) const
+{
+ bool bFound = false;
+ SCCOL nMinX = aCol.size()-1;
+ SCROW nMinY = rDocument.MaxRow();
+ SCCOL i;
+
+ for (i=0; i<aCol.size(); i++) // Test attribute
+ {
+ SCROW nFirstRow;
+ if (aCol[i].GetFirstVisibleAttr( nFirstRow ))
+ {
+ if (!bFound)
+ nMinX = i;
+ bFound = true;
+ if (nFirstRow < nMinY)
+ nMinY = nFirstRow;
+ }
+ }
+
+ if (nMinX == 0) // omit attribute at the right
+ {
+ if ( aCol.size() > 1 && aCol[0].IsVisibleAttrEqual(aCol[1], 0, rDocument.MaxRow())) // no single ones
+ {
+ ++nMinX;
+ while ( nMinX<(aCol.size()-1) && aCol[nMinX].IsVisibleAttrEqual(aCol[nMinX-1], 0, rDocument.MaxRow()))
+ ++nMinX;
+ }
+ }
+
+ bool bDatFound = false;
+ for (i=0; i<aCol.size(); i++) // Test data
+ {
+ if (!aCol[i].IsEmptyData())
+ {
+ if (!bDatFound && i<nMinX)
+ nMinX = i;
+ bFound = bDatFound = true;
+ SCROW nRow = aCol[i].GetFirstDataPos();
+ if (nRow < nMinY)
+ nMinY = nRow;
+ }
+ if ( aCol[i].HasCellNotes() )
+ {
+ SCROW minNoteRow = aCol[i].GetCellNotesMinRow();
+ if (minNoteRow <= nMinY)
+ {
+ bFound = true;
+ nMinY = minNoteRow;
+ }
+ if (i<nMinX)
+ {
+ bFound = true;
+ nMinX = i;
+ }
+ }
+ if (aCol[i].HasSparklines())
+ {
+ SCROW minSparkline = aCol[i].GetSparklinesMinRow();
+ if (minSparkline <= nMinY)
+ {
+ bFound = true;
+ nMinY = minSparkline;
+ }
+ if (i < nMinX)
+ {
+ bFound = true;
+ nMinX = i;
+ }
+ }
+ }
+ rStartCol = nMinX;
+ rStartRow = nMinY;
+ return bFound;
+}
+
+void ScTable::GetDataArea( SCCOL& rStartCol, SCROW& rStartRow, SCCOL& rEndCol, SCROW& rEndRow,
+ bool bIncludeOld, bool bOnlyDown ) const
+{
+ // return the smallest area containing at least all contiguous cells having data. This area
+ // is a square containing also empty cells. It may shrink or extend the area given as input
+ // Flags as modifiers:
+ //
+ // bIncludeOld = true ensure that the returned area contains at least the initial area,
+ // independently of the emptiness of rows / columns (i.e. does not allow shrinking)
+ // bOnlyDown = true means extend / shrink the inputted area only down, i.e modify only rEndRow
+
+ rStartCol = std::min<SCCOL>( rStartCol, aCol.size()-1 );
+ rEndCol = std::min<SCCOL>( rEndCol, aCol.size()-1 );
+
+ bool bLeft = false;
+ bool bRight = false;
+ bool bTop = false;
+ bool bBottom = false;
+ bool bChanged = false;
+
+ // We need to cache sc::ColumnBlockConstPosition per each column.
+ std::vector< sc::ColumnBlockConstPosition > blockPos( rEndCol + 1 );
+ for( SCCOL i = 0; i <= rEndCol; ++i )
+ aCol[ i ].InitBlockPosition( blockPos[ i ] );
+
+ do
+ {
+ bChanged = false;
+
+ if (!bOnlyDown)
+ {
+ SCROW nStart = rStartRow;
+ SCROW nEnd = rEndRow;
+ if (nStart>0) --nStart;
+ if (nEnd<rDocument.MaxRow()) ++nEnd;
+
+ if (rEndCol < (aCol.size()-1))
+ if (!aCol[rEndCol+1].IsEmptyData(nStart,nEnd))
+ {
+ assert( int( blockPos.size()) == rEndCol + 1 );
+ ++rEndCol;
+ blockPos.resize( blockPos.size() + 1 );
+ aCol[ rEndCol ].InitBlockPosition( blockPos[ rEndCol ] );
+ bChanged = true;
+ bRight = true;
+ }
+
+ if (rStartCol > 0)
+ if (!aCol[rStartCol-1].IsEmptyData(nStart,nEnd))
+ {
+ --rStartCol;
+ bChanged = true;
+ bLeft = true;
+ }
+
+ if (rStartRow > 0)
+ {
+ SCROW nTest = rStartRow-1;
+ bool needExtend = false;
+ for ( SCCOL i = rStartCol; i<=rEndCol && !needExtend; i++)
+ if (aCol[i].HasDataAt(blockPos[i], nTest))
+ needExtend = true;
+ if (needExtend)
+ {
+ --rStartRow;
+ bChanged = true;
+ bTop = true;
+ }
+ }
+ }
+
+ if (rEndRow < rDocument.MaxRow())
+ {
+ SCROW nTest = rEndRow+1;
+ bool needExtend = false;
+ for ( SCCOL i = rStartCol; i<=rEndCol && !needExtend; i++)
+ if (aCol[i].HasDataAt(blockPos[ i ], nTest))
+ needExtend = true;
+ if (needExtend)
+ {
+ ++rEndRow;
+ bChanged = true;
+ bBottom = true;
+ }
+ }
+ }
+ while( bChanged );
+
+ if ( !bIncludeOld && !bOnlyDown )
+ {
+ if ( !bLeft )
+ while ( rStartCol < rEndCol && rStartCol < (aCol.size()-1) && aCol[rStartCol].IsEmptyData(rStartRow,rEndRow) )
+ ++rStartCol;
+
+ if ( !bRight )
+ while ( rEndCol > 0 && rStartCol < rEndCol && aCol[rEndCol].IsEmptyData(rStartRow,rEndRow) )
+ --rEndCol;
+
+ if ( !bTop && rStartRow < rDocument.MaxRow() && rStartRow < rEndRow )
+ {
+ bool bShrink = true;
+ do
+ {
+ for ( SCCOL i = rStartCol; i<=rEndCol && bShrink; i++)
+ if (aCol[i].HasDataAt(rStartRow))
+ bShrink = false;
+ if (bShrink)
+ ++rStartRow;
+ } while (bShrink && rStartRow < rDocument.MaxRow() && rStartRow < rEndRow);
+ }
+ }
+
+ if ( !bIncludeOld )
+ {
+ if ( !bBottom && rEndRow > 0 && rStartRow < rEndRow )
+ {
+ SCROW nLastDataRow = GetLastDataRow( rStartCol, rEndCol, rEndRow);
+ if (nLastDataRow < rEndRow)
+ rEndRow = std::max( rStartRow, nLastDataRow);
+ }
+ }
+}
+
+bool ScTable::GetDataAreaSubrange( ScRange& rRange ) const
+{
+ SCCOL nCol1 = rRange.aStart.Col(), nCol2 = rRange.aEnd.Col();
+
+ if ( nCol1 >= aCol.size() )
+ return false;
+
+ nCol2 = std::min<SCCOL>( nCol2, aCol.size()-1 );
+
+ SCROW nRow1 = rRange.aStart.Row(), nRow2 = rRange.aEnd.Row();
+
+ SCCOL nFirstNonEmptyCol = -1, nLastNonEmptyCol = -1;
+ SCROW nRowStart = nRow2, nRowEnd = nRow1;
+
+ for ( SCCOL nCol = nCol1; nCol <= nCol2; ++nCol )
+ {
+ SCROW nRowStartThis = nRow1, nRowEndThis = nRow2;
+ bool bTrimmed = aCol[nCol].TrimEmptyBlocks(nRowStartThis, nRowEndThis);
+ if ( bTrimmed )
+ {
+ if ( nFirstNonEmptyCol == -1 )
+ nFirstNonEmptyCol = nCol;
+ nLastNonEmptyCol = nCol;
+
+ nRowStart = std::min<SCROW>(nRowStart, nRowStartThis);
+ nRowEnd = std::max<SCROW>(nRowEnd, nRowEndThis);
+ }
+ }
+
+ if ( nFirstNonEmptyCol == -1 )
+ return false;
+
+ assert(nFirstNonEmptyCol <= nLastNonEmptyCol);
+ assert(nRowStart <= nRowEnd);
+
+ rRange.aStart.Set(nFirstNonEmptyCol, nRowStart, rRange.aStart.Tab());
+ rRange.aEnd.Set(nLastNonEmptyCol, nRowEnd, rRange.aEnd.Tab());
+
+ return true;
+}
+
+bool ScTable::ShrinkToUsedDataArea( bool& o_bShrunk, SCCOL& rStartCol, SCROW& rStartRow,
+ SCCOL& rEndCol, SCROW& rEndRow, bool bColumnsOnly, bool bStickyTopRow, bool bStickyLeftCol,
+ ScDataAreaExtras* pDataAreaExtras ) const
+{
+ rStartCol = std::min<SCCOL>( rStartCol, aCol.size()-1 );
+ // check for rEndCol is done below.
+
+ o_bShrunk = false;
+
+ PutInOrder( rStartCol, rEndCol);
+ PutInOrder( rStartRow, rEndRow);
+ if (rStartCol < 0)
+ {
+ rStartCol = 0;
+ o_bShrunk = true;
+ }
+ if (rStartRow < 0)
+ {
+ rStartRow = 0;
+ o_bShrunk = true;
+ }
+ if (rEndCol >= aCol.size())
+ {
+ rEndCol = aCol.size()-1;
+ o_bShrunk = true;
+ }
+ if (rEndRow > rDocument.MaxRow())
+ {
+ rEndRow = rDocument.MaxRow();
+ o_bShrunk = true;
+ }
+
+ while (rStartCol < rEndCol)
+ {
+ if (aCol[rEndCol].IsEmptyData( rStartRow, rEndRow))
+ {
+ if (pDataAreaExtras && pDataAreaExtras->mnEndCol < rEndCol)
+ {
+ // Check in order of likeliness.
+ if ( (pDataAreaExtras->mbCellFormats
+ && aCol[rEndCol].GetPatternCount( rStartRow, rEndRow) > 1
+ && aCol[rEndCol].HasVisibleAttrIn( rStartRow, rEndRow)) ||
+ (pDataAreaExtras->mbCellNotes
+ && !aCol[rEndCol].IsNotesEmptyBlock( rStartRow, rEndRow)) ||
+ (pDataAreaExtras->mbCellDrawObjects
+ && !aCol[rEndCol].IsDrawObjectsEmptyBlock( rStartRow, rEndRow)))
+ pDataAreaExtras->mnEndCol = rEndCol;
+ }
+
+ --rEndCol;
+ o_bShrunk = true;
+ }
+ else
+ break; // while
+ }
+
+ if (!bStickyLeftCol)
+ {
+ while (rStartCol < rEndCol)
+ {
+ if (aCol[rStartCol].IsEmptyData( rStartRow, rEndRow))
+ {
+ if (pDataAreaExtras && pDataAreaExtras->mnStartCol > rStartCol)
+ {
+ // Check in order of likeliness.
+ if ( (pDataAreaExtras->mbCellFormats
+ && aCol[rStartCol].GetPatternCount( rStartRow, rEndRow) > 1
+ && aCol[rStartCol].HasVisibleAttrIn( rStartRow, rEndRow)) ||
+ (pDataAreaExtras->mbCellNotes
+ && !aCol[rStartCol].IsNotesEmptyBlock( rStartRow, rEndRow)) ||
+ (pDataAreaExtras->mbCellDrawObjects
+ && !aCol[rStartCol].IsDrawObjectsEmptyBlock( rStartRow, rEndRow)))
+ pDataAreaExtras->mnStartCol = rStartCol;
+ }
+
+ ++rStartCol;
+ o_bShrunk = true;
+ }
+ else
+ break; // while
+ }
+ }
+
+ if (!bColumnsOnly)
+ {
+ while (rStartRow < rEndRow)
+ {
+ SCROW nLastDataRow = GetLastDataRow(rStartCol, rEndCol, rEndRow, pDataAreaExtras);
+ if (0 <= nLastDataRow && nLastDataRow < rEndRow)
+ {
+ rEndRow = std::max( rStartRow, nLastDataRow);
+ o_bShrunk = true;
+ }
+ else
+ break; // while
+ }
+
+ if (!bStickyTopRow)
+ {
+ while (rStartRow < rEndRow)
+ {
+ bool bFound = false;
+ for (SCCOL i=rStartCol; i<=rEndCol && !bFound; i++)
+ {
+ if (aCol[i].HasDataAt(rStartRow, pDataAreaExtras))
+ bFound = true;
+ }
+ if (!bFound)
+ {
+ ++rStartRow;
+ o_bShrunk = true;
+ }
+ else
+ break; // while
+ }
+ }
+ }
+
+ return rStartCol != rEndCol || (bColumnsOnly ?
+ !aCol[rStartCol].IsEmptyData( rStartRow, rEndRow) :
+ (rStartRow != rEndRow ||
+ aCol[rStartCol].HasDataAt( rStartRow, pDataAreaExtras)));
+}
+
+SCROW ScTable::GetLastDataRow( SCCOL nCol1, SCCOL nCol2, SCROW nLastRow, ScDataAreaExtras* pDataAreaExtras ) const
+{
+ if ( !IsColValid( nCol1 ) || !ValidCol( nCol2 ) )
+ return -1;
+
+ nCol2 = std::min<SCCOL>( nCol2, aCol.size() - 1 );
+
+ SCROW nNewLastRow = 0;
+ for (SCCOL i = nCol1; i <= nCol2; ++i)
+ {
+ SCROW nThis = aCol[i].GetLastDataPos(nLastRow, pDataAreaExtras);
+ if (nNewLastRow < nThis)
+ nNewLastRow = nThis;
+ }
+
+ return nNewLastRow;
+}
+
+bool ScTable::IsEmptyData( SCCOL nStartCol, SCROW nStartRow,
+ SCCOL nEndCol, SCROW nEndRow ) const
+{
+ for( SCCOL col : GetAllocatedColumnsRange( nStartCol, nEndCol ))
+ if( !aCol[col].IsEmptyData( nStartRow, nEndRow ))
+ return false;
+ return true;
+}
+
+SCSIZE ScTable::GetEmptyLinesInBlock( SCCOL nStartCol, SCROW nStartRow,
+ SCCOL nEndCol, SCROW nEndRow, ScDirection eDir ) const
+{
+ SCCOL nStartColOrig = nStartCol;
+ SCCOL nEndColOrig = nEndCol;
+ nStartCol = std::min<SCCOL>( nStartCol, aCol.size()-1 );
+ nEndCol = std::min<SCCOL>( nEndCol, aCol.size()-1 );
+
+ // The region is not allocated and does not contain any data.
+ if ( nStartColOrig != nStartCol )
+ return ( ((eDir == DIR_BOTTOM) || (eDir == DIR_TOP)) ?
+ static_cast<SCSIZE>(nEndRow - nStartRow + 1) :
+ static_cast<SCSIZE>(nEndColOrig - nStartColOrig + 1) );
+
+ SCSIZE nGapRight = static_cast<SCSIZE>(nEndColOrig - nEndCol);
+ SCSIZE nCount = 0;
+ SCCOL nCol;
+ if ((eDir == DIR_BOTTOM) || (eDir == DIR_TOP))
+ {
+ nCount = static_cast<SCSIZE>(nEndRow - nStartRow + 1);
+ for (nCol = nStartCol; nCol <= nEndCol; nCol++)
+ nCount = std::min(nCount, aCol[nCol].GetEmptyLinesInBlock(nStartRow, nEndRow, eDir));
+ }
+ else if (eDir == DIR_RIGHT)
+ {
+ nCol = nEndCol;
+ while ((nCol >= nStartCol) &&
+ aCol[nCol].IsEmptyData(nStartRow, nEndRow))
+ {
+ nCount++;
+ nCol--;
+ }
+ nCount += nGapRight;
+ }
+ else
+ {
+ nCol = nStartCol;
+ while ((nCol <= nEndCol) && aCol[nCol].IsEmptyData(nStartRow, nEndRow))
+ {
+ nCount++;
+ nCol++;
+ }
+
+ // If the area between nStartCol and nEndCol are empty,
+ // add the count of unallocated columns on the right.
+ if ( nCol > nEndCol )
+ nCount += nGapRight;
+ }
+ return nCount;
+}
+
+bool ScTable::IsEmptyLine( SCROW nRow, SCCOL nStartCol, SCCOL nEndCol ) const
+{
+ // The range of columns are unallocated hence empty.
+ if ( nStartCol >= aCol.size() )
+ return true;
+
+ nEndCol = std::min<SCCOL>( nEndCol, aCol.size()-1 );
+
+ for (SCCOL i=nStartCol; i<=nEndCol; i++)
+ if (aCol[i].HasDataAt(nRow))
+ return false;
+ return true;
+}
+
+void ScTable::LimitChartArea( SCCOL& rStartCol, SCROW& rStartRow, SCCOL& rEndCol, SCROW& rEndRow ) const
+{
+ rStartCol = std::min<SCCOL>( rStartCol, aCol.size()-1 );
+ rEndCol = std::min<SCCOL>( rEndCol, aCol.size()-1 );
+
+ while ( rStartCol<rEndCol && aCol[rStartCol].IsEmptyData(rStartRow,rEndRow) )
+ ++rStartCol;
+
+ while ( rStartCol<rEndCol && aCol[rEndCol].IsEmptyData(rStartRow,rEndRow) )
+ --rEndCol;
+
+ while ( rStartRow<rEndRow && IsEmptyLine(rStartRow, rStartCol, rEndCol) )
+ ++rStartRow;
+
+ // Optimised loop for finding the bottom of the area, can be costly in large
+ // spreadsheets.
+ SCROW lastDataPos = 0;
+ for (SCCOL i=rStartCol; i<=rEndCol; i++)
+ lastDataPos = std::max(lastDataPos, aCol[i].GetLastDataPos());
+ // reduce EndRow to the last row with data
+ rEndRow = std::min(rEndRow, lastDataPos);
+ // but make sure EndRow is >= StartRow
+ rEndRow = std::max(rStartRow, rEndRow);
+}
+
+SCCOL ScTable::FindNextVisibleCol( SCCOL nCol, bool bRight ) const
+{
+ if(bRight)
+ {
+ nCol++;
+ SCCOL nEnd = 0;
+ bool bHidden = rDocument.ColHidden(nCol, nTab, nullptr, &nEnd);
+ if(bHidden)
+ nCol = nEnd +1;
+
+ return std::min<SCCOL>(rDocument.MaxCol(), nCol);
+ }
+ else
+ {
+ nCol--;
+ SCCOL nStart = rDocument.MaxCol();
+ bool bHidden = rDocument.ColHidden(nCol, nTab, &nStart);
+ if(bHidden)
+ nCol = nStart - 1;
+
+ return std::max<SCCOL>(0, nCol);
+ }
+}
+
+SCCOL ScTable::FindNextVisibleColWithContent( SCCOL nCol, bool bRight, SCROW nRow ) const
+{
+ const SCCOL nLastCol = aCol.size() - 1;
+ if(bRight)
+ {
+ // If nCol is the last allocated column index, there won't be any content to its right.
+ // To maintain the original return behaviour, return rDocument.MaxCol().
+ if(nCol >= nLastCol)
+ return rDocument.MaxCol();
+
+ do
+ {
+ nCol++;
+ SCCOL nEndCol = 0;
+ bool bHidden = rDocument.ColHidden( nCol, nTab, nullptr, &nEndCol );
+ if(bHidden)
+ {
+ nCol = nEndCol +1;
+ // Can end search early as there is no data after nLastCol.
+ // For nCol == nLastCol, it may still have data so don't want to return rDocument.MaxCol().
+ if(nCol > nLastCol)
+ return rDocument.MaxCol();
+ }
+
+ if(aCol[nCol].HasVisibleDataAt(nRow))
+ return nCol;
+ }
+ while(nCol < nLastCol); // Stop search as soon as the last allocated column is searched.
+
+ return rDocument.MaxCol();
+ }
+ else
+ {
+ // If nCol is in the unallocated range [nLastCol+1, rDocument.MaxCol()], then move it directly to nLastCol
+ // as there is no data in the unallocated range. This also makes the search faster and avoids
+ // the need for more range checks in the loop below.
+ if ( nCol > nLastCol )
+ nCol = nLastCol;
+
+ if(nCol == 0)
+ return 0;
+
+ do
+ {
+ nCol--;
+ SCCOL nStartCol = rDocument.MaxCol();
+ bool bHidden = rDocument.ColHidden( nCol, nTab, &nStartCol );
+ if(bHidden)
+ {
+ nCol = nStartCol -1;
+ if(nCol <= 0)
+ return 0;
+ }
+
+ if(aCol[nCol].HasVisibleDataAt(nRow))
+ return nCol;
+ }
+ while(nCol > 0);
+
+ return 0;
+ }
+}
+
+void ScTable::FindAreaPos( SCCOL& rCol, SCROW& rRow, ScMoveDirection eDirection ) const
+{
+ const SCCOL nLastCol = aCol.size() - 1;
+
+ if (eDirection == SC_MOVE_LEFT || eDirection == SC_MOVE_RIGHT)
+ {
+ SCCOL nNewCol = rCol;
+ bool bThere = ( nNewCol <= nLastCol ) && aCol[nNewCol].HasVisibleDataAt(rRow);
+ bool bRight = (eDirection == SC_MOVE_RIGHT);
+ if (bThere)
+ {
+ if(nNewCol >= rDocument.MaxCol() && eDirection == SC_MOVE_RIGHT)
+ return;
+ else if(nNewCol == 0 && eDirection == SC_MOVE_LEFT)
+ return;
+
+ SCCOL nNextCol = FindNextVisibleCol( nNewCol, bRight );
+
+ if( nNextCol <= nLastCol && aCol[nNextCol].HasVisibleDataAt(rRow) )
+ {
+ bool bFound = false;
+ nNewCol = nNextCol;
+ do
+ {
+ nNextCol = FindNextVisibleCol( nNewCol, bRight );
+ if( nNextCol <= nLastCol && aCol[nNextCol].HasVisibleDataAt(rRow) )
+ nNewCol = nNextCol;
+ else
+ bFound = true;
+ }
+ while(!bFound && nNextCol > 0 && nNextCol < rDocument.MaxCol());
+ }
+ else
+ {
+ nNewCol = FindNextVisibleColWithContent(nNewCol, bRight, rRow);
+ }
+ }
+ else
+ {
+ nNewCol = FindNextVisibleColWithContent(nNewCol, bRight, rRow);
+ }
+
+ if (nNewCol<0)
+ nNewCol=0;
+ if (nNewCol>rDocument.MaxCol())
+ nNewCol=rDocument.MaxCol();
+ rCol = nNewCol;
+ }
+ else
+ {
+ if ( rCol <= nLastCol )
+ aCol[rCol].FindDataAreaPos(rRow,eDirection == SC_MOVE_DOWN);
+ else
+ {
+ // The cell (rCol, rRow) is equivalent to an empty cell (although not allocated).
+ // Set rRow to 0 or rDocument.MaxRow() depending on eDirection to maintain the behaviour of
+ // ScColumn::FindDataAreaPos() when the given column is empty.
+ rRow = ( eDirection == SC_MOVE_DOWN ) ? rDocument.MaxRow() : 0;
+ }
+ }
+}
+
+bool ScTable::ValidNextPos( SCCOL nCol, SCROW nRow, const ScMarkData& rMark,
+ bool bMarked, bool bUnprotected ) const
+{
+ if (!ValidCol(nCol) || !ValidRow(nRow))
+ return false;
+
+ if (rDocument.HasAttrib(nCol, nRow, nTab, nCol, nRow, nTab, HasAttrFlags::Overlapped))
+ // Skip an overlapped cell.
+ return false;
+
+ if (bMarked && !rMark.IsCellMarked(nCol,nRow))
+ return false;
+
+ /* TODO: for cursor movement *only* this should even take the protection
+ * options (select locked, select unlocked) into account, see
+ * ScTabView::SkipCursorHorizontal() and ScTabView::SkipCursorVertical(). */
+ if (bUnprotected && rDocument.HasAttrib(nCol, nRow, nTab, nCol, nRow, nTab, HasAttrFlags::Protected))
+ return false;
+
+ if (bMarked || bUnprotected) //TODO: also in other case ???
+ {
+ // Hidden cells must be skipped, as the cursor would end up on the next cell
+ // even if it is protected or not marked.
+ //TODO: control per Extra-Parameter, only for Cursor movement ???
+
+ if (RowHidden(nRow))
+ return false;
+
+ if (ColHidden(nCol))
+ return false;
+ }
+
+ return true;
+}
+
+// Skips the current cell if it is Hidden, Overlapped or Protected and Sheet is Protected
+bool ScTable::SkipRow( const SCCOL nCol, SCROW& rRow, const SCROW nMovY,
+ const ScMarkData& rMark, const bool bUp, const SCROW nUsedY,
+ const bool bMarked, const bool bSheetProtected ) const
+{
+ if ( !ValidRow( rRow ))
+ return false;
+
+ if (bSheetProtected && rDocument.HasAttrib( nCol, rRow, nTab, nCol, rRow, nTab, HasAttrFlags::Protected))
+ {
+ if ( rRow > nUsedY )
+ rRow = (bUp ? nUsedY : rDocument.MaxRow() + nMovY);
+ else
+ rRow += nMovY;
+
+ if (bMarked)
+ rRow = rMark.GetNextMarked( nCol, rRow, bUp );
+
+ return true;
+ }
+ else
+ {
+ bool bRowHidden = RowHidden( rRow );
+ bool bOverlapped = rDocument.HasAttrib( nCol, rRow, nTab, nCol, rRow, nTab, HasAttrFlags::Overlapped );
+
+ if ( bRowHidden || bOverlapped )
+ {
+ rRow += nMovY;
+ if (bMarked)
+ rRow = rMark.GetNextMarked( nCol, rRow, bUp );
+
+ return true;
+ }
+ }
+
+ return false;
+}
+
+void ScTable::GetNextPos( SCCOL& rCol, SCROW& rRow, SCCOL nMovX, SCROW nMovY,
+ bool bMarked, bool bUnprotected, const ScMarkData& rMark, SCCOL nTabStartCol ) const
+{
+ // Ensure bMarked is set only if there is a mark.
+ assert( !bMarked || rMark.IsMarked() || rMark.IsMultiMarked());
+
+ const bool bSheetProtected = IsProtected();
+
+ if ( bUnprotected && !bSheetProtected ) // Is sheet really protected?
+ bUnprotected = false;
+
+ SCCOL nCol = rCol + nMovX;
+ SCROW nRow = rRow + nMovY;
+
+ SCCOL nStartCol, nEndCol;
+ SCROW nStartRow, nEndRow;
+ if (bMarked)
+ {
+ ScRange aRange( ScAddress::UNINITIALIZED);
+ if (rMark.IsMarked())
+ aRange = rMark.GetMarkArea();
+ else if (rMark.IsMultiMarked())
+ aRange = rMark.GetMultiMarkArea();
+ else
+ {
+ // Covered by assert() above, but for NDEBUG build.
+ if (ValidColRow(nCol,nRow))
+ {
+ rCol = nCol;
+ rRow = nRow;
+ }
+ return;
+ }
+ nStartCol = aRange.aStart.Col();
+ nStartRow = aRange.aStart.Row();
+ nEndCol = aRange.aEnd.Col();
+ nEndRow = aRange.aEnd.Row();
+ }
+ else if (bUnprotected)
+ {
+ nStartCol = 0;
+ nStartRow = 0;
+ nEndCol = rCol;
+ nEndRow = rRow;
+ rDocument.GetPrintArea( nTab, nEndCol, nEndRow, true );
+ // Add some cols/rows to the print area (which is "content or
+ // visually different from empty") to enable travelling through
+ // protected forms with empty cells and no visual indicator.
+ // 42 might be good enough and not too much...
+ nEndCol = std::min<SCCOL>( nEndCol+42, rDocument.MaxCol());
+ nEndRow = std::min<SCROW>( nEndRow+42, rDocument.MaxRow());
+ }
+ else
+ {
+ // Invalid values show up for instance for Tab, when nothing is
+ // selected and not protected (left / right edge), then leave values
+ // unchanged.
+ if (ValidColRow(nCol,nRow))
+ {
+ rCol = nCol;
+ rRow = nRow;
+ }
+
+ // Caller ensures actually moving nMovY to jump to prev/next row's
+ // start col.
+ if (nTabStartCol != SC_TABSTART_NONE)
+ rCol = nTabStartCol;
+
+ return;
+ }
+
+ if ( nMovY && (bMarked || bUnprotected))
+ {
+ do
+ {
+ const bool bUp = (nMovY < 0);
+ const SCCOL nColAdd = (bUp ? -1 : 1);
+
+ if (bMarked)
+ nRow = rMark.GetNextMarked( nCol, nRow, bUp );
+
+ if (nTabStartCol != SC_TABSTART_NONE)
+ {
+ /* NOTE: If current rCol < nTabStartCol when going down, there
+ * is no way to detect if the previous Tab wrapped around to
+ * the next row or if it was a Shift+Tab going backwards. The
+ * result after a wrap is an odd jump to the next row's
+ * nTabStartCol, which is logical though and always has been
+ * the case. Similar for rCol > nTabStartCol when going up.
+ * Related, it would be nice to limit advancing the position
+ * within bounds even if another wrap would occur, but again we
+ * can't tell if previously Tab or Shift+Tab was used, so we
+ * don't know if it would be nTabStartCol to nEndCol (for Tab)
+ * or nStartCol to nTabStartCol (for Shift+Tab). */
+
+ // Continue moving horizontally.
+ nMovX = nColAdd;
+ nCol = nTabStartCol;
+ break; // do
+ }
+
+ while ( SkipRow( nCol, nRow, nMovY, rMark, bUp, nEndRow, bMarked, bSheetProtected ))
+ ;
+
+ sal_uInt16 nWrap = 0;
+ while ( nRow < nStartRow || nRow > nEndRow )
+ {
+ nCol += nColAdd;
+
+ while (nStartCol <= nCol && nCol <= nEndCol && ValidCol(nCol) && ColHidden(nCol))
+ nCol += nColAdd; // skip hidden cols
+
+ if (nCol < nStartCol)
+ {
+ nCol = nEndCol;
+
+ if (++nWrap >= 2)
+ return;
+ }
+ else if (nCol > nEndCol)
+ {
+ nCol = nStartCol;
+
+ if (++nWrap >= 2)
+ return;
+ }
+ if (nRow < nStartRow)
+ nRow = nEndRow;
+ else if (nRow > nEndRow)
+ nRow = nStartRow;
+
+ if (bMarked)
+ nRow = rMark.GetNextMarked( nCol, nRow, bUp );
+
+ while ( SkipRow( nCol, nRow, nMovY, rMark, bUp, nEndRow, bMarked, bSheetProtected ))
+ ;
+ }
+ } while (false);
+ }
+
+ if ( nMovX && ( bMarked || bUnprotected ) )
+ {
+ // wrap initial skip counting:
+ if (nCol < nStartCol)
+ {
+ nCol = nEndCol;
+ --nRow;
+ if (nRow < nStartRow)
+ nRow = nEndRow;
+ }
+ if (nCol > nEndCol)
+ {
+ nCol = nStartCol;
+ ++nRow;
+ if (nRow > nEndRow)
+ nRow = nStartRow;
+ }
+
+ if ( !ValidNextPos(nCol, nRow, rMark, bMarked, bUnprotected) )
+ {
+ const SCCOL nColCount = nEndCol - nStartCol + 1;
+ std::unique_ptr<SCROW[]> pNextRows( new SCROW[nColCount]);
+ const SCCOL nLastCol = aCol.size() - 1;
+ const bool bUp = (nMovX < 0); // Moving left also means moving up in rows.
+ const SCROW nRowAdd = (bUp ? -1 : 1);
+ sal_uInt16 nWrap = 0;
+
+ if (bUp)
+ {
+ for (SCCOL i = 0; i < nColCount; ++i)
+ pNextRows[i] = (i + nStartCol > nCol) ? (nRow + nRowAdd) : nRow;
+ }
+ else
+ {
+ for (SCCOL i = 0; i < nColCount; ++i)
+ pNextRows[i] = (i + nStartCol < nCol) ? (nRow + nRowAdd) : nRow;
+ }
+ do
+ {
+ SCROW nNextRow = pNextRows[nCol - nStartCol] + nRowAdd;
+ if ( bMarked )
+ nNextRow = rMark.GetNextMarked( nCol, nNextRow, bUp );
+ if ( bUnprotected )
+ nNextRow = ( nCol <= nLastCol ) ? aCol[nCol].GetNextUnprotected( nNextRow, bUp ) :
+ aDefaultColData.GetNextUnprotected( nNextRow, bUp );
+ pNextRows[nCol - nStartCol] = nNextRow;
+
+ if (bUp)
+ {
+ SCROW nMaxRow = nStartRow - 1;
+ for (SCCOL i = 0; i < nColCount; ++i)
+ {
+ if (pNextRows[i] >= nMaxRow) // when two equal the right one
+ {
+ nMaxRow = pNextRows[i];
+ nCol = i + nStartCol;
+ }
+ }
+ nRow = nMaxRow;
+
+ if ( nRow < nStartRow )
+ {
+ if (++nWrap >= 2)
+ return;
+ nCol = nEndCol;
+ nRow = nEndRow;
+ for (SCCOL i = 0; i < nColCount; ++i)
+ pNextRows[i] = nEndRow; // do it all over again
+ }
+ }
+ else
+ {
+ SCROW nMinRow = nEndRow + 1;
+ for (SCCOL i = 0; i < nColCount; ++i)
+ {
+ if (pNextRows[i] < nMinRow) // when two equal the left one
+ {
+ nMinRow = pNextRows[i];
+ nCol = i + nStartCol;
+ }
+ }
+ nRow = nMinRow;
+
+ if ( nRow > nEndRow )
+ {
+ if (++nWrap >= 2)
+ return;
+ nCol = nStartCol;
+ nRow = nStartRow;
+ for (SCCOL i = 0; i < nColCount; ++i)
+ pNextRows[i] = nStartRow; // do it all over again
+ }
+ }
+ }
+ while ( !ValidNextPos(nCol, nRow, rMark, bMarked, bUnprotected) );
+ }
+ }
+
+ if (ValidColRow(nCol,nRow))
+ {
+ rCol = nCol;
+ rRow = nRow;
+ }
+}
+
+bool ScTable::GetNextMarkedCell( SCCOL& rCol, SCROW& rRow, const ScMarkData& rMark ) const
+{
+ ++rRow; // next row
+
+ while ( rCol < aCol.size() )
+ {
+ ScMarkArray aArray( rMark.GetMarkArray( rCol ) );
+ while ( rRow <= rDocument.MaxRow() )
+ {
+ SCROW nStart = aArray.GetNextMarked( rRow, false );
+ if ( nStart <= rDocument.MaxRow() )
+ {
+ SCROW nEnd = aArray.GetMarkEnd( nStart, false );
+
+ const sc::CellStoreType& rCells = aCol[rCol].maCells;
+ std::pair<sc::CellStoreType::const_iterator,size_t> aPos = rCells.position(nStart);
+ sc::CellStoreType::const_iterator it = aPos.first;
+ SCROW nTestRow = nStart;
+ if (it->type == sc::element_type_empty)
+ {
+ // Skip the empty block.
+ nTestRow += it->size - aPos.second;
+ ++it;
+ if (it == rCells.end())
+ {
+ // No more block. Move on to the next column.
+ rRow = rDocument.MaxRow() + 1;
+ continue;
+ }
+ }
+
+ if (nTestRow <= nEnd)
+ {
+ // Cell found.
+ rRow = nTestRow;
+ return true;
+ }
+
+ rRow = nEnd + 1; // Search for next selected range
+ }
+ else
+ rRow = rDocument.MaxRow() + 1; // End of column
+ }
+ rRow = 0;
+ ++rCol; // test next column
+ }
+
+ // Though searched only the allocated columns, it is equivalent to a search till rDocument.MaxCol().
+ rCol = rDocument.MaxCol() + 1;
+ return false; // Through all columns
+}
+
+void ScTable::UpdateDrawRef( UpdateRefMode eUpdateRefMode, SCCOL nCol1, SCROW nRow1, SCTAB nTab1,
+ SCCOL nCol2, SCROW nRow2, SCTAB nTab2,
+ SCCOL nDx, SCROW nDy, SCTAB nDz, bool bUpdateNoteCaptionPos )
+{
+ if ( !(nTab >= nTab1 && nTab <= nTab2 && nDz == 0) ) // only within the table
+ return;
+
+ ScDrawLayer* pDrawLayer = rDocument.GetDrawLayer();
+ if ( eUpdateRefMode != URM_COPY && pDrawLayer )
+ {
+ if ( eUpdateRefMode == URM_MOVE )
+ { // source range
+ nCol1 = sal::static_int_cast<SCCOL>( nCol1 - nDx );
+ nRow1 = sal::static_int_cast<SCROW>( nRow1 - nDy );
+ nCol2 = sal::static_int_cast<SCCOL>( nCol2 - nDx );
+ nRow2 = sal::static_int_cast<SCROW>( nRow2 - nDy );
+ }
+ pDrawLayer->MoveArea( nTab, nCol1,nRow1, nCol2,nRow2, nDx,nDy,
+ (eUpdateRefMode == URM_INSDEL), bUpdateNoteCaptionPos );
+ }
+}
+
+void ScTable::UpdateReference(
+ sc::RefUpdateContext& rCxt, ScDocument* pUndoDoc, bool bIncludeDraw, bool bUpdateNoteCaptionPos )
+{
+ bool bUpdated = false;
+ UpdateRefMode eUpdateRefMode = rCxt.meMode;
+ SCCOL nDx = rCxt.mnColDelta;
+ SCROW nDy = rCxt.mnRowDelta;
+ SCTAB nDz = rCxt.mnTabDelta;
+ SCCOL nCol1 = rCxt.maRange.aStart.Col(), nCol2 = rCxt.maRange.aEnd.Col();
+ SCROW nRow1 = rCxt.maRange.aStart.Row(), nRow2 = rCxt.maRange.aEnd.Row();
+ SCTAB nTab1 = rCxt.maRange.aStart.Tab(), nTab2 = rCxt.maRange.aEnd.Tab();
+
+ // Named expressions need to be updated before formulas accessing them.
+ if (mpRangeName)
+ mpRangeName->UpdateReference(rCxt, nTab);
+
+ if (rCxt.meMode == URM_COPY )
+ {
+ for( SCCOL col : GetAllocatedColumnsRange( rCxt.maRange.aStart.Col(), rCxt.maRange.aEnd.Col()))
+ bUpdated |= aCol[col].UpdateReference(rCxt, pUndoDoc);
+ }
+ else
+ {
+ for( SCCOL col : GetAllocatedColumnsRange( 0, rDocument.MaxCol()))
+ bUpdated |= aCol[col].UpdateReference(rCxt, pUndoDoc);
+ }
+
+ if ( bIncludeDraw )
+ UpdateDrawRef( eUpdateRefMode, nCol1, nRow1, nTab1, nCol2, nRow2, nTab2, nDx, nDy, nDz, bUpdateNoteCaptionPos );
+
+ if ( nTab >= nTab1 && nTab <= nTab2 && nDz == 0 ) // print ranges: only within the table
+ {
+ SCTAB nSTab = nTab;
+ SCTAB nETab = nTab;
+ SCCOL nSCol = 0;
+ SCROW nSRow = 0;
+ SCCOL nECol = 0;
+ SCROW nERow = 0;
+ bool bRecalcPages = false;
+
+ for ( auto& rPrintRange : aPrintRanges )
+ {
+ nSCol = rPrintRange.aStart.Col();
+ nSRow = rPrintRange.aStart.Row();
+ nECol = rPrintRange.aEnd.Col();
+ nERow = rPrintRange.aEnd.Row();
+
+ // do not try to modify sheet index of print range
+ if ( ScRefUpdate::Update( &rDocument, eUpdateRefMode,
+ nCol1,nRow1,nTab, nCol2,nRow2,nTab,
+ nDx,nDy,0,
+ nSCol,nSRow,nSTab, nECol,nERow,nETab ) )
+ {
+ rPrintRange = ScRange( nSCol, nSRow, 0, nECol, nERow, 0 );
+ bRecalcPages = true;
+ }
+ }
+
+ if ( moRepeatColRange )
+ {
+ nSCol = moRepeatColRange->aStart.Col();
+ nSRow = moRepeatColRange->aStart.Row();
+ nECol = moRepeatColRange->aEnd.Col();
+ nERow = moRepeatColRange->aEnd.Row();
+
+ // do not try to modify sheet index of repeat range
+ if ( ScRefUpdate::Update( &rDocument, eUpdateRefMode,
+ nCol1,nRow1,nTab, nCol2,nRow2,nTab,
+ nDx,nDy,0,
+ nSCol,nSRow,nSTab, nECol,nERow,nETab ) )
+ {
+ *moRepeatColRange = ScRange( nSCol, nSRow, 0, nECol, nERow, 0 );
+ bRecalcPages = true;
+ nRepeatStartX = nSCol; // for UpdatePageBreaks
+ nRepeatEndX = nECol;
+ }
+ }
+
+ if ( moRepeatRowRange )
+ {
+ nSCol = moRepeatRowRange->aStart.Col();
+ nSRow = moRepeatRowRange->aStart.Row();
+ nECol = moRepeatRowRange->aEnd.Col();
+ nERow = moRepeatRowRange->aEnd.Row();
+
+ // do not try to modify sheet index of repeat range
+ if ( ScRefUpdate::Update( &rDocument, eUpdateRefMode,
+ nCol1,nRow1,nTab, nCol2,nRow2,nTab,
+ nDx,nDy,0,
+ nSCol,nSRow,nSTab, nECol,nERow,nETab ) )
+ {
+ *moRepeatRowRange = ScRange( nSCol, nSRow, 0, nECol, nERow, 0 );
+ bRecalcPages = true;
+ nRepeatStartY = nSRow; // for UpdatePageBreaks
+ nRepeatEndY = nERow;
+ }
+ }
+
+ // updating print ranges is not necessary with multiple print ranges
+ if ( bRecalcPages && GetPrintRangeCount() <= 1 )
+ {
+ UpdatePageBreaks(nullptr);
+
+ rDocument.RepaintRange( ScRange(0,0,nTab,rDocument.MaxCol(),rDocument.MaxRow(),nTab) );
+ }
+ }
+
+ if (bUpdated)
+ SetStreamValid(false);
+
+ if(mpCondFormatList)
+ mpCondFormatList->UpdateReference(rCxt);
+
+ if (pTabProtection)
+ pTabProtection->updateReference( eUpdateRefMode, rDocument, rCxt.maRange, nDx, nDy, nDz);
+}
+
+void ScTable::UpdateTranspose( const ScRange& rSource, const ScAddress& rDest,
+ ScDocument* pUndoDoc )
+{
+ for (auto const & rpCol : aCol)
+ rpCol->UpdateTranspose( rSource, rDest, pUndoDoc );
+}
+
+void ScTable::UpdateGrow( const ScRange& rArea, SCCOL nGrowX, SCROW nGrowY )
+{
+ for (auto const & rpCol : aCol)
+ rpCol->UpdateGrow( rArea, nGrowX, nGrowY );
+}
+
+void ScTable::UpdateInsertTab( sc::RefUpdateInsertTabContext& rCxt )
+{
+ // Store the old tab number in sc::UpdatedRangeNames for
+ // ScTokenArray::AdjustReferenceOnInsertedTab() to check with
+ // isNameModified()
+ if (mpRangeName)
+ mpRangeName->UpdateInsertTab(rCxt, nTab);
+
+ if (nTab >= rCxt.mnInsertPos)
+ {
+ nTab += rCxt.mnSheets;
+ if (pDBDataNoName)
+ pDBDataNoName->UpdateMoveTab(nTab - 1 ,nTab);
+ }
+
+ if (mpCondFormatList)
+ mpCondFormatList->UpdateInsertTab(rCxt);
+
+ if (pTabProtection)
+ pTabProtection->updateReference( URM_INSDEL, rDocument,
+ ScRange( 0, 0, rCxt.mnInsertPos, rDocument.MaxCol(), rDocument.MaxRow(), MAXTAB),
+ 0, 0, rCxt.mnSheets);
+
+ for (SCCOL i=0; i < aCol.size(); i++)
+ aCol[i].UpdateInsertTab(rCxt);
+
+ SetStreamValid(false);
+}
+
+void ScTable::UpdateDeleteTab( sc::RefUpdateDeleteTabContext& rCxt )
+{
+ // Store the old tab number in sc::UpdatedRangeNames for
+ // ScTokenArray::AdjustReferenceOnDeletedTab() to check with
+ // isNameModified()
+ if (mpRangeName)
+ mpRangeName->UpdateDeleteTab(rCxt, nTab);
+
+ if (nTab > rCxt.mnDeletePos)
+ {
+ nTab -= rCxt.mnSheets;
+ if (pDBDataNoName)
+ pDBDataNoName->UpdateMoveTab(nTab + 1,nTab);
+ }
+
+ if (mpCondFormatList)
+ mpCondFormatList->UpdateDeleteTab(rCxt);
+
+ if (pTabProtection)
+ pTabProtection->updateReference( URM_INSDEL, rDocument,
+ ScRange( 0, 0, rCxt.mnDeletePos, rDocument.MaxCol(), rDocument.MaxRow(), MAXTAB),
+ 0, 0, -rCxt.mnSheets);
+
+ for (SCCOL i = 0; i < aCol.size(); ++i)
+ aCol[i].UpdateDeleteTab(rCxt);
+
+ SetStreamValid(false);
+}
+
+void ScTable::UpdateMoveTab(
+ sc::RefUpdateMoveTabContext& rCxt, SCTAB nTabNo, ScProgress* pProgress )
+{
+ nTab = nTabNo;
+ if (mpRangeName)
+ mpRangeName->UpdateMoveTab(rCxt, nTab);
+
+ if (pDBDataNoName)
+ pDBDataNoName->UpdateMoveTab(rCxt.mnOldPos, rCxt.mnNewPos);
+
+ if(mpCondFormatList)
+ mpCondFormatList->UpdateMoveTab(rCxt);
+
+ if (pTabProtection)
+ pTabProtection->updateReference( URM_REORDER, rDocument,
+ ScRange( 0, 0, rCxt.mnOldPos, rDocument.MaxCol(), rDocument.MaxRow(), MAXTAB),
+ 0, 0, rCxt.mnNewPos - rCxt.mnOldPos);
+
+ for ( SCCOL i=0; i < aCol.size(); i++ )
+ {
+ aCol[i].UpdateMoveTab(rCxt, nTabNo);
+ if (pProgress)
+ pProgress->SetState(pProgress->GetState() + aCol[i].GetCodeCount());
+ }
+
+ SetStreamValid(false);
+}
+
+void ScTable::UpdateCompile( bool bForceIfNameInUse )
+{
+ for (SCCOL i=0; i < aCol.size(); i++)
+ {
+ aCol[i].UpdateCompile( bForceIfNameInUse );
+ }
+}
+
+void ScTable::SetTabNo(SCTAB nNewTab)
+{
+ nTab = nNewTab;
+ for (SCCOL i=0; i < aCol.size(); i++)
+ aCol[i].SetTabNo(nNewTab);
+}
+
+void ScTable::FindRangeNamesInUse(SCCOL nCol1, SCROW nRow1, SCCOL nCol2, SCROW nRow2,
+ sc::UpdatedRangeNames& rIndexes) const
+{
+ for (SCCOL i = nCol1; i <= nCol2 && IsColValid( i ); i++)
+ aCol[i].FindRangeNamesInUse(nRow1, nRow2, rIndexes);
+}
+
+void ScTable::ExtendPrintArea( OutputDevice* pDev,
+ SCCOL /* nStartCol */, SCROW nStartRow, SCCOL& rEndCol, SCROW nEndRow )
+{
+ if ( !mpColFlags || !pRowFlags )
+ {
+ OSL_FAIL("ExtendPrintArea: No ColInfo or RowInfo");
+ return;
+ }
+
+ Point aPix1000 = pDev->LogicToPixel(Point(1000,1000), MapMode(MapUnit::MapTwip));
+ double nPPTX = aPix1000.X() / 1000.0;
+ double nPPTY = aPix1000.Y() / 1000.0;
+
+ // First, mark those columns that we need to skip i.e. hidden and empty columns.
+
+ ScFlatBoolColSegments aSkipCols(rDocument.MaxCol());
+ aSkipCols.setFalse(0, rDocument.MaxCol());
+ for (SCCOL i = 0; i <= rDocument.MaxCol(); ++i)
+ {
+ SCCOL nLastCol = i;
+ if (ColHidden(i, nullptr, &nLastCol))
+ {
+ // Columns are hidden in this range.
+ aSkipCols.setTrue(i, nLastCol);
+ }
+ else
+ {
+ // These columns are visible. Check for empty columns.
+ for (SCCOL j = i; j <= nLastCol; ++j)
+ {
+ if ( j >= aCol.size() )
+ {
+ aSkipCols.setTrue( j, rDocument.MaxCol() );
+ break;
+ }
+ if (aCol[j].GetCellCount() == 0)
+ // empty
+ aSkipCols.setTrue(j,j);
+ }
+ }
+ i = nLastCol;
+ }
+
+ ScFlatBoolColSegments::RangeData aColData;
+ for (SCCOL nCol = rEndCol; nCol >= 0; --nCol)
+ {
+ if (!aSkipCols.getRangeData(nCol, aColData))
+ // Failed to get the data. This should never happen!
+ return;
+
+ if (aColData.mbValue)
+ {
+ // Skip these columns.
+ nCol = aColData.mnCol1; // move toward 0.
+ continue;
+ }
+
+ // These are visible and non-empty columns.
+ for (SCCOL nDataCol = nCol; 0 <= nDataCol && nDataCol >= aColData.mnCol1; --nDataCol)
+ {
+ SCCOL nPrintCol = nDataCol;
+ VisibleDataCellIterator aIter(rDocument, *mpHiddenRows, aCol[nDataCol]);
+ ScRefCellValue aCell = aIter.reset(nStartRow);
+ if (aCell.isEmpty())
+ // No visible cells found in this column. Skip it.
+ continue;
+
+ while (!aCell.isEmpty())
+ {
+ SCCOL nNewCol = nDataCol;
+ SCROW nRow = aIter.getRow();
+ if (nRow > nEndRow)
+ // Went past the last row position. Bail out.
+ break;
+
+ MaybeAddExtraColumn(nNewCol, nRow, pDev, nPPTX, nPPTY);
+ if (nNewCol > nPrintCol)
+ nPrintCol = nNewCol;
+ aCell = aIter.next();
+ }
+
+ if (nPrintCol > rEndCol)
+ // Make sure we don't shrink the print area.
+ rEndCol = nPrintCol;
+ }
+ nCol = aColData.mnCol1; // move toward 0.
+ }
+}
+
+void ScTable::MaybeAddExtraColumn(SCCOL& rCol, SCROW nRow, OutputDevice* pDev, double nPPTX, double nPPTY)
+{
+ // tdf#128873 we do not need to calculate text width (heavy operation)
+ // when we for sure know that an additional column will not be added
+ if (GetAllocatedColumnsCount() > rCol + 1)
+ {
+ ScRefCellValue aNextCell = aCol[rCol + 1].GetCellValue(nRow);
+ if (!aNextCell.isEmpty())
+ {
+ // return rCol as is
+ return;
+ }
+ }
+
+ ScColumn& rColumn = aCol[rCol];
+ ScRefCellValue aCell = rColumn.GetCellValue(nRow);
+ if (!aCell.hasString())
+ return;
+
+ tools::Long nPixel = rColumn.GetTextWidth(nRow);
+
+ // Width already calculated in Idle-Handler ?
+ if ( TEXTWIDTH_DIRTY == nPixel )
+ {
+ ScNeededSizeOptions aOptions;
+ aOptions.bTotalSize = true;
+ aOptions.bFormula = false; //TODO: pass as parameter
+ aOptions.bSkipMerged = false;
+
+ Fraction aZoom(1,1);
+ nPixel = rColumn.GetNeededSize(
+ nRow, pDev, nPPTX, nPPTY, aZoom, aZoom, true, aOptions, nullptr );
+
+ rColumn.SetTextWidth(nRow, static_cast<sal_uInt16>(nPixel));
+ }
+
+ tools::Long nTwips = static_cast<tools::Long>(nPixel / nPPTX);
+ tools::Long nDocW = GetColWidth( rCol );
+
+ tools::Long nMissing = nTwips - nDocW;
+ if ( nMissing > 0 )
+ {
+ // look at alignment
+
+ const ScPatternAttr* pPattern = GetPattern( rCol, nRow );
+ const SfxItemSet* pCondSet = rDocument.GetCondResult( rCol, nRow, nTab );
+
+ SvxCellHorJustify eHorJust =
+ pPattern->GetItem( ATTR_HOR_JUSTIFY, pCondSet ).GetValue();
+ if ( eHorJust == SvxCellHorJustify::Center )
+ nMissing /= 2; // distributed into both directions
+ else
+ {
+ // STANDARD is LEFT (only text is handled here)
+ bool bRight = ( eHorJust == SvxCellHorJustify::Right );
+ if ( IsLayoutRTL() )
+ bRight = !bRight;
+ if ( bRight )
+ nMissing = 0; // extended only to the left (logical)
+ }
+ }
+
+ SCCOL nNewCol = rCol;
+ while (nMissing > 0 && nNewCol < rDocument.MaxCol())
+ {
+ auto nNextCol = nNewCol + 1;
+ bool bNextEmpty = true;
+ if (GetAllocatedColumnsCount() > nNextCol)
+ {
+ ScRefCellValue aNextCell = aCol[nNextCol].GetCellValue(nRow);
+ bNextEmpty = aNextCell.isEmpty();
+ }
+ if (!bNextEmpty)
+ {
+ // Cell content in a next column ends display of this string.
+ nMissing = 0;
+ }
+ else
+ nMissing -= GetColWidth(++nNewCol);
+ }
+ rCol = nNewCol;
+}
+
+namespace {
+
+class SetTableIndex
+{
+ SCTAB mnTab;
+public:
+ explicit SetTableIndex(SCTAB nTab) : mnTab(nTab) {}
+
+ void operator() (ScRange& rRange) const
+ {
+ rRange.aStart.SetTab(mnTab);
+ rRange.aEnd.SetTab(mnTab);
+ }
+};
+
+}
+
+void ScTable::CopyPrintRange(const ScTable& rTable)
+{
+ // The table index shouldn't be used when the print range is used, but
+ // just in case set the correct table index.
+
+ aPrintRanges = rTable.aPrintRanges;
+ ::std::for_each(aPrintRanges.begin(), aPrintRanges.end(), SetTableIndex(nTab));
+
+ bPrintEntireSheet = rTable.bPrintEntireSheet;
+
+ moRepeatColRange.reset();
+ if (rTable.moRepeatColRange)
+ {
+ moRepeatColRange.emplace(*rTable.moRepeatColRange);
+ moRepeatColRange->aStart.SetTab(nTab);
+ moRepeatColRange->aEnd.SetTab(nTab);
+ }
+
+ moRepeatRowRange.reset();
+ if (rTable.moRepeatRowRange)
+ {
+ moRepeatRowRange.emplace(*rTable.moRepeatRowRange);
+ moRepeatRowRange->aStart.SetTab(nTab);
+ moRepeatRowRange->aEnd.SetTab(nTab);
+ }
+}
+
+void ScTable::SetRepeatColRange( std::optional<ScRange> oNew )
+{
+ moRepeatColRange = std::move(oNew);
+
+ SetStreamValid(false);
+
+ InvalidatePageBreaks();
+}
+
+void ScTable::SetRepeatRowRange( std::optional<ScRange> oNew )
+{
+ moRepeatRowRange = std::move(oNew);
+
+ SetStreamValid(false);
+
+ InvalidatePageBreaks();
+}
+
+void ScTable::ClearPrintRanges()
+{
+ aPrintRanges.clear();
+ bPrintEntireSheet = false;
+
+ SetStreamValid(false);
+
+ InvalidatePageBreaks(); // #i117952# forget page breaks for an old print range
+}
+
+void ScTable::AddPrintRange( const ScRange& rNew )
+{
+ bPrintEntireSheet = false;
+ if( aPrintRanges.size() < 0xFFFF )
+ aPrintRanges.push_back( rNew );
+
+ SetStreamValid(false);
+
+ InvalidatePageBreaks();
+}
+
+void ScTable::SetPrintEntireSheet()
+{
+ if( !IsPrintEntireSheet() )
+ {
+ ClearPrintRanges();
+ bPrintEntireSheet = true;
+ }
+}
+
+const ScRange* ScTable::GetPrintRange(sal_uInt16 nPos) const
+{
+ return (nPos < GetPrintRangeCount()) ? &aPrintRanges[ nPos ] : nullptr;
+}
+
+void ScTable::FillPrintSaver( ScPrintSaverTab& rSaveTab ) const
+{
+ rSaveTab.SetAreas( std::vector(aPrintRanges), bPrintEntireSheet );
+ rSaveTab.SetRepeat( moRepeatColRange, moRepeatRowRange );
+}
+
+void ScTable::RestorePrintRanges( const ScPrintSaverTab& rSaveTab )
+{
+ aPrintRanges = rSaveTab.GetPrintRanges();
+ bPrintEntireSheet = rSaveTab.IsEntireSheet();
+ SetRepeatColRange( rSaveTab.GetRepeatCol() );
+ SetRepeatRowRange( rSaveTab.GetRepeatRow() );
+
+ InvalidatePageBreaks(); // #i117952# forget page breaks for an old print range
+ UpdatePageBreaks(nullptr);
+}
+
+ScTable::VisibleDataCellIterator::VisibleDataCellIterator(const ScDocument& rDoc, ScFlatBoolRowSegments& rRowSegs, ScColumn& rColumn) :
+ mrDocument(rDoc),
+ mrRowSegs(rRowSegs),
+ mrColumn(rColumn),
+ mnCurRow(ROW_NOT_FOUND),
+ mnUBound(ROW_NOT_FOUND)
+{
+}
+
+ScRefCellValue ScTable::VisibleDataCellIterator::reset(SCROW nRow)
+{
+ if (nRow > mrDocument.MaxRow())
+ {
+ mnCurRow = ROW_NOT_FOUND;
+ return ScRefCellValue();
+ }
+
+ ScFlatBoolRowSegments::RangeData aData;
+ if (!mrRowSegs.getRangeData(nRow, aData))
+ {
+ mnCurRow = ROW_NOT_FOUND;
+ return ScRefCellValue();
+ }
+
+ if (!aData.mbValue)
+ {
+ // specified row is visible. Take it.
+ mnCurRow = nRow;
+ mnUBound = aData.mnRow2;
+ }
+ else
+ {
+ // specified row is not-visible. The first visible row is the start of
+ // the next segment.
+ mnCurRow = aData.mnRow2 + 1;
+ mnUBound = mnCurRow; // get range data on the next iteration.
+ if (mnCurRow > mrDocument.MaxRow())
+ {
+ // Make sure the row doesn't exceed our current limit.
+ mnCurRow = ROW_NOT_FOUND;
+ return ScRefCellValue();
+ }
+ }
+
+ maCell = mrColumn.GetCellValue(mnCurRow);
+ if (!maCell.isEmpty())
+ // First visible cell found.
+ return maCell;
+
+ // Find a first visible cell below this row (if any).
+ return next();
+}
+
+ScRefCellValue ScTable::VisibleDataCellIterator::next()
+{
+ if (mnCurRow == ROW_NOT_FOUND)
+ return ScRefCellValue();
+
+ while (mrColumn.GetNextDataPos(mnCurRow))
+ {
+ if (mnCurRow > mnUBound)
+ {
+ // We don't know the visibility of this row range. Query it.
+ ScFlatBoolRowSegments::RangeData aData;
+ if (!mrRowSegs.getRangeData(mnCurRow, aData))
+ {
+ mnCurRow = ROW_NOT_FOUND;
+ return ScRefCellValue();
+ }
+
+ if (aData.mbValue)
+ {
+ // This row is invisible. Skip to the last invisible row and
+ // try again.
+ mnCurRow = mnUBound = aData.mnRow2;
+ continue;
+ }
+
+ // This row is visible.
+ mnUBound = aData.mnRow2;
+ }
+
+ maCell = mrColumn.GetCellValue(mnCurRow);
+ if (!maCell.isEmpty())
+ return maCell;
+ }
+
+ mnCurRow = ROW_NOT_FOUND;
+ return ScRefCellValue();
+}
+
+void ScTable::SetAnonymousDBData(std::unique_ptr<ScDBData> pDBData)
+{
+ pDBDataNoName = std::move(pDBData);
+}
+
+sal_uLong ScTable::AddCondFormat( std::unique_ptr<ScConditionalFormat> pNew )
+{
+ if(!mpCondFormatList)
+ mpCondFormatList.reset(new ScConditionalFormatList());
+
+ sal_uInt32 nMax = mpCondFormatList->getMaxKey();
+
+ pNew->SetKey(nMax+1);
+ mpCondFormatList->InsertNew(std::move(pNew));
+
+ return nMax + 1;
+}
+
+SvtScriptType ScTable::GetScriptType( SCCOL nCol, SCROW nRow ) const
+{
+ if ( !IsColValid( nCol ) )
+ return SvtScriptType::NONE;
+
+ return aCol[nCol].GetScriptType(nRow);
+}
+
+void ScTable::SetScriptType( SCCOL nCol, SCROW nRow, SvtScriptType nType )
+{
+ if (!ValidCol(nCol))
+ return;
+
+ aCol[nCol].SetScriptType(nRow, nType);
+}
+
+SvtScriptType ScTable::GetRangeScriptType(
+ sc::ColumnBlockPosition& rBlockPos, SCCOL nCol, SCROW nRow1, SCROW nRow2 )
+{
+ if ( !IsColValid( nCol ) )
+ return SvtScriptType::NONE;
+
+ sc::CellStoreType::iterator itr = aCol[nCol].maCells.begin();
+ return aCol[nCol].GetRangeScriptType(rBlockPos.miCellTextAttrPos, nRow1, nRow2, itr);
+}
+
+formula::FormulaTokenRef ScTable::ResolveStaticReference( SCCOL nCol, SCROW nRow )
+{
+ if ( !ValidCol( nCol ) || !ValidRow( nRow ) )
+ return formula::FormulaTokenRef();
+ if ( nCol >= aCol.size() )
+ // Return a value of 0.0 if column not exists
+ return formula::FormulaTokenRef(new formula::FormulaDoubleToken(0.0));
+ return aCol[nCol].ResolveStaticReference(nRow);
+}
+
+formula::FormulaTokenRef ScTable::ResolveStaticReference( SCCOL nCol1, SCROW nRow1, SCCOL nCol2, SCROW nRow2 )
+{
+ if (nCol2 < nCol1 || nRow2 < nRow1)
+ return formula::FormulaTokenRef();
+
+ if ( !ValidCol( nCol1 ) || !ValidCol( nCol2 ) || !ValidRow( nRow1 ) || !ValidRow( nRow2 ) )
+ return formula::FormulaTokenRef();
+
+ SCCOL nMaxCol;
+ if ( nCol2 >= aCol.size() )
+ nMaxCol = aCol.size() - 1;
+ else
+ nMaxCol = nCol2;
+
+ ScMatrixRef pMat(new ScMatrix(nCol2-nCol1+1, nRow2-nRow1+1, 0.0));
+ for (SCCOL nCol = nCol1; nCol <= nMaxCol; ++nCol)
+ {
+ if (!aCol[nCol].ResolveStaticReference(*pMat, nCol2-nCol1, nRow1, nRow2))
+ // Column contains non-static cell. Failed.
+ return formula::FormulaTokenRef();
+ }
+
+ return formula::FormulaTokenRef(new ScMatrixToken(pMat));
+}
+
+formula::VectorRefArray ScTable::FetchVectorRefArray( SCCOL nCol, SCROW nRow1, SCROW nRow2 )
+{
+ if (nRow2 < nRow1)
+ return formula::VectorRefArray();
+
+ if ( !IsColValid( nCol ) || !ValidRow( nRow1 ) || !ValidRow( nRow2 ) )
+ return formula::VectorRefArray();
+
+ return aCol[nCol].FetchVectorRefArray(nRow1, nRow2);
+}
+
+#ifdef DBG_UTIL
+void ScTable::AssertNoInterpretNeeded( SCCOL nCol, SCROW nRow1, SCROW nRow2 )
+{
+ assert( nRow2 >= nRow1 );
+ assert( IsColValid( nCol ) && ValidRow( nRow1 ) && ValidRow( nRow2 ) );
+ return aCol[nCol].AssertNoInterpretNeeded(nRow1, nRow2);
+}
+#endif
+
+bool ScTable::HandleRefArrayForParallelism( SCCOL nCol, SCROW nRow1, SCROW nRow2, const ScFormulaCellGroupRef& mxGroup )
+{
+ if (nRow2 < nRow1)
+ return false;
+
+ if ( !IsColValid( nCol ) || !ValidRow( nRow1 ) || !ValidRow( nRow2 ) )
+ return false;
+
+ mpHiddenCols->makeReady();
+ mpHiddenRows->makeReady();
+ mpFilteredCols->makeReady();
+ mpFilteredRows->makeReady();
+
+ return aCol[nCol].HandleRefArrayForParallelism(nRow1, nRow2, mxGroup);
+}
+
+ScRefCellValue ScTable::GetRefCellValue( SCCOL nCol, SCROW nRow )
+{
+ if ( !IsColRowValid( nCol, nRow ) )
+ return ScRefCellValue();
+
+ return aCol[nCol].GetCellValue(nRow);
+}
+
+ScRefCellValue ScTable::GetRefCellValue( SCCOL nCol, SCROW nRow, sc::ColumnBlockPosition& rBlockPos )
+{
+ if ( !IsColRowValid( nCol, nRow ) )
+ return ScRefCellValue();
+
+ return aCol[nCol].GetCellValue(rBlockPos, nRow);
+}
+
+SvtBroadcaster* ScTable::GetBroadcaster( SCCOL nCol, SCROW nRow )
+{
+ if ( !IsColRowValid( nCol, nRow ) )
+ return nullptr;
+
+ return aCol[nCol].GetBroadcaster(nRow);
+}
+
+void ScTable::DeleteBroadcasters(
+ sc::ColumnBlockPosition& rBlockPos, SCCOL nCol, SCROW nRow1, SCROW nRow2 )
+{
+ if ( !IsColValid( nCol ) )
+ return;
+
+ aCol[nCol].DeleteBroadcasters(rBlockPos, nRow1, nRow2);
+}
+
+void ScTable::DeleteEmptyBroadcasters()
+{
+ for( auto& col : aCol )
+ col->DeleteEmptyBroadcasters();
+}
+
+void ScTable::FillMatrix( ScMatrix& rMat, SCCOL nCol1, SCROW nRow1, SCCOL nCol2, SCROW nRow2, svl::SharedStringPool* pPool ) const
+{
+ size_t nMatCol = 0;
+ nCol2 = ClampToAllocatedColumns(nCol2);
+ for (SCCOL nCol = nCol1; nCol <= nCol2; ++nCol, ++nMatCol)
+ aCol[nCol].FillMatrix(rMat, nMatCol, nRow1, nRow2, pPool);
+}
+
+void ScTable::InterpretDirtyCells( SCCOL nCol1, SCROW nRow1, SCCOL nCol2, SCROW nRow2 )
+{
+ nCol2 = ClampToAllocatedColumns(nCol2);
+ for (SCCOL nCol = nCol1; nCol <= nCol2; ++nCol)
+ aCol[nCol].InterpretDirtyCells(nRow1, nRow2);
+}
+
+bool ScTable::InterpretCellsIfNeeded( SCCOL nCol1, SCROW nRow1, SCCOL nCol2, SCROW nRow2 )
+{
+ nCol2 = ClampToAllocatedColumns(nCol2);
+ bool allInterpreted = true;
+ for (SCCOL nCol = nCol1; nCol <= nCol2; ++nCol)
+ if(!aCol[nCol].InterpretCellsIfNeeded(nRow1, nRow2))
+ allInterpreted = false;
+ return allInterpreted;
+}
+
+void ScTable::SetFormulaResults( SCCOL nCol, SCROW nRow, const double* pResults, size_t nLen )
+{
+ if (!ValidCol(nCol))
+ return;
+
+ aCol[nCol].SetFormulaResults(nRow, pResults, nLen);
+}
+
+void ScTable::CalculateInColumnInThread( ScInterpreterContext& rContext,
+ SCCOL nColStart, SCCOL nColEnd,
+ SCROW nRowStart, SCROW nRowEnd,
+ unsigned nThisThread, unsigned nThreadsTotal)
+{
+ if (!ValidCol(nColStart) || !ValidCol(nColEnd))
+ return;
+
+ size_t nLen = nRowEnd - nRowStart + 1;
+ size_t nOffset = 0;
+ for (SCCOL nCurrCol = nColStart; nCurrCol <= nColEnd; ++nCurrCol)
+ {
+ aCol[nCurrCol].CalculateInThread( rContext, nRowStart, nLen, nOffset, nThisThread, nThreadsTotal );
+ nOffset += nLen;
+ }
+}
+
+void ScTable::HandleStuffAfterParallelCalculation( SCCOL nColStart, SCCOL nColEnd, SCROW nRow, size_t nLen,
+ ScInterpreter* pInterpreter)
+{
+ assert(ValidCol(nColStart) && ValidCol(nColEnd));
+
+ for (SCCOL nCurrCol = nColStart; nCurrCol <= nColEnd; ++nCurrCol)
+ aCol[nCurrCol].HandleStuffAfterParallelCalculation( nRow, nLen, pInterpreter );
+}
+
+#if DUMP_COLUMN_STORAGE
+void ScTable::DumpColumnStorage( SCCOL nCol ) const
+{
+ if ( !IsColValid( nCol ) )
+ return;
+
+ aCol[nCol].DumpColumnStorage();
+}
+#endif
+
+const SvtBroadcaster* ScTable::GetBroadcaster( SCCOL nCol, SCROW nRow ) const
+{
+ if ( !IsColRowValid( nCol, nRow ) )
+ return nullptr;
+
+ return aCol[nCol].GetBroadcaster(nRow);
+}
+
+void ScTable::DeleteConditionalFormat( sal_uLong nIndex )
+{
+ mpCondFormatList->erase(nIndex);
+}
+
+void ScTable::SetCondFormList( ScConditionalFormatList* pNew )
+{
+ mpCondFormatList.reset( pNew );
+}
+
+ScConditionalFormatList* ScTable::GetCondFormList()
+{
+ if(!mpCondFormatList)
+ mpCondFormatList.reset( new ScConditionalFormatList() );
+
+ return mpCondFormatList.get();
+}
+
+const ScConditionalFormatList* ScTable::GetCondFormList() const
+{
+ return mpCondFormatList.get();
+}
+
+ScColumnsRange ScTable::GetWritableColumnsRange(SCCOL nColBegin, SCCOL nColEnd)
+{
+ // because the range is inclusive, some code will pass nColEnd<nColBegin to indicate an empty range
+ if (nColEnd < nColBegin)
+ return ScColumnsRange(-1, -1);
+ assert( nColEnd >= 0 && nColEnd <= GetDoc().MaxCol());
+ CreateColumnIfNotExists(nColEnd);
+ return GetColumnsRange(nColBegin, nColEnd);
+}
+
+ScColumnsRange ScTable::GetAllocatedColumnsRange(SCCOL nColBegin, SCCOL nColEnd) const
+{
+ if (nColBegin >= aCol.size())
+ return ScColumnsRange(-1, -1);
+ // clamp end of range to available columns
+ if (nColEnd >= aCol.size())
+ nColEnd = aCol.size() - 1;
+ return GetColumnsRange(nColBegin, nColEnd);
+}
+
+ScColumnsRange ScTable::GetColumnsRange(SCCOL nColBegin, SCCOL nColEnd) const
+{
+ // because the range is inclusive, some code will pass nColEnd<nColBegin to indicate an empty range
+ if (nColEnd < nColBegin)
+ return ScColumnsRange(-1, -1);
+ assert( nColBegin >= 0 && nColBegin <= GetDoc().MaxCol());
+ assert( nColEnd >= 0 && nColEnd <= GetDoc().MaxCol());
+ return ScColumnsRange(nColBegin, nColEnd + 1); // change inclusive end to past-end
+}
+
+// out-of-line the cold part of the CreateColumnIfNotExists function
+void ScTable::CreateColumnIfNotExistsImpl( const SCCOL nScCol )
+{
+ // When doing multi-threaded load of, e.g. XLS files, we can hit this, which calls
+ // into SfxItemPool::Put, in parallel with other code that calls into SfxItemPool::Put,
+ // which is bad since that code is not thread-safe.
+ SolarMutexGuard aGuard;
+ const SCCOL aOldColSize = aCol.size();
+ aCol.resize( rDocument.GetSheetLimits(), static_cast< size_t >( nScCol + 1 ) );
+ for (SCCOL i = aOldColSize; i <= nScCol; i++)
+ aCol[i].Init( i, nTab, rDocument, false );
+}
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sc/source/core/data/table2.cxx b/sc/source/core/data/table2.cxx
new file mode 100644
index 000000000..713be792a
--- /dev/null
+++ b/sc/source/core/data/table2.cxx
@@ -0,0 +1,4372 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4; fill-column: 100 -*- */
+/*
+ * 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 <algorithm>
+#include <memory>
+#include <table.hxx>
+#include <patattr.hxx>
+#include <docpool.hxx>
+#include <formulacell.hxx>
+#include <document.hxx>
+#include <drwlayer.hxx>
+#include <olinetab.hxx>
+#include <stlpool.hxx>
+#include <attarray.hxx>
+#include <markdata.hxx>
+#include <dociter.hxx>
+#include <conditio.hxx>
+#include <chartlis.hxx>
+#include <fillinfo.hxx>
+#include <bcaslot.hxx>
+#include <postit.hxx>
+#include <sheetevents.hxx>
+#include <segmenttree.hxx>
+#include <dbdata.hxx>
+#include <tokenarray.hxx>
+#include <clipcontext.hxx>
+#include <types.hxx>
+#include <editutil.hxx>
+#include <mtvcellfunc.hxx>
+#include <refupdatecontext.hxx>
+#include <scopetools.hxx>
+#include <tabprotection.hxx>
+#include <columnspanset.hxx>
+#include <rowheightcontext.hxx>
+#include <listenercontext.hxx>
+#include <compressedarray.hxx>
+#include <refdata.hxx>
+#include <docsh.hxx>
+
+#include <scitems.hxx>
+#include <editeng/boxitem.hxx>
+#include <editeng/editobj.hxx>
+#include <o3tl/safeint.hxx>
+#include <o3tl/unit_conversion.hxx>
+#include <osl/diagnose.h>
+#include <sal/log.hxx>
+#include <svl/poolcach.hxx>
+#include <unotools/charclass.hxx>
+#include <math.h>
+
+namespace {
+
+class ColumnRegroupFormulaCells
+{
+ ScColContainer& mrCols;
+ std::vector<ScAddress>* mpGroupPos;
+
+public:
+ ColumnRegroupFormulaCells( ScColContainer& rCols, std::vector<ScAddress>* pGroupPos ) :
+ mrCols(rCols), mpGroupPos(pGroupPos) {}
+
+ void operator() (SCCOL nCol)
+ {
+ mrCols[nCol].RegroupFormulaCells(mpGroupPos);
+ }
+};
+
+}
+
+sal_uInt16 ScTable::GetTextWidth(SCCOL nCol, SCROW nRow) const
+{
+ return aCol[nCol].GetTextWidth(nRow);
+}
+
+bool ScTable::SetOutlineTable( const ScOutlineTable* pNewOutline )
+{
+ sal_uInt16 nOldSizeX = 0;
+ sal_uInt16 nOldSizeY = 0;
+ sal_uInt16 nNewSizeX = 0;
+ sal_uInt16 nNewSizeY = 0;
+
+ if (pOutlineTable)
+ {
+ nOldSizeX = pOutlineTable->GetColArray().GetDepth();
+ nOldSizeY = pOutlineTable->GetRowArray().GetDepth();
+ pOutlineTable.reset();
+ }
+
+ if (pNewOutline)
+ {
+ pOutlineTable.reset(new ScOutlineTable( *pNewOutline ));
+ nNewSizeX = pOutlineTable->GetColArray().GetDepth();
+ nNewSizeY = pOutlineTable->GetRowArray().GetDepth();
+ }
+
+ return ( nNewSizeX != nOldSizeX || nNewSizeY != nOldSizeY ); // changed size?
+}
+
+void ScTable::StartOutlineTable()
+{
+ if (!pOutlineTable)
+ pOutlineTable.reset(new ScOutlineTable);
+}
+
+void ScTable::SetSheetEvents( std::unique_ptr<ScSheetEvents> pNew )
+{
+ pSheetEvents = std::move(pNew);
+
+ SetCalcNotification( false ); // discard notifications before the events were set
+
+ SetStreamValid(false);
+}
+
+void ScTable::SetCalcNotification( bool bSet )
+{
+ bCalcNotification = bSet;
+}
+
+bool ScTable::TestInsertRow( SCCOL nStartCol, SCCOL nEndCol, SCROW nStartRow, SCSIZE nSize ) const
+{
+ if ( nStartCol==0 && nEndCol==rDocument.MaxCol() && pOutlineTable )
+ if (!pOutlineTable->TestInsertRow(nSize))
+ return false;
+
+ SCCOL maxCol = ClampToAllocatedColumns(nEndCol);
+ for (SCCOL i=nStartCol; i<=maxCol; i++)
+ if (!aCol[i].TestInsertRow(nStartRow, nSize))
+ return false;
+
+ if( maxCol != nEndCol )
+ if (!aDefaultColData.TestInsertRow(nSize))
+ return false;
+
+ return true;
+}
+
+void ScTable::InsertRow( SCCOL nStartCol, SCCOL nEndCol, SCROW nStartRow, SCSIZE nSize )
+{
+ if (nStartCol==0 && nEndCol==rDocument.MaxCol())
+ {
+ if (mpRowHeights && pRowFlags)
+ {
+ mpRowHeights->insertSegment(nStartRow, nSize);
+ CRFlags nNewFlags = pRowFlags->Insert( nStartRow, nSize);
+ // only copy manual size flag, clear all others
+ if (nNewFlags != CRFlags::NONE && (nNewFlags != CRFlags::ManualSize))
+ pRowFlags->SetValue( nStartRow, nStartRow + nSize - 1,
+ nNewFlags & CRFlags::ManualSize);
+ }
+
+ if (pOutlineTable)
+ pOutlineTable->InsertRow( nStartRow, nSize );
+
+ mpFilteredRows->insertSegment(nStartRow, nSize);
+ mpHiddenRows->insertSegment(nStartRow, nSize);
+
+ if (!maRowManualBreaks.empty())
+ {
+ // Copy all breaks up to nStartRow (non-inclusive).
+ ::std::set<SCROW>::iterator itr1 = maRowManualBreaks.lower_bound(nStartRow);
+ ::std::set<SCROW> aNewBreaks(maRowManualBreaks.begin(), itr1);
+
+ // Copy all breaks from nStartRow (inclusive) to the last element,
+ // but add nSize to each value.
+ ::std::set<SCROW>::iterator itr2 = maRowManualBreaks.end();
+ for (; itr1 != itr2; ++itr1)
+ aNewBreaks.insert(static_cast<SCROW>(*itr1 + nSize));
+
+ maRowManualBreaks.swap(aNewBreaks);
+ }
+ }
+
+ for (SCCOL j : GetAllocatedColumnsRange(nStartCol, nEndCol))
+ aCol[j].InsertRow( nStartRow, nSize );
+ aDefaultColData.InsertRow( nStartRow, nSize );
+
+ mpCondFormatList->InsertRow(nTab, nStartCol, nEndCol, nStartRow, nSize);
+
+ InvalidatePageBreaks();
+
+ // TODO: In the future we may want to check if the table has been
+ // really modified before setting the stream invalid.
+ SetStreamValid(false);
+}
+
+void ScTable::DeleteRow(
+ const sc::ColumnSet& rRegroupCols, SCCOL nStartCol, SCCOL nEndCol, SCROW nStartRow, SCSIZE nSize,
+ bool* pUndoOutline, std::vector<ScAddress>* pGroupPos )
+{
+ if (nStartCol==0 && nEndCol==rDocument.MaxCol())
+ {
+ if (pRowFlags)
+ pRowFlags->Remove( nStartRow, nSize);
+
+ if (mpRowHeights)
+ mpRowHeights->removeSegment(nStartRow, nStartRow+nSize);
+
+ if (pOutlineTable)
+ if (pOutlineTable->DeleteRow( nStartRow, nSize ))
+ if (pUndoOutline)
+ *pUndoOutline = true;
+
+ mpFilteredRows->removeSegment(nStartRow, nStartRow+nSize);
+ mpHiddenRows->removeSegment(nStartRow, nStartRow+nSize);
+
+ if (!maRowManualBreaks.empty())
+ {
+ // Erase all manual breaks between nStartRow and nStartRow + nSize - 1 (inclusive).
+ std::set<SCROW>::iterator itr1 = maRowManualBreaks.lower_bound(nStartRow);
+ std::set<SCROW>::iterator itr2 = maRowManualBreaks.upper_bound(static_cast<SCROW>(nStartRow + nSize - 1));
+ maRowManualBreaks.erase(itr1, itr2);
+
+ // Copy all breaks from the 1st element up to nStartRow to the new container.
+ itr1 = maRowManualBreaks.lower_bound(nStartRow);
+ ::std::set<SCROW> aNewBreaks(maRowManualBreaks.begin(), itr1);
+
+ // Copy all breaks from nStartRow to the last element, but subtract each value by nSize.
+ itr2 = maRowManualBreaks.end();
+ for (; itr1 != itr2; ++itr1)
+ aNewBreaks.insert(static_cast<SCROW>(*itr1 - nSize));
+
+ maRowManualBreaks.swap(aNewBreaks);
+ }
+ }
+
+ { // scope for bulk broadcast
+ ScBulkBroadcast aBulkBroadcast( rDocument.GetBASM(), SfxHintId::ScDataChanged);
+ for (SCCOL j=nStartCol; j<=ClampToAllocatedColumns(nEndCol); j++)
+ aCol[j].DeleteRow(nStartRow, nSize, pGroupPos);
+ }
+
+ std::vector<SCCOL> aRegroupCols;
+ rRegroupCols.getColumns(nTab, aRegroupCols);
+ std::for_each(
+ aRegroupCols.begin(), aRegroupCols.end(), ColumnRegroupFormulaCells(aCol, pGroupPos));
+
+ InvalidatePageBreaks();
+
+ // TODO: In the future we may want to check if the table has been
+ // really modified before setting the stream invalid.
+ SetStreamValid(false);
+}
+
+bool ScTable::TestInsertCol( SCROW nStartRow, SCROW nEndRow, SCSIZE nSize ) const
+{
+ if ( nSize > o3tl::make_unsigned(rDocument.MaxCol()) )
+ return false;
+
+ if ( nStartRow==0 && nEndRow==rDocument.MaxRow() && pOutlineTable
+ && ! pOutlineTable->TestInsertCol(nSize) )
+ return false;
+
+ auto range = GetAllocatedColumnsRange( rDocument.MaxCol() - static_cast<SCCOL>(nSize) + 1, rDocument.MaxCol() );
+ for (auto it = range.rbegin(); it != range.rend(); ++it )
+ if (! aCol[*it].TestInsertCol(nStartRow, nEndRow))
+ return false;
+
+ return true;
+}
+
+void ScTable::InsertCol(
+ const sc::ColumnSet& rRegroupCols, SCCOL nStartCol, SCROW nStartRow, SCROW nEndRow, SCSIZE nSize )
+{
+ if (nStartRow==0 && nEndRow==rDocument.MaxRow())
+ {
+ if (mpColWidth && mpColFlags)
+ {
+ mpColWidth->InsertPreservingSize(nStartCol, nSize, STD_COL_WIDTH);
+ // The inserted columns have the same widths as the columns, which were selected for insert.
+ for (SCSIZE i=0; i < std::min(rDocument.MaxCol()-nSize-nStartCol, nSize); ++i)
+ mpColWidth->SetValue(nStartCol + i, mpColWidth->GetValue(nStartCol+i+nSize));
+ mpColFlags->InsertPreservingSize(nStartCol, nSize, CRFlags::NONE);
+ }
+ if (pOutlineTable)
+ pOutlineTable->InsertCol( nStartCol, nSize );
+
+ mpHiddenCols->insertSegment(nStartCol, static_cast<SCCOL>(nSize));
+ mpFilteredCols->insertSegment(nStartCol, static_cast<SCCOL>(nSize));
+
+ if (!maColManualBreaks.empty())
+ {
+ // Copy all breaks up to nStartCol (non-inclusive).
+ ::std::set<SCCOL>::iterator itr1 = maColManualBreaks.lower_bound(nStartCol);
+ ::std::set<SCCOL> aNewBreaks(maColManualBreaks.begin(), itr1);
+
+ // Copy all breaks from nStartCol (inclusive) to the last element,
+ // but add nSize to each value.
+ ::std::set<SCCOL>::iterator itr2 = maColManualBreaks.end();
+ for (; itr1 != itr2; ++itr1)
+ aNewBreaks.insert(static_cast<SCCOL>(*itr1 + nSize));
+
+ maColManualBreaks.swap(aNewBreaks);
+ }
+ }
+
+ // Make sure there are enough columns at the end.
+ CreateColumnIfNotExists(std::min<SCCOL>(rDocument.MaxCol(), std::max(nStartCol, aCol.size()) + nSize - 1 ));
+ if ((nStartRow == 0) && (nEndRow == rDocument.MaxRow()))
+ {
+ // Move existing columns back, this will swap last empty columns in the inserted place.
+ for (SCCOL nCol = aCol.size() - 1 - nSize; nCol >= nStartCol; --nCol)
+ aCol[nCol].SwapCol(aCol[nCol+nSize]);
+ }
+ else
+ {
+ for (SCSIZE i=0; static_cast<SCCOL>(i+nSize)+nStartCol < aCol.size(); i++)
+ aCol[aCol.size() - 1 - nSize - i].MoveTo(nStartRow, nEndRow, aCol[aCol.size() - 1 - i]);
+ }
+
+ std::vector<SCCOL> aRegroupCols;
+ rRegroupCols.getColumns(nTab, aRegroupCols);
+ std::for_each(aRegroupCols.begin(), aRegroupCols.end(), ColumnRegroupFormulaCells(aCol, nullptr));
+
+ if (nStartCol>0) // copy old attributes
+ {
+ sal_uInt16 nWhichArray[2];
+ nWhichArray[0] = ATTR_MERGE;
+ nWhichArray[1] = 0;
+
+ sc::CopyToDocContext aCxt(rDocument);
+ for (SCSIZE i=0; i<nSize; i++)
+ {
+ aCol[nStartCol-1].CopyToColumn(aCxt, nStartRow, nEndRow, InsertDeleteFlags::ATTRIB,
+ false, aCol[nStartCol+i] );
+ aCol[nStartCol+i].RemoveFlags( nStartRow, nEndRow,
+ ScMF::Hor | ScMF::Ver | ScMF::Auto );
+ aCol[nStartCol+i].ClearItems( nStartRow, nEndRow, nWhichArray );
+ }
+ }
+
+ mpCondFormatList->InsertCol(nTab, nStartRow, nEndRow, nStartCol, nSize);
+
+ InvalidatePageBreaks();
+
+ // TODO: In the future we may want to check if the table has been
+ // really modified before setting the stream invalid.
+ SetStreamValid(false);
+}
+
+void ScTable::DeleteCol(
+ const sc::ColumnSet& rRegroupCols, SCCOL nStartCol, SCROW nStartRow, SCROW nEndRow, SCSIZE nSize, bool* pUndoOutline )
+{
+ if (nStartRow==0 && nEndRow==rDocument.MaxRow())
+ {
+ if (mpColWidth && mpColFlags)
+ {
+ assert( nStartCol + nSize <= o3tl::make_unsigned(rDocument.MaxCol()+1) ); // moving 0 if ==rDocument.MaxCol()+1 is correct
+ mpColWidth->RemovePreservingSize(nStartCol, nSize, STD_COL_WIDTH);
+ mpColFlags->RemovePreservingSize(nStartCol, nSize, CRFlags::NONE);
+ }
+ if (pOutlineTable)
+ if (pOutlineTable->DeleteCol( nStartCol, nSize ))
+ if (pUndoOutline)
+ *pUndoOutline = true;
+
+ SCCOL nRmSize = nStartCol + static_cast<SCCOL>(nSize);
+ mpHiddenCols->removeSegment(nStartCol, nRmSize);
+ mpFilteredCols->removeSegment(nStartCol, nRmSize);
+
+ if (!maColManualBreaks.empty())
+ {
+ // Erase all manual breaks between nStartCol and nStartCol + nSize - 1 (inclusive).
+ std::set<SCCOL>::iterator itr1 = maColManualBreaks.lower_bound(nStartCol);
+ std::set<SCCOL>::iterator itr2 = maColManualBreaks.upper_bound(static_cast<SCCOL>(nStartCol + nSize - 1));
+ maColManualBreaks.erase(itr1, itr2);
+
+ // Copy all breaks from the 1st element up to nStartCol to the new container.
+ itr1 = maColManualBreaks.lower_bound(nStartCol);
+ ::std::set<SCCOL> aNewBreaks(maColManualBreaks.begin(), itr1);
+
+ // Copy all breaks from nStartCol to the last element, but subtract each value by nSize.
+ itr2 = maColManualBreaks.end();
+ for (; itr1 != itr2; ++itr1)
+ aNewBreaks.insert(static_cast<SCCOL>(*itr1 - nSize));
+
+ maColManualBreaks.swap(aNewBreaks);
+ }
+ }
+
+ for (SCCOL col = nStartCol; col <= ClampToAllocatedColumns(nStartCol + nSize - 1); ++col)
+ aCol[col].DeleteArea(nStartRow, nEndRow, InsertDeleteFlags::ALL, false);
+
+ if ((nStartRow == 0) && (nEndRow == rDocument.MaxRow()))
+ {
+ for (SCCOL nCol = nStartCol + nSize; nCol < aCol.size(); ++nCol)
+ aCol[nCol].SwapCol(aCol[nCol - nSize]);
+ }
+ else
+ {
+ for (SCSIZE i=0; static_cast<SCCOL>(i+nSize)+nStartCol < aCol.size(); i++)
+ aCol[nStartCol + nSize + i].MoveTo(nStartRow, nEndRow, aCol[nStartCol + i]);
+ }
+
+ std::vector<SCCOL> aRegroupCols;
+ rRegroupCols.getColumns(nTab, aRegroupCols);
+ std::for_each(aRegroupCols.begin(), aRegroupCols.end(), ColumnRegroupFormulaCells(aCol, nullptr));
+
+ InvalidatePageBreaks();
+
+ // TODO: In the future we may want to check if the table has been
+ // really modified before setting the stream invalid.
+ SetStreamValid(false);
+}
+
+void ScTable::DeleteArea(
+ SCCOL nCol1, SCROW nRow1, SCCOL nCol2, SCROW nRow2, InsertDeleteFlags nDelFlag,
+ bool bBroadcast, sc::ColumnSpanSet* pBroadcastSpans )
+{
+ if ( nCol2 >= aCol.size() ) nCol2 = aCol.size() - 1;
+ if (nRow2 > rDocument.MaxRow()) nRow2 = rDocument.MaxRow();
+ if (ValidColRow(nCol1, nRow1) && ValidColRow(nCol2, nRow2))
+ {
+ { // scope for bulk broadcast
+ ScBulkBroadcast aBulkBroadcast( rDocument.GetBASM(), SfxHintId::ScDataChanged);
+ for (SCCOL i = nCol1; i <= nCol2; i++)
+ aCol[i].DeleteArea(nRow1, nRow2, nDelFlag, bBroadcast, pBroadcastSpans);
+ }
+
+ // Do not set protected cell in a protected table
+
+ if ( IsProtected() && (nDelFlag & InsertDeleteFlags::ATTRIB) )
+ {
+ ScPatternAttr aPattern(rDocument.GetPool());
+ aPattern.GetItemSet().Put( ScProtectionAttr( false ) );
+ ApplyPatternArea( nCol1, nRow1, nCol2, nRow2, aPattern );
+ }
+
+ if( nDelFlag & InsertDeleteFlags::ATTRIB )
+ mpCondFormatList->DeleteArea( nCol1, nRow1, nCol2, nRow2 );
+ }
+
+ // TODO: In the future we may want to check if the table has been
+ // really modified before setting the stream invalid.
+ SetStreamValid(false);
+}
+
+void ScTable::DeleteSelection( InsertDeleteFlags nDelFlag, const ScMarkData& rMark, bool bBroadcast )
+{
+ { // scope for bulk broadcast
+ ScBulkBroadcast aBulkBroadcast( rDocument.GetBASM(), SfxHintId::ScDataChanged);
+ for (SCCOL i=0; i < aCol.size(); i++)
+ aCol[i].DeleteSelection(nDelFlag, rMark, bBroadcast);
+ }
+
+ ScRangeList aRangeList;
+ rMark.FillRangeListWithMarks(&aRangeList, false);
+
+ for (size_t i = 0; i < aRangeList.size(); ++i)
+ {
+ const ScRange & rRange = aRangeList[i];
+
+ if((nDelFlag & InsertDeleteFlags::ATTRIB) && rRange.aStart.Tab() == nTab)
+ mpCondFormatList->DeleteArea( rRange.aStart.Col(), rRange.aStart.Row(), rRange.aEnd.Col(), rRange.aEnd.Row() );
+ }
+
+ // Do not set protected cell in a protected sheet
+
+ if ( IsProtected() && (nDelFlag & InsertDeleteFlags::ATTRIB) )
+ {
+ ScDocumentPool* pPool = rDocument.GetPool();
+ SfxItemSetFixed<ATTR_PATTERN_START, ATTR_PATTERN_END> aSet( *pPool );
+ aSet.Put( ScProtectionAttr( false ) );
+ SfxItemPoolCache aCache( pPool, &aSet );
+ ApplySelectionCache( &aCache, rMark );
+ }
+
+ // TODO: In the future we may want to check if the table has been
+ // really modified before setting the stream invalid.
+ SetStreamValid(false);
+}
+
+// pTable = Clipboard
+void ScTable::CopyToClip(
+ sc::CopyToClipContext& rCxt, SCCOL nCol1, SCROW nRow1, SCCOL nCol2, SCROW nRow2,
+ ScTable* pTable )
+{
+ if (!ValidColRow(nCol1, nRow1) || !ValidColRow(nCol2, nRow2))
+ return;
+
+ // copy content
+ //local range names need to be copied first for formula cells
+ if (!pTable->mpRangeName && mpRangeName)
+ pTable->mpRangeName.reset( new ScRangeName(*mpRangeName) );
+
+ nCol2 = ClampToAllocatedColumns(nCol2);
+
+ for ( SCCOL i = nCol1; i <= nCol2; i++)
+ aCol[i].CopyToClip(rCxt, nRow1, nRow2, pTable->CreateColumnIfNotExists(i)); // notes are handled at column level
+
+ // copy widths/heights, and only "hidden", "filtered" and "manual" flags
+ // also for all preceding columns/rows, to have valid positions for drawing objects
+
+ if (mpColWidth && pTable->mpColWidth)
+ pTable->mpColWidth->CopyFrom(*mpColWidth, 0, nCol2);
+
+ pTable->CopyColHidden(*this, 0, nCol2);
+ pTable->CopyColFiltered(*this, 0, nCol2);
+ if (pDBDataNoName)
+ pTable->SetAnonymousDBData(std::unique_ptr<ScDBData>(new ScDBData(*pDBDataNoName)));
+
+ if (pRowFlags && pTable->pRowFlags && mpRowHeights && pTable->mpRowHeights)
+ {
+ pTable->pRowFlags->CopyFromAnded( *pRowFlags, 0, nRow2, CRFlags::ManualSize);
+ pTable->CopyRowHeight(*this, 0, nRow2, 0);
+ }
+
+ pTable->CopyRowHidden(*this, 0, nRow2);
+ pTable->CopyRowFiltered(*this, 0, nRow2);
+
+ // If necessary replace formulas with values
+
+ if ( IsProtected() )
+ for (SCCOL i = nCol1; i <= nCol2; i++)
+ pTable->aCol[i].RemoveProtected(nRow1, nRow2);
+
+ pTable->mpCondFormatList.reset(new ScConditionalFormatList(pTable->rDocument, *mpCondFormatList));
+}
+
+void ScTable::CopyToClip(
+ sc::CopyToClipContext& rCxt, const ScRangeList& rRanges, ScTable* pTable )
+{
+ for ( size_t i = 0, nListSize = rRanges.size(); i < nListSize; ++i )
+ {
+ const ScRange & r = rRanges[ i ];
+ CopyToClip( rCxt, r.aStart.Col(), r.aStart.Row(), r.aEnd.Col(), r.aEnd.Row(), pTable);
+ }
+}
+
+void ScTable::CopyStaticToDocument(
+ SCCOL nCol1, SCROW nRow1, SCCOL nCol2, SCROW nRow2, const SvNumberFormatterMergeMap& rMap, ScTable* pDestTab )
+{
+ if (nCol1 > nCol2 || nRow1 > nRow2)
+ return;
+
+ const SCCOL nFirstUnallocated = std::clamp<SCCOL>(GetAllocatedColumnsCount(), nCol1, nCol2 + 1);
+ if (nFirstUnallocated > nCol1)
+ pDestTab->CreateColumnIfNotExists(nFirstUnallocated - 1);
+
+ for (SCCOL i = nCol1; i < nFirstUnallocated; ++i)
+ {
+ ScColumn& rSrcCol = aCol[i];
+ ScColumn& rDestCol = pDestTab->aCol[i];
+ rSrcCol.CopyStaticToDocument(nRow1, nRow2, rMap, rDestCol);
+ }
+
+ // Maybe copy this table's default attrs to dest not limiting to already allocated in dest?
+ const SCCOL nLastInDest = std::min<SCCOL>(pDestTab->GetAllocatedColumnsCount() - 1, nCol2);
+ for (SCCOL i = nFirstUnallocated; i <= nLastInDest; ++i)
+ {
+ ScColumn& rDestCol = pDestTab->aCol[i];
+ rDestCol.maCellTextAttrs.set_empty(nRow1, nRow2);
+ rDestCol.maCells.set_empty(nRow1, nRow2);
+ for (SCROW nRow = nRow1; nRow <= nRow2; ++nRow)
+ {
+ sal_uInt32 nNumFmt = aDefaultColData.GetPattern(nRow)->GetNumberFormat(
+ rDocument.GetNonThreadedContext().GetFormatTable());
+ SvNumberFormatterMergeMap::const_iterator itNum = rMap.find(nNumFmt);
+ if (itNum != rMap.end())
+ nNumFmt = itNum->second;
+
+ rDestCol.SetNumberFormat(nRow, nNumFmt);
+ }
+ rDestCol.CellStorageModified();
+ }
+}
+
+void ScTable::CopyCellToDocument(SCCOL nSrcCol, SCROW nSrcRow, SCCOL nDestCol, SCROW nDestRow, ScTable& rDestTab )
+{
+ if (!ValidColRow(nSrcCol, nSrcRow) || !ValidColRow(nDestCol, nDestRow))
+ return;
+
+ if (nSrcCol >= GetAllocatedColumnsCount())
+ {
+ if (nDestCol < rDestTab.GetAllocatedColumnsCount())
+ {
+ ScColumn& rDestCol = rDestTab.aCol[nDestCol];
+ rDestCol.maCells.set_empty(nDestRow, nDestRow);
+ rDestCol.maCellTextAttrs.set_empty(nDestRow, nDestRow);
+ rDestCol.maCellNotes.set_empty(nDestRow, nDestRow);
+ rDestCol.CellStorageModified();
+ }
+ return;
+ }
+
+ ScColumn& rSrcCol = aCol[nSrcCol];
+ ScColumn& rDestCol = rDestTab.CreateColumnIfNotExists(nDestCol);
+ rSrcCol.CopyCellToDocument(nSrcRow, nDestRow, rDestCol);
+}
+
+namespace {
+
+bool CheckAndDeduplicateCondFormat(ScDocument& rDocument, ScConditionalFormat* pOldFormat, const ScConditionalFormat* pNewFormat, SCTAB nTab)
+{
+ if (!pOldFormat)
+ return false;
+
+ if (pOldFormat->EqualEntries(*pNewFormat, true))
+ {
+ const ScRangeList& rNewRangeList = pNewFormat->GetRange();
+ ScRangeList& rDstRangeList = pOldFormat->GetRangeList();
+ for (size_t i = 0; i < rNewRangeList.size(); ++i)
+ {
+ rDstRangeList.Join(rNewRangeList[i]);
+ }
+ rDocument.AddCondFormatData(rNewRangeList, nTab, pOldFormat->GetKey());
+ return true;
+ }
+
+ return false;
+}
+
+}
+
+void ScTable::CopyConditionalFormat( SCCOL nCol1, SCROW nRow1, SCCOL nCol2, SCROW nRow2,
+ SCCOL nDx, SCROW nDy, const ScTable* pTable)
+{
+ ScRange aOldRange( nCol1 - nDx, nRow1 - nDy, pTable->nTab, nCol2 - nDx, nRow2 - nDy, pTable->nTab);
+ ScRange aNewRange( nCol1, nRow1, nTab, nCol2, nRow2, nTab );
+ bool bSameDoc = rDocument.GetStyleSheetPool() == pTable->rDocument.GetStyleSheetPool();
+
+ for(const auto& rxCondFormat : *pTable->mpCondFormatList)
+ {
+ const ScRangeList& rCondFormatRange = rxCondFormat->GetRange();
+ if(!rCondFormatRange.Intersects( aOldRange ))
+ continue;
+
+ ScRangeList aIntersectedRange = rCondFormatRange.GetIntersectedRange(aOldRange);
+ std::unique_ptr<ScConditionalFormat> pNewFormat = rxCondFormat->Clone(&rDocument);
+
+ pNewFormat->SetRange(aIntersectedRange);
+ sc::RefUpdateContext aRefCxt(rDocument);
+ aRefCxt.meMode = URM_COPY;
+ aRefCxt.maRange = aNewRange;
+ aRefCxt.mnColDelta = nDx;
+ aRefCxt.mnRowDelta = nDy;
+ aRefCxt.mnTabDelta = nTab - pTable->nTab;
+ pNewFormat->UpdateReference(aRefCxt, true);
+
+ if (bSameDoc && pTable->nTab == nTab && CheckAndDeduplicateCondFormat(rDocument, mpCondFormatList->GetFormat(rxCondFormat->GetKey()), pNewFormat.get(), nTab))
+ {
+ continue;
+ }
+ sal_uLong nMax = 0;
+ bool bDuplicate = false;
+ for(const auto& rxCond : *mpCondFormatList)
+ {
+ // Check if there is the same format in the destination
+ // If there is, then simply expand its range
+ if (CheckAndDeduplicateCondFormat(rDocument, rxCond.get(), pNewFormat.get(), nTab))
+ {
+ bDuplicate = true;
+ break;
+ }
+
+ if (rxCond->GetKey() > nMax)
+ nMax = rxCond->GetKey();
+ }
+ // Do not add duplicate entries
+ if (bDuplicate)
+ {
+ continue;
+ }
+
+ pNewFormat->SetKey(nMax + 1);
+ auto pNewFormatTmp = pNewFormat.get();
+ mpCondFormatList->InsertNew(std::move(pNewFormat));
+
+ if(!bSameDoc)
+ {
+ for(size_t i = 0, n = pNewFormatTmp->size();
+ i < n; ++i)
+ {
+ OUString aStyleName;
+ const ScFormatEntry* pEntry = pNewFormatTmp->GetEntry(i);
+ if(pEntry->GetType() == ScFormatEntry::Type::Condition ||
+ pEntry->GetType() == ScFormatEntry::Type::ExtCondition)
+ aStyleName = static_cast<const ScCondFormatEntry*>(pEntry)->GetStyle();
+ else if(pEntry->GetType() == ScFormatEntry::Type::Date)
+ aStyleName = static_cast<const ScCondDateFormatEntry*>(pEntry)->GetStyleName();
+
+ if(!aStyleName.isEmpty())
+ {
+ if(rDocument.GetStyleSheetPool()->Find(aStyleName, SfxStyleFamily::Para))
+ continue;
+
+ rDocument.GetStyleSheetPool()->CopyStyleFrom(
+ pTable->rDocument.GetStyleSheetPool(), aStyleName, SfxStyleFamily::Para );
+ }
+ }
+ }
+
+ rDocument.AddCondFormatData( pNewFormatTmp->GetRange(), nTab, pNewFormatTmp->GetKey() );
+ }
+}
+
+bool ScTable::InitColumnBlockPosition( sc::ColumnBlockPosition& rBlockPos, SCCOL nCol )
+{
+ if (!ValidCol(nCol))
+ return false;
+
+ CreateColumnIfNotExists(nCol).InitBlockPosition(rBlockPos);
+ return true;
+}
+
+// pTable is source
+
+void ScTable::CopyFromClip(
+ sc::CopyFromClipContext& rCxt, SCCOL nCol1, SCROW nRow1, SCCOL nCol2, SCROW nRow2,
+ SCCOL nDx, SCROW nDy, ScTable* pTable )
+{
+ if (nCol2 > rDocument.MaxCol())
+ nCol2 = rDocument.MaxCol();
+ if (nRow2 > rDocument.MaxRow())
+ nRow2 = rDocument.MaxRow();
+
+ if (!(ValidColRow(nCol1, nRow1) && ValidColRow(nCol2, nRow2)))
+ return;
+
+ CreateColumnIfNotExists(nCol2);
+ for ( SCCOL i = nCol1; i <= nCol2; i++)
+ {
+ pTable->CreateColumnIfNotExists(i - nDx);
+ aCol[i].CopyFromClip(rCxt, nRow1, nRow2, nDy, pTable->aCol[i - nDx]); // notes are handles at column level
+ }
+
+ if (rCxt.getInsertFlag() & InsertDeleteFlags::ATTRIB)
+ {
+ // make sure that there are no old references to the cond formats
+ sal_uInt16 nWhichArray[2];
+ nWhichArray[0] = ATTR_CONDITIONAL;
+ nWhichArray[1] = 0;
+ for ( SCCOL i = nCol1; i <= nCol2; ++i)
+ aCol[i].ClearItems(nRow1, nRow2, nWhichArray);
+ }
+
+ if ((rCxt.getInsertFlag() & InsertDeleteFlags::ATTRIB) == InsertDeleteFlags::NONE)
+ return;
+
+ if (nRow1==0 && nRow2==rDocument.MaxRow() && mpColWidth && pTable->mpColWidth)
+ mpColWidth->CopyFrom(*pTable->mpColWidth, nCol1, nCol2, nCol1 - nDx);
+
+ if (nCol1==0 && nCol2==rDocument.MaxCol() && mpRowHeights && pTable->mpRowHeights &&
+ pRowFlags && pTable->pRowFlags)
+ {
+ CopyRowHeight(*pTable, nRow1, nRow2, -nDy);
+ // Must copy CRFlags::ManualSize bit too, otherwise pRowHeight doesn't make sense
+ for (SCROW j=nRow1; j<=nRow2; j++)
+ {
+ if ( pTable->pRowFlags->GetValue(j-nDy) & CRFlags::ManualSize )
+ pRowFlags->OrValue( j, CRFlags::ManualSize);
+ else
+ pRowFlags->AndValue( j, ~CRFlags::ManualSize);
+ }
+ }
+
+ // Do not set protected cell in a protected sheet
+ if (IsProtected() && (rCxt.getInsertFlag() & InsertDeleteFlags::ATTRIB))
+ {
+ ScPatternAttr aPattern(rDocument.GetPool());
+ aPattern.GetItemSet().Put( ScProtectionAttr( false ) );
+ ApplyPatternArea( nCol1, nRow1, nCol2, nRow2, aPattern );
+ }
+
+ // create deep copies for conditional formatting
+ CopyConditionalFormat( nCol1, nRow1, nCol2, nRow2, nDx, nDy, pTable);
+}
+
+void ScTable::MixData(
+ sc::MixDocContext& rCxt, SCCOL nCol1, SCROW nRow1, SCCOL nCol2, SCROW nRow2,
+ ScPasteFunc nFunction, bool bSkipEmpty, const ScTable* pSrcTab )
+{
+ for (SCCOL i=nCol1; i<=nCol2; i++)
+ aCol[i].MixData(rCxt, nRow1, nRow2, nFunction, bSkipEmpty, pSrcTab->aCol[i]);
+}
+
+// Selection form this document
+void ScTable::MixMarked(
+ sc::MixDocContext& rCxt, const ScMarkData& rMark, ScPasteFunc nFunction,
+ bool bSkipEmpty, const ScTable* pSrcTab )
+{
+ for (SCCOL i=0; i < aCol.size(); i++)
+ aCol[i].MixMarked(rCxt, rMark, nFunction, bSkipEmpty, pSrcTab->aCol[i]);
+}
+
+namespace {
+
+class TransClipHandler
+{
+ ScTable& mrClipTab;
+ const ScTable& mrSrcTab;
+ SCTAB mnSrcTab;
+ SCCOL mnCol1;
+ SCCOL mnSrcCol;
+ size_t mnTopRow;
+ size_t mnEndRow;
+ SCROW mnTransRow;
+ SCROW mnFilteredRows = 0;
+ SCROW mnRowDestOffset = 0;
+ bool mbAsLink;
+ bool mbWasCut;
+ bool mbIncludeFiltered;
+ InsertDeleteFlags mnFlags;
+
+ ScAddress getDestPos(size_t nRow) const
+ {
+ return ScAddress(static_cast<SCCOL>(mnCol1 + nRow - mnTopRow), mnTransRow,
+ mrClipTab.GetTab());
+ }
+
+ ScFormulaCell* createRefCell(size_t nSrcRow, const ScAddress& rDestPos) const
+ {
+ ScAddress aSrcPos(mnSrcCol, nSrcRow, mnSrcTab);
+ ScSingleRefData aRef;
+ aRef.InitAddress(aSrcPos); // Absolute reference.
+ aRef.SetFlag3D(true);
+
+ ScTokenArray aArr(mrClipTab.GetDoc());
+ aArr.AddSingleReference(aRef);
+ return new ScFormulaCell(mrClipTab.GetDoc(), rDestPos, aArr);
+ }
+
+ void setLink(size_t nRow)
+ {
+ SCCOL nTransCol = mnCol1 + nRow - mnTopRow - mnFilteredRows + mnRowDestOffset;
+ mrClipTab.SetFormulaCell(nTransCol, mnTransRow,
+ createRefCell(nRow, getDestPos(nRow)));
+ }
+
+public:
+ TransClipHandler(ScTable& rClipTab, const ScTable& rSrcTab, SCTAB nSrcTab, SCCOL nCol1,
+ SCCOL nSrcCol, size_t nTopRow, size_t nEndRow, SCROW nCombinedStartRow,
+ SCROW nRowDestOffset, bool bAsLink, bool bWasCut,
+ const InsertDeleteFlags& nFlags, const bool bIncludeFiltered,
+ std::vector<SCROW>& rFilteredRows)
+ : mrClipTab(rClipTab)
+ , mrSrcTab(rSrcTab)
+ , mnSrcTab(nSrcTab)
+ , mnCol1(nCol1)
+ , mnSrcCol(nSrcCol)
+ , mnTopRow(nTopRow)
+ , mnEndRow(nEndRow)
+ , mnTransRow(nSrcCol - nCol1 + nCombinedStartRow)
+ , mnRowDestOffset(nRowDestOffset)
+ , mbAsLink(bAsLink)
+ , mbWasCut(bWasCut)
+ , mbIncludeFiltered(bIncludeFiltered)
+ , mnFlags(nFlags)
+ {
+ // Create list of filtered rows.
+ if (!mbIncludeFiltered)
+ {
+ for (SCROW curRow = nTopRow; curRow <= static_cast<SCROW>(mnEndRow); ++curRow)
+ {
+ // maybe this loop could be optimized
+ bool bFiltered = mrSrcTab.RowFiltered(curRow, nullptr, nullptr);
+ if (bFiltered)
+ rFilteredRows.push_back(curRow);
+ }
+ }
+ }
+
+ void operator() (size_t nRow, double fVal)
+ {
+ bool bFiltered = mrSrcTab.RowFiltered(nRow, nullptr, nullptr);
+ if (!mbIncludeFiltered && bFiltered)
+ {
+ mnFilteredRows++;
+ return;
+ }
+
+ if (mbAsLink)
+ {
+ setLink(nRow);
+ return;
+ }
+
+ SCCOL nTransCol = mnCol1 + nRow - mnTopRow - mnFilteredRows + mnRowDestOffset;
+ mrClipTab.SetValue(nTransCol, mnTransRow, fVal);
+ }
+
+ void operator() (size_t nRow, const svl::SharedString& rStr)
+ {
+ bool bFiltered = mrSrcTab.RowFiltered(nRow, nullptr, nullptr);
+ if (!mbIncludeFiltered && bFiltered)
+ {
+ mnFilteredRows++;
+ return;
+ }
+
+ if (mbAsLink)
+ {
+ setLink(nRow);
+ return;
+ }
+
+ SCCOL nTransCol = mnCol1 + nRow - mnTopRow - mnFilteredRows + mnRowDestOffset;
+ mrClipTab.SetRawString(nTransCol, mnTransRow, rStr);
+ }
+
+ void operator() (size_t nRow, const EditTextObject* p)
+ {
+ bool bFiltered = mrSrcTab.RowFiltered(nRow, nullptr, nullptr);
+ if (!mbIncludeFiltered && bFiltered)
+ {
+ mnFilteredRows++;
+ return;
+ }
+
+ if (mbAsLink)
+ {
+ setLink(nRow);
+ return;
+ }
+
+ SCCOL nTransCol = mnCol1 + nRow - mnTopRow - mnFilteredRows + mnRowDestOffset;
+ mrClipTab.SetEditText(nTransCol, mnTransRow, ScEditUtil::Clone(*p, mrClipTab.GetDoc()));
+ }
+
+ void operator() (size_t nRow, const ScFormulaCell* p)
+ {
+ bool bFiltered = mrSrcTab.RowFiltered(nRow, nullptr, nullptr);
+ if (!mbIncludeFiltered && bFiltered)
+ {
+ mnFilteredRows++;
+ return;
+ }
+
+ if (mbAsLink)
+ {
+ setLink(nRow);
+ return;
+ }
+
+ ScFormulaCell* pNew = new ScFormulaCell(*p, mrClipTab.GetDoc(),
+ getDestPos(nRow - mnFilteredRows + mnRowDestOffset),
+ ScCloneFlags::StartListening);
+
+ // rotate reference
+ // for Cut, the references are later adjusted through UpdateTranspose
+
+ if (!mbWasCut)
+ pNew->TransposeReference();
+
+ SCCOL nTransCol = mnCol1 + nRow - mnTopRow - mnFilteredRows + mnRowDestOffset;
+ mrClipTab.SetFormulaCell(nTransCol, mnTransRow, pNew);
+ }
+
+ // empty cells
+ void operator()(const int /*type*/, size_t nRow, size_t nDataSize)
+ {
+ for (size_t curRow = nRow; curRow < nRow + nDataSize; ++curRow)
+ {
+ bool bFiltered = mrSrcTab.RowFiltered(curRow, nullptr, nullptr);
+ if (!mbIncludeFiltered && bFiltered)
+ {
+ mnFilteredRows++;
+ continue;
+ }
+
+ if (mbAsLink && mnFlags == InsertDeleteFlags::ALL)
+ {
+ // with InsertDeleteFlags::ALL, also create links (formulas) for empty cells
+ setLink(nRow);
+ continue;
+ }
+ }
+ }
+};
+}
+
+void ScTable::TransposeClip(SCCOL nCol1, SCROW nRow1, SCCOL nCol2, SCROW nRow2,
+ SCROW nCombinedStartRow, SCROW nRowDestOffset, ScTable* pTransClip,
+ InsertDeleteFlags nFlags, bool bAsLink, bool bIncludeFiltered)
+{
+ bool bWasCut = rDocument.IsCutMode();
+
+ for (SCCOL nCol : GetWritableColumnsRange(nCol1, nCol2))
+ {
+ std::vector<SCROW> aFilteredRows;
+
+ TransClipHandler aFunc(*pTransClip, *this, nTab, nCol1, nCol, nRow1, nRow2,
+ nCombinedStartRow, nRowDestOffset, bAsLink, bWasCut, nFlags,
+ bIncludeFiltered, aFilteredRows);
+
+ const sc::CellStoreType& rCells = aCol[nCol].maCells;
+
+ // Loop through all rows by iterator and call aFunc operators
+ sc::ParseAll(rCells.begin(), rCells, nRow1, nRow2, aFunc,
+ aFunc);
+
+ // Attributes
+ if (nFlags & InsertDeleteFlags::ATTRIB)
+ TransposeColPatterns(pTransClip, nCol1, nCol, nRow1, nRow2, nCombinedStartRow,
+ bIncludeFiltered, aFilteredRows, nRowDestOffset);
+
+ // Cell Notes - fdo#68381 paste cell notes on Transpose
+ if ((nFlags & InsertDeleteFlags::NOTE) && rDocument.HasColNotes(nCol, nTab))
+ TransposeColNotes(pTransClip, nCol1, nCol, nRow1, nRow2, nCombinedStartRow,
+ bIncludeFiltered, nRowDestOffset);
+ }
+}
+
+static void lcl_SetTransposedPatternInRows(ScTable* pTransClip, SCROW nAttrRow1, SCROW nAttrRow2,
+ SCCOL nCol1, SCROW nRow1, SCROW nCombinedStartRow, SCCOL nCol,
+ const ScPatternAttr& rPatternAttr, bool bIncludeFiltered,
+ const std::vector<SCROW>& rFilteredRows,
+ SCROW nRowDestOffset)
+{
+ for (SCROW nRow = nAttrRow1; nRow <= nAttrRow2; nRow++)
+ {
+ size_t nFilteredRowAdjustment = 0;
+ if (!bIncludeFiltered)
+ {
+ // aFilteredRows is sorted thus lower_bound() can be used.
+ // lower_bound() has a logarithmic complexity O(log(n))
+ auto itRow1 = std::lower_bound(rFilteredRows.begin(), rFilteredRows.end(), nRow1);
+ auto itRow = std::lower_bound(rFilteredRows.begin(), rFilteredRows.end(), nRow);
+ bool bRefRowIsFiltered = itRow != rFilteredRows.end() && *itRow == nRow;
+ if (bRefRowIsFiltered)
+ continue;
+
+ // How many filtered rows are between the formula cell and the reference?
+ // distance() has a constant complexity O(1) for vectors
+ nFilteredRowAdjustment = std::distance(itRow1, itRow);
+ }
+
+ pTransClip->SetPattern(
+ static_cast<SCCOL>(nCol1 + nRow - nRow1 - nFilteredRowAdjustment + nRowDestOffset),
+ static_cast<SCROW>(nCombinedStartRow + nCol - nCol1), rPatternAttr);
+ }
+}
+
+void ScTable::TransposeColPatterns(ScTable* pTransClip, SCCOL nCol1, SCCOL nCol, SCROW nRow1,
+ SCROW nRow2, SCROW nCombinedStartRow, bool bIncludeFiltered,
+ const std::vector<SCROW>& rFilteredRows, SCROW nRowDestOffset)
+{
+ SCROW nAttrRow1 = {}; // spurious -Werror=maybe-uninitialized
+ SCROW nAttrRow2 = {}; // spurious -Werror=maybe-uninitialized
+ const ScPatternAttr* pPattern;
+ std::unique_ptr<ScAttrIterator> pAttrIter(aCol[nCol].CreateAttrIterator( nRow1, nRow2 ));
+ while ( (pPattern = pAttrIter->Next( nAttrRow1, nAttrRow2 )) != nullptr )
+ {
+ if ( !IsDefaultItem( pPattern ) )
+ {
+ const SfxItemSet& rSet = pPattern->GetItemSet();
+ if ( rSet.GetItemState( ATTR_MERGE, false ) == SfxItemState::DEFAULT &&
+ rSet.GetItemState( ATTR_MERGE_FLAG, false ) == SfxItemState::DEFAULT &&
+ rSet.GetItemState( ATTR_BORDER, false ) == SfxItemState::DEFAULT )
+ {
+ // Set pattern in cells from nAttrRow1 to nAttrRow2
+ // no borders or merge items involved - use pattern as-is
+ lcl_SetTransposedPatternInRows(pTransClip, nAttrRow1, nAttrRow2, nCol1, nRow1,
+ nCombinedStartRow, nCol, *pPattern,
+ bIncludeFiltered, rFilteredRows, nRowDestOffset);
+ }
+ else
+ {
+ // transpose borders and merge values, remove merge flags (refreshed after pasting)
+ ScPatternAttr aNewPattern( *pPattern );
+ SfxItemSet& rNewSet = aNewPattern.GetItemSet();
+
+ const SvxBoxItem& rOldBox = rSet.Get(ATTR_BORDER);
+ if ( rOldBox.GetTop() || rOldBox.GetBottom() || rOldBox.GetLeft() || rOldBox.GetRight() )
+ {
+ SvxBoxItem aNew( ATTR_BORDER );
+ aNew.SetLine( rOldBox.GetLine( SvxBoxItemLine::TOP ), SvxBoxItemLine::LEFT );
+ aNew.SetLine( rOldBox.GetLine( SvxBoxItemLine::LEFT ), SvxBoxItemLine::TOP );
+ aNew.SetLine( rOldBox.GetLine( SvxBoxItemLine::BOTTOM ), SvxBoxItemLine::RIGHT );
+ aNew.SetLine( rOldBox.GetLine( SvxBoxItemLine::RIGHT ), SvxBoxItemLine::BOTTOM );
+ aNew.SetDistance( rOldBox.GetDistance( SvxBoxItemLine::TOP ), SvxBoxItemLine::LEFT );
+ aNew.SetDistance( rOldBox.GetDistance( SvxBoxItemLine::LEFT ), SvxBoxItemLine::TOP );
+ aNew.SetDistance( rOldBox.GetDistance( SvxBoxItemLine::BOTTOM ), SvxBoxItemLine::RIGHT );
+ aNew.SetDistance( rOldBox.GetDistance( SvxBoxItemLine::RIGHT ), SvxBoxItemLine::BOTTOM );
+ rNewSet.Put( aNew );
+ }
+
+ const ScMergeAttr& rOldMerge = rSet.Get(ATTR_MERGE);
+ if (rOldMerge.IsMerged())
+ rNewSet.Put( ScMergeAttr( std::min(
+ static_cast<SCCOL>(rOldMerge.GetRowMerge()),
+ static_cast<SCCOL>(rDocument.MaxCol()+1 - (nAttrRow2-nRow1))),
+ std::min(
+ static_cast<SCROW>(rOldMerge.GetColMerge()),
+ static_cast<SCROW>(rDocument.MaxRow()+1 - (nCol-nCol1)))));
+ const ScMergeFlagAttr& rOldFlag = rSet.Get(ATTR_MERGE_FLAG);
+ if (rOldFlag.IsOverlapped())
+ {
+ ScMF nNewFlags = rOldFlag.GetValue() & ~ScMF( ScMF::Hor | ScMF::Ver );
+ if ( nNewFlags != ScMF::NONE )
+ rNewSet.Put( ScMergeFlagAttr( nNewFlags ) );
+ else
+ rNewSet.ClearItem( ATTR_MERGE_FLAG );
+ }
+
+ // Set pattern in cells from nAttrRow1 to nAttrRow2
+ lcl_SetTransposedPatternInRows(pTransClip, nAttrRow1, nAttrRow2, nCol1, nRow1,
+ nCombinedStartRow, nCol, aNewPattern,
+ bIncludeFiltered, rFilteredRows, nRowDestOffset);
+ }
+ }
+ }
+}
+
+void ScTable::TransposeColNotes(ScTable* pTransClip, SCCOL nCol1, SCCOL nCol, SCROW nRow1,
+ SCROW nRow2, SCROW nCombinedStartRow, bool bIncludeFiltered,
+ SCROW nRowDestOffset)
+{
+ sc::CellNoteStoreType::const_iterator itBlk = aCol[nCol].maCellNotes.begin(), itBlkEnd = aCol[nCol].maCellNotes.end();
+
+ // Locate the top row position.
+ size_t nOffsetInBlock = 0;
+ size_t nBlockStart = 0, nBlockEnd = 0, nRowPos = static_cast<size_t>(nRow1);
+ for (; itBlk != itBlkEnd; ++itBlk, nBlockStart = nBlockEnd)
+ {
+ nBlockEnd = nBlockStart + itBlk->size;
+ if (nBlockStart <= nRowPos && nRowPos < nBlockEnd)
+ {
+ // Found.
+ nOffsetInBlock = nRowPos - nBlockStart;
+ break;
+ }
+ }
+
+ if (itBlk == itBlkEnd)
+ // Specified range found
+ return;
+
+ nRowPos = static_cast<size_t>(nRow2); // End row position.
+ SCCOL nFilteredRows = 0;
+
+ // Keep processing until we hit the end row position.
+ sc::cellnote_block::const_iterator itData, itDataEnd;
+ for (; itBlk != itBlkEnd; ++itBlk, nBlockStart = nBlockEnd, nOffsetInBlock = 0)
+ {
+ nBlockEnd = nBlockStart + itBlk->size;
+
+ if (itBlk->data)
+ {
+ itData = sc::cellnote_block::begin(*itBlk->data);
+ std::advance(itData, nOffsetInBlock);
+
+ // selected area is smaller than the iteration block
+ if (nBlockStart <= nRowPos && nRowPos < nBlockEnd)
+ {
+ // This block contains the end row. Only process partially.
+ size_t nOffsetEnd = nRowPos - nBlockStart + 1;
+ itDataEnd = sc::cellnote_block::begin(*itBlk->data);
+ std::advance(itDataEnd, nOffsetEnd);
+ size_t curRow = nBlockStart + nOffsetInBlock;
+ for (; itData != itDataEnd; ++itData, ++curRow)
+ {
+ bool bFiltered = this->RowFiltered(curRow, nullptr, nullptr);
+ if (!bIncludeFiltered && bFiltered)
+ {
+ nFilteredRows++;
+ continue;
+ }
+
+ ScAddress aDestPos(
+ static_cast<SCCOL>(nCol1 + curRow - nRow1 - nFilteredRows + nRowDestOffset),
+ static_cast<SCROW>(nCombinedStartRow + nCol - nCol1), pTransClip->nTab);
+ pTransClip->rDocument.ReleaseNote(aDestPos);
+ ScPostIt* pNote = *itData;
+ if (pNote)
+ {
+ std::unique_ptr<ScPostIt> pClonedNote = pNote->Clone( ScAddress(nCol, curRow, nTab), pTransClip->rDocument, aDestPos, true );
+ pTransClip->rDocument.SetNote(aDestPos, std::move(pClonedNote));
+ }
+ }
+ break; // we reached the last valid block
+ }
+ else
+ {
+ itDataEnd = sc::cellnote_block::end(*itBlk->data);
+ size_t curRow = nBlockStart + nOffsetInBlock;
+ for (; itData != itDataEnd; ++itData, ++curRow)
+ {
+ bool bFiltered = this->RowFiltered(curRow, nullptr, nullptr);
+ if (!bIncludeFiltered && bFiltered)
+ {
+ nFilteredRows++;
+ continue;
+ }
+
+ ScAddress aDestPos(
+ static_cast<SCCOL>(nCol1 + curRow - nRow1 - nFilteredRows + nRowDestOffset),
+ static_cast<SCROW>(nCombinedStartRow + nCol - nCol1), pTransClip->nTab);
+ pTransClip->rDocument.ReleaseNote(aDestPos);
+ ScPostIt* pNote = *itData;
+ if (pNote)
+ {
+ std::unique_ptr<ScPostIt> pClonedNote = pNote->Clone( ScAddress(nCol, curRow, nTab), pTransClip->rDocument, aDestPos, true );
+ pTransClip->rDocument.SetNote(aDestPos, std::move(pClonedNote));
+ }
+ }
+ }
+ }
+ else // remove dest notes for rows without notes
+ {
+ for (size_t curRow = nBlockStart + nOffsetInBlock;
+ curRow <= nBlockEnd && curRow <= nRowPos; ++curRow)
+ {
+ bool bFiltered = this->RowFiltered(curRow, nullptr, nullptr);
+ if (!bIncludeFiltered && bFiltered && curRow < nBlockEnd)
+ {
+ nFilteredRows++;
+ continue;
+ }
+
+ ScAddress aDestPos(
+ static_cast<SCCOL>(nCol1 + curRow - nRow1 - nFilteredRows + nRowDestOffset),
+ static_cast<SCROW>(nCombinedStartRow + nCol - nCol1), pTransClip->nTab);
+ pTransClip->rDocument.ReleaseNote(aDestPos);
+ }
+ }
+ }
+}
+
+ScColumn* ScTable::FetchColumn( SCCOL nCol )
+{
+ if (!ValidCol(nCol))
+ return nullptr;
+
+ return &CreateColumnIfNotExists(nCol);
+}
+
+const ScColumn* ScTable::FetchColumn( SCCOL nCol ) const
+{
+ if (!ValidCol(nCol) || nCol >= GetAllocatedColumnsCount())
+ return nullptr;
+
+ return &aCol[nCol];
+}
+
+void ScTable::StartListeners( sc::StartListeningContext& rCxt, bool bAll )
+{
+ std::shared_ptr<const sc::ColumnSet> pColSet = rCxt.getColumnSet();
+ if (!pColSet)
+ {
+ for (SCCOL i=0; i < aCol.size(); i++)
+ aCol[i].StartListeners(rCxt, bAll);
+ }
+ else if (pColSet->hasTab( nTab))
+ {
+ std::vector<SCCOL> aColumns;
+ pColSet->getColumns( nTab, aColumns);
+ for (auto i : aColumns)
+ {
+ if (0 <= i && i < aCol.size())
+ aCol[i].StartListeners(rCxt, bAll);
+ }
+ }
+}
+
+void ScTable::AttachFormulaCells(
+ sc::StartListeningContext& rCxt, SCCOL nCol1, SCROW nRow1, SCCOL nCol2, SCROW nRow2 )
+{
+ nCol2 = ClampToAllocatedColumns(nCol2);
+ for (SCCOL nCol = nCol1; nCol <= nCol2; ++nCol)
+ aCol[nCol].AttachFormulaCells(rCxt, nRow1, nRow2);
+}
+
+void ScTable::DetachFormulaCells(
+ sc::EndListeningContext& rCxt, SCCOL nCol1, SCROW nRow1, SCCOL nCol2, SCROW nRow2 )
+{
+ nCol2 = ClampToAllocatedColumns(nCol2);
+ for (SCCOL nCol = nCol1; nCol <= nCol2; ++nCol)
+ aCol[nCol].DetachFormulaCells(rCxt, nRow1, nRow2);
+}
+
+void ScTable::SetDirtyFromClip(
+ SCCOL nCol1, SCROW nRow1, SCCOL nCol2, SCROW nRow2, sc::ColumnSpanSet& rBroadcastSpans )
+{
+ if ( nCol2 >= aCol.size() ) nCol2 = aCol.size() - 1;
+ if (nCol2 > rDocument.MaxCol()) nCol2 = rDocument.MaxCol();
+ if (nRow2 > rDocument.MaxRow()) nRow2 = rDocument.MaxRow();
+ if (ValidColRow(nCol1, nRow1) && ValidColRow(nCol2, nRow2))
+ for (SCCOL i = nCol1; i <= nCol2; i++)
+ aCol[i].SetDirtyFromClip(nRow1, nRow2, rBroadcastSpans);
+}
+
+void ScTable::StartListeningFormulaCells(
+ sc::StartListeningContext& rStartCxt, sc::EndListeningContext& rEndCxt,
+ SCCOL nCol1, SCROW nRow1, SCCOL nCol2, SCROW nRow2 )
+{
+ if ( nCol2 >= aCol.size() ) nCol2 = aCol.size() - 1;
+ if (nCol2 > rDocument.MaxCol()) nCol2 = rDocument.MaxCol();
+ if (nRow2 > rDocument.MaxRow()) nRow2 = rDocument.MaxRow();
+ if (ValidColRow(nCol1, nRow1) && ValidColRow(nCol2, nRow2))
+ for (SCCOL i = nCol1; i <= nCol2; i++)
+ aCol[i].StartListeningFormulaCells(rStartCxt, rEndCxt, nRow1, nRow2);
+}
+
+void ScTable::CopyToTable(
+ sc::CopyToDocContext& rCxt, SCCOL nCol1, SCROW nRow1, SCCOL nCol2, SCROW nRow2,
+ InsertDeleteFlags nFlags, bool bMarked, ScTable* pDestTab, const ScMarkData* pMarkData,
+ bool bAsLink, bool bColRowFlags, bool bGlobalNamesToLocal, bool bCopyCaptions )
+{
+ if (!ValidColRow(nCol1, nRow1) || !ValidColRow(nCol2, nRow2))
+ return;
+
+ const bool bToUndoDoc = pDestTab->rDocument.IsUndo();
+ const bool bFromUndoDoc = rDocument.IsUndo();
+
+ if ((bToUndoDoc || bFromUndoDoc) && (nFlags & InsertDeleteFlags::CONTENTS) && mpRangeName)
+ {
+ // Copying formulas may create sheet-local named expressions on the
+ // destination sheet. Add existing to Undo first.
+ // During Undo restore the previous named expressions.
+ pDestTab->SetRangeName( std::unique_ptr<ScRangeName>( new ScRangeName( *GetRangeName())));
+ if (!pDestTab->rDocument.IsClipOrUndo())
+ {
+ ScDocShell* pDocSh = static_cast<ScDocShell*>(pDestTab->rDocument.GetDocumentShell());
+ if (pDocSh)
+ pDocSh->SetAreasChangedNeedBroadcast();
+ }
+ }
+
+ if (nFlags != InsertDeleteFlags::NONE)
+ {
+ InsertDeleteFlags nTempFlags( nFlags &
+ ~InsertDeleteFlags( InsertDeleteFlags::NOTE | InsertDeleteFlags::ADDNOTES));
+ // tdf#102364 - in some pathological cases CopyToTable() replacing cells with new cells
+ // can lead to repetitive splitting and rejoining of the same formula group, which can get
+ // quadratically expensive with large groups. So do the grouping just once at the end.
+ sc::DelayFormulaGroupingSwitch delayGrouping( pDestTab->rDocument, true );
+ for (SCCOL i = nCol1; i <= ClampToAllocatedColumns(nCol2); i++)
+ aCol[i].CopyToColumn(rCxt, nRow1, nRow2, bToUndoDoc ? nFlags : nTempFlags, bMarked,
+ pDestTab->CreateColumnIfNotExists(i), pMarkData, bAsLink, bGlobalNamesToLocal);
+ }
+
+ if (!bColRowFlags) // Column widths/Row heights/Flags
+ return;
+
+ if (bToUndoDoc && (nFlags & InsertDeleteFlags::ATTRIB))
+ {
+ pDestTab->mpCondFormatList.reset(new ScConditionalFormatList(pDestTab->rDocument, *mpCondFormatList));
+ }
+
+ if (pDBDataNoName)
+ {
+ std::unique_ptr<ScDBData> pNewDBData(new ScDBData(*pDBDataNoName));
+ SCCOL aCol1, aCol2;
+ SCROW aRow1, aRow2;
+ SCTAB aTab;
+ pNewDBData->GetArea(aTab, aCol1, aRow1, aCol2, aRow2);
+ pNewDBData->MoveTo(pDestTab->nTab, aCol1, aRow1, aCol2, aRow2);
+ pDestTab->SetAnonymousDBData(std::move(pNewDBData));
+ }
+ // Charts have to be adjusted when hide/show
+ ScChartListenerCollection* pCharts = pDestTab->rDocument.GetChartListenerCollection();
+
+ bool bFlagChange = false;
+
+ bool bWidth = (nRow1==0 && nRow2==rDocument.MaxRow() && mpColWidth && pDestTab->mpColWidth);
+ bool bHeight = (nCol1==0 && nCol2==rDocument.MaxCol() && mpRowHeights && pDestTab->mpRowHeights);
+
+ if (bWidth || bHeight)
+ {
+ if (bWidth)
+ {
+ auto destTabColWidthIt = pDestTab->mpColWidth->begin() + nCol1;
+ auto thisTabColWidthIt = mpColWidth->begin() + nCol1;
+ pDestTab->mpColWidth->CopyFrom(*mpColWidth, nCol1, nCol2);
+ pDestTab->mpColFlags->CopyFrom(*mpColFlags, nCol1, nCol2);
+ for (SCCOL i = nCol1; i <= nCol2; ++i)
+ {
+ bool bThisHidden = ColHidden(i);
+ bool bHiddenChange = (pDestTab->ColHidden(i) != bThisHidden);
+ bool bChange = bHiddenChange || (*destTabColWidthIt != *thisTabColWidthIt);
+ pDestTab->SetColHidden(i, i, bThisHidden);
+ //TODO: collect changes?
+ if (bHiddenChange && pCharts)
+ pCharts->SetRangeDirty(ScRange( i, 0, nTab, i, rDocument.MaxRow(), nTab ));
+
+ if (bChange)
+ bFlagChange = true;
+
+ ++destTabColWidthIt;
+ ++thisTabColWidthIt;
+ }
+ pDestTab->SetColManualBreaks( std::set(maColManualBreaks) );
+ }
+
+ if (bHeight)
+ {
+ bool bChange = pDestTab->GetRowHeight(nRow1, nRow2) != GetRowHeight(nRow1, nRow2);
+
+ if (bChange)
+ bFlagChange = true;
+
+ pDestTab->CopyRowHeight(*this, nRow1, nRow2, 0);
+ pDestTab->pRowFlags->CopyFrom(*pRowFlags, nRow1, nRow2);
+
+ // Hidden flags.
+ for (SCROW i = nRow1; i <= nRow2; ++i)
+ {
+ SCROW nLastRow;
+ bool bHidden = RowHidden(i, nullptr, &nLastRow);
+ if (nLastRow >= nRow2)
+ // the last row shouldn't exceed the upper bound the caller specified.
+ nLastRow = nRow2;
+
+ bool bHiddenChanged = pDestTab->SetRowHidden(i, nLastRow, bHidden);
+ if (bHiddenChanged && pCharts)
+ // Hidden flags differ.
+ pCharts->SetRangeDirty(ScRange(0, i, nTab, rDocument.MaxCol(), nLastRow, nTab));
+
+ if (bHiddenChanged)
+ bFlagChange = true;
+
+ // Jump to the last row of the identical flag segment.
+ i = nLastRow;
+ }
+
+ // Filtered flags.
+ for (SCROW i = nRow1; i <= nRow2; ++i)
+ {
+ SCROW nLastRow;
+ bool bFiltered = RowFiltered(i, nullptr, &nLastRow);
+ if (nLastRow >= nRow2)
+ // the last row shouldn't exceed the upper bound the caller specified.
+ nLastRow = nRow2;
+ pDestTab->SetRowFiltered(i, nLastRow, bFiltered);
+ i = nLastRow;
+ }
+ pDestTab->SetRowManualBreaks( std::set(maRowManualBreaks) );
+ }
+ }
+
+ if (bFlagChange)
+ pDestTab->InvalidatePageBreaks();
+
+ if(nFlags & InsertDeleteFlags::ATTRIB)
+ {
+ pDestTab->mpCondFormatList->DeleteArea(nCol1, nRow1, nCol2, nRow2);
+ pDestTab->CopyConditionalFormat(nCol1, nRow1, nCol2, nRow2, 0, 0, this);
+ }
+
+ if(nFlags & InsertDeleteFlags::OUTLINE) // also only when bColRowFlags
+ pDestTab->SetOutlineTable( pOutlineTable.get() );
+
+ if (nFlags & InsertDeleteFlags::SPARKLINES)
+ {
+ CopySparklinesToTable(nCol1, nRow1, nCol2, nRow2, pDestTab);
+ }
+
+ if (!bToUndoDoc && bCopyCaptions && (nFlags & (InsertDeleteFlags::NOTE | InsertDeleteFlags::ADDNOTES)))
+ {
+ bool bCloneCaption = (nFlags & InsertDeleteFlags::NOCAPTIONS) == InsertDeleteFlags::NONE;
+ CopyCaptionsToTable( nCol1, nRow1, nCol2, nRow2, pDestTab, bCloneCaption);
+ }
+}
+
+void ScTable::CopySparklinesToTable(SCCOL nCol1, SCROW nRow1, SCCOL nCol2, SCROW nRow2, ScTable* pDestTab)
+{
+ if (!ValidColRow(nCol1, nRow1) || !ValidColRow(nCol2, nRow2))
+ return;
+
+ nCol2 = ClampToAllocatedColumns(nCol2);
+ for (SCCOL i = nCol1; i <= nCol2; i++)
+ {
+ aCol[i].CopyCellSparklinesToDocument(nRow1, nRow2, pDestTab->CreateColumnIfNotExists(i));
+ }
+}
+
+void ScTable::CopyCaptionsToTable( SCCOL nCol1, SCROW nRow1, SCCOL nCol2, SCROW nRow2, ScTable* pDestTab,
+ bool bCloneCaption )
+{
+ if (!ValidColRow(nCol1, nRow1) || !ValidColRow(nCol2, nRow2))
+ return;
+
+ nCol2 = ClampToAllocatedColumns(nCol2);
+ for (SCCOL i = nCol1; i <= nCol2; i++)
+ {
+ aCol[i].CopyCellNotesToDocument(nRow1, nRow2, pDestTab->CreateColumnIfNotExists(i), bCloneCaption);
+ pDestTab->aCol[i].UpdateNoteCaptions(nRow1, nRow2);
+ }
+}
+
+void ScTable::UndoToTable(
+ sc::CopyToDocContext& rCxt, SCCOL nCol1, SCROW nRow1, SCCOL nCol2, SCROW nRow2,
+ InsertDeleteFlags nFlags, bool bMarked, ScTable* pDestTab )
+{
+ if (!(ValidColRow(nCol1, nRow1) && ValidColRow(nCol2, nRow2)))
+ return;
+
+ bool bWidth = (nRow1==0 && nRow2==rDocument.MaxRow() && mpColWidth && pDestTab->mpColWidth);
+ bool bHeight = (nCol1==0 && nCol2==rDocument.MaxCol() && mpRowHeights && pDestTab->mpRowHeights);
+
+ if ((nFlags & InsertDeleteFlags::CONTENTS) && mpRangeName)
+ {
+ // Undo sheet-local named expressions created during copying
+ // formulas. If mpRangeName is not set then the Undo wasn't even
+ // set to an empty ScRangeName map so don't "undo" that.
+ pDestTab->SetRangeName( std::unique_ptr<ScRangeName>( new ScRangeName( *GetRangeName())));
+ if (!pDestTab->rDocument.IsClipOrUndo())
+ {
+ ScDocShell* pDocSh = static_cast<ScDocShell*>(pDestTab->rDocument.GetDocumentShell());
+ if (pDocSh)
+ pDocSh->SetAreasChangedNeedBroadcast();
+ }
+
+ }
+
+ for ( SCCOL i = 0; i < aCol.size(); i++)
+ {
+ auto& rDestCol = pDestTab->CreateColumnIfNotExists(i);
+ if ( i >= nCol1 && i <= nCol2 )
+ aCol[i].UndoToColumn(rCxt, nRow1, nRow2, nFlags, bMarked, rDestCol);
+ else
+ aCol[i].CopyToColumn(rCxt, 0, rDocument.MaxRow(), InsertDeleteFlags::FORMULA, false, rDestCol);
+ }
+
+ if (nFlags & InsertDeleteFlags::ATTRIB)
+ pDestTab->mpCondFormatList.reset(new ScConditionalFormatList(pDestTab->rDocument, *mpCondFormatList));
+
+ if (!(bWidth||bHeight))
+ return;
+
+ if (bWidth)
+ {
+ pDestTab->mpColWidth->CopyFrom(*mpColWidth, nCol1, nCol2);
+ pDestTab->SetColManualBreaks( std::set(maColManualBreaks) );
+ }
+ if (bHeight)
+ {
+ pDestTab->CopyRowHeight(*this, nRow1, nRow2, 0);
+ pDestTab->SetRowManualBreaks( std::set(maRowManualBreaks) );
+ }
+}
+
+void ScTable::CopyUpdated( const ScTable* pPosTab, ScTable* pDestTab ) const
+{
+ pDestTab->CreateColumnIfNotExists(aCol.size()-1);
+ for (SCCOL i=0; i < aCol.size(); i++)
+ aCol[i].CopyUpdated( pPosTab->FetchColumn(i), pDestTab->aCol[i] );
+}
+
+void ScTable::InvalidateTableArea()
+{
+ bTableAreaValid = false;
+ bTableAreaVisibleValid = false;
+}
+
+void ScTable::InvalidatePageBreaks()
+{
+ mbPageBreaksValid = false;
+}
+
+void ScTable::CopyScenarioTo( ScTable* pDestTab ) const
+{
+ OSL_ENSURE( bScenario, "bScenario == FALSE" );
+
+ for (SCCOL i=0; i < aCol.size(); i++)
+ aCol[i].CopyScenarioTo( pDestTab->CreateColumnIfNotExists(i) );
+}
+
+void ScTable::CopyScenarioFrom( const ScTable* pSrcTab )
+{
+ OSL_ENSURE( bScenario, "bScenario == FALSE" );
+
+ SCCOL nEndCol = pSrcTab->aCol.size();
+ CreateColumnIfNotExists(nEndCol);
+ for (SCCOL i=0; i < nEndCol; i++)
+ aCol[i].CopyScenarioFrom( pSrcTab->aCol[i] );
+}
+
+void ScTable::MarkScenarioIn( ScMarkData& rDestMark, ScScenarioFlags nNeededBits ) const
+{
+ OSL_ENSURE( bScenario, "bScenario == FALSE" );
+
+ if ( ( nScenarioFlags & nNeededBits ) != nNeededBits ) // Are all Bits set?
+ return;
+
+ for (SCCOL i=0; i < aCol.size(); i++)
+ aCol[i].MarkScenarioIn( rDestMark );
+}
+
+bool ScTable::HasScenarioRange( const ScRange& rRange ) const
+{
+ OSL_ENSURE( bScenario, "bScenario == FALSE" );
+
+ ScRange aTabRange = rRange;
+ aTabRange.aStart.SetTab( nTab );
+ aTabRange.aEnd.SetTab( nTab );
+
+ const ScRangeList* pList = GetScenarioRanges();
+
+ if (pList)
+ {
+ for ( size_t j = 0, n = pList->size(); j < n; j++ )
+ {
+ const ScRange & rR = (*pList)[j];
+ if ( rR.Intersects( aTabRange ) )
+ return true;
+ }
+ }
+
+ return false;
+}
+
+void ScTable::InvalidateScenarioRanges()
+{
+ pScenarioRanges.reset();
+}
+
+const ScRangeList* ScTable::GetScenarioRanges() const
+{
+ OSL_ENSURE( bScenario, "bScenario == FALSE" );
+
+ if (!pScenarioRanges)
+ {
+ const_cast<ScTable*>(this)->pScenarioRanges.reset(new ScRangeList);
+ ScMarkData aMark(rDocument.GetSheetLimits());
+ MarkScenarioIn( aMark, ScScenarioFlags::NONE ); // always
+ aMark.FillRangeListWithMarks( pScenarioRanges.get(), false );
+ }
+ return pScenarioRanges.get();
+}
+
+bool ScTable::TestCopyScenarioTo( const ScTable* pDestTab ) const
+{
+ OSL_ENSURE( bScenario, "bScenario == FALSE" );
+
+ if (!pDestTab->IsProtected())
+ return true;
+
+ bool bOk = true;
+ for (SCCOL i=0; i < aCol.size() && bOk; i++)
+ bOk = aCol[i].TestCopyScenarioTo( pDestTab->aCol[i] );
+ return bOk;
+}
+
+bool ScTable::SetString( SCCOL nCol, SCROW nRow, SCTAB nTabP, const OUString& rString,
+ const ScSetStringParam * pParam )
+{
+ if (!ValidColRow(nCol,nRow))
+ {
+ return false;
+ }
+
+ return CreateColumnIfNotExists(nCol).SetString(
+ nRow, nTabP, rString, rDocument.GetAddressConvention(), pParam);
+}
+
+bool ScTable::SetEditText( SCCOL nCol, SCROW nRow, std::unique_ptr<EditTextObject> pEditText )
+{
+ if (!ValidColRow(nCol, nRow))
+ {
+ return false;
+ }
+
+ CreateColumnIfNotExists(nCol).SetEditText(nRow, std::move(pEditText));
+ return true;
+}
+
+void ScTable::SetEditText( SCCOL nCol, SCROW nRow, const EditTextObject& rEditText, const SfxItemPool* pEditPool )
+{
+ if (!ValidColRow(nCol, nRow))
+ return;
+
+ CreateColumnIfNotExists(nCol).SetEditText(nRow, rEditText, pEditPool);
+}
+
+SCROW ScTable::GetFirstEditTextRow( SCCOL nCol1, SCROW nRow1, SCCOL nCol2, SCROW nRow2 ) const
+{
+ if (!ValidCol(nCol1) || !ValidCol(nCol2) || nCol2 < nCol1)
+ return -1;
+
+ if (!ValidRow(nRow1) || !ValidRow(nRow2) || nRow2 < nRow1)
+ return -1;
+
+ nCol2 = ClampToAllocatedColumns(nCol2);
+ SCROW nFirst = rDocument.MaxRow()+1;
+ for (SCCOL i = nCol1; i <= nCol2; ++i)
+ {
+ const ScColumn& rCol = aCol[i];
+ SCROW nThisFirst = -1;
+ if (const_cast<ScColumn&>(rCol).HasEditCells(nRow1, nRow2, nThisFirst))
+ {
+ if (nThisFirst == nRow1)
+ return nRow1;
+
+ if (nThisFirst < nFirst)
+ nFirst = nThisFirst;
+ }
+ }
+
+ return nFirst == (rDocument.MaxRow()+1) ? -1 : nFirst;
+}
+
+void ScTable::SetEmptyCell( SCCOL nCol, SCROW nRow )
+{
+ if (!ValidColRow(nCol, nRow) || nCol >= GetAllocatedColumnsCount())
+ return;
+
+ aCol[nCol].Delete(nRow);
+}
+
+void ScTable::SetFormula(
+ SCCOL nCol, SCROW nRow, const ScTokenArray& rArray, formula::FormulaGrammar::Grammar eGram )
+{
+ if (!ValidColRow(nCol, nRow))
+ return;
+
+ CreateColumnIfNotExists(nCol).SetFormula(nRow, rArray, eGram);
+}
+
+void ScTable::SetFormula(
+ SCCOL nCol, SCROW nRow, const OUString& rFormula, formula::FormulaGrammar::Grammar eGram )
+{
+ if (!ValidColRow(nCol, nRow))
+ return;
+
+ CreateColumnIfNotExists(nCol).SetFormula(nRow, rFormula, eGram);
+}
+
+ScFormulaCell* ScTable::SetFormulaCell( SCCOL nCol, SCROW nRow, ScFormulaCell* pCell )
+{
+ if (!ValidColRow(nCol, nRow))
+ {
+ delete pCell;
+ return nullptr;
+ }
+
+ return CreateColumnIfNotExists(nCol).SetFormulaCell(nRow, pCell, sc::ConvertToGroupListening);
+}
+
+bool ScTable::SetFormulaCells( SCCOL nCol, SCROW nRow, std::vector<ScFormulaCell*>& rCells )
+{
+ if (!ValidCol(nCol))
+ return false;
+
+ return CreateColumnIfNotExists(nCol).SetFormulaCells(nRow, rCells);
+}
+
+svl::SharedString ScTable::GetSharedString( SCCOL nCol, SCROW nRow ) const
+{
+ if (!ValidColRow(nCol, nRow) || nCol >= GetAllocatedColumnsCount())
+ return svl::SharedString();
+
+ return aCol[nCol].GetSharedString(nRow);
+}
+
+void ScTable::SetValue( SCCOL nCol, SCROW nRow, const double& rVal )
+{
+ if (ValidColRow(nCol, nRow))
+ CreateColumnIfNotExists(nCol).SetValue(nRow, rVal);
+}
+
+void ScTable::SetRawString( SCCOL nCol, SCROW nRow, const svl::SharedString& rStr )
+{
+ if (ValidColRow(nCol, nRow))
+ CreateColumnIfNotExists(nCol).SetRawString(nRow, rStr);
+}
+
+OUString ScTable::GetString( SCCOL nCol, SCROW nRow, const ScInterpreterContext* pContext ) const
+{
+ if (ValidColRow(nCol,nRow) && nCol < GetAllocatedColumnsCount())
+ return aCol[nCol].GetString( nRow, pContext );
+ else
+ return OUString();
+}
+
+double* ScTable::GetValueCell( SCCOL nCol, SCROW nRow )
+{
+ if (!ValidColRow(nCol, nRow))
+ return nullptr;
+
+ return CreateColumnIfNotExists(nCol).GetValueCell(nRow);
+}
+
+OUString ScTable::GetInputString( SCCOL nCol, SCROW nRow, const svl::SharedString** pShared, bool bForceSystemLocale ) const
+{
+ if (ValidColRow(nCol, nRow) && nCol < GetAllocatedColumnsCount())
+ return aCol[nCol].GetInputString( nRow, pShared, bForceSystemLocale );
+ else
+ return OUString();
+}
+
+double ScTable::GetValue( SCCOL nCol, SCROW nRow ) const
+{
+ if (ValidColRow(nCol, nRow) && nCol < GetAllocatedColumnsCount())
+ return aCol[nCol].GetValue( nRow );
+ return 0.0;
+}
+
+const EditTextObject* ScTable::GetEditText( SCCOL nCol, SCROW nRow ) const
+{
+ if (!ValidColRow(nCol, nRow) || nCol >= GetAllocatedColumnsCount())
+ return nullptr;
+
+ return aCol[nCol].GetEditText(nRow);
+}
+
+void ScTable::RemoveEditTextCharAttribs( SCCOL nCol, SCROW nRow, const ScPatternAttr& rAttr )
+{
+ if (!ValidColRow(nCol, nRow) || nCol >= GetAllocatedColumnsCount())
+ return;
+
+ return aCol[nCol].RemoveEditTextCharAttribs(nRow, rAttr);
+}
+
+OUString ScTable::GetFormula( SCCOL nCol, SCROW nRow ) const
+{
+ if (ValidColRow(nCol, nRow) && nCol < GetAllocatedColumnsCount())
+ return aCol[nCol].GetFormula( nRow );
+ else
+ return OUString();
+}
+
+const ScFormulaCell* ScTable::GetFormulaCell( SCCOL nCol, SCROW nRow ) const
+{
+ if (!ValidColRow(nCol, nRow) || nCol >= GetAllocatedColumnsCount())
+ return nullptr;
+
+ return aCol[nCol].GetFormulaCell(nRow);
+}
+
+ScFormulaCell* ScTable::GetFormulaCell( SCCOL nCol, SCROW nRow )
+{
+ if (!ValidColRow(nCol, nRow) || nCol >= GetAllocatedColumnsCount())
+ return nullptr;
+
+ return aCol[nCol].GetFormulaCell(nRow);
+}
+
+// Sparklines
+
+std::shared_ptr<sc::Sparkline> ScTable::GetSparkline(SCCOL nCol, SCROW nRow)
+{
+ if (!ValidCol(nCol) || nCol >= GetAllocatedColumnsCount())
+ return std::shared_ptr<sc::Sparkline>();
+
+ sc::SparklineCell* pSparklineCell = aCol[nCol].GetSparklineCell(nRow);
+ if (!pSparklineCell)
+ return std::shared_ptr<sc::Sparkline>();
+
+ return pSparklineCell->getSparkline();
+}
+
+sc::Sparkline* ScTable::CreateSparkline(SCCOL nCol, SCROW nRow, std::shared_ptr<sc::SparklineGroup> const& pSparklineGroup)
+{
+ if (!ValidCol(nCol))
+ return nullptr;
+
+ ScColumn& rColumn = CreateColumnIfNotExists(nCol);
+
+ std::shared_ptr<sc::Sparkline> pSparkline(new sc::Sparkline(nCol, nRow, pSparklineGroup));
+ rColumn.CreateSparklineCell(nRow, pSparkline);
+
+ return pSparkline.get();
+}
+
+bool ScTable::DeleteSparkline(SCCOL nCol, SCROW nRow)
+{
+ if (!ValidCol(nCol) || nCol >= GetAllocatedColumnsCount())
+ return false;
+
+ aCol[nCol].DeleteSparkline(nRow);
+
+ return true;
+}
+
+sc::SparklineList& ScTable::GetSparklineList()
+{
+ return maSparklineList;
+}
+
+// Notes
+
+std::unique_ptr<ScPostIt> ScTable::ReleaseNote( SCCOL nCol, SCROW nRow )
+{
+ if (!ValidCol(nCol) || nCol >= GetAllocatedColumnsCount())
+ return nullptr;
+
+ return aCol[nCol].ReleaseNote(nRow);
+}
+
+ScPostIt* ScTable::GetNote( SCCOL nCol, SCROW nRow )
+{
+ if (!ValidCol(nCol) || nCol >= GetAllocatedColumnsCount())
+ return nullptr;
+ return aCol[nCol].GetCellNote(nRow);
+}
+
+void ScTable::SetNote( SCCOL nCol, SCROW nRow, std::unique_ptr<ScPostIt> pNote )
+{
+ if (!ValidColRow(nCol, nRow))
+ return;
+
+ CreateColumnIfNotExists(nCol).SetCellNote(nRow, std::move(pNote));
+}
+
+size_t ScTable::GetNoteCount( SCCOL nCol ) const
+{
+ if (!ValidCol(nCol) || nCol >= GetAllocatedColumnsCount())
+ return 0;
+
+ return aCol[nCol].GetNoteCount();
+}
+
+SCROW ScTable::GetNotePosition( SCCOL nCol, size_t nIndex ) const
+{
+ if (!ValidCol(nCol) || nCol >= GetAllocatedColumnsCount())
+ return -1;
+
+ return aCol[nCol].GetNotePosition(nIndex);
+}
+
+void ScTable::CreateAllNoteCaptions()
+{
+ for (SCCOL i = 0; i < aCol.size(); ++i)
+ aCol[i].CreateAllNoteCaptions();
+}
+
+void ScTable::ForgetNoteCaptions( SCCOL nCol1, SCROW nRow1, SCCOL nCol2, SCROW nRow2, bool bPreserveData )
+{
+ if (!ValidCol(nCol1) || !ValidCol(nCol2))
+ return;
+ if ( nCol2 >= aCol.size() ) nCol2 = aCol.size() - 1;
+ for (SCCOL i = nCol1; i <= nCol2; ++i)
+ aCol[i].ForgetNoteCaptions(nRow1, nRow2, bPreserveData);
+}
+
+void ScTable::GetAllNoteEntries( std::vector<sc::NoteEntry>& rNotes ) const
+{
+ for (SCCOL nCol = 0; nCol < aCol.size(); ++nCol)
+ aCol[nCol].GetAllNoteEntries(rNotes);
+}
+
+void ScTable::GetNotesInRange( const ScRange& rRange, std::vector<sc::NoteEntry>& rNotes ) const
+{
+ SCROW nStartRow = rRange.aStart.Row();
+ SCROW nEndRow = rRange.aEnd.Row();
+ SCCOL nEndCol = ClampToAllocatedColumns(rRange.aEnd.Col());
+ for (SCCOL nCol = rRange.aStart.Col(); nCol <= nEndCol; ++nCol)
+ {
+ aCol[nCol].GetNotesInRange(nStartRow, nEndRow, rNotes);
+ }
+}
+
+CommentCaptionState ScTable::GetAllNoteCaptionsState(const ScRange& rRange, std::vector<sc::NoteEntry>& rNotes )
+{
+ SCROW nStartRow = rRange.aStart.Row();
+ SCROW nEndRow = rRange.aEnd.Row();
+ bool bIsFirstNoteShownState = true; // because of error: -Werror=maybe-uninitialized
+ bool bFirstControl = true;
+
+ ScTable* pTab = rDocument.FetchTable(nTab);
+ assert(pTab);
+ const SCCOL nEndCol = pTab->ClampToAllocatedColumns(rRange.aEnd.Col());
+ for (SCCOL nCol = rRange.aStart.Col(); nCol <= nEndCol; ++nCol)
+ {
+ if (bFirstControl && rDocument.HasColNotes(nCol, nTab)) // detect status of first note caption
+ {
+ aCol[nCol].GetNotesInRange(nStartRow, nEndRow, rNotes);
+ bIsFirstNoteShownState = rNotes.begin()->mpNote->IsCaptionShown();
+ bFirstControl = false;
+ }
+
+ if (rDocument.HasColNotes(nCol, nTab))
+ {
+ aCol[nCol].GetNotesInRange(nStartRow, nEndRow, rNotes);
+
+ bool bIsMixedState = std::any_of(rNotes.begin(), rNotes.end(), [bIsFirstNoteShownState](const sc::NoteEntry& rNote) {
+ // compare the first note caption with others
+ return bIsFirstNoteShownState != rNote.mpNote->IsCaptionShown(); });
+ if (bIsMixedState)
+ return CommentCaptionState::MIXED;
+ }
+ }
+ return bIsFirstNoteShownState ? CommentCaptionState::ALLSHOWN : CommentCaptionState::ALLHIDDEN;
+}
+
+void ScTable::GetUnprotectedCells( ScRangeList& rRangeList ) const
+{
+ for (auto const & pCol : aCol)
+ pCol->GetUnprotectedCells(0, rDocument.MaxRow(), rRangeList);
+}
+
+bool ScTable::ContainsNotesInRange( const ScRange& rRange ) const
+{
+ SCROW nStartRow = rRange.aStart.Row();
+ SCROW nEndRow = rRange.aEnd.Row();
+ SCCOL nEndCol = ClampToAllocatedColumns(rRange.aEnd.Col());
+ for (SCCOL nCol = rRange.aStart.Col(); nCol <= nEndCol; ++nCol)
+ {
+ bool bContainsNote = !aCol[nCol].IsNotesEmptyBlock(nStartRow, nEndRow);
+ if(bContainsNote)
+ return true;
+ }
+
+ return false;
+}
+
+CellType ScTable::GetCellType( SCCOL nCol, SCROW nRow ) const
+{
+ if (ValidColRow(nCol, nRow) && nCol < GetAllocatedColumnsCount())
+ return aCol[nCol].GetCellType( nRow );
+ return CELLTYPE_NONE;
+}
+
+ScRefCellValue ScTable::GetCellValue( SCCOL nCol, SCROW nRow ) const
+{
+ if (!ValidColRow(nCol, nRow) || nCol >= GetAllocatedColumnsCount())
+ return ScRefCellValue();
+
+ return aCol[nCol].GetCellValue(nRow);
+}
+
+ScRefCellValue ScTable::GetCellValue( SCCOL nCol, sc::ColumnBlockPosition& rBlockPos, SCROW nRow )
+{
+ if (!ValidColRow(nCol, nRow) || nCol >= GetAllocatedColumnsCount())
+ return ScRefCellValue();
+
+ return aCol[nCol].GetCellValue(rBlockPos, nRow);
+}
+
+void ScTable::GetFirstDataPos(SCCOL& rCol, SCROW& rRow) const
+{
+ rCol = 0;
+ rRow = rDocument.MaxRow()+1;
+ while (rCol < (aCol.size() - 1) && aCol[rCol].IsEmptyData() )
+ ++rCol;
+ SCCOL nCol = rCol;
+ while (nCol < aCol.size() && rRow > 0)
+ {
+ if (!aCol[nCol].IsEmptyData())
+ rRow = ::std::min( rRow, aCol[nCol].GetFirstDataPos());
+ ++nCol;
+ }
+}
+
+void ScTable::GetLastDataPos(SCCOL& rCol, SCROW& rRow) const
+{
+ rCol = aCol.size() - 1;
+ rRow = 0;
+ while (aCol[rCol].IsEmptyData() && (rCol > 0))
+ rCol--;
+ SCCOL nCol = rCol;
+ while (nCol >= 0 && rRow < rDocument.MaxRow())
+ rRow = ::std::max( rRow, aCol[nCol--].GetLastDataPos());
+}
+
+bool ScTable::HasData( SCCOL nCol, SCROW nRow ) const
+{
+ if (ValidColRow(nCol, nRow) && nCol < GetAllocatedColumnsCount())
+ return aCol[nCol].HasDataAt( nRow );
+ else
+ return false;
+}
+
+bool ScTable::HasStringData( SCCOL nCol, SCROW nRow ) const
+{
+ if (ValidColRow(nCol, nRow) && nCol < GetAllocatedColumnsCount())
+ return aCol[nCol].HasStringData( nRow );
+ else
+ return false;
+}
+
+bool ScTable::HasValueData( SCCOL nCol, SCROW nRow ) const
+{
+ if (ValidColRow(nCol, nRow) && nCol < GetAllocatedColumnsCount())
+ return aCol[nCol].HasValueData( nRow );
+ else
+ return false;
+}
+
+bool ScTable::HasStringCells( SCCOL nStartCol, SCROW nStartRow,
+ SCCOL nEndCol, SCROW nEndRow ) const
+{
+ if (ValidCol(nEndCol))
+ {
+ nEndCol = ClampToAllocatedColumns(nEndCol);
+ for (SCCOL nCol = nStartCol; nCol <= nEndCol; nCol++)
+ if (aCol[nCol].HasStringCells(nStartRow, nEndRow))
+ return true;
+ }
+
+ return false;
+}
+
+void ScTable::SetDirtyVar()
+{
+ for (SCCOL i=0; i < aCol.size(); i++)
+ aCol[i].SetDirtyVar();
+}
+
+void ScTable::CheckVectorizationState()
+{
+ sc::AutoCalcSwitch aACSwitch(rDocument, false);
+
+ for (SCCOL i = 0; i < aCol.size(); i++)
+ aCol[i].CheckVectorizationState();
+}
+
+void ScTable::SetAllFormulasDirty( const sc::SetFormulaDirtyContext& rCxt )
+{
+ sc::AutoCalcSwitch aACSwitch(rDocument, false);
+
+ for (SCCOL i=0; i < aCol.size(); i++)
+ aCol[i].SetAllFormulasDirty(rCxt);
+}
+
+void ScTable::SetDirty( const ScRange& rRange, ScColumn::BroadcastMode eMode )
+{
+ sc::AutoCalcSwitch aSwitch(rDocument, false);
+ SCCOL nCol2 = rRange.aEnd.Col();
+ nCol2 = ClampToAllocatedColumns(nCol2);
+ for (SCCOL i=rRange.aStart.Col(); i<=nCol2; i++)
+ aCol[i].SetDirty(rRange.aStart.Row(), rRange.aEnd.Row(), eMode);
+}
+
+void ScTable::SetTableOpDirty( const ScRange& rRange )
+{
+ sc::AutoCalcSwitch aSwitch(rDocument, false);
+ const SCCOL nCol2 = ClampToAllocatedColumns(rRange.aEnd.Col());
+ for (SCCOL i=rRange.aStart.Col(); i<=nCol2; i++)
+ aCol[i].SetTableOpDirty( rRange );
+}
+
+void ScTable::SetDirtyAfterLoad()
+{
+ sc::AutoCalcSwitch aSwitch(rDocument, false);
+ for (SCCOL i=0; i < aCol.size(); i++)
+ aCol[i].SetDirtyAfterLoad();
+}
+
+void ScTable::SetDirtyIfPostponed()
+{
+ sc::AutoCalcSwitch aSwitch(rDocument, false);
+ ScBulkBroadcast aBulkBroadcast( rDocument.GetBASM(), SfxHintId::ScDataChanged);
+ for (SCCOL i=0; i < aCol.size(); i++)
+ aCol[i].SetDirtyIfPostponed();
+}
+
+void ScTable::BroadcastRecalcOnRefMove()
+{
+ sc::AutoCalcSwitch aSwitch(rDocument, false);
+ for (SCCOL i = 0; i < aCol.size(); ++i)
+ aCol[i].BroadcastRecalcOnRefMove();
+}
+
+bool ScTable::BroadcastBroadcasters( SCCOL nCol1, SCROW nRow1, SCCOL nCol2, SCROW nRow2, SfxHintId nHint )
+{
+ bool bBroadcasted = false;
+ sc::AutoCalcSwitch aSwitch(rDocument, false);
+ nCol2 = ClampToAllocatedColumns(nCol2);
+ for (SCCOL nCol = nCol1; nCol <= nCol2; ++nCol)
+ bBroadcasted |= aCol[nCol].BroadcastBroadcasters( nRow1, nRow2, nHint);
+ return bBroadcasted;
+}
+
+void ScTable::SetLoadingMedium(bool bLoading)
+{
+ mpRowHeights->enableTreeSearch(!bLoading);
+}
+
+void ScTable::CalcAll()
+{
+ for (SCCOL i=0; i < aCol.size(); i++)
+ aCol[i].CalcAll();
+
+ mpCondFormatList->CalcAll();
+}
+
+void ScTable::CompileAll( sc::CompileFormulaContext& rCxt )
+{
+ for (SCCOL i = 0; i < aCol.size(); ++i)
+ aCol[i].CompileAll(rCxt);
+
+ if(mpCondFormatList)
+ mpCondFormatList->CompileAll();
+}
+
+void ScTable::CompileXML( sc::CompileFormulaContext& rCxt, ScProgress& rProgress )
+{
+ if (mpRangeName)
+ mpRangeName->CompileUnresolvedXML(rCxt);
+
+ for (SCCOL i=0; i < aCol.size(); i++)
+ {
+ aCol[i].CompileXML(rCxt, rProgress);
+ }
+
+ if(mpCondFormatList)
+ mpCondFormatList->CompileXML();
+}
+
+bool ScTable::CompileErrorCells( sc::CompileFormulaContext& rCxt, FormulaError nErrCode )
+{
+ bool bCompiled = false;
+ for (SCCOL i = 0; i < aCol.size(); ++i)
+ {
+ if (aCol[i].CompileErrorCells(rCxt, nErrCode))
+ bCompiled = true;
+ }
+
+ return bCompiled;
+}
+
+void ScTable::CalcAfterLoad( sc::CompileFormulaContext& rCxt, bool bStartListening )
+{
+ for (SCCOL i = 0; i < aCol.size(); ++i)
+ aCol[i].CalcAfterLoad(rCxt, bStartListening);
+}
+
+void ScTable::ResetChanged( const ScRange& rRange )
+{
+ SCCOL nStartCol = rRange.aStart.Col();
+ SCROW nStartRow = rRange.aStart.Row();
+ SCCOL nEndCol = ClampToAllocatedColumns(rRange.aEnd.Col());
+ SCROW nEndRow = rRange.aEnd.Row();
+
+ for (SCCOL nCol=nStartCol; nCol<=nEndCol; nCol++)
+ aCol[nCol].ResetChanged(nStartRow, nEndRow);
+}
+
+// Attribute
+
+const SfxPoolItem* ScTable::GetAttr( SCCOL nCol, SCROW nRow, sal_uInt16 nWhich ) const
+{
+ if (!ValidColRow(nCol, nRow))
+ return nullptr;
+ return &ColumnData(nCol).GetAttr( nRow, nWhich );
+}
+
+const SfxPoolItem* ScTable::GetAttr( SCCOL nCol, SCROW nRow, sal_uInt16 nWhich, SCROW& nStartRow, SCROW& nEndRow ) const
+{
+ if (!ValidColRow(nCol, nRow))
+ return nullptr;
+ return &ColumnData(nCol).GetAttr( nRow, nWhich, nStartRow, nEndRow );
+}
+
+sal_uInt32 ScTable::GetNumberFormat( const ScInterpreterContext& rContext, const ScAddress& rPos ) const
+{
+ if (ValidColRow(rPos.Col(), rPos.Row()))
+ return ColumnData(rPos.Col()).GetNumberFormat(rContext, rPos.Row());
+ return 0;
+}
+
+sal_uInt32 ScTable::GetNumberFormat( SCCOL nCol, SCROW nRow ) const
+{
+ return GetNumberFormat(rDocument.GetNonThreadedContext(), ScAddress(nCol, nRow, nTab));
+}
+
+sal_uInt32 ScTable::GetNumberFormat( SCCOL nCol, SCROW nStartRow, SCROW nEndRow ) const
+{
+ if (!ValidCol(nCol) || !ValidRow(nStartRow) || !ValidRow(nEndRow))
+ return 0;
+
+ return ColumnData(nCol).GetNumberFormat(nStartRow, nEndRow);
+}
+
+void ScTable::SetNumberFormat( SCCOL nCol, SCROW nRow, sal_uInt32 nNumberFormat )
+{
+ if (!ValidColRow(nCol, nRow))
+ return;
+
+ CreateColumnIfNotExists(nCol).SetNumberFormat(nRow, nNumberFormat);
+}
+
+const ScPatternAttr* ScTable::GetPattern( SCCOL nCol, SCROW nRow ) const
+{
+ if (!ValidColRow(nCol,nRow))
+ return nullptr;
+ return ColumnData(nCol).GetPattern( nRow );
+}
+
+const ScPatternAttr* ScTable::GetMostUsedPattern( SCCOL nCol, SCROW nStartRow, SCROW nEndRow ) const
+{
+ if ( ValidColRow( nCol, nStartRow ) && ValidRow( nEndRow ) && (nStartRow <= nEndRow))
+ return ColumnData(nCol).GetMostUsedPattern( nStartRow, nEndRow );
+ return nullptr;
+}
+
+bool ScTable::HasAttrib( SCCOL nCol1, SCROW nRow1, SCCOL nCol2, SCROW nRow2, HasAttrFlags nMask ) const
+{
+ for(SCCOL nCol = nCol1; nCol <= nCol2 && nCol < aCol.size(); ++nCol )
+ if( aCol[nCol].HasAttrib( nRow1, nRow2, nMask ))
+ return true;
+ if( nCol2 >= aCol.size())
+ return aDefaultColData.HasAttrib( nRow1, nRow2, nMask );
+ return false;
+}
+
+bool ScTable::HasAttrib( SCCOL nCol, SCROW nRow, HasAttrFlags nMask, SCROW* nStartRow, SCROW* nEndRow ) const
+{
+ return ColumnData(nCol).HasAttrib( nRow, nMask, nStartRow, nEndRow );
+}
+
+bool ScTable::HasAttribSelection( const ScMarkData& rMark, HasAttrFlags nMask ) const
+{
+ std::vector<sc::ColRowSpan> aSpans = rMark.GetMarkedColSpans();
+
+ for (const sc::ColRowSpan & aSpan : aSpans)
+ {
+ for (SCCOLROW j = aSpan.mnStart; j <= aSpan.mnEnd; ++j)
+ {
+ if (aCol[j].HasAttribSelection(rMark, nMask))
+ return true;
+ }
+ }
+ return false;
+}
+
+bool ScTable::ExtendMerge( SCCOL nStartCol, SCROW nStartRow,
+ SCCOL& rEndCol, SCROW& rEndRow,
+ bool bRefresh )
+{
+ if (!(ValidCol(nStartCol) && ValidCol(rEndCol)))
+ {
+ OSL_FAIL("ScTable::ExtendMerge: invalid column number");
+ return false;
+ }
+ if( rEndCol >= aCol.size())
+ assert( !aDefaultColData.GetAttr( nStartRow, ATTR_MERGE ).IsMerged());
+ bool bFound = false;
+ SCCOL nOldEndX = ClampToAllocatedColumns(rEndCol);
+ SCROW nOldEndY = rEndRow;
+ for (SCCOL i=nStartCol; i<=nOldEndX; i++)
+ bFound |= aCol[i].ExtendMerge( i, nStartRow, nOldEndY, rEndCol, rEndRow, bRefresh );
+ return bFound;
+}
+
+void ScTable::SetMergedCells( SCCOL nCol1, SCROW nRow1, SCCOL nCol2, SCROW nRow2 )
+{
+ ScMergeAttr aAttr(nCol2-nCol1+1, nRow2-nRow1+1);
+ ApplyAttr(nCol1, nRow1, aAttr);
+
+ if (nCol1 < nCol2)
+ ApplyFlags(nCol1+1, nRow1, nCol2, nRow2, ScMF::Hor);
+
+ if (nRow1 < nRow2)
+ ApplyFlags(nCol1, nRow1+1, nCol1, nRow2, ScMF::Ver);
+
+ if (nCol1 < nCol2 && nRow1 < nRow2)
+ ApplyFlags(nCol1+1, nRow1+1, nCol2, nRow2, ScMF::Hor | ScMF::Ver);
+}
+
+bool ScTable::IsBlockEmpty( SCCOL nCol1, SCROW nRow1, SCCOL nCol2, SCROW nRow2 ) const
+{
+ if (!(ValidCol(nCol1) && ValidCol(nCol2)))
+ {
+ OSL_FAIL("ScTable::IsBlockEmpty: invalid column number");
+ return false;
+ }
+ nCol2 = ClampToAllocatedColumns(nCol2);
+ bool bEmpty = true;
+ for (SCCOL i=nCol1; i<=nCol2 && bEmpty; i++)
+ {
+ bEmpty = aCol[i].IsEmptyData( nRow1, nRow2 );
+ if (bEmpty)
+ {
+ bEmpty = aCol[i].IsSparklinesEmptyBlock(nRow1, nRow2);
+ }
+ if (bEmpty)
+ {
+ bEmpty = aCol[i].IsNotesEmptyBlock(nRow1, nRow2);
+ }
+ }
+ return bEmpty;
+}
+
+SCSIZE ScTable::FillMaxRot( RowInfo* pRowInfo, SCSIZE nArrCount, SCCOL nX1, SCCOL nX2,
+ SCCOL nCol, SCROW nAttrRow1, SCROW nAttrRow2, SCSIZE nArrY,
+ const ScPatternAttr* pPattern, const SfxItemSet* pCondSet )
+{
+ // Return value = new nArrY
+
+ ScRotateDir nRotDir = pPattern->GetRotateDir( pCondSet );
+ if ( nRotDir != ScRotateDir::NONE )
+ {
+ bool bHit = true;
+ if ( nCol+1 < nX1 ) // column to the left
+ bHit = ( nRotDir != ScRotateDir::Left );
+ else if ( nCol > nX2+1 ) // column to the right
+ bHit = ( nRotDir != ScRotateDir::Right ); // ScRotateDir::Standard may now also be extended to the left
+
+ if ( bHit )
+ {
+ double nFactor = 0.0;
+ if ( nCol > nX2+1 )
+ {
+ Degree100 nRotVal = pPattern->
+ GetItem( ATTR_ROTATE_VALUE, pCondSet ).GetValue();
+ double nRealOrient = toRadians(nRotVal);
+ double nCos = cos( nRealOrient );
+ double nSin = sin( nRealOrient );
+ //TODO: limit !!!
+ //TODO: additional factor for varying PPT X/Y !!!
+
+ // for ScRotateDir::Left this gives a negative value,
+ // if the mode is considered
+ nFactor = -fabs( nCos / nSin );
+ }
+
+ for ( SCROW nRow = nAttrRow1; nRow <= nAttrRow2; nRow++ )
+ {
+ if (!RowHidden(nRow))
+ {
+ bool bHitOne = true;
+ if ( nCol > nX2+1 )
+ {
+ // Does the rotated cell extend into the visible range?
+
+ SCCOL nTouchedCol = nCol;
+ tools::Long nWidth = static_cast<tools::Long>(mpRowHeights->getValue(nRow) * nFactor);
+ OSL_ENSURE(nWidth <= 0, "Wrong direction");
+ while ( nWidth < 0 && nTouchedCol > 0 )
+ {
+ --nTouchedCol;
+ nWidth += GetColWidth( nTouchedCol );
+ }
+ if ( nTouchedCol > nX2 )
+ bHitOne = false;
+ }
+
+ if (bHitOne)
+ {
+ while ( nArrY<nArrCount && pRowInfo[nArrY].nRowNo < nRow )
+ ++nArrY;
+ if ( nArrY<nArrCount && pRowInfo[nArrY].nRowNo == nRow )
+ pRowInfo[nArrY].nRotMaxCol = nCol;
+ }
+ }
+ }
+ }
+ }
+
+ return nArrY;
+}
+
+void ScTable::FindMaxRotCol( RowInfo* pRowInfo, SCSIZE nArrCount, SCCOL nX1, SCCOL nX2 )
+{
+ if ( !mpColWidth || !mpRowHeights || !mpColFlags || !pRowFlags )
+ {
+ OSL_FAIL( "Row/column info missing" );
+ return;
+ }
+
+ // nRotMaxCol is initialized to SC_ROTMAX_NONE, nRowNo is already set
+
+ SCROW nY1 = pRowInfo[0].nRowNo;
+ SCROW nY2 = pRowInfo[nArrCount-1].nRowNo;
+
+ for (SCCOL nCol : GetColumnsRange(0, rDocument.MaxCol()))
+ {
+ if (!ColHidden(nCol))
+ {
+ SCSIZE nArrY = 0;
+ ScDocAttrIterator aIter( rDocument, nTab, nCol, nY1, nCol, nY2 );
+ SCCOL nAttrCol;
+ SCROW nAttrRow1, nAttrRow2;
+ const ScPatternAttr* pPattern = aIter.GetNext( nAttrCol, nAttrRow1, nAttrRow2 );
+ while ( pPattern )
+ {
+ if ( const ScCondFormatItem* pCondItem = pPattern->GetItemSet().GetItemIfSet( ATTR_CONDITIONAL ) )
+ {
+ // Run through all formats, so that each cell does not have to be
+ // handled individually
+
+ const ScCondFormatIndexes& rCondFormatData = pCondItem->GetCondFormatData();
+ ScStyleSheetPool* pStylePool = rDocument.GetStyleSheetPool();
+ if (mpCondFormatList && pStylePool && !rCondFormatData.empty())
+ {
+ for(const auto& rItem : rCondFormatData)
+ {
+ const ScConditionalFormat* pFormat = mpCondFormatList->GetFormat(rItem);
+ if ( pFormat )
+ {
+ size_t nEntryCount = pFormat->size();
+ for (size_t nEntry=0; nEntry<nEntryCount; nEntry++)
+ {
+ const ScFormatEntry* pEntry = pFormat->GetEntry(nEntry);
+ if(pEntry->GetType() != ScFormatEntry::Type::Condition &&
+ pEntry->GetType() != ScFormatEntry::Type::ExtCondition)
+ continue;
+
+ OUString aStyleName = static_cast<const ScCondFormatEntry*>(pEntry)->GetStyle();
+ if (!aStyleName.isEmpty())
+ {
+ SfxStyleSheetBase* pStyleSheet =
+ pStylePool->Find( aStyleName, SfxStyleFamily::Para );
+ if ( pStyleSheet )
+ {
+ FillMaxRot( pRowInfo, nArrCount, nX1, nX2,
+ nCol, nAttrRow1, nAttrRow2,
+ nArrY, pPattern, &pStyleSheet->GetItemSet() );
+ // not changing nArrY
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+
+ nArrY = FillMaxRot( pRowInfo, nArrCount, nX1, nX2,
+ nCol, nAttrRow1, nAttrRow2,
+ nArrY, pPattern, nullptr );
+
+ pPattern = aIter.GetNext( nAttrCol, nAttrRow1, nAttrRow2 );
+ }
+ }
+ }
+}
+
+bool ScTable::HasBlockMatrixFragment( const SCCOL nCol1, SCROW nRow1, const SCCOL nCol2, SCROW nRow2,
+ bool bNoMatrixAtAll ) const
+{
+ using namespace sc;
+
+ if ( !IsColValid( nCol1 ) )
+ return false;
+
+ const SCCOL nMaxCol2 = std::min<SCCOL>( nCol2, aCol.size() - 1 );
+
+ MatrixEdge nEdges = MatrixEdge::Nothing;
+
+ if ( nCol1 == nMaxCol2 )
+ { // left and right column
+ const MatrixEdge n = MatrixEdge::Left | MatrixEdge::Right;
+ nEdges = aCol[nCol1].GetBlockMatrixEdges( nRow1, nRow2, n, bNoMatrixAtAll );
+ if ((nEdges != MatrixEdge::Nothing) && (((nEdges & n)!=n) || (nEdges & (MatrixEdge::Inside|MatrixEdge::Open))))
+ return true; // left or right edge is missing or open
+ }
+ else
+ { // left column
+ nEdges = aCol[nCol1].GetBlockMatrixEdges(nRow1, nRow2, MatrixEdge::Left, bNoMatrixAtAll);
+ if ((nEdges != MatrixEdge::Nothing) && ((!(nEdges & MatrixEdge::Left)) || (nEdges & (MatrixEdge::Inside|MatrixEdge::Open))))
+ return true; // left edge missing or open
+ // right column
+ nEdges = aCol[nMaxCol2].GetBlockMatrixEdges(nRow1, nRow2, MatrixEdge::Right, bNoMatrixAtAll);
+ if ((nEdges != MatrixEdge::Nothing) && ((!(nEdges & MatrixEdge::Right)) || (nEdges & (MatrixEdge::Inside|MatrixEdge::Open))))
+ return true; // right edge is missing or open
+ }
+
+ if (bNoMatrixAtAll)
+ {
+ for (SCCOL i=nCol1; i<=nMaxCol2; i++)
+ {
+ nEdges = aCol[i].GetBlockMatrixEdges( nRow1, nRow2, MatrixEdge::Nothing, bNoMatrixAtAll);
+ if (nEdges != MatrixEdge::Nothing
+ && (nEdges != (MatrixEdge::Top | MatrixEdge::Left | MatrixEdge::Bottom | MatrixEdge::Right)))
+ return true;
+ }
+ }
+ else if ( nRow1 == nRow2 )
+ { // Row on top and on bottom
+ bool bOpen = false;
+ const MatrixEdge n = MatrixEdge::Bottom | MatrixEdge::Top;
+ for ( SCCOL i=nCol1; i<=nMaxCol2; i++)
+ {
+ nEdges = aCol[i].GetBlockMatrixEdges( nRow1, nRow1, n, bNoMatrixAtAll );
+ if (nEdges != MatrixEdge::Nothing)
+ {
+ if ( (nEdges & n) != n )
+ return true; // Top or bottom edge missing
+ if (nEdges & MatrixEdge::Left)
+ bOpen = true; // left edge open, continue
+ else if ( !bOpen )
+ return true; // Something exist that has not been opened
+ if (nEdges & MatrixEdge::Right)
+ bOpen = false; // Close right edge
+ }
+ }
+ if ( bOpen )
+ return true;
+ }
+ else
+ {
+ int j;
+ MatrixEdge n;
+ SCROW nR;
+ // first top row, then bottom row
+ for ( j=0, n = MatrixEdge::Top, nR=nRow1; j<2;
+ j++, n = MatrixEdge::Bottom, nR=nRow2)
+ {
+ bool bOpen = false;
+ for ( SCCOL i=nCol1; i<=nMaxCol2; i++)
+ {
+ nEdges = aCol[i].GetBlockMatrixEdges( nR, nR, n, bNoMatrixAtAll );
+ if ( nEdges != MatrixEdge::Nothing)
+ {
+ // in top row no top edge respectively
+ // in bottom row no bottom edge
+ if ( (nEdges & n) != n )
+ return true;
+ if (nEdges & MatrixEdge::Left)
+ bOpen = true; // open left edge, continue
+ else if ( !bOpen )
+ return true; // Something exist that has not been opened
+ if (nEdges & MatrixEdge::Right)
+ bOpen = false; // Close right edge
+ }
+ }
+ if ( bOpen )
+ return true;
+ }
+ }
+ return false;
+}
+
+bool ScTable::HasSelectionMatrixFragment( const ScMarkData& rMark ) const
+{
+ std::vector<sc::ColRowSpan> aSpans = rMark.GetMarkedColSpans();
+ ScRangeList rangeList = rMark.GetMarkedRanges();
+
+ for (const sc::ColRowSpan & aSpan : aSpans)
+ {
+ SCCOL nEndCol = ClampToAllocatedColumns(aSpan.mnEnd);
+ for ( SCCOLROW j=aSpan.mnStart; j<=nEndCol; j++ )
+ {
+ if ( aCol[j].HasSelectionMatrixFragment(rMark, rangeList) )
+ return true;
+ }
+ }
+ return false;
+}
+
+bool ScTable::IsBlockEditable( SCCOL nCol1, SCROW nRow1, SCCOL nCol2,
+ SCROW nRow2, bool* pOnlyNotBecauseOfMatrix /* = NULL */,
+ bool bNoMatrixAtAll ) const
+{
+ if ( !ValidColRow( nCol2, nRow2 ) )
+ {
+ SAL_WARN("sc", "IsBlockEditable: invalid column or row " << nCol2 << " " << nRow2);
+ if (pOnlyNotBecauseOfMatrix)
+ *pOnlyNotBecauseOfMatrix = false;
+ return false;
+ }
+
+ bool bIsEditable = true;
+ if ( nLockCount )
+ bIsEditable = false;
+ else if ( IsProtected() && !rDocument.IsScenario(nTab) )
+ {
+ bIsEditable = !HasAttrib( nCol1, nRow1, nCol2, nRow2, HasAttrFlags::Protected );
+ if (!bIsEditable)
+ {
+ // An enhanced protection permission may override the attribute.
+ if (pTabProtection)
+ bIsEditable = pTabProtection->isBlockEditable( ScRange( nCol1, nRow1, nTab, nCol2, nRow2, nTab));
+ }
+ if (bIsEditable)
+ {
+ // If Sheet is protected and cells are not protected then
+ // check the active scenario protect flag if this range is
+ // on the active scenario range. Note the 'copy back' must also
+ // be set to apply protection.
+ sal_uInt16 nScenTab = nTab+1;
+ while(rDocument.IsScenario(nScenTab))
+ {
+ ScRange aEditRange(nCol1, nRow1, nScenTab, nCol2, nRow2, nScenTab);
+ if(rDocument.IsActiveScenario(nScenTab) && rDocument.HasScenarioRange(nScenTab, aEditRange))
+ {
+ ScScenarioFlags nFlags;
+ rDocument.GetScenarioFlags(nScenTab,nFlags);
+ bIsEditable = !((nFlags & ScScenarioFlags::Protected) && (nFlags & ScScenarioFlags::TwoWay));
+ break;
+ }
+ nScenTab++;
+ }
+ }
+ }
+ else if (rDocument.IsScenario(nTab))
+ {
+ // Determine if the preceding sheet is protected
+ SCTAB nActualTab = nTab;
+ do
+ {
+ nActualTab--;
+ }
+ while(rDocument.IsScenario(nActualTab));
+
+ if(rDocument.IsTabProtected(nActualTab))
+ {
+ ScRange aEditRange(nCol1, nRow1, nTab, nCol2, nRow2, nTab);
+ if(rDocument.HasScenarioRange(nTab, aEditRange))
+ {
+ ScScenarioFlags nFlags;
+ rDocument.GetScenarioFlags(nTab,nFlags);
+ bIsEditable = !(nFlags & ScScenarioFlags::Protected);
+ }
+ }
+ }
+ if ( bIsEditable )
+ {
+ if (HasBlockMatrixFragment( nCol1, nRow1, nCol2, nRow2, bNoMatrixAtAll))
+ {
+ bIsEditable = false;
+ if ( pOnlyNotBecauseOfMatrix )
+ *pOnlyNotBecauseOfMatrix = true;
+ }
+ else if ( pOnlyNotBecauseOfMatrix )
+ *pOnlyNotBecauseOfMatrix = false;
+ }
+ else if ( pOnlyNotBecauseOfMatrix )
+ *pOnlyNotBecauseOfMatrix = false;
+ return bIsEditable;
+}
+
+bool ScTable::IsSelectionEditable( const ScMarkData& rMark,
+ bool* pOnlyNotBecauseOfMatrix /* = NULL */ ) const
+{
+ bool bIsEditable = true;
+ if ( nLockCount )
+ bIsEditable = false;
+ else if ( IsProtected() && !rDocument.IsScenario(nTab) )
+ {
+ ScRangeList aRanges;
+ rMark.FillRangeListWithMarks( &aRanges, false );
+ bIsEditable = !HasAttribSelection( rMark, HasAttrFlags::Protected );
+ if (!bIsEditable)
+ {
+ // An enhanced protection permission may override the attribute.
+ if (pTabProtection)
+ bIsEditable = pTabProtection->isSelectionEditable( aRanges);
+ }
+ if (bIsEditable)
+ {
+ // If Sheet is protected and cells are not protected then
+ // check the active scenario protect flag if this area is
+ // in the active scenario range.
+ SCTAB nScenTab = nTab+1;
+ while(rDocument.IsScenario(nScenTab) && bIsEditable)
+ {
+ if(rDocument.IsActiveScenario(nScenTab))
+ {
+ for (size_t i=0, nRange = aRanges.size(); (i < nRange) && bIsEditable; i++ )
+ {
+ const ScRange & rRange = aRanges[ i ];
+ if(rDocument.HasScenarioRange(nScenTab, rRange))
+ {
+ ScScenarioFlags nFlags;
+ rDocument.GetScenarioFlags(nScenTab,nFlags);
+ bIsEditable = !((nFlags & ScScenarioFlags::Protected) && (nFlags & ScScenarioFlags::TwoWay));
+ }
+ }
+ }
+ nScenTab++;
+ }
+ }
+ }
+ else if (rDocument.IsScenario(nTab))
+ {
+ // Determine if the preceding sheet is protected
+ SCTAB nActualTab = nTab;
+ do
+ {
+ nActualTab--;
+ }
+ while(rDocument.IsScenario(nActualTab));
+
+ if(rDocument.IsTabProtected(nActualTab))
+ {
+ ScRangeList aRanges;
+ rMark.FillRangeListWithMarks( &aRanges, false );
+ for (size_t i = 0, nRange = aRanges.size(); (i < nRange) && bIsEditable; i++)
+ {
+ const ScRange & rRange = aRanges[ i ];
+ if(rDocument.HasScenarioRange(nTab, rRange))
+ {
+ ScScenarioFlags nFlags;
+ rDocument.GetScenarioFlags(nTab,nFlags);
+ bIsEditable = !(nFlags & ScScenarioFlags::Protected);
+ }
+ }
+ }
+ }
+ if ( bIsEditable )
+ {
+ if ( HasSelectionMatrixFragment( rMark ) )
+ {
+ bIsEditable = false;
+ if ( pOnlyNotBecauseOfMatrix )
+ *pOnlyNotBecauseOfMatrix = true;
+ }
+ else if ( pOnlyNotBecauseOfMatrix )
+ *pOnlyNotBecauseOfMatrix = false;
+ }
+ else if ( pOnlyNotBecauseOfMatrix )
+ *pOnlyNotBecauseOfMatrix = false;
+ return bIsEditable;
+}
+
+void ScTable::LockTable()
+{
+ ++nLockCount;
+}
+
+void ScTable::UnlockTable()
+{
+ if (nLockCount)
+ --nLockCount;
+ else
+ {
+ OSL_FAIL("UnlockTable without LockTable");
+ }
+}
+
+void ScTable::MergeSelectionPattern( ScMergePatternState& rState, const ScMarkData& rMark, bool bDeep ) const
+{
+ std::vector<sc::ColRowSpan> aSpans = rMark.GetMarkedColSpans();
+
+ for (const sc::ColRowSpan & rSpan : aSpans)
+ {
+ SCCOL maxCol = ClampToAllocatedColumns(rSpan.mnEnd);
+ for (SCCOL i = rSpan.mnStart; i <= maxCol; ++i)
+ {
+ aCol[i].MergeSelectionPattern( rState, rMark, bDeep );
+ }
+ }
+}
+
+void ScTable::MergePatternArea( ScMergePatternState& rState, SCCOL nCol1, SCROW nRow1,
+ SCCOL nCol2, SCROW nRow2, bool bDeep ) const
+{
+ const SCCOL nEndCol = ClampToAllocatedColumns(nCol2);
+ for (SCCOL i=nCol1; i<=nEndCol; i++)
+ aCol[i].MergePatternArea( rState, nRow1, nRow2, bDeep );
+ if (nEndCol != nCol2)
+ aDefaultColData.MergePatternArea( rState, nRow1, nRow2, bDeep );
+}
+
+void ScTable::MergeBlockFrame( SvxBoxItem* pLineOuter, SvxBoxInfoItem* pLineInner, ScLineFlags& rFlags,
+ SCCOL nStartCol, SCROW nStartRow, SCCOL nEndCol, SCROW nEndRow ) const
+{
+ if (ValidColRow(nStartCol, nStartRow) && ValidColRow(nEndCol, nEndRow))
+ {
+ PutInOrder(nStartCol, nEndCol);
+ PutInOrder(nStartRow, nEndRow);
+ nEndCol = ClampToAllocatedColumns(nEndCol);
+ for (SCCOL i=nStartCol; i<=nEndCol; i++)
+ aCol[i].MergeBlockFrame( pLineOuter, pLineInner, rFlags,
+ nStartRow, nEndRow, (i==nStartCol), nEndCol-i );
+ }
+}
+
+void ScTable::ApplyBlockFrame(const SvxBoxItem& rLineOuter, const SvxBoxInfoItem* pLineInner,
+ SCCOL nStartCol, SCROW nStartRow, SCCOL nEndCol, SCROW nEndRow)
+{
+ if (ValidColRow(nStartCol, nStartRow) && ValidColRow(nEndCol, nEndRow))
+ {
+ PutInOrder(nStartCol, nEndCol);
+ PutInOrder(nStartRow, nEndRow);
+ CreateColumnIfNotExists(nEndCol);
+ for (SCCOL i=nStartCol; i<=nEndCol; i++)
+ aCol[i].ApplyBlockFrame(rLineOuter, pLineInner,
+ nStartRow, nEndRow, (i==nStartCol), nEndCol-i);
+ }
+}
+
+void ScTable::ApplyPattern( SCCOL nCol, SCROW nRow, const ScPatternAttr& rAttr )
+{
+ if (ValidColRow(nCol,nRow))
+ CreateColumnIfNotExists(nCol).ApplyPattern( nRow, rAttr );
+}
+
+void ScTable::ApplyPatternArea( SCCOL nStartCol, SCROW nStartRow, SCCOL nEndCol, SCROW nEndRow,
+ const ScPatternAttr& rAttr, ScEditDataArray* pDataArray,
+ bool* const pIsChanged )
+{
+ if (!ValidColRow(nStartCol, nStartRow) || !ValidColRow(nEndCol, nEndRow))
+ return;
+ PutInOrder(nStartCol, nEndCol);
+ PutInOrder(nStartRow, nEndRow);
+ SCCOL maxCol = nEndCol;
+ if( nEndCol == GetDoc().MaxCol())
+ {
+ // For the same unallocated columns until the end we can change just the default.
+ maxCol = std::max( nStartCol, aCol.size()) - 1;
+ if( maxCol >= 0 )
+ CreateColumnIfNotExists(maxCol); // Allocate needed different columns before changing the default.
+ aDefaultColData.ApplyPatternArea(nStartRow, nEndRow, rAttr, pDataArray, pIsChanged);
+ }
+ for (SCCOL i = nStartCol; i <= maxCol; i++)
+ CreateColumnIfNotExists(i).ApplyPatternArea(nStartRow, nEndRow, rAttr, pDataArray, pIsChanged);
+}
+
+namespace
+{
+ std::vector<ScAttrEntry> duplicateScAttrEntries(ScDocument& rDocument, const std::vector<ScAttrEntry>& rOrigData)
+ {
+ std::vector<ScAttrEntry> aData(rOrigData);
+ for (size_t nIdx = 0; nIdx < aData.size(); ++nIdx)
+ {
+ ScPatternAttr aNewPattern(*aData[nIdx].pPattern);
+ aData[nIdx].pPattern = &rDocument.GetPool()->Put(aNewPattern);
+ }
+ return aData;
+ }
+}
+
+void ScTable::SetAttrEntries( SCCOL nStartCol, SCCOL nEndCol, std::vector<ScAttrEntry> && vNewData)
+{
+ if (!ValidCol(nStartCol) || !ValidCol(nEndCol))
+ return;
+ if ( nEndCol == rDocument.MaxCol() )
+ {
+ if ( nStartCol < aCol.size() )
+ {
+ // If we would like set all columns to same attrs, then change only attrs for not existing columns
+ nEndCol = aCol.size() - 1;
+ for (SCCOL i = nStartCol; i <= nEndCol; i++)
+ aCol[i].SetAttrEntries(duplicateScAttrEntries(rDocument, vNewData));
+ aDefaultColData.SetAttrEntries(std::move(vNewData));
+ }
+ else
+ {
+ CreateColumnIfNotExists( nStartCol - 1 );
+ aDefaultColData.SetAttrEntries(std::move(vNewData));
+ }
+ }
+ else
+ {
+ CreateColumnIfNotExists( nEndCol );
+ for (SCCOL i = nStartCol; i < nEndCol; i++) // all but last need a copy
+ aCol[i].SetAttrEntries(duplicateScAttrEntries(rDocument, vNewData));
+ aCol[nEndCol].SetAttrEntries( std::move(vNewData));
+ }
+}
+
+
+
+void ScTable::ApplyPatternIfNumberformatIncompatible( const ScRange& rRange,
+ const ScPatternAttr& rPattern, SvNumFormatType nNewType )
+{
+ SCCOL nEndCol = rRange.aEnd.Col();
+ for ( SCCOL nCol = rRange.aStart.Col(); nCol <= nEndCol; nCol++ )
+ {
+ aCol[nCol].ApplyPatternIfNumberformatIncompatible( rRange, rPattern, nNewType );
+ }
+}
+
+void ScTable::AddCondFormatData( const ScRangeList& rRangeList, sal_uInt32 nIndex )
+{
+ size_t n = rRangeList.size();
+ for(size_t i = 0; i < n; ++i)
+ {
+ const ScRange & rRange = rRangeList[i];
+ SCCOL nColStart = rRange.aStart.Col();
+ SCCOL nColEnd = rRange.aEnd.Col();
+ SCROW nRowStart = rRange.aStart.Row();
+ SCROW nRowEnd = rRange.aEnd.Row();
+ for(SCCOL nCol = nColStart; nCol <= nColEnd; ++nCol)
+ {
+ CreateColumnIfNotExists(nCol).AddCondFormat(nRowStart, nRowEnd, nIndex);
+ }
+ }
+}
+
+void ScTable::RemoveCondFormatData( const ScRangeList& rRangeList, sal_uInt32 nIndex )
+{
+ size_t n = rRangeList.size();
+ for(size_t i = 0; i < n; ++i)
+ {
+ const ScRange & rRange = rRangeList[i];
+ SCCOL nColStart = rRange.aStart.Col();
+ SCCOL nColEnd = ClampToAllocatedColumns(rRange.aEnd.Col());
+ SCROW nRowStart = rRange.aStart.Row();
+ SCROW nRowEnd = rRange.aEnd.Row();
+ for(SCCOL nCol = nColStart; nCol <= nColEnd; ++nCol)
+ {
+ aCol[nCol].RemoveCondFormat(nRowStart, nRowEnd, nIndex);
+ }
+ }
+}
+
+void ScTable::SetPatternAreaCondFormat( SCCOL nCol, SCROW nStartRow, SCROW nEndRow,
+ const ScPatternAttr& rAttr, const ScCondFormatIndexes& rCondFormatIndexes )
+{
+ CreateColumnIfNotExists(nCol).SetPatternArea( nStartRow, nEndRow, rAttr);
+
+ for (const auto& rIndex : rCondFormatIndexes)
+ {
+ ScConditionalFormat* pCondFormat = mpCondFormatList->GetFormat(rIndex);
+ if (pCondFormat)
+ {
+ ScRangeList aRange = pCondFormat->GetRange();
+ aRange.Join( ScRange( nCol, nStartRow, nTab, nCol, nEndRow, nTab));
+ pCondFormat->SetRange(aRange);
+ }
+ }
+}
+
+void ScTable::ApplyStyle( SCCOL nCol, SCROW nRow, const ScStyleSheet* rStyle )
+{
+ if (ValidColRow(nCol,nRow))
+ // If column not exists then we need to create it
+ CreateColumnIfNotExists( nCol ).ApplyStyle( nRow, rStyle );
+}
+
+void ScTable::ApplyStyleArea( SCCOL nStartCol, SCROW nStartRow, SCCOL nEndCol, SCROW nEndRow, const ScStyleSheet& rStyle )
+{
+ if (!(ValidColRow(nStartCol, nStartRow) && ValidColRow(nEndCol, nEndRow)))
+ return;
+
+ PutInOrder(nStartCol, nEndCol);
+ PutInOrder(nStartRow, nEndRow);
+ if ( nEndCol == rDocument.MaxCol() )
+ {
+ if ( nStartCol < aCol.size() )
+ {
+ // If we would like set all columns to specific style, then change only default style for not existing columns
+ nEndCol = aCol.size() - 1;
+ for (SCCOL i = nStartCol; i <= nEndCol; i++)
+ aCol[i].ApplyStyleArea(nStartRow, nEndRow, rStyle);
+ aDefaultColData.ApplyStyleArea(nStartRow, nEndRow, rStyle );
+ }
+ else
+ {
+ CreateColumnIfNotExists( nStartCol - 1 );
+ aDefaultColData.ApplyStyleArea(nStartRow, nEndRow, rStyle );
+ }
+ }
+ else
+ {
+ CreateColumnIfNotExists( nEndCol );
+ for (SCCOL i = nStartCol; i <= nEndCol; i++)
+ aCol[i].ApplyStyleArea(nStartRow, nEndRow, rStyle);
+ }
+}
+
+void ScTable::ApplySelectionStyle(const ScStyleSheet& rStyle, const ScMarkData& rMark)
+{
+ for (SCCOL i=0; i < aCol.size(); i++)
+ aCol[i].ApplySelectionStyle( rStyle, rMark );
+}
+
+void ScTable::ApplySelectionLineStyle( const ScMarkData& rMark,
+ const ::editeng::SvxBorderLine* pLine, bool bColorOnly )
+{
+ if ( bColorOnly && !pLine )
+ return;
+
+ for (SCCOL i=0; i < aCol.size(); i++)
+ aCol[i].ApplySelectionLineStyle( rMark, pLine, bColorOnly );
+}
+
+const ScStyleSheet* ScTable::GetStyle( SCCOL nCol, SCROW nRow ) const
+{
+ if ( !ValidColRow( nCol, nRow ) )
+ return nullptr;
+ return ColumnData(nCol).GetStyle( nRow );
+}
+
+const ScStyleSheet* ScTable::GetSelectionStyle( const ScMarkData& rMark, bool& rFound ) const
+{
+ rFound = false;
+
+ bool bEqual = true;
+ bool bColFound;
+
+ const ScStyleSheet* pStyle = nullptr;
+ const ScStyleSheet* pNewStyle;
+
+ for (SCCOL i=0; i < aCol.size() && bEqual; i++)
+ if (rMark.HasMultiMarks(i))
+ {
+ pNewStyle = aCol[i].GetSelectionStyle( rMark, bColFound );
+ if (bColFound)
+ {
+ rFound = true;
+ if ( !pNewStyle || ( pStyle && pNewStyle != pStyle ) )
+ bEqual = false;
+ pStyle = pNewStyle;
+ }
+ }
+
+ return bEqual ? pStyle : nullptr;
+}
+
+const ScStyleSheet* ScTable::GetAreaStyle( bool& rFound, SCCOL nCol1, SCROW nRow1,
+ SCCOL nCol2, SCROW nRow2 ) const
+{
+ rFound = false;
+
+ bool bEqual = true;
+ bool bColFound;
+
+ const ScStyleSheet* pStyle = nullptr;
+ const ScStyleSheet* pNewStyle;
+ nCol2 = ClampToAllocatedColumns(nCol2);
+ for (SCCOL i=nCol1; i<=nCol2 && bEqual; i++)
+ {
+ pNewStyle = aCol[i].GetAreaStyle(bColFound, nRow1, nRow2);
+ if (bColFound)
+ {
+ rFound = true;
+ if ( !pNewStyle || ( pStyle && pNewStyle != pStyle ) )
+ bEqual = false;
+ pStyle = pNewStyle;
+ }
+ }
+
+ return bEqual ? pStyle : nullptr;
+}
+
+bool ScTable::IsStyleSheetUsed( const ScStyleSheet& rStyle ) const
+{
+ bool bIsUsed = false;
+
+ for ( SCCOL i=0; i < aCol.size(); i++ )
+ {
+ if ( aCol[i].IsStyleSheetUsed( rStyle ) )
+ {
+ bIsUsed = true;
+ }
+ }
+
+ return bIsUsed;
+}
+
+void ScTable::StyleSheetChanged( const SfxStyleSheetBase* pStyleSheet, bool bRemoved,
+ OutputDevice* pDev,
+ double nPPTX, double nPPTY,
+ const Fraction& rZoomX, const Fraction& rZoomY )
+{
+ ScFlatBoolRowSegments aUsedRows(rDocument.MaxRow());
+ for (SCCOL i = 0; i < aCol.size(); ++i)
+ aCol[i].FindStyleSheet(pStyleSheet, aUsedRows, bRemoved);
+
+ sc::RowHeightContext aCxt(rDocument.MaxRow(), nPPTX, nPPTY, rZoomX, rZoomY, pDev);
+ SCROW nRow = 0;
+ while (nRow <= rDocument.MaxRow())
+ {
+ ScFlatBoolRowSegments::RangeData aData;
+ if (!aUsedRows.getRangeData(nRow, aData))
+ // search failed!
+ return;
+
+ SCROW nEndRow = aData.mnRow2;
+ if (aData.mbValue)
+ SetOptimalHeight(aCxt, nRow, nEndRow, true);
+
+ nRow = nEndRow + 1;
+ }
+}
+
+bool ScTable::ApplyFlags( SCCOL nStartCol, SCROW nStartRow, SCCOL nEndCol, SCROW nEndRow,
+ ScMF nFlags )
+{
+ bool bChanged = false;
+ if (ValidColRow(nStartCol, nStartRow) && ValidColRow(nEndCol, nEndRow))
+ for (SCCOL i = nStartCol; i <= nEndCol; i++)
+ bChanged |= CreateColumnIfNotExists(i).ApplyFlags(nStartRow, nEndRow, nFlags);
+ return bChanged;
+}
+
+bool ScTable::RemoveFlags( SCCOL nStartCol, SCROW nStartRow, SCCOL nEndCol, SCROW nEndRow,
+ ScMF nFlags )
+{
+ if (!ValidColRow(nStartCol, nStartRow) || !ValidColRow(nEndCol, nEndRow))
+ return false;
+ bool bChanged = false;
+ nEndCol = ClampToAllocatedColumns(nEndCol);
+ for (SCCOL i = nStartCol; i <= nEndCol; i++)
+ bChanged |= aCol[i].RemoveFlags(nStartRow, nEndRow, nFlags);
+ return bChanged;
+}
+
+void ScTable::SetPattern( const ScAddress& rPos, const ScPatternAttr& rAttr )
+{
+ if (ValidColRow(rPos.Col(),rPos.Row()))
+ CreateColumnIfNotExists(rPos.Col()).SetPattern(rPos.Row(), rAttr);
+}
+
+const ScPatternAttr* ScTable::SetPattern( SCCOL nCol, SCROW nRow, std::unique_ptr<ScPatternAttr> pAttr )
+{
+ if (ValidColRow(nCol,nRow))
+ return CreateColumnIfNotExists(nCol).SetPattern(nRow, std::move(pAttr));
+ return nullptr;
+}
+
+void ScTable::SetPattern( SCCOL nCol, SCROW nRow, const ScPatternAttr& rAttr )
+{
+ if (ValidColRow(nCol,nRow))
+ CreateColumnIfNotExists(nCol).SetPattern(nRow, rAttr);
+}
+
+void ScTable::ApplyAttr( SCCOL nCol, SCROW nRow, const SfxPoolItem& rAttr )
+{
+ if (ValidColRow(nCol,nRow))
+ CreateColumnIfNotExists(nCol).ApplyAttr( nRow, rAttr );
+}
+
+void ScTable::ApplySelectionCache( SfxItemPoolCache* pCache, const ScMarkData& rMark,
+ ScEditDataArray* pDataArray, bool* const pIsChanged )
+{
+ if(!rMark.GetTableSelect(nTab))
+ return;
+ SCCOL lastChangeCol;
+ if( rMark.GetArea().aEnd.Col() == GetDoc().MaxCol())
+ {
+ // For the same unallocated columns until the end we can change just the default.
+ lastChangeCol = rMark.GetStartOfEqualColumns( GetDoc().MaxCol(), aCol.size()) - 1;
+ if( lastChangeCol >= 0 )
+ CreateColumnIfNotExists(lastChangeCol); // Allocate needed different columns before changing the default.
+ aDefaultColData.ApplySelectionCache( pCache, rMark, pDataArray, pIsChanged, GetDoc().MaxCol());
+ }
+ else // need to allocate all columns affected
+ {
+ lastChangeCol = rMark.GetArea().aEnd.Col();
+ CreateColumnIfNotExists(lastChangeCol);
+ }
+
+ for (SCCOL i=0; i <= lastChangeCol; i++)
+ aCol[i].ApplySelectionCache( pCache, rMark, pDataArray, pIsChanged );
+}
+
+void ScTable::ChangeSelectionIndent( bool bIncrement, const ScMarkData& rMark )
+{
+ if(!rMark.GetTableSelect(nTab))
+ return;
+ SCCOL lastChangeCol;
+ if( rMark.GetArea().aEnd.Col() == GetDoc().MaxCol())
+ {
+ // For the same unallocated columns until the end we can change just the default.
+ lastChangeCol = rMark.GetStartOfEqualColumns( GetDoc().MaxCol(), aCol.size()) - 1;
+ if( lastChangeCol >= 0 )
+ CreateColumnIfNotExists(lastChangeCol); // Allocate needed different columns before changing the default.
+ aDefaultColData.ChangeSelectionIndent( bIncrement, rMark, GetDoc().MaxCol());
+ }
+ else
+ {
+ lastChangeCol = rMark.GetArea().aEnd.Col();
+ CreateColumnIfNotExists(lastChangeCol);
+ }
+
+ for (SCCOL i=0; i <= lastChangeCol; i++)
+ aCol[i].ChangeSelectionIndent( bIncrement, rMark );
+}
+
+void ScTable::ClearSelectionItems( const sal_uInt16* pWhich, const ScMarkData& rMark )
+{
+ if(!rMark.GetTableSelect(nTab))
+ return;
+ SCCOL lastChangeCol;
+ if( rMark.GetArea().aEnd.Col() == GetDoc().MaxCol())
+ {
+ // For the same unallocated columns until the end we can change just the default.
+ lastChangeCol = rMark.GetStartOfEqualColumns( GetDoc().MaxCol(), aCol.size()) - 1;
+ if( lastChangeCol >= 0 )
+ CreateColumnIfNotExists(lastChangeCol); // Allocate needed different columns before changing the default.
+ aDefaultColData.ClearSelectionItems( pWhich, rMark, GetDoc().MaxCol());
+ }
+ else
+ {
+ lastChangeCol = rMark.GetArea().aEnd.Col();
+ CreateColumnIfNotExists(lastChangeCol);
+ }
+
+ for (SCCOL i=0; i <= lastChangeCol; i++)
+ aCol[i].ClearSelectionItems( pWhich, rMark );
+}
+
+// Column widths / Row heights
+
+void ScTable::SetColWidth( SCCOL nCol, sal_uInt16 nNewWidth )
+{
+ if (ValidCol(nCol) && mpColWidth)
+ {
+ if (!nNewWidth)
+ {
+ nNewWidth = STD_COL_WIDTH;
+ }
+
+ if ( nNewWidth != mpColWidth->GetValue(nCol) )
+ {
+ mpColWidth->SetValue(nCol, nNewWidth);
+ InvalidatePageBreaks();
+ }
+ }
+ else
+ {
+ OSL_FAIL("Invalid column number or no widths");
+ }
+}
+
+void ScTable::SetColWidthOnly( SCCOL nCol, sal_uInt16 nNewWidth )
+{
+ if (!ValidCol(nCol) || !mpColWidth)
+ return;
+
+ if (!nNewWidth)
+ nNewWidth = STD_COL_WIDTH;
+
+ if (nNewWidth != mpColWidth->GetValue(nCol))
+ mpColWidth->SetValue(nCol, nNewWidth);
+}
+
+void ScTable::SetRowHeight( SCROW nRow, sal_uInt16 nNewHeight )
+{
+ if (ValidRow(nRow) && mpRowHeights)
+ {
+ if (!nNewHeight)
+ {
+ OSL_FAIL("SetRowHeight: Row height zero");
+ nNewHeight = ScGlobal::nStdRowHeight;
+ }
+
+ sal_uInt16 nOldHeight = mpRowHeights->getValue(nRow);
+ if ( nNewHeight != nOldHeight )
+ {
+ mpRowHeights->setValue(nRow, nRow, nNewHeight);
+ InvalidatePageBreaks();
+ }
+ }
+ else
+ {
+ OSL_FAIL("Invalid row number or no heights");
+ }
+}
+
+namespace {
+
+/**
+ * Check if the new pixel size is different from the old size between
+ * specified ranges.
+ */
+bool lcl_pixelSizeChanged(
+ ScFlatUInt16RowSegments& rRowHeights, SCROW nStartRow, SCROW nEndRow,
+ sal_uInt16 nNewHeight, double nPPTY, bool bApi)
+{
+ tools::Long nNewPix = static_cast<tools::Long>(nNewHeight * nPPTY);
+
+ ScFlatUInt16RowSegments::ForwardIterator aFwdIter(rRowHeights);
+ for (SCROW nRow = nStartRow; nRow <= nEndRow; ++nRow)
+ {
+ sal_uInt16 nHeight;
+ if (!aFwdIter.getValue(nRow, nHeight))
+ break;
+
+ if (nHeight != nNewHeight)
+ {
+ tools::Long nOldPix = static_cast<tools::Long>(nHeight * nPPTY);
+
+ // Heuristic: Don't bother when handling interactive input, if changing just one row and
+ // the height will shrink.
+ bool bChanged = (nNewPix != nOldPix) && (bApi || nEndRow - nStartRow > 0 || nNewPix > nOldPix);
+ if (bChanged)
+ return true;
+ }
+
+ // Skip ahead to the last position of the current range.
+ nRow = aFwdIter.getLastPos();
+ }
+ return false;
+}
+
+}
+
+bool ScTable::SetRowHeightRange( SCROW nStartRow, SCROW nEndRow, sal_uInt16 nNewHeight,
+ double nPPTY, bool bApi )
+{
+ bool bChanged = false;
+ if (ValidRow(nStartRow) && ValidRow(nEndRow) && mpRowHeights)
+ {
+ if (!nNewHeight)
+ {
+ OSL_FAIL("SetRowHeight: Row height zero");
+ nNewHeight = ScGlobal::nStdRowHeight;
+ }
+
+ bool bSingle = false; // true = process every row for its own
+ ScDrawLayer* pDrawLayer = rDocument.GetDrawLayer();
+ if (pDrawLayer)
+ if (pDrawLayer->HasObjectsInRows( nTab, nStartRow, nEndRow ))
+ bSingle = true;
+
+ if (bSingle)
+ {
+ ScFlatUInt16RowSegments::RangeData aData;
+ if (mpRowHeights->getRangeData(nStartRow, aData) &&
+ nNewHeight == aData.mnValue && nEndRow <= aData.mnRow2)
+ {
+ bSingle = false; // no difference in this range
+ }
+ }
+
+ // No idea why 20 is used here
+ if (!bSingle || nEndRow - nStartRow < 20)
+ {
+ bChanged = lcl_pixelSizeChanged(*mpRowHeights, nStartRow, nEndRow, nNewHeight, nPPTY, bApi);
+ if (bChanged)
+ mpRowHeights->setValue(nStartRow, nEndRow, nNewHeight);
+ }
+ else
+ {
+ SCROW nMid = (nStartRow + nEndRow) / 2;
+ // No idea why nPPTY is ignored in these recursive calls and instead 1.0 is used
+ if (SetRowHeightRange(nStartRow, nMid, nNewHeight, 1.0, bApi))
+ bChanged = true;
+ if (SetRowHeightRange(nMid + 1, nEndRow, nNewHeight, 1.0, bApi))
+ bChanged = true;
+ }
+
+ if (bChanged)
+ InvalidatePageBreaks();
+ }
+ else
+ {
+ OSL_FAIL("Invalid row number or no heights");
+ }
+
+ return bChanged;
+}
+
+void ScTable::SetRowHeightOnly( SCROW nStartRow, SCROW nEndRow, sal_uInt16 nNewHeight )
+{
+ if (!ValidRow(nStartRow) || !ValidRow(nEndRow) || !mpRowHeights)
+ return;
+
+ if (!nNewHeight)
+ nNewHeight = ScGlobal::nStdRowHeight;
+
+ mpRowHeights->setValue(nStartRow, nEndRow, nNewHeight);
+}
+
+void ScTable::SetManualHeight( SCROW nStartRow, SCROW nEndRow, bool bManual )
+{
+ if (ValidRow(nStartRow) && ValidRow(nEndRow) && pRowFlags)
+ {
+ if (bManual)
+ pRowFlags->OrValue( nStartRow, nEndRow, CRFlags::ManualSize);
+ else
+ pRowFlags->AndValue( nStartRow, nEndRow, ~CRFlags::ManualSize);
+ }
+ else
+ {
+ OSL_FAIL("Invalid row number or no column flags");
+ }
+}
+
+sal_uInt16 ScTable::GetColWidth( SCCOL nCol, bool bHiddenAsZero ) const
+{
+ OSL_ENSURE(ValidCol(nCol),"wrong column number");
+
+ if (ValidCol(nCol) && mpColFlags && mpColWidth)
+ {
+ if (bHiddenAsZero && ColHidden(nCol))
+ return 0;
+ else
+ return mpColWidth->GetValue(nCol);
+ }
+ else
+ return sal_uInt16(STD_COL_WIDTH);
+}
+
+tools::Long ScTable::GetColWidth( SCCOL nStartCol, SCCOL nEndCol ) const
+{
+ if (!ValidCol(nStartCol) || !ValidCol(nEndCol) || nStartCol > nEndCol)
+ return 0;
+
+ tools::Long nW = 0;
+ bool bHidden = false;
+ SCCOL nLastHiddenCol = -1;
+ auto colWidthIt = mpColWidth->begin() + nStartCol;
+ for (SCCOL nCol = nStartCol; nCol <= nEndCol; (++nCol <= nEndCol) ? ++colWidthIt : (void)false)
+ {
+ if (nCol > nLastHiddenCol)
+ bHidden = ColHidden(nCol, nullptr, &nLastHiddenCol);
+
+ if (bHidden)
+ continue;
+
+ nW += *colWidthIt;
+ }
+ return nW;
+}
+
+sal_uInt16 ScTable::GetOriginalWidth( SCCOL nCol ) const // always the set value
+{
+ OSL_ENSURE(ValidCol(nCol),"wrong column number");
+
+ if (ValidCol(nCol) && mpColWidth)
+ return mpColWidth->GetValue(nCol);
+ else
+ return sal_uInt16(STD_COL_WIDTH);
+}
+
+sal_uInt16 ScTable::GetCommonWidth( SCCOL nEndCol ) const
+{
+ // get the width that is used in the largest continuous column range (up to nEndCol)
+
+ if ( !ValidCol(nEndCol) )
+ {
+ OSL_FAIL("wrong column");
+ nEndCol = rDocument.MaxCol();
+ }
+
+ sal_uInt16 nMaxWidth = 0;
+ sal_uInt16 nMaxCount = 0;
+ SCCOL nRangeStart = 0;
+ while ( nRangeStart <= nEndCol )
+ {
+ // skip hidden columns
+ while ( nRangeStart <= nEndCol && ColHidden(nRangeStart) )
+ ++nRangeStart;
+ if ( nRangeStart <= nEndCol )
+ {
+ sal_uInt16 nThisCount = 0;
+ auto colWidthIt = mpColWidth->begin() + nRangeStart;
+ sal_uInt16 nThisWidth = *colWidthIt;
+ SCCOL nRangeEnd = nRangeStart;
+ while ( nRangeEnd <= nEndCol && *colWidthIt == nThisWidth )
+ {
+ ++nThisCount;
+ ++nRangeEnd;
+ ++colWidthIt;
+
+ // skip hidden columns
+ while ( nRangeEnd <= nEndCol && ColHidden(nRangeEnd) )
+ {
+ ++nRangeEnd;
+ ++colWidthIt;
+ }
+ }
+
+ if ( nThisCount > nMaxCount )
+ {
+ nMaxCount = nThisCount;
+ nMaxWidth = nThisWidth;
+ }
+
+ nRangeStart = nRangeEnd; // next range
+ }
+ }
+
+ return nMaxWidth;
+}
+
+sal_uInt16 ScTable::GetRowHeight( SCROW nRow, SCROW* pStartRow, SCROW* pEndRow, bool bHiddenAsZero ) const
+{
+ SAL_WARN_IF(!ValidRow(nRow), "sc", "Invalid row number " << nRow);
+
+ if (ValidRow(nRow) && mpRowHeights)
+ {
+ if (bHiddenAsZero && RowHidden( nRow, pStartRow, pEndRow))
+ return 0;
+ else
+ {
+ ScFlatUInt16RowSegments::RangeData aData;
+ if (!mpRowHeights->getRangeData(nRow, aData))
+ {
+ if (pStartRow)
+ *pStartRow = nRow;
+ if (pEndRow)
+ *pEndRow = nRow;
+ // TODO: What should we return in case the search fails?
+ return 0;
+ }
+
+ // If bHiddenAsZero, pStartRow and pEndRow were initialized to
+ // boundaries of a non-hidden segment. Assume that the previous and
+ // next segment are hidden then and limit the current height
+ // segment.
+ if (pStartRow)
+ *pStartRow = (bHiddenAsZero ? std::max( *pStartRow, aData.mnRow1) : aData.mnRow1);
+ if (pEndRow)
+ *pEndRow = (bHiddenAsZero ? std::min( *pEndRow, aData.mnRow2) : aData.mnRow2);
+ return aData.mnValue;
+ }
+ }
+ else
+ {
+ if (pStartRow)
+ *pStartRow = nRow;
+ if (pEndRow)
+ *pEndRow = nRow;
+ return ScGlobal::nStdRowHeight;
+ }
+}
+
+tools::Long ScTable::GetRowHeight( SCROW nStartRow, SCROW nEndRow, bool bHiddenAsZero ) const
+{
+ OSL_ENSURE(ValidRow(nStartRow) && ValidRow(nEndRow),"wrong row number");
+
+ if (ValidRow(nStartRow) && ValidRow(nEndRow) && mpRowHeights)
+ {
+ tools::Long nHeight = 0;
+ SCROW nRow = nStartRow;
+ while (nRow <= nEndRow)
+ {
+ SCROW nLastRow = -1;
+ if (!( ( RowHidden(nRow, nullptr, &nLastRow) ) && bHiddenAsZero ) )
+ {
+ if (nLastRow > nEndRow)
+ nLastRow = nEndRow;
+ nHeight += mpRowHeights->getSumValue(nRow, nLastRow);
+ }
+ nRow = nLastRow + 1;
+ }
+ return nHeight;
+ }
+ else
+ return (nEndRow - nStartRow + 1) * static_cast<tools::Long>(ScGlobal::nStdRowHeight);
+}
+
+tools::Long ScTable::GetScaledRowHeight( SCROW nStartRow, SCROW nEndRow, double fScale ) const
+{
+ OSL_ENSURE(ValidRow(nStartRow) && ValidRow(nEndRow),"wrong row number");
+
+ if (ValidRow(nStartRow) && ValidRow(nEndRow) && mpRowHeights)
+ {
+ tools::Long nHeight = 0;
+ SCROW nRow = nStartRow;
+ while (nRow <= nEndRow)
+ {
+ SCROW nLastRow = -1;
+ if (!RowHidden(nRow, nullptr, &nLastRow))
+ {
+ if (nLastRow > nEndRow)
+ nLastRow = nEndRow;
+
+ // #i117315# can't use getSumValue, because individual values must be rounded
+ ScFlatUInt16RowSegments::ForwardIterator aSegmentIter(*mpRowHeights);
+ while (nRow <= nLastRow)
+ {
+ sal_uInt16 nRowVal;
+ if (!aSegmentIter.getValue(nRow, nRowVal))
+ return nHeight; // shouldn't happen
+
+ SCROW nSegmentEnd = std::min( nLastRow, aSegmentIter.getLastPos() );
+
+ // round-down a single height value, multiply resulting (pixel) values
+ tools::Long nOneHeight = static_cast<tools::Long>( nRowVal * fScale );
+ nHeight += nOneHeight * ( nSegmentEnd + 1 - nRow );
+
+ nRow = nSegmentEnd + 1;
+ }
+ }
+ nRow = nLastRow + 1;
+ }
+ return nHeight;
+ }
+ else
+ return static_cast<tools::Long>((nEndRow - nStartRow + 1) * ScGlobal::nStdRowHeight * fScale);
+}
+
+sal_uInt16 ScTable::GetOriginalHeight( SCROW nRow ) const // non-0 even if hidden
+{
+ OSL_ENSURE(ValidRow(nRow),"wrong row number");
+
+ if (ValidRow(nRow) && mpRowHeights)
+ return mpRowHeights->getValue(nRow);
+ else
+ return ScGlobal::nStdRowHeight;
+}
+
+// Column/Row -Flags
+
+SCROW ScTable::GetHiddenRowCount( SCROW nRow ) const
+{
+ if (!ValidRow(nRow))
+ return 0;
+
+ SCROW nLastRow = -1;
+ if (!RowHidden(nRow, nullptr, &nLastRow) || !ValidRow(nLastRow))
+ return 0;
+
+ return nLastRow - nRow + 1;
+}
+
+//TODO: combine ShowRows / DBShowRows
+
+void ScTable::ShowCol(SCCOL nCol, bool bShow)
+{
+ if (ValidCol(nCol))
+ {
+ bool bWasVis = !ColHidden(nCol);
+ if (bWasVis != bShow)
+ {
+ SetColHidden(nCol, nCol, !bShow);
+
+ ScChartListenerCollection* pCharts = rDocument.GetChartListenerCollection();
+ if ( pCharts )
+ pCharts->SetRangeDirty(ScRange( nCol, 0, nTab, nCol, rDocument.MaxRow(), nTab ));
+ }
+ }
+ else
+ {
+ OSL_FAIL("Invalid column number or no flags");
+ }
+}
+
+void ScTable::ShowRow(SCROW nRow, bool bShow)
+{
+ if (ValidRow(nRow) && pRowFlags)
+ {
+ bool bWasVis = !RowHidden(nRow);
+ if (bWasVis != bShow)
+ {
+ SetRowHidden(nRow, nRow, !bShow);
+ if (bShow)
+ SetRowFiltered(nRow, nRow, false);
+ ScChartListenerCollection* pCharts = rDocument.GetChartListenerCollection();
+ if ( pCharts )
+ pCharts->SetRangeDirty(ScRange( 0, nRow, nTab, rDocument.MaxCol(), nRow, nTab ));
+
+ InvalidatePageBreaks();
+ }
+ }
+ else
+ {
+ OSL_FAIL("Invalid row number or no flags");
+ }
+}
+
+void ScTable::DBShowRow(SCROW nRow, bool bShow)
+{
+ if (ValidRow(nRow) && pRowFlags)
+ {
+ // Always set filter flag; unchanged when Hidden
+ bool bChanged = SetRowHidden(nRow, nRow, !bShow);
+ SetRowFiltered(nRow, nRow, !bShow);
+
+ if (bChanged)
+ {
+ ScChartListenerCollection* pCharts = rDocument.GetChartListenerCollection();
+ if ( pCharts )
+ pCharts->SetRangeDirty(ScRange( 0, nRow, nTab, rDocument.MaxCol(), nRow, nTab ));
+
+ if (pOutlineTable)
+ UpdateOutlineRow( nRow, nRow, bShow );
+
+ InvalidatePageBreaks();
+ }
+ }
+ else
+ {
+ OSL_FAIL("Invalid row number or no flags");
+ }
+}
+
+void ScTable::DBShowRows(SCROW nRow1, SCROW nRow2, bool bShow)
+{
+ SCROW nStartRow = nRow1;
+ while (nStartRow <= nRow2)
+ {
+ SCROW nEndRow = -1;
+ bool bWasVis = !RowHiddenLeaf(nStartRow, nullptr, &nEndRow);
+ if (nEndRow > nRow2)
+ nEndRow = nRow2;
+
+ bool bChanged = ( bWasVis != bShow );
+
+ SetRowHidden(nStartRow, nEndRow, !bShow);
+ SetRowFiltered(nStartRow, nEndRow, !bShow);
+
+ if ( bChanged )
+ {
+ ScChartListenerCollection* pCharts = rDocument.GetChartListenerCollection();
+ if ( pCharts )
+ pCharts->SetRangeDirty(ScRange( 0, nStartRow, nTab, rDocument.MaxCol(), nEndRow, nTab ));
+ }
+
+ nStartRow = nEndRow + 1;
+ }
+
+ // #i12341# For Show/Hide rows, the outlines are updated separately from the outside.
+ // For filtering, the changes aren't visible to the caller, so UpdateOutlineRow has
+ // to be done here.
+ if (pOutlineTable)
+ UpdateOutlineRow( nRow1, nRow2, bShow );
+}
+
+void ScTable::ShowRows(SCROW nRow1, SCROW nRow2, bool bShow)
+{
+ SCROW nStartRow = nRow1;
+
+ // #i116164# if there are no drawing objects within the row range, a single HeightChanged call is enough
+ ScDrawLayer* pDrawLayer = rDocument.GetDrawLayer();
+ bool bHasObjects = pDrawLayer && pDrawLayer->HasObjectsInRows( nTab, nRow1, nRow2 );
+
+ while (nStartRow <= nRow2)
+ {
+ SCROW nEndRow = -1;
+ bool bWasVis = !RowHiddenLeaf(nStartRow, nullptr, &nEndRow);
+ if (nEndRow > nRow2)
+ nEndRow = nRow2;
+
+ bool bChanged = ( bWasVis != bShow );
+
+ SetRowHidden(nStartRow, nEndRow, !bShow);
+ if (bShow)
+ SetRowFiltered(nStartRow, nEndRow, false);
+
+ if ( bChanged )
+ {
+ ScChartListenerCollection* pCharts = rDocument.GetChartListenerCollection();
+ if ( pCharts )
+ pCharts->SetRangeDirty(ScRange( 0, nStartRow, nTab, rDocument.MaxCol(), nEndRow, nTab ));
+
+ InvalidatePageBreaks();
+ }
+
+ nStartRow = nEndRow + 1;
+ }
+
+ if ( !bHasObjects )
+ {
+ // #i116164# set the flags for the whole range at once
+ SetRowHidden(nRow1, nRow2, !bShow);
+ if (bShow)
+ SetRowFiltered(nRow1, nRow2, false);
+ }
+}
+
+bool ScTable::IsDataFiltered(SCCOL nColStart, SCROW nRowStart, SCCOL nColEnd, SCROW nRowEnd) const
+{
+ assert(nColStart <= nColEnd && nRowStart <= nRowEnd
+ && "range must be normalized to obtain a valid result");
+ for (SCROW i = nRowStart; i <= nRowEnd; ++i)
+ {
+ if (RowHidden(i))
+ return true;
+ }
+ for (SCCOL i = nColStart; i <= nColEnd; ++i)
+ {
+ if (ColHidden(i))
+ return true;
+ }
+ return false;
+}
+
+bool ScTable::IsDataFiltered(const ScRange& rRange) const
+{
+ ScRange aNormalized(rRange.aStart, rRange.aEnd);
+ return IsDataFiltered(aNormalized.aStart.Col(), aNormalized.aStart.Row(),
+ aNormalized.aEnd.Col(), aNormalized.aEnd.Row());
+}
+
+void ScTable::SetRowFlags( SCROW nRow, CRFlags nNewFlags )
+{
+ if (ValidRow(nRow) && pRowFlags)
+ pRowFlags->SetValue( nRow, nNewFlags);
+ else
+ {
+ OSL_FAIL("Invalid row number or no flags");
+ }
+}
+
+void ScTable::SetRowFlags( SCROW nStartRow, SCROW nEndRow, CRFlags nNewFlags )
+{
+ if (ValidRow(nStartRow) && ValidRow(nEndRow) && pRowFlags)
+ pRowFlags->SetValue( nStartRow, nEndRow, nNewFlags);
+ else
+ {
+ OSL_FAIL("Invalid row number(s) or no flags");
+ }
+}
+
+CRFlags ScTable::GetColFlags( SCCOL nCol ) const
+{
+ if (ValidCol(nCol) && mpColFlags)
+ return mpColFlags->GetValue(nCol);
+ else
+ return CRFlags::NONE;
+}
+
+CRFlags ScTable::GetRowFlags( SCROW nRow ) const
+{
+ if (ValidRow(nRow) && pRowFlags)
+ return pRowFlags->GetValue(nRow);
+ else
+ return CRFlags::NONE;
+}
+
+SCROW ScTable::GetLastFlaggedRow() const
+{
+ SCROW nLastFound = 0;
+ if (pRowFlags)
+ {
+ SCROW nRow = pRowFlags->GetLastAnyBitAccess( CRFlags::All );
+ if (ValidRow(nRow))
+ nLastFound = nRow;
+ }
+
+ if (!maRowManualBreaks.empty())
+ nLastFound = ::std::max(nLastFound, *maRowManualBreaks.rbegin());
+
+ if (mpHiddenRows)
+ {
+ SCROW nRow = mpHiddenRows->findLastTrue();
+ if (ValidRow(nRow))
+ nLastFound = ::std::max(nLastFound, nRow);
+ }
+
+ if (mpFilteredRows)
+ {
+ SCROW nRow = mpFilteredRows->findLastTrue();
+ if (ValidRow(nRow))
+ nLastFound = ::std::max(nLastFound, nRow);
+ }
+
+ return nLastFound;
+}
+
+SCCOL ScTable::GetLastChangedColFlagsWidth() const
+{
+ if ( !mpColFlags )
+ return 0;
+
+ SCCOL nLastFound = 0;
+ auto colWidthIt = mpColWidth->begin() + 1;
+ for (SCCOL nCol = 1; nCol <= GetDoc().MaxCol(); (++nCol <= GetDoc().MaxCol()) ? ++colWidthIt : (void)false)
+ if ((mpColFlags->GetValue(nCol) & CRFlags::All) || (*colWidthIt != STD_COL_WIDTH))
+ nLastFound = nCol;
+
+ return nLastFound;
+}
+
+SCROW ScTable::GetLastChangedRowFlagsWidth() const
+{
+ if ( !pRowFlags )
+ return 0;
+
+ SCROW nLastFlags = GetLastFlaggedRow();
+
+ // Find the last row position where the height is NOT the standard row
+ // height.
+ // KOHEI: Test this to make sure it does what it's supposed to.
+ SCROW nLastHeight = mpRowHeights->findLastTrue(ScGlobal::nStdRowHeight);
+ if (!ValidRow(nLastHeight))
+ nLastHeight = 0;
+
+ return std::max( nLastFlags, nLastHeight);
+}
+
+bool ScTable::UpdateOutlineCol( SCCOL nStartCol, SCCOL nEndCol, bool bShow )
+{
+ if (pOutlineTable && mpColFlags)
+ {
+ return pOutlineTable->GetColArray().ManualAction( nStartCol, nEndCol, bShow, *this, true );
+ }
+ else
+ return false;
+}
+
+bool ScTable::UpdateOutlineRow( SCROW nStartRow, SCROW nEndRow, bool bShow )
+{
+ if (pOutlineTable && pRowFlags)
+ return pOutlineTable->GetRowArray().ManualAction( nStartRow, nEndRow, bShow, *this, false );
+ else
+ return false;
+}
+
+void ScTable::ExtendHidden( SCCOL& rX1, SCROW& rY1, SCCOL& rX2, SCROW& rY2 )
+{
+ // Column-wise expansion
+
+ while (rX1 > 0 && ColHidden(rX1-1))
+ --rX1;
+
+ while (rX2 < rDocument.MaxCol() && ColHidden(rX2+1))
+ ++rX2;
+
+ // Row-wise expansion
+
+ if (rY1 > 0)
+ {
+ ScFlatBoolRowSegments::RangeData aData;
+ if (mpHiddenRows->getRangeData(rY1-1, aData) && aData.mbValue)
+ {
+ SCROW nStartRow = aData.mnRow1;
+ if (ValidRow(nStartRow))
+ rY1 = nStartRow;
+ }
+ }
+ if (rY2 < rDocument.MaxRow())
+ {
+ SCROW nEndRow = -1;
+ if (RowHidden(rY2+1, nullptr, &nEndRow) && ValidRow(nEndRow))
+ rY2 = nEndRow;
+ }
+}
+
+void ScTable::StripHidden( SCCOL& rX1, SCROW& rY1, SCCOL& rX2, SCROW& rY2 )
+{
+ while ( rX2>rX1 && ColHidden(rX2) )
+ --rX2;
+ while ( rX2>rX1 && ColHidden(rX1) )
+ ++rX1;
+
+ if (rY1 < rY2)
+ {
+ ScFlatBoolRowSegments::RangeData aData;
+ if (mpHiddenRows->getRangeData(rY2, aData) && aData.mbValue)
+ {
+ SCROW nStartRow = aData.mnRow1;
+ if (ValidRow(nStartRow) && nStartRow >= rY1)
+ rY2 = nStartRow;
+ }
+ }
+
+ if (rY1 < rY2)
+ {
+ SCROW nEndRow = -1;
+ if (RowHidden(rY1, nullptr, &nEndRow) && ValidRow(nEndRow) && nEndRow <= rY2)
+ rY1 = nEndRow;
+ }
+}
+
+// Auto-Outline
+
+template< typename T >
+static short DiffSign( T a, T b )
+{
+ return (a<b) ? -1 :
+ (a>b) ? 1 : 0;
+}
+
+namespace {
+
+class OutlineArrayFinder
+{
+ ScRange maRef;
+ SCCOL mnCol;
+ SCTAB mnTab;
+ ScOutlineArray* mpArray;
+ bool mbSizeChanged;
+
+public:
+ OutlineArrayFinder(const ScRange& rRef, SCCOL nCol, SCTAB nTab, ScOutlineArray* pArray, bool bSizeChanged) :
+ maRef(rRef), mnCol(nCol), mnTab(nTab), mpArray(pArray),
+ mbSizeChanged(bSizeChanged) {}
+
+ bool operator() (size_t nRow, const ScFormulaCell* pCell)
+ {
+ SCROW nRow2 = static_cast<SCROW>(nRow);
+
+ if (!pCell->HasRefListExpressibleAsOneReference(maRef))
+ return false;
+
+ if (maRef.aStart.Row() != nRow2 || maRef.aEnd.Row() != nRow2 ||
+ maRef.aStart.Tab() != mnTab || maRef.aEnd.Tab() != mnTab)
+ return false;
+
+ if (DiffSign(maRef.aStart.Col(), mnCol) != DiffSign(maRef.aEnd.Col(), mnCol))
+ return false;
+
+ return mpArray->Insert(maRef.aStart.Col(), maRef.aEnd.Col(), mbSizeChanged);
+ }
+};
+
+}
+
+void ScTable::DoAutoOutline( SCCOL nStartCol, SCROW nStartRow, SCCOL nEndCol, SCROW nEndRow )
+{
+ typedef mdds::flat_segment_tree<SCROW, bool> UsedRowsType;
+
+ bool bSizeChanged = false;
+
+ SCCOL nCol;
+ SCROW nRow;
+ bool bFound;
+ ScRange aRef;
+
+ nEndCol = ClampToAllocatedColumns(nEndCol);
+
+ StartOutlineTable();
+
+ // Rows
+
+ UsedRowsType aUsed(0, rDocument.MaxRow()+1, false);
+ for (nCol=nStartCol; nCol<=nEndCol; nCol++)
+ aCol[nCol].FindUsed(nStartRow, nEndRow, aUsed);
+ aUsed.build_tree();
+
+ ScOutlineArray& rRowArray = pOutlineTable->GetRowArray();
+ for (nRow=nStartRow; nRow<=nEndRow; nRow++)
+ {
+ bool bUsed = false;
+ SCROW nLastRow = nRow;
+ aUsed.search_tree(nRow, bUsed, nullptr, &nLastRow);
+ if (!bUsed)
+ {
+ nRow = nLastRow;
+ continue;
+ }
+
+ bFound = false;
+ for (nCol=nStartCol; nCol<=nEndCol && !bFound; nCol++)
+ {
+ ScRefCellValue aCell = aCol[nCol].GetCellValue(nRow);
+
+ if (aCell.meType != CELLTYPE_FORMULA)
+ continue;
+
+ if (!aCell.mpFormula->HasRefListExpressibleAsOneReference(aRef))
+ continue;
+
+ if ( aRef.aStart.Col() == nCol && aRef.aEnd.Col() == nCol &&
+ aRef.aStart.Tab() == nTab && aRef.aEnd.Tab() == nTab &&
+ DiffSign( aRef.aStart.Row(), nRow ) ==
+ DiffSign( aRef.aEnd.Row(), nRow ) )
+ {
+ if (rRowArray.Insert( aRef.aStart.Row(), aRef.aEnd.Row(), bSizeChanged ))
+ {
+ bFound = true;
+ }
+ }
+ }
+ }
+
+ // Column
+ ScOutlineArray& rColArray = pOutlineTable->GetColArray();
+ for (nCol=nStartCol; nCol<=nEndCol; nCol++)
+ {
+ if (aCol[nCol].IsEmptyData())
+ continue;
+
+ OutlineArrayFinder aFunc(aRef, nCol, nTab, &rColArray, bSizeChanged);
+ sc::FindFormula(aCol[nCol].maCells, nStartRow, nEndRow, aFunc);
+ }
+}
+
+ // CopyData - for Query in other range
+
+void ScTable::CopyData( SCCOL nStartCol, SCROW nStartRow, SCCOL nEndCol, SCROW nEndRow,
+ SCCOL nDestCol, SCROW nDestRow, SCTAB nDestTab )
+{
+ //TODO: if used for multiple rows, optimize after columns!
+
+ ScAddress aSrc( nStartCol, nStartRow, nTab );
+ ScAddress aDest( nDestCol, nDestRow, nDestTab );
+ ScRange aRange( aSrc, aDest );
+ bool bThisTab = ( nDestTab == nTab );
+ SCROW nDestY = nDestRow;
+ for (SCROW nRow=nStartRow; nRow<=nEndRow; nRow++)
+ {
+ aSrc.SetRow( nRow );
+ aDest.SetRow( nDestY );
+ SCCOL nDestX = nDestCol;
+ for (SCCOL nCol=nStartCol; nCol<=nEndCol; nCol++)
+ {
+ aSrc.SetCol( nCol );
+ aDest.SetCol( nDestX );
+ ScCellValue aCell;
+ aCell.assign(rDocument, ScAddress(nCol, nRow, nTab));
+
+ if (aCell.meType == CELLTYPE_FORMULA)
+ {
+ sc::RefUpdateContext aCxt(rDocument);
+ aCxt.meMode = URM_COPY;
+ aCxt.maRange = aRange;
+ aCxt.mnColDelta = nDestCol - nStartCol;
+ aCxt.mnRowDelta = nDestRow - nStartRow;
+ aCxt.mnTabDelta = nDestTab - nTab;
+ aCell.mpFormula->UpdateReference(aCxt);
+ aCell.mpFormula->aPos = aDest;
+ }
+
+ if (bThisTab)
+ {
+ aCell.release(CreateColumnIfNotExists(nDestX), nDestY);
+ SetPattern( nDestX, nDestY, *GetPattern( nCol, nRow ) );
+ }
+ else
+ {
+ aCell.release(rDocument, aDest);
+ rDocument.SetPattern( aDest, *GetPattern( nCol, nRow ) );
+ }
+
+ ++nDestX;
+ }
+ ++nDestY;
+ }
+}
+
+bool ScTable::RefVisible(const ScFormulaCell* pCell)
+{
+ ScRange aRef;
+
+ if (pCell->HasOneReference(aRef))
+ {
+ if (aRef.aStart.Col()==aRef.aEnd.Col() && aRef.aStart.Tab()==aRef.aEnd.Tab())
+ {
+ SCROW nEndRow;
+ if (!RowFiltered(aRef.aStart.Row(), nullptr, &nEndRow))
+ // row not filtered.
+ nEndRow = ::std::numeric_limits<SCROW>::max();
+
+ if (!ValidRow(nEndRow) || nEndRow < aRef.aEnd.Row())
+ return true; // at least partly visible
+ return false; // completely invisible
+ }
+ }
+
+ return true; // somehow different
+}
+
+OUString ScTable::GetUpperCellString(SCCOL nCol, SCROW nRow)
+{
+ return ScGlobal::getCharClass().uppercase(GetInputString(nCol, nRow).trim());
+}
+
+// Calculate the size of the sheet and set the size on DrawPage
+
+void ScTable::SetDrawPageSize(bool bResetStreamValid, bool bUpdateNoteCaptionPos,
+ ScObjectHandling eObjectHandling)
+{
+ ScDrawLayer* pDrawLayer = rDocument.GetDrawLayer();
+ if( pDrawLayer )
+ {
+ const sal_Int64 nMax = ::std::numeric_limits<tools::Long>::max();
+ // #i113884# Avoid int32 overflow with possible negative results than can cause bad effects.
+ // If the draw page size is smaller than all rows, only the bottom of the sheet is affected.
+ tools::Long x = std::min(o3tl::convert(GetColOffset(rDocument.MaxCol() + 1),
+ o3tl::Length::twip, o3tl::Length::mm100),
+ nMax);
+ tools::Long y = std::min(o3tl::convert(GetRowOffset(rDocument.MaxRow() + 1),
+ o3tl::Length::twip, o3tl::Length::mm100),
+ nMax);
+
+ if ( IsLayoutRTL() ) // IsNegativePage
+ x = -x;
+
+ pDrawLayer->SetPageSize(static_cast<sal_uInt16>(nTab), Size(x, y), bUpdateNoteCaptionPos,
+ eObjectHandling);
+ }
+
+ // #i102616# actions that modify the draw page size count as sheet modification
+ // (exception: InitDrawLayer)
+ if (bResetStreamValid)
+ SetStreamValid(false);
+}
+
+void ScTable::SetRangeName(std::unique_ptr<ScRangeName> pNew)
+{
+ mpRangeName = std::move(pNew);
+
+ //fdo#39792: mark stream as invalid, otherwise new ScRangeName will not be written to file
+ SetStreamValid(false);
+}
+
+ScRangeName* ScTable::GetRangeName() const
+{
+ if (!mpRangeName)
+ mpRangeName.reset(new ScRangeName);
+ return mpRangeName.get();
+}
+
+tools::Long ScTable::GetRowOffset( SCROW nRow, bool bHiddenAsZero ) const
+{
+ tools::Long n = 0;
+ if ( mpHiddenRows && mpRowHeights )
+ {
+ if (nRow == 0)
+ return 0;
+ else if (nRow == 1)
+ return GetRowHeight(0, nullptr, nullptr, bHiddenAsZero );
+
+ n = GetTotalRowHeight(0, nRow-1, bHiddenAsZero);
+#if OSL_DEBUG_LEVEL > 0
+ if (n == ::std::numeric_limits<tools::Long>::max())
+ OSL_FAIL("ScTable::GetRowOffset: row heights overflow");
+#endif
+ }
+ else
+ {
+ OSL_FAIL("GetRowOffset: Data missing");
+ }
+ return n;
+}
+
+SCROW ScTable::GetRowForHeight(tools::Long nHeight) const
+{
+ tools::Long nSum = 0;
+
+ ScFlatBoolRowSegments::RangeData aData;
+
+ ScFlatUInt16RowSegments::RangeData aRowHeightRange;
+ aRowHeightRange.mnRow2 = -1;
+ aRowHeightRange.mnValue = 1; // silence MSVC C4701
+
+ for (SCROW nRow = 0; nRow <= rDocument.MaxRow(); ++nRow)
+ {
+ if (!mpHiddenRows->getRangeData(nRow, aData))
+ // Failed to fetch the range data for whatever reason.
+ break;
+
+ if (aData.mbValue)
+ {
+ // This row is hidden. Skip ahead all hidden rows.
+ nRow = aData.mnRow2;
+ continue;
+ }
+
+ if (aRowHeightRange.mnRow2 < nRow)
+ {
+ if (!mpRowHeights->getRangeData(nRow, aRowHeightRange))
+ // Failed to fetch the range data for whatever reason.
+ break;
+ }
+
+ // find the last common row between hidden & height spans
+ SCROW nLastCommon = std::min(aData.mnRow2, aRowHeightRange.mnRow2);
+ assert (nLastCommon >= nRow);
+ SCROW nCommon = nLastCommon - nRow + 1;
+
+ // how much further to go ?
+ tools::Long nPixelsLeft = nHeight - nSum;
+ tools::Long nCommonPixels = static_cast<tools::Long>(aRowHeightRange.mnValue) * nCommon;
+
+ // are we in the zone ?
+ if (nCommonPixels > nPixelsLeft)
+ {
+ nRow += (nPixelsLeft + aRowHeightRange.mnValue - 1) / aRowHeightRange.mnValue;
+
+ // FIXME: finding this next row is far from elegant,
+ // we have a single caller, which subtracts one as well(!?)
+ if (nRow >= rDocument.MaxRow())
+ return rDocument.MaxRow();
+
+ if (!mpHiddenRows->getRangeData(nRow, aData))
+ // Failed to fetch the range data for whatever reason.
+ break;
+
+ if (aData.mbValue)
+ // These rows are hidden.
+ nRow = aData.mnRow2 + 1;
+
+ return nRow <= rDocument.MaxRow() ? nRow : rDocument.MaxRow();
+ }
+
+ // skip the range and keep hunting
+ nSum += nCommonPixels;
+ nRow = nLastCommon;
+ }
+ return -1;
+}
+
+tools::Long ScTable::GetColOffset( SCCOL nCol, bool bHiddenAsZero ) const
+{
+ tools::Long n = 0;
+ if ( mpColWidth )
+ {
+ auto colWidthIt = mpColWidth->begin();
+ for (SCCOL i = 0; i < nCol; (++i < nCol) ? ++colWidthIt : (void)false)
+ if (!( bHiddenAsZero && ColHidden(i) ))
+ n += *colWidthIt;
+ }
+ else
+ {
+ OSL_FAIL("GetColumnOffset: Data missing");
+ }
+ return n;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sc/source/core/data/table3.cxx b/sc/source/core/data/table3.cxx
new file mode 100644
index 000000000..c30032582
--- /dev/null
+++ b/sc/source/core/data/table3.cxx
@@ -0,0 +1,3099 @@
+/* -*- 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 <comphelper/processfactory.hxx>
+#include <comphelper/random.hxx>
+#include <svl/numformat.hxx>
+#include <svl/zforlist.hxx>
+#include <svl/zformat.hxx>
+#include <unotools/collatorwrapper.hxx>
+#include <stdlib.h>
+#include <com/sun/star/i18n/KParseTokens.hpp>
+#include <com/sun/star/i18n/KParseType.hpp>
+#include <sal/log.hxx>
+#include <osl/diagnose.h>
+
+#include <refdata.hxx>
+#include <table.hxx>
+#include <scitems.hxx>
+#include <formulacell.hxx>
+#include <document.hxx>
+#include <globstr.hrc>
+#include <scresid.hxx>
+#include <global.hxx>
+#include <stlpool.hxx>
+#include <patattr.hxx>
+#include <subtotal.hxx>
+#include <markdata.hxx>
+#include <rangelst.hxx>
+#include <userlist.hxx>
+#include <progress.hxx>
+#include <queryparam.hxx>
+#include <queryentry.hxx>
+#include <subtotalparam.hxx>
+#include <docpool.hxx>
+#include <cellvalue.hxx>
+#include <tokenarray.hxx>
+#include <mtvcellfunc.hxx>
+#include <columnspanset.hxx>
+#include <fstalgorithm.hxx>
+#include <listenercontext.hxx>
+#include <sharedformula.hxx>
+#include <stlsheet.hxx>
+#include <refhint.hxx>
+#include <listenerquery.hxx>
+#include <bcaslot.hxx>
+#include <reordermap.hxx>
+#include <drwlayer.hxx>
+#include <queryevaluator.hxx>
+#include <scopetools.hxx>
+
+#include <svl/sharedstringpool.hxx>
+
+#include <memory>
+#include <set>
+#include <unordered_set>
+#include <vector>
+#include <mdds/flat_segment_tree.hpp>
+
+using namespace ::com::sun::star;
+
+namespace naturalsort {
+
+using namespace ::com::sun::star::i18n;
+
+/** Splits a given string into three parts: the prefix, number string, and
+ the suffix.
+
+ @param sWhole
+ Original string to be split into pieces
+
+ @param sPrefix
+ Prefix string that consists of the part before the first number token.
+ If no number was found, sPrefix is unchanged.
+
+ @param sSuffix
+ String after the last number token. This may still contain number strings.
+ If no number was found, sSuffix is unchanged.
+
+ @param fNum
+ Number converted from the middle number string
+ If no number was found, fNum is unchanged.
+
+ @return Returns TRUE if a numeral element is found in a given string, or
+ FALSE if no numeral element is found.
+*/
+static bool SplitString( const OUString &sWhole,
+ OUString &sPrefix, OUString &sSuffix, double &fNum )
+{
+ // Get prefix element, search for any digit and stop.
+ sal_Int32 nPos = 0;
+ while (nPos < sWhole.getLength())
+ {
+ const sal_uInt16 nType = ScGlobal::getCharClass().getCharacterType( sWhole, nPos);
+ if (nType & KCharacterType::DIGIT)
+ break;
+ sWhole.iterateCodePoints( &nPos );
+ }
+
+ // Return FALSE if no numeral element is found
+ if ( nPos == sWhole.getLength() )
+ return false;
+
+ // Get numeral element
+ const OUString& sUser = ScGlobal::getLocaleData().getNumDecimalSep();
+ ParseResult aPRNum = ScGlobal::getCharClass().parsePredefinedToken(
+ KParseType::ANY_NUMBER, sWhole, nPos,
+ KParseTokens::ANY_NUMBER, "", KParseTokens::ANY_NUMBER, sUser );
+
+ if ( aPRNum.EndPos == nPos )
+ {
+ SAL_WARN("sc.core","naturalsort::SplitString - digit found but no number parsed, pos " <<
+ nPos << " : " << sWhole);
+ return false;
+ }
+
+ sPrefix = sWhole.copy( 0, nPos );
+ fNum = aPRNum.Value;
+ sSuffix = sWhole.copy( aPRNum.EndPos );
+
+ return true;
+}
+
+/** Naturally compares two given strings.
+
+ This is the main function that should be called externally. It returns
+ either 1, 0, or -1 depending on the comparison result of given two strings.
+
+ @param sInput1
+ Input string 1
+
+ @param sInput2
+ Input string 2
+
+ @param bCaseSens
+ Boolean value for case sensitivity
+
+ @param pData
+ Pointer to user defined sort list
+
+ @param pCW
+ Pointer to collator wrapper for normal string comparison
+
+ @return Returns 1 if sInput1 is greater, 0 if sInput1 == sInput2, and -1 if
+ sInput2 is greater.
+*/
+static short Compare( const OUString &sInput1, const OUString &sInput2,
+ const bool bCaseSens, const ScUserListData* pData, const CollatorWrapper *pCW )
+{
+ OUString sStr1( sInput1 ), sStr2( sInput2 ), sPre1, sSuf1, sPre2, sSuf2;
+
+ do
+ {
+ double nNum1, nNum2;
+ bool bNumFound1 = SplitString( sStr1, sPre1, sSuf1, nNum1 );
+ bool bNumFound2 = SplitString( sStr2, sPre2, sSuf2, nNum2 );
+
+ short nPreRes; // Prefix comparison result
+ if ( pData )
+ {
+ if ( bCaseSens )
+ {
+ if ( !bNumFound1 || !bNumFound2 )
+ return static_cast<short>(pData->Compare( sStr1, sStr2 ));
+ else
+ nPreRes = pData->Compare( sPre1, sPre2 );
+ }
+ else
+ {
+ if ( !bNumFound1 || !bNumFound2 )
+ return static_cast<short>(pData->ICompare( sStr1, sStr2 ));
+ else
+ nPreRes = pData->ICompare( sPre1, sPre2 );
+ }
+ }
+ else
+ {
+ if ( !bNumFound1 || !bNumFound2 )
+ return static_cast<short>(pCW->compareString( sStr1, sStr2 ));
+ else
+ nPreRes = static_cast<short>(pCW->compareString( sPre1, sPre2 ));
+ }
+
+ // Prefix strings differ. Return immediately.
+ if ( nPreRes != 0 ) return nPreRes;
+
+ if ( nNum1 != nNum2 )
+ {
+ if ( nNum1 < nNum2 ) return -1;
+ return (nNum1 > nNum2) ? 1 : 0;
+ }
+
+ // The prefix and the first numerical elements are equal, but the suffix
+ // strings may still differ. Stay in the loop.
+
+ sStr1 = sSuf1;
+ sStr2 = sSuf2;
+
+ } while (true);
+
+ return 0;
+}
+
+}
+
+namespace {
+
+struct ScSortInfo final
+{
+ ScRefCellValue maCell;
+ SCCOLROW nOrg;
+};
+
+}
+
+class ScSortInfoArray
+{
+public:
+
+ struct Cell
+ {
+ ScRefCellValue maCell;
+ const sc::CellTextAttr* mpAttr;
+ const ScPostIt* mpNote;
+ std::vector<SdrObject*> maDrawObjects;
+ const ScPatternAttr* mpPattern;
+
+ Cell() : mpAttr(nullptr), mpNote(nullptr), mpPattern(nullptr) {}
+ };
+
+ struct Row
+ {
+ std::vector<Cell> maCells;
+
+ bool mbHidden:1;
+ bool mbFiltered:1;
+
+ explicit Row( size_t nColSize ) : maCells(nColSize, Cell()), mbHidden(false), mbFiltered(false) {}
+ };
+
+ typedef std::vector<Row> RowsType;
+
+private:
+ std::unique_ptr<RowsType> mpRows; /// row-wise data table for sort by row operation.
+
+ std::vector<std::unique_ptr<ScSortInfo[]>> mvppInfo;
+ SCCOLROW nStart;
+ SCCOLROW mnLastIndex; /// index of last non-empty cell position.
+
+ std::vector<SCCOLROW> maOrderIndices;
+ bool mbKeepQuery;
+ bool mbUpdateRefs;
+
+public:
+ ScSortInfoArray(const ScSortInfoArray&) = delete;
+ const ScSortInfoArray& operator=(const ScSortInfoArray&) = delete;
+
+ ScSortInfoArray( sal_uInt16 nSorts, SCCOLROW nInd1, SCCOLROW nInd2 ) :
+ mvppInfo(nSorts),
+ nStart( nInd1 ),
+ mnLastIndex(nInd2),
+ mbKeepQuery(false),
+ mbUpdateRefs(false)
+ {
+ SCSIZE nCount( nInd2 - nInd1 + 1 );
+ if (nSorts)
+ {
+ for ( sal_uInt16 nSort = 0; nSort < nSorts; nSort++ )
+ {
+ mvppInfo[nSort].reset(new ScSortInfo[nCount]);
+ }
+ }
+
+ for (size_t i = 0; i < nCount; ++i)
+ maOrderIndices.push_back(i+nStart);
+ }
+
+ void SetKeepQuery( bool b ) { mbKeepQuery = b; }
+
+ bool IsKeepQuery() const { return mbKeepQuery; }
+
+ void SetUpdateRefs( bool b ) { mbUpdateRefs = b; }
+
+ bool IsUpdateRefs() const { return mbUpdateRefs; }
+
+ /**
+ * Call this only during normal sorting, not from reordering.
+ */
+ std::unique_ptr<ScSortInfo[]> const & GetFirstArray() const
+ {
+ return mvppInfo[0];
+ }
+
+ /**
+ * Call this only during normal sorting, not from reordering.
+ */
+ ScSortInfo & Get( sal_uInt16 nSort, SCCOLROW nInd )
+ {
+ return mvppInfo[nSort][ nInd - nStart ];
+ }
+
+ /**
+ * Call this only during normal sorting, not from reordering.
+ */
+ void Swap( SCCOLROW nInd1, SCCOLROW nInd2 )
+ {
+ if (nInd1 == nInd2) // avoid self-move-assign
+ return;
+ SCSIZE n1 = static_cast<SCSIZE>(nInd1 - nStart);
+ SCSIZE n2 = static_cast<SCSIZE>(nInd2 - nStart);
+ for ( sal_uInt16 nSort = 0; nSort < static_cast<sal_uInt16>(mvppInfo.size()); nSort++ )
+ {
+ auto & ppInfo = mvppInfo[nSort];
+ std::swap(ppInfo[n1], ppInfo[n2]);
+ }
+
+ std::swap(maOrderIndices[n1], maOrderIndices[n2]);
+
+ if (mpRows)
+ {
+ // Swap rows in data table.
+ RowsType& rRows = *mpRows;
+ std::swap(rRows[n1], rRows[n2]);
+ }
+ }
+
+ void SetOrderIndices( std::vector<SCCOLROW>&& rIndices )
+ {
+ maOrderIndices = std::move(rIndices);
+ }
+
+ /**
+ * @param rIndices indices are actual row positions on the sheet, not an
+ * offset from the top row.
+ */
+ void ReorderByRow( const std::vector<SCCOLROW>& rIndices )
+ {
+ if (!mpRows)
+ return;
+
+ RowsType& rRows = *mpRows;
+
+ std::vector<SCCOLROW> aOrderIndices2;
+ aOrderIndices2.reserve(rIndices.size());
+
+ RowsType aRows2;
+ aRows2.reserve(rRows.size());
+
+ for (const auto& rIndex : rIndices)
+ {
+ size_t nPos = rIndex - nStart; // switch to an offset to top row.
+ aRows2.push_back(rRows[nPos]);
+ aOrderIndices2.push_back(maOrderIndices[nPos]);
+ }
+
+ rRows.swap(aRows2);
+ maOrderIndices.swap(aOrderIndices2);
+ }
+
+ sal_uInt16 GetUsedSorts() const { return mvppInfo.size(); }
+
+ SCCOLROW GetStart() const { return nStart; }
+ SCCOLROW GetLast() const { return mnLastIndex; }
+
+ const std::vector<SCCOLROW>& GetOrderIndices() const { return maOrderIndices; }
+
+ RowsType& InitDataRows( size_t nRowSize, size_t nColSize )
+ {
+ mpRows.reset(new RowsType);
+ mpRows->resize(nRowSize, Row(nColSize));
+ return *mpRows;
+ }
+
+ RowsType* GetDataRows()
+ {
+ return mpRows.get();
+ }
+};
+
+// Assume that we can handle 512MB, which with a ~100 bytes
+// ScSortInfoArray::Cell element for 500MB are about 5 million cells plus
+// overhead in one chunk.
+constexpr sal_Int32 kSortCellsChunk = 500 * 1024 * 1024 / sizeof(ScSortInfoArray::Cell);
+
+namespace {
+
+void initDataRows(
+ ScSortInfoArray& rArray, ScTable& rTab, ScColContainer& rCols,
+ SCCOL nCol1, SCROW nRow1, SCCOL nCol2, SCROW nRow2,
+ bool bHiddenFiltered, bool bPattern, bool bCellNotes, bool bCellDrawObjects, bool bOnlyDataAreaExtras )
+{
+ // Fill row-wise data table.
+ ScSortInfoArray::RowsType& rRows = rArray.InitDataRows(nRow2-nRow1+1, nCol2-nCol1+1);
+
+ const std::vector<SCCOLROW>& rOrderIndices = rArray.GetOrderIndices();
+ assert(!bOnlyDataAreaExtras || (rOrderIndices.size() == static_cast<size_t>(nRow2 - nRow1 + 1)
+ && nRow1 == rArray.GetStart()));
+
+ ScDrawLayer* pDrawLayer = (bCellDrawObjects ? rTab.GetDoc().GetDrawLayer() : nullptr);
+ for (SCCOL nCol = nCol1; nCol <= nCol2; ++nCol)
+ {
+ ScColumn& rCol = rCols[nCol];
+
+ // Skip reordering of cell formats if the whole span is on the same pattern entry.
+ bool bUniformPattern = rCol.GetPatternCount(nRow1, nRow2) < 2u;
+
+ sc::ColumnBlockConstPosition aBlockPos;
+ rCol.InitBlockPosition(aBlockPos);
+ std::map<SCROW, std::vector<SdrObject*>> aRowDrawObjects;
+ if (pDrawLayer)
+ aRowDrawObjects = pDrawLayer->GetObjectsAnchoredToRange(rTab.GetTab(), nCol, nRow1, nRow2);
+
+ for (SCROW nR = nRow1; nR <= nRow2; ++nR)
+ {
+ const SCROW nRow = (bOnlyDataAreaExtras ? rOrderIndices[nR - rArray.GetStart()] : nR);
+ ScSortInfoArray::Row& rRow = rRows[nR-nRow1];
+ ScSortInfoArray::Cell& rCell = rRow.maCells[nCol-nCol1];
+ if (!bOnlyDataAreaExtras)
+ {
+ rCell.maCell = rCol.GetCellValue(aBlockPos, nRow);
+ rCell.mpAttr = rCol.GetCellTextAttr(aBlockPos, nRow);
+ }
+ if (bCellNotes)
+ rCell.mpNote = rCol.GetCellNote(aBlockPos, nRow);
+ if (pDrawLayer)
+ rCell.maDrawObjects = aRowDrawObjects[nRow];
+
+ if (!bUniformPattern && bPattern)
+ rCell.mpPattern = rCol.GetPattern(nRow);
+ }
+ }
+
+ if (!bOnlyDataAreaExtras && bHiddenFiltered)
+ {
+ for (SCROW nRow = nRow1; nRow <= nRow2; ++nRow)
+ {
+ ScSortInfoArray::Row& rRow = rRows[nRow-nRow1];
+ rRow.mbHidden = rTab.RowHidden(nRow);
+ rRow.mbFiltered = rTab.RowFiltered(nRow);
+ }
+ }
+}
+
+}
+
+std::unique_ptr<ScSortInfoArray> ScTable::CreateSortInfoArray( const sc::ReorderParam& rParam )
+{
+ std::unique_ptr<ScSortInfoArray> pArray;
+
+ if (rParam.mbByRow)
+ {
+ // Create a sort info array with just the data table.
+ SCROW nRow1 = rParam.maSortRange.aStart.Row();
+ SCROW nRow2 = rParam.maSortRange.aEnd.Row();
+ SCCOL nCol1 = rParam.maSortRange.aStart.Col();
+ SCCOL nCol2 = rParam.maSortRange.aEnd.Col();
+
+ pArray.reset(new ScSortInfoArray(0, nRow1, nRow2));
+ pArray->SetKeepQuery(rParam.mbHiddenFiltered);
+ pArray->SetUpdateRefs(rParam.mbUpdateRefs);
+
+ CreateColumnIfNotExists(nCol2);
+ initDataRows( *pArray, *this, aCol, nCol1, nRow1, nCol2, nRow2, rParam.mbHiddenFiltered,
+ rParam.maDataAreaExtras.mbCellFormats, true, true, false);
+ }
+ else
+ {
+ SCCOLROW nCol1 = rParam.maSortRange.aStart.Col();
+ SCCOLROW nCol2 = rParam.maSortRange.aEnd.Col();
+
+ pArray.reset(new ScSortInfoArray(0, nCol1, nCol2));
+ pArray->SetKeepQuery(rParam.mbHiddenFiltered);
+ pArray->SetUpdateRefs(rParam.mbUpdateRefs);
+ }
+
+ return pArray;
+}
+
+std::unique_ptr<ScSortInfoArray> ScTable::CreateSortInfoArray(
+ const ScSortParam& rSortParam, SCCOLROW nInd1, SCCOLROW nInd2,
+ bool bKeepQuery, bool bUpdateRefs )
+{
+ sal_uInt16 nUsedSorts = 1;
+ while ( nUsedSorts < rSortParam.GetSortKeyCount() && rSortParam.maKeyState[nUsedSorts].bDoSort )
+ nUsedSorts++;
+ std::unique_ptr<ScSortInfoArray> pArray(new ScSortInfoArray( nUsedSorts, nInd1, nInd2 ));
+ pArray->SetKeepQuery(bKeepQuery);
+ pArray->SetUpdateRefs(bUpdateRefs);
+
+ if ( rSortParam.bByRow )
+ {
+ for ( sal_uInt16 nSort = 0; nSort < nUsedSorts; nSort++ )
+ {
+ SCCOL nCol = static_cast<SCCOL>(rSortParam.maKeyState[nSort].nField);
+ ScColumn* pCol = &aCol[nCol];
+ sc::ColumnBlockConstPosition aBlockPos;
+ pCol->InitBlockPosition(aBlockPos);
+ for ( SCROW nRow = nInd1; nRow <= nInd2; nRow++ )
+ {
+ ScSortInfo & rInfo = pArray->Get( nSort, nRow );
+ rInfo.maCell = pCol->GetCellValue(aBlockPos, nRow);
+ rInfo.nOrg = nRow;
+ }
+ }
+
+ CreateColumnIfNotExists(rSortParam.nCol2);
+ initDataRows( *pArray, *this, aCol, rSortParam.nCol1, nInd1, rSortParam.nCol2, nInd2, bKeepQuery,
+ rSortParam.aDataAreaExtras.mbCellFormats, true, true, false);
+ }
+ else
+ {
+ for ( sal_uInt16 nSort = 0; nSort < nUsedSorts; nSort++ )
+ {
+ SCROW nRow = rSortParam.maKeyState[nSort].nField;
+ for ( SCCOL nCol = static_cast<SCCOL>(nInd1);
+ nCol <= static_cast<SCCOL>(nInd2); nCol++ )
+ {
+ ScSortInfo & rInfo = pArray->Get( nSort, nCol );
+ rInfo.maCell = GetCellValue(nCol, nRow);
+ rInfo.nOrg = nCol;
+ }
+ }
+ }
+ return pArray;
+}
+
+namespace {
+
+struct SortedColumn
+{
+ typedef mdds::flat_segment_tree<SCROW, const ScPatternAttr*> PatRangeType;
+
+ sc::CellStoreType maCells;
+ sc::CellTextAttrStoreType maCellTextAttrs;
+ sc::BroadcasterStoreType maBroadcasters;
+ sc::CellNoteStoreType maCellNotes;
+ std::vector<std::vector<SdrObject*>> maCellDrawObjects;
+
+ PatRangeType maPatterns;
+ PatRangeType::const_iterator miPatternPos;
+
+ SortedColumn(const SortedColumn&) = delete;
+ const SortedColumn operator=(const SortedColumn&) = delete;
+
+ explicit SortedColumn( size_t nTopEmptyRows, const ScSheetLimits& rSheetLimits ) :
+ maCells(nTopEmptyRows),
+ maCellTextAttrs(nTopEmptyRows),
+ maBroadcasters(nTopEmptyRows),
+ maCellNotes(nTopEmptyRows),
+ maPatterns(0, rSheetLimits.GetMaxRowCount(), nullptr),
+ miPatternPos(maPatterns.begin()) {}
+
+ void setPattern( SCROW nRow, const ScPatternAttr* pPat )
+ {
+ miPatternPos = maPatterns.insert(miPatternPos, nRow, nRow+1, pPat).first;
+ }
+};
+
+struct SortedRowFlags
+{
+ typedef mdds::flat_segment_tree<SCROW,bool> FlagsType;
+
+ FlagsType maRowsHidden;
+ FlagsType maRowsFiltered;
+ FlagsType::const_iterator miPosHidden;
+ FlagsType::const_iterator miPosFiltered;
+
+ SortedRowFlags(const ScSheetLimits& rSheetLimits) :
+ maRowsHidden(0, rSheetLimits.GetMaxRowCount(), false),
+ maRowsFiltered(0, rSheetLimits.GetMaxRowCount(), false),
+ miPosHidden(maRowsHidden.begin()),
+ miPosFiltered(maRowsFiltered.begin()) {}
+
+ void setRowHidden( SCROW nRow, bool b )
+ {
+ miPosHidden = maRowsHidden.insert(miPosHidden, nRow, nRow+1, b).first;
+ }
+
+ void setRowFiltered( SCROW nRow, bool b )
+ {
+ miPosFiltered = maRowsFiltered.insert(miPosFiltered, nRow, nRow+1, b).first;
+ }
+
+ void swap( SortedRowFlags& r )
+ {
+ maRowsHidden.swap(r.maRowsHidden);
+ maRowsFiltered.swap(r.maRowsFiltered);
+
+ // Just reset the position hints.
+ miPosHidden = maRowsHidden.begin();
+ miPosFiltered = maRowsFiltered.begin();
+ }
+};
+
+struct PatternSpan
+{
+ SCROW mnRow1;
+ SCROW mnRow2;
+ const ScPatternAttr* mpPattern;
+
+ PatternSpan( SCROW nRow1, SCROW nRow2, const ScPatternAttr* pPat ) :
+ mnRow1(nRow1), mnRow2(nRow2), mpPattern(pPat) {}
+};
+
+}
+
+bool ScTable::IsSortCollatorGlobal() const
+{
+ return pSortCollator == &ScGlobal::GetCollator() ||
+ pSortCollator == &ScGlobal::GetCaseCollator();
+}
+
+void ScTable::InitSortCollator( const ScSortParam& rPar )
+{
+ if ( !rPar.aCollatorLocale.Language.isEmpty() )
+ {
+ if ( !pSortCollator || IsSortCollatorGlobal() )
+ pSortCollator = new CollatorWrapper( comphelper::getProcessComponentContext() );
+ pSortCollator->loadCollatorAlgorithm( rPar.aCollatorAlgorithm,
+ rPar.aCollatorLocale, (rPar.bCaseSens ? 0 : SC_COLLATOR_IGNORES) );
+ }
+ else
+ { // SYSTEM
+ DestroySortCollator();
+ pSortCollator = &ScGlobal::GetCollator(rPar.bCaseSens);
+ }
+}
+
+void ScTable::DestroySortCollator()
+{
+ if ( pSortCollator )
+ {
+ if ( !IsSortCollatorGlobal() )
+ delete pSortCollator;
+ pSortCollator = nullptr;
+ }
+}
+
+namespace {
+
+template<typename Hint, typename ReorderMap, typename Index>
+class ReorderNotifier
+{
+ Hint maHint;
+public:
+ ReorderNotifier( const ReorderMap& rMap, SCTAB nTab, Index nPos1, Index nPos2 ) :
+ maHint(rMap, nTab, nPos1, nPos2) {}
+
+ void operator() ( SvtListener* p )
+ {
+ p->Notify(maHint);
+ }
+};
+
+class FormulaGroupPosCollector
+{
+ sc::RefQueryFormulaGroup& mrQuery;
+
+public:
+ explicit FormulaGroupPosCollector( sc::RefQueryFormulaGroup& rQuery ) : mrQuery(rQuery) {}
+
+ void operator() ( const SvtListener* p )
+ {
+ p->Query(mrQuery);
+ }
+};
+
+void fillSortedColumnArray(
+ std::vector<std::unique_ptr<SortedColumn>>& rSortedCols,
+ SortedRowFlags& rRowFlags,
+ std::vector<SvtListener*>& rCellListeners,
+ ScSortInfoArray* pArray, SCTAB nTab, SCCOL nCol1, SCCOL nCol2, ScProgress* pProgress, const ScTable* pTable,
+ bool bOnlyDataAreaExtras )
+{
+ assert(!bOnlyDataAreaExtras || !pArray->IsUpdateRefs());
+
+ SCROW nRow1 = pArray->GetStart();
+ ScSortInfoArray::RowsType* pRows = pArray->GetDataRows();
+ std::vector<SCCOLROW> aOrderIndices = pArray->GetOrderIndices();
+
+ size_t nColCount = nCol2 - nCol1 + 1;
+ std::vector<std::unique_ptr<SortedColumn>> aSortedCols; // storage for copied cells.
+ SortedRowFlags aRowFlags(pTable->GetDoc().GetSheetLimits());
+ aSortedCols.reserve(nColCount);
+ for (size_t i = 0; i < nColCount; ++i)
+ {
+ // In the sorted column container, element positions and row
+ // positions must match, else formula cells may mis-behave during
+ // grouping.
+ aSortedCols.push_back(std::make_unique<SortedColumn>(nRow1, pTable->GetDoc().GetSheetLimits()));
+ }
+
+ for (size_t i = 0; i < pRows->size(); ++i)
+ {
+ const SCROW nRow = nRow1 + i;
+
+ ScSortInfoArray::Row& rRow = (*pRows)[i];
+ for (size_t j = 0; j < rRow.maCells.size(); ++j)
+ {
+ ScSortInfoArray::Cell& rCell = rRow.maCells[j];
+
+ // If bOnlyDataAreaExtras,
+ // sc::CellStoreType aSortedCols.at(j)->maCells
+ // and
+ // sc::CellTextAttrStoreType aSortedCols.at(j)->maCellTextAttrs
+ // are by definition all empty mdds::multi_type_vector, so nothing
+ // needs to be done to push *all* empty.
+
+ if (!bOnlyDataAreaExtras)
+ {
+ sc::CellStoreType& rCellStore = aSortedCols.at(j)->maCells;
+ switch (rCell.maCell.meType)
+ {
+ case CELLTYPE_STRING:
+ assert(rCell.mpAttr);
+ rCellStore.push_back(*rCell.maCell.mpString);
+ break;
+ case CELLTYPE_VALUE:
+ assert(rCell.mpAttr);
+ rCellStore.push_back(rCell.maCell.mfValue);
+ break;
+ case CELLTYPE_EDIT:
+ assert(rCell.mpAttr);
+ rCellStore.push_back(rCell.maCell.mpEditText->Clone().release());
+ break;
+ case CELLTYPE_FORMULA:
+ {
+ assert(rCell.mpAttr);
+ ScAddress aOldPos = rCell.maCell.mpFormula->aPos;
+
+ const ScAddress aCellPos(nCol1 + j, nRow, nTab);
+ ScFormulaCell* pNew = rCell.maCell.mpFormula->Clone( aCellPos );
+ if (pArray->IsUpdateRefs())
+ {
+ pNew->CopyAllBroadcasters(*rCell.maCell.mpFormula);
+ pNew->GetCode()->AdjustReferenceOnMovedOrigin(aOldPos, aCellPos);
+ }
+ else
+ {
+ pNew->GetCode()->AdjustReferenceOnMovedOriginIfOtherSheet(aOldPos, aCellPos);
+ }
+
+ if (!rCellListeners.empty())
+ {
+ // Original source cells will be deleted during
+ // sc::CellStoreType::transfer(), SvtListener is a base
+ // class, so we need to replace it.
+ auto it( ::std::find( rCellListeners.begin(), rCellListeners.end(), rCell.maCell.mpFormula));
+ if (it != rCellListeners.end())
+ *it = pNew;
+ }
+
+ rCellStore.push_back(pNew);
+ }
+ break;
+ default:
+ //assert(!rCell.mpAttr);
+ // This assert doesn't hold, for example
+ // CopyCellsFromClipHandler may omit copying cells during
+ // PasteSpecial for which CopyTextAttrsFromClipHandler
+ // still copies a CellTextAttr. So if that really is not
+ // expected then fix it there.
+ rCellStore.push_back_empty();
+ }
+
+ sc::CellTextAttrStoreType& rAttrStore = aSortedCols.at(j)->maCellTextAttrs;
+ if (rCell.mpAttr)
+ rAttrStore.push_back(*rCell.mpAttr);
+ else
+ rAttrStore.push_back_empty();
+ }
+
+ if (pArray->IsUpdateRefs())
+ {
+ // At this point each broadcaster instance is managed by 2
+ // containers. We will release those in the original storage
+ // below before transferring them to the document.
+ const SvtBroadcaster* pBroadcaster = pTable->GetBroadcaster( nCol1 + j, aOrderIndices[i]);
+ sc::BroadcasterStoreType& rBCStore = aSortedCols.at(j)->maBroadcasters;
+ if (pBroadcaster)
+ // A const pointer would be implicitly converted to a bool type.
+ rBCStore.push_back(const_cast<SvtBroadcaster*>(pBroadcaster));
+ else
+ rBCStore.push_back_empty();
+ }
+
+ // The same with cell note instances ...
+ sc::CellNoteStoreType& rNoteStore = aSortedCols.at(j)->maCellNotes;
+ if (rCell.mpNote)
+ rNoteStore.push_back(const_cast<ScPostIt*>(rCell.mpNote));
+ else
+ rNoteStore.push_back_empty();
+
+ // Add cell anchored images
+ aSortedCols.at(j)->maCellDrawObjects.push_back(rCell.maDrawObjects);
+
+ if (rCell.mpPattern)
+ aSortedCols.at(j)->setPattern(nRow, rCell.mpPattern);
+ }
+
+ if (!bOnlyDataAreaExtras && pArray->IsKeepQuery())
+ {
+ // Hidden and filtered flags are first converted to segments.
+ aRowFlags.setRowHidden(nRow, rRow.mbHidden);
+ aRowFlags.setRowFiltered(nRow, rRow.mbFiltered);
+ }
+
+ if (pProgress)
+ pProgress->SetStateOnPercent(i);
+ }
+
+ rSortedCols.swap(aSortedCols);
+ rRowFlags.swap(aRowFlags);
+}
+
+void expandRowRange( ScRange& rRange, SCROW nTop, SCROW nBottom )
+{
+ if (nTop < rRange.aStart.Row())
+ rRange.aStart.SetRow(nTop);
+
+ if (rRange.aEnd.Row() < nBottom)
+ rRange.aEnd.SetRow(nBottom);
+}
+
+class FormulaCellCollectAction : public sc::ColumnSpanSet::ColumnAction
+{
+ std::vector<ScFormulaCell*>& mrCells;
+ ScColumn* mpCol;
+
+public:
+ explicit FormulaCellCollectAction( std::vector<ScFormulaCell*>& rCells ) :
+ mrCells(rCells), mpCol(nullptr) {}
+
+ virtual void startColumn( ScColumn* pCol ) override
+ {
+ mpCol = pCol;
+ }
+
+ virtual void execute( SCROW nRow1, SCROW nRow2, bool bVal ) override
+ {
+ assert(mpCol);
+
+ if (!bVal)
+ return;
+
+ mpCol->CollectFormulaCells(mrCells, nRow1, nRow2);
+ }
+};
+
+class ListenerStartAction : public sc::ColumnSpanSet::ColumnAction
+{
+ ScColumn* mpCol;
+
+ std::shared_ptr<sc::ColumnBlockPositionSet> mpPosSet;
+ sc::StartListeningContext maStartCxt;
+ sc::EndListeningContext maEndCxt;
+
+public:
+ explicit ListenerStartAction( ScDocument& rDoc ) :
+ mpCol(nullptr),
+ mpPosSet(std::make_shared<sc::ColumnBlockPositionSet>(rDoc)),
+ maStartCxt(rDoc, mpPosSet),
+ maEndCxt(rDoc, mpPosSet) {}
+
+ virtual void startColumn( ScColumn* pCol ) override
+ {
+ mpCol = pCol;
+ }
+
+ virtual void execute( SCROW nRow1, SCROW nRow2, bool bVal ) override
+ {
+ assert(mpCol);
+
+ if (!bVal)
+ return;
+
+ mpCol->StartListeningFormulaCells(maStartCxt, maEndCxt, nRow1, nRow2);
+ }
+};
+
+}
+
+void ScTable::SortReorderAreaExtrasByRow( ScSortInfoArray* pArray,
+ SCCOL nDataCol1, SCCOL nDataCol2,
+ const ScDataAreaExtras& rDataAreaExtras, ScProgress* pProgress )
+{
+ const SCROW nRow1 = pArray->GetStart();
+ const SCROW nLastRow = pArray->GetLast();
+ const SCCOL nChunkCols = std::max<SCCOL>( 1, kSortCellsChunk / (nLastRow - nRow1 + 1));
+ // Before data area.
+ for (SCCOL nCol = rDataAreaExtras.mnStartCol; nCol < nDataCol1; nCol += nChunkCols)
+ {
+ const SCCOL nEndCol = std::min<SCCOL>( nCol + nChunkCols - 1, nDataCol1 - 1);
+ CreateColumnIfNotExists(nEndCol);
+ initDataRows( *pArray, *this, aCol, nCol, nRow1, nEndCol, nLastRow, false,
+ rDataAreaExtras.mbCellFormats, rDataAreaExtras.mbCellNotes, rDataAreaExtras.mbCellDrawObjects, true);
+ SortReorderByRow( pArray, nCol, nEndCol, pProgress, true);
+ }
+ // Behind data area.
+ for (SCCOL nCol = nDataCol2 + 1; nCol <= rDataAreaExtras.mnEndCol; nCol += nChunkCols)
+ {
+ const SCCOL nEndCol = std::min<SCCOL>( nCol + nChunkCols - 1, rDataAreaExtras.mnEndCol);
+ CreateColumnIfNotExists(nEndCol);
+ initDataRows( *pArray, *this, aCol, nCol, nRow1, nEndCol, nLastRow, false,
+ rDataAreaExtras.mbCellFormats, rDataAreaExtras.mbCellNotes, rDataAreaExtras.mbCellDrawObjects, true);
+ SortReorderByRow( pArray, nCol, nEndCol, pProgress, true);
+ }
+}
+
+void ScTable::SortReorderAreaExtrasByColumn( const ScSortInfoArray* pArray,
+ SCROW nDataRow1, SCROW nDataRow2, const ScDataAreaExtras& rDataAreaExtras, ScProgress* pProgress )
+{
+ const SCCOL nCol1 = static_cast<SCCOL>(pArray->GetStart());
+ const SCCOL nLastCol = static_cast<SCCOL>(pArray->GetLast());
+ const SCROW nChunkRows = std::max<SCROW>( 1, kSortCellsChunk / (nLastCol - nCol1 + 1));
+ // Above data area.
+ for (SCROW nRow = rDataAreaExtras.mnStartRow; nRow < nDataRow1; nRow += nChunkRows)
+ {
+ const SCROW nEndRow = std::min<SCROW>( nRow + nChunkRows - 1, nDataRow1 - 1);
+ SortReorderByColumn( pArray, nRow, nEndRow, rDataAreaExtras.mbCellFormats, pProgress);
+ }
+ // Below data area.
+ for (SCROW nRow = nDataRow2 + 1; nRow <= rDataAreaExtras.mnEndRow; nRow += nChunkRows)
+ {
+ const SCROW nEndRow = std::min<SCROW>( nRow + nChunkRows - 1, rDataAreaExtras.mnEndRow);
+ SortReorderByColumn( pArray, nRow, nEndRow, rDataAreaExtras.mbCellFormats, pProgress);
+ }
+}
+
+void ScTable::SortReorderByColumn(
+ const ScSortInfoArray* pArray, SCROW nRow1, SCROW nRow2, bool bPattern, ScProgress* pProgress )
+{
+ SCCOLROW nStart = pArray->GetStart();
+ SCCOLROW nLast = pArray->GetLast();
+
+ std::vector<SCCOLROW> aIndices = pArray->GetOrderIndices();
+ size_t nCount = aIndices.size();
+
+ // Cut formula grouping at row and reference boundaries before the reordering.
+ ScRange aSortRange(nStart, nRow1, nTab, nLast, nRow2, nTab);
+ for (SCCOL nCol = nStart; nCol <= static_cast<SCCOL>(nLast); ++nCol)
+ aCol[nCol].SplitFormulaGroupByRelativeRef(aSortRange);
+
+ // Collect all listeners of cell broadcasters of sorted range.
+ std::vector<SvtListener*> aCellListeners;
+
+ if (!pArray->IsUpdateRefs())
+ {
+ // Collect listeners of cell broadcasters.
+ for (SCCOL nCol = nStart; nCol <= static_cast<SCCOL>(nLast); ++nCol)
+ aCol[nCol].CollectListeners(aCellListeners, nRow1, nRow2);
+
+ // Remove any duplicate listener entries. We must ensure that we
+ // notify each unique listener only once.
+ std::sort(aCellListeners.begin(), aCellListeners.end());
+ aCellListeners.erase(std::unique(aCellListeners.begin(), aCellListeners.end()), aCellListeners.end());
+
+ // Notify the cells' listeners to stop listening.
+ /* TODO: for performance this could be enhanced to stop and later
+ * restart only listening to within the reordered range and keep
+ * listening to everything outside untouched. */
+ sc::RefStopListeningHint aHint;
+ for (auto const & l : aCellListeners)
+ l->Notify(aHint);
+ }
+
+ // table to keep track of column index to position in the index table.
+ std::vector<SCCOLROW> aPosTable(nCount);
+ for (size_t i = 0; i < nCount; ++i)
+ aPosTable[aIndices[i]-nStart] = i;
+
+ SCCOLROW nDest = nStart;
+ for (size_t i = 0; i < nCount; ++i, ++nDest)
+ {
+ SCCOLROW nSrc = aIndices[i];
+ if (nDest != nSrc)
+ {
+ aCol[nDest].Swap(aCol[nSrc], nRow1, nRow2, bPattern);
+
+ // Update the position of the index that was originally equal to nDest.
+ size_t nPos = aPosTable[nDest-nStart];
+ aIndices[nPos] = nSrc;
+ aPosTable[nSrc-nStart] = nPos;
+ }
+
+ if (pProgress)
+ pProgress->SetStateOnPercent(i);
+ }
+
+ // Reset formula cell positions which became out-of-sync after column reordering.
+ bool bUpdateRefs = pArray->IsUpdateRefs();
+ for (SCCOL nCol = nStart; nCol <= static_cast<SCCOL>(nLast); ++nCol)
+ aCol[nCol].ResetFormulaCellPositions(nRow1, nRow2, bUpdateRefs);
+
+ if (pArray->IsUpdateRefs())
+ {
+ // Set up column reorder map (for later broadcasting of reference updates).
+ sc::ColRowReorderMapType aColMap;
+ const std::vector<SCCOLROW>& rOldIndices = pArray->GetOrderIndices();
+ for (size_t i = 0, n = rOldIndices.size(); i < n; ++i)
+ {
+ SCCOL nNew = i + nStart;
+ SCCOL nOld = rOldIndices[i];
+ aColMap.emplace(nOld, nNew);
+ }
+
+ // Collect all listeners within sorted range ahead of time.
+ std::vector<SvtListener*> aListeners;
+
+ for (SCCOL nCol = nStart; nCol <= static_cast<SCCOL>(nLast); ++nCol)
+ aCol[nCol].CollectListeners(aListeners, nRow1, nRow2);
+
+ // Get all area listeners that listen on one column within the range
+ // and end their listening.
+ ScRange aMoveRange( nStart, nRow1, nTab, nLast, nRow2, nTab);
+ std::vector<sc::AreaListener> aAreaListeners = rDocument.GetBASM()->GetAllListeners(
+ aMoveRange, sc::AreaOverlapType::OneColumnInside);
+ {
+ for (auto& rAreaListener : aAreaListeners)
+ {
+ rDocument.EndListeningArea(rAreaListener.maArea, rAreaListener.mbGroupListening, rAreaListener.mpListener);
+ aListeners.push_back( rAreaListener.mpListener);
+ }
+ }
+
+ // Remove any duplicate listener entries and notify all listeners
+ // afterward. We must ensure that we notify each unique listener only
+ // once.
+ std::sort(aListeners.begin(), aListeners.end());
+ aListeners.erase(std::unique(aListeners.begin(), aListeners.end()), aListeners.end());
+ ReorderNotifier<sc::RefColReorderHint, sc::ColRowReorderMapType, SCCOL> aFunc(aColMap, nTab, nRow1, nRow2);
+ std::for_each(aListeners.begin(), aListeners.end(), aFunc);
+
+ // Re-start area listeners on the reordered columns.
+ {
+ for (auto& rAreaListener : aAreaListeners)
+ {
+ ScRange aNewRange = rAreaListener.maArea;
+ sc::ColRowReorderMapType::const_iterator itCol = aColMap.find( aNewRange.aStart.Col());
+ if (itCol != aColMap.end())
+ {
+ aNewRange.aStart.SetCol( itCol->second);
+ aNewRange.aEnd.SetCol( itCol->second);
+ }
+ rDocument.StartListeningArea(aNewRange, rAreaListener.mbGroupListening, rAreaListener.mpListener);
+ }
+ }
+ }
+ else // !(pArray->IsUpdateRefs())
+ {
+ // Notify the cells' listeners to (re-)start listening.
+ sc::RefStartListeningHint aHint;
+ for (auto const & l : aCellListeners)
+ l->Notify(aHint);
+ }
+
+ // Re-join formulas at row boundaries now that all the references have
+ // been adjusted for column reordering.
+ for (SCCOL nCol = nStart; nCol <= static_cast<SCCOL>(nLast); ++nCol)
+ {
+ sc::CellStoreType& rCells = aCol[nCol].maCells;
+ sc::CellStoreType::position_type aPos = rCells.position(nRow1);
+ sc::SharedFormulaUtil::joinFormulaCellAbove(aPos);
+ if (nRow2 < rDocument.MaxRow())
+ {
+ aPos = rCells.position(aPos.first, nRow2+1);
+ sc::SharedFormulaUtil::joinFormulaCellAbove(aPos);
+ }
+ }
+}
+
+void ScTable::SortReorderByRow( ScSortInfoArray* pArray, SCCOL nCol1, SCCOL nCol2,
+ ScProgress* pProgress, bool bOnlyDataAreaExtras )
+{
+ assert(!pArray->IsUpdateRefs());
+
+ if (nCol2 < nCol1)
+ return;
+
+ // bOnlyDataAreaExtras:
+ // Data area extras by definition do not have any cell content so no
+ // formula cells either, so that handling doesn't need to be executed.
+ // However, there may be listeners of formulas listening to broadcasters of
+ // empty cells.
+
+ SCROW nRow1 = pArray->GetStart();
+ SCROW nRow2 = pArray->GetLast();
+
+ // Collect all listeners of cell broadcasters of sorted range.
+ std::vector<SvtListener*> aCellListeners;
+
+ // When the update ref mode is disabled, we need to detach all formula
+ // cells in the sorted range before reordering, and re-start them
+ // afterward.
+ if (!bOnlyDataAreaExtras)
+ {
+ sc::EndListeningContext aCxt(rDocument);
+ DetachFormulaCells(aCxt, nCol1, nRow1, nCol2, nRow2);
+ }
+
+ // Collect listeners of cell broadcasters.
+ for (SCCOL nCol = nCol1; nCol <= nCol2; ++nCol)
+ aCol[nCol].CollectListeners(aCellListeners, nRow1, nRow2);
+
+ // Remove any duplicate listener entries. We must ensure that we notify
+ // each unique listener only once.
+ std::sort(aCellListeners.begin(), aCellListeners.end());
+ aCellListeners.erase(std::unique(aCellListeners.begin(), aCellListeners.end()), aCellListeners.end());
+
+ // Notify the cells' listeners to stop listening.
+ /* TODO: for performance this could be enhanced to stop and later
+ * restart only listening to within the reordered range and keep
+ * listening to everything outside untouched. */
+ {
+ sc::RefStopListeningHint aHint;
+ for (auto const & l : aCellListeners)
+ l->Notify(aHint);
+ }
+
+ // Split formula groups at the sort range boundaries (if applicable).
+ if (!bOnlyDataAreaExtras)
+ {
+ std::vector<SCROW> aRowBounds
+ {
+ nRow1,
+ nRow2+1
+ };
+ for (SCCOL nCol = nCol1; nCol <= nCol2; ++nCol)
+ SplitFormulaGroups(nCol, aRowBounds);
+ }
+
+ // Cells in the data rows only reference values in the document. Make
+ // a copy before updating the document.
+ std::vector<std::unique_ptr<SortedColumn>> aSortedCols; // storage for copied cells.
+ SortedRowFlags aRowFlags(GetDoc().GetSheetLimits());
+ fillSortedColumnArray(aSortedCols, aRowFlags, aCellListeners, pArray, nTab, nCol1, nCol2,
+ pProgress, this, bOnlyDataAreaExtras);
+
+ for (size_t i = 0, n = aSortedCols.size(); i < n; ++i)
+ {
+ SCCOL nThisCol = i + nCol1;
+
+ if (!bOnlyDataAreaExtras)
+ {
+ {
+ sc::CellStoreType& rDest = aCol[nThisCol].maCells;
+ sc::CellStoreType& rSrc = aSortedCols[i]->maCells;
+ rSrc.transfer(nRow1, nRow2, rDest, nRow1);
+ }
+
+ {
+ sc::CellTextAttrStoreType& rDest = aCol[nThisCol].maCellTextAttrs;
+ sc::CellTextAttrStoreType& rSrc = aSortedCols[i]->maCellTextAttrs;
+ rSrc.transfer(nRow1, nRow2, rDest, nRow1);
+ }
+ }
+
+ {
+ sc::CellNoteStoreType& rSrc = aSortedCols[i]->maCellNotes;
+ sc::CellNoteStoreType& rDest = aCol[nThisCol].maCellNotes;
+
+ // Do the same as broadcaster storage transfer (to prevent double deletion).
+ rDest.release_range(nRow1, nRow2);
+ rSrc.transfer(nRow1, nRow2, rDest, nRow1);
+ aCol[nThisCol].UpdateNoteCaptions(nRow1, nRow2);
+ }
+
+ // Update draw object positions
+ aCol[nThisCol].UpdateDrawObjects(aSortedCols[i]->maCellDrawObjects, nRow1, nRow2);
+
+ {
+ // Get all row spans where the pattern is not NULL.
+ std::vector<PatternSpan> aSpans =
+ sc::toSpanArrayWithValue<SCROW,const ScPatternAttr*,PatternSpan>(
+ aSortedCols[i]->maPatterns);
+
+ for (const auto& rSpan : aSpans)
+ {
+ assert(rSpan.mpPattern); // should never be NULL.
+ rDocument.GetPool()->Put(*rSpan.mpPattern);
+ }
+
+ for (const auto& rSpan : aSpans)
+ {
+ aCol[nThisCol].SetPatternArea(rSpan.mnRow1, rSpan.mnRow2, *rSpan.mpPattern);
+ rDocument.GetPool()->Remove(*rSpan.mpPattern);
+ }
+ }
+
+ aCol[nThisCol].CellStorageModified();
+ }
+
+ if (!bOnlyDataAreaExtras && pArray->IsKeepQuery())
+ {
+ aRowFlags.maRowsHidden.build_tree();
+ aRowFlags.maRowsFiltered.build_tree();
+
+ // Remove all flags in the range first.
+ SetRowHidden(nRow1, nRow2, false);
+ SetRowFiltered(nRow1, nRow2, false);
+
+ std::vector<sc::RowSpan> aSpans =
+ sc::toSpanArray<SCROW,sc::RowSpan>(aRowFlags.maRowsHidden, nRow1);
+
+ for (const auto& rSpan : aSpans)
+ SetRowHidden(rSpan.mnRow1, rSpan.mnRow2, true);
+
+ aSpans = sc::toSpanArray<SCROW,sc::RowSpan>(aRowFlags.maRowsFiltered, nRow1);
+
+ for (const auto& rSpan : aSpans)
+ SetRowFiltered(rSpan.mnRow1, rSpan.mnRow2, true);
+ }
+
+ // Notify the cells' listeners to (re-)start listening.
+ {
+ sc::RefStartListeningHint aHint;
+ for (auto const & l : aCellListeners)
+ l->Notify(aHint);
+ }
+
+ if (!bOnlyDataAreaExtras)
+ {
+ // Re-group columns in the sorted range too.
+ for (SCCOL i = nCol1; i <= nCol2; ++i)
+ aCol[i].RegroupFormulaCells();
+
+ {
+ sc::StartListeningContext aCxt(rDocument);
+ AttachFormulaCells(aCxt, nCol1, nRow1, nCol2, nRow2);
+ }
+ }
+}
+
+void ScTable::SortReorderByRowRefUpdate(
+ ScSortInfoArray* pArray, SCCOL nCol1, SCCOL nCol2, ScProgress* pProgress )
+{
+ assert(pArray->IsUpdateRefs());
+
+ if (nCol2 < nCol1)
+ return;
+
+ SCROW nRow1 = pArray->GetStart();
+ SCROW nRow2 = pArray->GetLast();
+
+ ScRange aMoveRange( nCol1, nRow1, nTab, nCol2, nRow2, nTab);
+ sc::ColumnSpanSet aGrpListenerRanges;
+
+ {
+ // Get the range of formula group listeners within sorted range (if any).
+ sc::QueryRange aQuery;
+
+ ScBroadcastAreaSlotMachine* pBASM = rDocument.GetBASM();
+ std::vector<sc::AreaListener> aGrpListeners =
+ pBASM->GetAllListeners(
+ aMoveRange, sc::AreaOverlapType::InsideOrOverlap, sc::ListenerGroupType::Group);
+
+ {
+ for (const auto& rGrpListener : aGrpListeners)
+ {
+ assert(rGrpListener.mbGroupListening);
+ SvtListener* pGrpLis = rGrpListener.mpListener;
+ pGrpLis->Query(aQuery);
+ rDocument.EndListeningArea(rGrpListener.maArea, rGrpListener.mbGroupListening, pGrpLis);
+ }
+ }
+
+ ScRangeList aTmp;
+ aQuery.swapRanges(aTmp);
+
+ // If the range is within the sorted range, we need to expand its rows
+ // to the top and bottom of the sorted range, since the formula cells
+ // could be anywhere in the sorted range after reordering.
+ for (size_t i = 0, n = aTmp.size(); i < n; ++i)
+ {
+ ScRange aRange = aTmp[i];
+ if (!aMoveRange.Intersects(aRange))
+ {
+ // Doesn't overlap with the sorted range at all.
+ aGrpListenerRanges.set(GetDoc(), aRange, true);
+ continue;
+ }
+
+ if (aMoveRange.aStart.Col() <= aRange.aStart.Col() && aRange.aEnd.Col() <= aMoveRange.aEnd.Col())
+ {
+ // Its column range is within the column range of the sorted range.
+ expandRowRange(aRange, aMoveRange.aStart.Row(), aMoveRange.aEnd.Row());
+ aGrpListenerRanges.set(GetDoc(), aRange, true);
+ continue;
+ }
+
+ // It intersects with the sorted range, but its column range is
+ // not within the column range of the sorted range. Split it into
+ // 2 ranges.
+ ScRange aR1 = aRange;
+ ScRange aR2 = aRange;
+ if (aRange.aStart.Col() < aMoveRange.aStart.Col())
+ {
+ // Left half is outside the sorted range while the right half is inside.
+ aR1.aEnd.SetCol(aMoveRange.aStart.Col()-1);
+ aR2.aStart.SetCol(aMoveRange.aStart.Col());
+ expandRowRange(aR2, aMoveRange.aStart.Row(), aMoveRange.aEnd.Row());
+ }
+ else
+ {
+ // Left half is inside the sorted range while the right half is outside.
+ aR1.aEnd.SetCol(aMoveRange.aEnd.Col()-1);
+ aR2.aStart.SetCol(aMoveRange.aEnd.Col());
+ expandRowRange(aR1, aMoveRange.aStart.Row(), aMoveRange.aEnd.Row());
+ }
+
+ aGrpListenerRanges.set(GetDoc(), aR1, true);
+ aGrpListenerRanges.set(GetDoc(), aR2, true);
+ }
+ }
+
+ // Split formula groups at the sort range boundaries (if applicable).
+ std::vector<SCROW> aRowBounds
+ {
+ nRow1,
+ nRow2+1
+ };
+ for (SCCOL nCol = nCol1; nCol <= nCol2; ++nCol)
+ SplitFormulaGroups(nCol, aRowBounds);
+
+ // Cells in the data rows only reference values in the document. Make
+ // a copy before updating the document.
+ std::vector<std::unique_ptr<SortedColumn>> aSortedCols; // storage for copied cells.
+ SortedRowFlags aRowFlags(GetDoc().GetSheetLimits());
+ std::vector<SvtListener*> aListenersDummy;
+ fillSortedColumnArray(aSortedCols, aRowFlags, aListenersDummy, pArray, nTab, nCol1, nCol2, pProgress, this, false);
+
+ for (size_t i = 0, n = aSortedCols.size(); i < n; ++i)
+ {
+ SCCOL nThisCol = i + nCol1;
+
+ {
+ sc::CellStoreType& rDest = aCol[nThisCol].maCells;
+ sc::CellStoreType& rSrc = aSortedCols[i]->maCells;
+ rSrc.transfer(nRow1, nRow2, rDest, nRow1);
+ }
+
+ {
+ sc::CellTextAttrStoreType& rDest = aCol[nThisCol].maCellTextAttrs;
+ sc::CellTextAttrStoreType& rSrc = aSortedCols[i]->maCellTextAttrs;
+ rSrc.transfer(nRow1, nRow2, rDest, nRow1);
+ }
+
+ {
+ sc::BroadcasterStoreType& rSrc = aSortedCols[i]->maBroadcasters;
+ sc::BroadcasterStoreType& rDest = aCol[nThisCol].maBroadcasters;
+
+ // Release current broadcasters first, to prevent them from getting deleted.
+ rDest.release_range(nRow1, nRow2);
+
+ // Transfer sorted broadcaster segment to the document.
+ rSrc.transfer(nRow1, nRow2, rDest, nRow1);
+ }
+
+ {
+ sc::CellNoteStoreType& rSrc = aSortedCols[i]->maCellNotes;
+ sc::CellNoteStoreType& rDest = aCol[nThisCol].maCellNotes;
+
+ // Do the same as broadcaster storage transfer (to prevent double deletion).
+ rDest.release_range(nRow1, nRow2);
+ rSrc.transfer(nRow1, nRow2, rDest, nRow1);
+ aCol[nThisCol].UpdateNoteCaptions(nRow1, nRow2);
+ }
+
+ // Update draw object positions
+ aCol[nThisCol].UpdateDrawObjects(aSortedCols[i]->maCellDrawObjects, nRow1, nRow2);
+
+ {
+ // Get all row spans where the pattern is not NULL.
+ std::vector<PatternSpan> aSpans =
+ sc::toSpanArrayWithValue<SCROW,const ScPatternAttr*,PatternSpan>(
+ aSortedCols[i]->maPatterns);
+
+ for (const auto& rSpan : aSpans)
+ {
+ assert(rSpan.mpPattern); // should never be NULL.
+ rDocument.GetPool()->Put(*rSpan.mpPattern);
+ }
+
+ for (const auto& rSpan : aSpans)
+ {
+ aCol[nThisCol].SetPatternArea(rSpan.mnRow1, rSpan.mnRow2, *rSpan.mpPattern);
+ rDocument.GetPool()->Remove(*rSpan.mpPattern);
+ }
+ }
+
+ aCol[nThisCol].CellStorageModified();
+ }
+
+ if (pArray->IsKeepQuery())
+ {
+ aRowFlags.maRowsHidden.build_tree();
+ aRowFlags.maRowsFiltered.build_tree();
+
+ // Remove all flags in the range first.
+ SetRowHidden(nRow1, nRow2, false);
+ SetRowFiltered(nRow1, nRow2, false);
+
+ std::vector<sc::RowSpan> aSpans =
+ sc::toSpanArray<SCROW,sc::RowSpan>(aRowFlags.maRowsHidden, nRow1);
+
+ for (const auto& rSpan : aSpans)
+ SetRowHidden(rSpan.mnRow1, rSpan.mnRow2, true);
+
+ aSpans = sc::toSpanArray<SCROW,sc::RowSpan>(aRowFlags.maRowsFiltered, nRow1);
+
+ for (const auto& rSpan : aSpans)
+ SetRowFiltered(rSpan.mnRow1, rSpan.mnRow2, true);
+ }
+
+ // Set up row reorder map (for later broadcasting of reference updates).
+ sc::ColRowReorderMapType aRowMap;
+ const std::vector<SCCOLROW>& rOldIndices = pArray->GetOrderIndices();
+ for (size_t i = 0, n = rOldIndices.size(); i < n; ++i)
+ {
+ SCROW nNew = i + nRow1;
+ SCROW nOld = rOldIndices[i];
+ aRowMap.emplace(nOld, nNew);
+ }
+
+ // Collect all listeners within sorted range ahead of time.
+ std::vector<SvtListener*> aListeners;
+
+ // Collect listeners of cell broadcasters.
+ for (SCCOL nCol = nCol1; nCol <= nCol2; ++nCol)
+ aCol[nCol].CollectListeners(aListeners, nRow1, nRow2);
+
+ // Get all area listeners that listen on one row within the range and end
+ // their listening.
+ std::vector<sc::AreaListener> aAreaListeners = rDocument.GetBASM()->GetAllListeners(
+ aMoveRange, sc::AreaOverlapType::OneRowInside);
+ {
+ for (auto& rAreaListener : aAreaListeners)
+ {
+ rDocument.EndListeningArea(rAreaListener.maArea, rAreaListener.mbGroupListening, rAreaListener.mpListener);
+ aListeners.push_back( rAreaListener.mpListener);
+ }
+ }
+
+ {
+ // Get all formula cells from the former group area listener ranges.
+
+ std::vector<ScFormulaCell*> aFCells;
+ FormulaCellCollectAction aAction(aFCells);
+ aGrpListenerRanges.executeColumnAction(rDocument, aAction);
+
+ aListeners.insert( aListeners.end(), aFCells.begin(), aFCells.end() );
+ }
+
+ // Remove any duplicate listener entries. We must ensure that we notify
+ // each unique listener only once.
+ std::sort(aListeners.begin(), aListeners.end());
+ aListeners.erase(std::unique(aListeners.begin(), aListeners.end()), aListeners.end());
+
+ // Collect positions of all shared formula cells outside the sorted range,
+ // and make them unshared before notifying them.
+ sc::RefQueryFormulaGroup aFormulaGroupPos;
+ aFormulaGroupPos.setSkipRange(ScRange(nCol1, nRow1, nTab, nCol2, nRow2, nTab));
+
+ std::for_each(aListeners.begin(), aListeners.end(), FormulaGroupPosCollector(aFormulaGroupPos));
+ const sc::RefQueryFormulaGroup::TabsType& rGroupTabs = aFormulaGroupPos.getAllPositions();
+ for (const auto& [rTab, rCols] : rGroupTabs)
+ {
+ for (const auto& [nCol, rCol] : rCols)
+ {
+ std::vector<SCROW> aBounds(rCol);
+ rDocument.UnshareFormulaCells(rTab, nCol, aBounds);
+ }
+ }
+
+ // Notify the listeners to update their references.
+ ReorderNotifier<sc::RefRowReorderHint, sc::ColRowReorderMapType, SCROW> aFunc(aRowMap, nTab, nCol1, nCol2);
+ std::for_each(aListeners.begin(), aListeners.end(), aFunc);
+
+ // Re-group formulas in affected columns.
+ for (const auto& [rTab, rCols] : rGroupTabs)
+ {
+ for (const auto& rEntry : rCols)
+ rDocument.RegroupFormulaCells(rTab, rEntry.first);
+ }
+
+ // Re-start area listeners on the reordered rows.
+ for (const auto& rAreaListener : aAreaListeners)
+ {
+ ScRange aNewRange = rAreaListener.maArea;
+ sc::ColRowReorderMapType::const_iterator itRow = aRowMap.find( aNewRange.aStart.Row());
+ if (itRow != aRowMap.end())
+ {
+ aNewRange.aStart.SetRow( itRow->second);
+ aNewRange.aEnd.SetRow( itRow->second);
+ }
+ rDocument.StartListeningArea(aNewRange, rAreaListener.mbGroupListening, rAreaListener.mpListener);
+ }
+
+ // Re-group columns in the sorted range too.
+ for (SCCOL i = nCol1; i <= nCol2; ++i)
+ aCol[i].RegroupFormulaCells();
+
+ {
+ // Re-start area listeners on the old group listener ranges.
+ ListenerStartAction aAction(rDocument);
+ aGrpListenerRanges.executeColumnAction(rDocument, aAction);
+ }
+}
+
+short ScTable::CompareCell(
+ sal_uInt16 nSort,
+ ScRefCellValue& rCell1, SCCOL nCell1Col, SCROW nCell1Row,
+ ScRefCellValue& rCell2, SCCOL nCell2Col, SCROW nCell2Row ) const
+{
+ short nRes = 0;
+
+ CellType eType1 = rCell1.meType, eType2 = rCell2.meType;
+
+ if (!rCell1.isEmpty())
+ {
+ if (!rCell2.isEmpty())
+ {
+ bool bErr1 = false;
+ bool bStr1 = ( eType1 != CELLTYPE_VALUE );
+ if (eType1 == CELLTYPE_FORMULA)
+ {
+ if (rCell1.mpFormula->GetErrCode() != FormulaError::NONE)
+ {
+ bErr1 = true;
+ bStr1 = false;
+ }
+ else if (rCell1.mpFormula->IsValue())
+ {
+ bStr1 = false;
+ }
+ }
+
+ bool bErr2 = false;
+ bool bStr2 = ( eType2 != CELLTYPE_VALUE );
+ if (eType2 == CELLTYPE_FORMULA)
+ {
+ if (rCell2.mpFormula->GetErrCode() != FormulaError::NONE)
+ {
+ bErr2 = true;
+ bStr2 = false;
+ }
+ else if (rCell2.mpFormula->IsValue())
+ {
+ bStr2 = false;
+ }
+ }
+
+ if ( bStr1 && bStr2 ) // only compare strings as strings!
+ {
+ OUString aStr1;
+ OUString aStr2;
+ if (eType1 == CELLTYPE_STRING)
+ aStr1 = rCell1.mpString->getString();
+ else
+ aStr1 = GetString(nCell1Col, nCell1Row);
+ if (eType2 == CELLTYPE_STRING)
+ aStr2 = rCell2.mpString->getString();
+ else
+ aStr2 = GetString(nCell2Col, nCell2Row);
+
+ bool bUserDef = aSortParam.bUserDef; // custom sort order
+ bool bNaturalSort = aSortParam.bNaturalSort; // natural sort
+ bool bCaseSens = aSortParam.bCaseSens; // case sensitivity
+
+ ScUserList* pList = ScGlobal::GetUserList();
+ if (bUserDef && pList && pList->size() > aSortParam.nUserIndex )
+ {
+ const ScUserListData& rData = (*pList)[aSortParam.nUserIndex];
+
+ if ( bNaturalSort )
+ nRes = naturalsort::Compare( aStr1, aStr2, bCaseSens, &rData, pSortCollator );
+ else
+ {
+ if ( bCaseSens )
+ nRes = sal::static_int_cast<short>( rData.Compare(aStr1, aStr2) );
+ else
+ nRes = sal::static_int_cast<short>( rData.ICompare(aStr1, aStr2) );
+ }
+
+ }
+ if (!bUserDef)
+ {
+ if ( bNaturalSort )
+ nRes = naturalsort::Compare( aStr1, aStr2, bCaseSens, nullptr, pSortCollator );
+ else
+ nRes = static_cast<short>( pSortCollator->compareString( aStr1, aStr2 ) );
+ }
+ }
+ else if ( bStr1 ) // String <-> Number or Error
+ {
+ if (bErr2)
+ nRes = -1; // String in front of Error
+ else
+ nRes = 1; // Number in front of String
+ }
+ else if ( bStr2 ) // Number or Error <-> String
+ {
+ if (bErr1)
+ nRes = 1; // String in front of Error
+ else
+ nRes = -1; // Number in front of String
+ }
+ else if (bErr1 && bErr2)
+ {
+ // nothing, two Errors are equal
+ }
+ else if (bErr1) // Error <-> Number
+ {
+ nRes = 1; // Number in front of Error
+ }
+ else if (bErr2) // Number <-> Error
+ {
+ nRes = -1; // Number in front of Error
+ }
+ else // Mixed numbers
+ {
+ double nVal1 = rCell1.getValue();
+ double nVal2 = rCell2.getValue();
+ if (nVal1 < nVal2)
+ nRes = -1;
+ else if (nVal1 > nVal2)
+ nRes = 1;
+ }
+ if ( !aSortParam.maKeyState[nSort].bAscending )
+ nRes = -nRes;
+ }
+ else
+ nRes = -1;
+ }
+ else
+ {
+ if (!rCell2.isEmpty())
+ nRes = 1;
+ else
+ nRes = 0; // both empty
+ }
+ return nRes;
+}
+
+short ScTable::Compare( ScSortInfoArray* pArray, SCCOLROW nIndex1, SCCOLROW nIndex2 ) const
+{
+ short nRes;
+ sal_uInt16 nSort = 0;
+ do
+ {
+ ScSortInfo& rInfo1 = pArray->Get( nSort, nIndex1 );
+ ScSortInfo& rInfo2 = pArray->Get( nSort, nIndex2 );
+ if ( aSortParam.bByRow )
+ nRes = CompareCell( nSort,
+ rInfo1.maCell, static_cast<SCCOL>(aSortParam.maKeyState[nSort].nField), rInfo1.nOrg,
+ rInfo2.maCell, static_cast<SCCOL>(aSortParam.maKeyState[nSort].nField), rInfo2.nOrg );
+ else
+ nRes = CompareCell( nSort,
+ rInfo1.maCell, static_cast<SCCOL>(rInfo1.nOrg), aSortParam.maKeyState[nSort].nField,
+ rInfo2.maCell, static_cast<SCCOL>(rInfo2.nOrg), aSortParam.maKeyState[nSort].nField );
+ } while ( nRes == 0 && ++nSort < pArray->GetUsedSorts() );
+ if( nRes == 0 )
+ {
+ ScSortInfo& rInfo1 = pArray->Get( 0, nIndex1 );
+ ScSortInfo& rInfo2 = pArray->Get( 0, nIndex2 );
+ if( rInfo1.nOrg < rInfo2.nOrg )
+ nRes = -1;
+ else if( rInfo1.nOrg > rInfo2.nOrg )
+ nRes = 1;
+ }
+ return nRes;
+}
+
+void ScTable::QuickSort( ScSortInfoArray* pArray, SCCOLROW nLo, SCCOLROW nHi )
+{
+ if ((nHi - nLo) == 1)
+ {
+ if (Compare(pArray, nLo, nHi) > 0)
+ pArray->Swap( nLo, nHi );
+ }
+ else
+ {
+ SCCOLROW ni = nLo;
+ SCCOLROW nj = nHi;
+ do
+ {
+ while ((ni <= nHi) && (Compare(pArray, ni, nLo)) < 0)
+ ni++;
+ while ((nj >= nLo) && (Compare(pArray, nLo, nj)) < 0)
+ nj--;
+ if (ni <= nj)
+ {
+ if (ni != nj)
+ pArray->Swap( ni, nj );
+ ni++;
+ nj--;
+ }
+ } while (ni < nj);
+ if ((nj - nLo) < (nHi - ni))
+ {
+ if (nLo < nj)
+ QuickSort(pArray, nLo, nj);
+ if (ni < nHi)
+ QuickSort(pArray, ni, nHi);
+ }
+ else
+ {
+ if (ni < nHi)
+ QuickSort(pArray, ni, nHi);
+ if (nLo < nj)
+ QuickSort(pArray, nLo, nj);
+ }
+ }
+}
+
+short ScTable::Compare(SCCOLROW nIndex1, SCCOLROW nIndex2) const
+{
+ short nRes;
+ sal_uInt16 nSort = 0;
+ const sal_uInt32 nMaxSorts = aSortParam.GetSortKeyCount();
+ if (aSortParam.bByRow)
+ {
+ do
+ {
+ SCCOL nCol = static_cast<SCCOL>(aSortParam.maKeyState[nSort].nField);
+ nRes = 0;
+ if(nCol < GetAllocatedColumnsCount())
+ {
+ ScRefCellValue aCell1 = aCol[nCol].GetCellValue(nIndex1);
+ ScRefCellValue aCell2 = aCol[nCol].GetCellValue(nIndex2);
+ nRes = CompareCell(nSort, aCell1, nCol, nIndex1, aCell2, nCol, nIndex2);
+ }
+ } while ( nRes == 0 && ++nSort < nMaxSorts && aSortParam.maKeyState[nSort].bDoSort );
+ }
+ else
+ {
+ do
+ {
+ SCROW nRow = aSortParam.maKeyState[nSort].nField;
+ ScRefCellValue aCell1;
+ ScRefCellValue aCell2;
+ if(nIndex1 < GetAllocatedColumnsCount())
+ aCell1 = aCol[nIndex1].GetCellValue(nRow);
+ if(nIndex2 < GetAllocatedColumnsCount())
+ aCell2 = aCol[nIndex2].GetCellValue(nRow);
+ nRes = CompareCell( nSort, aCell1, static_cast<SCCOL>(nIndex1),
+ nRow, aCell2, static_cast<SCCOL>(nIndex2), nRow );
+ } while ( nRes == 0 && ++nSort < nMaxSorts && aSortParam.maKeyState[nSort].bDoSort );
+ }
+ return nRes;
+}
+
+bool ScTable::IsSorted( SCCOLROW nStart, SCCOLROW nEnd ) const // over aSortParam
+{
+ for (SCCOLROW i=nStart; i<nEnd; i++)
+ {
+ if (Compare( i, i+1 ) > 0)
+ return false;
+ }
+ return true;
+}
+
+void ScTable::DecoladeRow( ScSortInfoArray* pArray, SCROW nRow1, SCROW nRow2 )
+{
+ SCROW nRow;
+ int nMax = nRow2 - nRow1;
+ for (SCROW i = nRow1; (i + 4) <= nRow2; i += 4)
+ {
+ nRow = comphelper::rng::uniform_int_distribution(0, nMax-1);
+ pArray->Swap(i, nRow1 + nRow);
+ }
+}
+
+void ScTable::Sort(
+ const ScSortParam& rSortParam, bool bKeepQuery, bool bUpdateRefs,
+ ScProgress* pProgress, sc::ReorderParam* pUndo )
+{
+ sc::DelayDeletingBroadcasters delayDeletingBroadcasters(GetDoc());
+ InitSortCollator( rSortParam );
+ bGlobalKeepQuery = bKeepQuery;
+
+ if (pUndo)
+ {
+ // Copy over the basic sort parameters.
+ pUndo->maDataAreaExtras = rSortParam.aDataAreaExtras;
+ pUndo->mbByRow = rSortParam.bByRow;
+ pUndo->mbHiddenFiltered = bKeepQuery;
+ pUndo->mbUpdateRefs = bUpdateRefs;
+ pUndo->mbHasHeaders = rSortParam.bHasHeader;
+ }
+
+ // It is assumed that the data area has already been trimmed as necessary.
+
+ aSortParam = rSortParam; // must be assigned before calling IsSorted()
+ if (rSortParam.bByRow)
+ {
+ const SCROW nLastRow = rSortParam.nRow2;
+ const SCROW nRow1 = (rSortParam.bHasHeader ? rSortParam.nRow1 + 1 : rSortParam.nRow1);
+ if (nRow1 < nLastRow && !IsSorted(nRow1, nLastRow))
+ {
+ if(pProgress)
+ pProgress->SetState( 0, nLastRow-nRow1 );
+
+ std::unique_ptr<ScSortInfoArray> pArray( CreateSortInfoArray(
+ aSortParam, nRow1, nLastRow, bKeepQuery, bUpdateRefs));
+
+ if ( nLastRow - nRow1 > 255 )
+ DecoladeRow(pArray.get(), nRow1, nLastRow);
+
+ QuickSort(pArray.get(), nRow1, nLastRow);
+ if (pArray->IsUpdateRefs())
+ SortReorderByRowRefUpdate(pArray.get(), aSortParam.nCol1, aSortParam.nCol2, pProgress);
+ else
+ {
+ SortReorderByRow(pArray.get(), aSortParam.nCol1, aSortParam.nCol2, pProgress, false);
+ if (rSortParam.aDataAreaExtras.anyExtrasWanted())
+ SortReorderAreaExtrasByRow( pArray.get(), aSortParam.nCol1, aSortParam.nCol2,
+ rSortParam.aDataAreaExtras, pProgress);
+ }
+
+ if (pUndo)
+ {
+ // Stored is the first data row without header row.
+ pUndo->maSortRange = ScRange(rSortParam.nCol1, nRow1, nTab, rSortParam.nCol2, nLastRow, nTab);
+ pUndo->maDataAreaExtras.mnStartRow = nRow1;
+ pUndo->maOrderIndices = pArray->GetOrderIndices();
+ }
+ }
+ }
+ else
+ {
+ const SCCOL nLastCol = rSortParam.nCol2;
+ const SCCOL nCol1 = (rSortParam.bHasHeader ? rSortParam.nCol1 + 1 : rSortParam.nCol1);
+ if (nCol1 < nLastCol && !IsSorted(nCol1, nLastCol))
+ {
+ if(pProgress)
+ pProgress->SetState( 0, nLastCol-nCol1 );
+
+ std::unique_ptr<ScSortInfoArray> pArray( CreateSortInfoArray(
+ aSortParam, nCol1, nLastCol, bKeepQuery, bUpdateRefs));
+
+ QuickSort(pArray.get(), nCol1, nLastCol);
+ SortReorderByColumn(pArray.get(), rSortParam.nRow1, rSortParam.nRow2,
+ rSortParam.aDataAreaExtras.mbCellFormats, pProgress);
+ if (rSortParam.aDataAreaExtras.anyExtrasWanted() && !pArray->IsUpdateRefs())
+ SortReorderAreaExtrasByColumn( pArray.get(),
+ rSortParam.nRow1, rSortParam.nRow2, rSortParam.aDataAreaExtras, pProgress);
+
+ if (pUndo)
+ {
+ // Stored is the first data column without header column.
+ pUndo->maSortRange = ScRange(nCol1, aSortParam.nRow1, nTab, nLastCol, aSortParam.nRow2, nTab);
+ pUndo->maDataAreaExtras.mnStartCol = nCol1;
+ pUndo->maOrderIndices = pArray->GetOrderIndices();
+ }
+ }
+ }
+ DestroySortCollator();
+}
+
+void ScTable::Reorder( const sc::ReorderParam& rParam )
+{
+ if (rParam.maOrderIndices.empty())
+ return;
+
+ std::unique_ptr<ScSortInfoArray> pArray(CreateSortInfoArray(rParam));
+ if (!pArray)
+ return;
+
+ if (rParam.mbByRow)
+ {
+ // Re-play sorting from the known sort indices.
+ pArray->ReorderByRow(rParam.maOrderIndices);
+ if (pArray->IsUpdateRefs())
+ SortReorderByRowRefUpdate(
+ pArray.get(), rParam.maSortRange.aStart.Col(), rParam.maSortRange.aEnd.Col(), nullptr);
+ else
+ {
+ SortReorderByRow( pArray.get(),
+ rParam.maSortRange.aStart.Col(), rParam.maSortRange.aEnd.Col(), nullptr, false);
+ if (rParam.maDataAreaExtras.anyExtrasWanted())
+ SortReorderAreaExtrasByRow( pArray.get(),
+ rParam.maSortRange.aStart.Col(), rParam.maSortRange.aEnd.Col(),
+ rParam.maDataAreaExtras, nullptr);
+ }
+ }
+ else
+ {
+ // Ordering by column is much simpler. Just set the order indices and we are done.
+ pArray->SetOrderIndices(std::vector(rParam.maOrderIndices));
+ SortReorderByColumn(
+ pArray.get(), rParam.maSortRange.aStart.Row(), rParam.maSortRange.aEnd.Row(),
+ rParam.maDataAreaExtras.mbCellFormats, nullptr);
+ if (rParam.maDataAreaExtras.anyExtrasWanted() && !pArray->IsUpdateRefs())
+ SortReorderAreaExtrasByColumn( pArray.get(),
+ rParam.maSortRange.aStart.Row(), rParam.maSortRange.aEnd.Row(),
+ rParam.maDataAreaExtras, nullptr);
+ }
+}
+
+namespace {
+
+class SubTotalRowFinder
+{
+ const ScTable& mrTab;
+ const ScSubTotalParam& mrParam;
+
+public:
+ SubTotalRowFinder(const ScTable& rTab, const ScSubTotalParam& rParam) :
+ mrTab(rTab), mrParam(rParam) {}
+
+ bool operator() (size_t nRow, const ScFormulaCell* pCell)
+ {
+ if (!pCell->IsSubTotal())
+ return false;
+
+ SCCOL nStartCol = mrParam.nCol1;
+ SCCOL nEndCol = mrParam.nCol2;
+
+ for (SCCOL nCol : mrTab.GetAllocatedColumnsRange(0, nStartCol - 1))
+ {
+ if (mrTab.HasData(nCol, nRow))
+ return true;
+ }
+ for (SCCOL nCol : mrTab.GetAllocatedColumnsRange(nEndCol + 1, mrTab.GetDoc().MaxCol()))
+ {
+ if (mrTab.HasData(nCol, nRow))
+ return true;
+ }
+ return false;
+ }
+};
+
+}
+
+bool ScTable::TestRemoveSubTotals( const ScSubTotalParam& rParam )
+{
+ SCCOL nStartCol = rParam.nCol1;
+ SCROW nStartRow = rParam.nRow1 + 1; // Header
+ SCCOL nEndCol = ClampToAllocatedColumns(rParam.nCol2);
+ SCROW nEndRow = rParam.nRow2;
+
+ for (SCCOL nCol = nStartCol; nCol <= nEndCol; ++nCol)
+ {
+ const sc::CellStoreType& rCells = aCol[nCol].maCells;
+ SubTotalRowFinder aFunc(*this, rParam);
+ std::pair<sc::CellStoreType::const_iterator,size_t> aPos =
+ sc::FindFormula(rCells, nStartRow, nEndRow, aFunc);
+ if (aPos.first != rCells.end())
+ return true;
+ }
+ return false;
+}
+
+namespace {
+
+struct RemoveSubTotalsHandler
+{
+ std::set<SCROW> aRemoved;
+
+ void operator() (size_t nRow, const ScFormulaCell* p)
+ {
+ if (p->IsSubTotal())
+ aRemoved.insert(nRow);
+ }
+};
+
+}
+
+void ScTable::RemoveSubTotals( ScSubTotalParam& rParam )
+{
+ SCCOL nStartCol = rParam.nCol1;
+ SCROW nStartRow = rParam.nRow1 + 1; // Header
+ SCCOL nEndCol = ClampToAllocatedColumns(rParam.nCol2);
+ SCROW nEndRow = rParam.nRow2; // will change
+
+ RemoveSubTotalsHandler aFunc;
+ for (SCCOL nCol = nStartCol; nCol <= nEndCol; ++nCol)
+ {
+ const sc::CellStoreType& rCells = aCol[nCol].maCells;
+ sc::ParseFormula(rCells.begin(), rCells, nStartRow, nEndRow, aFunc);
+ }
+
+ auto& aRows = aFunc.aRemoved;
+
+ std::for_each(aRows.rbegin(), aRows.rend(), [this](const SCROW nRow) {
+ RemoveRowBreak(nRow+1, false, true);
+ rDocument.DeleteRow(0, nTab, rDocument.MaxCol(), nTab, nRow, 1);
+ });
+
+ rParam.nRow2 -= aRows.size();
+}
+
+// Delete hard number formats (for result formulas)
+
+static void lcl_RemoveNumberFormat( ScTable* pTab, SCCOL nCol, SCROW nRow )
+{
+ const ScPatternAttr* pPattern = pTab->GetPattern( nCol, nRow );
+ if ( pPattern->GetItemSet().GetItemState( ATTR_VALUE_FORMAT, false )
+ == SfxItemState::SET )
+ {
+ auto pNewPattern = std::make_unique<ScPatternAttr>( *pPattern );
+ SfxItemSet& rSet = pNewPattern->GetItemSet();
+ rSet.ClearItem( ATTR_VALUE_FORMAT );
+ rSet.ClearItem( ATTR_LANGUAGE_FORMAT );
+ pTab->SetPattern( nCol, nRow, std::move(pNewPattern) );
+ }
+}
+
+namespace {
+
+struct RowEntry
+{
+ sal_uInt16 nGroupNo;
+ SCROW nSubStartRow;
+ SCROW nDestRow;
+ SCROW nFuncStart;
+ SCROW nFuncEnd;
+};
+
+}
+
+static TranslateId lcl_GetSubTotalStrId(int id)
+{
+ switch ( id )
+ {
+ case SUBTOTAL_FUNC_AVE: return STR_FUN_TEXT_AVG;
+ case SUBTOTAL_FUNC_CNT:
+ case SUBTOTAL_FUNC_CNT2: return STR_FUN_TEXT_COUNT;
+ case SUBTOTAL_FUNC_MAX: return STR_FUN_TEXT_MAX;
+ case SUBTOTAL_FUNC_MIN: return STR_FUN_TEXT_MIN;
+ case SUBTOTAL_FUNC_PROD: return STR_FUN_TEXT_PRODUCT;
+ case SUBTOTAL_FUNC_STD:
+ case SUBTOTAL_FUNC_STDP: return STR_FUN_TEXT_STDDEV;
+ case SUBTOTAL_FUNC_SUM: return STR_FUN_TEXT_SUM;
+ case SUBTOTAL_FUNC_VAR:
+ case SUBTOTAL_FUNC_VARP: return STR_FUN_TEXT_VAR;
+ default:
+ {
+ return STR_EMPTYDATA;
+ // added to avoid warnings
+ }
+ }
+}
+
+// new intermediate results
+// rParam.nRow2 is changed!
+
+bool ScTable::DoSubTotals( ScSubTotalParam& rParam )
+{
+ SCCOL nStartCol = rParam.nCol1;
+ SCROW nStartRow = rParam.nRow1 + 1; // Header
+ SCCOL nEndCol = rParam.nCol2;
+ SCROW nEndRow = rParam.nRow2; // will change
+ sal_uInt16 i;
+
+ // Remove empty rows at the end
+ // so that all exceeding (rDocument.MaxRow()) can be found by InsertRow (#35180#)
+ // If sorted, all empty rows are at the end.
+ SCSIZE nEmpty = GetEmptyLinesInBlock( nStartCol, nStartRow, nEndCol, nEndRow, DIR_BOTTOM );
+ nEndRow -= nEmpty;
+
+ sal_uInt16 nLevelCount = 0; // Number of levels
+ bool bDoThis = true;
+ for (i=0; i<MAXSUBTOTAL && bDoThis; i++)
+ if (rParam.bGroupActive[i])
+ nLevelCount = i+1;
+ else
+ bDoThis = false;
+
+ if (nLevelCount==0) // do nothing
+ return true;
+
+ SCCOL* nGroupCol = rParam.nField; // columns which will be used when grouping
+
+ // With (blank) as a separate category, subtotal rows from
+ // the other columns must always be tested
+ // (previously only when a column occurred more than once)
+ bool bTestPrevSub = ( nLevelCount > 1 );
+
+ OUString aSubString;
+
+ bool bIgnoreCase = !rParam.bCaseSens;
+
+ OUString aCompString[MAXSUBTOTAL];
+
+ //TODO: sort?
+
+ ScStyleSheet* pStyle = static_cast<ScStyleSheet*>(rDocument.GetStyleSheetPool()->Find(
+ ScResId(STR_STYLENAME_RESULT), SfxStyleFamily::Para ));
+
+ bool bSpaceLeft = true; // Success when inserting?
+
+ // For performance reasons collect formula entries so their
+ // references don't have to be tested for updates each time a new row is
+ // inserted
+ RowEntry aRowEntry;
+ ::std::vector< RowEntry > aRowVector;
+
+ for (sal_uInt16 nLevel=0; nLevel<nLevelCount && bSpaceLeft; nLevel++)
+ {
+ aRowEntry.nGroupNo = nLevelCount - nLevel - 1;
+
+ // how many results per level
+ SCCOL nResCount = rParam.nSubTotals[aRowEntry.nGroupNo];
+ // result functions
+ ScSubTotalFunc* pResFunc = rParam.pFunctions[aRowEntry.nGroupNo].get();
+
+ if (nResCount > 0) // otherwise only sort
+ {
+ for (i=0; i<=aRowEntry.nGroupNo; i++)
+ {
+ aSubString = GetString( nGroupCol[i], nStartRow );
+ if ( bIgnoreCase )
+ aCompString[i] = ScGlobal::getCharClass().uppercase( aSubString );
+ else
+ aCompString[i] = aSubString;
+ } // aSubString stays on the last
+
+ bool bBlockVis = false; // group visible?
+ aRowEntry.nSubStartRow = nStartRow;
+ for (SCROW nRow=nStartRow; nRow<=nEndRow+1 && bSpaceLeft; nRow++)
+ {
+ bool bChanged;
+ if (nRow>nEndRow)
+ bChanged = true;
+ else
+ {
+ bChanged = false;
+ OUString aString;
+ for (i=0; i<=aRowEntry.nGroupNo && !bChanged; i++)
+ {
+ aString = GetString( nGroupCol[i], nRow );
+ if (bIgnoreCase)
+ aString = ScGlobal::getCharClass().uppercase(aString);
+ // when sorting, blanks are separate group
+ // otherwise blank cells are allowed below
+ bChanged = ( ( !aString.isEmpty() || rParam.bDoSort ) &&
+ aString != aCompString[i] );
+ }
+ if ( bChanged && bTestPrevSub )
+ {
+ // No group change on rows that will contain subtotal formulas
+ bChanged = std::none_of(aRowVector.begin(), aRowVector.end(),
+ [&nRow](const RowEntry& rEntry) { return rEntry.nDestRow == nRow; });
+ }
+ }
+ if ( bChanged )
+ {
+ aRowEntry.nDestRow = nRow;
+ aRowEntry.nFuncStart = aRowEntry.nSubStartRow;
+ aRowEntry.nFuncEnd = nRow-1;
+
+ bSpaceLeft = rDocument.InsertRow( 0, nTab, rDocument.MaxCol(), nTab,
+ aRowEntry.nDestRow, 1 );
+ DBShowRow( aRowEntry.nDestRow, bBlockVis );
+ if ( rParam.bPagebreak && nRow < rDocument.MaxRow() &&
+ aRowEntry.nSubStartRow != nStartRow && nLevel == 0)
+ SetRowBreak(aRowEntry.nSubStartRow, false, true);
+
+ if (bSpaceLeft)
+ {
+ for ( auto& rRowEntry : aRowVector)
+ {
+ if ( aRowEntry.nDestRow <= rRowEntry.nSubStartRow )
+ ++rRowEntry.nSubStartRow;
+ if ( aRowEntry.nDestRow <= rRowEntry.nDestRow )
+ ++rRowEntry.nDestRow;
+ if ( aRowEntry.nDestRow <= rRowEntry.nFuncStart )
+ ++rRowEntry.nFuncStart;
+ if ( aRowEntry.nDestRow <= rRowEntry.nFuncEnd )
+ ++rRowEntry.nFuncEnd;
+ }
+ // collect formula positions
+ aRowVector.push_back( aRowEntry );
+
+ OUString aOutString = aSubString;
+ if (aOutString.isEmpty())
+ aOutString = ScResId( STR_EMPTYDATA );
+ aOutString += " ";
+ TranslateId pStrId = STR_TABLE_ERGEBNIS;
+ if ( nResCount == 1 )
+ pStrId = lcl_GetSubTotalStrId(pResFunc[0]);
+ aOutString += ScResId(pStrId);
+ SetString( nGroupCol[aRowEntry.nGroupNo], aRowEntry.nDestRow, nTab, aOutString );
+ ApplyStyle( nGroupCol[aRowEntry.nGroupNo], aRowEntry.nDestRow, pStyle );
+
+ ++nRow;
+ ++nEndRow;
+ aRowEntry.nSubStartRow = nRow;
+ for (i=0; i<=aRowEntry.nGroupNo; i++)
+ {
+ aSubString = GetString( nGroupCol[i], nRow );
+ if ( bIgnoreCase )
+ aCompString[i] = ScGlobal::getCharClass().uppercase( aSubString );
+ else
+ aCompString[i] = aSubString;
+ }
+ }
+ }
+ bBlockVis = !RowFiltered(nRow);
+ }
+ }
+ }
+
+ if (!aRowVector.empty())
+ {
+ // generate global total
+ SCROW nGlobalStartRow = aRowVector[0].nSubStartRow;
+ SCROW nGlobalStartFunc = aRowVector[0].nFuncStart;
+ SCROW nGlobalEndRow = 0;
+ SCROW nGlobalEndFunc = 0;
+ for (const auto& rRowEntry : aRowVector)
+ {
+ nGlobalEndRow = (nGlobalEndRow < rRowEntry.nDestRow) ? rRowEntry.nDestRow : nGlobalEndRow;
+ nGlobalEndFunc = (nGlobalEndFunc < rRowEntry.nFuncEnd) ? rRowEntry.nFuncEnd : nGlobalEndRow;
+ }
+
+ for (sal_uInt16 nLevel = 0; nLevel<nLevelCount; nLevel++)
+ {
+ const sal_uInt16 nGroupNo = nLevelCount - nLevel - 1;
+ const ScSubTotalFunc* pResFunc = rParam.pFunctions[nGroupNo].get();
+ if (!pResFunc)
+ {
+ // No subtotal function given for this group => no formula or
+ // label and do not insert a row.
+ continue;
+ }
+
+ // increment end row
+ nGlobalEndRow++;
+
+ // add row entry for formula
+ aRowEntry.nGroupNo = nGroupNo;
+ aRowEntry.nSubStartRow = nGlobalStartRow;
+ aRowEntry.nFuncStart = nGlobalStartFunc;
+ aRowEntry.nDestRow = nGlobalEndRow;
+ aRowEntry.nFuncEnd = nGlobalEndFunc;
+
+ // increment row
+ nGlobalEndFunc++;
+
+ bSpaceLeft = rDocument.InsertRow(0, nTab, rDocument.MaxCol(), nTab, aRowEntry.nDestRow, 1);
+
+ if (bSpaceLeft)
+ {
+ aRowVector.push_back(aRowEntry);
+ nEndRow++;
+ DBShowRow(aRowEntry.nDestRow, true);
+
+ // insert label
+ OUString label = ScResId(STR_TABLE_GRAND) + " " + ScResId(lcl_GetSubTotalStrId(pResFunc[0]));
+ SetString(nGroupCol[aRowEntry.nGroupNo], aRowEntry.nDestRow, nTab, label);
+ ApplyStyle(nGroupCol[aRowEntry.nGroupNo], aRowEntry.nDestRow, pStyle);
+ }
+ }
+ }
+
+ // now insert the formulas
+ ScComplexRefData aRef;
+ aRef.InitFlags();
+ aRef.Ref1.SetAbsTab(nTab);
+ aRef.Ref2.SetAbsTab(nTab);
+ for (const auto& rRowEntry : aRowVector)
+ {
+ SCCOL nResCount = rParam.nSubTotals[rRowEntry.nGroupNo];
+ SCCOL* nResCols = rParam.pSubTotals[rRowEntry.nGroupNo].get();
+ ScSubTotalFunc* pResFunc = rParam.pFunctions[rRowEntry.nGroupNo].get();
+ for ( SCCOL nResult=0; nResult < nResCount; ++nResult )
+ {
+ aRef.Ref1.SetAbsCol(nResCols[nResult]);
+ aRef.Ref1.SetAbsRow(rRowEntry.nFuncStart);
+ aRef.Ref2.SetAbsCol(nResCols[nResult]);
+ aRef.Ref2.SetAbsRow(rRowEntry.nFuncEnd);
+
+ ScTokenArray aArr(rDocument);
+ aArr.AddOpCode( ocSubTotal );
+ aArr.AddOpCode( ocOpen );
+ aArr.AddDouble( static_cast<double>(pResFunc[nResult]) );
+ aArr.AddOpCode( ocSep );
+ aArr.AddDoubleReference( aRef );
+ aArr.AddOpCode( ocClose );
+ aArr.AddOpCode( ocStop );
+ ScFormulaCell* pCell = new ScFormulaCell(
+ rDocument, ScAddress(nResCols[nResult], rRowEntry.nDestRow, nTab), aArr);
+ if ( rParam.bIncludePattern )
+ pCell->SetNeedNumberFormat(true);
+
+ SetFormulaCell(nResCols[nResult], rRowEntry.nDestRow, pCell);
+ if ( nResCols[nResult] != nGroupCol[rRowEntry.nGroupNo] )
+ {
+ ApplyStyle( nResCols[nResult], rRowEntry.nDestRow, pStyle );
+
+ lcl_RemoveNumberFormat( this, nResCols[nResult], rRowEntry.nDestRow );
+ }
+ }
+
+ }
+
+ //TODO: according to setting, shift intermediate-sum rows up?
+
+ //TODO: create Outlines directly?
+
+ if (bSpaceLeft)
+ DoAutoOutline( nStartCol, nStartRow, nEndCol, nEndRow );
+
+ rParam.nRow2 = nEndRow; // new end
+ return bSpaceLeft;
+}
+
+void ScTable::TopTenQuery( ScQueryParam& rParam )
+{
+ bool bSortCollatorInitialized = false;
+ SCSIZE nEntryCount = rParam.GetEntryCount();
+ SCROW nRow1 = (rParam.bHasHeader ? rParam.nRow1 + 1 : rParam.nRow1);
+ SCSIZE nCount = static_cast<SCSIZE>(rParam.nRow2 - nRow1 + 1);
+ for ( SCSIZE i=0; (i<nEntryCount) && (rParam.GetEntry(i).bDoQuery); i++ )
+ {
+ ScQueryEntry& rEntry = rParam.GetEntry(i);
+ ScQueryEntry::QueryItemsType& rItems = rEntry.GetQueryItems();
+
+ for (ScQueryEntry::Item& rItem : rItems)
+ {
+ switch (rEntry.eOp)
+ {
+ case SC_TOPVAL:
+ case SC_BOTVAL:
+ case SC_TOPPERC:
+ case SC_BOTPERC:
+ {
+ ScSortParam aLocalSortParam(rParam, static_cast<SCCOL>(rEntry.nField));
+ aSortParam = aLocalSortParam; // used in CreateSortInfoArray, Compare
+ if (!bSortCollatorInitialized)
+ {
+ bSortCollatorInitialized = true;
+ InitSortCollator(aLocalSortParam);
+ }
+ std::unique_ptr<ScSortInfoArray> pArray(CreateSortInfoArray(aSortParam, nRow1, rParam.nRow2, bGlobalKeepQuery, false));
+ DecoladeRow(pArray.get(), nRow1, rParam.nRow2);
+ QuickSort(pArray.get(), nRow1, rParam.nRow2);
+ std::unique_ptr<ScSortInfo[]> const& ppInfo = pArray->GetFirstArray();
+ SCSIZE nValidCount = nCount;
+ // Don't count note or blank cells, they are sorted to the end
+ while (nValidCount > 0 && ppInfo[nValidCount - 1].maCell.isEmpty())
+ nValidCount--;
+ // Don't count Strings, they are between Value and blank
+ while (nValidCount > 0 && ppInfo[nValidCount - 1].maCell.hasString())
+ nValidCount--;
+ if (nValidCount > 0)
+ {
+ if (rItem.meType == ScQueryEntry::ByString)
+ { // by string ain't going to work
+ rItem.meType = ScQueryEntry::ByValue;
+ rItem.mfVal = 10; // 10 and 10% respectively
+ }
+ SCSIZE nVal = (rItem.mfVal >= 1 ? static_cast<SCSIZE>(rItem.mfVal) : 1);
+ SCSIZE nOffset = 0;
+ switch (rEntry.eOp)
+ {
+ case SC_TOPVAL:
+ {
+ rEntry.eOp = SC_GREATER_EQUAL;
+ if (nVal > nValidCount)
+ nVal = nValidCount;
+ nOffset = nValidCount - nVal; // 1 <= nVal <= nValidCount
+ }
+ break;
+ case SC_BOTVAL:
+ {
+ rEntry.eOp = SC_LESS_EQUAL;
+ if (nVal > nValidCount)
+ nVal = nValidCount;
+ nOffset = nVal - 1; // 1 <= nVal <= nValidCount
+ }
+ break;
+ case SC_TOPPERC:
+ {
+ rEntry.eOp = SC_GREATER_EQUAL;
+ if (nVal > 100)
+ nVal = 100;
+ nOffset = nValidCount - (nValidCount * nVal / 100);
+ if (nOffset >= nValidCount)
+ nOffset = nValidCount - 1;
+ }
+ break;
+ case SC_BOTPERC:
+ {
+ rEntry.eOp = SC_LESS_EQUAL;
+ if (nVal > 100)
+ nVal = 100;
+ nOffset = (nValidCount * nVal / 100);
+ if (nOffset >= nValidCount)
+ nOffset = nValidCount - 1;
+ }
+ break;
+ default:
+ {
+ // added to avoid warnings
+ }
+ }
+ ScRefCellValue aCell = ppInfo[nOffset].maCell;
+ if (aCell.hasNumeric())
+ rItem.mfVal = aCell.getValue();
+ else
+ {
+ OSL_FAIL("TopTenQuery: pCell no ValueData");
+ rEntry.eOp = SC_GREATER_EQUAL;
+ rItem.mfVal = 0;
+ }
+ }
+ else
+ {
+ rEntry.eOp = SC_GREATER_EQUAL;
+ rItem.meType = ScQueryEntry::ByValue;
+ rItem.mfVal = 0;
+ }
+ }
+ break;
+ default:
+ {
+ // added to avoid warnings
+ }
+ }
+ }
+ }
+ if ( bSortCollatorInitialized )
+ DestroySortCollator();
+}
+
+namespace {
+
+bool CanOptimizeQueryStringToNumber( const SvNumberFormatter* pFormatter, sal_uInt32 nFormatIndex, bool& bDateFormat )
+{
+ // tdf#105629: ScQueryEntry::ByValue queries are faster than ScQueryEntry::ByString.
+ // The problem with this optimization is that the autofilter dialog apparently converts
+ // the value to text and then converts that back to a number for filtering.
+ // If that leads to any change of value (such as when time is rounded to seconds),
+ // even matching values will be filtered out. Therefore query by value only for formats
+ // where no such change should occur.
+ if(const SvNumberformat* pEntry = pFormatter->GetEntry(nFormatIndex))
+ {
+ switch(pEntry->GetType())
+ {
+ case SvNumFormatType::NUMBER:
+ case SvNumFormatType::FRACTION:
+ case SvNumFormatType::SCIENTIFIC:
+ return true;
+ case SvNumFormatType::DATE:
+ case SvNumFormatType::DATETIME:
+ bDateFormat = true;
+ break;
+ default:
+ break;
+ }
+ }
+ return false;
+}
+
+class PrepareQueryItem
+{
+ const ScDocument& mrDoc;
+ const bool mbRoundForFilter;
+public:
+ explicit PrepareQueryItem(const ScDocument& rDoc, bool bRoundForFilter) :
+ mrDoc(rDoc), mbRoundForFilter(bRoundForFilter) {}
+
+ void operator() (ScQueryEntry::Item& rItem)
+ {
+ rItem.mbRoundForFilter = mbRoundForFilter;
+
+ if (rItem.meType != ScQueryEntry::ByString && rItem.meType != ScQueryEntry::ByDate)
+ return;
+
+ sal_uInt32 nIndex = 0;
+ bool bNumber = mrDoc.GetFormatTable()->
+ IsNumberFormat(rItem.maString.getString(), nIndex, rItem.mfVal);
+
+ // Advanced Filter creates only ByString queries that need to be
+ // converted to ByValue if appropriate. rItem.mfVal now holds the value
+ // if bNumber==true.
+
+ if (rItem.meType == ScQueryEntry::ByString)
+ {
+ bool bDateFormat = false;
+ if (bNumber && CanOptimizeQueryStringToNumber( mrDoc.GetFormatTable(), nIndex, bDateFormat ))
+ rItem.meType = ScQueryEntry::ByValue;
+ if (!bDateFormat)
+ return;
+ }
+
+ // Double-check if the query by date is really appropriate.
+
+ if (bNumber && ((nIndex % SV_COUNTRY_LANGUAGE_OFFSET) != 0))
+ {
+ const SvNumberformat* pEntry = mrDoc.GetFormatTable()->GetEntry(nIndex);
+ if (pEntry)
+ {
+ SvNumFormatType nNumFmtType = pEntry->GetType();
+ if (!(nNumFmtType & SvNumFormatType::DATE) || (nNumFmtType & SvNumFormatType::TIME))
+ rItem.meType = ScQueryEntry::ByValue; // not a date only
+ else
+ rItem.meType = ScQueryEntry::ByDate; // date only
+ }
+ else
+ rItem.meType = ScQueryEntry::ByValue; // what the ... not a date
+ }
+ else
+ rItem.meType = ScQueryEntry::ByValue; // not a date
+ }
+};
+
+void lcl_PrepareQuery( const ScDocument* pDoc, ScTable* pTab, ScQueryParam& rParam, bool bRoundForFilter )
+{
+ bool bTopTen = false;
+ SCSIZE nEntryCount = rParam.GetEntryCount();
+
+ for ( SCSIZE i = 0; i < nEntryCount; ++i )
+ {
+ ScQueryEntry& rEntry = rParam.GetEntry(i);
+ if (!rEntry.bDoQuery)
+ continue;
+
+ ScQueryEntry::QueryItemsType& rItems = rEntry.GetQueryItems();
+ std::for_each(rItems.begin(), rItems.end(), PrepareQueryItem(*pDoc, bRoundForFilter));
+
+ if ( !bTopTen )
+ {
+ switch ( rEntry.eOp )
+ {
+ case SC_TOPVAL:
+ case SC_BOTVAL:
+ case SC_TOPPERC:
+ case SC_BOTPERC:
+ {
+ bTopTen = true;
+ }
+ break;
+ default:
+ {
+ }
+ }
+ }
+ }
+
+ if ( bTopTen )
+ {
+ pTab->TopTenQuery( rParam );
+ }
+}
+
+}
+
+void ScTable::PrepareQuery( ScQueryParam& rQueryParam )
+{
+ lcl_PrepareQuery(&rDocument, this, rQueryParam, false);
+}
+
+SCSIZE ScTable::Query(const ScQueryParam& rParamOrg, bool bKeepSub)
+{
+ ScQueryParam aParam( rParamOrg );
+ typedef std::unordered_set<OUString> StrSetType;
+ StrSetType aStrSet;
+
+ bool bStarted = false;
+ bool bOldResult = true;
+ SCROW nOldStart = 0;
+ SCROW nOldEnd = 0;
+
+ SCSIZE nCount = 0;
+ SCROW nOutRow = 0;
+ SCROW nHeader = aParam.bHasHeader ? 1 : 0;
+
+ lcl_PrepareQuery(&rDocument, this, aParam, true);
+
+ if (!aParam.bInplace)
+ {
+ nOutRow = aParam.nDestRow + nHeader;
+ if (nHeader > 0)
+ CopyData( aParam.nCol1, aParam.nRow1, aParam.nCol2, aParam.nRow1,
+ aParam.nDestCol, aParam.nDestRow, aParam.nDestTab );
+ }
+
+ sc::TableColumnBlockPositionSet blockPos( GetDoc(), nTab ); // cache mdds access
+ ScQueryEvaluator queryEvaluator(GetDoc(), *this, aParam);
+
+ SCROW nRealRow2 = aParam.nRow2;
+ for (SCROW j = aParam.nRow1 + nHeader; j <= nRealRow2; ++j)
+ {
+ bool bResult; // Filter result
+ bool bValid = queryEvaluator.ValidQuery(j, nullptr, &blockPos);
+ if (!bValid && bKeepSub) // Keep subtotals
+ {
+ for (SCCOL nCol=aParam.nCol1; nCol<=aParam.nCol2 && !bValid; nCol++)
+ {
+ ScRefCellValue aCell = GetCellValue(nCol, j);
+ if (aCell.meType != CELLTYPE_FORMULA)
+ continue;
+
+ if (!aCell.mpFormula->IsSubTotal())
+ continue;
+
+ if (RefVisible(aCell.mpFormula))
+ bValid = true;
+ }
+ }
+ if (bValid)
+ {
+ if (aParam.bDuplicate)
+ bResult = true;
+ else
+ {
+ OUStringBuffer aStr;
+ for (SCCOL k=aParam.nCol1; k <= aParam.nCol2; k++)
+ {
+ OUString aCellStr = GetString(k, j);
+ aStr.append(aCellStr + u"\x0001");
+ }
+
+ bResult = aStrSet.insert(aStr.makeStringAndClear()).second; // unique if inserted.
+ }
+ }
+ else
+ bResult = false;
+
+ if (aParam.bInplace)
+ {
+ if (bResult == bOldResult && bStarted)
+ nOldEnd = j;
+ else
+ {
+ if (bStarted)
+ DBShowRows(nOldStart,nOldEnd, bOldResult);
+ nOldStart = nOldEnd = j;
+ bOldResult = bResult;
+ }
+ bStarted = true;
+ }
+ else
+ {
+ if (bResult)
+ {
+ CopyData( aParam.nCol1,j, aParam.nCol2,j, aParam.nDestCol,nOutRow,aParam.nDestTab );
+ if( nTab == aParam.nDestTab ) // copy to self, changes may invalidate caching position hints
+ blockPos.invalidate();
+ ++nOutRow;
+ }
+ }
+ if (bResult)
+ ++nCount;
+ }
+
+ if (aParam.bInplace && bStarted)
+ DBShowRows(nOldStart,nOldEnd, bOldResult);
+
+ if (aParam.bInplace)
+ SetDrawPageSize();
+
+ return nCount;
+}
+
+bool ScTable::CreateExcelQuery(SCCOL nCol1, SCROW nRow1, SCCOL nCol2, SCROW nRow2, ScQueryParam& rQueryParam)
+{
+ bool bValid = true;
+ std::unique_ptr<SCCOL[]> pFields(new SCCOL[nCol2-nCol1+1]);
+ OUString aCellStr;
+ SCCOL nCol = nCol1;
+ OSL_ENSURE( rQueryParam.nTab != SCTAB_MAX, "rQueryParam.nTab no value, not bad but no good" );
+ SCTAB nDBTab = (rQueryParam.nTab == SCTAB_MAX ? nTab : rQueryParam.nTab);
+ SCROW nDBRow1 = rQueryParam.nRow1;
+ SCCOL nDBCol2 = rQueryParam.nCol2;
+ // First row must be column headers
+ while (bValid && (nCol <= nCol2))
+ {
+ OUString aQueryStr = GetUpperCellString(nCol, nRow1);
+ bool bFound = false;
+ SCCOL i = rQueryParam.nCol1;
+ while (!bFound && (i <= nDBCol2))
+ {
+ if ( nTab == nDBTab )
+ aCellStr = GetUpperCellString(i, nDBRow1);
+ else
+ aCellStr = rDocument.GetUpperCellString(i, nDBRow1, nDBTab);
+ bFound = (aCellStr == aQueryStr);
+ if (!bFound) i++;
+ }
+ if (bFound)
+ pFields[nCol - nCol1] = i;
+ else
+ bValid = false;
+ nCol++;
+ }
+ if (bValid)
+ {
+ sal_uLong nVisible = 0;
+ for ( nCol=nCol1; nCol<=ClampToAllocatedColumns(nCol2); nCol++ )
+ nVisible += aCol[nCol].VisibleCount( nRow1+1, nRow2 );
+
+ if ( nVisible > SCSIZE_MAX / sizeof(void*) )
+ {
+ OSL_FAIL("too many filter criteria");
+ nVisible = 0;
+ }
+
+ SCSIZE nNewEntries = nVisible;
+ rQueryParam.Resize( nNewEntries );
+
+ SCSIZE nIndex = 0;
+ SCROW nRow = nRow1 + 1;
+ svl::SharedStringPool& rPool = rDocument.GetSharedStringPool();
+ while (nRow <= nRow2)
+ {
+ nCol = nCol1;
+ while (nCol <= nCol2)
+ {
+ aCellStr = GetInputString( nCol, nRow );
+ if (!aCellStr.isEmpty())
+ {
+ if (nIndex < nNewEntries)
+ {
+ rQueryParam.GetEntry(nIndex).nField = pFields[nCol - nCol1];
+ rQueryParam.FillInExcelSyntax(rPool, aCellStr, nIndex, nullptr);
+ nIndex++;
+ if (nIndex < nNewEntries)
+ rQueryParam.GetEntry(nIndex).eConnect = SC_AND;
+ }
+ else
+ bValid = false;
+ }
+ nCol++;
+ }
+ nRow++;
+ if (nIndex < nNewEntries)
+ rQueryParam.GetEntry(nIndex).eConnect = SC_OR;
+ }
+ }
+ return bValid;
+}
+
+bool ScTable::CreateStarQuery(SCCOL nCol1, SCROW nRow1, SCCOL nCol2, SCROW nRow2, ScQueryParam& rQueryParam)
+{
+ // A valid StarQuery must be at least 4 columns wide. To be precise it
+ // should be exactly 4 columns ...
+ // Additionally, if this wasn't checked, a formula pointing to a valid 1-3
+ // column Excel style query range immediately left to itself would result
+ // in a circular reference when the field name or operator or value (first
+ // to third query range column) is obtained (#i58354#). Furthermore, if the
+ // range wasn't sufficiently specified data changes wouldn't flag formula
+ // cells for recalculation.
+ if (nCol2 - nCol1 < 3)
+ return false;
+
+ bool bValid;
+ OUString aCellStr;
+ SCSIZE nIndex = 0;
+ SCROW nRow = nRow1;
+ OSL_ENSURE( rQueryParam.nTab != SCTAB_MAX, "rQueryParam.nTab no value, not bad but no good" );
+ SCTAB nDBTab = (rQueryParam.nTab == SCTAB_MAX ? nTab : rQueryParam.nTab);
+ SCROW nDBRow1 = rQueryParam.nRow1;
+ SCCOL nDBCol2 = rQueryParam.nCol2;
+
+ SCSIZE nNewEntries = static_cast<SCSIZE>(nRow2-nRow1+1);
+ rQueryParam.Resize( nNewEntries );
+ svl::SharedStringPool& rPool = rDocument.GetSharedStringPool();
+
+ do
+ {
+ ScQueryEntry& rEntry = rQueryParam.GetEntry(nIndex);
+
+ bValid = false;
+ // First column AND/OR
+ if (nIndex > 0)
+ {
+ aCellStr = GetUpperCellString(nCol1, nRow);
+ if ( aCellStr == ScResId(STR_TABLE_AND) )
+ {
+ rEntry.eConnect = SC_AND;
+ bValid = true;
+ }
+ else if ( aCellStr == ScResId(STR_TABLE_OR) )
+ {
+ rEntry.eConnect = SC_OR;
+ bValid = true;
+ }
+ }
+ // Second column field name
+ if ((nIndex < 1) || bValid)
+ {
+ bool bFound = false;
+ aCellStr = GetUpperCellString(nCol1 + 1, nRow);
+ for (SCCOL i=rQueryParam.nCol1; (i <= nDBCol2) && (!bFound); i++)
+ {
+ OUString aFieldStr;
+ if ( nTab == nDBTab )
+ aFieldStr = GetUpperCellString(i, nDBRow1);
+ else
+ aFieldStr = rDocument.GetUpperCellString(i, nDBRow1, nDBTab);
+ bFound = (aCellStr == aFieldStr);
+ if (bFound)
+ {
+ rEntry.nField = i;
+ bValid = true;
+ }
+ else
+ bValid = false;
+ }
+ }
+ // Third column operator =<>...
+ if (bValid)
+ {
+ aCellStr = GetUpperCellString(nCol1 + 2, nRow);
+ if (aCellStr.startsWith("<"))
+ {
+ if (aCellStr[1] == '>')
+ rEntry.eOp = SC_NOT_EQUAL;
+ else if (aCellStr[1] == '=')
+ rEntry.eOp = SC_LESS_EQUAL;
+ else
+ rEntry.eOp = SC_LESS;
+ }
+ else if (aCellStr.startsWith(">"))
+ {
+ if (aCellStr[1] == '=')
+ rEntry.eOp = SC_GREATER_EQUAL;
+ else
+ rEntry.eOp = SC_GREATER;
+ }
+ else if (aCellStr.startsWith("="))
+ rEntry.eOp = SC_EQUAL;
+
+ }
+ // Fourth column values
+ if (bValid)
+ {
+ OUString aStr = GetString(nCol1 + 3, nRow);
+ rEntry.GetQueryItem().maString = rPool.intern(aStr);
+ rEntry.bDoQuery = true;
+ }
+ nIndex++;
+ nRow++;
+ }
+ while (bValid && (nRow <= nRow2) /* && (nIndex < MAXQUERY) */ );
+ return bValid;
+}
+
+bool ScTable::CreateQueryParam(SCCOL nCol1, SCROW nRow1, SCCOL nCol2, SCROW nRow2, ScQueryParam& rQueryParam)
+{
+ SCSIZE i, nCount;
+ PutInOrder(nCol1, nCol2);
+ PutInOrder(nRow1, nRow2);
+
+ nCount = rQueryParam.GetEntryCount();
+ for (i=0; i < nCount; i++)
+ rQueryParam.GetEntry(i).Clear();
+
+ // Standard query table
+ bool bValid = CreateStarQuery(nCol1, nRow1, nCol2, nRow2, rQueryParam);
+ // Excel Query table
+ if (!bValid)
+ bValid = CreateExcelQuery(nCol1, nRow1, nCol2, nRow2, rQueryParam);
+
+ SvNumberFormatter* pFormatter = rDocument.GetFormatTable();
+ nCount = rQueryParam.GetEntryCount();
+ if (bValid)
+ {
+ // query type must be set
+ for (i=0; i < nCount; i++)
+ {
+ ScQueryEntry::Item& rItem = rQueryParam.GetEntry(i).GetQueryItem();
+ sal_uInt32 nIndex = 0;
+ bool bNumber = pFormatter->IsNumberFormat(
+ rItem.maString.getString(), nIndex, rItem.mfVal);
+ bool bDateFormat = false;
+ rItem.meType = bNumber && CanOptimizeQueryStringToNumber( pFormatter, nIndex, bDateFormat )
+ ? ScQueryEntry::ByValue : (bDateFormat ? ScQueryEntry::ByDate : ScQueryEntry::ByString);
+ }
+ }
+ else
+ {
+ for (i=0; i < nCount; i++)
+ rQueryParam.GetEntry(i).Clear();
+ }
+ return bValid;
+}
+
+bool ScTable::HasColHeader( SCCOL nStartCol, SCROW nStartRow, SCCOL nEndCol, SCROW nEndRow) const
+{
+ if (nStartRow == nEndRow)
+ // Assume only data.
+ /* XXX NOTE: previous behavior still checked this one row and could
+ * evaluate it has header row, but that doesn't make much sense. */
+ return false;
+
+ if (nStartCol == nEndCol)
+ {
+ CellType eFirstCellType = GetCellType(nStartCol, nStartRow);
+ CellType eSecondCellType = GetCellType(nStartCol, nStartRow+1);
+ return ((eFirstCellType == CELLTYPE_STRING || eFirstCellType == CELLTYPE_EDIT) &&
+ (eSecondCellType != CELLTYPE_STRING && eSecondCellType != CELLTYPE_EDIT));
+ }
+
+ for (SCCOL nCol=nStartCol; nCol<=nEndCol; nCol++)
+ {
+ CellType eType = GetCellType( nCol, nStartRow );
+ // Any non-text cell in first row => not headers.
+ if (eType != CELLTYPE_STRING && eType != CELLTYPE_EDIT)
+ return false;
+ }
+
+ // First row all text cells, any non-text cell in second row => headers.
+ SCROW nTestRow = nStartRow + 1;
+ for (SCCOL nCol=nStartCol; nCol<=nEndCol; nCol++)
+ {
+ CellType eType = GetCellType( nCol, nTestRow );
+ if (eType != CELLTYPE_STRING && eType != CELLTYPE_EDIT)
+ return true;
+ }
+
+ // Also second row all text cells => first row not headers.
+ return false;
+}
+
+bool ScTable::HasRowHeader( SCCOL nStartCol, SCROW nStartRow, SCCOL nEndCol, SCROW nEndRow ) const
+{
+ if (nStartCol == nEndCol)
+ // Assume only data.
+ /* XXX NOTE: previous behavior still checked this one column and could
+ * evaluate it has header column, but that doesn't make much sense. */
+ return false;
+
+ if (nStartRow == nEndRow)
+ {
+ CellType eFirstCellType = GetCellType(nStartCol, nStartRow);
+ CellType eSecondCellType = GetCellType(nStartCol+1, nStartRow);
+ return ((eFirstCellType == CELLTYPE_STRING || eFirstCellType == CELLTYPE_EDIT) &&
+ (eSecondCellType != CELLTYPE_STRING && eSecondCellType != CELLTYPE_EDIT));
+ }
+
+ for (SCROW nRow=nStartRow; nRow<=nEndRow; nRow++)
+ {
+ CellType eType = GetCellType( nStartCol, nRow );
+ // Any non-text cell in first column => not headers.
+ if (eType != CELLTYPE_STRING && eType != CELLTYPE_EDIT)
+ return false;
+ }
+
+ // First column all text cells, any non-text cell in second column => headers.
+ SCCOL nTestCol = nStartCol + 1;
+ for (SCROW nRow=nStartRow; nRow<=nEndRow; nRow++)
+ {
+ CellType eType = GetCellType( nRow, nTestCol );
+ if (eType != CELLTYPE_STRING && eType != CELLTYPE_EDIT)
+ return true;
+ }
+
+ // Also second column all text cells => first column not headers.
+ return false;
+}
+
+void ScTable::GetFilterEntries( SCCOL nCol, SCROW nRow1, SCROW nRow2, ScFilterEntries& rFilterEntries, bool bFiltering )
+{
+ if (nCol >= aCol.size())
+ return;
+
+ sc::ColumnBlockConstPosition aBlockPos;
+ aCol[nCol].InitBlockPosition(aBlockPos);
+ aCol[nCol].GetFilterEntries(aBlockPos, nRow1, nRow2, rFilterEntries, bFiltering);
+}
+
+void ScTable::GetFilteredFilterEntries(
+ SCCOL nCol, SCROW nRow1, SCROW nRow2, const ScQueryParam& rParam, ScFilterEntries& rFilterEntries, bool bFiltering )
+{
+ if (nCol >= aCol.size())
+ return;
+
+ sc::ColumnBlockConstPosition aBlockPos;
+ aCol[nCol].InitBlockPosition(aBlockPos);
+
+ // remove the entry for this column from the query parameter
+ ScQueryParam aParam( rParam );
+ aParam.RemoveEntryByField(nCol);
+
+ lcl_PrepareQuery(&rDocument, this, aParam, true);
+ ScQueryEvaluator queryEvaluator(GetDoc(), *this, aParam);
+ for ( SCROW j = nRow1; j <= nRow2; ++j )
+ {
+ if (queryEvaluator.ValidQuery(j))
+ {
+ aCol[nCol].GetFilterEntries(aBlockPos, j, j, rFilterEntries, bFiltering);
+ }
+ }
+}
+
+bool ScTable::GetDataEntries(SCCOL nCol, SCROW nRow, std::set<ScTypedStrData>& rStrings)
+{
+ if (!ValidCol(nCol) || nCol >= GetAllocatedColumnsCount())
+ return false;
+ return aCol[nCol].GetDataEntries( nRow, rStrings);
+}
+
+sal_uInt64 ScTable::GetCellCount() const
+{
+ sal_uInt64 nCellCount = 0;
+
+ for ( SCCOL nCol=0; nCol < aCol.size(); nCol++ )
+ nCellCount += aCol[nCol].GetCellCount();
+
+ return nCellCount;
+}
+
+sal_uInt64 ScTable::GetWeightedCount() const
+{
+ sal_uInt64 nCellCount = 0;
+
+ for ( SCCOL nCol=0; nCol < aCol.size(); nCol++ )
+ nCellCount += aCol[nCol].GetWeightedCount();
+
+ return nCellCount;
+}
+
+sal_uInt64 ScTable::GetWeightedCount(SCROW nStartRow, SCROW nEndRow) const
+{
+ sal_uInt64 nCellCount = 0;
+
+ for ( SCCOL nCol=0; nCol < aCol.size(); nCol++ )
+ nCellCount += aCol[nCol].GetWeightedCount(nStartRow, nEndRow);
+
+ return nCellCount;
+}
+
+sal_uInt64 ScTable::GetCodeCount() const
+{
+ sal_uInt64 nCodeCount = 0;
+
+ for ( SCCOL nCol=0; nCol < aCol.size(); nCol++ )
+ if ( aCol[nCol].GetCellCount() )
+ nCodeCount += aCol[nCol].GetCodeCount();
+
+ return nCodeCount;
+}
+
+sal_Int32 ScTable::GetMaxStringLen( SCCOL nCol, SCROW nRowStart,
+ SCROW nRowEnd, rtl_TextEncoding eCharSet ) const
+{
+ if ( IsColValid( nCol ) )
+ return aCol[nCol].GetMaxStringLen( nRowStart, nRowEnd, eCharSet );
+ else
+ return 0;
+}
+
+sal_Int32 ScTable::GetMaxNumberStringLen(
+ sal_uInt16& nPrecision, SCCOL nCol, SCROW nRowStart, SCROW nRowEnd ) const
+{
+ if ( IsColValid( nCol ) )
+ return aCol[nCol].GetMaxNumberStringLen( nPrecision, nRowStart, nRowEnd );
+ else
+ return 0;
+}
+
+void ScTable::UpdateSelectionFunction( ScFunctionData& rData, const ScMarkData& rMark )
+{
+ ScRangeList aRanges = rMark.GetMarkedRangesForTab( nTab );
+ ScRange aMarkArea( ScAddress::UNINITIALIZED );
+ if (rMark.IsMultiMarked())
+ aMarkArea = rMark.GetMultiMarkArea();
+ else if (rMark.IsMarked())
+ aMarkArea = rMark.GetMarkArea();
+ else
+ {
+ assert(!"ScTable::UpdateSelectionFunction - called without anything marked");
+ aMarkArea.aStart.SetCol(0);
+ aMarkArea.aEnd.SetCol(rDocument.MaxCol());
+ }
+ const SCCOL nStartCol = aMarkArea.aStart.Col();
+ const SCCOL nEndCol = ClampToAllocatedColumns(aMarkArea.aEnd.Col());
+ for (SCCOL nCol = nStartCol; nCol <= nEndCol && !rData.getError(); ++nCol)
+ {
+ if (mpColFlags && ColHidden(nCol))
+ continue;
+
+ aCol[nCol].UpdateSelectionFunction(aRanges, rData, *mpHiddenRows);
+ }
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sc/source/core/data/table4.cxx b/sc/source/core/data/table4.cxx
new file mode 100644
index 000000000..f6f926c86
--- /dev/null
+++ b/sc/source/core/data/table4.cxx
@@ -0,0 +1,2990 @@
+/* -*- 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 <scitems.hxx>
+#include <comphelper/string.hxx>
+#include <editeng/boxitem.hxx>
+#include <editeng/editeng.hxx>
+#include <editeng/eeitem.hxx>
+#include <editeng/escapementitem.hxx>
+#include <svl/numformat.hxx>
+#include <svl/zforlist.hxx>
+#include <vcl/keycodes.hxx>
+#include <rtl/math.hxx>
+#include <unotools/charclass.hxx>
+#include <osl/diagnose.h>
+
+#include <attrib.hxx>
+#include <patattr.hxx>
+#include <formulacell.hxx>
+#include <table.hxx>
+#include <global.hxx>
+#include <document.hxx>
+#include <autoform.hxx>
+#include <userlist.hxx>
+#include <zforauto.hxx>
+#include <subtotal.hxx>
+#include <formula/errorcodes.hxx>
+#include <docpool.hxx>
+#include <progress.hxx>
+#include <conditio.hxx>
+#include <editutil.hxx>
+#include <listenercontext.hxx>
+#include <scopetools.hxx>
+#include <o3tl/string_view.hxx>
+
+#include <math.h>
+#include <memory>
+#include <list>
+#include <string_view>
+
+#define D_MAX_LONG_ double(0x7fffffff)
+
+namespace {
+
+short lcl_DecompValueString( OUString& rValue, sal_Int32& nVal, sal_uInt16* pMinDigits = nullptr )
+{
+ if ( rValue.isEmpty() )
+ {
+ nVal = 0;
+ return 0;
+ }
+ const sal_Unicode* p = rValue.getStr();
+ sal_Int32 nSign = 0;
+ sal_Int32 nNum = 0;
+ if ( p[nNum] == '-' || p[nNum] == '+' )
+ nNum = nSign = 1;
+ while ( p[nNum] && CharClass::isAsciiNumeric( std::u16string_view(&p[nNum], 1) ) )
+ nNum++;
+
+ sal_Unicode cNext = p[nNum]; // 0 if at the end
+ sal_Unicode cLast = p[rValue.getLength()-1];
+
+ // #i5550# If there are numbers at the beginning and the end,
+ // prefer the one at the beginning only if it's followed by a space.
+ // Otherwise, use the number at the end, to enable things like IP addresses.
+ if ( nNum > nSign && ( cNext == 0 || cNext == ' ' || !CharClass::isAsciiNumeric(std::u16string_view(&cLast, 1)) ) )
+ { // number at the beginning
+ nVal = o3tl::toInt32(rValue.subView( 0, nNum ));
+ // any number with a leading zero sets the minimum number of digits
+ if ( p[nSign] == '0' && pMinDigits && ( nNum - nSign > *pMinDigits ) )
+ *pMinDigits = nNum - nSign;
+ rValue = rValue.copy(nNum);
+ return -1;
+ }
+ else
+ {
+ nSign = 0;
+ sal_Int32 nEnd = nNum = rValue.getLength() - 1;
+ while ( nNum && CharClass::isAsciiNumeric( std::u16string_view(&p[nNum], 1) ) )
+ nNum--;
+ if ( p[nNum] == '-' || p[nNum] == '+' )
+ {
+ nNum--;
+ nSign = 1;
+ }
+ if ( nNum < nEnd - nSign )
+ { // number at the end
+ nVal = o3tl::toInt32(rValue.subView( nNum + 1 ));
+ // any number with a leading zero sets the minimum number of digits
+ if ( p[nNum+1+nSign] == '0' && pMinDigits && ( nEnd - nNum - nSign > *pMinDigits ) )
+ *pMinDigits = nEnd - nNum - nSign;
+ rValue = rValue.copy(0, nNum + 1);
+ if (nSign) // use the return value = 2 to put back the '+'
+ return 2;
+ else
+ return 1;
+ }
+ }
+ nVal = 0;
+ return 0;
+}
+
+OUString lcl_ValueString( sal_Int32 nValue, sal_uInt16 nMinDigits )
+{
+ if ( nMinDigits <= 1 )
+ return OUString::number( nValue ); // simple case...
+ else
+ {
+ OUString aStr = OUString::number( std::abs( nValue ) );
+ if ( aStr.getLength() < nMinDigits )
+ {
+ OUStringBuffer aZero(nMinDigits);
+ comphelper::string::padToLength(aZero, nMinDigits - aStr.getLength(), '0');
+ aStr = aZero.append(aStr).makeStringAndClear();
+ }
+ // nMinDigits doesn't include the '-' sign -> add after inserting zeros
+ if ( nValue < 0 )
+ aStr = "-" + aStr;
+ return aStr;
+ }
+}
+
+void setSuffixCell(
+ ScColumn& rColumn, SCROW nRow, sal_Int32 nValue, sal_uInt16 nDigits,
+ std::u16string_view rSuffix,
+ CellType eCellType, bool bIsOrdinalSuffix )
+{
+ ScDocument& rDoc = rColumn.GetDoc();
+ OUString aValue = lcl_ValueString(nValue, nDigits);
+ if (!bIsOrdinalSuffix)
+ {
+ aValue += rSuffix;
+ rColumn.SetRawString(nRow, aValue);
+ return;
+ }
+
+ OUString aOrdinalSuffix = ScGlobal::GetOrdinalSuffix(nValue);
+ if (eCellType != CELLTYPE_EDIT)
+ {
+ aValue += aOrdinalSuffix;
+ rColumn.SetRawString(nRow, aValue);
+ return;
+ }
+
+ EditEngine aEngine(rDoc.GetEnginePool());
+ aEngine.SetEditTextObjectPool(rDoc.GetEditPool());
+
+ SfxItemSet aAttr = aEngine.GetEmptyItemSet();
+ aAttr.Put( SvxEscapementItem( SvxEscapement::Superscript, EE_CHAR_ESCAPEMENT));
+ aEngine.SetText( aValue );
+ aEngine.QuickInsertText(
+ aOrdinalSuffix,
+ ESelection(0, aValue.getLength(), 0, aValue.getLength() + aOrdinalSuffix.getLength()));
+
+ aEngine.QuickSetAttribs(
+ aAttr,
+ ESelection(0, aValue.getLength(), 0, aValue.getLength() + aOrdinalSuffix.getLength()));
+
+ // Text object instance will be owned by the cell.
+ rColumn.SetEditText(nRow, aEngine.CreateTextObject());
+}
+
+}
+
+namespace {
+/* TODO: move this to rtl::math::approxDiff() ? Though the name is funny, the
+ * approx is expected to be more correct than the raw diff. */
+/** Calculate a-b trying to diminish precision errors such as for 0.11-0.12
+ not return -0.009999999999999995 but -0.01 instead.
+ */
+double approxDiff( double a, double b )
+{
+ if (a == b)
+ return 0.0;
+ if (a == 0.0)
+ return -b;
+ if (b == 0.0)
+ return a;
+ const double c = a - b;
+ const double aa = fabs(a);
+ const double ab = fabs(b);
+ if (aa < 1e-16 || aa > 1e+16 || ab < 1e-16 || ab > 1e+16)
+ // This is going nowhere, live with the result.
+ return c;
+
+ const double q = aa < ab ? b / a : a / b;
+ const double d = (a * q - b * q) / q;
+ if (d == c)
+ // No differing error, live with the result.
+ return c;
+
+ // We now have two subtractions with a similar but not equal error. Obtain
+ // the exponent of the error magnitude and round accordingly.
+ const double e = fabs(d - c);
+ const int nExp = static_cast<int>(floor(log10(e))) + 1;
+ // tdf#129606: Limit precision to the 16th significant digit of the least precise argument.
+ // Cf. mnMaxGeneralPrecision in sc/source/core/data/column3.cxx.
+ const int nExpArg = static_cast<int>(floor(log10(std::max(aa, ab)))) - 15;
+ return rtl::math::round(c, -std::max(nExp, nExpArg));
+}
+
+double approxTimeDiff( double a, double b )
+{
+ // Scale to hours, round to "nanohours" (multiple nanoseconds), scale back.
+ // Get back 0.0416666666666667 instead of 0.041666666700621136 or
+ // 0.041666666664241347 (raw a-b) for one hour, or worse the approxDiff()
+ // 0.041666666659999997 value. Though there is no such correct value,
+ // IEEE-754 nearest values are
+ // 0.041666666666666664353702032030923874117434024810791015625
+ // (0x3FA5555555555555) and
+ // 0.04166666666666667129259593593815225176513195037841796875
+ // (0x3FA5555555555556).
+ // This works also for a diff of seconds, unless corner cases would be
+ // discovered, which would make it necessary to ditch the floating point
+ // and convert to/from time structure values instead.
+ return rtl::math::round((a - b) * 24, 9) / 24;
+}
+
+double approxTypedDiff( double a, double b, bool bTime )
+{
+ return bTime ? approxTimeDiff( a, b) : approxDiff( a, b);
+}
+}
+
+void ScTable::FillAnalyse( SCCOL nCol1, SCROW nRow1, SCCOL nCol2, SCROW nRow2,
+ FillCmd& rCmd, FillDateCmd& rDateCmd,
+ double& rInc, sal_uInt16& rMinDigits,
+ ScUserListData*& rListData, sal_uInt16& rListIndex,
+ bool bHasFiltered, bool& rSkipOverlappedCells,
+ std::vector<sal_Int32>& rNonOverlappedCellIdx)
+{
+ OSL_ENSURE( nCol1==nCol2 || nRow1==nRow2, "FillAnalyse: invalid range" );
+
+ rInc = 0.0;
+ rMinDigits = 0;
+ rListData = nullptr;
+ rCmd = FILL_SIMPLE;
+ rSkipOverlappedCells = false;
+ if ( nScFillModeMouseModifier & KEY_MOD1 )
+ return ; // Ctrl-key: Copy
+
+ SCCOL nAddX;
+ SCROW nAddY;
+ SCSIZE nCount;
+ if (nCol1 == nCol2)
+ {
+ nAddX = 0;
+ nAddY = 1;
+ nCount = static_cast<SCSIZE>(nRow2 - nRow1 + 1);
+ }
+ else
+ {
+ nAddX = 1;
+ nAddY = 0;
+ nCount = static_cast<SCSIZE>(nCol2 - nCol1 + 1);
+ }
+
+ // Try to analyse the merged cells only if there are no filtered rows in the destination area
+ // Else fallback to the old way to avoid regression.
+ // Filling merged cells into an area with filtered (hidden) rows, is a very complex task
+ // that is not implemented, but not even decided how to do, even excel can't handle that well
+ if (!bHasFiltered)
+ {
+ bool bHasOverlappedCells = false;
+ bool bSkipOverlappedCells = true;
+ SCCOL nColCurr = nCol1;
+ SCROW nRowCurr = nRow1;
+
+ // collect cells that are not empty or not overlapped
+ rNonOverlappedCellIdx.resize(nCount);
+ SCSIZE nValueCount = 0;
+ for (SCSIZE i = 0; i < nCount; ++i)
+ {
+ const ScPatternAttr* pPattern = GetPattern(nColCurr, nRowCurr);
+ bool bOverlapped
+ = pPattern->GetItemSet().GetItemState(ATTR_MERGE_FLAG, false) == SfxItemState::SET
+ && pPattern->GetItem(ATTR_MERGE_FLAG).IsOverlapped();
+
+ if (bOverlapped)
+ bHasOverlappedCells = true;
+
+ if (!bOverlapped || GetCellValue(nColCurr, nRowCurr).meType != CELLTYPE_NONE)
+ {
+ rNonOverlappedCellIdx[nValueCount++] = i;
+ // if there is at least 1 non empty overlapped cell, then no cell should be skipped
+ if (bOverlapped)
+ bSkipOverlappedCells = false;
+ }
+
+ nColCurr += nAddX;
+ nRowCurr += nAddY;
+ }
+ rNonOverlappedCellIdx.resize(nValueCount);
+
+ // if all the values are overlapped CELLTYPE_NONE, then there is no need to analyse it.
+ if (nValueCount == 0)
+ return;
+
+ // if there is no overlapped cells, there is nothing to skip
+ if (!bHasOverlappedCells)
+ bSkipOverlappedCells = false;
+
+ if (bSkipOverlappedCells)
+ {
+ nColCurr = nCol1 + rNonOverlappedCellIdx[0] * nAddX;
+ nRowCurr = nRow1 + rNonOverlappedCellIdx[0] * nAddY;
+ ScRefCellValue aPrevCell, aCurrCell;
+ aCurrCell = GetCellValue(nColCurr, nRowCurr);
+ CellType eCellType = aCurrCell.meType;
+ if (eCellType == CELLTYPE_VALUE)
+ {
+ bool bVal = true;
+ double fVal;
+ SvNumFormatType nCurrCellFormatType
+ = rDocument.GetFormatTable()->GetType(GetNumberFormat(nColCurr, nRowCurr));
+ if (nCurrCellFormatType == SvNumFormatType::DATE)
+ {
+ if (nValueCount >= 2)
+ {
+ tools::Long nCmpInc = 0;
+ FillDateCmd eType = FILL_YEAR; // just some temporary default values
+ tools::Long nDDiff = 0, nMDiff = 0, nYDiff = 0; // to avoid warnings
+ Date aNullDate = rDocument.GetFormatTable()->GetNullDate();
+ Date aCurrDate = aNullDate, aPrevDate = aNullDate;
+ aCurrDate.AddDays(aCurrCell.mfValue);
+ for (SCSIZE i = 1; i < nValueCount && bVal; i++)
+ {
+ aPrevCell = aCurrCell;
+ aPrevDate = aCurrDate;
+ nColCurr = nCol1 + rNonOverlappedCellIdx[i] * nAddX;
+ nRowCurr = nRow1 + rNonOverlappedCellIdx[i] * nAddY;
+ aCurrCell = GetCellValue(nColCurr, nRowCurr);
+ if (aCurrCell.meType == CELLTYPE_VALUE)
+ {
+ aCurrDate = aNullDate + static_cast<sal_Int32>(aCurrCell.mfValue);
+ if (eType != FILL_DAY) {
+ nDDiff = aCurrDate.GetDay()
+ - static_cast<tools::Long>(aPrevDate.GetDay());
+ nMDiff = aCurrDate.GetMonth()
+ - static_cast<tools::Long>(aPrevDate.GetMonth());
+ nYDiff = aCurrDate.GetYear()
+ - static_cast<tools::Long>(aPrevDate.GetYear());
+ }
+ if (i == 1)
+ {
+ if (nDDiff != 0)
+ {
+ eType = FILL_DAY;
+ nCmpInc = aCurrDate - aPrevDate;
+ }
+ else
+ {
+ eType = FILL_MONTH;
+ nCmpInc = nMDiff + 12 * nYDiff;
+ }
+ }
+ else if (eType == FILL_DAY)
+ {
+ if (aCurrDate - aPrevDate != nCmpInc)
+ bVal = false;
+ }
+ else
+ {
+ if (nDDiff || (nMDiff + 12 * nYDiff != nCmpInc))
+ bVal = false;
+ }
+ }
+ else
+ bVal = false; // No date is also not ok
+ }
+ if (bVal)
+ {
+ if (eType == FILL_MONTH && (nCmpInc % 12 == 0))
+ {
+ eType = FILL_YEAR;
+ nCmpInc /= 12;
+ }
+ rCmd = FILL_DATE;
+ rDateCmd = eType;
+ rInc = nCmpInc;
+ rSkipOverlappedCells = true;
+ return;
+ }
+ }
+ else
+ {
+ rCmd = FILL_DATE;
+ rDateCmd = FILL_DAY;
+ rInc = 1.0;
+ rSkipOverlappedCells = true;
+ return;
+ }
+ }
+ else if (nCurrCellFormatType == SvNumFormatType::LOGICAL
+ && ((fVal = aCurrCell.mfValue) == 0.0 || fVal == 1.0))
+ {
+ }
+ else if (nValueCount >= 2)
+ {
+ for (SCSIZE i = 1; i < nValueCount && bVal; i++)
+ {
+ aPrevCell = aCurrCell;
+ nColCurr = nCol1 + rNonOverlappedCellIdx[i] * nAddX;
+ nRowCurr = nRow1 + rNonOverlappedCellIdx[i] * nAddY;
+ aCurrCell = GetCellValue(nColCurr, nRowCurr);
+ if (aCurrCell.meType == CELLTYPE_VALUE)
+ {
+ double nDiff = approxTypedDiff(aCurrCell.mfValue, aPrevCell.mfValue,
+ (nCurrCellFormatType == SvNumFormatType::TIME ||
+ nCurrCellFormatType == SvNumFormatType::DATETIME));
+ if (i == 1)
+ rInc = nDiff;
+ if (!::rtl::math::approxEqual(nDiff, rInc, 13))
+ bVal = false;
+ else if ((aCurrCell.mfValue == 0.0 || aCurrCell.mfValue == 1.0)
+ && (rDocument.GetFormatTable()->GetType(
+ GetNumberFormat(nColCurr, nRowCurr))
+ == SvNumFormatType::LOGICAL))
+ bVal = false;
+ }
+ else
+ bVal = false;
+ }
+ if (bVal)
+ {
+ rCmd = FILL_LINEAR;
+ rSkipOverlappedCells = true;
+ return;
+ }
+ }
+ }
+ else if (eCellType == CELLTYPE_STRING || eCellType == CELLTYPE_EDIT)
+ {
+ OUString aStr = GetString(nColCurr, nRowCurr );
+ OUString aStr2;
+
+ rListData = const_cast<ScUserListData*>(ScGlobal::GetUserList()->GetData(aStr));
+ if (rListData)
+ {
+ bool bMatchCase = false;
+ (void)rListData->GetSubIndex(aStr, rListIndex, bMatchCase);
+ size_t nListStrCount = rListData->GetSubCount();
+ sal_uInt16 nPrevListIndex, nInc = 1;
+ for (SCSIZE i = 1; i < nValueCount && rListData; i++)
+ {
+ nColCurr = nCol1 + rNonOverlappedCellIdx[i] * nAddX;
+ nRowCurr = nRow1 + rNonOverlappedCellIdx[i] * nAddY;
+ aStr2 = GetString(nColCurr, nRowCurr);
+
+ nPrevListIndex = rListIndex;
+ if (!rListData->GetSubIndex(aStr2, rListIndex, bMatchCase))
+ rListData = nullptr;
+ else
+ {
+ sal_Int32 nIncCurr = rListIndex - nPrevListIndex;
+ if (nIncCurr < 0)
+ nIncCurr += nListStrCount;
+ if (i == 1)
+ nInc = nIncCurr;
+ else if (nInc != nIncCurr)
+ rListData = nullptr;
+ }
+ }
+ if (rListData) {
+ rInc = nInc;
+ rSkipOverlappedCells = true;
+ return;
+ }
+ }
+ short nFlag1, nFlag2;
+ sal_Int32 nVal1, nVal2;
+ nFlag1 = lcl_DecompValueString(aStr, nVal1, &rMinDigits);
+ if (nFlag1)
+ {
+ bool bVal = true;
+ rInc = 1;
+ for (SCSIZE i = 1; i < nValueCount && bVal; i++)
+ {
+ nColCurr = nCol1 + rNonOverlappedCellIdx[i] * nAddX;
+ nRowCurr = nRow1 + rNonOverlappedCellIdx[i] * nAddY;
+ ScRefCellValue aCell = GetCellValue(nColCurr, nRowCurr);
+ CellType eType = aCell.meType;
+ if (eType == CELLTYPE_STRING || eType == CELLTYPE_EDIT)
+ {
+ aStr2 = aCell.getString(&rDocument);
+ nFlag2 = lcl_DecompValueString(aStr2, nVal2, &rMinDigits);
+ if (nFlag1 == nFlag2 && aStr == aStr2)
+ {
+ double nDiff = approxDiff(nVal2, nVal1);
+ if (i == 1)
+ rInc = nDiff;
+ else if (!::rtl::math::approxEqual(nDiff, rInc, 13))
+ bVal = false;
+ nVal1 = nVal2;
+ }
+ else
+ bVal = false;
+ }
+ else
+ bVal = false;
+ }
+ if (bVal)
+ {
+ rCmd = FILL_LINEAR;
+ rSkipOverlappedCells = true;
+ return;
+ }
+ }
+ }
+ }
+ }
+
+ //if it is not a FILL_LINEAR - CELLTYPE_VALUE - with merged cells [without hidden values]
+ //then do it in the old way
+
+ SCCOL nCol = nCol1;
+ SCROW nRow = nRow1;
+
+ ScRefCellValue aFirstCell = GetCellValue(nCol, nRow);
+ CellType eCellType = aFirstCell.meType;
+
+ if (eCellType == CELLTYPE_VALUE)
+ {
+ double fVal;
+ sal_uInt32 nFormat = GetAttr(nCol,nRow,ATTR_VALUE_FORMAT)->GetValue();
+ const SvNumFormatType nFormatType = rDocument.GetFormatTable()->GetType(nFormat);
+ bool bDate = (nFormatType == SvNumFormatType::DATE); // date without time
+ bool bTime = (nFormatType == SvNumFormatType::TIME || nFormatType == SvNumFormatType::DATETIME);
+ bool bBooleanCell = (nFormatType == SvNumFormatType::LOGICAL);
+ if (bDate)
+ {
+ if (nCount > 1)
+ {
+ double nVal;
+ Date aNullDate = rDocument.GetFormatTable()->GetNullDate();
+ Date aDate1 = aNullDate;
+ nVal = aFirstCell.mfValue;
+ aDate1.AddDays(nVal);
+ Date aDate2 = aNullDate;
+ nVal = GetValue(nCol+nAddX, nRow+nAddY);
+ aDate2.AddDays(nVal);
+ if ( aDate1 != aDate2 )
+ {
+ tools::Long nCmpInc = 0;
+ FillDateCmd eType;
+ tools::Long nDDiff = aDate2.GetDay() - static_cast<tools::Long>(aDate1.GetDay());
+ tools::Long nMDiff = aDate2.GetMonth() - static_cast<tools::Long>(aDate1.GetMonth());
+ tools::Long nYDiff = aDate2.GetYear() - static_cast<tools::Long>(aDate1.GetYear());
+ if (nMDiff && aDate1.IsEndOfMonth() && aDate2.IsEndOfMonth())
+ {
+ eType = FILL_END_OF_MONTH;
+ nCmpInc = nMDiff + 12 * nYDiff;
+ }
+ else if (nDDiff)
+ {
+ eType = FILL_DAY;
+ nCmpInc = aDate2 - aDate1;
+ }
+ else
+ {
+ eType = FILL_MONTH;
+ nCmpInc = nMDiff + 12 * nYDiff;
+ }
+
+ nCol = sal::static_int_cast<SCCOL>( nCol + nAddX );
+ nRow = sal::static_int_cast<SCROW>( nRow + nAddY );
+ bool bVal = true;
+ for (SCSIZE i=1; i<nCount && bVal; i++)
+ {
+ ScRefCellValue aCell = GetCellValue(nCol,nRow);
+ if (aCell.meType == CELLTYPE_VALUE)
+ {
+ nVal = aCell.mfValue;
+ aDate2 = aNullDate + static_cast<sal_Int32>(nVal);
+ if ( eType == FILL_DAY )
+ {
+ if ( aDate2-aDate1 != nCmpInc )
+ bVal = false;
+ }
+ else
+ {
+ nDDiff = aDate2.GetDay() - static_cast<tools::Long>(aDate1.GetDay());
+ nMDiff = aDate2.GetMonth() - static_cast<tools::Long>(aDate1.GetMonth());
+ nYDiff = aDate2.GetYear() - static_cast<tools::Long>(aDate1.GetYear());
+ if ((nDDiff && !aDate1.IsEndOfMonth() && !aDate2.IsEndOfMonth())
+ || (nMDiff + 12 * nYDiff != nCmpInc))
+ bVal = false;
+ }
+ aDate1 = aDate2;
+ nCol = sal::static_int_cast<SCCOL>( nCol + nAddX );
+ nRow = sal::static_int_cast<SCROW>( nRow + nAddY );
+ }
+ else
+ bVal = false; // No date is also not ok
+ }
+ if (bVal)
+ {
+ if ((eType == FILL_MONTH || eType == FILL_END_OF_MONTH)
+ && (nCmpInc % 12 == 0))
+ {
+ eType = FILL_YEAR;
+ nCmpInc /= 12;
+ }
+ rCmd = FILL_DATE;
+ rDateCmd = eType;
+ rInc = nCmpInc;
+ }
+ }
+ else
+ {
+ // tdf#89754 - don't increment non different consecutive date cells
+ rCmd = FILL_DATE;
+ rDateCmd = FILL_DAY;
+ rInc = 0.0;
+ }
+ }
+ else // single date -> increment by days
+ {
+ rCmd = FILL_DATE;
+ rDateCmd = FILL_DAY;
+ rInc = 1.0;
+ }
+ }
+ else if (bBooleanCell && ((fVal = aFirstCell.mfValue) == 0.0 || fVal == 1.0))
+ {
+ // Nothing, rInc stays 0.0, no specific fill mode.
+ }
+ else
+ {
+ if (nCount > 1)
+ {
+ double nVal1 = aFirstCell.mfValue;
+ double nVal2 = GetValue(nCol+nAddX, nRow+nAddY);
+ rInc = approxTypedDiff( nVal2, nVal1, bTime);
+ nCol = sal::static_int_cast<SCCOL>( nCol + nAddX );
+ nRow = sal::static_int_cast<SCROW>( nRow + nAddY );
+ bool bVal = true;
+ for (SCSIZE i=1; i<nCount && bVal; i++)
+ {
+ ScRefCellValue aCell = GetCellValue(nCol,nRow);
+ if (aCell.meType == CELLTYPE_VALUE)
+ {
+ nVal2 = aCell.mfValue;
+ double nDiff = approxTypedDiff( nVal2, nVal1, bTime);
+ if ( !::rtl::math::approxEqual( nDiff, rInc, 13 ) )
+ bVal = false;
+ else if ((nVal2 == 0.0 || nVal2 == 1.0) &&
+ (rDocument.GetFormatTable()->GetType(GetNumberFormat(nCol,nRow)) ==
+ SvNumFormatType::LOGICAL))
+ bVal = false;
+ nVal1 = nVal2;
+ }
+ else
+ bVal = false;
+ nCol = sal::static_int_cast<SCCOL>( nCol + nAddX );
+ nRow = sal::static_int_cast<SCROW>( nRow + nAddY );
+ }
+ if (bVal)
+ rCmd = FILL_LINEAR;
+ }
+ else if(nFormatType == SvNumFormatType::PERCENT)
+ {
+ rInc = 0.01; // tdf#89998 increment by 1% at a time
+ }
+ }
+ }
+ else if (eCellType == CELLTYPE_STRING || eCellType == CELLTYPE_EDIT)
+ {
+ OUString aStr = GetString(nCol, nRow);
+
+ rListData = const_cast<ScUserListData*>(ScGlobal::GetUserList()->GetData(aStr));
+ if (rListData)
+ {
+ bool bMatchCase = false;
+ (void)rListData->GetSubIndex(aStr, rListIndex, bMatchCase);
+ size_t nListStrCount = rListData->GetSubCount();
+ sal_uInt16 nPrevListIndex, nInc = 1;
+ nCol = sal::static_int_cast<SCCOL>( nCol + nAddX );
+ nRow = sal::static_int_cast<SCROW>( nRow + nAddY );
+ for (SCSIZE i=1; i<nCount && rListData; i++)
+ {
+ nPrevListIndex = rListIndex;
+ aStr = GetString(nCol, nRow);
+ if (!rListData->GetSubIndex(aStr, rListIndex, bMatchCase))
+ rListData = nullptr;
+ else
+ {
+ sal_Int32 nIncCurr = rListIndex - nPrevListIndex;
+ if (nIncCurr < 0)
+ nIncCurr += nListStrCount;
+ if (i == 1)
+ nInc = nIncCurr;
+ else if (nInc != nIncCurr)
+ rListData = nullptr;
+ }
+ nCol = sal::static_int_cast<SCCOL>( nCol + nAddX );
+ nRow = sal::static_int_cast<SCROW>( nRow + nAddY );
+ }
+ if (rListData)
+ rInc = nInc;
+ }
+ else if ( nCount > 1 )
+ {
+ // pass rMinDigits to all DecompValueString calls
+ // -> longest number defines rMinDigits
+
+ sal_Int32 nVal1;
+ short nFlag1 = lcl_DecompValueString( aStr, nVal1, &rMinDigits );
+ if ( nFlag1 )
+ {
+ sal_Int32 nVal2;
+ aStr = GetString( nCol+nAddX, nRow+nAddY );
+ short nFlag2 = lcl_DecompValueString( aStr, nVal2, &rMinDigits );
+ if ( nFlag1 == nFlag2 )
+ {
+ rInc = approxDiff( nVal2, nVal1);
+ nCol = sal::static_int_cast<SCCOL>( nCol + nAddX );
+ nRow = sal::static_int_cast<SCROW>( nRow + nAddY );
+ bool bVal = true;
+ for (SCSIZE i=1; i<nCount && bVal; i++)
+ {
+ ScRefCellValue aCell = GetCellValue(nCol, nRow);
+ CellType eType = aCell.meType;
+ if ( eType == CELLTYPE_STRING || eType == CELLTYPE_EDIT )
+ {
+ aStr = aCell.getString(&rDocument);
+ nFlag2 = lcl_DecompValueString( aStr, nVal2, &rMinDigits );
+ if ( nFlag1 == nFlag2 )
+ {
+ double nDiff = approxDiff( nVal2, nVal1);
+ if ( !::rtl::math::approxEqual( nDiff, rInc, 13 ) )
+ bVal = false;
+ nVal1 = nVal2;
+ }
+ else
+ bVal = false;
+ }
+ else
+ bVal = false;
+ nCol = sal::static_int_cast<SCCOL>( nCol + nAddX );
+ nRow = sal::static_int_cast<SCROW>( nRow + nAddY );
+ }
+ if (bVal)
+ rCmd = FILL_LINEAR;
+ }
+ }
+ }
+ else
+ {
+ // call DecompValueString to set rMinDigits
+ sal_Int32 nDummy;
+ lcl_DecompValueString( aStr, nDummy, &rMinDigits );
+ }
+ }
+}
+
+void ScTable::FillFormula(
+ const ScFormulaCell* pSrcCell, SCCOL nDestCol, SCROW nDestRow, bool bLast )
+{
+
+ rDocument.SetNoListening( true ); // still the wrong reference
+ ScAddress aAddr( nDestCol, nDestRow, nTab );
+ ScFormulaCell* pDestCell = new ScFormulaCell( *pSrcCell, rDocument, aAddr );
+ aCol[nDestCol].SetFormulaCell(nDestRow, pDestCell);
+
+ if ( bLast && pDestCell->GetMatrixFlag() != ScMatrixMode::NONE )
+ {
+ ScAddress aOrg;
+ if ( pDestCell->GetMatrixOrigin( GetDoc(), aOrg ) )
+ {
+ if ( nDestCol >= aOrg.Col() && nDestRow >= aOrg.Row() )
+ {
+ ScFormulaCell* pOrgCell = rDocument.GetFormulaCell(aOrg);
+ if (pOrgCell && pOrgCell->GetMatrixFlag() == ScMatrixMode::Formula)
+ {
+ pOrgCell->SetMatColsRows(
+ nDestCol - aOrg.Col() + 1,
+ nDestRow - aOrg.Row() + 1 );
+ }
+ else
+ {
+ OSL_FAIL( "FillFormula: MatrixOrigin no formula cell with ScMatrixMode::Formula" );
+ }
+ }
+ else
+ {
+ OSL_FAIL( "FillFormula: MatrixOrigin bottom right" );
+ }
+ }
+ else
+ {
+ OSL_FAIL( "FillFormula: no MatrixOrigin" );
+ }
+ }
+ rDocument.SetNoListening( false );
+ pDestCell->StartListeningTo( rDocument );
+}
+
+void ScTable::FillAuto( SCCOL nCol1, SCROW nRow1, SCCOL nCol2, SCROW nRow2,
+ sal_uInt64 nFillCount, FillDir eFillDir, ScProgress* pProgress )
+{
+ if ( (nFillCount == 0) || !ValidColRow(nCol1, nRow1) || !ValidColRow(nCol2, nRow2) )
+ return;
+
+ // Detect direction
+
+ bool bVertical = (eFillDir == FILL_TO_BOTTOM || eFillDir == FILL_TO_TOP);
+ bool bPositive = (eFillDir == FILL_TO_BOTTOM || eFillDir == FILL_TO_RIGHT);
+
+ SCCOLROW nCol = 0;
+ SCCOLROW nRow = 0;
+ SCCOLROW& rInner = bVertical ? nRow : nCol; // loop variables
+ SCCOLROW& rOuter = bVertical ? nCol : nRow;
+ SCCOLROW nOStart;
+ SCCOLROW nOEnd;
+ SCCOLROW nIStart;
+ SCCOLROW nIEnd;
+ SCCOLROW nISrcStart;
+ SCCOLROW nISrcEnd;
+ ScRange aFillRange;
+
+ if (bVertical)
+ {
+ nOStart = nCol1;
+ nOEnd = nCol2;
+ if (bPositive)
+ {
+ nISrcStart = nRow1;
+ nISrcEnd = nRow2;
+ nIStart = nRow2 + 1;
+ nIEnd = nRow2 + nFillCount;
+ aFillRange = ScRange(nCol1, nRow2+1, 0, nCol2, nRow2 + nFillCount, 0);
+ }
+ else
+ {
+ nISrcStart = nRow2;
+ nISrcEnd = nRow1;
+ nIStart = nRow1 - 1;
+ nIEnd = nRow1 - nFillCount;
+ aFillRange = ScRange(nCol1, nRow1-1, 0, nCol2, nRow2 - nFillCount, 0);
+ }
+ }
+ else
+ {
+ nOStart = nRow1;
+ nOEnd = nRow2;
+ if (bPositive)
+ {
+ nISrcStart = nCol1;
+ nISrcEnd = nCol2;
+ nIStart = nCol2 + 1;
+ nIEnd = nCol2 + nFillCount;
+ aFillRange = ScRange(nCol2 + 1, nRow1, 0, nCol2 + nFillCount, nRow2, 0);
+ }
+ else
+ {
+ nISrcStart = nCol2;
+ nISrcEnd = nCol1;
+ nIStart = nCol1 - 1;
+ nIEnd = nCol1 - nFillCount;
+ aFillRange = ScRange(nCol1 - 1, nRow1, 0, nCol1 - nFillCount, nRow2, 0);
+ }
+ }
+ sal_uInt64 nIMin = nIStart;
+ sal_uInt64 nIMax = nIEnd;
+ PutInOrder(nIMin,nIMax);
+ bool bHasFiltered = IsDataFiltered(aFillRange);
+
+ if (!bHasFiltered)
+ {
+ if (bVertical)
+ DeleteArea(nCol1, static_cast<SCROW>(nIMin), nCol2, static_cast<SCROW>(nIMax), InsertDeleteFlags::AUTOFILL);
+ else
+ DeleteArea(static_cast<SCCOL>(nIMin), nRow1, static_cast<SCCOL>(nIMax), nRow2, InsertDeleteFlags::AUTOFILL);
+ }
+
+ sal_uInt64 nProgress = 0;
+ if (pProgress)
+ nProgress = pProgress->GetState();
+
+ // Avoid possible repeated calls to StartListeningFormulaCells() (tdf#132165).
+ std::list< sc::DelayStartListeningFormulaCells > delayStartListening;
+ SCCOL delayStartColumn, delayEndColumn;
+ if(bVertical)
+ {
+ delayStartColumn = std::min( nOStart, nOEnd );
+ delayEndColumn = std::max( nOStart, nOEnd );
+ }
+ else
+ {
+ delayStartColumn = std::min( nIStart, nIEnd );
+ delayEndColumn = std::max( nIStart, nIEnd );
+ }
+ for( SCROW col = delayStartColumn; col <= delayEndColumn; ++col )
+ {
+ if( ScColumn* column = FetchColumn( col ))
+ delayStartListening.emplace_back( *column, true );
+ }
+
+ // execute
+
+ sal_uInt64 nActFormCnt = 0;
+ for (rOuter = nOStart; rOuter <= nOEnd; rOuter++)
+ {
+ sal_uInt64 nMaxFormCnt = 0; // for formulas
+
+ // transfer attributes
+
+ const ScPatternAttr* pSrcPattern = nullptr;
+ const ScStyleSheet* pStyleSheet = nullptr;
+ SCCOLROW nAtSrc = nISrcStart;
+ std::unique_ptr<ScPatternAttr> pNewPattern;
+ bool bGetPattern = true;
+ rInner = nIStart;
+ while (true) // #i53728# with "for (;;)" old solaris/x86 compiler mis-optimizes
+ {
+ if (!ColHidden(nCol) && !RowHidden(nRow))
+ {
+ if ( bGetPattern )
+ {
+ if (bVertical) // rInner&:=nRow, rOuter&:=nCol
+ pSrcPattern = GetColumnData(nCol).GetPattern(static_cast<SCROW>(nAtSrc));
+ else // rInner&:=nCol, rOuter&:=nRow
+ pSrcPattern = GetColumnData(nAtSrc).GetPattern(static_cast<SCROW>(nRow));
+ bGetPattern = false;
+ pStyleSheet = pSrcPattern->GetStyleSheet();
+ // do transfer ATTR_MERGE / ATTR_MERGE_FLAG
+ //
+ // Note: ATTR_MERGE is an attribute of the top left cell of a merged area
+ // containing the size of the area. ATTR_MERGE_FLAGs are attributes of the
+ // other cells of a merged area, containing the information about also
+ // overlapping, i.e. visibility of their content.
+ //
+ // TODO: extend the similar incomplete selections to a bounding rectangle to
+ // avoid incomplete fill, where not all AUTO_MERGE_FLAGs are synchronized with
+ // the copied ATTR_MERGE, resulting broken grid and visibility during run-time.
+ //
+ // +--+ +--+--+
+ // | | | | |
+ // +--+--+ +--+--+
+ // | | -> | |
+ // +--+--+ +--+--+
+ // | | | | |
+ // +--+ +--+--+
+ //
+ // TODO: protect incompatible merged cells of the destination area, for example
+ // by skipping the fill operation.
+ //
+ // TODO: by dragging the fill handle select only the multiples of the height
+ // of the originally selected area which is merged vertically to avoid of
+ // incomplete fill.
+ //
+ // +--+ +--+
+ // |XX| |XX|
+ // +XX+ +XX+
+ // |XX| -> |XX|
+ // +--+ +--+
+ // | | | |
+ // +--+ +--+
+ // | |
+ // +--+
+ //
+ // Other things stored in ATTR_MERGE_FLAG, like autofilter button, will be
+ // deleted now, but may need to be repaired later, like at ScDocument::Fill.
+ const SfxItemSet& rSet = pSrcPattern->GetItemSet();
+ if ( rSet.GetItemState(ATTR_MERGE_FLAG, false) == SfxItemState::SET )
+ {
+ ScMF nOldValue = pSrcPattern->GetItem(ATTR_MERGE_FLAG).GetValue();
+ ScMF nOldValueMerge = nOldValue & (ScMF::Hor | ScMF::Ver);
+ // keep only the merge flags
+ if ( nOldValue != nOldValueMerge )
+ {
+ pNewPattern.reset(new ScPatternAttr(*pSrcPattern));
+ SfxItemSet& rNewSet = pNewPattern->GetItemSet();
+ if ( nOldValueMerge == ScMF::NONE )
+ rNewSet.ClearItem(ATTR_MERGE_FLAG);
+ else
+ rNewSet.Put(ScMergeFlagAttr(nOldValueMerge));
+ }
+ else
+ pNewPattern.reset();
+ }
+ else
+ pNewPattern.reset();
+ }
+
+ const ScCondFormatItem& rCondFormatItem = pSrcPattern->GetItem(ATTR_CONDITIONAL);
+ const ScCondFormatIndexes& rCondFormatIndex = rCondFormatItem.GetCondFormatData();
+
+ if ( bVertical && nISrcStart == nISrcEnd && !bHasFiltered )
+ {
+ // set all attributes at once (en bloc)
+ if (pNewPattern || pSrcPattern != rDocument.GetDefPattern())
+ {
+ // Default is already present (DeleteArea)
+ SCROW nY1 = static_cast<SCROW>(std::min( nIStart, nIEnd ));
+ SCROW nY2 = static_cast<SCROW>(std::max( nIStart, nIEnd ));
+ if ( pStyleSheet )
+ aCol[nCol].ApplyStyleArea( nY1, nY2, *pStyleSheet );
+ if ( pNewPattern )
+ aCol[nCol].ApplyPatternArea( nY1, nY2, *pNewPattern );
+ else
+ aCol[nCol].ApplyPatternArea( nY1, nY2, *pSrcPattern );
+
+ for(const auto& rIndex : rCondFormatIndex)
+ {
+ ScConditionalFormat* pCondFormat = mpCondFormatList->GetFormat(rIndex);
+ if (pCondFormat)
+ {
+ ScRangeList aRange = pCondFormat->GetRange();
+ aRange.Join(ScRange(nCol, nY1, nTab, nCol, nY2, nTab));
+ pCondFormat->SetRange(aRange);
+ }
+ }
+ }
+
+ break;
+ }
+
+ if ( bHasFiltered )
+ DeleteArea(static_cast<SCCOL>(nCol), static_cast<SCROW>(nRow),
+ static_cast<SCCOL>(nCol), static_cast<SCROW>(nRow), InsertDeleteFlags::AUTOFILL);
+
+ if ( pSrcPattern != aCol[nCol].GetPattern( static_cast<SCROW>(nRow) ) )
+ {
+ // Transfer template too
+ //TODO: Merge ApplyPattern to AttrArray ??
+ if ( pStyleSheet )
+ aCol[nCol].ApplyStyle( static_cast<SCROW>(nRow), pStyleSheet );
+
+ // Use ApplyPattern instead of SetPattern to keep old MergeFlags
+ if ( pNewPattern )
+ aCol[nCol].ApplyPattern( static_cast<SCROW>(nRow), *pNewPattern );
+ else
+ aCol[nCol].ApplyPattern( static_cast<SCROW>(nRow), *pSrcPattern );
+
+ for(const auto& rIndex : rCondFormatIndex)
+ {
+ ScConditionalFormat* pCondFormat = mpCondFormatList->GetFormat(rIndex);
+ if (pCondFormat)
+ {
+ ScRangeList aRange = pCondFormat->GetRange();
+ aRange.Join(ScRange(nCol, nRow, nTab, nCol, nRow, nTab));
+ pCondFormat->SetRange(aRange);
+ }
+ }
+ }
+
+ if (nAtSrc==nISrcEnd)
+ {
+ if ( nAtSrc != nISrcStart )
+ { // More than one source cell
+ nAtSrc = nISrcStart;
+ bGetPattern = true;
+ }
+ }
+ else if (bPositive)
+ {
+ ++nAtSrc;
+ bGetPattern = true;
+ }
+ else
+ {
+ --nAtSrc;
+ bGetPattern = true;
+ }
+ }
+
+ if (rInner == nIEnd) break;
+ if (bPositive) ++rInner; else --rInner;
+ }
+ pNewPattern.reset();
+
+ // Analyse
+
+ FillCmd eFillCmd;
+ FillDateCmd eDateCmd = {};
+ double nInc;
+ sal_uInt16 nMinDigits;
+ ScUserListData* pListData = nullptr;
+ sal_uInt16 nListIndex;
+ bool bSkipOverlappedCells;
+ std::vector<sal_Int32> aNonOverlappedCellIdx;
+ if (bVertical)
+ FillAnalyse(static_cast<SCCOL>(nCol),nRow1,
+ static_cast<SCCOL>(nCol),nRow2, eFillCmd,eDateCmd,
+ nInc, nMinDigits, pListData, nListIndex,
+ bHasFiltered, bSkipOverlappedCells, aNonOverlappedCellIdx);
+ else
+ FillAnalyse(nCol1,static_cast<SCROW>(nRow),
+ nCol2,static_cast<SCROW>(nRow), eFillCmd,eDateCmd,
+ nInc, nMinDigits, pListData, nListIndex,
+ bHasFiltered, bSkipOverlappedCells, aNonOverlappedCellIdx);
+
+ if (pListData)
+ {
+ sal_uInt16 nListCount = pListData->GetSubCount();
+ if (bSkipOverlappedCells)
+ {
+ int nFillerCount = 1 + ( nISrcEnd - nISrcStart ) * (bPositive ? 1 : -1);
+ std::vector<bool> aIsNonEmptyCell(nFillerCount, false);
+ SCCOLROW nLastValueIdx;
+ if (bPositive)
+ {
+ nLastValueIdx = nISrcEnd - (nFillerCount - 1 - aNonOverlappedCellIdx.back());
+ for (auto i : aNonOverlappedCellIdx)
+ aIsNonEmptyCell[i] = true;
+ }
+ else
+ {
+ nLastValueIdx = nISrcEnd + aNonOverlappedCellIdx[0];
+ for (auto i : aNonOverlappedCellIdx)
+ aIsNonEmptyCell[nFillerCount - 1 - i] = true;
+ }
+
+ OUString aStr;
+ if (bVertical)
+ aStr = GetString(rOuter, nLastValueIdx);
+ else
+ aStr = GetString(nLastValueIdx, rOuter);
+
+ bool bMatchCase = false;
+ (void)pListData->GetSubIndex(aStr, nListIndex, bMatchCase);
+
+ sal_Int32 nFillerIdx = 0;
+ rInner = nIStart;
+ while (true)
+ {
+ if (aIsNonEmptyCell[nFillerIdx])
+ {
+ if (bPositive)
+ {
+ nListIndex += nInc;
+ if (nListIndex >= nListCount) nListIndex -= nListCount;
+ }
+ else
+ {
+ if (nListIndex < nInc) nListIndex += nListCount;
+ nListIndex -= nInc;
+ }
+ aCol[nCol].SetRawString(static_cast<SCROW>(nRow), pListData->GetSubStr(nListIndex));
+
+ }
+ if (rInner == nIEnd) break;
+ nFillerIdx = (nFillerIdx + 1) % nFillerCount;
+ if (bPositive)
+ ++rInner;
+ else
+ --rInner;
+ }
+ }
+ else
+ {
+ if (!bPositive)
+ {
+ // nListIndex of FillAnalyse points to the last entry -> adjust
+ sal_Int64 nAdjust = nListIndex - (nISrcStart - nISrcEnd) * nInc;
+ nAdjust = nAdjust % nListCount;
+ if (nAdjust < 0)
+ nAdjust += nListCount;
+ nListIndex = nAdjust;
+ }
+
+ rInner = nIStart;
+ while (true) // #i53728# with "for (;;)" old solaris/x86 compiler mis-optimizes
+ {
+ if (!ColHidden(nCol) && !RowHidden(nRow))
+ {
+ if (bPositive)
+ {
+ nListIndex += nInc;
+ if (nListIndex >= nListCount) nListIndex -= nListCount;
+ }
+ else
+ {
+ if (nListIndex < nInc) nListIndex += nListCount;
+ nListIndex -= nInc;
+ }
+ aCol[nCol].SetRawString(static_cast<SCROW>(nRow), pListData->GetSubStr(nListIndex));
+ }
+
+ if (rInner == nIEnd) break;
+ if (bPositive) ++rInner; else --rInner;
+ }
+ }
+ if(pProgress)
+ {
+ nProgress += nIMax - nIMin + 1;
+ pProgress->SetStateOnPercent( nProgress );
+ }
+ }
+ else if (eFillCmd == FILL_SIMPLE) // fill with pattern/sample
+ {
+ FillAutoSimple(
+ nISrcStart, nISrcEnd, nIStart, nIEnd, rInner, nCol, nRow,
+ nActFormCnt, nMaxFormCnt, bHasFiltered, bVertical, bPositive, pProgress, nProgress);
+ }
+ else
+ {
+ if (!bPositive)
+ nInc = -nInc;
+ double nEndVal = (nInc>=0.0) ? MAXDOUBLE : -MAXDOUBLE;
+ if (bVertical)
+ FillSeries( static_cast<SCCOL>(nCol), nRow1,
+ static_cast<SCCOL>(nCol), nRow2, nFillCount, eFillDir,
+ eFillCmd, eDateCmd, nInc, nEndVal, nMinDigits, false,
+ pProgress, bSkipOverlappedCells, &aNonOverlappedCellIdx);
+ else
+ FillSeries( nCol1, static_cast<SCROW>(nRow), nCol2,
+ static_cast<SCROW>(nRow), nFillCount, eFillDir,
+ eFillCmd, eDateCmd, nInc, nEndVal, nMinDigits, false,
+ pProgress, bSkipOverlappedCells, &aNonOverlappedCellIdx);
+ if (pProgress)
+ nProgress = pProgress->GetState();
+ }
+
+ if (bVertical)
+ FillSparkline(bVertical, nCol, nRow1, nRow2, nIStart, nIEnd);
+ else
+ FillSparkline(bVertical, nRow, nCol1, nCol2, nIStart, nIEnd);
+
+ nActFormCnt += nMaxFormCnt;
+ }
+}
+
+void ScTable::FillSparkline(bool bVertical, SCCOLROW nFixed,
+ SCCOLROW nStart, SCCOLROW nEnd,
+ SCCOLROW nFillStart, SCCOLROW nFillEnd)
+{
+ bool bHasSparklines = false;
+ std::vector<std::shared_ptr<sc::Sparkline>> aSparklineSeries;
+
+ for (SCROW nCurrent = nStart; nCurrent <= nEnd; nCurrent++)
+ {
+ auto pSparkline = bVertical ? GetSparkline(nFixed, nCurrent) : GetSparkline(nCurrent, nFixed);
+ bHasSparklines = bHasSparklines || bool(pSparkline);
+ aSparklineSeries.push_back(pSparkline);
+ }
+
+ if (bHasSparklines)
+ {
+ for (SCCOLROW nCurrent = nFillStart; nCurrent <= nFillEnd; nCurrent++)
+ {
+ size_t nIndex = size_t(nFillStart - nCurrent) % aSparklineSeries.size();
+ if (auto& rpSparkline = aSparklineSeries[nIndex])
+ {
+ auto pGroup = rpSparkline->getSparklineGroup();
+
+ auto* pNewSparkline = bVertical ? CreateSparkline(nFixed, nCurrent, pGroup)
+ : CreateSparkline(nCurrent, nFixed, pGroup);
+ if (pNewSparkline)
+ {
+ SCCOLROW nPosition = bVertical ? rpSparkline->getRow()
+ : rpSparkline->getColumn();
+ SCCOLROW nDelta = nCurrent - nPosition;
+ ScRangeList aRangeList(rpSparkline->getInputRange());
+ for (ScRange& rRange : aRangeList)
+ {
+ if (bVertical)
+ {
+ rRange.aStart.IncRow(nDelta);
+ rRange.aEnd.IncRow(nDelta);
+ }
+ else
+ {
+ rRange.aStart.IncCol(nDelta);
+ rRange.aEnd.IncCol(nDelta);
+ }
+ }
+ pNewSparkline->setInputRange(aRangeList);
+ }
+ }
+ }
+ }
+}
+
+OUString ScTable::GetAutoFillPreview( const ScRange& rSource, SCCOL nEndX, SCROW nEndY )
+{
+ OUString aValue;
+
+ SCCOL nCol1 = rSource.aStart.Col();
+ SCROW nRow1 = rSource.aStart.Row();
+ SCCOL nCol2 = rSource.aEnd.Col();
+ SCROW nRow2 = rSource.aEnd.Row();
+ bool bOk = true;
+ tools::Long nIndex = 0;
+ sal_uInt64 nSrcCount = 0;
+ FillDir eFillDir = FILL_TO_BOTTOM;
+ if ( nEndX == nCol2 && nEndY == nRow2 ) // empty
+ bOk = false;
+ else if ( nEndX == nCol2 ) // to up / down
+ {
+ nCol2 = nCol1; // use only first column
+ nSrcCount = nRow2 - nRow1 + 1;
+ nIndex = static_cast<tools::Long>(nEndY) - nRow1; // can be negative
+ if ( nEndY >= nRow1 )
+ eFillDir = FILL_TO_BOTTOM;
+ else
+ eFillDir = FILL_TO_TOP;
+ }
+ else if ( nEndY == nRow2 ) // to left / right
+ {
+ nEndY = nRow2 = nRow1; // use only first row
+ nSrcCount = nCol2 - nCol1 + 1;
+ nIndex = static_cast<tools::Long>(nEndX) - nCol1; // can be negative
+ if ( nEndX >= nCol1 )
+ eFillDir = FILL_TO_RIGHT;
+ else
+ eFillDir = FILL_TO_LEFT;
+ }
+ else // direction not clear
+ bOk = false;
+
+ if ( bOk )
+ {
+ tools::Long nBegin = 0;
+ tools::Long nEnd = 0;
+ tools::Long nHidden = 0;
+ if (eFillDir == FILL_TO_BOTTOM || eFillDir == FILL_TO_TOP)
+ {
+ if (nEndY > nRow1)
+ {
+ nBegin = nRow2+1;
+ nEnd = nEndY;
+ }
+ else
+ {
+ nBegin = nEndY;
+ nEnd = nRow1 -1;
+ }
+
+ tools::Long nVisible = CountVisibleRows(nBegin, nEnd);
+ nHidden = nEnd + 1 - nBegin - nVisible;
+ }
+ else
+ {
+ if (nEndX > nCol1)
+ {
+ nBegin = nCol2+1;
+ nEnd = nEndX;
+ }
+ else
+ {
+ nBegin = nEndX;
+ nEnd = nCol1 -1;
+ }
+
+ tools::Long nVisible = CountVisibleCols(nBegin, nEnd);
+ nHidden = nEnd + 1 - nBegin - nVisible;
+ }
+ if (nHidden)
+ {
+ if (nIndex > 0)
+ nIndex = nIndex - nHidden;
+ else
+ nIndex = nIndex + nHidden;
+ }
+
+ FillCmd eFillCmd;
+ FillDateCmd eDateCmd;
+ double nInc;
+ sal_uInt16 nMinDigits;
+ ScUserListData* pListData = nullptr;
+ sal_uInt16 nListIndex;
+ bool bSkipOverlappedCells;
+ std::vector<sal_Int32> aNonOverlappedCellIdx;
+
+ // Todo: update this function to calculate with merged cell fills,
+ // after FillAnalyse / FillSeries fully handle them.
+ // Now FillAnalyse called as if there are filtered rows, so it will work in the old way.
+ FillAnalyse(nCol1, nRow1, nCol2, nRow2, eFillCmd, eDateCmd,
+ nInc, nMinDigits, pListData, nListIndex,
+ true, bSkipOverlappedCells, aNonOverlappedCellIdx);
+
+ if ( pListData ) // user defined list
+ {
+ sal_uInt16 nListCount = pListData->GetSubCount();
+ if ( nListCount )
+ {
+ sal_uInt64 nSub = nSrcCount - 1; // nListIndex is from last source entry
+ while ( nIndex < sal::static_int_cast<tools::Long>(nSub) )
+ nIndex += nListCount;
+ sal_uInt64 nPos = ( nListIndex + nIndex - nSub ) % nListCount;
+ aValue = pListData->GetSubStr(sal::static_int_cast<sal_uInt16>(nPos));
+ }
+ }
+ else if ( eFillCmd == FILL_SIMPLE ) // fill with pattern/sample
+ {
+ tools::Long nPosIndex = nIndex;
+ while ( nPosIndex < 0 )
+ nPosIndex += nSrcCount;
+ sal_uInt64 nPos = nPosIndex % nSrcCount;
+ SCCOL nSrcX = nCol1;
+ SCROW nSrcY = nRow1;
+ if ( eFillDir == FILL_TO_TOP || eFillDir == FILL_TO_BOTTOM )
+ nSrcY = sal::static_int_cast<SCROW>( nSrcY + static_cast<SCROW>(nPos) );
+ else
+ nSrcX = sal::static_int_cast<SCCOL>( nSrcX + static_cast<SCCOL>(nPos) );
+
+ ScRefCellValue aCell = GetCellValue(nSrcX, nSrcY);
+ if (!aCell.isEmpty())
+ {
+ sal_Int32 nDelta;
+ if (nIndex >= 0)
+ nDelta = nIndex / nSrcCount;
+ else
+ nDelta = ( nIndex - nSrcCount + 1 ) / nSrcCount; // -1 -> -1
+
+ CellType eType = aCell.meType;
+ switch ( eType )
+ {
+ case CELLTYPE_STRING:
+ case CELLTYPE_EDIT:
+ {
+ aValue = aCell.getString(&rDocument);
+
+ if ( !(nScFillModeMouseModifier & KEY_MOD1) )
+ {
+ sal_Int32 nVal;
+ sal_uInt16 nCellDigits = 0; // look at each source cell individually
+ short nFlag = lcl_DecompValueString( aValue, nVal, &nCellDigits );
+ if ( nFlag < 0 )
+ {
+ if (aValue == ScGlobal::GetOrdinalSuffix( nVal))
+ aValue = ScGlobal::GetOrdinalSuffix( nVal + nDelta);
+ aValue = lcl_ValueString( nVal + nDelta, nCellDigits ) + aValue;
+ }
+ else if ( nFlag > 0 )
+ {
+ sal_Int32 nNextValue;
+ if ( nVal < 0 )
+ nNextValue = nVal - nDelta;
+ else
+ nNextValue = nVal + nDelta;
+ if ( nFlag == 2 && nNextValue >= 0 ) // Put back the '+'
+ aValue += "+";
+ aValue += lcl_ValueString( nNextValue, nCellDigits );
+ }
+ }
+ }
+ break;
+ case CELLTYPE_VALUE:
+ {
+ sal_uInt32 nNumFmt = GetNumberFormat( nSrcX, nSrcY );
+ // overflow is possible...
+ double nVal = aCell.mfValue;
+ if ( !(nScFillModeMouseModifier & KEY_MOD1) )
+ {
+ const SvNumFormatType nFormatType = rDocument.GetFormatTable()->GetType(nNumFmt);
+ bool bPercentCell = (nFormatType == SvNumFormatType::PERCENT);
+ if (bPercentCell)
+ {
+ // tdf#89998 increment by 1% at a time
+ nVal += static_cast<double>(nDelta) * 0.01;
+ }
+ else if (nVal == 0.0 || nVal == 1.0)
+ {
+ bool bBooleanCell = (nFormatType == SvNumFormatType::LOGICAL);
+ if (!bBooleanCell)
+ nVal += static_cast<double>(nDelta);
+ }
+ else
+ {
+ nVal += static_cast<double>(nDelta);
+ }
+ }
+
+ const Color* pColor;
+ rDocument.GetFormatTable()->GetOutputString( nVal, nNumFmt, aValue, &pColor );
+ }
+ break;
+ // not for formulas
+ default:
+ {
+ // added to avoid warnings
+ }
+ }
+ }
+ }
+ else if ( eFillCmd == FILL_LINEAR || eFillCmd == FILL_DATE ) // values
+ {
+ bool bValueOk;
+ double nStart;
+ sal_Int32 nVal = 0;
+ short nHeadNoneTail = 0;
+ ScRefCellValue aCell = GetCellValue(nCol1, nRow1);
+ if (!aCell.isEmpty())
+ {
+ CellType eType = aCell.meType;
+ switch ( eType )
+ {
+ case CELLTYPE_STRING:
+ case CELLTYPE_EDIT:
+ {
+ aValue = aCell.getString(&rDocument);
+ nHeadNoneTail = lcl_DecompValueString( aValue, nVal );
+ if ( nHeadNoneTail )
+ nStart = static_cast<double>(nVal);
+ else
+ nStart = 0.0;
+ }
+ break;
+ case CELLTYPE_VALUE:
+ nStart = aCell.mfValue;
+ break;
+ case CELLTYPE_FORMULA:
+ nStart = aCell.mpFormula->GetValue();
+ break;
+ default:
+ nStart = 0.0;
+ }
+ }
+ else
+ nStart = 0.0;
+ if ( eFillCmd == FILL_LINEAR )
+ {
+ double nAdd = nInc;
+ bValueOk = ( SubTotal::SafeMult( nAdd, static_cast<double>(nIndex) ) &&
+ SubTotal::SafePlus( nStart, nAdd ) );
+ }
+ else // date
+ {
+ bValueOk = true;
+ sal_uInt16 nDayOfMonth = 0;
+ if ( nIndex < 0 )
+ {
+ nIndex = -nIndex;
+ nInc = -nInc;
+ }
+ for (tools::Long i=0; i<nIndex; i++)
+ IncDate( nStart, nDayOfMonth, nInc, eDateCmd );
+ }
+
+ if (bValueOk)
+ {
+ if ( nHeadNoneTail )
+ {
+ if ( nHeadNoneTail < 0 )
+ {
+ if (aValue == ScGlobal::GetOrdinalSuffix( nVal))
+ aValue = ScGlobal::GetOrdinalSuffix( static_cast<sal_Int32>(nStart) );
+
+ aValue = lcl_ValueString( static_cast<sal_Int32>(nStart), nMinDigits ) + aValue;
+ }
+ else
+ {
+ if ( nHeadNoneTail == 2 && nStart >= 0 ) // Put back the '+'
+ aValue += "+";
+ aValue += lcl_ValueString( static_cast<sal_Int32>(nStart), nMinDigits );
+ }
+ }
+ else
+ {
+ //TODO: get number format according to Index?
+ const Color* pColor;
+ sal_uInt32 nNumFmt = GetNumberFormat( nCol1, nRow1 );
+ rDocument.GetFormatTable()->GetOutputString( nStart, nNumFmt, aValue, &pColor );
+ }
+ }
+ }
+ else
+ {
+ OSL_FAIL("GetAutoFillPreview: invalid mode");
+ }
+ }
+
+ return aValue;
+}
+
+void ScTable::IncDate(double& rVal, sal_uInt16& nDayOfMonth, double nStep, FillDateCmd eCmd)
+{
+ if (eCmd == FILL_DAY)
+ {
+ rVal += nStep;
+ return;
+ }
+
+ // class Date limits
+ const sal_uInt16 nMinYear = 1583;
+ const sal_uInt16 nMaxYear = 9956;
+
+ tools::Long nInc = static_cast<tools::Long>(nStep); // upper/lower limits ?
+ Date aNullDate = rDocument.GetFormatTable()->GetNullDate();
+ Date aDate = aNullDate;
+ aDate.AddDays(rVal);
+ switch (eCmd)
+ {
+ case FILL_WEEKDAY:
+ {
+ aDate.AddDays(nInc);
+ DayOfWeek eWeekDay = aDate.GetDayOfWeek();
+ if (nInc >= 0)
+ {
+ if (eWeekDay == SATURDAY)
+ aDate.AddDays(2);
+ else if (eWeekDay == SUNDAY)
+ aDate.AddDays(1);
+ }
+ else
+ {
+ if (eWeekDay == SATURDAY)
+ aDate.AddDays(-1);
+ else if (eWeekDay == SUNDAY)
+ aDate.AddDays(-2);
+ }
+ }
+ break;
+ case FILL_MONTH:
+ case FILL_END_OF_MONTH:
+ {
+ if ( nDayOfMonth == 0 )
+ nDayOfMonth = aDate.GetDay(); // init
+ tools::Long nMonth = aDate.GetMonth();
+ tools::Long nYear = aDate.GetYear();
+
+ nMonth += nInc;
+
+ if (nInc >= 0)
+ {
+ if (nMonth > 12)
+ {
+ tools::Long nYAdd = (nMonth-1) / 12;
+ nMonth -= nYAdd * 12;
+ nYear += nYAdd;
+ }
+ }
+ else
+ {
+ if (nMonth < 1)
+ {
+ tools::Long nYAdd = 1 - nMonth / 12; // positive
+ nMonth += nYAdd * 12;
+ nYear -= nYAdd;
+ }
+ }
+
+ if ( nYear < nMinYear )
+ aDate = Date( 1,1, nMinYear );
+ else if ( nYear > nMaxYear )
+ aDate = Date( 31,12, nMaxYear );
+ else
+ {
+ aDate.SetMonth(static_cast<sal_uInt16>(nMonth));
+ aDate.SetYear(static_cast<sal_uInt16>(nYear));
+ if (eCmd == FILL_END_OF_MONTH)
+ {
+ aDate.SetDay(Date::GetDaysInMonth(nMonth, nYear));
+ }
+ else
+ {
+ aDate.SetDay(std::min(Date::GetDaysInMonth(nMonth, nYear), nDayOfMonth));
+ }
+ }
+ }
+ break;
+ case FILL_YEAR:
+ {
+ tools::Long nYear = aDate.GetYear();
+ nYear += nInc;
+ if ( nYear < nMinYear )
+ aDate = Date( 1,1, nMinYear );
+ else if ( nYear > nMaxYear )
+ aDate = Date( 31,12, nMaxYear );
+ else
+ aDate.SetYear(static_cast<sal_uInt16>(nYear));
+ }
+ break;
+ default:
+ {
+ // added to avoid warnings
+ }
+ }
+
+ rVal = aDate - aNullDate;
+}
+
+namespace {
+
+bool HiddenRowColumn(const ScTable* pTable, SCCOLROW nRowColumn, bool bVertical, SCCOLROW& rLastPos)
+{
+ bool bHidden = false;
+ if(bVertical)
+ {
+ SCROW nLast;
+ bHidden = pTable->RowHidden(nRowColumn, nullptr, &nLast);
+ rLastPos = nLast;
+ }
+ else
+ {
+ SCCOL nLast;
+ bHidden = pTable->ColHidden(static_cast<SCCOL>(nRowColumn), nullptr, &nLast);
+ rLastPos = nLast;
+ }
+ return bHidden;
+}
+
+}
+
+void ScTable::FillFormulaVertical(
+ const ScFormulaCell& rSrcCell,
+ SCCOLROW& rInner, SCCOL nCol, SCROW nRow1, SCROW nRow2,
+ ScProgress* pProgress, sal_uInt64& rProgress )
+{
+ // rInner is the row position when filling vertically. Also, when filling
+ // across hidden regions, it may create multiple dis-jointed spans of
+ // formula cells.
+
+ bool bHidden = false;
+ SCCOLROW nHiddenLast = -1;
+
+ SCCOLROW nRowStart = -1, nRowEnd = -1;
+ std::vector<sc::RowSpan> aSpans;
+ PutInOrder(nRow1, nRow2);
+ for (rInner = nRow1; rInner <= nRow2; ++rInner)
+ {
+ if (rInner > nHiddenLast)
+ bHidden = HiddenRowColumn(this, rInner, true, nHiddenLast);
+
+ if (bHidden)
+ {
+ if (nRowStart >= 0)
+ {
+ nRowEnd = rInner - 1;
+ aSpans.emplace_back(nRowStart, nRowEnd);
+ nRowStart = -1;
+ }
+ rInner = nHiddenLast;
+ continue;
+ }
+
+ if (nRowStart < 0)
+ nRowStart = rInner;
+ }
+
+ if (nRowStart >= 0)
+ {
+ nRowEnd = rInner - 1;
+ aSpans.emplace_back(nRowStart, nRowEnd);
+ }
+
+ if (aSpans.empty())
+ return;
+
+ aCol[nCol].DeleteRanges(aSpans, InsertDeleteFlags::VALUE | InsertDeleteFlags::DATETIME | InsertDeleteFlags::STRING | InsertDeleteFlags::FORMULA | InsertDeleteFlags::OUTLINE);
+ aCol[nCol].CloneFormulaCell(rSrcCell, sc::CellTextAttr(), aSpans);
+
+ auto pSet = std::make_shared<sc::ColumnBlockPositionSet>(rDocument);
+ sc::StartListeningContext aStartCxt(rDocument, pSet);
+ sc::EndListeningContext aEndCxt(rDocument, pSet);
+
+ SCROW nStartRow = aSpans.front().mnRow1;
+ SCROW nEndRow = aSpans.back().mnRow2;
+ aCol[nCol].EndListeningFormulaCells(aEndCxt, nStartRow, nEndRow, &nStartRow, &nEndRow);
+ aCol[nCol].StartListeningFormulaCells(aStartCxt, aEndCxt, nStartRow, nEndRow);
+
+ for (const auto& rSpan : aSpans)
+ aCol[nCol].SetDirty(rSpan.mnRow1, rSpan.mnRow2, ScColumn::BROADCAST_NONE);
+
+ rProgress += nRow2 - nRow1 + 1;
+ if (pProgress)
+ pProgress->SetStateOnPercent(rProgress);
+}
+
+void ScTable::FillSeriesSimple(
+ const ScCellValue& rSrcCell, SCCOLROW& rInner, SCCOLROW nIMin, SCCOLROW nIMax,
+ const SCCOLROW& rCol, const SCCOLROW& rRow, bool bVertical, ScProgress* pProgress, sal_uInt64& rProgress )
+{
+ bool bHidden = false;
+ SCCOLROW nHiddenLast = -1;
+
+ if (bVertical)
+ {
+ switch (rSrcCell.meType)
+ {
+ case CELLTYPE_FORMULA:
+ {
+ FillFormulaVertical(
+ *rSrcCell.mpFormula, rInner, rCol, nIMin, nIMax, pProgress, rProgress);
+ }
+ break;
+ default:
+ {
+ for (rInner = nIMin; rInner <= nIMax; ++rInner)
+ {
+ if (rInner > nHiddenLast)
+ bHidden = HiddenRowColumn(this, rInner, bVertical, nHiddenLast);
+
+ if (bHidden)
+ {
+ rInner = nHiddenLast;
+ continue;
+ }
+
+ ScAddress aDestPos(rCol, rRow, nTab);
+ rSrcCell.commit(aCol[rCol], aDestPos.Row());
+ }
+ rProgress += nIMax - nIMin + 1;
+ if (pProgress)
+ pProgress->SetStateOnPercent(rProgress);
+ }
+ }
+ }
+ else
+ {
+ switch (rSrcCell.meType)
+ {
+ case CELLTYPE_FORMULA:
+ {
+ for (rInner = nIMin; rInner <= nIMax; ++rInner)
+ {
+ if (rInner > nHiddenLast)
+ bHidden = HiddenRowColumn(this, rInner, bVertical, nHiddenLast);
+
+ if (bHidden)
+ continue;
+
+ FillFormula(rSrcCell.mpFormula, rCol, rRow, (rInner == nIMax));
+ if (pProgress)
+ pProgress->SetStateOnPercent(++rProgress);
+ }
+ }
+ break;
+ default:
+ {
+ for (rInner = nIMin; rInner <= nIMax; ++rInner)
+ {
+ if (rInner > nHiddenLast)
+ bHidden = HiddenRowColumn(this, rInner, bVertical, nHiddenLast);
+
+ if (bHidden)
+ continue;
+
+ ScAddress aDestPos(rCol, rRow, nTab);
+ rSrcCell.commit(aCol[rCol], aDestPos.Row());
+ }
+ rProgress += nIMax - nIMin + 1;
+ if (pProgress)
+ pProgress->SetStateOnPercent(rProgress);
+ }
+ }
+ }
+}
+
+void ScTable::FillAutoSimple(
+ SCCOLROW nISrcStart, SCCOLROW nISrcEnd, SCCOLROW nIStart, SCCOLROW nIEnd,
+ SCCOLROW& rInner, const SCCOLROW& rCol, const SCCOLROW& rRow, sal_uInt64 nActFormCnt,
+ sal_uInt64 nMaxFormCnt, bool bHasFiltered, bool bVertical, bool bPositive,
+ ScProgress* pProgress, sal_uInt64& rProgress )
+{
+ SCCOLROW nSource = nISrcStart;
+ double nDelta;
+ if ( nScFillModeMouseModifier & KEY_MOD1 )
+ nDelta = 0.0;
+ else if ( bPositive )
+ nDelta = 1.0;
+ else
+ nDelta = -1.0;
+ sal_uInt64 nFormulaCounter = nActFormCnt;
+ bool bGetCell = true;
+ bool bBooleanCell = false;
+ bool bPercentCell = false;
+ sal_uInt16 nCellDigits = 0;
+ short nHeadNoneTail = 0;
+ sal_Int32 nStringValue = 0;
+ OUString aValue;
+ ScCellValue aSrcCell;
+ bool bIsOrdinalSuffix = false;
+
+ bool bColHidden = false, bRowHidden = false;
+ SCCOL nColHiddenFirst = rDocument.MaxCol();
+ SCCOL nColHiddenLast = -1;
+ SCROW nRowHiddenFirst = rDocument.MaxRow();
+ SCROW nRowHiddenLast = -1;
+
+ rInner = nIStart;
+ while (true) // #i53728# with "for (;;)" old solaris/x86 compiler mis-optimizes
+ {
+ if (bPositive)
+ {
+ if (rCol > nColHiddenLast)
+ bColHidden = ColHidden(rCol, nullptr, &nColHiddenLast);
+ if (rRow > nRowHiddenLast)
+ bRowHidden = RowHidden(rRow, nullptr, &nRowHiddenLast);
+ }
+ else
+ {
+ if (rCol < nColHiddenFirst)
+ bColHidden = ColHidden(rCol, &nColHiddenFirst);
+ if (rRow < nRowHiddenFirst)
+ bRowHidden = RowHidden(rRow, &nRowHiddenFirst);
+ }
+
+ if (!bColHidden && !bRowHidden)
+ {
+ if ( bGetCell )
+ {
+ if (bVertical) // rInner&:=nRow, rOuter&:=nCol
+ {
+ aSrcCell = GetCellValue(rCol, nSource);
+ if (nISrcStart == nISrcEnd && aSrcCell.meType == CELLTYPE_FORMULA)
+ {
+ FillFormulaVertical(*aSrcCell.mpFormula, rInner, rCol, nIStart, nIEnd, pProgress, rProgress);
+ return;
+ }
+ const SvNumFormatType nFormatType = rDocument.GetFormatTable()->GetType(
+ GetColumnData(rCol).GetNumberFormat( rDocument.GetNonThreadedContext(), nSource));
+ bBooleanCell = (nFormatType == SvNumFormatType::LOGICAL);
+ bPercentCell = (nFormatType == SvNumFormatType::PERCENT);
+
+ }
+ else // rInner&:=nCol, rOuter&:=nRow
+ {
+ aSrcCell = GetCellValue(nSource, rRow);
+ const SvNumFormatType nFormatType = rDocument.GetFormatTable()->GetType(
+ GetColumnData(nSource).GetNumberFormat( rDocument.GetNonThreadedContext(), rRow));
+ bBooleanCell = (nFormatType == SvNumFormatType::LOGICAL);
+ bPercentCell = (nFormatType == SvNumFormatType::PERCENT);
+ }
+
+ bGetCell = false;
+ if (!aSrcCell.isEmpty())
+ {
+ switch (aSrcCell.meType)
+ {
+ case CELLTYPE_STRING:
+ case CELLTYPE_EDIT:
+ if (aSrcCell.meType == CELLTYPE_STRING)
+ aValue = aSrcCell.mpString->getString();
+ else
+ aValue = ScEditUtil::GetString(*aSrcCell.mpEditText, &rDocument);
+ if ( !(nScFillModeMouseModifier & KEY_MOD1) && !bHasFiltered )
+ {
+ nCellDigits = 0; // look at each source cell individually
+ nHeadNoneTail = lcl_DecompValueString(
+ aValue, nStringValue, &nCellDigits );
+
+ bIsOrdinalSuffix = aValue ==
+ ScGlobal::GetOrdinalSuffix(nStringValue);
+ }
+ break;
+ default:
+ {
+ // added to avoid warnings
+ }
+ }
+ }
+ }
+
+ switch (aSrcCell.meType)
+ {
+ case CELLTYPE_VALUE:
+ {
+ double fVal;
+ if (bBooleanCell && ((fVal = aSrcCell.mfValue) == 0.0 || fVal == 1.0))
+ aCol[rCol].SetValue(rRow, aSrcCell.mfValue);
+ else if(bPercentCell)
+ aCol[rCol].SetValue(rRow, aSrcCell.mfValue + nDelta * 0.01); // tdf#89998 increment by 1% at a time
+ else
+ aCol[rCol].SetValue(rRow, aSrcCell.mfValue + nDelta);
+ }
+ break;
+ case CELLTYPE_STRING:
+ case CELLTYPE_EDIT:
+ if ( nHeadNoneTail )
+ {
+ sal_Int32 nNextValue;
+ if (nStringValue < 0)
+ nNextValue = nStringValue - static_cast<sal_Int32>(nDelta);
+ else
+ nNextValue = nStringValue + static_cast<sal_Int32>(nDelta);
+
+ if ( nHeadNoneTail < 0 )
+ {
+ setSuffixCell(
+ aCol[rCol], rRow,
+ nNextValue, nCellDigits, aValue,
+ aSrcCell.meType, bIsOrdinalSuffix);
+ }
+ else
+ {
+ OUString aStr;
+ if (nHeadNoneTail == 2 && nNextValue >= 0) // Put back the '+'
+ aStr = aValue + "+" + lcl_ValueString(nNextValue, nCellDigits);
+ else
+ aStr = aValue + lcl_ValueString(nNextValue, nCellDigits);
+
+ aCol[rCol].SetRawString(rRow, aStr);
+ }
+ }
+ else
+ aSrcCell.commit(aCol[rCol], rRow);
+
+ break;
+ case CELLTYPE_FORMULA :
+ FillFormula(
+ aSrcCell.mpFormula, rCol, rRow, (rInner == nIEnd));
+ if (nFormulaCounter - nActFormCnt > nMaxFormCnt)
+ nMaxFormCnt = nFormulaCounter - nActFormCnt;
+ break;
+ default:
+ {
+ // added to avoid warnings
+ }
+ }
+
+ if (nSource == nISrcEnd)
+ {
+ if ( nSource != nISrcStart )
+ { // More than one source cell
+ nSource = nISrcStart;
+ bGetCell = true;
+ }
+ if ( !(nScFillModeMouseModifier & KEY_MOD1) )
+ {
+ if ( bPositive )
+ nDelta += 1.0;
+ else
+ nDelta -= 1.0;
+ }
+ nFormulaCounter = nActFormCnt;
+ }
+ else if (bPositive)
+ {
+ ++nSource;
+ bGetCell = true;
+ }
+ else
+ {
+ --nSource;
+ bGetCell = true;
+ }
+ }
+
+ if (rInner == nIEnd)
+ break;
+ if (bPositive)
+ ++rInner;
+ else
+ --rInner;
+
+ // Progress in inner loop only for expensive cells,
+ // and even then not individually for each one
+
+ ++rProgress;
+ if ( pProgress && (aSrcCell.meType == CELLTYPE_FORMULA || aSrcCell.meType == CELLTYPE_EDIT) )
+ pProgress->SetStateOnPercent( rProgress );
+
+ }
+ if (pProgress)
+ pProgress->SetStateOnPercent( rProgress );
+}
+
+namespace
+{
+// Target value exceeded?
+inline bool isOverflow( const double& rVal, const double& rMax, const double& rStep,
+ const double& rStartVal, FillCmd eFillCmd )
+{
+ switch (eFillCmd)
+ {
+ case FILL_LINEAR:
+ case FILL_DATE:
+ if (rStep >= 0.0)
+ return rVal > rMax;
+ else
+ return rVal < rMax;
+ case FILL_GROWTH:
+ if (rStep > 0.0)
+ {
+ if (rStep >= 1.0)
+ {
+ // Growing away from zero, including zero growth (1.0).
+ if (rVal >= 0.0)
+ return rVal > rMax;
+ else
+ return rVal < rMax;
+ }
+ else
+ {
+ // Shrinking towards zero.
+ if (rVal >= 0.0)
+ return rVal < rMax;
+ else
+ return rVal > rMax;
+ }
+ }
+ else if (rStep < 0.0)
+ {
+ // Alternating positive and negative values.
+ if (rStep <= -1.0)
+ {
+ // Growing away from zero, including zero growth (-1.0).
+ if (rVal >= 0.0)
+ {
+ if (rMax >= 0.0)
+ return rVal > rMax;
+ else
+ // Regard negative rMax as lower limit, which will
+ // be reached only by a negative rVal.
+ return false;
+ }
+ else
+ {
+ if (rMax <= 0.0)
+ return rVal < rMax;
+ else
+ // Regard positive rMax as upper limit, which will
+ // be reached only by a positive rVal.
+ return false;
+ }
+ }
+ else
+ {
+ // Shrinking towards zero.
+ if (rVal >= 0.0)
+ return rVal < rMax;
+ else
+ return rVal > rMax;
+ }
+ }
+ else // if (rStep == 0.0)
+ {
+ // All values become zero.
+ // Corresponds with bEntireArea in FillSeries().
+ if (rMax > 0.0)
+ return rMax < rStartVal;
+ else if (rMax < 0.0)
+ return rStartVal < rMax;
+ }
+ break;
+ default:
+ assert(!"eFillCmd");
+ }
+ return false;
+}
+}
+
+void ScTable::FillSeries( SCCOL nCol1, SCROW nRow1, SCCOL nCol2, SCROW nRow2,
+ sal_uInt64 nFillCount, FillDir eFillDir, FillCmd eFillCmd, FillDateCmd eFillDateCmd,
+ double nStepValue, double nMaxValue, sal_uInt16 nArgMinDigits,
+ bool bAttribs, ScProgress* pProgress,
+ bool bSkipOverlappedCells, std::vector<sal_Int32>* pNonOverlappedCellIdx )
+{
+ // The term 'inner' here refers to the loop in the filling direction i.e.
+ // when filling vertically, the inner position is the row position whereas
+ // when filling horizontally the column position becomes the inner
+ // position. The term 'outer' refers to the column position when filling
+ // vertically, or the row position when filling horizontally. The fill is
+ // performed once in each 'outer' position e.g. when filling vertically,
+ // we perform the fill once in each column.
+
+ // Detect direction
+
+ bool bVertical = (eFillDir == FILL_TO_BOTTOM || eFillDir == FILL_TO_TOP);
+ bool bPositive = (eFillDir == FILL_TO_BOTTOM || eFillDir == FILL_TO_RIGHT);
+
+ SCCOLROW nCol = 0;
+ SCCOLROW nRow = 0;
+ SCCOLROW& rInner = bVertical ? nRow : nCol; // loop variables
+ SCCOLROW& rOuter = bVertical ? nCol : nRow;
+ SCCOLROW nOStart;
+ SCCOLROW nOEnd;
+ SCCOLROW nIStart;
+ SCCOLROW nIEnd;
+ SCCOLROW nISource;
+ ScRange aFillRange;
+ sal_uInt64 nFillerCount;
+ std::vector<bool> aIsNonEmptyCell;
+
+ if (bVertical)
+ {
+ nFillerCount = (nRow2 - nRow1) + 1;
+ nFillCount += (nRow2 - nRow1);
+ if (nFillCount == 0)
+ return;
+ nOStart = nCol1;
+ nOEnd = nCol2;
+ if (bPositive)
+ {
+ // downward fill
+ nISource = nRow1; // top row of the source range.
+ nIStart = nRow1 + 1; // first row where we start filling.
+ nIEnd = nRow1 + nFillCount;
+ aFillRange = ScRange(nCol1, nRow1 + 1, nTab, nCol2, nRow1 + nFillCount, nTab);
+ }
+ else
+ {
+ // upward fill
+ nISource = nRow2;
+ nIStart = nRow2 - 1;
+ nIEnd = nRow2 - nFillCount;
+ aFillRange = ScRange(nCol1, nRow2 -1, nTab, nCol2, nRow2 - nFillCount, nTab);
+ }
+ }
+ else
+ {
+ nFillerCount = (nCol2 - nCol1) + 1;
+ nFillCount += (nCol2 - nCol1);
+ if (nFillCount == 0)
+ return;
+ nOStart = nRow1;
+ nOEnd = nRow2;
+ if (bPositive)
+ {
+ // to the right
+ nISource = nCol1;
+ nIStart = nCol1 + 1;
+ nIEnd = nCol1 + nFillCount;
+ aFillRange = ScRange(nCol1 + 1, nRow1, nTab, nCol1 + nFillCount, nRow2, nTab);
+ }
+ else
+ {
+ // to the left
+ nISource = nCol2;
+ nIStart = nCol2 - 1;
+ nIEnd = nCol2 - nFillCount;
+ aFillRange = ScRange(nCol2 - 1, nRow1, nTab, nCol2 - nFillCount, nRow2, nTab);
+ }
+ }
+
+ SCCOLROW nIMin = nIStart;
+ SCCOLROW nIMax = nIEnd;
+ PutInOrder(nIMin,nIMax);
+
+ const bool bIsFiltered = IsDataFiltered(aFillRange);
+ bool bEntireArea = (!bIsFiltered && eFillCmd == FILL_SIMPLE);
+ if (!bIsFiltered && !bEntireArea && (eFillCmd == FILL_LINEAR || eFillCmd == FILL_GROWTH)
+ && (nOEnd - nOStart == 0))
+ {
+ // For the usual case of one col/row determine if a numeric series is
+ // at least as long as the area to be filled and does not end earlier,
+ // so we can treat it as entire area for performance reasons at least
+ // in the vertical case.
+ // This is not exact in case of merged cell fills with skipping overlapped parts, but
+ // it is still a good upper estimation.
+ ScCellValue aSrcCell;
+ if (bVertical)
+ aSrcCell = GetCellValue(static_cast<SCCOL>(nOStart), static_cast<SCROW>(nISource));
+ else
+ aSrcCell = GetCellValue(static_cast<SCCOL>(nISource), static_cast<SCROW>(nOStart));
+ // Same logic as for the actual series.
+ if (!aSrcCell.isEmpty() && (aSrcCell.meType == CELLTYPE_VALUE || aSrcCell.meType == CELLTYPE_FORMULA))
+ {
+ double nStartVal;
+ if (aSrcCell.meType == CELLTYPE_VALUE)
+ nStartVal = aSrcCell.mfValue;
+ else
+ nStartVal = aSrcCell.mpFormula->GetValue();
+ if (eFillCmd == FILL_LINEAR)
+ {
+ if (nStepValue == 0.0)
+ bEntireArea = (nStartVal <= nMaxValue); // fill with same value
+ else if (((nMaxValue - nStartVal) / nStepValue) >= nFillCount)
+ bEntireArea = true;
+ }
+ else if (eFillCmd == FILL_GROWTH)
+ {
+ if (nStepValue == 1.0)
+ bEntireArea = (nStartVal <= nMaxValue); // fill with same value
+ else if (nStepValue == -1.0)
+ bEntireArea = (fabs(nStartVal) <= fabs(nMaxValue)); // fill with alternating value
+ else if (nStepValue == 0.0)
+ bEntireArea = (nStartVal == 0.0
+ || (nStartVal < 0.0 && nMaxValue >= 0.0)
+ || (nStartVal > 0.0 && nMaxValue <= 0.0)); // fill with 0.0
+ }
+ }
+ }
+ if (bEntireArea)
+ {
+ InsertDeleteFlags nDel = (bAttribs ? InsertDeleteFlags::AUTOFILL :
+ (InsertDeleteFlags::AUTOFILL & InsertDeleteFlags::CONTENTS));
+ if (bVertical)
+ DeleteArea(nCol1, static_cast<SCROW>(nIMin), nCol2, static_cast<SCROW>(nIMax), nDel);
+ else
+ DeleteArea(static_cast<SCCOL>(nIMin), nRow1, static_cast<SCCOL>(nIMax), nRow2, nDel);
+ }
+
+ sal_uInt64 nProgress = 0;
+ if (pProgress)
+ nProgress = pProgress->GetState();
+
+ // Perform the fill once per each 'outer' position i.e. one per column
+ // when filling vertically.
+
+ for (rOuter = nOStart; rOuter <= nOEnd; rOuter++)
+ {
+ rInner = nISource;
+
+ CreateColumnIfNotExists(nCol);
+
+ // Source cell value. We need to clone the value since it may be inserted repeatedly.
+ ScCellValue aSrcCell = GetCellValue(nCol, static_cast<SCROW>(nRow));
+
+ // Maybe another source cell need to be searched, if the fill is going through merged cells,
+ // where overlapped parts does not contain any information, so they can be skipped.
+ if (bSkipOverlappedCells)
+ {
+ // create a vector to make it easier to decide if a cell need to be filled, or skipped.
+ aIsNonEmptyCell.resize(nFillerCount, false);
+
+ SCCOLROW nFirstValueIdx;
+ if (bPositive)
+ {
+ nFirstValueIdx = nISource + (*pNonOverlappedCellIdx)[0];
+ for (auto i : (*pNonOverlappedCellIdx))
+ aIsNonEmptyCell[i] = true;
+ }
+ else
+ {
+ nFirstValueIdx = nISource - (nFillerCount - 1 - (*pNonOverlappedCellIdx).back());
+ for (auto i : (*pNonOverlappedCellIdx))
+ aIsNonEmptyCell[nFillerCount - 1 - i] = true;
+ }
+
+ //Set the real source cell
+ if (bVertical)
+ aSrcCell = GetCellValue(nOStart, static_cast<SCROW>(nFirstValueIdx));
+ else
+ aSrcCell = GetCellValue(nFirstValueIdx, static_cast<SCROW>(nOStart));
+ }
+
+ const ScPatternAttr* pSrcPattern = aCol[nCol].GetPattern(static_cast<SCROW>(nRow));
+ const ScCondFormatItem& rCondFormatItem = pSrcPattern->GetItem(ATTR_CONDITIONAL);
+ const ScCondFormatIndexes& rCondFormatIndex = rCondFormatItem.GetCondFormatData();
+
+ if (bAttribs)
+ {
+ if (bVertical)
+ {
+ // If entire area (not filtered and simple fill) use the faster
+ // method, else hidden cols/rows should be skipped and series
+ // fill needs to determine the end row dynamically.
+ if (bEntireArea)
+ {
+ SetPatternAreaCondFormat( nCol, static_cast<SCROW>(nIMin),
+ static_cast<SCROW>(nIMax), *pSrcPattern, rCondFormatIndex);
+ }
+ else if (eFillCmd == FILL_SIMPLE)
+ {
+ assert(bIsFiltered);
+ for(SCROW nAtRow = static_cast<SCROW>(nIMin); nAtRow <= static_cast<SCROW>(nIMax); ++nAtRow)
+ {
+ if(!RowHidden(nAtRow))
+ {
+ SetPatternAreaCondFormat( nCol, nAtRow, nAtRow, *pSrcPattern, rCondFormatIndex);
+ }
+ }
+
+ }
+ }
+ else if (bEntireArea || eFillCmd == FILL_SIMPLE)
+ {
+ for (SCCOL nAtCol = static_cast<SCCOL>(nIMin); nAtCol <= sal::static_int_cast<SCCOL>(nIMax); nAtCol++)
+ {
+ if(!ColHidden(nAtCol))
+ {
+ SetPatternAreaCondFormat( nAtCol, nRow, nRow, *pSrcPattern, rCondFormatIndex);
+ }
+ }
+ }
+ }
+
+ if (!aSrcCell.isEmpty())
+ {
+ CellType eCellType = aSrcCell.meType;
+
+ if (eFillCmd == FILL_SIMPLE) // copy
+ {
+ FillSeriesSimple(aSrcCell, rInner, nIMin, nIMax, nCol, nRow, bVertical, pProgress, nProgress);
+ }
+ else if (eCellType == CELLTYPE_VALUE || eCellType == CELLTYPE_FORMULA)
+ {
+ const double nStartVal = (eCellType == CELLTYPE_VALUE ? aSrcCell.mfValue :
+ aSrcCell.mpFormula->GetValue());
+ double nVal = nStartVal;
+ tools::Long nIndex = 0;
+
+ bool bError = false;
+ bool bOverflow = false;
+ bool bNonEmpty = true;
+
+ sal_uInt16 nDayOfMonth = 0;
+ sal_Int32 nFillerIdx = 0;
+ if (bSkipOverlappedCells && !aIsNonEmptyCell[0])
+ --nIndex;
+ rInner = nIStart;
+ while (true)
+ {
+ if (bSkipOverlappedCells)
+ {
+ nFillerIdx = (nFillerIdx + 1) % nFillerCount;
+ bNonEmpty = aIsNonEmptyCell[nFillerIdx];
+ }
+
+ if(!ColHidden(nCol) && !RowHidden(nRow))
+ {
+ if (!bError && bNonEmpty)
+ {
+ switch (eFillCmd)
+ {
+ case FILL_LINEAR:
+ {
+ // use multiplication instead of repeated addition
+ // to avoid accumulating rounding errors
+ nVal = nStartVal;
+ double nAdd = nStepValue;
+ if ( !SubTotal::SafeMult( nAdd, static_cast<double>(++nIndex) ) ||
+ !SubTotal::SafePlus( nVal, nAdd ) )
+ bError = true;
+ }
+ break;
+ case FILL_GROWTH:
+ if (!SubTotal::SafeMult(nVal, nStepValue))
+ bError = true;
+ break;
+ case FILL_DATE:
+ if (fabs(nVal) > D_MAX_LONG_)
+ bError = true;
+ else
+ IncDate(nVal, nDayOfMonth, nStepValue, eFillDateCmd);
+ break;
+ default:
+ {
+ // added to avoid warnings
+ }
+ }
+
+ if (!bError)
+ bOverflow = isOverflow( nVal, nMaxValue, nStepValue, nStartVal, eFillCmd);
+ }
+
+ CreateColumnIfNotExists(nCol);
+ if (bError)
+ aCol[nCol].SetError(static_cast<SCROW>(nRow), FormulaError::NoValue);
+ else if (!bOverflow && bNonEmpty)
+ aCol[nCol].SetValue(static_cast<SCROW>(nRow), nVal);
+
+ if (bAttribs && !bEntireArea && !bOverflow)
+ SetPatternAreaCondFormat( nCol, nRow, nRow, *pSrcPattern, rCondFormatIndex);
+ }
+
+ if (rInner == nIEnd || bOverflow)
+ break;
+ if (bPositive)
+ {
+ ++rInner;
+ }
+ else
+ {
+ --rInner;
+ }
+ }
+ nProgress += nIMax - nIMin + 1;
+ if(pProgress)
+ pProgress->SetStateOnPercent( nProgress );
+ }
+ else if (eCellType == CELLTYPE_STRING || eCellType == CELLTYPE_EDIT)
+ {
+ if ( nStepValue >= 0 )
+ {
+ if ( nMaxValue >= double(LONG_MAX) )
+ nMaxValue = double(LONG_MAX) - 1;
+ }
+ else
+ {
+ if ( nMaxValue <= double(LONG_MIN) )
+ nMaxValue = double(LONG_MIN) + 1;
+ }
+ OUString aValue;
+ if (eCellType == CELLTYPE_STRING)
+ aValue = aSrcCell.mpString->getString();
+ else
+ aValue = ScEditUtil::GetString(*aSrcCell.mpEditText, &rDocument);
+ sal_Int32 nStringValue;
+ sal_uInt16 nMinDigits = nArgMinDigits;
+ short nHeadNoneTail = lcl_DecompValueString( aValue, nStringValue, &nMinDigits );
+ if ( nHeadNoneTail )
+ {
+ const double nStartVal = static_cast<double>(nStringValue);
+ double nVal = nStartVal;
+ tools::Long nIndex = 0;
+ bool bError = false;
+ bool bOverflow = false;
+ bool bNonEmpty = true;
+
+ bool bIsOrdinalSuffix = aValue == ScGlobal::GetOrdinalSuffix(
+ static_cast<sal_Int32>(nStartVal));
+
+ sal_Int32 nFillerIdx = 0;
+ if (bSkipOverlappedCells && !aIsNonEmptyCell[0])
+ --nIndex;
+ rInner = nIStart;
+ while (true)
+ {
+ if (bSkipOverlappedCells)
+ {
+ nFillerIdx = (nFillerIdx + 1) % nFillerCount;
+ bNonEmpty = aIsNonEmptyCell[nFillerIdx];
+ }
+ if(!ColHidden(nCol) && !RowHidden(nRow))
+ {
+ if (!bError && bNonEmpty)
+ {
+ switch (eFillCmd)
+ {
+ case FILL_LINEAR:
+ {
+ // use multiplication instead of repeated addition
+ // to avoid accumulating rounding errors
+ nVal = nStartVal;
+ double nAdd = nStepValue;
+ if ( !SubTotal::SafeMult( nAdd, static_cast<double>(++nIndex) ) ||
+ !SubTotal::SafePlus( nVal, nAdd ) )
+ bError = true;
+ }
+ break;
+ case FILL_GROWTH:
+ if (!SubTotal::SafeMult(nVal, nStepValue))
+ bError = true;
+ break;
+ default:
+ {
+ // added to avoid warnings
+ }
+ }
+
+ if (!bError)
+ bOverflow = isOverflow( nVal, nMaxValue, nStepValue, nStartVal, eFillCmd);
+ }
+
+ if (bError)
+ aCol[nCol].SetError(static_cast<SCROW>(nRow), FormulaError::NoValue);
+ else if (!bOverflow && bNonEmpty)
+ {
+ nStringValue = static_cast<sal_Int32>(nVal);
+ if ( nHeadNoneTail < 0 )
+ {
+ setSuffixCell(
+ aCol[nCol], static_cast<SCROW>(nRow),
+ nStringValue, nMinDigits, aValue,
+ eCellType, bIsOrdinalSuffix);
+ }
+ else
+ {
+ OUString aStr;
+ if (nHeadNoneTail == 2 && nStringValue >= 0) // Put back the '+'
+ aStr = aValue + "+";
+ else
+ aStr = aValue;
+ aStr += lcl_ValueString( nStringValue, nMinDigits );
+ aCol[nCol].SetRawString(static_cast<SCROW>(nRow), aStr);
+ }
+ }
+
+ if (bAttribs && !bEntireArea && !bOverflow)
+ SetPatternAreaCondFormat( nCol, nRow, nRow, *pSrcPattern, rCondFormatIndex);
+ }
+
+ if (rInner == nIEnd || bOverflow)
+ break;
+ if (bPositive)
+ ++rInner;
+ else
+ --rInner;
+ }
+ }
+ if(pProgress)
+ {
+ nProgress += nIMax - nIMin + 1;
+ pProgress->SetStateOnPercent( nProgress );
+ }
+ }
+ }
+ else if(pProgress)
+ {
+ nProgress += nIMax - nIMin + 1;
+ pProgress->SetStateOnPercent( nProgress );
+ }
+ }
+}
+
+void ScTable::Fill( SCCOL nCol1, SCROW nRow1, SCCOL nCol2, SCROW nRow2,
+ sal_uInt64 nFillCount, FillDir eFillDir, FillCmd eFillCmd, FillDateCmd eFillDateCmd,
+ double nStepValue, double nMaxValue, ScProgress* pProgress)
+{
+ if (eFillCmd == FILL_AUTO)
+ FillAuto(nCol1, nRow1, nCol2, nRow2, nFillCount, eFillDir, pProgress);
+ else
+ FillSeries(nCol1, nRow1, nCol2, nRow2, nFillCount, eFillDir,
+ eFillCmd, eFillDateCmd, nStepValue, nMaxValue, 0, true, pProgress);
+}
+
+void ScTable::AutoFormatArea(SCCOL nStartCol, SCROW nStartRow, SCCOL nEndCol, SCROW nEndRow,
+ const ScPatternAttr& rAttr, sal_uInt16 nFormatNo)
+{
+ ScAutoFormat& rFormat = *ScGlobal::GetOrCreateAutoFormat();
+ ScAutoFormatData* pData = rFormat.findByIndex(nFormatNo);
+ if (pData)
+ {
+ ApplyPatternArea(nStartCol, nStartRow, nEndCol, nEndRow, rAttr);
+ }
+}
+
+void ScTable::AutoFormat( SCCOL nStartCol, SCROW nStartRow, SCCOL nEndCol, SCROW nEndRow,
+ sal_uInt16 nFormatNo )
+{
+ if (!(ValidColRow(nStartCol, nStartRow) && ValidColRow(nEndCol, nEndRow)))
+ return;
+
+ ScAutoFormat& rFormat = *ScGlobal::GetOrCreateAutoFormat();
+ ScAutoFormatData* pData = rFormat.findByIndex(nFormatNo);
+ if (!pData)
+ return;
+
+ std::unique_ptr<ScPatternAttr> pPatternAttrs[16];
+ for (sal_uInt8 i = 0; i < 16; ++i)
+ {
+ pPatternAttrs[i].reset(new ScPatternAttr(rDocument.GetPool()));
+ pData->FillToItemSet(i, pPatternAttrs[i]->GetItemSet(), rDocument);
+ }
+
+ SCCOL nCol = nStartCol;
+ SCROW nRow = nStartRow;
+ sal_uInt16 nIndex = 0;
+ // Left top corner
+ AutoFormatArea(nCol, nRow, nCol, nRow, *pPatternAttrs[nIndex], nFormatNo);
+ // Left column
+ if (pData->IsEqualData(4, 8))
+ AutoFormatArea(nStartCol, nStartRow + 1, nStartCol, nEndRow - 1, *pPatternAttrs[4], nFormatNo);
+ else
+ {
+ nIndex = 4;
+ for (nRow = nStartRow + 1; nRow < nEndRow; nRow++)
+ {
+ AutoFormatArea(nCol, nRow, nCol, nRow, *pPatternAttrs[nIndex], nFormatNo);
+ if (nIndex == 4)
+ nIndex = 8;
+ else
+ nIndex = 4;
+ }
+ }
+ // Left bottom corner
+ nRow = nEndRow;
+ nIndex = 12;
+ AutoFormatArea(nCol, nRow, nCol, nRow, *pPatternAttrs[nIndex], nFormatNo);
+ // Right top corner
+ nCol = nEndCol;
+ nRow = nStartRow;
+ nIndex = 3;
+ AutoFormatArea(nCol, nRow, nCol, nRow, *pPatternAttrs[nIndex], nFormatNo);
+ // Right column
+ if (pData->IsEqualData(7, 11))
+ AutoFormatArea(nEndCol, nStartRow + 1, nEndCol, nEndRow - 1, *pPatternAttrs[7], nFormatNo);
+ else
+ {
+ nIndex = 7;
+ for (nRow = nStartRow + 1; nRow < nEndRow; nRow++)
+ {
+ AutoFormatArea(nCol, nRow, nCol, nRow, *pPatternAttrs[nIndex], nFormatNo);
+ if (nIndex == 7)
+ nIndex = 11;
+ else
+ nIndex = 7;
+ }
+ }
+ // Right bottom corner
+ nRow = nEndRow;
+ nIndex = 15;
+ AutoFormatArea(nCol, nRow, nCol, nRow, *pPatternAttrs[nIndex], nFormatNo);
+ nRow = nStartRow;
+ nIndex = 1;
+ for (nCol = nStartCol + 1; nCol < nEndCol; nCol++)
+ {
+ AutoFormatArea(nCol, nRow, nCol, nRow, *pPatternAttrs[nIndex], nFormatNo);
+ if (nIndex == 1)
+ nIndex = 2;
+ else
+ nIndex = 1;
+ }
+ // Bottom row
+ nRow = nEndRow;
+ nIndex = 13;
+ for (nCol = nStartCol + 1; nCol < nEndCol; nCol++)
+ {
+ AutoFormatArea(nCol, nRow, nCol, nRow, *pPatternAttrs[nIndex], nFormatNo);
+ if (nIndex == 13)
+ nIndex = 14;
+ else
+ nIndex = 13;
+ }
+ // Body
+ if ((pData->IsEqualData(5, 6)) && (pData->IsEqualData(9, 10)) && (pData->IsEqualData(5, 9)))
+ AutoFormatArea(nStartCol + 1, nStartRow + 1, nEndCol-1, nEndRow - 1, *pPatternAttrs[5], nFormatNo);
+ else
+ {
+ if ((pData->IsEqualData(5, 9)) && (pData->IsEqualData(6, 10)))
+ {
+ nIndex = 5;
+ for (nCol = nStartCol + 1; nCol < nEndCol; nCol++)
+ {
+ AutoFormatArea(nCol, nStartRow + 1, nCol, nEndRow - 1, *pPatternAttrs[nIndex], nFormatNo);
+ if (nIndex == 5)
+ nIndex = 6;
+ else
+ nIndex = 5;
+ }
+ }
+ else
+ {
+ nIndex = 5;
+ for (nCol = nStartCol + 1; nCol < nEndCol; nCol++)
+ {
+ for (nRow = nStartRow + 1; nRow < nEndRow; nRow++)
+ {
+ AutoFormatArea(nCol, nRow, nCol, nRow, *pPatternAttrs[nIndex], nFormatNo);
+ if ((nIndex == 5) || (nIndex == 9))
+ {
+ if (nIndex == 5)
+ nIndex = 9;
+ else
+ nIndex = 5;
+ }
+ else
+ {
+ if (nIndex == 6)
+ nIndex = 10;
+ else
+ nIndex = 6;
+ }
+ } // for nRow
+ if ((nIndex == 5) || (nIndex == 9))
+ nIndex = 6;
+ else
+ nIndex = 5;
+ } // for nCol
+ } // if not equal Column
+ } // if not all equal
+}
+
+void ScTable::GetAutoFormatAttr(SCCOL nCol, SCROW nRow, sal_uInt16 nIndex, ScAutoFormatData& rData)
+{
+ sal_uInt32 nFormatIndex = GetNumberFormat( nCol, nRow );
+ ScNumFormatAbbrev aNumFormat( nFormatIndex, *rDocument.GetFormatTable() );
+ rData.GetFromItemSet( nIndex, GetPattern( nCol, nRow )->GetItemSet(), aNumFormat );
+}
+
+#define LF_LEFT 1
+#define LF_TOP 2
+#define LF_RIGHT 4
+#define LF_BOTTOM 8
+#define LF_ALL (LF_LEFT | LF_TOP | LF_RIGHT | LF_BOTTOM)
+
+void ScTable::GetAutoFormatFrame(SCCOL nCol, SCROW nRow, sal_uInt16 nFlags, sal_uInt16 nIndex, ScAutoFormatData& rData)
+{
+ const SvxBoxItem* pTheBox = GetAttr(nCol, nRow, ATTR_BORDER);
+ const SvxBoxItem* pLeftBox = GetAttr(nCol - 1, nRow, ATTR_BORDER);
+ const SvxBoxItem* pTopBox = GetAttr(nCol, nRow - 1, ATTR_BORDER);
+ const SvxBoxItem* pRightBox = GetAttr(nCol + 1, nRow, ATTR_BORDER);
+ const SvxBoxItem* pBottomBox = GetAttr(nCol, nRow + 1, ATTR_BORDER);
+
+ SvxBoxItem aBox( ATTR_BORDER );
+ if (nFlags & LF_LEFT)
+ {
+ if (pLeftBox)
+ {
+ if (ScHasPriority(pTheBox->GetLeft(), pLeftBox->GetRight()))
+ aBox.SetLine(pTheBox->GetLeft(), SvxBoxItemLine::LEFT);
+ else
+ aBox.SetLine(pLeftBox->GetRight(), SvxBoxItemLine::LEFT);
+ }
+ else
+ aBox.SetLine(pTheBox->GetLeft(), SvxBoxItemLine::LEFT);
+ }
+ if (nFlags & LF_TOP)
+ {
+ if (pTopBox)
+ {
+ if (ScHasPriority(pTheBox->GetTop(), pTopBox->GetBottom()))
+ aBox.SetLine(pTheBox->GetTop(), SvxBoxItemLine::TOP);
+ else
+ aBox.SetLine(pTopBox->GetBottom(), SvxBoxItemLine::TOP);
+ }
+ else
+ aBox.SetLine(pTheBox->GetTop(), SvxBoxItemLine::TOP);
+ }
+ if (nFlags & LF_RIGHT)
+ {
+ if (pRightBox)
+ {
+ if (ScHasPriority(pTheBox->GetRight(), pRightBox->GetLeft()))
+ aBox.SetLine(pTheBox->GetRight(), SvxBoxItemLine::RIGHT);
+ else
+ aBox.SetLine(pRightBox->GetLeft(), SvxBoxItemLine::RIGHT);
+ }
+ else
+ aBox.SetLine(pTheBox->GetRight(), SvxBoxItemLine::RIGHT);
+ }
+ if (nFlags & LF_BOTTOM)
+ {
+ if (pBottomBox)
+ {
+ if (ScHasPriority(pTheBox->GetBottom(), pBottomBox->GetTop()))
+ aBox.SetLine(pTheBox->GetBottom(), SvxBoxItemLine::BOTTOM);
+ else
+ aBox.SetLine(pBottomBox->GetTop(), SvxBoxItemLine::BOTTOM);
+ }
+ else
+ aBox.SetLine(pTheBox->GetBottom(), SvxBoxItemLine::BOTTOM);
+ }
+ rData.PutItem( nIndex, aBox );
+}
+
+void ScTable::GetAutoFormatData(SCCOL nStartCol, SCROW nStartRow, SCCOL nEndCol, SCROW nEndRow, ScAutoFormatData& rData)
+{
+ if (!(ValidColRow(nStartCol, nStartRow) && ValidColRow(nEndCol, nEndRow)))
+ return;
+
+ if ((nEndCol - nStartCol < 3) || (nEndRow - nStartRow < 3))
+ return;
+
+ // Left top corner
+ GetAutoFormatAttr(nStartCol, nStartRow, 0, rData);
+ GetAutoFormatFrame(nStartCol, nStartRow, LF_ALL, 0, rData);
+ // Left column
+ GetAutoFormatAttr(nStartCol, nStartRow + 1, 4, rData);
+ GetAutoFormatAttr(nStartCol, nStartRow + 2, 8, rData);
+ GetAutoFormatFrame(nStartCol, nStartRow + 1, LF_LEFT | LF_RIGHT | LF_BOTTOM, 4, rData);
+ if (nEndRow - nStartRow >= 4)
+ GetAutoFormatFrame(nStartCol, nStartRow + 2, LF_LEFT | LF_RIGHT | LF_BOTTOM, 8, rData);
+ else
+ rData.CopyItem( 8, 4, ATTR_BORDER );
+ // Left bottom corner
+ GetAutoFormatAttr(nStartCol, nEndRow, 12, rData);
+ GetAutoFormatFrame(nStartCol, nEndRow, LF_ALL, 12, rData);
+ // Right top corner
+ GetAutoFormatAttr(nEndCol, nStartRow, 3, rData);
+ GetAutoFormatFrame(nEndCol, nStartRow, LF_ALL, 3, rData);
+ // Right column
+ GetAutoFormatAttr(nEndCol, nStartRow + 1, 7, rData);
+ GetAutoFormatAttr(nEndCol, nStartRow + 2, 11, rData);
+ GetAutoFormatFrame(nEndCol, nStartRow + 1, LF_LEFT | LF_RIGHT | LF_BOTTOM, 7, rData);
+ if (nEndRow - nStartRow >= 4)
+ GetAutoFormatFrame(nEndCol, nStartRow + 2, LF_LEFT | LF_RIGHT | LF_BOTTOM, 11, rData);
+ else
+ rData.CopyItem( 11, 7, ATTR_BORDER );
+ // Right bottom corner
+ GetAutoFormatAttr(nEndCol, nEndRow, 15, rData);
+ GetAutoFormatFrame(nEndCol, nEndRow, LF_ALL, 15, rData);
+ // Top row
+ GetAutoFormatAttr(nStartCol + 1, nStartRow, 1, rData);
+ GetAutoFormatAttr(nStartCol + 2, nStartRow, 2, rData);
+ GetAutoFormatFrame(nStartCol + 1, nStartRow, LF_TOP | LF_BOTTOM | LF_RIGHT, 1, rData);
+ if (nEndCol - nStartCol >= 4)
+ GetAutoFormatFrame(nStartCol + 2, nStartRow, LF_TOP | LF_BOTTOM | LF_RIGHT, 2, rData);
+ else
+ rData.CopyItem( 2, 1, ATTR_BORDER );
+ // Bottom row
+ GetAutoFormatAttr(nStartCol + 1, nEndRow, 13, rData);
+ GetAutoFormatAttr(nStartCol + 2, nEndRow, 14, rData);
+ GetAutoFormatFrame(nStartCol + 1, nEndRow, LF_TOP | LF_BOTTOM | LF_RIGHT, 13, rData);
+ if (nEndCol - nStartCol >= 4)
+ GetAutoFormatFrame(nStartCol + 2, nEndRow, LF_TOP | LF_BOTTOM | LF_RIGHT, 14, rData);
+ else
+ rData.CopyItem( 14, 13, ATTR_BORDER );
+ // Body
+ GetAutoFormatAttr(nStartCol + 1, nStartRow + 1, 5, rData);
+ GetAutoFormatAttr(nStartCol + 2, nStartRow + 1, 6, rData);
+ GetAutoFormatAttr(nStartCol + 1, nStartRow + 2, 9, rData);
+ GetAutoFormatAttr(nStartCol + 2, nStartRow + 2, 10, rData);
+ GetAutoFormatFrame(nStartCol + 1, nStartRow + 1, LF_RIGHT | LF_BOTTOM, 5, rData);
+ if ((nEndCol - nStartCol >= 4) && (nEndRow - nStartRow >= 4))
+ {
+ GetAutoFormatFrame(nStartCol + 2, nStartRow + 1, LF_RIGHT | LF_BOTTOM, 6, rData);
+ GetAutoFormatFrame(nStartCol + 1, nStartRow + 2, LF_RIGHT | LF_BOTTOM, 9, rData);
+ GetAutoFormatFrame(nStartCol + 2, nStartRow + 2, LF_RIGHT | LF_BOTTOM, 10, rData);
+ }
+ else
+ {
+ rData.CopyItem( 6, 5, ATTR_BORDER );
+ rData.CopyItem( 9, 5, ATTR_BORDER );
+ rData.CopyItem( 10, 5, ATTR_BORDER );
+ }
+}
+
+void ScTable::SetError( SCCOL nCol, SCROW nRow, FormulaError nError)
+{
+ if (ValidColRow(nCol, nRow))
+ aCol[nCol].SetError( nRow, nError );
+}
+
+void ScTable::UpdateInsertTabAbs(SCTAB nTable)
+{
+ for (SCCOL i=0; i < aCol.size(); i++)
+ aCol[i].UpdateInsertTabAbs(nTable);
+}
+
+bool ScTable::GetNextSpellingCell(SCCOL& rCol, SCROW& rRow, bool bInSel,
+ const ScMarkData& rMark) const
+{
+ if (rRow == rDocument.MaxRow()+2) // end of table
+ {
+ rRow = 0;
+ rCol = 0;
+ }
+ else
+ {
+ rRow++;
+ if (rRow == rDocument.MaxRow()+1)
+ {
+ rCol++;
+ rRow = 0;
+ }
+ }
+ if (rCol == rDocument.MaxCol()+1)
+ return true;
+ for (;;)
+ {
+ if (!ValidCol(rCol))
+ return true;
+ if (rCol >= GetAllocatedColumnsCount())
+ return true;
+ if (aCol[rCol].GetNextSpellingCell(rRow, bInSel, rMark))
+ return true;
+ /*else (rRow == rDocument.MaxRow()+1) */
+ rCol++;
+ rRow = 0;
+ }
+}
+
+void ScTable::TestTabRefAbs(SCTAB nTable) const
+{
+ for (SCCOL i=0; i < aCol.size(); i++)
+ if (aCol[i].TestTabRefAbs(nTable))
+ return;
+}
+
+void ScTable::CompileDBFormula( sc::CompileFormulaContext& rCxt )
+{
+ for (SCCOL i = 0; i < aCol.size(); ++i)
+ aCol[i].CompileDBFormula(rCxt);
+}
+
+void ScTable::CompileColRowNameFormula( sc::CompileFormulaContext& rCxt )
+{
+ for (SCCOL i = 0; i < aCol.size(); ++i)
+ aCol[i].CompileColRowNameFormula(rCxt);
+}
+
+SCSIZE ScTable::GetPatternCount( SCCOL nCol ) const
+{
+ if( ValidCol( nCol ) )
+ return aCol[nCol].GetPatternCount();
+ else
+ return 0;
+}
+
+SCSIZE ScTable::GetPatternCount( SCCOL nCol, SCROW nRow1, SCROW nRow2 ) const
+{
+ if( ValidCol( nCol ) && ValidRow( nRow1 ) && ValidRow( nRow2 ) )
+ return aCol[nCol].GetPatternCount( nRow1, nRow2 );
+ else
+ return 0;
+}
+
+bool ScTable::ReservePatternCount( SCCOL nCol, SCSIZE nReserve )
+{
+ if( ValidCol( nCol ) )
+ return aCol[nCol].ReservePatternCount( nReserve );
+ else
+ return false;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sc/source/core/data/table5.cxx b/sc/source/core/data/table5.cxx
new file mode 100644
index 000000000..cddb28e6e
--- /dev/null
+++ b/sc/source/core/data/table5.cxx
@@ -0,0 +1,1295 @@
+/* -*- 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 <scitems.hxx>
+#include <attrib.hxx>
+#include <formulacell.hxx>
+#include <table.hxx>
+#include <column.hxx>
+#include <document.hxx>
+#include <drwlayer.hxx>
+#include <global.hxx>
+#include <stlpool.hxx>
+#include <tabprotection.hxx>
+#include <globstr.hrc>
+#include <scresid.hxx>
+#include <segmenttree.hxx>
+#include <columniterator.hxx>
+#include <globalnames.hxx>
+#include <scmod.hxx>
+#include <printopt.hxx>
+#include <bcaslot.hxx>
+#include <compressedarray.hxx>
+#include <userdat.hxx>
+
+#include <com/sun/star/sheet/TablePageBreakData.hpp>
+
+#include <osl/diagnose.h>
+
+#include <algorithm>
+#include <limits>
+
+using ::com::sun::star::uno::Sequence;
+using ::com::sun::star::sheet::TablePageBreakData;
+using ::std::set;
+
+void ScTable::UpdatePageBreaks(const ScRange* pUserArea)
+{
+ if (rDocument.IsImportingXML())
+ return;
+
+ // pUserArea != NULL -> print area is specified. We need to force-update
+ // the page breaks.
+
+ if (!pUserArea)
+ {
+ if (!bPageSizeValid)
+ return;
+
+ // Always update breaks if force breaks option has changed
+ if (mbPageBreaksValid && mbForceBreaks == SC_MOD()->GetPrintOptions().GetForceBreaks())
+ return;
+ }
+
+ SfxStyleSheetBase* pStyle
+ = rDocument.GetStyleSheetPool()->Find(aPageStyle, SfxStyleFamily::Page);
+ if (!pStyle)
+ {
+ OSL_FAIL("UpdatePageBreaks: Style not found");
+ return;
+ }
+ SfxItemSet* pStyleSet = &pStyle->GetItemSet();
+
+ SCCOL nStartCol = 0;
+ SCROW nStartRow = 0;
+ SCCOL nEndCol = rDocument.MaxCol();
+ SCROW nEndRow = rDocument.MaxRow();
+ if (pUserArea)
+ {
+ nStartCol = pUserArea->aStart.Col();
+ nStartRow = pUserArea->aStart.Row();
+ nEndCol = pUserArea->aEnd.Col();
+ nEndRow = pUserArea->aEnd.Row();
+ }
+ else
+ {
+ sal_uInt16 nAreaCount = GetPrintRangeCount();
+ if (nAreaCount > 1)
+ {
+ // Show nothing, when multiple ranges
+
+ for (SCCOL nX : GetColumnsRange(0, rDocument.MaxCol()))
+ RemoveColBreak(nX, true, false);
+
+ RemoveRowPageBreaks(0, rDocument.MaxRow() - 1);
+
+ return;
+ }
+ else if (nAreaCount == 1)
+ {
+ const ScRange* pArea = GetPrintRange(0);
+ if (pArea)
+ {
+ nStartCol = pArea->aStart.Col();
+ nStartRow = pArea->aStart.Row();
+ nEndCol = pArea->aEnd.Col();
+ nEndRow = pArea->aEnd.Row();
+ }
+ } // otherwise show everything
+ }
+
+ // get bSkipColBreaks/bSkipRowBreaks flags:
+ // fdo#40788 - print range scale settings can cause manual breaks to be
+ // ignored (see below). This behaviour may now be set by the user.
+ mbForceBreaks = SC_MOD()->GetPrintOptions().GetForceBreaks();
+ bool bSkipColBreaks = false;
+ bool bSkipRowBreaks = false;
+
+ if (!mbForceBreaks)
+ {
+ if (const SfxUInt16Item* pItem = pStyleSet->GetItemIfSet(ATTR_PAGE_SCALETOPAGES, false))
+ {
+ bSkipColBreaks = bSkipRowBreaks = pItem->GetValue() > 0;
+ }
+
+ const ScPageScaleToItem* pScaleToItem;
+ if (!bSkipColBreaks && (pScaleToItem = pStyleSet->GetItemIfSet(ATTR_PAGE_SCALETO, false)))
+ {
+ // #i54993# when fitting to width or height, ignore only manual breaks in that direction
+ if (pScaleToItem->GetWidth() > 0)
+ bSkipColBreaks = true;
+ if (pScaleToItem->GetHeight() > 0)
+ bSkipRowBreaks = true;
+ }
+ }
+
+ tools::Long nPageSizeX = aPageSizeTwips.Width();
+ tools::Long nPageSizeY = aPageSizeTwips.Height();
+
+ // Beginning: Remove breaks
+
+ for (SCCOL nX : GetColumnsRange(0, nStartCol - 1))
+ RemoveColBreak(nX, true, false);
+ RemoveRowPageBreaks(0, nStartRow - 1);
+
+ if (nStartCol > 0)
+ SetColBreak(nStartCol, true, false); // AREABREAK
+ if (nStartRow > 0)
+ SetRowBreak(nStartRow, true, false); // AREABREAK
+
+ // Middle part: Distribute breaks
+
+ bool bRepeatCol = (nRepeatStartX != SCCOL_REPEAT_NONE);
+ bool bColFound = false;
+ tools::Long nSizeX = 0;
+ for (SCCOL nX = nStartCol; nX <= nEndCol; nX++)
+ {
+ bool bStartOfPage = false;
+ tools::Long nThisX = ColHidden(nX) ? 0 : mpColWidth->GetValue(nX);
+ bool bManualBreak = HasColManualBreak(nX);
+ if ((nSizeX + nThisX > nPageSizeX) || (bManualBreak && !bSkipColBreaks))
+ {
+ SetColBreak(nX, true, false);
+ nSizeX = 0;
+ bStartOfPage = true;
+ }
+ else if (nX != nStartCol)
+ RemoveColBreak(nX, true, false);
+ else
+ bStartOfPage = true;
+
+ if (bStartOfPage && bRepeatCol && nX > nRepeatStartX && !bColFound)
+ {
+ // subtract size of repeat columns from page size
+ for (SCCOL i = nRepeatStartX; i <= nRepeatEndX; i++)
+ nPageSizeX -= ColHidden(i) ? 0 : mpColWidth->GetValue(i);
+ while (nX <= nRepeatEndX)
+ RemoveColBreak(++nX, true, false);
+ bColFound = true;
+ }
+
+ nSizeX += nThisX;
+ }
+
+ // Remove all page breaks in range.
+ RemoveRowPageBreaks(nStartRow + 1, nEndRow);
+
+ // And set new page breaks.
+ bool bRepeatRow = (nRepeatStartY != SCROW_REPEAT_NONE);
+ bool bRowFound = false;
+ tools::Long nSizeY = 0;
+ ScFlatBoolRowSegments::ForwardIterator aIterHidden(*mpHiddenRows);
+ ScFlatUInt16RowSegments::ForwardIterator aIterHeights(*mpRowHeights);
+ SCROW nNextManualBreak = GetNextManualBreak(nStartRow); // -1 => no more manual breaks
+ for (SCROW nY = nStartRow; nY <= nEndRow; ++nY)
+ {
+ bool bStartOfPage = false;
+ bool bThisRowHidden = false;
+ const bool bHasValue = aIterHidden.getValue(nY, bThisRowHidden);
+ assert(bHasValue);
+ (void)bHasValue;
+ tools::Long nThisY = 0;
+ if (!bThisRowHidden)
+ {
+ sal_uInt16 nTmp;
+ const bool bHasHeight = aIterHeights.getValue(nY, nTmp);
+ assert(bHasHeight);
+ if (bHasHeight)
+ nThisY = static_cast<tools::Long>(nTmp);
+ }
+
+ bool bManualBreak = false;
+ if (nNextManualBreak >= 0)
+ {
+ bManualBreak = (nY == nNextManualBreak);
+ if (nY >= nNextManualBreak)
+ // Query the next manual break position.
+ nNextManualBreak = GetNextManualBreak(nY + 1);
+ }
+
+ if ((nSizeY + nThisY > nPageSizeY) || (bManualBreak && !bSkipRowBreaks))
+ {
+ SetRowBreak(nY, true, false);
+ nSizeY = 0;
+ bStartOfPage = true;
+ }
+ else if (nY != nStartRow)
+ ; // page break already removed
+ else
+ bStartOfPage = true;
+
+ if (bStartOfPage && bRepeatRow && nY > nRepeatStartY && !bRowFound)
+ {
+ // subtract size of repeat rows from page size
+ tools::Long nHeights = GetTotalRowHeight(nRepeatStartY, nRepeatEndY);
+#if OSL_DEBUG_LEVEL > 0
+ if (nHeights == ::std::numeric_limits<tools::Long>::max())
+ OSL_FAIL("ScTable::UpdatePageBreaks: row heights overflow");
+#endif
+ nPageSizeY -= nHeights;
+ if (nY <= nRepeatEndY)
+ RemoveRowPageBreaks(nY, nRepeatEndY);
+ bRowFound = true;
+ }
+
+ if (bThisRowHidden)
+ {
+ // Hidden row range. Skip them unless there is a manual break.
+ SCROW nLastCommon = aIterHidden.getLastPos();
+ if (nNextManualBreak >= 0)
+ nLastCommon = ::std::min(nLastCommon, nNextManualBreak - 1);
+ nY = nLastCommon;
+ }
+ else
+ {
+ // Visible row range.
+
+ SCROW nLastHidden = aIterHidden.getLastPos();
+ SCROW nLastHeight = aIterHeights.getLastPos();
+ SCROW nLastCommon = ::std::min(nLastHidden, nLastHeight);
+ if (nNextManualBreak >= 0)
+ nLastCommon = ::std::min(nLastCommon, nNextManualBreak - 1);
+
+ if (nLastCommon > nY)
+ {
+ tools::Long nMaxMultiple = static_cast<tools::Long>(nLastCommon - nY);
+ tools::Long nMultiple = (nPageSizeY - nSizeY) / nThisY;
+ if (nMultiple > nMaxMultiple)
+ nMultiple = nMaxMultiple;
+ if (nMultiple > 1)
+ {
+ nSizeY += nThisY * (nMultiple - 1);
+ nY += nMultiple - 1;
+ }
+ }
+ }
+
+ nSizeY += nThisY;
+ }
+
+ // End: Remove Break
+
+ if (nEndCol < rDocument.MaxCol())
+ {
+ SetColBreak(nEndCol + 1, true, false); // AREABREAK
+ for (SCCOL nCol : GetColumnsRange(nEndCol + 2, rDocument.MaxCol()))
+ RemoveColBreak(nCol, true, false);
+ }
+ if (nEndRow < rDocument.MaxRow())
+ {
+ SetRowBreak(nEndRow + 1, true, false); // AREABREAK
+ if (nEndRow + 2 <= rDocument.MaxRow())
+ RemoveRowPageBreaks(nEndRow + 2, rDocument.MaxRow());
+ }
+ mbPageBreaksValid
+ = !pUserArea; // #i116881# the valid flag can only apply to the "no user area" case
+}
+
+void ScTable::RemoveManualBreaks()
+{
+ maRowManualBreaks.clear();
+ maColManualBreaks.clear();
+ InvalidatePageBreaks();
+
+ SetStreamValid(false);
+}
+
+bool ScTable::HasManualBreaks() const
+{
+ return !maRowManualBreaks.empty() || !maColManualBreaks.empty();
+}
+
+void ScTable::SetRowManualBreaks(::std::set<SCROW>&& rBreaks)
+{
+ maRowManualBreaks = std::move(rBreaks);
+ InvalidatePageBreaks();
+ SetStreamValid(false);
+}
+
+void ScTable::SetColManualBreaks(::std::set<SCCOL>&& rBreaks)
+{
+ maColManualBreaks = std::move(rBreaks);
+ InvalidatePageBreaks();
+ SetStreamValid(false);
+}
+
+void ScTable::GetAllRowBreaks(set<SCROW>& rBreaks, bool bPage, bool bManual) const
+{
+ if (bPage)
+ rBreaks = maRowPageBreaks;
+
+ if (bManual)
+ {
+ copy(maRowManualBreaks.begin(), maRowManualBreaks.end(),
+ inserter(rBreaks, rBreaks.begin()));
+ }
+}
+
+void ScTable::GetAllColBreaks(set<SCCOL>& rBreaks, bool bPage, bool bManual) const
+{
+ if (bPage)
+ rBreaks = maColPageBreaks;
+
+ if (bManual)
+ {
+ copy(maColManualBreaks.begin(), maColManualBreaks.end(),
+ inserter(rBreaks, rBreaks.begin()));
+ }
+}
+
+bool ScTable::HasRowPageBreak(SCROW nRow) const
+{
+ if (!ValidRow(nRow))
+ return false;
+
+ return maRowPageBreaks.find(nRow) != maRowPageBreaks.end();
+}
+
+bool ScTable::HasColPageBreak(SCCOL nCol) const
+{
+ if (!ValidCol(nCol))
+ return false;
+
+ return maColPageBreaks.find(nCol) != maColPageBreaks.end();
+}
+
+bool ScTable::HasRowManualBreak(SCROW nRow) const
+{
+ if (!ValidRow(nRow))
+ return false;
+
+ return maRowManualBreaks.find(nRow) != maRowManualBreaks.end();
+}
+
+bool ScTable::HasColManualBreak(SCCOL nCol) const
+{
+ if (!ValidCol(nCol))
+ return false;
+
+ return maColManualBreaks.find(nCol) != maColManualBreaks.end();
+}
+
+SCROW ScTable::GetNextManualBreak(SCROW nRow) const
+{
+ set<SCROW>::const_iterator itr = maRowManualBreaks.lower_bound(nRow);
+ return itr == maRowManualBreaks.end() ? -1 : *itr;
+}
+
+void ScTable::RemoveRowPageBreaks(SCROW nStartRow, SCROW nEndRow)
+{
+ if (!ValidRow(nStartRow) || !ValidRow(nEndRow))
+ return;
+
+ set<SCROW>::iterator low = maRowPageBreaks.lower_bound(nStartRow);
+ set<SCROW>::iterator high = maRowPageBreaks.upper_bound(nEndRow);
+ maRowPageBreaks.erase(low, high);
+}
+
+void ScTable::RemoveRowBreak(SCROW nRow, bool bPage, bool bManual)
+{
+ if (!ValidRow(nRow))
+ return;
+
+ if (bPage)
+ maRowPageBreaks.erase(nRow);
+
+ if (bManual)
+ {
+ maRowManualBreaks.erase(nRow);
+ InvalidatePageBreaks();
+ }
+}
+
+void ScTable::RemoveColBreak(SCCOL nCol, bool bPage, bool bManual)
+{
+ if (!ValidCol(nCol))
+ return;
+
+ if (bPage)
+ maColPageBreaks.erase(nCol);
+
+ if (bManual)
+ {
+ maColManualBreaks.erase(nCol);
+ InvalidatePageBreaks();
+ }
+}
+
+void ScTable::SetRowBreak(SCROW nRow, bool bPage, bool bManual)
+{
+ if (!ValidRow(nRow))
+ return;
+
+ if (bPage)
+ maRowPageBreaks.insert(nRow);
+
+ if (bManual)
+ {
+ maRowManualBreaks.insert(nRow);
+ InvalidatePageBreaks();
+ }
+}
+
+void ScTable::SetColBreak(SCCOL nCol, bool bPage, bool bManual)
+{
+ if (!ValidCol(nCol))
+ return;
+
+ if (bPage)
+ maColPageBreaks.insert(nCol);
+
+ if (bManual)
+ {
+ maColManualBreaks.insert(nCol);
+ InvalidatePageBreaks();
+ }
+}
+
+Sequence<TablePageBreakData> ScTable::GetRowBreakData() const
+{
+ using ::std::inserter;
+
+ set<SCROW> aRowBreaks = maRowPageBreaks;
+ copy(maRowManualBreaks.begin(), maRowManualBreaks.end(),
+ inserter(aRowBreaks, aRowBreaks.begin()));
+
+ Sequence<TablePageBreakData> aSeq(aRowBreaks.size());
+ std::transform(aRowBreaks.begin(), aRowBreaks.end(), aSeq.getArray(), [this](const SCROW nRow) {
+ return TablePageBreakData(nRow, HasRowManualBreak(nRow));
+ });
+
+ return aSeq;
+}
+
+bool ScTable::RowHidden(SCROW nRow, SCROW* pFirstRow, SCROW* pLastRow) const
+{
+ if (!ValidRow(nRow))
+ {
+ if (pFirstRow)
+ *pFirstRow = nRow;
+ if (pLastRow)
+ *pLastRow = nRow;
+ return true;
+ }
+
+ ScFlatBoolRowSegments::RangeData aData;
+ if (!mpHiddenRows->getRangeData(nRow, aData))
+ {
+ // search failed.
+ if (pFirstRow)
+ *pFirstRow = nRow;
+ if (pLastRow)
+ *pLastRow = nRow;
+ return true;
+ }
+
+ if (pFirstRow)
+ *pFirstRow = aData.mnRow1;
+ if (pLastRow)
+ *pLastRow = aData.mnRow2;
+
+ return aData.mbValue;
+}
+
+bool ScTable::RowHiddenLeaf(SCROW nRow, SCROW* pFirstRow, SCROW* pLastRow) const
+{
+ if (!ValidRow(nRow))
+ {
+ if (pFirstRow)
+ *pFirstRow = nRow;
+ if (pLastRow)
+ *pLastRow = nRow;
+ return true;
+ }
+
+ ScFlatBoolRowSegments::RangeData aData;
+ if (!mpHiddenRows->getRangeDataLeaf(nRow, aData))
+ {
+ // search failed.
+ if (pFirstRow)
+ *pFirstRow = nRow;
+ if (pLastRow)
+ *pLastRow = nRow;
+ return true;
+ }
+
+ if (pFirstRow)
+ *pFirstRow = aData.mnRow1;
+ if (pLastRow)
+ *pLastRow = aData.mnRow2;
+
+ return aData.mbValue;
+}
+
+bool ScTable::HasHiddenRows(SCROW nStartRow, SCROW nEndRow) const
+{
+ SCROW nRow = nStartRow;
+ while (nRow <= nEndRow)
+ {
+ SCROW nLastRow = -1;
+ bool bHidden = RowHidden(nRow, nullptr, &nLastRow);
+ if (bHidden)
+ return true;
+
+ nRow = nLastRow + 1;
+ }
+ return false;
+}
+
+bool ScTable::ColHidden(SCCOL nCol, SCCOL* pFirstCol, SCCOL* pLastCol) const
+{
+ if (!ValidCol(nCol))
+ return true;
+
+ ScFlatBoolColSegments::RangeData aData;
+ if (!mpHiddenCols->getRangeData(nCol, aData))
+ return true;
+
+ if (pFirstCol)
+ *pFirstCol = aData.mnCol1;
+ if (pLastCol)
+ *pLastCol = aData.mnCol2;
+
+ return aData.mbValue;
+}
+
+bool ScTable::SetRowHidden(SCROW nStartRow, SCROW nEndRow, bool bHidden)
+{
+ bool bChanged = false;
+ if (bHidden)
+ bChanged = mpHiddenRows->setTrue(nStartRow, nEndRow);
+ else
+ bChanged = mpHiddenRows->setFalse(nStartRow, nEndRow);
+
+ // Cell anchored objects might change visibility
+ ScDrawLayer* pDrawLayer = rDocument.GetDrawLayer();
+ if (pDrawLayer)
+ {
+ std::vector<SdrObject*> aRowDrawObjects;
+ aRowDrawObjects = pDrawLayer->GetObjectsAnchoredToRows(GetTab(), nStartRow, nEndRow);
+ for (auto aObj : aRowDrawObjects)
+ {
+ ScDrawObjData* pData = ScDrawLayer::GetObjData(aObj);
+ if (pData)
+ {
+ if (bHidden)
+ aObj->SetVisible(false);
+ else if (!GetDoc().ColHidden(pData->maStart.Col(), pData->maStart.Tab()))
+ {
+ // Only change visibility if object is not hidden by a hidden col
+ aObj->SetVisible(true);
+ }
+ }
+ }
+ }
+
+ if (bChanged)
+ {
+ SetStreamValid(false);
+
+ { // Scoped bulk broadcast.
+ // Only subtotal formula cells will accept the notification of
+ // SfxHintId::ScHiddenRowsChanged, leaving the bulk will track
+ // those and broadcast SfxHintId::ScDataChanged to notify all
+ // dependents.
+ ScBulkBroadcast aBulkBroadcast(rDocument.GetBASM(), SfxHintId::ScDataChanged);
+ for (SCCOL i = 0; i < aCol.size(); i++)
+ {
+ aCol[i].BroadcastRows(nStartRow, nEndRow, SfxHintId::ScHiddenRowsChanged);
+ }
+ }
+ }
+
+ return bChanged;
+}
+
+void ScTable::SetColHidden(SCCOL nStartCol, SCCOL nEndCol, bool bHidden)
+{
+ bool bChanged = false;
+ if (bHidden)
+ bChanged = mpHiddenCols->setTrue(nStartCol, nEndCol);
+ else
+ bChanged = mpHiddenCols->setFalse(nStartCol, nEndCol);
+
+ // Cell anchored objects might change visibility
+ ScDrawLayer* pDrawLayer = rDocument.GetDrawLayer();
+ if (pDrawLayer)
+ {
+ std::vector<SdrObject*> aColDrawObjects;
+ aColDrawObjects = pDrawLayer->GetObjectsAnchoredToCols(GetTab(), nStartCol, nEndCol);
+ for (auto aObj : aColDrawObjects)
+ {
+ ScDrawObjData* pData = ScDrawLayer::GetObjData(aObj);
+ if (pData)
+ {
+ if (bHidden)
+ aObj->SetVisible(false);
+ else if (!GetDoc().RowHidden(pData->maStart.Row(), pData->maStart.Tab()))
+ {
+ // Only change visibility if object is not hidden by a hidden row
+ aObj->SetVisible(true);
+ }
+ }
+ }
+ }
+
+ if (bChanged)
+ SetStreamValid(false);
+}
+
+void ScTable::CopyColHidden(const ScTable& rTable, SCCOL nStartCol, SCCOL nEndCol)
+{
+ SCCOL nCol = nStartCol;
+ while (nCol <= nEndCol)
+ {
+ SCCOL nLastCol = -1;
+ bool bHidden = rTable.ColHidden(nCol, nullptr, &nLastCol);
+ if (nLastCol > nEndCol)
+ nLastCol = nEndCol;
+
+ SetColHidden(nCol, nLastCol, bHidden);
+ nCol = nLastCol + 1;
+ }
+}
+
+void ScTable::CopyRowHidden(const ScTable& rTable, SCROW nStartRow, SCROW nEndRow)
+{
+ SCROW nRow = nStartRow;
+ while (nRow <= nEndRow)
+ {
+ SCROW nLastRow = -1;
+ bool bHidden = rTable.RowHidden(nRow, nullptr, &nLastRow);
+ if (nLastRow > nEndRow)
+ nLastRow = nEndRow;
+ SetRowHidden(nRow, nLastRow, bHidden);
+ nRow = nLastRow + 1;
+ }
+}
+
+void ScTable::CopyRowHeight(const ScTable& rSrcTable, SCROW nStartRow, SCROW nEndRow,
+ SCROW nSrcOffset)
+{
+ SCROW nRow = nStartRow;
+ ScFlatUInt16RowSegments::RangeData aSrcData;
+ while (nRow <= nEndRow)
+ {
+ if (!rSrcTable.mpRowHeights->getRangeData(nRow + nSrcOffset, aSrcData))
+ // Something is wrong !
+ return;
+
+ SCROW nLastRow = aSrcData.mnRow2 - nSrcOffset;
+ if (nLastRow > nEndRow)
+ nLastRow = nEndRow;
+
+ mpRowHeights->setValue(nRow, nLastRow, aSrcData.mnValue);
+ nRow = nLastRow + 1;
+ }
+}
+
+SCROW ScTable::FirstVisibleRow(SCROW nStartRow, SCROW nEndRow) const
+{
+ SCROW nRow = nStartRow;
+ ScFlatBoolRowSegments::RangeData aData;
+ while (nRow <= nEndRow)
+ {
+ if (!ValidRow(nRow))
+ break;
+
+ if (!mpHiddenRows->getRangeData(nRow, aData))
+ // failed to get range data.
+ break;
+
+ if (!aData.mbValue)
+ // visible row found
+ return nRow;
+
+ nRow = aData.mnRow2 + 1;
+ }
+
+ return ::std::numeric_limits<SCROW>::max();
+}
+
+SCROW ScTable::LastVisibleRow(SCROW nStartRow, SCROW nEndRow) const
+{
+ SCROW nRow = nEndRow;
+ ScFlatBoolRowSegments::RangeData aData;
+ while (nRow >= nStartRow)
+ {
+ if (!ValidRow(nRow))
+ break;
+
+ if (!mpHiddenRows->getRangeData(nRow, aData))
+ // failed to get range data.
+ break;
+
+ if (!aData.mbValue)
+ // visible row found
+ return nRow;
+
+ nRow = aData.mnRow1 - 1;
+ }
+
+ return ::std::numeric_limits<SCROW>::max();
+}
+
+SCROW ScTable::CountVisibleRows(SCROW nStartRow, SCROW nEndRow) const
+{
+ SCROW nCount = 0;
+ SCROW nRow = nStartRow;
+ ScFlatBoolRowSegments::RangeData aData;
+ while (nRow <= nEndRow)
+ {
+ if (!mpHiddenRows->getRangeData(nRow, aData))
+ break;
+
+ if (aData.mnRow2 > nEndRow)
+ aData.mnRow2 = nEndRow;
+
+ if (!aData.mbValue)
+ nCount += aData.mnRow2 - nRow + 1;
+
+ nRow = aData.mnRow2 + 1;
+ }
+ return nCount;
+}
+
+tools::Long ScTable::GetTotalRowHeight(SCROW nStartRow, SCROW nEndRow, bool bHiddenAsZero) const
+{
+ tools::Long nHeight = 0;
+ SCROW nRow = nStartRow;
+ ScFlatBoolRowSegments::RangeData aData;
+ while (nRow <= nEndRow)
+ {
+ if (!mpHiddenRows->getRangeData(nRow, aData))
+ break;
+
+ if (aData.mnRow2 > nEndRow)
+ aData.mnRow2 = nEndRow;
+
+ if (!(bHiddenAsZero && aData.mbValue))
+ // visible row range.
+ nHeight += mpRowHeights->getSumValue(nRow, aData.mnRow2);
+
+ nRow = aData.mnRow2 + 1;
+ }
+
+ return nHeight;
+}
+
+SCCOL ScTable::CountVisibleCols(SCCOL nStartCol, SCCOL nEndCol) const
+{
+ assert(nStartCol <= nEndCol);
+ SCCOL nCount = 0;
+ SCCOL nCol = nStartCol;
+ ScFlatBoolColSegments::RangeData aData;
+ while (nCol <= nEndCol)
+ {
+ if (!mpHiddenCols->getRangeData(nCol, aData))
+ break;
+
+ if (aData.mnCol2 > nEndCol)
+ aData.mnCol2 = nEndCol;
+
+ if (!aData.mbValue)
+ nCount += aData.mnCol2 - nCol + 1;
+
+ nCol = aData.mnCol2 + 1;
+ }
+ return nCount;
+}
+
+SCCOLROW ScTable::LastHiddenColRow(SCCOLROW nPos, bool bCol) const
+{
+ if (bCol)
+ {
+ SCCOL nCol = static_cast<SCCOL>(nPos);
+ if (ColHidden(nCol))
+ {
+ for (SCCOL i = nCol + 1; i <= rDocument.MaxCol(); ++i)
+ {
+ if (!ColHidden(i))
+ return i - 1;
+ }
+ }
+ }
+ else
+ {
+ SCROW nRow = static_cast<SCROW>(nPos);
+ SCROW nLastRow;
+ if (RowHidden(nRow, nullptr, &nLastRow))
+ return static_cast<SCCOLROW>(nLastRow);
+ }
+ return ::std::numeric_limits<SCCOLROW>::max();
+}
+
+bool ScTable::RowFiltered(SCROW nRow, SCROW* pFirstRow, SCROW* pLastRow) const
+{
+ if (!ValidRow(nRow))
+ return false;
+
+ ScFlatBoolRowSegments::RangeData aData;
+ if (!mpFilteredRows->getRangeData(nRow, aData))
+ // search failed.
+ return false;
+
+ if (pFirstRow)
+ *pFirstRow = aData.mnRow1;
+ if (pLastRow)
+ *pLastRow = aData.mnRow2;
+
+ return aData.mbValue;
+}
+
+bool ScTable::ColFiltered(SCCOL nCol, SCCOL* pFirstCol, SCCOL* pLastCol) const
+{
+ if (!ValidCol(nCol))
+ return false;
+
+ ScFlatBoolColSegments::RangeData aData;
+ if (!mpFilteredCols->getRangeData(nCol, aData))
+ // search failed.
+ return false;
+
+ if (pFirstCol)
+ *pFirstCol = aData.mnCol1;
+ if (pLastCol)
+ *pLastCol = aData.mnCol2;
+
+ return aData.mbValue;
+}
+
+bool ScTable::HasFilteredRows(SCROW nStartRow, SCROW nEndRow) const
+{
+ SCROW nRow = nStartRow;
+ while (nRow <= nEndRow)
+ {
+ SCROW nLastRow = nRow;
+ bool bFiltered = RowFiltered(nRow, nullptr, &nLastRow);
+ if (bFiltered)
+ return true;
+
+ nRow = nLastRow + 1;
+ }
+ return false;
+}
+
+void ScTable::CopyColFiltered(const ScTable& rTable, SCCOL nStartCol, SCCOL nEndCol)
+{
+ SCCOL nCol = nStartCol;
+ while (nCol <= nEndCol)
+ {
+ SCCOL nLastCol = -1;
+ bool bFiltered = rTable.ColFiltered(nCol, nullptr, &nLastCol);
+ if (nLastCol > nEndCol)
+ nLastCol = nEndCol;
+
+ SetColFiltered(nCol, nLastCol, bFiltered);
+ nCol = nLastCol + 1;
+ }
+}
+
+void ScTable::CopyRowFiltered(const ScTable& rTable, SCROW nStartRow, SCROW nEndRow)
+{
+ SCROW nRow = nStartRow;
+ while (nRow <= nEndRow)
+ {
+ SCROW nLastRow = -1;
+ bool bFiltered = rTable.RowFiltered(nRow, nullptr, &nLastRow);
+ if (nLastRow > nEndRow)
+ nLastRow = nEndRow;
+ SetRowFiltered(nRow, nLastRow, bFiltered);
+ nRow = nLastRow + 1;
+ }
+}
+
+void ScTable::SetRowFiltered(SCROW nStartRow, SCROW nEndRow, bool bFiltered)
+{
+ if (bFiltered)
+ mpFilteredRows->setTrue(nStartRow, nEndRow);
+ else
+ mpFilteredRows->setFalse(nStartRow, nEndRow);
+}
+
+void ScTable::SetColFiltered(SCCOL nStartCol, SCCOL nEndCol, bool bFiltered)
+{
+ if (bFiltered)
+ mpFilteredCols->setTrue(nStartCol, nEndCol);
+ else
+ mpFilteredCols->setFalse(nStartCol, nEndCol);
+}
+
+SCROW ScTable::FirstNonFilteredRow(SCROW nStartRow, SCROW nEndRow) const
+{
+ SCROW nRow = nStartRow;
+ ScFlatBoolRowSegments::RangeData aData;
+ while (nRow <= nEndRow)
+ {
+ if (!ValidRow(nRow))
+ break;
+
+ if (!mpFilteredRows->getRangeData(nRow, aData))
+ // failed to get range data.
+ break;
+
+ if (!aData.mbValue)
+ // non-filtered row found
+ return nRow;
+
+ nRow = aData.mnRow2 + 1;
+ }
+
+ return ::std::numeric_limits<SCROW>::max();
+}
+
+SCROW ScTable::LastNonFilteredRow(SCROW nStartRow, SCROW nEndRow) const
+{
+ SCROW nRow = nEndRow;
+ ScFlatBoolRowSegments::RangeData aData;
+ while (nRow >= nStartRow)
+ {
+ if (!ValidRow(nRow))
+ break;
+
+ if (!mpFilteredRows->getRangeData(nRow, aData))
+ // failed to get range data.
+ break;
+
+ if (!aData.mbValue)
+ // non-filtered row found
+ return nRow;
+
+ nRow = aData.mnRow1 - 1;
+ }
+
+ return ::std::numeric_limits<SCROW>::max();
+}
+
+SCROW ScTable::CountNonFilteredRows(SCROW nStartRow, SCROW nEndRow) const
+{
+ SCROW nCount = 0;
+ SCROW nRow = nStartRow;
+ ScFlatBoolRowSegments::RangeData aData;
+ while (nRow <= nEndRow)
+ {
+ if (!mpFilteredRows->getRangeData(nRow, aData))
+ break;
+
+ if (aData.mnRow2 > nEndRow)
+ aData.mnRow2 = nEndRow;
+
+ if (!aData.mbValue)
+ nCount += aData.mnRow2 - nRow + 1;
+
+ nRow = aData.mnRow2 + 1;
+ }
+ return nCount;
+}
+
+bool ScTable::IsManualRowHeight(SCROW nRow) const
+{
+ return bool(pRowFlags->GetValue(nRow) & CRFlags::ManualSize);
+}
+
+namespace
+{
+void lcl_syncFlags(const ScDocument* pDocument, ScFlatBoolColSegments& rColSegments,
+ const ScFlatBoolRowSegments& rRowSegments,
+ ScBitMaskCompressedArray<SCCOL, CRFlags>* pColFlags,
+ ScBitMaskCompressedArray<SCROW, CRFlags>* pRowFlags, const CRFlags nFlagMask)
+{
+ using ::sal::static_int_cast;
+
+ CRFlags nFlagMaskComplement = ~nFlagMask;
+
+ pRowFlags->AndValue(0, pDocument->MaxRow(), nFlagMaskComplement);
+ pColFlags->AndValue(0, pDocument->MaxCol() + 1, nFlagMaskComplement);
+
+ {
+ // row hidden flags.
+
+ SCROW nRow = 0;
+ ScFlatBoolRowSegments::RangeData aData;
+ while (nRow <= pDocument->MaxRow())
+ {
+ if (!rRowSegments.getRangeData(nRow, aData))
+ break;
+
+ if (aData.mbValue)
+ pRowFlags->OrValue(nRow, aData.mnRow2, nFlagMask);
+
+ nRow = aData.mnRow2 + 1;
+ }
+ }
+
+ {
+ // column hidden flags.
+
+ SCCOL nCol = 0;
+ ScFlatBoolColSegments::RangeData aData;
+ while (nCol <= pDocument->MaxCol())
+ {
+ if (!rColSegments.getRangeData(nCol, aData))
+ break;
+
+ if (aData.mbValue)
+ pColFlags->OrValue(nCol, aData.mnCol2, nFlagMask);
+
+ nCol = aData.mnCol2 + 1;
+ }
+ }
+}
+}
+
+void ScTable::SyncColRowFlags()
+{
+ CRFlags nManualBreakComplement = ~CRFlags::ManualBreak;
+
+ // Manual breaks.
+ pRowFlags->AndValue(0, rDocument.MaxRow(), nManualBreakComplement);
+ mpColFlags->AndValue(0, rDocument.MaxCol() + 1, nManualBreakComplement);
+
+ for (const auto& rBreakPos : maRowManualBreaks)
+ pRowFlags->OrValue(rBreakPos, CRFlags::ManualBreak);
+
+ for (const auto& rBreakPos : maColManualBreaks)
+ mpColFlags->OrValue(rBreakPos, CRFlags::ManualBreak);
+
+ // Hidden flags.
+ lcl_syncFlags(&rDocument, *mpHiddenCols, *mpHiddenRows, mpColFlags.get(), pRowFlags.get(),
+ CRFlags::Hidden);
+ lcl_syncFlags(&rDocument, *mpFilteredCols, *mpFilteredRows, mpColFlags.get(), pRowFlags.get(),
+ CRFlags::Filtered);
+}
+
+void ScTable::SetPageSize(const Size& rSize)
+{
+ if (!rSize.IsEmpty())
+ {
+ if (aPageSizeTwips != rSize)
+ InvalidatePageBreaks();
+
+ bPageSizeValid = true;
+ aPageSizeTwips = rSize;
+ }
+ else
+ bPageSizeValid = false;
+}
+
+bool ScTable::IsProtected() const { return pTabProtection && pTabProtection->isProtected(); }
+
+void ScTable::SetProtection(const ScTableProtection* pProtect)
+{
+ if (pProtect)
+ pTabProtection.reset(new ScTableProtection(*pProtect));
+ else
+ pTabProtection.reset();
+
+ SetStreamValid(false);
+}
+
+const ScTableProtection* ScTable::GetProtection() const { return pTabProtection.get(); }
+
+Size ScTable::GetPageSize() const
+{
+ if (bPageSizeValid)
+ return aPageSizeTwips;
+ else
+ return Size(); // blank
+}
+
+void ScTable::SetRepeatArea(SCCOL nStartCol, SCCOL nEndCol, SCROW nStartRow, SCROW nEndRow)
+{
+ // #i117952# page break calculation uses these values (set from ScPrintFunc), not pRepeatColRange/pRepeatRowRange
+ if (nStartCol != nRepeatStartX || nEndCol != nRepeatEndX || nStartRow != nRepeatStartY
+ || nEndRow != nRepeatEndY)
+ InvalidatePageBreaks();
+
+ nRepeatStartX = nStartCol;
+ nRepeatEndX = nEndCol;
+ nRepeatStartY = nStartRow;
+ nRepeatEndY = nEndRow;
+}
+
+void ScTable::StartListening(const ScAddress& rAddress, SvtListener* pListener)
+{
+ if (!ValidCol(rAddress.Col()))
+ return;
+
+ CreateColumnIfNotExists(rAddress.Col()).StartListening(*pListener, rAddress.Row());
+}
+
+void ScTable::EndListening(const ScAddress& rAddress, SvtListener* pListener)
+{
+ if (!ValidCol(rAddress.Col()))
+ return;
+
+ if (rAddress.Col() < aCol.size())
+ aCol[rAddress.Col()].EndListening(*pListener, rAddress.Row());
+}
+
+void ScTable::StartListening(sc::StartListeningContext& rCxt, const ScAddress& rAddress,
+ SvtListener& rListener)
+{
+ if (!ValidCol(rAddress.Col()))
+ return;
+
+ CreateColumnIfNotExists(rAddress.Col()).StartListening(rCxt, rAddress, rListener);
+}
+
+void ScTable::EndListening(sc::EndListeningContext& rCxt, const ScAddress& rAddress,
+ SvtListener& rListener)
+{
+ if (!ValidCol(rAddress.Col()))
+ return;
+
+ if (rAddress.Col() < aCol.size())
+ aCol[rAddress.Col()].EndListening(rCxt, rAddress, rListener);
+}
+
+void ScTable::SetPageStyle(const OUString& rName)
+{
+ if (aPageStyle == rName)
+ return;
+
+ OUString aStrNew = rName;
+ SfxStyleSheetBasePool* pStylePool = rDocument.GetStyleSheetPool();
+ SfxStyleSheetBase* pNewStyle = pStylePool->Find(aStrNew, SfxStyleFamily::Page);
+
+ if (!pNewStyle)
+ {
+ aStrNew = ScResId(STR_STYLENAME_STANDARD);
+ pNewStyle = pStylePool->Find(aStrNew, SfxStyleFamily::Page);
+ }
+
+ if (aPageStyle == aStrNew)
+ return;
+
+ SfxStyleSheetBase* pOldStyle = pStylePool->Find(aPageStyle, SfxStyleFamily::Page);
+ if (pOldStyle && pNewStyle)
+ {
+ SfxItemSet& rOldSet = pOldStyle->GetItemSet();
+ SfxItemSet& rNewSet = pNewStyle->GetItemSet();
+ auto getScaleValue = [](const SfxItemSet& rSet, sal_uInt16 nId) {
+ return static_cast<const SfxUInt16Item&>(rSet.Get(nId)).GetValue();
+ };
+
+ const sal_uInt16 nOldScale = getScaleValue(rOldSet, ATTR_PAGE_SCALE);
+ const sal_uInt16 nOldScaleToPages = getScaleValue(rOldSet, ATTR_PAGE_SCALETOPAGES);
+ const sal_uInt16 nNewScale = getScaleValue(rNewSet, ATTR_PAGE_SCALE);
+ const sal_uInt16 nNewScaleToPages = getScaleValue(rNewSet, ATTR_PAGE_SCALETOPAGES);
+
+ if ((nOldScale != nNewScale) || (nOldScaleToPages != nNewScaleToPages))
+ InvalidateTextWidth(nullptr, nullptr, false, false);
+ }
+
+ if (pNewStyle) // also without the old one (for UpdateStdNames)
+ aPageStyle = aStrNew;
+
+ SetStreamValid(false);
+}
+
+void ScTable::PageStyleModified(const OUString& rNewName)
+{
+ aPageStyle = rNewName;
+ InvalidateTextWidth(nullptr, nullptr, false, false); // don't know what was in the style before
+}
+
+void ScTable::InvalidateTextWidth(const ScAddress* pAdrFrom, const ScAddress* pAdrTo,
+ bool bNumFormatChanged, bool bBroadcast)
+{
+ if (pAdrFrom && !pAdrTo)
+ {
+ // Special case: only process the "from" cell.
+ SCCOL nCol = pAdrFrom->Col();
+ SCROW nRow = pAdrFrom->Row();
+ if (nCol >= aCol.size())
+ return;
+ ScColumn& rCol = aCol[nCol];
+ ScRefCellValue aCell = rCol.GetCellValue(nRow);
+ if (aCell.isEmpty())
+ return;
+
+ rCol.SetTextWidth(nRow, TEXTWIDTH_DIRTY);
+
+ if (bNumFormatChanged)
+ rCol.SetScriptType(nRow, SvtScriptType::UNKNOWN);
+
+ if (bBroadcast)
+ { // Only with CalcAsShown
+ switch (aCell.meType)
+ {
+ case CELLTYPE_VALUE:
+ rCol.Broadcast(nRow);
+ break;
+ case CELLTYPE_FORMULA:
+ aCell.mpFormula->SetDirty();
+ break;
+ default:
+ {
+ // added to avoid warnings
+ }
+ }
+ }
+
+ return;
+ }
+
+ const SCCOL nCol1 = pAdrFrom ? pAdrFrom->Col() : 0;
+ const SCROW nRow1 = pAdrFrom ? pAdrFrom->Row() : 0;
+ const SCCOL nCol2 = pAdrTo ? pAdrTo->Col() : aCol.size() - 1;
+ const SCROW nRow2 = pAdrTo ? pAdrTo->Row() : rDocument.MaxRow();
+
+ for (SCCOL nCol = nCol1; nCol <= nCol2; ++nCol)
+ {
+ ScColumnTextWidthIterator aIter(GetDoc(), aCol[nCol], nRow1, nRow2);
+ sc::ColumnBlockPosition blockPos; // cache mdds position
+ InitColumnBlockPosition(blockPos, nCol);
+
+ for (; aIter.hasCell(); aIter.next())
+ {
+ SCROW nRow = aIter.getPos();
+ aIter.setValue(TEXTWIDTH_DIRTY);
+ ScRefCellValue aCell = aCol[nCol].GetCellValue(blockPos, nRow);
+ if (aCell.isEmpty())
+ continue;
+
+ if (bNumFormatChanged)
+ aCol[nCol].SetScriptType(nRow, SvtScriptType::UNKNOWN);
+
+ if (bBroadcast)
+ { // Only with CalcAsShown
+ switch (aCell.meType)
+ {
+ case CELLTYPE_VALUE:
+ aCol[nCol].Broadcast(nRow);
+ break;
+ case CELLTYPE_FORMULA:
+ aCell.mpFormula->SetDirty();
+ break;
+ default:
+ {
+ // added to avoid warnings
+ }
+ }
+ }
+ }
+ }
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sc/source/core/data/table6.cxx b/sc/source/core/data/table6.cxx
new file mode 100644
index 000000000..966b4491c
--- /dev/null
+++ b/sc/source/core/data/table6.cxx
@@ -0,0 +1,1155 @@
+/* -*- 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 <unotools/textsearch.hxx>
+#include <com/sun/star/util/SearchResult.hpp>
+#include <svl/srchitem.hxx>
+#include <editeng/editobj.hxx>
+#include <osl/diagnose.h>
+#include <sal/log.hxx>
+
+#include <table.hxx>
+#include <formulacell.hxx>
+#include <document.hxx>
+#include <stlpool.hxx>
+#include <stlsheet.hxx>
+#include <markdata.hxx>
+#include <editutil.hxx>
+#include <postit.hxx>
+
+namespace {
+
+bool lcl_GetTextWithBreaks( const EditTextObject& rData, ScDocument* pDoc, OUString& rVal )
+{
+ // true = more than 1 paragraph
+
+ EditEngine& rEngine = pDoc->GetEditEngine();
+ rEngine.SetText(rData);
+ rVal = rEngine.GetText();
+ return ( rEngine.GetParagraphCount() > 1 );
+}
+
+}
+
+bool ScTable::SearchCell(const SvxSearchItem& rSearchItem, SCCOL nCol, sc::ColumnBlockConstPosition& rBlockPos, SCROW nRow,
+ const ScMarkData& rMark, OUString& rUndoStr, ScDocument* pUndoDoc)
+{
+ if ( !IsColRowValid( nCol, nRow ) )
+ return false;
+
+ bool bFound = false;
+ bool bDoSearch = true;
+ bool bDoBack = rSearchItem.GetBackward();
+ bool bSearchFormatted = rSearchItem.IsSearchFormatted();
+
+ OUString aString;
+ ScRefCellValue aCell;
+ if (rSearchItem.GetSelection())
+ bDoSearch = rMark.IsCellMarked(nCol, nRow);
+
+ if (!bDoSearch)
+ return false;
+
+ ScPostIt* pNote;
+ if (rSearchItem.GetCellType() == SvxSearchCellType::NOTE)
+ {
+ pNote = aCol[nCol].GetCellNote(rBlockPos, nRow);
+ if (!pNote)
+ return false;
+ }
+ else
+ {
+ aCell = aCol[nCol].GetCellValue(rBlockPos, nRow);
+ if (aCell.isEmpty())
+ return false;
+ pNote = nullptr;
+ }
+
+ bool bMultiLine = false;
+ CellType eCellType = aCell.meType;
+ switch (rSearchItem.GetCellType())
+ {
+ case SvxSearchCellType::FORMULA:
+ {
+ if ( eCellType == CELLTYPE_FORMULA )
+ aString = aCell.mpFormula->GetFormula(rDocument.GetGrammar());
+ else if ( eCellType == CELLTYPE_EDIT )
+ bMultiLine = lcl_GetTextWithBreaks(*aCell.mpEditText, &rDocument, aString);
+ else
+ {
+ if( !bSearchFormatted )
+ aString = aCol[nCol].GetInputString( rBlockPos, nRow );
+ else
+ aString = aCol[nCol].GetString( rBlockPos, nRow );
+ }
+ break;
+ }
+ case SvxSearchCellType::VALUE:
+ if ( eCellType == CELLTYPE_EDIT )
+ bMultiLine = lcl_GetTextWithBreaks(*aCell.mpEditText, &rDocument, aString);
+ else
+ {
+ if( !bSearchFormatted )
+ aString = aCol[nCol].GetInputString( rBlockPos, nRow );
+ else
+ aString = aCol[nCol].GetString( rBlockPos, nRow );
+ }
+ break;
+ case SvxSearchCellType::NOTE:
+ {
+ if (pNote)
+ {
+ aString = pNote->GetText();
+ bMultiLine = pNote->HasMultiLineText();
+ }
+ break;
+ }
+ default:
+ break;
+ }
+ sal_Int32 nStart = 0;
+ sal_Int32 nEnd = aString.getLength();
+ css::util::SearchResult aSearchResult;
+ if (pSearchText)
+ {
+ if ( bDoBack )
+ {
+ sal_Int32 nTemp=nStart; nStart=nEnd; nEnd=nTemp;
+ bFound = pSearchText->SearchBackward(aString, &nStart, &nEnd, &aSearchResult);
+ // change results to definition before 614:
+ --nEnd;
+ }
+ else
+ {
+ bFound = pSearchText->SearchForward(aString, &nStart, &nEnd, &aSearchResult);
+ // change results to definition before 614:
+ --nEnd;
+ }
+
+ if (bFound && rSearchItem.GetWordOnly())
+ bFound = (nStart == 0 && nEnd == aString.getLength() - 1);
+ }
+ else
+ {
+ OSL_FAIL("pSearchText == NULL");
+ return bFound;
+ }
+
+ if (!bFound)
+ return false;
+ if ( rSearchItem.GetCommand() != SvxSearchCmd::REPLACE
+ && rSearchItem.GetCommand() != SvxSearchCmd::REPLACE_ALL )
+ return bFound;
+
+ if (!IsBlockEditable(nCol, nRow, nCol, nRow))
+ return bFound;
+
+ ScMatrixMode cMatrixFlag = ScMatrixMode::NONE;
+
+ // Don't split the matrix, only replace Matrix formulas
+ if (eCellType == CELLTYPE_FORMULA)
+ {
+ cMatrixFlag = aCell.mpFormula->GetMatrixFlag();
+ if(cMatrixFlag == ScMatrixMode::Reference)
+ return bFound;
+ }
+ // No UndoDoc => Matrix not restorable => don't replace
+ if (cMatrixFlag != ScMatrixMode::NONE && !pUndoDoc)
+ return bFound;
+
+ if ( cMatrixFlag == ScMatrixMode::NONE && rSearchItem.GetCommand() == SvxSearchCmd::REPLACE )
+ rUndoStr = aString;
+ else if (pUndoDoc)
+ {
+ ScAddress aAdr( nCol, nRow, nTab );
+ aCell.commit(*pUndoDoc, aAdr);
+ }
+
+ bool bRepeat = !rSearchItem.GetWordOnly();
+ do
+ {
+ // don't continue search if the found text is empty,
+ // otherwise it would never stop (#35410#)
+ if ( nEnd < nStart )
+ bRepeat = false;
+
+ OUString sReplStr = rSearchItem.GetReplaceString();
+ if (rSearchItem.GetRegExp())
+ {
+ pSearchText->ReplaceBackReferences( sReplStr, aString, aSearchResult );
+ OUStringBuffer aStrBuffer(aString);
+ aStrBuffer.remove(nStart, nEnd-nStart+1);
+ aStrBuffer.insert(nStart, sReplStr);
+ aString = aStrBuffer.makeStringAndClear();
+ }
+ else
+ {
+ OUStringBuffer aStrBuffer(aString);
+ aStrBuffer.remove(nStart, nEnd-nStart+1);
+ aStrBuffer.insert(nStart, rSearchItem.GetReplaceString());
+ aString = aStrBuffer.makeStringAndClear();
+ }
+
+ // Adjust index
+ if (bDoBack)
+ {
+ nEnd = nStart;
+ nStart = 0;
+ }
+ else
+ {
+ nStart = nStart + sReplStr.getLength();
+ nEnd = aString.getLength();
+ }
+
+ // continue search ?
+ if (bRepeat)
+ {
+ if ( rSearchItem.GetCommand() != SvxSearchCmd::REPLACE_ALL || nStart >= nEnd )
+ bRepeat = false;
+ else if (bDoBack)
+ {
+ sal_Int32 nTemp=nStart; nStart=nEnd; nEnd=nTemp;
+ bRepeat = pSearchText->SearchBackward(aString, &nStart, &nEnd, &aSearchResult);
+ // change results to definition before 614:
+ --nEnd;
+ }
+ else
+ {
+ bRepeat = pSearchText->SearchForward(aString, &nStart, &nEnd, &aSearchResult);
+ // change results to definition before 614:
+ --nEnd;
+ }
+ }
+ }
+ while (bRepeat);
+ if (rSearchItem.GetCellType() == SvxSearchCellType::NOTE)
+ {
+ // NB: rich text format is lost.
+ // This is also true of Cells.
+ if (pNote)
+ pNote->SetText( ScAddress( nCol, nRow, nTab ), aString );
+ }
+ else if ( cMatrixFlag != ScMatrixMode::NONE )
+ { // don't split Matrix
+ if ( aString.getLength() > 2 )
+ { // remove {} here so that "{=" can be replaced by "{=..."
+ if ( aString[ aString.getLength()-1 ] == '}' )
+ aString = aString.copy( 0, aString.getLength()-1 );
+ if ( aString[0] == '{' )
+ aString = aString.copy( 1 );
+ }
+ ScAddress aAdr( nCol, nRow, nTab );
+ ScFormulaCell* pFCell = new ScFormulaCell( rDocument, aAdr,
+ aString, rDocument.GetGrammar(), cMatrixFlag );
+ SCCOL nMatCols;
+ SCROW nMatRows;
+ aCell.mpFormula->GetMatColsRows(nMatCols, nMatRows);
+ pFCell->SetMatColsRows( nMatCols, nMatRows );
+ aCol[nCol].SetFormulaCell(nRow, pFCell);
+ }
+ else if ( bMultiLine && aString.indexOf('\n') != -1 )
+ {
+ ScFieldEditEngine& rEngine = rDocument.GetEditEngine();
+ rEngine.SetTextCurrentDefaults(aString);
+ SetEditText(nCol, nRow, rEngine.CreateTextObject());
+ }
+ else
+ aCol[nCol].SetString(nRow, nTab, aString, rDocument.GetAddressConvention());
+ // pCell is invalid now (deleted)
+ aCol[nCol].InitBlockPosition( rBlockPos ); // invalidate also the cached position
+
+ return bFound;
+}
+
+void ScTable::SkipFilteredRows(SCROW& rRow, SCROW& rLastNonFilteredRow, bool bForward)
+{
+ if (bForward)
+ {
+ // forward search
+
+ if (rRow <= rLastNonFilteredRow)
+ return;
+
+ SCROW nLastRow = rRow;
+ if (RowFiltered(rRow, nullptr, &nLastRow))
+ // move to the first non-filtered row.
+ rRow = nLastRow + 1;
+ else
+ // record the last non-filtered row to avoid checking
+ // the filtered state for each and every row.
+ rLastNonFilteredRow = nLastRow;
+ }
+ else
+ {
+ // backward search
+
+ if (rRow >= rLastNonFilteredRow)
+ return;
+
+ SCROW nFirstRow = rRow;
+ if (RowFiltered(rRow, &nFirstRow))
+ // move to the first non-filtered row.
+ rRow = nFirstRow - 1;
+ else
+ // record the last non-filtered row to avoid checking
+ // the filtered state for each and every row.
+ rLastNonFilteredRow = nFirstRow;
+ }
+}
+
+bool ScTable::Search(const SvxSearchItem& rSearchItem, SCCOL& rCol, SCROW& rRow,
+ const ScMarkData& rMark, OUString& rUndoStr, ScDocument* pUndoDoc)
+{
+ SCCOL nLastCol;
+ SCROW nLastRow;
+ if (rSearchItem.GetCellType() == SvxSearchCellType::NOTE)
+ GetCellArea( nLastCol, nLastRow);
+ else
+ GetLastDataPos(nLastCol, nLastRow);
+ std::vector< sc::ColumnBlockConstPosition > blockPos;
+ return Search(rSearchItem, rCol, rRow, nLastCol, nLastRow, rMark, rUndoStr, pUndoDoc, blockPos);
+}
+
+bool ScTable::Search(const SvxSearchItem& rSearchItem, SCCOL& rCol, SCROW& rRow,
+ SCCOL nLastCol, SCROW nLastRow,
+ const ScMarkData& rMark, OUString& rUndoStr, ScDocument* pUndoDoc,
+ std::vector< sc::ColumnBlockConstPosition >& blockPos)
+{
+ bool bFound = false;
+ bool bAll = (rSearchItem.GetCommand() == SvxSearchCmd::FIND_ALL)
+ ||(rSearchItem.GetCommand() == SvxSearchCmd::REPLACE_ALL);
+ SCCOL nCol = rCol;
+ SCROW nRow = rRow;
+
+ bool bSkipFiltered = !rSearchItem.IsSearchFiltered();
+ bool bSearchNotes = (rSearchItem.GetCellType() == SvxSearchCellType::NOTE);
+ // We need to cache sc::ColumnBlockConstPosition per each column.
+ if (static_cast<SCCOL>(blockPos.size()) != nLastCol + 1)
+ {
+ blockPos.resize( nLastCol + 1 );
+ for( SCCOL i = 0; i <= nLastCol; ++i )
+ aCol[ i ].InitBlockPosition( blockPos[ i ] );
+ }
+ if (!bAll && rSearchItem.GetBackward())
+ {
+ SCROW nLastNonFilteredRow = rDocument.MaxRow() + 1;
+ if (rSearchItem.GetRowDirection())
+ {
+ nCol--;
+ nCol = std::min(nCol, nLastCol);
+ nRow = std::min(nRow, nLastRow);
+ while (!bFound && (nRow >= 0))
+ {
+ if (bSkipFiltered)
+ SkipFilteredRows(nRow, nLastNonFilteredRow, false);
+
+ while (!bFound && (nCol >= 0))
+ {
+ bFound = SearchCell(rSearchItem, nCol, blockPos[ nCol ], nRow,
+ rMark, rUndoStr, pUndoDoc);
+ if (!bFound)
+ {
+ bool bIsEmpty;
+ do
+ {
+ nCol--;
+ if (nCol >= 0)
+ {
+ if (bSearchNotes)
+ bIsEmpty = !aCol[nCol].HasCellNotes();
+ else
+ bIsEmpty = aCol[nCol].IsEmptyData();
+ }
+ else
+ bIsEmpty = true;
+ }
+ while ((nCol >= 0) && bIsEmpty);
+ }
+ }
+ if (!bFound)
+ {
+ nCol = nLastCol;
+ nRow--;
+ }
+ }
+ }
+ else
+ {
+ nRow--;
+ nCol = std::min(nCol, nLastCol);
+ nRow = std::min(nRow, nLastRow);
+ while (!bFound && (nCol >= 0))
+ {
+ while (!bFound && (nRow >= 0))
+ {
+ if (bSkipFiltered)
+ SkipFilteredRows(nRow, nLastNonFilteredRow, false);
+
+ bFound = SearchCell(rSearchItem, nCol, blockPos[ nCol ],
+ nRow, rMark, rUndoStr, pUndoDoc);
+ if (!bFound)
+ {
+ if (bSearchNotes)
+ {
+ /* TODO: can we look for the previous cell note instead? */
+ --nRow;
+ }
+ else
+ {
+ if (!aCol[nCol].GetPrevDataPos(nRow))
+ nRow = -1;
+ }
+ }
+ }
+ if (!bFound)
+ {
+ // Not found in this column. Move to the next column.
+ bool bIsEmpty;
+ nRow = nLastRow;
+ nLastNonFilteredRow = rDocument.MaxRow() + 1;
+ do
+ {
+ nCol--;
+ if (nCol >= 0)
+ {
+ if (bSearchNotes)
+ bIsEmpty = !aCol[nCol].HasCellNotes();
+ else
+ bIsEmpty = aCol[nCol].IsEmptyData();
+ }
+ else
+ bIsEmpty = true;
+ }
+ while ((nCol >= 0) && bIsEmpty);
+ }
+ }
+ }
+ }
+ else
+ {
+ SCROW nLastNonFilteredRow = -1;
+ if (rSearchItem.GetRowDirection())
+ {
+ nCol++;
+ while (!bFound && (nRow <= nLastRow))
+ {
+ if (bSkipFiltered)
+ SkipFilteredRows(nRow, nLastNonFilteredRow, true);
+
+ while (!bFound && (nCol <= nLastCol))
+ {
+ bFound = SearchCell(rSearchItem, nCol, blockPos[ nCol ],
+ nRow, rMark, rUndoStr, pUndoDoc);
+ if (!bFound)
+ {
+ nCol++;
+ while ((nCol <= nLastCol) &&
+ (bSearchNotes ? !aCol[nCol].HasCellNotes() : aCol[nCol].IsEmptyData()))
+ nCol++;
+ }
+ }
+ if (!bFound)
+ {
+ nCol = 0;
+ nRow++;
+ }
+ }
+ }
+ else
+ {
+ nRow++;
+ while (!bFound && (nCol <= nLastCol))
+ {
+ while (!bFound && (nRow <= nLastRow))
+ {
+ if (bSkipFiltered)
+ SkipFilteredRows(nRow, nLastNonFilteredRow, true);
+
+ bFound = SearchCell(rSearchItem, nCol, blockPos[ nCol ],
+ nRow, rMark, rUndoStr, pUndoDoc);
+ if (!bFound)
+ {
+ if (bSearchNotes)
+ {
+ /* TODO: can we look for the next cell note instead? */
+ ++nRow;
+ }
+ else
+ {
+ if (!aCol[nCol].GetNextDataPos(nRow))
+ nRow = rDocument.MaxRow() + 1;
+ }
+ }
+ }
+ if (!bFound)
+ {
+ // Not found in this column. Move to the next column.
+ nRow = 0;
+ nLastNonFilteredRow = -1;
+ nCol++;
+ while ((nCol <= nLastCol) &&
+ (bSearchNotes ? !aCol[nCol].HasCellNotes() : aCol[nCol].IsEmptyData()))
+ nCol++;
+ }
+ }
+ }
+ }
+ if (bFound)
+ {
+ rCol = nCol;
+ rRow = nRow;
+ }
+ return bFound;
+}
+
+bool ScTable::SearchAll(const SvxSearchItem& rSearchItem, const ScMarkData& rMark,
+ ScRangeList& rMatchedRanges, OUString& rUndoStr, ScDocument* pUndoDoc)
+{
+ bool bFound = true;
+ SCCOL nCol = 0;
+ SCROW nRow = -1;
+ bool bEverFound = false;
+
+ SCCOL nLastCol;
+ SCROW nLastRow;
+ if (rSearchItem.GetCellType() == SvxSearchCellType::NOTE)
+ GetCellArea( nLastCol, nLastRow);
+ else
+ GetLastDataPos(nLastCol, nLastRow);
+
+ std::vector< sc::ColumnBlockConstPosition > blockPos;
+ do
+ {
+ bFound = Search(rSearchItem, nCol, nRow, nLastCol, nLastRow, rMark, rUndoStr, pUndoDoc, blockPos);
+ if (bFound)
+ {
+ bEverFound = true;
+ rMatchedRanges.Join(ScRange(nCol, nRow, nTab));
+ }
+ }
+ while (bFound);
+
+ return bEverFound;
+}
+
+void ScTable::UpdateSearchItemAddressForReplace( const SvxSearchItem& rSearchItem, SCCOL& rCol, SCROW& rRow )
+{
+ if (rSearchItem.GetBackward())
+ {
+ if (rSearchItem.GetRowDirection())
+ rCol += 1;
+ else
+ rRow += 1;
+ }
+ else
+ {
+ if (rSearchItem.GetRowDirection())
+ rCol -= 1;
+ else
+ rRow -= 1;
+ }
+}
+
+bool ScTable::Replace(const SvxSearchItem& rSearchItem, SCCOL& rCol, SCROW& rRow,
+ const ScMarkData& rMark, OUString& rUndoStr, ScDocument* pUndoDoc)
+{
+ SCCOL nCol = rCol;
+ SCROW nRow = rRow;
+
+ UpdateSearchItemAddressForReplace( rSearchItem, nCol, nRow );
+ bool bFound = Search(rSearchItem, nCol, nRow, rMark, rUndoStr, pUndoDoc);
+ if (bFound)
+ {
+ rCol = nCol;
+ rRow = nRow;
+ }
+ return bFound;
+}
+
+bool ScTable::ReplaceAll(
+ const SvxSearchItem& rSearchItem, const ScMarkData& rMark, ScRangeList& rMatchedRanges,
+ OUString& rUndoStr, ScDocument* pUndoDoc)
+{
+ SCCOL nCol = 0;
+ SCROW nRow = -1;
+
+ SCCOL nLastCol;
+ SCROW nLastRow;
+ if (rSearchItem.GetCellType() == SvxSearchCellType::NOTE)
+ GetCellArea( nLastCol, nLastRow);
+ else
+ GetLastDataPos(nLastCol, nLastRow);
+
+ // tdf#92160 - columnar replace is faster, and more memory efficient.
+ SvxSearchItem aCopyItem(rSearchItem);
+ aCopyItem.SetRowDirection(false);
+
+ std::vector< sc::ColumnBlockConstPosition > blockPos;
+ bool bEverFound = false;
+ while (true)
+ {
+ bool bFound = Search(aCopyItem, nCol, nRow, nLastCol, nLastRow, rMark, rUndoStr, pUndoDoc, blockPos);
+
+ if (bFound)
+ {
+ bEverFound = true;
+ rMatchedRanges.Join(ScRange(nCol, nRow, nTab));
+ }
+ else
+ break;
+ }
+ return bEverFound;
+}
+
+bool ScTable::SearchStyle(const SvxSearchItem& rSearchItem, SCCOL& rCol, SCROW& rRow,
+ const ScMarkData& rMark)
+{
+ const ScStyleSheet* pSearchStyle = static_cast<const ScStyleSheet*>(
+ rDocument.GetStyleSheetPool()->Find(
+ rSearchItem.GetSearchString(), SfxStyleFamily::Para ));
+
+ SCCOL nCol = rCol;
+ SCROW nRow = rRow;
+ bool bFound = false;
+
+ bool bSelect = rSearchItem.GetSelection();
+ bool bRows = rSearchItem.GetRowDirection();
+ bool bBack = rSearchItem.GetBackward();
+ short nAdd = bBack ? -1 : 1;
+
+ if (bRows) // by row
+ {
+ if ( !IsColValid( nCol ) )
+ {
+ SAL_WARN( "sc.core", "SearchStyle: bad column " << nCol);
+ return false;
+ }
+ nRow += nAdd;
+ do
+ {
+ SCROW nNextRow = aCol[nCol].SearchStyle( nRow, pSearchStyle, bBack, bSelect, rMark );
+ if (!ValidRow(nNextRow))
+ {
+ nRow = bBack ? rDocument.MaxRow() : 0;
+ nCol = sal::static_int_cast<SCCOL>( nCol + nAdd );
+ }
+ else
+ {
+ nRow = nNextRow;
+ bFound = true;
+ }
+ }
+ while ( !bFound && IsColValid( nCol ) );
+ }
+ else // by column
+ {
+ SCCOL aColSize = aCol.size();
+ std::vector< SCROW > nNextRows ( aColSize );
+ SCCOL i;
+ for (i=0; i < aColSize; ++i)
+ {
+ SCROW nSRow = nRow;
+ if (bBack)
+ {
+ if (i>=nCol) --nSRow;
+ }
+ else
+ {
+ if (i<=nCol) ++nSRow;
+ }
+ nNextRows[i] = aCol[i].SearchStyle( nSRow, pSearchStyle, bBack, bSelect, rMark );
+ }
+ if (bBack) // backwards
+ {
+ nRow = -1;
+ for (i = aColSize - 1; i>=0; --i)
+ if (nNextRows[i]>nRow)
+ {
+ nCol = i;
+ nRow = nNextRows[i];
+ bFound = true;
+ }
+ }
+ else // forwards
+ {
+ nRow = rDocument.MaxRow()+1;
+ for (i=0; i < aColSize; ++i)
+ if (nNextRows[i]<nRow)
+ {
+ nCol = i;
+ nRow = nNextRows[i];
+ bFound = true;
+ }
+ }
+ }
+
+ if (bFound)
+ {
+ rCol = nCol;
+ rRow = nRow;
+ }
+ return bFound;
+}
+
+//TODO: return single Pattern for Undo
+
+bool ScTable::ReplaceStyle(const SvxSearchItem& rSearchItem, SCCOL& rCol, SCROW& rRow,
+ const ScMarkData& rMark, bool bIsUndo)
+{
+ bool bRet;
+ if (bIsUndo)
+ bRet = true;
+ else
+ bRet = SearchStyle(rSearchItem, rCol, rRow, rMark);
+ if (bRet)
+ {
+ const ScStyleSheet* pReplaceStyle = static_cast<const ScStyleSheet*>(
+ rDocument.GetStyleSheetPool()->Find(
+ rSearchItem.GetReplaceString(), SfxStyleFamily::Para ));
+
+ if (pReplaceStyle)
+ ApplyStyle( rCol, rRow, pReplaceStyle );
+ else
+ {
+ OSL_FAIL("pReplaceStyle==0");
+ }
+ }
+
+ return bRet;
+}
+
+bool ScTable::SearchAllStyle(
+ const SvxSearchItem& rSearchItem, const ScMarkData& rMark, ScRangeList& rMatchedRanges)
+{
+ const ScStyleSheet* pSearchStyle = static_cast<const ScStyleSheet*>(
+ rDocument.GetStyleSheetPool()->Find(
+ rSearchItem.GetSearchString(), SfxStyleFamily::Para ));
+ bool bSelect = rSearchItem.GetSelection();
+ bool bBack = rSearchItem.GetBackward();
+ bool bEverFound = false;
+
+ for (SCCOL i=0; i < aCol.size(); ++i)
+ {
+ bool bFound = true;
+ SCROW nRow = 0;
+ SCROW nEndRow;
+ while (bFound && nRow <= rDocument.MaxRow())
+ {
+ bFound = aCol[i].SearchStyleRange( nRow, nEndRow, pSearchStyle, bBack, bSelect, rMark );
+ if (bFound)
+ {
+ if (nEndRow<nRow)
+ {
+ SCROW nTemp = nRow;
+ nRow = nEndRow;
+ nEndRow = nTemp;
+ }
+ rMatchedRanges.Join(ScRange(i, nRow, nTab, i, nEndRow, nTab));
+ nRow = nEndRow + 1;
+ bEverFound = true;
+ }
+ }
+ }
+
+ return bEverFound;
+}
+
+bool ScTable::ReplaceAllStyle(
+ const SvxSearchItem& rSearchItem, const ScMarkData& rMark, ScRangeList& rMatchedRanges,
+ ScDocument* pUndoDoc)
+{
+ bool bRet = SearchAllStyle(rSearchItem, rMark, rMatchedRanges);
+ if (bRet)
+ {
+ const ScStyleSheet* pReplaceStyle = static_cast<const ScStyleSheet*>(
+ rDocument.GetStyleSheetPool()->Find(
+ rSearchItem.GetReplaceString(), SfxStyleFamily::Para ));
+
+ if (pReplaceStyle)
+ {
+ if (pUndoDoc)
+ rDocument.CopyToDocument(0, 0 ,nTab, rDocument.MaxCol(),rDocument.MaxRow(),nTab,
+ InsertDeleteFlags::ATTRIB, true, *pUndoDoc, &rMark);
+ ApplySelectionStyle( *pReplaceStyle, rMark );
+ }
+ else
+ {
+ OSL_FAIL("pReplaceStyle==0");
+ }
+ }
+
+ return bRet;
+}
+
+bool ScTable::SearchAndReplace(
+ const SvxSearchItem& rSearchItem, SCCOL& rCol, SCROW& rRow, const ScMarkData& rMark,
+ ScRangeList& rMatchedRanges, OUString& rUndoStr, ScDocument* pUndoDoc)
+{
+ SvxSearchCmd nCommand = rSearchItem.GetCommand();
+ bool bFound = false;
+ if ( ValidColRow(rCol, rRow) ||
+ ((nCommand == SvxSearchCmd::FIND || nCommand == SvxSearchCmd::REPLACE) &&
+ (((rCol == GetDoc().GetMaxColCount() || rCol == -1) && ValidRow(rRow)) ||
+ ((rRow == GetDoc().GetMaxRowCount() || rRow == -1) && ValidCol(rCol))
+ )
+ )
+ )
+ {
+ bool bStyles = rSearchItem.GetPattern();
+ if (bStyles)
+ {
+ if (nCommand == SvxSearchCmd::FIND)
+ bFound = SearchStyle(rSearchItem, rCol, rRow, rMark);
+ else if (nCommand == SvxSearchCmd::REPLACE)
+ bFound = ReplaceStyle(rSearchItem, rCol, rRow, rMark, false);
+ else if (nCommand == SvxSearchCmd::FIND_ALL)
+ bFound = SearchAllStyle(rSearchItem, rMark, rMatchedRanges);
+ else if (nCommand == SvxSearchCmd::REPLACE_ALL)
+ bFound = ReplaceAllStyle(rSearchItem, rMark, rMatchedRanges, pUndoDoc);
+ }
+ else if (ScDocument::IsEmptyCellSearch( rSearchItem))
+ {
+ // Search for empty cells.
+ bFound = SearchAndReplaceEmptyCells(rSearchItem, rCol, rRow, rMark, rMatchedRanges, rUndoStr, pUndoDoc);
+ }
+ else
+ {
+ // SearchParam no longer needed - SearchOptions contains all settings
+ i18nutil::SearchOptions2 aSearchOptions = rSearchItem.GetSearchOptions();
+ aSearchOptions.Locale = ScGlobal::GetLocale();
+
+ // reflect UseAsianOptions flag in SearchOptions
+ // (use only ignore case and width if asian options are disabled).
+ // This is also done in SvxSearchDialog CommandHdl, but not in API object.
+ if ( !rSearchItem.IsUseAsianOptions() )
+ aSearchOptions.transliterateFlags &=
+ TransliterationFlags::IGNORE_CASE |
+ TransliterationFlags::IGNORE_WIDTH;
+
+ pSearchText.reset( new utl::TextSearch( aSearchOptions ) );
+
+ if (nCommand == SvxSearchCmd::FIND)
+ bFound = Search(rSearchItem, rCol, rRow, rMark, rUndoStr, pUndoDoc);
+ else if (nCommand == SvxSearchCmd::FIND_ALL)
+ bFound = SearchAll(rSearchItem, rMark, rMatchedRanges, rUndoStr, pUndoDoc);
+ else if (nCommand == SvxSearchCmd::REPLACE)
+ bFound = Replace(rSearchItem, rCol, rRow, rMark, rUndoStr, pUndoDoc);
+ else if (nCommand == SvxSearchCmd::REPLACE_ALL)
+ bFound = ReplaceAll(rSearchItem, rMark, rMatchedRanges, rUndoStr, pUndoDoc);
+
+ pSearchText.reset();
+ }
+ }
+ return bFound;
+}
+
+bool ScTable::SearchAndReplaceEmptyCells(
+ const SvxSearchItem& rSearchItem, SCCOL& rCol, SCROW& rRow, const ScMarkData& rMark,
+ ScRangeList& rMatchedRanges, OUString& rUndoStr, ScDocument* pUndoDoc)
+{
+ SCCOL nColStart, nColEnd;
+ SCROW nRowStart, nRowEnd;
+ GetFirstDataPos(nColStart, nRowStart);
+ GetLastDataPos(nColEnd, nRowEnd);
+
+ ScRangeList aRanges(ScRange(nColStart, nRowStart, nTab, nColEnd, nRowEnd, nTab));
+
+ if (rSearchItem.GetSelection())
+ {
+ // current selection only.
+ if (!rMark.IsMarked() && !rMark.IsMultiMarked())
+ // There is no selection. Bail out.
+ return false;
+
+ ScRangeList aMarkedRanges, aNewRanges;
+ rMark.FillRangeListWithMarks(&aMarkedRanges, true);
+ for ( size_t i = 0, n = aMarkedRanges.size(); i < n; ++i )
+ {
+ ScRange & rRange = aMarkedRanges[ i ];
+ if (rRange.aStart.Col() > nColEnd || rRange.aStart.Row() > nRowEnd || rRange.aEnd.Col() < nColStart || rRange.aEnd.Row() < nRowStart)
+ // This range is outside the data area. Skip it.
+ continue;
+
+ // Shrink the range into data area only.
+ if (rRange.aStart.Col() < nColStart)
+ rRange.aStart.SetCol(nColStart);
+ if (rRange.aStart.Row() < nRowStart)
+ rRange.aStart.SetRow(nRowStart);
+
+ if (rRange.aEnd.Col() > nColEnd)
+ rRange.aEnd.SetCol(nColEnd);
+ if (rRange.aEnd.Row() > nRowEnd)
+ rRange.aEnd.SetRow(nRowEnd);
+
+ aNewRanges.push_back(rRange);
+ }
+ aRanges = aNewRanges;
+ }
+
+ SvxSearchCmd nCommand = rSearchItem.GetCommand();
+ if (nCommand == SvxSearchCmd::FIND || nCommand == SvxSearchCmd::REPLACE)
+ {
+ if (rSearchItem.GetBackward())
+ {
+ for ( size_t i = aRanges.size(); i > 0; --i )
+ {
+ const ScRange & rRange = aRanges[ i - 1 ];
+ if (SearchRangeForEmptyCell(rRange, rSearchItem, rCol, rRow, rUndoStr))
+ return true;
+ }
+ }
+ else
+ {
+ for ( size_t i = 0, nListSize = aRanges.size(); i < nListSize; ++i )
+ {
+ const ScRange & rRange = aRanges[ i ];
+ if (SearchRangeForEmptyCell(rRange, rSearchItem, rCol, rRow, rUndoStr))
+ return true;
+ }
+ }
+ }
+ else if (nCommand == SvxSearchCmd::FIND_ALL || nCommand == SvxSearchCmd::REPLACE_ALL)
+ {
+ bool bFound = false;
+ for ( size_t i = 0, nListSize = aRanges.size(); i < nListSize; ++i )
+ {
+ ScRange const & rRange = aRanges[ i ];
+ bFound |= SearchRangeForAllEmptyCells(rRange, rSearchItem, rMatchedRanges, rUndoStr, pUndoDoc);
+ }
+ return bFound;
+ }
+ return false;
+}
+
+namespace {
+
+bool lcl_maybeReplaceCellString(
+ ScColumn& rColObj, SCCOL& rCol, SCROW& rRow, OUString& rUndoStr, SCCOL nCol, SCROW nRow, const SvxSearchItem& rSearchItem)
+{
+ ScRefCellValue aCell = rColObj.GetCellValue(nRow);
+ if (aCell.isEmpty())
+ {
+ // empty cell found.
+ rCol = nCol;
+ rRow = nRow;
+ if (rSearchItem.GetCommand() == SvxSearchCmd::REPLACE &&
+ !rSearchItem.GetReplaceString().isEmpty())
+ {
+ rColObj.SetRawString(nRow, rSearchItem.GetReplaceString());
+ rUndoStr.clear();
+ }
+ return true;
+ }
+ return false;
+}
+
+}
+
+bool ScTable::SearchRangeForEmptyCell(
+ const ScRange& rRange, const SvxSearchItem& rSearchItem,
+ SCCOL& rCol, SCROW& rRow, OUString& rUndoStr)
+{
+ SvxSearchCmd nCmd = rSearchItem.GetCommand();
+ bool bSkipFiltered = rSearchItem.IsSearchFiltered();
+ if (rSearchItem.GetBackward())
+ {
+ // backward search
+ if (rSearchItem.GetRowDirection())
+ {
+ // row direction.
+ SCROW nLastNonFilteredRow = rDocument.MaxRow() + 1;
+ SCROW nBeginRow = std::min(rRange.aEnd.Row(), rRow);
+ for (SCROW nRow = nBeginRow; nRow >= rRange.aStart.Row(); --nRow)
+ {
+ if (bSkipFiltered)
+ SkipFilteredRows(nRow, nLastNonFilteredRow, false);
+ if (nRow < rRange.aStart.Row())
+ break;
+
+ SCCOL nBeginCol = rRange.aEnd.Col();
+ if (nRow == rRow && nBeginCol >= rCol)
+ // always start from one cell before the cursor.
+ nBeginCol = rCol - (nCmd == SvxSearchCmd::FIND ? 1 : 0);
+
+ for (SCCOL nCol = nBeginCol; nCol >= rRange.aStart.Col(); --nCol)
+ {
+ if (lcl_maybeReplaceCellString(aCol[nCol], rCol, rRow, rUndoStr, nCol, nRow, rSearchItem))
+ return true;
+ }
+ }
+ }
+ else
+ {
+ // column direction.
+ SCCOL nBeginCol = std::min(rRange.aEnd.Col(), rCol);
+ for (SCCOL nCol = nBeginCol; nCol >= rRange.aStart.Col(); --nCol)
+ {
+ SCROW nLastNonFilteredRow = rDocument.MaxRow() + 1;
+ SCROW nBeginRow = rRange.aEnd.Row();
+ if (nCol == rCol && nBeginRow >= rRow)
+ // always start from one cell before the cursor.
+ nBeginRow = rRow - (nCmd == SvxSearchCmd::FIND ? 1 : 0);
+ for (SCROW nRow = nBeginRow; nRow >= rRange.aStart.Row(); --nRow)
+ {
+ if (bSkipFiltered)
+ SkipFilteredRows(nRow, nLastNonFilteredRow, false);
+ if (nRow < rRange.aStart.Row())
+ break;
+
+ if (lcl_maybeReplaceCellString(aCol[nCol], rCol, rRow, rUndoStr, nCol, nRow, rSearchItem))
+ return true;
+ }
+ }
+ }
+ }
+ else
+ {
+ // forward search
+ if (rSearchItem.GetRowDirection())
+ {
+ // row direction.
+ SCROW nLastNonFilteredRow = -1;
+ SCROW nBeginRow = rRange.aStart.Row() < rRow ? rRow : rRange.aStart.Row();
+ for (SCROW nRow = nBeginRow; nRow <= rRange.aEnd.Row(); ++nRow)
+ {
+ if (bSkipFiltered)
+ SkipFilteredRows(nRow, nLastNonFilteredRow, true);
+ if (nRow > rRange.aEnd.Row())
+ break;
+
+ SCCOL nBeginCol = rRange.aStart.Col();
+ if (nRow == rRow && nBeginCol <= rCol)
+ // always start from one cell past the cursor.
+ nBeginCol = rCol + (nCmd == SvxSearchCmd::FIND ? 1 : 0);
+ for (SCCOL nCol = nBeginCol; nCol <= rRange.aEnd.Col(); ++nCol)
+ {
+ if (lcl_maybeReplaceCellString(aCol[nCol], rCol, rRow, rUndoStr, nCol, nRow, rSearchItem))
+ return true;
+ }
+ }
+ }
+ else
+ {
+ // column direction.
+ SCCOL nBeginCol = rRange.aStart.Col() < rCol ? rCol : rRange.aStart.Col();
+ for (SCCOL nCol = nBeginCol; nCol <= rRange.aEnd.Col(); ++nCol)
+ {
+ SCROW nLastNonFilteredRow = -1;
+ SCROW nBeginRow = rRange.aStart.Row();
+ if (nCol == rCol && nBeginRow <= rRow)
+ // always start from one cell past the cursor.
+ nBeginRow = rRow + (nCmd == SvxSearchCmd::FIND ? 1 : 0);
+ for (SCROW nRow = nBeginRow; nRow <= rRange.aEnd.Row(); ++nRow)
+ {
+ if (bSkipFiltered)
+ SkipFilteredRows(nRow, nLastNonFilteredRow, true);
+ if (nRow > rRange.aEnd.Row())
+ break;
+
+ if (lcl_maybeReplaceCellString(aCol[nCol], rCol, rRow, rUndoStr, nCol, nRow, rSearchItem))
+ return true;
+ }
+ }
+ }
+ }
+ return false;
+}
+
+bool ScTable::SearchRangeForAllEmptyCells(
+ const ScRange& rRange, const SvxSearchItem& rSearchItem,
+ ScRangeList& rMatchedRanges, OUString& rUndoStr, ScDocument* pUndoDoc)
+{
+ bool bFound = false;
+ bool bReplace = (rSearchItem.GetCommand() == SvxSearchCmd::REPLACE_ALL) &&
+ !rSearchItem.GetReplaceString().isEmpty();
+ bool bSkipFiltered = rSearchItem.IsSearchFiltered();
+
+ for (SCCOL nCol = rRange.aStart.Col(); nCol <= rRange.aEnd.Col(); ++nCol)
+ {
+ SCROW nLastNonFilteredRow = -1;
+ if (aCol[nCol].IsEmptyData())
+ {
+ // The entire column is empty.
+ const SCROW nEndRow = rRange.aEnd.Row();
+ for (SCROW nRow = rRange.aStart.Row(); nRow <= nEndRow; ++nRow)
+ {
+ SCROW nLastRow;
+ const bool bFiltered = RowFiltered(nRow, nullptr, &nLastRow);
+ if (nLastRow > nEndRow)
+ nLastRow = nEndRow;
+ if (!bFiltered)
+ {
+ rMatchedRanges.Join(ScRange(nCol, nRow, nTab, nCol, nLastRow, nTab));
+ if (bReplace)
+ {
+ const OUString& rNewStr = rSearchItem.GetReplaceString();
+ for (SCROW i = nRow; i <= nLastRow; ++i)
+ {
+ aCol[nCol].SetRawString(i, rNewStr);
+ if (pUndoDoc)
+ {
+ // TODO: I'm using a string cell with empty content to
+ // trigger deletion of cell instance on undo. Maybe I
+ // should create a new cell type for this?
+ pUndoDoc->SetString(ScAddress(nCol, i, nTab), OUString());
+ }
+ }
+ rUndoStr.clear();
+ }
+ }
+
+ nRow = nLastRow; // move to the last filtered row.
+ }
+ bFound = true;
+ continue;
+ }
+
+ for (SCROW nRow = rRange.aStart.Row(); nRow <= rRange.aEnd.Row(); ++nRow)
+ {
+ if (bSkipFiltered)
+ SkipFilteredRows(nRow, nLastNonFilteredRow, true);
+ if (nRow > rRange.aEnd.Row())
+ break;
+
+ ScRefCellValue aCell = aCol[nCol].GetCellValue(nRow);
+ if (aCell.isEmpty())
+ {
+ // empty cell found
+ rMatchedRanges.Join(ScRange(nCol, nRow, nTab));
+ bFound = true;
+
+ if (bReplace)
+ {
+ aCol[nCol].SetRawString(nRow, rSearchItem.GetReplaceString());
+ if (pUndoDoc)
+ {
+ // TODO: I'm using a string cell with empty content to
+ // trigger deletion of cell instance on undo. Maybe I
+ // should create a new cell type for this?
+ pUndoDoc->SetString(ScAddress(nCol, nRow, nTab), OUString());
+ }
+ }
+ }
+ }
+ }
+ return bFound;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sc/source/core/data/table7.cxx b/sc/source/core/data/table7.cxx
new file mode 100644
index 000000000..9af01cba7
--- /dev/null
+++ b/sc/source/core/data/table7.cxx
@@ -0,0 +1,652 @@
+/* -*- 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/.
+ */
+
+#include <table.hxx>
+#include <clipcontext.hxx>
+#include <document.hxx>
+#include <clipparam.hxx>
+#include <segmenttree.hxx>
+#include <sharedformula.hxx>
+#include <cellvalues.hxx>
+#include <olinetab.hxx>
+#include <tabprotection.hxx>
+#include <columniterator.hxx>
+#include <drwlayer.hxx>
+#include <compressedarray.hxx>
+
+#include <osl/diagnose.h>
+#include <sal/log.hxx>
+#include <tools/stream.hxx>
+
+bool ScTable::IsMerged( SCCOL nCol, SCROW nRow ) const
+{
+ if (!ValidCol(nCol) || nCol >= GetAllocatedColumnsCount() )
+ return false;
+
+ return aCol[nCol].IsMerged(nRow);
+}
+
+sc::MultiDataCellState ScTable::HasMultipleDataCells( SCCOL nCol1, SCROW nRow1, SCCOL nCol2, SCROW nRow2 ) const
+{
+ if (!ValidColRow(nCol1, nRow1) || !ValidColRow(nCol2, nRow2))
+ return sc::MultiDataCellState();
+
+ if (nCol1 > nCol2 || nRow1 > nRow2)
+ // invalid range.
+ return sc::MultiDataCellState();
+
+ if (aCol.empty())
+ return sc::MultiDataCellState(sc::MultiDataCellState::Empty);
+
+ auto setFirstCell = []( sc::MultiDataCellState& rRet, SCCOL nCurCol, SCROW nCurRow )
+ {
+ if (rRet.mnCol1 < 0)
+ {
+ // First cell not yet set. Set it.
+ rRet.mnCol1 = nCurCol;
+ rRet.mnRow1 = nCurRow;
+ }
+ };
+
+ SCCOL nMaxCol = aCol.size()-1;
+ bool bHasOne = false;
+ sc::MultiDataCellState aRet(sc::MultiDataCellState::Empty);
+
+ for (SCCOL nCol = nCol1; nCol <= nCol2 && nCol <= nMaxCol; ++nCol)
+ {
+ SCROW nFirstDataRow = -1;
+ switch (aCol[nCol].HasDataCellsInRange(nRow1, nRow2, &nFirstDataRow))
+ {
+ case sc::MultiDataCellState::HasOneCell:
+ {
+ setFirstCell(aRet, nCol, nFirstDataRow);
+
+ if (bHasOne)
+ {
+ // We've already found one data cell in another column.
+ aRet.meState = sc::MultiDataCellState::HasMultipleCells;
+ return aRet;
+ }
+ bHasOne = true;
+ break;
+ }
+ case sc::MultiDataCellState::HasMultipleCells:
+ {
+ setFirstCell(aRet, nCol, nFirstDataRow);
+
+ aRet.meState = sc::MultiDataCellState::HasMultipleCells;
+ return aRet;
+ }
+ case sc::MultiDataCellState::Empty:
+ default:
+ ;
+ }
+ }
+
+ if (bHasOne)
+ aRet.meState = sc::MultiDataCellState::HasOneCell;
+
+ return aRet;
+}
+
+void ScTable::DeleteBeforeCopyFromClip(
+ sc::CopyFromClipContext& rCxt, const ScTable& rClipTab, sc::ColumnSpanSet& rBroadcastSpans )
+{
+ sc::CopyFromClipContext::Range aRange = rCxt.getDestRange();
+ if (!ValidCol(aRange.mnCol1) || !ValidCol(aRange.mnCol2))
+ return;
+
+ // Pass some stuff to the columns via context.
+ rCxt.setTableProtected(IsProtected());
+ rCxt.setCondFormatList(mpCondFormatList.get());
+
+ ScRange aClipRange = rCxt.getClipDoc()->GetClipParam().getWholeRange();
+ SCCOL nClipCol = aClipRange.aStart.Col();
+ {
+ const SCCOL nMaxCol2 = std::min<SCCOL>( aRange.mnCol2, aCol.size() - 1 );
+ for (SCCOL nCol = aRange.mnCol1; nCol <= nMaxCol2; ++nCol, ++nClipCol)
+ {
+ if (nClipCol > aClipRange.aEnd.Col())
+ nClipCol = aClipRange.aStart.Col(); // loop through columns.
+
+ const ScColumn& rClipCol = const_cast<ScTable&>(rClipTab).CreateColumnIfNotExists(nClipCol);
+ aCol[nCol].DeleteBeforeCopyFromClip(rCxt, rClipCol, rBroadcastSpans);
+ }
+ }
+
+ SetStreamValid(false);
+}
+
+void ScTable::CopyOneCellFromClip(
+ sc::CopyFromClipContext& rCxt, const SCCOL nCol1, const SCROW nRow1, const SCCOL nCol2, const SCROW nRow2, const SCROW nSrcRow, const ScTable* pSrcTab )
+{
+ ScRange aSrcRange = rCxt.getClipDoc()->GetClipParam().getWholeRange();
+ SCCOL nSrcColSize = aSrcRange.aEnd.Col() - aSrcRange.aStart.Col() + 1;
+
+ for (SCCOL nCol = nCol1; nCol <= nCol2; ++nCol)
+ {
+ SCCOL nColOffset = nCol - nCol1;
+ nColOffset = nColOffset % nSrcColSize;
+ assert(nColOffset >= 0);
+ CreateColumnIfNotExists(nCol).CopyOneCellFromClip(rCxt, nRow1, nRow2, nColOffset);
+
+ if (rCxt.getInsertFlag() & InsertDeleteFlags::ATTRIB)
+ {
+ for (SCROW nRow = nRow1; nRow <= nRow2; ++nRow)
+ CopyConditionalFormat(nCol, nRow, nCol, nRow, nCol - aSrcRange.aStart.Col() - nColOffset,
+ nRow - nSrcRow, pSrcTab);
+ }
+ }
+
+ if (nCol1 == 0 && nCol2 == rDocument.MaxCol() && mpRowHeights)
+ {
+ mpRowHeights->setValue(nRow1, nRow2, pSrcTab->GetOriginalHeight(nSrcRow));
+
+ if (pRowFlags && pSrcTab->pRowFlags) {
+ if (pSrcTab->pRowFlags->GetValue(nSrcRow) & CRFlags::ManualSize)
+ pRowFlags->OrValue(nRow1, CRFlags::ManualSize);
+ else
+ pRowFlags->AndValue(nRow1, ~CRFlags::ManualSize);
+ }
+ }
+
+ // Copy graphics over too
+ bool bCopyGraphics
+ = (rCxt.getInsertFlag() & InsertDeleteFlags::OBJECTS) != InsertDeleteFlags::NONE;
+ if (!(bCopyGraphics && rCxt.getClipDoc()->mpDrawLayer))
+ return;
+
+ ScDrawLayer* pDrawLayer = GetDoc().GetDrawLayer();
+ OSL_ENSURE(pDrawLayer, "No drawing layer");
+ if (pDrawLayer)
+ {
+ const ScAddress aSrcStartPos
+ = rCxt.getClipDoc()->GetClipParam().getWholeRange().aStart;
+ const ScAddress aSrcEndPos = rCxt.getClipDoc()->GetClipParam().getWholeRange().aEnd;
+ tools::Rectangle aSourceRect = rCxt.getClipDoc()->GetMMRect(
+ aSrcStartPos.Col(), aSrcStartPos.Row(), aSrcEndPos.Col(), aSrcEndPos.Row(),
+ aSrcStartPos.Tab());
+ tools::Rectangle aDestRect = GetDoc().GetMMRect(nCol1, nRow1, nCol2, nRow2, nTab);
+ pDrawLayer->CopyFromClip(rCxt.getClipDoc()->mpDrawLayer.get(), aSrcStartPos.Tab(),
+ aSourceRect, ScAddress(nCol1, nRow1, nTab), aDestRect);
+ }
+}
+
+void ScTable::SetValues( const SCCOL nCol, const SCROW nRow, const std::vector<double>& rVals )
+{
+ if (!ValidCol(nCol))
+ return;
+
+ CreateColumnIfNotExists(nCol).SetValues(nRow, rVals);
+}
+
+void ScTable::TransferCellValuesTo( const SCCOL nCol, SCROW nRow, size_t nLen, sc::CellValues& rDest )
+{
+ if (!ValidCol(nCol))
+ return;
+
+ CreateColumnIfNotExists(nCol).TransferCellValuesTo(nRow, nLen, rDest);
+}
+
+void ScTable::CopyCellValuesFrom( const SCCOL nCol, SCROW nRow, const sc::CellValues& rSrc )
+{
+ if (!ValidCol(nCol))
+ return;
+
+ CreateColumnIfNotExists(nCol).CopyCellValuesFrom(nRow, rSrc);
+}
+
+void ScTable::ConvertFormulaToValue(
+ sc::EndListeningContext& rCxt, const SCCOL nCol1, const SCROW nRow1, const SCCOL nCol2, const SCROW nRow2,
+ sc::TableValues* pUndo )
+{
+ if (!ValidCol(nCol1) || !ValidCol(nCol2) || nCol1 > nCol2)
+ return;
+
+ for (SCCOL nCol = nCol1; nCol <= nCol2; ++nCol)
+ CreateColumnIfNotExists(nCol).ConvertFormulaToValue(rCxt, nRow1, nRow2, pUndo);
+}
+
+void ScTable::SwapNonEmpty(
+ sc::TableValues& rValues, sc::StartListeningContext& rStartCxt, sc::EndListeningContext& rEndCxt )
+{
+ const ScRange& rRange = rValues.getRange();
+ assert(rRange.IsValid());
+ for (SCCOL nCol = rRange.aStart.Col(); nCol <= rRange.aEnd.Col(); ++nCol)
+ CreateColumnIfNotExists(nCol).SwapNonEmpty(rValues, rStartCxt, rEndCxt);
+}
+
+void ScTable::PreprocessRangeNameUpdate(
+ sc::EndListeningContext& rEndListenCxt, sc::CompileFormulaContext& rCompileCxt )
+{
+ for (SCCOL i = 0; i < aCol.size(); ++i)
+ aCol[i].PreprocessRangeNameUpdate(rEndListenCxt, rCompileCxt);
+}
+
+void ScTable::PreprocessDBDataUpdate(
+ sc::EndListeningContext& rEndListenCxt, sc::CompileFormulaContext& rCompileCxt )
+{
+ for (SCCOL i = 0; i < aCol.size(); ++i)
+ aCol[i].PreprocessDBDataUpdate(rEndListenCxt, rCompileCxt);
+}
+
+void ScTable::CompileHybridFormula(
+ sc::StartListeningContext& rStartListenCxt, sc::CompileFormulaContext& rCompileCxt )
+{
+ for (SCCOL i = 0; i < aCol.size(); ++i)
+ aCol[i].CompileHybridFormula(rStartListenCxt, rCompileCxt);
+}
+
+void ScTable::UpdateScriptTypes( const SCCOL nCol1, SCROW nRow1, const SCCOL nCol2, SCROW nRow2 )
+{
+ if (!IsColValid(nCol1) || !ValidCol(nCol2) || nCol1 > nCol2)
+ return;
+
+ const SCCOL nMaxCol2 = std::min<SCCOL>( nCol2, aCol.size() - 1 );
+
+ for (SCCOL nCol = nCol1; nCol <= nMaxCol2; ++nCol)
+ aCol[nCol].UpdateScriptTypes(nRow1, nRow2);
+}
+
+bool ScTable::HasUniformRowHeight( SCROW nRow1, SCROW nRow2 ) const
+{
+ if (!ValidRow(nRow1) || !ValidRow(nRow2) || nRow1 > nRow2)
+ return false;
+
+ ScFlatUInt16RowSegments::RangeData aData;
+ if (!mpRowHeights->getRangeData(nRow1, aData))
+ // Search failed.
+ return false;
+
+ return nRow2 <= aData.mnRow2;
+}
+
+void ScTable::SplitFormulaGroups( SCCOL nCol, std::vector<SCROW>& rRows )
+{
+ if (!IsColValid(nCol))
+ return;
+
+ sc::SharedFormulaUtil::splitFormulaCellGroups(GetDoc(), aCol[nCol].maCells, rRows);
+}
+
+void ScTable::UnshareFormulaCells( SCCOL nCol, std::vector<SCROW>& rRows )
+{
+ if (!IsColValid(nCol))
+ return;
+
+ sc::SharedFormulaUtil::unshareFormulaCells(rDocument, aCol[nCol].maCells, rRows);
+}
+
+void ScTable::RegroupFormulaCells( SCCOL nCol )
+{
+ if (!IsColValid(nCol))
+ return;
+
+ aCol[nCol].RegroupFormulaCells();
+}
+
+bool ScTable::HasFormulaCell( const SCCOL nCol1, SCROW nRow1, const SCCOL nCol2, SCROW nRow2 ) const
+{
+ if (nCol2 < nCol1 || !IsColValid(nCol1) || !ValidCol(nCol2))
+ return false;
+
+ const SCCOL nMaxCol2 = std::min<SCCOL>( nCol2, aCol.size() - 1 );
+
+ for (SCCOL nCol = nCol1; nCol <= nMaxCol2; ++nCol)
+ if (aCol[nCol].HasFormulaCell(nRow1, nRow2))
+ return true;
+
+ return false;
+}
+
+void ScTable::EndListeningIntersectedGroup(
+ sc::EndListeningContext& rCxt, SCCOL nCol, SCROW nRow, std::vector<ScAddress>* pGroupPos )
+{
+ if (!IsColValid(nCol))
+ return;
+
+ aCol[nCol].EndListeningIntersectedGroup(rCxt, nRow, pGroupPos);
+}
+
+void ScTable::EndListeningIntersectedGroups(
+ sc::EndListeningContext& rCxt, const SCCOL nCol1, SCROW nRow1, const SCCOL nCol2, SCROW nRow2,
+ std::vector<ScAddress>* pGroupPos )
+{
+ if (nCol2 < nCol1 || !IsColValid(nCol1) || !ValidCol(nCol2))
+ return;
+
+ for (SCCOL nCol : GetAllocatedColumnsRange(nCol1, nCol2))
+ aCol[nCol].EndListeningIntersectedGroups(rCxt, nRow1, nRow2, pGroupPos);
+}
+
+void ScTable::EndListeningGroup( sc::EndListeningContext& rCxt, const SCCOL nCol, SCROW nRow )
+{
+ if (!IsColValid(nCol))
+ return;
+
+ aCol[nCol].EndListeningGroup(rCxt, nRow);
+}
+
+void ScTable::SetNeedsListeningGroup( SCCOL nCol, SCROW nRow )
+{
+ if (!ValidCol(nCol))
+ return;
+
+ CreateColumnIfNotExists(nCol).SetNeedsListeningGroup(nRow);
+}
+
+bool ScTable::IsEditActionAllowed(
+ sc::ColRowEditAction eAction, SCCOLROW nStart, SCCOLROW nEnd ) const
+{
+ if (!IsProtected())
+ {
+ SCCOL nCol1 = 0, nCol2 = aCol.size() - 1;
+ SCROW nRow1 = 0, nRow2 = rDocument.MaxRow();
+
+ switch (eAction)
+ {
+ case sc::ColRowEditAction::InsertColumnsBefore:
+ case sc::ColRowEditAction::InsertColumnsAfter:
+ case sc::ColRowEditAction::DeleteColumns:
+ {
+ nCol1 = nStart;
+ nCol2 = nEnd;
+ break;
+ }
+ case sc::ColRowEditAction::InsertRowsBefore:
+ case sc::ColRowEditAction::InsertRowsAfter:
+ case sc::ColRowEditAction::DeleteRows:
+ {
+ nRow1 = nStart;
+ nRow2 = nEnd;
+ break;
+ }
+ default:
+ ;
+ }
+
+ return IsBlockEditable(nCol1, nRow1, nCol2, nRow2, nullptr);
+ }
+
+ if (IsScenario())
+ // TODO: I don't even know what this scenario thingie is. Perhaps we
+ // should check it against the scenario ranges?
+ return false;
+
+ assert(pTabProtection);
+
+ switch (eAction)
+ {
+ case sc::ColRowEditAction::InsertColumnsBefore:
+ case sc::ColRowEditAction::InsertColumnsAfter:
+ {
+ // TODO: improve the matrix range handling for the insert-before action.
+ if (HasBlockMatrixFragment(nStart, 0, nEnd, rDocument.MaxRow()))
+ return false;
+
+ return pTabProtection->isOptionEnabled(ScTableProtection::INSERT_COLUMNS);
+ }
+ case sc::ColRowEditAction::InsertRowsBefore:
+ case sc::ColRowEditAction::InsertRowsAfter:
+ {
+ // TODO: improve the matrix range handling for the insert-before action.
+ if (HasBlockMatrixFragment(0, nStart, rDocument.MaxCol(), nEnd))
+ return false;
+
+ return pTabProtection->isOptionEnabled(ScTableProtection::INSERT_ROWS);
+ }
+ case sc::ColRowEditAction::DeleteColumns:
+ {
+ if (!pTabProtection->isOptionEnabled(ScTableProtection::DELETE_COLUMNS))
+ return false;
+
+ return !HasAttrib(nStart, 0, nEnd, rDocument.MaxRow(), HasAttrFlags::Protected);
+ }
+ case sc::ColRowEditAction::DeleteRows:
+ {
+ if (!pTabProtection->isOptionEnabled(ScTableProtection::DELETE_ROWS))
+ return false;
+
+ return !HasAttrib(0, nStart, rDocument.MaxCol(), nEnd, HasAttrFlags::Protected);
+ }
+ default:
+ ;
+ }
+
+ return false;
+}
+
+std::optional<sc::ColumnIterator> ScTable::GetColumnIterator( SCCOL nCol, SCROW nRow1, SCROW nRow2 ) const
+{
+ if (!ValidCol(nCol))
+ return {};
+
+ return const_cast<ScTable*>(this)->CreateColumnIfNotExists(nCol).GetColumnIterator(nRow1, nRow2);
+}
+
+bool ScTable::EnsureFormulaCellResults( const SCCOL nCol1, SCROW nRow1, const SCCOL nCol2, SCROW nRow2, bool bSkipRunning )
+{
+ if (nCol2 < nCol1 || !IsColValid(nCol1) || !ValidCol(nCol2))
+ return false;
+
+ const SCCOL nMaxCol2 = std::min<SCCOL>( nCol2, aCol.size() - 1 );
+
+ bool bAnyDirty = false;
+
+ for (SCCOL nCol = nCol1; nCol <= nMaxCol2; ++nCol)
+ {
+ bool bRet = aCol[nCol].EnsureFormulaCellResults(nRow1, nRow2, bSkipRunning);
+ bAnyDirty = bAnyDirty || bRet;
+ }
+
+ return bAnyDirty;
+}
+
+void ScTable::finalizeOutlineImport()
+{
+ if (pOutlineTable && pRowFlags)
+ {
+ pOutlineTable->GetRowArray().finalizeImport(*this);
+ }
+}
+
+void ScTable::StoreToCache(SvStream& rStrm) const
+{
+ SCCOL nStartCol = 0;
+ SCCOL nEndCol = rDocument.MaxCol();
+ SCROW nStartRow = 0;
+ SCROW nEndRow = rDocument.MaxRow();
+
+ GetDataArea(nStartCol, nStartRow, nEndCol, nEndRow, false, false);
+
+ rStrm.WriteUInt64(nEndCol + 1);
+ for (SCCOL nCol = 0; nCol <= nEndCol; ++nCol)
+ {
+ aCol[nCol].StoreToCache(rStrm);
+ }
+}
+
+void ScTable::RestoreFromCache(SvStream& rStrm)
+{
+ sal_uInt64 nCols = 0;
+ rStrm.ReadUInt64(nCols);
+ for (SCCOL nCol = 0; nCol < static_cast<SCCOL>(nCols); ++nCol)
+ {
+ aCol[nCol].RestoreFromCache(rStrm);
+ }
+}
+
+OString ScTable::dumpSheetGeomData(bool bColumns, SheetGeomType eGeomType)
+{
+ switch (eGeomType)
+ {
+ case SheetGeomType::SIZES:
+ // returns a non-empty space separated list of spans with trailing space.
+ // The format of the span is <size of any row/col in the span in print twips>:<last row/col of the span>
+ // Example (for columns with three spans if MAXCOL is 1023): "1280:3 1049:50 1280:1023"
+ return dumpColumnRowSizes(bColumns);
+ case SheetGeomType::HIDDEN:
+ // returns a non-empty space separated list of spans with trailing space.
+ // The format of the span is:
+ // 1) First span: <1 (span is hidden) / 0 (not hidden)>:<last row/col of the span>
+ // 2) Rest of the spans: <last row/col of the span>
+ // The hidden state of the spans after the first can be inferred from the first span's flag as no adjacent
+ // spans can have the same state by definition of span.
+ return dumpHiddenFiltered(bColumns, /*bHidden*/ true);
+ case SheetGeomType::FILTERED:
+ // has exactly the same format as 'hidden'.
+ return dumpHiddenFiltered(bColumns, /*bHidden*/ false);
+ case SheetGeomType::GROUPS:
+ // returns a space separated list of 'levels' with trailing space.
+ // A 'level' is a comma separated list of groups(outline entries) with trailing comma.
+ // format of a group is:
+ // <start row/col of group>:<number of rows/cols in the group>:<1/0(group is hidden?)>:<1/0(control is visible?)>
+ return dumpColumnRowGroups(bColumns);
+ default:
+ ;
+ }
+
+ return "";
+}
+
+OString ScTable::dumpColumnRowSizes(bool bColumns)
+{
+ // If the data-structures are not available, just report that all
+ // rows/cols have the default sizes.
+ static const OString aDefaultForCols
+ = OString::number(STD_COL_WIDTH) + ":" + OString::number(GetDoc().MaxCol()) + " ";
+ static const OString aDefaultForRows
+ = OString::number(ScGlobal::nStdRowHeight) + ":" + OString::number(GetDoc().MaxRow()) + " ";
+
+ // ScCompressedArray is a template class and we don't want to impose
+ // the restriction that its value type should be string serializable,
+ // instead just operate on the specialized object.
+ typedef ScCompressedArray<SCCOL, sal_uInt16> ColWidthsType;
+ auto dumpColWidths = [this](const ColWidthsType& rWidths) -> OString {
+ OString aOutput;
+ OString aSegment;
+ SCCOL nStartCol = 0;
+ const SCCOL nMaxCol = std::min(rWidths.GetLastPos(), GetDoc().MaxCol());
+ size_t nDummy = 0;
+ while (nStartCol <= nMaxCol)
+ {
+ SCCOL nEndCol;
+ sal_uInt16 nWidth = rWidths.GetValue(nStartCol, nDummy, nEndCol);
+ // The last span nEndCol is always MAXCOL+1 for some reason, and we don't want that.
+ if (nEndCol > nMaxCol)
+ nEndCol = nMaxCol;
+ aSegment = OString::number(nWidth) + ":" + OString::number(nEndCol) + " ";
+ aOutput += aSegment;
+ nStartCol = nEndCol + 1;
+ }
+
+ return aOutput;
+ };
+
+ if (bColumns)
+ return mpColWidth ? dumpColWidths(*mpColWidth) : aDefaultForCols;
+
+ return mpRowHeights ? mpRowHeights->dumpAsString() : aDefaultForRows;
+}
+
+OString ScTable::dumpHiddenFiltered(bool bColumns, bool bHidden)
+{
+ // defaults to no hidden/filtered row/cols.
+ static const OString aDefaultForCols = "0:" + OString::number(GetDoc().MaxCol()) + " ";
+ static const OString aDefaultForRows = "0:" + OString::number(GetDoc().MaxRow()) + " ";
+
+ if (bHidden)
+ {
+ if (bColumns)
+ return mpHiddenCols ? mpHiddenCols->dumpAsString() : aDefaultForCols;
+
+ return mpHiddenRows ? mpHiddenRows->dumpAsString() : aDefaultForRows;
+ }
+
+ if (bColumns)
+ return mpFilteredCols ? mpFilteredCols->dumpAsString() : aDefaultForCols;
+
+ return mpFilteredRows ? mpFilteredRows->dumpAsString() : aDefaultForRows;
+}
+
+OString ScTable::dumpColumnRowGroups(bool bColumns) const
+{
+ if (!pOutlineTable)
+ return "";
+
+ if (bColumns)
+ return pOutlineTable->GetColArray().dumpAsString();
+
+ return pOutlineTable->GetRowArray().dumpAsString();
+}
+
+SCCOL ScTable::GetLOKFreezeCol() const
+{
+ return maLOKFreezeCell.Col();
+}
+
+SCROW ScTable::GetLOKFreezeRow() const
+{
+ return maLOKFreezeCell.Row();
+}
+
+bool ScTable::SetLOKFreezeCol(SCCOL nFreezeCol)
+{
+ if (!ValidCol(nFreezeCol))
+ {
+ SAL_WARN("sc.core", "ScTable::SetLOKFreezeCol : invalid nFreezeCol = " << nFreezeCol);
+ return false;
+ }
+
+ if (maLOKFreezeCell.Col() != nFreezeCol)
+ {
+ maLOKFreezeCell.SetCol(nFreezeCol);
+ return true;
+ }
+
+ return false;
+}
+
+bool ScTable::SetLOKFreezeRow(SCROW nFreezeRow)
+{
+ if (!ValidRow(nFreezeRow))
+ {
+ SAL_WARN("sc.core", "ScTable::SetLOKFreezeRow : invalid nFreezeRow = " << nFreezeRow);
+ return false;
+ }
+
+ if (maLOKFreezeCell.Row() != nFreezeRow)
+ {
+ maLOKFreezeCell.SetRow(nFreezeRow);
+ return true;
+ }
+
+ return false;
+}
+
+std::set<SCCOL> ScTable::QueryColumnsWithFormulaCells() const
+{
+ std::set<SCCOL> aColIndices;
+
+ for (const auto& pCol : aCol)
+ {
+ if (pCol->HasFormulaCell())
+ aColIndices.insert(pCol->GetCol());
+ }
+
+ return aColIndices;
+}
+
+void ScTable::CheckIntegrity() const
+{
+ for (const auto& pCol : aCol)
+ pCol->CheckIntegrity();
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sc/source/core/data/tabprotection.cxx b/sc/source/core/data/tabprotection.cxx
new file mode 100644
index 000000000..12bfff06b
--- /dev/null
+++ b/sc/source/core/data/tabprotection.cxx
@@ -0,0 +1,724 @@
+/* -*- 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 <tabprotection.hxx>
+#include <svl/PasswordHelper.hxx>
+#include <comphelper/docpasswordhelper.hxx>
+#include <comphelper/hash.hxx>
+#include <comphelper/sequence.hxx>
+#include <osl/diagnose.h>
+#include <document.hxx>
+
+#include <vector>
+
+#define DEBUG_TAB_PROTECTION 0
+
+constexpr OUStringLiteral URI_SHA1 = u"http://www.w3.org/2000/09/xmldsig#sha1";
+constexpr OUStringLiteral URI_SHA256_ODF12 = u"http://www.w3.org/2000/09/xmldsig#sha256";
+constexpr OUStringLiteral URI_SHA256_W3C = u"http://www.w3.org/2001/04/xmlenc#sha256";
+constexpr OUStringLiteral URI_XLS_LEGACY = u"http://docs.oasis-open.org/office/ns/table/legacy-hash-excel";
+
+using namespace ::com::sun::star;
+using ::com::sun::star::uno::Sequence;
+using ::std::vector;
+
+bool ScPassHashHelper::needsPassHashRegen(const ScDocument& rDoc, ScPasswordHash eHash1, ScPasswordHash eHash2)
+{
+ if (rDoc.IsDocProtected())
+ {
+ const ScDocProtection* p = rDoc.GetDocProtection();
+ if (!p->isPasswordEmpty() && !p->hasPasswordHash(eHash1, eHash2))
+ return true;
+ }
+
+ SCTAB nTabCount = rDoc.GetTableCount();
+ for (SCTAB i = 0; i < nTabCount; ++i)
+ {
+ const ScTableProtection* p = rDoc.GetTabProtection(i);
+ if (!p || !p->isProtected())
+ // Sheet not protected. Skip it.
+ continue;
+
+ if (!p->isPasswordEmpty() && !p->hasPasswordHash(eHash1, eHash2))
+ return true;
+ }
+
+ return false;
+}
+
+OUString ScPassHashHelper::getHashURI(ScPasswordHash eHash)
+{
+ switch (eHash)
+ {
+ case PASSHASH_SHA256:
+ return URI_SHA256_ODF12;
+ case PASSHASH_SHA1:
+ return URI_SHA1;
+ case PASSHASH_XL:
+ return URI_XLS_LEGACY;
+ case PASSHASH_UNSPECIFIED:
+ default:
+ ;
+ }
+ return OUString();
+}
+
+ScPasswordHash ScPassHashHelper::getHashTypeFromURI(std::u16string_view rURI)
+{
+ if (rURI == URI_SHA256_ODF12 || rURI == URI_SHA256_W3C)
+ return PASSHASH_SHA256;
+ if ( rURI == URI_SHA1 )
+ return PASSHASH_SHA1;
+ else if ( rURI == URI_XLS_LEGACY )
+ return PASSHASH_XL;
+ return PASSHASH_UNSPECIFIED;
+}
+
+bool ScOoxPasswordHash::verifyPassword( const OUString& aPassText ) const
+{
+ if (!hasPassword())
+ return false;
+
+ const OUString aHash( comphelper::DocPasswordHelper::GetOoxHashAsBase64(
+ aPassText, maSaltValue, mnSpinCount, comphelper::Hash::IterCount::APPEND, maAlgorithmName));
+ if (aHash.isEmpty())
+ // unsupported algorithm
+ return false;
+
+ return aHash == maHashValue;
+}
+
+ScPassHashProtectable::~ScPassHashProtectable()
+{
+}
+
+class ScTableProtectionImpl
+{
+public:
+ static Sequence<sal_Int8> hashPassword(std::u16string_view aPassText, ScPasswordHash eHash);
+ static Sequence<sal_Int8> hashPassword(const Sequence<sal_Int8>& rPassHash, ScPasswordHash eHash);
+
+ explicit ScTableProtectionImpl(SCSIZE nOptSize);
+ explicit ScTableProtectionImpl(const ScTableProtectionImpl& r);
+
+ bool isProtected() const { return mbProtected;}
+ bool isProtectedWithPass() const;
+ void setProtected(bool bProtected);
+
+ bool isPasswordEmpty() const { return mbEmptyPass;}
+ bool hasPasswordHash(ScPasswordHash eHash, ScPasswordHash eHash2) const;
+ void setPassword(const OUString& aPassText);
+ css::uno::Sequence<sal_Int8> getPasswordHash(
+ ScPasswordHash eHash, ScPasswordHash eHash2) const;
+ const ScOoxPasswordHash& getPasswordHash() const;
+ void setPasswordHash(
+ const css::uno::Sequence<sal_Int8>& aPassword,
+ ScPasswordHash eHash, ScPasswordHash eHash2);
+ void setPasswordHash( const OUString& rAlgorithmName, const OUString& rHashValue,
+ const OUString& rSaltValue, sal_uInt32 nSpinCount );
+ bool verifyPassword(const OUString& aPassText) const;
+
+ bool isOptionEnabled(SCSIZE nOptId) const;
+ void setOption(SCSIZE nOptId, bool bEnabled);
+
+ void setEnhancedProtection( ::std::vector< ScEnhancedProtection > && rProt );
+ const ::std::vector< ScEnhancedProtection > & getEnhancedProtection() const { return maEnhancedProtection;}
+ bool updateReference( UpdateRefMode, const ScDocument&, const ScRange& rWhere, SCCOL nDx, SCROW nDy, SCTAB nDz );
+ bool isBlockEditable( const ScRange& rRange ) const;
+ bool isSelectionEditable( const ScRangeList& rRangeList ) const;
+
+private:
+ OUString maPassText;
+ css::uno::Sequence<sal_Int8> maPassHash;
+ ::std::vector<bool> maOptions;
+ bool mbEmptyPass;
+ bool mbProtected;
+ ScPasswordHash meHash1;
+ ScPasswordHash meHash2;
+ ScOoxPasswordHash maPasswordHash;
+ ::std::vector< ScEnhancedProtection > maEnhancedProtection;
+};
+
+Sequence<sal_Int8> ScTableProtectionImpl::hashPassword(std::u16string_view aPassText, ScPasswordHash eHash)
+{
+ Sequence<sal_Int8> aHash;
+ switch (eHash)
+ {
+ case PASSHASH_XL:
+ aHash = ::comphelper::DocPasswordHelper::GetXLHashAsSequence( aPassText );
+ break;
+ case PASSHASH_SHA1:
+ SvPasswordHelper::GetHashPassword(aHash, aPassText);
+ break;
+ case PASSHASH_SHA1_UTF8:
+ SvPasswordHelper::GetHashPasswordSHA1UTF8(aHash, aPassText);
+ break;
+ case PASSHASH_SHA256:
+ SvPasswordHelper::GetHashPasswordSHA256(aHash, aPassText);
+ break;
+ default:
+ ;
+ }
+ return aHash;
+}
+
+Sequence<sal_Int8> ScTableProtectionImpl::hashPassword(
+ const Sequence<sal_Int8>& rPassHash, ScPasswordHash eHash)
+{
+ if (!rPassHash.hasElements() || eHash == PASSHASH_UNSPECIFIED)
+ return rPassHash;
+
+ // TODO: Right now, we only support double-hash by SHA1.
+ if (eHash == PASSHASH_SHA1)
+ {
+ auto aChars = comphelper::sequenceToContainer<vector<char>>(rPassHash);
+
+ Sequence<sal_Int8> aNewHash;
+ SvPasswordHelper::GetHashPassword(aNewHash, aChars.data(), aChars.size());
+ return aNewHash;
+ }
+
+ return rPassHash;
+}
+
+ScTableProtectionImpl::ScTableProtectionImpl(SCSIZE nOptSize) :
+ maOptions(nOptSize),
+ mbEmptyPass(true),
+ mbProtected(false),
+ meHash1(PASSHASH_SHA1),
+ meHash2(PASSHASH_UNSPECIFIED)
+{
+}
+
+ScTableProtectionImpl::ScTableProtectionImpl(const ScTableProtectionImpl& r) :
+ maPassText(r.maPassText),
+ maPassHash(r.maPassHash),
+ maOptions(r.maOptions),
+ mbEmptyPass(r.mbEmptyPass),
+ mbProtected(r.mbProtected),
+ meHash1(r.meHash1),
+ meHash2(r.meHash2),
+ maPasswordHash(r.maPasswordHash),
+ maEnhancedProtection(r.maEnhancedProtection)
+{
+}
+
+bool ScTableProtectionImpl::isProtectedWithPass() const
+{
+ if (!mbProtected)
+ return false;
+
+ return !maPassText.isEmpty() || maPassHash.hasElements() || maPasswordHash.hasPassword();
+}
+
+void ScTableProtectionImpl::setProtected(bool bProtected)
+{
+ mbProtected = bProtected;
+ // We need to keep the old password even when the protection is off. So,
+ // don't erase the password data here.
+}
+
+void ScTableProtectionImpl::setPassword(const OUString& aPassText)
+{
+ // We can't hash it here because we don't know whether this document will
+ // get saved to Excel or ODF, depending on which we will need to use a
+ // different hashing algorithm. One alternative is to hash it using all
+ // hash algorithms that we support, and store them all.
+
+ maPassText = aPassText;
+ mbEmptyPass = aPassText.isEmpty();
+ if (mbEmptyPass)
+ {
+ maPassHash = Sequence<sal_Int8>();
+ }
+ maPasswordHash.clear();
+}
+
+bool ScTableProtectionImpl::hasPasswordHash(ScPasswordHash eHash, ScPasswordHash eHash2) const
+{
+ if (mbEmptyPass)
+ return true;
+
+ if (!maPassText.isEmpty())
+ return true;
+
+ if (meHash1 == eHash)
+ {
+ if (meHash2 == PASSHASH_UNSPECIFIED)
+ // single hash.
+ return true;
+
+ return meHash2 == eHash2;
+ }
+
+ return false;
+}
+
+Sequence<sal_Int8> ScTableProtectionImpl::getPasswordHash(
+ ScPasswordHash eHash, ScPasswordHash eHash2) const
+{
+ Sequence<sal_Int8> aPassHash;
+
+ if (mbEmptyPass)
+ // Flagged as empty.
+ return aPassHash;
+
+ if (!maPassText.isEmpty())
+ {
+ // Cleartext password exists. Hash it.
+ aPassHash = hashPassword(maPassText, eHash);
+ if (eHash2 != PASSHASH_UNSPECIFIED)
+ // Double-hash it.
+ aPassHash = hashPassword(aPassHash, eHash2);
+
+ return aPassHash;
+ }
+ else
+ {
+ // No clear text password. Check if we have a hash value of the right hash type.
+ if (meHash1 == eHash)
+ {
+ aPassHash = maPassHash;
+
+ if (meHash2 == eHash2)
+ // Matching double-hash requested.
+ return aPassHash;
+ else if (meHash2 == PASSHASH_UNSPECIFIED)
+ // primary hashing type match. Double hash it by the requested
+ // double-hash type.
+ return hashPassword(aPassHash, eHash2);
+ }
+ }
+
+ // failed.
+ return Sequence<sal_Int8>();
+}
+
+const ScOoxPasswordHash& ScTableProtectionImpl::getPasswordHash() const
+{
+ return maPasswordHash;
+}
+
+void ScTableProtectionImpl::setPasswordHash(
+ const uno::Sequence<sal_Int8>& aPassword, ScPasswordHash eHash, ScPasswordHash eHash2)
+{
+ sal_Int32 nLen = aPassword.getLength();
+ mbEmptyPass = nLen <= 0;
+ meHash1 = eHash;
+ meHash2 = eHash2;
+ maPassHash = aPassword;
+
+#if DEBUG_TAB_PROTECTION
+ for (sal_Int8 n : aPassword)
+ printf("%2.2X ", static_cast<sal_uInt8>(n));
+ printf("\n");
+#endif
+}
+
+void ScTableProtectionImpl::setPasswordHash( const OUString& rAlgorithmName, const OUString& rHashValue,
+ const OUString& rSaltValue, sal_uInt32 nSpinCount )
+{
+ if (!rHashValue.isEmpty())
+ {
+ // Invalidate the other hashes.
+ setPasswordHash( uno::Sequence<sal_Int8>(), PASSHASH_UNSPECIFIED, PASSHASH_UNSPECIFIED);
+
+ // We don't know whether this is an empty password (or would
+ // unnecessarily have to try to verify an empty password), assume it is
+ // not. A later verifyPassword() with an empty password will determine.
+ // If this was not set to false then a verifyPassword() with an empty
+ // password would unlock even if this hash here wasn't for an empty
+ // password. Ugly stuff.
+ mbEmptyPass = false;
+ }
+
+ maPasswordHash.maAlgorithmName = rAlgorithmName;
+ maPasswordHash.maHashValue = rHashValue;
+ maPasswordHash.maSaltValue = rSaltValue;
+ maPasswordHash.mnSpinCount = nSpinCount;
+}
+
+bool ScTableProtectionImpl::verifyPassword(const OUString& aPassText) const
+{
+#if DEBUG_TAB_PROTECTION
+ fprintf(stdout, "ScTableProtectionImpl::verifyPassword: input = '%s'\n",
+ OUStringToOString(aPassText, RTL_TEXTENCODING_UTF8).getStr());
+#endif
+
+ if (mbEmptyPass)
+ return aPassText.isEmpty();
+
+ if (!maPassText.isEmpty())
+ // Clear text password exists, and this one takes precedence.
+ return aPassText == maPassText;
+
+ // For PASSHASH_UNSPECIFIED also maPassHash is empty and any aPassText
+ // would yield an empty hash as well and thus compare true. Don't.
+ if (meHash1 != PASSHASH_UNSPECIFIED)
+ {
+ Sequence<sal_Int8> aHash = hashPassword(aPassText, meHash1);
+ aHash = hashPassword(aHash, meHash2);
+
+#if DEBUG_TAB_PROTECTION
+ fprintf(stdout, "ScTableProtectionImpl::verifyPassword: hash = ");
+ for (sal_Int32 i = 0; i < aHash.getLength(); ++i)
+ printf("%2.2X ", static_cast<sal_uInt8>(aHash[i]));
+ printf("\n");
+#endif
+
+ if (aHash == maPassHash)
+ {
+ return true;
+ }
+ }
+
+ // tdf#115483 compat hack for ODF 1.2; for now UTF8-SHA1 passwords are only
+ // verified, not generated
+ if (meHash1 == PASSHASH_SHA1 && meHash2 == PASSHASH_UNSPECIFIED)
+ {
+ Sequence<sal_Int8> const aHash2 = hashPassword(aPassText, PASSHASH_SHA1_UTF8);
+ return aHash2 == maPassHash;
+ }
+
+ // Not yet generated or tracked with meHash1 or meHash2, but can be read
+ // from OOXML.
+ return maPasswordHash.verifyPassword( aPassText);
+}
+
+bool ScTableProtectionImpl::isOptionEnabled(SCSIZE nOptId) const
+{
+ if ( maOptions.size() <= static_cast<size_t>(nOptId) )
+ {
+ OSL_FAIL("ScTableProtectionImpl::isOptionEnabled: wrong size");
+ return false;
+ }
+
+ return maOptions[nOptId];
+}
+
+void ScTableProtectionImpl::setOption(SCSIZE nOptId, bool bEnabled)
+{
+ if ( maOptions.size() <= static_cast<size_t>(nOptId) )
+ {
+ OSL_FAIL("ScTableProtectionImpl::setOption: wrong size");
+ return;
+ }
+
+ maOptions[nOptId] = bEnabled;
+}
+
+void ScTableProtectionImpl::setEnhancedProtection( ::std::vector< ScEnhancedProtection > && rProt )
+{
+ maEnhancedProtection = std::move(rProt);
+}
+
+bool ScTableProtectionImpl::updateReference( UpdateRefMode eMode, const ScDocument& rDoc,
+ const ScRange& rWhere, SCCOL nDx, SCROW nDy, SCTAB nDz )
+{
+ bool bChanged = false;
+ for (auto& rEnhancedProtection : maEnhancedProtection)
+ {
+ if (rEnhancedProtection.maRangeList.is())
+ bChanged |= rEnhancedProtection.maRangeList->UpdateReference( eMode, &rDoc, rWhere, nDx, nDy, nDz);
+ }
+ return bChanged;
+}
+
+bool ScTableProtectionImpl::isBlockEditable( const ScRange& rRange ) const
+{
+ /* TODO: ask for password (and remember) if a password was set for
+ * a matching range and no matching range without password was encountered.
+ * Would need another return type than boolean to reflect
+ * "password required for a specific protection". */
+
+ // No protection exception or overriding permission to edit if empty.
+ if (maEnhancedProtection.empty())
+ return false;
+
+ // No security descriptor in an enhanced protection means the ranges of
+ // that protection are editable. If there is any security descriptor
+ // present we assume the permission to edit is not granted. Until we
+ // actually can evaluate the descriptors...
+
+ auto lIsEditable = [rRange](const ScEnhancedProtection& rEnhancedProtection) {
+ return !rEnhancedProtection.hasSecurityDescriptor()
+ && rEnhancedProtection.maRangeList.is() && rEnhancedProtection.maRangeList->Contains( rRange)
+ && !rEnhancedProtection.hasPassword(); // Range is editable if no password is assigned.
+ };
+ if (std::any_of(maEnhancedProtection.begin(), maEnhancedProtection.end(), lIsEditable))
+ return true;
+
+ // For a single address, a simple check with single ranges was sufficient.
+ if (rRange.aStart == rRange.aEnd)
+ return false;
+
+ // Test also for cases where rRange is encompassed by a union of two or
+ // more ranges of the list. The original ranges are not necessarily joined.
+ for (const auto& rEnhancedProtection : maEnhancedProtection)
+ {
+ if (!rEnhancedProtection.hasSecurityDescriptor() && rEnhancedProtection.maRangeList.is())
+ {
+ ScRangeList aList( rEnhancedProtection.maRangeList->GetIntersectedRange( rRange));
+ if (aList.size() == 1 && aList[0] == rRange)
+ {
+ // Range is editable if no password is assigned.
+ if (!rEnhancedProtection.hasPassword())
+ return true;
+ }
+ }
+ }
+
+ // Ranges may even be distributed over different protection records, for
+ // example if they are assigned different names, and can have different
+ // passwords. Combine the ones that can be edited.
+ /* TODO: once we handle passwords, remember a successful unlock at
+ * ScEnhancedProtection so we can use that here. */
+ ScRangeList aRangeList;
+ for (const auto& rEnhancedProtection : maEnhancedProtection)
+ {
+ if (!rEnhancedProtection.hasSecurityDescriptor() && rEnhancedProtection.maRangeList.is())
+ {
+ // Ranges are editable if no password is assigned.
+ if (!rEnhancedProtection.hasPassword())
+ {
+ const ScRangeList& rRanges = *rEnhancedProtection.maRangeList;
+ size_t nRanges = rRanges.size();
+ for (size_t i=0; i < nRanges; ++i)
+ {
+ aRangeList.push_back( rRanges[i]);
+ }
+ }
+ }
+ }
+ ScRangeList aResultList( aRangeList.GetIntersectedRange( rRange));
+ return aResultList.size() == 1 && aResultList[0] == rRange;
+}
+
+bool ScTableProtectionImpl::isSelectionEditable( const ScRangeList& rRangeList ) const
+{
+ if (rRangeList.empty())
+ return false;
+
+ for (size_t i=0, nRanges = rRangeList.size(); i < nRanges; ++i)
+ {
+ if (!isBlockEditable( rRangeList[i]))
+ return false;
+ }
+ return true;
+}
+
+ScDocProtection::ScDocProtection() :
+ mpImpl(new ScTableProtectionImpl(static_cast<SCSIZE>(ScDocProtection::NONE)))
+{
+}
+
+ScDocProtection::ScDocProtection(const ScDocProtection& r) :
+ ScPassHashProtectable(),
+ mpImpl(new ScTableProtectionImpl(*r.mpImpl))
+{
+}
+
+ScDocProtection::~ScDocProtection()
+{
+}
+
+bool ScDocProtection::isProtected() const
+{
+ return mpImpl->isProtected();
+}
+
+bool ScDocProtection::isProtectedWithPass() const
+{
+ return mpImpl->isProtectedWithPass();
+}
+
+void ScDocProtection::setProtected(bool bProtected)
+{
+ mpImpl->setProtected(bProtected);
+
+ // Currently Calc doesn't support document protection options. So, let's
+ // assume that when the document is protected, its structure is protected.
+ // We need to do this for Excel export.
+ mpImpl->setOption(ScDocProtection::STRUCTURE, bProtected);
+}
+
+bool ScDocProtection::isPasswordEmpty() const
+{
+ return mpImpl->isPasswordEmpty();
+}
+
+bool ScDocProtection::hasPasswordHash(ScPasswordHash eHash, ScPasswordHash eHash2) const
+{
+ return mpImpl->hasPasswordHash(eHash, eHash2);
+}
+
+void ScDocProtection::setPassword(const OUString& aPassText)
+{
+ mpImpl->setPassword(aPassText);
+}
+
+uno::Sequence<sal_Int8> ScDocProtection::getPasswordHash(ScPasswordHash eHash, ScPasswordHash eHash2) const
+{
+ return mpImpl->getPasswordHash(eHash, eHash2);
+}
+
+const ScOoxPasswordHash& ScDocProtection::getPasswordHash() const
+{
+ return mpImpl->getPasswordHash();
+}
+
+void ScDocProtection::setPasswordHash(
+ const uno::Sequence<sal_Int8>& aPassword, ScPasswordHash eHash, ScPasswordHash eHash2)
+{
+ mpImpl->setPasswordHash(aPassword, eHash, eHash2);
+}
+
+void ScDocProtection::setPasswordHash( const OUString& rAlgorithmName, const OUString& rHashValue,
+ const OUString& rSaltValue, sal_uInt32 nSpinCount )
+{
+ mpImpl->setPasswordHash( rAlgorithmName, rHashValue, rSaltValue, nSpinCount);
+}
+
+bool ScDocProtection::verifyPassword(const OUString& aPassText) const
+{
+ return mpImpl->verifyPassword(aPassText);
+}
+
+bool ScDocProtection::isOptionEnabled(Option eOption) const
+{
+ return mpImpl->isOptionEnabled(eOption);
+}
+
+void ScDocProtection::setOption(Option eOption, bool bEnabled)
+{
+ mpImpl->setOption(eOption, bEnabled);
+}
+
+ScTableProtection::ScTableProtection() :
+ mpImpl(new ScTableProtectionImpl(static_cast<SCSIZE>(ScTableProtection::NONE)))
+{
+ // Set default values for the options.
+ mpImpl->setOption(SELECT_LOCKED_CELLS, true);
+ mpImpl->setOption(SELECT_UNLOCKED_CELLS, true);
+}
+
+ScTableProtection::ScTableProtection(const ScTableProtection& r) :
+ ScPassHashProtectable(),
+ mpImpl(new ScTableProtectionImpl(*r.mpImpl))
+{
+}
+
+ScTableProtection::~ScTableProtection()
+{
+}
+
+bool ScTableProtection::isProtected() const
+{
+ return mpImpl->isProtected();
+}
+
+bool ScTableProtection::isProtectedWithPass() const
+{
+ return mpImpl->isProtectedWithPass();
+}
+
+void ScTableProtection::setProtected(bool bProtected)
+{
+ mpImpl->setProtected(bProtected);
+}
+
+bool ScTableProtection::isPasswordEmpty() const
+{
+ return mpImpl->isPasswordEmpty();
+}
+
+bool ScTableProtection::hasPasswordHash(ScPasswordHash eHash, ScPasswordHash eHash2) const
+{
+ return mpImpl->hasPasswordHash(eHash, eHash2);
+}
+
+void ScTableProtection::setPassword(const OUString& aPassText)
+{
+ mpImpl->setPassword(aPassText);
+}
+
+Sequence<sal_Int8> ScTableProtection::getPasswordHash(ScPasswordHash eHash, ScPasswordHash eHash2) const
+{
+ return mpImpl->getPasswordHash(eHash, eHash2);
+}
+
+const ScOoxPasswordHash& ScTableProtection::getPasswordHash() const
+{
+ return mpImpl->getPasswordHash();
+}
+
+void ScTableProtection::setPasswordHash(
+ const uno::Sequence<sal_Int8>& aPassword, ScPasswordHash eHash, ScPasswordHash eHash2)
+{
+ mpImpl->setPasswordHash(aPassword, eHash, eHash2);
+}
+
+void ScTableProtection::setPasswordHash( const OUString& rAlgorithmName, const OUString& rHashValue,
+ const OUString& rSaltValue, sal_uInt32 nSpinCount )
+{
+ mpImpl->setPasswordHash( rAlgorithmName, rHashValue, rSaltValue, nSpinCount);
+}
+
+bool ScTableProtection::verifyPassword(const OUString& aPassText) const
+{
+ return mpImpl->verifyPassword(aPassText);
+}
+
+bool ScTableProtection::isOptionEnabled(Option eOption) const
+{
+ return mpImpl->isOptionEnabled(eOption);
+}
+
+void ScTableProtection::setOption(Option eOption, bool bEnabled)
+{
+ mpImpl->setOption(eOption, bEnabled);
+}
+
+void ScTableProtection::setEnhancedProtection( ::std::vector< ScEnhancedProtection > && rProt )
+{
+ mpImpl->setEnhancedProtection(std::move(rProt));
+}
+
+const ::std::vector< ScEnhancedProtection > & ScTableProtection::getEnhancedProtection() const
+{
+ return mpImpl->getEnhancedProtection();
+}
+
+bool ScTableProtection::updateReference( UpdateRefMode eMode, const ScDocument& rDoc, const ScRange& rWhere,
+ SCCOL nDx, SCROW nDy, SCTAB nDz )
+{
+ return mpImpl->updateReference( eMode, rDoc, rWhere, nDx, nDy, nDz);
+}
+
+bool ScTableProtection::isBlockEditable( const ScRange& rRange ) const
+{
+ return mpImpl->isBlockEditable( rRange);
+}
+
+bool ScTableProtection::isSelectionEditable( const ScRangeList& rRangeList ) const
+{
+ return mpImpl->isSelectionEditable( rRangeList);
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sc/source/core/data/types.cxx b/sc/source/core/data/types.cxx
new file mode 100644
index 000000000..e5f97c92d
--- /dev/null
+++ b/sc/source/core/data/types.cxx
@@ -0,0 +1,32 @@
+/* -*- 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/.
+ */
+
+#include <types.hxx>
+#include <scmatrix.hxx>
+
+namespace sc {
+
+RangeMatrix::RangeMatrix() :
+ mpMat(nullptr), mnCol1(-1), mnRow1(-1), mnTab1(-1), mnCol2(-1), mnRow2(-1), mnTab2(-1) {}
+
+bool RangeMatrix::isRangeValid() const
+{
+ return mnCol1 >= 0 && mnRow1 >= 0 && mnTab1 >=0 &&
+ mnCol2 >= 0 && mnRow2 >= 0 && mnTab2 >= 0 &&
+ mnCol1 <= mnCol2 && mnRow1 <= mnRow2 && mnTab1 <= mnTab2;
+}
+
+MultiDataCellState::MultiDataCellState() :
+ mnRow1(-1), mnCol1(-1), meState(StateType::Invalid) {}
+MultiDataCellState::MultiDataCellState( StateType eState ) :
+ mnRow1(-1), mnCol1(-1), meState(eState) {}
+
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sc/source/core/data/userdat.cxx b/sc/source/core/data/userdat.cxx
new file mode 100644
index 000000000..9a65caaa1
--- /dev/null
+++ b/sc/source/core/data/userdat.cxx
@@ -0,0 +1,51 @@
+/* -*- 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 <userdat.hxx>
+
+
+ScDrawObjData::ScDrawObjData() :
+ SdrObjUserData( SdrInventor::ScOrSwDraw, SC_UD_OBJDATA ),
+ maStart( ScAddress::INITIALIZE_INVALID ),
+ maEnd( ScAddress::INITIALIZE_INVALID ),
+ meType( DrawingObject ),
+ mbResizeWithCell( false )
+{
+}
+
+std::unique_ptr<SdrObjUserData> ScDrawObjData::Clone( SdrObject* ) const
+{
+ return std::unique_ptr<SdrObjUserData>(new ScDrawObjData( *this ));
+}
+
+ScMacroInfo::ScMacroInfo() :
+ SdrObjUserData( SdrInventor::ScOrSwDraw, SC_UD_MACRODATA )
+{
+}
+
+ScMacroInfo::~ScMacroInfo()
+{
+}
+
+std::unique_ptr<SdrObjUserData> ScMacroInfo::Clone( SdrObject* /*pObj*/ ) const
+{
+ return std::unique_ptr<SdrObjUserData>(new ScMacroInfo( *this ));
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sc/source/core/data/validat.cxx b/sc/source/core/data/validat.cxx
new file mode 100644
index 000000000..688007885
--- /dev/null
+++ b/sc/source/core/data/validat.cxx
@@ -0,0 +1,1087 @@
+/* -*- 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 <config_features.h>
+
+#include <validat.hxx>
+#include <com/sun/star/sheet/TableValidationVisibility.hpp>
+
+#include <sfx2/app.hxx>
+#include <sfx2/objsh.hxx>
+#include <sfx2/viewsh.hxx>
+#include <basic/sbmeth.hxx>
+#include <basic/sbmod.hxx>
+#include <basic/sbstar.hxx>
+#include <basic/sberrors.hxx>
+
+#include <basic/sbx.hxx>
+#include <svl/numformat.hxx>
+#include <svl/sharedstringpool.hxx>
+#include <vcl/svapp.hxx>
+#include <vcl/weld.hxx>
+#include <rtl/math.hxx>
+#include <osl/diagnose.h>
+
+#include <document.hxx>
+#include <formulacell.hxx>
+#include <patattr.hxx>
+#include <globstr.hrc>
+#include <scresid.hxx>
+#include <rangenam.hxx>
+#include <dbdata.hxx>
+#include <typedstrdata.hxx>
+#include <editutil.hxx>
+#include <tokenarray.hxx>
+#include <scmatrix.hxx>
+#include <cellvalue.hxx>
+#include <comphelper/lok.hxx>
+
+#include <math.h>
+#include <memory>
+
+using namespace formula;
+
+// Entries for validation (with only one condition)
+
+ScValidationData::ScValidationData( ScValidationMode eMode, ScConditionMode eOper,
+ const OUString& rExpr1, const OUString& rExpr2,
+ ScDocument& rDocument, const ScAddress& rPos,
+ const OUString& rExprNmsp1, const OUString& rExprNmsp2,
+ FormulaGrammar::Grammar eGrammar1,
+ FormulaGrammar::Grammar eGrammar2 )
+ : ScConditionEntry( eOper, rExpr1, rExpr2, rDocument, rPos, rExprNmsp1,
+ rExprNmsp2, eGrammar1, eGrammar2 )
+ , nKey( 0 )
+ , eDataMode( eMode )
+ , bShowInput(false)
+ , bShowError(false)
+ , eErrorStyle( SC_VALERR_STOP )
+ , mnListType( css::sheet::TableValidationVisibility::UNSORTED )
+{
+}
+
+ScValidationData::ScValidationData( ScValidationMode eMode, ScConditionMode eOper,
+ const ScTokenArray* pArr1, const ScTokenArray* pArr2,
+ ScDocument& rDocument, const ScAddress& rPos )
+ : ScConditionEntry( eOper, pArr1, pArr2, rDocument, rPos )
+ , nKey( 0 )
+ , eDataMode( eMode )
+ , bShowInput(false)
+ , bShowError(false)
+ , eErrorStyle( SC_VALERR_STOP )
+ , mnListType( css::sheet::TableValidationVisibility::UNSORTED )
+{
+}
+
+ScValidationData::ScValidationData( const ScValidationData& r )
+ : ScConditionEntry( r )
+ , nKey( r.nKey )
+ , eDataMode( r.eDataMode )
+ , bShowInput( r.bShowInput )
+ , bShowError( r.bShowError )
+ , eErrorStyle( r.eErrorStyle )
+ , mnListType( r.mnListType )
+ , aInputTitle( r.aInputTitle )
+ , aInputMessage( r.aInputMessage )
+ , aErrorTitle( r.aErrorTitle )
+ , aErrorMessage( r.aErrorMessage )
+{
+ // Formulae copied by RefCount
+}
+
+ScValidationData::ScValidationData( ScDocument& rDocument, const ScValidationData& r )
+ : ScConditionEntry( rDocument, r )
+ , nKey( r.nKey )
+ , eDataMode( r.eDataMode )
+ , bShowInput( r.bShowInput )
+ , bShowError( r.bShowError )
+ , eErrorStyle( r.eErrorStyle )
+ , mnListType( r.mnListType )
+ , aInputTitle( r.aInputTitle )
+ , aInputMessage( r.aInputMessage )
+ , aErrorTitle( r.aErrorTitle )
+ , aErrorMessage( r.aErrorMessage )
+{
+ // Formulae really copied
+}
+
+ScValidationData::~ScValidationData()
+{
+}
+
+bool ScValidationData::IsEmpty() const
+{
+ ScValidationData aDefault( SC_VALID_ANY, ScConditionMode::Equal, "", "", *GetDocument(), ScAddress() );
+ return EqualEntries( aDefault );
+}
+
+bool ScValidationData::EqualEntries( const ScValidationData& r ) const
+{
+ // test same parameters (excluding Key)
+
+ return ScConditionEntry::operator==(r) &&
+ eDataMode == r.eDataMode &&
+ bShowInput == r.bShowInput &&
+ bShowError == r.bShowError &&
+ eErrorStyle == r.eErrorStyle &&
+ mnListType == r.mnListType &&
+ aInputTitle == r.aInputTitle &&
+ aInputMessage == r.aInputMessage &&
+ aErrorTitle == r.aErrorTitle &&
+ aErrorMessage == r.aErrorMessage;
+}
+
+void ScValidationData::ResetInput()
+{
+ bShowInput = false;
+}
+
+void ScValidationData::ResetError()
+{
+ bShowError = false;
+}
+
+void ScValidationData::SetInput( const OUString& rTitle, const OUString& rMsg )
+{
+ bShowInput = true;
+ aInputTitle = rTitle;
+ aInputMessage = rMsg;
+}
+
+void ScValidationData::SetError( const OUString& rTitle, const OUString& rMsg,
+ ScValidErrorStyle eStyle )
+{
+ bShowError = true;
+ eErrorStyle = eStyle;
+ aErrorTitle = rTitle;
+ aErrorMessage = rMsg;
+}
+
+bool ScValidationData::GetErrMsg( OUString& rTitle, OUString& rMsg,
+ ScValidErrorStyle& rStyle ) const
+{
+ rTitle = aErrorTitle;
+ rMsg = aErrorMessage;
+ rStyle = eErrorStyle;
+ return bShowError;
+}
+
+bool ScValidationData::DoScript( const ScAddress& rPos, const OUString& rInput,
+ ScFormulaCell* pCell, weld::Window* pParent ) const
+{
+ ScDocument* pDocument = GetDocument();
+ SfxObjectShell* pDocSh = pDocument->GetDocumentShell();
+ if ( !pDocSh )
+ return false;
+
+ bool bScriptReturnedFalse = false; // default: do not abort
+
+ // 1) entered or calculated value
+ css::uno::Any aParam0(rInput);
+ if ( pCell ) // if cell exists, call interpret
+ {
+ if ( pCell->IsValue() )
+ aParam0 <<= pCell->GetValue();
+ else
+ aParam0 <<= pCell->GetString().getString();
+ }
+
+ // 2) Position of the cell
+ OUString aPosStr(rPos.Format(ScRefFlags::VALID | ScRefFlags::TAB_3D, pDocument, pDocument->GetAddressConvention()));
+
+ // Set up parameters
+ css::uno::Sequence< css::uno::Any > aParams{ aParam0, css::uno::Any(aPosStr) };
+
+ // use link-update flag to prevent closing the document
+ // while the macro is running
+ bool bWasInLinkUpdate = pDocument->IsInLinkUpdate();
+ if ( !bWasInLinkUpdate )
+ pDocument->SetInLinkUpdate( true );
+
+ if ( pCell )
+ pDocument->LockTable( rPos.Tab() );
+
+ css::uno::Any aRet;
+ css::uno::Sequence< sal_Int16 > aOutArgsIndex;
+ css::uno::Sequence< css::uno::Any > aOutArgs;
+
+ ErrCode eRet = pDocSh->CallXScript(
+ aErrorTitle, aParams, aRet, aOutArgsIndex, aOutArgs );
+
+ if ( pCell )
+ pDocument->UnlockTable( rPos.Tab() );
+
+ if ( !bWasInLinkUpdate )
+ pDocument->SetInLinkUpdate( false );
+
+ // Check the return value from the script
+ // The contents of the cell get reset if the script returns false
+ bool bTmp = false;
+ if ( eRet == ERRCODE_NONE &&
+ aRet.getValueType() == cppu::UnoType<bool>::get() &&
+ ( aRet >>= bTmp ) &&
+ !bTmp )
+ {
+ bScriptReturnedFalse = true;
+ }
+
+ if ( eRet == ERRCODE_BASIC_METHOD_NOT_FOUND && !pCell )
+ // Macro not found (only with input)
+ {
+ //TODO: different error message, if found, but not bAllowed ??
+ std::unique_ptr<weld::MessageDialog> xBox(Application::CreateMessageDialog(pParent,
+ VclMessageType::Warning, VclButtonsType::Ok,
+ ScResId(STR_VALID_MACRONOTFOUND)));
+ xBox->run();
+ }
+
+ return bScriptReturnedFalse;
+}
+
+ // true -> abort
+
+bool ScValidationData::DoMacro( const ScAddress& rPos, const OUString& rInput,
+ ScFormulaCell* pCell, weld::Window* pParent ) const
+{
+ if ( SfxApplication::IsXScriptURL( aErrorTitle ) )
+ {
+ return DoScript( rPos, rInput, pCell, pParent );
+ }
+
+ ScDocument* pDocument = GetDocument();
+ SfxObjectShell* pDocSh = pDocument->GetDocumentShell();
+ if ( !pDocSh )
+ return false;
+
+ bool bDone = false;
+ bool bRet = false; // default: do not abort
+
+ // If the Doc was loaded during a Basic-Calls,
+ // the Sbx-object may not be created (?)
+// pDocSh->GetSbxObject();
+
+#if HAVE_FEATURE_SCRIPTING
+ // no security check ahead (only CheckMacroWarn), that happens in CallBasic
+
+ // Function search by their simple name,
+ // then assemble aBasicStr, aMacroStr for SfxObjectShell::CallBasic
+
+ StarBASIC* pRoot = pDocSh->GetBasic();
+ SbxVariable* pVar = pRoot->Find( aErrorTitle, SbxClassType::Method );
+ if (SbMethod* pMethod = dynamic_cast<SbMethod*>(pVar))
+ {
+ SbModule* pModule = pMethod->GetModule();
+ SbxObject* pObject = pModule->GetParent();
+ OUString aMacroStr(
+ pObject->GetName() + "." + pModule->GetName() + "." + pMethod->GetName());
+ OUString aBasicStr;
+
+ // the distinction between document- and app-basic has to be done
+ // by checking the parent (as in ScInterpreter::ScMacro), not by looping
+ // over all open documents, because this may be called from within loading,
+ // when SfxObjectShell::GetFirst/GetNext won't find the document.
+
+ if ( pObject->GetParent() )
+ aBasicStr = pObject->GetParent()->GetName(); // Basic of document
+ else
+ aBasicStr = SfxGetpApp()->GetName(); // Basic of application
+
+ // Parameter for Macro
+ SbxArrayRef refPar = new SbxArray;
+
+ // 1) entered or calculated value
+ OUString aValStr = rInput;
+ double nValue = 0.0;
+ bool bIsValue = false;
+ if ( pCell ) // if cell set, called from interpret
+ {
+ bIsValue = pCell->IsValue();
+ if ( bIsValue )
+ nValue = pCell->GetValue();
+ else
+ aValStr = pCell->GetString().getString();
+ }
+ if ( bIsValue )
+ refPar->Get(1)->PutDouble(nValue);
+ else
+ refPar->Get(1)->PutString(aValStr);
+
+ // 2) Position of the cell
+ OUString aPosStr(rPos.Format(ScRefFlags::VALID | ScRefFlags::TAB_3D, pDocument, pDocument->GetAddressConvention()));
+ refPar->Get(2)->PutString(aPosStr);
+
+ // use link-update flag to prevent closing the document
+ // while the macro is running
+ bool bWasInLinkUpdate = pDocument->IsInLinkUpdate();
+ if ( !bWasInLinkUpdate )
+ pDocument->SetInLinkUpdate( true );
+
+ if ( pCell )
+ pDocument->LockTable( rPos.Tab() );
+ SbxVariableRef refRes = new SbxVariable;
+ ErrCode eRet = pDocSh->CallBasic( aMacroStr, aBasicStr, refPar.get(), refRes.get() );
+ if ( pCell )
+ pDocument->UnlockTable( rPos.Tab() );
+
+ if ( !bWasInLinkUpdate )
+ pDocument->SetInLinkUpdate( false );
+
+ // Interrupt input if Basic macro returns false
+ if ( eRet == ERRCODE_NONE && refRes->GetType() == SbxBOOL && !refRes->GetBool() )
+ bRet = true;
+ bDone = true;
+ }
+#endif
+ if ( !bDone && !pCell ) // Macro not found (only with input)
+ {
+ //TODO: different error message, if found, but not bAllowed ??
+ std::unique_ptr<weld::MessageDialog> xBox(Application::CreateMessageDialog(pParent,
+ VclMessageType::Warning, VclButtonsType::Ok,
+ ScResId(STR_VALID_MACRONOTFOUND)));
+ xBox->run();
+ }
+
+ return bRet;
+}
+
+void ScValidationData::DoCalcError( ScFormulaCell* pCell ) const
+{
+ if ( eErrorStyle == SC_VALERR_MACRO )
+ DoMacro( pCell->aPos, OUString(), pCell, nullptr );
+}
+
+ // true -> abort
+
+bool ScValidationData::DoError(weld::Window* pParent, const OUString& rInput,
+ const ScAddress& rPos) const
+{
+ if ( eErrorStyle == SC_VALERR_MACRO )
+ return DoMacro(rPos, rInput, nullptr, pParent);
+
+ // Output error message
+
+ OUString aTitle = aErrorTitle;
+ if (aTitle.isEmpty())
+ aTitle = ScResId( STR_MSSG_DOSUBTOTALS_0 ); // application title
+ OUString aMessage = aErrorMessage;
+ if (aMessage.isEmpty())
+ aMessage = ScResId( STR_VALID_DEFERROR );
+
+ VclButtonsType eStyle = VclButtonsType::Ok;
+ VclMessageType eType = VclMessageType::Error;
+ switch (eErrorStyle)
+ {
+ case SC_VALERR_INFO:
+ eType = VclMessageType::Info;
+ eStyle = VclButtonsType::OkCancel;
+ break;
+ case SC_VALERR_WARNING:
+ eType = VclMessageType::Warning;
+ eStyle = VclButtonsType::OkCancel;
+ break;
+ default:
+ break;
+ }
+
+ bool bIsMobile = comphelper::LibreOfficeKit::isActive() && SfxViewShell::Current()
+ && SfxViewShell::Current()->isLOKMobilePhone();
+
+ std::unique_ptr<weld::MessageDialog> xBox(Application::CreateMessageDialog(pParent, eType,
+ eStyle, aMessage, bIsMobile));
+ xBox->set_title(aTitle);
+
+ switch (eErrorStyle)
+ {
+ case SC_VALERR_INFO:
+ xBox->set_default_response(RET_OK);
+ break;
+ case SC_VALERR_WARNING:
+ xBox->set_default_response(RET_CANCEL);
+ break;
+ default:
+ break;
+ }
+
+ short nRet = xBox->run();
+
+ return ( eErrorStyle == SC_VALERR_STOP || nRet == RET_CANCEL );
+}
+
+bool ScValidationData::IsDataValidCustom(
+ const OUString& rTest,
+ const ScPatternAttr& rPattern,
+ const ScAddress& rPos,
+ const CustomValidationPrivateAccess& ) const
+{
+ OSL_ENSURE(GetDataMode() == SC_VALID_CUSTOM,
+ "ScValidationData::IsDataValidCustom invoked for a non-custom validation");
+
+ if (rTest.isEmpty()) // check whether empty cells are allowed
+ return IsIgnoreBlank();
+
+ if (rTest[0] == '=') // formulas do not pass the validity test
+ return false;
+
+ SvNumberFormatter* pFormatter = GetDocument()->GetFormatTable();
+
+ // get the value if any
+ sal_uInt32 nFormat = rPattern.GetNumberFormat( pFormatter );
+ double nVal;
+ bool bIsVal = pFormatter->IsNumberFormat( rTest, nFormat, nVal );
+
+ ScRefCellValue aTmpCell;
+ svl::SharedString aSS;
+ if (bIsVal)
+ {
+ aTmpCell.meType = CELLTYPE_VALUE;
+ aTmpCell.mfValue = nVal;
+ }
+ else
+ {
+ aTmpCell.meType = CELLTYPE_STRING;
+ aSS = mpDoc->GetSharedStringPool().intern(rTest);
+ aTmpCell.mpString = &aSS;
+ }
+
+ ScCellValue aOriginalCellValue(ScRefCellValue(*GetDocument(), rPos));
+
+ aTmpCell.commit(*GetDocument(), rPos);
+ bool bRet = IsCellValid(aTmpCell, rPos);
+ aOriginalCellValue.commit(*GetDocument(), rPos);
+
+ return bRet;
+}
+
+/** To test numeric data text length in IsDataValidTextLen().
+
+ If mpFormatter is not set, it is obtained from the document and the format
+ key is determined from the cell position's attribute pattern.
+ */
+struct ScValidationDataIsNumeric
+{
+ SvNumberFormatter* mpFormatter;
+ double mfVal;
+ sal_uInt32 mnFormat;
+
+ ScValidationDataIsNumeric( double fVal, SvNumberFormatter* pFormatter = nullptr, sal_uInt32 nFormat = 0 )
+ : mpFormatter(pFormatter), mfVal(fVal), mnFormat(nFormat)
+ {
+ }
+
+ void init( const ScDocument& rDoc, const ScAddress& rPos )
+ {
+ const ScPatternAttr* pPattern = rDoc.GetPattern( rPos.Col(), rPos.Row(), rPos.Tab());
+ mpFormatter = rDoc.GetFormatTable();
+ mnFormat = pPattern->GetNumberFormat( mpFormatter);
+ }
+};
+
+bool ScValidationData::IsDataValidTextLen( const OUString& rTest, const ScAddress& rPos,
+ ScValidationDataIsNumeric* pDataNumeric ) const
+{
+ sal_Int32 nLen;
+ if (!pDataNumeric)
+ nLen = rTest.getLength();
+ else
+ {
+ if (!pDataNumeric->mpFormatter)
+ pDataNumeric->init( *GetDocument(), rPos);
+
+ // For numeric values use the resulting input line string to
+ // determine length, otherwise an once accepted value maybe could
+ // not be edited again, for example abbreviated dates or leading
+ // zeros or trailing zeros after decimal separator change length.
+ OUString aStr;
+ pDataNumeric->mpFormatter->GetInputLineString( pDataNumeric->mfVal, pDataNumeric->mnFormat, aStr);
+ nLen = aStr.getLength();
+ }
+ ScRefCellValue aTmpCell( static_cast<double>(nLen));
+ return IsCellValid( aTmpCell, rPos);
+}
+
+bool ScValidationData::IsDataValid(
+ const OUString& rTest, const ScPatternAttr& rPattern, const ScAddress& rPos ) const
+{
+ if ( eDataMode == SC_VALID_ANY ) // check if any cell content is allowed
+ return true;
+
+ if (rTest.isEmpty()) // check whether empty cells are allowed
+ return IsIgnoreBlank();
+
+ if (rTest[0] == '=') // formulas do not pass the validity test
+ return false;
+
+ SvNumberFormatter* pFormatter = GetDocument()->GetFormatTable();
+
+ // get the value if any
+ sal_uInt32 nFormat = rPattern.GetNumberFormat( pFormatter );
+ double nVal;
+ bool bIsVal = pFormatter->IsNumberFormat( rTest, nFormat, nVal );
+
+ bool bRet;
+ if (SC_VALID_TEXTLEN == eDataMode)
+ {
+ if (!bIsVal)
+ bRet = IsDataValidTextLen( rTest, rPos, nullptr);
+ else
+ {
+ ScValidationDataIsNumeric aDataNumeric( nVal, pFormatter, nFormat);
+ bRet = IsDataValidTextLen( rTest, rPos, &aDataNumeric);
+ }
+ }
+ else
+ {
+ if (bIsVal)
+ {
+ ScRefCellValue aTmpCell(nVal);
+ bRet = IsDataValid(aTmpCell, rPos);
+ }
+ else
+ {
+ svl::SharedString aSS = mpDoc->GetSharedStringPool().intern(rTest);
+ ScRefCellValue aTmpCell(&aSS);
+ bRet = IsDataValid(aTmpCell, rPos);
+ }
+ }
+
+ return bRet;
+}
+
+bool ScValidationData::IsDataValid( ScRefCellValue& rCell, const ScAddress& rPos ) const
+{
+ if( eDataMode == SC_VALID_LIST )
+ return IsListValid(rCell, rPos);
+
+ if ( eDataMode == SC_VALID_CUSTOM )
+ return IsCellValid(rCell, rPos);
+
+ double nVal = 0.0;
+ OUString aString;
+ bool bIsVal = true;
+
+ switch (rCell.meType)
+ {
+ case CELLTYPE_VALUE:
+ nVal = rCell.mfValue;
+ break;
+ case CELLTYPE_STRING:
+ aString = rCell.mpString->getString();
+ bIsVal = false;
+ break;
+ case CELLTYPE_EDIT:
+ if (rCell.mpEditText)
+ aString = ScEditUtil::GetString(*rCell.mpEditText, GetDocument());
+ bIsVal = false;
+ break;
+ case CELLTYPE_FORMULA:
+ {
+ ScFormulaCell* pFCell = rCell.mpFormula;
+ bIsVal = pFCell->IsValue();
+ if ( bIsVal )
+ nVal = pFCell->GetValue();
+ else
+ aString = pFCell->GetString().getString();
+ }
+ break;
+ default: // Notes, Broadcaster
+ return IsIgnoreBlank(); // as set
+ }
+
+ bool bOk = true;
+ switch (eDataMode)
+ {
+ // SC_VALID_ANY already above
+
+ case SC_VALID_WHOLE:
+ case SC_VALID_DECIMAL:
+ case SC_VALID_DATE: // Date/Time is only formatting
+ case SC_VALID_TIME:
+ bOk = bIsVal;
+ if ( bOk && eDataMode == SC_VALID_WHOLE )
+ bOk = ::rtl::math::approxEqual( nVal, floor(nVal+0.5) ); // integers
+ if ( bOk )
+ bOk = IsCellValid(rCell, rPos);
+ break;
+
+ case SC_VALID_TEXTLEN:
+ if (!bIsVal)
+ bOk = IsDataValidTextLen( aString, rPos, nullptr);
+ else
+ {
+ ScValidationDataIsNumeric aDataNumeric( nVal);
+ bOk = IsDataValidTextLen( aString, rPos, &aDataNumeric);
+ }
+ break;
+
+ default:
+ OSL_FAIL("not yet done");
+ break;
+ }
+
+ return bOk;
+}
+
+namespace {
+
+/** Token array helper. Iterates over all string tokens.
+ @descr The token array must contain separated string tokens only.
+ @param bSkipEmpty true = Ignores string tokens with empty strings. */
+class ScStringTokenIterator
+{
+public:
+ explicit ScStringTokenIterator( const ScTokenArray& rTokArr ) :
+ maIter( rTokArr ), mbOk( true ) {}
+
+ /** Returns the string of the first string token or NULL on error or empty token array. */
+ rtl_uString* First();
+ /** Returns the string of the next string token or NULL on error or end of token array. */
+ rtl_uString* Next();
+
+ /** Returns false, if a wrong token has been found. Does NOT return false on end of token array. */
+ bool Ok() const { return mbOk; }
+
+private:
+ svl::SharedString maCurString; /// Current string.
+ FormulaTokenArrayPlainIterator maIter;
+ bool mbOk; /// true = correct token or end of token array.
+};
+
+rtl_uString* ScStringTokenIterator::First()
+{
+ maIter.Reset();
+ mbOk = true;
+ return Next();
+}
+
+rtl_uString* ScStringTokenIterator::Next()
+{
+ if( !mbOk )
+ return nullptr;
+
+ // seek to next non-separator token
+ const FormulaToken* pToken = maIter.NextNoSpaces();
+ while( pToken && (pToken->GetOpCode() == ocSep) )
+ pToken = maIter.NextNoSpaces();
+
+ mbOk = !pToken || (pToken->GetType() == formula::svString);
+
+ maCurString = svl::SharedString(); // start with invalid string.
+ if (mbOk && pToken)
+ maCurString = pToken->GetString();
+
+ // string found but empty -> get next token; otherwise return it
+ return (maCurString.isValid() && maCurString.isEmpty()) ? Next() : maCurString.getData();
+}
+
+/** Returns the number format of the passed cell, or the standard format. */
+sal_uLong lclGetCellFormat( const ScDocument& rDoc, const ScAddress& rPos )
+{
+ const ScPatternAttr* pPattern = rDoc.GetPattern( rPos.Col(), rPos.Row(), rPos.Tab() );
+ if( !pPattern )
+ pPattern = rDoc.GetDefPattern();
+ return pPattern->GetNumberFormat( rDoc.GetFormatTable() );
+}
+
+} // namespace
+
+bool ScValidationData::HasSelectionList() const
+{
+ return (eDataMode == SC_VALID_LIST) && (mnListType != css::sheet::TableValidationVisibility::INVISIBLE);
+}
+
+bool ScValidationData::GetSelectionFromFormula(
+ std::vector<ScTypedStrData>* pStrings, ScRefCellValue& rCell, const ScAddress& rPos,
+ const ScTokenArray& rTokArr, int& rMatch) const
+{
+ bool bOk = true;
+
+ // pDoc is private in condition, use an accessor and a long winded name.
+ ScDocument* pDocument = GetDocument();
+ if( nullptr == pDocument )
+ return false;
+
+ ScFormulaCell aValidationSrc(
+ *pDocument, rPos, rTokArr, formula::FormulaGrammar::GRAM_DEFAULT, ScMatrixMode::Formula);
+
+ // Make sure the formula gets interpreted and a result is delivered,
+ // regardless of the AutoCalc setting.
+ aValidationSrc.Interpret();
+
+ ScMatrixRef xMatRef;
+ const ScMatrix *pValues = aValidationSrc.GetMatrix();
+ if (!pValues)
+ {
+ // The somewhat nasty case of either an error occurred, or the
+ // dereferenced value of a single cell reference or an immediate result
+ // is stored as a single value.
+
+ // Use an interim matrix to create the TypedStrData below.
+ xMatRef = new ScMatrix(1, 1, 0.0);
+
+ FormulaError nErrCode = aValidationSrc.GetErrCode();
+ if (nErrCode != FormulaError::NONE)
+ {
+ /* TODO : to use later in an alert box?
+ * OUString rStrResult = "...";
+ * rStrResult += ScGlobal::GetLongErrorString(nErrCode);
+ */
+
+ xMatRef->PutError( nErrCode, 0, 0);
+ bOk = false;
+ }
+ else if (aValidationSrc.IsValue())
+ xMatRef->PutDouble( aValidationSrc.GetValue(), 0);
+ else
+ {
+ svl::SharedString aStr = aValidationSrc.GetString();
+ xMatRef->PutString(aStr, 0);
+ }
+
+ pValues = xMatRef.get();
+ }
+
+ // which index matched. We will want it eventually to pre-select that item.
+ rMatch = -1;
+
+ SvNumberFormatter* pFormatter = GetDocument()->GetFormatTable();
+
+ SCSIZE nCol, nRow, nCols, nRows, n = 0;
+ pValues->GetDimensions( nCols, nRows );
+
+ bool bRef = false;
+ ScRange aRange;
+
+ ScTokenArray* pArr = const_cast<ScTokenArray*>(&rTokArr);
+ if (pArr->GetLen() == 1)
+ {
+ formula::FormulaTokenArrayPlainIterator aIter(*pArr);
+ formula::FormulaToken* t = aIter.GetNextReferenceOrName();
+ if (t)
+ {
+ OpCode eOpCode = t->GetOpCode();
+ if (eOpCode == ocDBArea || eOpCode == ocTableRef)
+ {
+ if (const ScDBData* pDBData = pDocument->GetDBCollection()->getNamedDBs().findByIndex(t->GetIndex()))
+ {
+ pDBData->GetArea(aRange);
+ bRef = true;
+ }
+ }
+ else if (eOpCode == ocName)
+ {
+ const ScRangeData* pName = pDocument->FindRangeNameBySheetAndIndex( t->GetSheet(), t->GetIndex());
+ if (pName && pName->IsReference(aRange))
+ {
+ bRef = true;
+ }
+ }
+ else if (t->GetType() != svIndex)
+ {
+ if (pArr->IsValidReference(aRange, rPos))
+ {
+ bRef = true;
+ }
+ }
+ }
+ }
+
+ bool bHaveEmpty = false;
+ svl::SharedStringPool& rSPool = pDocument->GetSharedStringPool();
+
+ /* XL artificially limits things to a single col or row in the UI but does
+ * not list the constraint in MOOXml. If a defined name or INDIRECT
+ * resulting in 1D is entered in the UI and the definition later modified
+ * to 2D, it is evaluated fine and also stored and loaded. Lets get ahead
+ * of the curve and support 2d. In XL, values are listed row-wise, do the
+ * same. */
+ for( nRow = 0; nRow < nRows ; nRow++ )
+ {
+ for( nCol = 0; nCol < nCols ; nCol++ )
+ {
+ ScTokenArray aCondTokArr(*pDocument);
+ std::unique_ptr<ScTypedStrData> pEntry;
+ OUString aValStr;
+ ScMatrixValue nMatVal = pValues->Get( nCol, nRow);
+
+ // strings and empties
+ if( ScMatrix::IsNonValueType( nMatVal.nType ) )
+ {
+ aValStr = nMatVal.GetString().getString();
+
+ // Do not add multiple empty strings to the validation list,
+ // especially not if they'd bloat the tail with a million empty
+ // entries for a column range, fdo#61520
+ if (aValStr.isEmpty())
+ {
+ if (bHaveEmpty)
+ continue;
+ bHaveEmpty = true;
+ }
+
+ if( nullptr != pStrings )
+ pEntry.reset(new ScTypedStrData(aValStr, 0.0, 0.0, ScTypedStrData::Standard));
+
+ if (!rCell.isEmpty() && rMatch < 0)
+ aCondTokArr.AddString(rSPool.intern(aValStr));
+ }
+ else
+ {
+ FormulaError nErr = nMatVal.GetError();
+
+ if( FormulaError::NONE != nErr )
+ {
+ aValStr = ScGlobal::GetErrorString( nErr );
+ }
+ else
+ {
+ // FIXME FIXME FIXME
+ // Feature regression. Date formats are lost passing through the matrix
+ //pFormatter->GetInputLineString( pMatVal->fVal, 0, aValStr );
+ //For external reference and a formula that results in an area or array, date formats are still lost.
+ if ( bRef )
+ {
+ aValStr = pDocument->GetInputString(static_cast<SCCOL>(nCol+aRange.aStart.Col()),
+ static_cast<SCROW>(nRow+aRange.aStart.Row()), aRange.aStart.Tab());
+ }
+ else
+ {
+ pFormatter->GetInputLineString( nMatVal.fVal, 0, aValStr );
+ }
+ }
+
+ if (!rCell.isEmpty() && rMatch < 0)
+ {
+ // I am not sure errors will work here, but a user can no
+ // manually enter an error yet so the point is somewhat moot.
+ aCondTokArr.AddDouble( nMatVal.fVal );
+ }
+ if( nullptr != pStrings )
+ pEntry.reset(new ScTypedStrData(aValStr, nMatVal.fVal, nMatVal.fVal, ScTypedStrData::Value));
+ }
+
+ if (rMatch < 0 && !rCell.isEmpty() && IsEqualToTokenArray(rCell, rPos, aCondTokArr))
+ {
+ rMatch = n;
+ // short circuit on the first match if not filling the list
+ if( nullptr == pStrings )
+ return true;
+ }
+
+ if( pEntry )
+ {
+ assert(pStrings);
+ pStrings->push_back(*pEntry);
+ n++;
+ }
+ }
+ }
+
+ // In case of no match needed and an error occurred, return that error
+ // entry as valid instead of silently failing.
+ return bOk || rCell.isEmpty();
+}
+
+bool ScValidationData::FillSelectionList(std::vector<ScTypedStrData>& rStrColl, const ScAddress& rPos) const
+{
+ bool bOk = false;
+
+ if( HasSelectionList() )
+ {
+ std::unique_ptr<ScTokenArray> pTokArr( CreateFlatCopiedTokenArray(0) );
+
+ // *** try if formula is a string list ***
+
+ sal_uInt32 nFormat = lclGetCellFormat( *GetDocument(), rPos );
+ ScStringTokenIterator aIt( *pTokArr );
+ for (rtl_uString* pString = aIt.First(); pString && aIt.Ok(); pString = aIt.Next())
+ {
+ double fValue;
+ OUString aStr(pString);
+ bool bIsValue = GetDocument()->GetFormatTable()->IsNumberFormat(aStr, nFormat, fValue);
+ rStrColl.emplace_back(
+ aStr, fValue, fValue, bIsValue ? ScTypedStrData::Value : ScTypedStrData::Standard);
+ }
+ bOk = aIt.Ok();
+
+ // *** if not a string list, try if formula results in a cell range or
+ // anything else we recognize as valid ***
+
+ if (!bOk)
+ {
+ int nMatch;
+ ScRefCellValue aEmptyCell;
+ bOk = GetSelectionFromFormula(&rStrColl, aEmptyCell, rPos, *pTokArr, nMatch);
+ }
+ }
+
+ return bOk;
+}
+
+bool ScValidationData::IsEqualToTokenArray( ScRefCellValue& rCell, const ScAddress& rPos, const ScTokenArray& rTokArr ) const
+{
+ // create a condition entry that tests on equality and set the passed token array
+ ScConditionEntry aCondEntry( ScConditionMode::Equal, &rTokArr, nullptr, *GetDocument(), rPos );
+ return aCondEntry.IsCellValid(rCell, rPos);
+}
+
+bool ScValidationData::IsListValid( ScRefCellValue& rCell, const ScAddress& rPos ) const
+{
+ bool bIsValid = false;
+
+ /* Compare input cell with all supported tokens from the formula.
+ Currently a formula may contain:
+ 1) A list of strings (at least one string).
+ 2) A single cell or range reference.
+ 3) A single defined name (must contain a cell/range reference, another
+ name, or DB range, or a formula resulting in a cell/range reference
+ or matrix/array).
+ 4) A single database range.
+ 5) A formula resulting in a cell/range reference or matrix/array.
+ */
+
+ std::unique_ptr< ScTokenArray > pTokArr( CreateFlatCopiedTokenArray( 0 ) );
+
+ // *** try if formula is a string list ***
+
+ svl::SharedStringPool& rSPool = GetDocument()->GetSharedStringPool();
+ sal_uInt32 nFormat = lclGetCellFormat( *GetDocument(), rPos );
+ ScStringTokenIterator aIt( *pTokArr );
+ for (rtl_uString* pString = aIt.First(); pString && aIt.Ok(); pString = aIt.Next())
+ {
+ /* Do not break the loop, if a valid string has been found.
+ This is to find invalid tokens following in the formula. */
+ if( !bIsValid )
+ {
+ // create a formula containing a single string or number
+ ScTokenArray aCondTokArr(*GetDocument());
+ double fValue;
+ OUString aStr(pString);
+ if (GetDocument()->GetFormatTable()->IsNumberFormat(aStr, nFormat, fValue))
+ aCondTokArr.AddDouble( fValue );
+ else
+ aCondTokArr.AddString(rSPool.intern(aStr));
+
+ bIsValid = IsEqualToTokenArray(rCell, rPos, aCondTokArr);
+ }
+ }
+
+ if( !aIt.Ok() )
+ bIsValid = false;
+
+ // *** if not a string list, try if formula results in a cell range or
+ // anything else we recognize as valid ***
+
+ if (!bIsValid)
+ {
+ int nMatch;
+ bIsValid = GetSelectionFromFormula(nullptr, rCell, rPos, *pTokArr, nMatch);
+ bIsValid = bIsValid && nMatch >= 0;
+ }
+
+ return bIsValid;
+}
+
+ScValidationDataList::ScValidationDataList(const ScValidationDataList& rList)
+{
+ // for Ref-Undo - real copy with new tokens!
+
+ for (const auto& rxItem : rList)
+ {
+ InsertNew( std::unique_ptr<ScValidationData>(rxItem->Clone()) );
+ }
+
+ //TODO: faster insert for sorted entries from rList ???
+}
+
+ScValidationDataList::ScValidationDataList(ScDocument& rNewDoc,
+ const ScValidationDataList& rList)
+{
+ // for new documents - real copy with new tokens!
+
+ for (const auto& rxItem : rList)
+ {
+ InsertNew( std::unique_ptr<ScValidationData>(rxItem->Clone(&rNewDoc)) );
+ }
+
+ //TODO: faster insert for sorted entries from rList ???
+}
+
+ScValidationData* ScValidationDataList::GetData( sal_uInt32 nKey )
+{
+ //TODO: binary search
+
+ for( iterator it = begin(); it != end(); ++it )
+ if( (*it)->GetKey() == nKey )
+ return it->get();
+
+ OSL_FAIL("ScValidationDataList: Entry not found");
+ return nullptr;
+}
+
+void ScValidationDataList::CompileXML()
+{
+ for( iterator it = begin(); it != end(); ++it )
+ (*it)->CompileXML();
+}
+
+void ScValidationDataList::UpdateReference( sc::RefUpdateContext& rCxt )
+{
+ for( iterator it = begin(); it != end(); ++it )
+ (*it)->UpdateReference(rCxt);
+}
+
+void ScValidationDataList::UpdateInsertTab( sc::RefUpdateInsertTabContext& rCxt )
+{
+ for (iterator it = begin(); it != end(); ++it)
+ (*it)->UpdateInsertTab(rCxt);
+}
+
+void ScValidationDataList::UpdateDeleteTab( sc::RefUpdateDeleteTabContext& rCxt )
+{
+ for (iterator it = begin(); it != end(); ++it)
+ (*it)->UpdateDeleteTab(rCxt);
+}
+
+void ScValidationDataList::UpdateMoveTab( sc::RefUpdateMoveTabContext& rCxt )
+{
+ for (iterator it = begin(); it != end(); ++it)
+ (*it)->UpdateMoveTab(rCxt);
+}
+
+ScValidationDataList::iterator ScValidationDataList::begin()
+{
+ return maData.begin();
+}
+
+ScValidationDataList::const_iterator ScValidationDataList::begin() const
+{
+ return maData.begin();
+}
+
+ScValidationDataList::iterator ScValidationDataList::end()
+{
+ return maData.end();
+}
+
+ScValidationDataList::const_iterator ScValidationDataList::end() const
+{
+ return maData.end();
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */