diff --git a/autotests/kfilewidgettest.cpp b/autotests/kfilewidgettest.cpp --- a/autotests/kfilewidgettest.cpp +++ b/autotests/kfilewidgettest.cpp @@ -32,6 +32,11 @@ #include #include "kiotesthelper.h" // createTestFile +#include +#include +#include +#include + /** * Unit test for KFileWidget */ @@ -364,6 +369,83 @@ QCOMPARE(fw.locationEdit()->currentText(), QStringLiteral("directory")); QCOMPARE(fw.filterWidget()->currentFilter(), QStringLiteral("*.txt")); } + + void testDropFile_data() + { + QTest::addColumn("dir"); + QTest::addColumn("fileName"); + QTest::addColumn("expectedCurrentText"); + + QTest::newRow("some.txt") + << "" + << "some.txt" + << "some.txt" + + // XXX perhaps the "extension" should not be modified when it does not + // match any of the existing types? Should "some.2019.txt" be expected? + QTest::newRow("subdir/some.txt") + << "subdir" + << "subdir/some.txt" + << "some.txt" + } + + void testDropFile() + { + QFETCH(QString, dir); + QFETCH(QString, fileName); + QFETCH(QString, expectedCurrentText); + + // Use a temporary directory since the presence of existing files + // influences whether an extension is automatically appended. + QTemporaryDir tempDir; + QUrl dirUrl = QUrl::fromLocalFile(tempDir.path()); + const QUrl fileUrl = QUrl::fromLocalFile(tempDir.filePath(fileName)); + if (!dir.isEmpty()) { + createTestDirectory(tempDir.filePath(dir)); + dirUrl = QUrl::fromLocalFile(tempDir.filePath(dir)); + } + createTestFile(tempDir.filePath(fileName)); + + QDir f(tempDir.filePath(dir)); + + KFileWidget fw(QUrl::fromLocalFile(tempDir.path())); + fw.setOperationMode(KFileWidget::Opening); + fw.setMode(KFile::File); + fw.show(); + fw.activateWindow(); + + QList list_urls = {fileUrl}; + QMimeData *mimeData = new QMimeData(); + mimeData->setUrls(list_urls); + + QDragEnterEvent event1(QPoint(), Qt::DropAction::MoveAction, mimeData, Qt::MouseButton::LeftButton, Qt::KeyboardModifier::NoModifier); + + QVERIFY(qApp->sendEvent(fw.dirOperator()->view()->viewport(), &event1)); + + // Fake drop + QDropEvent event(QPoint(), Qt::DropAction::MoveAction, mimeData, Qt::MouseButton::LeftButton, Qt::KeyboardModifier::NoModifier); + + QVERIFY(qApp->sendEvent(fw.dirOperator()->view()->viewport(), &event)); + + QVERIFY(QTest::qWaitForWindowActive(&fw)); + + // once we drop a file the dirlister scans the dir + // wait a bit to the dirlister time to finish + QTest::qWait(100); + + // Verify the expected populated name. + QCOMPARE(fw.baseUrl().adjusted(QUrl::StripTrailingSlash), dirUrl); + QCOMPARE(fw.locationEdit()->currentText(), expectedCurrentText); + + // QFileDialog ends up calling KDEPlatformFileDialog::selectedFiles() + // which calls KFileWidget::selectedUrls(). + // Accept the filename to ensure that a filename is selected. + connect(&fw, &KFileWidget::accepted, &fw, &KFileWidget::accept); + QTest::keyClick(fw.locationEdit(), Qt::Key_Return); + QList urls = fw.selectedUrls(); + QCOMPARE(urls.size(), 1); + QCOMPARE(urls[0], fileUrl); + } }; QTEST_MAIN(KFileWidgetTest) diff --git a/src/filewidgets/kdiroperator.h b/src/filewidgets/kdiroperator.h --- a/src/filewidgets/kdiroperator.h +++ b/src/filewidgets/kdiroperator.h @@ -550,7 +550,7 @@ void setupMenu(int whichActions); /** - * Reimplemented - allow dropping of files if @p b is true + * Reimplemented - allow dropping of files if @p b is true, defaults to true since 5.58 * @param b true if the widget should allow dropping of files */ virtual void setAcceptDrops(bool b); diff --git a/src/filewidgets/kdiroperator.cpp b/src/filewidgets/kdiroperator.cpp --- a/src/filewidgets/kdiroperator.cpp +++ b/src/filewidgets/kdiroperator.cpp @@ -28,6 +28,7 @@ #include "kfilemetapreview_p.h" #include "kpreviewwidgetbase.h" #include "knewfilemenu.h" +#include #include "../pathhelpers_p.h" #include @@ -194,6 +195,8 @@ KFile::FileView allViews(); + QMetaObject::Connection m_connection; + // private slots void _k_slotDetailedView(); void _k_slotSimpleView(); @@ -414,6 +417,7 @@ d->updateSorting(QDir::Name | QDir::DirsFirst); setFocusPolicy(Qt::WheelFocus); + setAcceptDrops(true); } KDirOperator::~KDirOperator() @@ -1379,6 +1383,49 @@ } } break; + case QEvent::DragEnter: { + // Accepts drops of one file or folder only + QDragEnterEvent *evt = static_cast(event); + const QList urls = KUrlMimeData::urlsFromMimeData(evt->mimeData()); + // only one file/folder can be dropped at the moment + event->setAccepted(urls.size() == 1); + } + break; + case QEvent::Drop: { + QDropEvent *evt = static_cast(event); + const QList urls = KUrlMimeData::urlsFromMimeData(evt->mimeData(), KUrlMimeData::DecodeOptions::PreferLocalUrls); + + const QUrl url = urls.constFirst(); + + // stat the url to get details + KIO::StatJob *job = KIO::stat(url, KIO::HideProgressInfo); + job->exec(); + + this->setFocus(); + + KIO::UDSEntry entry = job->statResult(); + + if (entry.isDir()) { + // if this was a directory + setUrl(url, false); + } else { + // if the current url is not known + if (d->dirLister->findByUrl(url).isNull()) { + setUrl(url.adjusted(QUrl::RemoveFilename), false); + + // Will set the current item once loading has finished + auto urlSetterClosure = [this, url](){ + this->setCurrentItem(url); + QObject::disconnect(d->m_connection); + }; + d->m_connection = connect(this, &KDirOperator::finishedLoading, this, urlSetterClosure); + } else { + this->setCurrentItem(url); + } + } + evt->accept(); + } + break; default: break; } @@ -1461,12 +1508,17 @@ return itemView; } -void KDirOperator::setAcceptDrops(bool b) +void KDirOperator::setAcceptDrops(bool acceptsDrops) { - // TODO: - //if (d->fileView) - // d->fileView->widget()->setAcceptDrops(b); - QWidget::setAcceptDrops(b); + QWidget::setAcceptDrops(acceptsDrops); + if (view()) { + view()->setAcceptDrops(acceptsDrops); + if (acceptsDrops) { + view()->installEventFilter(this); + } else { + view()->removeEventFilter(this); + } + } } void KDirOperator::setDropOptions(int options) @@ -1503,6 +1555,11 @@ QAbstractItemView *newView = createView(this, viewKind); setView(newView); + if (this->acceptDrops()) { + newView->setAcceptDrops(true); + newView->installEventFilter(this); + } + d->_k_togglePreview(preview); } diff --git a/src/widgets/kdirmodel.h b/src/widgets/kdirmodel.h --- a/src/widgets/kdirmodel.h +++ b/src/widgets/kdirmodel.h @@ -243,6 +243,8 @@ */ bool jobTransfersVisible() const; + Qt::DropActions supportedDropActions() const override; + Q_SIGNALS: /** * Emitted for each subdirectory that is a parent of a url passed to expandToUrl diff --git a/src/widgets/kdirmodel.cpp b/src/widgets/kdirmodel.cpp --- a/src/widgets/kdirmodel.cpp +++ b/src/widgets/kdirmodel.cpp @@ -419,6 +419,12 @@ [this](const QUrl &oldUrl, const QUrl &newUrl){d->_k_slotRedirection(oldUrl, newUrl);} ); } +Qt::DropActions KDirModel::supportedDropActions() const +{ + return Qt::CopyAction | Qt::MoveAction | Qt::LinkAction | Qt::IgnoreAction; +} + + KDirLister *KDirModel::dirLister() const { return d->m_dirLister; diff --git a/tests/kfilewidgettest_gui.cpp b/tests/kfilewidgettest_gui.cpp --- a/tests/kfilewidgettest_gui.cpp +++ b/tests/kfilewidgettest_gui.cpp @@ -31,6 +31,7 @@ fileWidget->setAttribute(Qt::WA_DeleteOnClose); fileWidget->show(); + QObject::connect(fileWidget, &KFileWidget::destroyed, &app, &QApplication::quit); + return app.exec(); } -