File indexing completed on 2024-05-19 05:49:21

0001 /*
0002   Copyright (C) 2014-2021 Harald Sitter <apachelogger@kubuntu.org>
0003 
0004   This program is free software; you can redistribute it and/or
0005   modify it under the terms of the GNU General Public License as
0006   published by the Free Software Foundation; either version 2 of
0007   the License or (at your option) version 3 or any later version
0008   accepted by the membership of KDE e.V. (or its successor approved
0009   by the membership of KDE e.V.), which shall act as a proxy
0010   defined in Section 14 of version 3 of the license.
0011 
0012   This program is distributed in the hope that it will be useful,
0013   but WITHOUT ANY WARRANTY; without even the implied warranty of
0014   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
0015   GNU General Public License for more details.
0016 
0017   You should have received a copy of the GNU General Public License
0018   along with this program.  If not, see <http://www.gnu.org/licenses/>.
0019 */
0020 
0021 #include "l10nevent.h"
0022 
0023 #include <QDebug>
0024 
0025 #include <KConfigGroup>
0026 #include <KToolInvocation>
0027 #include <KSharedConfig>
0028 
0029 #include <Kubuntu/l10n_language.h>
0030 #include <Kubuntu/l10n_languagecollection.h>
0031 
0032 L10nEvent::L10nEvent(QObject *parent)
0033     : Event(parent, "L10n")
0034     , m_languageCollection(nullptr)
0035 {
0036     // We only want this notification once for now.
0037     // NOTE: might be viable to watch the config and apt lock to
0038     //       issue a notification when the user installs software that
0039     //       requires additional language support packages.
0040     show(); // noop if nothing needs showing
0041 }
0042 
0043 L10nEvent::~L10nEvent()
0044 {
0045 }
0046 
0047 void L10nEvent::show()
0048 {
0049     if (isHidden()) {
0050         return;
0051     }
0052     if (!m_languageCollection) {
0053         m_languageCollection = new Kubuntu::LanguageCollection(this);
0054         connect(m_languageCollection, SIGNAL(updated()),
0055                 this, SLOT(showOnLanguageCollectionUpdated()));
0056     }
0057     if (!m_languageCollection->isUpdated()) {
0058         m_languageCollection->update();
0059         return;
0060     }
0061     showOnLanguageCollectionUpdated();
0062 }
0063 
0064 void L10nEvent::showOnLanguageCollectionUpdated()
0065 {
0066     const KSharedConfig::Ptr userConfig = KSharedConfig::openConfig("kdeglobals", KConfig::IncludeGlobals);
0067     const KConfigGroup userSettings = KConfigGroup(userConfig, "Locale");
0068     const QString languageConfigString = userSettings.readEntry("Language", QString());
0069     const QStringList kdeLanguageList = languageConfigString.split(QLatin1Char(':'),
0070                                                                    QString::SkipEmptyParts);
0071     qDebug() << "KDE Languages:" << kdeLanguageList;
0072 
0073     m_missingPackages.clear();
0074 
0075     // languages() at the time of writing has no caching capability, so make sure
0076     // that it is not called more than necessary.
0077     const QSet<Kubuntu::Language *> languages = m_languageCollection->languages();
0078 
0079     // Check if we can find the kde languages and if they are complete.
0080     foreach (const QString &kdeLanguage, kdeLanguageList) {
0081         foreach (Kubuntu::Language *language, languages) {
0082             if (kdeLanguage == language->kdeLanguageCode()) {
0083                 qDebug() << "matched" << kdeLanguage;
0084                 checkForMissingPackages(language);
0085             }
0086         }
0087     }
0088 
0089     // Additionally check the system locale.
0090     // This is necessary because
0091     //   - after installation there won't be a KCM config, but a possibly
0092     //     incomplete language support.
0093     //   - if the user mangles his locale manually or through other tools
0094     //     we still want him to complete his language support.
0095     const QStringList matchables = systemLocaleMatchables();
0096     qDebug() << "System Language Matchables:" << matchables;
0097     bool matched = false;
0098     foreach (const QString &matchable, matchables) {
0099         foreach (Kubuntu::Language *language, languages) {
0100             if (matchable == language->kdeLanguageCode()) {
0101                 qDebug() << "matched" << matchable;
0102                 checkForMissingPackages(language);
0103                 // If we had a match we abort as we only want the most generic
0104                 // match.
0105                 // e.g. we want 'ca@valencia' but not 'ca'.
0106                 matched = true;
0107             }
0108         }
0109         if (matched) {
0110             continue;
0111         }
0112     }
0113 
0114     m_missingPackages.removeDuplicates();
0115 
0116     if (m_missingPackages.isEmpty()) {
0117         return;
0118     }
0119 
0120     QString icon = QString("preferences-desktop-locale");
0121     QString text(i18nc("Notification when additional packages are required for complete system localization",
0122                        "Language support is incomplete, additional packages are required"));
0123     QStringList actions;
0124     actions << i18nc("Installs additional localization packages", "Install");
0125     actions << i18nc("Button to dismiss this notification once", "Ignore for now");
0126     actions << i18nc("Button to make this notification never show up again",
0127                      "Never show again");
0128     Event::show(icon, text, actions);
0129 }
0130 
0131 void L10nEvent::run()
0132 {
0133     qDebug() << m_missingPackages;
0134     if (!m_missingPackages.isEmpty()) {
0135         QStringList args;
0136         args.append("--install");
0137         args.append(m_missingPackages);
0138         KToolInvocation::kdeinitExec("qapt-batch", args);
0139     }
0140     Event::run();
0141 }
0142 
0143 bool L10nEvent::checkForMissingPackages(Kubuntu::Language *language)
0144 {
0145     // Not cached, so cache here.
0146     const bool isSupportComplete = language->isSupportComplete();
0147     qDebug() << "  completeness:" << isSupportComplete;
0148     if (!isSupportComplete) {
0149         m_missingPackages.append(language->missingPackages());
0150         return true;
0151     }
0152     return false;
0153 }
0154 
0155 QStringList L10nEvent::systemLocaleMatchables() const
0156 {
0157     const QString systemLocale = qgetenv("LANG");
0158 
0159     // Valid locales may be C or LANGUAGE followed by country and/or encoding
0160     // and/or variant. Leading to the following regex:
0161     // Please note that the regex is a bit blown up because greedy matching
0162     // requires us to except the seperator characters to get the intended result.
0163     //                   v language exactly once
0164     //                   .            v country one match at the most
0165     //                   .            .               v encoding one match at the most
0166     //                   .            .               .              v variant one match at the most
0167     QRegExp localeRegex("([^_\\.\\@]+)(_([^\\.\\@]+))?(\\.([^\\@]+))?(\\@(.+))?");
0168     if (localeRegex.indexIn(systemLocale) == -1) {
0169         return QStringList();
0170     }
0171     qDebug() << localeRegex.capturedTexts();
0172 
0173     const QString language = localeRegex.capturedTexts().at(1);
0174     const QString country = localeRegex.capturedTexts().at(3);
0175     // Not useful for language resolution:
0176     // const QString encoding = localeRegex.capturedTexts().at(5);
0177     const QString variant = localeRegex.capturedTexts().at(7);
0178 
0179     QStringList matchables;
0180     matchables.reserve(4); // We can only ever match 4.
0181 
0182     if (!language.isEmpty()) { // Language cannot really be empty, but oh well.
0183         matchables.prepend(language);
0184     }
0185     if (!language.isEmpty() && !country.isEmpty()) {
0186         matchables.prepend(QString::fromLatin1("%1_%2").arg(language, country));
0187     }
0188     if (!language.isEmpty() && !variant.isEmpty()) {
0189         matchables.prepend(QString::fromLatin1("%1@%2").arg(language, variant));
0190     }
0191     if (!language.isEmpty() && !country.isEmpty() && !variant.isEmpty()) {
0192         matchables.prepend(QString::fromLatin1("%1_%2@%3").arg(language, country, variant));
0193     }
0194     return matchables;
0195 }