/* -*- 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 .
*/
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.
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();
}
}
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; elseif (rEntry.nField >= nUpdateCol + nDifX2)
rEntry.Clear();
}
if (rEntry.nField > nCol2)
{
rEntry.nField = 0;
rEntry.bDoQuery = false;
}
} for (auto& group : mpSubTotal->aGroups)
{
group.nField += nDifX; if (group.nField > nCol2)
{
group.nField = 0;
group.bActive = false;
}
}
// 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::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; elseif (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;
maTableColumnAttributes.resize(aNames.size());
mbTableColumnNamesDirty = bTableColumnNamesDirty;
}
// MoveTo() is not necessary if only the sheet changed.
if (bDoUpdate && eRet != UR_INVALID)
{ // MoveTo() invalidates column names via SetArea(); adjust, remember and set new.
AdjustTableColumnAttributes( 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;
maTableColumnAttributes.resize(aNames.size());
mbTableColumnNamesDirty = bTableColumnNamesDirty;
}
//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::ExtendBackColorArea(const ScDocument& rDoc)
{ // Extend the DB area to include data rows immediately below.
SCCOL nOldCol1 = nStartCol, nOldCol2 = nEndCol;
SCROW nOldEndRow = nEndRow;
rDoc.GetBackColorArea(nTable, nStartCol, nStartRow, nEndCol, nEndRow);
SCCOL nDiff1 = nNewCol1 - nOldCol1;
SCCOL nDiff2 = nNewCol2 - nOldCol2; if (nDiff1 == nDiff2) return; // not moved or entirely moved, nothing to do
::std::vector<OUString> aNewNames;
::std::vector<TableColumnAttributes> aNewAttributes; 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);
aNewAttributes.resize(n); // Copy head. for (size_t i = 0; i < nHead; ++i)
{
aNewNames[i] = maTableColumnNames[i];
aNewAttributes[i] = maTableColumnAttributes[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];
aNewAttributes[i] = maTableColumnAttributes[j];
}
}
} // else empty aNewNames invalidates names/offsets
SAL_WARN_IF( !maTableColumnNames.empty() && aNewNames.empty(), "sc.core", "ScDBData::AdjustTableColumnAttributes - invalidating column attributes/offsets");
aNewNames.swap( maTableColumnNames);
aNewAttributes.swap(maTableColumnAttributes); 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));
}
}
}
/** 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);
}
}
// 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);
}
}
void ScDBData::RefreshTableColumnNames( ScDocument& rDoc, const ScRange& rRange )
{ // Header-less tables get names generated, completely empty a full refresh. if (mbTableColumnNamesDirty && (!HasHeader() || maTableColumnNames.empty()))
{
RefreshTableColumnNames( &rDoc); 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( &rDoc);
}
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( ScRange(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( ScRange(aHintAddress) );
}
}
// Do not refresh column names here, which might trigger unwanted // recalculation.
}
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"
// 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;
}
ScDBCollection::NamedDBs::NamedDBs(const NamedDBs& r, ScDBCollection& rParent)
: ScDBDataContainerBase(r.mrDoc)
, mrParent(rParent)
{ for (autoconst& 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);
}
}
bool ScDBCollection::NamedDBs::insert(std::unique_ptr<ScDBData> pData)
{ auto p = pData.get(); if (!pData->GetIndex())
pData->SetIndex(mrParent.nEntryIndex++);
std::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;
}
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) returnconst_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) returnconst_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 (autoconst& 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 (constauto& 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;
}
}
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.