/* -*- 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/.
*/
#include <grouparealistener.hxx>
#include <brdcst.hxx>
#include <formulacell.hxx>
#include <bulkdatahint.hxx>
#include <columnspanset.hxx>
#include <column.hxx>
#include <listenerquery.hxx>
#include <listenerqueryids.hxx>
#include <document.hxx>
#include <table.hxx>
#include <o3tl/safeint.hxx>
#include <sal/log.hxx>
namespace sc {
namespace {
class Notifier
{
const SfxHint& mrHint;
public:
explicit Notifier( const SfxHint& rHint ) : mrHint(rHint) {}
void operator() ( ScFormulaCell* pCell )
{
pCell->Notify(mrHint);
}
};
class CollectCellAction : public sc::ColumnSpanSet::ColumnAction
{
const FormulaGroupAreaListener& mrAreaListener;
ScAddress maPos;
std::vector<ScFormulaCell*> maCells;
public:
explicit CollectCellAction( const FormulaGroupAreaListener& rAreaListener ) :
mrAreaListener(rAreaListener) {}
virtual void startColumn( ScColumn* pCol ) override
{
maPos.SetTab(pCol->GetTab());
maPos.SetCol(pCol->GetCol());
}
virtual void execute( SCROW nRow1, SCROW nRow2, bool bVal ) override
{
if (!bVal)
return;
mrAreaListener.collectFormulaCells(maPos.Tab(), maPos.Col(), nRow1, nRow2, maCells);
};
void swapCells( std::vector<ScFormulaCell*>& rCells )
{
// Remove duplicate before the swap. Take care to sort them by tab,col,row before sorting by pointer,
// as many calc algorithms perform better if cells are processed in this order.
std::sort(maCells.begin(), maCells.end(), [](const ScFormulaCell* cell1, const ScFormulaCell* cell2)
{
if( cell1->aPos != cell2->aPos )
return cell1->aPos < cell2->aPos;
return cell1 < cell2;
});
std::vector<ScFormulaCell*>::iterator it = std::unique(maCells.begin(), maCells.end());
maCells.erase(it, maCells.end());
rCells.swap(maCells);
}
};
}
FormulaGroupAreaListener::FormulaGroupAreaListener( const ScRange& rRange, const ScDocument& rDocument,
const ScAddress& rTopCellPos, SCROW nGroupLen, bool bStartFixed, bool bEndFixed ) :
maRange(rRange),
mrDocument(rDocument),
mpColumn(nullptr),
mnTopCellRow(rTopCellPos.Row()),
mnGroupLen(nGroupLen),
mbStartFixed(bStartFixed),
mbEndFixed(bEndFixed)
{
const ScTable* pTab = rDocument.FetchTable( rTopCellPos.Tab());
assert(pTab);
mpColumn = pTab->FetchColumn( rTopCellPos.Col());
assert(mpColumn);
SAL_INFO( "sc.core.grouparealistener",
"FormulaGroupAreaListener ctor this " << this <<
" range " << (maRange == BCA_LISTEN_ALWAYS ? u"LISTEN-ALWAYS"_ustr : maRange.Format(mrDocument, ScRefFlags::VALID)) <<
" mnTopCellRow " << mnTopCellRow << " length " << mnGroupLen <<
", col/tab " << mpColumn->GetCol() << "/" << mpColumn->GetTab());
}
FormulaGroupAreaListener::~FormulaGroupAreaListener()
{
SAL_INFO( "sc.core.grouparealistener",
"FormulaGroupAreaListener dtor this " << this);
}
ScRange FormulaGroupAreaListener::getListeningRange() const
{
ScRange aRet = maRange;
if (!mbEndFixed)
aRet.aEnd.IncRow(mnGroupLen-1);
return aRet;
}
void FormulaGroupAreaListener::Notify( const SfxHint& rHint )
{
if ( rHint.GetId() == SfxHintId::ScBulkData )
{
const BulkDataHint* pBulkHint = static_cast<const BulkDataHint*>(&rHint);
notifyBulkChange(*pBulkHint);
}
else if (rHint.GetId() == SfxHintId::ScDataChanged || rHint.GetId() == SfxHintId::ScTableOpDirty)
{
const ScHint& rScHint = static_cast<const ScHint&>(rHint);
notifyCellChange(rHint, rScHint.GetStartAddress(), rScHint.GetRowCount());
}
}
void FormulaGroupAreaListener::Query( QueryBase& rQuery ) const
{
switch (rQuery.getId())
{
case SC_LISTENER_QUERY_FORMULA_GROUP_RANGE:
{
const ScFormulaCell* pTop = getTopCell();
ScRange aRange(pTop->aPos);
aRange.aEnd.IncRow(mnGroupLen-1);
QueryRange& rQR = static_cast<QueryRange&>(rQuery);
rQR.add(aRange);
}
break;
default:
;
}
}
void FormulaGroupAreaListener::notifyBulkChange( const BulkDataHint& rHint )
{
const ColumnSpanSet* pSpans = rHint.getSpans();
if (!pSpans)
return;
ScDocument& rDoc = const_cast<BulkDataHint&>(rHint).getDoc();
CollectCellAction aAction(*this);
pSpans->executeColumnAction(rDoc, aAction);
std::vector<ScFormulaCell*> aCells;
aAction.swapCells(aCells);
ScHint aHint(SfxHintId::ScDataChanged, ScAddress());
std::for_each(aCells.begin(), aCells.end(), Notifier(aHint));
}
void FormulaGroupAreaListener::collectFormulaCells(
SCTAB nTab, SCCOL nCol, SCROW nRow1, SCROW nRow2, std::vector<ScFormulaCell*>& rCells ) const
{
PutInOrder(nRow1, nRow2);
if (nTab < maRange.aStart.Tab() || maRange.aEnd.Tab() < nTab)
// Wrong sheet.
return;
if (nCol < maRange.aStart.Col() || maRange.aEnd.Col() < nCol)
// Outside the column range.
return;
collectFormulaCells(nRow1, nRow2, rCells);
}
void FormulaGroupAreaListener::collectFormulaCells(
SCROW nRow1, SCROW nRow2, std::vector<ScFormulaCell*>& rCells ) const
{
SAL_INFO( "sc.core.grouparealistener",
"FormulaGroupAreaListener::collectFormulaCells() this " << this <<
" range " << (maRange == BCA_LISTEN_ALWAYS ? u"LISTEN-ALWAYS"_ustr : maRange.Format(mrDocument, ScRefFlags::VALID)) <<
" mnTopCellRow " << mnTopCellRow << " length " << mnGroupLen <<
", col/tab " << mpColumn->GetCol() << "/" << mpColumn->GetTab());
size_t nBlockSize = 0;
ScFormulaCell* const * pp = mpColumn->GetFormulaCellBlockAddress( mnTopCellRow, nBlockSize);
if (!pp)
{
SAL_WARN("sc.core", "GetFormulaCellBlockAddress not found");
return;
}
/* FIXME: this is tdf#90717, when deleting a row fixed size area listeners
* such as BCA_ALWAYS or entire row listeners are (rightly) not destroyed,
* but mnTopCellRow and mnGroupLen also not updated, which needs fixing.
* Until then pull things as straight as possible here in such situation
* and prevent crash. */
if (!(*pp)->IsSharedTop())
{
SCROW nRow = (*pp)->GetSharedTopRow();
if (nRow < 0)
SAL_WARN("sc.core", "FormulaGroupAreaListener::collectFormulaCells() no shared top");
else
{
SAL_WARN("sc.core","FormulaGroupAreaListener::collectFormulaCells() syncing mnTopCellRow from " <<
mnTopCellRow << " to " << nRow);
const_cast<FormulaGroupAreaListener*>(this)->mnTopCellRow = nRow;
pp = mpColumn->GetFormulaCellBlockAddress( mnTopCellRow, nBlockSize);
if (!pp)
{
SAL_WARN("sc.core", "GetFormulaCellBlockAddress not found");
return;
}
}
}
SCROW nLen = (*pp)->GetSharedLength();
if (nLen != mnGroupLen)
{
SAL_WARN("sc.core", "FormulaGroupAreaListener::collectFormulaCells() syncing mnGroupLen from " <<
mnGroupLen << " to " << nLen);
const_cast<FormulaGroupAreaListener*>(this)->mnGroupLen = nLen;
}
/* With tdf#89957 it happened that the actual block size in column
* AP (shifted from AO) of sheet 'w' was smaller than the remembered group
* length and correct. This is just a very ugly workaround, the real cause
* is yet unknown, but at least don't crash in such case. The intermediate
* cause is that not all affected group area listeners are destroyed and
* newly created, so mpColumn still points to the old column that then has
* the content of a shifted column. Effectively this workaround has the
* consequence that the group area listener is fouled up and not all
* formula cells are notified... */
if (nBlockSize < o3tl::make_unsigned(mnGroupLen))
{
SAL_WARN("sc.core","FormulaGroupAreaListener::collectFormulaCells() nBlockSize " <<
nBlockSize << " < " << mnGroupLen << " mnGroupLen");
const_cast<FormulaGroupAreaListener*>(this)->mnGroupLen = static_cast<SCROW>(nBlockSize);
// erAck: 2016-11-09T18:30+01:00 XXX This doesn't occur anymore, at
// least not in the original bug scenario (insert a column before H on
// sheet w) of tdf#89957 with
// http://bugs.documentfoundation.org/attachment.cgi?id=114042
// Apparently this was fixed in the meantime, let's assume and get the
// assert bat out to hit us if it wasn't.
assert(!"something is still messing up the formula group and block size length");
}
ScFormulaCell* const * ppEnd = pp + mnGroupLen;
if (mbStartFixed)
{
if (mbEndFixed)
{
// Both top and bottom row positions are absolute. Use the original range as-is.
SCROW nRefRow1 = maRange.aStart.Row();
SCROW nRefRow2 = maRange.aEnd.Row();
if (nRow2 < nRefRow1 || nRefRow2 < nRow1)
return;
rCells.insert(rCells.end(), pp, ppEnd);
}
else
{
// Only the end row is relative.
SCROW nRefRow1 = maRange.aStart.Row();
SCROW nRefRow2 = maRange.aEnd.Row();
SCROW nMaxRefRow = nRefRow2 + mnGroupLen - 1;
if (nRow2 < nRefRow1 || nMaxRefRow < nRow1)
return;
if (nRefRow2 < nRow1)
{
// Skip ahead to the first hit.
SCROW nSkip = nRow1 - nRefRow2;
pp += nSkip;
nRefRow2 += nSkip;
}
assert(nRow1 <= nRefRow2);
// Notify the first hit cell and all subsequent ones.
rCells.insert(rCells.end(), pp, ppEnd);
}
}
else if (mbEndFixed)
{
// Only the start row is relative.
SCROW nRefRow1 = maRange.aStart.Row();
SCROW nRefRow2 = maRange.aEnd.Row();
if (nRow2 < nRefRow1 || nRefRow2 < nRow1)
return;
for (; pp != ppEnd && nRefRow1 <= nRefRow2; ++pp, ++nRefRow1)
rCells.push_back(*pp);
}
else
{
// Both top and bottom row positions are relative.
SCROW nRefRow1 = maRange.aStart.Row();
SCROW nRefRow2 = maRange.aEnd.Row();
SCROW nMaxRefRow = nRefRow2 + mnGroupLen - 1;
if (nMaxRefRow < nRow1)
return;
if (nRefRow2 < nRow1)
{
// The ref row range is above the changed row span. Skip ahead.
SCROW nSkip = nRow1 - nRefRow2;
pp += nSkip;
nRefRow1 += nSkip;
nRefRow2 += nSkip;
}
// At this point the initial ref row range should be overlapping the
// dirty cell range.
assert(nRow1 <= nRefRow2);
// Keep sliding down until the top ref row position is below the
// bottom row of the dirty cell range.
for (; pp != ppEnd && nRefRow1 <= nRow2; ++pp, ++nRefRow1, ++nRefRow2)
rCells.push_back(*pp);
}
}
const ScFormulaCell* FormulaGroupAreaListener::getTopCell() const
{
size_t nBlockSize = 0;
const ScFormulaCell* const * pp = mpColumn->GetFormulaCellBlockAddress( mnTopCellRow, nBlockSize);
SAL_WARN_IF(!pp, "sc.core.grouparealistener", "GetFormulaCellBlockAddress not found");
return pp ? *pp : nullptr;
}
void FormulaGroupAreaListener::notifyCellChange( const SfxHint& rHint, const ScAddress& rPos, SCROW nNumRows )
{
// Determine which formula cells within the group need to be notified of this change.
std::vector<ScFormulaCell*> aCells;
collectFormulaCells(rPos.Tab(), rPos.Col(), rPos.Row(), rPos.Row() + (nNumRows - 1), aCells);
std::for_each(aCells.begin(), aCells.end(), Notifier(rHint));
}
}
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
↑ V547 Expression is always false.