Krita Source Code Documentation
Loading...
Searching...
No Matches
kswitchlanguagedialog_p.cpp
Go to the documentation of this file.
1/*
2 * This file is part of the KDE Libraries
3 * SPDX-FileCopyrightText: 2007 Krzysztof Lichota (lichota@mimuw.edu.pl)
4 *
5 * SPDX-License-Identifier: LGPL-2.0-or-later
6 *
7 */
8
9#include <QApplication>
10#include <QDialogButtonBox>
11#include <QDir>
12#include <QLayout>
13#include <QLabel>
14#include <QPushButton>
15#include <QEvent>
16#include <QMap>
17#include <QSettings>
18#include <QSharedPointer>
19#include <QStandardPaths>
20#include <QDebug>
21
23
24#include <klanguagebutton.h>
25#include <klocalizedstring.h>
26#include <kmessagebox.h>
27
28// On Android, KF5I18n's loadMessageCatalog function is unbelievably,
29// unusably slow when setting a fallback language, causing Krita's startup
30// time to balloon several minutes long. Creating dialogs or other widgets
31// also ends up taking forever. Since it's non-functional anyway, we'll
32// disable the ability to set a fallback language. Other operating systems
33// don't seem to have this problem. If/when removing this, also remove the
34// matching check in main.cc!
35#ifdef Q_OS_ANDROID
36static constexpr bool ALLOW_FALLBACK_LANGUAGES = false;
37#else
38static constexpr bool ALLOW_FALLBACK_LANGUAGES = true;
39#endif
40
41// Believe it or not we can't use KConfig from here
42// (we need KConfig during QCoreApplication ctor which is too early for it)
43// So we cooked a QSettings based solution
45
47{
48 const QString configPath = QStandardPaths::writableLocation(QStandardPaths::GenericConfigLocation);
49 const QDir configDir(configPath);
50 if (!configDir.exists()) {
51 configDir.mkpath(QStringLiteral("."));
52 }
53 return QSettingsPtr(new QSettings(configPath + QStringLiteral("/klanguageoverridesrc"), QSettings::IniFormat));
54}
55
56static QByteArray getApplicationSpecificLanguage(const QByteArray &defaultCode = QByteArray())
57{
59 settings->beginGroup(QStringLiteral("Language"));
60 //qDebug() << "our language" << settings->value(qAppName(), defaultCode).toByteArray();
61 return settings->value(qAppName(), defaultCode).toByteArray();
62}
63
64static void setApplicationSpecificLanguage(const QByteArray &languageCode)
65{
67 settings->beginGroup(QStringLiteral("Language"));
68
69 if (languageCode.isEmpty()) {
70 settings->remove(qAppName());
71 } else {
72 settings->setValue(qAppName(), languageCode);
73 }
74}
75
77{
78 const QByteArray languageCode = getApplicationSpecificLanguage();
79
80 if (!languageCode.isEmpty()) {
81 QByteArray languages = qgetenv("LANGUAGE");
82 if (languages.isEmpty()) {
83 qputenv("LANGUAGE", languageCode);
84 } else {
85 QByteArray ba(languageCode);
86 ba.append(":");
87 ba.append(languages);
88 qputenv("LANGUAGE", ba);
89 }
90 }
91 //qDebug() << ">>>>>>>>>>>>>> LANGUAGE" << qgetenv("LANGUAGE");
92 //qDebug() << ">>>>>>>>>>>>>> DATADIRS" << qgetenv("XDG_DATA_DIRS");
93}
94
95Q_COREAPP_STARTUP_FUNCTION(initializeLanguages)
96
97namespace KDEPrivate
98{
99
101
102 QLabel *label {nullptr};
104 QPushButton *removeButton {nullptr};
105
107 QLabel *label,
109 QPushButton *removeButton
110 )
111 {
112 this->label = label;
113 this->languageButton = languageButton;
114 this->removeButton = removeButton;
115 }
116
117};
118
120{
121public:
123
124 KisKSwitchLanguageDialog *p {nullptr}; //parent class
125
130
134 void addLanguageButton(const QString &languageCode, bool primaryLanguage);
135
140
141 QMap<QPushButton *, LanguageRowData> languageRows;
143 QGridLayout *languagesLayout {nullptr};
144};
145
146/*************************** KisKSwitchLanguageDialog **************************/
147
149 : QDialog(parent),
151{
152 setWindowTitle(i18n("Switch Application Language"));
153
154 QVBoxLayout *topLayout = new QVBoxLayout(this);
155
156 QLabel *label = new QLabel(i18n("Please choose the language which should be used for this application:"), this);
157 topLayout->addWidget(label);
158
159 QHBoxLayout *languageHorizontalLayout = new QHBoxLayout();
160 topLayout->addLayout(languageHorizontalLayout);
161
162 d->languagesLayout = new QGridLayout();
163 languageHorizontalLayout->addLayout(d->languagesLayout);
164 languageHorizontalLayout->addStretch();
165
166 const QStringList defaultLanguages = d->applicationLanguageList();
167
168 int count = defaultLanguages.count();
169 if (!ALLOW_FALLBACK_LANGUAGES && count > 1) {
170 count = 1;
171 }
172
173 for (int i = 0; i < count; ++i) {
174 QString language = defaultLanguages[i];
175 bool primaryLanguage = (i == 0);
176 d->addLanguageButton(language, primaryLanguage);
177 }
178
179 if (!count) {
180 QLocale l;
181 d->addLanguageButton(l.name(), true);
182 }
183
185 QHBoxLayout *addButtonHorizontalLayout = new QHBoxLayout();
186 topLayout->addLayout(addButtonHorizontalLayout);
187
188 QPushButton *addLangButton = new QPushButton(i18n("Add Fallback Language"), this);
189 addLangButton->setToolTip(i18n("Adds one more language which will be used if other translations do not contain a proper translation."));
190 connect(addLangButton, SIGNAL(clicked()), this, SLOT(slotAddLanguageButton()));
191 addButtonHorizontalLayout->addWidget(addLangButton);
192 addButtonHorizontalLayout->addStretch();
193 }
194
195 topLayout->addStretch(10);
196
197 QDialogButtonBox *buttonBox = new QDialogButtonBox(this);
198 buttonBox->setStandardButtons(QDialogButtonBox::Ok
199 | QDialogButtonBox::Cancel
200 | QDialogButtonBox::RestoreDefaults);
201 KGuiItem::assign(buttonBox->button(QDialogButtonBox::Ok), KStandardGuiItem::ok());
202 KGuiItem::assign(buttonBox->button(QDialogButtonBox::Cancel), KStandardGuiItem::cancel());
203 KGuiItem::assign(buttonBox->button(QDialogButtonBox::RestoreDefaults), KStandardGuiItem::defaults());
204
205 topLayout->addWidget(buttonBox);
206
207 connect(buttonBox, SIGNAL(accepted()), this, SLOT(slotOk()));
208 connect(buttonBox, SIGNAL(accepted()), this, SLOT(accept()));
209 connect(buttonBox, SIGNAL(rejected()), this, SLOT(reject()));
210 connect(buttonBox->button(QDialogButtonBox::RestoreDefaults), SIGNAL(clicked()),
211 this, SLOT(slotDefault()));
212}
213
218
220{
221 //adding new button with en_US as it should always be present
222 d->addLanguageButton(QStringLiteral("en_US"), d->languageButtons.isEmpty());
223}
224
226{
227 QObject const *signalSender = sender();
228 if (!signalSender) {
229 qCritical() << "KisKSwitchLanguageDialog::removeButtonClicked() called directly, not using signal" << Qt::endl;
230 return;
231 }
232
233 QPushButton *removeButton = const_cast<QPushButton *>(::qobject_cast<const QPushButton *>(signalSender));
234 if (!removeButton) {
235 qCritical() << "KisKSwitchLanguageDialog::removeButtonClicked() called from something else than QPushButton" << Qt::endl;
236 return;
237 }
238
239 QMap<QPushButton *, LanguageRowData>::iterator it = d->languageRows.find(removeButton);
240 if (it == d->languageRows.end()) {
241 qCritical() << "KisKSwitchLanguageDialog::removeButtonClicked called from unknown QPushButton" << Qt::endl;
242 return;
243 }
244
245 LanguageRowData languageRowData = it.value();
246
247 d->languageButtons.removeAll(languageRowData.languageButton);
248
249 languageRowData.label->deleteLater();
250 languageRowData.languageButton->deleteLater();
251 languageRowData.removeButton->deleteLater();
252 d->languageRows.erase(it);
253}
254
256{
257 Q_UNUSED(languageCode);
258#if 0
259 for (int i = 0, count = d->languageButtons.count(); i < count; ++i) {
260 KLanguageButton *languageButton = d->languageButtons[i];
261 if (languageButton->current() == languageCode) {
262 //update all buttons which have matching id
263 //might update buttons which were not changed, but well...
264 languageButton->setText(KLocale::global()->languageCodeToName(languageCode));
265 }
266 }
267#endif
268}
269
271{
272 QStringList languages;
273
274 for (int i = 0, count = d->languageButtons.count(); i < count; ++i) {
275 KLanguageButton *languageButton = d->languageButtons[i];
276 languages << languageButton->current();
277 }
278
279 if (d->applicationLanguageList() != languages) {
280 QString languageString = languages.join(QLatin1Char(':'));
281 //list is different from defaults or saved languages list
282 setApplicationSpecificLanguage(languageString.toLatin1());
283
284 QMessageBox::information(this,
285 i18nc("@title:window:", "Application Language Changed"), //caption
286 i18n("The language for this application has been changed. The change will take effect the next time the application is started."));
287 }
288
289 accept();
290}
291
293{
294 setApplicationSpecificLanguage(QByteArray());
295 accept();
296}
297
298/************************ KisKSwitchLanguageDialogPrivate ***********************/
299
302 : p(parent)
303{
304 //NOTE: do NOT use "p" in constructor, it is not fully constructed
305}
306
307static bool stripCountryCode(QString *languageCode)
308{
309 const int idx = languageCode->indexOf(QLatin1String("_"));
310 if (idx != -1) {
311 *languageCode = languageCode->left(idx);
312 return true;
313 }
314 return false;
315}
316
318{
319 QLocale defaultLocale;
320 QLocale cLocale(QLocale::C);
321 QLocale::setDefault(cLocale);
322 QSet<QString> insertedLanguages;
323
324 const QList<QLocale> allLocales = QLocale::matchingLocales(QLocale::AnyLanguage, QLocale::AnyScript, QLocale::AnyCountry);
325 Q_FOREACH (const QLocale &l, allLocales) {
326 QString languageCode = l.name();
327 if (l != cLocale) {
328 const QString nativeName = l.nativeLanguageName();
329 // For some languages the native name might be empty.
330 // In this case use the non native language name as fallback.
331 // See: QTBUG-51323
332 const QString languageName = nativeName.isEmpty() ? QLocale::languageToString(l.language()) : nativeName;
333 if (!insertedLanguages.contains(languageCode) && KLocalizedString::isApplicationTranslatedInto(languageCode)) {
334 QString displayName;
335 // Check if languageCode contains a territory name.
336 // For en and en_GB their native names already contain "American"
337 // and "British", so no need to append the territory name.
338 // Same applies for zh_CN and zh_TW, however we will need to
339 // disambiguate between zh_TW and zh_HK if it is eventually
340 // added.
341#if QT_VERSION < QT_VERSION_CHECK(6, 2, 0)
342 if (l.language() != QLocale::English && l.language() != QLocale::Chinese && languageCode.contains('_')) {
343 QString territoryName = l.nativeCountryName();
344 // Fallback just in case.
345 if (territoryName.isEmpty()) {
346 territoryName = QLocale::countryToString(l.country());
347#else
348 if (l.language() != QLocale::English && l.language() != QLocale::Chinese && languageCode.contains('_')) {
349 QString territoryName = l.nativeTerritoryName();
350 // Fallback just in case.
351 if (territoryName.isEmpty()) {
352 territoryName = QLocale::territoryToString(l.territory());
353#endif
354 }
355 // Append the territory name for disambiguation.
356 displayName = languageName % " (" % territoryName % ")";
357 } else {
358 displayName = languageName;
359 }
360 button->insertLanguage(languageCode, displayName);
361 insertedLanguages << languageCode;
362 } else if (stripCountryCode(&languageCode)) {
363 if (!insertedLanguages.contains(languageCode) && KLocalizedString::isApplicationTranslatedInto(languageCode)) {
364 button->insertLanguage(languageCode, languageName);
365 insertedLanguages << languageCode;
366 }
367 }
368 }
369 }
370
371 QLocale::setDefault(defaultLocale);
372}
373
375{
376 QStringList languagesList;
377
378 QByteArray languageCode = getApplicationSpecificLanguage();
379 if (!languageCode.isEmpty()) {
380 languagesList = QString::fromLatin1(languageCode).split(QLatin1Char(':'));
381 }
382 if (languagesList.isEmpty()) {
383 QLocale l;
384 languagesList = l.uiLanguages();
385
386 // We get en-US here but we use en_US
387 for (int i = 0; i < languagesList.count(); ++i) {
388 languagesList[i].replace(QLatin1String("-"), QLatin1String("_"));
389 }
390 }
391
392 for (int i = 0; i < languagesList.count();) {
393 QString languageCode = languagesList[i];
394 if (!KLocalizedString::isApplicationTranslatedInto(languageCode)) {
395 if (stripCountryCode(&languageCode)) {
396 if (KLocalizedString::isApplicationTranslatedInto(languageCode)) {
397 languagesList[i] = languageCode;
398 ++i;
399 continue;
400 }
401 }
402 languagesList.removeAt(i);
403 } else {
404 ++i;
405 }
406 }
407
408 return languagesList;
409}
410
411void KisKSwitchLanguageDialogPrivate::addLanguageButton(const QString &languageCode, bool primaryLanguage)
412{
413 QString labelText = primaryLanguage ? i18n("Primary language:") : i18n("Fallback language:");
414
415 KLanguageButton *languageButton = new KLanguageButton(p);
416
417 fillApplicationLanguages(languageButton);
418
419 languageButton->setCurrentItem(languageCode);
420
421 QObject::connect(
422 languageButton,
423 SIGNAL(activated(QString)),
424 p,
425 SLOT(languageOnButtonChanged(QString))
426 );
427
428 LanguageRowData languageRowData;
429 QPushButton *removeButton = 0;
430
431 if (!primaryLanguage) {
432 removeButton = new QPushButton(i18n("Remove"), p);
433
434 QObject::connect(
435 removeButton,
436 SIGNAL(clicked()),
437 p,
438 SLOT(removeButtonClicked())
439 );
440 }
441
442 languageButton->setToolTip(primaryLanguage
443 ? i18n("This is the main application language which will be used first, before any other languages.")
444 : i18n("This is the language which will be used if any previous languages do not contain a proper translation."));
445
446 int numRows = languagesLayout->rowCount();
447
448 QLabel *languageLabel = new QLabel(labelText, p);
449 languagesLayout->addWidget(languageLabel, numRows + 1, 1, Qt::AlignLeft);
450 languagesLayout->addWidget(languageButton, numRows + 1, 2, Qt::AlignLeft);
451
452 if (!primaryLanguage) {
453 languagesLayout->addWidget(removeButton, numRows + 1, 3, Qt::AlignLeft);
454 languageRowData.setRowWidgets(languageLabel, languageButton, removeButton);
455 removeButton->show();
456 }
457
458 languageRows.insert(removeButton, languageRowData);
459
460 languageButtons.append(languageButton);
461 languageButton->show();
462 languageLabel->show();
463}
464
465}
const Params2D p
KisKSwitchLanguageDialogPrivate(KisKSwitchLanguageDialog *parent)
void addLanguageButton(const QString &languageCode, bool primaryLanguage)
QMap< QPushButton *, LanguageRowData > languageRows
Standard "switch application language" dialog box.
virtual void languageOnButtonChanged(const QString &)
KisKSwitchLanguageDialogPrivate *const d
QString current() const
void setCurrentItem(const QString &languageCode)
void setText(const QString &text)
QString button(const QWheelEvent &ev)
static QSettingsPtr localeOverridesSettings()
static QByteArray getApplicationSpecificLanguage(const QByteArray &defaultCode=QByteArray())
static void setApplicationSpecificLanguage(const QByteArray &languageCode)
QSharedPointer< QSettings > QSettingsPtr
static void initializeLanguages()
static constexpr bool ALLOW_FALLBACK_LANGUAGES
static bool stripCountryCode(QString *languageCode)
void setRowWidgets(QLabel *label, KLanguageButton *languageButton, QPushButton *removeButton)