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 }