mirror of
https://invent.kde.org/system/dolphin
synced 2024-07-04 17:30:55 +00:00
Port back to KFilePlacesView
This removes the custom-view engine version of the places panel and replaces it with the upstream `KFilePlacesView` from KIO.
This commit is contained in:
parent
3abc4cfcd4
commit
0603e18cd4
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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);
|
||||
});
|
||||
|
||||
connect(m_placesPanel, &PlacesPanel::showHiddenEntriesChanged, this, [actionShowAllPlaces] (bool checked){
|
||||
actionShowAllPlaces->setChecked(checked);
|
||||
actionShowAllPlaces->setIcon(QIcon::fromTheme(checked ? QStringLiteral("view-visible") : QStringLiteral("view-hidden")));
|
||||
m_placesPanel->setShowAll(checked);
|
||||
});
|
||||
connect(m_placesPanel, &PlacesPanel::allPlacesShownChanged, actionShowAllPlaces, &QAction::setChecked);
|
||||
|
||||
actionCollection()->action(QStringLiteral("show_places_panel"))
|
||||
->setWhatsThis(xi18nc("@info:whatsthis", "<para>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());
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -5,12 +5,61 @@
|
|||
*/
|
||||
|
||||
#include "dolphinplacesmodelsingleton.h"
|
||||
#include "trash/dolphintrash.h"
|
||||
|
||||
#include <KAboutData>
|
||||
#include <KFilePlacesModel>
|
||||
|
||||
#include <QIcon>
|
||||
|
||||
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()))
|
||||
{
|
||||
|
||||
}
|
||||
|
|
|
@ -10,7 +10,33 @@
|
|||
#include <QString>
|
||||
#include <QScopedPointer>
|
||||
|
||||
class KFilePlacesModel;
|
||||
#include <KFilePlacesModel>
|
||||
|
||||
/**
|
||||
* @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.
|
||||
|
|
|
@ -8,7 +8,7 @@
|
|||
<kcfgfile name="dolphinrc"/>
|
||||
<group name="PlacesPanel">
|
||||
<entry name="IconSize" type="Int">
|
||||
<label>Size of icons in the Places Panel (-1 means "use the style's small size")</label>
|
||||
<label>Size of icons in the Places Panel (-1 means "automatic")</label>
|
||||
<default code="true">KIconLoader::SizeSmallMedium</default>
|
||||
</entry>
|
||||
</group>
|
||||
|
|
|
@ -1,274 +0,0 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: 2012 Peter Penz <peter.penz19@gmail.com>
|
||||
* SPDX-FileCopyrightText: 2018 Elvis Angelaccio <elvis.angelaccio@kde.org>
|
||||
*
|
||||
* Based on KFilePlacesItem from kdelibs:
|
||||
* SPDX-FileCopyrightText: 2007 Kevin Ottens <ervin@kde.org>
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later
|
||||
*/
|
||||
|
||||
#include "placesitem.h"
|
||||
#include "trash/dolphintrash.h"
|
||||
|
||||
#include "dolphindebug.h"
|
||||
#include "placesitemsignalhandler.h"
|
||||
|
||||
#include <KDirLister>
|
||||
#include <KLocalizedString>
|
||||
#include <Solid/Block>
|
||||
|
||||
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<QByteArray, QVariant>& current,
|
||||
const QHash<QByteArray, QVariant>& previous)
|
||||
{
|
||||
Q_UNUSED(previous)
|
||||
|
||||
if (!m_bookmark.isNull()) {
|
||||
QHashIterator<QByteArray, QVariant> 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<Solid::StorageAccess>();
|
||||
m_volume = m_device.as<Solid::StorageVolume>();
|
||||
m_disc = m_device.as<Solid::OpticalDisc>();
|
||||
m_player = m_device.as<Solid::PortableMediaPlayer>();
|
||||
|
||||
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<Solid::Block>();
|
||||
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();
|
||||
}
|
|
@ -1,105 +0,0 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: 2012 Peter Penz <peter.penz19@gmail.com>
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later
|
||||
*/
|
||||
|
||||
#ifndef PLACESITEM_H
|
||||
#define PLACESITEM_H
|
||||
|
||||
#include "kitemviews/kstandarditem.h"
|
||||
|
||||
#include <KBookmark>
|
||||
#include <Solid/Device>
|
||||
#include <Solid/OpticalDisc>
|
||||
#include <Solid/PortableMediaPlayer>
|
||||
#include <Solid/StorageAccess>
|
||||
#include <Solid/StorageVolume>
|
||||
|
||||
#include <QPointer>
|
||||
#include <QUrl>
|
||||
|
||||
|
||||
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<QByteArray, QVariant>& current,
|
||||
const QHash<QByteArray, QVariant>& 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<Solid::StorageAccess> m_access;
|
||||
QPointer<Solid::StorageVolume> m_volume;
|
||||
QPointer<Solid::OpticalDisc> m_disc;
|
||||
QPointer<Solid::PortableMediaPlayer> m_player;
|
||||
QPointer<PlacesItemSignalHandler> m_signalHandler;
|
||||
KBookmark m_bookmark;
|
||||
|
||||
friend class PlacesItemSignalHandler; // Calls onAccessibilityChanged()
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
|
|
@ -1,30 +0,0 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: 2012 Peter Penz <peter.penz19@gmail.com>
|
||||
*
|
||||
* 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;
|
||||
}
|
||||
|
|
@ -1,27 +0,0 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: 2012 Peter Penz <peter.penz19@gmail.com>
|
||||
*
|
||||
* 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
|
||||
|
||||
|
|
@ -1,146 +0,0 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: 2012 Peter Penz <peter.penz19@gmail.com>
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later
|
||||
*/
|
||||
|
||||
#include "placesitemlistwidget.h"
|
||||
|
||||
#include <QStyleOption>
|
||||
|
||||
#include <KColorScheme>
|
||||
|
||||
#include <Solid/Device>
|
||||
#include <Solid/NetworkShare>
|
||||
|
||||
#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();
|
||||
}
|
|
@ -1,61 +0,0 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: 2012 Peter Penz <peter.penz19@gmail.com>
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later
|
||||
*/
|
||||
|
||||
#ifndef PLACESITEMLISTWIDGET_H
|
||||
#define PLACESITEMLISTWIDGET_H
|
||||
|
||||
#include "kitemviews/kstandarditemlistwidget.h"
|
||||
|
||||
#include <QDeadlineTimer>
|
||||
#include <QPainter>
|
||||
#include <QPointer>
|
||||
#include <QStyleOptionGraphicsItem>
|
||||
#include <QWidget>
|
||||
|
||||
#include <KIO/FileSystemFreeSpaceJob>
|
||||
|
||||
|
||||
// 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<KIO::FileSystemFreeSpaceJob> 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
|
||||
|
||||
|
|
@ -1,784 +0,0 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: 2012 Peter Penz <peter.penz19@gmail.com>
|
||||
*
|
||||
* Based on KFilePlacesModel from kdelibs:
|
||||
* SPDX-FileCopyrightText: 2007 Kevin Ottens <ervin@kde.org>
|
||||
* SPDX-FileCopyrightText: 2007 David Faure <faure@kde.org>
|
||||
*
|
||||
* 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 <KAboutData>
|
||||
#include <KLocalizedString>
|
||||
#include <KUrlMimeData>
|
||||
#include <Solid/DeviceNotifier>
|
||||
#include <Solid/OpticalDrive>
|
||||
#include <KCoreAddons/KProcessList>
|
||||
#include <KCoreAddons/KListOpenFilesJob>
|
||||
|
||||
#include <QAction>
|
||||
#include <QIcon>
|
||||
#include <QMimeData>
|
||||
#include <QTimer>
|
||||
|
||||
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<PlacesItem*>(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<QByteArray>& 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<Solid::OpticalDisc>()) {
|
||||
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<Solid::StorageAccess>() &&
|
||||
device.as<Solid::StorageAccess>()->isAccessible();
|
||||
if (!providesTearDown) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
Solid::StorageDrive* drive = device.as<Solid::StorageDrive>();
|
||||
if (!drive) {
|
||||
drive = device.parent().as<Solid::StorageDrive>();
|
||||
}
|
||||
|
||||
bool hotPluggable = false;
|
||||
bool removable = false;
|
||||
if (drive) {
|
||||
hotPluggable = drive->isHotpluggable();
|
||||
removable = drive->isRemovable();
|
||||
}
|
||||
|
||||
QString iconName;
|
||||
QString text;
|
||||
if (device.is<Solid::OpticalDisc>()) {
|
||||
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<Solid::OpticalDrive>();
|
||||
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<Solid::StorageAccess>();
|
||||
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<Solid::StorageAccess>()
|
||||
&& !m_storageSetupInProgress.contains(device.as<Solid::StorageAccess>())
|
||||
&& !device.as<Solid::StorageAccess>()->isAccessible();
|
||||
if (setup) {
|
||||
Solid::StorageAccess* access = device.as<Solid::StorageAccess>();
|
||||
|
||||
m_storageSetupInProgress[access] = index;
|
||||
|
||||
connect(access, &Solid::StorageAccess::setupDone,
|
||||
this, &PlacesItemModel::slotStorageSetupDone);
|
||||
|
||||
access->setup();
|
||||
}
|
||||
}
|
||||
|
||||
QMimeData* PlacesItemModel::createMimeData(const KItemSet& indexes) const
|
||||
{
|
||||
QList<QUrl> 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<QUrl> 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 <application>\"%2\"</application>.",
|
||||
"One or more files on this device are opened in following applications: <application>%2</application>.",
|
||||
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<int> &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<QUrl> 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;
|
||||
}
|
||||
|
|
@ -1,216 +0,0 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: 2012 Peter Penz <peter.penz19@gmail.com>
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later
|
||||
*/
|
||||
|
||||
#ifndef PLACESITEMMODEL_H
|
||||
#define PLACESITEMMODEL_H
|
||||
|
||||
#include "kitemviews/kstandarditemmodel.h"
|
||||
|
||||
#include <KFilePlacesModel>
|
||||
#include <Solid/Predicate>
|
||||
#include <Solid/StorageAccess>
|
||||
|
||||
#include <QHash>
|
||||
#include <QList>
|
||||
#include <QSet>
|
||||
#include <QUrl>
|
||||
|
||||
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<QByteArray>& 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<int> &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<QObject*, int> m_storageSetupInProgress;
|
||||
|
||||
KFilePlacesModel *m_sourceModel;
|
||||
|
||||
QVector<QPersistentModelIndex> m_indexMap;
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
|
|
@ -1,46 +0,0 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: 2012 Peter Penz <peter.penz19@gmail.com>
|
||||
*
|
||||
* 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<Solid::StorageAccess>();
|
||||
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"));
|
||||
}
|
||||
}
|
||||
|
|
@ -1,57 +0,0 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: 2012 Peter Penz <peter.penz19@gmail.com>
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later
|
||||
*/
|
||||
|
||||
#ifndef PLACESITEMSIGNALHANDLER_H
|
||||
#define PLACESITEMSIGNALHANDLER_H
|
||||
|
||||
#include <QObject>
|
||||
|
||||
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:
|
||||
* <code>
|
||||
* QObject::connect(storageAccess, SIGNAL(accessibilityChanged(bool,QString)),
|
||||
* signalHandler, SLOT(onAccessibilityChanged()));
|
||||
* </code>
|
||||
*
|
||||
* 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
|
|
@ -1,5 +1,6 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: 2008-2012 Peter Penz <peter.penz19@gmail.com>
|
||||
* SPDX-FileCopyrightText: 2021 Kai Uwe Broulik <kde@broulik.de>
|
||||
*
|
||||
* Based on KFilePlacesView from kdelibs:
|
||||
* SPDX-FileCopyrightText: 2007 Kevin Ottens <ervin@kde.org>
|
||||
|
@ -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 <KFilePlaceEditDialog>
|
||||
#include <KFilePlacesModel>
|
||||
#include <KIO/DropJob>
|
||||
#include <KIO/EmptyTrashJob>
|
||||
#include <KIO/Job>
|
||||
#include <KIconLoader>
|
||||
#include <KListOpenFilesJob>
|
||||
#include <KLocalizedString>
|
||||
#include <KMountPoint>
|
||||
#include <KPropertiesDialog>
|
||||
|
||||
#include <QActionGroup>
|
||||
#include <QApplication>
|
||||
#include <QGraphicsSceneDragDropEvent>
|
||||
#include <QIcon>
|
||||
#include <QMenu>
|
||||
#include <QMimeData>
|
||||
#include <QVBoxLayout>
|
||||
#include <QToolTip>
|
||||
#include <QShowEvent>
|
||||
#include <QTimer>
|
||||
|
||||
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 <Solid/StorageAccess>
|
||||
|
||||
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<QAction*> PlacesPanel::customContextMenuActions() const
|
||||
{
|
||||
return m_customContextMenuActions;
|
||||
}
|
||||
|
||||
void PlacesPanel::setCustomContextMenuActions(const QList<QAction *> &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<PlacesItemListWidget>());
|
||||
m_view->setGroupHeaderCreator(new KItemListGroupHeaderCreator<PlacesItemListGroupHeader>());
|
||||
|
||||
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()));
|
||||
}
|
||||
|
||||
Panel::showEvent(event);
|
||||
}
|
||||
|
||||
bool PlacesPanel::eventFilter(QObject * /* obj */, QEvent *event)
|
||||
{
|
||||
if (event->type() == QEvent::ToolTip) {
|
||||
|
||||
QHelpEvent *hoverEvent = reinterpret_cast<QHelpEvent *>(event);
|
||||
|
||||
m_hoveredIndex = m_view->itemAt(hoverEvent->pos());
|
||||
m_hoverPos = mapToGlobal(hoverEvent->pos());
|
||||
|
||||
m_tooltipTimer.start();
|
||||
return true;
|
||||
setUrl(m_url);
|
||||
}
|
||||
return false;
|
||||
|
||||
KFilePlacesView::showEvent(event);
|
||||
}
|
||||
|
||||
void PlacesPanel::slotItemActivated(int index)
|
||||
void PlacesPanel::dragMoveEvent(QDragMoveEvent *event)
|
||||
{
|
||||
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);
|
||||
}
|
||||
}
|
||||
KFilePlacesView::dragMoveEvent(event);
|
||||
|
||||
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();
|
||||
const QModelIndex index = indexAt(event->pos());
|
||||
if (!index.isValid()) {
|
||||
return;
|
||||
}
|
||||
|
||||
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();
|
||||
QPersistentModelIndex persistentIndex(index);
|
||||
if (!m_pendingDragActivation.isValid() || m_pendingDragActivation != persistentIndex) {
|
||||
m_pendingDragActivation = persistentIndex;
|
||||
m_dragActivationTimer->start();
|
||||
}
|
||||
}
|
||||
|
||||
if (isDevice) {
|
||||
ejectAction = m_model->ejectAction(index);
|
||||
if (ejectAction) {
|
||||
ejectAction->setParent(&menu);
|
||||
menu.addAction(ejectAction);
|
||||
}
|
||||
void PlacesPanel::dragLeaveEvent(QDragLeaveEvent *event)
|
||||
{
|
||||
KFilePlacesView::dragLeaveEvent(event);
|
||||
|
||||
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;
|
||||
if (m_dragActivationTimer) {
|
||||
m_dragActivationTimer->stop();
|
||||
m_pendingDragActivation = QPersistentModelIndex();
|
||||
}
|
||||
}
|
||||
teardownAction->setEnabled(teardownEnabled);
|
||||
}
|
||||
|
||||
teardownAction->setParent(&menu);
|
||||
menu.addAction(teardownAction);
|
||||
}
|
||||
void PlacesPanel::slotConfigureTrash()
|
||||
{
|
||||
const QUrl url = currentIndex().data(KFilePlacesModel::UrlRole).toUrl();
|
||||
|
||||
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);
|
||||
DolphinSettingsDialog* settingsDialog = new DolphinSettingsDialog(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.
|
||||
}
|
||||
|
||||
void PlacesPanel::slotDragActivationTimeout()
|
||||
{
|
||||
if (!m_pendingDragActivation.isValid()) {
|
||||
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<QAction*, int> 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) {
|
||||
return;
|
||||
}
|
||||
|
||||
const PlacesItem* destItem = m_model->placesItem(index);
|
||||
|
||||
if (destItem->isSearchOrTimelineUrl()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (m_model->storageSetupNeeded(index)) {
|
||||
connect(m_model, &PlacesItemModel::storageSetupDone,
|
||||
this, &PlacesPanel::slotItemDropEventStorageSetupDone);
|
||||
|
||||
m_itemDropEventIndex = index;
|
||||
|
||||
// Make a full copy of the Mime-Data
|
||||
m_itemDropEventMimeData = new QMimeData;
|
||||
m_itemDropEventMimeData->setText(event->mimeData()->text());
|
||||
m_itemDropEventMimeData->setHtml(event->mimeData()->html());
|
||||
m_itemDropEventMimeData->setUrls(event->mimeData()->urls());
|
||||
m_itemDropEventMimeData->setImageData(event->mimeData()->imageData());
|
||||
m_itemDropEventMimeData->setColorData(event->mimeData()->colorData());
|
||||
|
||||
m_itemDropEvent = new QDropEvent(event->pos().toPoint(),
|
||||
event->possibleActions(),
|
||||
m_itemDropEventMimeData,
|
||||
event->buttons(),
|
||||
event->modifiers());
|
||||
|
||||
m_model->requestStorageSetup(index);
|
||||
return;
|
||||
}
|
||||
|
||||
QUrl destUrl = destItem->url();
|
||||
QDropEvent dropEvent(event->pos().toPoint(),
|
||||
event->possibleActions(),
|
||||
event->mimeData(),
|
||||
event->buttons(),
|
||||
event->modifiers());
|
||||
|
||||
slotUrlsDropped(destUrl, &dropEvent, this);
|
||||
}
|
||||
|
||||
void PlacesPanel::slotItemDropEventStorageSetupDone(int index, bool success)
|
||||
{
|
||||
disconnect(m_model, &PlacesItemModel::storageSetupDone,
|
||||
this, &PlacesPanel::slotItemDropEventStorageSetupDone);
|
||||
|
||||
if ((index == m_itemDropEventIndex) && m_itemDropEvent && m_itemDropEventMimeData) {
|
||||
if (success) {
|
||||
QUrl destUrl = m_model->placesItem(index)->url();
|
||||
slotUrlsDropped(destUrl, m_itemDropEvent, this);
|
||||
}
|
||||
|
||||
delete m_itemDropEventMimeData;
|
||||
delete m_itemDropEvent;
|
||||
|
||||
m_itemDropEventIndex = -1;
|
||||
m_itemDropEventMimeData = nullptr;
|
||||
m_itemDropEvent = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
void PlacesPanel::slotAboveItemDropEvent(int index, QGraphicsSceneDragDropEvent* event)
|
||||
{
|
||||
m_model->dropMimeDataBefore(index, event->mimeData());
|
||||
auto *placesModel = static_cast<KFilePlacesModel *>(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) {
|
||||
auto *placesModel = static_cast<KFilePlacesModel *>(model());
|
||||
const QUrl url = placesModel->url(index);
|
||||
const Solid::Device device = placesModel->deviceForIndex(index);
|
||||
|
||||
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 {
|
||||
const auto actions = this->actions();
|
||||
for (QAction *action : actions) {
|
||||
if (m_customContextMenuActions.contains(action)) {
|
||||
removeAction(action);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void PlacesPanel::slotTearDownRequested(const QModelIndex &index)
|
||||
{
|
||||
auto *placesModel = static_cast<KFilePlacesModel *>(model());
|
||||
|
||||
Solid::StorageAccess *storageAccess = placesModel->deviceForIndex(index).as<Solid::StorageAccess>();
|
||||
if (!storageAccess) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (success) {
|
||||
Q_ASSERT(!m_model->storageSetupNeeded(index));
|
||||
triggerItem(index, m_triggerStorageSetupModifier);
|
||||
m_triggerStorageSetupModifier = TriggerItemModifier::None;
|
||||
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());
|
||||
}
|
||||
|
||||
void PlacesPanel::slotTearDownRequestedExternally(const QString &udi)
|
||||
{
|
||||
Q_UNUSED(udi);
|
||||
auto *storageAccess = static_cast<Solid::StorageAccess*>(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 {
|
||||
setUrl(m_storageSetupFailedUrl);
|
||||
m_storageSetupFailedUrl = QUrl();
|
||||
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 <application>\"%2\"</application>.",
|
||||
"One or more files on this device are opened in following applications: <application>%2</application>.",
|
||||
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::slotShowTooltip()
|
||||
void PlacesPanel::slotRowsAboutToBeRemoved(const QModelIndex &parent, int first, int last)
|
||||
{
|
||||
const QUrl url = m_model->data(m_hoveredIndex.value_or(-1)).value("url").value<QUrl>();
|
||||
const QString text = url.toDisplayString(QUrl::PreferLocalFile);
|
||||
QToolTip::showText(m_hoverPos, text);
|
||||
}
|
||||
auto *placesModel = static_cast<KFilePlacesModel *>(model());
|
||||
|
||||
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();
|
||||
for (int i = first; i <= last; ++i) {
|
||||
const QModelIndex index = placesModel->index(i, 0, parent);
|
||||
|
||||
QPointer<KFilePlaceEditDialog> 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);
|
||||
Solid::StorageAccess *storageAccess = placesModel->deviceForIndex(index).as<Solid::StorageAccess>();
|
||||
if (!storageAccess) {
|
||||
continue;
|
||||
}
|
||||
|
||||
delete dialog;
|
||||
}
|
||||
|
||||
void PlacesPanel::editEntry(int index)
|
||||
{
|
||||
QHash<QByteArray, QVariant> 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<KFilePlaceEditDialog> 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);
|
||||
disconnect(storageAccess, &Solid::StorageAccess::teardownRequested, this, nullptr);
|
||||
}
|
||||
}
|
||||
|
||||
void PlacesPanel::triggerItem(int index, TriggerItemModifier modifier)
|
||||
void PlacesPanel::connectDeviceSignals(const QModelIndex &index)
|
||||
{
|
||||
const PlacesItem* item = m_model->placesItem(index);
|
||||
if (!item) {
|
||||
auto *placesModel = static_cast<KFilePlacesModel *>(model());
|
||||
|
||||
Solid::StorageAccess *storageAccess = placesModel->deviceForIndex(index).as<Solid::StorageAccess>();
|
||||
if (!storageAccess) {
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void PlacesPanel::showHiddenEntries(bool shown)
|
||||
{
|
||||
m_model->setHiddenItemsShown(shown);
|
||||
Q_EMIT showHiddenEntriesChanged(shown);
|
||||
}
|
||||
|
||||
int PlacesPanel::hiddenListCount()
|
||||
{
|
||||
if(!m_model) {
|
||||
return 0;
|
||||
}
|
||||
return m_model->hiddenCount();
|
||||
connect(storageAccess, &Solid::StorageAccess::teardownRequested, this, &PlacesPanel::slotTearDownRequestedExternally);
|
||||
}
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: 2008-2012 Peter Penz <peter.penz19@gmail.com>
|
||||
* SPDX-FileCopyrightText: 2010 Christian Muehlhaeuser <muesli@gmail.com>
|
||||
* SPDX-FileCopyrightText: 2021 Kai Uwe Broulik <kde@broulik.de>
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later
|
||||
*/
|
||||
|
@ -8,95 +9,78 @@
|
|||
#ifndef PLACESPANEL_H
|
||||
#define PLACESPANEL_H
|
||||
|
||||
#include <optional>
|
||||
|
||||
#include "panels/panel.h"
|
||||
|
||||
#include <QUrl>
|
||||
#include <QTimer>
|
||||
#include <KFilePlacesView>
|
||||
|
||||
#include <Solid/SolidNamespace> // 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<QAction*> customContextMenuActions() const;
|
||||
void setCustomContextMenuActions(const QList<QAction*>& 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<QAction*> 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<int> m_hoveredIndex;
|
||||
QPoint m_hoverPos;
|
||||
QAction *m_configureTrashAction;
|
||||
QAction *m_lockPanelsAction;
|
||||
};
|
||||
|
||||
#endif // PLACESPANEL_H
|
||||
|
|
|
@ -1,49 +0,0 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: 2012 Frank Reininghaus <frank78ac@googlemail.com>
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later
|
||||
*/
|
||||
|
||||
#include "placesview.h"
|
||||
|
||||
#include "dolphin_placespanelsettings.h"
|
||||
#include "kitemviews/kitemlistheader.h"
|
||||
|
||||
#include <QGraphicsSceneResizeEvent>
|
||||
|
||||
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());
|
||||
}
|
|
@ -1,31 +0,0 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: 2012 Frank Reininghaus <frank78ac@googlemail.com>
|
||||
*
|
||||
* 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
|
|
@ -9,8 +9,8 @@
|
|||
|
||||
#include "dolphin_searchsettings.h"
|
||||
#include "dolphinfacetswidget.h"
|
||||
#include "dolphinplacesmodelsingleton.h"
|
||||
#include "dolphinquery.h"
|
||||
#include "panels/places/placesitemmodel.h"
|
||||
|
||||
#include <KLocalizedString>
|
||||
#include <KNS3/KMoreToolsMenuFactory>
|
||||
|
@ -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"));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -1,939 +0,0 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: 2017 Renato Araujo Oliveira <renato.araujo@kdab.com>
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later
|
||||
*/
|
||||
|
||||
#include <QTest>
|
||||
#include <QSignalSpy>
|
||||
#include <QStandardPaths>
|
||||
#include <QAction>
|
||||
#include <QDBusInterface>
|
||||
|
||||
#include <KBookmarkManager>
|
||||
#include <KConfig>
|
||||
#include <KConfigGroup>
|
||||
#include <KAboutData>
|
||||
#include <KFilePlacesModel>
|
||||
#include <KProtocolInfo>
|
||||
|
||||
#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<int> m_tobeRemoved;
|
||||
QMap<QString, QDBusInterface *> 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<int> &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<int> &indexes) const
|
||||
{
|
||||
QByteArray itemData;
|
||||
QDataStream stream(&itemData, QIODevice::WriteOnly);
|
||||
QList<QUrl> 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<KItemRangeList>();
|
||||
qRegisterMetaType<KItemRange>();
|
||||
}
|
||||
|
||||
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<QUrl>("url");
|
||||
QTest::addColumn<bool>("expectedIsHidden");
|
||||
QTest::addColumn<bool>("expectedIsSystemItem");
|
||||
QTest::addColumn<QString>("expectedGroup");
|
||||
QTest::addColumn<bool>("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<QVariant> spyItemsRemovedArgs = spyItemsRemoved.takeFirst();
|
||||
const KItemRangeList removedRange = spyItemsRemovedArgs.at(0).value<KItemRangeList>();
|
||||
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<QVariant> args = spyItemsInserted.takeFirst();
|
||||
const KItemRangeList insertedRange = args.at(0).value<KItemRangeList>();
|
||||
QCOMPARE(insertedRange.size(), 1);
|
||||
QCOMPARE(insertedRange.first().index, index);
|
||||
QCOMPARE(insertedRange.first().count, 1);
|
||||
}
|
||||
|
||||
void PlacesItemModelTest::testDefaultViewProperties_data()
|
||||
{
|
||||
QTest::addColumn<QUrl>("url");
|
||||
QTest::addColumn<DolphinView::Mode>("expectedViewMode");
|
||||
QTest::addColumn<bool>("expectedPreviewShow");
|
||||
QTest::addColumn<QList<QByteArray> >("expectedVisibleRole");
|
||||
|
||||
// places
|
||||
QTest::newRow("Places - Home") << QUrl::fromLocalFile(QDir::homePath()) << DolphinView::IconsView << true << QList<QByteArray>({"text"});
|
||||
|
||||
// baloo -search
|
||||
QTest::newRow("Baloo - Documents") << QUrl("search:/documents") << DolphinView::DetailsView << false << QList<QByteArray>({"text", "path"});
|
||||
|
||||
// audio files
|
||||
QTest::newRow("Places - Audio") << QUrl("search:/audio") << DolphinView::DetailsView << false << QList<QByteArray>({"text", "artist", "album"});
|
||||
|
||||
// devices
|
||||
QTest::newRow("Devices - Floppy") << QUrl("file:///media/floppy0") << DolphinView::IconsView << true << QList<QByteArray>({"text"});
|
||||
|
||||
}
|
||||
|
||||
void PlacesItemModelTest::testDefaultViewProperties()
|
||||
{
|
||||
QFETCH(QUrl, url);
|
||||
QFETCH(DolphinView::Mode, expectedViewMode);
|
||||
QFETCH(bool, expectedPreviewShow);
|
||||
QFETCH(QList<QByteArray>, 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<QVariant> spyItemsRemovedArgs;
|
||||
KItemRangeList removedRange;
|
||||
|
||||
QSignalSpy spyItemsInserted(m_model, &PlacesItemModel::itemsInserted);
|
||||
QList<QVariant> 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<KItemRangeList>();
|
||||
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<KItemRangeList>();
|
||||
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<QVariant> args = itemsInsertedSpy.takeFirst();
|
||||
KItemRangeList range = args.at(0).value<KItemRangeList>();
|
||||
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<KItemRangeList>();
|
||||
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<PlacesItemModel> 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<QVariant> args = itemsChangedSply.takeFirst();
|
||||
KItemRangeList range = args.at(0).value<KItemRangeList>();
|
||||
QCOMPARE(range.first().index, tempDirIndex);
|
||||
QCOMPARE(range.first().count, 1);
|
||||
QSet<QByteArray> roles = args.at(1).value<QSet<QByteArray> >();
|
||||
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<QUrl>("url");
|
||||
QTest::addColumn<QString>("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<QVariant> 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<int>() << 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<KItemRangeList>();
|
||||
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<KItemRangeList>();
|
||||
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<int>() << 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<KItemRangeList>();
|
||||
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<KItemRangeList>();
|
||||
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"
|
Loading…
Reference in New Issue
Block a user