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 "documentimport.hxx"
11 : #include "document.hxx"
12 : #include "table.hxx"
13 : #include "column.hxx"
14 : #include "formulacell.hxx"
15 : #include "docoptio.hxx"
16 : #include "globalnames.hxx"
17 : #include "mtvelements.hxx"
18 : #include "tokenarray.hxx"
19 : #include "stringutil.hxx"
20 : #include "compiler.hxx"
21 : #include "paramisc.hxx"
22 : #include "listenercontext.hxx"
23 :
24 : #include "svl/sharedstringpool.hxx"
25 :
26 0 : struct ScDocumentImportImpl
27 : {
28 : ScDocument& mrDoc;
29 : sc::StartListeningContext maListenCxt;
30 : sc::ColumnBlockPositionSet maBlockPosSet;
31 : sal_uInt16 mnDefaultScriptNumeric;
32 :
33 0 : ScDocumentImportImpl(ScDocument& rDoc) :
34 : mrDoc(rDoc),
35 : maListenCxt(rDoc),
36 : maBlockPosSet(rDoc),
37 0 : mnDefaultScriptNumeric(SC_SCRIPTTYPE_UNKNOWN) {}
38 : };
39 :
40 0 : ScDocumentImport::ScDocumentImport(ScDocument& rDoc) : mpImpl(new ScDocumentImportImpl(rDoc)) {}
41 0 : ScDocumentImport::~ScDocumentImport()
42 : {
43 0 : delete mpImpl;
44 0 : }
45 :
46 0 : ScDocument& ScDocumentImport::getDoc()
47 : {
48 0 : return mpImpl->mrDoc;
49 : }
50 :
51 0 : const ScDocument& ScDocumentImport::getDoc() const
52 : {
53 0 : return mpImpl->mrDoc;
54 : }
55 :
56 0 : void ScDocumentImport::setDefaultNumericScript(sal_uInt16 nScript)
57 : {
58 0 : mpImpl->mnDefaultScriptNumeric = nScript;
59 0 : }
60 :
61 0 : void ScDocumentImport::setCellStyleToSheet(SCTAB nTab, const ScStyleSheet& rStyle)
62 : {
63 0 : ScTable* pTab = mpImpl->mrDoc.FetchTable(nTab);
64 0 : if (!pTab)
65 0 : return;
66 :
67 0 : pTab->ApplyStyleArea(0, 0, MAXCOL, MAXROW, rStyle);
68 : }
69 :
70 0 : SCTAB ScDocumentImport::getSheetIndex(const OUString& rName) const
71 : {
72 0 : SCTAB nTab = -1;
73 0 : if (!mpImpl->mrDoc.GetTable(rName, nTab))
74 0 : return -1;
75 :
76 0 : return nTab;
77 : }
78 :
79 0 : SCTAB ScDocumentImport::getSheetCount() const
80 : {
81 0 : return mpImpl->mrDoc.maTabs.size();
82 : }
83 :
84 0 : bool ScDocumentImport::appendSheet(const OUString& rName)
85 : {
86 0 : SCTAB nTabCount = mpImpl->mrDoc.maTabs.size();
87 0 : if (!ValidTab(nTabCount))
88 0 : return false;
89 :
90 0 : mpImpl->mrDoc.maTabs.push_back(new ScTable(&mpImpl->mrDoc, nTabCount, rName));
91 0 : return true;
92 : }
93 :
94 0 : void ScDocumentImport::setOriginDate(sal_uInt16 nYear, sal_uInt16 nMonth, sal_uInt16 nDay)
95 : {
96 0 : if (!mpImpl->mrDoc.pDocOptions)
97 0 : mpImpl->mrDoc.pDocOptions = new ScDocOptions;
98 :
99 0 : mpImpl->mrDoc.pDocOptions->SetDate(nDay, nMonth, nYear);
100 0 : }
101 :
102 0 : void ScDocumentImport::setAutoInput(const ScAddress& rPos, const OUString& rStr, ScSetStringParam* pStringParam)
103 : {
104 0 : ScTable* pTab = mpImpl->mrDoc.FetchTable(rPos.Tab());
105 0 : if (!pTab)
106 0 : return;
107 :
108 : sc::ColumnBlockPosition* pBlockPos =
109 0 : mpImpl->maBlockPosSet.getBlockPosition(rPos.Tab(), rPos.Col());
110 :
111 0 : if (!pBlockPos)
112 0 : return;
113 :
114 0 : ScCellValue aCell;
115 0 : pTab->aCol[rPos.Col()].ParseString(
116 0 : aCell, rPos.Row(), rPos.Tab(), rStr, mpImpl->mrDoc.GetAddressConvention(), pStringParam);
117 :
118 0 : sc::CellStoreType& rCells = pTab->aCol[rPos.Col()].maCells;
119 0 : switch (aCell.meType)
120 : {
121 : case CELLTYPE_STRING:
122 : // string is copied.
123 0 : pBlockPos->miCellPos = rCells.set(pBlockPos->miCellPos, rPos.Row(), *aCell.mpString);
124 0 : break;
125 : case CELLTYPE_EDIT:
126 : // Cell takes the ownership of the text object.
127 0 : pBlockPos->miCellPos = rCells.set(pBlockPos->miCellPos, rPos.Row(), aCell.mpEditText);
128 0 : aCell.mpEditText = NULL;
129 0 : break;
130 : case CELLTYPE_VALUE:
131 0 : pBlockPos->miCellPos = rCells.set(pBlockPos->miCellPos, rPos.Row(), aCell.mfValue);
132 0 : break;
133 : case CELLTYPE_FORMULA:
134 : // This formula cell instance is directly placed in the document without copying.
135 0 : pBlockPos->miCellPos = rCells.set(pBlockPos->miCellPos, rPos.Row(), aCell.mpFormula);
136 0 : aCell.mpFormula = NULL;
137 0 : break;
138 : default:
139 0 : pBlockPos->miCellPos = rCells.set_empty(pBlockPos->miCellPos, rPos.Row(), rPos.Row());
140 0 : }
141 : }
142 :
143 0 : void ScDocumentImport::setNumericCell(const ScAddress& rPos, double fVal)
144 : {
145 0 : ScTable* pTab = mpImpl->mrDoc.FetchTable(rPos.Tab());
146 0 : if (!pTab)
147 0 : return;
148 :
149 : sc::ColumnBlockPosition* pBlockPos =
150 0 : mpImpl->maBlockPosSet.getBlockPosition(rPos.Tab(), rPos.Col());
151 :
152 0 : if (!pBlockPos)
153 0 : return;
154 :
155 0 : sc::CellStoreType& rCells = pTab->aCol[rPos.Col()].maCells;
156 0 : pBlockPos->miCellPos = rCells.set(pBlockPos->miCellPos, rPos.Row(), fVal);
157 : }
158 :
159 0 : void ScDocumentImport::setStringCell(const ScAddress& rPos, const OUString& rStr)
160 : {
161 0 : ScTable* pTab = mpImpl->mrDoc.FetchTable(rPos.Tab());
162 0 : if (!pTab)
163 0 : return;
164 :
165 : sc::ColumnBlockPosition* pBlockPos =
166 0 : mpImpl->maBlockPosSet.getBlockPosition(rPos.Tab(), rPos.Col());
167 :
168 0 : if (!pBlockPos)
169 0 : return;
170 :
171 0 : svl::SharedString aSS = mpImpl->mrDoc.GetSharedStringPool().intern(rStr);
172 0 : if (!aSS.getData())
173 0 : return;
174 :
175 0 : sc::CellStoreType& rCells = pTab->aCol[rPos.Col()].maCells;
176 0 : pBlockPos->miCellPos = rCells.set(pBlockPos->miCellPos, rPos.Row(), aSS);
177 : }
178 :
179 0 : void ScDocumentImport::setEditCell(const ScAddress& rPos, EditTextObject* pEditText)
180 : {
181 0 : ScTable* pTab = mpImpl->mrDoc.FetchTable(rPos.Tab());
182 0 : if (!pTab)
183 0 : return;
184 :
185 : sc::ColumnBlockPosition* pBlockPos =
186 0 : mpImpl->maBlockPosSet.getBlockPosition(rPos.Tab(), rPos.Col());
187 :
188 0 : if (!pBlockPos)
189 0 : return;
190 :
191 0 : pEditText->NormalizeString(mpImpl->mrDoc.GetSharedStringPool());
192 0 : sc::CellStoreType& rCells = pTab->aCol[rPos.Col()].maCells;
193 0 : pBlockPos->miCellPos = rCells.set(pBlockPos->miCellPos, rPos.Row(), pEditText);
194 : }
195 :
196 0 : void ScDocumentImport::setFormulaCell(
197 : const ScAddress& rPos, const OUString& rFormula, formula::FormulaGrammar::Grammar eGrammar)
198 : {
199 0 : ScTable* pTab = mpImpl->mrDoc.FetchTable(rPos.Tab());
200 0 : if (!pTab)
201 0 : return;
202 :
203 : sc::ColumnBlockPosition* pBlockPos =
204 0 : mpImpl->maBlockPosSet.getBlockPosition(rPos.Tab(), rPos.Col());
205 :
206 0 : if (!pBlockPos)
207 0 : return;
208 :
209 0 : sc::CellStoreType& rCells = pTab->aCol[rPos.Col()].maCells;
210 0 : pBlockPos->miCellPos =
211 0 : rCells.set(pBlockPos->miCellPos, rPos.Row(), new ScFormulaCell(&mpImpl->mrDoc, rPos, rFormula, eGrammar));
212 : }
213 :
214 0 : void ScDocumentImport::setFormulaCell(const ScAddress& rPos, ScTokenArray* pArray)
215 : {
216 0 : ScTable* pTab = mpImpl->mrDoc.FetchTable(rPos.Tab());
217 0 : if (!pTab)
218 0 : return;
219 :
220 : sc::ColumnBlockPosition* pBlockPos =
221 0 : mpImpl->maBlockPosSet.getBlockPosition(rPos.Tab(), rPos.Col());
222 :
223 0 : if (!pBlockPos)
224 0 : return;
225 :
226 0 : sc::CellStoreType& rCells = pTab->aCol[rPos.Col()].maCells;
227 0 : pBlockPos->miCellPos =
228 0 : rCells.set(pBlockPos->miCellPos, rPos.Row(), new ScFormulaCell(&mpImpl->mrDoc, rPos, pArray));
229 : }
230 :
231 0 : void ScDocumentImport::setFormulaCell(const ScAddress& rPos, ScFormulaCell* pCell)
232 : {
233 0 : ScTable* pTab = mpImpl->mrDoc.FetchTable(rPos.Tab());
234 0 : if (!pTab)
235 0 : return;
236 :
237 : sc::ColumnBlockPosition* pBlockPos =
238 0 : mpImpl->maBlockPosSet.getBlockPosition(rPos.Tab(), rPos.Col());
239 :
240 0 : if (!pBlockPos)
241 0 : return;
242 :
243 0 : sc::CellStoreType& rCells = pTab->aCol[rPos.Col()].maCells;
244 0 : pBlockPos->miCellPos =
245 0 : rCells.set(pBlockPos->miCellPos, rPos.Row(), pCell);
246 : }
247 :
248 0 : void ScDocumentImport::setMatrixCells(
249 : const ScRange& rRange, const ScTokenArray& rArray, formula::FormulaGrammar::Grammar eGram)
250 : {
251 0 : const ScAddress& rBasePos = rRange.aStart;
252 :
253 0 : ScTable* pTab = mpImpl->mrDoc.FetchTable(rBasePos.Tab());
254 0 : if (!pTab)
255 0 : return;
256 :
257 : sc::ColumnBlockPosition* pBlockPos =
258 0 : mpImpl->maBlockPosSet.getBlockPosition(rBasePos.Tab(), rBasePos.Col());
259 :
260 0 : if (!pBlockPos)
261 0 : return;
262 :
263 0 : sc::CellStoreType& rCells = pTab->aCol[rBasePos.Col()].maCells;
264 :
265 : // Set the master cell.
266 0 : ScFormulaCell* pCell = new ScFormulaCell(&mpImpl->mrDoc, rBasePos, rArray, eGram, MM_FORMULA);
267 :
268 0 : pBlockPos->miCellPos =
269 0 : rCells.set(pBlockPos->miCellPos, rBasePos.Row(), pCell);
270 :
271 : // Matrix formulas currently need re-calculation on import.
272 : pCell->SetMatColsRows(
273 0 : rRange.aEnd.Col()-rRange.aStart.Col()+1, rRange.aEnd.Row()-rRange.aStart.Row()+1, true);
274 :
275 : // Set the reference cells.
276 : ScSingleRefData aRefData;
277 0 : aRefData.InitFlags();
278 0 : aRefData.SetColRel(true);
279 0 : aRefData.SetRowRel(true);
280 0 : aRefData.SetTabRel(true);
281 0 : aRefData.SetAddress(rBasePos, rBasePos);
282 :
283 0 : ScTokenArray aArr; // consists only of one single reference token.
284 0 : ScToken* t = static_cast<ScToken*>(aArr.AddMatrixSingleReference(aRefData));
285 :
286 0 : ScAddress aPos = rBasePos;
287 0 : for (SCROW nRow = rRange.aStart.Row()+1; nRow <= rRange.aEnd.Row(); ++nRow)
288 : {
289 : // Token array must be cloned so that each formula cell receives its own copy.
290 0 : aPos.SetRow(nRow);
291 : // Reference in each cell must point to the origin cell relative to the current cell.
292 0 : aRefData.SetAddress(rBasePos, aPos);
293 0 : t->GetSingleRef() = aRefData;
294 0 : boost::scoped_ptr<ScTokenArray> pTokArr(aArr.Clone());
295 0 : pCell = new ScFormulaCell(&mpImpl->mrDoc, aPos, *pTokArr, eGram, MM_REFERENCE);
296 0 : pBlockPos->miCellPos =
297 0 : rCells.set(pBlockPos->miCellPos, aPos.Row(), pCell);
298 0 : }
299 :
300 0 : for (SCCOL nCol = rRange.aStart.Col()+1; nCol <= rRange.aEnd.Col(); ++nCol)
301 : {
302 0 : pBlockPos = mpImpl->maBlockPosSet.getBlockPosition(rBasePos.Tab(), nCol);
303 0 : if (!pBlockPos)
304 0 : return;
305 :
306 0 : sc::CellStoreType& rColCells = pTab->aCol[nCol].maCells;
307 :
308 0 : aPos.SetCol(nCol);
309 0 : for (SCROW nRow = rRange.aStart.Row(); nRow <= rRange.aEnd.Row(); ++nRow)
310 : {
311 0 : aPos.SetRow(nRow);
312 0 : aRefData.SetAddress(rBasePos, aPos);
313 0 : t->GetSingleRef() = aRefData;
314 0 : boost::scoped_ptr<ScTokenArray> pTokArr(aArr.Clone());
315 0 : pCell = new ScFormulaCell(&mpImpl->mrDoc, aPos, *pTokArr, eGram, MM_REFERENCE);
316 0 : pBlockPos->miCellPos =
317 0 : rColCells.set(pBlockPos->miCellPos, aPos.Row(), pCell);
318 0 : }
319 0 : }
320 : }
321 :
322 0 : void ScDocumentImport::setTableOpCells(const ScRange& rRange, const ScTabOpParam& rParam)
323 : {
324 0 : SCTAB nTab = rRange.aStart.Tab();
325 0 : SCCOL nCol1 = rRange.aStart.Col();
326 0 : SCROW nRow1 = rRange.aStart.Row();
327 0 : SCCOL nCol2 = rRange.aEnd.Col();
328 0 : SCROW nRow2 = rRange.aEnd.Row();
329 :
330 0 : ScTable* pTab = mpImpl->mrDoc.FetchTable(nTab);
331 0 : if (!pTab)
332 0 : return;
333 :
334 0 : ScDocument* pDoc = &mpImpl->mrDoc;
335 0 : ScRefAddress aRef;
336 0 : OUStringBuffer aFormulaBuf('=');
337 0 : aFormulaBuf.append(ScCompiler::GetNativeSymbol(ocTableOp));
338 0 : aFormulaBuf.append(ScCompiler::GetNativeSymbol(ocOpen));
339 :
340 0 : OUString aSep = ScCompiler::GetNativeSymbol(ocSep);
341 0 : if (rParam.meMode == ScTabOpParam::Column) // column only
342 : {
343 0 : aRef.Set(rParam.aRefFormulaCell.GetAddress(), true, false, false);
344 0 : aFormulaBuf.append(aRef.GetRefString(pDoc, nTab));
345 0 : aFormulaBuf.append(aSep);
346 0 : aFormulaBuf.append(rParam.aRefColCell.GetRefString(pDoc, nTab));
347 0 : aFormulaBuf.append(aSep);
348 0 : aRef.Set(nCol1, nRow1, nTab, false, true, true);
349 0 : aFormulaBuf.append(aRef.GetRefString(pDoc, nTab));
350 0 : nCol1++;
351 0 : nCol2 = std::min( nCol2, (SCCOL)(rParam.aRefFormulaEnd.Col() -
352 0 : rParam.aRefFormulaCell.Col() + nCol1 + 1));
353 : }
354 0 : else if (rParam.meMode == ScTabOpParam::Row) // row only
355 : {
356 0 : aRef.Set(rParam.aRefFormulaCell.GetAddress(), false, true, false);
357 0 : aFormulaBuf.append(aRef.GetRefString(pDoc, nTab));
358 0 : aFormulaBuf.append(aSep);
359 0 : aFormulaBuf.append(rParam.aRefRowCell.GetRefString(pDoc, nTab));
360 0 : aFormulaBuf.append(aSep);
361 0 : aRef.Set(nCol1, nRow1, nTab, true, false, true);
362 0 : aFormulaBuf.append(aRef.GetRefString(pDoc, nTab));
363 0 : ++nRow1;
364 : nRow2 = std::min(
365 0 : nRow2, rParam.aRefFormulaEnd.Row() - rParam.aRefFormulaCell.Row() + nRow1 + 1);
366 : }
367 : else // both
368 : {
369 0 : aFormulaBuf.append(rParam.aRefFormulaCell.GetRefString(pDoc, nTab));
370 0 : aFormulaBuf.append(aSep);
371 0 : aFormulaBuf.append(rParam.aRefColCell.GetRefString(pDoc, nTab));
372 0 : aFormulaBuf.append(aSep);
373 0 : aRef.Set(nCol1, nRow1 + 1, nTab, false, true, true);
374 0 : aFormulaBuf.append(aRef.GetRefString(pDoc, nTab));
375 0 : aFormulaBuf.append(aSep);
376 0 : aFormulaBuf.append(rParam.aRefRowCell.GetRefString(pDoc, nTab));
377 0 : aFormulaBuf.append(aSep);
378 0 : aRef.Set(nCol1 + 1, nRow1, nTab, true, false, true);
379 0 : aFormulaBuf.append(aRef.GetRefString(pDoc, nTab));
380 0 : ++nCol1;
381 0 : ++nRow1;
382 : }
383 :
384 0 : aFormulaBuf.append(ScCompiler::GetNativeSymbol(ocClose));
385 :
386 : ScFormulaCell aRefCell(
387 : pDoc, ScAddress(nCol1, nRow1, nTab), aFormulaBuf.makeStringAndClear(),
388 0 : formula::FormulaGrammar::GRAM_NATIVE, MM_NONE);
389 :
390 0 : for (SCCOL nCol = nCol1; nCol <= nCol2; ++nCol)
391 : {
392 : sc::ColumnBlockPosition* pBlockPos =
393 0 : mpImpl->maBlockPosSet.getBlockPosition(nTab, nCol);
394 :
395 0 : if (!pBlockPos)
396 : // Something went horribly wrong.
397 0 : return;
398 :
399 0 : sc::CellStoreType& rColCells = pTab->aCol[nCol].maCells;
400 :
401 0 : for (SCROW nRow = nRow1; nRow <= nRow2; ++nRow)
402 : {
403 0 : ScAddress aPos(nCol, nRow, nTab);
404 0 : ScFormulaCell* pCell = new ScFormulaCell(aRefCell, *pDoc, aPos);
405 0 : pBlockPos->miCellPos =
406 0 : rColCells.set(pBlockPos->miCellPos, nRow, pCell);
407 : }
408 0 : }
409 : }
410 :
411 : namespace {
412 :
413 0 : class CellStoreInitializer
414 : {
415 : // The pimpl pattern here is intentional.
416 : //
417 : // The problem with having the attributes in CellStoreInitializer
418 : // directly is that, as a functor, it might be copied around. In
419 : // that case miPos in _copied_ object points ot maAttrs in the
420 : // original object, not in the copy. So later, deep in mdds, we end
421 : // up comparing iterators from different sequences.
422 : //
423 : // This could be solved by defining copy constructor and operator=,
424 : // but given the limited usage of the class, I think it is simpler
425 : // to let copies share the state.
426 0 : struct Impl
427 : {
428 : sc::CellTextAttrStoreType maAttrs;
429 : sc::CellTextAttrStoreType::iterator miPos;
430 : sal_uInt16 mnScriptNumeric;
431 :
432 0 : Impl(const sal_uInt32 nMaxRowCount, const sal_uInt16 nScriptNumeric)
433 0 : : maAttrs(nMaxRowCount), miPos(maAttrs.begin()), mnScriptNumeric(nScriptNumeric)
434 0 : {}
435 : };
436 :
437 : ScDocument& mrDoc;
438 : sc::StartListeningContext& mrListenCxt;
439 :
440 : public:
441 0 : CellStoreInitializer(ScDocument& rDoc, sc::StartListeningContext& rCxt, sal_uInt16 nScriptNumeric) :
442 : mrDoc(rDoc),
443 : mrListenCxt(rCxt),
444 0 : mpImpl(new Impl(MAXROWCOUNT, nScriptNumeric))
445 0 : {}
446 :
447 : boost::shared_ptr<Impl> mpImpl;
448 :
449 0 : void operator() (const sc::CellStoreType::value_type& node)
450 : {
451 0 : if (node.type == sc::element_type_empty)
452 0 : return;
453 :
454 : // Fill with default values for non-empty cell segments.
455 0 : sc::CellTextAttr aDefault;
456 0 : if (node.type == sc::element_type_numeric)
457 0 : aDefault.mnScriptType = mpImpl->mnScriptNumeric;
458 0 : std::vector<sc::CellTextAttr> aDefaults(node.size, aDefault);
459 0 : mpImpl->miPos = mpImpl->maAttrs.set(mpImpl->miPos, node.position, aDefaults.begin(), aDefaults.end());
460 :
461 0 : if (node.type == sc::element_type_formula)
462 : {
463 : // Have all formula cells start listening to the document.
464 0 : sc::formula_block::iterator it = sc::formula_block::begin(*node.data);
465 0 : sc::formula_block::iterator itEnd = sc::formula_block::end(*node.data);
466 0 : for (; it != itEnd; ++it)
467 : {
468 0 : ScFormulaCell& rFC = **it;
469 0 : rFC.StartListeningTo(mrListenCxt);
470 : }
471 0 : }
472 : }
473 :
474 0 : void swap(sc::CellTextAttrStoreType& rAttrs)
475 : {
476 0 : mpImpl->maAttrs.swap(rAttrs);
477 0 : }
478 : };
479 :
480 : }
481 :
482 0 : void ScDocumentImport::finalize()
483 : {
484 : // Populate the text width and script type arrays in all columns. Also
485 : // activate all formula cells.
486 0 : ScDocument::TableContainer::iterator itTab = mpImpl->mrDoc.maTabs.begin(), itTabEnd = mpImpl->mrDoc.maTabs.end();
487 0 : for (; itTab != itTabEnd; ++itTab)
488 : {
489 0 : if (!*itTab)
490 0 : continue;
491 :
492 0 : ScTable& rTab = **itTab;
493 0 : ScColumn* pCol = &rTab.aCol[0];
494 0 : ScColumn* pColEnd = pCol + static_cast<size_t>(MAXCOLCOUNT);
495 0 : for (; pCol != pColEnd; ++pCol)
496 0 : initColumn(*pCol);
497 : }
498 0 : }
499 :
500 0 : void ScDocumentImport::initColumn(ScColumn& rCol)
501 : {
502 0 : CellStoreInitializer aFunc(mpImpl->mrDoc, mpImpl->maListenCxt, mpImpl->mnDefaultScriptNumeric);
503 0 : std::for_each(rCol.maCells.begin(), rCol.maCells.end(), aFunc);
504 0 : aFunc.swap(rCol.maCellTextAttrs);
505 0 : rCol.RegroupFormulaCells();
506 0 : rCol.CellStorageModified();
507 0 : }
508 :
509 : /* vim:set shiftwidth=4 softtabstop=4 expandtab: */
|