Line data Source code
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 : namespace sc {
22 :
23 : namespace {
24 :
25 : class Notifier : std::unary_function<ScFormulaCell*, void>
26 : {
27 : const SfxHint& mrHint;
28 : public:
29 12 : Notifier( const SfxHint& rHint ) : mrHint(rHint) {}
30 :
31 34 : void operator() ( ScFormulaCell* pCell )
32 : {
33 34 : pCell->Notify(mrHint);
34 34 : }
35 : };
36 :
37 12 : class CollectCellAction : public sc::ColumnSpanSet::ColumnAction
38 : {
39 : const FormulaGroupAreaListener& mrAreaListener;
40 : ScAddress maPos;
41 : std::vector<ScFormulaCell*> maCells;
42 :
43 : public:
44 12 : CollectCellAction( const FormulaGroupAreaListener& rAreaListener ) :
45 12 : mrAreaListener(rAreaListener) {}
46 :
47 17 : virtual void startColumn( ScColumn* pCol ) SAL_OVERRIDE
48 : {
49 17 : maPos.SetTab(pCol->GetTab());
50 17 : maPos.SetCol(pCol->GetCol());
51 17 : }
52 :
53 48 : virtual void execute( SCROW nRow1, SCROW nRow2, bool bVal ) SAL_OVERRIDE
54 : {
55 48 : if (!bVal)
56 79 : return;
57 :
58 17 : mrAreaListener.collectFormulaCells(maPos.Tab(), maPos.Col(), nRow1, nRow2, maCells);
59 : };
60 :
61 12 : void swapCells( std::vector<ScFormulaCell*>& rCells )
62 : {
63 : // Remove duplicate before the swap.
64 12 : std::sort(maCells.begin(), maCells.end());
65 12 : std::vector<ScFormulaCell*>::iterator it = std::unique(maCells.begin(), maCells.end());
66 12 : maCells.erase(it, maCells.end());
67 :
68 12 : rCells.swap(maCells);
69 12 : }
70 : };
71 :
72 : }
73 :
74 478 : FormulaGroupAreaListener::FormulaGroupAreaListener( const ScRange& rRange, const ScDocument& rDocument,
75 : const ScAddress& rTopCellPos, SCROW nGroupLen, bool bStartFixed, bool bEndFixed ) :
76 : maRange(rRange),
77 : mpColumn(NULL),
78 478 : mnTopCellRow(rTopCellPos.Row()),
79 : mnGroupLen(nGroupLen),
80 : mbStartFixed(bStartFixed),
81 956 : mbEndFixed(bEndFixed)
82 : {
83 478 : const ScTable* pTab = rDocument.FetchTable( rTopCellPos.Tab());
84 : assert(pTab);
85 478 : mpColumn = pTab->FetchColumn( rTopCellPos.Col());
86 : assert(mpColumn);
87 : SAL_INFO( "sc.core.grouparealistener",
88 : "FormulaGroupAreaListener ctor this " << this <<
89 : " range " << (maRange == BCA_LISTEN_ALWAYS ? "LISTEN-ALWAYS" : maRange.Format(SCA_VALID)) <<
90 : " mnTopCellRow " << mnTopCellRow << " length " << mnGroupLen <<
91 : ", col/tab " << mpColumn->GetCol() << "/" << mpColumn->GetTab());
92 478 : }
93 :
94 956 : FormulaGroupAreaListener::~FormulaGroupAreaListener()
95 : {
96 : SAL_INFO( "sc.core.grouparealistener",
97 : "FormulaGroupAreaListener dtor this " << this <<
98 : " range " << (maRange == BCA_LISTEN_ALWAYS ? "LISTEN-ALWAYS" : maRange.Format(SCA_VALID)) <<
99 : " mnTopCellRow " << mnTopCellRow << " length " << mnGroupLen <<
100 : ", col/tab " << mpColumn->GetCol() << "/" << mpColumn->GetTab());
101 956 : }
102 :
103 13 : ScRange FormulaGroupAreaListener::getListeningRange() const
104 : {
105 13 : ScRange aRet = maRange;
106 13 : if (!mbEndFixed)
107 12 : aRet.aEnd.IncRow(mnGroupLen-1);
108 13 : return aRet;
109 : }
110 :
111 476 : void FormulaGroupAreaListener::Notify( const SfxHint& rHint )
112 : {
113 476 : const SfxSimpleHint* pSimpleHint = dynamic_cast<const SfxSimpleHint*>(&rHint);
114 476 : if (!pSimpleHint)
115 480 : return;
116 :
117 472 : switch (pSimpleHint->GetId())
118 : {
119 : case SC_HINT_DATACHANGED:
120 0 : notifyCellChange(rHint, static_cast<const ScHint*>(pSimpleHint)->GetAddress());
121 0 : break;
122 : case SC_HINT_BULK_DATACHANGED:
123 : {
124 12 : const BulkDataHint& rBulkHint = static_cast<const BulkDataHint&>(*pSimpleHint);
125 12 : notifyBulkChange(rBulkHint);
126 : }
127 12 : break;
128 : default:
129 : ;
130 : }
131 : }
132 :
133 4 : void FormulaGroupAreaListener::Query( QueryBase& rQuery ) const
134 : {
135 4 : switch (rQuery.getId())
136 : {
137 : case SC_LISTENER_QUERY_FORMULA_GROUP_RANGE:
138 : {
139 4 : const ScFormulaCell* pTop = getTopCell();
140 4 : ScRange aRange(pTop->aPos);
141 4 : aRange.aEnd.IncRow(mnGroupLen-1);
142 4 : QueryRange& rQR = static_cast<QueryRange&>(rQuery);
143 4 : rQR.add(aRange);
144 : }
145 4 : break;
146 : default:
147 : ;
148 : }
149 4 : }
150 :
151 12 : void FormulaGroupAreaListener::notifyBulkChange( const BulkDataHint& rHint )
152 : {
153 12 : const ColumnSpanSet* pSpans = rHint.getSpans();
154 12 : if (!pSpans)
155 12 : return;
156 :
157 12 : ScDocument& rDoc = const_cast<BulkDataHint&>(rHint).getDoc();
158 :
159 12 : CollectCellAction aAction(*this);
160 12 : pSpans->executeColumnAction(rDoc, aAction);
161 :
162 24 : std::vector<ScFormulaCell*> aCells;
163 12 : aAction.swapCells(aCells);
164 24 : ScHint aHint(SC_HINT_DATACHANGED, ScAddress());
165 24 : std::for_each(aCells.begin(), aCells.end(), Notifier(aHint));
166 : }
167 :
168 17 : void FormulaGroupAreaListener::collectFormulaCells(
169 : SCTAB nTab, SCCOL nCol, SCROW nRow1, SCROW nRow2, std::vector<ScFormulaCell*>& rCells ) const
170 : {
171 17 : PutInOrder(nRow1, nRow2);
172 :
173 17 : if (nTab < maRange.aStart.Tab() || maRange.aEnd.Tab() < nTab)
174 : // Wrong sheet.
175 0 : return;
176 :
177 17 : if (nCol < maRange.aStart.Col() || maRange.aEnd.Col() < nCol)
178 : // Outside the column range.
179 0 : return;
180 :
181 17 : collectFormulaCells(nRow1, nRow2, rCells);
182 : }
183 :
184 17 : void FormulaGroupAreaListener::collectFormulaCells(
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(SCA_VALID)) <<
190 : " mnTopCellRow " << mnTopCellRow << " length " << mnGroupLen <<
191 : ", col/tab " << mpColumn->GetCol() << "/" << mpColumn->GetTab());
192 :
193 17 : size_t nBlockSize = 0;
194 17 : ScFormulaCell* const * pp = mpColumn->GetFormulaCellBlockAddress( mnTopCellRow, nBlockSize);
195 17 : if (!pp)
196 : {
197 : SAL_WARN("sc", "GetFormulaCellBlockAddress not found");
198 0 : return;
199 : }
200 :
201 : /* FIXME: with tdf#89957 it happened that the actual block size in column
202 : * AP (shifted from AO) of sheet 'w' was smaller than the remembered group
203 : * length and correct. This is just a very ugly workaround, the real cause
204 : * is yet unknown, but at least don't crash in such case. The intermediate
205 : * cause is that not all affected group area listeners are destroyed and
206 : * newly created, so mpColumn still points to the old column that then has
207 : * the content of a shifted column. Effectively this workaround has the
208 : * consequence that the group area listener is fouled up and not all
209 : * formula cells are notified.. */
210 17 : if (nBlockSize < static_cast<size_t>(mnGroupLen))
211 : {
212 : SAL_WARN("sc.core","FormulaGroupAreaListener::collectFormulaCells() nBlockSize " <<
213 : nBlockSize << " < " << mnGroupLen << " mnGroupLen");
214 0 : const_cast<FormulaGroupAreaListener*>(this)->mnGroupLen = static_cast<SCROW>(nBlockSize);
215 : }
216 :
217 17 : ScFormulaCell* const * ppEnd = pp + mnGroupLen;
218 :
219 17 : if (mbStartFixed)
220 : {
221 1 : if (mbEndFixed)
222 : {
223 : // Both top and bottom row positions are absolute. Use the original range as-is.
224 1 : SCROW nRefRow1 = maRange.aStart.Row();
225 1 : SCROW nRefRow2 = maRange.aEnd.Row();
226 1 : if (nRow2 < nRefRow1 || nRefRow2 < nRow1)
227 0 : return;
228 :
229 3 : for (; pp != ppEnd; ++pp)
230 2 : rCells.push_back(*pp);
231 : }
232 : else
233 : {
234 : // Only the end row is relative.
235 0 : SCROW nRefRow1 = maRange.aStart.Row();
236 0 : SCROW nRefRow2 = maRange.aEnd.Row();
237 0 : SCROW nMaxRefRow = nRefRow2 + mnGroupLen - 1;
238 0 : if (nRow2 < nRefRow1 || nMaxRefRow < nRow1)
239 0 : return;
240 :
241 0 : if (nRefRow2 < nRow1)
242 : {
243 : // Skip ahead to the first hit.
244 0 : SCROW nSkip = nRow1 - nRefRow2;
245 0 : pp += nSkip;
246 0 : nRefRow2 += nSkip;
247 : }
248 :
249 : assert(nRow1 <= nRefRow2);
250 :
251 : // Notify the first hit cell and all subsequent ones.
252 0 : for (; pp != ppEnd; ++pp)
253 0 : rCells.push_back(*pp);
254 : }
255 : }
256 16 : else if (mbEndFixed)
257 : {
258 : // Only the start row is relative.
259 0 : SCROW nRefRow1 = maRange.aStart.Row();
260 0 : SCROW nRefRow2 = maRange.aEnd.Row();
261 :
262 0 : if (nRow2 < nRefRow1 || nRefRow2 < nRow1)
263 0 : return;
264 :
265 0 : for (; pp != ppEnd && nRefRow1 <= nRefRow2; ++pp, ++nRefRow1)
266 0 : rCells.push_back(*pp);
267 : }
268 : else
269 : {
270 : // Both top and bottom row positions are relative.
271 16 : SCROW nRefRow1 = maRange.aStart.Row();
272 16 : SCROW nRefRow2 = maRange.aEnd.Row();
273 16 : SCROW nMaxRefRow = nRefRow2 + mnGroupLen - 1;
274 16 : if (nMaxRefRow < nRow1)
275 0 : return;
276 :
277 16 : if (nRefRow2 < nRow1)
278 : {
279 : // The ref row range is above the changed row span. Skip ahead.
280 4 : SCROW nSkip = nRow1 - nRefRow2;
281 4 : pp += nSkip;
282 4 : nRefRow1 += nSkip;
283 4 : nRefRow2 += nSkip;
284 : }
285 :
286 : // At this point the initial ref row range should be overlapping the
287 : // dirty cell range.
288 : assert(nRow1 <= nRefRow2);
289 :
290 : // Keep sliding down until the top ref row position is below the
291 : // bottom row of the dirty cell range.
292 70 : for (; pp != ppEnd && nRefRow1 <= nRow2; ++pp, ++nRefRow1, ++nRefRow2)
293 54 : rCells.push_back(*pp);
294 : }
295 : }
296 :
297 0 : ScAddress FormulaGroupAreaListener::getTopCellPos() const
298 : {
299 0 : const ScFormulaCell* p = getTopCell();
300 0 : return p ? p->aPos : ScAddress();
301 : }
302 :
303 4 : const ScFormulaCell* FormulaGroupAreaListener::getTopCell() const
304 : {
305 4 : size_t nBlockSize = 0;
306 4 : const ScFormulaCell* const * pp = mpColumn->GetFormulaCellBlockAddress( mnTopCellRow, nBlockSize);
307 : SAL_WARN_IF(!pp, "sc", "GetFormulaCellBlockAddress not found");
308 4 : return pp ? *pp : NULL;
309 : }
310 :
311 0 : const ScRange& FormulaGroupAreaListener::getRange() const
312 : {
313 0 : return maRange;
314 : }
315 :
316 0 : SCROW FormulaGroupAreaListener::getGroupLength() const
317 : {
318 0 : return mnGroupLen;
319 : }
320 :
321 0 : void FormulaGroupAreaListener::notifyCellChange( const SfxHint& rHint, const ScAddress& rPos )
322 : {
323 : // Determine which formula cells within the group need to be notified of this change.
324 0 : std::vector<ScFormulaCell*> aCells;
325 0 : collectFormulaCells(rPos.Tab(), rPos.Col(), rPos.Row(), rPos.Row(), aCells);
326 0 : std::for_each(aCells.begin(), aCells.end(), Notifier(rHint));
327 0 : }
328 :
329 156 : }
330 :
331 : /* vim:set shiftwidth=4 softtabstop=4 expandtab: */
|