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.
67  std::sort(maCells.begin(), maCells.end());
68  std::vector<ScFormulaCell*>::iterator it = std::unique(maCells.begin(), maCells.end());
69  maCells.erase(it, maCells.end());
70 
71  rCells.swap(maCells);
72  }
73 };
74 
75 }
76 
78  const ScAddress& rTopCellPos, SCROW nGroupLen, bool bStartFixed, bool bEndFixed ) :
79  maRange(rRange),
80  mrDocument(rDocument),
81  mpColumn(nullptr),
82  mnTopCellRow(rTopCellPos.Row()),
83  mnGroupLen(nGroupLen),
84  mbStartFixed(bStartFixed),
85  mbEndFixed(bEndFixed)
86 {
87  const ScTable* pTab = rDocument.FetchTable( rTopCellPos.Tab());
88  assert(pTab);
89  mpColumn = pTab->FetchColumn( rTopCellPos.Col());
91  SAL_INFO( "sc.core.grouparealistener",
92  "FormulaGroupAreaListener ctor this " << this <<
93  " range " << (maRange == BCA_LISTEN_ALWAYS ? "LISTEN-ALWAYS" : maRange.Format(mrDocument, ScRefFlags::VALID)) <<
94  " mnTopCellRow " << mnTopCellRow << " length " << mnGroupLen <<
95  ", col/tab " << mpColumn->GetCol() << "/" << mpColumn->GetTab());
96 }
97 
99 {
100  SAL_INFO( "sc.core.grouparealistener",
101  "FormulaGroupAreaListener dtor this " << this);
102 }
103 
105 {
106  ScRange aRet = maRange;
107  if (!mbEndFixed)
108  aRet.aEnd.IncRow(mnGroupLen-1);
109  return aRet;
110 }
111 
113 {
114  // BulkDataHint may include (SfxHintId::ScDataChanged |
115  // SfxHintId::ScTableOpDirty) so has to be checked first.
116  if ( const BulkDataHint* pBulkHint = dynamic_cast<const BulkDataHint*>(&rHint) )
117  {
118  notifyBulkChange(*pBulkHint);
119  }
120  else if (rHint.GetId() == SfxHintId::ScDataChanged || rHint.GetId() == SfxHintId::ScTableOpDirty)
121  {
122  notifyCellChange(rHint, static_cast<const ScHint*>(&rHint)->GetAddress());
123  }
124 }
125 
126 void FormulaGroupAreaListener::Query( QueryBase& rQuery ) const
127 {
128  switch (rQuery.getId())
129  {
131  {
132  const ScFormulaCell* pTop = getTopCell();
133  ScRange aRange(pTop->aPos);
134  aRange.aEnd.IncRow(mnGroupLen-1);
135  QueryRange& rQR = static_cast<QueryRange&>(rQuery);
136  rQR.add(aRange);
137  }
138  break;
139  default:
140  ;
141  }
142 }
143 
145 {
146  const ColumnSpanSet* pSpans = rHint.getSpans();
147  if (!pSpans)
148  return;
149 
150  ScDocument& rDoc = const_cast<BulkDataHint&>(rHint).getDoc();
151 
152  CollectCellAction aAction(*this);
153  pSpans->executeColumnAction(rDoc, aAction);
154 
155  std::vector<ScFormulaCell*> aCells;
156  aAction.swapCells(aCells);
157  ScHint aHint(SfxHintId::ScDataChanged, ScAddress());
158  std::for_each(aCells.begin(), aCells.end(), Notifier(aHint));
159 }
160 
162  SCTAB nTab, SCCOL nCol, SCROW nRow1, SCROW nRow2, std::vector<ScFormulaCell*>& rCells ) const
163 {
164  PutInOrder(nRow1, nRow2);
165 
166  if (nTab < maRange.aStart.Tab() || maRange.aEnd.Tab() < nTab)
167  // Wrong sheet.
168  return;
169 
170  if (nCol < maRange.aStart.Col() || maRange.aEnd.Col() < nCol)
171  // Outside the column range.
172  return;
173 
174  collectFormulaCells(nRow1, nRow2, rCells);
175 }
176 
178  SCROW nRow1, SCROW nRow2, std::vector<ScFormulaCell*>& rCells ) const
179 {
180  SAL_INFO( "sc.core.grouparealistener",
181  "FormulaGroupAreaListener::collectFormulaCells() this " << this <<
182  " range " << (maRange == BCA_LISTEN_ALWAYS ? "LISTEN-ALWAYS" : maRange.Format(mrDocument, ScRefFlags::VALID)) <<
183  " mnTopCellRow " << mnTopCellRow << " length " << mnGroupLen <<
184  ", col/tab " << mpColumn->GetCol() << "/" << mpColumn->GetTab());
185 
186  size_t nBlockSize = 0;
187  ScFormulaCell* const * pp = mpColumn->GetFormulaCellBlockAddress( mnTopCellRow, nBlockSize);
188  if (!pp)
189  {
190  SAL_WARN("sc.core", "GetFormulaCellBlockAddress not found");
191  return;
192  }
193 
194  /* FIXME: this is tdf#90717, when deleting a row fixed size area listeners
195  * such as BCA_ALWAYS or entire row listeners are (rightly) not destroyed,
196  * but mnTopCellRow and mnGroupLen also not updated, which needs fixing.
197  * Until then pull things as straight as possible here in such situation
198  * and prevent crash. */
199  if (!(*pp)->IsSharedTop())
200  {
201  SCROW nRow = (*pp)->GetSharedTopRow();
202  if (nRow < 0)
203  SAL_WARN("sc.core", "FormulaGroupAreaListener::collectFormulaCells() no shared top");
204  else
205  {
206  SAL_WARN("sc.core","FormulaGroupAreaListener::collectFormulaCells() syncing mnTopCellRow from " <<
207  mnTopCellRow << " to " << nRow);
208  const_cast<FormulaGroupAreaListener*>(this)->mnTopCellRow = nRow;
210  if (!pp)
211  {
212  SAL_WARN("sc.core", "GetFormulaCellBlockAddress not found");
213  return;
214  }
215  }
216  }
217  SCROW nLen = (*pp)->GetSharedLength();
218  if (nLen != mnGroupLen)
219  {
220  SAL_WARN("sc.core", "FormulaGroupAreaListener::collectFormulaCells() syncing mnGroupLen from " <<
221  mnGroupLen << " to " << nLen);
222  const_cast<FormulaGroupAreaListener*>(this)->mnGroupLen = nLen;
223  }
224 
225  /* With tdf#89957 it happened that the actual block size in column
226  * AP (shifted from AO) of sheet 'w' was smaller than the remembered group
227  * length and correct. This is just a very ugly workaround, the real cause
228  * is yet unknown, but at least don't crash in such case. The intermediate
229  * cause is that not all affected group area listeners are destroyed and
230  * newly created, so mpColumn still points to the old column that then has
231  * the content of a shifted column. Effectively this workaround has the
232  * consequence that the group area listener is fouled up and not all
233  * formula cells are notified... */
234  if (nBlockSize < o3tl::make_unsigned(mnGroupLen))
235  {
236  SAL_WARN("sc.core","FormulaGroupAreaListener::collectFormulaCells() nBlockSize " <<
237  nBlockSize << " < " << mnGroupLen << " mnGroupLen");
238  const_cast<FormulaGroupAreaListener*>(this)->mnGroupLen = static_cast<SCROW>(nBlockSize);
239 
240  // erAck: 2016-11-09T18:30+01:00 XXX This doesn't occur anymore, at
241  // least not in the original bug scenario (insert a column before H on
242  // sheet w) of tdf#89957 with
243  // http://bugs.documentfoundation.org/attachment.cgi?id=114042
244  // Apparently this was fixed in the meantime, let's assume and get the
245  // assert bat out to hit us if it wasn't.
246  assert(!"something is still messing up the formula goup and block size length");
247  }
248 
249  ScFormulaCell* const * ppEnd = pp + mnGroupLen;
250 
251  if (mbStartFixed)
252  {
253  if (mbEndFixed)
254  {
255  // Both top and bottom row positions are absolute. Use the original range as-is.
256  SCROW nRefRow1 = maRange.aStart.Row();
257  SCROW nRefRow2 = maRange.aEnd.Row();
258  if (nRow2 < nRefRow1 || nRefRow2 < nRow1)
259  return;
260 
261  for (; pp != ppEnd; ++pp)
262  rCells.push_back(*pp);
263  }
264  else
265  {
266  // Only the end row is relative.
267  SCROW nRefRow1 = maRange.aStart.Row();
268  SCROW nRefRow2 = maRange.aEnd.Row();
269  SCROW nMaxRefRow = nRefRow2 + mnGroupLen - 1;
270  if (nRow2 < nRefRow1 || nMaxRefRow < nRow1)
271  return;
272 
273  if (nRefRow2 < nRow1)
274  {
275  // Skip ahead to the first hit.
276  SCROW nSkip = nRow1 - nRefRow2;
277  pp += nSkip;
278  nRefRow2 += nSkip;
279  }
280 
281  assert(nRow1 <= nRefRow2);
282 
283  // Notify the first hit cell and all subsequent ones.
284  for (; pp != ppEnd; ++pp)
285  rCells.push_back(*pp);
286  }
287  }
288  else if (mbEndFixed)
289  {
290  // Only the start row is relative.
291  SCROW nRefRow1 = maRange.aStart.Row();
292  SCROW nRefRow2 = maRange.aEnd.Row();
293 
294  if (nRow2 < nRefRow1 || nRefRow2 < nRow1)
295  return;
296 
297  for (; pp != ppEnd && nRefRow1 <= nRefRow2; ++pp, ++nRefRow1)
298  rCells.push_back(*pp);
299  }
300  else
301  {
302  // Both top and bottom row positions are relative.
303  SCROW nRefRow1 = maRange.aStart.Row();
304  SCROW nRefRow2 = maRange.aEnd.Row();
305  SCROW nMaxRefRow = nRefRow2 + mnGroupLen - 1;
306  if (nMaxRefRow < nRow1)
307  return;
308 
309  if (nRefRow2 < nRow1)
310  {
311  // The ref row range is above the changed row span. Skip ahead.
312  SCROW nSkip = nRow1 - nRefRow2;
313  pp += nSkip;
314  nRefRow1 += nSkip;
315  nRefRow2 += nSkip;
316  }
317 
318  // At this point the initial ref row range should be overlapping the
319  // dirty cell range.
320  assert(nRow1 <= nRefRow2);
321 
322  // Keep sliding down until the top ref row position is below the
323  // bottom row of the dirty cell range.
324  for (; pp != ppEnd && nRefRow1 <= nRow2; ++pp, ++nRefRow1, ++nRefRow2)
325  rCells.push_back(*pp);
326  }
327 }
328 
330 {
331  size_t nBlockSize = 0;
332  const ScFormulaCell* const * pp = mpColumn->GetFormulaCellBlockAddress( mnTopCellRow, nBlockSize);
333  SAL_WARN_IF(!pp, "sc.core.grouparealistener", "GetFormulaCellBlockAddress not found");
334  return pp ? *pp : nullptr;
335 }
336 
338 {
339  // Determine which formula cells within the group need to be notified of this change.
340  std::vector<ScFormulaCell*> aCells;
341  collectFormulaCells(rPos.Tab(), rPos.Col(), rPos.Row(), rPos.Row(), aCells);
342  std::for_each(aCells.begin(), aCells.end(), Notifier(rHint));
343 }
344 
345 }
346 
347 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */
ScAddress aStart
Definition: address.hxx:500
#define BCA_LISTEN_ALWAYS
Definition: address.hxx:952
const ScFormulaCell * getTopCell() const
SCROW Row() const
Definition: address.hxx:262
ScTable * FetchTable(SCTAB nTab)
Definition: document.cxx:2505
ScColumn * FetchColumn(SCCOL nCol)
Definition: table2.cxx:1087
void add(const ScRange &rRange)
ScAddress aEnd
Definition: address.hxx:501
#define SC_LISTENER_QUERY_FORMULA_GROUP_RANGE
virtual void Notify(const SfxHint &rHint) override
SfxHintId GetId() const
virtual void Query(QueryBase &rQuery) const override
const BorderLinePrimitive2D *pCandidateB assert(pCandidateA)
SCTAB Tab() const
Definition: address.hxx:271
ScAddress aPos
void SetCol(SCCOL nColP)
Definition: address.hxx:279
void PutInOrder(T &nStart, T &nEnd)
Definition: address.hxx:954
void executeColumnAction(ScDocument &rDoc, ColumnAction &ac) const
void SetTab(SCTAB nTabP)
Definition: address.hxx:283
const ColumnSpanSet * getSpans() const
sal_Int16 SCCOL
Definition: types.hxx:22
ScFormulaCell *const * GetFormulaCellBlockAddress(SCROW nRow, size_t &rBlockSize) const
Definition: column2.cxx:3045
SC_DLLPUBLIC 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:2203
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:300
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:267
void notifyBulkChange(const BulkDataHint &rHint)
sal_Int32 SCROW
Definition: types.hxx:18
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:184
#define SAL_INFO(area, stream)
virtual ~FormulaGroupAreaListener() override
SCTAB GetTab() const
Definition: column.hxx:183
#define SAL_WARN(area, stream)
void notifyCellChange(const SfxHint &rHint, const ScAddress &rPos)
B2DRange maRange
sal_Int16 SCTAB
Definition: types.hxx:23