File: | home/maarten/src/libreoffice/core/sc/source/core/tool/sharedformula.cxx |
Warning: | line 121, column 1 Potential leak of memory pointed to by 'xGroup2.px' |
Press '?' to see keyboard shortcuts
Keyboard shortcuts:
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->Clone(); | |||
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_LISTENER1 | |||
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. | |||
99 | ScFormulaCellGroupRef xNone; | |||
100 | rPrevTop.SetCellGroup(xNone); | |||
101 | } | |||
102 | ||||
103 | // Apply the lower group object to the lower cells. | |||
104 | #if DEBUG_COLUMN_STORAGE0 | |||
105 | if (xGroup2->mpTopCell->aPos.Row() + size_t(xGroup2->mnLength) > aPos.first->position + aPos.first->size) | |||
106 | { | |||
107 | cerr << "ScColumn::SplitFormulaCellGroup: Shared formula region goes beyond the formula block. Not good." << endl; | |||
108 | cerr.flush(); | |||
109 | abort(); | |||
110 | } | |||
111 | #endif | |||
112 | sc::formula_block::iterator itEnd = it; | |||
113 | std::advance(itEnd, nLength2); | |||
114 | for (; it != itEnd; ++it) | |||
115 | { | |||
116 | ScFormulaCell& rCell = **it; | |||
117 | rCell.SetCellGroup(xGroup2); | |||
118 | } | |||
119 | ||||
120 | return true; | |||
121 | } | |||
| ||||
122 | ||||
123 | bool SharedFormulaUtil::splitFormulaCellGroups(const ScDocument& rDoc, CellStoreType& rCells, std::vector<SCROW>& rBounds) | |||
124 | { | |||
125 | if (rBounds.empty()) | |||
126 | return false; | |||
127 | ||||
128 | // Sort and remove duplicates. | |||
129 | std::sort(rBounds.begin(), rBounds.end()); | |||
130 | std::vector<SCROW>::iterator it = std::unique(rBounds.begin(), rBounds.end()); | |||
131 | rBounds.erase(it, rBounds.end()); | |||
132 | ||||
133 | it = rBounds.begin(); | |||
134 | SCROW nRow = *it; | |||
135 | CellStoreType::position_type aPos = rCells.position(nRow); | |||
136 | if (aPos.first == rCells.end()) | |||
137 | return false; | |||
138 | ||||
139 | bool bSplit = splitFormulaCellGroup(aPos, nullptr); | |||
140 | std::vector<SCROW>::iterator itEnd = rBounds.end(); | |||
141 | for (++it; it != itEnd; ++it) | |||
142 | { | |||
143 | nRow = *it; | |||
144 | if (rDoc.ValidRow(nRow)) | |||
145 | { | |||
146 | aPos = rCells.position(aPos.first, nRow); | |||
147 | if (aPos.first == rCells.end()) | |||
148 | return bSplit; | |||
149 | bSplit |= splitFormulaCellGroup(aPos, nullptr); | |||
150 | } | |||
151 | } | |||
152 | return bSplit; | |||
153 | } | |||
154 | ||||
155 | bool SharedFormulaUtil::joinFormulaCells( | |||
156 | const CellStoreType::position_type& rPos, ScFormulaCell& rCell1, ScFormulaCell& rCell2 ) | |||
157 | { | |||
158 | if( rCell1.GetDocument().IsDelayedFormulaGrouping()) | |||
159 | { | |||
160 | rCell1.GetDocument().AddDelayedFormulaGroupingCell( &rCell1 ); | |||
161 | rCell1.GetDocument().AddDelayedFormulaGroupingCell( &rCell2 ); | |||
162 | return false; | |||
163 | } | |||
164 | ||||
165 | ScFormulaCell::CompareState eState = rCell1.CompareByTokenArray(rCell2); | |||
166 | if (eState == ScFormulaCell::NotEqual) | |||
167 | return false; | |||
168 | ||||
169 | // Formula tokens equal those of the previous formula cell. | |||
170 | ScFormulaCellGroupRef xGroup1 = rCell1.GetCellGroup(); | |||
171 | ScFormulaCellGroupRef xGroup2 = rCell2.GetCellGroup(); | |||
172 | if (xGroup1) | |||
173 | { | |||
174 | if (xGroup2) | |||
175 | { | |||
176 | // Both cell 1 and cell 2 are shared. Merge them together. | |||
177 | if (xGroup1.get() == xGroup2.get()) | |||
178 | // They belong to the same group. | |||
179 | return false; | |||
180 | ||||
181 | // Set the group object from cell 1 to all cells in group 2. | |||
182 | xGroup1->mnLength += xGroup2->mnLength; | |||
183 | size_t nOffset = rPos.second + 1; // position of cell 2 | |||
184 | for (size_t i = 0, n = xGroup2->mnLength; i < n; ++i) | |||
185 | { | |||
186 | ScFormulaCell& rCell = *sc::formula_block::at(*rPos.first->data, nOffset+i); | |||
187 | rCell.SetCellGroup(xGroup1); | |||
188 | } | |||
189 | } | |||
190 | else | |||
191 | { | |||
192 | // cell 1 is shared but cell 2 is not. | |||
193 | rCell2.SetCellGroup(xGroup1); | |||
194 | ++xGroup1->mnLength; | |||
195 | } | |||
196 | } | |||
197 | else | |||
198 | { | |||
199 | if (xGroup2) | |||
200 | { | |||
201 | // cell 1 is not shared, but cell 2 is already shared. | |||
202 | rCell1.SetCellGroup(xGroup2); | |||
203 | xGroup2->mpTopCell = &rCell1; | |||
204 | ++xGroup2->mnLength; | |||
205 | } | |||
206 | else | |||
207 | { | |||
208 | // neither cells are shared. | |||
209 | assert(rCell1.aPos.Row() == static_cast<SCROW>(rPos.first->position + rPos.second))(static_cast <bool> (rCell1.aPos.Row() == static_cast< SCROW>(rPos.first->position + rPos.second)) ? void (0) : __assert_fail ("rCell1.aPos.Row() == static_cast<SCROW>(rPos.first->position + rPos.second)" , "/home/maarten/src/libreoffice/core/sc/source/core/tool/sharedformula.cxx" , 209, __extension__ __PRETTY_FUNCTION__)); | |||
210 | xGroup1 = rCell1.CreateCellGroup(2, eState == ScFormulaCell::EqualInvariant); | |||
211 | rCell2.SetCellGroup(xGroup1); | |||
212 | } | |||
213 | } | |||
214 | ||||
215 | return true; | |||
216 | } | |||
217 | ||||
218 | bool SharedFormulaUtil::joinFormulaCellAbove( const CellStoreType::position_type& aPos ) | |||
219 | { | |||
220 | if (aPos.first->type != sc::element_type_formula) | |||
221 | // This is not a formula cell. | |||
222 | return false; | |||
223 | ||||
224 | if (aPos.second == 0) | |||
225 | // This cell is already the top cell in a formula block; the previous | |||
226 | // cell is not a formula cell. | |||
227 | return false; | |||
228 | ||||
229 | ScFormulaCell& rPrev = *sc::formula_block::at(*aPos.first->data, aPos.second-1); | |||
230 | ScFormulaCell& rCell = *sc::formula_block::at(*aPos.first->data, aPos.second); | |||
231 | sc::CellStoreType::position_type aPosPrev = aPos; | |||
232 | --aPosPrev.second; | |||
233 | return joinFormulaCells(aPosPrev, rPrev, rCell); | |||
234 | } | |||
235 | ||||
236 | void SharedFormulaUtil::unshareFormulaCell(const CellStoreType::position_type& aPos, ScFormulaCell& rCell) | |||
237 | { | |||
238 | if (!rCell.IsShared()) | |||
239 | return; | |||
240 | ||||
241 | ScFormulaCellGroupRef xNone; | |||
242 | sc::CellStoreType::iterator it = aPos.first; | |||
243 | ||||
244 | // This formula cell is shared. Adjust the shared group. | |||
245 | if (rCell.aPos.Row() == rCell.GetSharedTopRow()) | |||
246 | { | |||
247 | // Top of the shared range. | |||
248 | const ScFormulaCellGroupRef& xGroup = rCell.GetCellGroup(); | |||
249 | if (xGroup->mnLength == 2) | |||
250 | { | |||
251 | // Group consists of only two cells. Mark the second one non-shared. | |||
252 | #if DEBUG_COLUMN_STORAGE0 | |||
253 | if (aPos.second+1 >= aPos.first->size) | |||
254 | { | |||
255 | cerr << "ScColumn::UnshareFormulaCell: There is no next formula cell but there should be!" << endl; | |||
256 | cerr.flush(); | |||
257 | abort(); | |||
258 | } | |||
259 | #endif | |||
260 | ScFormulaCell& rNext = *sc::formula_block::at(*it->data, aPos.second+1); | |||
261 | rNext.SetCellGroup(xNone); | |||
262 | } | |||
263 | else | |||
264 | { | |||
265 | // Move the top cell to the next formula cell down. | |||
266 | ScFormulaCell& rNext = *sc::formula_block::at(*it->data, aPos.second+1); | |||
267 | xGroup->mpTopCell = &rNext; | |||
268 | } | |||
269 | --xGroup->mnLength; | |||
270 | } | |||
271 | else if (rCell.aPos.Row() == rCell.GetSharedTopRow() + rCell.GetSharedLength() - 1) | |||
272 | { | |||
273 | // Bottom of the shared range. | |||
274 | const ScFormulaCellGroupRef& xGroup = rCell.GetCellGroup(); | |||
275 | if (xGroup->mnLength == 2) | |||
276 | { | |||
277 | // Mark the top cell non-shared. | |||
278 | #if DEBUG_COLUMN_STORAGE0 | |||
279 | if (aPos.second == 0) | |||
280 | { | |||
281 | cerr << "ScColumn::UnshareFormulaCell: There is no previous formula cell but there should be!" << endl; | |||
282 | cerr.flush(); | |||
283 | abort(); | |||
284 | } | |||
285 | #endif | |||
286 | ScFormulaCell& rPrev = *sc::formula_block::at(*it->data, aPos.second-1); | |||
287 | rPrev.SetCellGroup(xNone); | |||
288 | } | |||
289 | else | |||
290 | { | |||
291 | // Just shorten the shared range length by one. | |||
292 | --xGroup->mnLength; | |||
293 | } | |||
294 | } | |||
295 | else | |||
296 | { | |||
297 | // In the middle of the shared range. Split it into two groups. | |||
298 | ScFormulaCellGroupRef xGroup = rCell.GetCellGroup(); | |||
299 | SCROW nEndRow = xGroup->mpTopCell->aPos.Row() + xGroup->mnLength - 1; | |||
300 | xGroup->mnLength = rCell.aPos.Row() - xGroup->mpTopCell->aPos.Row(); // Shorten the top group. | |||
301 | if (xGroup->mnLength == 1) | |||
302 | { | |||
303 | // Make the top cell non-shared. | |||
304 | #if DEBUG_COLUMN_STORAGE0 | |||
305 | if (aPos.second == 0) | |||
306 | { | |||
307 | cerr << "ScColumn::UnshareFormulaCell: There is no previous formula cell but there should be!" << endl; | |||
308 | cerr.flush(); | |||
309 | abort(); | |||
310 | } | |||
311 | #endif | |||
312 | ScFormulaCell& rPrev = *sc::formula_block::at(*it->data, aPos.second-1); | |||
313 | rPrev.SetCellGroup(xNone); | |||
314 | } | |||
315 | ||||
316 | SCROW nLength2 = nEndRow - rCell.aPos.Row(); | |||
317 | if (nLength2 >= 2) | |||
318 | { | |||
319 | ScFormulaCellGroupRef xGroup2; | |||
320 | xGroup2.reset(new ScFormulaCellGroup); | |||
321 | ScFormulaCell& rNext = *sc::formula_block::at(*it->data, aPos.second+1); | |||
322 | xGroup2->mpTopCell = &rNext; | |||
323 | xGroup2->mnLength = nLength2; | |||
324 | xGroup2->mbInvariant = xGroup->mbInvariant; | |||
325 | xGroup2->mpCode = xGroup->mpCode->Clone(); | |||
326 | #if DEBUG_COLUMN_STORAGE0 | |||
327 | if (xGroup2->mpTopCell->aPos.Row() + size_t(xGroup2->mnLength) > it->position + it->size) | |||
328 | { | |||
329 | cerr << "ScColumn::UnshareFormulaCell: Shared formula region goes beyond the formula block. Not good." << endl; | |||
330 | cerr.flush(); | |||
331 | abort(); | |||
332 | } | |||
333 | #endif | |||
334 | sc::formula_block::iterator itCell = sc::formula_block::begin(*it->data); | |||
335 | std::advance(itCell, aPos.second+1); | |||
336 | sc::formula_block::iterator itCellEnd = itCell; | |||
337 | std::advance(itCellEnd, xGroup2->mnLength); | |||
338 | for (; itCell != itCellEnd; ++itCell) | |||
339 | { | |||
340 | ScFormulaCell& rCell2 = **itCell; | |||
341 | rCell2.SetCellGroup(xGroup2); | |||
342 | } | |||
343 | } | |||
344 | else | |||
345 | { | |||
346 | // Make the next cell non-shared. | |||
347 | sc::formula_block::iterator itCell = sc::formula_block::begin(*it->data); | |||
348 | std::advance(itCell, aPos.second+1); | |||
349 | ScFormulaCell& rCell2 = **itCell; | |||
350 | rCell2.SetCellGroup(xNone); | |||
351 | } | |||
352 | } | |||
353 | ||||
354 | rCell.SetCellGroup(xNone); | |||
355 | } | |||
356 | ||||
357 | void SharedFormulaUtil::unshareFormulaCells(const ScDocument& rDoc, CellStoreType& rCells, std::vector<SCROW>& rRows) | |||
358 | { | |||
359 | if (rRows.empty()) | |||
| ||||
360 | return; | |||
361 | ||||
362 | // Sort and remove duplicates. | |||
363 | std::sort(rRows.begin(), rRows.end()); | |||
364 | rRows.erase(std::unique(rRows.begin(), rRows.end()), rRows.end()); | |||
365 | ||||
366 | // Add next cell positions to the list (to ensure that each position becomes a single cell). | |||
367 | std::vector<SCROW> aRows2; | |||
368 | for (const auto& rRow : rRows) | |||
369 | { | |||
370 | if (rRow > rDoc.MaxRow()) | |||
371 | break; | |||
372 | ||||
373 | aRows2.push_back(rRow); | |||
374 | ||||
375 | if (rRow < rDoc.MaxRow()) | |||
376 | aRows2.push_back(rRow+1); | |||
377 | } | |||
378 | ||||
379 | // Remove duplicates again (the vector should still be sorted). | |||
380 | aRows2.erase(std::unique(aRows2.begin(), aRows2.end()), aRows2.end()); | |||
381 | ||||
382 | splitFormulaCellGroups(rDoc, rCells, aRows2); | |||
383 | } | |||
384 | ||||
385 | void SharedFormulaUtil::startListeningAsGroup( sc::StartListeningContext& rCxt, ScFormulaCell** ppSharedTop ) | |||
386 | { | |||
387 | ScFormulaCell& rTopCell = **ppSharedTop; | |||
388 | assert(rTopCell.IsSharedTop())(static_cast <bool> (rTopCell.IsSharedTop()) ? void (0) : __assert_fail ("rTopCell.IsSharedTop()", "/home/maarten/src/libreoffice/core/sc/source/core/tool/sharedformula.cxx" , 388, __extension__ __PRETTY_FUNCTION__)); | |||
389 | ||||
390 | #if USE_FORMULA_GROUP_LISTENER1 | |||
391 | ScDocument& rDoc = rCxt.getDoc(); | |||
392 | rDoc.SetDetectiveDirty(true); | |||
393 | ||||
394 | ScFormulaCellGroupRef xGroup = rTopCell.GetCellGroup(); | |||
395 | const ScTokenArray* pCode = xGroup->mpCode.get(); | |||
396 | assert(pCode == rTopCell.GetCode())(static_cast <bool> (pCode == rTopCell.GetCode()) ? void (0) : __assert_fail ("pCode == rTopCell.GetCode()", "/home/maarten/src/libreoffice/core/sc/source/core/tool/sharedformula.cxx" , 396, __extension__ __PRETTY_FUNCTION__)); | |||
397 | if (pCode->IsRecalcModeAlways()) | |||
398 | { | |||
399 | rDoc.StartListeningArea( | |||
400 | BCA_LISTEN_ALWAYSScRange( ScAddress( 0, SCROW_MAX, 0 ), ScAddress( 0, SCROW_MAX , 0 ) ), false, | |||
401 | xGroup->getAreaListener(ppSharedTop, BCA_LISTEN_ALWAYSScRange( ScAddress( 0, SCROW_MAX, 0 ), ScAddress( 0, SCROW_MAX , 0 ) ), true, true)); | |||
402 | } | |||
403 | ||||
404 | formula::FormulaToken** p = pCode->GetCode(); | |||
405 | formula::FormulaToken** pEnd = p + pCode->GetCodeLen(); | |||
406 | for (; p != pEnd; ++p) | |||
407 | { | |||
408 | const formula::FormulaToken* t = *p; | |||
409 | switch (t->GetType()) | |||
410 | { | |||
411 | case formula::svSingleRef: | |||
412 | { | |||
413 | const ScSingleRefData* pRef = t->GetSingleRef(); | |||
414 | ScAddress aPos = pRef->toAbs(rDoc, rTopCell.aPos); | |||
415 | ScFormulaCell** pp = ppSharedTop; | |||
416 | ScFormulaCell** ppEnd = ppSharedTop + xGroup->mnLength; | |||
417 | for (; pp != ppEnd; ++pp) | |||
418 | { | |||
419 | if (!aPos.IsValid()) | |||
420 | break; | |||
421 | ||||
422 | rDoc.StartListeningCell(rCxt, aPos, **pp); | |||
423 | if (pRef->IsRowRel()) | |||
424 | aPos.IncRow(); | |||
425 | } | |||
426 | } | |||
427 | break; | |||
428 | case formula::svDoubleRef: | |||
429 | { | |||
430 | const ScSingleRefData& rRef1 = *t->GetSingleRef(); | |||
431 | const ScSingleRefData& rRef2 = *t->GetSingleRef2(); | |||
432 | ScAddress aPos1 = rRef1.toAbs(rDoc, rTopCell.aPos); | |||
433 | ScAddress aPos2 = rRef2.toAbs(rDoc, rTopCell.aPos); | |||
434 | ||||
435 | ScRange aOrigRange(aPos1, aPos2); | |||
436 | ScRange aListenedRange = aOrigRange; | |||
437 | if (rRef2.IsRowRel()) | |||
438 | aListenedRange.aEnd.IncRow(xGroup->mnLength-1); | |||
439 | ||||
440 | if (aPos1.IsValid() && aPos2.IsValid()) | |||
441 | { | |||
442 | rDoc.StartListeningArea( | |||
443 | aListenedRange, true, | |||
444 | xGroup->getAreaListener(ppSharedTop, aOrigRange, !rRef1.IsRowRel(), !rRef2.IsRowRel())); | |||
445 | } | |||
446 | } | |||
447 | break; | |||
448 | default: | |||
449 | ; | |||
450 | } | |||
451 | } | |||
452 | ||||
453 | ScFormulaCell** pp = ppSharedTop; | |||
454 | ScFormulaCell** ppEnd = ppSharedTop + xGroup->mnLength; | |||
455 | for (; pp != ppEnd; ++pp) | |||
456 | { | |||
457 | ScFormulaCell& rCell = **pp; | |||
458 | rCell.SetNeedsListening(false); | |||
459 | } | |||
460 | ||||
461 | #else | |||
462 | ScFormulaCell** pp = ppSharedTop; | |||
463 | ScFormulaCell** ppEnd = ppSharedTop + rTopCell.GetSharedLength(); | |||
464 | for (; pp != ppEnd; ++pp) | |||
465 | { | |||
466 | ScFormulaCell& rFC = **pp; | |||
467 | rFC.StartListeningTo(rCxt); | |||
468 | } | |||
469 | #endif | |||
470 | } | |||
471 | ||||
472 | } | |||
473 | ||||
474 | /* vim:set shiftwidth=4 softtabstop=4 expandtab: */ |