diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 09:06:44 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 09:06:44 +0000 |
commit | ed5640d8b587fbcfed7dd7967f3de04b37a76f26 (patch) | |
tree | 7a5f7c6c9d02226d7471cb3cc8fbbf631b415303 /sc/source/core/tool/dbdata.cxx | |
parent | Initial commit. (diff) | |
download | libreoffice-upstream.tar.xz libreoffice-upstream.zip |
Adding upstream version 4:7.4.7.upstream/4%7.4.7upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'sc/source/core/tool/dbdata.cxx')
-rw-r--r-- | sc/source/core/tool/dbdata.cxx | 1637 |
1 files changed, 1637 insertions, 0 deletions
diff --git a/sc/source/core/tool/dbdata.cxx b/sc/source/core/tool/dbdata.cxx new file mode 100644 index 000000000..0e36eb960 --- /dev/null +++ b/sc/source/core/tool/dbdata.cxx @@ -0,0 +1,1637 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <sal/config.h> +#include <sal/log.hxx> + +#include <o3tl/safeint.hxx> +#include <unotools/transliterationwrapper.hxx> +#include <unotools/charclass.hxx> + +#include <dbdata.hxx> +#include <globalnames.hxx> +#include <refupdat.hxx> +#include <document.hxx> +#include <queryparam.hxx> +#include <queryentry.hxx> +#include <globstr.hrc> +#include <scresid.hxx> +#include <subtotalparam.hxx> +#include <sortparam.hxx> +#include <dociter.hxx> +#include <brdcst.hxx> + +#include <comphelper/stl_types.hxx> + +#include <memory> +#include <utility> + +using ::std::unique_ptr; +using ::std::for_each; +using ::std::find_if; +using ::std::remove_if; +using ::std::pair; + +bool ScDBData::less::operator() (const std::unique_ptr<ScDBData>& left, const std::unique_ptr<ScDBData>& right) const +{ + return ScGlobal::GetTransliteration().compareString(left->GetUpperName(), right->GetUpperName()) < 0; +} + +ScDBData::ScDBData( const OUString& rName, + SCTAB nTab, + SCCOL nCol1, SCROW nRow1, SCCOL nCol2, SCROW nRow2, + bool bByR, bool bHasH, bool bTotals) : + // Listeners are to be setup by the "parent" container. + mpSortParam(new ScSortParam), + mpQueryParam(new ScQueryParam), + mpSubTotal(new ScSubTotalParam), + mpImportParam(new ScImportParam), + mpContainer (nullptr), + aName (rName), + aUpper (rName), + nTable (nTab), + nStartCol (nCol1), + nStartRow (nRow1), + nEndCol (nCol2), + nEndRow (nRow2), + bByRow (bByR), + bHasHeader (bHasH), + bHasTotals (bTotals), + bDoSize (false), + bKeepFmt (false), + bStripData (false), + bIsAdvanced (false), + bDBSelection(false), + nIndex (0), + bAutoFilter (false), + bModified (false), + mbTableColumnNamesDirty(true), + nFilteredRowCount(SCSIZE_MAX) +{ + aUpper = ScGlobal::getCharClass().uppercase(aUpper); +} + +ScDBData::ScDBData( const ScDBData& rData ) : + // Listeners are to be setup by the "parent" container. + SvtListener (), + ScRefreshTimer ( rData ), + mpSortParam(new ScSortParam(*rData.mpSortParam)), + mpQueryParam(new ScQueryParam(*rData.mpQueryParam)), + mpSubTotal(new ScSubTotalParam(*rData.mpSubTotal)), + mpImportParam(new ScImportParam(*rData.mpImportParam)), + mpContainer (nullptr), + aName (rData.aName), + aUpper (rData.aUpper), + nTable (rData.nTable), + nStartCol (rData.nStartCol), + nStartRow (rData.nStartRow), + nEndCol (rData.nEndCol), + nEndRow (rData.nEndRow), + bByRow (rData.bByRow), + bHasHeader (rData.bHasHeader), + bHasTotals (rData.bHasTotals), + bDoSize (rData.bDoSize), + bKeepFmt (rData.bKeepFmt), + bStripData (rData.bStripData), + bIsAdvanced (rData.bIsAdvanced), + aAdvSource (rData.aAdvSource), + bDBSelection (rData.bDBSelection), + nIndex (rData.nIndex), + bAutoFilter (rData.bAutoFilter), + bModified (rData.bModified), + maTableColumnNames (rData.maTableColumnNames), + mbTableColumnNamesDirty(rData.mbTableColumnNamesDirty), + nFilteredRowCount (rData.nFilteredRowCount) +{ +} + +ScDBData::ScDBData( const OUString& rName, const ScDBData& rData ) : + // Listeners are to be setup by the "parent" container. + SvtListener (), + ScRefreshTimer ( rData ), + mpSortParam(new ScSortParam(*rData.mpSortParam)), + mpQueryParam(new ScQueryParam(*rData.mpQueryParam)), + mpSubTotal(new ScSubTotalParam(*rData.mpSubTotal)), + mpImportParam(new ScImportParam(*rData.mpImportParam)), + mpContainer (nullptr), + aName (rName), + aUpper (rName), + nTable (rData.nTable), + nStartCol (rData.nStartCol), + nStartRow (rData.nStartRow), + nEndCol (rData.nEndCol), + nEndRow (rData.nEndRow), + bByRow (rData.bByRow), + bHasHeader (rData.bHasHeader), + bHasTotals (rData.bHasTotals), + bDoSize (rData.bDoSize), + bKeepFmt (rData.bKeepFmt), + bStripData (rData.bStripData), + bIsAdvanced (rData.bIsAdvanced), + aAdvSource (rData.aAdvSource), + bDBSelection (rData.bDBSelection), + nIndex (rData.nIndex), + bAutoFilter (rData.bAutoFilter), + bModified (rData.bModified), + maTableColumnNames (rData.maTableColumnNames), + mbTableColumnNamesDirty (rData.mbTableColumnNamesDirty), + nFilteredRowCount (rData.nFilteredRowCount) +{ + aUpper = ScGlobal::getCharClass().uppercase(aUpper); +} + +ScDBData& ScDBData::operator= (const ScDBData& rData) +{ + if (this != &rData) + { + // Don't modify the name. The name is not mutable as it is used as a key + // in the container to keep the db ranges sorted by the name. + + bool bHeaderRangeDiffers = (nTable != rData.nTable || nStartCol != rData.nStartCol || + nEndCol != rData.nEndCol || nStartRow != rData.nStartRow); + bool bNeedsListening = ((bHasHeader && bHeaderRangeDiffers) || (!bHasHeader && rData.bHasHeader)); + if (bHasHeader && (!rData.bHasHeader || bHeaderRangeDiffers)) + { + EndTableColumnNamesListener(); + } + ScRefreshTimer::operator=( rData ); + mpSortParam.reset(new ScSortParam(*rData.mpSortParam)); + mpQueryParam.reset(new ScQueryParam(*rData.mpQueryParam)); + mpSubTotal.reset(new ScSubTotalParam(*rData.mpSubTotal)); + mpImportParam.reset(new ScImportParam(*rData.mpImportParam)); + // Keep mpContainer. + nTable = rData.nTable; + nStartCol = rData.nStartCol; + nStartRow = rData.nStartRow; + nEndCol = rData.nEndCol; + nEndRow = rData.nEndRow; + bByRow = rData.bByRow; + bHasHeader = rData.bHasHeader; + bHasTotals = rData.bHasTotals; + bDoSize = rData.bDoSize; + bKeepFmt = rData.bKeepFmt; + bStripData = rData.bStripData; + bIsAdvanced = rData.bIsAdvanced; + aAdvSource = rData.aAdvSource; + bDBSelection = rData.bDBSelection; + nIndex = rData.nIndex; + bAutoFilter = rData.bAutoFilter; + nFilteredRowCount = rData.nFilteredRowCount; + + if (bHeaderRangeDiffers) + InvalidateTableColumnNames( true); + else + { + maTableColumnNames = rData.maTableColumnNames; + mbTableColumnNamesDirty = rData.mbTableColumnNamesDirty; + } + + if (bNeedsListening) + StartTableColumnNamesListener(); + } + return *this; +} + +bool ScDBData::operator== (const ScDBData& rData) const +{ + // Data that is not in sort or query params. + + if ( nTable != rData.nTable || + bDoSize != rData.bDoSize || + bKeepFmt != rData.bKeepFmt || + bIsAdvanced!= rData.bIsAdvanced|| + bStripData != rData.bStripData || +// SAB: I think this should be here, but I don't want to break something +// bAutoFilter!= rData.bAutoFilter|| + ScRefreshTimer::operator!=( rData ) + ) + return false; + + if ( bIsAdvanced && aAdvSource != rData.aAdvSource ) + return false; + + ScSortParam aSort1, aSort2; + GetSortParam(aSort1); + rData.GetSortParam(aSort2); + if (!(aSort1 == aSort2)) + return false; + + ScQueryParam aQuery1, aQuery2; + GetQueryParam(aQuery1); + rData.GetQueryParam(aQuery2); + if (!(aQuery1 == aQuery2)) + return false; + + ScSubTotalParam aSubTotal1, aSubTotal2; + GetSubTotalParam(aSubTotal1); + rData.GetSubTotalParam(aSubTotal2); + if (!(aSubTotal1 == aSubTotal2)) + return false; + + ScImportParam aImport1, aImport2; + GetImportParam(aImport1); + rData.GetImportParam(aImport2); + return aImport1 == aImport2; +} + +ScDBData::~ScDBData() +{ + StopRefreshTimer(); +} + +OUString ScDBData::GetSourceString() const +{ + OUStringBuffer aBuf; + if (mpImportParam->bImport) + { + aBuf.append(mpImportParam->aDBName); + aBuf.append('/'); + aBuf.append(mpImportParam->aStatement); + } + return aBuf.makeStringAndClear(); +} + +OUString ScDBData::GetOperations() const +{ + OUStringBuffer aBuf; + if (mpQueryParam->GetEntryCount()) + { + const ScQueryEntry& rEntry = mpQueryParam->GetEntry(0); + if (rEntry.bDoQuery) + aBuf.append(ScResId(STR_OPERATION_FILTER)); + } + + if (mpSortParam->maKeyState[0].bDoSort) + { + if (!aBuf.isEmpty()) + aBuf.append(", "); + aBuf.append(ScResId(STR_OPERATION_SORT)); + } + + if (mpSubTotal->bGroupActive[0] && !mpSubTotal->bRemoveOnly) + { + if (!aBuf.isEmpty()) + aBuf.append(", "); + aBuf.append(ScResId(STR_OPERATION_SUBTOTAL)); + } + + if (aBuf.isEmpty()) + aBuf.append(ScResId(STR_OPERATION_NONE)); + + return aBuf.makeStringAndClear(); +} + +void ScDBData::GetArea(SCTAB& rTab, SCCOL& rCol1, SCROW& rRow1, SCCOL& rCol2, SCROW& rRow2) const +{ + rTab = nTable; + rCol1 = nStartCol; + rRow1 = nStartRow; + rCol2 = nEndCol; + rRow2 = nEndRow; +} + +void ScDBData::GetArea(ScRange& rRange) const +{ + SCROW nNewEndRow = nEndRow; + rRange = ScRange( nStartCol, nStartRow, nTable, nEndCol, nNewEndRow, nTable ); +} + +ScRange ScDBData::GetHeaderArea() const +{ + if (HasHeader()) + return ScRange( nStartCol, nStartRow, nTable, nEndCol, nStartRow, nTable); + return ScRange( ScAddress::INITIALIZE_INVALID); +} + +void ScDBData::SetArea(SCTAB nTab, SCCOL nCol1, SCROW nRow1, SCCOL nCol2, SCROW nRow2) +{ + bool bHeaderRangeChange = (nTab != nTable || nCol1 != nStartCol || nCol2 != nEndCol || nRow1 != nStartRow); + if (bHeaderRangeChange) + EndTableColumnNamesListener(); + + nTable = nTab; + nStartCol = nCol1; + nStartRow = nRow1; + nEndCol = nCol2; + nEndRow = nRow2; + + if (bHeaderRangeChange) + { + SAL_WARN_IF( !maTableColumnNames.empty(), "sc.core", "ScDBData::SetArea - invalidating column names/offsets"); + // Invalidate *after* new area has been set above to add the proper + // header range to dirty list. + InvalidateTableColumnNames( true); + StartTableColumnNamesListener(); + } +} + +void ScDBData::MoveTo(SCTAB nTab, SCCOL nCol1, SCROW nRow1, SCCOL nCol2, SCROW nRow2, + SCCOL nUpdateCol) +{ + tools::Long nDifX = static_cast<tools::Long>(nCol1) - static_cast<tools::Long>(nStartCol); + tools::Long nDifY = static_cast<tools::Long>(nRow1) - static_cast<tools::Long>(nStartRow); + + tools::Long nSortDif = bByRow ? nDifX : nDifY; + tools::Long nSortEnd = bByRow ? static_cast<tools::Long>(nCol2) : static_cast<tools::Long>(nRow2); + + for (sal_uInt16 i=0; i<mpSortParam->GetSortKeyCount(); i++) + { + mpSortParam->maKeyState[i].nField += nSortDif; + if (mpSortParam->maKeyState[i].nField > nSortEnd) + { + mpSortParam->maKeyState[i].nField = 0; + mpSortParam->maKeyState[i].bDoSort = false; + } + } + + SCSIZE nCount = mpQueryParam->GetEntryCount(); + for (SCSIZE i = 0; i < nCount; ++i) + { + ScQueryEntry& rEntry = mpQueryParam->GetEntry(i); + rEntry.nField += nDifX; + + // tdf#48025, tdf#141946: update the column index of the filter criteria, + // when the deleted/inserted columns are inside the data range + if (nUpdateCol != -1) + { + nUpdateCol += nDifX; + tools::Long nDifX2 + = static_cast<tools::Long>(nCol2) - static_cast<tools::Long>(nEndCol); + if (rEntry.nField >= nUpdateCol) + rEntry.nField += nDifX2; + else if (rEntry.nField >= nUpdateCol + nDifX2) + rEntry.Clear(); + } + + if (rEntry.nField > nCol2) + { + rEntry.nField = 0; + rEntry.bDoQuery = false; + } + } + for (sal_uInt16 i=0; i<MAXSUBTOTAL; i++) + { + mpSubTotal->nField[i] = sal::static_int_cast<SCCOL>( mpSubTotal->nField[i] + nDifX ); + if (mpSubTotal->nField[i] > nCol2) + { + mpSubTotal->nField[i] = 0; + mpSubTotal->bGroupActive[i] = false; + } + } + + SetArea( nTab, nCol1, nRow1, nCol2, nRow2 ); +} + +void ScDBData::GetSortParam( ScSortParam& rSortParam ) const +{ + rSortParam = *mpSortParam; + rSortParam.nCol1 = nStartCol; + rSortParam.nRow1 = nStartRow; + rSortParam.nCol2 = nEndCol; + rSortParam.nRow2 = nEndRow; + rSortParam.bByRow = bByRow; + rSortParam.bHasHeader = bHasHeader; + /* TODO: add Totals to ScSortParam? */ +} + +void ScDBData::SetSortParam( const ScSortParam& rSortParam ) +{ + mpSortParam.reset(new ScSortParam(rSortParam)); + bByRow = rSortParam.bByRow; +} + +void ScDBData::UpdateFromSortParam( const ScSortParam& rSortParam ) +{ + bHasHeader = rSortParam.bHasHeader; +} + +void ScDBData::GetQueryParam( ScQueryParam& rQueryParam ) const +{ + rQueryParam = *mpQueryParam; + rQueryParam.nCol1 = nStartCol; + rQueryParam.nRow1 = nStartRow; + rQueryParam.nCol2 = nEndCol; + rQueryParam.nRow2 = nEndRow; + rQueryParam.nTab = nTable; + rQueryParam.bByRow = bByRow; + rQueryParam.bHasHeader = bHasHeader; + /* TODO: add Totals to ScQueryParam? */ +} + +void ScDBData::SetQueryParam(const ScQueryParam& rQueryParam) +{ + mpQueryParam.reset(new ScQueryParam(rQueryParam)); + + // set bIsAdvanced to false for everything that is not from the + // advanced filter dialog + bIsAdvanced = false; +} + +void ScDBData::SetAdvancedQuerySource(const ScRange* pSource) +{ + if (pSource) + { + aAdvSource = *pSource; + bIsAdvanced = true; + } + else + bIsAdvanced = false; +} + +bool ScDBData::GetAdvancedQuerySource(ScRange& rSource) const +{ + rSource = aAdvSource; + return bIsAdvanced; +} + +void ScDBData::GetSubTotalParam(ScSubTotalParam& rSubTotalParam) const +{ + rSubTotalParam = *mpSubTotal; + + // Share the data range with the parent db data. The range in the subtotal + // param struct is not used. + rSubTotalParam.nCol1 = nStartCol; + rSubTotalParam.nRow1 = nStartRow; + rSubTotalParam.nCol2 = nEndCol; + rSubTotalParam.nRow2 = nEndRow; +} + +void ScDBData::SetSubTotalParam(const ScSubTotalParam& rSubTotalParam) +{ + mpSubTotal.reset(new ScSubTotalParam(rSubTotalParam)); +} + +void ScDBData::GetImportParam(ScImportParam& rImportParam) const +{ + rImportParam = *mpImportParam; + // set the range. + rImportParam.nCol1 = nStartCol; + rImportParam.nRow1 = nStartRow; + rImportParam.nCol2 = nEndCol; + rImportParam.nRow2 = nEndRow; +} + +void ScDBData::SetImportParam(const ScImportParam& rImportParam) +{ + // the range is ignored. + mpImportParam.reset(new ScImportParam(rImportParam)); +} + +bool ScDBData::IsDBAtCursor(SCCOL nCol, SCROW nRow, SCTAB nTab, ScDBDataPortion ePortion) const +{ + if (nTab == nTable) + { + switch (ePortion) + { + case ScDBDataPortion::TOP_LEFT: + return nCol == nStartCol && nRow == nStartRow; + case ScDBDataPortion::AREA: + return nCol >= nStartCol && nCol <= nEndCol && nRow >= nStartRow && nRow <= nEndRow; + } + } + + return false; +} + +bool ScDBData::IsDBAtArea(SCTAB nTab, SCCOL nCol1, SCROW nRow1, SCCOL nCol2, SCROW nRow2) const +{ + return (nTab == nTable) + && (nCol1 == nStartCol) && (nRow1 == nStartRow) + && (nCol2 == nEndCol) && (nRow2 == nEndRow); +} + +bool ScDBData::HasImportParam() const +{ + return mpImportParam && mpImportParam->bImport; +} + +bool ScDBData::HasQueryParam() const +{ + if (!mpQueryParam) + return false; + + if (!mpQueryParam->GetEntryCount()) + return false; + + return mpQueryParam->GetEntry(0).bDoQuery; +} + +bool ScDBData::HasSortParam() const +{ + return mpSortParam && + !mpSortParam->maKeyState.empty() && + mpSortParam->maKeyState[0].bDoSort; +} + +bool ScDBData::HasSubTotalParam() const +{ + return mpSubTotal && mpSubTotal->bGroupActive[0]; +} + +void ScDBData::UpdateMoveTab(SCTAB nOldPos, SCTAB nNewPos) +{ + ScRange aRange; + GetArea(aRange); + SCTAB nTab = aRange.aStart.Tab(); // a database range is only on one sheet + + // customize as the current table as ScTablesHint (tabvwsh5.cxx) + + if (nTab == nOldPos) // moved sheet + nTab = nNewPos; + else if (nOldPos < nNewPos) // moved to the back + { + if (nTab > nOldPos && nTab <= nNewPos) // move this sheet + --nTab; + } + else // moved to the front + { + if (nTab >= nNewPos && nTab < nOldPos) // move this sheet + ++nTab; + } + + bool bChanged = (nTab != aRange.aStart.Tab()); + if (bChanged) + { + // SetArea() invalidates column names, but it is the same column range + // just on a different sheet; remember and set new. + ::std::vector<OUString> aNames(maTableColumnNames); + bool bTableColumnNamesDirty = mbTableColumnNamesDirty; + // Same column range. + SetArea(nTab, aRange.aStart.Col(), aRange.aStart.Row(), aRange.aEnd.Col(), + aRange.aEnd.Row()); + // Do not use SetTableColumnNames() because that resets mbTableColumnNamesDirty. + maTableColumnNames = aNames; + mbTableColumnNamesDirty = bTableColumnNamesDirty; + } + + // MoveTo() is not necessary if only the sheet changed. + + SetModified(bChanged); +} + +bool ScDBData::UpdateReference(const ScDocument* pDoc, UpdateRefMode eUpdateRefMode, + SCCOL nCol1, SCROW nRow1, SCTAB nTab1, + SCCOL nCol2, SCROW nRow2, SCTAB nTab2, + SCCOL nDx, SCROW nDy, SCTAB nDz) +{ + SCCOL theCol1; + SCROW theRow1; + SCTAB theTab1; + SCCOL theCol2; + SCROW theRow2; + SCTAB theTab2; + GetArea( theTab1, theCol1, theRow1, theCol2, theRow2 ); + theTab2 = theTab1; + SCCOL nOldCol1 = theCol1, nOldCol2 = theCol2; + + ScRefUpdateRes eRet + = ScRefUpdate::Update(pDoc, eUpdateRefMode, nCol1, nRow1, nTab1, nCol2, nRow2, nTab2, nDx, + nDy, nDz, theCol1, theRow1, theTab1, theCol2, theRow2, theTab2); + + bool bDoUpdate = eRet != UR_NOTHING; + + if (bDoUpdate && eRet != UR_INVALID) + { + // MoveTo() invalidates column names via SetArea(); adjust, remember and set new. + AdjustTableColumnNames( eUpdateRefMode, nDx, nCol1, nOldCol1, nOldCol2, theCol1, theCol2); + ::std::vector<OUString> aNames( maTableColumnNames); + bool bTableColumnNamesDirty = mbTableColumnNamesDirty; + // tdf#48025, tdf#141946: update the column index of the filter criteria, + // when the deleted/inserted columns are inside the data range + if (HasAutoFilter() && theCol1 - nOldCol1 != theCol2 - nOldCol2) + MoveTo(theTab1, theCol1, theRow1, theCol2, theRow2, nCol1); + else + MoveTo( theTab1, theCol1, theRow1, theCol2, theRow2 ); + // Do not use SetTableColumnNames() because that resets mbTableColumnNamesDirty. + maTableColumnNames = aNames; + mbTableColumnNamesDirty = bTableColumnNamesDirty; + } + + ScRange aRangeAdvSource; + if ( GetAdvancedQuerySource(aRangeAdvSource) ) + { + aRangeAdvSource.GetVars( theCol1,theRow1,theTab1, theCol2,theRow2,theTab2 ); + if ( ScRefUpdate::Update( pDoc, eUpdateRefMode, + nCol1,nRow1,nTab1, nCol2,nRow2,nTab2, nDx,nDy,nDz, + theCol1,theRow1,theTab1, theCol2,theRow2,theTab2 ) ) + { + aRangeAdvSource.aStart.Set( theCol1,theRow1,theTab1 ); + aRangeAdvSource.aEnd.Set( theCol2,theRow2,theTab2 ); + SetAdvancedQuerySource( &aRangeAdvSource ); + + bDoUpdate = true; // DBData is modified + } + } + + SetModified(bDoUpdate); + + return eRet == UR_INVALID; + + //TODO: check if something was deleted/inserted with-in the range !!! +} + +void ScDBData::ExtendDataArea(const ScDocument& rDoc) +{ + // Extend the DB area to include data rows immediately below. + SCCOL nOldCol1 = nStartCol, nOldCol2 = nEndCol; + SCROW nOldEndRow = nEndRow; + rDoc.GetDataArea(nTable, nStartCol, nStartRow, nEndCol, nEndRow, false, true); + // nOldEndRow==rDoc.MaxRow() may easily happen when selecting whole columns and + // setting an AutoFilter (i.e. creating an anonymous database-range). We + // certainly don't want to iterate over nearly a million empty cells, but + // keep only an intentionally user selected range. + if (nOldEndRow < rDoc.MaxRow() && nEndRow < nOldEndRow) + nEndRow = nOldEndRow; + if (nStartCol != nOldCol1 || nEndCol != nOldCol2) + { + SAL_WARN_IF( !maTableColumnNames.empty(), "sc.core", "ScDBData::ExtendDataArea - invalidating column names/offsets"); + InvalidateTableColumnNames( true); + } +} + +void ScDBData::StartTableColumnNamesListener() +{ + if (mpContainer && bHasHeader) + { + ScDocument& rDoc = mpContainer->GetDocument(); + if (!rDoc.IsClipOrUndo()) + rDoc.StartListeningArea( GetHeaderArea(), false, this); + } +} + +void ScDBData::EndTableColumnNamesListener() +{ + EndListeningAll(); +} + +void ScDBData::SetTableColumnNames( ::std::vector< OUString >&& rNames ) +{ + maTableColumnNames = std::move(rNames); + mbTableColumnNamesDirty = false; +} + +void ScDBData::AdjustTableColumnNames( UpdateRefMode eUpdateRefMode, SCCOL nDx, SCCOL nCol1, + SCCOL nOldCol1, SCCOL nOldCol2, SCCOL nNewCol1, SCCOL nNewCol2 ) +{ + if (maTableColumnNames.empty()) + return; + + SCCOL nDiff1 = nNewCol1 - nOldCol1; + SCCOL nDiff2 = nNewCol2 - nOldCol2; + if (nDiff1 == nDiff2) + return; // not moved or entirely moved, nothing to do + + ::std::vector<OUString> aNewNames; + if (eUpdateRefMode == URM_INSDEL) + { + if (nDx > 0) + mbTableColumnNamesDirty = true; // inserted columns will have empty names + + // nCol1 is the first column of the block that gets shifted, determine + // the head and tail elements that are to be copied for deletion or + // insertion. + size_t nHead = static_cast<size_t>(::std::max( nCol1 + std::min<SCCOL>(nDx, 0) - nOldCol1, 0)); + size_t nTail = static_cast<size_t>(::std::max( nOldCol2 - nCol1 + 1, 0)); + size_t n = nHead + nTail; + if (0 < n && n <= maTableColumnNames.size()) + { + if (nDx > 0) + n += nDx; + aNewNames.resize(n); + // Copy head. + for (size_t i = 0; i < nHead; ++i) + { + aNewNames[i] = maTableColumnNames[i]; + } + // Copy tail, inserted middle range, if any, stays empty. + for (size_t i = n - nTail, j = maTableColumnNames.size() - nTail; i < n; ++i, ++j) + { + aNewNames[i] = maTableColumnNames[j]; + } + } + } // else empty aNewNames invalidates names/offsets + + SAL_WARN_IF( !maTableColumnNames.empty() && aNewNames.empty(), + "sc.core", "ScDBData::AdjustTableColumnNames - invalidating column names/offsets"); + aNewNames.swap( maTableColumnNames); + if (maTableColumnNames.empty()) + mbTableColumnNamesDirty = true; + if (mbTableColumnNamesDirty) + InvalidateTableColumnNames( false); // preserve new column names array +} + +void ScDBData::InvalidateTableColumnNames( bool bSwapToEmptyNames ) +{ + mbTableColumnNamesDirty = true; + if (bSwapToEmptyNames && !maTableColumnNames.empty()) + ::std::vector<OUString>().swap( maTableColumnNames); + if (mpContainer) + { + // Add header range to dirty list. + if (HasHeader()) + mpContainer->GetDirtyTableColumnNames().Join( GetHeaderArea()); + else + { + // We need *some* range in the dirty list even without header area, + // otherwise the container would not attempt to call a refresh. + mpContainer->GetDirtyTableColumnNames().Join( ScRange( nStartCol, nStartRow, nTable)); + } + } +} + +namespace { +class TableColumnNameSearch +{ +public: + explicit TableColumnNameSearch( const OUString& rSearchName ) : + maSearchName( rSearchName ) + { + } + + bool operator()( const OUString& rName ) const + { + return ScGlobal::GetTransliteration().isEqual( maSearchName, rName); + } + +private: + OUString maSearchName; +}; + +/** Set a numbered table column name at given nIndex, preventing duplicates, + numbering starting at nCount. If nCount==0 then the first attempt is made + with an unnumbered name and if already present the next attempt with + nCount=2, so "Original" and "Original2". No check whether nIndex is valid. */ +void SetTableColumnName( ::std::vector<OUString>& rVec, size_t nIndex, const OUString& rName, size_t nCount ) +{ + OUString aStr; + do + { + if (nCount) + aStr = rName + OUString::number( nCount); + else + { + aStr = rName; + ++nCount; + } + + if (std::none_of( rVec.begin(), rVec.end(), TableColumnNameSearch( aStr))) + { + rVec[nIndex] = aStr; + break; // do while + } + ++nCount; + } while(true); +} +} + +void ScDBData::RefreshTableColumnNames( ScDocument* pDoc ) +{ + ::std::vector<OUString> aNewNames; + aNewNames.resize( nEndCol - nStartCol + 1); + bool bHaveEmpty = false; + if (!HasHeader() || !pDoc) + bHaveEmpty = true; // Assume we have empty ones and fill below. + else + { + ScHorizontalCellIterator aIter(*pDoc, nTable, nStartCol, nStartRow, nEndCol, nStartRow); // header row only + ScRefCellValue* pCell; + SCCOL nCol, nLastColFilled = nStartCol - 1; + SCROW nRow; + while ((pCell = aIter.GetNext( nCol, nRow)) != nullptr) + { + if (pCell->hasString()) + { + const OUString& rStr = pCell->getString( pDoc); + if (rStr.isEmpty()) + bHaveEmpty = true; + else + { + SetTableColumnName( aNewNames, nCol-nStartCol, rStr, 0); + if (nLastColFilled < nCol-1) + bHaveEmpty = true; + } + nLastColFilled = nCol; + } + else + bHaveEmpty = true; + } + } + + // Never leave us with empty names, try to remember previous name that + // might had been used to compile formulas, but only if same number of + // columns and no duplicates. + if (bHaveEmpty && aNewNames.size() == maTableColumnNames.size()) + { + bHaveEmpty = false; + for (size_t i=0, n=aNewNames.size(); i < n; ++i) + { + if (aNewNames[i].isEmpty()) + { + const OUString& rStr = maTableColumnNames[i]; + if (rStr.isEmpty()) + bHaveEmpty = true; + else + SetTableColumnName( aNewNames, i, rStr, 0); + } + } + } + + // If we still have empty ones then fill those with "Column#" with # + // starting at the column offset number. Still no duplicates of course. + if (bHaveEmpty) + { + OUString aColumn( ScResId(STR_COLUMN)); + for (size_t i=0, n=aNewNames.size(); i < n; ++i) + { + if (aNewNames[i].isEmpty()) + SetTableColumnName( aNewNames, i, aColumn, i+1); + } + } + + aNewNames.swap( maTableColumnNames); + mbTableColumnNamesDirty = false; +} + +void ScDBData::RefreshTableColumnNames( ScDocument* pDoc, const ScRange& rRange ) +{ + // Header-less tables get names generated, completely empty a full refresh. + if (mbTableColumnNamesDirty && (!HasHeader() || maTableColumnNames.empty())) + { + RefreshTableColumnNames( pDoc); + return; + } + + // Check if this is affected for the range requested. + ScRange aIntersection( GetHeaderArea().Intersection( rRange)); + if (!aIntersection.IsValid()) + return; + + // Always fully refresh, only one cell of a range was broadcasted per area + // listener if multiple cells were affected. We don't know if there were + // more. Also, we need the full check anyway in case a duplicated name was + // entered. + RefreshTableColumnNames( pDoc); +} + +sal_Int32 ScDBData::GetColumnNameOffset( const OUString& rName ) const +{ + if (maTableColumnNames.empty()) + return -1; + + ::std::vector<OUString>::const_iterator it( + ::std::find_if( maTableColumnNames.begin(), maTableColumnNames.end(), TableColumnNameSearch( rName))); + if (it != maTableColumnNames.end()) + return it - maTableColumnNames.begin(); + + return -1; +} + +OUString ScDBData::GetTableColumnName( SCCOL nCol ) const +{ + if (maTableColumnNames.empty()) + return OUString(); + + SCCOL nOffset = nCol - nStartCol; + if (nOffset < 0 || maTableColumnNames.size() <= o3tl::make_unsigned(nOffset)) + return OUString(); + + return maTableColumnNames[nOffset]; +} + +void ScDBData::Notify( const SfxHint& rHint ) +{ + const ScHint* pScHint = dynamic_cast<const ScHint*>(&rHint); + if (!pScHint) + return; + + if (pScHint->GetId() != SfxHintId::ScDataChanged) + return; + + mbTableColumnNamesDirty = true; + if (!mpContainer) + assert(!"ScDBData::Notify - how did we end up here without container?"); + else + { + // Only one cell of a range is broadcasted per area listener if + // multiple cells are affected. Expand the range to what this is + // listening to. Broadcasted address outside should not happen, + // but... let it trigger a refresh if. + const ScRange aHeaderRange( GetHeaderArea()); + ScAddress aHintAddress( pScHint->GetStartAddress()); + if (aHeaderRange.IsValid()) + { + mpContainer->GetDirtyTableColumnNames().Join( aHeaderRange); + // Header range is one row. + // The ScHint's "range" is an address with row count. + // Though broadcasted is usually only one cell, check for the + // possible case of row block and for one cell in the same row. + if (aHintAddress.Row() <= aHeaderRange.aStart.Row() + && aHeaderRange.aStart.Row() < aHintAddress.Row() + pScHint->GetRowCount()) + { + aHintAddress.SetRow( aHeaderRange.aStart.Row()); + if (!aHeaderRange.Contains( aHintAddress)) + mpContainer->GetDirtyTableColumnNames().Join( aHintAddress); + } + } + else + { + // We need *some* range in the dirty list even without header area, + // otherwise the container would not attempt to call a refresh. + aHintAddress.SetRow( nStartRow); + mpContainer->GetDirtyTableColumnNames().Join( aHintAddress); + } + } + + // Do not refresh column names here, which might trigger unwanted + // recalculation. +} + +void ScDBData::CalcSaveFilteredCount( SCSIZE nNonFilteredRowCount ) +{ + SCSIZE nTotal = nEndRow - nStartRow + 1; + if ( bHasHeader ) + nTotal -= 1; + nFilteredRowCount = nTotal - nNonFilteredRowCount; +} + +void ScDBData::GetFilterSelCount( SCSIZE& nSelected, SCSIZE& nTotal ) +{ + nTotal = nEndRow - nStartRow + 1; + if ( bHasHeader ) + nTotal -= 1; + if( nFilteredRowCount != SCSIZE_MAX ) + nSelected = nTotal - nFilteredRowCount; + else + nSelected = nFilteredRowCount; +} + +namespace { + +class FindByTable +{ + SCTAB mnTab; +public: + explicit FindByTable(SCTAB nTab) : mnTab(nTab) {} + + bool operator() (std::unique_ptr<ScDBData> const& p) const + { + ScRange aRange; + p->GetArea(aRange); + return aRange.aStart.Tab() == mnTab; + } +}; + +class UpdateMoveTabFunc +{ + SCTAB mnOldTab; + SCTAB mnNewTab; +public: + UpdateMoveTabFunc(SCTAB nOld, SCTAB nNew) : mnOldTab(nOld), mnNewTab(nNew) {} + void operator() (std::unique_ptr<ScDBData> const& p) + { + p->UpdateMoveTab(mnOldTab, mnNewTab); + } +}; + +OUString lcl_IncrementNumberInNamedRange(ScDBCollection::NamedDBs& namedDBs, + std::u16string_view rOldName) +{ + // Append or increment a numeric suffix and do not generate names that + // could result in a cell reference by ensuring at least one underscore is + // present. + // "aa" => "aa_2" + // "aaaa1" => "aaaa1_2" + // "aa_a" => "aa_a_2" + // "aa_a_" => "aa_a__2" + // "aa_a1" => "aa_a1_2" + // "aa_1a" => "aa_1a_2" + // "aa_1" => "aa_2" + // "aa_2" => "aa_3" + + size_t nLastIndex = rOldName.rfind('_'); + sal_Int32 nOldNumber = 1; + OUString aPrefix; + if (nLastIndex != std::u16string_view::npos) + { + ++nLastIndex; + std::u16string_view sLastPart(rOldName.substr(nLastIndex)); + nOldNumber = o3tl::toInt32(sLastPart); + + // If that number is exactly at the end then increment the number; else + // append "_" and number. + // toInt32() returns 0 on failure and also stops at trailing non-digit + // characters (toInt32("1a")==1). + if (OUString::number(nOldNumber) == sLastPart) + aPrefix = rOldName.substr(0, nLastIndex); + else + { + aPrefix = OUString::Concat(rOldName) + "_"; + nOldNumber = 1; + } + } + else // No "_" found, append "_" and number. + aPrefix = OUString::Concat(rOldName) + "_"; + OUString sNewName; + do + { + sNewName = aPrefix + OUString::number(++nOldNumber); + } while (namedDBs.findByName(sNewName) != nullptr); + return sNewName; +} + +class FindByCursor +{ + SCCOL mnCol; + SCROW mnRow; + SCTAB mnTab; + ScDBDataPortion mePortion; +public: + FindByCursor(SCCOL nCol, SCROW nRow, SCTAB nTab, ScDBDataPortion ePortion) : + mnCol(nCol), mnRow(nRow), mnTab(nTab), mePortion(ePortion) {} + + bool operator() (std::unique_ptr<ScDBData> const& p) + { + return p->IsDBAtCursor(mnCol, mnRow, mnTab, mePortion); + } +}; + +class FindByRange +{ + const ScRange& mrRange; +public: + explicit FindByRange(const ScRange& rRange) : mrRange(rRange) {} + + bool operator() (std::unique_ptr<ScDBData> const& p) + { + return p->IsDBAtArea( + mrRange.aStart.Tab(), mrRange.aStart.Col(), mrRange.aStart.Row(), mrRange.aEnd.Col(), mrRange.aEnd.Row()); + } +}; + +class FindByIndex +{ + sal_uInt16 mnIndex; +public: + explicit FindByIndex(sal_uInt16 nIndex) : mnIndex(nIndex) {} + bool operator() (std::unique_ptr<ScDBData> const& p) const + { + return p->GetIndex() == mnIndex; + } +}; + +class FindByUpperName +{ + const OUString& mrName; +public: + explicit FindByUpperName(const OUString& rName) : mrName(rName) {} + bool operator() (std::unique_ptr<ScDBData> const& p) const + { + return p->GetUpperName() == mrName; + } +}; + +class FindByName +{ + const OUString& mrName; +public: + explicit FindByName(const OUString& rName) : mrName(rName) {} + bool operator() (std::unique_ptr<ScDBData> const& p) const + { + return p->GetName() == mrName; + } +}; + +class FindByPointer +{ + const ScDBData* mpDBData; +public: + explicit FindByPointer(const ScDBData* pDBData) : mpDBData(pDBData) {} + bool operator() (std::unique_ptr<ScDBData> const& p) const + { + return p.get() == mpDBData; + } +}; + +} + +ScDocument& ScDBDataContainerBase::GetDocument() const +{ + return mrDoc; +} + +ScRangeList& ScDBDataContainerBase::GetDirtyTableColumnNames() +{ + return maDirtyTableColumnNames; +} + +ScDBCollection::NamedDBs::NamedDBs(ScDBCollection& rParent, ScDocument& rDoc) : + ScDBDataContainerBase(rDoc), mrParent(rParent) {} + +ScDBCollection::NamedDBs::NamedDBs(const NamedDBs& r, ScDBCollection& rParent) + : ScDBDataContainerBase(r.mrDoc) + , mrParent(rParent) +{ + for (auto const& it : r.m_DBs) + { + ScDBData* p = new ScDBData(*it); + std::unique_ptr<ScDBData> pData(p); + if (m_DBs.insert( std::move(pData)).second) + initInserted(p); + } +} + +ScDBCollection::NamedDBs::~NamedDBs() +{ +} + +void ScDBCollection::NamedDBs::initInserted( ScDBData* p ) +{ + p->SetContainer( this); + if (mrDoc.IsClipOrUndo()) + return; + + p->StartTableColumnNamesListener(); // needs the container be set already + if (!p->AreTableColumnNamesDirty()) + return; + + if (p->HasHeader()) + { + // Refresh table column names in next round. + maDirtyTableColumnNames.Join( p->GetHeaderArea()); + } + else + { + // Header-less table can generate its column names + // already without accessing the document. + p->RefreshTableColumnNames( nullptr); + } +} + +ScDBCollection::NamedDBs::iterator ScDBCollection::NamedDBs::begin() +{ + return m_DBs.begin(); +} + +ScDBCollection::NamedDBs::iterator ScDBCollection::NamedDBs::end() +{ + return m_DBs.end(); +} + +ScDBCollection::NamedDBs::const_iterator ScDBCollection::NamedDBs::begin() const +{ + return m_DBs.begin(); +} + +ScDBCollection::NamedDBs::const_iterator ScDBCollection::NamedDBs::end() const +{ + return m_DBs.end(); +} + +ScDBData* ScDBCollection::NamedDBs::findByIndex(sal_uInt16 nIndex) +{ + DBsType::iterator itr = find_if( + m_DBs.begin(), m_DBs.end(), FindByIndex(nIndex)); + return itr == m_DBs.end() ? nullptr : itr->get(); +} + +ScDBData* ScDBCollection::NamedDBs::findByUpperName(const OUString& rName) +{ + DBsType::iterator itr = find_if( + m_DBs.begin(), m_DBs.end(), FindByUpperName(rName)); + return itr == m_DBs.end() ? nullptr : itr->get(); +} + +auto ScDBCollection::NamedDBs::findByUpperName2(const OUString& rName) -> iterator +{ + return find_if( + m_DBs.begin(), m_DBs.end(), FindByUpperName(rName)); +} + +ScDBData* ScDBCollection::NamedDBs::findByName(const OUString& rName) +{ + DBsType::iterator itr = find_if(m_DBs.begin(), m_DBs.end(), FindByName(rName)); + return itr == m_DBs.end() ? nullptr : itr->get(); +} + +bool ScDBCollection::NamedDBs::insert(std::unique_ptr<ScDBData> pData) +{ + auto p = pData.get(); + if (!pData->GetIndex()) + pData->SetIndex(mrParent.nEntryIndex++); + + pair<DBsType::iterator, bool> r = m_DBs.insert(std::move(pData)); + + if (r.second) + { + initInserted(p); + + /* TODO: shouldn't the import refresh not be setup for + * clipboard/undo documents? It was already like this before... */ + if (p->HasImportParam() && !p->HasImportSelection()) + { + p->SetRefreshHandler(mrParent.GetRefreshHandler()); + p->SetRefreshControl(&mrDoc.GetRefreshTimerControlAddress()); + } + } + return r.second; +} + +ScDBCollection::NamedDBs::iterator ScDBCollection::NamedDBs::erase(const iterator& itr) +{ + return m_DBs.erase(itr); +} + +bool ScDBCollection::NamedDBs::empty() const +{ + return m_DBs.empty(); +} + +size_t ScDBCollection::NamedDBs::size() const +{ + return m_DBs.size(); +} + +bool ScDBCollection::NamedDBs::operator== (const NamedDBs& r) const +{ + return ::comphelper::ContainerUniquePtrEquals(m_DBs, r.m_DBs); +} + +ScDBCollection::AnonDBs::iterator ScDBCollection::AnonDBs::begin() +{ + return m_DBs.begin(); +} + +ScDBCollection::AnonDBs::iterator ScDBCollection::AnonDBs::end() +{ + return m_DBs.end(); +} + +ScDBCollection::AnonDBs::const_iterator ScDBCollection::AnonDBs::begin() const +{ + return m_DBs.begin(); +} + +ScDBCollection::AnonDBs::const_iterator ScDBCollection::AnonDBs::end() const +{ + return m_DBs.end(); +} + +const ScDBData* ScDBCollection::AnonDBs::findAtCursor(SCCOL nCol, SCROW nRow, SCTAB nTab, + ScDBDataPortion ePortion) const +{ + DBsType::const_iterator itr = find_if( + m_DBs.begin(), m_DBs.end(), FindByCursor(nCol, nRow, nTab, ePortion)); + return itr == m_DBs.end() ? nullptr : itr->get(); +} + +const ScDBData* ScDBCollection::AnonDBs::findByRange(const ScRange& rRange) const +{ + DBsType::const_iterator itr = find_if( + m_DBs.begin(), m_DBs.end(), FindByRange(rRange)); + return itr == m_DBs.end() ? nullptr : itr->get(); +} + +void ScDBCollection::AnonDBs::deleteOnTab(SCTAB nTab) +{ + FindByTable func(nTab); + m_DBs.erase(std::remove_if(m_DBs.begin(), m_DBs.end(), func), m_DBs.end()); +} + +ScDBData* ScDBCollection::AnonDBs::getByRange(const ScRange& rRange) +{ + const ScDBData* pData = findByRange(rRange); + if (!pData) + { + // Insert a new db data. They all have identical names. + ::std::unique_ptr<ScDBData> pNew(new ScDBData( + STR_DB_GLOBAL_NONAME, rRange.aStart.Tab(), rRange.aStart.Col(), rRange.aStart.Row(), + rRange.aEnd.Col(), rRange.aEnd.Row(), true, false, false)); + pData = pNew.get(); + m_DBs.push_back(std::move(pNew)); + } + return const_cast<ScDBData*>(pData); +} + +void ScDBCollection::AnonDBs::insert(ScDBData* p) +{ + m_DBs.push_back(std::unique_ptr<ScDBData>(p)); +} + +ScDBCollection::AnonDBs::iterator ScDBCollection::AnonDBs::erase(const iterator& itr) +{ + return m_DBs.erase(itr); +} + +bool ScDBCollection::AnonDBs::empty() const +{ + return m_DBs.empty(); +} + +bool ScDBCollection::AnonDBs::has( const ScDBData* p ) const +{ + return any_of(m_DBs.begin(), m_DBs.end(), FindByPointer(p)); +} + +bool ScDBCollection::AnonDBs::operator== (const AnonDBs& r) const +{ + return ::comphelper::ContainerUniquePtrEquals(m_DBs, r.m_DBs); +} + +ScDBCollection::AnonDBs::AnonDBs() +{ +} + +ScDBCollection::AnonDBs::AnonDBs(AnonDBs const& r) +{ + m_DBs.reserve(r.m_DBs.size()); + for (auto const& it : r.m_DBs) + { + m_DBs.push_back(std::make_unique<ScDBData>(*it)); + } +} + +ScDBCollection::ScDBCollection(ScDocument& rDocument) : + rDoc(rDocument), nEntryIndex(1), maNamedDBs(*this, rDocument) {} + +ScDBCollection::ScDBCollection(const ScDBCollection& r) : + rDoc(r.rDoc), nEntryIndex(r.nEntryIndex), maNamedDBs(r.maNamedDBs, *this), maAnonDBs(r.maAnonDBs) {} + +const ScDBData* ScDBCollection::GetDBAtCursor(SCCOL nCol, SCROW nRow, SCTAB nTab, ScDBDataPortion ePortion) const +{ + // First, search the global named db ranges. + NamedDBs::DBsType::const_iterator itr = find_if( + maNamedDBs.begin(), maNamedDBs.end(), FindByCursor(nCol, nRow, nTab, ePortion)); + if (itr != maNamedDBs.end()) + return itr->get(); + + // Check for the sheet-local anonymous db range. + const ScDBData* pNoNameData = rDoc.GetAnonymousDBData(nTab); + if (pNoNameData) + if (pNoNameData->IsDBAtCursor(nCol,nRow,nTab,ePortion)) + return pNoNameData; + + // Check the global anonymous db ranges. + const ScDBData* pData = getAnonDBs().findAtCursor(nCol, nRow, nTab, ePortion); + if (pData) + return pData; + + // Do NOT check for the document global temporary anonymous db range here. + + return nullptr; +} + +ScDBData* ScDBCollection::GetDBAtCursor(SCCOL nCol, SCROW nRow, SCTAB nTab, ScDBDataPortion ePortion) +{ + // First, search the global named db ranges. + NamedDBs::DBsType::iterator itr = find_if( + maNamedDBs.begin(), maNamedDBs.end(), FindByCursor(nCol, nRow, nTab, ePortion)); + if (itr != maNamedDBs.end()) + return itr->get(); + + // Check for the sheet-local anonymous db range. + ScDBData* pNoNameData = rDoc.GetAnonymousDBData(nTab); + if (pNoNameData) + if (pNoNameData->IsDBAtCursor(nCol,nRow,nTab,ePortion)) + return pNoNameData; + + // Check the global anonymous db ranges. + const ScDBData* pData = getAnonDBs().findAtCursor(nCol, nRow, nTab, ePortion); + if (pData) + return const_cast<ScDBData*>(pData); + + // Do NOT check for the document global temporary anonymous db range here. + + return nullptr; +} + +const ScDBData* ScDBCollection::GetDBAtArea(SCTAB nTab, SCCOL nCol1, SCROW nRow1, SCCOL nCol2, SCROW nRow2) const +{ + // First, search the global named db ranges. + ScRange aRange(nCol1, nRow1, nTab, nCol2, nRow2, nTab); + NamedDBs::DBsType::const_iterator itr = find_if( + maNamedDBs.begin(), maNamedDBs.end(), FindByRange(aRange)); + if (itr != maNamedDBs.end()) + return itr->get(); + + // Check for the sheet-local anonymous db range. + ScDBData* pNoNameData = rDoc.GetAnonymousDBData(nTab); + if (pNoNameData) + if (pNoNameData->IsDBAtArea(nTab, nCol1, nRow1, nCol2, nRow2)) + return pNoNameData; + + // Lastly, check the global anonymous db ranges. + const ScDBData* pData = maAnonDBs.findByRange(aRange); + if (pData) + return pData; + + // As a last resort, check for the document global temporary anonymous db range. + pNoNameData = rDoc.GetAnonymousDBData(); + if (pNoNameData) + if (pNoNameData->IsDBAtArea(nTab, nCol1, nRow1, nCol2, nRow2)) + return pNoNameData; + + return nullptr; +} + +ScDBData* ScDBCollection::GetDBAtArea(SCTAB nTab, SCCOL nCol1, SCROW nRow1, SCCOL nCol2, SCROW nRow2) +{ + // First, search the global named db ranges. + ScRange aRange(nCol1, nRow1, nTab, nCol2, nRow2, nTab); + NamedDBs::DBsType::iterator itr = find_if( + maNamedDBs.begin(), maNamedDBs.end(), FindByRange(aRange)); + if (itr != maNamedDBs.end()) + return itr->get(); + + // Check for the sheet-local anonymous db range. + ScDBData* pNoNameData = rDoc.GetAnonymousDBData(nTab); + if (pNoNameData) + if (pNoNameData->IsDBAtArea(nTab, nCol1, nRow1, nCol2, nRow2)) + return pNoNameData; + + // Lastly, check the global anonymous db ranges. + const ScDBData* pData = getAnonDBs().findByRange(aRange); + if (pData) + return const_cast<ScDBData*>(pData); + + // As a last resort, check for the document global temporary anonymous db range. + pNoNameData = rDoc.GetAnonymousDBData(); + if (pNoNameData) + if (pNoNameData->IsDBAtArea(nTab, nCol1, nRow1, nCol2, nRow2)) + return pNoNameData; + + return nullptr; +} + +void ScDBCollection::RefreshDirtyTableColumnNames() +{ + for (size_t i=0; i < maNamedDBs.maDirtyTableColumnNames.size(); ++i) + { + const ScRange & rRange = maNamedDBs.maDirtyTableColumnNames[i]; + for (auto const& it : maNamedDBs) + { + if (it->AreTableColumnNamesDirty()) + it->RefreshTableColumnNames( &maNamedDBs.mrDoc, rRange); + } + } + maNamedDBs.maDirtyTableColumnNames.RemoveAll(); +} + +void ScDBCollection::DeleteOnTab( SCTAB nTab ) +{ + FindByTable func(nTab); + // First, collect the positions of all items that need to be deleted. + ::std::vector<NamedDBs::DBsType::iterator> v; + { + NamedDBs::DBsType::iterator itr = maNamedDBs.begin(), itrEnd = maNamedDBs.end(); + for (; itr != itrEnd; ++itr) + { + if (func(*itr)) + v.push_back(itr); + } + } + + // Delete them all. + for (const auto& rIter : v) + maNamedDBs.erase(rIter); + + maAnonDBs.deleteOnTab(nTab); +} + +void ScDBCollection::UpdateReference(UpdateRefMode eUpdateRefMode, + SCCOL nCol1, SCROW nRow1, SCTAB nTab1, + SCCOL nCol2, SCROW nRow2, SCTAB nTab2, + SCCOL nDx, SCROW nDy, SCTAB nDz ) +{ + ScDBData* pData = rDoc.GetAnonymousDBData(nTab1); + if (pData) + { + if (nTab1 == nTab2 && nDz == 0) + { + // Delete the database range, if some part of the reference became invalid. + if (pData->UpdateReference(&rDoc, eUpdateRefMode, nCol1, nRow1, nTab1, nCol2, nRow2, + nTab2, nDx, nDy, nDz)) + rDoc.SetAnonymousDBData(nTab1, nullptr); + } + else + { + //this will perhaps break undo + } + } + + for (auto it = maNamedDBs.begin(); it != maNamedDBs.end(); ) + { + // Delete the database range, if some part of the reference became invalid. + if (it->get()->UpdateReference(&rDoc, eUpdateRefMode, nCol1, nRow1, nTab1, nCol2, nRow2, + nTab2, nDx, nDy, nDz)) + it = maNamedDBs.erase(it); + else + ++it; + } + for (auto it = maAnonDBs.begin(); it != maAnonDBs.end(); ) + { + // Delete the database range, if some part of the reference became invalid. + if (it->get()->UpdateReference(&rDoc, eUpdateRefMode, nCol1, nRow1, nTab1, nCol2, nRow2, + nTab2, nDx, nDy, nDz)) + it = maAnonDBs.erase(it); + else + ++it; + } +} + +void ScDBCollection::UpdateMoveTab( SCTAB nOldPos, SCTAB nNewPos ) +{ + UpdateMoveTabFunc func(nOldPos, nNewPos); + for_each(maNamedDBs.begin(), maNamedDBs.end(), func); + for_each(maAnonDBs.begin(), maAnonDBs.end(), func); +} + +void ScDBCollection::CopyToTable(SCTAB nOldPos, SCTAB nNewPos) +{ + // Create temporary copy of pointers to not insert in a set we are + // iterating over. + std::vector<const ScDBData*> aTemp; + aTemp.reserve( maNamedDBs.size()); + for (const auto& rxNamedDB : maNamedDBs) + { + if (rxNamedDB->GetTab() != nOldPos) + continue; + aTemp.emplace_back( rxNamedDB.get()); + } + for (const auto& rxNamedDB : aTemp) + { + const OUString newName( lcl_IncrementNumberInNamedRange( maNamedDBs, rxNamedDB->GetName())); + std::unique_ptr<ScDBData> pDataCopy = std::make_unique<ScDBData>(newName, *rxNamedDB); + pDataCopy->UpdateMoveTab(nOldPos, nNewPos); + pDataCopy->SetIndex(0); + maNamedDBs.insert(std::move(pDataCopy)); + } +} + +ScDBData* ScDBCollection::GetDBNearCursor(SCCOL nCol, SCROW nRow, SCTAB nTab ) +{ + ScDBData* pNearData = nullptr; + for (const auto& rxNamedDB : maNamedDBs) + { + SCTAB nAreaTab; + SCCOL nStartCol, nEndCol; + SCROW nStartRow, nEndRow; + rxNamedDB->GetArea( nAreaTab, nStartCol, nStartRow, nEndCol, nEndRow ); + if ( nTab == nAreaTab && nCol+1 >= nStartCol && nCol <= nEndCol+1 && + nRow+1 >= nStartRow && nRow <= nEndRow+1 ) + { + if ( nCol < nStartCol || nCol > nEndCol || nRow < nStartRow || nRow > nEndRow ) + { + if (!pNearData) + pNearData = rxNamedDB.get(); // remember first adjacent area + } + else + return rxNamedDB.get(); // not "unbenannt"/"unnamed" and cursor within + } + } + if (pNearData) + return pNearData; // adjacent, if no direct hit + return rDoc.GetAnonymousDBData(nTab); // "unbenannt"/"unnamed" only if nothing else +} + +std::vector<ScDBData*> ScDBCollection::GetAllDBsFromTab(SCTAB nTab) +{ + std::vector<ScDBData*> pTabData; + for (const auto& rxNamedDB : maNamedDBs) + { + if (rxNamedDB->GetTab() == nTab) + pTabData.emplace_back(rxNamedDB.get()); + } + auto pAnonDBData = rDoc.GetAnonymousDBData(nTab); + if (pAnonDBData) + pTabData.emplace_back(pAnonDBData); + return pTabData; +} + +bool ScDBCollection::empty() const +{ + return maNamedDBs.empty() && maAnonDBs.empty(); +} + +bool ScDBCollection::operator== (const ScDBCollection& r) const +{ + return maNamedDBs == r.maNamedDBs && maAnonDBs == r.maAnonDBs && + nEntryIndex == r.nEntryIndex && &rDoc == &r.rDoc && aRefreshHandler == r.aRefreshHandler; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ |