File indexing completed on 2024-03-24 04:02:27

0001 /*
0002     This file is part of the KDE Libraries
0003     SPDX-FileCopyrightText: 2007 Krzysztof Lichota <lichota@mimuw.edu.pl>
0004 
0005     SPDX-License-Identifier: LGPL-2.0-or-later
0006 */
0007 
0008 #include "kswitchlanguagedialog_p.h"
0009 
0010 #include "debug.h"
0011 
0012 #include <QApplication>
0013 #include <QDialogButtonBox>
0014 #include <QDir>
0015 #include <QGridLayout>
0016 #include <QLabel>
0017 #include <QMap>
0018 #include <QPushButton>
0019 #include <QSettings>
0020 #include <QStandardPaths>
0021 #include <private/qlocale_p.h>
0022 
0023 #include <KLanguageButton>
0024 #include <KLocalizedString>
0025 #include <KMessageBox>
0026 
0027 // Believe it or not we can't use KConfig from here
0028 // (we need KConfig during QCoreApplication ctor which is too early for it)
0029 // So we cooked a QSettings based solution
0030 static std::unique_ptr<QSettings> localeOverridesSettings()
0031 {
0032     const QString configPath = QStandardPaths::writableLocation(QStandardPaths::GenericConfigLocation);
0033     const QDir configDir(configPath);
0034     if (!configDir.exists()) {
0035         configDir.mkpath(QStringLiteral("."));
0036     }
0037 
0038     return std::make_unique<QSettings>(configPath + QLatin1String("/klanguageoverridesrc"), QSettings::IniFormat);
0039 }
0040 
0041 static QByteArray getApplicationSpecificLanguage(const QByteArray &defaultCode = QByteArray())
0042 {
0043     std::unique_ptr<QSettings> settings = localeOverridesSettings();
0044     settings->beginGroup(QStringLiteral("Language"));
0045     return settings->value(qAppName(), defaultCode).toByteArray();
0046 }
0047 
0048 namespace KDEPrivate
0049 {
0050 Q_COREAPP_STARTUP_FUNCTION(initializeLanguages)
0051 
0052 void setApplicationSpecificLanguage(const QByteArray &languageCode)
0053 {
0054     std::unique_ptr<QSettings> settings = localeOverridesSettings();
0055     settings->beginGroup(QStringLiteral("Language"));
0056 
0057     if (languageCode.isEmpty()) {
0058         settings->remove(qAppName());
0059     } else {
0060         settings->setValue(qAppName(), languageCode);
0061     }
0062 }
0063 
0064 void initializeLanguages()
0065 {
0066     const QByteArray languageCode = getApplicationSpecificLanguage();
0067 
0068     if (!languageCode.isEmpty()) {
0069         QByteArray languages = qgetenv("LANGUAGE");
0070         if (languages.isEmpty()) {
0071             qputenv("LANGUAGE", languageCode);
0072         } else {
0073             qputenv("LANGUAGE", QByteArray(languageCode + ':' + languages));
0074         }
0075         // Ideally setting the LANGUAGE would change the default QLocale too
0076         // but unfortunately this is too late since the QCoreApplication constructor
0077         // already created a QLocale at this stage so we need to set the reset it
0078         // by triggering the creation and destruction of a QSystemLocale
0079         // this is highly dependent on Qt internals, so may break, but oh well
0080         QSystemLocale *dummy = new QSystemLocale();
0081         delete dummy;
0082     }
0083 }
0084 
0085 struct LanguageRowData {
0086     LanguageRowData()
0087     {
0088         label = nullptr;
0089         languageButton = nullptr;
0090         removeButton = nullptr;
0091     }
0092     QLabel *label;
0093     KLanguageButton *languageButton;
0094     QPushButton *removeButton;
0095 
0096     void setRowWidgets(QLabel *label, KLanguageButton *languageButton, QPushButton *removeButton)
0097     {
0098         this->label = label;
0099         this->languageButton = languageButton;
0100         this->removeButton = removeButton;
0101     }
0102 };
0103 
0104 class KSwitchLanguageDialogPrivate
0105 {
0106 public:
0107     KSwitchLanguageDialogPrivate(KSwitchLanguageDialog *parent);
0108 
0109     KSwitchLanguageDialog *p; // parent class
0110 
0111     /**
0112         Fills language button with names of languages for which given application has translation.
0113     */
0114     void fillApplicationLanguages(KLanguageButton *button);
0115 
0116     /**
0117         Adds one button with language to widget.
0118     */
0119     void addLanguageButton(const QString &languageCode, bool primaryLanguage);
0120 
0121     /**
0122         Returns list of languages chosen for application or default languages is they are not set.
0123     */
0124     QStringList applicationLanguageList();
0125 
0126     QMap<QPushButton *, LanguageRowData> languageRows;
0127     QList<KLanguageButton *> languageButtons;
0128     QGridLayout *languagesLayout;
0129 };
0130 
0131 /*************************** KSwitchLanguageDialog **************************/
0132 
0133 KSwitchLanguageDialog::KSwitchLanguageDialog(QWidget *parent)
0134     : QDialog(parent)
0135     , d(new KSwitchLanguageDialogPrivate(this))
0136 {
0137     setWindowTitle(i18nc("@title:window", "Configure Language"));
0138 
0139     QVBoxLayout *topLayout = new QVBoxLayout(this);
0140 
0141     QLabel *label = new QLabel(i18n("Please choose the language which should be used for this application:"), this);
0142     topLayout->addWidget(label);
0143 
0144     QHBoxLayout *languageHorizontalLayout = new QHBoxLayout();
0145     topLayout->addLayout(languageHorizontalLayout);
0146 
0147     d->languagesLayout = new QGridLayout();
0148     languageHorizontalLayout->addLayout(d->languagesLayout);
0149     languageHorizontalLayout->addStretch();
0150 
0151     const QStringList defaultLanguages = d->applicationLanguageList();
0152 
0153     int count = defaultLanguages.count();
0154     for (int i = 0; i < count; ++i) {
0155         QString language = defaultLanguages[i];
0156         bool primaryLanguage = (i == 0);
0157         d->addLanguageButton(language, primaryLanguage);
0158     }
0159 
0160     if (!count) {
0161         QLocale l;
0162         d->addLanguageButton(l.name(), true);
0163     }
0164 
0165     QHBoxLayout *addButtonHorizontalLayout = new QHBoxLayout();
0166     topLayout->addLayout(addButtonHorizontalLayout);
0167 
0168     QPushButton *addLangButton = new QPushButton(i18nc("@action:button", "Add Fallback Language"), this);
0169     addLangButton->setToolTip(i18nc("@info:tooltip", "Adds one more language which will be used if other translations do not contain a proper translation."));
0170     connect(addLangButton, &QPushButton::clicked, this, &KSwitchLanguageDialog::slotAddLanguageButton);
0171     addButtonHorizontalLayout->addWidget(addLangButton);
0172     addButtonHorizontalLayout->addStretch();
0173 
0174     topLayout->addStretch(10);
0175 
0176     QDialogButtonBox *buttonBox = new QDialogButtonBox(this);
0177     buttonBox->setStandardButtons(QDialogButtonBox::Ok | QDialogButtonBox::Cancel | QDialogButtonBox::RestoreDefaults);
0178     KGuiItem::assign(buttonBox->button(QDialogButtonBox::Ok), KStandardGuiItem::ok());
0179     KGuiItem::assign(buttonBox->button(QDialogButtonBox::Cancel), KStandardGuiItem::cancel());
0180     KGuiItem::assign(buttonBox->button(QDialogButtonBox::RestoreDefaults), KStandardGuiItem::defaults());
0181 
0182     topLayout->addWidget(buttonBox);
0183 
0184     connect(buttonBox, &QDialogButtonBox::accepted, this, &KSwitchLanguageDialog::slotOk);
0185     connect(buttonBox, &QDialogButtonBox::accepted, this, &QDialog::accept);
0186     connect(buttonBox, &QDialogButtonBox::rejected, this, &QDialog::reject);
0187     connect(buttonBox->button(QDialogButtonBox::RestoreDefaults), &QPushButton::clicked, this, &KSwitchLanguageDialog::slotDefault);
0188 }
0189 
0190 KSwitchLanguageDialog::~KSwitchLanguageDialog()
0191 {
0192     delete d;
0193 }
0194 
0195 void KSwitchLanguageDialog::slotAddLanguageButton()
0196 {
0197     // adding new button with en_US as it should always be present
0198     d->addLanguageButton(QStringLiteral("en_US"), d->languageButtons.isEmpty());
0199 }
0200 
0201 void KSwitchLanguageDialog::removeButtonClicked()
0202 {
0203     QObject const *signalSender = sender();
0204     if (!signalSender) {
0205         qCCritical(DEBUG_KXMLGUI) << "KSwitchLanguageDialog::removeButtonClicked() called directly, not using signal";
0206         return;
0207     }
0208 
0209     QPushButton *removeButton = const_cast<QPushButton *>(::qobject_cast<const QPushButton *>(signalSender));
0210     if (!removeButton) {
0211         qCCritical(DEBUG_KXMLGUI) << "KSwitchLanguageDialog::removeButtonClicked() called from something else than QPushButton";
0212         return;
0213     }
0214 
0215     QMap<QPushButton *, LanguageRowData>::iterator it = d->languageRows.find(removeButton);
0216     if (it == d->languageRows.end()) {
0217         qCCritical(DEBUG_KXMLGUI) << "KSwitchLanguageDialog::removeButtonClicked called from unknown QPushButton";
0218         return;
0219     }
0220 
0221     LanguageRowData languageRowData = it.value();
0222 
0223     d->languageButtons.removeAll(languageRowData.languageButton);
0224 
0225     languageRowData.label->deleteLater();
0226     languageRowData.languageButton->deleteLater();
0227     languageRowData.removeButton->deleteLater();
0228     d->languageRows.erase(it);
0229 }
0230 
0231 void KSwitchLanguageDialog::languageOnButtonChanged(const QString &languageCode)
0232 {
0233     Q_UNUSED(languageCode);
0234 #if 0
0235     for (int i = 0, count = d->languageButtons.count(); i < count; ++i) {
0236         KLanguageButton *languageButton = d->languageButtons[i];
0237         if (languageButton->current() == languageCode) {
0238             //update all buttons which have matching id
0239             //might update buttons which were not changed, but well...
0240             languageButton->setText(KLocale::global()->languageCodeToName(languageCode));
0241         }
0242     }
0243 #endif
0244 }
0245 
0246 void KSwitchLanguageDialog::slotOk()
0247 {
0248     QStringList languages;
0249     languages.reserve(d->languageButtons.size());
0250     for (auto *languageButton : std::as_const(d->languageButtons)) {
0251         languages << languageButton->current();
0252     }
0253 
0254     if (d->applicationLanguageList() != languages) {
0255         QString languageString = languages.join(QLatin1Char(':'));
0256         // list is different from defaults or saved languages list
0257         setApplicationSpecificLanguage(languageString.toLatin1());
0258 
0259         KMessageBox::information(
0260             this,
0261             i18n("The language for this application has been changed. The change will take effect the next time the application is started."), // text
0262             i18nc("@title:window", "Application Language Changed"), // caption
0263             QStringLiteral("ApplicationLanguageChangedWarning") // dontShowAgainName
0264         );
0265     }
0266 
0267     accept();
0268 }
0269 
0270 void KSwitchLanguageDialog::slotDefault()
0271 {
0272     const QStringList defaultLanguages = d->applicationLanguageList();
0273 
0274     setApplicationSpecificLanguage(QByteArray());
0275 
0276     // read back the new default
0277     QString language = QString::fromLatin1(getApplicationSpecificLanguage("en_US"));
0278 
0279     if (defaultLanguages != (QStringList() << language)) {
0280         KMessageBox::information(
0281             this,
0282             i18n("The language for this application has been changed. The change will take effect the next time the application is started."), // text
0283             i18n("Application Language Changed"), // caption
0284             QStringLiteral("ApplicationLanguageChangedWarning") // dontShowAgainName
0285         );
0286     }
0287 
0288     accept();
0289 }
0290 
0291 /************************ KSwitchLanguageDialogPrivate ***********************/
0292 
0293 KSwitchLanguageDialogPrivate::KSwitchLanguageDialogPrivate(KSwitchLanguageDialog *parent)
0294     : p(parent)
0295 {
0296     // NOTE: do NOT use "p" in constructor, it is not fully constructed
0297 }
0298 
0299 static bool stripCountryCode(QString *languageCode)
0300 {
0301     const int idx = languageCode->indexOf(QLatin1Char('_'));
0302     if (idx != -1) {
0303         *languageCode = languageCode->left(idx);
0304         return true;
0305     }
0306     return false;
0307 }
0308 
0309 void KSwitchLanguageDialogPrivate::fillApplicationLanguages(KLanguageButton *button)
0310 {
0311     const QLocale cLocale(QLocale::C);
0312     QSet<QString> insertedLanguages;
0313 
0314     const QList<QLocale> allLocales = QLocale::matchingLocales(QLocale::AnyLanguage, QLocale::AnyScript, QLocale::AnyCountry);
0315     for (const QLocale &l : allLocales) {
0316         if (l != cLocale) {
0317             QString languageCode = l.name();
0318             if (!insertedLanguages.contains(languageCode) && KLocalizedString::isApplicationTranslatedInto(languageCode)) {
0319                 button->insertLanguage(languageCode);
0320                 insertedLanguages << languageCode;
0321             } else if (stripCountryCode(&languageCode)) {
0322                 if (!insertedLanguages.contains(languageCode) && KLocalizedString::isApplicationTranslatedInto(languageCode)) {
0323                     button->insertLanguage(languageCode);
0324                     insertedLanguages << languageCode;
0325                 }
0326             }
0327         }
0328     }
0329 }
0330 
0331 QStringList KSwitchLanguageDialogPrivate::applicationLanguageList()
0332 {
0333     QStringList languagesList;
0334 
0335     QByteArray languageCode = getApplicationSpecificLanguage();
0336     if (!languageCode.isEmpty()) {
0337         languagesList = QString::fromLatin1(languageCode).split(QLatin1Char(':'));
0338     }
0339     if (languagesList.isEmpty()) {
0340         QLocale l;
0341         languagesList = l.uiLanguages();
0342 
0343         // We get en-US here but we use en_US
0344         for (auto &language : languagesList) {
0345             language.replace(QLatin1Char('-'), QLatin1Char('_'));
0346         }
0347     }
0348 
0349     for (int i = 0; i < languagesList.count();) {
0350         QString languageCode = languagesList[i];
0351         if (!KLocalizedString::isApplicationTranslatedInto(languageCode)) {
0352             if (stripCountryCode(&languageCode)) {
0353                 if (KLocalizedString::isApplicationTranslatedInto(languageCode)) {
0354                     languagesList[i] = languageCode;
0355                     ++i;
0356                     continue;
0357                 }
0358             }
0359             languagesList.removeAt(i);
0360         } else {
0361             ++i;
0362         }
0363     }
0364 
0365     return languagesList;
0366 }
0367 
0368 void KSwitchLanguageDialogPrivate::addLanguageButton(const QString &languageCode, bool primaryLanguage)
0369 {
0370     QString labelText = primaryLanguage ? i18n("Primary language:") : i18n("Fallback language:");
0371 
0372     KLanguageButton *languageButton = new KLanguageButton(p);
0373     languageButton->showLanguageCodes(true);
0374 
0375     fillApplicationLanguages(languageButton);
0376 
0377     languageButton->setCurrentItem(languageCode);
0378 
0379     QObject::connect(languageButton, &KLanguageButton::activated, p, &KSwitchLanguageDialog::languageOnButtonChanged);
0380 
0381     LanguageRowData languageRowData;
0382     QPushButton *removeButton = nullptr;
0383 
0384     if (!primaryLanguage) {
0385         removeButton = new QPushButton(i18nc("@action:button", "Remove"), p);
0386 
0387         QObject::connect(removeButton, &QPushButton::clicked, p, &KSwitchLanguageDialog::removeButtonClicked);
0388     }
0389 
0390     languageButton->setToolTip(
0391         primaryLanguage ? i18nc("@info:tooltip", "This is the main application language which will be used first, before any other languages.")
0392                         : i18nc("@info:tooltip", "This is the language which will be used if any previous languages do not contain a proper translation."));
0393 
0394     int numRows = languagesLayout->rowCount();
0395 
0396     QLabel *languageLabel = new QLabel(labelText, p);
0397     languagesLayout->addWidget(languageLabel, numRows + 1, 1, Qt::AlignLeft);
0398     languagesLayout->addWidget(languageButton, numRows + 1, 2, Qt::AlignLeft);
0399 
0400     if (!primaryLanguage) {
0401         languagesLayout->addWidget(removeButton, numRows + 1, 3, Qt::AlignLeft);
0402         languageRowData.setRowWidgets(languageLabel, languageButton, removeButton);
0403         removeButton->show();
0404     }
0405 
0406     languageRows.insert(removeButton, languageRowData);
0407 
0408     languageButtons.append(languageButton);
0409     languageButton->show();
0410     languageLabel->show();
0411 }
0412 
0413 }
0414 
0415 #include "moc_kswitchlanguagedialog_p.cpp"