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 <vcl/settings.hxx>
21 #include <vcl/weld.hxx>
22 #include <i18nlangtag/languagetag.hxx>
23 #include <i18nlangtag/mslangid.hxx>
24 #include <unotools/lingucfg.hxx>
25 #include <unotools/linguprops.hxx>
26 #include <editeng/unolingu.hxx>
27 #include <linguistic/misc.hxx>
28 #include <sfx2/sfxsids.hrc>
29 #include <tools/debug.hxx>
30 #include <tools/urlobj.hxx>
31 #include <tools/diagnose_ex.h>
32 #include <comphelper/processfactory.hxx>
33 #include <com/sun/star/linguistic2/LinguServiceManager.hpp>
34 #include <com/sun/star/linguistic2/XSpellChecker.hpp>
35 #include <com/sun/star/linguistic2/XProofreader.hpp>
36 #include <com/sun/star/linguistic2/XHyphenator.hpp>
37 #include <com/sun/star/linguistic2/XThesaurus.hpp>
38 #include <com/sun/star/linguistic2/XDictionary.hpp>
39 #include <com/sun/star/linguistic2/XDictionaryList.hpp>
40 #include <com/sun/star/linguistic2/XLinguProperties.hpp>
41 #include <com/sun/star/lang/XServiceDisplayName.hpp>
42 #include <com/sun/star/frame/XStorable.hpp>
43 #include <unotools/extendedsecurityoptions.hxx>
44 #include <svl/eitem.hxx>
45 #include <vcl/svapp.hxx>
46 #include <sal/log.hxx>
47 #include <osl/diagnose.h>
48
49 #include <svx/svxdlg.hxx>
50 #include <editeng/optitems.hxx>
51 #include <optlingu.hxx>
52 #include <dialmgr.hxx>
53 #include <strings.hrc>
54
55 #include <ucbhelper/content.hxx>
56
57 #include <vector>
58 #include <map>
59
60 using namespace ::ucbhelper;
61 using namespace ::com::sun::star;
62 using namespace css::lang;
63 using namespace css::uno;
64 using namespace css::linguistic2;
65 using namespace css::beans;
66
67 static const sal_Char cSpell[] = SN_SPELLCHECKER;
68 static const sal_Char cGrammar[] = SN_GRAMMARCHECKER;
69 static const sal_Char cHyph[] = SN_HYPHENATOR;
70 static const sal_Char cThes[] = SN_THESAURUS;
71
72 // static ----------------------------------------------------------------
73
lcl_SeqGetEntryPos(const Sequence<OUString> & rSeq,const OUString & rEntry)74 static sal_Int32 lcl_SeqGetEntryPos(
75 const Sequence< OUString > &rSeq, const OUString &rEntry )
76 {
77 sal_Int32 i;
78 sal_Int32 nLen = rSeq.getLength();
79 const OUString *pItem = rSeq.getConstArray();
80 for (i = 0; i < nLen; ++i)
81 {
82 if (rEntry == pItem[i])
83 break;
84 }
85 return i < nLen ? i : -1;
86 }
87
KillFile_Impl(const OUString & rURL)88 static bool KillFile_Impl( const OUString& rURL )
89 {
90 bool bRet = true;
91 try
92 {
93 Content aCnt( rURL, uno::Reference< css::ucb::XCommandEnvironment >(), comphelper::getProcessComponentContext() );
94 aCnt.executeCommand( "delete", Any( true ) );
95 }
96 catch( ... )
97 {
98 TOOLS_WARN_EXCEPTION( "cui.options", "KillFile" );
99 bRet = false;
100 }
101
102 return bRet;
103 }
104
105 // 0x 0p 0t 0c nn
106 // p: 1 -> parent
107 // t: 1 -> spell, 2 -> hyph, 3 -> thes, 4 -> grammar
108 // c: 1 -> checked 0 -> unchecked
109 // n: index
110
111 #define TYPE_SPELL sal_uInt8(1)
112 #define TYPE_GRAMMAR sal_uInt8(2)
113 #define TYPE_HYPH sal_uInt8(3)
114 #define TYPE_THES sal_uInt8(4)
115
116 class ModuleUserData_Impl
117 {
118 bool bParent;
119 bool bIsChecked;
120 sal_uInt8 nType;
121 sal_uInt8 nIndex;
122 OUString sImplName;
123
124 public:
ModuleUserData_Impl(const OUString & sImpName,bool bIsParent,bool bChecked,sal_uInt8 nSetType,sal_uInt8 nSetIndex)125 ModuleUserData_Impl( const OUString& sImpName, bool bIsParent, bool bChecked, sal_uInt8 nSetType, sal_uInt8 nSetIndex ) :
126 bParent(bIsParent),
127 bIsChecked(bChecked),
128 nType(nSetType),
129 nIndex(nSetIndex),
130 sImplName(sImpName)
131 {
132 }
IsParent() const133 bool IsParent() const {return bParent;}
GetType() const134 sal_uInt8 GetType() const {return nType;}
IsChecked() const135 bool IsChecked() const {return bIsChecked;}
GetIndex() const136 sal_uInt8 GetIndex() const {return nIndex;}
GetImplName() const137 const OUString& GetImplName() const {return sImplName;}
138
139 };
140
141
142 // User for user-dictionaries (XDictionary interface)
143
144 class DicUserData
145 {
146 sal_uInt32 nVal;
147
148 public:
DicUserData(sal_uInt32 nUserData)149 explicit DicUserData(sal_uInt32 nUserData) : nVal( nUserData ) {}
150 DicUserData( sal_uInt16 nEID,
151 bool bChecked, bool bEditable, bool bDeletable );
152
GetUserData() const153 sal_uInt32 GetUserData() const { return nVal; }
GetEntryId() const154 sal_uInt16 GetEntryId() const { return static_cast<sal_uInt16>(nVal >> 16); }
IsChecked() const155 bool IsChecked() const { return static_cast<bool>((nVal >> 8) & 0x01); }
IsDeletable() const156 bool IsDeletable() const { return static_cast<bool>((nVal >> 10) & 0x01); }
157 };
158
159
DicUserData(sal_uInt16 nEID,bool bChecked,bool bEditable,bool bDeletable)160 DicUserData::DicUserData(
161 sal_uInt16 nEID,
162 bool bChecked, bool bEditable, bool bDeletable )
163 {
164 DBG_ASSERT( nEID < 65000, "Entry Id out of range" );
165 nVal = (static_cast<sal_uInt32>(0xFFFF & nEID) << 16) |
166 (static_cast<sal_uInt32>(bChecked ? 1 : 0) << 8) |
167 (static_cast<sal_uInt32>(bEditable ? 1 : 0) << 9) |
168 (static_cast<sal_uInt32>(bDeletable ? 1 : 0) << 10);
169 }
170
171 /*--------------------------------------------------
172 Entry IDs for options listbox of dialog
173 --------------------------------------------------*/
174
175 enum EID_OPTIONS
176 {
177 EID_SPELL_AUTO,
178 EID_GRAMMAR_AUTO,
179 EID_CAPITAL_WORDS,
180 EID_WORDS_WITH_DIGITS,
181 EID_SPELL_SPECIAL,
182 EID_NUM_MIN_WORDLEN,
183 EID_NUM_PRE_BREAK,
184 EID_NUM_POST_BREAK,
185 EID_HYPH_AUTO,
186 EID_HYPH_SPECIAL
187 };
188
189 //! this array must have an entry for every value of EID_OPTIONS.
190 // It is used to get the respective property name.
191 static const char * aEidToPropName[] =
192 {
193 UPN_IS_SPELL_AUTO, // EID_SPELL_AUTO
194 UPN_IS_GRAMMAR_AUTO, // EID_GRAMMAR_AUTO
195 UPN_IS_SPELL_UPPER_CASE, // EID_CAPITAL_WORDS
196 UPN_IS_SPELL_WITH_DIGITS, // EID_WORDS_WITH_DIGITS
197 UPN_IS_SPELL_SPECIAL, // EID_SPELL_SPECIAL
198 UPN_HYPH_MIN_WORD_LENGTH, // EID_NUM_MIN_WORDLEN,
199 UPN_HYPH_MIN_LEADING, // EID_NUM_PRE_BREAK
200 UPN_HYPH_MIN_TRAILING, // EID_NUM_POST_BREAK
201 UPN_IS_HYPH_AUTO, // EID_HYPH_AUTO
202 UPN_IS_HYPH_SPECIAL // EID_HYPH_SPECIAL
203 };
204
lcl_GetPropertyName(EID_OPTIONS eEntryId)205 static OUString lcl_GetPropertyName( EID_OPTIONS eEntryId )
206 {
207 DBG_ASSERT( static_cast<unsigned int>(eEntryId) < SAL_N_ELEMENTS(aEidToPropName), "index out of range" );
208 return OUString::createFromAscii( aEidToPropName[ static_cast<int>(eEntryId) ] );
209 }
210
211 class OptionsBreakSet : public weld::GenericDialogController
212 {
213 std::unique_ptr<weld::Widget> m_xBeforeFrame;
214 std::unique_ptr<weld::Widget> m_xAfterFrame;
215 std::unique_ptr<weld::Widget> m_xMinimalFrame;
216 std::unique_ptr<weld::SpinButton> m_xBreakNF;
217
218 public:
OptionsBreakSet(weld::Window * pParent,sal_uInt16 nRID)219 OptionsBreakSet(weld::Window* pParent, sal_uInt16 nRID)
220 : GenericDialogController(pParent, "cui/ui/breaknumberoption.ui", "BreakNumberOption")
221 , m_xBeforeFrame(m_xBuilder->weld_widget("beforeframe"))
222 , m_xAfterFrame(m_xBuilder->weld_widget("afterframe"))
223 , m_xMinimalFrame(m_xBuilder->weld_widget("miniframe"))
224 {
225 assert(EID_NUM_PRE_BREAK == nRID || EID_NUM_POST_BREAK == nRID || EID_NUM_MIN_WORDLEN == nRID); //unexpected ID
226
227 if (nRID == EID_NUM_PRE_BREAK)
228 {
229 m_xBeforeFrame->show();
230 m_xBreakNF = m_xBuilder->weld_spin_button("beforebreak");
231 }
232 else if(nRID == EID_NUM_POST_BREAK)
233 {
234 m_xAfterFrame->show();
235 m_xBreakNF = m_xBuilder->weld_spin_button("afterbreak");
236 }
237 else if(nRID == EID_NUM_MIN_WORDLEN)
238 {
239 m_xMinimalFrame->show();
240 m_xBreakNF = m_xBuilder->weld_spin_button("wordlength");
241 }
242 }
243
GetNumericFld()244 weld::SpinButton& GetNumericFld()
245 {
246 return *m_xBreakNF;
247 }
248 };
249
250 // class OptionsUserData -------------------------------------------------
251
252 class OptionsUserData
253 {
254 sal_uInt32 nVal;
255
256 public:
OptionsUserData(sal_uInt32 nUserData)257 explicit OptionsUserData( sal_uInt32 nUserData ) : nVal( nUserData ) {}
258 OptionsUserData( sal_uInt16 nEID,
259 bool bHasNV, sal_uInt16 nNumVal,
260 bool bCheckable, bool bChecked );
261
GetUserData() const262 sal_uInt32 GetUserData() const { return nVal; }
GetEntryId() const263 sal_uInt16 GetEntryId() const { return static_cast<sal_uInt16>(nVal >> 16); }
HasNumericValue() const264 bool HasNumericValue() const { return static_cast<bool>((nVal >> 10) & 0x01); }
GetNumericValue() const265 sal_uInt16 GetNumericValue() const { return static_cast<sal_uInt16>(nVal & 0xFF); }
IsCheckable() const266 bool IsCheckable() const { return static_cast<bool>((nVal >> 9) & 0x01); }
IsModified() const267 bool IsModified() const { return static_cast<bool>((nVal >> 11) & 0x01); }
268
269 void SetNumericValue( sal_uInt8 nNumVal );
270 };
271
OptionsUserData(sal_uInt16 nEID,bool bHasNV,sal_uInt16 nNumVal,bool bCheckable,bool bChecked)272 OptionsUserData::OptionsUserData( sal_uInt16 nEID,
273 bool bHasNV, sal_uInt16 nNumVal,
274 bool bCheckable, bool bChecked )
275 {
276 DBG_ASSERT( nEID < 65000, "Entry Id out of range" );
277 DBG_ASSERT( nNumVal < 256, "value out of range" );
278 nVal = (static_cast<sal_uInt32>(0xFFFF & nEID) << 16) |
279 (static_cast<sal_uInt32>(bHasNV ? 1 : 0) << 10) |
280 (static_cast<sal_uInt32>(bCheckable ? 1 : 0) << 9) |
281 (static_cast<sal_uInt32>(bChecked ? 1 : 0) << 8) |
282 static_cast<sal_uInt32>(0xFF & nNumVal);
283 }
284
SetNumericValue(sal_uInt8 nNumVal)285 void OptionsUserData::SetNumericValue( sal_uInt8 nNumVal )
286 {
287 if (HasNumericValue() && (GetNumericValue() != nNumVal))
288 {
289 nVal &= 0xffffff00;
290 nVal |= nNumVal;
291 nVal |= sal_uInt32(1) << 11; // mark as modified
292 }
293 }
294
295 // ServiceInfo_Impl ----------------------------------------------------
296
297 struct ServiceInfo_Impl
298 {
299 OUString sDisplayName;
300 OUString sSpellImplName;
301 OUString sHyphImplName;
302 OUString sThesImplName;
303 OUString sGrammarImplName;
304 uno::Reference< XSpellChecker > xSpell;
305 uno::Reference< XHyphenator > xHyph;
306 uno::Reference< XThesaurus > xThes;
307 uno::Reference< XProofreader > xGrammar;
308 bool bConfigured;
309
ServiceInfo_ImplServiceInfo_Impl310 ServiceInfo_Impl() : bConfigured(false) {}
311 };
312
313 typedef std::vector< ServiceInfo_Impl > ServiceInfoArr;
314 typedef std::map< LanguageType, Sequence< OUString > > LangImplNameTable;
315
316
317 // SvxLinguData_Impl ----------------------------------------------------
318
319 class SvxLinguData_Impl
320 {
321 //contains services and implementation names sorted by implementation names
322 ServiceInfoArr aDisplayServiceArr;
323 sal_uInt32 nDisplayServices;
324
325 Sequence< Locale > aAllServiceLocales;
326 LangImplNameTable aCfgSpellTable;
327 LangImplNameTable aCfgHyphTable;
328 LangImplNameTable aCfgThesTable;
329 LangImplNameTable aCfgGrammarTable;
330 uno::Reference< XLinguServiceManager2 > xLinguSrvcMgr;
331
332
333 static bool AddRemove( Sequence< OUString > &rConfigured,
334 const OUString &rImplName, bool bAdd );
335
336 public:
337 SvxLinguData_Impl();
338
GetManager()339 uno::Reference<XLinguServiceManager2> & GetManager() { return xLinguSrvcMgr; }
340
341 void SetChecked( const Sequence< OUString > &rConfiguredServices );
342 void Reconfigure( const OUString &rDisplayName, bool bEnable );
343
GetAllSupportedLocales() const344 const Sequence<Locale> & GetAllSupportedLocales() const { return aAllServiceLocales; }
345
GetSpellTable()346 LangImplNameTable & GetSpellTable() { return aCfgSpellTable; }
GetHyphTable()347 LangImplNameTable & GetHyphTable() { return aCfgHyphTable; }
GetThesTable()348 LangImplNameTable & GetThesTable() { return aCfgThesTable; }
GetGrammarTable()349 LangImplNameTable & GetGrammarTable() { return aCfgGrammarTable; }
350
GetDisplayServiceArray()351 ServiceInfoArr & GetDisplayServiceArray() { return aDisplayServiceArr; }
352
GetDisplayServiceCount() const353 const sal_uInt32 & GetDisplayServiceCount() const { return nDisplayServices; }
SetDisplayServiceCount(sal_uInt32 nVal)354 void SetDisplayServiceCount( sal_uInt32 nVal ) { nDisplayServices = nVal; }
355
356 // returns the list of service implementation names for the specified
357 // language and service (TYPE_SPELL, TYPE_HYPH, TYPE_THES) sorted in
358 // the proper order for the SvxEditModulesDlg (the ones from the
359 // configuration (keeping that order!) first and then the other ones.
360 // I.e. the ones available but not configured in arbitrary order).
361 // They available ones may contain names that do not(!) support that
362 // language.
363 Sequence< OUString > GetSortedImplNames( LanguageType nLang, sal_uInt8 nType );
364
365 ServiceInfo_Impl * GetInfoByImplName( const OUString &rSvcImplName );
366 };
367
368
lcl_SeqGetIndex(const Sequence<OUString> & rSeq,const OUString & rTxt)369 static sal_Int32 lcl_SeqGetIndex( const Sequence< OUString > &rSeq, const OUString &rTxt )
370 {
371 sal_Int32 nRes = -1;
372 sal_Int32 nLen = rSeq.getLength();
373 const OUString *pString = rSeq.getConstArray();
374 for (sal_Int32 i = 0; i < nLen && nRes == -1; ++i)
375 {
376 if (pString[i] == rTxt)
377 nRes = i;
378 }
379 return nRes;
380 }
381
382
GetSortedImplNames(LanguageType nLang,sal_uInt8 nType)383 Sequence< OUString > SvxLinguData_Impl::GetSortedImplNames( LanguageType nLang, sal_uInt8 nType )
384 {
385 LangImplNameTable *pTable = nullptr;
386 switch (nType)
387 {
388 case TYPE_SPELL : pTable = &aCfgSpellTable; break;
389 case TYPE_HYPH : pTable = &aCfgHyphTable; break;
390 case TYPE_THES : pTable = &aCfgThesTable; break;
391 case TYPE_GRAMMAR : pTable = &aCfgGrammarTable; break;
392 }
393 Sequence< OUString > aRes;
394 if (!pTable)
395 {
396 SAL_WARN( "cui.options", "unknown linguistic type" );
397 return aRes;
398 }
399 if (pTable->count( nLang ))
400 aRes = (*pTable)[ nLang ]; // add configured services
401 sal_Int32 nIdx = aRes.getLength();
402 DBG_ASSERT( static_cast<sal_Int32>(nDisplayServices) >= nIdx, "size mismatch" );
403 aRes.realloc( nDisplayServices );
404 OUString *pRes = aRes.getArray();
405
406 // add not configured services
407 for (sal_Int32 i = 0; i < static_cast<sal_Int32>(nDisplayServices); ++i)
408 {
409 const ServiceInfo_Impl &rInfo = aDisplayServiceArr[ i ];
410 OUString aImplName;
411 switch (nType)
412 {
413 case TYPE_SPELL : aImplName = rInfo.sSpellImplName; break;
414 case TYPE_HYPH : aImplName = rInfo.sHyphImplName; break;
415 case TYPE_THES : aImplName = rInfo.sThesImplName; break;
416 case TYPE_GRAMMAR : aImplName = rInfo.sGrammarImplName; break;
417 }
418
419 if (!aImplName.isEmpty() && (lcl_SeqGetIndex( aRes, aImplName) == -1)) // name not yet added
420 {
421 DBG_ASSERT( nIdx < aRes.getLength(), "index out of range" );
422 if (nIdx < aRes.getLength())
423 pRes[ nIdx++ ] = aImplName;
424 }
425 }
426 // don't forget to put aRes back to its actual size just in case you allocated too much
427 // since all of the names may have already been added
428 // otherwise you get duplicate entries in the edit dialog
429 aRes.realloc( nIdx );
430 return aRes;
431 }
432
433
GetInfoByImplName(const OUString & rSvcImplName)434 ServiceInfo_Impl * SvxLinguData_Impl::GetInfoByImplName( const OUString &rSvcImplName )
435 {
436 for (sal_uInt32 i = 0; i < nDisplayServices; ++i)
437 {
438 ServiceInfo_Impl &rTmp = aDisplayServiceArr[ i ];
439 if (rTmp.sSpellImplName == rSvcImplName ||
440 rTmp.sHyphImplName == rSvcImplName ||
441 rTmp.sThesImplName == rSvcImplName ||
442 rTmp.sGrammarImplName == rSvcImplName)
443 {
444 return &rTmp;
445 }
446 }
447 return nullptr;
448 }
449
450
lcl_MergeLocales(Sequence<Locale> & aAllLocales,const Sequence<Locale> & rAdd)451 static void lcl_MergeLocales(Sequence< Locale >& aAllLocales, const Sequence< Locale >& rAdd)
452 {
453 const Locale* pAdd = rAdd.getConstArray();
454 Sequence<Locale> aLocToAdd(rAdd.getLength());
455 const Locale* pAllLocales = aAllLocales.getConstArray();
456 Locale* pLocToAdd = aLocToAdd.getArray();
457 sal_Int32 nFound = 0;
458 sal_Int32 i;
459 for(i = 0; i < rAdd.getLength(); i++)
460 {
461 bool bFound = false;
462 for(sal_Int32 j = 0; j < aAllLocales.getLength() && !bFound; j++)
463 {
464 bFound = pAdd[i].Language == pAllLocales[j].Language &&
465 pAdd[i].Country == pAllLocales[j].Country &&
466 pAdd[i].Variant == pAllLocales[j].Variant;
467 }
468 if(!bFound)
469 {
470 pLocToAdd[nFound++] = pAdd[i];
471 }
472 }
473 sal_Int32 nLength = aAllLocales.getLength();
474 aAllLocales.realloc( nLength + nFound);
475 Locale* pAllLocales2 = aAllLocales.getArray();
476 for(i = 0; i < nFound; i++)
477 pAllLocales2[nLength++] = pLocToAdd[i];
478 }
479
lcl_MergeDisplayArray(SvxLinguData_Impl & rData,const ServiceInfo_Impl & rToAdd)480 static void lcl_MergeDisplayArray(
481 SvxLinguData_Impl &rData,
482 const ServiceInfo_Impl &rToAdd )
483 {
484 sal_uInt32 nCnt = 0;
485
486 ServiceInfoArr &rSvcInfoArr = rData.GetDisplayServiceArray();
487 sal_uInt32 nEntries = rData.GetDisplayServiceCount();
488
489 for (sal_uInt32 i = 0; i < nEntries; ++i)
490 {
491 ServiceInfo_Impl& rEntry = rSvcInfoArr[i];
492 if (rEntry.sDisplayName == rToAdd.sDisplayName)
493 {
494 if(rToAdd.xSpell.is())
495 {
496 DBG_ASSERT( !rEntry.xSpell.is() &&
497 rEntry.sSpellImplName.isEmpty(),
498 "merge conflict" );
499 rEntry.sSpellImplName = rToAdd.sSpellImplName;
500 rEntry.xSpell = rToAdd.xSpell;
501 }
502 if(rToAdd.xGrammar.is())
503 {
504 DBG_ASSERT( !rEntry.xGrammar.is() &&
505 rEntry.sGrammarImplName.isEmpty(),
506 "merge conflict" );
507 rEntry.sGrammarImplName = rToAdd.sGrammarImplName;
508 rEntry.xGrammar = rToAdd.xGrammar;
509 }
510 if(rToAdd.xHyph.is())
511 {
512 DBG_ASSERT( !rEntry.xHyph.is() &&
513 rEntry.sHyphImplName.isEmpty(),
514 "merge conflict" );
515 rEntry.sHyphImplName = rToAdd.sHyphImplName;
516 rEntry.xHyph = rToAdd.xHyph;
517 }
518 if(rToAdd.xThes.is())
519 {
520 DBG_ASSERT( !rEntry.xThes.is() &&
521 rEntry.sThesImplName.isEmpty(),
522 "merge conflict" );
523 rEntry.sThesImplName = rToAdd.sThesImplName;
524 rEntry.xThes = rToAdd.xThes;
525 }
526 return ;
527 }
528 ++nCnt;
529 }
530 rData.GetDisplayServiceArray().push_back( rToAdd );
531 rData.SetDisplayServiceCount( nCnt + 1 );
532 }
533
SvxLinguData_Impl()534 SvxLinguData_Impl::SvxLinguData_Impl() :
535 nDisplayServices (0)
536 {
537 uno::Reference< XComponentContext > xContext = ::comphelper::getProcessComponentContext();
538 xLinguSrvcMgr = LinguServiceManager::create(xContext);
539
540 const Locale& rCurrentLocale = Application::GetSettings().GetLanguageTag().getLocale();
541 Sequence<Any> aArgs(2);//second arguments has to be empty!
542 aArgs.getArray()[0] <<= LinguMgr::GetLinguPropertySet();
543
544 //read spell checker
545 Sequence< OUString > aSpellNames = xLinguSrvcMgr->getAvailableServices(
546 cSpell, Locale() );
547 const OUString* pSpellNames = aSpellNames.getConstArray();
548
549 sal_Int32 nIdx;
550 for(nIdx = 0; nIdx < aSpellNames.getLength(); nIdx++)
551 {
552 ServiceInfo_Impl aInfo;
553 aInfo.sSpellImplName = pSpellNames[nIdx];
554 aInfo.xSpell.set(
555 xContext->getServiceManager()->createInstanceWithArgumentsAndContext(aInfo.sSpellImplName, aArgs, xContext), UNO_QUERY);
556
557 uno::Reference<XServiceDisplayName> xDispName(aInfo.xSpell, UNO_QUERY);
558 if(xDispName.is())
559 aInfo.sDisplayName = xDispName->getServiceDisplayName( rCurrentLocale );
560
561 const Sequence< Locale > aLocales( aInfo.xSpell->getLocales() );
562 //! suppress display of entries with no supported languages (see feature 110994)
563 if (aLocales.hasElements())
564 {
565 lcl_MergeLocales( aAllServiceLocales, aLocales );
566 lcl_MergeDisplayArray( *this, aInfo );
567 }
568 }
569
570 //read grammar checker
571 Sequence< OUString > aGrammarNames = xLinguSrvcMgr->getAvailableServices(
572 cGrammar, Locale() );
573 const OUString* pGrammarNames = aGrammarNames.getConstArray();
574 for(nIdx = 0; nIdx < aGrammarNames.getLength(); nIdx++)
575 {
576 ServiceInfo_Impl aInfo;
577 aInfo.sGrammarImplName = pGrammarNames[nIdx];
578 aInfo.xGrammar.set(
579 xContext->getServiceManager()->createInstanceWithArgumentsAndContext(aInfo.sGrammarImplName, aArgs, xContext), UNO_QUERY);
580
581 uno::Reference<XServiceDisplayName> xDispName(aInfo.xGrammar, UNO_QUERY);
582 if(xDispName.is())
583 aInfo.sDisplayName = xDispName->getServiceDisplayName( rCurrentLocale );
584
585 const Sequence< Locale > aLocales( aInfo.xGrammar->getLocales() );
586 //! suppress display of entries with no supported languages (see feature 110994)
587 if (aLocales.hasElements())
588 {
589 lcl_MergeLocales( aAllServiceLocales, aLocales );
590 lcl_MergeDisplayArray( *this, aInfo );
591 }
592 }
593
594 //read hyphenator
595 Sequence< OUString > aHyphNames = xLinguSrvcMgr->getAvailableServices(
596 cHyph, Locale() );
597 const OUString* pHyphNames = aHyphNames.getConstArray();
598 for(nIdx = 0; nIdx < aHyphNames.getLength(); nIdx++)
599 {
600 ServiceInfo_Impl aInfo;
601 aInfo.sHyphImplName = pHyphNames[nIdx];
602 aInfo.xHyph.set( xContext->getServiceManager()->createInstanceWithArgumentsAndContext(aInfo.sHyphImplName, aArgs, xContext), UNO_QUERY);
603
604 uno::Reference<XServiceDisplayName> xDispName(aInfo.xHyph, UNO_QUERY);
605 if(xDispName.is())
606 aInfo.sDisplayName = xDispName->getServiceDisplayName( rCurrentLocale );
607
608 const Sequence< Locale > aLocales( aInfo.xHyph->getLocales() );
609 //! suppress display of entries with no supported languages (see feature 110994)
610 if (aLocales.hasElements())
611 {
612 lcl_MergeLocales( aAllServiceLocales, aLocales );
613 lcl_MergeDisplayArray( *this, aInfo );
614 }
615 }
616
617 //read thesauri
618 Sequence< OUString > aThesNames = xLinguSrvcMgr->getAvailableServices(
619 cThes, Locale() );
620 const OUString* pThesNames = aThesNames.getConstArray();
621 for(nIdx = 0; nIdx < aThesNames.getLength(); nIdx++)
622 {
623 ServiceInfo_Impl aInfo;
624 aInfo.sThesImplName = pThesNames[nIdx];
625 aInfo.xThes.set( xContext->getServiceManager()->createInstanceWithArgumentsAndContext(aInfo.sThesImplName, aArgs, xContext), UNO_QUERY);
626
627 uno::Reference<XServiceDisplayName> xDispName(aInfo.xThes, UNO_QUERY);
628 if(xDispName.is())
629 aInfo.sDisplayName = xDispName->getServiceDisplayName( rCurrentLocale );
630
631 const Sequence< Locale > aLocales( aInfo.xThes->getLocales() );
632 //! suppress display of entries with no supported languages (see feature 110994)
633 if (aLocales.hasElements())
634 {
635 lcl_MergeLocales( aAllServiceLocales, aLocales );
636 lcl_MergeDisplayArray( *this, aInfo );
637 }
638 }
639
640 Sequence< OUString > aCfgSvcs;
641 const Locale* pAllLocales = aAllServiceLocales.getConstArray();
642 for(sal_Int32 nLocale = 0; nLocale < aAllServiceLocales.getLength(); nLocale++)
643 {
644 LanguageType nLang = LanguageTag::convertToLanguageType( pAllLocales[nLocale] );
645
646 aCfgSvcs = xLinguSrvcMgr->getConfiguredServices(cSpell, pAllLocales[nLocale]);
647 SetChecked( aCfgSvcs );
648 if (aCfgSvcs.hasElements())
649 aCfgSpellTable[ nLang ] = aCfgSvcs;
650
651 aCfgSvcs = xLinguSrvcMgr->getConfiguredServices(cGrammar, pAllLocales[nLocale]);
652 SetChecked( aCfgSvcs );
653 if (aCfgSvcs.hasElements())
654 aCfgGrammarTable[ nLang ] = aCfgSvcs;
655
656 aCfgSvcs = xLinguSrvcMgr->getConfiguredServices(cHyph, pAllLocales[nLocale]);
657 SetChecked( aCfgSvcs );
658 if (aCfgSvcs.hasElements())
659 aCfgHyphTable[ nLang ] = aCfgSvcs;
660
661 aCfgSvcs = xLinguSrvcMgr->getConfiguredServices(cThes, pAllLocales[nLocale]);
662 SetChecked( aCfgSvcs );
663 if (aCfgSvcs.hasElements())
664 aCfgThesTable[ nLang ] = aCfgSvcs;
665 }
666 }
667
SetChecked(const Sequence<OUString> & rConfiguredServices)668 void SvxLinguData_Impl::SetChecked(const Sequence<OUString>& rConfiguredServices)
669 {
670 const OUString* pConfiguredServices = rConfiguredServices.getConstArray();
671 for(sal_Int32 n = 0; n < rConfiguredServices.getLength(); n++)
672 {
673 for (sal_uInt32 i = 0; i < nDisplayServices; ++i)
674 {
675 ServiceInfo_Impl& rEntry = aDisplayServiceArr[i];
676 if (!rEntry.bConfigured)
677 {
678 const OUString &rSrvcImplName = pConfiguredServices[n];
679 if (!rSrvcImplName.isEmpty() &&
680 (rEntry.sSpellImplName == rSrvcImplName ||
681 rEntry.sGrammarImplName == rSrvcImplName ||
682 rEntry.sHyphImplName == rSrvcImplName ||
683 rEntry.sThesImplName == rSrvcImplName))
684 {
685 rEntry.bConfigured = true;
686 break;
687 }
688 }
689 }
690 }
691 }
692
AddRemove(Sequence<OUString> & rConfigured,const OUString & rImplName,bool bAdd)693 bool SvxLinguData_Impl::AddRemove(
694 Sequence< OUString > &rConfigured,
695 const OUString &rImplName, bool bAdd )
696 {
697 bool bRet = false; // modified?
698
699 sal_Int32 nEntries = rConfigured.getLength();
700 sal_Int32 nPos = lcl_SeqGetEntryPos(rConfigured, rImplName);
701 if (bAdd && nPos < 0) // add new entry
702 {
703 rConfigured.realloc( ++nEntries );
704 OUString *pConfigured = rConfigured.getArray();
705 pConfigured[nEntries - 1] = rImplName;
706 bRet = true;
707 }
708 else if (!bAdd && nPos >= 0) // remove existing entry
709 {
710 OUString *pConfigured = rConfigured.getArray();
711 for (sal_Int32 i = nPos; i < nEntries - 1; ++i)
712 pConfigured[i] = pConfigured[i + 1];
713 rConfigured.realloc(--nEntries);
714 bRet = true;
715 }
716
717 return bRet;
718 }
719
720
Reconfigure(const OUString & rDisplayName,bool bEnable)721 void SvxLinguData_Impl::Reconfigure( const OUString &rDisplayName, bool bEnable )
722 {
723 DBG_ASSERT( !rDisplayName.isEmpty(), "empty DisplayName" );
724
725 ServiceInfo_Impl *pInfo = nullptr;
726 for (sal_uInt32 i = 0; i < nDisplayServices; ++i)
727 {
728 ServiceInfo_Impl& rTmp = aDisplayServiceArr[i];
729 if (rTmp.sDisplayName == rDisplayName)
730 {
731 pInfo = &rTmp;
732 break;
733 }
734 }
735 DBG_ASSERT( pInfo, "DisplayName entry not found" );
736 if (pInfo)
737 {
738 pInfo->bConfigured = bEnable;
739
740 Sequence< Locale > aLocales;
741 const Locale *pLocale = nullptr;
742 sal_Int32 nLocales = 0;
743 sal_Int32 i;
744
745 // update configured spellchecker entries
746 if (pInfo->xSpell.is())
747 {
748 aLocales = pInfo->xSpell->getLocales();
749 pLocale = aLocales.getConstArray();
750 nLocales = aLocales.getLength();
751 for (i = 0; i < nLocales; ++i)
752 {
753 LanguageType nLang = LanguageTag::convertToLanguageType( pLocale[i] );
754 if (!aCfgSpellTable.count( nLang ) && bEnable)
755 aCfgSpellTable[ nLang ] = Sequence< OUString >();
756 if (aCfgSpellTable.count( nLang ))
757 AddRemove( aCfgSpellTable[ nLang ], pInfo->sSpellImplName, bEnable );
758 }
759 }
760
761 // update configured grammar checker entries
762 if (pInfo->xGrammar.is())
763 {
764 aLocales = pInfo->xGrammar->getLocales();
765 pLocale = aLocales.getConstArray();
766 nLocales = aLocales.getLength();
767 for (i = 0; i < nLocales; ++i)
768 {
769 LanguageType nLang = LanguageTag::convertToLanguageType( pLocale[i] );
770 if (!aCfgGrammarTable.count( nLang ) && bEnable)
771 aCfgGrammarTable[ nLang ] = Sequence< OUString >();
772 if (aCfgGrammarTable.count( nLang ))
773 AddRemove( aCfgGrammarTable[ nLang ], pInfo->sGrammarImplName, bEnable );
774 }
775 }
776
777 // update configured hyphenator entries
778 if (pInfo->xHyph.is())
779 {
780 aLocales = pInfo->xHyph->getLocales();
781 pLocale = aLocales.getConstArray();
782 nLocales = aLocales.getLength();
783 for (i = 0; i < nLocales; ++i)
784 {
785 LanguageType nLang = LanguageTag::convertToLanguageType( pLocale[i] );
786 if (!aCfgHyphTable.count( nLang ) && bEnable)
787 aCfgHyphTable[ nLang ] = Sequence< OUString >();
788 if (aCfgHyphTable.count( nLang ))
789 AddRemove( aCfgHyphTable[ nLang ], pInfo->sHyphImplName, bEnable );
790 }
791 }
792
793 // update configured spellchecker entries
794 if (pInfo->xThes.is())
795 {
796 aLocales = pInfo->xThes->getLocales();
797 pLocale = aLocales.getConstArray();
798 nLocales = aLocales.getLength();
799 for (i = 0; i < nLocales; ++i)
800 {
801 LanguageType nLang = LanguageTag::convertToLanguageType( pLocale[i] );
802 if (!aCfgThesTable.count( nLang ) && bEnable)
803 aCfgThesTable[ nLang ] = Sequence< OUString >();
804 if (aCfgThesTable.count( nLang ))
805 AddRemove( aCfgThesTable[ nLang ], pInfo->sThesImplName, bEnable );
806 }
807 }
808 }
809 }
810
811
812 // class SvxLinguTabPage -------------------------------------------------
813
SvxLinguTabPage(weld::Container * pPage,weld::DialogController * pController,const SfxItemSet & rSet)814 SvxLinguTabPage::SvxLinguTabPage(weld::Container* pPage, weld::DialogController* pController, const SfxItemSet& rSet)
815 : SfxTabPage(pPage, pController, "cui/ui/optlingupage.ui", "OptLinguPage", &rSet)
816 , sCapitalWords (CuiResId(RID_SVXSTR_CAPITAL_WORDS))
817 , sWordsWithDigits(CuiResId(RID_SVXSTR_WORDS_WITH_DIGITS))
818 , sSpellSpecial (CuiResId(RID_SVXSTR_SPELL_SPECIAL))
819 , sSpellAuto (CuiResId(RID_SVXSTR_SPELL_AUTO))
820 , sGrammarAuto (CuiResId(RID_SVXSTR_GRAMMAR_AUTO))
821 , sNumMinWordlen (CuiResId(RID_SVXSTR_NUM_MIN_WORDLEN))
822 , sNumPreBreak (CuiResId(RID_SVXSTR_NUM_PRE_BREAK))
823 , sNumPostBreak (CuiResId(RID_SVXSTR_NUM_POST_BREAK))
824 , sHyphAuto (CuiResId(RID_SVXSTR_HYPH_AUTO))
825 , sHyphSpecial (CuiResId(RID_SVXSTR_HYPH_SPECIAL))
826 , nUPN_HYPH_MIN_WORD_LENGTH(-1)
827 , nUPN_HYPH_MIN_LEADING(-1)
828 , nUPN_HYPH_MIN_TRAILING(-1)
829 , m_nDlbClickEventId(nullptr)
830 , m_xLinguModulesFT(m_xBuilder->weld_label("lingumodulesft"))
831 , m_xLinguModulesCLB(m_xBuilder->weld_tree_view("lingumodules"))
832 , m_xLinguModulesEditPB(m_xBuilder->weld_button("lingumodulesedit"))
833 , m_xLinguDicsFT(m_xBuilder->weld_label("lingudictsft"))
834 , m_xLinguDicsCLB(m_xBuilder->weld_tree_view("lingudicts"))
835 , m_xLinguDicsNewPB(m_xBuilder->weld_button("lingudictsnew"))
836 , m_xLinguDicsEditPB(m_xBuilder->weld_button("lingudictsedit"))
837 , m_xLinguDicsDelPB(m_xBuilder->weld_button("lingudictsdelete"))
838 , m_xLinguOptionsCLB(m_xBuilder->weld_tree_view("linguoptions"))
839 , m_xLinguOptionsEditPB(m_xBuilder->weld_button("linguoptionsedit"))
840 , m_xMoreDictsLink(m_xBuilder->weld_link_button("moredictslink"))
841 {
842 std::vector<int> aWidths;
843 aWidths.push_back(m_xLinguModulesCLB->get_checkbox_column_width());
844
845 m_xLinguModulesCLB->set_column_fixed_widths(aWidths);
846 m_xLinguDicsCLB->set_column_fixed_widths(aWidths);
847 m_xLinguOptionsCLB->set_column_fixed_widths(aWidths);
848
849 m_xLinguModulesCLB->connect_changed( LINK( this, SvxLinguTabPage, SelectHdl_Impl ));
850 m_xLinguModulesCLB->connect_row_activated(LINK(this, SvxLinguTabPage, BoxDoubleClickHdl_Impl));
851 m_xLinguModulesCLB->connect_toggled(LINK(this, SvxLinguTabPage, ModulesBoxCheckButtonHdl_Impl));
852
853 m_xLinguModulesEditPB->connect_clicked( LINK( this, SvxLinguTabPage, ClickHdl_Impl ));
854 m_xLinguOptionsEditPB->connect_clicked( LINK( this, SvxLinguTabPage, ClickHdl_Impl ));
855
856 m_xLinguDicsCLB->connect_changed( LINK( this, SvxLinguTabPage, SelectHdl_Impl ));
857 m_xLinguDicsCLB->connect_toggled(LINK(this, SvxLinguTabPage, DicsBoxCheckButtonHdl_Impl));
858
859 m_xLinguDicsNewPB->connect_clicked( LINK( this, SvxLinguTabPage, ClickHdl_Impl ));
860 m_xLinguDicsEditPB->connect_clicked( LINK( this, SvxLinguTabPage, ClickHdl_Impl ));
861 m_xLinguDicsDelPB->connect_clicked( LINK( this, SvxLinguTabPage, ClickHdl_Impl ));
862
863 m_xLinguOptionsCLB->connect_changed( LINK( this, SvxLinguTabPage, SelectHdl_Impl ));
864 m_xLinguOptionsCLB->connect_row_activated(LINK(this, SvxLinguTabPage, BoxDoubleClickHdl_Impl));
865
866 if ( SvtExtendedSecurityOptions().GetOpenHyperlinkMode() == SvtExtendedSecurityOptions::OPEN_NEVER )
867 m_xMoreDictsLink->hide();
868
869 xProp = LinguMgr::GetLinguPropertySet();
870 xDicList.set( LinguMgr::GetDictionaryList(), UNO_QUERY );
871 if (xDicList.is())
872 {
873 // keep references to all **currently** available dictionaries,
874 // since the diclist may get changed meanwhile (e.g. through the API).
875 // We want the dialog to operate on the same set of dictionaries it
876 // was started with.
877 // Also we have to take care to not lose the last reference when
878 // someone else removes a dictionary from the list.
879 // removed dics will be replaced by NULL new entries be added to the end
880 // Thus we may use indices as consistent references.
881 aDics = xDicList->getDictionaries();
882
883 UpdateDicBox_Impl();
884 }
885 else
886 {
887 m_xLinguDicsFT->set_sensitive(false);
888 m_xLinguDicsCLB->set_sensitive(false);
889 m_xLinguDicsNewPB->set_sensitive(false);
890 m_xLinguDicsEditPB->set_sensitive(false);
891 m_xLinguDicsDelPB->set_sensitive(false);
892 }
893 }
894
~SvxLinguTabPage()895 SvxLinguTabPage::~SvxLinguTabPage()
896 {
897 if (m_nDlbClickEventId)
898 {
899 Application::RemoveUserEvent(m_nDlbClickEventId);
900 m_nDlbClickEventId = nullptr;
901 }
902 pLinguData.reset();
903 }
904
Create(weld::Container * pPage,weld::DialogController * pController,const SfxItemSet * rAttrSet)905 std::unique_ptr<SfxTabPage> SvxLinguTabPage::Create( weld::Container* pPage, weld::DialogController* pController,
906 const SfxItemSet* rAttrSet )
907 {
908 return std::make_unique<SvxLinguTabPage>( pPage, pController, *rAttrSet );
909 }
910
FillItemSet(SfxItemSet * rCoreSet)911 bool SvxLinguTabPage::FillItemSet( SfxItemSet* rCoreSet )
912 {
913 bool bModified = true; // !!!!
914
915 // if not HideGroups was called with GROUP_MODULES...
916 if (m_xLinguModulesCLB->get_visible())
917 {
918 DBG_ASSERT( pLinguData, "pLinguData not yet initialized" );
919 if (!pLinguData)
920 pLinguData.reset( new SvxLinguData_Impl );
921
922 // update spellchecker configuration entries
923 const LangImplNameTable *pTable = &pLinguData->GetSpellTable();
924 for (auto const& elem : *pTable)
925 {
926 LanguageType nLang = elem.first;
927 const Sequence< OUString > aImplNames(elem.second);
928 uno::Reference< XLinguServiceManager2 > xMgr( pLinguData->GetManager() );
929 Locale aLocale( LanguageTag::convertToLocale(nLang) );
930 if (xMgr.is())
931 xMgr->setConfiguredServices( cSpell, aLocale, aImplNames );
932 }
933
934 // update grammar checker configuration entries
935 pTable = &pLinguData->GetGrammarTable();
936 for (auto const& elem : *pTable)
937 {
938 LanguageType nLang = elem.first;
939 const Sequence< OUString > aImplNames(elem.second);
940 uno::Reference< XLinguServiceManager2 > xMgr( pLinguData->GetManager() );
941 Locale aLocale( LanguageTag::convertToLocale(nLang) );
942 if (xMgr.is())
943 xMgr->setConfiguredServices( cGrammar, aLocale, aImplNames );
944 }
945
946 // update hyphenator configuration entries
947 pTable = &pLinguData->GetHyphTable();
948 for (auto const& elem : *pTable)
949 {
950 LanguageType nLang = elem.first;
951 const Sequence< OUString > aImplNames(elem.second);
952 uno::Reference< XLinguServiceManager2 > xMgr( pLinguData->GetManager() );
953 Locale aLocale( LanguageTag::convertToLocale(nLang) );
954 if (xMgr.is())
955 xMgr->setConfiguredServices( cHyph, aLocale, aImplNames );
956 }
957
958 // update thesaurus configuration entries
959 pTable = &pLinguData->GetThesTable();
960 for (auto const& elem : *pTable)
961 {
962 LanguageType nLang = elem.first;
963 const Sequence< OUString > aImplNames(elem.second);
964 uno::Reference< XLinguServiceManager2 > xMgr( pLinguData->GetManager() );
965 Locale aLocale( LanguageTag::convertToLocale(nLang) );
966 if (xMgr.is())
967 xMgr->setConfiguredServices( cThes, aLocale, aImplNames );
968 }
969 }
970
971
972 // activate dictionaries according to checkbox state
973
974 Sequence< OUString > aActiveDics;
975 sal_Int32 nActiveDics = 0;
976 int nEntries = m_xLinguDicsCLB->n_children();
977 for (int i = 0; i < nEntries; ++i)
978 {
979 sal_Int32 nDics = aDics.getLength();
980
981 aActiveDics.realloc( nDics );
982 OUString *pActiveDic = aActiveDics.getArray();
983
984 DicUserData aData(m_xLinguDicsCLB->get_id(i).toUInt32());
985 if (aData.GetEntryId() < nDics)
986 {
987 bool bChecked = m_xLinguDicsCLB->get_toggle(i, 0) == TRISTATE_TRUE;
988 uno::Reference< XDictionary > xDic( aDics.getConstArray()[ i ] );
989 if (xDic.is())
990 {
991 if (LinguMgr::GetIgnoreAllList() == xDic)
992 bChecked = true;
993 xDic->setActive( bChecked );
994
995 if (bChecked)
996 {
997 OUString aDicName( xDic->getName() );
998 pActiveDic[ nActiveDics++ ] = aDicName;
999 }
1000 }
1001 }
1002 }
1003
1004 aActiveDics.realloc( nActiveDics );
1005 Any aTmp;
1006 aTmp <<= aActiveDics;
1007 SvtLinguConfig aLngCfg;
1008 aLngCfg.SetProperty( UPH_ACTIVE_DICTIONARIES, aTmp );
1009
1010
1011 nEntries = m_xLinguOptionsCLB->n_children();
1012 for (int j = 0; j < nEntries; ++j)
1013 {
1014 OptionsUserData aData(m_xLinguOptionsCLB->get_id(j).toUInt32());
1015 OUString aPropName( lcl_GetPropertyName( static_cast<EID_OPTIONS>(aData.GetEntryId()) ) );
1016
1017 Any aAny;
1018 if (aData.IsCheckable())
1019 {
1020 bool bChecked = m_xLinguOptionsCLB->get_toggle(j, 0) == TRISTATE_TRUE;
1021 aAny <<= bChecked;
1022 }
1023 else if (aData.HasNumericValue())
1024 {
1025 sal_Int16 nVal = aData.GetNumericValue();
1026 aAny <<= nVal;
1027 }
1028
1029 if (xProp.is())
1030 xProp->setPropertyValue( aPropName, aAny );
1031 aLngCfg.SetProperty( aPropName, aAny );
1032 }
1033
1034 OptionsUserData aPreBreakData(m_xLinguOptionsCLB->get_id(EID_NUM_PRE_BREAK).toUInt32());
1035 OptionsUserData aPostBreakData(m_xLinguOptionsCLB->get_id(EID_NUM_POST_BREAK).toUInt32());
1036 if ( aPreBreakData.IsModified() || aPostBreakData.IsModified() )
1037 {
1038 SfxHyphenRegionItem aHyp( GetWhich( SID_ATTR_HYPHENREGION ) );
1039 aHyp.GetMinLead() = static_cast<sal_uInt8>(aPreBreakData.GetNumericValue());
1040 aHyp.GetMinTrail() = static_cast<sal_uInt8>(aPostBreakData.GetNumericValue());
1041 rCoreSet->Put( aHyp );
1042 }
1043
1044 // automatic spell checking
1045 bool bNewAutoCheck = m_xLinguOptionsCLB->get_toggle(EID_SPELL_AUTO, 0) == TRISTATE_TRUE;
1046 const SfxPoolItem* pOld = GetOldItem( *rCoreSet, SID_AUTOSPELL_CHECK );
1047 if ( !pOld || static_cast<const SfxBoolItem*>(pOld)->GetValue() != bNewAutoCheck )
1048 {
1049 rCoreSet->Put( SfxBoolItem( GetWhich( SID_AUTOSPELL_CHECK ),
1050 bNewAutoCheck ) );
1051 bModified = true;
1052 }
1053
1054 return bModified;
1055 }
1056
GetDicUserData(const uno::Reference<XDictionary> & rxDic,sal_uInt16 nIdx)1057 sal_uInt32 SvxLinguTabPage::GetDicUserData( const uno::Reference< XDictionary > &rxDic, sal_uInt16 nIdx )
1058 {
1059 sal_uInt32 nRes = 0;
1060 DBG_ASSERT( rxDic.is(), "dictionary not supplied" );
1061 if (rxDic.is())
1062 {
1063 uno::Reference< frame::XStorable > xStor( rxDic, UNO_QUERY );
1064
1065 bool bChecked = rxDic->isActive();
1066 bool bEditable = !xStor.is() || !xStor->isReadonly();
1067 bool bDeletable = bEditable;
1068
1069 nRes = DicUserData( nIdx,
1070 bChecked, bEditable, bDeletable ).GetUserData();
1071 }
1072 return nRes;
1073 }
1074
1075
AddDicBoxEntry(const uno::Reference<XDictionary> & rxDic,sal_uInt16 nIdx)1076 void SvxLinguTabPage::AddDicBoxEntry(
1077 const uno::Reference< XDictionary > &rxDic,
1078 sal_uInt16 nIdx )
1079 {
1080 m_xLinguDicsCLB->freeze();
1081
1082 OUString aTxt( ::GetDicInfoStr( rxDic->getName(),
1083 LanguageTag( rxDic->getLocale() ).getLanguageType(),
1084 DictionaryType_NEGATIVE == rxDic->getDictionaryType() ) );
1085 m_xLinguDicsCLB->append(); // append at end
1086 int nEntry = m_xLinguDicsCLB->n_children() - 1;
1087 DicUserData aData( GetDicUserData( rxDic, nIdx ) );
1088 m_xLinguDicsCLB->set_id(nEntry, OUString::number(aData.GetUserData()));
1089 m_xLinguDicsCLB->set_toggle(nEntry, aData.IsChecked() ? TRISTATE_TRUE : TRISTATE_FALSE, 0);
1090 m_xLinguDicsCLB->set_text(nEntry, aTxt, 1); // append at end
1091
1092 m_xLinguDicsCLB->thaw();
1093 }
1094
UpdateDicBox_Impl()1095 void SvxLinguTabPage::UpdateDicBox_Impl()
1096 {
1097 m_xLinguDicsCLB->freeze();
1098 m_xLinguDicsCLB->clear();
1099
1100 sal_Int32 nDics = aDics.getLength();
1101 const uno::Reference< XDictionary > *pDic = aDics.getConstArray();
1102 for (sal_Int32 i = 0; i < nDics; ++i)
1103 {
1104 const uno::Reference< XDictionary > &rDic = pDic[i];
1105 if (rDic.is())
1106 AddDicBoxEntry( rDic, static_cast<sal_uInt16>(i) );
1107 }
1108
1109 m_xLinguDicsCLB->thaw();
1110 if (m_xLinguDicsCLB->n_children())
1111 {
1112 m_xLinguDicsCLB->select(0);
1113 SelectHdl_Impl(*m_xLinguDicsCLB);
1114 }
1115 }
1116
UpdateModulesBox_Impl()1117 void SvxLinguTabPage::UpdateModulesBox_Impl()
1118 {
1119 if (pLinguData)
1120 {
1121 const ServiceInfoArr &rAllDispSrvcArr = pLinguData->GetDisplayServiceArray();
1122 const sal_uInt32 nDispSrvcCount = pLinguData->GetDisplayServiceCount();
1123
1124 m_xLinguModulesCLB->clear();
1125
1126 for (sal_uInt32 i = 0; i < nDispSrvcCount; ++i)
1127 {
1128 const ServiceInfo_Impl &rInfo = rAllDispSrvcArr[i];
1129 m_xLinguModulesCLB->append();
1130 m_xLinguModulesCLB->set_id(i, OUString::number(reinterpret_cast<sal_Int64>(&rInfo)));
1131 m_xLinguModulesCLB->set_toggle(i, rInfo.bConfigured ? TRISTATE_TRUE : TRISTATE_FALSE, 0);
1132 m_xLinguModulesCLB->set_text(i, rInfo.sDisplayName, 1);
1133 }
1134 if (nDispSrvcCount)
1135 {
1136 m_xLinguModulesCLB->select(0);
1137 SelectHdl_Impl(*m_xLinguModulesCLB);
1138 }
1139 m_xLinguModulesEditPB->set_sensitive( nDispSrvcCount > 0 );
1140 }
1141 }
1142
Reset(const SfxItemSet * rSet)1143 void SvxLinguTabPage::Reset( const SfxItemSet* rSet )
1144 {
1145 // if not HideGroups was called with GROUP_MODULES...
1146 if (m_xLinguModulesCLB->get_visible())
1147 {
1148 if (!pLinguData)
1149 pLinguData.reset( new SvxLinguData_Impl );
1150 UpdateModulesBox_Impl();
1151 }
1152
1153
1154 // get data from configuration
1155 SvtLinguConfig aLngCfg;
1156
1157 m_xLinguOptionsCLB->freeze();
1158 m_xLinguOptionsCLB->clear();
1159
1160 sal_Int16 nVal = 0;
1161 bool bVal = false;
1162 sal_uInt32 nUserData = 0;
1163
1164 m_xLinguOptionsCLB->append();
1165 int nEntry = 0;
1166
1167 aLngCfg.GetProperty( UPN_IS_SPELL_AUTO ) >>= bVal;
1168 const SfxPoolItem* pItem = GetItem( *rSet, SID_AUTOSPELL_CHECK );
1169 if (pItem)
1170 bVal = static_cast<const SfxBoolItem *>(pItem)->GetValue();
1171 nUserData = OptionsUserData( EID_SPELL_AUTO, false, 0, true, bVal).GetUserData();
1172 m_xLinguOptionsCLB->set_toggle(nEntry, bVal ? TRISTATE_TRUE : TRISTATE_FALSE, 0);
1173 m_xLinguOptionsCLB->set_text(nEntry, sSpellAuto, 1);
1174 m_xLinguOptionsCLB->set_id(nEntry, OUString::number(nUserData));
1175
1176 m_xLinguOptionsCLB->append();
1177 ++nEntry;
1178
1179 aLngCfg.GetProperty( UPN_IS_GRAMMAR_AUTO ) >>= bVal;
1180 nUserData = OptionsUserData( EID_GRAMMAR_AUTO, false, 0, true, bVal).GetUserData();
1181 m_xLinguOptionsCLB->set_toggle(nEntry, bVal ? TRISTATE_TRUE : TRISTATE_FALSE, 0);
1182 m_xLinguOptionsCLB->set_text(nEntry, sGrammarAuto, 1);
1183 m_xLinguOptionsCLB->set_id(nEntry, OUString::number(nUserData));
1184
1185 m_xLinguOptionsCLB->append();
1186 ++nEntry;
1187
1188 aLngCfg.GetProperty( UPN_IS_SPELL_UPPER_CASE ) >>= bVal;
1189 nUserData = OptionsUserData( EID_CAPITAL_WORDS, false, 0, true, bVal).GetUserData();
1190 m_xLinguOptionsCLB->set_toggle(nEntry, bVal ? TRISTATE_TRUE : TRISTATE_FALSE, 0);
1191 m_xLinguOptionsCLB->set_text(nEntry, sCapitalWords, 1);
1192 m_xLinguOptionsCLB->set_id(nEntry, OUString::number(nUserData));
1193
1194 m_xLinguOptionsCLB->append();
1195 ++nEntry;
1196
1197 aLngCfg.GetProperty( UPN_IS_SPELL_WITH_DIGITS ) >>= bVal;
1198 nUserData = OptionsUserData( EID_WORDS_WITH_DIGITS, false, 0, true, bVal).GetUserData();
1199 m_xLinguOptionsCLB->set_toggle(nEntry, bVal ? TRISTATE_TRUE : TRISTATE_FALSE, 0);
1200 m_xLinguOptionsCLB->set_text(nEntry, sWordsWithDigits, 1);
1201 m_xLinguOptionsCLB->set_id(nEntry, OUString::number(nUserData));
1202
1203 m_xLinguOptionsCLB->append();
1204 ++nEntry;
1205
1206 aLngCfg.GetProperty( UPN_IS_SPELL_SPECIAL ) >>= bVal;
1207 nUserData = OptionsUserData( EID_SPELL_SPECIAL, false, 0, true, bVal).GetUserData();
1208 m_xLinguOptionsCLB->set_toggle(nEntry, bVal ? TRISTATE_TRUE : TRISTATE_FALSE, 0);
1209 m_xLinguOptionsCLB->set_text(nEntry, sSpellSpecial, 1);
1210 m_xLinguOptionsCLB->set_id(nEntry, OUString::number(nUserData));
1211
1212 m_xLinguOptionsCLB->append();
1213 ++nEntry;
1214
1215 aLngCfg.GetProperty( UPN_HYPH_MIN_WORD_LENGTH ) >>= nVal;
1216 nUserData = OptionsUserData( EID_NUM_MIN_WORDLEN, true, static_cast<sal_uInt16>(nVal), false, false).GetUserData();
1217 m_xLinguOptionsCLB->set_text(nEntry, sNumMinWordlen + " " + OUString::number(nVal), 1);
1218 m_xLinguOptionsCLB->set_id(nEntry, OUString::number(nUserData));
1219 nUPN_HYPH_MIN_WORD_LENGTH = nEntry;
1220
1221 const SfxHyphenRegionItem *pHyp = nullptr;
1222 sal_uInt16 nWhich = GetWhich( SID_ATTR_HYPHENREGION );
1223 if ( rSet->GetItemState( nWhich, false ) == SfxItemState::SET )
1224 pHyp = &static_cast<const SfxHyphenRegionItem &>( rSet->Get( nWhich ) );
1225
1226 m_xLinguOptionsCLB->append();
1227 ++nEntry;
1228
1229 aLngCfg.GetProperty( UPN_HYPH_MIN_LEADING ) >>= nVal;
1230 if (pHyp)
1231 nVal = static_cast<sal_Int16>(pHyp->GetMinLead());
1232 nUserData = OptionsUserData( EID_NUM_PRE_BREAK, true, static_cast<sal_uInt16>(nVal), false, false).GetUserData();
1233 m_xLinguOptionsCLB->set_text(nEntry, sNumPreBreak + " " + OUString::number(nVal), 1);
1234 m_xLinguOptionsCLB->set_id(nEntry, OUString::number(nUserData));
1235 nUPN_HYPH_MIN_LEADING = nEntry;
1236
1237 m_xLinguOptionsCLB->append();
1238 ++nEntry;
1239
1240 aLngCfg.GetProperty( UPN_HYPH_MIN_TRAILING ) >>= nVal;
1241 if (pHyp)
1242 nVal = static_cast<sal_Int16>(pHyp->GetMinTrail());
1243 nUserData = OptionsUserData( EID_NUM_POST_BREAK, true, static_cast<sal_uInt16>(nVal), false, false).GetUserData();
1244 m_xLinguOptionsCLB->set_text(nEntry, sNumPostBreak + " " + OUString::number(nVal), 1);
1245 m_xLinguOptionsCLB->set_id(nEntry, OUString::number(nUserData));
1246 nUPN_HYPH_MIN_TRAILING = nEntry;
1247
1248 m_xLinguOptionsCLB->append();
1249 ++nEntry;
1250
1251 aLngCfg.GetProperty( UPN_IS_HYPH_AUTO ) >>= bVal;
1252 nUserData = OptionsUserData( EID_HYPH_AUTO, false, 0, true, bVal).GetUserData();
1253 m_xLinguOptionsCLB->set_toggle(nEntry, bVal ? TRISTATE_TRUE : TRISTATE_FALSE, 0);
1254 m_xLinguOptionsCLB->set_text(nEntry, sHyphAuto, 1);
1255 m_xLinguOptionsCLB->set_id(nEntry, OUString::number(nUserData));
1256
1257 m_xLinguOptionsCLB->append();
1258 ++nEntry;
1259
1260 aLngCfg.GetProperty( UPN_IS_HYPH_SPECIAL ) >>= bVal;
1261 nUserData = OptionsUserData( EID_HYPH_SPECIAL, false, 0, true, bVal).GetUserData();
1262 m_xLinguOptionsCLB->set_toggle(nEntry, bVal ? TRISTATE_TRUE : TRISTATE_FALSE, 0);
1263 m_xLinguOptionsCLB->set_text(nEntry, sHyphSpecial, 1);
1264 m_xLinguOptionsCLB->set_id(nEntry, OUString::number(nUserData));
1265
1266 m_xLinguOptionsCLB->thaw();
1267
1268 m_xLinguOptionsCLB->select(0);
1269 SelectHdl_Impl(*m_xLinguOptionsCLB);
1270
1271 m_xLinguModulesCLB->set_size_request(m_xLinguModulesCLB->get_preferred_size().Width(),
1272 m_xLinguModulesCLB->get_height_rows(3));
1273 m_xLinguDicsCLB->set_size_request(m_xLinguDicsCLB->get_preferred_size().Width(),
1274 m_xLinguDicsCLB->get_height_rows(5));
1275 m_xLinguOptionsCLB->set_size_request(m_xLinguOptionsCLB->get_preferred_size().Width(),
1276 m_xLinguOptionsCLB->get_height_rows(5));
1277 }
1278
IMPL_LINK(SvxLinguTabPage,BoxDoubleClickHdl_Impl,weld::TreeView &,rBox,bool)1279 IMPL_LINK(SvxLinguTabPage, BoxDoubleClickHdl_Impl, weld::TreeView&, rBox, bool)
1280 {
1281 if (&rBox == m_xLinguModulesCLB.get() && !m_nDlbClickEventId)
1282 {
1283 //! in order to avoid a bug causing a GPF when double clicking
1284 //! on a module entry and exiting the "Edit Modules" dialog
1285 //! after that.
1286 m_nDlbClickEventId = Application::PostUserEvent(LINK(this, SvxLinguTabPage, PostDblClickHdl_Impl));
1287 }
1288 else if (&rBox == m_xLinguOptionsCLB.get())
1289 {
1290 ClickHdl_Impl(*m_xLinguOptionsEditPB);
1291 }
1292 return true;
1293 }
1294
IMPL_LINK_NOARG(SvxLinguTabPage,PostDblClickHdl_Impl,void *,void)1295 IMPL_LINK_NOARG(SvxLinguTabPage, PostDblClickHdl_Impl, void*, void)
1296 {
1297 m_nDlbClickEventId = nullptr;
1298 ClickHdl_Impl(*m_xLinguModulesEditPB);
1299 }
1300
IMPL_LINK(SvxLinguTabPage,ModulesBoxCheckButtonHdl_Impl,const row_col &,rRowCol,void)1301 IMPL_LINK(SvxLinguTabPage, ModulesBoxCheckButtonHdl_Impl, const row_col&, rRowCol, void)
1302 {
1303 if (!pLinguData)
1304 return;
1305 auto nPos = rRowCol.first;
1306 pLinguData->Reconfigure(m_xLinguModulesCLB->get_text(nPos, 1),
1307 m_xLinguModulesCLB->get_toggle(nPos, 0) == TRISTATE_TRUE);
1308 }
1309
IMPL_LINK(SvxLinguTabPage,DicsBoxCheckButtonHdl_Impl,const row_col &,rRowCol,void)1310 IMPL_LINK(SvxLinguTabPage, DicsBoxCheckButtonHdl_Impl, const row_col&, rRowCol, void)
1311 {
1312 auto nPos = rRowCol.first;
1313 const uno::Reference<XDictionary> &rDic = aDics.getConstArray()[ nPos ];
1314 if (LinguMgr::GetIgnoreAllList() == rDic)
1315 m_xLinguDicsCLB->set_toggle(nPos, TRISTATE_TRUE, 0);
1316 }
1317
IMPL_LINK(SvxLinguTabPage,ClickHdl_Impl,weld::Button &,rBtn,void)1318 IMPL_LINK(SvxLinguTabPage, ClickHdl_Impl, weld::Button&, rBtn, void)
1319 {
1320 if (m_xLinguModulesEditPB.get() == &rBtn)
1321 {
1322 if (!pLinguData)
1323 pLinguData.reset( new SvxLinguData_Impl );
1324
1325 SvxLinguData_Impl aOldLinguData( *pLinguData );
1326 SvxEditModulesDlg aDlg(GetFrameWeld(), *pLinguData);
1327 if (aDlg.run() != RET_OK)
1328 *pLinguData = aOldLinguData;
1329
1330 // evaluate new status of 'bConfigured' flag
1331 sal_uInt32 nLen = pLinguData->GetDisplayServiceCount();
1332 for (sal_uInt32 i = 0; i < nLen; ++i)
1333 pLinguData->GetDisplayServiceArray()[i].bConfigured = false;
1334 const Locale* pAllLocales = pLinguData->GetAllSupportedLocales().getConstArray();
1335 sal_Int32 nLocales = pLinguData->GetAllSupportedLocales().getLength();
1336 for (sal_Int32 k = 0; k < nLocales; ++k)
1337 {
1338 LanguageType nLang = LanguageTag::convertToLanguageType( pAllLocales[k] );
1339 if (pLinguData->GetSpellTable().count( nLang ))
1340 pLinguData->SetChecked( pLinguData->GetSpellTable()[ nLang ] );
1341 if (pLinguData->GetGrammarTable().count( nLang ))
1342 pLinguData->SetChecked( pLinguData->GetGrammarTable()[ nLang ] );
1343 if (pLinguData->GetHyphTable().count( nLang ))
1344 pLinguData->SetChecked( pLinguData->GetHyphTable()[ nLang ] );
1345 if (pLinguData->GetThesTable().count( nLang ))
1346 pLinguData->SetChecked( pLinguData->GetThesTable()[ nLang ] );
1347 }
1348
1349 // show new status of modules
1350 UpdateModulesBox_Impl();
1351 }
1352 else if (m_xLinguDicsNewPB.get() == &rBtn)
1353 {
1354 SvxAbstractDialogFactory* pFact = SvxAbstractDialogFactory::Create();
1355 ScopedVclPtr<AbstractSvxNewDictionaryDialog> aDlg(pFact->CreateSvxNewDictionaryDialog(GetFrameWeld()));
1356 uno::Reference< XDictionary > xNewDic;
1357 if ( aDlg->Execute() == RET_OK )
1358 xNewDic = aDlg->GetNewDictionary();
1359 if ( xNewDic.is() )
1360 {
1361 // add new dics to the end
1362 sal_Int32 nLen = aDics.getLength();
1363 aDics.realloc( nLen + 1 );
1364
1365 aDics.getArray()[ nLen ] = xNewDic;
1366
1367 AddDicBoxEntry( xNewDic, static_cast<sal_uInt16>(nLen) );
1368 }
1369 }
1370 else if (m_xLinguDicsEditPB.get() == &rBtn)
1371 {
1372 int nEntry = m_xLinguDicsCLB->get_selected_index();
1373 if (nEntry != -1)
1374 {
1375 DicUserData aData(m_xLinguDicsCLB->get_id(nEntry).toUInt32());
1376 sal_uInt16 nDicPos = aData.GetEntryId();
1377 sal_Int32 nDics = aDics.getLength();
1378 if (nDicPos < nDics)
1379 {
1380 uno::Reference< XDictionary > xDic = aDics.getConstArray()[ nDicPos ];
1381 if (xDic.is())
1382 {
1383 SvxAbstractDialogFactory* pFact = SvxAbstractDialogFactory::Create();
1384 ScopedVclPtr<VclAbstractDialog> aDlg(pFact->CreateSvxEditDictionaryDialog(GetFrameWeld(), xDic->getName()));
1385 aDlg->Execute();
1386 }
1387 }
1388 }
1389 }
1390 else if (m_xLinguDicsDelPB.get() == &rBtn)
1391 {
1392 std::unique_ptr<weld::Builder> xBuilder(Application::CreateBuilder(GetFrameWeld(), "cui/ui/querydeletedictionarydialog.ui"));
1393 std::unique_ptr<weld::MessageDialog> xQuery(xBuilder->weld_message_dialog("QueryDeleteDictionaryDialog"));
1394 if (RET_NO == xQuery->run())
1395 return;
1396
1397 int nEntry = m_xLinguDicsCLB->get_selected_index();
1398 if (nEntry != -1)
1399 {
1400 DicUserData aData(m_xLinguDicsCLB->get_id(nEntry).toUInt32());
1401 sal_uInt16 nDicPos = aData.GetEntryId();
1402 sal_Int32 nDics = aDics.getLength();
1403 if (nDicPos < nDics)
1404 {
1405 uno::Reference< XDictionary > xDic = aDics.getConstArray()[ nDicPos ];
1406 if (xDic.is())
1407 {
1408 if (LinguMgr::GetIgnoreAllList() == xDic)
1409 xDic->clear();
1410 else
1411 {
1412 if (xDicList.is())
1413 xDicList->removeDictionary( xDic );
1414
1415 uno::Reference< frame::XStorable > xStor( xDic, UNO_QUERY );
1416 if ( xStor->hasLocation() && !xStor->isReadonly() )
1417 {
1418 OUString sURL = xStor->getLocation();
1419 INetURLObject aObj(sURL);
1420 DBG_ASSERT( aObj.GetProtocol() == INetProtocol::File,
1421 "non-file URLs cannot be deleted" );
1422 if ( aObj.GetProtocol() == INetProtocol::File )
1423 {
1424 KillFile_Impl( aObj.GetMainURL( INetURLObject::DecodeMechanism::NONE ) );
1425 }
1426 }
1427
1428 aDics.getArray()[ nDicPos ] = nullptr;
1429
1430 // remove entry from checklistbox
1431 int nCnt = m_xLinguDicsCLB->n_children();
1432 for (int i = 0; i < nCnt; ++i)
1433 {
1434 DicUserData aDicData(m_xLinguDicsCLB->get_id(i).toUInt32());
1435 if (aDicData.GetEntryId() == nDicPos )
1436 {
1437 m_xLinguDicsCLB->remove(i);
1438 break;
1439 }
1440 }
1441 DBG_ASSERT( nCnt > m_xLinguDicsCLB->n_children(),
1442 "remove failed ?");
1443 }
1444 }
1445 }
1446 }
1447 }
1448 else if (m_xLinguOptionsEditPB.get() == &rBtn)
1449 {
1450 int nEntry = m_xLinguOptionsCLB->get_selected_index();
1451 DBG_ASSERT(nEntry != -1, "no entry selected");
1452 if (nEntry != -1)
1453 {
1454 OptionsUserData aData(m_xLinguOptionsCLB->get_id(nEntry).toUInt32());
1455 if (aData.HasNumericValue())
1456 {
1457 sal_uInt16 nRID = aData.GetEntryId();
1458 OptionsBreakSet aDlg(GetFrameWeld(), nRID);
1459 aDlg.GetNumericFld().set_value(aData.GetNumericValue());
1460 if (RET_OK == aDlg.run())
1461 {
1462 int nVal = aDlg.GetNumericFld().get_value();
1463 if (-1 != nVal && aData.GetNumericValue() != nVal)
1464 {
1465 aData.SetNumericValue( static_cast<sal_uInt8>(nVal) ); //! sets IsModified !
1466 m_xLinguOptionsCLB->set_id(nEntry, OUString::number(aData.GetUserData()));
1467 if (nEntry == nUPN_HYPH_MIN_WORD_LENGTH)
1468 m_xLinguOptionsCLB->set_text(nEntry, sNumMinWordlen + " " + OUString::number(nVal), 1);
1469 else if (nEntry == nUPN_HYPH_MIN_LEADING)
1470 m_xLinguOptionsCLB->set_text(nEntry, sNumPreBreak + " " + OUString::number(nVal), 1);
1471 else if (nEntry == nUPN_HYPH_MIN_TRAILING)
1472 m_xLinguOptionsCLB->set_text(nEntry, sNumPostBreak + " " + OUString::number(nVal), 1);
1473 m_xLinguOptionsCLB->set_id(nEntry, OUString::number(aData.GetUserData()));
1474 }
1475 }
1476 }
1477 }
1478 }
1479 else
1480 {
1481 OSL_FAIL( "rBtn unexpected value" );
1482 }
1483 }
1484
IMPL_LINK(SvxLinguTabPage,SelectHdl_Impl,weld::TreeView &,rBox,void)1485 IMPL_LINK(SvxLinguTabPage, SelectHdl_Impl, weld::TreeView&, rBox, void)
1486 {
1487 if (m_xLinguModulesCLB.get() == &rBox)
1488 {
1489 }
1490 else if (m_xLinguDicsCLB.get() == &rBox)
1491 {
1492 int nEntry = rBox.get_selected_index();
1493 if (nEntry != -1)
1494 {
1495 DicUserData aData(rBox.get_id(nEntry).toUInt32());
1496
1497 // always allow to edit (i.e. at least view the content of the dictionary)
1498 m_xLinguDicsEditPB->set_sensitive( true );
1499 m_xLinguDicsDelPB->set_sensitive( aData.IsDeletable() );
1500 }
1501 }
1502 else if (m_xLinguOptionsCLB.get() == &rBox)
1503 {
1504 int nEntry = rBox.get_selected_index();
1505 if (nEntry != -1)
1506 {
1507 OptionsUserData aData(rBox.get_id(nEntry).toUInt32());
1508 m_xLinguOptionsEditPB->set_sensitive( aData.HasNumericValue() );
1509 }
1510 }
1511 else
1512 {
1513 OSL_FAIL( "rBox unexpected value" );
1514 }
1515 }
1516
HideGroups(sal_uInt16 nGrp)1517 void SvxLinguTabPage::HideGroups( sal_uInt16 nGrp )
1518 {
1519 if ( 0 != ( GROUP_MODULES & nGrp ) )
1520 {
1521 m_xLinguModulesFT->hide();
1522 m_xLinguModulesCLB->hide();
1523 m_xLinguModulesEditPB->hide();
1524
1525 if ( SvtExtendedSecurityOptions().GetOpenHyperlinkMode()
1526 != SvtExtendedSecurityOptions::OPEN_NEVER )
1527 {
1528 m_xMoreDictsLink->show();
1529 }
1530 }
1531 }
1532
SvxEditModulesDlg(weld::Window * pParent,SvxLinguData_Impl & rData)1533 SvxEditModulesDlg::SvxEditModulesDlg(weld::Window* pParent, SvxLinguData_Impl& rData)
1534 : GenericDialogController(pParent, "cui/ui/editmodulesdialog.ui", "EditModulesDialog")
1535 , sSpell(CuiResId(RID_SVXSTR_SPELL))
1536 , sHyph(CuiResId(RID_SVXSTR_HYPH))
1537 , sThes(CuiResId(RID_SVXSTR_THES))
1538 , sGrammar(CuiResId(RID_SVXSTR_GRAMMAR))
1539 , rLinguData(rData)
1540 , m_xModulesCLB(m_xBuilder->weld_tree_view("lingudicts"))
1541 , m_xPrioUpPB(m_xBuilder->weld_button("up"))
1542 , m_xPrioDownPB(m_xBuilder->weld_button("down"))
1543 , m_xBackPB(m_xBuilder->weld_button("back"))
1544 , m_xMoreDictsLink(m_xBuilder->weld_link_button("moredictslink"))
1545 , m_xClosePB(m_xBuilder->weld_button("close"))
1546 , m_xLanguageLB(new SvxLanguageBox(m_xBuilder->weld_combo_box("language")))
1547 {
1548 m_xModulesCLB->set_size_request(m_xModulesCLB->get_approximate_digit_width() * 40,
1549 m_xModulesCLB->get_height_rows(12));
1550
1551 std::vector<int> aWidths;
1552 aWidths.push_back(m_xModulesCLB->get_checkbox_column_width());
1553 m_xModulesCLB->set_column_fixed_widths(aWidths);
1554
1555 pDefaultLinguData.reset( new SvxLinguData_Impl( rLinguData ) );
1556
1557 m_xModulesCLB->connect_changed( LINK( this, SvxEditModulesDlg, SelectHdl_Impl ));
1558 m_xModulesCLB->connect_toggled(LINK(this, SvxEditModulesDlg, BoxCheckButtonHdl_Impl));
1559
1560 m_xClosePB->connect_clicked( LINK( this, SvxEditModulesDlg, ClickHdl_Impl ));
1561 m_xPrioUpPB->connect_clicked( LINK( this, SvxEditModulesDlg, UpDownHdl_Impl ));
1562 m_xPrioDownPB->connect_clicked( LINK( this, SvxEditModulesDlg, UpDownHdl_Impl ));
1563 m_xBackPB->connect_clicked( LINK( this, SvxEditModulesDlg, BackHdl_Impl ));
1564 // in case of not installed language modules
1565 m_xPrioUpPB->set_sensitive( false );
1566 m_xPrioDownPB->set_sensitive( false );
1567
1568 if ( SvtExtendedSecurityOptions().GetOpenHyperlinkMode() == SvtExtendedSecurityOptions::OPEN_NEVER )
1569 m_xMoreDictsLink->hide();
1570
1571 // set that we want the checkbox shown if spellchecking is available
1572 m_xLanguageLB->SetLanguageList(SvxLanguageListFlags::EMPTY, false, false, true);
1573
1574 //fill language box
1575 const Sequence< Locale >& rLoc = rLinguData.GetAllSupportedLocales();
1576 const Locale* pLocales = rLoc.getConstArray();
1577 for (int i = 0; i < rLoc.getLength(); ++i)
1578 {
1579 LanguageType nLang = LanguageTag::convertToLanguageType( pLocales[i] );
1580 m_xLanguageLB->InsertLanguage(nLang);
1581 }
1582 LanguageType eSysLang = MsLangId::getSystemLanguage();
1583 m_xLanguageLB->set_active_id( eSysLang );
1584 if (m_xLanguageLB->get_active_id() != eSysLang)
1585 m_xLanguageLB->set_active(0);
1586
1587 m_xLanguageLB->connect_changed( LINK( this, SvxEditModulesDlg, LangSelectListBoxHdl_Impl ));
1588 LangSelectHdl_Impl(m_xLanguageLB.get());
1589 }
1590
~SvxEditModulesDlg()1591 SvxEditModulesDlg::~SvxEditModulesDlg()
1592 {
1593 for (int i = 0, nEntryCount = m_xModulesCLB->n_children(); i < nEntryCount; ++i)
1594 delete reinterpret_cast<ModuleUserData_Impl*>(m_xModulesCLB->get_id(i).toInt64());
1595 }
1596
IMPL_LINK(SvxEditModulesDlg,SelectHdl_Impl,weld::TreeView &,rBox,void)1597 IMPL_LINK( SvxEditModulesDlg, SelectHdl_Impl, weld::TreeView&, rBox, void )
1598 {
1599 int nCurPos = rBox.get_selected_index();
1600 if (nCurPos != -1)
1601 {
1602 bool bDisableUp = true;
1603 bool bDisableDown = true;
1604 ModuleUserData_Impl* pData = reinterpret_cast<ModuleUserData_Impl*>(rBox.get_id(nCurPos).toInt64());
1605 if (!pData->IsParent() && pData->GetType() != TYPE_HYPH)
1606 {
1607 if (nCurPos < rBox.n_children() - 1)
1608 {
1609 bDisableDown = reinterpret_cast<ModuleUserData_Impl*>(rBox.get_id(nCurPos + 1).toInt64())->IsParent();
1610 }
1611 if (nCurPos > 1)
1612 {
1613 bDisableUp = reinterpret_cast<ModuleUserData_Impl*>(rBox.get_id(nCurPos - 1).toInt64())->IsParent();
1614 }
1615 }
1616 m_xPrioUpPB->set_sensitive(!bDisableUp);
1617 m_xPrioDownPB->set_sensitive(!bDisableDown);
1618 }
1619 }
1620
IMPL_LINK(SvxEditModulesDlg,BoxCheckButtonHdl_Impl,const row_col &,rRowCol,void)1621 IMPL_LINK( SvxEditModulesDlg, BoxCheckButtonHdl_Impl, const row_col&, rRowCol, void )
1622 {
1623 auto nPos = rRowCol.first;
1624 ModuleUserData_Impl* pData = reinterpret_cast<ModuleUserData_Impl*>(m_xModulesCLB->get_id(nPos).toInt64());
1625 if (!pData->IsParent() && pData->GetType() == TYPE_HYPH)
1626 {
1627 // make hyphenator checkboxes function as radio-buttons
1628 // (at most one box may be checked)
1629 for (int i = 0, nEntryCount = m_xModulesCLB->n_children(); i < nEntryCount; ++i)
1630 {
1631 pData = reinterpret_cast<ModuleUserData_Impl*>(m_xModulesCLB->get_id(i).toInt64());
1632 if (!pData->IsParent() && pData->GetType() == TYPE_HYPH && i != nPos)
1633 {
1634 m_xModulesCLB->set_toggle(i, TRISTATE_FALSE, 0);
1635 }
1636 }
1637 }
1638 }
1639
IMPL_LINK_NOARG(SvxEditModulesDlg,LangSelectListBoxHdl_Impl,weld::ComboBox &,void)1640 IMPL_LINK_NOARG(SvxEditModulesDlg, LangSelectListBoxHdl_Impl, weld::ComboBox&, void)
1641 {
1642 LangSelectHdl_Impl(m_xLanguageLB.get());
1643 }
1644
LangSelectHdl_Impl(const SvxLanguageBox * pBox)1645 void SvxEditModulesDlg::LangSelectHdl_Impl(const SvxLanguageBox* pBox)
1646 {
1647 LanguageType eCurLanguage = m_xLanguageLB->get_active_id();
1648 static Locale aLastLocale;
1649 Locale aCurLocale( LanguageTag::convertToLocale( eCurLanguage));
1650
1651 if (pBox)
1652 {
1653 // save old probably changed settings
1654 // before switching to new language entries
1655
1656 LanguageType nLang = LanguageTag::convertToLanguageType( aLastLocale );
1657
1658 sal_Int32 nStart = 0, nLocalIndex = 0;
1659 Sequence< OUString > aChange;
1660 bool bChanged = false;
1661 for (int i = 0, nEntryCount = m_xModulesCLB->n_children(); i < nEntryCount; ++i)
1662 {
1663 ModuleUserData_Impl* pData = reinterpret_cast<ModuleUserData_Impl*>(m_xModulesCLB->get_id(i).toInt64());
1664 if (pData->IsParent())
1665 {
1666 if (bChanged)
1667 {
1668 LangImplNameTable *pTable = nullptr;
1669 sal_uInt8 nType = pData->GetType();
1670 switch (nType - 1)
1671 {
1672 case TYPE_SPELL : pTable = &rLinguData.GetSpellTable(); break;
1673 case TYPE_GRAMMAR : pTable = &rLinguData.GetGrammarTable(); break;
1674 case TYPE_HYPH : pTable = &rLinguData.GetHyphTable(); break;
1675 case TYPE_THES : pTable = &rLinguData.GetThesTable(); break;
1676 }
1677 if (pTable)
1678 {
1679 aChange.realloc(nStart);
1680 (*pTable)[ nLang ] = aChange;
1681 }
1682 }
1683 nLocalIndex = nStart = 0;
1684 aChange.realloc(nEntryCount);
1685 bChanged = false;
1686 }
1687 else
1688 {
1689 OUString* pChange = aChange.getArray();
1690 pChange[nStart] = pData->GetImplName();
1691 bChanged |= pData->GetIndex() != nLocalIndex ||
1692 static_cast<TriState>(pData->IsChecked()) != m_xModulesCLB->get_toggle(i, 0);
1693 if (m_xModulesCLB->get_toggle(i, 0))
1694 nStart++;
1695 ++nLocalIndex;
1696 }
1697 }
1698 if(bChanged)
1699 {
1700 aChange.realloc(nStart);
1701 rLinguData.GetThesTable()[ nLang ] = aChange;
1702 }
1703 }
1704
1705 for (int i = 0, nEntryCount = m_xModulesCLB->n_children(); i < nEntryCount; ++i)
1706 delete reinterpret_cast<ModuleUserData_Impl*>(m_xModulesCLB->get_id(i).toInt64());
1707 m_xModulesCLB->clear();
1708
1709 // display entries for new selected language
1710
1711 if (LANGUAGE_DONTKNOW != eCurLanguage)
1712 {
1713 sal_uLong n;
1714 ServiceInfo_Impl* pInfo;
1715
1716 int nRow = 0;
1717 // spellchecker entries
1718
1719 ModuleUserData_Impl* pUserData = new ModuleUserData_Impl(
1720 OUString(), true, false, TYPE_SPELL, 0 );
1721 OUString sId(OUString::number(reinterpret_cast<sal_Int64>(pUserData)));
1722 m_xModulesCLB->append(nullptr);
1723 m_xModulesCLB->set_id(nRow, sId);
1724 m_xModulesCLB->set_text(nRow, sSpell, 1);
1725 m_xModulesCLB->set_text_emphasis(nRow, true, 1);
1726 ++nRow;
1727
1728 Sequence< OUString > aNames( rLinguData.GetSortedImplNames( eCurLanguage, TYPE_SPELL ) );
1729 const OUString *pName = aNames.getConstArray();
1730 sal_uLong nNames = static_cast<sal_uLong>(aNames.getLength());
1731 sal_Int32 nLocalIndex = 0; // index relative to parent
1732 for (n = 0; n < nNames; ++n)
1733 {
1734 OUString aImplName;
1735 bool bIsSuppLang = false;
1736
1737 pInfo = rLinguData.GetInfoByImplName( pName[n] );
1738 if (pInfo)
1739 {
1740 bIsSuppLang = pInfo->xSpell.is() &&
1741 pInfo->xSpell->hasLocale( aCurLocale );
1742 aImplName = pInfo->sSpellImplName;
1743 }
1744 if (!aImplName.isEmpty() && bIsSuppLang)
1745 {
1746 OUString aTxt( pInfo->sDisplayName );
1747
1748 LangImplNameTable &rTable = rLinguData.GetSpellTable();
1749 const bool bHasLang = rTable.count( eCurLanguage );
1750 if (!bHasLang)
1751 {
1752 SAL_INFO( "cui.options", "language entry missing" ); // only relevant if all languages found should be supported
1753 }
1754 const bool bCheck = bHasLang && lcl_SeqGetEntryPos( rTable[ eCurLanguage ], aImplName ) >= 0;
1755 pUserData = new ModuleUserData_Impl( aImplName, false,
1756 bCheck, TYPE_SPELL, static_cast<sal_uInt8>(nLocalIndex++) );
1757 sId = OUString::number(reinterpret_cast<sal_Int64>(pUserData));
1758
1759 m_xModulesCLB->append(nullptr);
1760 m_xModulesCLB->set_id(nRow, sId);
1761 m_xModulesCLB->set_toggle(nRow, bCheck ? TRISTATE_TRUE : TRISTATE_FALSE, 0);
1762 m_xModulesCLB->set_text(nRow, aTxt, 1);
1763 ++nRow;
1764 }
1765 }
1766
1767 // grammar checker entries
1768
1769 pUserData = new ModuleUserData_Impl( OUString(), true, false, TYPE_GRAMMAR, 0 );
1770 sId = OUString::number(reinterpret_cast<sal_Int64>(pUserData));
1771 m_xModulesCLB->append(nullptr);
1772 m_xModulesCLB->set_id(nRow, sId);
1773 m_xModulesCLB->set_text(nRow, sGrammar, 1);
1774 m_xModulesCLB->set_text_emphasis(nRow, true, 1);
1775 ++nRow;
1776
1777 aNames = rLinguData.GetSortedImplNames( eCurLanguage, TYPE_GRAMMAR );
1778 pName = aNames.getConstArray();
1779 nNames = static_cast<sal_uLong>(aNames.getLength());
1780 nLocalIndex = 0;
1781 for (n = 0; n < nNames; ++n)
1782 {
1783 OUString aImplName;
1784 bool bIsSuppLang = false;
1785
1786 pInfo = rLinguData.GetInfoByImplName( pName[n] );
1787 if (pInfo)
1788 {
1789 bIsSuppLang = pInfo->xGrammar.is() &&
1790 pInfo->xGrammar->hasLocale( aCurLocale );
1791 aImplName = pInfo->sGrammarImplName;
1792 }
1793 if (!aImplName.isEmpty() && bIsSuppLang)
1794 {
1795 OUString aTxt( pInfo->sDisplayName );
1796
1797 LangImplNameTable &rTable = rLinguData.GetGrammarTable();
1798 const bool bHasLang = rTable.count( eCurLanguage );
1799 if (!bHasLang)
1800 {
1801 SAL_INFO( "cui.options", "language entry missing" ); // only relevant if all languages found should be supported
1802 }
1803 const bool bCheck = bHasLang && lcl_SeqGetEntryPos( rTable[ eCurLanguage ], aImplName ) >= 0;
1804 pUserData = new ModuleUserData_Impl( aImplName, false,
1805 bCheck, TYPE_GRAMMAR, static_cast<sal_uInt8>(nLocalIndex++) );
1806
1807 sId = OUString::number(reinterpret_cast<sal_Int64>(pUserData));
1808
1809 m_xModulesCLB->append(nullptr);
1810 m_xModulesCLB->set_id(nRow, sId);
1811 m_xModulesCLB->set_toggle(nRow, bCheck ? TRISTATE_TRUE : TRISTATE_FALSE, 0);
1812 m_xModulesCLB->set_text(nRow, aTxt, 1);
1813 ++nRow;
1814 }
1815 }
1816
1817 // hyphenator entries
1818
1819 pUserData = new ModuleUserData_Impl( OUString(), true, false, TYPE_HYPH, 0 );
1820 sId = OUString::number(reinterpret_cast<sal_Int64>(pUserData));
1821 m_xModulesCLB->append(nullptr);
1822 m_xModulesCLB->set_id(nRow, sId);
1823 m_xModulesCLB->set_text(nRow, sHyph, 1);
1824 m_xModulesCLB->set_text_emphasis(nRow, true, 1);
1825 ++nRow;
1826
1827 aNames = rLinguData.GetSortedImplNames( eCurLanguage, TYPE_HYPH );
1828 pName = aNames.getConstArray();
1829 nNames = static_cast<sal_uLong>(aNames.getLength());
1830 nLocalIndex = 0;
1831 for (n = 0; n < nNames; ++n)
1832 {
1833 OUString aImplName;
1834 bool bIsSuppLang = false;
1835
1836 pInfo = rLinguData.GetInfoByImplName( pName[n] );
1837 if (pInfo)
1838 {
1839 bIsSuppLang = pInfo->xHyph.is() &&
1840 pInfo->xHyph->hasLocale( aCurLocale );
1841 aImplName = pInfo->sHyphImplName;
1842 }
1843 if (!aImplName.isEmpty() && bIsSuppLang)
1844 {
1845 OUString aTxt( pInfo->sDisplayName );
1846
1847 LangImplNameTable &rTable = rLinguData.GetHyphTable();
1848 const bool bHasLang = rTable.count( eCurLanguage );
1849 if (!bHasLang)
1850 {
1851 SAL_INFO( "cui.options", "language entry missing" ); // only relevant if all languages found should be supported
1852 }
1853 const bool bCheck = bHasLang && lcl_SeqGetEntryPos( rTable[ eCurLanguage ], aImplName ) >= 0;
1854 pUserData = new ModuleUserData_Impl( aImplName, false,
1855 bCheck, TYPE_HYPH, static_cast<sal_uInt8>(nLocalIndex++) );
1856 sId = OUString::number(reinterpret_cast<sal_Int64>(pUserData));
1857
1858 m_xModulesCLB->append(nullptr);
1859 m_xModulesCLB->set_id(nRow, sId);
1860 m_xModulesCLB->set_toggle(nRow, bCheck ? TRISTATE_TRUE : TRISTATE_FALSE, 0);
1861 m_xModulesCLB->set_text(nRow, aTxt, 1);
1862 ++nRow;
1863 }
1864 }
1865
1866 // thesaurus entries
1867
1868 pUserData = new ModuleUserData_Impl( OUString(), true, false, TYPE_THES, 0 );
1869 sId = OUString::number(reinterpret_cast<sal_Int64>(pUserData));
1870 m_xModulesCLB->append(nullptr);
1871 m_xModulesCLB->set_id(nRow, sId);
1872 m_xModulesCLB->set_text(nRow, sThes, 1);
1873 m_xModulesCLB->set_text_emphasis(nRow, true, 1);
1874 ++nRow;
1875
1876 aNames = rLinguData.GetSortedImplNames( eCurLanguage, TYPE_THES );
1877 pName = aNames.getConstArray();
1878 nNames = static_cast<sal_uLong>(aNames.getLength());
1879 nLocalIndex = 0;
1880 for (n = 0; n < nNames; ++n)
1881 {
1882 OUString aImplName;
1883 bool bIsSuppLang = false;
1884
1885 pInfo = rLinguData.GetInfoByImplName( pName[n] );
1886 if (pInfo)
1887 {
1888 bIsSuppLang = pInfo->xThes.is() &&
1889 pInfo->xThes->hasLocale( aCurLocale );
1890 aImplName = pInfo->sThesImplName;
1891 }
1892 if (!aImplName.isEmpty() && bIsSuppLang)
1893 {
1894 OUString aTxt( pInfo->sDisplayName );
1895
1896 LangImplNameTable &rTable = rLinguData.GetThesTable();
1897 const bool bHasLang = rTable.count( eCurLanguage );
1898 if (!bHasLang)
1899 {
1900 SAL_INFO( "cui.options", "language entry missing" ); // only relevant if all languages found should be supported
1901 }
1902 const bool bCheck = bHasLang && lcl_SeqGetEntryPos( rTable[ eCurLanguage ], aImplName ) >= 0;
1903 pUserData = new ModuleUserData_Impl( aImplName, false,
1904 bCheck, TYPE_THES, static_cast<sal_uInt8>(nLocalIndex++) );
1905 sId = OUString::number(reinterpret_cast<sal_Int64>(pUserData));
1906
1907 m_xModulesCLB->append(nullptr);
1908 m_xModulesCLB->set_id(nRow, sId);
1909 m_xModulesCLB->set_toggle(nRow, bCheck ? TRISTATE_TRUE : TRISTATE_FALSE, 0);
1910 m_xModulesCLB->set_text(nRow, aTxt, 1);
1911 ++nRow;
1912 }
1913 }
1914 }
1915 aLastLocale = aCurLocale;
1916 }
1917
IMPL_LINK(SvxEditModulesDlg,UpDownHdl_Impl,weld::Button &,rBtn,void)1918 IMPL_LINK( SvxEditModulesDlg, UpDownHdl_Impl, weld::Button&, rBtn, void )
1919 {
1920 bool bUp = m_xPrioUpPB.get() == &rBtn;
1921 int nCurPos = m_xModulesCLB->get_selected_index();
1922 if (nCurPos != -1)
1923 {
1924 m_xModulesCLB->freeze();
1925
1926 OUString sId(m_xModulesCLB->get_id(nCurPos));
1927 OUString sStr(m_xModulesCLB->get_text(nCurPos));
1928 bool bIsChecked = m_xModulesCLB->get_toggle(nCurPos, nCurPos);
1929
1930 m_xModulesCLB->remove(nCurPos);
1931
1932 int nDestPos = bUp ? nCurPos - 1 : nCurPos + 1;
1933
1934 m_xModulesCLB->insert_text(nDestPos, sStr);
1935 m_xModulesCLB->set_id(nDestPos, sId);
1936 m_xModulesCLB->set_toggle(nDestPos, bIsChecked ? TRISTATE_TRUE : TRISTATE_FALSE, 0);
1937
1938 m_xModulesCLB->thaw();
1939
1940 m_xModulesCLB->select(nDestPos);
1941 SelectHdl_Impl(*m_xModulesCLB);
1942 }
1943 }
1944
IMPL_LINK_NOARG(SvxEditModulesDlg,ClickHdl_Impl,weld::Button &,void)1945 IMPL_LINK_NOARG(SvxEditModulesDlg, ClickHdl_Impl, weld::Button&, void)
1946 {
1947 // store language config
1948 LangSelectHdl_Impl(m_xLanguageLB.get());
1949 m_xDialog->response(RET_OK);
1950 }
1951
IMPL_LINK_NOARG(SvxEditModulesDlg,BackHdl_Impl,weld::Button &,void)1952 IMPL_LINK_NOARG(SvxEditModulesDlg, BackHdl_Impl, weld::Button&, void)
1953 {
1954 rLinguData = *pDefaultLinguData;
1955 LangSelectHdl_Impl(nullptr);
1956 }
1957
1958 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */
1959