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 * This file incorporates work covered by the following license notice:
10 *
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 .
18 */
19
20 #include <scitems.hxx>
21 #include <svx/algitem.hxx>
22 #include <editeng/brushitem.hxx>
23 #include <editeng/editobj.hxx>
24 #include <svl/srchitem.hxx>
25 #include <editeng/langitem.hxx>
26 #include <o3tl/unit_conversion.hxx>
27 #include <sfx2/docfile.hxx>
28 #include <sfx2/dispatch.hxx>
29 #include <sfx2/objsh.hxx>
30 #include <sfx2/sfxsids.hrc>
31 #include <sfx2/viewfrm.hxx>
32 #include <sfx2/viewsh.hxx>
33 #include <svl/intitem.hxx>
34 #include <svl/stritem.hxx>
35 #include <svl/zforlist.hxx>
36 #include <svl/zformat.hxx>
37 #include <vcl/keycodes.hxx>
38 #include <vcl/virdev.hxx>
39 #include <vcl/settings.hxx>
40 #include <vcl/svapp.hxx>
41 #include <unotools/charclass.hxx>
42 #include <unotools/securityoptions.hxx>
43 #include <osl/diagnose.h>
44
45 #include <i18nlangtag/mslangid.hxx>
46 #include <comphelper/doublecheckedinit.hxx>
47 #include <comphelper/processfactory.hxx>
48 #include <comphelper/string.hxx>
49 #include <unotools/calendarwrapper.hxx>
50 #include <unotools/collatorwrapper.hxx>
51 #include <unotools/syslocale.hxx>
52 #include <unotools/transliterationwrapper.hxx>
53
54 #include <comphelper/lok.hxx>
55
56 #include <global.hxx>
57 #include <scresid.hxx>
58 #include <autoform.hxx>
59 #include <patattr.hxx>
60 #include <addincol.hxx>
61 #include <adiasync.hxx>
62 #include <userlist.hxx>
63 #include <interpre.hxx>
64 #include <unitconv.hxx>
65 #include <compiler.hxx>
66 #include <parclass.hxx>
67 #include <funcdesc.hxx>
68 #include <globstr.hrc>
69 #include <strings.hrc>
70 #include <scmod.hxx>
71 #include <editutil.hxx>
72 #include <docsh.hxx>
73
74 tools::SvRef<ScDocShell> ScGlobal::xDrawClipDocShellRef;
75 std::unique_ptr<SvxSearchItem> ScGlobal::xSearchItem;
76 std::unique_ptr<ScAutoFormat> ScGlobal::xAutoFormat;
77 std::atomic<LegacyFuncCollection*> ScGlobal::pLegacyFuncCollection(nullptr);
78 std::atomic<ScUnoAddInCollection*> ScGlobal::pAddInCollection(nullptr);
79 std::unique_ptr<ScUserList> ScGlobal::xUserList;
80 LanguageType ScGlobal::eLnge = LANGUAGE_SYSTEM;
81 std::atomic<css::lang::Locale*> ScGlobal::pLocale(nullptr);
82 std::unique_ptr<SvtSysLocale> ScGlobal::xSysLocale;
83 std::unique_ptr<CalendarWrapper> ScGlobal::xCalendar;
84 std::atomic<CollatorWrapper*> ScGlobal::pCollator(nullptr);
85 std::atomic<CollatorWrapper*> ScGlobal::pCaseCollator(nullptr);
86 std::atomic<::utl::TransliterationWrapper*> ScGlobal::pTransliteration(nullptr);
87 std::atomic<::utl::TransliterationWrapper*> ScGlobal::pCaseTransliteration(nullptr);
88 css::uno::Reference< css::i18n::XOrdinalSuffix> ScGlobal::xOrdinalSuffix;
89 const OUString ScGlobal::aEmptyOUString;
90 OUString ScGlobal::aStrClipDocName;
91 OUString ScGlobal::aStrErrorStringNoRef;
92
93 std::unique_ptr<SvxBrushItem> ScGlobal::xEmptyBrushItem;
94 std::unique_ptr<SvxBrushItem> ScGlobal::xButtonBrushItem;
95
96 std::unique_ptr<ScFunctionList> ScGlobal::xStarCalcFunctionList;
97 std::unique_ptr<ScFunctionMgr> ScGlobal::xStarCalcFunctionMgr;
98
99 std::atomic<ScUnitConverter*> ScGlobal::pUnitConverter(nullptr);
100 std::unique_ptr<SvNumberFormatter> ScGlobal::xEnglishFormatter;
101 std::unique_ptr<ScFieldEditEngine> ScGlobal::xFieldEditEngine;
102
103 double ScGlobal::nScreenPPTX = 96.0;
104 double ScGlobal::nScreenPPTY = 96.0;
105
106 sal_uInt16 ScGlobal::nDefFontHeight = 225;
107 sal_uInt16 ScGlobal::nStdRowHeight = 256;
108
109 tools::Long ScGlobal::nLastRowHeightExtra = 0;
110 tools::Long ScGlobal::nLastColWidthExtra = STD_EXTRA_WIDTH;
111
112 SfxViewShell* pScActiveViewShell = nullptr; //FIXME: Make this a member
113 sal_uInt16 nScClickMouseModifier = 0; //FIXME: This too
114 sal_uInt16 nScFillModeMouseModifier = 0; //FIXME: And this
115
116 bool ScGlobal::bThreadedGroupCalcInProgress = false;
117
118 // Static functions
119
HasAttrChanged(const SfxItemSet & rNewAttrs,const SfxItemSet & rOldAttrs,const sal_uInt16 nWhich)120 bool ScGlobal::HasAttrChanged( const SfxItemSet& rNewAttrs,
121 const SfxItemSet& rOldAttrs,
122 const sal_uInt16 nWhich )
123 {
124 bool bInvalidate = false;
125 const SfxPoolItem* pNewItem = nullptr;
126 const SfxItemState eNewState = rNewAttrs.GetItemState( nWhich, true, &pNewItem );
127 const SfxPoolItem* pOldItem = nullptr;
128 const SfxItemState eOldState = rOldAttrs.GetItemState( nWhich, true, &pOldItem );
129
130 if ( eNewState == eOldState )
131 {
132 // Both Items set
133 // PoolItems, meaning comparing pointers is valid
134 if ( SfxItemState::SET == eOldState )
135 bInvalidate = (pNewItem != pOldItem);
136 }
137 else
138 {
139 // Contains a Default Item
140 // PoolItems, meaning Item comparison necessary
141 if (!pOldItem)
142 pOldItem = &rOldAttrs.GetPool()->GetDefaultItem( nWhich );
143
144 if (!pNewItem)
145 pNewItem = &rNewAttrs.GetPool()->GetDefaultItem( nWhich );
146
147 bInvalidate = (*pNewItem != *pOldItem);
148 }
149
150 return bInvalidate;
151 }
152
GetStandardFormat(SvNumberFormatter & rFormatter,sal_uInt32 nFormat,SvNumFormatType nType)153 sal_uInt32 ScGlobal::GetStandardFormat( SvNumberFormatter& rFormatter,
154 sal_uInt32 nFormat, SvNumFormatType nType )
155 {
156 const SvNumberformat* pFormat = rFormatter.GetEntry( nFormat );
157 if ( pFormat )
158 return rFormatter.GetStandardFormat( nFormat, nType, pFormat->GetLanguage() );
159 return rFormatter.GetStandardFormat( nType, eLnge );
160 }
161
GetStandardRowHeight()162 sal_uInt16 ScGlobal::GetStandardRowHeight()
163 {
164 return nStdRowHeight;
165 }
166
GetEnglishFormatter()167 SvNumberFormatter* ScGlobal::GetEnglishFormatter()
168 {
169 assert(!bThreadedGroupCalcInProgress);
170 if ( !xEnglishFormatter )
171 {
172 xEnglishFormatter.reset( new SvNumberFormatter(
173 ::comphelper::getProcessComponentContext(), LANGUAGE_ENGLISH_US ) );
174 xEnglishFormatter->SetEvalDateFormat( NF_EVALDATEFORMAT_INTL_FORMAT );
175 }
176 return xEnglishFormatter.get();
177 }
178
CheckWidthInvalidate(bool & bNumFormatChanged,const SfxItemSet & rNewAttrs,const SfxItemSet & rOldAttrs)179 bool ScGlobal::CheckWidthInvalidate( bool& bNumFormatChanged,
180 const SfxItemSet& rNewAttrs,
181 const SfxItemSet& rOldAttrs )
182 {
183 // Check whether attribute changes in rNewAttrs compared to rOldAttrs render
184 // the text width at a cell invalid
185 bNumFormatChanged =
186 HasAttrChanged( rNewAttrs, rOldAttrs, ATTR_VALUE_FORMAT );
187 return ( bNumFormatChanged
188 || HasAttrChanged( rNewAttrs, rOldAttrs, ATTR_LANGUAGE_FORMAT )
189 || HasAttrChanged( rNewAttrs, rOldAttrs, ATTR_FONT )
190 || HasAttrChanged( rNewAttrs, rOldAttrs, ATTR_CJK_FONT )
191 || HasAttrChanged( rNewAttrs, rOldAttrs, ATTR_CTL_FONT )
192 || HasAttrChanged( rNewAttrs, rOldAttrs, ATTR_FONT_HEIGHT )
193 || HasAttrChanged( rNewAttrs, rOldAttrs, ATTR_CJK_FONT_HEIGHT )
194 || HasAttrChanged( rNewAttrs, rOldAttrs, ATTR_CTL_FONT_HEIGHT )
195 || HasAttrChanged( rNewAttrs, rOldAttrs, ATTR_FONT_WEIGHT )
196 || HasAttrChanged( rNewAttrs, rOldAttrs, ATTR_CJK_FONT_WEIGHT )
197 || HasAttrChanged( rNewAttrs, rOldAttrs, ATTR_CTL_FONT_WEIGHT )
198 || HasAttrChanged( rNewAttrs, rOldAttrs, ATTR_FONT_POSTURE )
199 || HasAttrChanged( rNewAttrs, rOldAttrs, ATTR_CJK_FONT_POSTURE )
200 || HasAttrChanged( rNewAttrs, rOldAttrs, ATTR_CTL_FONT_POSTURE )
201 || HasAttrChanged( rNewAttrs, rOldAttrs, ATTR_FONT_UNDERLINE )
202 || HasAttrChanged( rNewAttrs, rOldAttrs, ATTR_FONT_OVERLINE )
203 || HasAttrChanged( rNewAttrs, rOldAttrs, ATTR_FONT_CROSSEDOUT )
204 || HasAttrChanged( rNewAttrs, rOldAttrs, ATTR_FONT_CONTOUR )
205 || HasAttrChanged( rNewAttrs, rOldAttrs, ATTR_FONT_SHADOWED )
206 || HasAttrChanged( rNewAttrs, rOldAttrs, ATTR_STACKED )
207 || HasAttrChanged( rNewAttrs, rOldAttrs, ATTR_ROTATE_VALUE )
208 || HasAttrChanged( rNewAttrs, rOldAttrs, ATTR_ROTATE_MODE )
209 || HasAttrChanged( rNewAttrs, rOldAttrs, ATTR_LINEBREAK )
210 || HasAttrChanged( rNewAttrs, rOldAttrs, ATTR_MARGIN )
211 );
212 }
213
GetSearchItem()214 const SvxSearchItem& ScGlobal::GetSearchItem()
215 {
216 assert(!bThreadedGroupCalcInProgress);
217 if (!xSearchItem)
218 {
219 xSearchItem.reset(new SvxSearchItem( SID_SEARCH_ITEM ));
220 xSearchItem->SetAppFlag( SvxSearchApp::CALC );
221 }
222 return *xSearchItem;
223 }
224
SetSearchItem(const SvxSearchItem & rNew)225 void ScGlobal::SetSearchItem( const SvxSearchItem& rNew )
226 {
227 assert(!bThreadedGroupCalcInProgress);
228 // FIXME: An assignment operator would be nice here
229 xSearchItem.reset(rNew.Clone());
230
231 xSearchItem->SetWhich( SID_SEARCH_ITEM );
232 xSearchItem->SetAppFlag( SvxSearchApp::CALC );
233 }
234
ClearAutoFormat()235 void ScGlobal::ClearAutoFormat()
236 {
237 assert(!bThreadedGroupCalcInProgress);
238 if (xAutoFormat)
239 {
240 // When modified via StarOne then only the SaveLater flag is set and no saving is done.
241 // If the flag is set then save now.
242 if (xAutoFormat->IsSaveLater())
243 xAutoFormat->Save();
244 xAutoFormat.reset();
245 }
246 }
247
GetAutoFormat()248 ScAutoFormat* ScGlobal::GetAutoFormat()
249 {
250 return xAutoFormat.get();
251 }
252
GetOrCreateAutoFormat()253 ScAutoFormat* ScGlobal::GetOrCreateAutoFormat()
254 {
255 assert(!bThreadedGroupCalcInProgress);
256 if ( !xAutoFormat )
257 {
258 xAutoFormat.reset(new ScAutoFormat);
259 xAutoFormat->Load();
260 }
261
262 return xAutoFormat.get();
263 }
264
GetLegacyFuncCollection()265 LegacyFuncCollection* ScGlobal::GetLegacyFuncCollection()
266 {
267 return comphelper::doubleCheckedInit( pLegacyFuncCollection, []() { return new LegacyFuncCollection(); });
268 }
269
GetAddInCollection()270 ScUnoAddInCollection* ScGlobal::GetAddInCollection()
271 {
272 return comphelper::doubleCheckedInit( pAddInCollection, []() { return new ScUnoAddInCollection(); });
273 }
274
GetUserList()275 ScUserList* ScGlobal::GetUserList()
276 {
277 assert(!bThreadedGroupCalcInProgress);
278 // Hack: Load Cfg item at the App
279 global_InitAppOptions();
280
281 if (!xUserList)
282 xUserList.reset(new ScUserList());
283 return xUserList.get();
284 }
285
SetUserList(const ScUserList * pNewList)286 void ScGlobal::SetUserList( const ScUserList* pNewList )
287 {
288 assert(!bThreadedGroupCalcInProgress);
289 if ( pNewList )
290 {
291 if ( !xUserList )
292 xUserList.reset( new ScUserList( *pNewList ) );
293 else
294 *xUserList = *pNewList;
295 }
296 else
297 {
298 xUserList.reset();
299 }
300 }
301
GetErrorString(FormulaError nErr)302 OUString ScGlobal::GetErrorString(FormulaError nErr)
303 {
304 const char* pErrNumber;
305 switch (nErr)
306 {
307 case FormulaError::NoRef:
308 // tdf#144249 This may get called very extensively, so cached.
309 return aStrErrorStringNoRef;
310 case FormulaError::NoAddin:
311 pErrNumber = STR_NO_ADDIN;
312 break;
313 case FormulaError::NoMacro:
314 pErrNumber = STR_NO_MACRO;
315 break;
316 case FormulaError::NotAvailable:
317 return ScCompiler::GetNativeSymbol(ocErrNA);
318 case FormulaError::NoName:
319 return ScCompiler::GetNativeSymbol(ocErrName);
320 case FormulaError::NoValue:
321 return ScCompiler::GetNativeSymbol(ocErrValue);
322 case FormulaError::NoCode:
323 return ScCompiler::GetNativeSymbol(ocErrNull);
324 case FormulaError::DivisionByZero:
325 return ScCompiler::GetNativeSymbol(ocErrDivZero);
326 case FormulaError::IllegalFPOperation:
327 return ScCompiler::GetNativeSymbol(ocErrNum);
328 default:
329 return ScResId(STR_ERROR_STR) + OUString::number( static_cast<int>(nErr) );
330 }
331 return ScResId(pErrNumber);
332 }
333
GetLongErrorString(FormulaError nErr)334 OUString ScGlobal::GetLongErrorString(FormulaError nErr)
335 {
336 const char* pErrNumber;
337 switch (nErr)
338 {
339 case FormulaError::NONE:
340 return OUString();
341 case FormulaError::IllegalArgument:
342 pErrNumber = STR_LONG_ERR_ILL_ARG;
343 break;
344 case FormulaError::IllegalFPOperation:
345 pErrNumber = STR_LONG_ERR_ILL_FPO;
346 break;
347 case FormulaError::IllegalChar:
348 pErrNumber = STR_LONG_ERR_ILL_CHAR;
349 break;
350 case FormulaError::IllegalParameter:
351 pErrNumber = STR_LONG_ERR_ILL_PAR;
352 break;
353 case FormulaError::Pair:
354 case FormulaError::PairExpected:
355 pErrNumber = STR_LONG_ERR_PAIR;
356 break;
357 case FormulaError::OperatorExpected:
358 pErrNumber = STR_LONG_ERR_OP_EXP;
359 break;
360 case FormulaError::VariableExpected:
361 case FormulaError::ParameterExpected:
362 pErrNumber = STR_LONG_ERR_VAR_EXP;
363 break;
364 case FormulaError::CodeOverflow:
365 pErrNumber = STR_LONG_ERR_CODE_OVF;
366 break;
367 case FormulaError::StringOverflow:
368 pErrNumber = STR_LONG_ERR_STR_OVF;
369 break;
370 case FormulaError::StackOverflow:
371 pErrNumber = STR_LONG_ERR_STACK_OVF;
372 break;
373 case FormulaError::MatrixSize:
374 pErrNumber = STR_LONG_ERR_MATRIX_SIZE;
375 break;
376 case FormulaError::UnknownState:
377 case FormulaError::UnknownVariable:
378 case FormulaError::UnknownOpCode:
379 case FormulaError::UnknownStackVariable:
380 case FormulaError::UnknownToken:
381 case FormulaError::NoCode:
382 pErrNumber = STR_LONG_ERR_SYNTAX;
383 break;
384 case FormulaError::CircularReference:
385 pErrNumber = STR_LONG_ERR_CIRC_REF;
386 break;
387 case FormulaError::NoConvergence:
388 pErrNumber = STR_LONG_ERR_NO_CONV;
389 break;
390 case FormulaError::NoRef:
391 pErrNumber = STR_LONG_ERR_NO_REF;
392 break;
393 case FormulaError::NoName:
394 pErrNumber = STR_LONG_ERR_NO_NAME;
395 break;
396 case FormulaError::NoAddin:
397 pErrNumber = STR_LONG_ERR_NO_ADDIN;
398 break;
399 case FormulaError::NoMacro:
400 pErrNumber = STR_LONG_ERR_NO_MACRO;
401 break;
402 case FormulaError::DivisionByZero:
403 pErrNumber = STR_LONG_ERR_DIV_ZERO;
404 break;
405 case FormulaError::NestedArray:
406 pErrNumber = STR_ERR_LONG_NESTED_ARRAY;
407 break;
408 case FormulaError::BadArrayContent:
409 pErrNumber = STR_ERR_LONG_BAD_ARRAY_CONTENT;
410 break;
411 case FormulaError::LinkFormulaNeedingCheck:
412 pErrNumber = STR_ERR_LONG_LINK_FORMULA_NEEDING_CHECK;
413 break;
414 case FormulaError::NoValue:
415 pErrNumber = STR_LONG_ERR_NO_VALUE;
416 break;
417 case FormulaError::NotAvailable:
418 pErrNumber = STR_LONG_ERR_NV;
419 break;
420 default:
421 return ScResId(STR_ERROR_STR) + OUString::number( static_cast<int>(nErr) );
422 }
423 return ScResId(pErrNumber);
424 }
425
GetButtonBrushItem()426 SvxBrushItem* ScGlobal::GetButtonBrushItem()
427 {
428 assert(!bThreadedGroupCalcInProgress);
429 xButtonBrushItem->SetColor( Application::GetSettings().GetStyleSettings().GetFaceColor() );
430 return xButtonBrushItem.get();
431 }
432
Init()433 void ScGlobal::Init()
434 {
435 // The default language for number formats (ScGlobal::eLnge) must
436 // always be LANGUAGE_SYSTEM
437 // FIXME: So remove this variable?
438 eLnge = LANGUAGE_SYSTEM;
439
440 xSysLocale = std::make_unique<SvtSysLocale>();
441
442 xEmptyBrushItem = std::make_unique<SvxBrushItem>( COL_TRANSPARENT, ATTR_BACKGROUND );
443 xButtonBrushItem = std::make_unique<SvxBrushItem>( Color(), ATTR_BACKGROUND );
444
445 InitPPT();
446 //ScCompiler::InitSymbolsNative();
447 // ScParameterClassification _after_ Compiler, needs function resources if
448 // arguments are to be merged in, which in turn need strings of function
449 // names from the compiler.
450 ScParameterClassification::Init();
451
452 InitAddIns();
453
454 aStrClipDocName = ScResId( SCSTR_NONAME ) + "1";
455 aStrErrorStringNoRef = ScResId( STR_NO_REF_TABLE );
456
457 // ScDocumentPool::InitVersionMaps() has been called earlier already
458 }
459
InitPPT()460 void ScGlobal::InitPPT()
461 {
462 OutputDevice* pDev = Application::GetDefaultDevice();
463
464 if (comphelper::LibreOfficeKit::isActive())
465 {
466 // LOK: the below limited precision is not enough for RowColumnHeader.
467 nScreenPPTX = o3tl::convert<double>(pDev->GetDPIX(), o3tl::Length::twip, o3tl::Length::in);
468 nScreenPPTY = o3tl::convert<double>(pDev->GetDPIY(), o3tl::Length::twip, o3tl::Length::in);
469 }
470 else
471 {
472 // Avoid cumulative placement errors by intentionally limiting
473 // precision.
474 Point aPix1000 = pDev->LogicToPixel(Point(1000, 1000), MapMode(MapUnit::MapTwip));
475 nScreenPPTX = aPix1000.X() / 1000.0;
476 nScreenPPTY = aPix1000.Y() / 1000.0;
477 }
478 }
479
GetClipDocName()480 const OUString& ScGlobal::GetClipDocName()
481 {
482 return aStrClipDocName;
483 }
484
SetClipDocName(const OUString & rNew)485 void ScGlobal::SetClipDocName( const OUString& rNew )
486 {
487 assert(!bThreadedGroupCalcInProgress);
488 aStrClipDocName = rNew;
489 }
490
InitTextHeight(const SfxItemPool * pPool)491 void ScGlobal::InitTextHeight(const SfxItemPool* pPool)
492 {
493 if (!pPool)
494 {
495 OSL_FAIL("ScGlobal::InitTextHeight: No Pool");
496 return;
497 }
498
499 const ScPatternAttr& rPattern = pPool->GetDefaultItem(ATTR_PATTERN);
500
501 OutputDevice* pDefaultDev = Application::GetDefaultDevice();
502 ScopedVclPtrInstance< VirtualDevice > pVirtWindow( *pDefaultDev );
503 pVirtWindow->SetMapMode(MapMode(MapUnit::MapPixel));
504 vcl::Font aDefFont;
505 rPattern.GetFont(aDefFont, SC_AUTOCOL_BLACK, pVirtWindow); // Font color doesn't matter here
506 pVirtWindow->SetFont(aDefFont);
507 sal_uInt16 nTest = static_cast<sal_uInt16>(
508 pVirtWindow->PixelToLogic(Size(0, pVirtWindow->GetTextHeight()), MapMode(MapUnit::MapTwip)).Height());
509
510 if (nTest > nDefFontHeight)
511 nDefFontHeight = nTest;
512
513 const SvxMarginItem& rMargin = rPattern.GetItem(ATTR_MARGIN);
514
515 nTest = static_cast<sal_uInt16>(nDefFontHeight + rMargin.GetTopMargin()
516 + rMargin.GetBottomMargin() - STD_ROWHEIGHT_DIFF);
517
518 if (nTest > nStdRowHeight)
519 nStdRowHeight = nTest;
520 }
521
Clear()522 void ScGlobal::Clear()
523 {
524 // Destroy asyncs _before_ ExitExternalFunc!
525 theAddInAsyncTbl.clear();
526 ExitExternalFunc();
527 ClearAutoFormat();
528 xSearchItem.reset();
529 delete pLegacyFuncCollection.load(); pLegacyFuncCollection = nullptr;
530 delete pAddInCollection.load(); pAddInCollection = nullptr;
531 xUserList.reset();
532 xStarCalcFunctionList.reset(); // Destroy before ResMgr!
533 xStarCalcFunctionMgr.reset();
534 ScParameterClassification::Exit();
535 ScCompiler::DeInit();
536 ScInterpreter::GlobalExit(); // Delete static Stack
537
538 xEmptyBrushItem.reset();
539 xButtonBrushItem.reset();
540 xEnglishFormatter.reset();
541 delete pCaseTransliteration.load(); pCaseTransliteration = nullptr;
542 delete pTransliteration.load(); pTransliteration = nullptr;
543 delete pCaseCollator.load(); pCaseCollator = nullptr;
544 delete pCollator.load(); pCollator = nullptr;
545 xCalendar.reset();
546 xSysLocale.reset();
547 delete pLocale.load(); pLocale = nullptr;
548
549 delete pUnitConverter.load(); pUnitConverter = nullptr;
550 xFieldEditEngine.reset();
551
552 xDrawClipDocShellRef.clear();
553 }
554
GetCharsetValue(const OUString & rCharSet)555 rtl_TextEncoding ScGlobal::GetCharsetValue( const OUString& rCharSet )
556 {
557 // new TextEncoding values
558 if ( CharClass::isAsciiNumeric( rCharSet ) )
559 {
560 sal_Int32 nVal = rCharSet.toInt32();
561 if ( nVal == RTL_TEXTENCODING_DONTKNOW )
562 return osl_getThreadTextEncoding();
563 return static_cast<rtl_TextEncoding>(nVal);
564 }
565 // old CharSet values for compatibility
566 else if (rCharSet.equalsIgnoreAsciiCase("ANSI") ) return RTL_TEXTENCODING_MS_1252;
567 else if (rCharSet.equalsIgnoreAsciiCase("MAC") ) return RTL_TEXTENCODING_APPLE_ROMAN;
568 else if (rCharSet.equalsIgnoreAsciiCase("IBMPC") ) return RTL_TEXTENCODING_IBM_850;
569 else if (rCharSet.equalsIgnoreAsciiCase("IBMPC_437")) return RTL_TEXTENCODING_IBM_437;
570 else if (rCharSet.equalsIgnoreAsciiCase("IBMPC_850")) return RTL_TEXTENCODING_IBM_850;
571 else if (rCharSet.equalsIgnoreAsciiCase("IBMPC_860")) return RTL_TEXTENCODING_IBM_860;
572 else if (rCharSet.equalsIgnoreAsciiCase("IBMPC_861")) return RTL_TEXTENCODING_IBM_861;
573 else if (rCharSet.equalsIgnoreAsciiCase("IBMPC_863")) return RTL_TEXTENCODING_IBM_863;
574 else if (rCharSet.equalsIgnoreAsciiCase("IBMPC_865")) return RTL_TEXTENCODING_IBM_865;
575 // Some wrong "help" on the net mentions UTF8 and even unoconv uses it,
576 // which worked accidentally if the system encoding is UTF-8 anyway, so
577 // support it ;) but only when reading.
578 else if (rCharSet.equalsIgnoreAsciiCase("UTF8")) return RTL_TEXTENCODING_UTF8;
579 else if (rCharSet.equalsIgnoreAsciiCase("UTF-8")) return RTL_TEXTENCODING_UTF8;
580 else return osl_getThreadTextEncoding();
581 }
582
GetCharsetString(rtl_TextEncoding eVal)583 OUString ScGlobal::GetCharsetString( rtl_TextEncoding eVal )
584 {
585 const char* pChar;
586 switch ( eVal )
587 {
588 // old CharSet strings for compatibility
589 case RTL_TEXTENCODING_MS_1252: pChar = "ANSI"; break;
590 case RTL_TEXTENCODING_APPLE_ROMAN: pChar = "MAC"; break;
591 // IBMPC == IBMPC_850
592 case RTL_TEXTENCODING_IBM_437: pChar = "IBMPC_437"; break;
593 case RTL_TEXTENCODING_IBM_850: pChar = "IBMPC_850"; break;
594 case RTL_TEXTENCODING_IBM_860: pChar = "IBMPC_860"; break;
595 case RTL_TEXTENCODING_IBM_861: pChar = "IBMPC_861"; break;
596 case RTL_TEXTENCODING_IBM_863: pChar = "IBMPC_863"; break;
597 case RTL_TEXTENCODING_IBM_865: pChar = "IBMPC_865"; break;
598 case RTL_TEXTENCODING_DONTKNOW: pChar = "SYSTEM"; break;
599 // new string of TextEncoding value
600 default:
601 return OUString::number( eVal );
602 }
603 return OUString::createFromAscii(pChar);
604 }
605
HasStarCalcFunctionList()606 bool ScGlobal::HasStarCalcFunctionList()
607 {
608 return bool(xStarCalcFunctionList);
609 }
610
GetStarCalcFunctionList()611 ScFunctionList* ScGlobal::GetStarCalcFunctionList()
612 {
613 assert(!bThreadedGroupCalcInProgress);
614 if ( !xStarCalcFunctionList )
615 xStarCalcFunctionList.reset(new ScFunctionList);
616
617 return xStarCalcFunctionList.get();
618 }
619
GetStarCalcFunctionMgr()620 ScFunctionMgr* ScGlobal::GetStarCalcFunctionMgr()
621 {
622 assert(!bThreadedGroupCalcInProgress);
623 if ( !xStarCalcFunctionMgr )
624 xStarCalcFunctionMgr.reset(new ScFunctionMgr);
625
626 return xStarCalcFunctionMgr.get();
627 }
628
ResetFunctionList()629 void ScGlobal::ResetFunctionList()
630 {
631 // FunctionMgr has pointers into FunctionList, must also be updated
632 xStarCalcFunctionMgr.reset();
633 xStarCalcFunctionList.reset();
634 }
635
GetUnitConverter()636 ScUnitConverter* ScGlobal::GetUnitConverter()
637 {
638 return comphelper::doubleCheckedInit( pUnitConverter,
639 []() { return new ScUnitConverter; });
640 }
641
UnicodeStrChr(const sal_Unicode * pStr,sal_Unicode c)642 const sal_Unicode* ScGlobal::UnicodeStrChr( const sal_Unicode* pStr,
643 sal_Unicode c )
644 {
645 if ( !pStr )
646 return nullptr;
647 while ( *pStr )
648 {
649 if ( *pStr == c )
650 return pStr;
651 pStr++;
652 }
653 return nullptr;
654 }
655
addToken(const OUString & rTokenList,std::u16string_view rToken,sal_Unicode cSep,sal_Int32 nSepCount,bool bForceSep)656 OUString ScGlobal::addToken(const OUString& rTokenList, std::u16string_view rToken,
657 sal_Unicode cSep, sal_Int32 nSepCount, bool bForceSep)
658 {
659 OUStringBuffer aBuf(rTokenList);
660 if( bForceSep || (!rToken.empty() && !rTokenList.isEmpty()) )
661 comphelper::string::padToLength(aBuf, aBuf.getLength() + nSepCount, cSep);
662 aBuf.append(rToken);
663 return aBuf.makeStringAndClear();
664 }
665
IsQuoted(const OUString & rString,sal_Unicode cQuote)666 bool ScGlobal::IsQuoted( const OUString& rString, sal_Unicode cQuote )
667 {
668 return (rString.getLength() >= 2) && (rString[0] == cQuote) && (rString[ rString.getLength() - 1 ] == cQuote);
669 }
670
AddQuotes(OUString & rString,sal_Unicode cQuote,bool bEscapeEmbedded)671 void ScGlobal::AddQuotes( OUString& rString, sal_Unicode cQuote, bool bEscapeEmbedded )
672 {
673 if (bEscapeEmbedded)
674 {
675 sal_Unicode pQ[3];
676 pQ[0] = pQ[1] = cQuote;
677 pQ[2] = 0;
678 OUString aQuotes( pQ );
679 rString = rString.replaceAll( OUStringChar(cQuote), aQuotes);
680 }
681 rString = OUStringChar( cQuote ) + rString + OUStringChar( cQuote );
682 }
683
EraseQuotes(OUString & rString,sal_Unicode cQuote,bool bUnescapeEmbedded)684 void ScGlobal::EraseQuotes( OUString& rString, sal_Unicode cQuote, bool bUnescapeEmbedded )
685 {
686 if ( IsQuoted( rString, cQuote ) )
687 {
688 rString = rString.copy( 1, rString.getLength() - 2 );
689 if (bUnescapeEmbedded)
690 {
691 sal_Unicode pQ[3];
692 pQ[0] = pQ[1] = cQuote;
693 pQ[2] = 0;
694 OUString aQuotes( pQ );
695 rString = rString.replaceAll( aQuotes, OUStringChar(cQuote));
696 }
697 }
698 }
699
FindUnquoted(const OUString & rString,sal_Unicode cChar,sal_Int32 nStart)700 sal_Int32 ScGlobal::FindUnquoted( const OUString& rString, sal_Unicode cChar, sal_Int32 nStart )
701 {
702 assert(nStart >= 0);
703 const sal_Unicode cQuote = '\'';
704 const sal_Unicode* const pStart = rString.getStr();
705 const sal_Unicode* const pStop = pStart + rString.getLength();
706 const sal_Unicode* p = pStart + nStart;
707 bool bQuoted = false;
708 while (p < pStop)
709 {
710 if (*p == cChar && !bQuoted)
711 return sal::static_int_cast< sal_Int32 >( p - pStart );
712 else if (*p == cQuote)
713 {
714 if (!bQuoted)
715 bQuoted = true;
716 else if (p < pStop-1 && *(p+1) == cQuote)
717 ++p;
718 else
719 bQuoted = false;
720 }
721 ++p;
722 }
723 return -1;
724 }
725
FindUnquoted(const sal_Unicode * pString,sal_Unicode cChar)726 const sal_Unicode* ScGlobal::FindUnquoted( const sal_Unicode* pString, sal_Unicode cChar )
727 {
728 sal_Unicode cQuote = '\'';
729 const sal_Unicode* p = pString;
730 bool bQuoted = false;
731 while (*p)
732 {
733 if (*p == cChar && !bQuoted)
734 return p;
735 else if (*p == cQuote)
736 {
737 if (!bQuoted)
738 bQuoted = true;
739 else if (*(p+1) == cQuote)
740 ++p;
741 else
742 bQuoted = false;
743 }
744 ++p;
745 }
746 return nullptr;
747 }
748
EETextObjEqual(const EditTextObject * pObj1,const EditTextObject * pObj2)749 bool ScGlobal::EETextObjEqual( const EditTextObject* pObj1,
750 const EditTextObject* pObj2 )
751 {
752 if ( pObj1 == pObj2 ) // Both empty or the same object
753 return true;
754
755 if ( pObj1 && pObj2 )
756 return pObj1->Equals( *pObj2);
757
758 return false;
759 }
760
OpenURL(const OUString & rURL,const OUString & rTarget,bool bIgnoreSettings)761 void ScGlobal::OpenURL(const OUString& rURL, const OUString& rTarget, bool bIgnoreSettings)
762 {
763 // OpenURL is always called in the GridWindow by mouse clicks in some way or another.
764 // That's why pScActiveViewShell and nScClickMouseModifier are correct.
765
766 if (!bIgnoreSettings && !ShouldOpenURL())
767 return;
768
769 SfxViewFrame* pViewFrm = SfxViewFrame::Current();
770 if (!pViewFrm)
771 return;
772
773 OUString aUrlName( rURL );
774 SfxViewFrame* pFrame = nullptr;
775 const SfxObjectShell* pObjShell = nullptr;
776 OUString aReferName;
777 if ( pScActiveViewShell )
778 {
779 pFrame = pScActiveViewShell->GetViewFrame();
780 pObjShell = pFrame->GetObjectShell();
781 const SfxMedium* pMed = pObjShell->GetMedium();
782 if (pMed)
783 aReferName = pMed->GetName();
784 }
785
786 // Don't fiddle with fragments pointing into current document.
787 // Also don't mess around with a vnd.sun.star.script or service or other
788 // internal "URI".
789 if (!aUrlName.startsWith("#")
790 && !aUrlName.startsWithIgnoreAsciiCase("vnd.sun.star.script:")
791 && !aUrlName.startsWithIgnoreAsciiCase("macro:")
792 && !aUrlName.startsWithIgnoreAsciiCase("slot:")
793 && !aUrlName.startsWithIgnoreAsciiCase("service:")
794 && !aUrlName.startsWithIgnoreAsciiCase(".uno:"))
795 {
796 // Any relative reference would fail with "not an absolute URL"
797 // error, try to construct an absolute URI with the path relative
798 // to the current document's path or work path, as usual for all
799 // external references.
800 // This then also, as ScGlobal::GetAbsDocName() uses
801 // INetURLObject::smartRel2Abs(), supports "\\" UNC path names as
802 // smb:// Samba shares and DOS path separators converted to proper
803 // file:// URI.
804 const OUString aNewUrlName( ScGlobal::GetAbsDocName( aUrlName, pObjShell));
805 if (!aNewUrlName.isEmpty())
806 aUrlName = aNewUrlName;
807 }
808
809 SfxStringItem aUrl( SID_FILE_NAME, aUrlName );
810 SfxStringItem aTarget( SID_TARGETNAME, rTarget );
811 if ( nScClickMouseModifier & KEY_SHIFT ) // control-click -> into new window
812 aTarget.SetValue("_blank");
813
814 SfxFrameItem aFrm( SID_DOCFRAME, pFrame );
815 SfxStringItem aReferer( SID_REFERER, aReferName );
816
817 SfxBoolItem aNewView( SID_OPEN_NEW_VIEW, false );
818 SfxBoolItem aBrowsing( SID_BROWSE, true );
819
820 // No SID_SILENT anymore
821 pViewFrm->GetDispatcher()->ExecuteList(SID_OPENDOC,
822 SfxCallMode::ASYNCHRON | SfxCallMode::RECORD,
823 { &aUrl, &aTarget, &aFrm, &aReferer, &aNewView, &aBrowsing });
824 }
825
ShouldOpenURL()826 bool ScGlobal::ShouldOpenURL()
827 {
828 SvtSecurityOptions aSecOpt;
829 bool bCtrlClickHappened = (nScClickMouseModifier & KEY_MOD1);
830 bool bCtrlClickSecOption = aSecOpt.IsOptionSet( SvtSecurityOptions::EOption::CtrlClickHyperlink );
831 if( bCtrlClickHappened && ! bCtrlClickSecOption )
832 {
833 // return since ctrl+click happened when the
834 // ctrl+click security option was disabled, link should not open
835 return false;
836 }
837 else if( ! bCtrlClickHappened && bCtrlClickSecOption )
838 {
839 // ctrl+click did not happen; only click happened maybe with some
840 // other key combo. and security option is set, so return
841 return false;
842 }
843 return true;
844 }
845
IsSystemRTL()846 bool ScGlobal::IsSystemRTL()
847 {
848 return MsLangId::isRightToLeft( Application::GetSettings().GetLanguageTag().getLanguageType() );
849 }
850
GetDefaultScriptType()851 SvtScriptType ScGlobal::GetDefaultScriptType()
852 {
853 // Used when text contains only WEAK characters.
854 // Script type of office language is used then (same as GetEditDefaultLanguage,
855 // to get consistent behavior of text in simple cells and EditEngine,
856 // also same as GetAppLanguage() in Writer)
857 return SvtLanguageOptions::GetScriptTypeOfLanguage( Application::GetSettings().GetLanguageTag().getLanguageType() );
858 }
859
GetEditDefaultLanguage()860 LanguageType ScGlobal::GetEditDefaultLanguage()
861 {
862 // Used for EditEngine::SetDefaultLanguage
863 return Application::GetSettings().GetLanguageTag().getLanguageType();
864 }
865
GetScriptedWhichID(SvtScriptType nScriptType,sal_uInt16 nWhich)866 sal_uInt16 ScGlobal::GetScriptedWhichID( SvtScriptType nScriptType, sal_uInt16 nWhich )
867 {
868 switch ( nScriptType )
869 {
870 case SvtScriptType::LATIN:
871 case SvtScriptType::ASIAN:
872 case SvtScriptType::COMPLEX:
873 break; // take exact matches
874 default: // prefer one, first COMPLEX, then ASIAN
875 if ( nScriptType & SvtScriptType::COMPLEX )
876 nScriptType = SvtScriptType::COMPLEX;
877 else if ( nScriptType & SvtScriptType::ASIAN )
878 nScriptType = SvtScriptType::ASIAN;
879 }
880 switch ( nScriptType )
881 {
882 case SvtScriptType::COMPLEX:
883 {
884 switch ( nWhich )
885 {
886 case ATTR_FONT:
887 case ATTR_CJK_FONT:
888 nWhich = ATTR_CTL_FONT;
889 break;
890 case ATTR_FONT_HEIGHT:
891 case ATTR_CJK_FONT_HEIGHT:
892 nWhich = ATTR_CTL_FONT_HEIGHT;
893 break;
894 case ATTR_FONT_WEIGHT:
895 case ATTR_CJK_FONT_WEIGHT:
896 nWhich = ATTR_CTL_FONT_WEIGHT;
897 break;
898 case ATTR_FONT_POSTURE:
899 case ATTR_CJK_FONT_POSTURE:
900 nWhich = ATTR_CTL_FONT_POSTURE;
901 break;
902 }
903 }
904 break;
905 case SvtScriptType::ASIAN:
906 {
907 switch ( nWhich )
908 {
909 case ATTR_FONT:
910 case ATTR_CTL_FONT:
911 nWhich = ATTR_CJK_FONT;
912 break;
913 case ATTR_FONT_HEIGHT:
914 case ATTR_CTL_FONT_HEIGHT:
915 nWhich = ATTR_CJK_FONT_HEIGHT;
916 break;
917 case ATTR_FONT_WEIGHT:
918 case ATTR_CTL_FONT_WEIGHT:
919 nWhich = ATTR_CJK_FONT_WEIGHT;
920 break;
921 case ATTR_FONT_POSTURE:
922 case ATTR_CTL_FONT_POSTURE:
923 nWhich = ATTR_CJK_FONT_POSTURE;
924 break;
925 }
926 }
927 break;
928 default:
929 {
930 switch ( nWhich )
931 {
932 case ATTR_CTL_FONT:
933 case ATTR_CJK_FONT:
934 nWhich = ATTR_FONT;
935 break;
936 case ATTR_CTL_FONT_HEIGHT:
937 case ATTR_CJK_FONT_HEIGHT:
938 nWhich = ATTR_FONT_HEIGHT;
939 break;
940 case ATTR_CTL_FONT_WEIGHT:
941 case ATTR_CJK_FONT_WEIGHT:
942 nWhich = ATTR_FONT_WEIGHT;
943 break;
944 case ATTR_CTL_FONT_POSTURE:
945 case ATTR_CJK_FONT_POSTURE:
946 nWhich = ATTR_FONT_POSTURE;
947 break;
948 }
949 }
950 }
951 return nWhich;
952 }
953
AddLanguage(SfxItemSet & rSet,const SvNumberFormatter & rFormatter)954 void ScGlobal::AddLanguage( SfxItemSet& rSet, const SvNumberFormatter& rFormatter )
955 {
956 OSL_ENSURE( rSet.GetItemState( ATTR_LANGUAGE_FORMAT, false ) == SfxItemState::DEFAULT,
957 "ScGlobal::AddLanguage - language already added");
958
959 const SfxPoolItem* pHardItem;
960 if ( rSet.GetItemState( ATTR_VALUE_FORMAT, false, &pHardItem ) != SfxItemState::SET )
961 return;
962
963 const SvNumberformat* pHardFormat = rFormatter.GetEntry(
964 static_cast<const SfxUInt32Item*>(pHardItem)->GetValue() );
965
966 sal_uInt32 nParentFmt = 0; // Pool default
967 const SfxItemSet* pParent = rSet.GetParent();
968 if ( pParent )
969 nParentFmt = pParent->Get( ATTR_VALUE_FORMAT ).GetValue();
970 const SvNumberformat* pParFormat = rFormatter.GetEntry( nParentFmt );
971
972 if ( pHardFormat && pParFormat &&
973 (pHardFormat->GetLanguage() != pParFormat->GetLanguage()) )
974 rSet.Put( SvxLanguageItem( pHardFormat->GetLanguage(), ATTR_LANGUAGE_FORMAT ) );
975 }
976
GetpTransliteration()977 utl::TransliterationWrapper* ScGlobal::GetpTransliteration()
978 {
979 return comphelper::doubleCheckedInit( pTransliteration,
980 []()
981 {
982 const LanguageType eOfficeLanguage = Application::GetSettings().GetLanguageTag().getLanguageType();
983 ::utl::TransliterationWrapper* p = new ::utl::TransliterationWrapper(
984 ::comphelper::getProcessComponentContext(), TransliterationFlags::IGNORE_CASE );
985 p->loadModuleIfNeeded( eOfficeLanguage );
986 return p;
987 });
988 }
GetCaseTransliteration()989 ::utl::TransliterationWrapper* ScGlobal::GetCaseTransliteration()
990 {
991 return comphelper::doubleCheckedInit( pCaseTransliteration,
992 []()
993 {
994 const LanguageType eOfficeLanguage = Application::GetSettings().GetLanguageTag().getLanguageType();
995 ::utl::TransliterationWrapper* p = new ::utl::TransliterationWrapper(
996 ::comphelper::getProcessComponentContext(), TransliterationFlags::NONE );
997 p->loadModuleIfNeeded( eOfficeLanguage );
998 return p;
999 });
1000 }
1001
getLocaleDataPtr()1002 const LocaleDataWrapper* ScGlobal::getLocaleDataPtr()
1003 {
1004 OSL_ENSURE(
1005 xSysLocale,
1006 "ScGlobal::getLocaleDataPtr() called before ScGlobal::Init()");
1007
1008 return &xSysLocale->GetLocaleData();
1009 }
1010
getCharClassPtr()1011 const CharClass* ScGlobal::getCharClassPtr()
1012 {
1013 OSL_ENSURE(
1014 xSysLocale,
1015 "ScGlobal::getCharClassPtr() called before ScGlobal::Init()");
1016
1017 return xSysLocale->GetCharClassPtr();
1018 }
1019
GetCalendar()1020 CalendarWrapper* ScGlobal::GetCalendar()
1021 {
1022 assert(!bThreadedGroupCalcInProgress);
1023 if ( !xCalendar )
1024 {
1025 xCalendar.reset( new CalendarWrapper( ::comphelper::getProcessComponentContext() ) );
1026 xCalendar->loadDefaultCalendar( *GetLocale() );
1027 }
1028 return xCalendar.get();
1029 }
GetCollator()1030 CollatorWrapper* ScGlobal::GetCollator()
1031 {
1032 return comphelper::doubleCheckedInit( pCollator,
1033 []()
1034 {
1035 CollatorWrapper* p = new CollatorWrapper( ::comphelper::getProcessComponentContext() );
1036 p->loadDefaultCollator( *GetLocale(), SC_COLLATOR_IGNORES );
1037 return p;
1038 });
1039 }
GetCaseCollator()1040 CollatorWrapper* ScGlobal::GetCaseCollator()
1041 {
1042 return comphelper::doubleCheckedInit( pCaseCollator,
1043 []()
1044 {
1045 CollatorWrapper* p = new CollatorWrapper( ::comphelper::getProcessComponentContext() );
1046 p->loadDefaultCollator( *GetLocale(), 0 );
1047 return p;
1048 });
1049 }
GetLocale()1050 css::lang::Locale* ScGlobal::GetLocale()
1051 {
1052 return comphelper::doubleCheckedInit( pLocale,
1053 []() { return new css::lang::Locale( Application::GetSettings().GetLanguageTag().getLocale()); });
1054 }
1055
GetStaticFieldEditEngine()1056 ScFieldEditEngine& ScGlobal::GetStaticFieldEditEngine()
1057 {
1058 assert(!bThreadedGroupCalcInProgress);
1059 if (!xFieldEditEngine)
1060 {
1061 // Creating a ScFieldEditEngine with pDocument=NULL leads to document
1062 // specific fields not being resolvable! See
1063 // ScFieldEditEngine::CalcFieldValue(). pEnginePool=NULL lets
1064 // EditEngine internally create and delete a default pool.
1065 xFieldEditEngine.reset(new ScFieldEditEngine( nullptr, nullptr));
1066 }
1067 return *xFieldEditEngine;
1068 }
1069
ReplaceOrAppend(const OUString & rString,std::u16string_view rPlaceholder,const OUString & rReplacement)1070 OUString ScGlobal::ReplaceOrAppend( const OUString& rString,
1071 std::u16string_view rPlaceholder, const OUString& rReplacement )
1072 {
1073 if (rString.isEmpty())
1074 return rReplacement;
1075 sal_Int32 nFound = rString.indexOf( rPlaceholder);
1076 if (nFound < 0)
1077 {
1078 if (rString[rString.getLength()-1] == ' ')
1079 return rString + rReplacement;
1080 return rString + " " + rReplacement;
1081 }
1082 return rString.replaceFirst( rPlaceholder, rReplacement, &nFound);
1083 }
1084
1085 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */
1086