Warning, file /plasma/plasma-workspace/kcms/region_language/languagelistmodel.cpp was not indexed or was modified since last indexation (in which case cross-reference links may be missing, inaccurate or erroneous).
0001 /* 0002 languagelistmodel.h 0003 SPDX-FileCopyrightText: 2021 Han Young <hanyoung@protonmail.com> 0004 SPDX-FileCopyrightText: 2019 Kevin Ottens <kevin.ottens@enioka.com> 0005 SPDX-FileCopyrightText: 2023 Serenity Cybersecurity, LLC <license@futurecrew.ru> 0006 Author: Gleb Popov <arrowd@FreeBSD.org> 0007 SPDX-License-Identifier: GPL-2.0-or-later 0008 */ 0009 0010 #include "languagelistmodel.h" 0011 #include "exampleutility.h" 0012 #include "kcm_regionandlang_debug.h" 0013 #include "kcmregionandlang.h" 0014 #include "regionandlangsettings.h" 0015 0016 using namespace KCM_RegionAndLang; 0017 0018 LanguageListModel::LanguageListModel(QObject *parent) 0019 : QAbstractListModel(parent) 0020 , m_selectedLanguageModel(new SelectedLanguageModel(this)) 0021 { 0022 connect(this, &LanguageListModel::isPreviewExampleChanged, this, &LanguageListModel::exampleChanged); 0023 connect(m_selectedLanguageModel, &SelectedLanguageModel::exampleChanged, this, &LanguageListModel::exampleChanged); 0024 /* explicitly set pt to pt_PT as a workaround for GNU Gettext and CLDR treat the default dialect of 'pt' differently 0025 * 0026 * see https://invent.kde.org/plasma/plasma-workspace/-/merge_requests/2478 0027 * and https://mail.kde.org/pipermail/kde-i18n-doc/2023-January/001340.html 0028 * for more info on the matter 0029 */ 0030 auto availableLanguages = KLocalizedString::availableDomainTranslations("plasmashell"); 0031 if (availableLanguages.contains(QStringLiteral("pt"))) { 0032 availableLanguages.remove(QStringLiteral("pt")); 0033 availableLanguages.insert(QStringLiteral("pt_PT")); 0034 } 0035 0036 m_availableLanguages = availableLanguages.values(); 0037 m_availableLanguages.sort(); 0038 m_availableLanguages.push_front(QStringLiteral("C")); 0039 } 0040 0041 int LanguageListModel::rowCount(const QModelIndex &parent) const 0042 { 0043 Q_UNUSED(parent) 0044 return m_availableLanguages.size(); 0045 } 0046 0047 QVariant LanguageListModel::data(const QModelIndex &index, int role) const 0048 { 0049 const auto row = index.row(); 0050 if (row < 0 || row >= m_availableLanguages.size()) { 0051 return {}; 0052 } 0053 switch (role) { 0054 case NativeName: 0055 return languageCodeToName(m_availableLanguages.at(row)); 0056 case LanguageCode: 0057 return m_availableLanguages.at(row); 0058 case Flag: { 0059 QString flagCode; 0060 const QStringList split = QLocale(m_availableLanguages.at(row)).name().split(QLatin1Char('_')); 0061 if (split.size() > 1) { 0062 flagCode = split[1].toLower(); 0063 } 0064 return QStandardPaths::locate(QStandardPaths::GenericDataLocation, QStringLiteral("kf5/locale/countries/%1/flag.png").arg(flagCode)); 0065 } 0066 } 0067 Q_UNREACHABLE(); 0068 return {}; 0069 } 0070 0071 QHash<int, QByteArray> LanguageListModel::roleNames() const 0072 { 0073 return {{NativeName, QByteArrayLiteral("nativeName")}, {LanguageCode, QByteArrayLiteral("languageCode")}, {Flag, QByteArrayLiteral("flag")}}; 0074 } 0075 0076 QString LanguageListModel::languageCodeToName(const QString &languageCode) 0077 { 0078 const QLocale locale(languageCode); 0079 const QString languageName = locale.nativeLanguageName(); 0080 0081 if (languageName.isEmpty()) { 0082 return languageCode; 0083 } 0084 0085 if (languageCode.contains(QLatin1Char('@'))) { 0086 return i18nc("%1 is language name, %2 is language code name", "%1 (%2)", languageName, languageCode); 0087 } 0088 0089 // KDE languageCode got translated by QLocale to a locale code we also have on 0090 // the list. Currently this only happens with pt that gets translated to pt_BR. 0091 if (languageCode == QStringLiteral("pt_BR")) { 0092 return i18nc("%1 is português in system locale name, Brazil is to distinguish European português and Brazilian português", "%1 (Brazil)", languageName); 0093 } 0094 0095 return languageName; 0096 } 0097 0098 bool LanguageListModel::isSupportedLanguage(const QString &language) const 0099 { 0100 // If the available language list contains the full language string outright, e.g. en_US 0101 if (m_availableLanguages.contains(language)) { 0102 return true; 0103 } 0104 0105 // If the language string passed has a territory attached (like fr_FR) then chop it off, 0106 // and try searching for just the language. 0107 if (language.contains('_')) { 0108 const QString languageName{language.left(language.indexOf('_'))}; 0109 return m_availableLanguages.contains(languageName); 0110 } 0111 0112 return false; 0113 } 0114 0115 int LanguageListModel::currentIndex() const 0116 { 0117 return m_index; 0118 } 0119 0120 void LanguageListModel::setCurrentIndex(int index) 0121 { 0122 if (index == m_index || index < 0 || index >= m_availableLanguages.size()) { 0123 return; 0124 } 0125 0126 m_index = index; 0127 Q_EMIT exampleChanged(); 0128 } 0129 0130 QString LanguageListModel::exampleHelper(const std::function<QString(const QLocale &)> &func) const 0131 { 0132 if (!m_settings) { 0133 return {}; 0134 } 0135 0136 QString result = func(QLocale(m_settings->langWithFallback())); 0137 if (m_isPreviewExample) { 0138 if (m_index < 0) { 0139 result = func(QLocale(m_settings->langWithFallback())); 0140 } else { 0141 result = func(QLocale(m_availableLanguages[m_index])); 0142 } 0143 } 0144 return result; 0145 } 0146 0147 QString LanguageListModel::numberExample() const 0148 { 0149 return exampleHelper(Utility::numericExample); 0150 } 0151 0152 QString LanguageListModel::currencyExample() const 0153 { 0154 return exampleHelper(Utility::monetaryExample); 0155 } 0156 0157 QString LanguageListModel::timeExample() const 0158 { 0159 return exampleHelper(Utility::timeExample); 0160 } 0161 0162 QString LanguageListModel::paperSizeExample() const 0163 { 0164 return exampleHelper(Utility::paperSizeExample); 0165 } 0166 0167 #ifdef LC_ADDRESS 0168 QString LanguageListModel::addressExample() const 0169 { 0170 return exampleHelper(Utility::addressExample); 0171 } 0172 0173 QString LanguageListModel::nameStyleExample() const 0174 { 0175 return exampleHelper(Utility::nameStyleExample); 0176 } 0177 0178 QString LanguageListModel::phoneNumbersExample() const 0179 { 0180 return exampleHelper(Utility::phoneNumbersExample); 0181 } 0182 #endif 0183 0184 QString LanguageListModel::metric() const 0185 { 0186 return exampleHelper(Utility::measurementExample); 0187 } 0188 0189 void LanguageListModel::setRegionAndLangSettings(QObject *settings, QObject *kcm) 0190 { 0191 if (auto *regionandlangsettings = qobject_cast<RegionAndLangSettings *>(settings)) { 0192 if (auto *regionandlangkcm = qobject_cast<KCMRegionAndLang *>(kcm)) { 0193 m_settings = regionandlangsettings; 0194 m_selectedLanguageModel->setRegionAndLangSettings(regionandlangsettings, regionandlangkcm); 0195 Q_EMIT exampleChanged(); 0196 } 0197 } 0198 } 0199 0200 bool LanguageListModel::isPreviewExample() const 0201 { 0202 return m_isPreviewExample; 0203 } 0204 0205 void LanguageListModel::setIsPreviewExample(bool preview) 0206 { 0207 m_isPreviewExample = preview; 0208 } 0209 0210 SelectedLanguageModel::SelectedLanguageModel(LanguageListModel *parent) 0211 : QAbstractListModel(parent) 0212 , m_parent(parent) 0213 { 0214 } 0215 0216 void SelectedLanguageModel::setRegionAndLangSettings(RegionAndLangSettings *settings, KCMRegionAndLang *kcm) 0217 { 0218 m_settings = settings; 0219 m_kcm = kcm; 0220 0221 beginResetModel(); 0222 if (m_settings->language().isEmpty()) { 0223 // no language but have lang 0224 m_selectedLanguages = {m_settings->lang()}; 0225 } else { 0226 // have language, ignore lang 0227 m_selectedLanguages = m_settings->language().split(QLatin1Char(':')); 0228 } 0229 0230 // Chop off the UTF-8 codepoint 0231 for (auto &language : m_selectedLanguages) { 0232 language.remove(QStringLiteral(".UTF-8")); 0233 } 0234 0235 if (m_settings->isDefaultSetting(SettingType::Lang) && m_settings->isDefaultSetting(SettingType::Language)) { 0236 m_hasImplicitLang = true; 0237 Q_EMIT hasImplicitLangChanged(); 0238 } 0239 0240 endResetModel(); 0241 0242 // check for invalid lang 0243 if (!m_selectedLanguages.empty() && !m_parent->isSupportedLanguage(m_selectedLanguages.front())) { 0244 m_unsupportedLanguage = m_selectedLanguages.front(); 0245 Q_EMIT unsupportedLanguageChanged(); 0246 } else if (!m_unsupportedLanguage.isEmpty()) { 0247 m_unsupportedLanguage.clear(); 0248 Q_EMIT unsupportedLanguageChanged(); 0249 } 0250 } 0251 0252 SelectedLanguageModel *LanguageListModel::selectedLanguageModel() const 0253 { 0254 return m_selectedLanguageModel; 0255 } 0256 0257 int SelectedLanguageModel::rowCount(const QModelIndex &parent) const 0258 { 0259 Q_UNUSED(parent) 0260 return m_selectedLanguages.size(); 0261 } 0262 0263 QVariant SelectedLanguageModel::data(const QModelIndex &index, int role) const 0264 { 0265 Q_UNUSED(role) 0266 const auto row = index.row(); 0267 if (row < 0 || row > m_selectedLanguages.size()) { 0268 return {}; 0269 } 0270 // "add Language" Item 0271 if (row == m_selectedLanguages.size()) { 0272 return {}; 0273 } 0274 0275 return LanguageListModel::languageCodeToName(m_selectedLanguages.at(row)); 0276 } 0277 0278 bool SelectedLanguageModel::shouldWarnMultipleLang() const 0279 { 0280 if (m_selectedLanguages.size() >= 2) { 0281 if (m_selectedLanguages.front() == QStringLiteral("en_US")) { 0282 return true; 0283 } 0284 } 0285 return false; 0286 } 0287 0288 void SelectedLanguageModel::move(int from, int to) 0289 { 0290 if (from == to || from < 0 || from >= m_selectedLanguages.size() || to < 0 || to >= m_selectedLanguages.size()) { 0291 return; 0292 } 0293 0294 if (m_hasImplicitLang) { 0295 m_hasImplicitLang = false; 0296 Q_EMIT hasImplicitLangChanged(); 0297 } 0298 0299 beginResetModel(); 0300 m_selectedLanguages.move(from, to); 0301 endResetModel(); 0302 saveLanguages(); 0303 Q_EMIT shouldWarnMultipleLangChanged(); 0304 Q_EMIT exampleChanged(); 0305 } 0306 0307 void SelectedLanguageModel::remove(int index) 0308 { 0309 if (index < 0 || index >= m_selectedLanguages.size()) { 0310 return; 0311 } 0312 beginRemoveRows(QModelIndex(), index, index); 0313 m_selectedLanguages.removeAt(index); 0314 endRemoveRows(); 0315 saveLanguages(); 0316 Q_EMIT shouldWarnMultipleLangChanged(); 0317 Q_EMIT exampleChanged(); 0318 } 0319 0320 void SelectedLanguageModel::addLanguage(const QString &lang) 0321 { 0322 if (lang.isEmpty() || m_selectedLanguages.indexOf(lang) != -1) { 0323 return; 0324 } 0325 0326 // fix Kirigami.SwipeListItem doesn't connect to Actions' visible property. 0327 // Reset model enforce a refresh of delegate 0328 beginResetModel(); 0329 if (m_hasImplicitLang) { 0330 m_hasImplicitLang = false; 0331 Q_EMIT hasImplicitLangChanged(); 0332 } 0333 m_selectedLanguages.push_back(lang); 0334 endResetModel(); 0335 saveLanguages(); 0336 Q_EMIT shouldWarnMultipleLangChanged(); 0337 Q_EMIT exampleChanged(); 0338 } 0339 0340 void SelectedLanguageModel::replaceLanguage(int index, const QString &lang) 0341 { 0342 if (index < 0 || index >= m_selectedLanguages.size() || lang.isEmpty()) { 0343 return; 0344 } 0345 0346 int existLangIndex = m_selectedLanguages.indexOf(lang); 0347 // return if no change, but allow change implicit lang to explicit 0348 if (existLangIndex == index && !m_hasImplicitLang) { 0349 return; 0350 } 0351 0352 beginResetModel(); 0353 m_selectedLanguages[index] = lang; 0354 if (!m_hasImplicitLang) { 0355 // delete duplicate lang 0356 if (existLangIndex != -1) { 0357 m_selectedLanguages.removeAt(existLangIndex); 0358 } 0359 } else { 0360 m_hasImplicitLang = false; 0361 Q_EMIT hasImplicitLangChanged(); 0362 } 0363 endResetModel(); 0364 saveLanguages(); 0365 Q_EMIT shouldWarnMultipleLangChanged(); 0366 Q_EMIT exampleChanged(); 0367 } 0368 0369 void SelectedLanguageModel::saveLanguages() 0370 { 0371 // implicit lang means no change 0372 if (!m_settings || m_hasImplicitLang) { 0373 return; 0374 } 0375 if (m_selectedLanguages.empty()) { 0376 m_settings->setLang(m_settings->defaultLangValue()); 0377 m_settings->config()->group(QStringLiteral("Formats")).deleteEntry("lang"); 0378 m_settings->config()->group(QStringLiteral("Translations")).deleteEntry("language"); 0379 } else { 0380 if (!m_parent->isSupportedLanguage(m_selectedLanguages.front())) { 0381 m_unsupportedLanguage = m_selectedLanguages.front(); 0382 Q_EMIT unsupportedLanguageChanged(); 0383 } else { 0384 if (!m_unsupportedLanguage.isEmpty()) { 0385 m_unsupportedLanguage.clear(); 0386 Q_EMIT unsupportedLanguageChanged(); 0387 } 0388 0389 #ifdef GLIBC_LOCALE 0390 auto glibcLang = m_kcm->toGlibcLocale(m_selectedLanguages.front()); 0391 if (glibcLang.has_value()) { 0392 m_settings->setLang(glibcLang.value()); 0393 } 0394 #else 0395 m_settings->setLang(m_selectedLanguages.front()); 0396 #endif 0397 } 0398 QString languages; 0399 for (auto i = m_selectedLanguages.cbegin(); i != m_selectedLanguages.cend(); i++) { 0400 languages.push_back(*i); 0401 // no ':' at end 0402 if (i + 1 != m_selectedLanguages.cend()) { 0403 languages.push_back(QLatin1Char(':')); 0404 } 0405 } 0406 m_settings->setLanguage(languages); 0407 } 0408 } 0409 0410 bool SelectedLanguageModel::hasImplicitLang() const 0411 { 0412 return m_hasImplicitLang; 0413 } 0414 0415 const QString &SelectedLanguageModel::unsupportedLanguage() const 0416 { 0417 return m_unsupportedLanguage; 0418 }