/src/libreoffice/svtools/source/misc/langtab.cxx
Line | Count | Source |
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 <sal/config.h> |
21 | | |
22 | | #include <string_view> |
23 | | |
24 | | #include <com/sun/star/container/XNameAccess.hpp> |
25 | | #include <com/sun/star/i18n/DirectionProperty.hpp> |
26 | | #include <com/sun/star/i18n/ScriptType.hpp> |
27 | | #include <com/sun/star/uno/Sequence.hxx> |
28 | | #include <com/sun/star/uno/Any.h> |
29 | | |
30 | | #include <comphelper/lok.hxx> |
31 | | #include <i18nlangtag/lang.h> |
32 | | #include <i18nlangtag/mslangid.hxx> |
33 | | #include <i18nlangtag/languagetag.hxx> |
34 | | #include <i18nlangtag/languagetagicu.hxx> |
35 | | |
36 | | #include <i18nutil/unicode.hxx> |
37 | | |
38 | | #include <sal/log.hxx> |
39 | | #include <vcl/svapp.hxx> |
40 | | #include <vcl/settings.hxx> |
41 | | #include <svtools/svtresid.hxx> |
42 | | #include <svtools/langtab.hxx> |
43 | | #include <unotools/syslocale.hxx> |
44 | | #include <unotools/charclass.hxx> |
45 | | #include <tools/resary.hxx> |
46 | | #include <officecfg/VCL.hxx> |
47 | | #include <langtab.hrc> |
48 | | |
49 | | using namespace ::com::sun::star; |
50 | | |
51 | | namespace { |
52 | | |
53 | | class SvtLanguageTableImpl |
54 | | { |
55 | | private: |
56 | | std::vector<std::pair<OUString, LanguageType>> m_aStrings; |
57 | | OUString m_aUILanguage; // UI language used to build the table |
58 | | const OUString& AddItem(const OUString& rLanguage, const LanguageType eType) |
59 | 0 | { |
60 | 0 | return m_aStrings.emplace_back(rLanguage, eType).first; |
61 | 0 | } |
62 | | void Build(); |
63 | | |
64 | | public: |
65 | | |
66 | | SvtLanguageTableImpl(); |
67 | | |
68 | | // In LOKit mode, rebuild the table if the UI language has changed. |
69 | | void RebuildIfNeeded(); |
70 | | |
71 | | bool HasType( const LanguageType eType ) const; |
72 | | const OUString& GetString( const LanguageType eType ) const; |
73 | | LanguageType GetType( std::u16string_view rStr ) const; |
74 | | sal_uInt32 GetEntryCount() const; |
75 | | LanguageType GetTypeAtIndex( sal_uInt32 nIndex ) const; |
76 | | LanguageType GetValue(sal_uInt32 nIndex) const |
77 | 0 | { |
78 | 0 | return (nIndex < m_aStrings.size()) ? m_aStrings[nIndex].second : LANGUAGE_DONTKNOW; |
79 | 0 | } |
80 | | sal_uInt32 FindIndex(LanguageType nValue) const |
81 | 0 | { |
82 | 0 | const size_t nItems = m_aStrings.size(); |
83 | 0 | for (size_t i = 0; i < nItems; ++i) |
84 | 0 | { |
85 | 0 | if (m_aStrings[i].second == nValue) |
86 | 0 | return i; |
87 | 0 | } |
88 | 0 | return RESARRAY_INDEX_NOTFOUND; |
89 | 0 | } |
90 | | const OUString& AddEntry( const OUString& rString, const LanguageType eType); |
91 | | }; |
92 | | |
93 | | SvtLanguageTableImpl& theLanguageTable() |
94 | 0 | { |
95 | 0 | static SvtLanguageTableImpl aTable; |
96 | 0 | aTable.RebuildIfNeeded(); |
97 | 0 | return aTable; |
98 | 0 | } |
99 | | } |
100 | | |
101 | | OUString ApplyLreOrRleEmbedding( const OUString &rText ) |
102 | 0 | { |
103 | 0 | const sal_Int32 nLen = rText.getLength(); |
104 | 0 | if (nLen == 0) |
105 | 0 | return OUString(); |
106 | | |
107 | 0 | constexpr sal_Unicode cLRE_Embedding = 0x202A; // the start char of an LRE embedding |
108 | 0 | constexpr sal_Unicode cRLE_Embedding = 0x202B; // the start char of an RLE embedding |
109 | 0 | constexpr sal_Unicode cPopDirectionalFormat = 0x202C; // the unicode PDF (POP_DIRECTIONAL_FORMAT) char that terminates an LRE/RLE embedding |
110 | | |
111 | | // check if there are already embedding characters at the strings start |
112 | | // if so change nothing |
113 | 0 | const sal_Unicode cChar = rText[0]; |
114 | 0 | if (cChar == cLRE_Embedding || cChar == cRLE_Embedding) |
115 | 0 | return rText; |
116 | | |
117 | | // since we only call the function getCharacterDirection |
118 | | // it does not matter which locale the CharClass is for. |
119 | | // Thus we can readily make use of SvtSysLocale::GetCharClass() |
120 | | // which should come at no cost... |
121 | 0 | SvtSysLocale aSysLocale; |
122 | 0 | const CharClass &rCharClass = aSysLocale.GetCharClass(); |
123 | | |
124 | | // we should look for the first non-neutral LTR or RTL character |
125 | | // and use that to determine the embedding of the whole text... |
126 | | // Thus we can avoid to check every character of the text. |
127 | 0 | bool bFound = false; |
128 | 0 | bool bIsRtlText = false; |
129 | 0 | for (sal_Int32 i = 0; i < nLen && !bFound; ++i) |
130 | 0 | { |
131 | 0 | i18n::DirectionProperty nDirection = rCharClass.getCharacterDirection( rText, i ); |
132 | 0 | switch (nDirection) |
133 | 0 | { |
134 | 0 | case i18n::DirectionProperty_LEFT_TO_RIGHT : |
135 | 0 | case i18n::DirectionProperty_LEFT_TO_RIGHT_EMBEDDING : |
136 | 0 | case i18n::DirectionProperty_LEFT_TO_RIGHT_OVERRIDE : |
137 | 0 | case i18n::DirectionProperty_EUROPEAN_NUMBER : |
138 | 0 | case i18n::DirectionProperty_ARABIC_NUMBER : // yes! arabic numbers are written from left to right |
139 | 0 | { |
140 | 0 | bIsRtlText = false; |
141 | 0 | bFound = true; |
142 | 0 | break; |
143 | 0 | } |
144 | | |
145 | 0 | case i18n::DirectionProperty_RIGHT_TO_LEFT : |
146 | 0 | case i18n::DirectionProperty_RIGHT_TO_LEFT_ARABIC : |
147 | 0 | case i18n::DirectionProperty_RIGHT_TO_LEFT_EMBEDDING : |
148 | 0 | case i18n::DirectionProperty_RIGHT_TO_LEFT_OVERRIDE : |
149 | 0 | { |
150 | 0 | bIsRtlText = true; |
151 | 0 | bFound = true; |
152 | 0 | break; |
153 | 0 | } |
154 | | |
155 | 0 | default: |
156 | 0 | { |
157 | | // nothing to be done, character is considered to be neutral we need to look further ... |
158 | 0 | } |
159 | 0 | } |
160 | 0 | } |
161 | | |
162 | 0 | sal_Unicode cStart = cLRE_Embedding; // default is to use LRE embedding characters |
163 | 0 | if (bIsRtlText) |
164 | 0 | cStart = cRLE_Embedding; // then use RLE embedding |
165 | | |
166 | | // add embedding start and end chars to the text if the direction could be determined |
167 | 0 | OUString aRes( rText ); |
168 | 0 | if (bFound) |
169 | 0 | { |
170 | 0 | aRes = OUStringChar(cStart) + aRes |
171 | 0 | + OUStringChar(cPopDirectionalFormat); |
172 | 0 | } |
173 | |
|
174 | 0 | return aRes; |
175 | 0 | } |
176 | | |
177 | | static OUString lcl_getDescription( const LanguageTag& rTag ) |
178 | 0 | { |
179 | 0 | OUString aStr( LanguageTagIcu::getDisplayName( rTag, Application::GetSettings().GetUILanguageTag())); |
180 | 0 | if (aStr.isEmpty() || aStr == rTag.getBcp47()) |
181 | 0 | { |
182 | | // Place in curly brackets, so all on-the-fly tags without display name |
183 | | // are grouped together at the top of a listbox (but behind the |
184 | | // "[None]" entry), and not sprinkled all over, which alphabetically |
185 | | // might make sense in an English UI only anyway. Also a visual |
186 | | // indicator that it is a programmatical name, IMHO. |
187 | 0 | return OUString::Concat("{") + aStr + "}"; |
188 | 0 | } |
189 | 0 | else |
190 | 0 | { |
191 | | // The ICU display name might be identical to a predefined name or even |
192 | | // to another tag's ICU name; clarify that this is a generated name and |
193 | | // append the language tag in curly brackets to distinguish. |
194 | 0 | return aStr + " {" + rTag.getBcp47() + "}"; |
195 | 0 | } |
196 | 0 | } |
197 | | |
198 | | SvtLanguageTableImpl::SvtLanguageTableImpl() |
199 | 0 | { |
200 | 0 | Build(); |
201 | 0 | } |
202 | | |
203 | | void SvtLanguageTableImpl::Build() |
204 | 0 | { |
205 | 0 | m_aStrings.clear(); |
206 | |
|
207 | 0 | for (const auto& [rResId, rType] : STR_ARR_SVT_LANGUAGE_TABLE) |
208 | 0 | { |
209 | 0 | m_aStrings.emplace_back(SvtResId(rResId), rType); |
210 | 0 | } |
211 | |
|
212 | 0 | auto xNA = officecfg::VCL::ExtraLanguages::get(); |
213 | 0 | const uno::Sequence <OUString> rElementNames = xNA->getElementNames(); |
214 | 0 | for (const OUString& rBcp47 : rElementNames) |
215 | 0 | { |
216 | 0 | OUString aName; |
217 | 0 | sal_Int32 nType = 0; |
218 | 0 | uno::Reference <container::XNameAccess> xNB; |
219 | 0 | xNA->getByName(rBcp47) >>= xNB; |
220 | 0 | bool bSuccess = (xNB->getByName(u"Name"_ustr) >>= aName) && |
221 | 0 | (xNB->getByName(u"ScriptType"_ustr) >>= nType); |
222 | 0 | if (bSuccess) |
223 | 0 | { |
224 | 0 | LanguageTag aLang(rBcp47); |
225 | 0 | LanguageType nLangType = aLang.getLanguageType(); |
226 | 0 | if (nType <= sal_Int32(LanguageTag::ScriptType::RTL) && nType > sal_Int32(LanguageTag::ScriptType::UNKNOWN)) |
227 | 0 | aLang.setScriptType(LanguageTag::ScriptType(nType)); |
228 | 0 | sal_uInt32 nPos = FindIndex(nLangType); |
229 | 0 | if (nPos == RESARRAY_INDEX_NOTFOUND) |
230 | 0 | AddEntry( (aName.isEmpty() ? lcl_getDescription(aLang) : aName), nLangType); |
231 | 0 | } |
232 | 0 | } |
233 | |
|
234 | 0 | m_aUILanguage = SvtSysLocale().GetUILanguageTag().getBcp47(); |
235 | 0 | } |
236 | | |
237 | | void SvtLanguageTableImpl::RebuildIfNeeded() |
238 | 0 | { |
239 | 0 | if (!comphelper::LibreOfficeKit::isActive()) |
240 | 0 | return; |
241 | | |
242 | 0 | OUString aCurrLang = SvtSysLocale().GetUILanguageTag().getBcp47(); |
243 | 0 | if (m_aUILanguage != aCurrLang) |
244 | 0 | Build(); |
245 | 0 | } |
246 | | |
247 | | bool SvtLanguageTableImpl::HasType( const LanguageType eType ) const |
248 | 0 | { |
249 | 0 | LanguageType eLang = MsLangId::getReplacementForObsoleteLanguage( eType ); |
250 | 0 | sal_uInt32 nPos = FindIndex(eLang); |
251 | |
|
252 | 0 | return RESARRAY_INDEX_NOTFOUND != nPos && nPos < GetEntryCount(); |
253 | 0 | } |
254 | | |
255 | | bool SvtLanguageTable::HasLanguageType( const LanguageType eType ) |
256 | 0 | { |
257 | 0 | return theLanguageTable().HasType( eType ); |
258 | 0 | } |
259 | | |
260 | | const OUString& SvtLanguageTableImpl::GetString( const LanguageType eType ) const |
261 | 0 | { |
262 | 0 | const LanguageType nLang = MsLangId::getReplacementForObsoleteLanguage( eType); |
263 | 0 | const sal_uInt32 nPos = (eType == LANGUAGE_PROCESS_OR_USER_DEFAULT ? |
264 | 0 | FindIndex(LANGUAGE_SYSTEM) : FindIndex( nLang)); |
265 | |
|
266 | 0 | if ( RESARRAY_INDEX_NOTFOUND != nPos && nPos < GetEntryCount() ) |
267 | 0 | return m_aStrings[nPos].first; |
268 | | |
269 | | // Obtain from ICU, or a geeky but usable-in-a-pinch lang-tag. |
270 | 0 | OUString sLangTag( lcl_getDescription( LanguageTag(nLang))); |
271 | 0 | SAL_WARN("svtools.misc", "Language: 0x" |
272 | 0 | << std::hex << nLang |
273 | 0 | << " with unknown name, so returning generated: " |
274 | 0 | << sLangTag); |
275 | | |
276 | | // And add it to the table, so it is available in all subsequent language boxes. |
277 | 0 | return const_cast<SvtLanguageTableImpl*>(this)->AddEntry( sLangTag, nLang); |
278 | 0 | } |
279 | | |
280 | | const OUString& SvtLanguageTable::GetLanguageString( const LanguageType eType ) |
281 | 0 | { |
282 | 0 | return theLanguageTable().GetString( eType ); |
283 | 0 | } |
284 | | |
285 | | LanguageType SvtLanguageTableImpl::GetType( std::u16string_view rStr ) const |
286 | 0 | { |
287 | 0 | LanguageType eType = LANGUAGE_DONTKNOW; |
288 | 0 | sal_uInt32 nCount = GetEntryCount(); |
289 | |
|
290 | 0 | for ( sal_uInt32 i = 0; i < nCount; ++i ) |
291 | 0 | { |
292 | 0 | if (m_aStrings[i].first == rStr) |
293 | 0 | { |
294 | 0 | eType = GetValue(i); |
295 | 0 | break; |
296 | 0 | } |
297 | 0 | } |
298 | 0 | return eType; |
299 | 0 | } |
300 | | |
301 | | LanguageType SvtLanguageTable::GetLanguageType( std::u16string_view rStr ) |
302 | 0 | { |
303 | 0 | return theLanguageTable().GetType( rStr ); |
304 | 0 | } |
305 | | |
306 | | sal_uInt32 SvtLanguageTableImpl::GetEntryCount() const |
307 | 0 | { |
308 | 0 | return m_aStrings.size(); |
309 | 0 | } |
310 | | |
311 | | sal_uInt32 SvtLanguageTable::GetLanguageEntryCount() |
312 | 0 | { |
313 | 0 | return theLanguageTable().GetEntryCount(); |
314 | 0 | } |
315 | | |
316 | | |
317 | | LanguageType SvtLanguageTableImpl::GetTypeAtIndex( sal_uInt32 nIndex ) const |
318 | 0 | { |
319 | 0 | LanguageType nType = LANGUAGE_DONTKNOW; |
320 | 0 | if (nIndex < GetEntryCount()) |
321 | 0 | nType = GetValue(nIndex); |
322 | 0 | return nType; |
323 | 0 | } |
324 | | |
325 | | LanguageType SvtLanguageTable::GetLanguageTypeAtIndex( sal_uInt32 nIndex ) |
326 | 0 | { |
327 | 0 | return theLanguageTable().GetTypeAtIndex( nIndex); |
328 | 0 | } |
329 | | |
330 | | const OUString& SvtLanguageTableImpl::AddEntry( const OUString& rString, const LanguageType eType ) |
331 | 0 | { |
332 | 0 | if (LanguageTag::isOnTheFlyID(eType) |
333 | 0 | && LanguageTag::getOnTheFlyScriptType(eType) == LanguageTag::ScriptType::UNKNOWN) |
334 | 0 | { |
335 | | // Classify the script type to distribute the entry into the proper |
336 | | // language list later. |
337 | 0 | LanguageTag aLanguageTag(eType); |
338 | 0 | const sal_Int16 nScriptClass = unicode::getScriptClassFromLanguageTag( aLanguageTag); |
339 | 0 | LanguageTag::ScriptType eScriptType; |
340 | 0 | switch (nScriptClass) |
341 | 0 | { |
342 | 0 | default: |
343 | 0 | eScriptType = LanguageTag::ScriptType::WESTERN; |
344 | 0 | assert(!"unexpected ScriptType"); |
345 | 0 | break; |
346 | 0 | case css::i18n::ScriptType::WEAK: |
347 | 0 | case css::i18n::ScriptType::LATIN: |
348 | 0 | eScriptType = LanguageTag::ScriptType::WESTERN; |
349 | 0 | break; |
350 | 0 | case css::i18n::ScriptType::ASIAN: |
351 | 0 | eScriptType = LanguageTag::ScriptType::CJK; |
352 | 0 | break; |
353 | 0 | case css::i18n::ScriptType::COMPLEX: |
354 | | /* TODO: determine if it would be LanguageTag::ScriptType::RTL |
355 | | * instead; could that be done by |
356 | | * getScriptClassFromLanguageTag() as well by asking Unicode |
357 | | * properties? */ |
358 | 0 | eScriptType = LanguageTag::ScriptType::CTL; |
359 | 0 | break; |
360 | 0 | } |
361 | 0 | aLanguageTag.setScriptType( eScriptType); |
362 | 0 | } |
363 | 0 | return AddItem( rString, eType); |
364 | 0 | } |
365 | | |
366 | | void SvtLanguageTable::AddLanguageTag( const LanguageTag& rLanguageTag ) |
367 | 0 | { |
368 | 0 | theLanguageTable().AddEntry( lcl_getDescription(rLanguageTag), rLanguageTag.getLanguageType()); |
369 | 0 | } |
370 | | |
371 | | /* vim:set shiftwidth=4 softtabstop=4 expandtab: */ |