diff --git a/autotests/kfileplacesmodeltest.cpp b/autotests/kfileplacesmodeltest.cpp index 1a583086..de840e3f 100644 --- a/autotests/kfileplacesmodeltest.cpp +++ b/autotests/kfileplacesmodeltest.cpp @@ -1,1260 +1,1261 @@ /* This file is part of the KDE project Copyright (C) 2007 Kevin Ottens This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License version 2 as published by the Free Software Foundation. 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 #include #include #include #include #include #include #include #include #include #include #include Q_DECLARE_METATYPE(KFilePlacesModel::GroupType) // Avoid QHash randomization so that the order of the devices is stable static void seedInit() { qputenv("QT_HASH_SEED", "0"); // This env var has no effect because this comes too late. qCpuFeatures() was already called by // a Q_CONSTRUCTOR_FUNCTION inside QtGui (see image/qimage_conversions.cpp). Argh. QTBUG-47566. qputenv("QT_NO_CPU_FEATURE", "sse4.2"); } Q_CONSTRUCTOR_FUNCTION(seedInit) class KFilePlacesModelTest : public QObject { Q_OBJECT private Q_SLOTS: void initTestCase(); void cleanupTestCase(); void testInitialState(); void testInitialList(); void testReparse(); void testInternalBookmarksHaveIds(); void testHiding(); void testMove(); void testPlacesLifecycle(); void testDevicePlugging(); void testDragAndDrop(); void testDeviceSetupTeardown(); void testEnableBaloo(); void testRemoteUrls_data(); void testRemoteUrls(); void testRefresh(); void testConvertedUrl_data(); void testConvertedUrl(); void testBookmarkObject(); void testDataChangedSignal(); void testIconRole_data(); void testIconRole(); void testMoveFunction(); void testPlaceGroupHidden(); void testPlaceGroupHiddenVsPlaceChildShown(); void testPlaceGroupHiddenAndShownWithHiddenChild(); void testPlaceGroupHiddenGroupIndexesIntegrity(); void testPlaceGroupHiddenSignal(); void testPlaceGroupHiddenRole(); void testFilterWithAlternativeApplicationName(); void testSupportedSchemes(); private: QStringList placesUrls(KFilePlacesModel *model = nullptr) const; QDBusInterface *fakeManager(); QDBusInterface *fakeDevice(const QString &udi); KFilePlacesModel *m_places; KFilePlacesModel *m_places2; // To check that they always stay in sync // actually supposed to work across processes, // but much harder to test QMap m_interfacesMap; QTemporaryDir m_tmpHome; }; static QString bookmarksFile() { return QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + QStringLiteral("/user-places.xbel"); } void KFilePlacesModelTest::initTestCase() { QVERIFY(m_tmpHome.isValid()); qputenv("HOME", m_tmpHome.path().toUtf8()); // use a empty home dir qputenv("KDE_FORK_SLAVES", "yes"); // to avoid a runtime dependency on klauncher QStandardPaths::setTestModeEnabled(true); // Ensure we'll have a clean bookmark file to start QFile::remove(bookmarksFile()); // disable baloo by default KConfig config(QStringLiteral("baloofilerc")); KConfigGroup basicSettings = config.group("Basic Settings"); basicSettings.writeEntry("Indexing-Enabled", false); config.sync(); qRegisterMetaType(); qRegisterMetaType(); const QString fakeHw = QFINDTESTDATA("fakecomputer.xml"); QVERIFY(!fakeHw.isEmpty()); qputenv("SOLID_FAKEHW", QFile::encodeName(fakeHw)); m_places = new KFilePlacesModel(); m_places2 = new KFilePlacesModel(); } void KFilePlacesModelTest::cleanupTestCase() { delete m_places; delete m_places2; qDeleteAll(m_interfacesMap); QFile::remove(bookmarksFile()); } QStringList KFilePlacesModelTest::placesUrls(KFilePlacesModel *model) const { KFilePlacesModel *currentModel = model; if (!currentModel) { currentModel = m_places; } QStringList urls; for (int row = 0; row < currentModel->rowCount(); ++row) { QModelIndex index = currentModel->index(row, 0); urls << currentModel->url(index).toDisplayString(QUrl::PreferLocalFile); } return urls; } #define CHECK_PLACES_URLS(urls) \ if (placesUrls() != urls) { \ qDebug() << "Expected:" << urls; \ qDebug() << "Got:" << placesUrls(); \ QCOMPARE(placesUrls(), urls); \ } \ for (int row = 0; row < urls.size(); ++row) { \ QModelIndex index = m_places->index(row, 0); \ \ QCOMPARE(m_places->url(index).toString(), QUrl::fromUserInput(urls[row]).toString()); \ QCOMPARE(m_places->data(index, KFilePlacesModel::UrlRole).toUrl(), \ QUrl(m_places->url(index))); \ \ index = m_places2->index(row, 0); \ \ QCOMPARE(m_places2->url(index).toString(), QUrl::fromUserInput(urls[row]).toString()); \ QCOMPARE(m_places2->data(index, KFilePlacesModel::UrlRole).toUrl(), \ QUrl(m_places2->url(index))); \ } \ \ QCOMPARE(urls.size(), m_places->rowCount()); \ QCOMPARE(urls.size(), m_places2->rowCount()); QDBusInterface *KFilePlacesModelTest::fakeManager() { return fakeDevice(QStringLiteral("/org/kde/solid/fakehw")); } QDBusInterface *KFilePlacesModelTest::fakeDevice(const QString &udi) { QDBusInterface *interface = m_interfacesMap[udi]; if (interface) { return interface; } QDBusInterface *iface = new QDBusInterface(QDBusConnection::sessionBus().baseService(), udi); m_interfacesMap[udi] = iface; return iface; } void KFilePlacesModelTest::testInitialState() { QCOMPARE(m_places->rowCount(), 3); // when the xbel file is empty, KFilePlacesModel fills it with 3 default items QCoreApplication::processEvents(); // Devices have a delayed loading QCOMPARE(m_places->rowCount(), 8); } static const QStringList initialListOfPlaces() { return QStringList() << QDir::homePath() << QStringLiteral("trash:/"); } static const QStringList initialListOfShared() { return QStringList() << QStringLiteral("remote:/") << QStringLiteral("/media/nfs"); } static const QStringList initialListOfDevices() { return QStringList() << QStringLiteral("/foreign"); } static const QStringList initialListOfRemovableDevices() { return QStringList() << QStringLiteral("/media/floppy0") << QStringLiteral("/media/XO-Y4") << QStringLiteral("/media/cdrom"); } static const QStringList initialListOfUrls() { return QStringList() << initialListOfPlaces() << initialListOfShared() << initialListOfDevices() << initialListOfRemovableDevices(); } void KFilePlacesModelTest::testInitialList() { const QStringList urls = initialListOfUrls(); CHECK_PLACES_URLS(urls); } void KFilePlacesModelTest::testReparse() { QStringList urls; // add item m_places->addPlace(QStringLiteral("foo"), QUrl::fromLocalFile(QStringLiteral("/foo")), QString(), QString()); urls = initialListOfUrls(); // it will be added at the end of places section urls.insert(2, QStringLiteral("/foo")); CHECK_PLACES_URLS(urls); // reparse the bookmark file KBookmarkManager *bookmarkManager = KBookmarkManager::managerForFile(bookmarksFile(), QStringLiteral("kfilePlaces")); bookmarkManager->notifyCompleteChange(QString()); // check if they are the same CHECK_PLACES_URLS(urls); // try to remove item m_places->removePlace(m_places->index(2, 0)); urls = initialListOfUrls(); CHECK_PLACES_URLS(urls); } void KFilePlacesModelTest::testInternalBookmarksHaveIds() { KBookmarkManager *bookmarkManager = KBookmarkManager::managerForFile(bookmarksFile(), QStringLiteral("kfilePlaces")); KBookmarkGroup root = bookmarkManager->root(); // Verify every entry has an id or an udi KBookmark bookmark = root.first(); while (!bookmark.isNull()) { QVERIFY(!bookmark.metaDataItem(QStringLiteral("ID")).isEmpty() || !bookmark.metaDataItem(QStringLiteral("UDI")).isEmpty()); // It's mutually exclusive though QVERIFY(bookmark.metaDataItem(QStringLiteral("ID")).isEmpty() || bookmark.metaDataItem(QStringLiteral("UDI")).isEmpty()); bookmark = root.next(bookmark); } // Verify that adding a bookmark behind its back the model gives it an id // (in real life it requires the user to modify the file by hand, // unlikely but better safe than sorry). // It induces a small race condition which means several ids will be // successively set on the same bookmark but no big deal since it won't // break the system KBookmark foo = root.addBookmark(QStringLiteral("Foo"), QUrl(QStringLiteral("file:/foo")), QStringLiteral("red-folder")); QCOMPARE(foo.text(), QStringLiteral("Foo")); QVERIFY(foo.metaDataItem(QStringLiteral("ID")).isEmpty()); bookmarkManager->emitChanged(root); QCOMPARE(foo.text(), QStringLiteral("Foo")); QVERIFY(!foo.metaDataItem(QStringLiteral("ID")).isEmpty()); // Verify that all the ids are different bookmark = root.first(); QSet ids; while (!bookmark.isNull()) { QString id; if (!bookmark.metaDataItem(QStringLiteral("UDI")).isEmpty()) { id = bookmark.metaDataItem(QStringLiteral("UDI")); } else { id = bookmark.metaDataItem(QStringLiteral("ID")); } if (ids.contains(id)) { // Some debugging help qDebug() << "Bookmarks file:" << bookmarksFile() << "contains one (or more) duplicated bookmarks:"; QFile debugFile(bookmarksFile()); QVERIFY(debugFile.open(QIODevice::ReadOnly)); qDebug() << QString::fromUtf8(debugFile.readAll()); QVERIFY2(!ids.contains(id), qPrintable("Duplicated ID found: " + id)); } ids << id; bookmark = root.next(bookmark); } // Cleanup foo root.deleteBookmark(foo); bookmarkManager->emitChanged(root); } void KFilePlacesModelTest::testHiding() { // Verify that nothing is hidden for (int row = 0; row < m_places->rowCount(); ++row) { QModelIndex index = m_places->index(row, 0); QVERIFY(!m_places->isHidden(index)); } QModelIndex a = m_places->index(2, 0); QModelIndex b = m_places->index(6, 0); QList args; QSignalSpy spy(m_places, SIGNAL(dataChanged(QModelIndex,QModelIndex))); // Verify that hidden is taken into account and is not global m_places->setPlaceHidden(a, true); QVERIFY(m_places->isHidden(a)); QVERIFY(m_places->data(a, KFilePlacesModel::HiddenRole).toBool()); QVERIFY(!m_places->isHidden(b)); QVERIFY(!m_places->data(b, KFilePlacesModel::HiddenRole).toBool()); QCOMPARE(spy.count(), 1); args = spy.takeFirst(); QCOMPARE(args.at(0).toModelIndex(), a); QCOMPARE(args.at(1).toModelIndex(), a); m_places->setPlaceHidden(b, true); QVERIFY(m_places->isHidden(a)); QVERIFY(m_places->data(a, KFilePlacesModel::HiddenRole).toBool()); QVERIFY(m_places->isHidden(b)); QVERIFY(m_places->data(b, KFilePlacesModel::HiddenRole).toBool()); QCOMPARE(spy.count(), 1); args = spy.takeFirst(); QCOMPARE(args.at(0).toModelIndex(), b); QCOMPARE(args.at(1).toModelIndex(), b); m_places->setPlaceHidden(a, false); m_places->setPlaceHidden(b, false); QVERIFY(!m_places->isHidden(a)); QVERIFY(!m_places->data(a, KFilePlacesModel::HiddenRole).toBool()); QVERIFY(!m_places->isHidden(b)); QVERIFY(!m_places->data(b, KFilePlacesModel::HiddenRole).toBool()); QCOMPARE(spy.count(), 2); args = spy.takeFirst(); QCOMPARE(args.at(0).toModelIndex(), a); QCOMPARE(args.at(1).toModelIndex(), a); args = spy.takeFirst(); QCOMPARE(args.at(0).toModelIndex(), b); QCOMPARE(args.at(1).toModelIndex(), b); } void KFilePlacesModelTest::testMove() { QList args; QSignalSpy spy_inserted(m_places, SIGNAL(rowsInserted(QModelIndex,int,int))); QSignalSpy spy_removed(m_places, SIGNAL(rowsRemoved(QModelIndex,int,int))); KBookmarkManager *bookmarkManager = KBookmarkManager::managerForFile(bookmarksFile(), QStringLiteral("kfilePlaces")); KBookmarkGroup root = bookmarkManager->root(); KBookmark system_home = m_places->bookmarkForIndex(m_places->index(0, 0)); // Trying move the root at the end of the list, should move it to the end of places section instead // to keep it grouped KBookmark last = root.first(); while (!root.next(last).isNull()) { last = root.next(last); } root.moveBookmark(system_home, last); bookmarkManager->emitChanged(root); QStringList urls; urls << QStringLiteral("trash:/") << QDir::homePath() << initialListOfShared() << initialListOfDevices() << initialListOfRemovableDevices(); CHECK_PLACES_URLS(urls); QCOMPARE(spy_inserted.count(), 1); args = spy_inserted.takeFirst(); QCOMPARE(args.at(0).toModelIndex(), QModelIndex()); QCOMPARE(args.at(1).toInt(), 1); QCOMPARE(args.at(2).toInt(), 1); QCOMPARE(spy_removed.count(), 1); args = spy_removed.takeFirst(); QCOMPARE(args.at(0).toModelIndex(), QModelIndex()); QCOMPARE(args.at(1).toInt(), 0); QCOMPARE(args.at(2).toInt(), 0); // Move home at the beginning of the list (at its original place) root.moveBookmark(system_home, KBookmark()); bookmarkManager->emitChanged(root); urls.clear(); urls << initialListOfPlaces() << initialListOfShared() << initialListOfDevices() << initialListOfRemovableDevices(); CHECK_PLACES_URLS(urls); QCOMPARE(spy_inserted.count(), 1); args = spy_inserted.takeFirst(); QCOMPARE(args.at(0).toModelIndex(), QModelIndex()); QCOMPARE(args.at(1).toInt(), 1); QCOMPARE(args.at(2).toInt(), 1); QCOMPARE(spy_removed.count(), 1); args = spy_removed.takeFirst(); QCOMPARE(args.at(0).toModelIndex(), QModelIndex()); QCOMPARE(args.at(1).toInt(), 0); QCOMPARE(args.at(2).toInt(), 0); } void KFilePlacesModelTest::testDragAndDrop() { QList args; QSignalSpy spy_moved(m_places, SIGNAL(rowsMoved(QModelIndex,int,int,QModelIndex,int))); // Monitor rowsInserted() and rowsRemoved() to ensure they are never emitted: // Moving with drag and drop is expected to emit rowsMoved() QSignalSpy spy_inserted(m_places, SIGNAL(rowsInserted(QModelIndex,int,int))); QSignalSpy spy_removed(m_places, SIGNAL(rowsRemoved(QModelIndex,int,int))); // Move /home at the end of the places list QModelIndexList indexes; indexes << m_places->index(0, 0); QMimeData *mimeData = m_places->mimeData(indexes); QVERIFY(m_places->dropMimeData(mimeData, Qt::MoveAction, 2, 0, QModelIndex())); QStringList urls; urls << QStringLiteral("trash:/") << QDir::homePath() << initialListOfShared() << initialListOfDevices() << initialListOfRemovableDevices(); CHECK_PLACES_URLS(urls); QCOMPARE(spy_inserted.count(), 0); QCOMPARE(spy_removed.count(), 0); QCOMPARE(spy_moved.count(), 1); args = spy_moved.takeFirst(); QCOMPARE(args.at(0).toModelIndex(), QModelIndex()); QCOMPARE(args.at(1).toInt(), 0); QCOMPARE(args.at(2).toInt(), 0); QCOMPARE(args.at(3).toModelIndex(), QModelIndex()); QCOMPARE(args.at(4).toInt(), 2); // Move home back at the beginning of the list indexes.clear(); indexes << m_places->index(1, 0); mimeData = m_places->mimeData(indexes); QVERIFY(m_places->dropMimeData(mimeData, Qt::MoveAction, 0, 0, QModelIndex())); urls.clear(); urls << QDir::homePath() << QStringLiteral("trash:/") << initialListOfShared() << initialListOfDevices() << initialListOfRemovableDevices(); CHECK_PLACES_URLS(urls); QCOMPARE(spy_inserted.count(), 0); QCOMPARE(spy_removed.count(), 0); QCOMPARE(spy_moved.count(), 1); args = spy_moved.takeFirst(); QCOMPARE(args.at(0).toModelIndex(), QModelIndex()); QCOMPARE(args.at(1).toInt(), 1); QCOMPARE(args.at(2).toInt(), 1); QCOMPARE(args.at(3).toModelIndex(), QModelIndex()); QCOMPARE(args.at(4).toInt(), 0); // Dropping on an item is not allowed indexes.clear(); indexes << m_places->index(4, 0); mimeData = m_places->mimeData(indexes); QVERIFY(!m_places->dropMimeData(mimeData, Qt::MoveAction, -1, 0, m_places->index(2, 0))); CHECK_PLACES_URLS(urls); QCOMPARE(spy_inserted.count(), 0); QCOMPARE(spy_removed.count(), 0); QCOMPARE(spy_moved.count(), 0); } void KFilePlacesModelTest::testPlacesLifecycle() { QList args; QSignalSpy spy_inserted(m_places, SIGNAL(rowsInserted(QModelIndex,int,int))); QSignalSpy spy_removed(m_places, SIGNAL(rowsRemoved(QModelIndex,int,int))); QSignalSpy spy_changed(m_places, SIGNAL(dataChanged(QModelIndex,QModelIndex))); m_places->addPlace(QStringLiteral("Foo"), QUrl::fromLocalFile(QStringLiteral("/home/foo"))); QStringList urls; urls << initialListOfPlaces() << QStringLiteral("/home/foo") << initialListOfShared() << initialListOfDevices() << initialListOfRemovableDevices(); CHECK_PLACES_URLS(urls); QCOMPARE(spy_inserted.count(), 1); args = spy_inserted.takeFirst(); QCOMPARE(args.at(0).toModelIndex(), QModelIndex()); QCOMPARE(args.at(1).toInt(), 2); QCOMPARE(args.at(2).toInt(), 2); QCOMPARE(spy_removed.count(), 0); KBookmarkManager *bookmarkManager = KBookmarkManager::managerForFile(bookmarksFile(), QStringLiteral("kfilePlaces")); KBookmarkGroup root = bookmarkManager->root(); KBookmark before_trash = m_places->bookmarkForIndex(m_places->index(0, 0)); KBookmark foo = m_places->bookmarkForIndex(m_places->index(2, 0)); root.moveBookmark(foo, before_trash); bookmarkManager->emitChanged(root); urls.clear(); urls << QDir::homePath() << QStringLiteral("/home/foo") << QStringLiteral("trash:/") << initialListOfShared() << initialListOfDevices() << initialListOfRemovableDevices(); CHECK_PLACES_URLS(urls); QCOMPARE(spy_inserted.count(), 1); args = spy_inserted.takeFirst(); QCOMPARE(args.at(0).toModelIndex(), QModelIndex()); QCOMPARE(args.at(1).toInt(), 2); QCOMPARE(args.at(2).toInt(), 2); QCOMPARE(spy_removed.count(), 1); args = spy_removed.takeFirst(); QCOMPARE(args.at(0).toModelIndex(), QModelIndex()); QCOMPARE(args.at(1).toInt(), 1); QCOMPARE(args.at(2).toInt(), 1); m_places->editPlace(m_places->index(1, 0), QStringLiteral("Foo"), QUrl::fromLocalFile(QStringLiteral("/mnt/foo"))); urls.clear(); urls << QDir::homePath() << QStringLiteral("/mnt/foo") << QStringLiteral("trash:/") << initialListOfShared() << initialListOfDevices() << initialListOfRemovableDevices(); CHECK_PLACES_URLS(urls); QCOMPARE(spy_inserted.count(), 0); QCOMPARE(spy_removed.count(), 0); QCOMPARE(spy_changed.count(), 1); args = spy_changed.takeFirst(); QCOMPARE(args.at(0).toModelIndex(), m_places->index(1, 0)); QCOMPARE(args.at(1).toModelIndex(), m_places->index(1, 0)); foo = m_places->bookmarkForIndex(m_places->index(1, 0)); foo.setFullText(QStringLiteral("Bar")); bookmarkManager->notifyCompleteChange(QString()); urls.clear(); urls << QDir::homePath() << QStringLiteral("/mnt/foo") << QStringLiteral("trash:/") << initialListOfShared() << initialListOfDevices() << initialListOfRemovableDevices(); CHECK_PLACES_URLS(urls); QCOMPARE(spy_inserted.count(), 0); QCOMPARE(spy_removed.count(), 0); QCOMPARE(spy_changed.count(), 9); args = spy_changed[2]; QCOMPARE(args.at(0).toModelIndex(), m_places->index(2, 0)); QCOMPARE(args.at(1).toModelIndex(), m_places->index(2, 0)); spy_changed.clear(); m_places->removePlace(m_places->index(1, 0)); urls.clear(); urls << initialListOfPlaces() << initialListOfShared() << initialListOfDevices() << initialListOfRemovableDevices(); CHECK_PLACES_URLS(urls); QCOMPARE(spy_inserted.count(), 0); QCOMPARE(spy_removed.count(), 1); args = spy_removed.takeFirst(); QCOMPARE(args.at(0).toModelIndex(), QModelIndex()); QCOMPARE(args.at(1).toInt(), 1); QCOMPARE(args.at(2).toInt(), 1); m_places->addPlace(QStringLiteral("Foo"), QUrl::fromLocalFile(QStringLiteral("/home/foo")), QString(), QString(), m_places->index(0, 0)); urls.clear(); urls << QDir::homePath() << QStringLiteral("/home/foo") << QStringLiteral("trash:/") << initialListOfShared() << initialListOfDevices() << initialListOfRemovableDevices(); CHECK_PLACES_URLS(urls); QCOMPARE(spy_inserted.count(), 1); args = spy_inserted.takeFirst(); QCOMPARE(args.at(0).toModelIndex(), QModelIndex()); QCOMPARE(args.at(1).toInt(), 1); QCOMPARE(args.at(2).toInt(), 1); QCOMPARE(spy_removed.count(), 0); m_places->removePlace(m_places->index(1, 0)); } void KFilePlacesModelTest::testDevicePlugging() { QList args; QSignalSpy spy_inserted(m_places, SIGNAL(rowsInserted(QModelIndex,int,int))); QSignalSpy spy_removed(m_places, SIGNAL(rowsRemoved(QModelIndex,int,int))); fakeManager()->call(QStringLiteral("unplug"), "/org/kde/solid/fakehw/volume_part1_size_993284096"); QStringList urls; urls << initialListOfPlaces() << initialListOfShared() << initialListOfDevices() << QStringLiteral("/media/floppy0") << QStringLiteral("/media/cdrom"); CHECK_PLACES_URLS(urls); QCOMPARE(spy_inserted.count(), 0); QCOMPARE(spy_removed.count(), 1); args = spy_removed.takeFirst(); QCOMPARE(args.at(0).toModelIndex(), QModelIndex()); QCOMPARE(args.at(1).toInt(), 6); QCOMPARE(args.at(2).toInt(), 6); fakeManager()->call(QStringLiteral("plug"), "/org/kde/solid/fakehw/volume_part1_size_993284096"); urls.clear(); urls << initialListOfPlaces() << initialListOfShared() << initialListOfDevices() << initialListOfRemovableDevices(); CHECK_PLACES_URLS(urls); QCOMPARE(spy_inserted.count(), 1); args = spy_inserted.takeFirst(); QCOMPARE(args.at(0).toModelIndex(), QModelIndex()); QCOMPARE(args.at(1).toInt(), 6); QCOMPARE(args.at(2).toInt(), 6); QCOMPARE(spy_removed.count(), 0); // Move the device in the list, and check that it memorizes the position across plug/unplug KBookmarkManager *bookmarkManager = KBookmarkManager::managerForFile(bookmarksFile(), QStringLiteral("kfilePlaces")); KBookmarkGroup root = bookmarkManager->root(); KBookmark before_floppy; KBookmark device = root.first(); // The device we'll move is the 6th bookmark for (int i = 0; i < 5; i++) { if (i == 2) { // store item before to be able to move it back to original position device = before_floppy = root.next(device); } else { device = root.next(device); } } root.moveBookmark(device, before_floppy); bookmarkManager->emitChanged(root); urls.clear(); urls << initialListOfPlaces() << initialListOfShared() << initialListOfDevices() << QStringLiteral("/media/XO-Y4") << QStringLiteral("/media/floppy0") << QStringLiteral("/media/cdrom"); CHECK_PLACES_URLS(urls); QCOMPARE(spy_inserted.count(), 1); args = spy_inserted.takeFirst(); QCOMPARE(args.at(0).toModelIndex(), QModelIndex()); QCOMPARE(args.at(1).toInt(), 6); QCOMPARE(args.at(2).toInt(), 6); QCOMPARE(spy_removed.count(), 1); args = spy_removed.takeFirst(); QCOMPARE(args.at(0).toModelIndex(), QModelIndex()); QCOMPARE(args.at(1).toInt(), 5); QCOMPARE(args.at(2).toInt(), 5); fakeManager()->call(QStringLiteral("unplug"), "/org/kde/solid/fakehw/volume_part1_size_993284096"); urls.clear(); urls << initialListOfPlaces() << initialListOfShared() << initialListOfDevices() << QStringLiteral("/media/floppy0") << QStringLiteral("/media/cdrom"); CHECK_PLACES_URLS(urls); QCOMPARE(spy_inserted.count(), 0); QCOMPARE(spy_removed.count(), 1); args = spy_removed.takeFirst(); QCOMPARE(args.at(0).toModelIndex(), QModelIndex()); QCOMPARE(args.at(1).toInt(), 5); QCOMPARE(args.at(2).toInt(), 5); fakeManager()->call(QStringLiteral("plug"), "/org/kde/solid/fakehw/volume_part1_size_993284096"); urls.clear(); urls << initialListOfPlaces() << initialListOfShared() << initialListOfDevices() << QStringLiteral("/media/XO-Y4") << QStringLiteral("/media/floppy0") << QStringLiteral("/media/cdrom"); CHECK_PLACES_URLS(urls); QCOMPARE(spy_inserted.count(), 1); args = spy_inserted.takeFirst(); QCOMPARE(args.at(0).toModelIndex(), QModelIndex()); QCOMPARE(args.at(1).toInt(), 5); QCOMPARE(args.at(2).toInt(), 5); QCOMPARE(spy_removed.count(), 0); KBookmark sixth = root.first(); for (int i = 0; i < 5; i++) { sixth = root.next(sixth); } root.moveBookmark(device, sixth); bookmarkManager->emitChanged(root); urls.clear(); urls << initialListOfPlaces() << initialListOfShared() << initialListOfDevices() << initialListOfRemovableDevices(); CHECK_PLACES_URLS(urls); QCOMPARE(spy_inserted.count(), 1); args = spy_inserted.takeFirst(); QCOMPARE(args.at(0).toModelIndex(), QModelIndex()); QCOMPARE(args.at(1).toInt(), 6); QCOMPARE(args.at(2).toInt(), 6); QCOMPARE(spy_removed.count(), 1); args = spy_removed.takeFirst(); QCOMPARE(args.at(0).toModelIndex(), QModelIndex()); QCOMPARE(args.at(1).toInt(), 5); QCOMPARE(args.at(2).toInt(), 5); } void KFilePlacesModelTest::testDeviceSetupTeardown() { QList args; QSignalSpy spy_changed(m_places, SIGNAL(dataChanged(QModelIndex,QModelIndex))); fakeDevice(QStringLiteral("/org/kde/solid/fakehw/volume_part1_size_993284096/StorageAccess"))->call(QStringLiteral("teardown")); QCOMPARE(spy_changed.count(), 1); args = spy_changed.takeFirst(); QCOMPARE(args.at(0).toModelIndex().row(), 6); QCOMPARE(args.at(1).toModelIndex().row(), 6); fakeDevice(QStringLiteral("/org/kde/solid/fakehw/volume_part1_size_993284096/StorageAccess"))->call(QStringLiteral("setup")); QCOMPARE(spy_changed.count(), 1); args = spy_changed.takeFirst(); QCOMPARE(args.at(0).toModelIndex().row(), 6); QCOMPARE(args.at(1).toModelIndex().row(), 6); } void KFilePlacesModelTest::testEnableBaloo() { KConfig config(QStringLiteral("baloofilerc")); KConfigGroup basicSettings = config.group("Basic Settings"); basicSettings.writeEntry("Indexing-Enabled", true); config.sync(); KFilePlacesModel places_with_baloo; QStringList urls; for (int row = 0; row < places_with_baloo.rowCount(); ++row) { QModelIndex index = places_with_baloo.index(row, 0); urls << places_with_baloo.url(index).toDisplayString(QUrl::PreferLocalFile); } QVERIFY(urls.contains("timeline:/today")); QVERIFY(urls.contains("timeline:/yesterday")); QVERIFY(urls.contains("search:/documents")); QVERIFY(urls.contains("search:/images")); QVERIFY(urls.contains("search:/audio")); QVERIFY(urls.contains("search:/videos")); } void KFilePlacesModelTest::testRemoteUrls_data() { QTest::addColumn("url"); QTest::addColumn("expectedRow"); QTest::addColumn("expectedGroup"); QTest::newRow("Ftp") << QUrl(QStringLiteral("ftp://192.168.1.1/ftp")) << 4 << QStringLiteral("Remote"); QTest::newRow("Samba") << QUrl(QStringLiteral("smb://192.168.1.1/share")) << 4 << QStringLiteral("Remote"); QTest::newRow("Sftp") << QUrl(QStringLiteral("sftp://192.168.1.1/share")) << 4 << QStringLiteral("Remote"); QTest::newRow("Fish") << QUrl(QStringLiteral("fish://192.168.1.1/share")) << 4 << QStringLiteral("Remote"); QTest::newRow("Webdav") << QUrl(QStringLiteral("webdav://192.168.1.1/share")) << 4 << QStringLiteral("Remote"); } void KFilePlacesModelTest::testRemoteUrls() { QFETCH(QUrl, url); QFETCH(int, expectedRow); QFETCH(QString, expectedGroup); QList args; QSignalSpy spy_inserted(m_places, SIGNAL(rowsInserted(QModelIndex,int,int))); // insert a new network url m_places->addPlace(QStringLiteral("My Shared"), url, QString(), QString(), QModelIndex()); // check if url list is correct after insertion QStringList urls; urls << QDir::homePath() << QStringLiteral("trash:/") // places << QStringLiteral("remote:/") << QStringLiteral("/media/nfs") << url.toString() << QStringLiteral("/foreign") << QStringLiteral("/media/floppy0") << QStringLiteral("/media/XO-Y4") << QStringLiteral("/media/cdrom"); CHECK_PLACES_URLS(urls); // check if the new url was inserted in the right position (end of "Remote" section) QTRY_COMPARE(spy_inserted.count(), 1); args = spy_inserted.takeFirst(); QCOMPARE(args.at(0).toModelIndex(), QModelIndex()); QCOMPARE(args.at(1).toInt(), expectedRow); QCOMPARE(args.at(2).toInt(), expectedRow); // check if the new url has the right group "Remote" const QModelIndex index = m_places->index(expectedRow, 0); QCOMPARE(index.data(KFilePlacesModel::GroupRole).toString(), expectedGroup); m_places->removePlace(index); } void KFilePlacesModelTest::testRefresh() { KBookmarkManager *bookmarkManager = KBookmarkManager::managerForFile(bookmarksFile(), QStringLiteral("kfilePlaces")); KBookmarkGroup root = bookmarkManager->root(); KBookmark homePlace = root.first(); const QModelIndex homePlaceIndex = m_places->index(0, 0); QCOMPARE(m_places->text(homePlaceIndex), homePlace.fullText()); // modify bookmark homePlace.setFullText("Test change the text"); QVERIFY(m_places->text(homePlaceIndex) != homePlace.fullText()); // reload bookmark data m_places->refresh(); QCOMPARE(m_places->text(homePlaceIndex), homePlace.fullText()); } void KFilePlacesModelTest::testConvertedUrl_data() { QTest::addColumn("url"); QTest::addColumn("expectedUrl"); // places QTest::newRow("Places - Home") << QUrl::fromLocalFile(QDir::homePath()) << QUrl::fromLocalFile(QDir::homePath()); // baloo -search QTest::newRow("Baloo - Documents") << QUrl("search:/documents") << QUrl("baloosearch:/documents"); QTest::newRow("Baloo - Unknown Type") << QUrl("search:/unknown") << QUrl("search:/unknown"); // baloo - timeline const QDate lastMonthDate = QDate::currentDate().addMonths(-1); QTest::newRow("Baloo - Last Month") << QUrl("timeline:/lastmonth") << QUrl(QString("timeline:/%1-%2").arg(lastMonthDate.year()).arg(lastMonthDate.month(), 2, 10, QLatin1Char('0'))); // devices QTest::newRow("Devices - Floppy") << QUrl("file:///media/floppy0") << QUrl("file:///media/floppy0"); } void KFilePlacesModelTest::testConvertedUrl() { QFETCH(QUrl, url); QFETCH(QUrl, expectedUrl); const QUrl convertedUrl = KFilePlacesModel::convertedUrl(url); QCOMPARE(convertedUrl.scheme(), expectedUrl.scheme()); QCOMPARE(convertedUrl.path(), expectedUrl.path()); QCOMPARE(convertedUrl, expectedUrl); } void KFilePlacesModelTest::testBookmarkObject() { //make sure that all items return a valid bookmark for (int row = 0; row < m_places->rowCount(); row++) { const QModelIndex index = m_places->index(row, 0); const KBookmark bookmark = m_places->bookmarkForIndex(index); QVERIFY(!bookmark.isNull()); } } void KFilePlacesModelTest::testDataChangedSignal() { QSignalSpy dataChangedSpy(m_places, &KFilePlacesModel::dataChanged); const QModelIndex index = m_places->index(1, 0); const KBookmark bookmark = m_places->bookmarkForIndex(index); // call function with the same data m_places->editPlace(index, bookmark.fullText(), bookmark.url(), bookmark.icon(), bookmark.metaDataItem(QStringLiteral("OnlyInApp"))); QCOMPARE(dataChangedSpy.count(), 0); // call function with different data const QString originalText = bookmark.fullText(); m_places->editPlace(index, QStringLiteral("My text"), bookmark.url(), bookmark.icon(), bookmark.metaDataItem(QStringLiteral("OnlyInApp"))); QCOMPARE(dataChangedSpy.count(), 1); QList args = dataChangedSpy.takeFirst(); QCOMPARE(args.at(0).toModelIndex().row(), 1); QCOMPARE(args.at(0).toModelIndex().column(), 0); QCOMPARE(args.at(1).toModelIndex().row(), 1); QCOMPARE(args.at(1).toModelIndex().column(), 0); QCOMPARE(m_places->text(index), QStringLiteral("My text")); // restore original value dataChangedSpy.clear(); m_places->editPlace(index, originalText, bookmark.url(), bookmark.icon(), bookmark.metaDataItem(QStringLiteral("OnlyInApp"))); QCOMPARE(dataChangedSpy.count(), 1); } void KFilePlacesModelTest::testIconRole_data() { QTest::addColumn("index"); QTest::addColumn("expectedIconName"); // places QTest::newRow("Places - Home") << m_places->index(0, 0) << QStringLiteral("user-home"); QTest::newRow("Places - Trash") << m_places->index(1, 0) << QStringLiteral("user-trash"); QTest::newRow("Remote - Network") << m_places->index(2, 0) << QStringLiteral("folder-network"); QTest::newRow("Devices - Nfs") << m_places->index(3, 0) << QStringLiteral("hwinfo"); QTest::newRow("Devices - foreign") << m_places->index(4, 0) << QStringLiteral("blockdevice"); QTest::newRow("Devices - Floppy") << m_places->index(5, 0) << QStringLiteral("blockdevice"); QTest::newRow("Devices - cdrom") << m_places->index(6, 0) << QStringLiteral("blockdevice"); } void KFilePlacesModelTest::testIconRole() { QFETCH(QModelIndex, index); QFETCH(QString, expectedIconName); QVERIFY(index.data(KFilePlacesModel::IconNameRole).toString().startsWith(expectedIconName)); } void KFilePlacesModelTest::testMoveFunction() { QList args; QStringList urls = initialListOfUrls(); QSignalSpy rowsMoved(m_places, &KFilePlacesModel::rowsMoved); // move item 0 to pos 1 QVERIFY(m_places->movePlace(0, 2)); urls.move(0, 1); QTRY_COMPARE(rowsMoved.count(), 1); args = rowsMoved.takeFirst(); QCOMPARE(args.at(1).toInt(), 0); // start QCOMPARE(args.at(2).toInt(), 0); // end QCOMPARE(args.at(4).toInt(), 2); // row (destination) QCOMPARE(placesUrls(), urls); rowsMoved.clear(); // move it back QVERIFY(m_places->movePlace(1, 0)); urls.move(1, 0); QTRY_COMPARE(rowsMoved.count(), 1); args = rowsMoved.takeFirst(); QCOMPARE(args.at(1).toInt(), 1); // start QCOMPARE(args.at(2).toInt(), 1); // end QCOMPARE(args.at(4).toInt(), 0); // row (destination) QCOMPARE(placesUrls(), urls); rowsMoved.clear(); // target position is greater than model rows // will move to the end of the first group QVERIFY(m_places->movePlace(0, 20)); urls.move(0, 1); QTRY_COMPARE(rowsMoved.count(), 1); args = rowsMoved.takeFirst(); QCOMPARE(args.at(1).toInt(), 0); // start QCOMPARE(args.at(2).toInt(), 0); // end QCOMPARE(args.at(4).toInt(), 2); // row (destination) QCOMPARE(placesUrls(), urls); rowsMoved.clear(); // move it back QVERIFY(m_places->movePlace(1, 0)); urls.move(1, 0); QTRY_COMPARE(rowsMoved.count(), 1); args = rowsMoved.takeFirst(); QCOMPARE(args.at(1).toInt(), 1); // start QCOMPARE(args.at(2).toInt(), 1); // end QCOMPARE(args.at(4).toInt(), 0); // row (destination) QCOMPARE(placesUrls(), urls); rowsMoved.clear(); QVERIFY(m_places->movePlace(7, 5)); urls.move(7, 5); QTRY_COMPARE(rowsMoved.count(), 1); args = rowsMoved.takeFirst(); QCOMPARE(args.at(1).toInt(), 7); // start QCOMPARE(args.at(2).toInt(), 7); // end QCOMPARE(args.at(4).toInt(), 5); // row (destination) QCOMPARE(placesUrls(), urls); rowsMoved.clear(); // move it back QVERIFY(m_places->movePlace(5, 8)); urls.move(5, 7); QTRY_COMPARE(rowsMoved.count(), 1); args = rowsMoved.takeFirst(); QCOMPARE(args.at(1).toInt(), 5); // start QCOMPARE(args.at(2).toInt(), 5); // end QCOMPARE(args.at(4).toInt(), 8); // row (destination) QCOMPARE(placesUrls(), urls); rowsMoved.clear(); //use a invalid start position QVERIFY(!m_places->movePlace(100, 20)); QCOMPARE(rowsMoved.count(), 0); //use same start and target position QVERIFY(!m_places->movePlace(1, 1)); QCOMPARE(rowsMoved.count(), 0); } void KFilePlacesModelTest::testPlaceGroupHidden() { // GIVEN QCOMPARE(m_places->hiddenCount(), 0); QStringList urls; urls << initialListOfPlaces() << initialListOfShared() << initialListOfDevices() << initialListOfRemovableDevices(); CHECK_PLACES_URLS(urls); QVector indexesHidden; // WHEN m_places->setGroupHidden(KFilePlacesModel::PlacesType, true); // THEN for (int row = 0; row < m_places->rowCount(); ++row) { QModelIndex index = m_places->index(row, 0); if (m_places->groupType(index) == KFilePlacesModel::PlacesType) { QVERIFY(m_places->isHidden(index)); indexesHidden << index; } } QCOMPARE(indexesHidden.count(), initialListOfPlaces().size()); QCOMPARE(m_places->hiddenCount(), indexesHidden.size()); // and GIVEN QVector indexesShown; // WHEN m_places->setGroupHidden(KFilePlacesModel::PlacesType, false); // THEN for (int row = 0; row < m_places->rowCount(); ++row) { QModelIndex index = m_places->index(row, 0); if (m_places->groupType(index) == KFilePlacesModel::PlacesType) { QVERIFY(!m_places->isHidden(index)); indexesShown << index; } } QCOMPARE(m_places->hiddenCount(), 0); QCOMPARE(indexesShown.count(), initialListOfPlaces().size()); } void KFilePlacesModelTest::testPlaceGroupHiddenVsPlaceChildShown() { // GIVEN for (int row = 0; row < m_places->rowCount(); ++row) { QModelIndex index = m_places->index(row, 0); QVERIFY(!m_places->isHidden(index)); } m_places->setGroupHidden(KFilePlacesModel::PlacesType, true); QModelIndex firstIndex = m_places->index(0,0); const int amountOfPlaces = initialListOfPlaces().size(); for (int row = 0; row < amountOfPlaces; ++row) { QModelIndex index = m_places->index(row, 0); QVERIFY(m_places->isHidden(index)); } // WHEN m_places->setPlaceHidden(firstIndex, false); // THEN QVERIFY(m_places->isHidden(firstIndex)); // a child cannot show against its parent state // leaving in a clean state m_places->setGroupHidden(KFilePlacesModel::PlacesType, false); } void KFilePlacesModelTest::testPlaceGroupHiddenAndShownWithHiddenChild() { // GIVEN QCOMPARE(m_places->hiddenCount(), 0); QStringList urls; urls << initialListOfPlaces() << initialListOfShared() << initialListOfDevices() << initialListOfRemovableDevices(); CHECK_PLACES_URLS(urls); QModelIndex firstIndexHidden = m_places->index(0,0); m_places->setPlaceHidden(firstIndexHidden, true); // first place index is hidden within an hidden parent m_places->setGroupHidden(KFilePlacesModel::PlacesType, true); QCOMPARE(m_places->hiddenCount(), initialListOfPlaces().size()); // WHEN m_places->setGroupHidden(KFilePlacesModel::PlacesType, false); // THEN QVector indexesShown; for (int row = 0; row < m_places->rowCount(); ++row) { QModelIndex index = m_places->index(row, 0); if (index == firstIndexHidden) { QVERIFY(m_places->isHidden(firstIndexHidden)); continue; } if (m_places->groupType(index) == KFilePlacesModel::PlacesType) { QVERIFY(!m_places->isHidden(index)); indexesShown << index; } } QCOMPARE(m_places->hiddenCount(), 1); QCOMPARE(indexesShown.count(), initialListOfPlaces().size() - 1 /*first child remains hidden*/); // leaving in a clean state m_places->setPlaceHidden(firstIndexHidden, false); } void KFilePlacesModelTest::testPlaceGroupHiddenGroupIndexesIntegrity() { // GIVEN m_places->setGroupHidden(KFilePlacesModel::PlacesType, true); QVERIFY(m_places->groupIndexes(KFilePlacesModel::UnknownType).isEmpty()); QVERIFY(m_places->isGroupHidden(KFilePlacesModel::PlacesType)); QCOMPARE(m_places->groupIndexes(KFilePlacesModel::PlacesType).count(), initialListOfPlaces().count()); QCOMPARE(m_places->groupIndexes(KFilePlacesModel::RecentlySavedType).count(), 0); QCOMPARE(m_places->groupIndexes(KFilePlacesModel::SearchForType).count(), 0); QCOMPARE(m_places->groupIndexes(KFilePlacesModel::DevicesType).count(), initialListOfDevices().count()); QCOMPARE(m_places->groupIndexes(KFilePlacesModel::RemovableDevicesType).count(), initialListOfRemovableDevices().count()); //WHEN m_places->setGroupHidden(KFilePlacesModel::PlacesType, false); // THEN // Make sure that hidden place group doesn't change model QVERIFY(!m_places->isGroupHidden(KFilePlacesModel::PlacesType)); QCOMPARE(m_places->groupIndexes(KFilePlacesModel::PlacesType).count(), initialListOfPlaces().count()); QCOMPARE(m_places->groupIndexes(KFilePlacesModel::RecentlySavedType).count(), 0); QCOMPARE(m_places->groupIndexes(KFilePlacesModel::SearchForType).count(), 0); QCOMPARE(m_places->groupIndexes(KFilePlacesModel::DevicesType).count(), initialListOfDevices().count()); QCOMPARE(m_places->groupIndexes(KFilePlacesModel::RemovableDevicesType).count(), initialListOfRemovableDevices().count()); } void KFilePlacesModelTest::testPlaceGroupHiddenSignal() { QSignalSpy groupHiddenSignal(m_places, &KFilePlacesModel::groupHiddenChanged); m_places->setGroupHidden(KFilePlacesModel::SearchForType, true); // hide SearchForType group QTRY_COMPARE(groupHiddenSignal.count(), 1); QList args = groupHiddenSignal.takeFirst(); QCOMPARE(args.at(0).toInt(), static_cast(KFilePlacesModel::SearchForType)); QCOMPARE(args.at(1).toBool(), true); groupHiddenSignal.clear(); // try hide SearchForType which is already hidden m_places->setGroupHidden(KFilePlacesModel::SearchForType, true); QCOMPARE(groupHiddenSignal.count(), 0); // show SearchForType group m_places->setGroupHidden(KFilePlacesModel::SearchForType, false); QTRY_COMPARE(groupHiddenSignal.count(), 1); args = groupHiddenSignal.takeFirst(); QCOMPARE(args.at(0).toInt(), static_cast(KFilePlacesModel::SearchForType)); QCOMPARE(args.at(1).toBool(), false); } void KFilePlacesModelTest::testPlaceGroupHiddenRole() { // on startup all groups are visible for (int r = 0, rMax = m_places->rowCount(); r < rMax; r++) { const QModelIndex index = m_places->index(r, 0); QCOMPARE(index.data(KFilePlacesModel::GroupHiddenRole).toBool(), false); } // set SearchFor group hidden m_places->setGroupHidden(KFilePlacesModel::SearchForType, true); for (auto groupType : {KFilePlacesModel::PlacesType, KFilePlacesModel::RemoteType, KFilePlacesModel::RecentlySavedType, KFilePlacesModel::SearchForType, KFilePlacesModel::DevicesType, KFilePlacesModel::RemovableDevicesType}) { const bool groupShouldBeHidden = (groupType == KFilePlacesModel::SearchForType); - for (auto index : m_places->groupIndexes(groupType)) { + const QModelIndexList indexes = m_places->groupIndexes(groupType); + for (const QModelIndex &index : indexes) { QCOMPARE(index.data(KFilePlacesModel::GroupHiddenRole).toBool(), groupShouldBeHidden); } } // set SearchFor group visible again m_places->setGroupHidden(KFilePlacesModel::SearchForType, false); for (int r = 0, rMax = m_places->rowCount(); r < rMax; r++) { const QModelIndex index = m_places->index(r, 0); QCOMPARE(index.data(KFilePlacesModel::GroupHiddenRole).toBool(), false); } } void KFilePlacesModelTest::testFilterWithAlternativeApplicationName() { const QStringList urls = initialListOfUrls(); const QString alternativeApplicationName = QStringLiteral("kfile_places_model_test"); KBookmarkManager *bookmarkManager = KBookmarkManager::managerForFile(bookmarksFile(), QStringLiteral("kfilePlaces")); KBookmarkGroup root = bookmarkManager->root(); // create a new entry with alternative application name KBookmark bookmark = root.addBookmark(QStringLiteral("Extra entry"), QUrl(QStringLiteral("search:/videos-alternative")), {}); const QString id = QUuid::createUuid().toString(); bookmark.setMetaDataItem(QStringLiteral("ID"), id); bookmark.setMetaDataItem(QStringLiteral("OnlyInApp"), alternativeApplicationName); bookmarkManager->emitChanged(bookmarkManager->root()); // make sure that the entry is not visible on the original model CHECK_PLACES_URLS(urls); // create a new model with alternativeApplicationName KFilePlacesModel *newModel = new KFilePlacesModel(alternativeApplicationName); QTRY_COMPARE(placesUrls(newModel).count(QStringLiteral("search:/videos-alternative")), 1); delete newModel; } void KFilePlacesModelTest::testSupportedSchemes() { QCoreApplication::processEvents(); // support running this test on its own QCOMPARE(m_places->supportedSchemes(), QStringList()); QCOMPARE(placesUrls(), initialListOfUrls()); m_places->setSupportedSchemes({"trash"}); QCOMPARE(m_places->supportedSchemes(), QStringList("trash")); QCOMPARE(placesUrls(), QStringList("trash:/")); m_places->setSupportedSchemes({}); QCOMPARE(m_places->supportedSchemes(), QStringList()); QCOMPARE(placesUrls(), initialListOfUrls()); } QTEST_MAIN(KFilePlacesModelTest) #include "kfileplacesmodeltest.moc" diff --git a/src/core/kmountpoint.cpp b/src/core/kmountpoint.cpp index b81599fb..bb4f2cae 100644 --- a/src/core/kmountpoint.cpp +++ b/src/core/kmountpoint.cpp @@ -1,487 +1,487 @@ /* * This file is part of the KDE libraries * Copyright (c) 2003 Waldo Bastian * 2007 David Faure * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License version 2 as published by the Free Software Foundation. * * This library 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 * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "kmountpoint.h" #include #include #include #include #include #include #ifdef Q_OS_WIN #include #endif #ifdef Q_OS_WIN static const Qt::CaseSensitivity cs = Qt::CaseInsensitive; #else static const Qt::CaseSensitivity cs = Qt::CaseSensitive; #endif #if HAVE_VOLMGT #include #endif #if HAVE_SYS_MNTTAB_H #include #endif #if HAVE_MNTENT_H #include #elif HAVE_SYS_MNTENT_H #include #endif // This is the *BSD branch #if HAVE_SYS_MOUNT_H #if HAVE_SYS_TYPES_H #include #endif #if HAVE_SYS_PARAM_H #include #endif #include #endif #if HAVE_FSTAB_H #include #endif #if ! HAVE_GETMNTINFO # ifdef _PATH_MOUNTED // On some Linux, MNTTAB points to /etc/fstab ! # undef MNTTAB # define MNTTAB _PATH_MOUNTED # else # ifndef MNTTAB # ifdef MTAB_FILE # define MNTTAB MTAB_FILE # else # define MNTTAB "/etc/mnttab" # endif # endif # endif #endif #ifdef _OS_SOLARIS_ #define FSTAB "/etc/vfstab" #else #define FSTAB "/etc/fstab" #endif class Q_DECL_HIDDEN KMountPoint::Private { public: void finalizePossibleMountPoint(DetailsNeededFlags infoNeeded); void finalizeCurrentMountPoint(DetailsNeededFlags infoNeeded); QString mountedFrom; QString device; // Only available when the NeedRealDeviceName flag was set. QString mountPoint; QString mountType; QStringList mountOptions; }; KMountPoint::KMountPoint() : d(new Private) { } KMountPoint::~KMountPoint() { delete d; } // There are (at least) four kind of APIs: // setmntent + getmntent + struct mntent (linux...) // getmntent + struct mnttab // getmntinfo + struct statfs&flags (BSD 4.4 and friends) // getfsent + char* (BSD 4.3 and friends) #if HAVE_SETMNTENT #define SETMNTENT setmntent #define ENDMNTENT endmntent #define STRUCT_MNTENT struct mntent * #define STRUCT_SETMNTENT FILE * #define GETMNTENT(file, var) ((var = getmntent(file)) != nullptr) #define MOUNTPOINT(var) var->mnt_dir #define MOUNTTYPE(var) var->mnt_type #define MOUNTOPTIONS(var) var->mnt_opts #define FSNAME(var) var->mnt_fsname #else #define SETMNTENT fopen #define ENDMNTENT fclose #define STRUCT_MNTENT struct mnttab #define STRUCT_SETMNTENT FILE * #define GETMNTENT(file, var) (getmntent(file, &var) == nullptr) #define MOUNTPOINT(var) var.mnt_mountp #define MOUNTTYPE(var) var.mnt_fstype #define MOUNTOPTIONS(var) var.mnt_mntopts #define FSNAME(var) var.mnt_special #endif void KMountPoint::Private::finalizePossibleMountPoint(DetailsNeededFlags infoNeeded) { if (mountedFrom.startsWith(QLatin1String("UUID="))) { const QStringRef uuid = mountedFrom.midRef(5); const QString potentialDevice = QFile::symLinkTarget(QLatin1String("/dev/disk/by-uuid/") + uuid); if (QFile::exists(potentialDevice)) { mountedFrom = potentialDevice; } } if (mountedFrom.startsWith(QLatin1String("LABEL="))) { const QStringRef label = mountedFrom.midRef(6); const QString potentialDevice = QFile::symLinkTarget(QLatin1String("/dev/disk/by-label/") + label); if (QFile::exists(potentialDevice)) { mountedFrom = potentialDevice; } } if (infoNeeded & NeedRealDeviceName) { if (mountedFrom.startsWith(QLatin1Char('/'))) { device = QFileInfo(mountedFrom).canonicalFilePath(); } } // Chop trailing slash if (mountedFrom.endsWith(QLatin1Char('/'))) { mountedFrom.chop(1); } } void KMountPoint::Private::finalizeCurrentMountPoint(DetailsNeededFlags infoNeeded) { if (infoNeeded & NeedRealDeviceName) { if (mountedFrom.startsWith(QLatin1Char('/'))) { device = QFileInfo(mountedFrom).canonicalFilePath(); } } } KMountPoint::List KMountPoint::possibleMountPoints(DetailsNeededFlags infoNeeded) { #ifdef Q_OS_WIN return KMountPoint::currentMountPoints(infoNeeded); #endif KMountPoint::List result; #if HAVE_SETMNTENT STRUCT_SETMNTENT fstab; if ((fstab = SETMNTENT(FSTAB, "r")) == nullptr) { return result; } STRUCT_MNTENT fe; while (GETMNTENT(fstab, fe)) { Ptr mp(new KMountPoint); mp->d->mountedFrom = QFile::decodeName(FSNAME(fe)); mp->d->mountPoint = QFile::decodeName(MOUNTPOINT(fe)); mp->d->mountType = QFile::decodeName(MOUNTTYPE(fe)); if (infoNeeded & NeedMountOptions) { QString options = QFile::decodeName(MOUNTOPTIONS(fe)); mp->d->mountOptions = options.split(QLatin1Char(',')); } mp->d->finalizePossibleMountPoint(infoNeeded); result.append(mp); } ENDMNTENT(fstab); #else QFile f(QLatin1String(FSTAB)); if (!f.open(QIODevice::ReadOnly)) { return result; } QTextStream t(&f); QString s; while (! t.atEnd()) { s = t.readLine().simplified(); if (s.isEmpty() || (s[0] == QLatin1Char('#'))) { continue; } // not empty or commented out by '#' const QStringList item = s.split(QLatin1Char(' ')); #ifdef _OS_SOLARIS_ if (item.count() < 5) { continue; } #else if (item.count() < 4) { continue; } #endif Ptr mp(new KMountPoint); int i = 0; mp->d->mountedFrom = item[i++]; #ifdef _OS_SOLARIS_ //device to fsck i++; #endif mp->d->mountPoint = item[i++]; mp->d->mountType = item[i++]; QString options = item[i++]; if (infoNeeded & NeedMountOptions) { mp->d->mountOptions = options.split(QLatin1Char(',')); } mp->d->finalizePossibleMountPoint(infoNeeded); result.append(mp); } //while f.close(); #endif return result; } KMountPoint::List KMountPoint::currentMountPoints(DetailsNeededFlags infoNeeded) { KMountPoint::List result; #if HAVE_GETMNTINFO #if GETMNTINFO_USES_STATVFS struct statvfs *mounted; #else struct statfs *mounted; #endif int num_fs = getmntinfo(&mounted, MNT_NOWAIT); for (int i = 0; i < num_fs; i++) { Ptr mp(new KMountPoint); mp->d->mountedFrom = QFile::decodeName(mounted[i].f_mntfromname); mp->d->mountPoint = QFile::decodeName(mounted[i].f_mntonname); #ifdef __osf__ mp->d->mountType = QFile::decodeName(mnt_names[mounted[i].f_type]); #else mp->d->mountType = QFile::decodeName(mounted[i].f_fstypename); #endif if (infoNeeded & NeedMountOptions) { struct fstab *ft = getfsfile(mounted[i].f_mntonname); if (ft != 0) { QString options = QFile::decodeName(ft->fs_mntops); mp->d->mountOptions = options.split(QLatin1Char(',')); } else { // TODO: get mount options if not mounted via fstab, see mounted[i].f_flags } } mp->d->finalizeCurrentMountPoint(infoNeeded); // TODO: Strip trailing '/' ? result.append(mp); } #elif defined(Q_OS_WIN) //nothing fancy with infoNeeded but it gets the job done DWORD bits = GetLogicalDrives(); if (!bits) { return result; } for (int i = 0; i < 26; i++) { if (bits & (1 << i)) { Ptr mp(new KMountPoint); mp->d->mountPoint = QString(QLatin1Char('A' + i) + QLatin1String(":/")); result.append(mp); } } #elif !defined(Q_OS_ANDROID) STRUCT_SETMNTENT mnttab; if ((mnttab = SETMNTENT(MNTTAB, "r")) == nullptr) { return result; } STRUCT_MNTENT fe; while (GETMNTENT(mnttab, fe)) { Ptr mp(new KMountPoint); mp->d->mountedFrom = QFile::decodeName(FSNAME(fe)); mp->d->mountPoint = QFile::decodeName(MOUNTPOINT(fe)); mp->d->mountType = QFile::decodeName(MOUNTTYPE(fe)); if (infoNeeded & NeedMountOptions) { QString options = QFile::decodeName(MOUNTOPTIONS(fe)); mp->d->mountOptions = options.split(QLatin1Char(',')); } // Resolve gvfs mountpoints if (mp->d->mountedFrom == QLatin1String("gvfsd-fuse")) { const QDir gvfsDir(mp->d->mountPoint); const QStringList mountDirs = gvfsDir.entryList(QDir::Dirs | QDir::NoDotAndDotDot); - for (const auto &mountDir : mountDirs) { + for (const QString &mountDir : mountDirs) { const QString type = mountDir.section(QLatin1Char(':'), 0, 0); if (type.isEmpty()) { continue; } Ptr gvfsmp(new KMountPoint); gvfsmp->d->mountedFrom = mp->d->mountedFrom; gvfsmp->d->mountPoint = mp->d->mountPoint + QLatin1Char('/') + mountDir; gvfsmp->d->mountType = type; result.append(gvfsmp); } } mp->d->finalizeCurrentMountPoint(infoNeeded); result.append(mp); } ENDMNTENT(mnttab); #endif return result; } QString KMountPoint::mountedFrom() const { return d->mountedFrom; } QString KMountPoint::realDeviceName() const { return d->device; } QString KMountPoint::mountPoint() const { return d->mountPoint; } QString KMountPoint::mountType() const { return d->mountType; } QStringList KMountPoint::mountOptions() const { return d->mountOptions; } KMountPoint::List::List() : QList() { } static bool pathsAreParentAndChildOrEqual(const QString &parent, const QString &child) { const QLatin1Char slash('/'); if (child.startsWith(parent, cs)) { // Check if either // (a) both paths are equal, or // (b) parent ends with '/', or // (c) the first character of child that is not shared with parent is '/'. // Note that child is guaranteed to be longer than parent if (a) is false. // // This prevents that we incorrectly consider "/books" a child of "/book". return parent.compare(child, cs) == 0 || parent.endsWith(slash) || child.at(parent.length()) == slash; } else { // Note that "/books" is a child of "/books/". return parent.endsWith(slash) && (parent.length() == child.length() + 1) && parent.startsWith(child, cs); } } KMountPoint::Ptr KMountPoint::List::findByPath(const QString &path) const { #ifndef Q_OS_WIN /* If the path contains symlinks, get the real name */ QFileInfo fileinfo(path); const QString realname = fileinfo.exists() ? fileinfo.canonicalFilePath() : fileinfo.absolutePath(); //canonicalFilePath won't work unless file exists #else const QString realname = QDir::fromNativeSeparators(QDir(path).absolutePath()); #endif int max = 0; KMountPoint::Ptr result; for (const_iterator it = begin(); it != end(); ++it) { const QString mountpoint = (*it)->d->mountPoint; const int length = mountpoint.length(); if (length > max && pathsAreParentAndChildOrEqual(mountpoint, realname)) { max = length; result = *it; // keep iterating to check for a better match (bigger max) } } return result; } KMountPoint::Ptr KMountPoint::List::findByDevice(const QString &device) const { const QString realDevice = QFileInfo(device).canonicalFilePath(); if (realDevice.isEmpty()) { // d->device can be empty in the loop below, don't match empty with it return Ptr(); } for (const_iterator it = begin(); it != end(); ++it) { if (realDevice.compare((*it)->d->device, cs) == 0 || realDevice.compare((*it)->d->mountedFrom, cs) == 0) { return *it; } } return Ptr(); } bool KMountPoint::probablySlow() const { return d->mountType == QLatin1String("nfs") || d->mountType == QLatin1String("nfs4") || d->mountType == QLatin1String("cifs") || d->mountType == QLatin1String("autofs") || d->mountType == QLatin1String("subfs"); } bool KMountPoint::testFileSystemFlag(FileSystemFlag flag) const { const bool isMsDos = (d->mountType == QLatin1String("msdos") || d->mountType == QLatin1String("fat") || d->mountType == QLatin1String("vfat")); const bool isNtfs = d->mountType.contains(QLatin1String("fuse.ntfs")) || d->mountType.contains(QLatin1String("fuseblk.ntfs")) // fuseblk could really be anything. But its most common use is for NTFS mounts, these days. || d->mountType == QLatin1String("fuseblk"); const bool isSmb = d->mountType == QLatin1String("cifs") || d->mountType == QLatin1String("smbfs") // gvfs-fuse mounted SMB share || d->mountType == QLatin1String("smb-share"); switch (flag) { case SupportsChmod: case SupportsChown: case SupportsUTime: case SupportsSymlinks: return !isMsDos && !isNtfs && !isSmb; // it's amazing the number of things Microsoft filesystems don't support :) case CaseInsensitive: return isMsDos; } return false; } diff --git a/src/filewidgets/knewfilemenu.cpp b/src/filewidgets/knewfilemenu.cpp index 80b9d7e9..0be0e4eb 100644 --- a/src/filewidgets/knewfilemenu.cpp +++ b/src/filewidgets/knewfilemenu.cpp @@ -1,1410 +1,1410 @@ /* This file is part of the KDE project Copyright (C) 1998-2009 David Faure 2003 Sven Leiber This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 or at your option version 3. This library 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 Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "knewfilemenu.h" #include "../pathhelpers_p.h" #include "knameandurlinputdialog.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef Q_OS_WIN #include #else #include #endif static QString expandTilde(const QString &name, bool isfile = false) { if (!name.isEmpty() && (!isfile || name[0] == QLatin1Char('\\'))) { const QString expandedName = KShell::tildeExpand(name); // When a tilde mark cannot be properly expanded, the above call // returns an empty string... if (!expandedName.isEmpty()) { return expandedName; } } return name; } // Singleton, with data shared by all KNewFileMenu instances class KNewFileMenuSingleton { public: KNewFileMenuSingleton() : dirWatch(nullptr), filesParsed(false), templatesList(nullptr), templatesVersion(0) { } ~KNewFileMenuSingleton() { delete dirWatch; delete templatesList; } /** * Opens the desktop files and completes the Entry list * Input: the entry list. Output: the entry list ;-) */ void parseFiles(); /** * For entryType * LINKTOTEMPLATE: a desktop file that points to a file or dir to copy * TEMPLATE: a real file to copy as is (the KDE-1.x solution) * SEPARATOR: to put a separator in the menu * 0 means: not parsed, i.e. we don't know */ enum EntryType { Unknown, LinkToTemplate = 1, Template, Separator }; KDirWatch *dirWatch; struct Entry { QString text; QString filePath; // empty for Separator QString templatePath; // same as filePath for Template QString icon; EntryType entryType; QString comment; QString mimeType; }; // NOTE: only filePath is known before we call parseFiles /** * List of all template files. It is important that they are in * the same order as the 'New' menu. */ typedef QList EntryList; /** * Set back to false each time new templates are found, * and to true on the first call to parseFiles */ bool filesParsed; EntryList *templatesList; /** * Is increased when templatesList has been updated and * menu needs to be re-filled. Menus have their own version and compare it * to templatesVersion before showing up */ int templatesVersion; }; void KNewFileMenuSingleton::parseFiles() { //qDebug(); filesParsed = true; QMutableListIterator templIter(*templatesList); while (templIter.hasNext()) { KNewFileMenuSingleton::Entry &templ = templIter.next(); const QString filePath = templ.filePath; if (!filePath.isEmpty()) { QString text; QString templatePath; // If a desktop file, then read the name from it. // Otherwise (or if no name in it?) use file name if (KDesktopFile::isDesktopFile(filePath)) { KDesktopFile desktopFile(filePath); if (desktopFile.noDisplay()) { templIter.remove(); continue; } text = desktopFile.readName(); templ.icon = desktopFile.readIcon(); templ.comment = desktopFile.readComment(); QString type = desktopFile.readType(); if (type == QLatin1String("Link")) { templatePath = desktopFile.desktopGroup().readPathEntry("URL", QString()); if (templatePath[0] != QLatin1Char('/') && !templatePath.startsWith(QLatin1String("__"))) { if (templatePath.startsWith(QLatin1String("file:/"))) { templatePath = QUrl(templatePath).toLocalFile(); } else { // A relative path, then (that's the default in the files we ship) const QStringRef linkDir = filePath.leftRef(filePath.lastIndexOf(QLatin1Char('/')) + 1 /*keep / */); //qDebug() << "linkDir=" << linkDir; templatePath = linkDir + templatePath; } } } if (templatePath.isEmpty()) { // No URL key, this is an old-style template templ.entryType = KNewFileMenuSingleton::Template; templ.templatePath = templ.filePath; // we'll copy the file } else { templ.entryType = KNewFileMenuSingleton::LinkToTemplate; templ.templatePath = templatePath; } } if (text.isEmpty()) { text = QUrl(filePath).fileName(); if (text.endsWith(QLatin1String(".desktop"))) { text.chop(8); } } templ.text = text; /*// qDebug() << "Updating entry with text=" << text << "entryType=" << templ.entryType << "templatePath=" << templ.templatePath;*/ } else { templ.entryType = KNewFileMenuSingleton::Separator; } } } Q_GLOBAL_STATIC(KNewFileMenuSingleton, kNewMenuGlobals) class KNewFileMenuCopyData { public: KNewFileMenuCopyData() { m_isSymlink = false; } QString chosenFileName() const { return m_chosenFileName; } // If empty, no copy is performed. QString sourceFileToCopy() const { return m_src; } QString tempFileToDelete() const { return m_tempFileToDelete; } bool m_isSymlink; QString m_chosenFileName; QString m_src; QString m_tempFileToDelete; QString m_templatePath; }; class KNewFileMenuPrivate { public: explicit KNewFileMenuPrivate(KActionCollection *collection, KNewFileMenu *qq) : m_actionCollection(collection), q(qq) {} bool checkSourceExists(const QString &src); /** * The strategy used for other desktop files than Type=Link. Example: Application, Device. */ void executeOtherDesktopFile(const KNewFileMenuSingleton::Entry &entry); /** * The strategy used for "real files or directories" (the common case) */ void executeRealFileOrDir(const KNewFileMenuSingleton::Entry &entry); /** * Actually performs file handling. Reads in m_copyData for needed data, that has been collected by execute*() before */ void executeStrategy(); /** * The strategy used when creating a symlink */ void executeSymLink(const KNewFileMenuSingleton::Entry &entry); /** * The strategy used for "url" desktop files */ void executeUrlDesktopFile(const KNewFileMenuSingleton::Entry &entry); /** * Fills the menu from the templates list. */ void fillMenu(); /** * Tries to map a local URL for the given URL. */ QUrl mostLocalUrl(const QUrl &url); /** * Just clears the string buffer d->m_text, but I need a slot for this to occur */ void _k_slotAbortDialog(); /** * Called when New->* is clicked */ void _k_slotActionTriggered(QAction *action); /** * Callback function that reads in directory name from dialog and processes it */ void _k_slotCreateDirectory(bool writeHiddenDir = false); /** * Callback function that reads in directory name from dialog and processes it. This will wirte * a hidden directory without further questions */ void _k_slotCreateHiddenDirectory(); /** * Fills the templates list. */ void _k_slotFillTemplates(); /** * Called when accepting the KPropertiesDialog (for "other desktop files") */ void _k_slotOtherDesktopFile(); /** * Called when closing the KPropertiesDialog is closed (whichever way, accepted and rejected) */ void _k_slotOtherDesktopFileClosed(); /** * Callback in KNewFileMenu for the RealFile Dialog. Handles dialog input and gives over * to executeStrategy() */ void _k_slotRealFileOrDir(); /** * Dialogs use this slot to write the changed string into KNewFile menu when the user * changes touches them */ void _k_slotTextChanged(const QString &text); /** * Callback in KNewFileMenu for the Symlink Dialog. Handles dialog input and gives over * to executeStrategy() */ void _k_slotSymLink(); /** * Callback in KNewFileMenu for the Url/Desktop Dialog. Handles dialog input and gives over * to executeStrategy() */ void _k_slotUrlDesktopFile(); KActionCollection *m_actionCollection; QDialog *m_fileDialog; KActionMenu *m_menuDev; int m_menuItemsVersion = 0; QAction *m_newDirAction; KMessageWidget* m_messageWidget = nullptr; QDialogButtonBox* m_buttonBox = nullptr; // This is used to allow _k_slotTextChanged to know whether it's being used to // create a file or a directory without duplicating code across two functions bool m_creatingDirectory = false; bool m_viewShowsHiddenFiles = false; bool m_modal = true; /** * The action group that our actions belong to */ QActionGroup *m_newMenuGroup; QWidget *m_parentWidget; /** * When the user pressed the right mouse button over an URL a popup menu * is displayed. The URL belonging to this popup menu is stored here. */ QList m_popupFiles; QStringList m_supportedMimeTypes; QString m_tempFileToDelete; // set when a tempfile was created for a Type=URL desktop file QString m_text; KNewFileMenuSingleton::Entry *m_firstFileEntry = nullptr; KNewFileMenu * const q; KNewFileMenuCopyData m_copyData; }; bool KNewFileMenuPrivate::checkSourceExists(const QString &src) { if (!QFile::exists(src)) { qWarning() << src << "doesn't exist"; QDialog *dialog = new QDialog(m_parentWidget); dialog->setWindowTitle(i18n("Sorry")); dialog->setObjectName(QStringLiteral("sorry")); dialog->setModal(q->isModal()); dialog->setAttribute(Qt::WA_DeleteOnClose); m_buttonBox = new QDialogButtonBox(dialog); m_buttonBox->setStandardButtons(QDialogButtonBox::Ok); KMessageBox::createKMessageBox(dialog, m_buttonBox, QMessageBox::Warning, i18n("The template file %1 does not exist.", src), QStringList(), QString(), nullptr, KMessageBox::NoExec); dialog->show(); return false; } return true; } void KNewFileMenuPrivate::executeOtherDesktopFile(const KNewFileMenuSingleton::Entry &entry) { if (!checkSourceExists(entry.templatePath)) { return; } QList::const_iterator it = m_popupFiles.constBegin(); for (; it != m_popupFiles.constEnd(); ++it) { QString text = entry.text; text.remove(QStringLiteral("...")); // the ... is fine for the menu item but not for the default filename text = text.trimmed(); // In some languages, there is a space in front of "...", see bug 268895 // KDE5 TODO: remove the "..." from link*.desktop files and use i18n("%1...") when making // the action. QString name = text; text.append(QStringLiteral(".desktop")); const QUrl directory = mostLocalUrl(*it); const QUrl defaultFile = QUrl::fromLocalFile(directory.toLocalFile() + QLatin1Char('/') + KIO::encodeFileName(text)); if (defaultFile.isLocalFile() && QFile::exists(defaultFile.toLocalFile())) { text = KFileUtils::suggestName(directory, text); } QUrl templateUrl; bool usingTemplate = false; if (entry.templatePath.startsWith(QLatin1String(":/"))) { QTemporaryFile *tmpFile = QTemporaryFile::createNativeFile(entry.templatePath); tmpFile->setAutoRemove(false); QString tempFileName = tmpFile->fileName(); tmpFile->close(); KDesktopFile df(tempFileName); KConfigGroup group = df.desktopGroup(); group.writeEntry("Name", name); templateUrl = QUrl::fromLocalFile(tempFileName); m_tempFileToDelete = tempFileName; usingTemplate = true; } else { templateUrl = QUrl::fromLocalFile(entry.templatePath); } QDialog *dlg = new KPropertiesDialog(templateUrl, directory, text, m_parentWidget); dlg->setModal(q->isModal()); dlg->setAttribute(Qt::WA_DeleteOnClose); QObject::connect(dlg, SIGNAL(applied()), q, SLOT(_k_slotOtherDesktopFile())); if (usingTemplate) { QObject::connect(dlg, SIGNAL(propertiesClosed()), q, SLOT(_k_slotOtherDesktopFileClosed())); } dlg->show(); } // We don't set m_src here -> there will be no copy, we are done. } void KNewFileMenuPrivate::executeRealFileOrDir(const KNewFileMenuSingleton::Entry &entry) { // The template is not a desktop file // Show the small dialog for getting the destination filename QString text = entry.text; text.remove(QStringLiteral("...")); // the ... is fine for the menu item but not for the default filename text = text.trimmed(); // In some languages, there is a space in front of "...", see bug 268895 m_copyData.m_src = entry.templatePath; const QUrl directory = mostLocalUrl(m_popupFiles.first()); const QUrl defaultFile = QUrl::fromLocalFile(directory.toLocalFile() + QLatin1Char('/') + KIO::encodeFileName(text)); if (defaultFile.isLocalFile() && QFile::exists(defaultFile.toLocalFile())) { text = KFileUtils::suggestName(directory, text); } QDialog *fileDialog = new QDialog(m_parentWidget); fileDialog->setAttribute(Qt::WA_DeleteOnClose); fileDialog->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed); fileDialog->setModal(q->isModal()); m_fileDialog = fileDialog; QVBoxLayout *layout = new QVBoxLayout; layout->setSizeConstraint(QLayout::SetFixedSize); m_messageWidget = new KMessageWidget(fileDialog); m_messageWidget->setCloseButtonVisible(false); m_messageWidget->setWordWrap(true); m_messageWidget->hide(); QLabel *label = new QLabel(entry.comment, fileDialog); QLineEdit *lineEdit = new QLineEdit(fileDialog); lineEdit->setClearButtonEnabled(true); lineEdit->setText(text); lineEdit->setMinimumWidth(400); m_buttonBox = new QDialogButtonBox(fileDialog); m_buttonBox->setStandardButtons(QDialogButtonBox::Ok | QDialogButtonBox::Cancel); QObject::connect(m_buttonBox, &QDialogButtonBox::accepted, fileDialog, &QDialog::accept); QObject::connect(m_buttonBox, &QDialogButtonBox::rejected, fileDialog, &QDialog::reject); m_creatingDirectory = false; _k_slotTextChanged(text); QObject::connect(lineEdit, SIGNAL(textChanged(QString)), q, SLOT(_k_slotTextChanged(QString))); layout->addWidget(label); layout->addWidget(lineEdit); layout->addWidget(m_buttonBox); layout->addWidget(m_messageWidget); layout->addStretch(); fileDialog->setLayout(layout); QObject::connect(fileDialog, SIGNAL(accepted()), q, SLOT(_k_slotRealFileOrDir())); QObject::connect(fileDialog, SIGNAL(rejected()), q, SLOT(_k_slotAbortDialog())); fileDialog->show(); lineEdit->selectAll(); lineEdit->setFocus(); } void KNewFileMenuPrivate::executeSymLink(const KNewFileMenuSingleton::Entry &entry) { KNameAndUrlInputDialog *dlg = new KNameAndUrlInputDialog(i18n("File name:"), entry.comment, m_popupFiles.first(), m_parentWidget); dlg->setModal(q->isModal()); dlg->setAttribute(Qt::WA_DeleteOnClose); dlg->setWindowTitle(i18n("Create Symlink")); m_fileDialog = dlg; QObject::connect(dlg, SIGNAL(accepted()), q, SLOT(_k_slotSymLink())); dlg->show(); } void KNewFileMenuPrivate::executeStrategy() { m_tempFileToDelete = m_copyData.tempFileToDelete(); const QString src = m_copyData.sourceFileToCopy(); QString chosenFileName = expandTilde(m_copyData.chosenFileName(), true); if (src.isEmpty()) { return; } QUrl uSrc(QUrl::fromLocalFile(src)); // In case the templates/.source directory contains symlinks, resolve // them to the target files. Fixes bug #149628. KFileItem item(uSrc, QString(), KFileItem::Unknown); if (item.isLink()) { uSrc.setPath(item.linkDest()); } if (!m_copyData.m_isSymlink) { // If the file is not going to be detected as a desktop file, due to a // known extension (e.g. ".pl"), append ".desktop". #224142. QFile srcFile(uSrc.toLocalFile()); if (srcFile.open(QIODevice::ReadOnly)) { QMimeDatabase db; QMimeType wantedMime = db.mimeTypeForUrl(uSrc); QMimeType mime = db.mimeTypeForFileNameAndData(m_copyData.m_chosenFileName, srcFile.read(1024)); //qDebug() << "mime=" << mime->name() << "wantedMime=" << wantedMime->name(); if (!mime.inherits(wantedMime.name())) if (!wantedMime.preferredSuffix().isEmpty()) { chosenFileName += QLatin1Char('.') + wantedMime.preferredSuffix(); } } } // The template is not a desktop file [or it's a URL one] // Copy it. QList::const_iterator it = m_popupFiles.constBegin(); for (; it != m_popupFiles.constEnd(); ++it) { QUrl dest = *it; dest.setPath(concatPaths(dest.path(), KIO::encodeFileName(chosenFileName))); QList lstSrc; lstSrc.append(uSrc); KIO::Job *kjob; if (m_copyData.m_isSymlink) { KIO::CopyJob *linkJob = KIO::linkAs(uSrc, dest); kjob = linkJob; KIO::FileUndoManager::self()->recordCopyJob(linkJob); } else if (src.startsWith(QLatin1String(":/"))) { QFile srcFile(src); if (!srcFile.open(QIODevice::ReadOnly)) { return; } // The QFile won't live long enough for the job, so let's buffer the contents const QByteArray srcBuf(srcFile.readAll()); KIO::StoredTransferJob* putJob = KIO::storedPut(srcBuf, dest, -1); kjob = putJob; KIO::FileUndoManager::self()->recordJob(KIO::FileUndoManager::Put, QList(), dest, putJob); } else { //qDebug() << "KIO::copyAs(" << uSrc.url() << "," << dest.url() << ")"; KIO::CopyJob *job = KIO::copyAs(uSrc, dest); job->setDefaultPermissions(true); kjob = job; KIO::FileUndoManager::self()->recordCopyJob(job); } KJobWidgets::setWindow(kjob, m_parentWidget); QObject::connect(kjob, &KJob::result, q, &KNewFileMenu::slotResult); } } void KNewFileMenuPrivate::executeUrlDesktopFile(const KNewFileMenuSingleton::Entry &entry) { KNameAndUrlInputDialog *dlg = new KNameAndUrlInputDialog(i18n("File name:"), entry.comment, m_popupFiles.first(), m_parentWidget); m_copyData.m_templatePath = entry.templatePath; dlg->setModal(q->isModal()); dlg->setAttribute(Qt::WA_DeleteOnClose); dlg->setWindowTitle(i18n("Create link to URL")); m_fileDialog = dlg; QObject::connect(dlg, SIGNAL(accepted()), q, SLOT(_k_slotUrlDesktopFile())); dlg->show(); } void KNewFileMenuPrivate::fillMenu() { QMenu *menu = q->menu(); menu->clear(); m_menuDev->menu()->clear(); m_newDirAction = nullptr; QSet seenTexts; QString lastTemplatePath; // these shall be put at special positions QAction *linkURL = nullptr; QAction *linkApp = nullptr; QAction *linkPath = nullptr; KNewFileMenuSingleton *s = kNewMenuGlobals(); int i = 1; KNewFileMenuSingleton::EntryList::iterator templ = s->templatesList->begin(); const KNewFileMenuSingleton::EntryList::iterator templ_end = s->templatesList->end(); for (; templ != templ_end; ++templ, ++i) { KNewFileMenuSingleton::Entry &entry = *templ; if (entry.entryType != KNewFileMenuSingleton::Separator) { // There might be a .desktop for that one already, if it's a kdelnk // This assumes we read .desktop files before .kdelnk files ... // In fact, we skip any second item that has the same text as another one. // Duplicates in a menu look bad in any case. const bool bSkip = seenTexts.contains(entry.text); if (bSkip) { // qDebug() << "skipping" << entry.filePath; } else { seenTexts.insert(entry.text); //const KNewFileMenuSingleton::Entry entry = templatesList->at(i-1); const QString templatePath = entry.templatePath; // The best way to identify the "Create Directory", "Link to Location", "Link to Application" was the template if (templatePath.endsWith(QLatin1String("emptydir"))) { QAction *act = new QAction(q); m_newDirAction = act; act->setIcon(QIcon::fromTheme(entry.icon)); act->setText(i18nc("@item:inmenu Create New", "%1", entry.text)); act->setActionGroup(m_newMenuGroup); // If there is a shortcut available in the action collection, use it. QAction *act2 = m_actionCollection->action(QStringLiteral("create_dir")); if (act2) { act->setShortcuts(act2->shortcuts()); // Both actions have now the same shortcut, so this will prevent the "Ambiguous shortcut detected" dialog. act->setShortcutContext(Qt::WidgetShortcut); // We also need to react to shortcut changes. QObject::connect(act2, &QAction::changed, act, [=]() { act->setShortcuts(act2->shortcuts()); }); } menu->addAction(act); menu->addSeparator(); } else { if (lastTemplatePath.startsWith(QDir::homePath()) && !templatePath.startsWith(QDir::homePath())) { menu->addSeparator(); } if (!m_supportedMimeTypes.isEmpty()) { bool keep = false; // We need to do mimetype filtering, for real files. const bool createSymlink = entry.templatePath == QLatin1String("__CREATE_SYMLINK__"); if (createSymlink) { keep = true; } else if (!KDesktopFile::isDesktopFile(entry.templatePath)) { // Determine mimetype on demand QMimeDatabase db; QMimeType mime; if (entry.mimeType.isEmpty()) { mime = db.mimeTypeForFile(entry.templatePath); //qDebug() << entry.templatePath << "is" << mime.name(); entry.mimeType = mime.name(); } else { mime = db.mimeTypeForName(entry.mimeType); } for (const QString &supportedMime : qAsConst(m_supportedMimeTypes)) { if (mime.inherits(supportedMime)) { keep = true; break; } } } if (!keep) { //qDebug() << "Not keeping" << entry.templatePath; continue; } } QAction *act = new QAction(q); act->setData(i); act->setIcon(QIcon::fromTheme(entry.icon)); act->setText(i18nc("@item:inmenu Create New", "%1", entry.text)); act->setActionGroup(m_newMenuGroup); //qDebug() << templatePath << entry.filePath; if (templatePath.endsWith(QLatin1String("/URL.desktop"))) { linkURL = act; } else if (templatePath.endsWith(QLatin1String("/Program.desktop"))) { linkApp = act; } else if (entry.filePath.endsWith(QLatin1String("/linkPath.desktop"))) { linkPath = act; } else if (KDesktopFile::isDesktopFile(templatePath)) { KDesktopFile df(templatePath); if (df.readType() == QLatin1String("FSDevice")) { m_menuDev->menu()->addAction(act); } else { menu->addAction(act); } } else { if (!m_firstFileEntry) { m_firstFileEntry = &entry; // If there is a shortcut available in the action collection, use it. QAction *act2 = m_actionCollection->action(QStringLiteral("create_file")); if (act2) { act->setShortcuts(act2->shortcuts()); // Both actions have now the same shortcut, so this will prevent the "Ambiguous shortcut detected" dialog. act->setShortcutContext(Qt::WidgetShortcut); // We also need to react to shortcut changes. QObject::connect(act2, &QAction::changed, act, [=]() { act->setShortcuts(act2->shortcuts()); }); } } menu->addAction(act); } } } lastTemplatePath = entry.templatePath; } else { // Separate system from personal templates Q_ASSERT(entry.entryType != 0); menu->addSeparator(); } } if (m_supportedMimeTypes.isEmpty()) { menu->addSeparator(); if (linkURL) { menu->addAction(linkURL); } if (linkPath) { menu->addAction(linkPath); } if (linkApp) { menu->addAction(linkApp); } Q_ASSERT(m_menuDev); if (!m_menuDev->menu()->isEmpty()) { menu->addAction(m_menuDev); } } } QUrl KNewFileMenuPrivate::mostLocalUrl(const QUrl &url) { if (url.isLocalFile()) { return url; } KIO::StatJob *job = KIO::stat(url); KJobWidgets::setWindow(job, m_parentWidget); if (!job->exec()) { return url; } KIO::UDSEntry entry = job->statResult(); const QString path = entry.stringValue(KIO::UDSEntry::UDS_LOCAL_PATH); return path.isEmpty() ? url : QUrl::fromLocalFile(path); } void KNewFileMenuPrivate::_k_slotAbortDialog() { m_text = QString(); } void KNewFileMenuPrivate::_k_slotActionTriggered(QAction *action) { q->trigger(); // was for kdesktop's slotNewMenuActivated() in kde3 times. Can't hurt to keep it... if (action == m_newDirAction) { q->createDirectory(); return; } const int id = action->data().toInt(); Q_ASSERT(id > 0); KNewFileMenuSingleton *s = kNewMenuGlobals(); const KNewFileMenuSingleton::Entry entry = s->templatesList->at(id - 1); const bool createSymlink = entry.templatePath == QLatin1String("__CREATE_SYMLINK__"); m_copyData = KNewFileMenuCopyData(); if (createSymlink) { m_copyData.m_isSymlink = true; executeSymLink(entry); } else if (KDesktopFile::isDesktopFile(entry.templatePath)) { KDesktopFile df(entry.templatePath); if (df.readType() == QLatin1String("Link")) { executeUrlDesktopFile(entry); } else { // any other desktop file (Device, App, etc.) executeOtherDesktopFile(entry); } } else { executeRealFileOrDir(entry); } } void KNewFileMenuPrivate::_k_slotCreateDirectory(bool writeHiddenDir) { QUrl url; QUrl baseUrl = m_popupFiles.first(); QString name = expandTilde(m_text); if (!name.isEmpty()) { if (QDir::isAbsolutePath(name)) { url = QUrl::fromLocalFile(name); } else { if (name == QLatin1Char('.') || name == QLatin1String("..")) { KGuiItem enterNewNameGuiItem(KStandardGuiItem::ok()); enterNewNameGuiItem.setText(i18nc("@action:button", "Enter a Different Name")); enterNewNameGuiItem.setIcon(QIcon::fromTheme(QStringLiteral("edit-rename"))); QDialog *confirmDialog = new QDialog(m_parentWidget); confirmDialog->setWindowTitle(i18n("Invalid Directory Name")); confirmDialog->setModal(m_modal); confirmDialog->setAttribute(Qt::WA_DeleteOnClose); m_buttonBox = new QDialogButtonBox(confirmDialog); m_buttonBox->setStandardButtons(QDialogButtonBox::Ok | QDialogButtonBox::Cancel); KGuiItem::assign(m_buttonBox->button(QDialogButtonBox::Ok), enterNewNameGuiItem); KMessageBox::createKMessageBox(confirmDialog, m_buttonBox, QMessageBox::Critical, xi18nc("@info", "Could not create a folder with the name %1because it is reserved for use by the operating system.", name), QStringList(), QString(), nullptr, KMessageBox::NoExec, QString()); m_creatingDirectory = true; QObject::connect(m_buttonBox, &QDialogButtonBox::accepted, q, &KNewFileMenu::createDirectory); m_fileDialog = confirmDialog; confirmDialog->show(); _k_slotAbortDialog(); return; } url = baseUrl; url.setPath(concatPaths(url.path(), name)); } } KIO::Job *job; if (name.contains(QLatin1Char('/'))) { // If the name contains any slashes, use mkpath so that a/b/c works. job = KIO::mkpath(url, baseUrl); KIO::FileUndoManager::self()->recordJob(KIO::FileUndoManager::Mkpath, QList(), url, job); } else { // If not, use mkdir so it will fail if the name of an existing folder was used job = KIO::mkdir(url); KIO::FileUndoManager::self()->recordJob(KIO::FileUndoManager::Mkdir, QList(), url, job); } job->setProperty("newDirectoryURL", url); job->uiDelegate()->setAutoErrorHandlingEnabled(true); KJobWidgets::setWindow(job, m_parentWidget); if (job) { // We want the error handling to be done by slotResult so that subclasses can reimplement it job->uiDelegate()->setAutoErrorHandlingEnabled(false); QObject::connect(job, &KJob::result, q, &KNewFileMenu::slotResult); } _k_slotAbortDialog(); } void KNewFileMenuPrivate::_k_slotCreateHiddenDirectory() { _k_slotCreateDirectory(true); } struct EntryWithName { QString key; KNewFileMenuSingleton::Entry entry; }; void KNewFileMenuPrivate::_k_slotFillTemplates() { KNewFileMenuSingleton *s = kNewMenuGlobals(); //qDebug(); const QStringList qrcTemplates = { QStringLiteral(":/kio5/newfile-templates") }; QStringList installedTemplates = { QStandardPaths::locateAll(QStandardPaths::GenericDataLocation, QStringLiteral("templates"), QStandardPaths::LocateDirectory) }; // Qt does not provide an easy way to receive the xdg dir for templates so we have to find it on our own #ifdef Q_OS_UNIX QString xdgUserDirs = QStandardPaths::locate(QStandardPaths::ConfigLocation, QStringLiteral("user-dirs.dirs"), QStandardPaths::LocateFile); QFile xdgUserDirsFile(xdgUserDirs); if (!xdgUserDirs.isEmpty() && xdgUserDirsFile.open(QIODevice::ReadOnly | QIODevice::Text)) { QTextStream in(&xdgUserDirsFile); while (!in.atEnd()) { QString line = in.readLine(); if (line.startsWith(QLatin1String("XDG_TEMPLATES_DIR="))) { QString xdgTemplates = line.mid(19, line.size()-20); xdgTemplates.replace(QStringLiteral("$HOME"), QDir::homePath()); QDir xdgTemplatesDir(xdgTemplates); if (xdgTemplatesDir.exists()) { installedTemplates << xdgTemplates; } break; } } } #endif const QStringList templates = qrcTemplates + installedTemplates; // Ensure any changes in the templates dir will call this if (! s->dirWatch) { s->dirWatch = new KDirWatch; for (const QString &dir : qAsConst(installedTemplates)) { s->dirWatch->addDir(dir); } QObject::connect(s->dirWatch, SIGNAL(dirty(QString)), q, SLOT(_k_slotFillTemplates())); QObject::connect(s->dirWatch, SIGNAL(created(QString)), q, SLOT(_k_slotFillTemplates())); QObject::connect(s->dirWatch, SIGNAL(deleted(QString)), q, SLOT(_k_slotFillTemplates())); // Ok, this doesn't cope with new dirs in XDG_DATA_DIRS, but that's another story } ++s->templatesVersion; s->filesParsed = false; s->templatesList->clear(); // Look into "templates" dirs. QStringList files; QDir dir; for (const QString &path : templates) { dir.setPath(path); const QStringList &entryList(dir.entryList(QStringList() << QStringLiteral("*.desktop"), QDir::Files)); files.reserve(files.size() + entryList.size()); for (const QString &entry : entryList) { const QString file = concatPaths(dir.path(), entry); files.append(file); } } QMap slist; // used for sorting QMap ulist; // entries with unique URLs for (const QString &file : qAsConst(files)) { //qDebug() << file; if (file[0] != QLatin1Char('.')) { KNewFileMenuSingleton::Entry e; e.filePath = file; e.entryType = KNewFileMenuSingleton::Unknown; // not parsed yet // Put Directory first in the list (a bit hacky), // and TextFile before others because it's the most used one. // This also sorts by user-visible name. // The rest of the re-ordering is done in fillMenu. const KDesktopFile config(file); QString url = config.desktopGroup().readEntry("URL"); QString key = config.desktopGroup().readEntry("Name"); if (file.endsWith(QLatin1String("Directory.desktop"))) { key.prepend(QLatin1Char('0')); } else if (file.startsWith(QDir::homePath())) { key.prepend(QLatin1Char('1')); } else if (file.endsWith(QLatin1String("TextFile.desktop"))) { key.prepend(QLatin1Char('2')); } else { key.prepend(QLatin1Char('3')); } EntryWithName en = { key, e }; if (ulist.contains(url)) { ulist.remove(url); } ulist.insert(url, en); } } QMap::iterator it = ulist.begin(); for (; it != ulist.end(); ++it) { EntryWithName ewn = *it; slist.insert(ewn.key, ewn.entry); } (*s->templatesList) += slist.values(); } void KNewFileMenuPrivate::_k_slotOtherDesktopFile() { // The properties dialog took care of the copying, so we're done KPropertiesDialog *dialog = qobject_cast(q->sender()); emit q->fileCreated(dialog->url()); } void KNewFileMenuPrivate::_k_slotOtherDesktopFileClosed() { QFile::remove(m_tempFileToDelete); } void KNewFileMenuPrivate::_k_slotRealFileOrDir() { m_copyData.m_chosenFileName = m_text; _k_slotAbortDialog(); executeStrategy(); } void KNewFileMenuPrivate::_k_slotSymLink() { KNameAndUrlInputDialog *dlg = static_cast(m_fileDialog); m_copyData.m_chosenFileName = dlg->name(); // no path const QString linkTarget = dlg->urlText(); if (m_copyData.m_chosenFileName.isEmpty() || linkTarget.isEmpty()) { return; } m_copyData.m_src = linkTarget; executeStrategy(); } void KNewFileMenuPrivate::_k_slotTextChanged(const QString &text) { m_buttonBox->button(QDialogButtonBox::Ok)->setEnabled(true); // Validate input, displaying a KMessageWidget for questionable names if (text.length() == 0) { m_messageWidget->hide(); m_buttonBox->button(QDialogButtonBox::Ok)->setEnabled(false); } // Don't allow creating folders that would mask . or .. else if (text == QLatin1Char('.') || text == QLatin1String("..")) { m_messageWidget->setText(xi18nc("@info", "The name %1 cannot be used because it is reserved for use by the operating system.", text)); m_messageWidget->setMessageType(KMessageWidget::Error); m_messageWidget->animatedShow(); m_buttonBox->button(QDialogButtonBox::Ok)->setEnabled(false); } // File or folder would be hidden; show warning else if (text.startsWith(QLatin1Char('.'))) { m_messageWidget->setText(xi18nc("@info", "The name %1 starts with a dot, so it will be hidden by default.", text)); m_messageWidget->setMessageType(KMessageWidget::Warning); m_messageWidget->animatedShow(); } #ifndef Q_OS_WIN // Inform the user that slashes in folder names create a directory tree else if (text.contains(QLatin1Char('/'))) { if (m_creatingDirectory) { QStringList folders = text.split(QLatin1Char('/')); if (!folders.isEmpty()) { if (folders.first().isEmpty()) { folders.removeFirst(); } } QString label; if (folders.count() > 1) { label = i18n("Using slashes in folder names will create sub-folders, like so:"); QString indentation = QString(); - for (const QString &folder : folders) { + for (const QString &folder : qAsConst(folders)) { label.append(QLatin1Char('\n')); label.append(indentation); label.append(folder); label.append(QStringLiteral("/")); indentation.append(QStringLiteral(" ")); } } else { label = i18n("Using slashes in folder names will create sub-folders."); } m_messageWidget->setText(label); m_messageWidget->setMessageType(KMessageWidget::Information); m_messageWidget->animatedShow(); } } #endif #ifdef Q_OS_WIN // Slashes and backslashes are not allowed in Windows filenames; show error else if (text.contains(QLatin1Char('/'))) { m_messageWidget->setText(i18n("Slashes cannot be used in file and folder names.")); m_messageWidget->setMessageType(KMessageWidget::Error); m_messageWidget->animatedShow(); m_buttonBox->button(QDialogButtonBox::Ok)->setEnabled(false); } else if (text.contains(QLatin1Char('\\'))) { m_messageWidget->setText(i18n("Backslashes cannot be used in file and folder names.")); m_messageWidget->setMessageType(KMessageWidget::Error); m_messageWidget->animatedShow(); m_buttonBox->button(QDialogButtonBox::Ok)->setEnabled(false); } #endif // Using a tilde to begin a file or folder name is not recommended else if (text.startsWith(QLatin1Char('~'))) { m_messageWidget->setText(i18n("Starting a file or folder name with a tilde is not recommended because it may be confusing or dangerous when using the terminal to delete things.")); m_messageWidget->setMessageType(KMessageWidget::Warning); m_messageWidget->animatedShow(); } else { m_messageWidget->hide(); } m_text = text; } void KNewFileMenuPrivate::_k_slotUrlDesktopFile() { KNameAndUrlInputDialog *dlg = static_cast(m_fileDialog); m_copyData.m_chosenFileName = dlg->name(); // no path QUrl linkUrl = dlg->url(); // Filter user input so that short uri entries, e.g. www.kde.org, are // handled properly. This not only makes the icon detection below work // properly, but opening the URL link where the short uri will not be // sent to the application (opening such link Konqueror fails). KUriFilterData uriData; uriData.setData(linkUrl); // the url to put in the file uriData.setCheckForExecutables(false); if (KUriFilter::self()->filterUri(uriData, QStringList() << QStringLiteral("kshorturifilter"))) { linkUrl = uriData.uri(); } if (m_copyData.m_chosenFileName.isEmpty() || linkUrl.isEmpty()) { return; } // It's a "URL" desktop file; we need to make a temp copy of it, to modify it // before copying it to the final destination [which could be a remote protocol] QTemporaryFile tmpFile; tmpFile.setAutoRemove(false); // done below if (!tmpFile.open()) { qCritical() << "Couldn't create temp file!"; return; } if (!checkSourceExists(m_copyData.m_templatePath)) { return; } // First copy the template into the temp file QFile file(m_copyData.m_templatePath); if (!file.open(QIODevice::ReadOnly)) { qCritical() << "Couldn't open template" << m_copyData.m_templatePath; return; } const QByteArray data = file.readAll(); tmpFile.write(data); const QString tempFileName = tmpFile.fileName(); Q_ASSERT(!tempFileName.isEmpty()); tmpFile.close(); file.close(); KDesktopFile df(tempFileName); KConfigGroup group = df.desktopGroup(); group.writeEntry("Icon", KProtocolInfo::icon(linkUrl.scheme())); group.writePathEntry("URL", linkUrl.toDisplayString()); df.sync(); m_copyData.m_src = tempFileName; m_copyData.m_tempFileToDelete = tempFileName; executeStrategy(); } KNewFileMenu::KNewFileMenu(KActionCollection *collection, const QString &name, QObject *parent) : KActionMenu(QIcon::fromTheme(QStringLiteral("document-new")), i18n("Create New"), parent), d(new KNewFileMenuPrivate(collection, this)) { // Don't fill the menu yet // We'll do that in checkUpToDate (should be connected to aboutToShow) d->m_newMenuGroup = new QActionGroup(this); connect(d->m_newMenuGroup, SIGNAL(triggered(QAction*)), this, SLOT(_k_slotActionTriggered(QAction*))); d->m_parentWidget = qobject_cast(parent); d->m_newDirAction = nullptr; if (d->m_actionCollection) { d->m_actionCollection->addAction(name, this); } d->m_menuDev = new KActionMenu(QIcon::fromTheme(QStringLiteral("drive-removable-media")), i18n("Link to Device"), this); } KNewFileMenu::~KNewFileMenu() { //qDebug() << this; delete d; } void KNewFileMenu::checkUpToDate() { KNewFileMenuSingleton *s = kNewMenuGlobals(); //qDebug() << this << "m_menuItemsVersion=" << d->m_menuItemsVersion // << "s->templatesVersion=" << s->templatesVersion; if (d->m_menuItemsVersion < s->templatesVersion || s->templatesVersion == 0) { //qDebug() << "recreating actions"; // We need to clean up the action collection // We look for our actions using the group foreach (QAction *action, d->m_newMenuGroup->actions()) { delete action; } if (!s->templatesList) { // No templates list up to now s->templatesList = new KNewFileMenuSingleton::EntryList; d->_k_slotFillTemplates(); s->parseFiles(); } // This might have been already done for other popupmenus, // that's the point in s->filesParsed. if (!s->filesParsed) { s->parseFiles(); } d->fillMenu(); d->m_menuItemsVersion = s->templatesVersion; } } void KNewFileMenu::createDirectory() { if (d->m_popupFiles.isEmpty()) { return; } QUrl baseUrl = d->m_popupFiles.first(); KIO::StatJob *job = KIO::mostLocalUrl(baseUrl); if (job->exec()) { baseUrl = job->mostLocalUrl(); } QString name = d->m_text.isEmpty() ? i18nc("Default name for a new folder", "New Folder") : d->m_text; if (baseUrl.isLocalFile() && QFileInfo::exists(baseUrl.toLocalFile() + QLatin1Char('/') + name)) { name = KFileUtils::suggestName(baseUrl, name); } QDialog *fileDialog = new QDialog(d->m_parentWidget); fileDialog->setModal(isModal()); fileDialog->setAttribute(Qt::WA_DeleteOnClose); fileDialog->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Minimum); fileDialog->setWindowTitle(i18nc("@title:window", "New Folder")); d->m_fileDialog = fileDialog; QVBoxLayout *layout = new QVBoxLayout; layout->setSizeConstraint(QLayout::SetFixedSize); d->m_messageWidget = new KMessageWidget(fileDialog); d->m_messageWidget->setCloseButtonVisible(false); d->m_messageWidget->setWordWrap(true); d->m_messageWidget->hide(); QLabel *label = new QLabel(i18n("Create new folder in %1:", baseUrl.toDisplayString(QUrl::PreferLocalFile)), fileDialog); QLineEdit *lineEdit = new QLineEdit(fileDialog); lineEdit->setClearButtonEnabled(true); lineEdit->setText(name); lineEdit->setMinimumWidth(400); d->m_buttonBox = new QDialogButtonBox(fileDialog); d->m_buttonBox->setStandardButtons(QDialogButtonBox::Ok | QDialogButtonBox::Cancel); QObject::connect(d->m_buttonBox, &QDialogButtonBox::accepted, fileDialog, &QDialog::accept); QObject::connect(d->m_buttonBox, &QDialogButtonBox::rejected, fileDialog, &QDialog::reject); d->m_creatingDirectory = true; d->_k_slotTextChanged(name); // have to save string in d->m_text in case user does not touch dialog connect(lineEdit, SIGNAL(textChanged(QString)), this, SLOT(_k_slotTextChanged(QString))); layout->addWidget(label); layout->addWidget(lineEdit); layout->addWidget(d->m_buttonBox); layout->addWidget(d->m_messageWidget); layout->addStretch(); fileDialog->setLayout(layout); connect(fileDialog, SIGNAL(accepted()), this, SLOT(_k_slotCreateDirectory())); connect(fileDialog, SIGNAL(rejected()), this, SLOT(_k_slotAbortDialog())); fileDialog->show(); lineEdit->selectAll(); lineEdit->setFocus(); } void KNewFileMenu::createFile() { if (d->m_popupFiles.isEmpty()) { return; } checkUpToDate(); if (!d->m_firstFileEntry) { return; } d->executeRealFileOrDir(*d->m_firstFileEntry); } bool KNewFileMenu::isModal() const { return d->m_modal; } QList KNewFileMenu::popupFiles() const { return d->m_popupFiles; } void KNewFileMenu::setModal(bool modal) { d->m_modal = modal; } void KNewFileMenu::setPopupFiles(const QList &files) { d->m_popupFiles = files; if (files.isEmpty()) { d->m_newMenuGroup->setEnabled(false); } else { QUrl firstUrl = files.first(); if (KProtocolManager::supportsWriting(firstUrl)) { d->m_newMenuGroup->setEnabled(true); if (d->m_newDirAction) { d->m_newDirAction->setEnabled(KProtocolManager::supportsMakeDir(firstUrl)); // e.g. trash:/ } } else { d->m_newMenuGroup->setEnabled(true); } } } void KNewFileMenu::setParentWidget(QWidget *parentWidget) { d->m_parentWidget = parentWidget; } void KNewFileMenu::setSupportedMimeTypes(const QStringList &mime) { d->m_supportedMimeTypes = mime; } void KNewFileMenu::setViewShowsHiddenFiles(bool b) { d->m_viewShowsHiddenFiles = b; } void KNewFileMenu::slotResult(KJob *job) { if (job->error()) { static_cast(job)->uiDelegate()->showErrorMessage(); } else { // Was this a copy or a mkdir? if (job->property("newDirectoryURL").isValid()) { QUrl newDirectoryURL = job->property("newDirectoryURL").toUrl(); emit directoryCreated(newDirectoryURL); } else { KIO::CopyJob *copyJob = ::qobject_cast(job); if (copyJob) { const QUrl destUrl = copyJob->destUrl(); const QUrl localUrl = d->mostLocalUrl(destUrl); if (localUrl.isLocalFile()) { // Normal (local) file. Need to "touch" it, kio_file copied the mtime. (void) ::utime(QFile::encodeName(localUrl.toLocalFile()).constData(), nullptr); } emit fileCreated(destUrl); } else if (KIO::SimpleJob *simpleJob = ::qobject_cast(job)) { // Called in the storedPut() case org::kde::KDirNotify::emitFilesAdded(simpleJob->url().adjusted(QUrl::RemoveFilename | QUrl::StripTrailingSlash)); emit fileCreated(simpleJob->url()); } } } if (!d->m_tempFileToDelete.isEmpty()) { QFile::remove(d->m_tempFileToDelete); } } QStringList KNewFileMenu::supportedMimeTypes() const { return d->m_supportedMimeTypes; } #include "moc_knewfilemenu.cpp" diff --git a/src/kcms/webshortcuts/main.cpp b/src/kcms/webshortcuts/main.cpp index 19b29544..28ab4d43 100644 --- a/src/kcms/webshortcuts/main.cpp +++ b/src/kcms/webshortcuts/main.cpp @@ -1,128 +1,128 @@ /* * Copyright (c) 2000 Yves Arrouye * * 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. */ // Own #include "main.h" // Qt #include #include // KDE #include #include #include #include #include #include K_PLUGIN_FACTORY(KURIFilterModuleFactory, registerPlugin();) KURIFilterModule::KURIFilterModule(QWidget *parent, const QVariantList &args) : KCModule(parent, args), m_widget(nullptr) { KAboutData *about = new KAboutData(QStringLiteral("kcm_webshortcuts"), i18n("Web Shortcuts"), QStringLiteral("0.1"), i18n("Configure enhanced browsing features"), KAboutLicense::GPL); setAboutData(about); KCModule::setButtons(KCModule::Buttons(KCModule::Default | KCModule::Apply | KCModule::Help)); filter = KUriFilter::self(); setQuickHelp(i18n("

Enhanced Browsing

In this module you can configure some enhanced browsing" " features of KDE. " "

Web Shortcuts

Web Shortcuts are a quick way of using Web search engines. For example, type \"duckduckgo:frobozz\"" " or \"dd:frobozz\" and your web browser will do a search on DuckDuckGo for \"frobozz\"." " Even easier: just press Alt+F2 (if you have not" " changed this keyboard shortcut) and enter the shortcut in the Run Command dialog.")); QVBoxLayout *layout = new QVBoxLayout(this); QMap helper; // Load the plugins. This saves a public method in KUriFilter just for this. QVector plugins = KPluginLoader::findPlugins(QStringLiteral("kf5/urifilters")); - for (const KPluginMetaData &pluginMetaData : plugins) { + for (const KPluginMetaData &pluginMetaData : qAsConst(plugins)) { KPluginFactory *factory = qobject_cast(pluginMetaData.instantiate()); if (factory) { KUriFilterPlugin *plugin = factory->create(nullptr); if (plugin) { KCModule *module = plugin->configModule(this, nullptr); if (module) { modules.append(module); helper.insert(plugin->configName(), module); connect(module, QOverload::of(&KCModule::changed), this, QOverload::of(&KCModule::changed)); } } } } if (modules.count() > 1) { QTabWidget *tab = new QTabWidget(this); QMap::iterator it2; for (it2 = helper.begin(); it2 != helper.end(); ++it2) { tab->addTab(it2.value(), it2.key()); } tab->setCurrentIndex(tab->indexOf(modules.first())); m_widget = tab; } else if (modules.count() == 1) { m_widget = modules.first(); if (m_widget->layout()) { m_widget->layout()->setContentsMargins(0, 0, 0, 0); } } if (m_widget) { layout->addWidget(m_widget); } } void KURIFilterModule::load() { // seems not to be necessary, since modules automatically call load() on show (uwolfer) // foreach( KCModule* module, modules ) // { // module->load(); // } } void KURIFilterModule::save() { foreach(KCModule * module, modules) { module->save(); } } void KURIFilterModule::defaults() { foreach(KCModule * module, modules) { module->defaults(); } } KURIFilterModule::~KURIFilterModule() { qDeleteAll(modules); } #include "main.moc"