1/****************************************************************************
2**
3** Copyright (C) 2016 The Qt Company Ltd.
4** Contact: https://www.qt.io/licensing/
5**
6** This file is part of the test suite of the Qt Toolkit.
7**
8** $QT_BEGIN_LICENSE:GPL-EXCEPT$
9** Commercial License Usage
10** Licensees holding valid commercial Qt licenses may use this file in
11** accordance with the commercial license agreement provided with the
12** Software or, alternatively, in accordance with the terms contained in
13** a written agreement between you and The Qt Company. For licensing terms
14** and conditions see https://www.qt.io/terms-conditions. For further
15** information use the contact form at https://www.qt.io/contact-us.
16**
17** GNU General Public License Usage
18** Alternatively, this file may be used under the terms of the GNU
19** General Public License version 3 as published by the Free Software
20** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
21** included in the packaging of this file. Please review the following
22** information to ensure the GNU General Public License requirements will
23** be met: https://www.gnu.org/licenses/gpl-3.0.html.
24**
25** $QT_END_LICENSE$
26**
27****************************************************************************/
28
29
30#include <QtTest/QtTest>
31
32#include <qapplication.h>
33#include <private/qguiapplication_p.h>
34#include <QtCore/QSet>
35#include <QtCore/QFile>
36#include <QtCore/QTranslator>
37#include <QtCore/QTemporaryDir>
38#include <private/qthread_p.h>
39#include <qpa/qplatformtheme.h>
40#include <QtWidgets/QInputDialog>
41#include <QtWidgets/QColorDialog>
42#include <QtWidgets/QDialogButtonBox>
43#include <QtWidgets/QFileDialog>
44#include <QtWidgets/QDesktopWidget>
45
46class tst_languageChange : public QObject
47{
48 Q_OBJECT
49public:
50 tst_languageChange();
51
52public slots:
53 void initTestCase();
54 void cleanupTestCase();
55private slots:
56 void retranslatability_data();
57 void retranslatability();
58
59private:
60 QDialogButtonBox::ButtonLayout m_layout;
61};
62
63
64tst_languageChange::tst_languageChange() :
65 m_layout(QDialogButtonBox::WinLayout)
66{
67 if (const QPlatformTheme *theme = QGuiApplicationPrivate::platformTheme())
68 m_layout = static_cast<QDialogButtonBox::ButtonLayout>(theme->themeHint(hint: QPlatformTheme::DialogButtonBoxLayout).toInt());
69}
70
71void tst_languageChange::initTestCase()
72{
73}
74
75void tst_languageChange::cleanupTestCase()
76{
77}
78/**
79 * Records all calls to translate()
80 */
81class TransformTranslator : public QTranslator
82{
83 Q_OBJECT
84public:
85 TransformTranslator() : QTranslator() {}
86 TransformTranslator(QObject *parent) : QTranslator(parent) {}
87 QString translate(const char *context, const char *sourceText,
88 const char *disambiguation = 0, int = -1) const
89 {
90 QByteArray total(context);
91 total.append(s: "::");
92 total.append(s: sourceText);
93 if (disambiguation) {
94 total.append(s: "::");
95 total.append(s: disambiguation);
96 }
97 m_translations.insert(value: total);
98 QString res;
99 for (int i = 0; i < int(qstrlen(str: sourceText)); ++i) {
100 QChar ch = QLatin1Char(sourceText[i]);
101 if (ch.isLower()) {
102 res.append(c: ch.toUpper());
103 } else if (ch.isUpper()) {
104 res.append(c: ch.toLower());
105 } else {
106 res.append(c: ch);
107 }
108 }
109 return res;
110 }
111
112 virtual bool isEmpty() const { return false; }
113
114public:
115 mutable QSet<QByteArray> m_translations;
116};
117
118// Install the translator and close all application windows after a while to
119// quit the event loop.
120class LanguageTestStateMachine : public QObject
121{
122 Q_OBJECT
123public:
124 LanguageTestStateMachine(QTranslator *translator);
125 void start() { m_timer.start(); }
126
127private slots:
128 void timeout();
129
130private:
131 enum State { InstallTranslator, CloseDialog };
132
133 QTimer m_timer;
134 QTranslator *m_translator;
135 State m_state;
136};
137
138LanguageTestStateMachine::LanguageTestStateMachine(QTranslator *translator) :
139 m_translator(translator), m_state(InstallTranslator)
140{
141 m_timer.setInterval(500);
142 connect(sender: &m_timer, SIGNAL(timeout()), receiver: this, SLOT(timeout()));
143}
144
145void LanguageTestStateMachine::timeout()
146{
147 switch (m_state) {
148 case InstallTranslator:
149 m_timer.stop();
150 QCoreApplication::installTranslator(messageFile: m_translator);
151 m_timer.setInterval(2500);
152 m_timer.start();
153 m_state = CloseDialog;
154 break;
155 case CloseDialog: // Close repeatedly in case file dialog is slow.
156 QApplication::closeAllWindows();
157 break;
158 }
159}
160
161enum DialogType {
162 InputDialog = 1,
163 ColorDialog,
164 FileDialog
165};
166
167typedef QSet<QByteArray> TranslationSet;
168Q_DECLARE_METATYPE(TranslationSet)
169
170void tst_languageChange::retranslatability_data()
171{
172 QTest::addColumn<int>(name: "dialogType");
173 QTest::addColumn<TranslationSet >(name: "expected");
174
175 //next we fill it with data
176 QTest::newRow( dataTag: "QInputDialog" )
177 << int(InputDialog) << (QSet<QByteArray>()
178 << "QPlatformTheme::Cancel");
179
180 QTest::newRow( dataTag: "QColorDialog" )
181 << int(ColorDialog) << (QSet<QByteArray>()
182 << "QPlatformTheme::Cancel"
183 << "QColorDialog::&Sat:"
184 << "QColorDialog::&Add to Custom Colors"
185 << "QColorDialog::&Green:"
186 << "QColorDialog::&Red:"
187 << "QColorDialog::Bl&ue:"
188 << "QColorDialog::A&lpha channel:"
189 << "QColorDialog::&Basic colors"
190 << "QColorDialog::&Custom colors"
191 << "QColorDialog::&Val:"
192 << "QColorDialog::Hu&e:");
193
194 QTest::newRow( dataTag: "QFileDialog" )
195 << int(FileDialog) << (QSet<QByteArray>()
196 << "QFileDialog::All Files (*)"
197 << "QFileDialog::Back"
198 << "QFileDialog::Create New Folder"
199 << "QFileDialog::Detail View"
200#if !defined(Q_OS_MAC) && !defined(Q_OS_WINRT)
201 << "QFileDialog::File"
202#endif
203 << "QFileDialog::Files of type:"
204 << "QFileDialog::Forward"
205 << "QFileDialog::List View"
206 << "QFileDialog::Look in:"
207 << "QFileDialog::Open"
208 << "QFileDialog::Parent Directory"
209 << "QFileDialog::Show "
210 << "QFileDialog::Show &hidden files"
211 << "QFileDialog::&Delete"
212 << "QFileDialog::&New Folder"
213 << "QFileDialog::&Rename"
214 << "QFileSystemModel::Date Modified"
215#ifdef Q_OS_WIN
216 << "QFileSystemModel::My Computer"
217#else
218 << "QFileSystemModel::Computer"
219#endif
220 << "QFileSystemModel::Size"
221#ifdef Q_OS_MAC
222 << "QFileSystemModel::Kind::Match OS X Finder"
223#else
224 << "QFileSystemModel::Type::All other platforms"
225#endif
226// << "QFileSystemModel::%1 KB"
227 << "QPlatformTheme::Cancel"
228 << "QPlatformTheme::Open"
229 << "QFileDialog::File &name:");
230}
231
232void tst_languageChange::retranslatability()
233{
234 QFETCH( int, dialogType);
235 QFETCH( TranslationSet, expected);
236
237 if (m_layout == QDialogButtonBox::GnomeLayout)
238 QSKIP("The input data are not suitable for this layout (QDialogButtonBox::GnomeLayout)");
239
240 // This will always be queried for when a language changes
241 expected.insert(value: "QGuiApplication::QT_LAYOUT_DIRECTION::Translate this string to the string 'LTR' in left-to-right "
242 "languages or to 'RTL' in right-to-left languages (such as Hebrew and Arabic) to "
243 "get proper widget layout.");
244
245 TransformTranslator translator;
246 LanguageTestStateMachine stateMachine(&translator);
247
248 switch (dialogType) {
249 case InputDialog:
250 stateMachine.start();
251 QInputDialog::getInt(parent: 0, title: QLatin1String("title"), label: QLatin1String("label"));
252 break;
253
254 case ColorDialog:
255#ifdef Q_OS_MAC
256 QSKIP("The native color dialog is used on Mac OS");
257#else
258 stateMachine.start();
259 QColorDialog::getColor();
260#endif
261 break;
262 case FileDialog: {
263#ifdef Q_OS_MAC
264 QSKIP("The native file dialog is used on Mac OS");
265#endif
266 QFileDialog dlg;
267 dlg.setOption(option: QFileDialog::DontUseNativeDialog);
268 QString tempDirPattern = QDir::tempPath();
269 if (!tempDirPattern.endsWith(c: QLatin1Char('/')))
270 tempDirPattern += QLatin1Char('/');
271 tempDirPattern += QStringLiteral("languagechangetestdirXXXXXX");
272 QTemporaryDir temporaryDir(tempDirPattern);
273 temporaryDir.setAutoRemove(true);
274 QVERIFY2(temporaryDir.isValid(), qPrintable(temporaryDir.errorString()));
275 const QString finalDir = temporaryDir.path() + QStringLiteral("/finaldir");
276 const QString fooName = temporaryDir.path() + QStringLiteral("/foo");
277 QDir dir;
278 QVERIFY(dir.mkpath(finalDir));
279 QFile fooFile(fooName);
280 QVERIFY(fooFile.open(QIODevice::WriteOnly|QIODevice::Text));
281 fooFile.write(data: "test");
282 fooFile.close();
283 dlg.setDirectory(temporaryDir.path());
284 dlg.setFileMode(QFileDialog::ExistingFiles);
285 dlg.setViewMode(QFileDialog::Detail);
286 stateMachine.start();
287 dlg.exec();
288 QTest::qWait(ms: 3000);
289 break; }
290 }
291
292 // In case we use a Color dialog, we do not want to test for
293 // strings non existing in the dialog and which do not get
294 // translated.
295 const QSize desktopSize = QApplication::desktop()->size();
296 if (dialogType == ColorDialog && (desktopSize.width() < 480 || desktopSize.height() < 350)) {
297 expected.remove(value: "QColorDialog::&Basic colors");
298 expected.remove(value: "QColorDialog::&Custom colors");
299 expected.remove(value: "QColorDialog::&Define Custom Colors >>");
300 expected.remove(value: "QColorDialog::&Add to Custom Colors");
301 }
302
303 // see if all of our *expected* translations was translated.
304 // (There might be more, but thats not that bad)
305 QSet<QByteArray> commonTranslations = expected;
306 commonTranslations.intersect(other: translator.m_translations);
307 if (!expected.subtract(other: commonTranslations).isEmpty()) {
308 qDebug() << "Missing:" << expected;
309 if (!translator.m_translations.subtract(other: commonTranslations).isEmpty())
310 qDebug() << "Unexpected:" << translator.m_translations;
311 }
312
313 QVERIFY(expected.isEmpty());
314}
315
316QTEST_MAIN(tst_languageChange)
317#include "tst_languagechange.moc"
318

source code of qtbase/tests/auto/other/languagechange/tst_languagechange.cpp