/* -*- 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include 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& left, const std::unique_ptr& 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(nCol1) - static_cast(nStartCol); tools::Long nDifY = static_cast(nRow1) - static_cast(nStartRow); tools::Long nSortDif = bByRow ? nDifX : nDifY; tools::Long nSortEnd = bByRow ? static_cast(nCol2) : static_cast(nRow2); for (sal_uInt16 i=0; iGetSortKeyCount(); 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(nCol2) - static_cast(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; inField[i] = sal::static_int_cast( 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 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 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 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(::std::max( nCol1 + std::min(nDx, 0) - nOldCol1, 0)); size_t nTail = static_cast(::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().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& 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 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::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(&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 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 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 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 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 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 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 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 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 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 pData) { auto p = pData.get(); if (!pData->GetIndex()) pData->SetIndex(mrParent.nEntryIndex++); pair 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 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(pData); } void ScDBCollection::AnonDBs::insert(ScDBData* p) { m_DBs.push_back(std::unique_ptr(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(*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(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(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 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 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 pDataCopy = std::make_unique(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 ScDBCollection::GetAllDBsFromTab(SCTAB nTab) { std::vector 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: */