diff options
Diffstat (limited to 'sc/source/core/data/dociter.cxx')
-rw-r--r-- | sc/source/core/data/dociter.cxx | 1779 |
1 files changed, 1779 insertions, 0 deletions
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: */ |