summaryrefslogtreecommitdiffstats
path: root/sc/source/core/tool/grouparealistener.cxx
diff options
context:
space:
mode:
Diffstat (limited to 'sc/source/core/tool/grouparealistener.cxx')
-rw-r--r--sc/source/core/tool/grouparealistener.cxx352
1 files changed, 352 insertions, 0 deletions
diff --git a/sc/source/core/tool/grouparealistener.cxx b/sc/source/core/tool/grouparealistener.cxx
new file mode 100644
index 000000000..39b92625b
--- /dev/null
+++ b/sc/source/core/tool/grouparealistener.cxx
@@ -0,0 +1,352 @@
+/* -*- 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 ? "LISTEN-ALWAYS" : 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 )
+{
+ // BulkDataHint may include (SfxHintId::ScDataChanged |
+ // SfxHintId::ScTableOpDirty) so has to be checked first.
+ if ( const BulkDataHint* pBulkHint = dynamic_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 ? "LISTEN-ALWAYS" : 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 goup 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: */