summaryrefslogtreecommitdiffstats
path: root/sc/source/core/tool/sharedformula.cxx
diff options
context:
space:
mode:
Diffstat (limited to 'sc/source/core/tool/sharedformula.cxx')
-rw-r--r--sc/source/core/tool/sharedformula.cxx442
1 files changed, 442 insertions, 0 deletions
diff --git a/sc/source/core/tool/sharedformula.cxx b/sc/source/core/tool/sharedformula.cxx
new file mode 100644
index 000000000..7680aac40
--- /dev/null
+++ b/sc/source/core/tool/sharedformula.cxx
@@ -0,0 +1,442 @@
+/* -*- 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 <sharedformula.hxx>
+#include <calcmacros.hxx>
+#include <tokenarray.hxx>
+#include <listenercontext.hxx>
+#include <document.hxx>
+#include <grouparealistener.hxx>
+#include <refdata.hxx>
+
+namespace sc {
+
+const ScFormulaCell* SharedFormulaUtil::getSharedTopFormulaCell(const CellStoreType::position_type& aPos)
+{
+ if (aPos.first->type != sc::element_type_formula)
+ // Not a formula cell block.
+ return nullptr;
+
+ sc::formula_block::iterator it = sc::formula_block::begin(*aPos.first->data);
+ std::advance(it, aPos.second);
+ const ScFormulaCell* pCell = *it;
+ if (!pCell->IsShared())
+ // Not a shared formula.
+ return nullptr;
+
+ return pCell->GetCellGroup()->mpTopCell;
+}
+
+bool SharedFormulaUtil::splitFormulaCellGroup(const CellStoreType::position_type& aPos, sc::EndListeningContext* pCxt)
+{
+ SCROW nRow = aPos.first->position + aPos.second;
+
+ if (aPos.first->type != sc::element_type_formula)
+ // Not a formula cell block.
+ return false;
+
+ if (aPos.second == 0)
+ // Split position coincides with the block border. Nothing to do.
+ return false;
+
+ sc::formula_block::iterator it = sc::formula_block::begin(*aPos.first->data);
+ std::advance(it, aPos.second);
+ ScFormulaCell& rTop = **it;
+ if (!rTop.IsShared())
+ // Not a shared formula.
+ return false;
+
+ if (nRow == rTop.GetSharedTopRow())
+ // Already the top cell of a shared group.
+ return false;
+
+ ScFormulaCellGroupRef xGroup = rTop.GetCellGroup();
+
+ SCROW nLength2 = xGroup->mpTopCell->aPos.Row() + xGroup->mnLength - nRow;
+ ScFormulaCellGroupRef xGroup2;
+ if (nLength2 > 1)
+ {
+ xGroup2.reset(new ScFormulaCellGroup);
+ xGroup2->mbInvariant = xGroup->mbInvariant;
+ xGroup2->mpTopCell = &rTop;
+ xGroup2->mnLength = nLength2;
+ xGroup2->mpCode = xGroup->mpCode->CloneValue();
+ }
+
+ xGroup->mnLength = nRow - xGroup->mpTopCell->aPos.Row();
+ ScFormulaCell& rPrevTop = *sc::formula_block::at(*aPos.first->data, aPos.second - xGroup->mnLength);
+
+#if USE_FORMULA_GROUP_LISTENER
+ // At least group area listeners will have to be adapted. As long as
+ // there's no update mechanism and no separated handling of group area and
+ // other listeners, all listeners of this group's top cell are to be reset.
+ if (nLength2)
+ {
+ // If a context exists it has to be used to not interfere with
+ // ScColumn::maBroadcasters iterators, which the EndListeningTo()
+ // without context would do when removing a broadcaster that had its
+ // last listener removed.
+ if (pCxt)
+ rPrevTop.EndListeningTo(*pCxt);
+ else
+ rPrevTop.EndListeningTo( rPrevTop.GetDocument(), nullptr, ScAddress( ScAddress::UNINITIALIZED));
+ rPrevTop.SetNeedsListening(true);
+
+ // The new group or remaining single cell needs a new listening.
+ rTop.SetNeedsListening(true);
+ }
+#endif
+
+ if (xGroup->mnLength == 1)
+ {
+ // The top group consists of only one cell. Ungroup this.
+ ScFormulaCellGroupRef xNone;
+ rPrevTop.SetCellGroup(xNone);
+ }
+
+ // Apply the lower group object to the lower cells.
+ assert ((xGroup2 == nullptr || xGroup2->mpTopCell->aPos.Row() + size_t(xGroup2->mnLength) <= aPos.first->position + aPos.first->size)
+ && "Shared formula region goes beyond the formula block.");
+ sc::formula_block::iterator itEnd = it;
+ std::advance(itEnd, nLength2);
+ for (; it != itEnd; ++it)
+ {
+ ScFormulaCell& rCell = **it;
+ rCell.SetCellGroup(xGroup2);
+ }
+
+ return true;
+}
+
+bool SharedFormulaUtil::splitFormulaCellGroups(const ScDocument& rDoc, CellStoreType& rCells, std::vector<SCROW>& rBounds)
+{
+ if (rBounds.empty())
+ return false;
+
+ // Sort and remove duplicates.
+ std::sort(rBounds.begin(), rBounds.end());
+ std::vector<SCROW>::iterator it = std::unique(rBounds.begin(), rBounds.end());
+ rBounds.erase(it, rBounds.end());
+
+ it = rBounds.begin();
+ SCROW nRow = *it;
+ CellStoreType::position_type aPos = rCells.position(nRow);
+ if (aPos.first == rCells.end())
+ return false;
+
+ bool bSplit = splitFormulaCellGroup(aPos, nullptr);
+ std::vector<SCROW>::iterator itEnd = rBounds.end();
+ for (++it; it != itEnd; ++it)
+ {
+ nRow = *it;
+ if (rDoc.ValidRow(nRow))
+ {
+ aPos = rCells.position(aPos.first, nRow);
+ if (aPos.first == rCells.end())
+ return bSplit;
+ bSplit |= splitFormulaCellGroup(aPos, nullptr);
+ }
+ }
+ return bSplit;
+}
+
+bool SharedFormulaUtil::joinFormulaCells(
+ const CellStoreType::position_type& rPos, ScFormulaCell& rCell1, ScFormulaCell& rCell2 )
+{
+ if( rCell1.GetDocument().IsDelayedFormulaGrouping())
+ {
+ rCell1.GetDocument().AddDelayedFormulaGroupingCell( &rCell1 );
+ rCell1.GetDocument().AddDelayedFormulaGroupingCell( &rCell2 );
+ return false;
+ }
+
+ ScFormulaCell::CompareState eState = rCell1.CompareByTokenArray(rCell2);
+ if (eState == ScFormulaCell::NotEqual)
+ return false;
+
+ // Formula tokens equal those of the previous formula cell.
+ ScFormulaCellGroupRef xGroup1 = rCell1.GetCellGroup();
+ ScFormulaCellGroupRef xGroup2 = rCell2.GetCellGroup();
+ if (xGroup1)
+ {
+ if (xGroup2)
+ {
+ // Both cell 1 and cell 2 are shared. Merge them together.
+ if (xGroup1.get() == xGroup2.get())
+ // They belong to the same group.
+ return false;
+
+ // Set the group object from cell 1 to all cells in group 2.
+ xGroup1->mnLength += xGroup2->mnLength;
+ size_t nOffset = rPos.second + 1; // position of cell 2
+ for (size_t i = 0, n = xGroup2->mnLength; i < n; ++i)
+ {
+ ScFormulaCell& rCell = *sc::formula_block::at(*rPos.first->data, nOffset+i);
+ rCell.SetCellGroup(xGroup1);
+ }
+ }
+ else
+ {
+ // cell 1 is shared but cell 2 is not.
+ rCell2.SetCellGroup(xGroup1);
+ ++xGroup1->mnLength;
+ }
+ }
+ else
+ {
+ if (xGroup2)
+ {
+ // cell 1 is not shared, but cell 2 is already shared.
+ rCell1.SetCellGroup(xGroup2);
+ xGroup2->mpTopCell = &rCell1;
+ ++xGroup2->mnLength;
+ }
+ else
+ {
+ // neither cells are shared.
+ assert(rCell1.aPos.Row() == static_cast<SCROW>(rPos.first->position + rPos.second));
+ xGroup1 = rCell1.CreateCellGroup(2, eState == ScFormulaCell::EqualInvariant);
+ rCell2.SetCellGroup(xGroup1);
+ }
+ }
+
+ return true;
+}
+
+bool SharedFormulaUtil::joinFormulaCellAbove( const CellStoreType::position_type& aPos )
+{
+ if (aPos.first->type != sc::element_type_formula)
+ // This is not a formula cell.
+ return false;
+
+ if (aPos.second == 0)
+ // This cell is already the top cell in a formula block; the previous
+ // cell is not a formula cell.
+ return false;
+
+ ScFormulaCell& rPrev = *sc::formula_block::at(*aPos.first->data, aPos.second-1);
+ ScFormulaCell& rCell = *sc::formula_block::at(*aPos.first->data, aPos.second);
+ sc::CellStoreType::position_type aPosPrev = aPos;
+ --aPosPrev.second;
+ return joinFormulaCells(aPosPrev, rPrev, rCell);
+}
+
+void SharedFormulaUtil::unshareFormulaCell(const CellStoreType::position_type& aPos, ScFormulaCell& rCell)
+{
+ if (!rCell.IsShared())
+ return;
+
+ ScFormulaCellGroupRef xNone;
+ sc::CellStoreType::iterator it = aPos.first;
+
+ // This formula cell is shared. Adjust the shared group.
+ if (rCell.aPos.Row() == rCell.GetSharedTopRow())
+ {
+ // Top of the shared range.
+ const ScFormulaCellGroupRef& xGroup = rCell.GetCellGroup();
+ if (xGroup->mnLength == 2)
+ {
+ // Group consists of only two cells. Mark the second one non-shared.
+ assert (aPos.second+1 < aPos.first->size
+ && "There is no next formula cell but there should be!");
+ ScFormulaCell& rNext = *sc::formula_block::at(*it->data, aPos.second+1);
+ rNext.SetCellGroup(xNone);
+ }
+ else
+ {
+ // Move the top cell to the next formula cell down.
+ ScFormulaCell& rNext = *sc::formula_block::at(*it->data, aPos.second+1);
+ xGroup->mpTopCell = &rNext;
+ }
+ --xGroup->mnLength;
+ }
+ else if (rCell.aPos.Row() == rCell.GetSharedTopRow() + rCell.GetSharedLength() - 1)
+ {
+ // Bottom of the shared range.
+ const ScFormulaCellGroupRef& xGroup = rCell.GetCellGroup();
+ if (xGroup->mnLength == 2)
+ {
+ // Mark the top cell non-shared.
+ assert(aPos.second != 0 && "There is no previous formula cell but there should be!");
+ ScFormulaCell& rPrev = *sc::formula_block::at(*it->data, aPos.second-1);
+ rPrev.SetCellGroup(xNone);
+ }
+ else
+ {
+ // Just shorten the shared range length by one.
+ --xGroup->mnLength;
+ }
+ }
+ else
+ {
+ // In the middle of the shared range. Split it into two groups.
+ ScFormulaCellGroupRef xGroup = rCell.GetCellGroup();
+ SCROW nEndRow = xGroup->mpTopCell->aPos.Row() + xGroup->mnLength - 1;
+ xGroup->mnLength = rCell.aPos.Row() - xGroup->mpTopCell->aPos.Row(); // Shorten the top group.
+ if (xGroup->mnLength == 1)
+ {
+ // Make the top cell non-shared.
+ assert(aPos.second != 0 && "There is no previous formula cell but there should be!");
+ ScFormulaCell& rPrev = *sc::formula_block::at(*it->data, aPos.second-1);
+ rPrev.SetCellGroup(xNone);
+ }
+
+ SCROW nLength2 = nEndRow - rCell.aPos.Row();
+ if (nLength2 >= 2)
+ {
+ ScFormulaCellGroupRef xGroup2;
+ xGroup2.reset(new ScFormulaCellGroup);
+ ScFormulaCell& rNext = *sc::formula_block::at(*it->data, aPos.second+1);
+ xGroup2->mpTopCell = &rNext;
+ xGroup2->mnLength = nLength2;
+ xGroup2->mbInvariant = xGroup->mbInvariant;
+ xGroup2->mpCode = xGroup->mpCode->CloneValue();
+ assert(xGroup2->mpTopCell->aPos.Row() + size_t(xGroup2->mnLength) <= it->position + it->size
+ && "Shared formula region goes beyond the formula block.");
+ sc::formula_block::iterator itCell = sc::formula_block::begin(*it->data);
+ std::advance(itCell, aPos.second+1);
+ sc::formula_block::iterator itCellEnd = itCell;
+ std::advance(itCellEnd, xGroup2->mnLength);
+ for (; itCell != itCellEnd; ++itCell)
+ {
+ ScFormulaCell& rCell2 = **itCell;
+ rCell2.SetCellGroup(xGroup2);
+ }
+ }
+ else
+ {
+ // Make the next cell non-shared.
+ sc::formula_block::iterator itCell = sc::formula_block::begin(*it->data);
+ std::advance(itCell, aPos.second+1);
+ ScFormulaCell& rCell2 = **itCell;
+ rCell2.SetCellGroup(xNone);
+ }
+ }
+
+ rCell.SetCellGroup(xNone);
+}
+
+void SharedFormulaUtil::unshareFormulaCells(const ScDocument& rDoc, CellStoreType& rCells, std::vector<SCROW>& rRows)
+{
+ if (rRows.empty())
+ return;
+
+ // Sort and remove duplicates.
+ std::sort(rRows.begin(), rRows.end());
+ rRows.erase(std::unique(rRows.begin(), rRows.end()), rRows.end());
+
+ // Add next cell positions to the list (to ensure that each position becomes a single cell).
+ std::vector<SCROW> aRows2;
+ for (const auto& rRow : rRows)
+ {
+ if (rRow > rDoc.MaxRow())
+ break;
+
+ aRows2.push_back(rRow);
+
+ if (rRow < rDoc.MaxRow())
+ aRows2.push_back(rRow+1);
+ }
+
+ // Remove duplicates again (the vector should still be sorted).
+ aRows2.erase(std::unique(aRows2.begin(), aRows2.end()), aRows2.end());
+
+ splitFormulaCellGroups(rDoc, rCells, aRows2);
+}
+
+void SharedFormulaUtil::startListeningAsGroup( sc::StartListeningContext& rCxt, ScFormulaCell** ppSharedTop )
+{
+ ScFormulaCell& rTopCell = **ppSharedTop;
+ assert(rTopCell.IsSharedTop());
+
+#if USE_FORMULA_GROUP_LISTENER
+ ScDocument& rDoc = rCxt.getDoc();
+ rDoc.SetDetectiveDirty(true);
+
+ ScFormulaCellGroupRef xGroup = rTopCell.GetCellGroup();
+ const ScTokenArray& rCode = *xGroup->mpCode;
+ assert(&rCode == rTopCell.GetCode());
+ if (rCode.IsRecalcModeAlways())
+ {
+ rDoc.StartListeningArea(
+ BCA_LISTEN_ALWAYS, false,
+ xGroup->getAreaListener(ppSharedTop, BCA_LISTEN_ALWAYS, true, true));
+ }
+
+ formula::FormulaToken** p = rCode.GetCode();
+ formula::FormulaToken** pEnd = p + rCode.GetCodeLen();
+ for (; p != pEnd; ++p)
+ {
+ const formula::FormulaToken* t = *p;
+ switch (t->GetType())
+ {
+ case formula::svSingleRef:
+ {
+ const ScSingleRefData* pRef = t->GetSingleRef();
+ ScAddress aPos = pRef->toAbs(rDoc, rTopCell.aPos);
+ ScFormulaCell** pp = ppSharedTop;
+ ScFormulaCell** ppEnd = ppSharedTop + xGroup->mnLength;
+ for (; pp != ppEnd; ++pp)
+ {
+ if (!aPos.IsValid())
+ break;
+
+ rDoc.StartListeningCell(rCxt, aPos, **pp);
+ if (pRef->IsRowRel())
+ aPos.IncRow();
+ }
+ }
+ break;
+ case formula::svDoubleRef:
+ {
+ const ScSingleRefData& rRef1 = *t->GetSingleRef();
+ const ScSingleRefData& rRef2 = *t->GetSingleRef2();
+ ScAddress aPos1 = rRef1.toAbs(rDoc, rTopCell.aPos);
+ ScAddress aPos2 = rRef2.toAbs(rDoc, rTopCell.aPos);
+
+ ScRange aOrigRange(aPos1, aPos2);
+ ScRange aListenedRange = aOrigRange;
+ if (rRef2.IsRowRel())
+ aListenedRange.aEnd.IncRow(xGroup->mnLength-1);
+
+ if (aPos1.IsValid() && aPos2.IsValid())
+ {
+ rDoc.StartListeningArea(
+ aListenedRange, true,
+ xGroup->getAreaListener(ppSharedTop, aOrigRange, !rRef1.IsRowRel(), !rRef2.IsRowRel()));
+ }
+ }
+ break;
+ default:
+ ;
+ }
+ }
+
+ ScFormulaCell** pp = ppSharedTop;
+ ScFormulaCell** ppEnd = ppSharedTop + xGroup->mnLength;
+ for (; pp != ppEnd; ++pp)
+ {
+ ScFormulaCell& rCell = **pp;
+ rCell.SetNeedsListening(false);
+ }
+
+#else
+ ScFormulaCell** pp = ppSharedTop;
+ ScFormulaCell** ppEnd = ppSharedTop + rTopCell.GetSharedLength();
+ for (; pp != ppEnd; ++pp)
+ {
+ ScFormulaCell& rFC = **pp;
+ rFC.StartListeningTo(rCxt);
+ }
+#endif
+}
+
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */