1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
3 * This file is part of the LibreOffice project.
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/.
9 * This file incorporates work covered by the following license notice:
11 * Licensed to the Apache Software Foundation (ASF) under one or more
12 * contributor license agreements. See the NOTICE file distributed
13 * with this work for additional information regarding copyright
14 * ownership. The ASF licenses this file to you under the Apache
15 * License, Version 2.0 (the "License"); you may not use this file
16 * except in compliance with the License. You may obtain a copy of
17 * the License at http://www.apache.org/licenses/LICENSE-2.0 .
20 #include <scitems.hxx>
21 #include <comphelper/fileformat.h>
22 #include <comphelper/processfactory.hxx>
23 #include <officecfg/Office/Common.hxx>
24 #include <tools/urlobj.hxx>
25 #include <editeng/frmdiritem.hxx>
26 #include <editeng/langitem.hxx>
27 #include <sfx2/linkmgr.hxx>
28 #include <sfx2/bindings.hxx>
29 #include <sfx2/printer.hxx>
30 #include <sfx2/viewfrm.hxx>
31 #include <sfx2/viewsh.hxx>
32 #include <svl/flagitem.hxx>
33 #include <svl/intitem.hxx>
34 #include <svl/numformat.hxx>
35 #include <svl/zformat.hxx>
36 #include <svl/ctloptions.hxx>
37 #include <unotools/transliterationwrapper.hxx>
38 #include <sal/log.hxx>
39 #include <osl/diagnose.h>
41 #include <vcl/svapp.hxx>
42 #include <vcl/virdev.hxx>
43 #include <vcl/weld.hxx>
44 #include <vcl/TaskStopwatch.hxx>
46 #include <inputopt.hxx>
50 #include <poolhelp.hxx>
51 #include <docpool.hxx>
53 #include <stlpool.hxx>
54 #include <stlsheet.hxx>
55 #include <docoptio.hxx>
56 #include <viewopti.hxx>
57 #include <scextopt.hxx>
58 #include <rechead.hxx>
59 #include <ddelink.hxx>
60 #include <scmatrix.hxx>
61 #include <arealink.hxx>
62 #include <patattr.hxx>
63 #include <editutil.hxx>
64 #include <progress.hxx>
65 #include <document.hxx>
66 #include <chartlis.hxx>
67 #include <chartlock.hxx>
68 #include <refupdat.hxx>
69 #include <markdata.hxx>
71 #include <externalrefmgr.hxx>
72 #include <globstr.hrc>
73 #include <strings.hrc>
75 #include <charthelper.hxx>
76 #include <macromgr.hxx>
78 #include <scresid.hxx>
79 #include <columniterator.hxx>
80 #include <globalnames.hxx>
81 #include <stringutil.hxx>
82 #include <documentlinkmgr.hxx>
83 #include <tokenarray.hxx>
84 #include <recursionhelper.hxx>
89 using namespace com::sun::star
;
93 sal_uInt16
getScaleValue(SfxStyleSheetBase
& rStyle
, sal_uInt16 nWhich
)
95 return static_cast<const SfxUInt16Item
&>(rStyle
.GetItemSet().Get(nWhich
)).GetValue();
100 void ScDocument::ImplCreateOptions()
102 pDocOptions
.reset( new ScDocOptions() );
103 pViewOptions
.reset( new ScViewOptions() );
106 void ScDocument::ImplDeleteOptions()
109 pViewOptions
.reset();
110 pExtDocOptions
.reset();
113 SfxPrinter
* ScDocument::GetPrinter(bool bCreateIfNotExist
)
115 if (!mpPrinter
&& bCreateIfNotExist
&& mxPoolHelper
)
118 std::make_unique
<SfxItemSetFixed
119 <SID_PRINTER_NOTFOUND_WARN
, SID_PRINTER_NOTFOUND_WARN
,
120 SID_PRINTER_CHANGESTODOC
, SID_PRINTER_CHANGESTODOC
,
121 SID_PRINT_SELECTEDSHEET
, SID_PRINT_SELECTEDSHEET
,
122 SID_SCPRINTOPTIONS
, SID_SCPRINTOPTIONS
>>(*mxPoolHelper
->GetDocPool());
124 SfxPrinterChangeFlags nFlags
= SfxPrinterChangeFlags::NONE
;
125 if (officecfg::Office::Common::Print::Warning::PaperOrientation::get())
126 nFlags
|= SfxPrinterChangeFlags::CHG_ORIENTATION
;
127 if (officecfg::Office::Common::Print::Warning::PaperSize::get())
128 nFlags
|= SfxPrinterChangeFlags::CHG_SIZE
;
129 pSet
->Put( SfxFlagItem( SID_PRINTER_CHANGESTODOC
, static_cast<int>(nFlags
) ) );
130 pSet
->Put( SfxBoolItem( SID_PRINTER_NOTFOUND_WARN
, officecfg::Office::Common::Print::Warning::NotFound::get() ) );
132 mpPrinter
= VclPtr
<SfxPrinter
>::Create( std::move(pSet
) );
133 mpPrinter
->SetMapMode(MapMode(MapUnit::Map100thMM
));
135 mpPrinter
->SetDigitLanguage( ScModule::GetOptDigitLanguage() );
141 void ScDocument::SetPrinter( VclPtr
<SfxPrinter
> const & pNewPrinter
)
143 if ( pNewPrinter
== mpPrinter
.get() )
145 // #i6706# SetPrinter is called with the same printer again if
146 // the JobSetup has changed. In that case just call UpdateDrawPrinter
147 // (SetRefDevice for drawing layer) because of changed text sizes.
152 ScopedVclPtr
<SfxPrinter
> xKeepAlive( mpPrinter
);
153 mpPrinter
= pNewPrinter
;
155 mpPrinter
->SetDigitLanguage( ScModule::GetOptDigitLanguage() );
157 InvalidateTextWidth(nullptr, nullptr, false); // in both cases
160 void ScDocument::SetPrintOptions()
162 if ( !mpPrinter
) GetPrinter(); // this sets mpPrinter
163 OSL_ENSURE( mpPrinter
, "Error in printer creation :-/" );
168 SfxItemSet
aOptSet( mpPrinter
->GetOptions() );
170 SfxPrinterChangeFlags nFlags
= SfxPrinterChangeFlags::NONE
;
171 if (officecfg::Office::Common::Print::Warning::PaperOrientation::get())
172 nFlags
|= SfxPrinterChangeFlags::CHG_ORIENTATION
;
173 if (officecfg::Office::Common::Print::Warning::PaperSize::get())
174 nFlags
|= SfxPrinterChangeFlags::CHG_SIZE
;
175 aOptSet
.Put( SfxFlagItem( SID_PRINTER_CHANGESTODOC
, static_cast<int>(nFlags
) ) );
176 aOptSet
.Put( SfxBoolItem( SID_PRINTER_NOTFOUND_WARN
, officecfg::Office::Common::Print::Warning::NotFound::get() ) );
178 mpPrinter
->SetOptions( aOptSet
);
181 VirtualDevice
* ScDocument::GetVirtualDevice_100th_mm()
183 if (!mpVirtualDevice_100th_mm
)
186 mpVirtualDevice_100th_mm
= VclPtr
<VirtualDevice
>::Create(DeviceFormat::GRAYSCALE
);
188 mpVirtualDevice_100th_mm
= VclPtr
<VirtualDevice
>::Create(DeviceFormat::WITHOUT_ALPHA
);
190 mpVirtualDevice_100th_mm
->SetReferenceDevice(VirtualDevice::RefDevMode::MSO1
);
191 MapMode
aMapMode( mpVirtualDevice_100th_mm
->GetMapMode() );
192 aMapMode
.SetMapUnit( MapUnit::Map100thMM
);
193 mpVirtualDevice_100th_mm
->SetMapMode( aMapMode
);
195 return mpVirtualDevice_100th_mm
;
198 OutputDevice
* ScDocument::GetRefDevice(bool bForceVirtDev
)
200 // Create printer like ref device, see Writer...
201 OutputDevice
* pRefDevice
= nullptr;
202 if (!bForceVirtDev
&& ScModule::get()->GetInputOptions().GetTextWysiwyg())
204 pRefDevice
= GetPrinter();
205 SAL_WARN_IF(!pRefDevice
, "sc", "unable to get a printer, fallback to virdev");
208 pRefDevice
= GetVirtualDevice_100th_mm();
212 void ScDocument::ModifyStyleSheet( SfxStyleSheetBase
& rStyleSheet
,
213 const SfxItemSet
& rChanges
)
215 SfxItemSet
& rSet
= rStyleSheet
.GetItemSet();
217 switch ( rStyleSheet
.GetFamily() )
219 case SfxStyleFamily::Page
:
221 const sal_uInt16 nOldScale
= getScaleValue(rStyleSheet
, ATTR_PAGE_SCALE
);
222 const sal_uInt16 nOldScaleToPages
= getScaleValue(rStyleSheet
, ATTR_PAGE_SCALETOPAGES
);
223 rSet
.Put( rChanges
);
224 const sal_uInt16 nNewScale
= getScaleValue(rStyleSheet
, ATTR_PAGE_SCALE
);
225 const sal_uInt16 nNewScaleToPages
= getScaleValue(rStyleSheet
, ATTR_PAGE_SCALETOPAGES
);
227 if ( (nOldScale
!= nNewScale
) || (nOldScaleToPages
!= nNewScaleToPages
) )
228 InvalidateTextWidth( rStyleSheet
.GetName() );
230 if( SvtCTLOptions::IsCTLFontEnabled() )
232 if( rChanges
.GetItemState(ATTR_WRITINGDIR
) == SfxItemState::SET
)
233 ScChartHelper::DoUpdateAllCharts( *this );
238 case SfxStyleFamily::Para
:
240 bool bNumFormatChanged
;
241 if ( ScGlobal::CheckWidthInvalidate( bNumFormatChanged
,
243 InvalidateTextWidth( nullptr, nullptr, bNumFormatChanged
);
245 for (SCTAB nTab
=0; nTab
<=MAXTAB
; ++nTab
)
247 maTabs
[nTab
]->SetStreamValid( false );
249 sal_uInt32 nOldFormat
=
250 rSet
.Get( ATTR_VALUE_FORMAT
).GetValue();
251 sal_uInt32 nNewFormat
=
252 rChanges
.Get( ATTR_VALUE_FORMAT
).GetValue();
253 LanguageType eNewLang
, eOldLang
;
254 eNewLang
= eOldLang
= LANGUAGE_DONTKNOW
;
255 if ( nNewFormat
!= nOldFormat
)
257 SvNumberFormatter
* pFormatter
= GetFormatTable();
258 eOldLang
= pFormatter
->GetEntry( nOldFormat
)->GetLanguage();
259 eNewLang
= pFormatter
->GetEntry( nNewFormat
)->GetLanguage();
262 // Explanation to Items in rChanges:
263 // Set Item - take over change
264 // Dontcare - Set Default
265 // Default - No change
266 // ("no change" is not possible with PutExtended, thus the loop)
267 for (sal_uInt16 nWhich
= ATTR_PATTERN_START
; nWhich
<= ATTR_PATTERN_END
; nWhich
++)
269 const SfxPoolItem
* pItem
;
270 SfxItemState eState
= rChanges
.GetItemState( nWhich
, false, &pItem
);
271 if ( eState
== SfxItemState::SET
)
273 else if ( eState
== SfxItemState::INVALID
)
274 rSet
.ClearItem( nWhich
);
275 // when Default nothing
278 if ( eNewLang
!= eOldLang
)
280 SvxLanguageItem( eNewLang
, ATTR_LANGUAGE_FORMAT
) );
285 // added to avoid warnings
290 void ScDocument::CopyStdStylesFrom( const ScDocument
& rSrcDoc
)
292 // number format exchange list has to be handled here, too
293 NumFmtMergeHandler
aNumFmtMergeHdl(*this, rSrcDoc
);
294 mxPoolHelper
->GetStylePool()->CopyStdStylesFrom( rSrcDoc
.mxPoolHelper
->GetStylePool() );
297 void ScDocument::InvalidateTextWidth( std::u16string_view rStyleName
)
299 const SCTAB nCount
= GetTableCount();
300 for ( SCTAB i
=0; i
<nCount
&& maTabs
[i
]; i
++ )
301 if ( maTabs
[i
]->GetPageStyle() == rStyleName
)
302 InvalidateTextWidth( i
);
305 void ScDocument::InvalidateTextWidth( SCTAB nTab
)
307 ScAddress
aAdrFrom( 0, 0, nTab
);
308 ScAddress
aAdrTo ( MaxCol(), MaxRow(), nTab
);
309 InvalidateTextWidth( &aAdrFrom
, &aAdrTo
, false );
312 bool ScDocument::IsPageStyleInUse( std::u16string_view rStrPageStyle
, SCTAB
* pInTab
)
315 const SCTAB nCount
= GetTableCount();
318 for ( i
= 0; !bInUse
&& i
< nCount
&& maTabs
[i
]; i
++ )
319 bInUse
= ( maTabs
[i
]->GetPageStyle() == rStrPageStyle
);
327 bool ScDocument::RemovePageStyleInUse( std::u16string_view rStyle
)
329 bool bWasInUse
= false;
330 const SCTAB nCount
= GetTableCount();
332 for ( SCTAB i
=0; i
<nCount
&& maTabs
[i
]; i
++ )
333 if ( maTabs
[i
]->GetPageStyle() == rStyle
)
336 maTabs
[i
]->SetPageStyle( ScResId(STR_STYLENAME_STANDARD
) );
342 bool ScDocument::RenamePageStyleInUse( std::u16string_view rOld
, const OUString
& rNew
)
344 bool bWasInUse
= false;
345 const SCTAB nCount
= GetTableCount();
347 for ( SCTAB i
=0; i
<nCount
&& maTabs
[i
]; i
++ )
348 if ( maTabs
[i
]->GetPageStyle() == rOld
)
351 maTabs
[i
]->SetPageStyle( rNew
);
357 EEHorizontalTextDirection
ScDocument::GetEditTextDirection(SCTAB nTab
) const
359 EEHorizontalTextDirection eRet
= EEHorizontalTextDirection::Default
;
361 OUString aStyleName
= GetPageStyle( nTab
);
362 SfxStyleSheetBase
* pStyle
= mxPoolHelper
->GetStylePool()->Find( aStyleName
, SfxStyleFamily::Page
);
365 SfxItemSet
& rStyleSet
= pStyle
->GetItemSet();
366 SvxFrameDirection eDirection
=
367 rStyleSet
.Get( ATTR_WRITINGDIR
).GetValue();
369 if ( eDirection
== SvxFrameDirection::Horizontal_LR_TB
)
370 eRet
= EEHorizontalTextDirection::L2R
;
371 else if ( eDirection
== SvxFrameDirection::Horizontal_RL_TB
)
372 eRet
= EEHorizontalTextDirection::R2L
;
373 // else (invalid for EditEngine): keep "default"
379 ScMacroManager
* ScDocument::GetMacroManager()
382 mpMacroMgr
.reset(new ScMacroManager(*this));
383 return mpMacroMgr
.get();
386 void ScDocument::FillMatrix(
387 ScMatrix
& rMat
, SCTAB nTab
, SCCOL nCol1
, SCROW nRow1
, SCCOL nCol2
, SCROW nRow2
, svl::SharedStringPool
* pPool
) const
389 const ScTable
* pTab
= FetchTable(nTab
);
393 if (nCol1
> nCol2
|| nRow1
> nRow2
)
397 rMat
.GetDimensions(nC
, nR
);
398 if (static_cast<SCROW
>(nR
) != nRow2
- nRow1
+ 1 || static_cast<SCCOL
>(nC
) != nCol2
- nCol1
+ 1)
401 pTab
->FillMatrix(rMat
, nCol1
, nRow1
, nCol2
, nRow2
, pPool
);
404 void ScDocument::SetFormulaResults( const ScAddress
& rTopPos
, const double* pResults
, size_t nLen
)
406 ScTable
* pTab
= FetchTable(rTopPos
.Tab());
410 pTab
->SetFormulaResults(rTopPos
.Col(), rTopPos
.Row(), pResults
, nLen
);
413 void ScDocument::CalculateInColumnInThread( ScInterpreterContext
& rContext
, const ScRange
& rCalcRange
, unsigned nThisThread
, unsigned nThreadsTotal
)
415 ScTable
* pTab
= FetchTable(rCalcRange
.aStart
.Tab());
419 assert(IsThreadedGroupCalcInProgress());
421 maThreadSpecific
.pContext
= &rContext
;
422 pTab
->CalculateInColumnInThread(rContext
, rCalcRange
.aStart
.Col(), rCalcRange
.aEnd
.Col(), rCalcRange
.aStart
.Row(), rCalcRange
.aEnd
.Row(), nThisThread
, nThreadsTotal
);
424 assert(IsThreadedGroupCalcInProgress());
425 maThreadSpecific
.pContext
= nullptr;
426 // If any of the thread_local data would cause problems if they stay around for too long
427 // (and e.g. outlive the ScDocument), clean them up here, they cannot be cleaned up
428 // later from the main thread.
429 if(maThreadSpecific
.xRecursionHelper
)
430 maThreadSpecific
.xRecursionHelper
->Clear();
433 void ScDocument::HandleStuffAfterParallelCalculation( SCCOL nColStart
, SCCOL nColEnd
, SCROW nRow
, size_t nLen
, SCTAB nTab
, ScInterpreter
* pInterpreter
)
435 assert(!IsThreadedGroupCalcInProgress());
436 for( const DelayedSetNumberFormat
& data
: GetNonThreadedContext().maDelayedSetNumberFormat
)
437 SetNumberFormat( ScAddress( data
.mCol
, data
.mRow
, nTab
), data
.mnNumberFormat
);
438 GetNonThreadedContext().maDelayedSetNumberFormat
.clear();
440 ScTable
* pTab
= FetchTable(nTab
);
444 pTab
->HandleStuffAfterParallelCalculation(nColStart
, nColEnd
, nRow
, nLen
, pInterpreter
);
447 void ScDocument::InvalidateTextWidth( const ScAddress
* pAdrFrom
, const ScAddress
* pAdrTo
,
448 bool bNumFormatChanged
)
450 bool bBroadcast
= (bNumFormatChanged
&& GetDocOptions().IsCalcAsShown() && !IsImportingXML() && !IsClipboard());
451 if ( pAdrFrom
&& !pAdrTo
)
453 const SCTAB nTab
= pAdrFrom
->Tab();
455 if (nTab
< static_cast<SCTAB
>(maTabs
.size()) && maTabs
[nTab
] )
456 maTabs
[nTab
]->InvalidateTextWidth( pAdrFrom
, nullptr, bNumFormatChanged
, bBroadcast
);
460 const SCTAB nTabStart
= pAdrFrom
? pAdrFrom
->Tab() : 0;
461 const SCTAB nTabEnd
= pAdrTo
? pAdrTo
->Tab() : MAXTAB
;
463 for ( SCTAB nTab
=nTabStart
; nTab
<=nTabEnd
&& nTab
< static_cast<SCTAB
>(maTabs
.size()); nTab
++ )
465 maTabs
[nTab
]->InvalidateTextWidth( pAdrFrom
, pAdrTo
, bNumFormatChanged
, bBroadcast
);
469 #define CALCMAX 1000 // Calculations
473 class IdleCalcTextWidthScope
: public TaskStopwatch
476 ScAddress
& mrCalcPos
;
477 MapMode maOldMapMode
;
478 ScStyleSheetPool
* mpStylePool
;
483 IdleCalcTextWidthScope(ScDocument
& rDoc
, ScAddress
& rCalcPos
) :
486 mpStylePool(rDoc
.GetStyleSheetPool()),
490 mrDoc
.EnableIdle(false);
493 ~IdleCalcTextWidthScope() COVERITY_NOEXCEPT_FALSE
495 SfxPrinter
* pDev
= mrDoc
.GetPrinter();
497 pDev
->SetMapMode(maOldMapMode
);
500 ScProgress::DeleteInterpretProgress();
502 mrDoc
.EnableIdle(true);
505 SCTAB
Tab() const { return mrCalcPos
.Tab(); }
506 SCCOL
Col() const { return mrCalcPos
.Col(); }
507 SCROW
Row() const { return mrCalcPos
.Row(); }
509 void setTab(SCTAB nTab
) { mrCalcPos
.SetTab(nTab
); }
510 void setCol(SCCOL nCol
) { mrCalcPos
.SetCol(nCol
); }
511 void setRow(SCROW nRow
) { mrCalcPos
.SetRow(nRow
); }
513 void incTab() { mrCalcPos
.IncTab(); }
514 void incCol(SCCOL nInc
) { mrCalcPos
.IncCol(nInc
); }
516 void setOldMapMode(const MapMode
& rOldMapMode
) { maOldMapMode
= rOldMapMode
; }
518 void setNeedMore(bool b
) { mbNeedMore
= b
; }
519 bool getNeedMore() const { return mbNeedMore
; }
521 void createProgressBar()
523 ScProgress::CreateInterpretProgress(&mrDoc
, false);
527 bool hasProgressBar() const { return mbProgress
; }
529 ScStyleSheetPool
* getStylePool() { return mpStylePool
; }
534 bool ScDocument::IdleCalcTextWidth() // true = try next again
536 // #i75610# if a printer hasn't been set or created yet, don't create one for this
537 if (!mbIdleEnabled
|| IsInLinkUpdate() || GetPrinter(false) == nullptr)
540 IdleCalcTextWidthScope
aScope(*this, aCurTextWidthCalcPos
);
542 if (!ValidRow(aScope
.Row()))
548 if (aScope
.Col() < 0)
550 aScope
.setCol(MaxCol());
554 if (!HasTable(aScope
.Tab()))
557 ScTable
* pTab
= maTabs
[aScope
.Tab()].get();
558 ScStyleSheet
* pStyle
= static_cast<ScStyleSheet
*>(aScope
.getStylePool()->Find(pTab
->aPageStyle
, SfxStyleFamily::Page
));
559 OSL_ENSURE( pStyle
, "Missing StyleSheet :-/" );
561 if (!pStyle
|| getScaleValue(*pStyle
, ATTR_PAGE_SCALETOPAGES
) == 0)
563 // Move to the next sheet as the current one has scale-to-pages set,
569 sal_uInt16 nZoom
= getScaleValue(*pStyle
, ATTR_PAGE_SCALE
);
570 Fraction
aZoomFract(nZoom
, 100);
572 aScope
.setCol(pTab
->ClampToAllocatedColumns(aScope
.Col()));
573 // Start at specified cell position (nCol, nRow, nTab).
574 ScColumn
* pCol
= &pTab
->aCol
[aScope
.Col()];
575 std::optional
<ScColumnTextWidthIterator
> pColIter(std::in_place
, *this, *pCol
, aScope
.Row(), MaxRow());
577 OutputDevice
* pDev
= nullptr;
578 sal_uInt16 nRestart
= 0;
579 sal_uInt16 nCount
= 0;
580 while ( (nZoom
> 0) && (nCount
< CALCMAX
) && (nRestart
< 2) )
582 if (pColIter
->hasCell())
584 // More cell in this column.
585 SCROW nRow
= pColIter
->getPos();
588 if (pColIter
->getValue() == TEXTWIDTH_DIRTY
)
590 // Calculate text width for this cell.
597 aScope
.setOldMapMode(pDev
->GetMapMode());
598 pDev
->SetMapMode(MapMode(MapUnit::MapPixel
)); // Important for GetNeededSize
600 Point aPix1000
= pDev
->LogicToPixel(Point(1000,1000), MapMode(MapUnit::MapTwip
));
601 nPPTX
= aPix1000
.X() / 1000.0;
602 nPPTY
= aPix1000
.Y() / 1000.0;
605 if (!aScope
.hasProgressBar() && pCol
->IsFormulaDirty(nRow
))
606 aScope
.createProgressBar();
608 sal_uInt16 nNewWidth
= static_cast<sal_uInt16
>(GetNeededSize(
609 aScope
.Col(), aScope
.Row(), aScope
.Tab(),
610 pDev
, nPPTX
, nPPTY
, aZoomFract
,aZoomFract
, true, true)); // bTotalSize
612 pColIter
->setValue(nNewWidth
);
613 aScope
.setNeedMore(true);
619 // No more cell in this column. Move to the left column and start at row 0.
621 bool bNewTab
= false;
626 if (aScope
.Col() < 0)
628 // No more column to the left. Move to the right-most column of the next sheet.
629 aScope
.setCol(MaxCol());
634 if (!HasTable(aScope
.Tab()))
636 // Sheet doesn't exist at specified sheet position. Restart at sheet 0.
646 pTab
= maTabs
[aScope
.Tab()].get();
647 aScope
.setCol(pTab
->ClampToAllocatedColumns(aScope
.Col()));
648 pStyle
= static_cast<ScStyleSheet
*>(aScope
.getStylePool()->Find(
649 pTab
->aPageStyle
, SfxStyleFamily::Page
));
653 // Check if the scale-to-pages setting is set. If
654 // set, we exit the loop. If not, get the page
655 // scale factor of the new sheet.
656 if (getScaleValue(*pStyle
, ATTR_PAGE_SCALETOPAGES
) == 0)
658 nZoom
= getScaleValue(*pStyle
, ATTR_PAGE_SCALE
);
659 aZoomFract
= Fraction(nZoom
, 100);
666 OSL_FAIL( "Missing StyleSheet :-/" );
672 pCol
= &pTab
->aCol
[aScope
.Col()];
673 pColIter
.emplace(*this, *pCol
, aScope
.Row(), MaxRow());
677 aScope
.incTab(); // Move to the next sheet as the current one has scale-to-pages set.
685 if (!aScope
.continueIter())
689 return aScope
.getNeedMore();
692 void ScDocument::RepaintRange( const ScRange
& rRange
)
694 if ( bIsVisible
&& mpShell
)
696 ScModelObj
* pModel
= mpShell
->GetModel();
698 pModel
->RepaintRange( rRange
); // locked repaints are checked there
702 void ScDocument::RepaintRange( const ScRangeList
& rRange
)
704 if ( bIsVisible
&& mpShell
)
706 ScModelObj
* pModel
= mpShell
->GetModel();
708 pModel
->RepaintRange( rRange
); // locked repaints are checked there
712 void ScDocument::SaveDdeLinks(SvStream
& rStream
) const
714 // when 4.0-Export, remove all with mode != DEFAULT
715 bool bExport40
= ( rStream
.GetVersion() <= SOFFICE_FILEFORMAT_40
);
717 const ::sfx2::SvBaseLinks
& rLinks
= GetLinkManager()->GetLinks();
718 sal_uInt16 nCount
= rLinks
.size();
722 sal_uInt16 nDdeCount
= 0;
724 for (i
=0; i
<nCount
; i
++)
726 ::sfx2::SvBaseLink
* pBase
= rLinks
[i
].get();
727 if (ScDdeLink
* pLink
= dynamic_cast<ScDdeLink
*>(pBase
))
728 if ( !bExport40
|| pLink
->GetMode() == SC_DDE_DEFAULT
)
734 ScMultipleWriteHeader
aHdr( rStream
);
735 rStream
.WriteUInt16( nDdeCount
);
739 for (i
=0; i
<nCount
; i
++)
741 ::sfx2::SvBaseLink
* pBase
= rLinks
[i
].get();
742 if (ScDdeLink
* pLink
= dynamic_cast<ScDdeLink
*>(pBase
))
744 if ( !bExport40
|| pLink
->GetMode() == SC_DDE_DEFAULT
)
745 pLink
->Store( rStream
, aHdr
);
750 void ScDocument::LoadDdeLinks(SvStream
& rStream
)
752 sfx2::LinkManager
* pMgr
= GetDocLinkManager().getLinkManager(bAutoCalc
);
756 ScMultipleReadHeader
aHdr( rStream
);
758 sal_uInt16
nCount(0);
759 rStream
.ReadUInt16( nCount
);
761 const rtl_TextEncoding eCharSet
= rStream
.GetStreamCharSet();
762 const size_t nMinStringSize
= eCharSet
== RTL_TEXTENCODING_UNICODE
? sizeof(sal_uInt32
) : sizeof(sal_uInt16
);
763 const size_t nMinRecordSize
= 1 + nMinStringSize
*3;
764 const size_t nMaxRecords
= rStream
.remainingSize() / nMinRecordSize
;
765 if (nCount
> nMaxRecords
)
767 SAL_WARN("sc", "Parsing error: " << nMaxRecords
<<
768 " max possible entries, but " << nCount
<< " claimed, truncating");
769 nCount
= nMaxRecords
;
772 for (sal_uInt16 i
=0; i
<nCount
; ++i
)
774 ScDdeLink
* pLink
= new ScDdeLink( *this, rStream
, aHdr
);
775 pMgr
->InsertDDELink(pLink
, pLink
->GetAppl(), pLink
->GetTopic(), pLink
->GetItem());
779 void ScDocument::SetInLinkUpdate(bool bSet
)
781 // called from TableLink and AreaLink
783 OSL_ENSURE( bInLinkUpdate
!= bSet
, "SetInLinkUpdate twice" );
784 bInLinkUpdate
= bSet
;
787 bool ScDocument::IsInLinkUpdate() const
789 return bInLinkUpdate
|| IsInDdeLinkUpdate();
792 void ScDocument::UpdateExternalRefLinks(weld::Window
* pWin
)
794 if (!pExternalRefMgr
)
797 sfx2::LinkManager
* pMgr
= GetDocLinkManager().getLinkManager(bAutoCalc
);
801 const ::sfx2::SvBaseLinks
& rLinks
= pMgr
->GetLinks();
802 sal_uInt16 nCount
= rLinks
.size();
806 // Collect all the external ref links first.
807 std::vector
<ScExternalRefLink
*> aRefLinks
;
808 for (sal_uInt16 i
= 0; i
< nCount
; ++i
)
810 ::sfx2::SvBaseLink
* pBase
= rLinks
[i
].get();
811 ScExternalRefLink
* pRefLink
= dynamic_cast<ScExternalRefLink
*>(pBase
);
813 aRefLinks
.push_back(pRefLink
);
816 weld::WaitObject
aWaitSwitch(pWin
);
818 pExternalRefMgr
->enableDocTimer(false);
819 ScProgress
aProgress(GetDocumentShell(), ScResId(SCSTR_UPDATE_EXTDOCS
), aRefLinks
.size(), true);
820 for (size_t i
= 0, n
= aRefLinks
.size(); i
< n
; ++i
)
822 aProgress
.SetState(i
+1);
824 ScExternalRefLink
* pRefLink
= aRefLinks
[i
];
825 if (pRefLink
->Update())
831 // Update failed. Notify the user.
834 sfx2::LinkManager::GetDisplayNames(pRefLink
, nullptr, &aFile
);
835 // Decode encoded URL for display friendliness.
836 INetURLObject
aUrl(aFile
,INetURLObject::EncodeMechanism::WasEncoded
);
837 aFile
= aUrl
.GetMainURL(INetURLObject::DecodeMechanism::Unambiguous
);
839 OUString sMessage
= ScResId(SCSTR_EXTDOC_NOT_LOADED
) +
842 std::unique_ptr
<weld::MessageDialog
> xBox(Application::CreateMessageDialog(pWin
,
843 VclMessageType::Warning
, VclButtonsType::Ok
,
848 pExternalRefMgr
->enableDocTimer(true);
854 mpShell
->Broadcast( SfxHint(SfxHintId::ScDataChanged
) );
856 // #i101960# set document modified, as in TrackTimeHdl for DDE links
857 if (!mpShell
->IsModified())
859 mpShell
->SetModified();
860 SfxBindings
* pBindings
= GetViewBindings();
863 pBindings
->Invalidate( SID_SAVEDOC
);
864 pBindings
->Invalidate( SID_DOC_MODIFIED
);
869 void ScDocument::CopyDdeLinks( ScDocument
& rDestDoc
) const
871 if (bIsClip
) // Create from Stream
876 rDestDoc
.LoadDdeLinks(*pClipData
);
882 const sfx2::LinkManager
* pMgr
= GetDocLinkManager().getExistingLinkManager();
886 sfx2::LinkManager
* pDestMgr
= rDestDoc
.GetDocLinkManager().getLinkManager(rDestDoc
.bAutoCalc
);
890 const sfx2::SvBaseLinks
& rLinks
= pMgr
->GetLinks();
891 for (const auto & rLink
: rLinks
)
893 const sfx2::SvBaseLink
* pBase
= rLink
.get();
894 if (const ScDdeLink
* p
= dynamic_cast<const ScDdeLink
*>(pBase
))
896 ScDdeLink
* pNew
= new ScDdeLink(rDestDoc
, *p
);
897 pDestMgr
->InsertDDELink(
898 pNew
, pNew
->GetAppl(), pNew
->GetTopic(), pNew
->GetItem());
905 /** Tries to find the specified DDE link.
906 @param pnDdePos (out-param) if not 0, the index of the DDE link is returned here
907 (does not include other links from link manager).
908 @return The DDE link, if it exists, otherwise 0. */
909 ScDdeLink
* lclGetDdeLink(
910 const sfx2::LinkManager
* pLinkManager
,
911 std::u16string_view rAppl
, std::u16string_view rTopic
, std::u16string_view rItem
, sal_uInt8 nMode
,
912 size_t* pnDdePos
= nullptr )
916 const ::sfx2::SvBaseLinks
& rLinks
= pLinkManager
->GetLinks();
917 if( pnDdePos
) *pnDdePos
= 0;
918 for( const auto& nLinks
: rLinks
)
920 if( ScDdeLink
* pDdeLink
= dynamic_cast<ScDdeLink
*>( nLinks
.get() ) )
922 if( (pDdeLink
->GetAppl() == rAppl
) &&
923 (pDdeLink
->GetTopic() == rTopic
) &&
924 (pDdeLink
->GetItem() == rItem
) &&
925 ((nMode
== SC_DDE_IGNOREMODE
) || (nMode
== pDdeLink
->GetMode())) )
927 if( pnDdePos
) ++*pnDdePos
;
934 /** Returns a pointer to the specified DDE link.
935 @param nDdePos Index of the DDE link (does not include other links from link manager).
936 @return The DDE link, if it exists, otherwise 0. */
937 ScDdeLink
* lclGetDdeLink( const sfx2::LinkManager
* pLinkManager
, size_t nDdePos
)
941 size_t nDdeIndex
= 0; // counts only the DDE links
942 for( const auto& pLink
: pLinkManager
->GetLinks() )
944 if( ScDdeLink
* pDdeLink
= dynamic_cast<ScDdeLink
*>( pLink
.get() ) )
946 if( nDdeIndex
== nDdePos
)
957 bool ScDocument::FindDdeLink( std::u16string_view rAppl
, std::u16string_view rTopic
, std::u16string_view rItem
,
958 sal_uInt8 nMode
, size_t& rnDdePos
)
960 return lclGetDdeLink( GetLinkManager(), rAppl
, rTopic
, rItem
, nMode
, &rnDdePos
) != nullptr;
963 bool ScDocument::GetDdeLinkData( size_t nDdePos
, OUString
& rAppl
, OUString
& rTopic
, OUString
& rItem
) const
965 if( const ScDdeLink
* pDdeLink
= lclGetDdeLink( GetLinkManager(), nDdePos
) )
967 rAppl
= pDdeLink
->GetAppl();
968 rTopic
= pDdeLink
->GetTopic();
969 rItem
= pDdeLink
->GetItem();
975 bool ScDocument::GetDdeLinkMode( size_t nDdePos
, sal_uInt8
& rnMode
) const
977 if( const ScDdeLink
* pDdeLink
= lclGetDdeLink( GetLinkManager(), nDdePos
) )
979 rnMode
= pDdeLink
->GetMode();
985 const ScMatrix
* ScDocument::GetDdeLinkResultMatrix( size_t nDdePos
) const
987 const ScDdeLink
* pDdeLink
= lclGetDdeLink( GetLinkManager(), nDdePos
);
988 return pDdeLink
? pDdeLink
->GetResult() : nullptr;
991 bool ScDocument::CreateDdeLink( const OUString
& rAppl
, const OUString
& rTopic
, const OUString
& rItem
, sal_uInt8 nMode
, const ScMatrixRef
& pResults
)
993 /* Create a DDE link without updating it (i.e. for Excel import), to prevent
994 unwanted connections. First try to find existing link. Set result array
995 on existing and new links. */
996 //TODO: store DDE links additionally at document (for efficiency)?
997 OSL_ENSURE( nMode
!= SC_DDE_IGNOREMODE
, "ScDocument::CreateDdeLink - SC_DDE_IGNOREMODE not allowed here" );
999 sfx2::LinkManager
* pMgr
= GetDocLinkManager().getLinkManager(bAutoCalc
);
1003 if (nMode
!= SC_DDE_IGNOREMODE
)
1005 ScDdeLink
* pDdeLink
= lclGetDdeLink(pMgr
, rAppl
, rTopic
, rItem
, nMode
);
1008 // create a new DDE link, but without TryUpdate
1009 pDdeLink
= new ScDdeLink( *this, rAppl
, rTopic
, rItem
, nMode
);
1010 pMgr
->InsertDDELink(pDdeLink
, rAppl
, rTopic
, rItem
);
1013 // insert link results
1015 pDdeLink
->SetResult( pResults
);
1022 bool ScDocument::SetDdeLinkResultMatrix( size_t nDdePos
, const ScMatrixRef
& pResults
)
1024 if( ScDdeLink
* pDdeLink
= lclGetDdeLink( GetLinkManager(), nDdePos
) )
1026 pDdeLink
->SetResult( pResults
);
1032 bool ScDocument::HasAreaLinks() const
1034 const sfx2::LinkManager
* pMgr
= GetDocLinkManager().getExistingLinkManager();
1038 const ::sfx2::SvBaseLinks
& rLinks
= pMgr
->GetLinks();
1039 sal_uInt16 nCount
= rLinks
.size();
1040 for (sal_uInt16 i
=0; i
<nCount
; i
++)
1041 if (nullptr != dynamic_cast<const ScAreaLink
* >(rLinks
[i
].get()))
1047 void ScDocument::UpdateAreaLinks()
1049 sfx2::LinkManager
* pMgr
= GetDocLinkManager().getLinkManager(false);
1053 const ::sfx2::SvBaseLinks
& rLinks
= pMgr
->GetLinks();
1054 for (const auto & rLink
: rLinks
)
1056 ::sfx2::SvBaseLink
* pBase
= rLink
.get();
1057 if (dynamic_cast<const ScAreaLink
*>( pBase
) != nullptr)
1062 void ScDocument::DeleteAreaLinksOnTab( SCTAB nTab
)
1064 sfx2::LinkManager
* pMgr
= GetDocLinkManager().getLinkManager(false);
1068 const ::sfx2::SvBaseLinks
& rLinks
= pMgr
->GetLinks();
1069 sfx2::SvBaseLinks::size_type nPos
= 0;
1070 while ( nPos
< rLinks
.size() )
1072 const ::sfx2::SvBaseLink
* pBase
= rLinks
[nPos
].get();
1073 const ScAreaLink
* pLink
= dynamic_cast<const ScAreaLink
*>(pBase
);
1074 if (pLink
&& pLink
->GetDestArea().aStart
.Tab() == nTab
)
1081 void ScDocument::UpdateRefAreaLinks( UpdateRefMode eUpdateRefMode
,
1082 const ScRange
& rRange
, SCCOL nDx
, SCROW nDy
, SCTAB nDz
)
1084 sfx2::LinkManager
* pMgr
= GetDocLinkManager().getLinkManager(false);
1088 bool bAnyUpdate
= false;
1090 const ::sfx2::SvBaseLinks
& rLinks
= pMgr
->GetLinks();
1091 sal_uInt16 nCount
= rLinks
.size();
1092 for (sal_uInt16 i
=0; i
<nCount
; i
++)
1094 ::sfx2::SvBaseLink
* pBase
= rLinks
[i
].get();
1095 if (ScAreaLink
* pLink
= dynamic_cast<ScAreaLink
*>(pBase
))
1097 ScRange aOutRange
= pLink
->GetDestArea();
1099 SCCOL nCol1
= aOutRange
.aStart
.Col();
1100 SCROW nRow1
= aOutRange
.aStart
.Row();
1101 SCTAB nTab1
= aOutRange
.aStart
.Tab();
1102 SCCOL nCol2
= aOutRange
.aEnd
.Col();
1103 SCROW nRow2
= aOutRange
.aEnd
.Row();
1104 SCTAB nTab2
= aOutRange
.aEnd
.Tab();
1106 ScRefUpdateRes eRes
=
1107 ScRefUpdate::Update( this, eUpdateRefMode
,
1108 rRange
.aStart
.Col(), rRange
.aStart
.Row(), rRange
.aStart
.Tab(),
1109 rRange
.aEnd
.Col(), rRange
.aEnd
.Row(), rRange
.aEnd
.Tab(), nDx
, nDy
, nDz
,
1110 nCol1
, nRow1
, nTab1
, nCol2
, nRow2
, nTab2
);
1111 if ( eRes
!= UR_NOTHING
)
1113 pLink
->SetDestArea( ScRange( nCol1
, nRow1
, nTab1
, nCol2
, nRow2
, nTab2
) );
1122 // #i52120# Look for duplicates (after updating all positions).
1123 // If several links start at the same cell, the one with the lower index is removed
1124 // (file format specifies only one link definition for a cell).
1126 sal_uInt16 nFirstIndex
= 0;
1127 while ( nFirstIndex
< nCount
)
1129 bool bFound
= false;
1130 ::sfx2::SvBaseLink
* pFirst
= rLinks
[nFirstIndex
].get();
1131 if (ScAreaLink
* pFirstLink
= dynamic_cast<ScAreaLink
*>(pFirst
))
1133 ScAddress aFirstPos
= pFirstLink
->GetDestArea().aStart
;
1134 for ( sal_uInt16 nSecondIndex
= nFirstIndex
+ 1; nSecondIndex
< nCount
&& !bFound
; ++nSecondIndex
)
1136 ::sfx2::SvBaseLink
* pSecond
= rLinks
[nSecondIndex
].get();
1137 ScAreaLink
* pSecondLink
= dynamic_cast<ScAreaLink
*>(pSecond
);
1138 if (pSecondLink
&& pSecondLink
->GetDestArea().aStart
== aFirstPos
)
1140 // remove the first link, exit the inner loop, don't increment nFirstIndex
1141 pMgr
->Remove(pFirst
);
1142 nCount
= rLinks
.size();
1152 void ScDocument::CheckLinkFormulaNeedingCheck( const ScTokenArray
& rCode
)
1154 if (HasLinkFormulaNeedingCheck())
1157 // Prefer RPN over tokenized formula if available.
1158 if (rCode
.GetCodeLen())
1160 if (rCode
.HasOpCodeRPN(ocDde
) || rCode
.HasOpCodeRPN(ocWebservice
))
1161 SetLinkFormulaNeedingCheck(true);
1163 else if (rCode
.GetLen())
1165 if (rCode
.HasOpCode(ocDde
) || rCode
.HasOpCode(ocWebservice
))
1166 SetLinkFormulaNeedingCheck(true);
1170 // Possible with named expression without expression like Excel
1171 // internal print ranges, obscure user define names, ... formula error
1172 // cells without formula ...
1173 SAL_WARN("sc.core","ScDocument::CheckLinkFormulaNeedingCheck - called with empty ScTokenArray");
1178 void ScDocument::KeyInput()
1180 if ( pChartListenerCollection
->hasListeners() )
1181 pChartListenerCollection
->StartTimer();
1182 if (apTemporaryChartLock
)
1183 apTemporaryChartLock
->StartOrContinueLocking();
1186 SfxBindings
* ScDocument::GetViewBindings()
1188 // used to invalidate slots after changes to this document
1191 return nullptr; // no ObjShell -> no view
1193 // first check current view
1194 SfxViewFrame
* pViewFrame
= SfxViewFrame::Current();
1195 if ( pViewFrame
&& pViewFrame
->GetObjectShell() != mpShell
) // wrong document?
1196 pViewFrame
= nullptr;
1198 // otherwise use first view for this doc
1200 pViewFrame
= SfxViewFrame::GetFirst( mpShell
);
1203 return &pViewFrame
->GetBindings();
1208 void ScDocument::TransliterateText( const ScMarkData
& rMultiMark
, TransliterationFlags nType
)
1210 OSL_ENSURE( rMultiMark
.IsMultiMarked(), "TransliterateText: no selection" );
1212 utl::TransliterationWrapper
aTransliterationWrapper( comphelper::getProcessComponentContext(), nType
);
1213 bool bConsiderLanguage
= aTransliterationWrapper
.needLanguageForTheMode();
1214 LanguageType nLanguage
= LANGUAGE_SYSTEM
;
1216 std::unique_ptr
<ScEditEngineDefaulter
> pEngine
; // not using mpEditEngine member because of defaults
1218 SCTAB nCount
= GetTableCount();
1219 for (const SCTAB
& nTab
: rMultiMark
)
1229 bool bFound
= rMultiMark
.IsCellMarked( nCol
, nRow
);
1231 bFound
= GetNextMarkedCell( nCol
, nRow
, nTab
, rMultiMark
);
1235 ScRefCellValue
aCell(*this, ScAddress(nCol
, nRow
, nTab
));
1237 // fdo#32786 TITLE_CASE/SENTENCE_CASE need the extra handling in EditEngine (loop over words/sentences).
1238 // Still use TransliterationWrapper directly for text cells with other transliteration types,
1239 // for performance reasons.
1240 if (aCell
.getType() == CELLTYPE_EDIT
||
1241 (aCell
.getType() == CELLTYPE_STRING
&&
1242 ( nType
== TransliterationFlags::SENTENCE_CASE
|| nType
== TransliterationFlags::TITLE_CASE
)))
1245 pEngine
.reset(new ScFieldEditEngine(this, GetEnginePool(), GetEditPool()));
1247 // defaults from cell attributes must be set so right language is used
1248 const ScPatternAttr
* pPattern
= GetPattern( nCol
, nRow
, nTab
);
1249 auto pDefaults
= std::make_unique
<SfxItemSet
>( pEngine
->GetEmptyItemSet() );
1250 if ( ScStyleSheet
* pPreviewStyle
= GetPreviewCellStyle( nCol
, nRow
, nTab
) )
1252 ScPatternAttr
aPreviewPattern( *pPattern
);
1253 aPreviewPattern
.SetStyleSheet(pPreviewStyle
);
1254 aPreviewPattern
.FillEditItemSet(pDefaults
.get());
1258 SfxItemSet
* pFontSet
= GetPreviewFont( nCol
, nRow
, nTab
);
1259 pPattern
->FillEditItemSet(pDefaults
.get(), pFontSet
);
1261 pEngine
->SetDefaults(std::move(pDefaults
));
1262 if (aCell
.getType() == CELLTYPE_STRING
)
1263 pEngine
->SetTextCurrentDefaults(aCell
.getSharedString()->getString());
1264 else if (aCell
.getEditText())
1265 pEngine
->SetTextCurrentDefaults(*aCell
.getEditText());
1267 pEngine
->ClearModifyFlag();
1269 sal_Int32 nLastPar
= pEngine
->GetParagraphCount();
1272 sal_Int32 nTxtLen
= pEngine
->GetTextLen(nLastPar
);
1273 ESelection
aSelAll( 0, 0, nLastPar
, nTxtLen
);
1275 pEngine
->TransliterateText( aSelAll
, nType
);
1277 if ( pEngine
->IsModified() )
1279 ScEditAttrTester
aTester( pEngine
.get() );
1280 if ( aTester
.NeedsObject() )
1282 // remove defaults (paragraph attributes) before creating text object
1283 pEngine
->SetDefaults(pEngine
->GetEmptyItemSet());
1285 // The cell will take ownership of the text object instance.
1286 SetEditText(ScAddress(nCol
,nRow
,nTab
), pEngine
->CreateTextObject());
1290 ScSetStringParam aParam
;
1291 aParam
.setTextInput();
1292 SetString(ScAddress(nCol
,nRow
,nTab
), pEngine
->GetText(), &aParam
);
1297 else if (aCell
.getType() == CELLTYPE_STRING
)
1299 OUString aOldStr
= aCell
.getSharedString()->getString();
1300 sal_Int32 nOldLen
= aOldStr
.getLength();
1302 if ( bConsiderLanguage
)
1304 SvtScriptType nScript
= GetStringScriptType( aOldStr
); //TODO: cell script type?
1305 sal_uInt16 nWhich
= ( nScript
== SvtScriptType::ASIAN
) ? ATTR_CJK_FONT_LANGUAGE
:
1306 ( ( nScript
== SvtScriptType::COMPLEX
) ? ATTR_CTL_FONT_LANGUAGE
:
1307 ATTR_FONT_LANGUAGE
);
1308 nLanguage
= static_cast<const SvxLanguageItem
*>(GetAttr( nCol
, nRow
, nTab
, nWhich
))->GetValue();
1311 uno::Sequence
<sal_Int32
> aOffsets
;
1312 OUString aNewStr
= aTransliterationWrapper
.transliterate( aOldStr
, nLanguage
, 0, nOldLen
, &aOffsets
);
1314 if ( aNewStr
!= aOldStr
)
1316 ScSetStringParam aParam
;
1317 aParam
.setTextInput();
1318 SetString(ScAddress(nCol
,nRow
,nTab
), aNewStr
, &aParam
);
1321 bFound
= GetNextMarkedCell( nCol
, nRow
, nTab
, rMultiMark
);
1327 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */