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 <qcoreapplication.h> |
33 | #include <qfile.h> |
34 | #include <qdebug.h> |
35 | #include <qsharedpointer.h> |
36 | #include <qfiledialog.h> |
37 | #include <qabstractitemdelegate.h> |
38 | #include <qdirmodel.h> |
39 | #include <qitemdelegate.h> |
40 | #include <qlistview.h> |
41 | #include <qcombobox.h> |
42 | #include <qpushbutton.h> |
43 | #include <qtoolbutton.h> |
44 | #include <qtreeview.h> |
45 | #include <qheaderview.h> |
46 | #include <qcompleter.h> |
47 | #include <qaction.h> |
48 | #include <qdialogbuttonbox.h> |
49 | #include <qsortfilterproxymodel.h> |
50 | #include <qlineedit.h> |
51 | #include <qlayout.h> |
52 | #include <qtemporarydir.h> |
53 | #include <private/qfiledialog_p.h> |
54 | #if defined QT_BUILD_INTERNAL |
55 | #include <private/qsidebar_p.h> |
56 | #include <private/qfilesystemmodel_p.h> |
57 | #endif |
58 | #include <private/qguiapplication_p.h> |
59 | #include <qpa/qplatformtheme.h> |
60 | #include <qpa/qplatformintegration.h> |
61 | #include <QFileDialog> |
62 | #include <QFileSystemModel> |
63 | |
64 | #if defined(Q_OS_UNIX) |
65 | #include <unistd.h> // for pathconf() on OS X |
66 | #ifdef QT_BUILD_INTERNAL |
67 | QT_BEGIN_NAMESPACE |
68 | extern Q_GUI_EXPORT QString qt_tildeExpansion(const QString &path); |
69 | QT_END_NAMESPACE |
70 | #endif |
71 | #endif |
72 | |
73 | static inline bool isCaseSensitiveFileSystem(const QString &path) |
74 | { |
75 | Q_UNUSED(path) |
76 | #if defined(Q_OS_MAC) |
77 | return pathconf(QFile::encodeName(path).constData(), _PC_CASE_SENSITIVE); |
78 | #elif defined(Q_OS_WIN) |
79 | return false; |
80 | #else |
81 | return true; |
82 | #endif |
83 | } |
84 | |
85 | class tst_QFiledialog : public QObject |
86 | { |
87 | Q_OBJECT |
88 | |
89 | private slots: |
90 | void initTestCase(); |
91 | void init(); |
92 | void cleanup(); |
93 | void currentChangedSignal(); |
94 | #ifdef QT_BUILD_INTERNAL |
95 | void directoryEnteredSignal(); |
96 | #endif |
97 | void filesSelectedSignal_data(); |
98 | void filesSelectedSignal(); |
99 | void filterSelectedSignal(); |
100 | |
101 | void args(); |
102 | void directory(); |
103 | void completer_data(); |
104 | void completer(); |
105 | void completer_up(); |
106 | void acceptMode(); |
107 | void confirmOverwrite(); |
108 | void defaultSuffix(); |
109 | void fileMode(); |
110 | void filters(); |
111 | void history(); |
112 | void iconProvider(); |
113 | void isReadOnly(); |
114 | void itemDelegate(); |
115 | void labelText(); |
116 | void resolveSymlinks(); |
117 | void selectFile_data(); |
118 | void selectFile(); |
119 | void selectFiles(); |
120 | void selectFileWrongCaseSaveAs(); |
121 | void selectFilter(); |
122 | void viewMode(); |
123 | void proxymodel(); |
124 | void setMimeTypeFilters_data(); |
125 | void setMimeTypeFilters(); |
126 | void setNameFilter_data(); |
127 | void setNameFilter(); |
128 | void setEmptyNameFilter(); |
129 | void focus(); |
130 | void caption(); |
131 | void historyBack(); |
132 | void historyForward(); |
133 | void disableSaveButton_data(); |
134 | void disableSaveButton(); |
135 | void saveButtonText_data(); |
136 | void saveButtonText(); |
137 | void clearLineEdit(); |
138 | void enableChooseButton(); |
139 | void selectedFilesWithoutWidgets(); |
140 | void trailingDotsAndSpaces(); |
141 | #ifdef Q_OS_UNIX |
142 | #ifdef QT_BUILD_INTERNAL |
143 | void tildeExpansion_data(); |
144 | void tildeExpansion(); |
145 | #endif // QT_BUILD_INTERNAL |
146 | #endif |
147 | void rejectModalDialogs(); |
148 | void QTBUG49600_nativeIconProviderCrash(); |
149 | void focusObjectDuringDestruction(); |
150 | |
151 | // NOTE: Please keep widgetlessNativeDialog() as the LAST test! |
152 | // |
153 | // widgetlessNativeDialog() is the only test function that creates |
154 | // a native file dialog instance. GTK+ versions prior 3.15.5 have |
155 | // a nasty bug (https://bugzilla.gnome.org/show_bug.cgi?id=725164) |
156 | // in GtkFileChooserWidget, which makes it leak its folder change |
157 | // callback, causing a crash "at some point later". Running the |
158 | // native test last is enough to avoid spinning the event loop after |
159 | // the test, and that way circumvent the crash. |
160 | // |
161 | // The crash has been fixed in GTK+ 3.15.5, but the RHEL 7.2 CI has |
162 | // GTK+ 3.14.13 installed (QTBUG-55276). |
163 | void widgetlessNativeDialog(); |
164 | |
165 | private: |
166 | void cleanupSettingsFile(); |
167 | }; |
168 | |
169 | void tst_QFiledialog::cleanupSettingsFile() |
170 | { |
171 | // clean up the sidebar between each test |
172 | QSettings settings(QSettings::UserScope, QLatin1String("QtProject" )); |
173 | settings.beginGroup(prefix: QLatin1String("FileDialog" )); |
174 | settings.remove(key: QString()); |
175 | settings.endGroup(); |
176 | settings.beginGroup(prefix: QLatin1String("Qt" )); // Compatibility settings |
177 | settings.remove(key: QLatin1String("filedialog" )); |
178 | settings.endGroup(); |
179 | } |
180 | |
181 | void tst_QFiledialog::initTestCase() |
182 | { |
183 | QStandardPaths::setTestModeEnabled(true); |
184 | cleanupSettingsFile(); |
185 | } |
186 | |
187 | void tst_QFiledialog::init() |
188 | { |
189 | // all tests, except widgetlessNativeDialog, use non-native dialogs |
190 | QCoreApplication::setAttribute(attribute: Qt::AA_DontUseNativeDialogs); |
191 | QFileDialogPrivate::setLastVisitedDirectory(QUrl()); |
192 | // populate the sidebar with some default settings |
193 | QFileDialog fd; |
194 | } |
195 | |
196 | void tst_QFiledialog::cleanup() |
197 | { |
198 | cleanupSettingsFile(); |
199 | } |
200 | |
201 | class MyAbstractItemDelegate : public QAbstractItemDelegate |
202 | { |
203 | public: |
204 | MyAbstractItemDelegate() : QAbstractItemDelegate() {}; |
205 | void paint(QPainter *, const QStyleOptionViewItem &, const QModelIndex &) const {} |
206 | QSize sizeHint(const QStyleOptionViewItem &, const QModelIndex &) const { return QSize(); } |
207 | }; |
208 | |
209 | // emitted any time the selection model emits current changed |
210 | void tst_QFiledialog::currentChangedSignal() |
211 | { |
212 | QFileDialog fd; |
213 | fd.setViewMode(QFileDialog::List); |
214 | QSignalSpy spyCurrentChanged(&fd, SIGNAL(currentChanged(QString))); |
215 | |
216 | QListView* listView = fd.findChild<QListView*>(aName: "listView" ); |
217 | QVERIFY(listView); |
218 | fd.setDirectory(QDir::root()); |
219 | QModelIndex root = listView->rootIndex(); |
220 | QTRY_COMPARE(listView->model()->rowCount(root) > 0, true); |
221 | |
222 | QModelIndex folder; |
223 | for (int i = 0; i < listView->model()->rowCount(parent: root); ++i) { |
224 | folder = listView->model()->index(row: i, column: 0, parent: root); |
225 | if (listView->model()->hasChildren(parent: folder)) |
226 | break; |
227 | } |
228 | QVERIFY(listView->model()->hasChildren(folder)); |
229 | listView->setCurrentIndex(folder); |
230 | |
231 | QCOMPARE(spyCurrentChanged.count(), 1); |
232 | } |
233 | |
234 | // only emitted from the views, sidebar, or lookin combo |
235 | #if defined QT_BUILD_INTERNAL |
236 | void tst_QFiledialog::directoryEnteredSignal() |
237 | { |
238 | QFileDialog fd(0, "" , QDir::root().path()); |
239 | QSidebar * = fd.findChild<QSidebar*>(aName: "sidebar" ); |
240 | QVERIFY(sidebar); |
241 | if (sidebar->model()->rowCount() < 2) |
242 | QSKIP("This test requires at least 2 side bar entries." ); |
243 | |
244 | fd.show(); |
245 | QTRY_COMPARE(fd.isVisible(), true); |
246 | QSignalSpy spyDirectoryEntered(&fd, SIGNAL(directoryEntered(QString))); |
247 | |
248 | // sidebar |
249 | QModelIndex secondItem = sidebar->model()->index(row: 1, column: 0); |
250 | QVERIFY(secondItem.isValid()); |
251 | sidebar->setCurrentIndex(secondItem); |
252 | QTest::keyPress(widget: sidebar->viewport(), key: Qt::Key_Return); |
253 | QCOMPARE(spyDirectoryEntered.count(), 1); |
254 | spyDirectoryEntered.clear(); |
255 | |
256 | // lookInCombo |
257 | QComboBox *comboBox = fd.findChild<QComboBox*>(aName: "lookInCombo" ); |
258 | comboBox->showPopup(); |
259 | QVERIFY(comboBox->view()->model()->index(1, 0).isValid()); |
260 | comboBox->view()->setCurrentIndex(comboBox->view()->model()->index(row: 1, column: 0)); |
261 | QTest::keyPress(widget: comboBox->view()->viewport(), key: Qt::Key_Return); |
262 | QCOMPARE(spyDirectoryEntered.count(), 1); |
263 | spyDirectoryEntered.clear(); |
264 | |
265 | // view |
266 | /* |
267 | // platform specific |
268 | fd.setViewMode(QFileDialog::ViewMode(QFileDialog::List)); |
269 | QListView* listView = fd.findChild<QListView*>("listView"); |
270 | QVERIFY(listView); |
271 | QModelIndex root = listView->rootIndex(); |
272 | QTRY_COMPARE(listView->model()->rowCount(root) > 0, true); |
273 | |
274 | QModelIndex folder; |
275 | for (int i = 0; i < listView->model()->rowCount(root); ++i) { |
276 | folder = listView->model()->index(i, 0, root); |
277 | if (listView->model()->hasChildren(folder)) |
278 | break; |
279 | } |
280 | QVERIFY(listView->model()->hasChildren(folder)); |
281 | listView->setCurrentIndex(folder); |
282 | QTRY_COMPARE((listView->indexAt(listView->visualRect(folder).center())), folder); |
283 | QTest::mouseDClick(listView->viewport(), Qt::LeftButton, 0, listView->visualRect(folder).center()); |
284 | QTRY_COMPARE(spyDirectoryEntered.count(), 1); |
285 | */ |
286 | } |
287 | #endif |
288 | |
289 | Q_DECLARE_METATYPE(QFileDialog::FileMode) |
290 | void tst_QFiledialog::filesSelectedSignal_data() |
291 | { |
292 | QTest::addColumn<QFileDialog::FileMode>(name: "fileMode" ); |
293 | QTest::newRow(dataTag: "any" ) << QFileDialog::AnyFile; |
294 | QTest::newRow(dataTag: "existing" ) << QFileDialog::ExistingFile; |
295 | QTest::newRow(dataTag: "directory" ) << QFileDialog::Directory; |
296 | QTest::newRow(dataTag: "directoryOnly" ) << QFileDialog::DirectoryOnly; |
297 | QTest::newRow(dataTag: "existingFiles" ) << QFileDialog::ExistingFiles; |
298 | } |
299 | |
300 | // emitted when the dialog closes with the selected files |
301 | void tst_QFiledialog::filesSelectedSignal() |
302 | { |
303 | QFileDialog fd; |
304 | fd.setViewMode(QFileDialog::List); |
305 | QDir testDir(SRCDIR); |
306 | fd.setDirectory(testDir); |
307 | QFETCH(QFileDialog::FileMode, fileMode); |
308 | fd.setFileMode(fileMode); |
309 | QSignalSpy spyFilesSelected(&fd, SIGNAL(filesSelected(QStringList))); |
310 | |
311 | fd.show(); |
312 | QVERIFY(QTest::qWaitForWindowExposed(&fd)); |
313 | QListView *listView = fd.findChild<QListView*>(aName: "listView" ); |
314 | QVERIFY(listView); |
315 | |
316 | QModelIndex root = listView->rootIndex(); |
317 | QTRY_COMPARE(listView->model()->rowCount(root) > 0, true); |
318 | QModelIndex file; |
319 | for (int i = 0; i < listView->model()->rowCount(parent: root); ++i) { |
320 | file = listView->model()->index(row: i, column: 0, parent: root); |
321 | if (fileMode == QFileDialog::Directory || fileMode == QFileDialog::DirectoryOnly) { |
322 | if (listView->model()->hasChildren(parent: file)) |
323 | break; |
324 | } else { |
325 | if (!listView->model()->hasChildren(parent: file)) |
326 | break; |
327 | } |
328 | file = QModelIndex(); |
329 | } |
330 | QVERIFY(file.isValid()); |
331 | listView->selectionModel()->select(index: file, command: QItemSelectionModel::Select | QItemSelectionModel::Rows); |
332 | listView->setCurrentIndex(file); |
333 | |
334 | QDialogButtonBox *buttonBox = fd.findChild<QDialogButtonBox*>(aName: "buttonBox" ); |
335 | QPushButton *button = buttonBox->button(which: QDialogButtonBox::Open); |
336 | QVERIFY(button); |
337 | QVERIFY(button->isEnabled()); |
338 | button->animateClick(); |
339 | QTRY_COMPARE(fd.isVisible(), false); |
340 | QCOMPARE(spyFilesSelected.count(), 1); |
341 | } |
342 | |
343 | // only emitted when the combo box is activated |
344 | void tst_QFiledialog::filterSelectedSignal() |
345 | { |
346 | QFileDialog fd; |
347 | fd.setAcceptMode(QFileDialog::AcceptSave); |
348 | fd.show(); |
349 | QSignalSpy spyFilterSelected(&fd, SIGNAL(filterSelected(QString))); |
350 | |
351 | QStringList filterChoices; |
352 | filterChoices << "Image files (*.png *.xpm *.jpg)" |
353 | << "Text files (*.txt)" |
354 | << "Any files (*.*)" ; |
355 | fd.setNameFilters(filterChoices); |
356 | QCOMPARE(fd.nameFilters(), filterChoices); |
357 | |
358 | QComboBox *filters = fd.findChild<QComboBox*>(aName: "fileTypeCombo" ); |
359 | QVERIFY(filters); |
360 | QVERIFY(filters->view()); |
361 | QCOMPARE(filters->isVisible(), true); |
362 | |
363 | QTest::keyPress(widget: filters, key: Qt::Key_Down); |
364 | |
365 | QCOMPARE(spyFilterSelected.count(), 1); |
366 | } |
367 | |
368 | void tst_QFiledialog::args() |
369 | { |
370 | QWidget *parent = 0; |
371 | QString caption = "caption" ; |
372 | QString directory = QDir::tempPath(); |
373 | QString filter = "*.mp3" ; |
374 | QFileDialog fd(parent, caption, directory, filter); |
375 | QCOMPARE(fd.parent(), (QObject *)parent); |
376 | QCOMPARE(fd.windowTitle(), caption); |
377 | #ifndef Q_OS_WIN |
378 | QCOMPARE(fd.directory(), QDir(directory)); |
379 | #endif |
380 | QCOMPARE(fd.nameFilters(), QStringList(filter)); |
381 | } |
382 | |
383 | void tst_QFiledialog::directory() |
384 | { |
385 | QFileDialog fd; |
386 | fd.setViewMode(QFileDialog::List); |
387 | QFileSystemModel *model = fd.findChild<QFileSystemModel*>(aName: "qt_filesystem_model" ); |
388 | QVERIFY(model); |
389 | fd.setDirectory(QDir::currentPath()); |
390 | QSignalSpy spyCurrentChanged(&fd, SIGNAL(currentChanged(QString))); |
391 | QSignalSpy spyDirectoryEntered(&fd, SIGNAL(directoryEntered(QString))); |
392 | QSignalSpy spyFilesSelected(&fd, SIGNAL(filesSelected(QStringList))); |
393 | QSignalSpy spyFilterSelected(&fd, SIGNAL(filterSelected(QString))); |
394 | |
395 | QCOMPARE(QDir::current().absolutePath(), fd.directory().absolutePath()); |
396 | QDir temp = QDir::temp(); |
397 | QString tempPath = temp.absolutePath(); |
398 | #ifdef Q_OS_WIN |
399 | // since the user can have lowercase temp dir, check that we are actually case-insensitive. |
400 | tempPath = tempPath.toLower(); |
401 | #endif |
402 | fd.setDirectory(tempPath); |
403 | #ifndef Q_OS_WIN |
404 | QCOMPARE(tempPath, fd.directory().absolutePath()); |
405 | #endif |
406 | QCOMPARE(spyCurrentChanged.count(), 0); |
407 | QCOMPARE(spyDirectoryEntered.count(), 0); |
408 | QCOMPARE(spyFilesSelected.count(), 0); |
409 | QCOMPARE(spyFilterSelected.count(), 0); |
410 | |
411 | // Check my way |
412 | QList<QListView*> list = fd.findChildren<QListView*>(aName: "listView" ); |
413 | QVERIFY(list.count() > 0); |
414 | #ifdef Q_OS_WIN |
415 | QCOMPARE(list.at(0)->rootIndex().data().toString().toLower(), temp.dirName().toLower()); |
416 | #else |
417 | QCOMPARE(list.at(0)->rootIndex().data().toString(), temp.dirName()); |
418 | #endif |
419 | QFileDialog *dlg = new QFileDialog(0, "" , tempPath); |
420 | QCOMPARE(model->index(tempPath), model->index(dlg->directory().absolutePath())); |
421 | QCOMPARE(model->index(tempPath).data(QFileSystemModel::FileNameRole).toString(), |
422 | model->index(dlg->directory().absolutePath()).data(QFileSystemModel::FileNameRole).toString()); |
423 | delete dlg; |
424 | dlg = new QFileDialog(); |
425 | QCOMPARE(model->index(tempPath), model->index(dlg->directory().absolutePath())); |
426 | delete dlg; |
427 | } |
428 | |
429 | void tst_QFiledialog::completer_data() |
430 | { |
431 | QTest::addColumn<QString>(name: "startPath" ); |
432 | QTest::addColumn<QString>(name: "input" ); |
433 | QTest::addColumn<int>(name: "expected" ); |
434 | |
435 | const QString rootPath = QDir::rootPath(); |
436 | |
437 | QTest::newRow(dataTag: "r, 10" ) << QString() << "r" << 10; |
438 | QTest::newRow(dataTag: "x, 0" ) << QString() << "x" << 0; |
439 | QTest::newRow(dataTag: "../, -1" ) << QString() << "../" << -1; |
440 | |
441 | QTest::newRow(dataTag: "goto root" ) << QString() << rootPath << -1; |
442 | QTest::newRow(dataTag: "start at root" ) << rootPath << QString() << -1; |
443 | |
444 | QFileInfoList list = QDir::root().entryInfoList(filters: QDir::Dirs | QDir::NoDotAndDotDot); |
445 | QVERIFY(!list.isEmpty()); |
446 | const QString folder = list.first().absoluteFilePath(); |
447 | QTest::newRow(dataTag: "start at one below root r" ) << folder << "r" << -1; |
448 | QTest::newRow(dataTag: "start at one below root ../" ) << folder << "../" << -1; |
449 | } |
450 | |
451 | void tst_QFiledialog::completer() |
452 | { |
453 | typedef QSharedPointer<QTemporaryFile> TemporaryFilePtr; |
454 | |
455 | #ifdef Q_OS_WIN |
456 | static const Qt::CaseSensitivity caseSensitivity = Qt::CaseInsensitive; |
457 | #else |
458 | static const Qt::CaseSensitivity caseSensitivity = Qt::CaseSensitive; |
459 | #endif |
460 | |
461 | QFETCH(QString, input); |
462 | QFETCH(QString, startPath); |
463 | QFETCH(int, expected); |
464 | |
465 | // make temp dir and files |
466 | QScopedPointer<QTemporaryDir> tempDir; |
467 | QList<TemporaryFilePtr> files; |
468 | |
469 | if (startPath.isEmpty()) { |
470 | tempDir.reset(other: new QTemporaryDir); |
471 | QVERIFY2(tempDir->isValid(), qPrintable(tempDir->errorString())); |
472 | startPath = tempDir->path(); |
473 | for (int i = 0; i < 10; ++i) { |
474 | TemporaryFilePtr file(new QTemporaryFile(startPath + QStringLiteral("/rXXXXXX" ))); |
475 | QVERIFY2(file->open(), qPrintable(file->errorString())); |
476 | // Force the temporary file to materialize with the requested name |
477 | (void) file->fileName(); |
478 | QVERIFY(file->exists()); |
479 | files.append(t: file); |
480 | } |
481 | } |
482 | |
483 | // ### flesh this out more |
484 | QFileDialog fd(0, QLatin1String(QTest::currentTestFunction()) |
485 | + QStringLiteral(" \"" ) + QLatin1String(QTest::currentDataTag()) |
486 | + QLatin1Char('"'), startPath); |
487 | fd.show(); |
488 | QVERIFY(QTest::qWaitForWindowExposed(&fd)); |
489 | QVERIFY(fd.isVisible()); |
490 | QFileSystemModel *model = fd.findChild<QFileSystemModel*>(aName: "qt_filesystem_model" ); |
491 | QVERIFY(model); |
492 | QLineEdit *lineEdit = fd.findChild<QLineEdit*>(aName: "fileNameEdit" ); |
493 | QVERIFY(lineEdit); |
494 | QCompleter *completer = lineEdit->completer(); |
495 | QVERIFY(completer); |
496 | QAbstractItemModel *cModel = completer->completionModel(); |
497 | QVERIFY(cModel); |
498 | |
499 | // path C:\depot\qt\examples\dialogs\standarddialogs |
500 | // files |
501 | // [debug] [release] [tmp] dialog dialog main makefile makefile.debug makefile.release standarddialgos |
502 | // |
503 | // d -> D:\ debug dialog.cpp dialog.h |
504 | // ..\ -> ..\classwizard ..\configdialog ..\dialogs.pro |
505 | // c -> C:\ control panel |
506 | // c: -> C:\ (nothing more) |
507 | // C:\ -> C:\, C:\_viminfo, ... |
508 | // \ -> \_viminfo |
509 | // c:\depot -> 'nothing' |
510 | // c:\depot\ -> C:\depot\devtools, C:\depot\dteske |
511 | QTRY_COMPARE(model->index(fd.directory().path()), model->index(startPath)); |
512 | |
513 | if (input.isEmpty()) { |
514 | // Try to find a suitable directory under root that does not |
515 | // start with 'C' to avoid issues with completing to "C:" drives on Windows. |
516 | const QString rootPath = model->rootPath(); |
517 | const QChar rootPathFirstChar = rootPath.at(i: 0).toLower(); |
518 | QModelIndex rootIndex = model->index(path: rootPath); |
519 | const int rowCount = model->rowCount(parent: rootIndex); |
520 | QVERIFY(rowCount > 0); |
521 | for (int row = 0; row < rowCount && input.isEmpty(); ++row) { |
522 | const QString name = model->index(row, column: 0, parent: rootIndex).data().toString(); |
523 | const QChar firstChar = name.at(i: 0); |
524 | if (firstChar.isLetter() && firstChar.toLower() != rootPathFirstChar) |
525 | input = firstChar; |
526 | } |
527 | QVERIFY2(!input.isEmpty(), "Unable to find a suitable input directory" ); |
528 | } |
529 | |
530 | // press 'keys' for the input |
531 | for (int i = 0; i < input.count(); ++i) |
532 | QTest::keyPress(widget: lineEdit, key: input[i].toLatin1()); |
533 | |
534 | QStringList expectedFiles; |
535 | if (expected == -1) { |
536 | QString fullPath = startPath; |
537 | if (!fullPath.endsWith(c: QLatin1Char('/'))) |
538 | fullPath.append(c: QLatin1Char('/')); |
539 | fullPath.append(s: input); |
540 | if (input.startsWith(s: QDir::rootPath(), cs: caseSensitivity)) { |
541 | fullPath = input; |
542 | input.clear(); |
543 | } |
544 | |
545 | QFileInfo fi(fullPath); |
546 | QDir x(fi.absolutePath()); |
547 | expectedFiles = x.entryList(filters: model->filter()); |
548 | expected = 0; |
549 | if (input.startsWith(s: ".." )) |
550 | input.clear(); |
551 | foreach (const QString &expectedFile, expectedFiles) { |
552 | if (expectedFile.startsWith(s: input, cs: caseSensitivity)) |
553 | ++expected; |
554 | } |
555 | // The temporary dir may create a node in QFileSystemModel |
556 | // which will bypass filters. If the path to the temporary |
557 | // dir contains an element which should be a subdirectory |
558 | // of x dir, but which is not listed, then take it into |
559 | // accont. |
560 | if (!tempDir.isNull()) { |
561 | QString xPath = x.absolutePath(); |
562 | if (!xPath.endsWith(c: QLatin1Char('/'))) |
563 | xPath.append(c: QLatin1Char('/')); |
564 | QString tmpPath = tempDir->path(); |
565 | if (tmpPath.startsWith(s: xPath)) { |
566 | QString bypassedDirName = tmpPath.mid(position: xPath.size()).section(asep: QLatin1Char('/'), astart: 0, aend: 0); |
567 | if (!expectedFiles.contains(str: bypassedDirName)) |
568 | ++expected; |
569 | } |
570 | } |
571 | } |
572 | |
573 | QTRY_COMPARE(cModel->rowCount(), expected); |
574 | } |
575 | |
576 | void tst_QFiledialog::completer_up() |
577 | { |
578 | QFileDialog fd; |
579 | QSignalSpy spyCurrentChanged(&fd, SIGNAL(currentChanged(QString))); |
580 | QSignalSpy spyDirectoryEntered(&fd, SIGNAL(directoryEntered(QString))); |
581 | QSignalSpy spyFilesSelected(&fd, SIGNAL(filesSelected(QStringList))); |
582 | QSignalSpy spyFilterSelected(&fd, SIGNAL(filterSelected(QString))); |
583 | |
584 | fd.show(); |
585 | QLineEdit *lineEdit = fd.findChild<QLineEdit*>(aName: "fileNameEdit" ); |
586 | QVERIFY(lineEdit); |
587 | QCOMPARE(spyFilesSelected.count(), 0); |
588 | int depth = QDir::currentPath().split(sep: '/').count(); |
589 | for (int i = 0; i <= depth * 3 + 1; ++i) { |
590 | lineEdit->insert("../" ); |
591 | qApp->processEvents(); |
592 | } |
593 | QCOMPARE(spyCurrentChanged.count(), 0); |
594 | QCOMPARE(spyDirectoryEntered.count(), 0); |
595 | QCOMPARE(spyFilesSelected.count(), 0); |
596 | QCOMPARE(spyFilterSelected.count(), 0); |
597 | } |
598 | |
599 | void tst_QFiledialog::acceptMode() |
600 | { |
601 | QFileDialog fd; |
602 | fd.show(); |
603 | |
604 | QToolButton* newButton = fd.findChild<QToolButton*>(aName: "newFolderButton" ); |
605 | QVERIFY(newButton); |
606 | |
607 | // default |
608 | QCOMPARE(fd.acceptMode(), QFileDialog::AcceptOpen); |
609 | QCOMPARE(newButton && newButton->isVisible(), true); |
610 | |
611 | //fd.setDetailsExpanded(true); |
612 | fd.setAcceptMode(QFileDialog::AcceptSave); |
613 | QCOMPARE(fd.acceptMode(), QFileDialog::AcceptSave); |
614 | QCOMPARE(newButton->isVisible(), true); |
615 | |
616 | fd.setAcceptMode(QFileDialog::AcceptOpen); |
617 | QCOMPARE(fd.acceptMode(), QFileDialog::AcceptOpen); |
618 | QCOMPARE(newButton->isVisible(), true); |
619 | } |
620 | |
621 | void tst_QFiledialog::confirmOverwrite() |
622 | { |
623 | QFileDialog fd; |
624 | QCOMPARE(fd.testOption(QFileDialog::DontConfirmOverwrite), false); |
625 | fd.setOption(option: QFileDialog::DontConfirmOverwrite, on: false); |
626 | QCOMPARE(fd.testOption(QFileDialog::DontConfirmOverwrite), false); |
627 | fd.setOption(option: QFileDialog::DontConfirmOverwrite, on: true); |
628 | QCOMPARE(fd.testOption(QFileDialog::DontConfirmOverwrite), true); |
629 | fd.setOption(option: QFileDialog::DontConfirmOverwrite, on: false); |
630 | QCOMPARE(fd.testOption(QFileDialog::DontConfirmOverwrite), false); |
631 | } |
632 | |
633 | void tst_QFiledialog::defaultSuffix() |
634 | { |
635 | QFileDialog fd; |
636 | QCOMPARE(fd.defaultSuffix(), QString()); |
637 | fd.setDefaultSuffix("txt" ); |
638 | QCOMPARE(fd.defaultSuffix(), QString("txt" )); |
639 | fd.setDefaultSuffix(".txt" ); |
640 | QCOMPARE(fd.defaultSuffix(), QString("txt" )); |
641 | fd.setDefaultSuffix(QString()); |
642 | QCOMPARE(fd.defaultSuffix(), QString()); |
643 | } |
644 | |
645 | void tst_QFiledialog::fileMode() |
646 | { |
647 | QFileDialog fd; |
648 | QCOMPARE(fd.fileMode(), QFileDialog::AnyFile); |
649 | fd.setFileMode(QFileDialog::ExistingFile); |
650 | QCOMPARE(fd.fileMode(), QFileDialog::ExistingFile); |
651 | fd.setFileMode(QFileDialog::Directory); |
652 | QCOMPARE(fd.fileMode(), QFileDialog::Directory); |
653 | fd.setFileMode(QFileDialog::DirectoryOnly); |
654 | QCOMPARE(fd.fileMode(), QFileDialog::DirectoryOnly); |
655 | fd.setFileMode(QFileDialog::ExistingFiles); |
656 | QCOMPARE(fd.fileMode(), QFileDialog::ExistingFiles); |
657 | } |
658 | |
659 | void tst_QFiledialog::caption() |
660 | { |
661 | QFileDialog fd; |
662 | fd.setWindowTitle("testing" ); |
663 | fd.setFileMode(QFileDialog::Directory); |
664 | QCOMPARE(fd.windowTitle(), QString("testing" )); |
665 | } |
666 | |
667 | void tst_QFiledialog::filters() |
668 | { |
669 | QFileDialog fd; |
670 | QSignalSpy spyCurrentChanged(&fd, SIGNAL(currentChanged(QString))); |
671 | QSignalSpy spyDirectoryEntered(&fd, SIGNAL(directoryEntered(QString))); |
672 | QSignalSpy spyFilesSelected(&fd, SIGNAL(filesSelected(QStringList))); |
673 | QSignalSpy spyFilterSelected(&fd, SIGNAL(filterSelected(QString))); |
674 | QCOMPARE(fd.nameFilters(), QStringList("All Files (*)" )); |
675 | |
676 | // effects |
677 | QList<QComboBox*> views = fd.findChildren<QComboBox*>(aName: "fileTypeCombo" ); |
678 | QCOMPARE(views.count(), 1); |
679 | QCOMPARE(views.at(0)->isVisible(), false); |
680 | |
681 | QStringList filters; |
682 | filters << "Image files (*.png *.xpm *.jpg)" |
683 | << "Text files (*.txt)" |
684 | << "Any files (*.*)" ; |
685 | fd.setNameFilters(filters); |
686 | QCOMPARE(views.at(0)->isVisible(), false); |
687 | fd.show(); |
688 | fd.setAcceptMode(QFileDialog::AcceptSave); |
689 | QCOMPARE(views.at(0)->isVisible(), true); |
690 | QCOMPARE(fd.nameFilters(), filters); |
691 | fd.setNameFilter("Image files (*.png *.xpm *.jpg);;Text files (*.txt);;Any files (*.*)" ); |
692 | QCOMPARE(fd.nameFilters(), filters); |
693 | QCOMPARE(spyCurrentChanged.count(), 0); |
694 | QCOMPARE(spyDirectoryEntered.count(), 0); |
695 | QCOMPARE(spyFilesSelected.count(), 0); |
696 | QCOMPARE(spyFilterSelected.count(), 0); |
697 | |
698 | // setting shouldn't emit any signals |
699 | for (int i = views.at(i: 0)->currentIndex(); i < views.at(i: 0)->count(); ++i) |
700 | views.at(i: 0)->setCurrentIndex(i); |
701 | QCOMPARE(spyFilterSelected.count(), 0); |
702 | |
703 | //Let check if filters with whitespaces |
704 | QFileDialog fd2; |
705 | QStringList expected; |
706 | expected << "C++ Source Files(*.cpp)" ; |
707 | expected << "Any(*.*)" ; |
708 | fd2.setNameFilter("C++ Source Files(*.cpp);;Any(*.*)" ); |
709 | QCOMPARE(expected, fd2.nameFilters()); |
710 | fd2.setNameFilter("C++ Source Files(*.cpp) ;;Any(*.*)" ); |
711 | QCOMPARE(expected, fd2.nameFilters()); |
712 | fd2.setNameFilter("C++ Source Files(*.cpp);; Any(*.*)" ); |
713 | QCOMPARE(expected, fd2.nameFilters()); |
714 | fd2.setNameFilter(" C++ Source Files(*.cpp);; Any(*.*)" ); |
715 | QCOMPARE(expected, fd2.nameFilters()); |
716 | fd2.setNameFilter("C++ Source Files(*.cpp) ;; Any(*.*)" ); |
717 | QCOMPARE(expected, fd2.nameFilters()); |
718 | } |
719 | |
720 | void tst_QFiledialog::selectFilter() |
721 | { |
722 | QFileDialog fd; |
723 | QSignalSpy spyFilterSelected(&fd, SIGNAL(filterSelected(QString))); |
724 | QCOMPARE(fd.selectedNameFilter(), QString("All Files (*)" )); |
725 | QStringList filters; |
726 | filters << "Image files (*.png *.xpm *.jpg)" |
727 | << "Text files (*.txt)" |
728 | << "Any files (*.*)" ; |
729 | fd.setNameFilters(filters); |
730 | QCOMPARE(fd.selectedNameFilter(), filters.at(0)); |
731 | fd.selectNameFilter(filter: filters.at(i: 1)); |
732 | QCOMPARE(fd.selectedNameFilter(), filters.at(1)); |
733 | fd.selectNameFilter(filter: filters.at(i: 2)); |
734 | QCOMPARE(fd.selectedNameFilter(), filters.at(2)); |
735 | |
736 | fd.selectNameFilter(filter: "bob" ); |
737 | QCOMPARE(fd.selectedNameFilter(), filters.at(2)); |
738 | fd.selectNameFilter(filter: "" ); |
739 | QCOMPARE(fd.selectedNameFilter(), filters.at(2)); |
740 | QCOMPARE(spyFilterSelected.count(), 0); |
741 | } |
742 | |
743 | void tst_QFiledialog::history() |
744 | { |
745 | QFileDialog fd; |
746 | fd.setViewMode(QFileDialog::List); |
747 | QFileSystemModel *model = fd.findChild<QFileSystemModel*>(aName: "qt_filesystem_model" ); |
748 | QVERIFY(model); |
749 | QSignalSpy spyCurrentChanged(&fd, SIGNAL(currentChanged(QString))); |
750 | QSignalSpy spyDirectoryEntered(&fd, SIGNAL(directoryEntered(QString))); |
751 | QSignalSpy spyFilesSelected(&fd, SIGNAL(filesSelected(QStringList))); |
752 | QSignalSpy spyFilterSelected(&fd, SIGNAL(filterSelected(QString))); |
753 | QCOMPARE(model->index(fd.history().first()), model->index(QDir::toNativeSeparators(fd.directory().absolutePath()))); |
754 | fd.setDirectory(QDir::current().absolutePath()); |
755 | QStringList history; |
756 | history << QDir::toNativeSeparators(pathName: QDir::current().absolutePath()) |
757 | << QDir::toNativeSeparators(pathName: QDir::home().absolutePath()) |
758 | << QDir::toNativeSeparators(pathName: QDir::temp().absolutePath()); |
759 | fd.setHistory(history); |
760 | if (fd.history() != history) { |
761 | qDebug() << fd.history() << history; |
762 | // quick and dirty output for windows failure. |
763 | QListView* list = fd.findChild<QListView*>(aName: "listView" ); |
764 | QVERIFY(list); |
765 | QModelIndex root = list->rootIndex(); |
766 | while (root.isValid()) { |
767 | qDebug() << root.data(); |
768 | root = root.parent(); |
769 | } |
770 | } |
771 | QCOMPARE(fd.history(), history); |
772 | |
773 | QStringList badHistory; |
774 | badHistory << "junk" ; |
775 | fd.setHistory(badHistory); |
776 | badHistory << QDir::toNativeSeparators(pathName: QDir::current().absolutePath()); |
777 | QCOMPARE(fd.history(), badHistory); |
778 | |
779 | QCOMPARE(spyCurrentChanged.count(), 0); |
780 | QCOMPARE(spyDirectoryEntered.count(), 0); |
781 | QCOMPARE(spyFilesSelected.count(), 0); |
782 | QCOMPARE(spyFilterSelected.count(), 0); |
783 | } |
784 | |
785 | void tst_QFiledialog::iconProvider() |
786 | { |
787 | QFileDialog *fd = new QFileDialog(); |
788 | QVERIFY(fd->iconProvider() != 0); |
789 | QFileIconProvider *ip = new QFileIconProvider(); |
790 | fd->setIconProvider(ip); |
791 | QCOMPARE(fd->iconProvider(), ip); |
792 | delete fd; |
793 | delete ip; |
794 | } |
795 | |
796 | void tst_QFiledialog::isReadOnly() |
797 | { |
798 | QFileDialog fd; |
799 | |
800 | QPushButton* newButton = fd.findChild<QPushButton*>(aName: "newFolderButton" ); |
801 | QAction* renameAction = fd.findChild<QAction*>(aName: "qt_rename_action" ); |
802 | QAction* deleteAction = fd.findChild<QAction*>(aName: "qt_delete_action" ); |
803 | |
804 | #if QT_DEPRECATED_SINCE(5, 13) |
805 | QCOMPARE(fd.isReadOnly(), false); |
806 | #endif |
807 | QCOMPARE(fd.testOption(QFileDialog::ReadOnly), false); |
808 | |
809 | // This is dependent upon the file/dir, find cross platform way to test |
810 | //fd.setDirectory(QDir::home()); |
811 | //QCOMPARE(newButton && newButton->isEnabled(), true); |
812 | //QCOMPARE(renameAction && renameAction->isEnabled(), true); |
813 | //QCOMPARE(deleteAction && deleteAction->isEnabled(), true); |
814 | |
815 | fd.setOption(option: QFileDialog::ReadOnly, on: true); |
816 | QCOMPARE(fd.testOption(QFileDialog::ReadOnly), true); |
817 | |
818 | QCOMPARE(newButton && newButton->isEnabled(), false); |
819 | QCOMPARE(renameAction && renameAction->isEnabled(), false); |
820 | QCOMPARE(deleteAction && deleteAction->isEnabled(), false); |
821 | } |
822 | |
823 | void tst_QFiledialog::itemDelegate() |
824 | { |
825 | QFileDialog fd; |
826 | QVERIFY(fd.itemDelegate() != 0); |
827 | QItemDelegate *id = new QItemDelegate(&fd); |
828 | fd.setItemDelegate(id); |
829 | QCOMPARE(fd.itemDelegate(), (QAbstractItemDelegate *)id); |
830 | } |
831 | |
832 | void tst_QFiledialog::labelText() |
833 | { |
834 | QFileDialog fd; |
835 | QDialogButtonBox buttonBox; |
836 | QPushButton *cancelButton = buttonBox.addButton(button: QDialogButtonBox::Cancel); |
837 | QCOMPARE(fd.labelText(QFileDialog::LookIn), QString("Look in:" )); |
838 | QCOMPARE(fd.labelText(QFileDialog::FileName), QString("File &name:" )); |
839 | QCOMPARE(fd.labelText(QFileDialog::FileType), QString("Files of type:" )); |
840 | QCOMPARE(fd.labelText(QFileDialog::Accept), QString("&Open" )); ///### see task 241462 |
841 | QCOMPARE(fd.labelText(QFileDialog::Reject), cancelButton->text()); |
842 | |
843 | fd.setLabelText(label: QFileDialog::LookIn, text: "1" ); |
844 | QCOMPARE(fd.labelText(QFileDialog::LookIn), QString("1" )); |
845 | fd.setLabelText(label: QFileDialog::FileName, text: "2" ); |
846 | QCOMPARE(fd.labelText(QFileDialog::FileName), QString("2" )); |
847 | fd.setLabelText(label: QFileDialog::FileType, text: "3" ); |
848 | QCOMPARE(fd.labelText(QFileDialog::FileType), QString("3" )); |
849 | fd.setLabelText(label: QFileDialog::Accept, text: "4" ); |
850 | QCOMPARE(fd.labelText(QFileDialog::Accept), QString("4" )); |
851 | fd.setLabelText(label: QFileDialog::Reject, text: "5" ); |
852 | QCOMPARE(fd.labelText(QFileDialog::Reject), QString("5" )); |
853 | } |
854 | |
855 | void tst_QFiledialog::resolveSymlinks() |
856 | { |
857 | QFileDialog fd; |
858 | |
859 | // default |
860 | QCOMPARE(fd.testOption(QFileDialog::DontResolveSymlinks), false); |
861 | fd.setOption(option: QFileDialog::DontResolveSymlinks, on: true); |
862 | QCOMPARE(fd.testOption(QFileDialog::DontResolveSymlinks), true); |
863 | fd.setOption(option: QFileDialog::DontResolveSymlinks, on: false); |
864 | QCOMPARE(fd.testOption(QFileDialog::DontResolveSymlinks), false); |
865 | |
866 | // the file dialog doesn't do anything based upon this, just passes it to the model |
867 | // the model should fully test it, don't test it here |
868 | } |
869 | |
870 | void tst_QFiledialog::selectFile_data() |
871 | { |
872 | QTest::addColumn<QString>(name: "file" ); |
873 | QTest::addColumn<int>(name: "count" ); |
874 | QTest::newRow(dataTag: "null" ) << QString() << 1; |
875 | QTest::newRow(dataTag: "file" ) << "foo" << 1; |
876 | QTest::newRow(dataTag: "tmp" ) << "temp" << 1; |
877 | } |
878 | |
879 | void tst_QFiledialog::selectFile() |
880 | { |
881 | QFETCH(QString, file); |
882 | QFETCH(int, count); |
883 | QScopedPointer<QFileDialog> fd(new QFileDialog); |
884 | QFileSystemModel *model = fd->findChild<QFileSystemModel*>(aName: "qt_filesystem_model" ); |
885 | QVERIFY(model); |
886 | fd->setDirectory(QDir::currentPath()); |
887 | // default value |
888 | QCOMPARE(fd->selectedFiles().count(), 1); |
889 | |
890 | QScopedPointer<QTemporaryFile> tempFile; |
891 | if (file == QLatin1String("temp" )) { |
892 | tempFile.reset(other: new QTemporaryFile(QDir::tempPath() + QStringLiteral("/aXXXXXX" ))); |
893 | QVERIFY2(tempFile->open(), qPrintable(tempFile->errorString())); |
894 | file = tempFile->fileName(); |
895 | } |
896 | |
897 | fd->selectFile(filename: file); |
898 | QCOMPARE(fd->selectedFiles().count(), count); |
899 | if (tempFile.isNull()) { |
900 | QCOMPARE(model->index(fd->directory().path()), model->index(QDir::currentPath())); |
901 | } else { |
902 | QCOMPARE(model->index(fd->directory().path()), model->index(QDir::tempPath())); |
903 | } |
904 | fd.reset(); // Ensure the file dialog let's go of the temporary file for "temp". |
905 | } |
906 | |
907 | void tst_QFiledialog::selectFileWrongCaseSaveAs() |
908 | { |
909 | const QString home = QDir::homePath(); |
910 | if (isCaseSensitiveFileSystem(path: home)) |
911 | QSKIP("This test is intended for case-insensitive file systems only." ); |
912 | // QTBUG-38162: when passing a wrongly capitalized path to selectFile() |
913 | // on a case-insensitive file system, the line edit should only |
914 | // contain the file name ("c:\PRogram files\foo.txt" -> "foo.txt"). |
915 | const QString fileName = QStringLiteral("foo.txt" ); |
916 | const QString path = home + QLatin1Char('/') + fileName; |
917 | QString wrongCasePath = path; |
918 | for (int c = 0; c < wrongCasePath.size(); c += 2) |
919 | wrongCasePath[c] = wrongCasePath.at(i: c).isLower() ? wrongCasePath.at(i: c).toUpper() : wrongCasePath.at(i: c).toLower(); |
920 | QFileDialog fd(0, "QTBUG-38162" , wrongCasePath); |
921 | fd.setAcceptMode(QFileDialog::AcceptSave); |
922 | fd.selectFile(filename: wrongCasePath); |
923 | const QLineEdit *lineEdit = fd.findChild<QLineEdit*>(aName: "fileNameEdit" ); |
924 | QVERIFY(lineEdit); |
925 | QCOMPARE(lineEdit->text().compare(fileName, Qt::CaseInsensitive), 0); |
926 | } |
927 | |
928 | void tst_QFiledialog::selectFiles() |
929 | { |
930 | QTemporaryDir tempDir; |
931 | QVERIFY2(tempDir.isValid(), qPrintable(tempDir.errorString())); |
932 | const QString tempPath = tempDir.path(); |
933 | { |
934 | QFileDialog fd; |
935 | fd.setViewMode(QFileDialog::List); |
936 | fd.setDirectory(tempPath); |
937 | QSignalSpy spyCurrentChanged(&fd, SIGNAL(currentChanged(QString))); |
938 | QSignalSpy spyDirectoryEntered(&fd, SIGNAL(directoryEntered(QString))); |
939 | QSignalSpy spyFilesSelected(&fd, SIGNAL(filesSelected(QStringList))); |
940 | QSignalSpy spyFilterSelected(&fd, SIGNAL(filterSelected(QString))); |
941 | fd.setFileMode(QFileDialog::ExistingFiles); |
942 | |
943 | QString filesPath = fd.directory().absolutePath(); |
944 | for (int i=0; i < 5; ++i) { |
945 | QFile file(filesPath + QLatin1String("/qfiledialog_auto_test_not_pres_" ) + QString::number(i)); |
946 | file.open(flags: QIODevice::WriteOnly); |
947 | file.resize(sz: 1024); |
948 | file.flush(); |
949 | file.close(); |
950 | } |
951 | |
952 | // Get a list of files in the view and then get the corresponding index's |
953 | QStringList list = fd.directory().entryList(filters: QDir::Files); |
954 | QModelIndexList toSelect; |
955 | QVERIFY(list.count() > 1); |
956 | QListView* listView = fd.findChild<QListView*>(aName: "listView" ); |
957 | QVERIFY(listView); |
958 | for (int i = 0; i < list.count(); ++i) { |
959 | fd.selectFile(filename: fd.directory().path() + QLatin1Char('/') + list.at(i)); |
960 | QTRY_VERIFY(!listView->selectionModel()->selectedRows().isEmpty()); |
961 | toSelect.append(t: listView->selectionModel()->selectedRows().last()); |
962 | } |
963 | QCOMPARE(spyFilesSelected.count(), 0); |
964 | |
965 | listView->selectionModel()->clear(); |
966 | QCOMPARE(spyFilesSelected.count(), 0); |
967 | |
968 | // select the indexes |
969 | for (int i = 0; i < toSelect.count(); ++i) { |
970 | listView->selectionModel()->select(index: toSelect.at(i), |
971 | command: QItemSelectionModel::Select | QItemSelectionModel::Rows); |
972 | } |
973 | QCOMPARE(fd.selectedFiles().count(), toSelect.count()); |
974 | QCOMPARE(spyCurrentChanged.count(), 0); |
975 | QCOMPARE(spyDirectoryEntered.count(), 0); |
976 | QCOMPARE(spyFilesSelected.count(), 0); |
977 | QCOMPARE(spyFilterSelected.count(), 0); |
978 | |
979 | } |
980 | |
981 | { |
982 | //If the selection is invalid then we fill the line edit but without the / |
983 | QFileDialog dialog( 0, "Save" ); |
984 | dialog.setFileMode( QFileDialog::AnyFile ); |
985 | dialog.setAcceptMode( QFileDialog::AcceptSave ); |
986 | dialog.selectFile(filename: tempPath + QStringLiteral("/blah" )); |
987 | dialog.show(); |
988 | QVERIFY(QTest::qWaitForWindowExposed(&dialog)); |
989 | QLineEdit *lineEdit = dialog.findChild<QLineEdit*>(aName: "fileNameEdit" ); |
990 | QVERIFY(lineEdit); |
991 | QCOMPARE(lineEdit->text(),QLatin1String("blah" )); |
992 | } |
993 | } |
994 | |
995 | void tst_QFiledialog::viewMode() |
996 | { |
997 | QFileDialog fd; |
998 | fd.setViewMode(QFileDialog::List); |
999 | fd.show(); |
1000 | |
1001 | // find widgets |
1002 | QList<QTreeView*> treeView = fd.findChildren<QTreeView*>(aName: "treeView" ); |
1003 | QCOMPARE(treeView.count(), 1); |
1004 | QList<QListView*> listView = fd.findChildren<QListView*>(aName: "listView" ); |
1005 | QCOMPARE(listView.count(), 1); |
1006 | QList<QToolButton*> listButton = fd.findChildren<QToolButton*>(aName: "listModeButton" ); |
1007 | QCOMPARE(listButton.count(), 1); |
1008 | QList<QToolButton*> treeButton = fd.findChildren<QToolButton*>(aName: "detailModeButton" ); |
1009 | QCOMPARE(treeButton.count(), 1); |
1010 | |
1011 | // default value |
1012 | QCOMPARE(fd.viewMode(), QFileDialog::List); |
1013 | |
1014 | // detail |
1015 | fd.setViewMode(QFileDialog::ViewMode(QFileDialog::Detail)); |
1016 | |
1017 | QCOMPARE(QFileDialog::ViewMode(QFileDialog::Detail), fd.viewMode()); |
1018 | QCOMPARE(listView.at(0)->isVisible(), false); |
1019 | QCOMPARE(listButton.at(0)->isDown(), false); |
1020 | QCOMPARE(treeView.at(0)->isVisible(), true); |
1021 | QCOMPARE(treeButton.at(0)->isDown(), true); |
1022 | |
1023 | // list |
1024 | fd.setViewMode(QFileDialog::ViewMode(QFileDialog::List)); |
1025 | |
1026 | QCOMPARE(QFileDialog::ViewMode(QFileDialog::List), fd.viewMode()); |
1027 | QCOMPARE(treeView.at(0)->isVisible(), false); |
1028 | QCOMPARE(treeButton.at(0)->isDown(), false); |
1029 | QCOMPARE(listView.at(0)->isVisible(), true); |
1030 | QCOMPARE(listButton.at(0)->isDown(), true); |
1031 | } |
1032 | |
1033 | void tst_QFiledialog::proxymodel() |
1034 | { |
1035 | QFileDialog fd; |
1036 | QCOMPARE(fd.proxyModel(), nullptr); |
1037 | |
1038 | fd.setProxyModel(0); |
1039 | QCOMPARE(fd.proxyModel(), nullptr); |
1040 | |
1041 | QSortFilterProxyModel *proxyModel = new QSortFilterProxyModel(&fd); |
1042 | fd.setProxyModel(proxyModel); |
1043 | QCOMPARE(fd.proxyModel(), (QAbstractProxyModel *)proxyModel); |
1044 | |
1045 | fd.setProxyModel(0); |
1046 | QCOMPARE(fd.proxyModel(), nullptr); |
1047 | } |
1048 | |
1049 | void tst_QFiledialog::setMimeTypeFilters_data() |
1050 | { |
1051 | QTest::addColumn<QStringList>(name: "mimeTypeFilters" ); |
1052 | QTest::addColumn<QString>(name: "targetMimeTypeFilter" ); |
1053 | QTest::addColumn<QString>(name: "expectedSelectedMimeTypeFilter" ); |
1054 | |
1055 | const auto = QStringLiteral("text/x-chdr" ); |
1056 | const auto pdfMime = QStringLiteral("application/pdf" ); |
1057 | const auto zipMime = QStringLiteral("application/zip" ); |
1058 | |
1059 | QTest::newRow(dataTag: "single mime filter (C header file)" ) << QStringList {headerMime} << headerMime << headerMime; |
1060 | QTest::newRow(dataTag: "single mime filter (JSON file)" ) << QStringList {pdfMime} << pdfMime << pdfMime; |
1061 | QTest::newRow(dataTag: "multiple mime filters" ) << QStringList {pdfMime, zipMime} << pdfMime << pdfMime; |
1062 | } |
1063 | |
1064 | void tst_QFiledialog::setMimeTypeFilters() |
1065 | { |
1066 | QFETCH(QStringList, mimeTypeFilters); |
1067 | QFETCH(QString, targetMimeTypeFilter); |
1068 | QFETCH(QString, expectedSelectedMimeTypeFilter); |
1069 | |
1070 | QFileDialog fd; |
1071 | fd.setMimeTypeFilters(mimeTypeFilters); |
1072 | fd.selectMimeTypeFilter(filter: targetMimeTypeFilter); |
1073 | |
1074 | QCOMPARE(fd.selectedMimeTypeFilter(), expectedSelectedMimeTypeFilter); |
1075 | |
1076 | auto *filters = fd.findChild<QComboBox*>(aName: "fileTypeCombo" ); |
1077 | filters->setCurrentIndex(filters->count() - 1); |
1078 | QCOMPARE(fd.selectedMimeTypeFilter(), mimeTypeFilters.last()); |
1079 | } |
1080 | |
1081 | void tst_QFiledialog::setEmptyNameFilter() |
1082 | { |
1083 | QFileDialog fd; |
1084 | fd.setNameFilter(QString()); |
1085 | fd.setNameFilters(QStringList()); |
1086 | } |
1087 | |
1088 | void tst_QFiledialog::setNameFilter_data() |
1089 | { |
1090 | QTest::addColumn<bool>(name: "nameFilterDetailsVisible" ); |
1091 | QTest::addColumn<QStringList>(name: "filters" ); |
1092 | QTest::addColumn<QString>(name: "selectFilter" ); |
1093 | QTest::addColumn<QString>(name: "expectedSelectedFilter" ); |
1094 | |
1095 | QTest::newRow(dataTag: "namedetailsvisible-empty" ) << true << QStringList() << QString() << QString(); |
1096 | QTest::newRow(dataTag: "namedetailsinvisible-empty" ) << false << QStringList() << QString() << QString(); |
1097 | |
1098 | const QString anyFileNoDetails = QLatin1String("Any files" ); |
1099 | const QString anyFile = anyFileNoDetails + QLatin1String(" (*)" ); |
1100 | const QString imageFilesNoDetails = QLatin1String("Image files" ); |
1101 | const QString imageFiles = imageFilesNoDetails + QLatin1String(" (*.png *.xpm *.jpg)" ); |
1102 | const QString textFileNoDetails = QLatin1String("Text files" ); |
1103 | const QString textFile = textFileNoDetails + QLatin1String(" (*.txt)" ); |
1104 | |
1105 | QStringList filters; |
1106 | filters << anyFile << imageFiles << textFile; |
1107 | |
1108 | QTest::newRow(dataTag: "namedetailsvisible-images" ) << true << filters << imageFiles << imageFiles; |
1109 | QTest::newRow(dataTag: "namedetailsinvisible-images" ) << false << filters << imageFiles << imageFilesNoDetails; |
1110 | |
1111 | const QString invalid = "foo" ; |
1112 | QTest::newRow(dataTag: "namedetailsvisible-invalid" ) << true << filters << invalid << anyFile; |
1113 | // Potential crash when trying to convert the invalid filter into a list and stripping it, resulting in an empty list. |
1114 | QTest::newRow(dataTag: "namedetailsinvisible-invalid" ) << false << filters << invalid << anyFileNoDetails; |
1115 | } |
1116 | |
1117 | void tst_QFiledialog::setNameFilter() |
1118 | { |
1119 | QFETCH(bool, nameFilterDetailsVisible); |
1120 | QFETCH(QStringList, filters); |
1121 | QFETCH(QString, selectFilter); |
1122 | QFETCH(QString, expectedSelectedFilter); |
1123 | |
1124 | QFileDialog fd; |
1125 | fd.setNameFilters(filters); |
1126 | fd.setOption(option: QFileDialog::HideNameFilterDetails, on: !nameFilterDetailsVisible); |
1127 | fd.selectNameFilter(filter: selectFilter); |
1128 | QCOMPARE(fd.selectedNameFilter(), expectedSelectedFilter); |
1129 | } |
1130 | |
1131 | void tst_QFiledialog::focus() |
1132 | { |
1133 | if (!QGuiApplicationPrivate::platformIntegration()->hasCapability(cap: QPlatformIntegration::WindowActivation)) |
1134 | QSKIP("Window activation is not supported" ); |
1135 | QFileDialog fd; |
1136 | fd.setDirectory(QDir::currentPath()); |
1137 | fd.show(); |
1138 | QApplication::setActiveWindow(&fd); |
1139 | QVERIFY(QTest::qWaitForWindowActive(&fd)); |
1140 | QCOMPARE(fd.isVisible(), true); |
1141 | QCOMPARE(QApplication::activeWindow(), static_cast<QWidget*>(&fd)); |
1142 | qApp->processEvents(); |
1143 | |
1144 | // make sure the tests work with focus follows mouse |
1145 | QCursor::setPos(fd.geometry().center()); |
1146 | |
1147 | QList<QWidget*> treeView = fd.findChildren<QWidget*>(aName: "fileNameEdit" ); |
1148 | QCOMPARE(treeView.count(), 1); |
1149 | QVERIFY(treeView.at(0)); |
1150 | QTRY_COMPARE(treeView.at(0)->hasFocus(), true); |
1151 | QCOMPARE(treeView.at(0)->hasFocus(), true); |
1152 | } |
1153 | |
1154 | |
1155 | void tst_QFiledialog::historyBack() |
1156 | { |
1157 | QFileDialog fd; |
1158 | QFileSystemModel *model = fd.findChild<QFileSystemModel*>(aName: "qt_filesystem_model" ); |
1159 | QVERIFY(model); |
1160 | QToolButton *backButton = fd.findChild<QToolButton*>(aName: "backButton" ); |
1161 | QVERIFY(backButton); |
1162 | QToolButton *forwardButton = fd.findChild<QToolButton*>(aName: "forwardButton" ); |
1163 | QVERIFY(forwardButton); |
1164 | |
1165 | QSignalSpy spy(model, SIGNAL(rootPathChanged(QString))); |
1166 | |
1167 | QString home = fd.directory().absolutePath(); |
1168 | QString desktop = QDir::homePath(); |
1169 | QString temp = QDir::tempPath(); |
1170 | |
1171 | QCOMPARE(backButton->isEnabled(), false); |
1172 | QCOMPARE(forwardButton->isEnabled(), false); |
1173 | fd.setDirectory(temp); |
1174 | qApp->processEvents(); |
1175 | QCOMPARE(backButton->isEnabled(), true); |
1176 | QCOMPARE(forwardButton->isEnabled(), false); |
1177 | fd.setDirectory(desktop); |
1178 | QCOMPARE(spy.count(), 2); |
1179 | |
1180 | backButton->click(); |
1181 | qApp->processEvents(); |
1182 | QCOMPARE(backButton->isEnabled(), true); |
1183 | QCOMPARE(forwardButton->isEnabled(), true); |
1184 | QCOMPARE(spy.count(), 3); |
1185 | QString currentPath = qvariant_cast<QString>(v: spy.last().first()); |
1186 | QCOMPARE(model->index(currentPath), model->index(temp)); |
1187 | |
1188 | backButton->click(); |
1189 | currentPath = qvariant_cast<QString>(v: spy.last().first()); |
1190 | QCOMPARE(currentPath, home); |
1191 | QCOMPARE(backButton->isEnabled(), false); |
1192 | QCOMPARE(forwardButton->isEnabled(), true); |
1193 | QCOMPARE(spy.count(), 4); |
1194 | |
1195 | // nothing should change at this point |
1196 | backButton->click(); |
1197 | QCOMPARE(spy.count(), 4); |
1198 | QCOMPARE(backButton->isEnabled(), false); |
1199 | QCOMPARE(forwardButton->isEnabled(), true); |
1200 | } |
1201 | |
1202 | void tst_QFiledialog::historyForward() |
1203 | { |
1204 | QFileDialog fd; |
1205 | fd.setDirectory(QDir::currentPath()); |
1206 | QToolButton *backButton = fd.findChild<QToolButton*>(aName: "backButton" ); |
1207 | QVERIFY(backButton); |
1208 | QToolButton *forwardButton = fd.findChild<QToolButton*>(aName: "forwardButton" ); |
1209 | QVERIFY(forwardButton); |
1210 | |
1211 | QFileSystemModel *model = fd.findChild<QFileSystemModel*>(aName: "qt_filesystem_model" ); |
1212 | QVERIFY(model); |
1213 | QSignalSpy spy(model, SIGNAL(rootPathChanged(QString))); |
1214 | |
1215 | QString home = fd.directory().absolutePath(); |
1216 | QString desktop = QDir::homePath(); |
1217 | QString temp = QDir::tempPath(); |
1218 | |
1219 | fd.setDirectory(home); |
1220 | fd.setDirectory(temp); |
1221 | fd.setDirectory(desktop); |
1222 | |
1223 | backButton->click(); |
1224 | QCOMPARE(forwardButton->isEnabled(), true); |
1225 | QCOMPARE(model->index(qvariant_cast<QString>(spy.last().first())), model->index(temp)); |
1226 | |
1227 | forwardButton->click(); |
1228 | QCOMPARE(model->index(qvariant_cast<QString>(spy.last().first())), model->index(desktop)); |
1229 | QCOMPARE(backButton->isEnabled(), true); |
1230 | QCOMPARE(forwardButton->isEnabled(), false); |
1231 | QCOMPARE(spy.count(), 4); |
1232 | |
1233 | backButton->click(); |
1234 | QCOMPARE(model->index(qvariant_cast<QString>(spy.last().first())), model->index(temp)); |
1235 | QCOMPARE(backButton->isEnabled(), true); |
1236 | |
1237 | backButton->click(); |
1238 | QCOMPARE(model->index(qvariant_cast<QString>(spy.last().first())), model->index(home)); |
1239 | QCOMPARE(backButton->isEnabled(), false); |
1240 | QCOMPARE(forwardButton->isEnabled(), true); |
1241 | QCOMPARE(spy.count(), 6); |
1242 | |
1243 | forwardButton->click(); |
1244 | QCOMPARE(model->index(qvariant_cast<QString>(spy.last().first())), model->index(temp)); |
1245 | backButton->click(); |
1246 | QCOMPARE(model->index(qvariant_cast<QString>(spy.last().first())), model->index(home)); |
1247 | QCOMPARE(spy.count(), 8); |
1248 | |
1249 | forwardButton->click(); |
1250 | QCOMPARE(model->index(qvariant_cast<QString>(spy.last().first())), model->index(temp)); |
1251 | forwardButton->click(); |
1252 | QCOMPARE(model->index(qvariant_cast<QString>(spy.last().first())), model->index(desktop)); |
1253 | |
1254 | backButton->click(); |
1255 | QCOMPARE(model->index(qvariant_cast<QString>(spy.last().first())), model->index(temp)); |
1256 | backButton->click(); |
1257 | QCOMPARE(model->index(qvariant_cast<QString>(spy.last().first())), model->index(home)); |
1258 | fd.setDirectory(desktop); |
1259 | QCOMPARE(forwardButton->isEnabled(), false); |
1260 | } |
1261 | |
1262 | void tst_QFiledialog::disableSaveButton_data() |
1263 | { |
1264 | QTest::addColumn<QString>(name: "path" ); |
1265 | QTest::addColumn<bool>(name: "isEnabled" ); |
1266 | |
1267 | QTest::newRow(dataTag: "valid path" ) << QDir::temp().absolutePath() + QDir::separator() + "qfiledialog.new_file" << true; |
1268 | QTest::newRow(dataTag: "no path" ) << "" << false; |
1269 | #if defined(Q_OS_UNIX) && !defined(Q_OS_MAC) && !defined(Q_OS_OPENBSD) |
1270 | QTest::newRow(dataTag: "too long path" ) << "iiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiii" << false; |
1271 | #endif |
1272 | QTest::newRow(dataTag: "file" ) << "foo.html" << true; |
1273 | } |
1274 | |
1275 | void tst_QFiledialog::disableSaveButton() |
1276 | { |
1277 | QFETCH(QString, path); |
1278 | QFETCH(bool, isEnabled); |
1279 | |
1280 | QFileDialog fd(0, "caption" , path); |
1281 | fd.setAcceptMode(QFileDialog::AcceptSave); |
1282 | QDialogButtonBox *buttonBox = fd.findChild<QDialogButtonBox*>(aName: "buttonBox" ); |
1283 | QPushButton *button = buttonBox->button(which: QDialogButtonBox::Save); |
1284 | QVERIFY(button); |
1285 | QCOMPARE(button->isEnabled(), isEnabled); |
1286 | } |
1287 | |
1288 | void tst_QFiledialog::saveButtonText_data() |
1289 | { |
1290 | QTest::addColumn<QString>(name: "path" ); |
1291 | QTest::addColumn<QString>(name: "label" ); |
1292 | QTest::addColumn<QString>(name: "caption" ); |
1293 | |
1294 | QTest::newRow(dataTag: "empty path" ) << "" << QString() << QFileDialog::tr(s: "&Save" ); |
1295 | QTest::newRow(dataTag: "file path" ) << "qfiledialog.new_file" << QString() << QFileDialog::tr(s: "&Save" ); |
1296 | QTest::newRow(dataTag: "dir" ) << QDir::temp().absolutePath() << QString() << QFileDialog::tr(s: "&Open" ); |
1297 | QTest::newRow(dataTag: "setTextLabel" ) << "qfiledialog.new_file" << "Mooo" << "Mooo" ; |
1298 | QTest::newRow(dataTag: "dir & label" ) << QDir::temp().absolutePath() << "Poo" << QFileDialog::tr(s: "&Open" ); |
1299 | } |
1300 | |
1301 | void tst_QFiledialog::saveButtonText() |
1302 | { |
1303 | QFETCH(QString, path); |
1304 | QFETCH(QString, label); |
1305 | QFETCH(QString, caption); |
1306 | |
1307 | QFileDialog fd(0, "auto test" , QDir::temp().absolutePath()); |
1308 | fd.setAcceptMode(QFileDialog::AcceptSave); |
1309 | if (!label.isNull()) |
1310 | fd.setLabelText(label: QFileDialog::Accept, text: label); |
1311 | fd.setDirectory(QDir::temp()); |
1312 | fd.selectFile(filename: path); |
1313 | QDialogButtonBox *buttonBox = fd.findChild<QDialogButtonBox*>(aName: "buttonBox" ); |
1314 | QVERIFY(buttonBox); |
1315 | QPushButton *button = buttonBox->button(which: QDialogButtonBox::Save); |
1316 | QVERIFY(button); |
1317 | QCOMPARE(button->text(), caption); |
1318 | } |
1319 | |
1320 | // Predicate for use with QTRY_VERIFY() that checks whether the file dialog list |
1321 | // has been populated (contains an entry). |
1322 | class DirPopulatedPredicate |
1323 | { |
1324 | public: |
1325 | explicit DirPopulatedPredicate(QListView *list, const QString &needle) : |
1326 | m_list(list), m_needle(needle) {} |
1327 | |
1328 | operator bool() const |
1329 | { |
1330 | const auto model = m_list->model(); |
1331 | const auto root = m_list->rootIndex(); |
1332 | for (int r = 0, count = model->rowCount(parent: root); r < count; ++r) { |
1333 | if (m_needle == model->index(row: r, column: 0, parent: root).data(arole: Qt::DisplayRole).toString()) |
1334 | return true; |
1335 | } |
1336 | return false; |
1337 | } |
1338 | |
1339 | private: |
1340 | QListView *m_list; |
1341 | QString m_needle; |
1342 | }; |
1343 | |
1344 | // A predicate for use with QTRY_VERIFY() that ensures an entry of the file dialog |
1345 | // list is selected by pressing cursor down. |
1346 | class SelectDirTestPredicate |
1347 | { |
1348 | public: |
1349 | explicit SelectDirTestPredicate(QListView *list, const QString &needle) : |
1350 | m_list(list), m_needle(needle) {} |
1351 | |
1352 | operator bool() const |
1353 | { |
1354 | if (m_needle == m_list->currentIndex().data(arole: Qt::DisplayRole).toString()) |
1355 | return true; |
1356 | QCoreApplication::processEvents(); |
1357 | QTest::keyClick(widget: m_list, key: Qt::Key_Down); |
1358 | return false; |
1359 | } |
1360 | |
1361 | private: |
1362 | QListView *m_list; |
1363 | QString m_needle; |
1364 | }; |
1365 | |
1366 | void tst_QFiledialog::clearLineEdit() |
1367 | { |
1368 | // Play it really safe by creating a directory which should show first in |
1369 | // a temporary dir |
1370 | QTemporaryDir workDir(QDir::tempPath() + QLatin1String("/tst_qfd_clearXXXXXX" )); |
1371 | QVERIFY2(workDir.isValid(), qPrintable(workDir.errorString())); |
1372 | const QString workDirPath = workDir.path(); |
1373 | const QString dirName = QLatin1String("aaaaa" ); |
1374 | QVERIFY(QDir(workDirPath).mkdir(dirName)); |
1375 | |
1376 | QFileDialog fd(nullptr, |
1377 | QLatin1String(QTest::currentTestFunction()) + QLatin1String(" AnyFile" ), |
1378 | "foo" ); |
1379 | fd.setViewMode(QFileDialog::List); |
1380 | fd.setFileMode(QFileDialog::AnyFile); |
1381 | fd.show(); |
1382 | |
1383 | QVERIFY(QTest::qWaitForWindowExposed(&fd)); |
1384 | QLineEdit *lineEdit = fd.findChild<QLineEdit*>(aName: "fileNameEdit" ); |
1385 | QVERIFY(lineEdit); |
1386 | QCOMPARE(lineEdit->text(), QLatin1String("foo" )); |
1387 | |
1388 | QListView* list = fd.findChild<QListView*>(aName: "listView" ); |
1389 | QVERIFY(list); |
1390 | |
1391 | // When in AnyFile mode, lineEdit's text shouldn't be cleared when entering |
1392 | // a directory by activating one in the list |
1393 | fd.setDirectory(workDirPath); |
1394 | DirPopulatedPredicate dirPopulated(list, dirName); |
1395 | QTRY_VERIFY(dirPopulated); |
1396 | |
1397 | #ifdef QT_KEYPAD_NAVIGATION |
1398 | list->setEditFocus(true); |
1399 | #endif |
1400 | |
1401 | SelectDirTestPredicate selectTestDir(list, dirName); |
1402 | QTRY_VERIFY(selectTestDir); |
1403 | |
1404 | #ifndef Q_OS_MAC |
1405 | QTest::keyClick(widget: list, key: Qt::Key_Return); |
1406 | #else |
1407 | QTest::keyClick(list, Qt::Key_O, Qt::ControlModifier); |
1408 | #endif |
1409 | |
1410 | QTRY_VERIFY(fd.directory().absolutePath() != workDirPath); |
1411 | QVERIFY(!lineEdit->text().isEmpty()); |
1412 | |
1413 | // When in Directory mode, lineEdit's text should be cleared when entering |
1414 | // a directory by activating one in the list so one can just hit ok |
1415 | // and it selects that directory |
1416 | fd.setFileMode(QFileDialog::Directory); |
1417 | fd.setWindowTitle(QLatin1String(QTest::currentTestFunction()) + QLatin1String(" Directory" )); |
1418 | fd.setDirectory(workDirPath); |
1419 | QTRY_VERIFY(dirPopulated); |
1420 | |
1421 | QTRY_VERIFY(selectTestDir); |
1422 | |
1423 | #ifndef Q_OS_MAC |
1424 | QTest::keyClick(widget: list, key: Qt::Key_Return); |
1425 | #else |
1426 | QTest::keyClick(list, Qt::Key_O, Qt::ControlModifier); |
1427 | #endif |
1428 | |
1429 | QTRY_VERIFY(fd.directory().absolutePath() != workDirPath); |
1430 | QVERIFY(lineEdit->text().isEmpty()); |
1431 | |
1432 | // QTBUG-71415: When pressing back, the selection (activated |
1433 | // directory) should be restored. |
1434 | QToolButton *backButton = fd.findChild<QToolButton*>(aName: "backButton" ); |
1435 | QVERIFY(backButton); |
1436 | QTreeView *treeView = fd.findChildren<QTreeView*>(aName: "treeView" ).value(i: 0); |
1437 | QVERIFY(treeView); |
1438 | backButton->click(); |
1439 | QTRY_COMPARE(treeView->selectionModel()->selectedIndexes().value(0).data().toString(), |
1440 | dirName); |
1441 | } |
1442 | |
1443 | void tst_QFiledialog::enableChooseButton() |
1444 | { |
1445 | QFileDialog fd; |
1446 | fd.setFileMode(QFileDialog::Directory); |
1447 | fd.show(); |
1448 | QDialogButtonBox *buttonBox = fd.findChild<QDialogButtonBox*>(aName: "buttonBox" ); |
1449 | QPushButton *button = buttonBox->button(which: QDialogButtonBox::Open); |
1450 | QVERIFY(button); |
1451 | QCOMPARE(button->isEnabled(), true); |
1452 | } |
1453 | |
1454 | void tst_QFiledialog::widgetlessNativeDialog() |
1455 | { |
1456 | if (!QGuiApplicationPrivate::platformTheme()->usePlatformNativeDialog(type: QPlatformTheme::FileDialog)) |
1457 | QSKIP("This platform always uses widgets to realize its QFileDialog, instead of the native file dialog." ); |
1458 | QApplication::setAttribute(attribute: Qt::AA_DontUseNativeDialogs, on: false); |
1459 | QFileDialog fd; |
1460 | fd.setWindowModality(Qt::ApplicationModal); |
1461 | fd.show(); |
1462 | QTRY_VERIFY(fd.isVisible()); |
1463 | QFileSystemModel *model = fd.findChild<QFileSystemModel*>(aName: "qt_filesystem_model" ); |
1464 | QVERIFY(!model); |
1465 | QPushButton *button = fd.findChild<QPushButton*>(); |
1466 | QVERIFY(!button); |
1467 | QApplication::setAttribute(attribute: Qt::AA_DontUseNativeDialogs, on: true); |
1468 | } |
1469 | |
1470 | void tst_QFiledialog::selectedFilesWithoutWidgets() |
1471 | { |
1472 | // Test for a crash when widgets are not instantiated yet. |
1473 | QFileDialog fd; |
1474 | fd.setAcceptMode(QFileDialog::AcceptOpen); |
1475 | QVERIFY(fd.selectedFiles().size() >= 0); |
1476 | } |
1477 | |
1478 | void tst_QFiledialog::trailingDotsAndSpaces() |
1479 | { |
1480 | #ifndef Q_OS_WIN |
1481 | QSKIP("This is only tested on Windows" ); |
1482 | #endif |
1483 | QFileDialog fd; |
1484 | fd.setViewMode(QFileDialog::List); |
1485 | fd.setFileMode(QFileDialog::ExistingFile); |
1486 | fd.show(); |
1487 | QLineEdit *lineEdit = fd.findChild<QLineEdit *>(aName: "fileNameEdit" ); |
1488 | QVERIFY(lineEdit); |
1489 | QListView *list = fd.findChild<QListView *>(aName: "listView" ); |
1490 | QVERIFY(list); |
1491 | QTest::qWait(ms: 1000); |
1492 | int currentChildrenCount = list->model()->rowCount(parent: list->rootIndex()); |
1493 | QTest::keyClick(widget: lineEdit, key: Qt::Key_Space); |
1494 | QTest::keyClick(widget: lineEdit, key: Qt::Key_Period); |
1495 | QTest::qWait(ms: 1000); |
1496 | QCOMPARE(currentChildrenCount, list->model()->rowCount(list->rootIndex())); |
1497 | lineEdit->clear(); |
1498 | QTest::keyClick(widget: lineEdit, key: Qt::Key_Period); |
1499 | QTest::keyClick(widget: lineEdit, key: Qt::Key_Space); |
1500 | QTest::qWait(ms: 1000); |
1501 | QCOMPARE(currentChildrenCount, list->model()->rowCount(list->rootIndex())); |
1502 | } |
1503 | |
1504 | #ifdef Q_OS_UNIX |
1505 | #ifdef QT_BUILD_INTERNAL |
1506 | void tst_QFiledialog::tildeExpansion_data() |
1507 | { |
1508 | QTest::addColumn<QString>(name: "tildePath" ); |
1509 | QTest::addColumn<QString>(name: "expandedPath" ); |
1510 | |
1511 | const QString tilde = QStringLiteral("~" ); |
1512 | const QString tildeUser = tilde + QString(qgetenv(varName: "USER" )); |
1513 | const QLatin1String someSubDir("/some/sub/dir" ); |
1514 | const QString homePath = QDir::homePath(); |
1515 | const QString invalid = QStringLiteral("~thisIsNotAValidUserName" ); |
1516 | |
1517 | QTest::newRow(dataTag: "empty path" ) << QString() << QString(); |
1518 | QTest::newRow(dataTag: "~" ) << tilde << homePath; |
1519 | QTest::newRow(dataTag: "~/some/sub/dir/" ) << tilde + someSubDir << homePath + someSubDir; |
1520 | QTest::newRow(dataTag: "~<user>" ) << tildeUser << homePath; |
1521 | QTest::newRow(dataTag: "~<user>/some/sub/dir" ) << tildeUser + someSubDir << homePath + someSubDir; |
1522 | QTest::newRow(dataTag: "invalid user name" ) << invalid << invalid; |
1523 | } |
1524 | #endif // QT_BUILD_INTERNAL |
1525 | |
1526 | #ifdef QT_BUILD_INTERNAL |
1527 | void tst_QFiledialog::tildeExpansion() |
1528 | { |
1529 | QFETCH(QString, tildePath); |
1530 | QFETCH(QString, expandedPath); |
1531 | |
1532 | QCOMPARE(qt_tildeExpansion(tildePath), expandedPath); |
1533 | } |
1534 | #endif // QT_BUILD_INTERNAL |
1535 | #endif |
1536 | |
1537 | class DialogRejecter : public QObject |
1538 | { |
1539 | Q_OBJECT |
1540 | public: |
1541 | DialogRejecter() |
1542 | { |
1543 | connect(qApp, signal: &QApplication::focusChanged, receiver: this, slot: &DialogRejecter::rejectFileDialog); |
1544 | } |
1545 | |
1546 | public slots: |
1547 | virtual void rejectFileDialog() |
1548 | { |
1549 | if (QWidget *w = QApplication::activeModalWidget()) |
1550 | if (QDialog *d = qobject_cast<QDialog *>(object: w)) |
1551 | QTest::keyClick(widget: d, key: Qt::Key_Escape); |
1552 | } |
1553 | }; |
1554 | |
1555 | void tst_QFiledialog::rejectModalDialogs() |
1556 | { |
1557 | if (QGuiApplication::platformName().startsWith(s: QLatin1String("wayland" ), cs: Qt::CaseInsensitive)) |
1558 | QSKIP("Wayland: This freezes. Figure out why." ); |
1559 | |
1560 | // QTBUG-38672 , static functions should return empty Urls |
1561 | DialogRejecter dr; |
1562 | |
1563 | QUrl url = QFileDialog::getOpenFileUrl(parent: 0, QStringLiteral("getOpenFileUrl" )); |
1564 | QVERIFY(url.isEmpty()); |
1565 | QVERIFY(!url.isValid()); |
1566 | |
1567 | url = QFileDialog::getExistingDirectoryUrl(parent: 0, QStringLiteral("getExistingDirectoryUrl" )); |
1568 | QVERIFY(url.isEmpty()); |
1569 | QVERIFY(!url.isValid()); |
1570 | |
1571 | url = QFileDialog::getSaveFileUrl(parent: 0, QStringLiteral("getSaveFileUrl" )); |
1572 | QVERIFY(url.isEmpty()); |
1573 | QVERIFY(!url.isValid()); |
1574 | |
1575 | // Same test with local files |
1576 | QString file = QFileDialog::getOpenFileName(parent: 0, QStringLiteral("getOpenFileName" )); |
1577 | QVERIFY(file.isEmpty()); |
1578 | |
1579 | file = QFileDialog::getExistingDirectory(parent: 0, QStringLiteral("getExistingDirectory" )); |
1580 | QVERIFY(file.isEmpty()); |
1581 | |
1582 | file = QFileDialog::getSaveFileName(parent: 0, QStringLiteral("getSaveFileName" )); |
1583 | QVERIFY(file.isEmpty()); |
1584 | } |
1585 | |
1586 | void tst_QFiledialog::QTBUG49600_nativeIconProviderCrash() |
1587 | { |
1588 | if (!QGuiApplicationPrivate::platformTheme()->usePlatformNativeDialog(type: QPlatformTheme::FileDialog)) |
1589 | QSKIP("This platform always uses widgets to realize its QFileDialog, instead of the native file dialog." ); |
1590 | QFileDialog fd; |
1591 | fd.iconProvider(); |
1592 | } |
1593 | |
1594 | class qtbug57193DialogRejecter : public DialogRejecter |
1595 | { |
1596 | public: |
1597 | void rejectFileDialog() override |
1598 | { |
1599 | QCOMPARE(QGuiApplication::topLevelWindows().size(), 1); |
1600 | const QWindow *window = QGuiApplication::topLevelWindows().constFirst(); |
1601 | |
1602 | const QFileDialog *fileDialog = qobject_cast<QFileDialog*>(object: QApplication::activeModalWidget()); |
1603 | if (!fileDialog) |
1604 | return; |
1605 | |
1606 | // The problem in QTBUG-57193 was from a platform input context plugin that was |
1607 | // connected to QWindow::focusObjectChanged(), and consequently accessed the focus |
1608 | // object (the QFileDialog) that was in the process of being destroyed. This test |
1609 | // checks that the QFileDialog is never set as the focus object after its destruction process begins. |
1610 | connect(sender: window, signal: &QWindow::focusObjectChanged, slot: [=](QObject *focusObject) { |
1611 | QVERIFY(focusObject != fileDialog); |
1612 | }); |
1613 | DialogRejecter::rejectFileDialog(); |
1614 | } |
1615 | }; |
1616 | |
1617 | void tst_QFiledialog::focusObjectDuringDestruction() |
1618 | { |
1619 | if (QGuiApplication::platformName().startsWith(s: QLatin1String("wayland" ), cs: Qt::CaseInsensitive)) |
1620 | QSKIP("Wayland: This freezes. Figure out why." ); |
1621 | |
1622 | QTRY_VERIFY(QGuiApplication::topLevelWindows().isEmpty()); |
1623 | |
1624 | qtbug57193DialogRejecter dialogRejecter; |
1625 | |
1626 | QFileDialog::getOpenFileName(parent: nullptr, caption: QString(), dir: QString(), filter: QString(), selectedFilter: nullptr); |
1627 | } |
1628 | |
1629 | QTEST_MAIN(tst_QFiledialog) |
1630 | #include "tst_qfiledialog.moc" |
1631 | |