LibreOffice Module sc (master)  1
grouparealistener.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 <grouparealistener.hxx>
11 #include <brdcst.hxx>
12 #include <formulacell.hxx>
13 #include <bulkdatahint.hxx>
14 #include <columnspanset.hxx>
15 #include <column.hxx>
16 #include <listenerquery.hxx>
17 #include <listenerqueryids.hxx>
18 #include <document.hxx>
19 #include <table.hxx>
20 
21 #include <o3tl/safeint.hxx>
22 #include <sal/log.hxx>
23 
24 namespace sc {
25 
26 namespace {
27 
28 class Notifier
29 {
30  const SfxHint& mrHint;
31 public:
32  explicit Notifier( const SfxHint& rHint ) : mrHint(rHint) {}
33 
34  void operator() ( ScFormulaCell* pCell )
35  {
36  pCell->Notify(mrHint);
37  }
38 };
39 
40 class CollectCellAction : public sc::ColumnSpanSet::ColumnAction
41 {
42  const FormulaGroupAreaListener& mrAreaListener;
43  ScAddress maPos;
44  std::vector<ScFormulaCell*> maCells;
45 
46 public:
47  explicit CollectCellAction( const FormulaGroupAreaListener& rAreaListener ) :
48  mrAreaListener(rAreaListener) {}
49 
50  virtual void startColumn( ScColumn* pCol ) override
51  {
52  maPos.SetTab(pCol->GetTab());
53  maPos.SetCol(pCol->GetCol());
54  }
55 
56  virtual void execute( SCROW nRow1, SCROW nRow2, bool bVal ) override
57  {
58  if (!bVal)
59  return;
60 
61  mrAreaListener.collectFormulaCells(maPos.Tab(), maPos.Col(), nRow1, nRow2, maCells);
62  };
63 
64  void swapCells( std::vector<ScFormulaCell*>& rCells )
65  {
66  // Remove duplicate before the swap. Take care to sort them by tab,col,row before sorting by pointer,
67  // as many calc algorithms perform better if cells are processed in this order.
68  std::sort(maCells.begin(), maCells.end(), [](const ScFormulaCell* cell1, const ScFormulaCell* cell2)
69  {
70  if( cell1->aPos != cell2->aPos )
71  return cell1->aPos < cell2->aPos;
72  return cell1 < cell2;
73  });
74  std::vector<ScFormulaCell*>::iterator it = std::unique(maCells.begin(), maCells.end());
75  maCells.erase(it, maCells.end());
76 
77  rCells.swap(maCells);
78  }
79 };
80 
81 }
82 
84  const ScAddress& rTopCellPos, SCROW nGroupLen, bool bStartFixed, bool bEndFixed ) :
85  maRange(rRange),
86  mrDocument(rDocument),
87  mpColumn(nullptr),
88  mnTopCellRow(rTopCellPos.Row()),
89  mnGroupLen(nGroupLen),
90  mbStartFixed(bStartFixed),
91  mbEndFixed(bEndFixed)
92 {
93  const ScTable* pTab = rDocument.FetchTable( rTopCellPos.Tab());
94  assert(pTab);
95  mpColumn = pTab->FetchColumn( rTopCellPos.Col());
96  assert(mpColumn);
97  SAL_INFO( "sc.core.grouparealistener",
98  "FormulaGroupAreaListener ctor this " << this <<
99  " range " << (maRange == BCA_LISTEN_ALWAYS ? "LISTEN-ALWAYS" : maRange.Format(mrDocument, ScRefFlags::VALID)) <<
100  " mnTopCellRow " << mnTopCellRow << " length " << mnGroupLen <<
101  ", col/tab " << mpColumn->GetCol() << "/" << mpColumn->GetTab());
102 }
103 
105 {
106  SAL_INFO( "sc.core.grouparealistener",
107  "FormulaGroupAreaListener dtor this " << this);
108 }
109 
111 {
112  ScRange aRet = maRange;
113  if (!mbEndFixed)
114  aRet.aEnd.IncRow(mnGroupLen-1);
115  return aRet;
116 }
117 
119 {
120  // BulkDataHint may include (SfxHintId::ScDataChanged |
121  // SfxHintId::ScTableOpDirty) so has to be checked first.
122  if ( const BulkDataHint* pBulkHint = dynamic_cast<const BulkDataHint*>(&rHint) )
123  {
124  notifyBulkChange(*pBulkHint);
125  }
126  else if (rHint.GetId() == SfxHintId::ScDataChanged || rHint.GetId() == SfxHintId::ScTableOpDirty)
127  {
128  const ScHint& rScHint = static_cast<const ScHint&>(rHint);
129  notifyCellChange(rHint, rScHint.GetStartAddress(), rScHint.GetRowCount());
130  }
131 }
132 
133 void FormulaGroupAreaListener::Query( QueryBase& rQuery ) const
134 {
135  switch (rQuery.getId())
136  {
138  {
139  const ScFormulaCell* pTop = getTopCell();
140  ScRange aRange(pTop->aPos);
141  aRange.aEnd.IncRow(mnGroupLen-1);
142  QueryRange& rQR = static_cast<QueryRange&>(rQuery);
143  rQR.add(aRange);
144  }
145  break;
146  default:
147  ;
148  }
149 }
150 
152 {
153  const ColumnSpanSet* pSpans = rHint.getSpans();
154  if (!pSpans)
155  return;
156 
157  ScDocument& rDoc = const_cast<BulkDataHint&>(rHint).getDoc();
158 
159  CollectCellAction aAction(*this);
160  pSpans->executeColumnAction(rDoc, aAction);
161 
162  std::vector<ScFormulaCell*> aCells;
163  aAction.swapCells(aCells);
164  ScHint aHint(SfxHintId::ScDataChanged, ScAddress());
165  std::for_each(aCells.begin(), aCells.end(), Notifier(aHint));
166 }
167 
169  SCTAB nTab, SCCOL nCol, SCROW nRow1, SCROW nRow2, std::vector<ScFormulaCell*>& rCells ) const
170 {
171  PutInOrder(nRow1, nRow2);
172 
173  if (nTab < maRange.aStart.Tab() || maRange.aEnd.Tab() < nTab)
174  // Wrong sheet.
175  return;
176 
177  if (nCol < maRange.aStart.Col() || maRange.aEnd.Col() < nCol)
178  // Outside the column range.
179  return;
180 
181  collectFormulaCells(nRow1, nRow2, rCells);
182 }
183 
185  SCROW nRow1, SCROW nRow2, std::vector<ScFormulaCell*>& rCells ) const
186 {
187  SAL_INFO( "sc.core.grouparealistener",
188  "FormulaGroupAreaListener::collectFormulaCells() this " << this <<
189  " range " << (maRange == BCA_LISTEN_ALWAYS ? "LISTEN-ALWAYS" : maRange.Format(mrDocument, ScRefFlags::VALID)) <<
190  " mnTopCellRow " << mnTopCellRow << " length " << mnGroupLen <<
191  ", col/tab " << mpColumn->GetCol() << "/" << mpColumn->GetTab());
192 
193  size_t nBlockSize = 0;
194  ScFormulaCell* const * pp = mpColumn->GetFormulaCellBlockAddress( mnTopCellRow, nBlockSize);
195  if (!pp)
196  {
197  SAL_WARN("sc.core", "GetFormulaCellBlockAddress not found");
198  return;
199  }
200 
201  /* FIXME: this is tdf#90717, when deleting a row fixed size area listeners
202  * such as BCA_ALWAYS or entire row listeners are (rightly) not destroyed,
203  * but mnTopCellRow and mnGroupLen also not updated, which needs fixing.
204  * Until then pull things as straight as possible here in such situation
205  * and prevent crash. */
206  if (!(*pp)->IsSharedTop())
207  {
208  SCROW nRow = (*pp)->GetSharedTopRow();
209  if (nRow < 0)
210  SAL_WARN("sc.core", "FormulaGroupAreaListener::collectFormulaCells() no shared top");
211  else
212  {
213  SAL_WARN("sc.core","FormulaGroupAreaListener::collectFormulaCells() syncing mnTopCellRow from " <<
214  mnTopCellRow << " to " << nRow);
215  const_cast<FormulaGroupAreaListener*>(this)->mnTopCellRow = nRow;
217  if (!pp)
218  {
219  SAL_WARN("sc.core", "GetFormulaCellBlockAddress not found");
220  return;
221  }
222  }
223  }
224  SCROW nLen = (*pp)->GetSharedLength();
225  if (nLen != mnGroupLen)
226  {
227  SAL_WARN("sc.core", "FormulaGroupAreaListener::collectFormulaCells() syncing mnGroupLen from " <<
228  mnGroupLen << " to " << nLen);
229  const_cast<FormulaGroupAreaListener*>(this)->mnGroupLen = nLen;
230  }
231 
232  /* With tdf#89957 it happened that the actual block size in column
233  * AP (shifted from AO) of sheet 'w' was smaller than the remembered group
234  * length and correct. This is just a very ugly workaround, the real cause
235  * is yet unknown, but at least don't crash in such case. The intermediate
236  * cause is that not all affected group area listeners are destroyed and
237  * newly created, so mpColumn still points to the old column that then has
238  * the content of a shifted column. Effectively this workaround has the
239  * consequence that the group area listener is fouled up and not all
240  * formula cells are notified... */
241  if (nBlockSize < o3tl::make_unsigned(mnGroupLen))
242  {
243  SAL_WARN("sc.core","FormulaGroupAreaListener::collectFormulaCells() nBlockSize " <<
244  nBlockSize << " < " << mnGroupLen << " mnGroupLen");
245  const_cast<FormulaGroupAreaListener*>(this)->mnGroupLen = static_cast<SCROW>(nBlockSize);
246 
247  // erAck: 2016-11-09T18:30+01:00 XXX This doesn't occur anymore, at
248  // least not in the original bug scenario (insert a column before H on
249  // sheet w) of tdf#89957 with
250  // http://bugs.documentfoundation.org/attachment.cgi?id=114042
251  // Apparently this was fixed in the meantime, let's assume and get the
252  // assert bat out to hit us if it wasn't.
253  assert(!"something is still messing up the formula goup and block size length");
254  }
255 
256  ScFormulaCell* const * ppEnd = pp + mnGroupLen;
257 
258  if (mbStartFixed)
259  {
260  if (mbEndFixed)
261  {
262  // Both top and bottom row positions are absolute. Use the original range as-is.
263  SCROW nRefRow1 = maRange.aStart.Row();
264  SCROW nRefRow2 = maRange.aEnd.Row();
265  if (nRow2 < nRefRow1 || nRefRow2 < nRow1)
266  return;
267 
268  rCells.insert(rCells.end(), pp, ppEnd);
269  }
270  else
271  {
272  // Only the end row is relative.
273  SCROW nRefRow1 = maRange.aStart.Row();
274  SCROW nRefRow2 = maRange.aEnd.Row();
275  SCROW nMaxRefRow = nRefRow2 + mnGroupLen - 1;
276  if (nRow2 < nRefRow1 || nMaxRefRow < nRow1)
277  return;
278 
279  if (nRefRow2 < nRow1)
280  {
281  // Skip ahead to the first hit.
282  SCROW nSkip = nRow1 - nRefRow2;
283  pp += nSkip;
284  nRefRow2 += nSkip;
285  }
286 
287  assert(nRow1 <= nRefRow2);
288 
289  // Notify the first hit cell and all subsequent ones.
290  rCells.insert(rCells.end(), pp, ppEnd);
291  }
292  }
293  else if (mbEndFixed)
294  {
295  // Only the start row is relative.
296  SCROW nRefRow1 = maRange.aStart.Row();
297  SCROW nRefRow2 = maRange.aEnd.Row();
298 
299  if (nRow2 < nRefRow1 || nRefRow2 < nRow1)
300  return;
301 
302  for (; pp != ppEnd && nRefRow1 <= nRefRow2; ++pp, ++nRefRow1)
303  rCells.push_back(*pp);
304  }
305  else
306  {
307  // Both top and bottom row positions are relative.
308  SCROW nRefRow1 = maRange.aStart.Row();
309  SCROW nRefRow2 = maRange.aEnd.Row();
310  SCROW nMaxRefRow = nRefRow2 + mnGroupLen - 1;
311  if (nMaxRefRow < nRow1)
312  return;
313 
314  if (nRefRow2 < nRow1)
315  {
316  // The ref row range is above the changed row span. Skip ahead.
317  SCROW nSkip = nRow1 - nRefRow2;
318  pp += nSkip;
319  nRefRow1 += nSkip;
320  nRefRow2 += nSkip;
321  }
322 
323  // At this point the initial ref row range should be overlapping the
324  // dirty cell range.
325  assert(nRow1 <= nRefRow2);
326 
327  // Keep sliding down until the top ref row position is below the
328  // bottom row of the dirty cell range.
329  for (; pp != ppEnd && nRefRow1 <= nRow2; ++pp, ++nRefRow1, ++nRefRow2)
330  rCells.push_back(*pp);
331  }
332 }
333 
335 {
336  size_t nBlockSize = 0;
337  const ScFormulaCell* const * pp = mpColumn->GetFormulaCellBlockAddress( mnTopCellRow, nBlockSize);
338  SAL_WARN_IF(!pp, "sc.core.grouparealistener", "GetFormulaCellBlockAddress not found");
339  return pp ? *pp : nullptr;
340 }
341 
342 void FormulaGroupAreaListener::notifyCellChange( const SfxHint& rHint, const ScAddress& rPos, SCROW nNumRows )
343 {
344  // Determine which formula cells within the group need to be notified of this change.
345  std::vector<ScFormulaCell*> aCells;
346  collectFormulaCells(rPos.Tab(), rPos.Col(), rPos.Row(), rPos.Row() + (nNumRows - 1), aCells);
347  std::for_each(aCells.begin(), aCells.end(), Notifier(rHint));
348 }
349 
350 }
351 
352 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */
ScAddress aStart
Definition: address.hxx:497
#define BCA_LISTEN_ALWAYS
Definition: address.hxx:945
const ScFormulaCell * getTopCell() const
SCROW Row() const
Definition: address.hxx:274
ScTable * FetchTable(SCTAB nTab)
Definition: document.cxx:2536
ScColumn * FetchColumn(SCCOL nCol)
Definition: table2.cxx:1235
void add(const ScRange &rRange)
SCROW GetRowCount() const
Definition: brdcst.hxx:32
ScAddress aEnd
Definition: address.hxx:498
#define SC_LISTENER_QUERY_FORMULA_GROUP_RANGE
virtual void Notify(const SfxHint &rHint) override
SfxHintId GetId() const
virtual void Query(QueryBase &rQuery) const override
SCTAB Tab() const
Definition: address.hxx:283
ScAddress aPos
void SetCol(SCCOL nColP)
Definition: address.hxx:291
const ColumnSpanSet * getSpans() const
void PutInOrder(T &nStart, T &nEnd)
Definition: address.hxx:150
void executeColumnAction(ScDocument &rDoc, ColumnAction &ac) const
void SetTab(SCTAB nTabP)
Definition: address.hxx:295
sal_Int16 SCCOL
Definition: types.hxx:21
ScFormulaCell *const * GetFormulaCellBlockAddress(SCROW nRow, size_t &rBlockSize) const
Definition: column2.cxx:3252
CAUTION! The following defines must be in the same namespace as the respective type.
OUString Format(const ScDocument &rDocument, ScRefFlags nFlags=ScRefFlags::ZERO, const ScAddress::Details &rDetails=ScAddress::detailsOOOa1, bool bFullAddressNotation=false) const
Returns string with formatted cell range from aStart to aEnd, according to provided address conventio...
Definition: address.cxx:2170
void notifyCellChange(const SfxHint &rHint, const ScAddress &rPos, SCROW nNumRows)
constexpr std::enable_if_t< std::is_signed_v< T >, std::make_unsigned_t< T > > make_unsigned(T value)
void IncRow(SCROW nDelta=1)
Definition: address.hxx:312
Structure that stores segments of boolean flags per column, and perform custom action on those segmen...
virtual void Notify(const SfxHint &rHint) override
SCCOL Col() const
Definition: address.hxx:279
void notifyBulkChange(const BulkDataHint &rHint)
sal_Int32 SCROW
Definition: types.hxx:17
void collectFormulaCells(SCTAB nTab, SCCOL nCol, SCROW nRow1, SCROW nRow2, std::vector< ScFormulaCell * > &rCells) const
Given the row span of changed cells within a single column, collect all formula cells that need to be...
#define SAL_WARN_IF(condition, area, stream)
SCCOL GetCol() const
Definition: column.hxx:256
#define SAL_INFO(area, stream)
virtual ~FormulaGroupAreaListener() override
SCTAB GetTab() const
Definition: column.hxx:255
#define SAL_WARN(area, stream)
B2DRange maRange
sal_Int16 SCTAB
Definition: types.hxx:22
const ScAddress & GetStartAddress() const
Definition: brdcst.hxx:31