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 }