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 | |
46 | class tst_languageChange : public QObject |
47 | { |
48 | Q_OBJECT |
49 | public: |
50 | tst_languageChange(); |
51 | |
52 | public slots: |
53 | void initTestCase(); |
54 | void cleanupTestCase(); |
55 | private slots: |
56 | void retranslatability_data(); |
57 | void retranslatability(); |
58 | |
59 | private: |
60 | QDialogButtonBox::ButtonLayout m_layout; |
61 | }; |
62 | |
63 | |
64 | tst_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 | |
71 | void tst_languageChange::initTestCase() |
72 | { |
73 | } |
74 | |
75 | void tst_languageChange::cleanupTestCase() |
76 | { |
77 | } |
78 | /** |
79 | * Records all calls to translate() |
80 | */ |
81 | class TransformTranslator : public QTranslator |
82 | { |
83 | Q_OBJECT |
84 | public: |
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 | |
114 | public: |
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. |
120 | class LanguageTestStateMachine : public QObject |
121 | { |
122 | Q_OBJECT |
123 | public: |
124 | LanguageTestStateMachine(QTranslator *translator); |
125 | void start() { m_timer.start(); } |
126 | |
127 | private slots: |
128 | void timeout(); |
129 | |
130 | private: |
131 | enum State { InstallTranslator, CloseDialog }; |
132 | |
133 | QTimer m_timer; |
134 | QTranslator *m_translator; |
135 | State m_state; |
136 | }; |
137 | |
138 | LanguageTestStateMachine::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 | |
145 | void 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 | |
161 | enum DialogType { |
162 | InputDialog = 1, |
163 | ColorDialog, |
164 | FileDialog |
165 | }; |
166 | |
167 | typedef QSet<QByteArray> TranslationSet; |
168 | Q_DECLARE_METATYPE(TranslationSet) |
169 | |
170 | void 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 | |
232 | void 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 | |
316 | QTEST_MAIN(tst_languageChange) |
317 | #include "tst_languagechange.moc" |
318 | |