diff --git a/CMakeLists.txt b/CMakeLists.txt index 2f26ae85f9..f58fab1fa7 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -8,7 +8,7 @@ set (RELEASE_SERVICE_VERSION "${RELEASE_SERVICE_VERSION_MAJOR}.${RELEASE_SERVICE project(Dolphin VERSION ${RELEASE_SERVICE_VERSION}) set(QT_MIN_VERSION "5.15.0") -set(KF5_MIN_VERSION "5.90.0") +set(KF5_MIN_VERSION "5.91.0") # ECM setup find_package(ECM ${KF5_MIN_VERSION} CONFIG REQUIRED) diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 651f021e64..a3a15aaf3b 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -223,12 +223,6 @@ target_sources(dolphinstatic PRIVATE trash/dolphintrash.cpp filterbar/filterbar.cpp panels/places/placespanel.cpp - panels/places/placesitem.cpp - panels/places/placesitemlistgroupheader.cpp - panels/places/placesitemlistwidget.cpp - panels/places/placesitemmodel.cpp - panels/places/placesitemsignalhandler.cpp - panels/places/placesview.cpp panels/panel.cpp panels/folders/foldersitemlistwidget.cpp panels/folders/treeviewcontextmenu.cpp diff --git a/src/dolphincontextmenu.cpp b/src/dolphincontextmenu.cpp index 028fd98bd2..340af6bd02 100644 --- a/src/dolphincontextmenu.cpp +++ b/src/dolphincontextmenu.cpp @@ -13,8 +13,6 @@ #include "dolphinplacesmodelsingleton.h" #include "dolphinremoveaction.h" #include "dolphinviewcontainer.h" -#include "panels/places/placesitem.h" -#include "panels/places/placesitemmodel.h" #include "trash/dolphintrash.h" #include "views/dolphinview.h" #include "views/viewmodecontroller.h" diff --git a/src/dolphinmainwindow.cpp b/src/dolphinmainwindow.cpp index c03095c3c0..644989be02 100644 --- a/src/dolphinmainwindow.cpp +++ b/src/dolphinmainwindow.cpp @@ -17,12 +17,12 @@ #include "dolphinnavigatorswidgetaction.h" #include "dolphinnewfilemenu.h" #include "dolphinrecenttabsmenu.h" +#include "dolphinplacesmodelsingleton.h" #include "dolphinurlnavigatorscontroller.h" #include "dolphinviewcontainer.h" #include "dolphintabpage.h" #include "middleclickactioneventfilter.h" #include "panels/folders/folderspanel.h" -#include "panels/places/placesitemmodel.h" #include "panels/places/placespanel.h" #include "panels/terminal/terminalpanel.h" #include "settings/dolphinsettingsdialog.h" @@ -429,14 +429,13 @@ void DolphinMainWindow::addToPlaces() name = dirToAdd.name(); } if (url.isValid()) { - PlacesItemModel model; QString icon; if (m_activeViewContainer->isSearchModeEnabled()) { icon = QStringLiteral("folder-saved-search-symbolic"); } else { icon = KIO::iconNameForUrl(url); } - model.createPlacesItem(name, url, icon); + DolphinPlacesModelSingleton::instance().placesModel()->addPlace(name, url, icon); } } @@ -1954,10 +1953,13 @@ void DolphinMainWindow::setupDockWidgets() addDockWidget(Qt::LeftDockWidgetArea, placesDock); connect(m_placesPanel, &PlacesPanel::placeActivated, this, &DolphinMainWindow::slotPlaceActivated); - connect(m_placesPanel, &PlacesPanel::placeActivatedInNewTab, + connect(m_placesPanel, &PlacesPanel::tabRequested, this, &DolphinMainWindow::openNewTab); - connect(m_placesPanel, &PlacesPanel::placeActivatedInNewActiveTab, + connect(m_placesPanel, &PlacesPanel::activeTabRequested, this, &DolphinMainWindow::openNewTabAndActivate); + connect(m_placesPanel, &PlacesPanel::newWindowRequested, this, [this](const QUrl &url) { + Dolphin::openNewWindow({url}, this); + }); connect(m_placesPanel, &PlacesPanel::errorMessage, this, &DolphinMainWindow::showErrorMessage); connect(this, &DolphinMainWindow::urlChanged, @@ -1980,14 +1982,9 @@ void DolphinMainWindow::setupDockWidgets() "appear semi-transparent unless you uncheck their hide property.")); connect(actionShowAllPlaces, &QAction::triggered, this, [actionShowAllPlaces, this](bool checked){ - actionShowAllPlaces->setIcon(QIcon::fromTheme(checked ? QStringLiteral("view-visible") : QStringLiteral("view-hidden"))); - m_placesPanel->showHiddenEntries(checked); + m_placesPanel->setShowAll(checked); }); - - connect(m_placesPanel, &PlacesPanel::showHiddenEntriesChanged, this, [actionShowAllPlaces] (bool checked){ - actionShowAllPlaces->setChecked(checked); - actionShowAllPlaces->setIcon(QIcon::fromTheme(checked ? QStringLiteral("view-visible") : QStringLiteral("view-hidden"))); - }); + connect(m_placesPanel, &PlacesPanel::allPlacesShownChanged, actionShowAllPlaces, &QAction::setChecked); actionCollection()->action(QStringLiteral("show_places_panel")) ->setWhatsThis(xi18nc("@info:whatsthis", "This toggles the " @@ -2025,7 +2022,7 @@ void DolphinMainWindow::setupDockWidgets() panelsMenu->addAction(lockLayoutAction); connect(panelsMenu->menu(), &QMenu::aboutToShow, this, [actionShowAllPlaces, this]{ - actionShowAllPlaces->setEnabled(m_placesPanel->hiddenListCount()); + actionShowAllPlaces->setEnabled(DolphinPlacesModelSingleton::instance().placesModel()->hiddenCount()); }); } diff --git a/src/dolphinplacesmodelsingleton.cpp b/src/dolphinplacesmodelsingleton.cpp index 30ec1b9b63..754070c929 100644 --- a/src/dolphinplacesmodelsingleton.cpp +++ b/src/dolphinplacesmodelsingleton.cpp @@ -5,12 +5,61 @@ */ #include "dolphinplacesmodelsingleton.h" +#include "trash/dolphintrash.h" #include #include +#include + +DolphinPlacesModel::DolphinPlacesModel(const QString &alternativeApplicationName, QObject *parent) + : KFilePlacesModel(alternativeApplicationName, parent) +{ + connect(&Trash::instance(), &Trash::emptinessChanged, this, &DolphinPlacesModel::slotTrashEmptinessChanged); +} + +DolphinPlacesModel::~DolphinPlacesModel() = default; + +QVariant DolphinPlacesModel::data(const QModelIndex &index, int role) const +{ + if (role == Qt::DecorationRole) { + if (isTrash(index)) { + if (m_isEmpty) { + return QIcon::fromTheme(QStringLiteral("user-trash")); + } else { + return QIcon::fromTheme(QStringLiteral("user-trash-full")); + } + } + } + + return KFilePlacesModel::data(index, role); +} + +void DolphinPlacesModel::slotTrashEmptinessChanged(bool isEmpty) +{ + if (m_isEmpty == isEmpty) { + return; + } + + // NOTE Trash::isEmpty() reads the config file whereas emptinessChanged is + // hooked up to whether a dirlister in trash:/ has any files and they disagree... + m_isEmpty = isEmpty; + + for (int i = 0; i < rowCount(); ++i) { + const QModelIndex index = this->index(i, 0); + if (isTrash(index)) { + Q_EMIT dataChanged(index, index, {Qt::DecorationRole}); + } + } +} + +bool DolphinPlacesModel::isTrash(const QModelIndex &index) const +{ + return url(index) == QUrl(QStringLiteral("trash:/")); +} + DolphinPlacesModelSingleton::DolphinPlacesModelSingleton() - : m_placesModel(new KFilePlacesModel(KAboutData::applicationData().componentName() + applicationNameSuffix())) + : m_placesModel(new DolphinPlacesModel(KAboutData::applicationData().componentName() + applicationNameSuffix())) { } diff --git a/src/dolphinplacesmodelsingleton.h b/src/dolphinplacesmodelsingleton.h index 645947aaa0..7efe6e093c 100644 --- a/src/dolphinplacesmodelsingleton.h +++ b/src/dolphinplacesmodelsingleton.h @@ -10,7 +10,33 @@ #include #include -class KFilePlacesModel; +#include + +/** + * @brief Dolphin's special-cased KFilePlacesModel + * + * It returns the trash's icon based on whether + * it is full or not. + */ +class DolphinPlacesModel : public KFilePlacesModel +{ + Q_OBJECT + +public: + explicit DolphinPlacesModel(const QString &alternativeApplicationName, QObject *parent = nullptr); + ~DolphinPlacesModel() override; + +protected: + QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; + +private Q_SLOTS: + void slotTrashEmptinessChanged(bool isEmpty); + +private: + bool isTrash(const QModelIndex &index) const; + + bool m_isEmpty = false; +}; /** * @brief Provides a global KFilePlacesModel instance. diff --git a/src/panels/places/dolphin_placespanelsettings.kcfg b/src/panels/places/dolphin_placespanelsettings.kcfg index db0ac9495b..58f77a9597 100644 --- a/src/panels/places/dolphin_placespanelsettings.kcfg +++ b/src/panels/places/dolphin_placespanelsettings.kcfg @@ -8,7 +8,7 @@ - + KIconLoader::SizeSmallMedium diff --git a/src/panels/places/placesitem.cpp b/src/panels/places/placesitem.cpp deleted file mode 100644 index 18f3f006e3..0000000000 --- a/src/panels/places/placesitem.cpp +++ /dev/null @@ -1,274 +0,0 @@ -/* - * SPDX-FileCopyrightText: 2012 Peter Penz - * SPDX-FileCopyrightText: 2018 Elvis Angelaccio - * - * Based on KFilePlacesItem from kdelibs: - * SPDX-FileCopyrightText: 2007 Kevin Ottens - * - * SPDX-License-Identifier: GPL-2.0-or-later - */ - -#include "placesitem.h" -#include "trash/dolphintrash.h" - -#include "dolphindebug.h" -#include "placesitemsignalhandler.h" - -#include -#include -#include - -PlacesItem::PlacesItem(const KBookmark& bookmark, PlacesItem* parent) : - KStandardItem(parent), - m_device(), - m_access(), - m_volume(), - m_disc(), - m_player(), - m_signalHandler(nullptr), - m_bookmark() -{ - m_signalHandler = new PlacesItemSignalHandler(this); - setBookmark(bookmark); -} - -PlacesItem::~PlacesItem() -{ - delete m_signalHandler; -} - -void PlacesItem::setUrl(const QUrl &url) -{ - // The default check in KStandardItem::setDataValue() - // for equal values does not work with a custom value - // like QUrl. Hence do a manual check to prevent that - // setting an equal URL results in an itemsChanged() - // signal. - if (dataValue("url").toUrl() != url) { - if (url.scheme() == QLatin1String("trash")) { - QObject::connect(&Trash::instance(), &Trash::emptinessChanged, m_signalHandler.data(), &PlacesItemSignalHandler::onTrashEmptinessChanged); - } - - setDataValue("url", url); - } -} - -QUrl PlacesItem::url() const -{ - return dataValue("url").toUrl(); -} - -void PlacesItem::setUdi(const QString& udi) -{ - setDataValue("udi", udi); -} - -QString PlacesItem::udi() const -{ - return dataValue("udi").toString(); -} - -void PlacesItem::setApplicationName(const QString &applicationName) -{ - setDataValue("applicationName", applicationName); -} - -QString PlacesItem::applicationName() const -{ - return dataValue("applicationName").toString(); -} - -void PlacesItem::setHidden(bool hidden) -{ - setDataValue("isHidden", hidden); -} - -bool PlacesItem::isHidden() const -{ - return dataValue("isHidden").toBool(); -} - -bool PlacesItem::isGroupHidden() const -{ - return dataValue("isGroupHidden").toBool(); -} - -void PlacesItem::setGroupHidden(bool hidden) -{ - setDataValue("isGroupHidden", hidden); -} - -void PlacesItem::setSystemItem(bool isSystemItem) -{ - setDataValue("isSystemItem", isSystemItem); -} - -bool PlacesItem::isSystemItem() const -{ - return dataValue("isSystemItem").toBool(); -} - -Solid::Device PlacesItem::device() const -{ - return m_device; -} - -void PlacesItem::setBookmark(const KBookmark& bookmark) -{ - const bool bookmarkDataChanged = !(bookmark == m_bookmark); - - // bookmark object must be updated to keep in sync with source model - m_bookmark = bookmark; - - if (!bookmarkDataChanged) { - return; - } - - delete m_access; - delete m_volume; - delete m_disc; - delete m_player; - - const QString udi = bookmark.metaDataItem(QStringLiteral("UDI")); - if (udi.isEmpty()) { - setIcon(bookmark.icon()); - setText(i18ndc("kio5", "KFile System Bookmarks", bookmark.text().toUtf8().constData())); - setUrl(bookmark.url()); - setSystemItem(bookmark.metaDataItem(QStringLiteral("isSystemItem")) == QLatin1String("true")); - } else { - initializeDevice(udi); - } - - setHidden(bookmark.metaDataItem(QStringLiteral("IsHidden")) == QLatin1String("true")); -} - -KBookmark PlacesItem::bookmark() const -{ - return m_bookmark; -} - -bool PlacesItem::storageSetupNeeded() const -{ - return m_access ? !m_access->isAccessible() : false; -} - -bool PlacesItem::isSearchOrTimelineUrl() const -{ - const QString urlScheme = url().scheme(); - return (urlScheme.contains("search") || urlScheme.contains("timeline")); -} - -void PlacesItem::onDataValueChanged(const QByteArray& role, - const QVariant& current, - const QVariant& previous) -{ - Q_UNUSED(current) - Q_UNUSED(previous) - - if (!m_bookmark.isNull()) { - updateBookmarkForRole(role); - } -} - -void PlacesItem::onDataChanged(const QHash& current, - const QHash& previous) -{ - Q_UNUSED(previous) - - if (!m_bookmark.isNull()) { - QHashIterator it(current); - while (it.hasNext()) { - it.next(); - updateBookmarkForRole(it.key()); - } - } -} - -void PlacesItem::initializeDevice(const QString& udi) -{ - m_device = Solid::Device(udi); - if (!m_device.isValid()) { - return; - } - - m_access = m_device.as(); - m_volume = m_device.as(); - m_disc = m_device.as(); - m_player = m_device.as(); - - setText(m_device.displayName()); - setIcon(m_device.icon()); - setIconOverlays(m_device.emblems()); - setUdi(udi); - - if (m_access) { - setUrl(QUrl::fromLocalFile(m_access->filePath())); - QObject::connect(m_access.data(), &Solid::StorageAccess::accessibilityChanged, - m_signalHandler.data(), &PlacesItemSignalHandler::onAccessibilityChanged); - QObject::connect(m_access.data(), &Solid::StorageAccess::teardownRequested, - m_signalHandler.data(), &PlacesItemSignalHandler::onTearDownRequested); - } else if (m_disc && (m_disc->availableContent() & Solid::OpticalDisc::Audio) != 0) { - Solid::Block *block = m_device.as(); - if (block) { - const QString device = block->device(); - setUrl(QUrl(QStringLiteral("audiocd:/?device=%1").arg(device))); - } else { - setUrl(QUrl(QStringLiteral("audiocd:/"))); - } - } else if (m_player) { - const QStringList protocols = m_player->supportedProtocols(); - if (!protocols.isEmpty()) { - setUrl(QUrl(QStringLiteral("%1:udi=%2").arg(protocols.first(), m_device.udi()))); - } - } -} - -void PlacesItem::onAccessibilityChanged() -{ - setIconOverlays(m_device.emblems()); - setUrl(QUrl::fromLocalFile(m_access->filePath())); -} - -void PlacesItem::updateBookmarkForRole(const QByteArray& role) -{ - Q_ASSERT(!m_bookmark.isNull()); - if (role == "iconName") { - m_bookmark.setIcon(icon()); - } else if (role == "text") { - // Only store the text in the KBookmark if it is not the translation of - // the current text. This makes sure that the text is re-translated if - // the user chooses another language, or the translation itself changes. - // - // NOTE: It is important to use "KFile System Bookmarks" as context - // (see PlacesItemModel::createSystemBookmarks()). - if (text() != i18ndc("kio5", "KFile System Bookmarks", m_bookmark.text().toUtf8().data())) { - m_bookmark.setFullText(text()); - } - } else if (role == "url") { - m_bookmark.setUrl(url()); - } else if (role == "udi") { - m_bookmark.setMetaDataItem(QStringLiteral("UDI"), udi()); - } else if (role == "applicationName") { - m_bookmark.setMetaDataItem(QStringLiteral("OnlyInApp"), applicationName()); - } else if (role == "isSystemItem") { - m_bookmark.setMetaDataItem(QStringLiteral("isSystemItem"), isSystemItem() ? QStringLiteral("true") : QStringLiteral("false")); - } else if (role == "isHidden") { - m_bookmark.setMetaDataItem(QStringLiteral("IsHidden"), isHidden() ? QStringLiteral("true") : QStringLiteral("false")); - } -} - -QString PlacesItem::generateNewId() -{ - // The ID-generation must be different as done in KFilePlacesItem from kdelibs - // to prevent identical IDs, because 'count' is of course not shared. We append a - // " (V2)" to indicate that the ID has been generated by - // a new version of the places view. - static int count = 0; - return QString::number(QDateTime::currentDateTimeUtc().toSecsSinceEpoch()) + - '/' + QString::number(count++) + " (V2)"; -} - -PlacesItemSignalHandler *PlacesItem::signalHandler() const -{ - return m_signalHandler.data(); -} diff --git a/src/panels/places/placesitem.h b/src/panels/places/placesitem.h deleted file mode 100644 index 8325923026..0000000000 --- a/src/panels/places/placesitem.h +++ /dev/null @@ -1,105 +0,0 @@ -/* - * SPDX-FileCopyrightText: 2012 Peter Penz - * - * SPDX-License-Identifier: GPL-2.0-or-later - */ - -#ifndef PLACESITEM_H -#define PLACESITEM_H - -#include "kitemviews/kstandarditem.h" - -#include -#include -#include -#include -#include -#include - -#include -#include - - -class KDirLister; -class PlacesItemSignalHandler; - -/** - * @brief Extends KStandardItem by places-specific properties. - */ -class PlacesItem : public KStandardItem -{ - -public: - explicit PlacesItem(const KBookmark& bookmark, PlacesItem* parent = nullptr); - ~PlacesItem() override; - - void setUrl(const QUrl& url); - QUrl url() const; - - void setUdi(const QString& udi); - QString udi() const; - - void setApplicationName(const QString& applicationName); - QString applicationName() const; - - void setHidden(bool hidden); - bool isHidden() const; - - void setGroupHidden(bool hidden); - bool isGroupHidden() const; - - void setSystemItem(bool isSystemItem); - bool isSystemItem() const; - - Solid::Device device() const; - - void setBookmark(const KBookmark& bookmark); - KBookmark bookmark() const; - - bool storageSetupNeeded() const; - - bool isSearchOrTimelineUrl() const; - - PlacesItemSignalHandler* signalHandler() const; - -protected: - void onDataValueChanged(const QByteArray& role, - const QVariant& current, - const QVariant& previous) override; - - void onDataChanged(const QHash& current, - const QHash& previous) override; - -private: - PlacesItem(const PlacesItem& item); - - void initializeDevice(const QString& udi); - - /** - * Is invoked if the accessibility of the storage access - * m_access has been changed and updates the emblem. - */ - void onAccessibilityChanged(); - - /** - * Applies the data-value from the role to m_bookmark. - */ - void updateBookmarkForRole(const QByteArray& role); - - static QString generateNewId(); - -private: - Solid::Device m_device; - QPointer m_access; - QPointer m_volume; - QPointer m_disc; - QPointer m_player; - QPointer m_signalHandler; - KBookmark m_bookmark; - - friend class PlacesItemSignalHandler; // Calls onAccessibilityChanged() -}; - -#endif - - diff --git a/src/panels/places/placesitemlistgroupheader.cpp b/src/panels/places/placesitemlistgroupheader.cpp deleted file mode 100644 index 76fd670b31..0000000000 --- a/src/panels/places/placesitemlistgroupheader.cpp +++ /dev/null @@ -1,30 +0,0 @@ -/* - * SPDX-FileCopyrightText: 2012 Peter Penz - * - * Based on the Itemviews NG project from Trolltech Labs - * - * SPDX-License-Identifier: GPL-2.0-or-later - */ - -#include "placesitemlistgroupheader.h" - -PlacesItemListGroupHeader::PlacesItemListGroupHeader(QGraphicsWidget* parent) : - KStandardItemListGroupHeader(parent) -{ -} - -PlacesItemListGroupHeader::~PlacesItemListGroupHeader() -{ -} - -void PlacesItemListGroupHeader::paintSeparator(QPainter* painter, const QColor& color) -{ - Q_UNUSED(painter) - Q_UNUSED(color) -} - -QPalette::ColorRole PlacesItemListGroupHeader::normalTextColorRole() const -{ - return QPalette::WindowText; -} - diff --git a/src/panels/places/placesitemlistgroupheader.h b/src/panels/places/placesitemlistgroupheader.h deleted file mode 100644 index dfb91f8477..0000000000 --- a/src/panels/places/placesitemlistgroupheader.h +++ /dev/null @@ -1,27 +0,0 @@ -/* - * SPDX-FileCopyrightText: 2012 Peter Penz - * - * SPDX-License-Identifier: GPL-2.0-or-later - */ - -#ifndef PLACESITEMLISTGROUPHEADER_H -#define PLACESITEMLISTGROUPHEADER_H - -#include "kitemviews/kstandarditemlistgroupheader.h" - -class PlacesItemListGroupHeader : public KStandardItemListGroupHeader -{ - Q_OBJECT - -public: - explicit PlacesItemListGroupHeader(QGraphicsWidget* parent = nullptr); - ~PlacesItemListGroupHeader() override; - -protected: - void paintSeparator(QPainter* painter, const QColor& color) override; - - QPalette::ColorRole normalTextColorRole() const override; -}; -#endif - - diff --git a/src/panels/places/placesitemlistwidget.cpp b/src/panels/places/placesitemlistwidget.cpp deleted file mode 100644 index ba7a0c4fa0..0000000000 --- a/src/panels/places/placesitemlistwidget.cpp +++ /dev/null @@ -1,146 +0,0 @@ -/* - * SPDX-FileCopyrightText: 2012 Peter Penz - * - * SPDX-License-Identifier: GPL-2.0-or-later - */ - -#include "placesitemlistwidget.h" - -#include - -#include - -#include -#include - -#define CAPACITYBAR_HEIGHT 2 -#define CAPACITYBAR_MARGIN 2 -#define CAPACITYBAR_CACHE_TTL 60000 - - -PlacesItemListWidget::PlacesItemListWidget(KItemListWidgetInformant* informant, QGraphicsItem* parent) : - KStandardItemListWidget(informant, parent) - , m_drawCapacityBar(false) -{ -} - -PlacesItemListWidget::~PlacesItemListWidget() -{ -} - -bool PlacesItemListWidget::isHidden() const -{ - return data().value("isHidden").toBool() || - data().value("isGroupHidden").toBool(); -} - -QPalette::ColorRole PlacesItemListWidget::normalTextColorRole() const -{ - return QPalette::WindowText; -} - -void PlacesItemListWidget::updateCapacityBar() -{ - const QString udi = data().value("udi").toString(); - if (udi.isEmpty()) { - resetCapacityBar(); - return; - } - const Solid::Device device = Solid::Device(udi); - if (device.isDeviceInterface(Solid::DeviceInterface::NetworkShare) - || device.isDeviceInterface(Solid::DeviceInterface::OpticalDrive) - || device.isDeviceInterface(Solid::DeviceInterface::OpticalDisc)) { - resetCapacityBar(); - return; - } - const QUrl url = data().value("url").toUrl(); - - if (url.isEmpty() || m_freeSpaceInfo.job || !m_freeSpaceInfo.lastUpdated.hasExpired()) { - // No url, job running or cache is still valid. - return; - } - - m_freeSpaceInfo.job = KIO::fileSystemFreeSpace(url); - connect( - m_freeSpaceInfo.job, - &KIO::FileSystemFreeSpaceJob::result, - this, - [this](KIO::Job *job, KIO::filesize_t size, KIO::filesize_t available) { - // even if we receive an error we want to refresh lastUpdated to avoid repeatedly querying in this case - m_freeSpaceInfo.lastUpdated.setRemainingTime(CAPACITYBAR_CACHE_TTL); - - if (job->error()) { - return; - } - - m_freeSpaceInfo.size = size; - m_freeSpaceInfo.used = size - available; - m_freeSpaceInfo.usedRatio = (qreal)m_freeSpaceInfo.used / (qreal)m_freeSpaceInfo.size; - m_drawCapacityBar = size > 0; - - update(); - } - ); -} - -void PlacesItemListWidget::resetCapacityBar() -{ - m_drawCapacityBar = false; - delete m_freeSpaceInfo.job; - m_freeSpaceInfo.lastUpdated.setRemainingTime(0); - m_freeSpaceInfo.size = 0; - m_freeSpaceInfo.used = 0; - m_freeSpaceInfo.usedRatio = 0; -} - -void PlacesItemListWidget::polishEvent() -{ - updateCapacityBar(); - - QGraphicsWidget::polishEvent(); -} - -void PlacesItemListWidget::paint(QPainter* painter, const QStyleOptionGraphicsItem* option, QWidget* widget) -{ - KStandardItemListWidget::paint(painter, option, widget); - - // We check if option=nullptr since it is null when the place is dragged (Bug #430441) - if (m_drawCapacityBar && option) { - const TextInfo* textInfo = m_textInfo.value("text"); - if (textInfo) { // See KStandarItemListWidget::paint() for info on why we check textInfo. - painter->save(); - - const QRect capacityRect( - textInfo->pos.x(), - option->rect.top() + option->rect.height() - CAPACITYBAR_HEIGHT - CAPACITYBAR_MARGIN, - qMin((qreal)option->rect.width(), selectionRect().width()) - (textInfo->pos.x() - option->rect.left()), - CAPACITYBAR_HEIGHT - ); - - const QPalette pal = palette(); - const QPalette::ColorGroup group = isActiveWindow() ? QPalette::Active : QPalette::Inactive; - - // Background - const QColor bgColor = isSelected() - ? pal.color(group, QPalette::Highlight).darker(180) - : pal.color(group, QPalette::Window).darker(120); - - painter->fillRect(capacityRect, bgColor); - - // Fill - const QRect fillRect(capacityRect.x(), capacityRect.y(), capacityRect.width() * m_freeSpaceInfo.usedRatio, capacityRect.height()); - if (m_freeSpaceInfo.usedRatio >= 0.95) { // More than 95% full! - const QColor dangerUsedColor = KColorScheme(group, KColorScheme::View).foreground(KColorScheme::NegativeText).color(); - painter->fillRect(fillRect, dangerUsedColor); - } else { - const QPalette::ColorRole role = isSelected() ? QPalette::HighlightedText : QPalette::Highlight; - const QColor normalUsedColor = styleOption().palette.color(group, role); - painter->fillRect(fillRect, normalUsedColor); - } - - painter->restore(); - } - } - - updateCapacityBar(); -} diff --git a/src/panels/places/placesitemlistwidget.h b/src/panels/places/placesitemlistwidget.h deleted file mode 100644 index 9c8272fb01..0000000000 --- a/src/panels/places/placesitemlistwidget.h +++ /dev/null @@ -1,61 +0,0 @@ -/* - * SPDX-FileCopyrightText: 2012 Peter Penz - * - * SPDX-License-Identifier: GPL-2.0-or-later - */ - -#ifndef PLACESITEMLISTWIDGET_H -#define PLACESITEMLISTWIDGET_H - -#include "kitemviews/kstandarditemlistwidget.h" - -#include -#include -#include -#include -#include - -#include - - -// The free space / capacity bar is based on KFilePlacesView. -// https://invent.kde.org/frameworks/kio/-/commit/933887dc334f3498505af7a86d25db7faae91019 -struct PlaceFreeSpaceInfo -{ - QDeadlineTimer lastUpdated; - KIO::filesize_t used = 0; - KIO::filesize_t size = 0; - qreal usedRatio = 0; - QPointer job; -}; - - -/** - * @brief Extends KStandardItemListWidget to interpret the hidden - * property of the PlacesModel and use the right text color. -*/ -class PlacesItemListWidget : public KStandardItemListWidget -{ - Q_OBJECT - -public: - PlacesItemListWidget(KItemListWidgetInformant* informant, QGraphicsItem* parent); - ~PlacesItemListWidget() override; - - void paint(QPainter* painter, const QStyleOptionGraphicsItem* option, QWidget* widget = nullptr) override; - void polishEvent() override; - -protected: - bool isHidden() const override; - QPalette::ColorRole normalTextColorRole() const override; - void updateCapacityBar(); - void resetCapacityBar(); - -private: - bool m_drawCapacityBar; - PlaceFreeSpaceInfo m_freeSpaceInfo; -}; - -#endif - - diff --git a/src/panels/places/placesitemmodel.cpp b/src/panels/places/placesitemmodel.cpp deleted file mode 100644 index 3da6f7e1f8..0000000000 --- a/src/panels/places/placesitemmodel.cpp +++ /dev/null @@ -1,784 +0,0 @@ -/* - * SPDX-FileCopyrightText: 2012 Peter Penz - * - * Based on KFilePlacesModel from kdelibs: - * SPDX-FileCopyrightText: 2007 Kevin Ottens - * SPDX-FileCopyrightText: 2007 David Faure - * - * SPDX-License-Identifier: GPL-2.0-or-later - */ - -#include "placesitemmodel.h" - -#include "dolphin_generalsettings.h" -#include "dolphindebug.h" -#include "dolphinplacesmodelsingleton.h" -#include "placesitem.h" -#include "placesitemsignalhandler.h" -#include "views/dolphinview.h" -#include "views/viewproperties.h" - -#include -#include -#include -#include -#include -#include -#include - -#include -#include -#include -#include - -PlacesItemModel::PlacesItemModel(QObject* parent) : - KStandardItemModel(parent), - m_hiddenItemsShown(false), - m_deviceToTearDown(nullptr), - m_storageSetupInProgress(), - m_sourceModel(DolphinPlacesModelSingleton::instance().placesModel()) -{ - cleanupBookmarks(); - loadBookmarks(); - initializeDefaultViewProperties(); - - connect(m_sourceModel, &KFilePlacesModel::rowsInserted, this, &PlacesItemModel::onSourceModelRowsInserted); - connect(m_sourceModel, &KFilePlacesModel::rowsAboutToBeRemoved, this, &PlacesItemModel::onSourceModelRowsAboutToBeRemoved); - connect(m_sourceModel, &KFilePlacesModel::dataChanged, this, &PlacesItemModel::onSourceModelDataChanged); - connect(m_sourceModel, &KFilePlacesModel::rowsAboutToBeMoved, this, &PlacesItemModel::onSourceModelRowsAboutToBeMoved); - connect(m_sourceModel, &KFilePlacesModel::rowsMoved, this, &PlacesItemModel::onSourceModelRowsMoved); - connect(m_sourceModel, &KFilePlacesModel::groupHiddenChanged, this, &PlacesItemModel::onSourceModelGroupHiddenChanged); -} - -PlacesItemModel::~PlacesItemModel() -{ -} - -void PlacesItemModel::createPlacesItem(const QString &text, const QUrl &url, const QString &iconName, const QString &appName) -{ - createPlacesItem(text, url, iconName, appName, -1); -} - -void PlacesItemModel::createPlacesItem(const QString &text, const QUrl &url, const QString &iconName, const QString &appName, int after) -{ - m_sourceModel->addPlace(text, url, iconName, appName, mapToSource(after)); -} - -PlacesItem* PlacesItemModel::placesItem(int index) const -{ - return dynamic_cast(item(index)); -} - -int PlacesItemModel::hiddenCount() const -{ - return m_sourceModel->hiddenCount(); -} - -void PlacesItemModel::setHiddenItemsShown(bool show) -{ - if (m_hiddenItemsShown == show) { - return; - } - - m_hiddenItemsShown = show; - - if (show) { - for (int r = 0, rMax = m_sourceModel->rowCount(); r < rMax; r++) { - const QModelIndex index = m_sourceModel->index(r, 0); - if (!m_sourceModel->isHidden(index)) { - continue; - } - addItemFromSourceModel(index); - } - } else { - for (int r = 0, rMax = m_sourceModel->rowCount(); r < rMax; r++) { - const QModelIndex index = m_sourceModel->index(r, 0); - if (m_sourceModel->isHidden(index)) { - removeItemByIndex(index); - } - } - } -} - -bool PlacesItemModel::hiddenItemsShown() const -{ - return m_hiddenItemsShown; -} - -int PlacesItemModel::closestItem(const QUrl& url) const -{ - return mapFromSource(m_sourceModel->closestItem(url)); -} - -// look for the correct position for the item based on source model -void PlacesItemModel::insertSortedItem(PlacesItem* item) -{ - if (!item) { - return; - } - - const KBookmark iBookmark = item->bookmark(); - const QString iBookmarkId = bookmarkId(iBookmark); - QModelIndex sourceIndex; - int pos = 0; - - for(int r = 0, rMax = m_sourceModel->rowCount(); r < rMax; r++) { - sourceIndex = m_sourceModel->index(r, 0); - const KBookmark sourceBookmark = m_sourceModel->bookmarkForIndex(sourceIndex); - - if (bookmarkId(sourceBookmark) == iBookmarkId) { - break; - } - - if (m_hiddenItemsShown || !m_sourceModel->isHidden(sourceIndex)) { - pos++; - } - } - - m_indexMap.insert(pos, sourceIndex); - insertItem(pos, item); -} - -void PlacesItemModel::onItemInserted(int index) -{ - KStandardItemModel::onItemInserted(index); -} - -void PlacesItemModel::onItemRemoved(int index, KStandardItem* removedItem) -{ - m_indexMap.removeAt(index); - - KStandardItemModel::onItemRemoved(index, removedItem); -} - -void PlacesItemModel::onItemChanged(int index, const QSet& changedRoles) -{ - const QModelIndex sourceIndex = mapToSource(index); - const PlacesItem *changedItem = placesItem(mapFromSource(sourceIndex)); - - if (!changedItem || !sourceIndex.isValid()) { - qWarning() << "invalid item changed signal"; - return; - } - if (changedRoles.contains("isHidden")) { - if (m_sourceModel->isHidden(sourceIndex) != changedItem->isHidden()) { - m_sourceModel->setPlaceHidden(sourceIndex, changedItem->isHidden()); - } else { - m_sourceModel->refresh(); - } - } - KStandardItemModel::onItemChanged(index, changedRoles); -} - -QAction* PlacesItemModel::ejectAction(int index) const -{ - const PlacesItem* item = placesItem(index); - if (item && item->device().is()) { - return new QAction(QIcon::fromTheme(QStringLiteral("media-eject")), i18nc("@item", "Eject"), nullptr); - } - - return nullptr; -} - -QAction* PlacesItemModel::teardownAction(int index) const -{ - const PlacesItem* item = placesItem(index); - if (!item) { - return nullptr; - } - - Solid::Device device = item->device(); - const bool providesTearDown = device.is() && - device.as()->isAccessible(); - if (!providesTearDown) { - return nullptr; - } - - Solid::StorageDrive* drive = device.as(); - if (!drive) { - drive = device.parent().as(); - } - - bool hotPluggable = false; - bool removable = false; - if (drive) { - hotPluggable = drive->isHotpluggable(); - removable = drive->isRemovable(); - } - - QString iconName; - QString text; - if (device.is()) { - text = i18nc("@item", "Release"); - } else if (removable || hotPluggable) { - text = i18nc("@item", "Safely Remove"); - iconName = QStringLiteral("media-eject"); - } else { - text = i18nc("@item", "Unmount"); - iconName = QStringLiteral("media-eject"); - } - - if (iconName.isEmpty()) { - return new QAction(text, nullptr); - } - - return new QAction(QIcon::fromTheme(iconName), text, nullptr); -} - -void PlacesItemModel::requestEject(int index) -{ - const PlacesItem* item = placesItem(index); - if (item) { - Solid::OpticalDrive* drive = item->device().parent().as(); - if (drive) { - connect(drive, &Solid::OpticalDrive::ejectDone, - this, &PlacesItemModel::slotStorageTearDownDone); - drive->eject(); - } else { - const QString label = item->text(); - const QString message = i18nc("@info", "The device '%1' is not a disk and cannot be ejected.", label); - Q_EMIT errorMessage(message); - } - } -} - -void PlacesItemModel::requestTearDown(int index) -{ - const PlacesItem* item = placesItem(index); - if (item) { - Solid::StorageAccess *tmp = item->device().as(); - if (tmp) { - m_deviceToTearDown = tmp; - // disconnect the Solid::StorageAccess::teardownRequested - // to prevent emitting PlacesItemModel::storageTearDownExternallyRequested - // after we have emitted PlacesItemModel::storageTearDownRequested - disconnect(tmp, &Solid::StorageAccess::teardownRequested, - item->signalHandler(), &PlacesItemSignalHandler::onTearDownRequested); - Q_EMIT storageTearDownRequested(tmp->filePath()); - } - } -} - -bool PlacesItemModel::storageSetupNeeded(int index) const -{ - const PlacesItem* item = placesItem(index); - return item ? item->storageSetupNeeded() : false; -} - -void PlacesItemModel::requestStorageSetup(int index) -{ - const PlacesItem* item = placesItem(index); - if (!item) { - return; - } - - Solid::Device device = item->device(); - const bool setup = device.is() - && !m_storageSetupInProgress.contains(device.as()) - && !device.as()->isAccessible(); - if (setup) { - Solid::StorageAccess* access = device.as(); - - m_storageSetupInProgress[access] = index; - - connect(access, &Solid::StorageAccess::setupDone, - this, &PlacesItemModel::slotStorageSetupDone); - - access->setup(); - } -} - -QMimeData* PlacesItemModel::createMimeData(const KItemSet& indexes) const -{ - QList urls; - QByteArray itemData; - - QDataStream stream(&itemData, QIODevice::WriteOnly); - - for (int index : indexes) { - const QUrl itemUrl = placesItem(index)->url(); - if (itemUrl.isValid()) { - urls << itemUrl; - } - stream << index; - } - - QMimeData* mimeData = new QMimeData(); - if (!urls.isEmpty()) { - mimeData->setUrls(urls); - } else { - // #378954: prevent itemDropEvent() drops if there isn't a source url. - mimeData->setData(blacklistItemDropEventMimeType(), QByteArrayLiteral("true")); - } - mimeData->setData(internalMimeType(), itemData); - - return mimeData; -} - -bool PlacesItemModel::supportsDropping(int index) const -{ - return index >= 0 && index < count(); -} - -void PlacesItemModel::dropMimeDataBefore(int index, const QMimeData* mimeData) -{ - if (mimeData->hasFormat(internalMimeType())) { - // The item has been moved inside the view - QByteArray itemData = mimeData->data(internalMimeType()); - QDataStream stream(&itemData, QIODevice::ReadOnly); - int oldIndex; - stream >> oldIndex; - - QModelIndex sourceIndex = mapToSource(index); - QModelIndex oldSourceIndex = mapToSource(oldIndex); - - m_sourceModel->movePlace(oldSourceIndex.row(), sourceIndex.row()); - } else if (mimeData->hasFormat(QStringLiteral("text/uri-list"))) { - // One or more items must be added to the model - const QList urls = KUrlMimeData::urlsFromMimeData(mimeData); - for (int i = urls.count() - 1; i >= 0; --i) { - const QUrl& url = urls[i]; - - QString text = url.fileName(); - if (text.isEmpty()) { - text = url.host(); - } - - if ((url.isLocalFile() && !QFileInfo(url.toLocalFile()).isDir()) - || url.scheme() == QLatin1String("trash")) { - // Only directories outside the trash are allowed - continue; - } - - createPlacesItem(text, url, KIO::iconNameForUrl(url), {}, qMax(0, index - 1)); - } - } - // will save bookmark alteration and fix sort if that is broken by the drag/drop operation - refresh(); -} - -void PlacesItemModel::addItemFromSourceModel(const QModelIndex &index) -{ - if (!m_hiddenItemsShown && m_sourceModel->isHidden(index)) { - return; - } - - const KBookmark bookmark = m_sourceModel->bookmarkForIndex(index); - Q_ASSERT(!bookmark.isNull()); - PlacesItem *item = new PlacesItem(bookmark); - updateItem(item, index); - insertSortedItem(item); - - if (m_sourceModel->isDevice(index)) { - connect(item->signalHandler(), &PlacesItemSignalHandler::tearDownExternallyRequested, - this, &PlacesItemModel::storageTearDownExternallyRequested); - } -} - -void PlacesItemModel::removeItemByIndex(const QModelIndex &sourceIndex) -{ - QString id = bookmarkId(m_sourceModel->bookmarkForIndex(sourceIndex)); - - for (int i = 0, iMax = count(); i < iMax; ++i) { - if (bookmarkId(placesItem(i)->bookmark()) == id) { - removeItem(i); - return; - } - } -} - -QString PlacesItemModel::bookmarkId(const KBookmark &bookmark) const -{ - QString id = bookmark.metaDataItem(QStringLiteral("UDI")); - if (id.isEmpty()) { - id = bookmark.metaDataItem(QStringLiteral("ID")); - } - return id; -} - -void PlacesItemModel::initializeDefaultViewProperties() const -{ - for(int i = 0, iMax = m_sourceModel->rowCount(); i < iMax; i++) { - const QModelIndex index = m_sourceModel->index(i, 0); - const PlacesItem *item = placesItem(mapFromSource(index)); - if (!item) { - continue; - } - - // Create default view-properties for all "Search For" and "Recently Saved" bookmarks - // in case the user has not already created custom view-properties for a corresponding - // query yet. - const bool createDefaultViewProperties = item->isSearchOrTimelineUrl() && !GeneralSettings::self()->globalViewProps(); - if (createDefaultViewProperties) { - const QUrl itemUrl = item->url(); - ViewProperties props(KFilePlacesModel::convertedUrl(itemUrl)); - if (!props.exist()) { - const QString path = itemUrl.path(); - if (path == QLatin1String("/documents")) { - props.setViewMode(DolphinView::DetailsView); - props.setPreviewsShown(false); - props.setVisibleRoles({"text", "path"}); - } else if (path == QLatin1String("/images")) { - props.setViewMode(DolphinView::IconsView); - props.setPreviewsShown(true); - props.setVisibleRoles({"text", "height", "width"}); - } else if (path == QLatin1String("/audio")) { - props.setViewMode(DolphinView::DetailsView); - props.setPreviewsShown(false); - props.setVisibleRoles({"text", "artist", "album"}); - } else if (path == QLatin1String("/videos")) { - props.setViewMode(DolphinView::IconsView); - props.setPreviewsShown(true); - props.setVisibleRoles({"text"}); - } else if (itemUrl.scheme() == QLatin1String("timeline")) { - props.setViewMode(DolphinView::DetailsView); - props.setVisibleRoles({"text", "modificationtime"}); - } - props.save(); - } - } - } -} - -void PlacesItemModel::updateItem(PlacesItem *item, const QModelIndex &index) -{ - item->setGroup(index.data(KFilePlacesModel::GroupRole).toString()); - item->setIcon(index.data(KFilePlacesModel::IconNameRole).toString()); - item->setGroupHidden(index.data(KFilePlacesModel::GroupHiddenRole).toBool()); -} - -void PlacesItemModel::slotStorageTearDownDone(Solid::ErrorType error, const QVariant& errorData) -{ - if (error && errorData.isValid()) { - if (error == Solid::ErrorType::DeviceBusy) { - KListOpenFilesJob* listOpenFilesJob = new KListOpenFilesJob(m_deviceToTearDown->filePath()); - connect(listOpenFilesJob, &KIO::Job::result, this, [this, listOpenFilesJob](KJob*) { - const KProcessList::KProcessInfoList blockingProcesses = listOpenFilesJob->processInfoList(); - QString errorString; - if (blockingProcesses.isEmpty()) { - errorString = i18n("One or more files on this device are open within an application."); - } else { - QStringList blockingApps; - for (const auto& process : blockingProcesses) { - blockingApps << process.name(); - } - blockingApps.removeDuplicates(); - errorString = xi18np("One or more files on this device are opened in application \"%2\".", - "One or more files on this device are opened in following applications: %2.", - blockingApps.count(), blockingApps.join(i18nc("separator in list of apps blocking device unmount", ", "))); - } - Q_EMIT errorMessage(errorString); - }); - listOpenFilesJob->start(); - } else { - Q_EMIT errorMessage(errorData.toString()); - } - } else { - // No error; it must have been unmounted successfully - Q_EMIT storageTearDownSuccessful(); - } - disconnect(m_deviceToTearDown, &Solid::StorageAccess::teardownDone, - this, &PlacesItemModel::slotStorageTearDownDone); - m_deviceToTearDown = nullptr; -} - -void PlacesItemModel::slotStorageSetupDone(Solid::ErrorType error, - const QVariant& errorData, - const QString& udi) -{ - Q_UNUSED(udi) - - const int index = m_storageSetupInProgress.take(sender()); - const PlacesItem* item = placesItem(index); - if (!item) { - return; - } - - if (error != Solid::NoError) { - if (errorData.isValid()) { - Q_EMIT errorMessage(i18nc("@info", "An error occurred while accessing '%1', the system responded: %2", - item->text(), - errorData.toString())); - } else { - Q_EMIT errorMessage(i18nc("@info", "An error occurred while accessing '%1'", - item->text())); - } - Q_EMIT storageSetupDone(index, false); - } else { - Q_EMIT storageSetupDone(index, true); - } -} - -void PlacesItemModel::onSourceModelRowsInserted(const QModelIndex &parent, int first, int last) -{ - for (int i = first; i <= last; i++) { - const QModelIndex index = m_sourceModel->index(i, 0, parent); - addItemFromSourceModel(index); - } -} - -void PlacesItemModel::onSourceModelRowsAboutToBeRemoved(const QModelIndex &parent, int first, int last) -{ - for(int r = first; r <= last; r++) { - const QModelIndex index = m_sourceModel->index(r, 0, parent); - int oldIndex = mapFromSource(index); - if (oldIndex != -1) { - removeItem(oldIndex); - } - } -} - -void PlacesItemModel::onSourceModelRowsAboutToBeMoved(const QModelIndex &parent, int start, int end, const QModelIndex &destination, int row) -{ - Q_UNUSED(destination) - Q_UNUSED(row) - - for(int r = start; r <= end; r++) { - const QModelIndex sourceIndex = m_sourceModel->index(r, 0, parent); - // remove moved item - removeItem(mapFromSource(sourceIndex)); - } -} - -void PlacesItemModel::onSourceModelRowsMoved(const QModelIndex &parent, int start, int end, const QModelIndex &destination, int row) -{ - Q_UNUSED(destination) - Q_UNUSED(parent) - - const int blockSize = (end - start) + 1; - - for (int r = start; r <= end; r++) { - // insert the moved item in the new position - const int targetRow = row + (start - r) - (r < row ? blockSize : 0); - const QModelIndex targetIndex = m_sourceModel->index(targetRow, 0, destination); - - addItemFromSourceModel(targetIndex); - } -} - -void PlacesItemModel::onSourceModelDataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight, const QVector &roles) -{ - Q_UNUSED(roles) - - for (int r = topLeft.row(); r <= bottomRight.row(); r++) { - const QModelIndex sourceIndex = m_sourceModel->index(r, 0); - const KBookmark bookmark = m_sourceModel->bookmarkForIndex(sourceIndex); - PlacesItem *placeItem = itemFromBookmark(bookmark); - - if (placeItem && (!m_hiddenItemsShown && m_sourceModel->isHidden(sourceIndex))) { - //hide item if it became invisible - removeItem(index(placeItem)); - return; - } - - if (!placeItem && (m_hiddenItemsShown || !m_sourceModel->isHidden(sourceIndex))) { - //show item if it became visible - addItemFromSourceModel(sourceIndex); - return; - } - - if (placeItem && !m_sourceModel->isDevice(sourceIndex)) { - // must update the bookmark object - placeItem->setBookmark(bookmark); - } - } -} - -void PlacesItemModel::onSourceModelGroupHiddenChanged(KFilePlacesModel::GroupType group, bool hidden) -{ - const auto groupIndexes = m_sourceModel->groupIndexes(group); - for (const QModelIndex &sourceIndex : groupIndexes) { - PlacesItem *item = placesItem(mapFromSource(sourceIndex)); - if (item) { - item->setGroupHidden(hidden); - } - } -} - -void PlacesItemModel::cleanupBookmarks() -{ - // KIO model now provides support for baloo urls, and because of that we - // need to remove old URLs that were visible only in Dolphin to avoid duplication - - static const QVector balooURLs = { - QUrl(QStringLiteral("timeline:/today")), - QUrl(QStringLiteral("timeline:/yesterday")), - QUrl(QStringLiteral("timeline:/thismonth")), - QUrl(QStringLiteral("timeline:/lastmonth")), - QUrl(QStringLiteral("search:/documents")), - QUrl(QStringLiteral("search:/images")), - QUrl(QStringLiteral("search:/audio")), - QUrl(QStringLiteral("search:/videos")) - }; - - int row = 0; - do { - const QModelIndex sourceIndex = m_sourceModel->index(row, 0); - const KBookmark bookmark = m_sourceModel->bookmarkForIndex(sourceIndex); - const QUrl url = bookmark.url(); - const QString appName = bookmark.metaDataItem(QStringLiteral("OnlyInApp")); - - if ((appName == KAboutData::applicationData().componentName() || - appName == KAboutData::applicationData().componentName() + DolphinPlacesModelSingleton::applicationNameSuffix()) && balooURLs.contains(url)) { - qCDebug(DolphinDebug) << "Removing old baloo url:" << url; - m_sourceModel->removePlace(sourceIndex); - } else { - row++; - } - } while (row < m_sourceModel->rowCount()); -} - -void PlacesItemModel::loadBookmarks() -{ - for(int r = 0, rMax = m_sourceModel->rowCount(); r < rMax; r++) { - const QModelIndex sourceIndex = m_sourceModel->index(r, 0); - if (m_hiddenItemsShown || !m_sourceModel->isHidden(sourceIndex)) { - addItemFromSourceModel(sourceIndex); - } - } -} - -void PlacesItemModel::clear() { - KStandardItemModel::clear(); -} - -void PlacesItemModel::proceedWithTearDown() -{ - Q_ASSERT(m_deviceToTearDown); - - connect(m_deviceToTearDown, &Solid::StorageAccess::teardownDone, - this, &PlacesItemModel::slotStorageTearDownDone); - m_deviceToTearDown->teardown(); -} - -void PlacesItemModel::deleteItem(int index) -{ - QModelIndex sourceIndex = mapToSource(index); - Q_ASSERT(sourceIndex.isValid()); - m_sourceModel->removePlace(sourceIndex); -} - -void PlacesItemModel::refresh() -{ - m_sourceModel->refresh(); -} - -void PlacesItemModel::hideItem(int index) -{ - PlacesItem* shownItem = placesItem(index); - if (!shownItem) { - return; - } - - shownItem->setHidden(true); -} - -QString PlacesItemModel::internalMimeType() const -{ - return "application/x-dolphinplacesmodel-" + - QString::number((qptrdiff)this); -} - -int PlacesItemModel::groupedDropIndex(int index, const PlacesItem* item) const -{ - Q_ASSERT(item); - - int dropIndex = index; - const QString group = item->group(); - - const int itemCount = count(); - if (index < 0) { - dropIndex = itemCount; - } - - // Search nearest previous item with the same group - int previousIndex = -1; - for (int i = dropIndex - 1; i >= 0; --i) { - if (placesItem(i)->group() == group) { - previousIndex = i; - break; - } - } - - // Search nearest next item with the same group - int nextIndex = -1; - for (int i = dropIndex; i < count(); ++i) { - if (placesItem(i)->group() == group) { - nextIndex = i; - break; - } - } - - // Adjust the drop-index to be inserted to the - // nearest item with the same group. - if (previousIndex >= 0 && nextIndex >= 0) { - dropIndex = (dropIndex - previousIndex < nextIndex - dropIndex) ? - previousIndex + 1 : nextIndex; - } else if (previousIndex >= 0) { - dropIndex = previousIndex + 1; - } else if (nextIndex >= 0) { - dropIndex = nextIndex; - } - - return dropIndex; -} - -bool PlacesItemModel::equalBookmarkIdentifiers(const KBookmark& b1, const KBookmark& b2) -{ - const QString udi1 = b1.metaDataItem(QStringLiteral("UDI")); - const QString udi2 = b2.metaDataItem(QStringLiteral("UDI")); - if (!udi1.isEmpty() && !udi2.isEmpty()) { - return udi1 == udi2; - } else { - return b1.metaDataItem(QStringLiteral("ID")) == b2.metaDataItem(QStringLiteral("ID")); - } -} - -int PlacesItemModel::mapFromSource(const QModelIndex &index) const -{ - if (!index.isValid()) { - return -1; - } - - return m_indexMap.indexOf(index); -} - -bool PlacesItemModel::isDir(int index) const -{ - Q_UNUSED(index) - return true; -} - -KFilePlacesModel::GroupType PlacesItemModel::groupType(int row) const -{ - return m_sourceModel->groupType(mapToSource(row)); -} - -bool PlacesItemModel::isGroupHidden(KFilePlacesModel::GroupType type) const -{ - return m_sourceModel->isGroupHidden(type); -} - -void PlacesItemModel::setGroupHidden(KFilePlacesModel::GroupType type, bool hidden) -{ - return m_sourceModel->setGroupHidden(type, hidden); -} - -QModelIndex PlacesItemModel::mapToSource(int row) const -{ - return m_indexMap.value(row); -} - -PlacesItem *PlacesItemModel::itemFromBookmark(const KBookmark &bookmark) const -{ - const QString id = bookmarkId(bookmark); - for (int i = 0, iMax = count(); i < iMax; i++) { - PlacesItem *item = placesItem(i); - const KBookmark itemBookmark = item->bookmark(); - if (bookmarkId(itemBookmark) == id) { - return item; - } - } - return nullptr; -} - diff --git a/src/panels/places/placesitemmodel.h b/src/panels/places/placesitemmodel.h deleted file mode 100644 index cd4079a73a..0000000000 --- a/src/panels/places/placesitemmodel.h +++ /dev/null @@ -1,216 +0,0 @@ -/* - * SPDX-FileCopyrightText: 2012 Peter Penz - * - * SPDX-License-Identifier: GPL-2.0-or-later - */ - -#ifndef PLACESITEMMODEL_H -#define PLACESITEMMODEL_H - -#include "kitemviews/kstandarditemmodel.h" - -#include -#include -#include - -#include -#include -#include -#include - -class KBookmark; -class PlacesItem; -class QAction; - -/** - * @brief Model for maintaining the bookmarks of the places panel. - * - * It is based on KFilePlacesModel from KIO. - */ -class PlacesItemModel: public KStandardItemModel -{ - Q_OBJECT - -public: - explicit PlacesItemModel(QObject* parent = nullptr); - ~PlacesItemModel() override; - - /** - * @brief Create a new place entry in the bookmark file - * and add it to the model - */ - void createPlacesItem(const QString& text, const QUrl& url, const QString& iconName = {}, const QString& appName = {}); - void createPlacesItem(const QString& text, const QUrl& url, const QString& iconName, const QString& appName, int after); - - PlacesItem* placesItem(int index) const; - - /** - * @brief Mark an item as hidden - * @param index of the item to be hidden - */ - void hideItem(int index); - - /** - * If set to true, all items that are marked as hidden - * will be shown in the view. The items will - * stay marked as hidden, which is visually indicated - * by the view by desaturating the icon and the text. - */ - void setHiddenItemsShown(bool show); - bool hiddenItemsShown() const; - - /** - * @return Number of items that are marked as hidden. - * Note that this does not mean that the items - * are really hidden - * (see PlacesItemModel::setHiddenItemsShown()). - */ - int hiddenCount() const; - - /** - * Search the item which is equal to the URL or at least - * is a parent URL. If there are more than one possible - * candidates, return the item which covers the biggest - * range of the URL. -1 is returned if no closest item - * could be found. - */ - int closestItem(const QUrl& url) const; - - QAction* ejectAction(int index) const; - QAction* teardownAction(int index) const; - - void requestEject(int index); - void requestTearDown(int index); - - bool storageSetupNeeded(int index) const; - void requestStorageSetup(int index); - - QMimeData* createMimeData(const KItemSet& indexes) const override; - - bool supportsDropping(int index) const override; - - void dropMimeDataBefore(int index, const QMimeData* mimeData); - - /** - * @return Converts the URL, which contains "virtual" URLs for system-items like - * "search:/documents" into a Query-URL that will be handled by - * the corresponding IO-slave. Virtual URLs for bookmarks are used to - * be independent from internal format changes. - */ - static QUrl convertedUrl(const QUrl& url); - - void clear() override; - - void proceedWithTearDown(); - - /** - * @brief Remove item from bookmark - * - * This function remove the index from bookmark file permanently - * - * @param index - the item to be removed - */ - void deleteItem(int index); - - /** - * Force a sync on the bookmarks and indicates to other applications that the - * state of the bookmarks has been changed. - */ - void refresh(); - - bool isDir(int index) const override; - - - KFilePlacesModel::GroupType groupType(int row) const; - bool isGroupHidden(KFilePlacesModel::GroupType type) const; - void setGroupHidden(KFilePlacesModel::GroupType type, bool hidden); - -Q_SIGNALS: - void errorMessage(const QString& message); - void storageSetupDone(int index, bool success); - void storageTearDownRequested(const QString& mountPath); - void storageTearDownExternallyRequested(const QString& mountPath); - void storageTearDownSuccessful(); - -protected: - void onItemInserted(int index) override; - void onItemRemoved(int index, KStandardItem* removedItem) override; - void onItemChanged(int index, const QSet& changedRoles) override; - -private Q_SLOTS: - void slotStorageTearDownDone(Solid::ErrorType error, const QVariant& errorData); - void slotStorageSetupDone(Solid::ErrorType error, const QVariant& errorData, const QString& udi); - - // source model control - void onSourceModelRowsInserted(const QModelIndex &parent, int first, int last); - void onSourceModelRowsAboutToBeRemoved(const QModelIndex &parent, int first, int last); - void onSourceModelRowsAboutToBeMoved(const QModelIndex &parent, int start, int end, const QModelIndex &destination, int row); - void onSourceModelRowsMoved(const QModelIndex &parent, int start, int end, const QModelIndex &destination, int row); - void onSourceModelDataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight, const QVector &roles); - void onSourceModelGroupHiddenChanged(KFilePlacesModel::GroupType group, bool hidden); - -private: - /** - * Remove bookmarks created by the previous version of dolphin that are - * not valid anymore - */ - void cleanupBookmarks(); - - /** - * Loads the bookmarks from the bookmark-manager and creates items for - * the model or moves hidden items to m_bookmarkedItems. - */ - void loadBookmarks(); - - QString internalMimeType() const; - - /** - * @return Adjusted drop index which assures that the item is aligned - * into the same group as specified by PlacesItem::groupType(). - */ - int groupedDropIndex(int index, const PlacesItem* item) const; - - /** - * @return True if the bookmarks have the same identifiers. The identifier - * is the unique "ID"-property in case if no UDI is set, otherwise - * the UDI is used as identifier. - */ - static bool equalBookmarkIdentifiers(const KBookmark& b1, const KBookmark& b2); - - /** - * Appends the item \a item as last element of the group - * the item belongs to. If no item with the same group is - * present, the item gets appended as last element of the - * model. PlacesItemModel takes the ownership - * of the item. - */ - void insertSortedItem(PlacesItem* item); - - PlacesItem *itemFromBookmark(const KBookmark &bookmark) const; - - void addItemFromSourceModel(const QModelIndex &index); - void removeItemByIndex(const QModelIndex &mapToSource); - - QString bookmarkId(const KBookmark &bookmark) const; - void initializeDefaultViewProperties() const; - - int mapFromSource(const QModelIndex &index) const; - QModelIndex mapToSource(int row) const; - - static void updateItem(PlacesItem *item, const QModelIndex &index); - -private: - bool m_hiddenItemsShown; - - Solid::StorageAccess *m_deviceToTearDown; - - QHash m_storageSetupInProgress; - - KFilePlacesModel *m_sourceModel; - - QVector m_indexMap; -}; - -#endif - - diff --git a/src/panels/places/placesitemsignalhandler.cpp b/src/panels/places/placesitemsignalhandler.cpp deleted file mode 100644 index 19f16c7b52..0000000000 --- a/src/panels/places/placesitemsignalhandler.cpp +++ /dev/null @@ -1,46 +0,0 @@ -/* - * SPDX-FileCopyrightText: 2012 Peter Penz - * - * SPDX-License-Identifier: GPL-2.0-or-later - */ - -#include "placesitemsignalhandler.h" - -#include "placesitem.h" - -PlacesItemSignalHandler::PlacesItemSignalHandler(PlacesItem* item, - QObject* parent) : - QObject(parent), - m_item(item) -{ -} - -PlacesItemSignalHandler::~PlacesItemSignalHandler() -{ -} - -void PlacesItemSignalHandler::onAccessibilityChanged() -{ - if (m_item) { - m_item->onAccessibilityChanged(); - } -} - -void PlacesItemSignalHandler::onTearDownRequested(const QString& udi) -{ - Q_UNUSED(udi) - if (m_item) { - Solid::StorageAccess *tmp = m_item->device().as(); - if (tmp) { - Q_EMIT tearDownExternallyRequested(tmp->filePath()); - } - } -} - -void PlacesItemSignalHandler::onTrashEmptinessChanged(bool isTrashEmpty) -{ - if (m_item) { - m_item->setIcon(isTrashEmpty ? QStringLiteral("user-trash") : QStringLiteral("user-trash-full")); - } -} - diff --git a/src/panels/places/placesitemsignalhandler.h b/src/panels/places/placesitemsignalhandler.h deleted file mode 100644 index da47839155..0000000000 --- a/src/panels/places/placesitemsignalhandler.h +++ /dev/null @@ -1,57 +0,0 @@ -/* - * SPDX-FileCopyrightText: 2012 Peter Penz - * - * SPDX-License-Identifier: GPL-2.0-or-later - */ - -#ifndef PLACESITEMSIGNALHANDLER_H -#define PLACESITEMSIGNALHANDLER_H - -#include - -class PlacesItem; - -/** - * @brief Helper class for PlacesItem to be able to listen to signals - * and performing a corresponding action. - * - * PlacesItem is derived from KStandardItem, which is no QObject-class - * on purpose. To be able to internally listen to signals and performing a - * corresponding action, PlacesItemSignalHandler is used. - * - * E.g. if the PlacesItem wants to react on accessibility-changes of a storage-access, - * the signal-handler can be used like this: - * - * QObject::connect(storageAccess, SIGNAL(accessibilityChanged(bool,QString)), - * signalHandler, SLOT(onAccessibilityChanged())); - * - * - * The slot PlacesItemSignalHandler::onAccessibilityChanged() will call - * the method PlacesItem::onAccessibilityChanged(). - */ -class PlacesItemSignalHandler: public QObject -{ - Q_OBJECT - -public: - explicit PlacesItemSignalHandler(PlacesItem* item, QObject* parent = nullptr); - ~PlacesItemSignalHandler() override; - -public Q_SLOTS: - /** - * Calls PlacesItem::onAccessibilityChanged() - */ - void onAccessibilityChanged(); - - void onTearDownRequested(const QString& udi); - - void onTrashEmptinessChanged(bool isTrashEmpty); - -Q_SIGNALS: - void tearDownExternallyRequested(const QString& udi); - -private: - PlacesItem* m_item; -}; - -#endif diff --git a/src/panels/places/placespanel.cpp b/src/panels/places/placespanel.cpp index 4d95c08eb3..8fc81bb326 100644 --- a/src/panels/places/placespanel.cpp +++ b/src/panels/places/placespanel.cpp @@ -1,5 +1,6 @@ /* * SPDX-FileCopyrightText: 2008-2012 Peter Penz + * SPDX-FileCopyrightText: 2021 Kai Uwe Broulik * * Based on KFilePlacesView from kdelibs: * SPDX-FileCopyrightText: 2007 Kevin Ottens @@ -10,476 +11,178 @@ #include "placespanel.h" +#include "dolphinplacesmodelsingleton.h" #include "dolphin_generalsettings.h" +#include "dolphin_placespanelsettings.h" #include "global.h" -#include "kitemviews/kitemlistcontainer.h" -#include "kitemviews/kitemlistcontroller.h" -#include "kitemviews/kitemlistselectionmanager.h" -#include "kitemviews/kstandarditem.h" -#include "placesitem.h" -#include "placesitemlistgroupheader.h" -#include "placesitemlistwidget.h" -#include "placesitemmodel.h" -#include "placesview.h" -#include "trash/dolphintrash.h" #include "views/draganddrophelper.h" #include "settings/dolphinsettingsdialog.h" -#include #include #include -#include #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_triggerStorageSetupModifier(), - m_itemDropEventIndex(-1), - m_itemDropEventMimeData(nullptr), - m_itemDropEvent(nullptr), - m_tooltipTimer() +#include + +PlacesPanel::PlacesPanel(QWidget* parent) + : KFilePlacesView(parent) { - m_tooltipTimer.setInterval(500); - m_tooltipTimer.setSingleShot(true); - connect(&m_tooltipTimer, &QTimer::timeout, this, &PlacesPanel::slotShowTooltip); + setDropOnPlaceEnabled(true); + connect(this, &PlacesPanel::urlsDropped, + this, &PlacesPanel::slotUrlsDropped); + + setAutoResizeItemsEnabled(false); + + setTeardownFunction([this](const QModelIndex &index) { + slotTearDownRequested(index); + }); + + m_configureTrashAction = new QAction(QIcon::fromTheme(QStringLiteral("configure")), i18nc("@action:inmenu", "Configure Trash…")); + m_configureTrashAction->setPriority(QAction::HighPriority); + connect(m_configureTrashAction, &QAction::triggered, this, &PlacesPanel::slotConfigureTrash); + addAction(m_configureTrashAction); + + connect(this, &PlacesPanel::contextMenuAboutToShow, this, &PlacesPanel::slotContextMenuAboutToShow); + + connect(this, &PlacesPanel::iconSizeChanged, this, [this](const QSize &newSize) { + int iconSize = qMin(newSize.width(), newSize.height()); + if (iconSize == 0) { + // Don't store 0 size, let's keep -1 for default/small/automatic + iconSize = -1; + } + PlacesPanelSettings* settings = PlacesPanelSettings::self(); + settings->setIconSize(iconSize); + settings->save(); + }); } -PlacesPanel::~PlacesPanel() +PlacesPanel::~PlacesPanel() = default; + +void PlacesPanel::setUrl(const QUrl &url) { + // KFilePlacesView::setUrl no-ops when no model is set but we only set it in showEvent() + // Remember the URL and set it in showEvent + m_url = url; + KFilePlacesView::setUrl(url); +} + +QList PlacesPanel::customContextMenuActions() const +{ + return m_customContextMenuActions; +} + +void PlacesPanel::setCustomContextMenuActions(const QList &actions) +{ + m_customContextMenuActions = actions; } void PlacesPanel::proceedWithTearDown() { - m_model->proceedWithTearDown(); -} + Q_ASSERT(m_deviceToTearDown); -bool PlacesPanel::urlChanged() -{ - if (!url().isValid() || url().scheme().contains(QLatin1String("search"))) { - // Skip results shown by a search, as possible identical - // directory names are useless without parent-path information. - return false; - } - - if (m_controller) { - selectItem(); - } - - return true; + connect(m_deviceToTearDown, &Solid::StorageAccess::teardownDone, + this, &PlacesPanel::slotTearDownDone); + m_deviceToTearDown->teardown(); } void PlacesPanel::readSettings() { - if (m_controller) { - const int delay = GeneralSettings::autoExpandFolders() ? 750 : -1; - m_controller->setAutoActivationDelay(delay); + if (GeneralSettings::autoExpandFolders()) { + if (!m_dragActivationTimer) { + m_dragActivationTimer = new QTimer(this); + m_dragActivationTimer->setInterval(750); + m_dragActivationTimer->setSingleShot(true); + connect(m_dragActivationTimer, &QTimer::timeout, + this, &PlacesPanel::slotDragActivationTimeout); + } + } else { + delete m_dragActivationTimer; + m_dragActivationTimer = nullptr; + m_pendingDragActivation = QPersistentModelIndex(); } + + const int iconSize = qMax(0, PlacesPanelSettings::iconSize()); + setIconSize(QSize(iconSize, iconSize)); } 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); - connect(m_model, &PlacesItemModel::storageTearDownSuccessful, - this, &PlacesPanel::storageTearDownSuccessful); - - m_view = new PlacesView(); - m_view->setWidgetCreator(new KItemListWidgetCreator()); - m_view->setGroupHeaderCreator(new KItemListGroupHeaderCreator()); - - installEventFilter(this); - - m_controller = new KItemListController(m_model, m_view, this); - m_controller->setSelectionBehavior(KItemListController::SingleSelection); - m_controller->setSingleClickActivationEnforced(true); - + if (!event->spontaneous() && !model()) { 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); + auto *placesModel = DolphinPlacesModelSingleton::instance().placesModel(); + setModel(placesModel); - KItemListContainer* container = new KItemListContainer(m_controller, this); - container->setEnabledFrame(false); + connect(placesModel, &KFilePlacesModel::errorMessage, this, &PlacesPanel::errorMessage); - QVBoxLayout* layout = new QVBoxLayout(this); - layout->setContentsMargins(0, 0, 0, 0); - layout->addWidget(container); + connect(placesModel, &QAbstractItemModel::rowsInserted, this, &PlacesPanel::slotRowsInserted); + connect(placesModel, &QAbstractItemModel::rowsAboutToBeRemoved, this, &PlacesPanel::slotRowsAboutToBeRemoved); - selectItem(); + for (int i = 0; i < model()->rowCount(); ++i) { + connectDeviceSignals(model()->index(i, 0, QModelIndex())); + } + + setUrl(m_url); } - Panel::showEvent(event); + KFilePlacesView::showEvent(event); } -bool PlacesPanel::eventFilter(QObject * /* obj */, QEvent *event) +void PlacesPanel::dragMoveEvent(QDragMoveEvent *event) { - if (event->type() == QEvent::ToolTip) { + KFilePlacesView::dragMoveEvent(event); - QHelpEvent *hoverEvent = reinterpret_cast(event); - - m_hoveredIndex = m_view->itemAt(hoverEvent->pos()); - m_hoverPos = mapToGlobal(hoverEvent->pos()); - - m_tooltipTimer.start(); - return true; - } - return false; -} - -void PlacesPanel::slotItemActivated(int index) -{ - const auto modifiers = QGuiApplication::keyboardModifiers(); - // keep in sync with KUrlNavigator::slotNavigatorButtonClicked - if (modifiers & Qt::ControlModifier && modifiers & Qt::ShiftModifier) { - triggerItem(index, TriggerItemModifier::ToNewActiveTab); - } else if (modifiers & Qt::ControlModifier) { - triggerItem(index, TriggerItemModifier::ToNewTab); - } else if (modifiers & Qt::ShiftModifier) { - triggerItem(index, TriggerItemModifier::ToNewWindow); - } else { - triggerItem(index, TriggerItemModifier::None); - } -} - -void PlacesPanel::slotItemMiddleClicked(int index) -{ - const auto modifiers = QGuiApplication::keyboardModifiers(); - // keep in sync with KUrlNavigator::slotNavigatorButtonClicked - if (modifiers & Qt::ShiftModifier) { - triggerItem(index, TriggerItemModifier::ToNewActiveTab); - } else { - triggerItem(index, TriggerItemModifier::ToNewTab); - } -} - -void PlacesPanel::slotItemContextMenuRequested(int index, const QPointF& pos) -{ - PlacesItem* item = m_model->placesItem(index); - if (!item) { + if (!m_dragActivationTimer) { return; } - QMenu menu(this); - - QAction* emptyTrashAction = nullptr; - QAction* configureTrashAction = nullptr; - QAction* editAction = nullptr; - QAction* teardownAction = nullptr; - QAction* ejectAction = nullptr; - QAction* mountAction = nullptr; - - const bool isDevice = !item->udi().isEmpty(); - const bool isTrash = (item->url().scheme() == QLatin1String("trash")); - 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* openInNewTabAction = menu.addAction(QIcon::fromTheme(QStringLiteral("tab-new")), i18nc("@item:inmenu", "Open in New Tab")); - QAction* openInNewWindowAction = menu.addAction(QIcon::fromTheme(QStringLiteral("window-new")), i18nc("@item:inmenu", "Open in New Window")); - QAction* propertiesAction = nullptr; - if (item->url().isLocalFile()) { - propertiesAction = menu.addAction(QIcon::fromTheme(QStringLiteral("document-properties")), i18nc("@action:inmenu", "Properties")); - } - if (!isDevice) { - menu.addSeparator(); - } - - if (isDevice) { - ejectAction = m_model->ejectAction(index); - if (ejectAction) { - ejectAction->setParent(&menu); - menu.addAction(ejectAction); - } - - teardownAction = m_model->teardownAction(index); - if (teardownAction) { - // Disable teardown option for root and home partitions - bool teardownEnabled = item->url() != QUrl::fromLocalFile(QDir::rootPath()); - if (teardownEnabled) { - KMountPoint::Ptr mountPoint = KMountPoint::currentMountPoints().findByPath(QDir::homePath()); - if (mountPoint && item->url() == QUrl::fromLocalFile(mountPoint->mountPoint())) { - teardownEnabled = false; - } - } - teardownAction->setEnabled(teardownEnabled); - - teardownAction->setParent(&menu); - menu.addAction(teardownAction); - } - - if (item->storageSetupNeeded()) { - mountAction = menu.addAction(QIcon::fromTheme(QStringLiteral("media-mount")), i18nc("@action:inmenu", "Mount")); - } - - if (teardownAction || ejectAction || mountAction) { - menu.addSeparator(); - } - } - - if (isTrash) { - configureTrashAction = menu.addAction(QIcon::fromTheme(QStringLiteral("configure")), i18nc("@action:inmenu", "Configure Trash...")); - } - - if (!isDevice) { - editAction = menu.addAction(QIcon::fromTheme(QStringLiteral("edit-entry")), 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(QIcon::fromTheme(QStringLiteral("view-hidden")), 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 if (action == configureTrashAction) { - DolphinSettingsDialog* settingsDialog = new DolphinSettingsDialog(item->url(), this); - settingsDialog->setCurrentPage(settingsDialog->trashSettings); - settingsDialog->setAttribute(Qt::WA_DeleteOnClose); - settingsDialog->show(); - } 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()); - if (!m_model->hiddenCount()) { - showHiddenEntries(false); - } - } else if (action == openInNewWindowAction) { - Dolphin::openNewWindow({KFilePlacesModel::convertedUrl(m_model->data(index).value("url").toUrl())}, this); - } else if (action == openInNewTabAction) { - triggerItem(index, TriggerItemModifier::ToNewTab); - } else if (action == mountAction) { - m_model->requestStorageSetup(index); - } else if (action == teardownAction) { - m_model->requestTearDown(index); - } else if (action == ejectAction) { - m_model->requestEject(index); - } else if (action == propertiesAction) { - KPropertiesDialog* dialog = new KPropertiesDialog(item->url(), this); - dialog->setAttribute(Qt::WA_DeleteOnClose); - dialog->show(); - } - } - } - - selectItem(); -} - -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 = menu.addAction(i18nc("@item:inmenu", "Show Hidden Places")); - showAllAction->setCheckable(true); - showAllAction->setChecked(m_model->hiddenItemsShown()); - showAllAction->setIcon(QIcon::fromTheme(m_model->hiddenItemsShown() ? QStringLiteral("view-visible") : QStringLiteral("view-hidden"))); - showAllAction->setEnabled(m_model->hiddenCount()); - - 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, I18NC_NOOP("Small icon size", "Small (%1x%2)")}, - {KIconLoader::SizeSmallMedium, I18NC_NOOP("Medium icon size", "Medium (%1x%2)")}, - {KIconLoader::SizeMedium, I18NC_NOOP("Large icon size", "Large (%1x%2)")}, - {KIconLoader::SizeLarge, I18NC_NOOP("Huge icon size", "Huge (%1x%2)")} - }; - - QHash 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(); - const auto actions = customContextMenuActions(); - for (QAction* action : actions) { - menu.addAction(action); - } - - QAction* action = menu.exec(pos.toPoint()); - if (action) { - if (action == addAction) { - addEntry(); - } else if (action == showAllAction) { - showHiddenEntries(showAllAction->isChecked()); - } else if (iconSizeActionMap.contains(action)) { - m_view->setIconSize(iconSizeActionMap.value(action)); - } - } - - selectItem(); -} - -QAction *PlacesPanel::buildGroupContextMenu(QMenu *menu, int index) -{ - if (index == -1) { - return nullptr; - } - - KFilePlacesModel::GroupType groupType = m_model->groupType(index); - QAction *hideGroupAction = menu->addAction(QIcon::fromTheme(QStringLiteral("view-hidden")), 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()); - if (!m_model->hiddenCount()) { - showHiddenEntries(false); - } - }); - - return hideGroupAction; -} - -void PlacesPanel::slotItemDropEvent(int index, QGraphicsSceneDragDropEvent* event) -{ - if (index < 0) { + const QModelIndex index = indexAt(event->pos()); + if (!index.isValid()) { 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; + QPersistentModelIndex persistentIndex(index); + if (!m_pendingDragActivation.isValid() || m_pendingDragActivation != persistentIndex) { + m_pendingDragActivation = persistentIndex; + m_dragActivationTimer->start(); } } -void PlacesPanel::slotAboveItemDropEvent(int index, QGraphicsSceneDragDropEvent* event) +void PlacesPanel::dragLeaveEvent(QDragLeaveEvent *event) { - m_model->dropMimeDataBefore(index, event->mimeData()); + KFilePlacesView::dragLeaveEvent(event); + + if (m_dragActivationTimer) { + m_dragActivationTimer->stop(); + m_pendingDragActivation = QPersistentModelIndex(); + } +} + +void PlacesPanel::slotConfigureTrash() +{ + const QUrl url = currentIndex().data(KFilePlacesModel::UrlRole).toUrl(); + + DolphinSettingsDialog* settingsDialog = new DolphinSettingsDialog(url, this); + settingsDialog->setCurrentPage(settingsDialog->trashSettings); + settingsDialog->setAttribute(Qt::WA_DeleteOnClose); + settingsDialog->show(); +} + +void PlacesPanel::slotDragActivationTimeout() +{ + if (!m_pendingDragActivation.isValid()) { + return; + } + + auto *placesModel = static_cast(model()); + Q_EMIT placeActivated(KFilePlacesModel::convertedUrl(placesModel->url(m_pendingDragActivation))); } void PlacesPanel::slotUrlsDropped(const QUrl& dest, QDropEvent* event, QWidget* parent) @@ -490,132 +193,121 @@ void PlacesPanel::slotUrlsDropped(const QUrl& dest, QDropEvent* event, QWidget* } } -void PlacesPanel::slotStorageSetupDone(int index, bool success) +void PlacesPanel::slotContextMenuAboutToShow(const QModelIndex &index, QMenu *menu) { - disconnect(m_model, &PlacesItemModel::storageSetupDone, - this, &PlacesPanel::slotStorageSetupDone); + Q_UNUSED(menu); - if (m_triggerStorageSetupModifier == TriggerItemModifier::None) { - return; - } + auto *placesModel = static_cast(model()); + const QUrl url = placesModel->url(index); + const Solid::Device device = placesModel->deviceForIndex(index); - if (success) { - Q_ASSERT(!m_model->storageSetupNeeded(index)); - triggerItem(index, m_triggerStorageSetupModifier); - m_triggerStorageSetupModifier = TriggerItemModifier::None; + m_configureTrashAction->setVisible(url.scheme() == QLatin1String("trash")); + + // show customContextMenuActions only on the view's context menu + if (!url.isValid() && !device.isValid()) { + addActions(m_customContextMenuActions); } else { - setUrl(m_storageSetupFailedUrl); - m_storageSetupFailedUrl = QUrl(); - } -} - -void PlacesPanel::slotShowTooltip() -{ - const QUrl url = m_model->data(m_hoveredIndex.value_or(-1)).value("url").value(); - const QString text = url.toDisplayString(QUrl::PreferLocalFile); - QToolTip::showText(m_hoverPos, text); -} - -void PlacesPanel::addEntry() -{ - const int index = m_controller->selectionManager()->currentItem(); - const QUrl url = m_model->data(index).value("url").toUrl(); - const QString text = url.fileName().isEmpty() ? url.toDisplayString(QUrl::PreferLocalFile) : url.fileName(); - - QPointer dialog = new KFilePlaceEditDialog(true, url, text, QString(), true, false, KIconLoader::SizeMedium, this); - if (dialog->exec() == QDialog::Accepted) { - const QString appName = dialog->applicationLocal() ? QCoreApplication::applicationName() : QString(); - m_model->createPlacesItem(dialog->label(), dialog->url(), dialog->icon(), appName); - } - - delete dialog; -} - -void PlacesPanel::editEntry(int index) -{ - QHash data = m_model->data(index); - const QUrl url = data.value("url").toUrl(); - const QString text = data.value("text").toString(); - const QString iconName = data.value("iconName").toString(); - const bool applicationLocal = !data.value("applicationName").toString().isEmpty(); - - QPointer dialog = new KFilePlaceEditDialog(true, url, text, iconName, true, applicationLocal, KIconLoader::SizeMedium, this); - if (dialog->exec() == QDialog::Accepted) { - PlacesItem* oldItem = m_model->placesItem(index); - if (oldItem) { - const QString appName = dialog->applicationLocal() ? QCoreApplication::applicationName() : QString(); - oldItem->setApplicationName(appName); - oldItem->setText(dialog->label()); - oldItem->setUrl(dialog->url()); - oldItem->setIcon(dialog->icon()); - m_model->refresh(); - } - } - - delete dialog; -} - -void PlacesPanel::selectItem() -{ - const int index = m_model->closestItem(url()); - KItemListSelectionManager* selectionManager = m_controller->selectionManager(); - selectionManager->setCurrentItem(index); - selectionManager->clearSelection(); - - const QUrl closestUrl = m_model->url(index); - if (!closestUrl.path().isEmpty() && url() == closestUrl) { - selectionManager->setSelected(index); - } -} - -void PlacesPanel::triggerItem(int index, TriggerItemModifier modifier) -{ - const PlacesItem* item = m_model->placesItem(index); - if (!item) { - return; - } - - if (m_model->storageSetupNeeded(index)) { - m_triggerStorageSetupModifier = modifier; - m_storageSetupFailedUrl = url(); - - connect(m_model, &PlacesItemModel::storageSetupDone, - this, &PlacesPanel::slotStorageSetupDone); - - m_model->requestStorageSetup(index); - } else { - m_triggerStorageSetupModifier = TriggerItemModifier::None; - - const QUrl url = m_model->data(index).value("url").toUrl(); - if (!url.isEmpty()) { - switch (modifier) { - case TriggerItemModifier::ToNewTab: - Q_EMIT placeActivatedInNewTab(KFilePlacesModel::convertedUrl(url)); - break; - case TriggerItemModifier::ToNewActiveTab: - Q_EMIT placeActivatedInNewActiveTab(KFilePlacesModel::convertedUrl(url)); - break; - case TriggerItemModifier::ToNewWindow: - Dolphin::openNewWindow({KFilePlacesModel::convertedUrl(url)}, this); - break; - case TriggerItemModifier::None: - Q_EMIT placeActivated(KFilePlacesModel::convertedUrl(url)); - break; + const auto actions = this->actions(); + for (QAction *action : actions) { + if (m_customContextMenuActions.contains(action)) { + removeAction(action); } } } } -void PlacesPanel::showHiddenEntries(bool shown) +void PlacesPanel::slotTearDownRequested(const QModelIndex &index) { - m_model->setHiddenItemsShown(shown); - Q_EMIT showHiddenEntriesChanged(shown); + auto *placesModel = static_cast(model()); + + Solid::StorageAccess *storageAccess = placesModel->deviceForIndex(index).as(); + if (!storageAccess) { + return; + } + + m_deviceToTearDown = storageAccess; + + // disconnect the Solid::StorageAccess::teardownRequested + // to prevent emitting PlacesPanel::storageTearDownExternallyRequested + // after we have emitted PlacesPanel::storageTearDownRequested + disconnect(storageAccess, &Solid::StorageAccess::teardownRequested, this, &PlacesPanel::slotTearDownRequestedExternally); + Q_EMIT storageTearDownRequested(storageAccess->filePath()); } -int PlacesPanel::hiddenListCount() +void PlacesPanel::slotTearDownRequestedExternally(const QString &udi) { - if(!m_model) { - return 0; - } - return m_model->hiddenCount(); + Q_UNUSED(udi); + auto *storageAccess = static_cast(sender()); + + Q_EMIT storageTearDownExternallyRequested(storageAccess->filePath()); +} + +void PlacesPanel::slotTearDownDone(Solid::ErrorType error, const QVariant& errorData) +{ + if (error && errorData.isValid()) { + if (error == Solid::ErrorType::DeviceBusy) { + KListOpenFilesJob* listOpenFilesJob = new KListOpenFilesJob(m_deviceToTearDown->filePath()); + connect(listOpenFilesJob, &KIO::Job::result, this, [this, listOpenFilesJob](KJob*) { + const KProcessList::KProcessInfoList blockingProcesses = listOpenFilesJob->processInfoList(); + QString errorString; + if (blockingProcesses.isEmpty()) { + errorString = i18n("One or more files on this device are open within an application."); + } else { + QStringList blockingApps; + for (const auto& process : blockingProcesses) { + blockingApps << process.name(); + } + blockingApps.removeDuplicates(); + errorString = xi18np("One or more files on this device are opened in application \"%2\".", + "One or more files on this device are opened in following applications: %2.", + blockingApps.count(), blockingApps.join(i18nc("separator in list of apps blocking device unmount", ", "))); + } + Q_EMIT errorMessage(errorString); + }); + listOpenFilesJob->start(); + } else { + Q_EMIT errorMessage(errorData.toString()); + } + } else { + // No error; it must have been unmounted successfully + Q_EMIT storageTearDownSuccessful(); + } + disconnect(m_deviceToTearDown, &Solid::StorageAccess::teardownDone, + this, &PlacesPanel::slotTearDownDone); + m_deviceToTearDown = nullptr; +} + +void PlacesPanel::slotRowsInserted(const QModelIndex &parent, int first, int last) +{ + for (int i = first; i <= last; ++i) { + connectDeviceSignals(model()->index(first, 0, parent)); + } +} + +void PlacesPanel::slotRowsAboutToBeRemoved(const QModelIndex &parent, int first, int last) +{ + auto *placesModel = static_cast(model()); + + for (int i = first; i <= last; ++i) { + const QModelIndex index = placesModel->index(i, 0, parent); + + Solid::StorageAccess *storageAccess = placesModel->deviceForIndex(index).as(); + if (!storageAccess) { + continue; + } + + disconnect(storageAccess, &Solid::StorageAccess::teardownRequested, this, nullptr); + } +} + +void PlacesPanel::connectDeviceSignals(const QModelIndex &index) +{ + auto *placesModel = static_cast(model()); + + Solid::StorageAccess *storageAccess = placesModel->deviceForIndex(index).as(); + if (!storageAccess) { + return; + } + + connect(storageAccess, &Solid::StorageAccess::teardownRequested, this, &PlacesPanel::slotTearDownRequestedExternally); } diff --git a/src/panels/places/placespanel.h b/src/panels/places/placespanel.h index e19447cbfe..fd6661c76f 100644 --- a/src/panels/places/placespanel.h +++ b/src/panels/places/placespanel.h @@ -1,6 +1,7 @@ /* * SPDX-FileCopyrightText: 2008-2012 Peter Penz * SPDX-FileCopyrightText: 2010 Christian Muehlhaeuser + * SPDX-FileCopyrightText: 2021 Kai Uwe Broulik * * SPDX-License-Identifier: GPL-2.0-or-later */ @@ -8,95 +9,78 @@ #ifndef PLACESPANEL_H #define PLACESPANEL_H -#include - #include "panels/panel.h" #include -#include +#include + +#include // Solid::ErrorType + +class QTimer; +namespace Solid +{ +class StorageAccess; +} -class KItemListController; -class PlacesItemModel; -class PlacesView; -class QGraphicsSceneDragDropEvent; -class QMenu; -class QMimeData; /** * @brief Combines bookmarks and mounted devices as list. */ -class PlacesPanel : public Panel +class PlacesPanel : public KFilePlacesView { Q_OBJECT public: explicit PlacesPanel(QWidget* parent); ~PlacesPanel() override; + + void setUrl(const QUrl &url); // override + + // for compatibility with Panel, actions that are shown + // on the view's context menu + QList customContextMenuActions() const; + void setCustomContextMenuActions(const QList& actions); + + void requestTearDown(); void proceedWithTearDown(); - bool eventFilter(QObject *obj, QEvent *event) override; +public Q_SLOTS: + void readSettings(); Q_SIGNALS: - void placeActivated(const QUrl& url); - void placeActivatedInNewTab(const QUrl &url); - void placeActivatedInNewActiveTab(const QUrl &url); void errorMessage(const QString& error); void storageTearDownRequested(const QString& mountPath); void storageTearDownExternallyRequested(const QString& mountPath); - void showHiddenEntriesChanged(bool shown); void storageTearDownSuccessful(); protected: - bool urlChanged() override; void showEvent(QShowEvent* event) override; - -public Q_SLOTS: - void readSettings() override; - void showHiddenEntries(bool shown); - int hiddenListCount(); + void dragMoveEvent(QDragMoveEvent *event) override; + void dragLeaveEvent(QDragLeaveEvent *event) override; private Q_SLOTS: - void slotItemActivated(int index); - void slotItemMiddleClicked(int index); - void slotItemContextMenuRequested(int index, const QPointF& pos); - void slotViewContextMenuRequested(const QPointF& pos); - void slotItemDropEvent(int index, QGraphicsSceneDragDropEvent* event); - void slotItemDropEventStorageSetupDone(int index, bool success); - void slotAboveItemDropEvent(int index, QGraphicsSceneDragDropEvent* event); + void slotConfigureTrash(); + void slotDragActivationTimeout(); void slotUrlsDropped(const QUrl& dest, QDropEvent* event, QWidget* parent); - void slotStorageSetupDone(int index, bool success); - void slotShowTooltip(); + void slotContextMenuAboutToShow(const QModelIndex &index, QMenu *menu); + void slotTearDownRequested(const QModelIndex &index); + void slotTearDownRequestedExternally(const QString &udi); + void slotTearDownDone(Solid::ErrorType error, const QVariant& errorData); + void slotRowsInserted(const QModelIndex &parent, int first, int last); + void slotRowsAboutToBeRemoved(const QModelIndex &parent, int first, int last); private: - enum class TriggerItemModifier { None, ToNewTab, ToNewActiveTab, ToNewWindow }; + void connectDeviceSignals(const QModelIndex &idx); -private: - void addEntry(); - void editEntry(int index); + QUrl m_url; // only used for initial setUrl + QList m_customContextMenuActions; - /** - * Selects the item that matches the URL set - * for the panel (see Panel::setUrl()). - */ - void selectItem(); + QTimer *m_dragActivationTimer = nullptr; + QPersistentModelIndex m_pendingDragActivation; - void triggerItem(int index, TriggerItemModifier modifier); + Solid::StorageAccess *m_deviceToTearDown = nullptr; - QAction* buildGroupContextMenu(QMenu* menu, int index); - -private: - KItemListController* m_controller; - PlacesItemModel* m_model; - PlacesView* m_view; - - QUrl m_storageSetupFailedUrl; - TriggerItemModifier m_triggerStorageSetupModifier; - - int m_itemDropEventIndex; - QMimeData* m_itemDropEventMimeData; - QDropEvent* m_itemDropEvent; - QTimer m_tooltipTimer; - std::optional m_hoveredIndex; - QPoint m_hoverPos; + QAction *m_configureTrashAction; + QAction *m_lockPanelsAction; }; #endif // PLACESPANEL_H diff --git a/src/panels/places/placesview.cpp b/src/panels/places/placesview.cpp deleted file mode 100644 index dc264e411f..0000000000 --- a/src/panels/places/placesview.cpp +++ /dev/null @@ -1,49 +0,0 @@ -/* - * SPDX-FileCopyrightText: 2012 Frank Reininghaus - * - * SPDX-License-Identifier: GPL-2.0-or-later - */ - -#include "placesview.h" - -#include "dolphin_placespanelsettings.h" -#include "kitemviews/kitemlistheader.h" - -#include - -PlacesView::PlacesView(QGraphicsWidget* parent) : - KStandardItemListView(parent) -{ - header()->setAutomaticColumnResizing(false); - - const int iconSize = PlacesPanelSettings::iconSize(); - if (iconSize >= 0) { - setIconSize(iconSize); - } -} - -void PlacesView::setIconSize(int size) -{ - if (size != iconSize()) { - PlacesPanelSettings* settings = PlacesPanelSettings::self(); - settings->setIconSize(size); - settings->save(); - - KItemListStyleOption option = styleOption(); - option.iconSize = size; - setStyleOption(option); - } -} - -int PlacesView::iconSize() const -{ - const KItemListStyleOption option = styleOption(); - return option.iconSize; -} - -void PlacesView::resizeEvent(QGraphicsSceneResizeEvent *event) -{ - KStandardItemListView::resizeEvent(event); - - header()->setColumnWidth(QByteArrayLiteral("text"), event->newSize().width()); -} diff --git a/src/panels/places/placesview.h b/src/panels/places/placesview.h deleted file mode 100644 index 86515f56b3..0000000000 --- a/src/panels/places/placesview.h +++ /dev/null @@ -1,31 +0,0 @@ -/* - * SPDX-FileCopyrightText: 2012 Frank Reininghaus - * - * SPDX-License-Identifier: GPL-2.0-or-later - */ - -#ifndef PLACESVIEW_H -#define PLACESVIEW_H - -#include "kitemviews/kstandarditemlistview.h" - -/** - * @brief View class for the Places Panel. - * - * Reads the icon size from GeneralSettings::placesPanelIconSize(). - */ -class PlacesView : public KStandardItemListView -{ - Q_OBJECT - -public: - explicit PlacesView(QGraphicsWidget* parent = nullptr); - - void setIconSize(int size); - int iconSize() const; - -protected: - void resizeEvent(QGraphicsSceneResizeEvent *event) override; -}; - -#endif diff --git a/src/search/dolphinsearchbox.cpp b/src/search/dolphinsearchbox.cpp index 9143ddcb77..860d9f6cd5 100644 --- a/src/search/dolphinsearchbox.cpp +++ b/src/search/dolphinsearchbox.cpp @@ -9,8 +9,8 @@ #include "dolphin_searchsettings.h" #include "dolphinfacetswidget.h" +#include "dolphinplacesmodelsingleton.h" #include "dolphinquery.h" -#include "panels/places/placesitemmodel.h" #include #include @@ -288,11 +288,8 @@ void DolphinSearchBox::slotSearchSaved() { const QUrl searchURL = urlForSearching(); if (searchURL.isValid()) { - PlacesItemModel model; const QString label = i18n("Search for %1 in %2", text(), searchPath().fileName()); - model.createPlacesItem(label, - searchURL, - QStringLiteral("folder-saved-search-symbolic")); + DolphinPlacesModelSingleton::instance().placesModel()->addPlace(label, searchURL, QStringLiteral("folder-saved-search-symbolic")); } } diff --git a/src/tests/CMakeLists.txt b/src/tests/CMakeLists.txt index e9a0e2dced..95ba018618 100644 --- a/src/tests/CMakeLists.txt +++ b/src/tests/CMakeLists.txt @@ -69,11 +69,6 @@ LINK_LIBRARIES dolphinprivate dolphinstatic Qt5::Test) # DragAndDropHelperTest ecm_add_test(draganddrophelpertest.cpp LINK_LIBRARIES dolphinprivate Qt5::Test) -# PlacesItemModelTest -ecm_add_test(placesitemmodeltest.cpp -TEST_NAME placesitemmodeltest -LINK_LIBRARIES dolphinprivate dolphinstatic Qt5::Test) - find_gem(test-unit) set_package_properties(Gem:test-unit PROPERTIES TYPE RECOMMENDED diff --git a/src/tests/placesitemmodeltest.cpp b/src/tests/placesitemmodeltest.cpp deleted file mode 100644 index 15a494691d..0000000000 --- a/src/tests/placesitemmodeltest.cpp +++ /dev/null @@ -1,939 +0,0 @@ -/* - * SPDX-FileCopyrightText: 2017 Renato Araujo Oliveira - * - * SPDX-License-Identifier: GPL-2.0-or-later - */ - -#include -#include -#include -#include -#include - -#include -#include -#include -#include -#include -#include - -#include "dolphin_generalsettings.h" -#include "panels/places/placesitemmodel.h" -#include "panels/places/placesitem.h" -#include "views/viewproperties.h" - -Q_DECLARE_METATYPE(KItemRangeList) -Q_DECLARE_METATYPE(KItemRange) - -static QString bookmarksFile() -{ - return QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + "/user-places.xbel"; -} - -class PlacesItemModelTest : public QObject -{ - Q_OBJECT - -private Q_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; - int m_expectedModelCount = 14; - bool m_hasDesktopFolder = false; - bool m_hasDocumentsFolder = false; - bool m_hasDownloadsFolder = false; - bool m_hasMusicFolder = false; - bool m_hasPicturesFolder = false; - bool m_hasVideosFolder = false; - - 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 schedulePlaceRemoval(int index); - void cancelPlaceRemoval(int index); - QMimeData *createMimeData(const QList &indexes) const; - void increaseIndexIfNeeded(int &index) const; - QTemporaryDir m_tempHomeDir; -}; - -#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(); - - if (m_hasDesktopFolder) { - urls << QDir::homePath() + QStringLiteral("/Desktop"); - } - - if (m_hasDocumentsFolder) { - urls << QDir::homePath() + QStringLiteral("/Documents"); - } - - if (m_hasDownloadsFolder) { - urls << QDir::homePath() + QStringLiteral("/Downloads"); - } - - if (m_hasMusicFolder) { - urls << QDir::homePath() + QStringLiteral("/Music"); - } - - if (m_hasPicturesFolder) { - urls << QDir::homePath() + QStringLiteral("/Pictures"); - } - - if (m_hasVideosFolder) { - urls << QDir::homePath() + QStringLiteral("/Videos"); - } - - urls << QStringLiteral("trash:/") - << QStringLiteral("remote:/") - << QStringLiteral("/media/nfs"); - - if (qEnvironmentVariableIsSet("KDE_FULL_SESSION") && KProtocolInfo::isKnownProtocol(QStringLiteral("recentlyused"))) { - urls << QStringLiteral("recentlyused:/files"); - urls << QStringLiteral("recentlyused:/locations"); - } else { - urls << QStringLiteral("timeline:/today") - << QStringLiteral("timeline:/yesterday"); - } - - urls << QStringLiteral("search:/documents") << QStringLiteral("search:/images") << QStringLiteral("search:/audio") << QStringLiteral("search:/videos") - << 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::schedulePlaceRemoval(int index) -{ - m_tobeRemoved.insert(index); -} - -void PlacesItemModelTest::cancelPlaceRemoval(int index) -{ - m_tobeRemoved.remove(index); -} - -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::increaseIndexIfNeeded(int &index) const -{ - if (m_hasDesktopFolder) { - index++; - } - if (m_hasDocumentsFolder) { - index++; - } - if (m_hasDownloadsFolder) { - index++; - } - if (m_hasMusicFolder) { - index++; - } - if (m_hasPicturesFolder) { - index++; - } - if (m_hasVideosFolder) { - index++; - } -} - -void PlacesItemModelTest::init() -{ - m_model = new PlacesItemModel(); - // WORKAROUND: need to wait for bookmark to load - QTest::qWait(300); - QCOMPARE(m_model->count(), m_expectedModelCount); -} - -void PlacesItemModelTest::cleanup() -{ - const auto tobeRemoved = m_tobeRemoved; - for (const int i : 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; -} - -void PlacesItemModelTest::initTestCase() -{ - QVERIFY(m_tempHomeDir.isValid()); - QVERIFY(qputenv("HOME", m_tempHomeDir.path().toUtf8())); - QVERIFY(qputenv("KDE_FORK_SLAVES", "yes")); - - QStandardPaths::setTestModeEnabled(true); - - 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)); - } - - if (QDir(QStandardPaths::writableLocation(QStandardPaths::DesktopLocation)).exists()) { - m_hasDesktopFolder = true; - m_expectedModelCount++; - } - - if (QDir(QStandardPaths::writableLocation(QStandardPaths::DocumentsLocation)).exists()) { - m_hasDocumentsFolder = true; - m_expectedModelCount++; - } - - if (QDir(QStandardPaths::writableLocation(QStandardPaths::DownloadLocation)).exists()) { - m_hasDownloadsFolder = true; - m_expectedModelCount++; - } - - if (QDir(QStandardPaths::writableLocation(QStandardPaths::MusicLocation)).exists()) { - m_hasMusicFolder = true; - m_expectedModelCount++; - } - - if (QDir(QStandardPaths::writableLocation(QStandardPaths::PicturesLocation)).exists()) { - m_hasPicturesFolder = true; - m_expectedModelCount++; - } - - if (QDir(QStandardPaths::writableLocation(QStandardPaths::MoviesLocation)).exists()) { - m_hasVideosFolder = true; - m_expectedModelCount++; - } - - qRegisterMetaType(); - qRegisterMetaType(); -} - -void PlacesItemModelTest::cleanupTestCase() -{ - qDeleteAll(m_interfacesMap); - QFile::remove(bookmarksFile()); -} - -void PlacesItemModelTest::testModelSort() -{ - CHECK_PLACES_URLS(initialUrls()); -} - -void PlacesItemModelTest::testGroups() -{ - const auto groups = m_model->groups(); - int expectedRemoteIndex = 2; - increaseIndexIfNeeded(expectedRemoteIndex); - - QCOMPARE(groups.size(), 6); - - QCOMPARE(groups.at(0).first, 0); - QCOMPARE(groups.at(0).second.toString(), QStringLiteral("Places")); - - QCOMPARE(groups.at(1).first, expectedRemoteIndex); - QCOMPARE(groups.at(1).second.toString(), QStringLiteral("Remote")); - - QCOMPARE(groups.at(2).first, expectedRemoteIndex + 2); - QCOMPARE(groups.at(2).second.toString(), QStringLiteral("Recent")); - - QCOMPARE(groups.at(3).first, expectedRemoteIndex + 4); - QCOMPARE(groups.at(3).second.toString(), QStringLiteral("Search For")); - - QCOMPARE(groups.at(4).first, expectedRemoteIndex + 8); - QCOMPARE(groups.at(4).second.toString(), QStringLiteral("Devices")); - - QCOMPARE(groups.at(5).first, expectedRemoteIndex + 9); - 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; - - // 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(); - - int tempDirIndex = 2; - increaseIndexIfNeeded(tempDirIndex); - - // create a new place - createPlaceItem(QStringLiteral("Temporary Dir"), tempUrl, QString()); - urls.insert(tempDirIndex, 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(tempDirIndex); - - // make sure that the new item is removed - QTRY_COMPARE(itemsRemovedSpy.count(), 1); - QTRY_COMPARE(m_model->count(), m_expectedModelCount); - 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(), m_expectedModelCount); - - QSignalSpy spyItemsRemoved(m_model, &PlacesItemModel::itemsRemoved); - fakeManager()->call(QStringLiteral("unplug"), "/org/kde/solid/fakehw/volume_part1_size_993284096"); - QTRY_COMPARE(m_model->count(), m_expectedModelCount - 1); - 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(), m_expectedModelCount); - 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"}); - - // 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); - - // In order to test the default view properties, turn off the global view properties and re-init the test to reload the model. - GeneralSettings* settings = GeneralSettings::self(); - settings->setGlobalViewProps(false); - settings->save(); - cleanup(); - init(); - - ViewProperties properties(KFilePlacesModel::convertedUrl(url)); - QCOMPARE(properties.viewMode(), expectedViewMode); - QCOMPARE(properties.previewsShown(), expectedPreviewShow); - QCOMPARE(properties.visibleRoles(), expectedVisibleRole); - - settings->setGlobalViewProps(true); - settings->save(); -} - -void PlacesItemModelTest::testClear() -{ - QCOMPARE(m_model->count(), m_expectedModelCount); - m_model->clear(); - QCOMPARE(m_model->count(), 0); - QCOMPARE(m_model->hiddenCount(), 0); - m_model->refresh(); - QTRY_COMPARE(m_model->count(), m_expectedModelCount); -} - -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(), m_expectedModelCount - 1); - 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(), m_expectedModelCount); -} - -void PlacesItemModelTest::testSystemItems() -{ - int tempDirIndex = 2; - increaseIndexIfNeeded(tempDirIndex); - - QCOMPARE(m_model->count(), m_expectedModelCount); - 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 - schedulePlaceRemoval(tempDirIndex); - - QList args = itemsInsertedSpy.takeFirst(); - KItemRangeList range = args.at(0).value(); - QCOMPARE(range.first().index, tempDirIndex); - QCOMPARE(range.first().count, 1); - QVERIFY(!m_model->placesItem(tempDirIndex)->isSystemItem()); - QCOMPARE(m_model->count(), m_expectedModelCount + 1); - - QTest::qWait(300); - // check if the removal signal is correct - QSignalSpy itemsRemovedSpy(m_model, &PlacesItemModel::itemsRemoved); - m_model->deleteItem(tempDirIndex); - QTRY_COMPARE(itemsRemovedSpy.count(), 1); - args = itemsRemovedSpy.takeFirst(); - range = args.at(0).value(); - QCOMPARE(range.first().index, tempDirIndex); - QCOMPARE(range.first().count, 1); - QTRY_COMPARE(m_model->count(), m_expectedModelCount); - - //cancel removal (it was removed above) - cancelPlaceRemoval(tempDirIndex); -} - -void PlacesItemModelTest::testEditBookmark() -{ - int tempDirIndex = 2; - increaseIndexIfNeeded(tempDirIndex); - - 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 - schedulePlaceRemoval(tempDirIndex); - - QSignalSpy itemsChangedSply(m_model, &PlacesItemModel::itemsChanged); - - // modify place text - m_model->item(tempDirIndex)->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, tempDirIndex); - QCOMPARE(range.first().count, 1); - QSet roles = args.at(1).value >(); - QCOMPARE(roles.size(), 1); - QCOMPARE(*roles.begin(), QByteArrayLiteral("text")); - QCOMPARE(m_model->item(tempDirIndex)->text(), QStringLiteral("Renamed place")); - - // check if the item was updated in the other model - QTRY_COMPARE(other->item(tempDirIndex)->text(), QStringLiteral("Renamed place")); -} - -void PlacesItemModelTest::testEditAfterCreation() -{ - int tempDirIndex = 2; - increaseIndexIfNeeded(tempDirIndex); - - 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 - schedulePlaceRemoval(tempDirIndex); - - // modify place text - PlacesItem *item = m_model->placesItem(tempDirIndex); - 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(tempDirIndex)->text(), m_model->placesItem(tempDirIndex)->text()); - QTRY_COMPARE(model->placesItem(tempDirIndex)->bookmark().metaDataItem(QStringLiteral("OnlyInApp")), - m_model->placesItem(tempDirIndex)->bookmark().metaDataItem(QStringLiteral("OnlyInApp"))); - QTRY_COMPARE(model->placesItem(tempDirIndex)->icon(), m_model->placesItem(tempDirIndex)->icon()); - QTRY_COMPARE(model->placesItem(tempDirIndex)->url(), m_model->placesItem(tempDirIndex)->url()); -} - -void PlacesItemModelTest::testEditMetadata() -{ - int tempDirIndex = 2; - increaseIndexIfNeeded(tempDirIndex); - - 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 - schedulePlaceRemoval(tempDirIndex); - - // modify place metadata - auto bookmark = m_model->placesItem(tempDirIndex)->bookmark(); - bookmark.setMetaDataItem(QStringLiteral("OnlyInApp"), KAboutData::applicationData().componentName()); - m_model->refresh(); - - // check if the place was modified in both models - QTRY_COMPARE(model->placesItem(tempDirIndex)->bookmark().metaDataItem(QStringLiteral("OnlyInApp")), - KAboutData::applicationData().componentName()); - QTRY_COMPARE(model->placesItem(tempDirIndex)->text(), m_model->placesItem(tempDirIndex)->text()); - QTRY_COMPARE(model->placesItem(tempDirIndex)->bookmark().metaDataItem(QStringLiteral("OnlyInApp")), - m_model->placesItem(tempDirIndex)->bookmark().metaDataItem(QStringLiteral("OnlyInApp"))); - QTRY_COMPARE(model->placesItem(tempDirIndex)->icon(), m_model->placesItem(tempDirIndex)->icon()); - QTRY_COMPARE(model->placesItem(tempDirIndex)->url(), m_model->placesItem(tempDirIndex)->url()); -} - -void PlacesItemModelTest::testRefresh() -{ - int tempDirIndex = 2; - increaseIndexIfNeeded(tempDirIndex); - - 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 - schedulePlaceRemoval(tempDirIndex); - - PlacesItem *item = m_model->placesItem(tempDirIndex); - PlacesItem *sameItem = model->placesItem(tempDirIndex); - 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"); - - // 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() -{ - int lastIndex = 1; // last index of places group - increaseIndexIfNeeded(lastIndex); - - 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 home directory to the end of the places group - QMimeData *dropData = createMimeData(QList() << 0); - m_model->dropMimeDataBefore(m_model->count() - 1, dropData); - urls.move(0, lastIndex); - 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, 0); - - // 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, lastIndex); - - CHECK_PLACES_URLS(urls); - - itemsInsertedSpy.clear(); - itemsRemovedSpy.clear(); - - // Move home directory item back to its original position - dropData = createMimeData(QList() << lastIndex); - m_model->dropMimeDataBefore(0, dropData); - urls.move(lastIndex, 0); - 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, lastIndex); - - // 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, 0); - - 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() -{ - int tempDirIndex = 2; - increaseIndexIfNeeded(tempDirIndex); - - 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(tempDirIndex, tempUrl.toLocalFile()); - - // make sure that the new item will be removed later - schedulePlaceRemoval(tempDirIndex); - - CHECK_PLACES_URLS(urls); - QCOMPARE(model->count(), m_model->count()); - - - // modify place text - QSignalSpy changedSpy(m_model, &PlacesItemModel::itemsChanged); - - PlacesItem *item = m_model->placesItem(tempDirIndex); - 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(tempDirIndex)->text(), QStringLiteral("New Temporary Dir")); - QTRY_COMPARE(model->placesItem(tempDirIndex)->text(), QStringLiteral("New Temporary Dir")); -} - -QTEST_MAIN(PlacesItemModelTest) - -#include "placesitemmodeltest.moc"