File indexing completed on 2024-09-08 03:42:33
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"