| 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 | |