diff --git a/src/kitemviews/kfileitemmodel.h b/src/kitemviews/kfileitemmodel.h --- a/src/kitemviews/kfileitemmodel.h +++ b/src/kitemviews/kfileitemmodel.h @@ -73,7 +73,7 @@ * the root-parent of all items. * @see rootItem() */ - QUrl directory() const; + QUrl directory() const Q_DECL_OVERRIDE; /** * Cancels the loading of a directory which has been started by either diff --git a/src/kitemviews/kitemlistcontroller.cpp b/src/kitemviews/kitemlistcontroller.cpp --- a/src/kitemviews/kitemlistcontroller.cpp +++ b/src/kitemviews/kitemlistcontroller.cpp @@ -39,6 +39,7 @@ #include #include #include +#include KItemListController::KItemListController(KItemModelBase* model, KItemListView* view, QObject* parent) : QObject(parent), @@ -842,6 +843,7 @@ Q_UNUSED(event); Q_UNUSED(transform); + m_autoActivationTimer->stop(); m_view->setAutoScroll(false); m_view->hideDropIndicator(); @@ -859,8 +861,8 @@ return false; } - event->acceptProposedAction(); + QUrl hoveredDir = m_model->directory(); KItemListWidget* oldHoveredWidget = hoveredWidget(); const QPointF pos = transform.map(event->pos()); @@ -883,6 +885,11 @@ } const int index = newHoveredWidget->index(); + + if (m_model->isDir(index)) { + hoveredDir = m_model->url(index); + } + if (!droppingBetweenItems) { if (m_model->supportsDropping(index)) { // Something has been dragged on an item. @@ -908,6 +915,8 @@ m_view->hideDropIndicator(); } + event->setAccepted(!DragAndDropHelper::urlListMatchesUrl(event->mimeData()->urls(), hoveredDir)); + return false; } diff --git a/src/kitemviews/kitemmodelbase.h b/src/kitemviews/kitemmodelbase.h --- a/src/kitemviews/kitemmodelbase.h +++ b/src/kitemviews/kitemmodelbase.h @@ -182,6 +182,20 @@ */ QString blacklistItemDropEventMimeType() const; + /** + * @return URL of the item at the specified index + */ + virtual QUrl url(int index) const; + + /** + * @return True, if item at specified index is a directory + */ + virtual bool isDir(int index) const; + + /** + * @return Parent directory of the items that are shown + */ + virtual QUrl directory() const; signals: /** * Is emitted if one or more items have been inserted. Each item-range consists diff --git a/src/kitemviews/kitemmodelbase.cpp b/src/kitemviews/kitemmodelbase.cpp --- a/src/kitemviews/kitemmodelbase.cpp +++ b/src/kitemviews/kitemmodelbase.cpp @@ -164,3 +164,17 @@ Q_UNUSED(previous); } +QUrl KItemModelBase::url(int index) const +{ + return data(index).value("url").toUrl(); +} + +bool KItemModelBase::isDir(int index) const +{ + return data(index).value("isDir").toBool(); +} + +QUrl KItemModelBase::directory() const +{ + return QUrl(); +} \ No newline at end of file diff --git a/src/panels/places/placesitemmodel.h b/src/panels/places/placesitemmodel.h --- a/src/panels/places/placesitemmodel.h +++ b/src/panels/places/placesitemmodel.h @@ -132,6 +132,7 @@ */ void saveBookmarks(); + bool isDir(int index) const Q_DECL_OVERRIDE; signals: void errorMessage(const QString& message); void storageSetupDone(int index, bool success); diff --git a/src/panels/places/placesitemmodel.cpp b/src/panels/places/placesitemmodel.cpp --- a/src/panels/places/placesitemmodel.cpp +++ b/src/panels/places/placesitemmodel.cpp @@ -1159,6 +1159,12 @@ return date; } +bool PlacesItemModel::isDir(int index) const +{ + Q_UNUSED(index); + return true; +} + QUrl PlacesItemModel::createSearchUrl(const QUrl& url) { QUrl searchUrl; diff --git a/src/tests/CMakeLists.txt b/src/tests/CMakeLists.txt --- a/src/tests/CMakeLists.txt +++ b/src/tests/CMakeLists.txt @@ -57,3 +57,5 @@ ecm_add_test(dolphinmainwindowtest.cpp TEST_NAME dolphinmainwindowtest LINK_LIBRARIES dolphinprivate dolphinstatic Qt5::Test) + +ecm_add_test(draganddrophelpertest.cpp LINK_LIBRARIES dolphinprivate Qt5::Test) \ No newline at end of file diff --git a/src/tests/draganddrophelpertest.cpp b/src/tests/draganddrophelpertest.cpp new file mode 100644 --- /dev/null +++ b/src/tests/draganddrophelpertest.cpp @@ -0,0 +1,81 @@ +/*************************************************************************** + * Copyright (C) 2017 by Emirald Mateli * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA * + ***************************************************************************/ + +#include +#include + +class DragAndDropHelperTest : public QObject +{ + Q_OBJECT + +private slots: + void testUrlListMatchesUrl_data(); + void testUrlListMatchesUrl(); +}; + +void DragAndDropHelperTest::testUrlListMatchesUrl_data() +{ + QTest::addColumn>("urlList"); + QTest::addColumn("url"); + QTest::addColumn("expected"); + + QTest::newRow("test_equal") + << QList {QUrl::fromLocalFile("/root")} + << QUrl::fromLocalFile("/root") + << true; + + QTest::newRow("test_trailing_slash") + << QList {QUrl::fromLocalFile("/root/")} + << QUrl::fromLocalFile("/root") + << true; + + QTest::newRow("test_ftp_scheme") + << QList {QUrl("ftp://server:2211/dir")} + << QUrl("ftp://server:2211/dir") + << true; + + QTest::newRow("test_not_matched") + << QList {QUrl::fromLocalFile("/usr/share"), QUrl::fromLocalFile("/usr/local/bin")} + << QUrl::fromLocalFile("/usr/bin") + << false; + + QTest::newRow("test_empty_target") + << QList {QUrl::fromLocalFile("/usr/share"), QUrl::fromLocalFile("/usr/local/bin")} + << QUrl() + << false; + + QTest::newRow("test_empty_list") + << QList() + << QUrl::fromLocalFile("/usr/bin") + << false; +} + +void DragAndDropHelperTest::testUrlListMatchesUrl() +{ + QFETCH(QList, urlList); + QFETCH(QUrl, url); + QFETCH(bool, expected); + + QCOMPARE(DragAndDropHelper::urlListMatchesUrl(urlList, url), expected); +} + + +QTEST_MAIN(DragAndDropHelperTest) + +#include "draganddrophelpertest.moc" diff --git a/src/views/draganddrophelper.h b/src/views/draganddrophelper.h --- a/src/views/draganddrophelper.h +++ b/src/views/draganddrophelper.h @@ -22,9 +22,10 @@ #define DRAGANDDROPHELPER_H #include "dolphin_export.h" +#include +#include -class QUrl; class QDropEvent; class QWidget; namespace KIO { class DropJob; } @@ -42,11 +43,17 @@ * is true. * @param event Drop event. * @param window Widget where the drop happened, will be used as parent of the drop menu. - * @return KIO::DropJob pointer + * @return KIO::DropJob pointer or null in case the destUrl is contained + * in the mimeData url list. */ static KIO::DropJob* dropUrls(const QUrl& destUrl, QDropEvent* event, QWidget *window); + + /** + * @return True if destUrl is contained in the urls parameter. + */ + static bool urlListMatchesUrl(const QList& urls, const QUrl& destUrl); }; #endif diff --git a/src/views/draganddrophelper.cpp b/src/views/draganddrophelper.cpp --- a/src/views/draganddrophelper.cpp +++ b/src/views/draganddrophelper.cpp @@ -29,6 +29,14 @@ #include #include + +bool DragAndDropHelper::urlListMatchesUrl(const QList& urls, const QUrl& destUrl) +{ + return std::find_if(urls.constBegin(), urls.constEnd(), [destUrl](const QUrl& url) { + return url.matches(destUrl, QUrl::StripTrailingSlash); + }) != urls.constEnd(); +} + KIO::DropJob* DragAndDropHelper::dropUrls(const QUrl& destUrl, QDropEvent* event, QWidget* window) { const QMimeData* mimeData = event->mimeData(); @@ -42,6 +50,10 @@ message.setArguments({destUrl.toDisplayString(QUrl::PreferLocalFile)}); QDBusConnection::sessionBus().call(message); } else { + if (urlListMatchesUrl(event->mimeData()->urls(), destUrl)) { + return nullptr; + } + // Drop into a directory or a desktop-file KIO::DropJob *job = KIO::drop(event, destUrl); KJobWidgets::setWindow(job, window);