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