LibreOffice Module sc (master)  1
sharedformula.cxx
Go to the documentation of this file.
1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 /*
3  * This file is part of the LibreOffice project.
4  *
5  * This Source Code Form is subject to the terms of the Mozilla Public
6  * License, v. 2.0. If a copy of the MPL was not distributed with this
7  * file, You can obtain one at http://mozilla.org/MPL/2.0/.
8  */
9 
10 #include <sharedformula.hxx>
11 #include <calcmacros.hxx>
12 #include <tokenarray.hxx>
13 #include <listenercontext.hxx>
14 #include <document.hxx>
15 #include <grouparealistener.hxx>
16 #include <refdata.hxx>
17 
18 namespace sc {
19 
20 const ScFormulaCell* SharedFormulaUtil::getSharedTopFormulaCell(const CellStoreType::position_type& aPos)
21 {
22  if (aPos.first->type != sc::element_type_formula)
23  // Not a formula cell block.
24  return nullptr;
25 
26  sc::formula_block::iterator it = sc::formula_block::begin(*aPos.first->data);
27  std::advance(it, aPos.second);
28  const ScFormulaCell* pCell = *it;
29  if (!pCell->IsShared())
30  // Not a shared formula.
31  return nullptr;
32 
33  return pCell->GetCellGroup()->mpTopCell;
34 }
35 
36 bool SharedFormulaUtil::splitFormulaCellGroup(const CellStoreType::position_type& aPos, sc::EndListeningContext* pCxt)
37 {
38  SCROW nRow = aPos.first->position + aPos.second;
39 
40  if (aPos.first->type != sc::element_type_formula)
41  // Not a formula cell block.
42  return false;
43 
44  if (aPos.second == 0)
45  // Split position coincides with the block border. Nothing to do.
46  return false;
47 
48  sc::formula_block::iterator it = sc::formula_block::begin(*aPos.first->data);
49  std::advance(it, aPos.second);
50  ScFormulaCell& rTop = **it;
51  if (!rTop.IsShared())
52  // Not a shared formula.
53  return false;
54 
55  if (nRow == rTop.GetSharedTopRow())
56  // Already the top cell of a shared group.
57  return false;
58 
59  ScFormulaCellGroupRef xGroup = rTop.GetCellGroup();
60 
61  SCROW nLength2 = xGroup->mpTopCell->aPos.Row() + xGroup->mnLength - nRow;
62  ScFormulaCellGroupRef xGroup2;
63  if (nLength2 > 1)
64  {
65  xGroup2.reset(new ScFormulaCellGroup);
66  xGroup2->mbInvariant = xGroup->mbInvariant;
67  xGroup2->mpTopCell = &rTop;
68  xGroup2->mnLength = nLength2;
69  xGroup2->mpCode = xGroup->mpCode->CloneValue();
70  }
71 
72  xGroup->mnLength = nRow - xGroup->mpTopCell->aPos.Row();
73  ScFormulaCell& rPrevTop = *sc::formula_block::at(*aPos.first->data, aPos.second - xGroup->mnLength);
74 
75 #if USE_FORMULA_GROUP_LISTENER
76  // At least group area listeners will have to be adapted. As long as
77  // there's no update mechanism and no separated handling of group area and
78  // other listeners, all listeners of this group's top cell are to be reset.
79  if (nLength2)
80  {
81  // If a context exists it has to be used to not interfere with
82  // ScColumn::maBroadcasters iterators, which the EndListeningTo()
83  // without context would do when removing a broadcaster that had its
84  // last listener removed.
85  if (pCxt)
86  rPrevTop.EndListeningTo(*pCxt);
87  else
88  rPrevTop.EndListeningTo( rPrevTop.GetDocument(), nullptr, ScAddress( ScAddress::UNINITIALIZED));
89  rPrevTop.SetNeedsListening(true);
90 
91  // The new group or remaining single cell needs a new listening.
92  rTop.SetNeedsListening(true);
93  }
94 #endif
95 
96  if (xGroup->mnLength == 1)
97  {
98  // The top group consists of only one cell. Ungroup this.
100  rPrevTop.SetCellGroup(xNone);
101  }
102 
103  // Apply the lower group object to the lower cells.
104  assert ((xGroup2 == nullptr || xGroup2->mpTopCell->aPos.Row() + size_t(xGroup2->mnLength) <= aPos.first->position + aPos.first->size)
105  && "Shared formula region goes beyond the formula block.");
106  sc::formula_block::iterator itEnd = it;
107  std::advance(itEnd, nLength2);
108  for (; it != itEnd; ++it)
109  {
110  ScFormulaCell& rCell = **it;
111  rCell.SetCellGroup(xGroup2);
112  }
113 
114  return true;
115 }
116 
117 bool SharedFormulaUtil::splitFormulaCellGroups(const ScDocument& rDoc, CellStoreType& rCells, std::vector<SCROW>& rBounds)
118 {
119  if (rBounds.empty())
120  return false;
121 
122  // Sort and remove duplicates.
123  std::sort(rBounds.begin(), rBounds.end());
124  std::vector<SCROW>::iterator it = std::unique(rBounds.begin(), rBounds.end());
125  rBounds.erase(it, rBounds.end());
126 
127  it = rBounds.begin();
128  SCROW nRow = *it;
129  CellStoreType::position_type aPos = rCells.position(nRow);
130  if (aPos.first == rCells.end())
131  return false;
132 
133  bool bSplit = splitFormulaCellGroup(aPos, nullptr);
134  std::vector<SCROW>::iterator itEnd = rBounds.end();
135  for (++it; it != itEnd; ++it)
136  {
137  nRow = *it;
138  if (rDoc.ValidRow(nRow))
139  {
140  aPos = rCells.position(aPos.first, nRow);
141  if (aPos.first == rCells.end())
142  return bSplit;
143  bSplit |= splitFormulaCellGroup(aPos, nullptr);
144  }
145  }
146  return bSplit;
147 }
148 
150  const CellStoreType::position_type& rPos, ScFormulaCell& rCell1, ScFormulaCell& rCell2 )
151 {
152  if( rCell1.GetDocument().IsDelayedFormulaGrouping())
153  {
154  rCell1.GetDocument().AddDelayedFormulaGroupingCell( &rCell1 );
155  rCell1.GetDocument().AddDelayedFormulaGroupingCell( &rCell2 );
156  return false;
157  }
158 
159  ScFormulaCell::CompareState eState = rCell1.CompareByTokenArray(rCell2);
160  if (eState == ScFormulaCell::NotEqual)
161  return false;
162 
163  // Formula tokens equal those of the previous formula cell.
164  ScFormulaCellGroupRef xGroup1 = rCell1.GetCellGroup();
165  ScFormulaCellGroupRef xGroup2 = rCell2.GetCellGroup();
166  if (xGroup1)
167  {
168  if (xGroup2)
169  {
170  // Both cell 1 and cell 2 are shared. Merge them together.
171  if (xGroup1.get() == xGroup2.get())
172  // They belong to the same group.
173  return false;
174 
175  // Set the group object from cell 1 to all cells in group 2.
176  xGroup1->mnLength += xGroup2->mnLength;
177  size_t nOffset = rPos.second + 1; // position of cell 2
178  for (size_t i = 0, n = xGroup2->mnLength; i < n; ++i)
179  {
180  ScFormulaCell& rCell = *sc::formula_block::at(*rPos.first->data, nOffset+i);
181  rCell.SetCellGroup(xGroup1);
182  }
183  }
184  else
185  {
186  // cell 1 is shared but cell 2 is not.
187  rCell2.SetCellGroup(xGroup1);
188  ++xGroup1->mnLength;
189  }
190  }
191  else
192  {
193  if (xGroup2)
194  {
195  // cell 1 is not shared, but cell 2 is already shared.
196  rCell1.SetCellGroup(xGroup2);
197  xGroup2->mpTopCell = &rCell1;
198  ++xGroup2->mnLength;
199  }
200  else
201  {
202  // neither cells are shared.
203  assert(rCell1.aPos.Row() == static_cast<SCROW>(rPos.first->position + rPos.second));
204  xGroup1 = rCell1.CreateCellGroup(2, eState == ScFormulaCell::EqualInvariant);
205  rCell2.SetCellGroup(xGroup1);
206  }
207  }
208 
209  return true;
210 }
211 
212 bool SharedFormulaUtil::joinFormulaCellAbove( const CellStoreType::position_type& aPos )
213 {
214  if (aPos.first->type != sc::element_type_formula)
215  // This is not a formula cell.
216  return false;
217 
218  if (aPos.second == 0)
219  // This cell is already the top cell in a formula block; the previous
220  // cell is not a formula cell.
221  return false;
222 
223  ScFormulaCell& rPrev = *sc::formula_block::at(*aPos.first->data, aPos.second-1);
224  ScFormulaCell& rCell = *sc::formula_block::at(*aPos.first->data, aPos.second);
225  sc::CellStoreType::position_type aPosPrev = aPos;
226  --aPosPrev.second;
227  return joinFormulaCells(aPosPrev, rPrev, rCell);
228 }
229 
230 void SharedFormulaUtil::unshareFormulaCell(const CellStoreType::position_type& aPos, ScFormulaCell& rCell)
231 {
232  if (!rCell.IsShared())
233  return;
234 
235  ScFormulaCellGroupRef xNone;
236  sc::CellStoreType::iterator it = aPos.first;
237 
238  // This formula cell is shared. Adjust the shared group.
239  if (rCell.aPos.Row() == rCell.GetSharedTopRow())
240  {
241  // Top of the shared range.
242  const ScFormulaCellGroupRef& xGroup = rCell.GetCellGroup();
243  if (xGroup->mnLength == 2)
244  {
245  // Group consists of only two cells. Mark the second one non-shared.
246  assert (aPos.second+1 < aPos.first->size
247  && "There is no next formula cell but there should be!");
248  ScFormulaCell& rNext = *sc::formula_block::at(*it->data, aPos.second+1);
249  rNext.SetCellGroup(xNone);
250  }
251  else
252  {
253  // Move the top cell to the next formula cell down.
254  ScFormulaCell& rNext = *sc::formula_block::at(*it->data, aPos.second+1);
255  xGroup->mpTopCell = &rNext;
256  }
257  --xGroup->mnLength;
258  }
259  else if (rCell.aPos.Row() == rCell.GetSharedTopRow() + rCell.GetSharedLength() - 1)
260  {
261  // Bottom of the shared range.
262  const ScFormulaCellGroupRef& xGroup = rCell.GetCellGroup();
263  if (xGroup->mnLength == 2)
264  {
265  // Mark the top cell non-shared.
266  assert(aPos.second != 0 && "There is no previous formula cell but there should be!");
267  ScFormulaCell& rPrev = *sc::formula_block::at(*it->data, aPos.second-1);
268  rPrev.SetCellGroup(xNone);
269  }
270  else
271  {
272  // Just shorten the shared range length by one.
273  --xGroup->mnLength;
274  }
275  }
276  else
277  {
278  // In the middle of the shared range. Split it into two groups.
279  ScFormulaCellGroupRef xGroup = rCell.GetCellGroup();
280  SCROW nEndRow = xGroup->mpTopCell->aPos.Row() + xGroup->mnLength - 1;
281  xGroup->mnLength = rCell.aPos.Row() - xGroup->mpTopCell->aPos.Row(); // Shorten the top group.
282  if (xGroup->mnLength == 1)
283  {
284  // Make the top cell non-shared.
285  assert(aPos.second != 0 && "There is no previous formula cell but there should be!");
286  ScFormulaCell& rPrev = *sc::formula_block::at(*it->data, aPos.second-1);
287  rPrev.SetCellGroup(xNone);
288  }
289 
290  SCROW nLength2 = nEndRow - rCell.aPos.Row();
291  if (nLength2 >= 2)
292  {
293  ScFormulaCellGroupRef xGroup2;
294  xGroup2.reset(new ScFormulaCellGroup);
295  ScFormulaCell& rNext = *sc::formula_block::at(*it->data, aPos.second+1);
296  xGroup2->mpTopCell = &rNext;
297  xGroup2->mnLength = nLength2;
298  xGroup2->mbInvariant = xGroup->mbInvariant;
299  xGroup2->mpCode = xGroup->mpCode->CloneValue();
300  assert(xGroup2->mpTopCell->aPos.Row() + size_t(xGroup2->mnLength) <= it->position + it->size
301  && "Shared formula region goes beyond the formula block.");
302  sc::formula_block::iterator itCell = sc::formula_block::begin(*it->data);
303  std::advance(itCell, aPos.second+1);
304  sc::formula_block::iterator itCellEnd = itCell;
305  std::advance(itCellEnd, xGroup2->mnLength);
306  for (; itCell != itCellEnd; ++itCell)
307  {
308  ScFormulaCell& rCell2 = **itCell;
309  rCell2.SetCellGroup(xGroup2);
310  }
311  }
312  else
313  {
314  // Make the next cell non-shared.
315  sc::formula_block::iterator itCell = sc::formula_block::begin(*it->data);
316  std::advance(itCell, aPos.second+1);
317  ScFormulaCell& rCell2 = **itCell;
318  rCell2.SetCellGroup(xNone);
319  }
320  }
321 
322  rCell.SetCellGroup(xNone);
323 }
324 
325 void SharedFormulaUtil::unshareFormulaCells(const ScDocument& rDoc, CellStoreType& rCells, std::vector<SCROW>& rRows)
326 {
327  if (rRows.empty())
328  return;
329 
330  // Sort and remove duplicates.
331  std::sort(rRows.begin(), rRows.end());
332  rRows.erase(std::unique(rRows.begin(), rRows.end()), rRows.end());
333 
334  // Add next cell positions to the list (to ensure that each position becomes a single cell).
335  std::vector<SCROW> aRows2;
336  for (const auto& rRow : rRows)
337  {
338  if (rRow > rDoc.MaxRow())
339  break;
340 
341  aRows2.push_back(rRow);
342 
343  if (rRow < rDoc.MaxRow())
344  aRows2.push_back(rRow+1);
345  }
346 
347  // Remove duplicates again (the vector should still be sorted).
348  aRows2.erase(std::unique(aRows2.begin(), aRows2.end()), aRows2.end());
349 
350  splitFormulaCellGroups(rDoc, rCells, aRows2);
351 }
352 
354 {
355  ScFormulaCell& rTopCell = **ppSharedTop;
356  assert(rTopCell.IsSharedTop());
357 
358 #if USE_FORMULA_GROUP_LISTENER
359  ScDocument& rDoc = rCxt.getDoc();
360  rDoc.SetDetectiveDirty(true);
361 
362  ScFormulaCellGroupRef xGroup = rTopCell.GetCellGroup();
363  const ScTokenArray& rCode = *xGroup->mpCode;
364  assert(&rCode == rTopCell.GetCode());
365  if (rCode.IsRecalcModeAlways())
366  {
367  rDoc.StartListeningArea(
368  BCA_LISTEN_ALWAYS, false,
369  xGroup->getAreaListener(ppSharedTop, BCA_LISTEN_ALWAYS, true, true));
370  }
371 
372  formula::FormulaToken** p = rCode.GetCode();
373  formula::FormulaToken** pEnd = p + rCode.GetCodeLen();
374  for (; p != pEnd; ++p)
375  {
376  const formula::FormulaToken* t = *p;
377  switch (t->GetType())
378  {
380  {
381  const ScSingleRefData* pRef = t->GetSingleRef();
382  ScAddress aPos = pRef->toAbs(rDoc, rTopCell.aPos);
383  ScFormulaCell** pp = ppSharedTop;
384  ScFormulaCell** ppEnd = ppSharedTop + xGroup->mnLength;
385  for (; pp != ppEnd; ++pp)
386  {
387  if (!aPos.IsValid())
388  break;
389 
390  rDoc.StartListeningCell(rCxt, aPos, **pp);
391  if (pRef->IsRowRel())
392  aPos.IncRow();
393  }
394  }
395  break;
397  {
398  const ScSingleRefData& rRef1 = *t->GetSingleRef();
399  const ScSingleRefData& rRef2 = *t->GetSingleRef2();
400  ScAddress aPos1 = rRef1.toAbs(rDoc, rTopCell.aPos);
401  ScAddress aPos2 = rRef2.toAbs(rDoc, rTopCell.aPos);
402 
403  ScRange aOrigRange(aPos1, aPos2);
404  ScRange aListenedRange = aOrigRange;
405  if (rRef2.IsRowRel())
406  aListenedRange.aEnd.IncRow(xGroup->mnLength-1);
407 
408  if (aPos1.IsValid() && aPos2.IsValid())
409  {
410  rDoc.StartListeningArea(
411  aListenedRange, true,
412  xGroup->getAreaListener(ppSharedTop, aOrigRange, !rRef1.IsRowRel(), !rRef2.IsRowRel()));
413  }
414  }
415  break;
416  default:
417  ;
418  }
419  }
420 
421  ScFormulaCell** pp = ppSharedTop;
422  ScFormulaCell** ppEnd = ppSharedTop + xGroup->mnLength;
423  for (; pp != ppEnd; ++pp)
424  {
425  ScFormulaCell& rCell = **pp;
426  rCell.SetNeedsListening(false);
427  }
428 
429 #else
430  ScFormulaCell** pp = ppSharedTop;
431  ScFormulaCell** ppEnd = ppSharedTop + rTopCell.GetSharedLength();
432  for (; pp != ppEnd; ++pp)
433  {
434  ScFormulaCell& rFC = **pp;
435  rFC.StartListeningTo(rCxt);
436  }
437 #endif
438 }
439 
440 }
441 
442 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */
void AddDelayedFormulaGroupingCell(const ScFormulaCell *cell)
To be used only by SharedFormulaUtil::joinFormulaCells().
Definition: document10.cxx:395
::boost::intrusive_ptr< ScFormulaCellGroup > ScFormulaCellGroupRef
Definition: types.hxx:43
#define BCA_LISTEN_ALWAYS
Definition: address.hxx:945
SCROW Row() const
Definition: address.hxx:274
Single reference (one address) into the sheet.
Definition: refdata.hxx:29
bool IsRecalcModeAlways() const
SCROW GetSharedTopRow() const
ScAddress toAbs(const ScSheetLimits &rLimits, const ScAddress &rPos) const
Definition: refdata.cxx:193
ScTokenArray * GetCode()
sal_Int64 n
sal_uInt16 GetCodeLen() const
ScAddress aEnd
Definition: address.hxx:498
static void startListeningAsGroup(StartListeningContext &rCxt, ScFormulaCell **ppSharedTop)
Have all formula cells belonging to a group start listening to their references.
static const ScFormulaCell * getSharedTopFormulaCell(const CellStoreType::position_type &aPos)
Get shared formula top cell from position, if any, else nullptr.
ScDocument & GetDocument() const
const ScFormulaCellGroupRef & GetCellGroup() const
const mdds::mtv::element_t element_type_formula
Definition: mtvelements.hxx:50
SC_DLLPUBLIC SCROW MaxRow() const
Definition: document.hxx:891
void StartListeningTo(ScDocument &rDoc)
virtual const ScSingleRefData * GetSingleRef() const
static bool splitFormulaCellGroup(const CellStoreType::position_type &aPos, sc::EndListeningContext *pCxt)
Split existing shared formula range at specified position.
ScAddress aPos
void SetNeedsListening(bool bVar)
bool IsValid() const
Definition: address.hxx:305
SCROW GetSharedLength() const
bool IsShared() const
static bool joinFormulaCellAbove(const CellStoreType::position_type &aPos)
Merge with an existing formula group (if any) located immediately above if the cell at specified posi...
int i
CAUTION! The following defines must be in the same namespace as the respective type.
void SetCellGroup(const ScFormulaCellGroupRef &xRef)
void IncRow(SCROW nDelta=1)
Definition: address.hxx:312
bool IsSharedTop() const
bool IsRowRel() const
Definition: refdata.hxx:67
XPropertyListType t
bool IsDelayedFormulaGrouping() const
Definition: document.hxx:1420
static void unshareFormulaCell(const CellStoreType::position_type &aPos, ScFormulaCell &rCell)
Turn a shared formula cell into a non-shared one, and split it off from the adjacent formula cell gro...
sal_Int32 SCROW
Definition: types.hxx:17
mdds::mtv::soa::multi_type_vector< CellFunc, CellStoreTrait > CellStoreType
bool ValidRow(SCROW nRow) const
Definition: document.hxx:898
virtual const ScSingleRefData * GetSingleRef2() const
FormulaToken ** GetCode() const
void SetDetectiveDirty(bool bSet)
Definition: document.hxx:2204
static bool splitFormulaCellGroups(const ScDocument &rDoc, CellStoreType &rCells, std::vector< SCROW > &rBounds)
Split existing shared formula ranges at specified row positions.
void * p
CompareState CompareByTokenArray(const ScFormulaCell &rOther) const
ScFormulaCellGroupRef CreateCellGroup(SCROW nLen, bool bInvariant)
Turn a non-grouped cell into the top of a grouped cell.
StackVar GetType() const
void EndListeningTo(ScDocument &rDoc, ScTokenArray *pArr=nullptr, ScAddress aPos=ScAddress())
static bool joinFormulaCells(const CellStoreType::position_type &rPos, ScFormulaCell &rCell1, ScFormulaCell &rCell2)
See if two specified adjacent formula cells can be merged, and if they can, merge them into the same ...
static void unshareFormulaCells(const ScDocument &rDoc, CellStoreType &rCells, std::vector< SCROW > &rRows)
Make specified formula cells non-shared ones, and split them off from their respective adjacent formu...