diff --git a/src/panels/places/placespanel.cpp b/src/panels/places/placespanel.cpp index 216b76038..1f81a1eaa 100644 --- a/src/panels/places/placespanel.cpp +++ b/src/panels/places/placespanel.cpp @@ -1,523 +1,521 @@ /*************************************************************************** * Copyright (C) 2008-2012 by Peter Penz * * * * Based on KFilePlacesView from kdelibs: * * Copyright (C) 2007 Kevin Ottens * * Copyright (C) 2007 David Faure * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program; if not, write to the * * Free Software Foundation, Inc., * * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA * ***************************************************************************/ #include "placespanel.h" #include "dolphin_generalsettings.h" #include "global.h" #include "kitemviews/kitemlistcontainer.h" #include "kitemviews/kitemlistcontroller.h" #include "kitemviews/kitemlistselectionmanager.h" #include "kitemviews/kstandarditem.h" #include "placesitem.h" #include "placesitemeditdialog.h" #include "placesitemlistgroupheader.h" #include "placesitemlistwidget.h" #include "placesitemmodel.h" #include "placesview.h" #include "trash/dolphintrash.h" #include "views/draganddrophelper.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include PlacesPanel::PlacesPanel(QWidget* parent) : Panel(parent), m_controller(nullptr), m_model(nullptr), m_view(nullptr), m_storageSetupFailedUrl(), m_triggerStorageSetupButton(), m_itemDropEventIndex(-1), m_itemDropEventMimeData(nullptr), m_itemDropEvent(nullptr) { } PlacesPanel::~PlacesPanel() { } void PlacesPanel::proceedWithTearDown() { m_model->proceedWithTearDown(); } bool PlacesPanel::urlChanged() { if (!url().isValid() || url().scheme().contains(QStringLiteral("search"))) { // Skip results shown by a search, as possible identical // directory names are useless without parent-path information. return false; } if (m_controller) { selectClosestItem(); } return true; } void PlacesPanel::readSettings() { if (m_controller) { const int delay = GeneralSettings::autoExpandFolders() ? 750 : -1; m_controller->setAutoActivationDelay(delay); } } void PlacesPanel::showEvent(QShowEvent* event) { if (event->spontaneous()) { Panel::showEvent(event); return; } if (!m_controller) { // Postpone the creating of the controller to the first show event. // This assures that no performance and memory overhead is given when the folders panel is not // used at all and stays invisible. m_model = new PlacesItemModel(this); m_model->setGroupedSorting(true); connect(m_model, &PlacesItemModel::errorMessage, this, &PlacesPanel::errorMessage); connect(m_model, &PlacesItemModel::storageTearDownRequested, this, &PlacesPanel::storageTearDownRequested); connect(m_model, &PlacesItemModel::storageTearDownExternallyRequested, this, &PlacesPanel::storageTearDownExternallyRequested); m_view = new PlacesView(); m_view->setWidgetCreator(new KItemListWidgetCreator()); m_view->setGroupHeaderCreator(new KItemListGroupHeaderCreator()); m_controller = new KItemListController(m_model, m_view, this); m_controller->setSelectionBehavior(KItemListController::SingleSelection); m_controller->setSingleClickActivationEnforced(true); readSettings(); connect(m_controller, &KItemListController::itemActivated, this, &PlacesPanel::slotItemActivated); connect(m_controller, &KItemListController::itemMiddleClicked, this, &PlacesPanel::slotItemMiddleClicked); connect(m_controller, &KItemListController::itemContextMenuRequested, this, &PlacesPanel::slotItemContextMenuRequested); connect(m_controller, &KItemListController::viewContextMenuRequested, this, &PlacesPanel::slotViewContextMenuRequested); connect(m_controller, &KItemListController::itemDropEvent, this, &PlacesPanel::slotItemDropEvent); connect(m_controller, &KItemListController::aboveItemDropEvent, this, &PlacesPanel::slotAboveItemDropEvent); KItemListContainer* container = new KItemListContainer(m_controller, this); container->setEnabledFrame(false); QVBoxLayout* layout = new QVBoxLayout(this); layout->setMargin(0); layout->addWidget(container); selectClosestItem(); } Panel::showEvent(event); } void PlacesPanel::slotItemActivated(int index) { triggerItem(index, Qt::LeftButton); } void PlacesPanel::slotItemMiddleClicked(int index) { triggerItem(index, Qt::MiddleButton); } void PlacesPanel::slotItemContextMenuRequested(int index, const QPointF& pos) { PlacesItem* item = m_model->placesItem(index); if (!item) { return; } QMenu menu(this); QAction* emptyTrashAction = nullptr; QAction* editAction = nullptr; QAction* teardownAction = nullptr; QAction* ejectAction = nullptr; - const QString label = item->text(); - const bool isDevice = !item->udi().isEmpty(); const bool isTrash = (item->url().scheme() == QLatin1String("trash")); if (isDevice) { ejectAction = m_model->ejectAction(index); if (ejectAction) { ejectAction->setParent(&menu); menu.addAction(ejectAction); } teardownAction = m_model->teardownAction(index); if (teardownAction) { teardownAction->setParent(&menu); menu.addAction(teardownAction); } if (teardownAction || ejectAction) { menu.addSeparator(); } } else { if (isTrash) { emptyTrashAction = menu.addAction(QIcon::fromTheme(QStringLiteral("trash-empty")), i18nc("@action:inmenu", "Empty Trash")); emptyTrashAction->setEnabled(item->icon() == QLatin1String("user-trash-full")); menu.addSeparator(); } } QAction* openInNewWindowAction = menu.addAction(QIcon::fromTheme("window-new"), i18nc("@item:inmenu", "Open in New Window")); QAction* openInNewTabAction = menu.addAction(QIcon::fromTheme("tab-new"), i18nc("@item:inmenu", "Open in New Tab")); if (!isDevice && !isTrash) { menu.addSeparator(); } if (!isDevice) { editAction = menu.addAction(QIcon::fromTheme("document-properties"), i18nc("@item:inmenu", "Edit...")); } QAction* removeAction = nullptr; if (!isDevice && !item->isSystemItem()) { removeAction = menu.addAction(QIcon::fromTheme(QStringLiteral("edit-delete")), i18nc("@item:inmenu", "Remove")); } QAction* hideAction = menu.addAction(i18nc("@item:inmenu", "Hide")); hideAction->setCheckable(true); hideAction->setChecked(item->isHidden()); buildGroupContextMenu(&menu, index); QAction* action = menu.exec(pos.toPoint()); if (action) { if (action == emptyTrashAction) { Trash::empty(this); } else { // The index might have changed if devices were added/removed while // the context menu was open. index = m_model->index(item); if (index < 0) { // The item is not in the model any more, probably because it was an // external device that has been removed while the context menu was open. return; } if (action == editAction) { editEntry(index); } else if (action == removeAction) { m_model->deleteItem(index); } else if (action == hideAction) { item->setHidden(hideAction->isChecked()); } else if (action == openInNewWindowAction) { Dolphin::openNewWindow({KFilePlacesModel::convertedUrl(m_model->data(index).value("url").toUrl())}, this); } else if (action == openInNewTabAction) { // TriggerItem does set up the storage first and then it will // emit the slotItemMiddleClicked signal, because of Qt::MiddleButton. triggerItem(index, Qt::MiddleButton); } else if (action == teardownAction) { m_model->requestTearDown(index); } else if (action == ejectAction) { m_model->requestEject(index); } } } selectClosestItem(); } void PlacesPanel::slotViewContextMenuRequested(const QPointF& pos) { QMenu menu(this); QAction* addAction = menu.addAction(QIcon::fromTheme(QStringLiteral("document-new")), i18nc("@item:inmenu", "Add Entry...")); QAction* showAllAction = nullptr; if (m_model->hiddenCount() > 0) { showAllAction = menu.addAction(i18nc("@item:inmenu", "Show All Entries")); showAllAction->setCheckable(true); showAllAction->setChecked(m_model->hiddenItemsShown()); } buildGroupContextMenu(&menu, m_controller->indexCloseToMousePressedPosition()); QMenu* iconSizeSubMenu = new QMenu(i18nc("@item:inmenu", "Icon Size"), &menu); struct IconSizeInfo { int size; const char* context; const char* text; }; const int iconSizeCount = 4; static const IconSizeInfo iconSizes[iconSizeCount] = { {KIconLoader::SizeSmall, I18N_NOOP2_NOSTRIP("Small icon size", "Small (%1x%2)")}, {KIconLoader::SizeSmallMedium, I18N_NOOP2_NOSTRIP("Medium icon size", "Medium (%1x%2)")}, {KIconLoader::SizeMedium, I18N_NOOP2_NOSTRIP("Large icon size", "Large (%1x%2)")}, {KIconLoader::SizeLarge, I18N_NOOP2_NOSTRIP("Huge icon size", "Huge (%1x%2)")} }; QMap iconSizeActionMap; QActionGroup* iconSizeGroup = new QActionGroup(iconSizeSubMenu); for (int i = 0; i < iconSizeCount; ++i) { const int size = iconSizes[i].size; const QString text = i18nc(iconSizes[i].context, iconSizes[i].text, size, size); QAction* action = iconSizeSubMenu->addAction(text); iconSizeActionMap.insert(action, size); action->setActionGroup(iconSizeGroup); action->setCheckable(true); action->setChecked(m_view->iconSize() == size); } menu.addMenu(iconSizeSubMenu); menu.addSeparator(); foreach (QAction* action, customContextMenuActions()) { menu.addAction(action); } QAction* action = menu.exec(pos.toPoint()); if (action) { if (action == addAction) { addEntry(); } else if (action == showAllAction) { m_model->setHiddenItemsShown(showAllAction->isChecked()); } else if (iconSizeActionMap.contains(action)) { m_view->setIconSize(iconSizeActionMap.value(action)); } } selectClosestItem(); } QAction *PlacesPanel::buildGroupContextMenu(QMenu *menu, int index) { if (index == -1) { return nullptr; } KFilePlacesModel::GroupType groupType = m_model->groupType(index); QAction *hideGroupAction = menu->addAction(i18nc("@item:inmenu", "Hide Section '%1'", m_model->item(index)->group())); hideGroupAction->setCheckable(true); hideGroupAction->setChecked(m_model->isGroupHidden(groupType)); connect(hideGroupAction, &QAction::triggered, this, [this, groupType, hideGroupAction]{ m_model->setGroupHidden(groupType, hideGroupAction->isChecked()); }); return hideGroupAction; } void PlacesPanel::slotItemDropEvent(int index, QGraphicsSceneDragDropEvent* event) { if (index < 0) { return; } const PlacesItem* destItem = m_model->placesItem(index); if (destItem->isSearchOrTimelineUrl()) { return; } if (m_model->storageSetupNeeded(index)) { connect(m_model, &PlacesItemModel::storageSetupDone, this, &PlacesPanel::slotItemDropEventStorageSetupDone); m_itemDropEventIndex = index; // Make a full copy of the Mime-Data m_itemDropEventMimeData = new QMimeData; m_itemDropEventMimeData->setText(event->mimeData()->text()); m_itemDropEventMimeData->setHtml(event->mimeData()->html()); m_itemDropEventMimeData->setUrls(event->mimeData()->urls()); m_itemDropEventMimeData->setImageData(event->mimeData()->imageData()); m_itemDropEventMimeData->setColorData(event->mimeData()->colorData()); m_itemDropEvent = new QDropEvent(event->pos().toPoint(), event->possibleActions(), m_itemDropEventMimeData, event->buttons(), event->modifiers()); m_model->requestStorageSetup(index); return; } QUrl destUrl = destItem->url(); QDropEvent dropEvent(event->pos().toPoint(), event->possibleActions(), event->mimeData(), event->buttons(), event->modifiers()); slotUrlsDropped(destUrl, &dropEvent, this); } void PlacesPanel::slotItemDropEventStorageSetupDone(int index, bool success) { disconnect(m_model, &PlacesItemModel::storageSetupDone, this, &PlacesPanel::slotItemDropEventStorageSetupDone); if ((index == m_itemDropEventIndex) && m_itemDropEvent && m_itemDropEventMimeData) { if (success) { QUrl destUrl = m_model->placesItem(index)->url(); slotUrlsDropped(destUrl, m_itemDropEvent, this); } delete m_itemDropEventMimeData; delete m_itemDropEvent; m_itemDropEventIndex = -1; m_itemDropEventMimeData = nullptr; m_itemDropEvent = nullptr; } } void PlacesPanel::slotAboveItemDropEvent(int index, QGraphicsSceneDragDropEvent* event) { m_model->dropMimeDataBefore(index, event->mimeData()); } void PlacesPanel::slotUrlsDropped(const QUrl& dest, QDropEvent* event, QWidget* parent) { KIO::DropJob *job = DragAndDropHelper::dropUrls(dest, event, parent); if (job) { connect(job, &KIO::DropJob::result, this, [this](KJob *job) { if (job->error()) emit errorMessage(job->errorString()); }); } } void PlacesPanel::slotStorageSetupDone(int index, bool success) { disconnect(m_model, &PlacesItemModel::storageSetupDone, this, &PlacesPanel::slotStorageSetupDone); if (m_triggerStorageSetupButton == Qt::NoButton) { return; } if (success) { Q_ASSERT(!m_model->storageSetupNeeded(index)); triggerItem(index, m_triggerStorageSetupButton); m_triggerStorageSetupButton = Qt::NoButton; } else { setUrl(m_storageSetupFailedUrl); m_storageSetupFailedUrl = QUrl(); } } void PlacesPanel::addEntry() { const int index = m_controller->selectionManager()->currentItem(); const QUrl url = m_model->data(index).value("url").toUrl(); QPointer dialog = new PlacesItemEditDialog(this); dialog->setWindowTitle(i18nc("@title:window", "Add Places Entry")); dialog->setAllowGlobal(true); dialog->setUrl(url); if (dialog->exec() == QDialog::Accepted) { m_model->createPlacesItem(dialog->text(), dialog->url(), dialog->icon()); } delete dialog; } void PlacesPanel::editEntry(int index) { QHash data = m_model->data(index); QPointer dialog = new PlacesItemEditDialog(this); dialog->setWindowTitle(i18nc("@title:window", "Edit Places Entry")); dialog->setIcon(data.value("iconName").toString()); dialog->setText(data.value("text").toString()); dialog->setUrl(data.value("url").toUrl()); dialog->setAllowGlobal(true); if (dialog->exec() == QDialog::Accepted) { PlacesItem* oldItem = m_model->placesItem(index); if (oldItem) { oldItem->setText(dialog->text()); oldItem->setUrl(dialog->url()); oldItem->setIcon(dialog->icon()); m_model->refresh(); } } delete dialog; } void PlacesPanel::selectClosestItem() { const int index = m_model->closestItem(url()); KItemListSelectionManager* selectionManager = m_controller->selectionManager(); selectionManager->setCurrentItem(index); selectionManager->clearSelection(); selectionManager->setSelected(index); } void PlacesPanel::triggerItem(int index, Qt::MouseButton button) { const PlacesItem* item = m_model->placesItem(index); if (!item) { return; } if (m_model->storageSetupNeeded(index)) { m_triggerStorageSetupButton = button; m_storageSetupFailedUrl = url(); connect(m_model, &PlacesItemModel::storageSetupDone, this, &PlacesPanel::slotStorageSetupDone); m_model->requestStorageSetup(index); } else { m_triggerStorageSetupButton = Qt::NoButton; const QUrl url = m_model->data(index).value("url").toUrl(); if (!url.isEmpty()) { if (button == Qt::MiddleButton) { emit placeMiddleClicked(KFilePlacesModel::convertedUrl(url)); } else { emit placeActivated(KFilePlacesModel::convertedUrl(url)); } } } } diff --git a/src/tests/placesitemmodeltest.cpp b/src/tests/placesitemmodeltest.cpp index ec44c8f31..3263537f9 100644 --- a/src/tests/placesitemmodeltest.cpp +++ b/src/tests/placesitemmodeltest.cpp @@ -1,845 +1,844 @@ /*************************************************************************** * Copyright (C) 2017 by Renato Araujo Oliveira * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program; if not, write to the * * Free Software Foundation, Inc., * * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA * ***************************************************************************/ #include #include #include #include #include #include #include #include #include #include #include "panels/places/placesitemmodel.h" #include "panels/places/placesitem.h" #include "views/viewproperties.h" Q_DECLARE_METATYPE(KItemRangeList) Q_DECLARE_METATYPE(KItemRange) #ifdef Q_OS_WIN //c:\ as root for windows #define KDE_ROOT_PATH "C:\\" #else #define KDE_ROOT_PATH "/" #endif static QString bookmarksFile() { return QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + "/user-places.xbel"; } class PlacesItemModelTest : public QObject { Q_OBJECT private slots: void init(); void cleanup(); void initTestCase(); void cleanupTestCase(); void testModelSort(); void testGroups(); void testDeletePlace(); void testPlaceItem_data(); void testPlaceItem(); void testTearDownDevice(); void testDefaultViewProperties_data(); void testDefaultViewProperties(); void testClear(); void testHideItem(); void testSystemItems(); void testEditBookmark(); void testEditAfterCreation(); void testEditMetadata(); void testRefresh(); void testIcons_data(); void testIcons(); void testDragAndDrop(); void testHideDevices(); void testDuplicatedEntries(); void renameAfterCreation(); private: PlacesItemModel* m_model; QSet m_tobeRemoved; QMap m_interfacesMap; void setBalooEnabled(bool enabled); int indexOf(const QUrl &url); QDBusInterface *fakeManager(); QDBusInterface *fakeDevice(const QString &udi); QStringList placesUrls(PlacesItemModel *model = nullptr) const; QStringList initialUrls() const; void createPlaceItem(const QString &text, const QUrl &url, const QString &icon); void removePlaceAfter(int index); void cancelPlaceRemoval(int index); void removeTestUserData(); QMimeData *createMimeData(const QList &indexes) const; }; #define CHECK_PLACES_URLS(urls) \ { \ QStringList places = placesUrls(); \ if (places != urls) { \ qWarning() << "Expected:" << urls; \ qWarning() << "Got:" << places; \ QCOMPARE(places, urls); \ } \ } void PlacesItemModelTest::setBalooEnabled(bool enabled) { KConfig config(QStringLiteral("baloofilerc")); KConfigGroup basicSettings = config.group("Basic Settings"); basicSettings.writeEntry("Indexing-Enabled", enabled); config.sync(); } int PlacesItemModelTest::indexOf(const QUrl &url) { for (int r = 0; r < m_model->count(); r++) { if (m_model->placesItem(r)->url() == url) { return r; } } return -1; } QDBusInterface *PlacesItemModelTest::fakeManager() { return fakeDevice(QStringLiteral("/org/kde/solid/fakehw")); } QDBusInterface *PlacesItemModelTest::fakeDevice(const QString &udi) { if (m_interfacesMap.contains(udi)) { return m_interfacesMap[udi]; } QDBusInterface *iface = new QDBusInterface(QDBusConnection::sessionBus().baseService(), udi); m_interfacesMap[udi] = iface; return iface; } QStringList PlacesItemModelTest::placesUrls(PlacesItemModel *model) const { QStringList urls; if (!model) { model = m_model; } for (int row = 0; row < model->count(); ++row) { urls << model->placesItem(row)->url().toDisplayString(QUrl::PreferLocalFile); } return urls; } QStringList PlacesItemModelTest::initialUrls() const { static QStringList urls; if (urls.isEmpty()) { urls << QDir::homePath() << QStringLiteral(KDE_ROOT_PATH) << QStringLiteral("trash:/") << QStringLiteral("remote:/") << QStringLiteral("timeline:/today") << QStringLiteral("timeline:/yesterday") << QStringLiteral("timeline:/thismonth") << QStringLiteral("timeline:/lastmonth") << QStringLiteral("search:/documents") << QStringLiteral("search:/images") << QStringLiteral("search:/audio") << QStringLiteral("search:/videos") << QStringLiteral("/media/nfs") << QStringLiteral("/foreign") << QStringLiteral("/media/floppy0") << QStringLiteral("/media/XO-Y4") << QStringLiteral("/media/cdrom"); } return urls; } void PlacesItemModelTest::createPlaceItem(const QString &text, const QUrl &url, const QString &icon) { m_model->createPlacesItem(text, url, icon); } void PlacesItemModelTest::removePlaceAfter(int index) { m_tobeRemoved.insert(index); } void PlacesItemModelTest::cancelPlaceRemoval(int index) { m_tobeRemoved.remove(index); } void PlacesItemModelTest::removeTestUserData() { // user hardcoded path to avoid removal of any user personal data QDir dir(QStringLiteral("/home/renato/.qttest/share/placesitemmodeltest")); if (dir.exists()) { QVERIFY(dir.removeRecursively()); } } QMimeData *PlacesItemModelTest::createMimeData(const QList &indexes) const { QByteArray itemData; QDataStream stream(&itemData, QIODevice::WriteOnly); QList urls; for (int index : indexes) { const QUrl itemUrl = m_model->placesItem(index)->url(); if (itemUrl.isValid()) { urls << itemUrl; } stream << index; } QMimeData* mimeData = new QMimeData(); mimeData->setUrls(urls); // copied from PlacesItemModel::internalMimeType() const QString internalMimeType = "application/x-dolphinplacesmodel-" + QString::number((qptrdiff)m_model); mimeData->setData(internalMimeType, itemData); return mimeData; } void PlacesItemModelTest::init() { m_model = new PlacesItemModel(); // WORKAROUND: need to wait for bookmark to load, check: PlacesItemModel::updateBookmarks QTest::qWait(300); QCOMPARE(m_model->count(), 17); } void PlacesItemModelTest::cleanup() { for (int i : m_tobeRemoved) { int before = m_model->count(); m_model->deleteItem(i); QTRY_COMPARE(m_model->count(), before - 1); } m_tobeRemoved.clear(); delete m_model; m_model = nullptr; removeTestUserData(); } void PlacesItemModelTest::initTestCase() { QStandardPaths::setTestModeEnabled(true); // remove test user data removeTestUserData(); const QString fakeHw = QFINDTESTDATA("data/fakecomputer.xml"); QVERIFY(!fakeHw.isEmpty()); qputenv("SOLID_FAKEHW", QFile::encodeName(fakeHw)); setBalooEnabled(true); const QString bookmarsFileName = bookmarksFile(); if (QFileInfo::exists(bookmarsFileName)) { // Ensure we'll have a clean bookmark file to start QVERIFY(QFile::remove(bookmarsFileName)); } qRegisterMetaType(); qRegisterMetaType(); } void PlacesItemModelTest::cleanupTestCase() { qDeleteAll(m_interfacesMap); QFile::remove(bookmarksFile()); // Remove any previous properties file removeTestUserData(); } void PlacesItemModelTest::testModelSort() { CHECK_PLACES_URLS(initialUrls()); } void PlacesItemModelTest::testGroups() { const auto groups = m_model->groups(); QCOMPARE(groups.size(), 6); QCOMPARE(groups.at(0).first, 0); QCOMPARE(groups.at(0).second.toString(), QStringLiteral("Places")); QCOMPARE(groups.at(1).first, 3); QCOMPARE(groups.at(1).second.toString(), QStringLiteral("Remote")); QCOMPARE(groups.at(2).first, 4); QCOMPARE(groups.at(2).second.toString(), QStringLiteral("Recently Saved")); QCOMPARE(groups.at(3).first, 8); QCOMPARE(groups.at(3).second.toString(), QStringLiteral("Search For")); QCOMPARE(groups.at(4).first, 12); QCOMPARE(groups.at(4).second.toString(), QStringLiteral("Devices")); QCOMPARE(groups.at(5).first, 14); QCOMPARE(groups.at(5).second.toString(), QStringLiteral("Removable Devices")); } void PlacesItemModelTest::testPlaceItem_data() { QTest::addColumn("url"); QTest::addColumn("expectedIsHidden"); QTest::addColumn("expectedIsSystemItem"); QTest::addColumn("expectedGroup"); QTest::addColumn("expectedStorageSetupNeeded"); // places QTest::newRow("Places - Home") << QUrl::fromLocalFile(QDir::homePath()) << false << true << QStringLiteral("Places") << false; // baloo -search QTest::newRow("Baloo - Documents") << QUrl("search:/documents") << false << true << QStringLiteral("Search For") << false; // baloo - timeline QTest::newRow("Baloo - Last Month") << QUrl("timeline:/lastmonth") << false << true << QStringLiteral("Recently Saved") << false; // devices QTest::newRow("Devices - Floppy") << QUrl("file:///media/floppy0") << false << false << QStringLiteral("Removable Devices") << false; } void PlacesItemModelTest::testPlaceItem() { QFETCH(QUrl, url); QFETCH(bool, expectedIsHidden); QFETCH(bool, expectedIsSystemItem); QFETCH(QString, expectedGroup); QFETCH(bool, expectedStorageSetupNeeded); const int index = indexOf(url); PlacesItem *item = m_model->placesItem(index); QCOMPARE(item->url(), url); QCOMPARE(item->isHidden(), expectedIsHidden); QCOMPARE(item->isSystemItem(), expectedIsSystemItem); QCOMPARE(item->group(), expectedGroup); QCOMPARE(item->storageSetupNeeded(), expectedStorageSetupNeeded); } void PlacesItemModelTest::testDeletePlace() { const QUrl tempUrl = QUrl::fromLocalFile(QStandardPaths::writableLocation(QStandardPaths::TempLocation)); QStringList urls = initialUrls(); QSignalSpy itemsInsertedSpy(m_model, &PlacesItemModel::itemsInserted); QSignalSpy itemsRemovedSpy(m_model, &PlacesItemModel::itemsRemoved); PlacesItemModel *model = new PlacesItemModel(); // create a new place createPlaceItem(QStringLiteral("Temporary Dir"), tempUrl, QString()); urls.insert(3, tempUrl.toLocalFile()); // check if the new entry was created QTRY_COMPARE(itemsInsertedSpy.count(), 1); CHECK_PLACES_URLS(urls); QTRY_COMPARE(model->count(), m_model->count()); // delete item m_model->deleteItem(3); // make sure that the new item is removed QTRY_COMPARE(itemsRemovedSpy.count(), 1); QTRY_COMPARE(m_model->count(), 17); CHECK_PLACES_URLS(initialUrls()); QTRY_COMPARE(model->count(), m_model->count()); } void PlacesItemModelTest::testTearDownDevice() { const QUrl mediaUrl = QUrl::fromLocalFile(QStringLiteral("/media/XO-Y4")); int index = indexOf(mediaUrl); QVERIFY(index != -1); auto ejectAction = m_model->ejectAction(index); QVERIFY(!ejectAction); auto teardownAction = m_model->teardownAction(index); QVERIFY(teardownAction); QCOMPARE(m_model->count(), 17); QSignalSpy spyItemsRemoved(m_model, &PlacesItemModel::itemsRemoved); fakeManager()->call(QStringLiteral("unplug"), "/org/kde/solid/fakehw/volume_part1_size_993284096"); QTRY_COMPARE(m_model->count(), 16); QCOMPARE(spyItemsRemoved.count(), 1); const QList spyItemsRemovedArgs = spyItemsRemoved.takeFirst(); const KItemRangeList removedRange = spyItemsRemovedArgs.at(0).value(); QCOMPARE(removedRange.size(), 1); QCOMPARE(removedRange.first().index, index); QCOMPARE(removedRange.first().count, 1); QCOMPARE(indexOf(mediaUrl), -1); QSignalSpy spyItemsInserted(m_model, &PlacesItemModel::itemsInserted); fakeManager()->call(QStringLiteral("plug"), "/org/kde/solid/fakehw/volume_part1_size_993284096"); QTRY_COMPARE(m_model->count(), 17); QCOMPARE(spyItemsInserted.count(), 1); index = indexOf(mediaUrl); const QList args = spyItemsInserted.takeFirst(); const KItemRangeList insertedRange = args.at(0).value(); QCOMPARE(insertedRange.size(), 1); QCOMPARE(insertedRange.first().index, index); QCOMPARE(insertedRange.first().count, 1); } void PlacesItemModelTest::testDefaultViewProperties_data() { QTest::addColumn("url"); QTest::addColumn("expectedViewMode"); QTest::addColumn("expectedPreviewShow"); QTest::addColumn >("expectedVisibleRole"); // places QTest::newRow("Places - Home") << QUrl::fromLocalFile(QDir::homePath()) << DolphinView::IconsView << true << QList({"text"}); // baloo -search QTest::newRow("Baloo - Documents") << QUrl("search:/documents") << DolphinView::DetailsView << false << QList({"text", "path"}); // audio files QTest::newRow("Places - Audio") << QUrl("search:/audio") << DolphinView::DetailsView << false << QList({"text", "artist", "album"}); // baloo - timeline QTest::newRow("Baloo - Last Month") << QUrl("timeline:/lastmonth") << DolphinView::DetailsView << true << QList({"text", "modificationtime"}); // devices QTest::newRow("Devices - Floppy") << QUrl("file:///media/floppy0") << DolphinView::IconsView << true << QList({"text"}); } void PlacesItemModelTest::testDefaultViewProperties() { QFETCH(QUrl, url); QFETCH(DolphinView::Mode, expectedViewMode); QFETCH(bool, expectedPreviewShow); QFETCH(QList, expectedVisibleRole); ViewProperties properties(KFilePlacesModel::convertedUrl(url)); QCOMPARE(properties.viewMode(), expectedViewMode); QCOMPARE(properties.previewsShown(), expectedPreviewShow); QCOMPARE(properties.visibleRoles(), expectedVisibleRole); } void PlacesItemModelTest::testClear() { QCOMPARE(m_model->count(), 17); m_model->clear(); QCOMPARE(m_model->count(), 0); QCOMPARE(m_model->hiddenCount(), 0); m_model->refresh(); QTRY_COMPARE(m_model->count(), 17); } void PlacesItemModelTest::testHideItem() { const QUrl mediaUrl = QUrl::fromLocalFile(QStringLiteral("/media/XO-Y4")); const int index = indexOf(mediaUrl); PlacesItem *item = m_model->placesItem(index); QSignalSpy spyItemsRemoved(m_model, &PlacesItemModel::itemsRemoved); QList spyItemsRemovedArgs; KItemRangeList removedRange; QSignalSpy spyItemsInserted(m_model, &PlacesItemModel::itemsInserted); QList spyItemsInsertedArgs; KItemRangeList insertedRange; QVERIFY(item); // hide an item item->setHidden(true); // check if items removed was fired QTRY_COMPARE(m_model->count(), 16); QCOMPARE(spyItemsRemoved.count(), 1); spyItemsRemovedArgs = spyItemsRemoved.takeFirst(); removedRange = spyItemsRemovedArgs.at(0).value(); QCOMPARE(removedRange.size(), 1); QCOMPARE(removedRange.first().index, index); QCOMPARE(removedRange.first().count, 1); // allow model to show hidden items m_model->setHiddenItemsShown(true); // check if the items inserted was fired spyItemsInsertedArgs = spyItemsInserted.takeFirst(); insertedRange = spyItemsInsertedArgs.at(0).value(); QCOMPARE(insertedRange.size(), 1); QCOMPARE(insertedRange.first().index, index); QCOMPARE(insertedRange.first().count, 1); // mark item as visible item = m_model->placesItem(index); item->setHidden(false); // mark model to hide invisible items m_model->setHiddenItemsShown(true); QTRY_COMPARE(m_model->count(), 17); } void PlacesItemModelTest::testSystemItems() { QCOMPARE(m_model->count(), 17); for (int r = 0; r < m_model->count(); r++) { QCOMPARE(m_model->placesItem(r)->isSystemItem(), !m_model->placesItem(r)->device().isValid()); } QSignalSpy itemsInsertedSpy(m_model, &PlacesItemModel::itemsInserted); // create a new entry (non system item) createPlaceItem(QStringLiteral("Temporary Dir"), QUrl::fromLocalFile(QStandardPaths::writableLocation(QStandardPaths::TempLocation)), QString()); // check if the new entry was created QTRY_COMPARE(itemsInsertedSpy.count(), 1); // make sure the new place get removed removePlaceAfter(3); QList args = itemsInsertedSpy.takeFirst(); KItemRangeList range = args.at(0).value(); QCOMPARE(range.first().index, 3); QCOMPARE(range.first().count, 1); QVERIFY(!m_model->placesItem(3)->isSystemItem()); QCOMPARE(m_model->count(), 18); QTest::qWait(300); // check if the removal signal is correct QSignalSpy itemsRemovedSpy(m_model, &PlacesItemModel::itemsRemoved); m_model->deleteItem(3); QTRY_COMPARE(itemsRemovedSpy.count(), 1); args = itemsRemovedSpy.takeFirst(); range = args.at(0).value(); QCOMPARE(range.first().index, 3); QCOMPARE(range.first().count, 1); QTRY_COMPARE(m_model->count(), 17); //cancel removal (it was removed above) cancelPlaceRemoval(3); } void PlacesItemModelTest::testEditBookmark() { - const QUrl tempUrl = QUrl::fromLocalFile(QStandardPaths::writableLocation(QStandardPaths::TempLocation)); QScopedPointer other(new PlacesItemModel()); createPlaceItem(QStringLiteral("Temporary Dir"), QUrl::fromLocalFile(QStandardPaths::writableLocation(QStandardPaths::TempLocation)), QString()); // make sure that the new item will be removed later removePlaceAfter(3); QSignalSpy itemsChangedSply(m_model, &PlacesItemModel::itemsChanged); // modify place text m_model->item(3)->setText(QStringLiteral("Renamed place")); m_model->refresh(); // check if the correct signal was fired QTRY_COMPARE(itemsChangedSply.count(), 1); QList args = itemsChangedSply.takeFirst(); KItemRangeList range = args.at(0).value(); QCOMPARE(range.first().index, 3); QCOMPARE(range.first().count, 1); QSet roles = args.at(1).value >(); QCOMPARE(roles.size(), 1); QCOMPARE(*roles.begin(), QByteArrayLiteral("text")); QCOMPARE(m_model->item(3)->text(), QStringLiteral("Renamed place")); // check if the item was updated in the other model QTRY_COMPARE(other->item(3)->text(), QStringLiteral("Renamed place")); } void PlacesItemModelTest::testEditAfterCreation() { const QUrl tempUrl = QUrl::fromLocalFile(QStandardPaths::writableLocation(QStandardPaths::TempLocation)); QSignalSpy itemsInsertedSpy(m_model, &PlacesItemModel::itemsInserted); // create a new place createPlaceItem(QStringLiteral("Temporary Dir"), tempUrl, QString()); QTRY_COMPARE(itemsInsertedSpy.count(), 1); PlacesItemModel *model = new PlacesItemModel(); QTRY_COMPARE(model->count(), m_model->count()); // make sure that the new item will be removed later removePlaceAfter(3); // modify place text PlacesItem *item = m_model->placesItem(3); item->setText(QStringLiteral("Renamed place")); m_model->refresh(); // check if the second model got the changes QTRY_COMPARE(model->count(), m_model->count()); QTRY_COMPARE(model->placesItem(3)->text(), m_model->placesItem(3)->text()); QTRY_COMPARE(model->placesItem(3)->bookmark().metaDataItem(QStringLiteral("OnlyInApp")), m_model->placesItem(3)->bookmark().metaDataItem(QStringLiteral("OnlyInApp"))); QTRY_COMPARE(model->placesItem(3)->icon(), m_model->placesItem(3)->icon()); QTRY_COMPARE(model->placesItem(3)->url(), m_model->placesItem(3)->url()); } void PlacesItemModelTest::testEditMetadata() { const QUrl tempUrl = QUrl::fromLocalFile(QStandardPaths::writableLocation(QStandardPaths::TempLocation)); QSignalSpy itemsInsertedSpy(m_model, &PlacesItemModel::itemsInserted); // create a new place createPlaceItem(QStringLiteral("Temporary Dir"), tempUrl, QString()); QTRY_COMPARE(itemsInsertedSpy.count(), 1); // check if the new entry was created PlacesItemModel *model = new PlacesItemModel(); QTRY_COMPARE(model->count(), m_model->count()); // make sure that the new item will be removed later removePlaceAfter(3); // modify place metadata PlacesItem *item = m_model->placesItem(3); item->bookmark().setMetaDataItem(QStringLiteral("OnlyInApp"), KAboutData::applicationData().componentName()); m_model->refresh(); // check if the place was modified in both models QTRY_COMPARE(model->placesItem(3)->bookmark().metaDataItem(QStringLiteral("OnlyInApp")), KAboutData::applicationData().componentName()); QTRY_COMPARE(model->placesItem(3)->text(), m_model->placesItem(3)->text()); QTRY_COMPARE(model->placesItem(3)->bookmark().metaDataItem(QStringLiteral("OnlyInApp")), m_model->placesItem(3)->bookmark().metaDataItem(QStringLiteral("OnlyInApp"))); QTRY_COMPARE(model->placesItem(3)->icon(), m_model->placesItem(3)->icon()); QTRY_COMPARE(model->placesItem(3)->url(), m_model->placesItem(3)->url()); } void PlacesItemModelTest::testRefresh() { const QUrl tempUrl = QUrl::fromLocalFile(QStandardPaths::writableLocation(QStandardPaths::TempLocation)); QSignalSpy itemsInsertedSpy(m_model, &PlacesItemModel::itemsInserted); // create a new place createPlaceItem(QStringLiteral("Temporary Dir"), tempUrl, QString()); QTRY_COMPARE(itemsInsertedSpy.count(), 1); PlacesItemModel *model = new PlacesItemModel(); QTRY_COMPARE(model->count(), m_model->count()); // make sure that the new item will be removed later removePlaceAfter(3); PlacesItem *item = m_model->placesItem(3); PlacesItem *sameItem = model->placesItem(3); QCOMPARE(item->text(), sameItem->text()); // modify place text item->setText(QStringLiteral("Renamed place")); // item from another model is not affected at the moment QVERIFY(item->text() != sameItem->text()); // propagate change m_model->refresh(); // item must be equal QTRY_COMPARE(item->text(), sameItem->text()); } void PlacesItemModelTest::testIcons_data() { QTest::addColumn("url"); QTest::addColumn("expectedIconName"); // places QTest::newRow("Places - Home") << QUrl::fromLocalFile(QDir::homePath()) << QStringLiteral("user-home"); // baloo -search QTest::newRow("Baloo - Documents") << QUrl("search:/documents") << QStringLiteral("folder-text"); // baloo - timeline QTest::newRow("Baloo - Last Month") << QUrl("timeline:/lastmonth") << QStringLiteral("view-calendar-month"); // devices QTest::newRow("Devices - Floppy") << QUrl("file:///media/floppy0") << QStringLiteral("blockdevice"); } void PlacesItemModelTest::testIcons() { QFETCH(QUrl, url); QFETCH(QString, expectedIconName); PlacesItem *item = m_model->placesItem(indexOf(url)); QCOMPARE(item->icon(), expectedIconName); for (int r = 0; r < m_model->count(); r++) { QVERIFY(!m_model->placesItem(r)->icon().isEmpty()); } } void PlacesItemModelTest::testDragAndDrop() { QList args; KItemRangeList range; QStringList urls = initialUrls(); QSignalSpy itemsInsertedSpy(m_model, &PlacesItemModel::itemsInserted); QSignalSpy itemsRemovedSpy(m_model, &PlacesItemModel::itemsRemoved); CHECK_PLACES_URLS(initialUrls()); // Move the KDE_ROOT_PATH at the end of the places list will case it to be moved to the end of the places group QMimeData *dropData = createMimeData(QList() << 1); m_model->dropMimeDataBefore(m_model->count() - 1, dropData); urls.move(1, 2); delete dropData; QTRY_COMPARE(itemsInsertedSpy.count(), 1); QTRY_COMPARE(itemsRemovedSpy.count(), 1); // remove item from actual position args = itemsRemovedSpy.takeFirst(); range = args.at(0).value(); QCOMPARE(range.size(), 1); QCOMPARE(range.at(0).count, 1); QCOMPARE(range.at(0).index, 1); // insert intem in his group args = itemsInsertedSpy.takeFirst(); range = args.at(0).value(); QCOMPARE(range.size(), 1); QCOMPARE(range.at(0).count, 1); QCOMPARE(range.at(0).index, 2); CHECK_PLACES_URLS(urls); itemsInsertedSpy.clear(); itemsRemovedSpy.clear(); // Move the KDE_ROOT_PATH to his original position dropData = createMimeData(QList() << 2); m_model->dropMimeDataBefore(1, dropData); urls.move(2, 1); delete dropData; QTRY_COMPARE(itemsInsertedSpy.count(), 1); QTRY_COMPARE(itemsRemovedSpy.count(), 1); // remove item from actual position args = itemsRemovedSpy.takeFirst(); range = args.at(0).value(); QCOMPARE(range.size(), 1); QCOMPARE(range.at(0).count, 1); QCOMPARE(range.at(0).index, 2); // insert intem in the requested position args = itemsInsertedSpy.takeFirst(); range = args.at(0).value(); QCOMPARE(range.size(), 1); QCOMPARE(range.at(0).count, 1); QCOMPARE(range.at(0).index, 1); CHECK_PLACES_URLS(urls); } void PlacesItemModelTest::testHideDevices() { QSignalSpy itemsRemoved(m_model, &PlacesItemModel::itemsRemoved); QStringList urls = initialUrls(); m_model->setGroupHidden(KFilePlacesModel::RemovableDevicesType, true); QTRY_VERIFY(m_model->isGroupHidden(KFilePlacesModel::RemovableDevicesType)); QTRY_COMPARE(itemsRemoved.count(), 3); // remove removable-devices urls.removeOne(QStringLiteral("/media/floppy0")); urls.removeOne(QStringLiteral("/media/XO-Y4")); urls.removeOne(QStringLiteral("/media/cdrom")); // check if the correct urls was removed CHECK_PLACES_URLS(urls); delete m_model; m_model = new PlacesItemModel(); QTRY_COMPARE(m_model->count(), urls.count()); CHECK_PLACES_URLS(urls); // revert changes m_model->setGroupHidden(KFilePlacesModel::RemovableDevicesType, false); urls = initialUrls(); QTRY_COMPARE(m_model->count(), urls.count()); CHECK_PLACES_URLS(urls); } void PlacesItemModelTest::testDuplicatedEntries() { QStringList urls = initialUrls(); // create a duplicated entry on bookmark KBookmarkManager *bookmarkManager = KBookmarkManager::managerForFile(bookmarksFile(), QStringLiteral("kfilePlaces")); KBookmarkGroup root = bookmarkManager->root(); KBookmark bookmark = root.addBookmark(QStringLiteral("Duplicated Search Videos"), QUrl("search:/videos"), {}); const QString id = QUuid::createUuid().toString(); bookmark.setMetaDataItem(QStringLiteral("ID"), id); bookmark.setMetaDataItem(QStringLiteral("OnlyInApp"), KAboutData::applicationData().componentName()); bookmarkManager->emitChanged(bookmarkManager->root()); PlacesItemModel *newModel = new PlacesItemModel(); QTRY_COMPARE(placesUrls(newModel).count(QStringLiteral("search:/videos")), 1); QTRY_COMPARE(urls, placesUrls(newModel)); delete newModel; } void PlacesItemModelTest::renameAfterCreation() { const QUrl tempUrl = QUrl::fromLocalFile(QStandardPaths::writableLocation(QStandardPaths::TempLocation)); QStringList urls = initialUrls(); PlacesItemModel *model = new PlacesItemModel(); CHECK_PLACES_URLS(urls); QTRY_COMPARE(model->count(), m_model->count()); // create a new place createPlaceItem(QStringLiteral("Temporary Dir"), tempUrl, QString()); urls.insert(3, tempUrl.toLocalFile()); // make sure that the new item will be removed later removePlaceAfter(3); CHECK_PLACES_URLS(urls); QCOMPARE(model->count(), m_model->count()); // modify place text QSignalSpy changedSpy(m_model, &PlacesItemModel::itemsChanged); PlacesItem *item = m_model->placesItem(3); item->setText(QStringLiteral("New Temporary Dir")); item->setUrl(item->url()); item->setIcon(item->icon()); m_model->refresh(); QTRY_COMPARE(changedSpy.count(), 1); // check if the place was modified in both models QTRY_COMPARE(m_model->placesItem(3)->text(), QStringLiteral("New Temporary Dir")); QTRY_COMPARE(model->placesItem(3)->text(), QStringLiteral("New Temporary Dir")); } QTEST_MAIN(PlacesItemModelTest) #include "placesitemmodeltest.moc"