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
24namespace sc {
25
26namespace {
27
28class Notifier
29{
30 const SfxHint& mrHint;
31public:
32 explicit Notifier( const SfxHint& rHint ) : mrHint(rHint) {}
33
34 void operator() ( ScFormulaCell* pCell )
35 {
36 pCell->Notify(mrHint);
37 }
38};
39
40class CollectCellAction : public sc::ColumnSpanSet::ColumnAction
41{
42 const FormulaGroupAreaListener& mrAreaListener;
43 ScAddress maPos;
44 std::vector<ScFormulaCell*> maCells;
45
46public:
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
133void 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;
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 group 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
342void 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: */
#define BCA_LISTEN_ALWAYS
Definition: address.hxx:945
void PutInOrder(T &nStart, T &nEnd)
Definition: address.hxx:150
B2DRange maRange
SCTAB Tab() const
Definition: address.hxx:283
void SetCol(SCCOL nColP)
Definition: address.hxx:291
SCROW Row() const
Definition: address.hxx:274
void SetTab(SCTAB nTabP)
Definition: address.hxx:295
void IncRow(SCROW nDelta=1)
Definition: address.hxx:312
SCCOL Col() const
Definition: address.hxx:279
SCTAB GetTab() const
Definition: column.hxx:254
SCCOL GetCol() const
Definition: column.hxx:255
ScFormulaCell *const * GetFormulaCellBlockAddress(SCROW nRow, size_t &rBlockSize) const
Definition: column2.cxx:3285
ScTable * FetchTable(SCTAB nTab)
Definition: document.cxx:2545
virtual void Notify(const SfxHint &rHint) override
ScAddress aPos
SCROW GetRowCount() const
Definition: brdcst.hxx:32
const ScAddress & GetStartAddress() const
Definition: brdcst.hxx:31
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
ScAddress aEnd
Definition: address.hxx:498
ScAddress aStart
Definition: address.hxx:497
ScColumn * FetchColumn(SCCOL nCol)
Definition: table2.cxx:1235
SfxHintId GetId() const
const ColumnSpanSet * getSpans() const
Structure that stores segments of boolean flags per column, and perform custom action on those segmen...
void executeColumnAction(ScDocument &rDoc, ColumnAction &ac) const
void notifyCellChange(const SfxHint &rHint, const ScAddress &rPos, SCROW nNumRows)
const ScFormulaCell * getTopCell() const
virtual void Notify(const SfxHint &rHint) override
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...
virtual void Query(QueryBase &rQuery) const override
virtual ~FormulaGroupAreaListener() override
void notifyBulkChange(const BulkDataHint &rHint)
void add(const ScRange &rRange)
#define SC_LISTENER_QUERY_FORMULA_GROUP_RANGE
#define SAL_WARN_IF(condition, area, stream)
#define SAL_WARN(area, stream)
#define SAL_INFO(area, stream)
constexpr std::enable_if_t< std::is_signed_v< T >, std::make_unsigned_t< T > > make_unsigned(T value)
CAUTION! The following defines must be in the same namespace as the respective type.
sal_Int16 SCTAB
Definition: types.hxx:22
sal_Int16 SCCOL
Definition: types.hxx:21
sal_Int32 SCROW
Definition: types.hxx:17