summaryrefslogtreecommitdiffstats
path: root/sc/source/core/data/table3.cxx
diff options
context:
space:
mode:
Diffstat (limited to 'sc/source/core/data/table3.cxx')
-rw-r--r--sc/source/core/data/table3.cxx3099
1 files changed, 3099 insertions, 0 deletions
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: */