1
0
mirror of https://invent.kde.org/system/dolphin synced 2024-07-02 16:31:23 +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:
Kai Uwe Broulik 2021-12-16 19:29:22 +01:00
parent 3abc4cfcd4
commit 0603e18cd4
24 changed files with 356 additions and 3389 deletions

View File

@ -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)

View File

@ -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

View File

@ -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"

View File

@ -17,12 +17,12 @@
#include "dolphinnavigatorswidgetaction.h"
#include "dolphinnewfilemenu.h"
#include "dolphinrecenttabsmenu.h"
#include "dolphinplacesmodelsingleton.h"
#include "dolphinurlnavigatorscontroller.h"
#include "dolphinviewcontainer.h"
#include "dolphintabpage.h"
#include "middleclickactioneventfilter.h"
#include "panels/folders/folderspanel.h"
#include "panels/places/placesitemmodel.h"
#include "panels/places/placespanel.h"
#include "panels/terminal/terminalpanel.h"
#include "settings/dolphinsettingsdialog.h"
@ -429,14 +429,13 @@ void DolphinMainWindow::addToPlaces()
name = dirToAdd.name();
}
if (url.isValid()) {
PlacesItemModel model;
QString icon;
if (m_activeViewContainer->isSearchModeEnabled()) {
icon = QStringLiteral("folder-saved-search-symbolic");
} else {
icon = KIO::iconNameForUrl(url);
}
model.createPlacesItem(name, url, icon);
DolphinPlacesModelSingleton::instance().placesModel()->addPlace(name, url, icon);
}
}
@ -1954,10 +1953,13 @@ void DolphinMainWindow::setupDockWidgets()
addDockWidget(Qt::LeftDockWidgetArea, placesDock);
connect(m_placesPanel, &PlacesPanel::placeActivated,
this, &DolphinMainWindow::slotPlaceActivated);
connect(m_placesPanel, &PlacesPanel::placeActivatedInNewTab,
connect(m_placesPanel, &PlacesPanel::tabRequested,
this, &DolphinMainWindow::openNewTab);
connect(m_placesPanel, &PlacesPanel::placeActivatedInNewActiveTab,
connect(m_placesPanel, &PlacesPanel::activeTabRequested,
this, &DolphinMainWindow::openNewTabAndActivate);
connect(m_placesPanel, &PlacesPanel::newWindowRequested, this, [this](const QUrl &url) {
Dolphin::openNewWindow({url}, this);
});
connect(m_placesPanel, &PlacesPanel::errorMessage,
this, &DolphinMainWindow::showErrorMessage);
connect(this, &DolphinMainWindow::urlChanged,
@ -1980,14 +1982,9 @@ void DolphinMainWindow::setupDockWidgets()
"appear semi-transparent unless you uncheck their hide property."));
connect(actionShowAllPlaces, &QAction::triggered, this, [actionShowAllPlaces, this](bool checked){
actionShowAllPlaces->setIcon(QIcon::fromTheme(checked ? QStringLiteral("view-visible") : QStringLiteral("view-hidden")));
m_placesPanel->showHiddenEntries(checked);
m_placesPanel->setShowAll(checked);
});
connect(m_placesPanel, &PlacesPanel::showHiddenEntriesChanged, this, [actionShowAllPlaces] (bool checked){
actionShowAllPlaces->setChecked(checked);
actionShowAllPlaces->setIcon(QIcon::fromTheme(checked ? QStringLiteral("view-visible") : QStringLiteral("view-hidden")));
});
connect(m_placesPanel, &PlacesPanel::allPlacesShownChanged, actionShowAllPlaces, &QAction::setChecked);
actionCollection()->action(QStringLiteral("show_places_panel"))
->setWhatsThis(xi18nc("@info:whatsthis", "<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());
});
}

View File

@ -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()))
{
}

View File

@ -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.

View File

@ -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>

View File

@ -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();
}

View File

@ -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

View File

@ -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;
}

View File

@ -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

View File

@ -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();
}

View File

@ -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

View File

@ -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;
}

View File

@ -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

View File

@ -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"));
}
}

View File

@ -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

View File

@ -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()));
}
setUrl(m_url);
}
Panel::showEvent(event);
KFilePlacesView::showEvent(event);
}
bool PlacesPanel::eventFilter(QObject * /* obj */, QEvent *event)
void PlacesPanel::dragMoveEvent(QDragMoveEvent *event)
{
if (event->type() == QEvent::ToolTip) {
KFilePlacesView::dragMoveEvent(event);
QHelpEvent *hoverEvent = reinterpret_cast<QHelpEvent *>(event);
m_hoveredIndex = m_view->itemAt(hoverEvent->pos());
m_hoverPos = mapToGlobal(hoverEvent->pos());
m_tooltipTimer.start();
return true;
}
return false;
}
void PlacesPanel::slotItemActivated(int index)
{
const auto modifiers = QGuiApplication::keyboardModifiers();
// keep in sync with KUrlNavigator::slotNavigatorButtonClicked
if (modifiers & Qt::ControlModifier && modifiers & Qt::ShiftModifier) {
triggerItem(index, TriggerItemModifier::ToNewActiveTab);
} else if (modifiers & Qt::ControlModifier) {
triggerItem(index, TriggerItemModifier::ToNewTab);
} else if (modifiers & Qt::ShiftModifier) {
triggerItem(index, TriggerItemModifier::ToNewWindow);
} else {
triggerItem(index, TriggerItemModifier::None);
}
}
void PlacesPanel::slotItemMiddleClicked(int index)
{
const auto modifiers = QGuiApplication::keyboardModifiers();
// keep in sync with KUrlNavigator::slotNavigatorButtonClicked
if (modifiers & Qt::ShiftModifier) {
triggerItem(index, TriggerItemModifier::ToNewActiveTab);
} else {
triggerItem(index, TriggerItemModifier::ToNewTab);
}
}
void PlacesPanel::slotItemContextMenuRequested(int index, const QPointF& pos)
{
PlacesItem* item = m_model->placesItem(index);
if (!item) {
if (!m_dragActivationTimer) {
return;
}
QMenu menu(this);
QAction* emptyTrashAction = nullptr;
QAction* configureTrashAction = nullptr;
QAction* editAction = nullptr;
QAction* teardownAction = nullptr;
QAction* ejectAction = nullptr;
QAction* mountAction = nullptr;
const bool isDevice = !item->udi().isEmpty();
const bool isTrash = (item->url().scheme() == QLatin1String("trash"));
if (isTrash) {
emptyTrashAction = menu.addAction(QIcon::fromTheme(QStringLiteral("trash-empty")), i18nc("@action:inmenu", "Empty Trash"));
emptyTrashAction->setEnabled(item->icon() == QLatin1String("user-trash-full"));
menu.addSeparator();
}
QAction* openInNewTabAction = menu.addAction(QIcon::fromTheme(QStringLiteral("tab-new")), i18nc("@item:inmenu", "Open in New Tab"));
QAction* openInNewWindowAction = menu.addAction(QIcon::fromTheme(QStringLiteral("window-new")), i18nc("@item:inmenu", "Open in New Window"));
QAction* propertiesAction = nullptr;
if (item->url().isLocalFile()) {
propertiesAction = menu.addAction(QIcon::fromTheme(QStringLiteral("document-properties")), i18nc("@action:inmenu", "Properties"));
}
if (!isDevice) {
menu.addSeparator();
}
if (isDevice) {
ejectAction = m_model->ejectAction(index);
if (ejectAction) {
ejectAction->setParent(&menu);
menu.addAction(ejectAction);
}
teardownAction = m_model->teardownAction(index);
if (teardownAction) {
// Disable teardown option for root and home partitions
bool teardownEnabled = item->url() != QUrl::fromLocalFile(QDir::rootPath());
if (teardownEnabled) {
KMountPoint::Ptr mountPoint = KMountPoint::currentMountPoints().findByPath(QDir::homePath());
if (mountPoint && item->url() == QUrl::fromLocalFile(mountPoint->mountPoint())) {
teardownEnabled = false;
}
}
teardownAction->setEnabled(teardownEnabled);
teardownAction->setParent(&menu);
menu.addAction(teardownAction);
}
if (item->storageSetupNeeded()) {
mountAction = menu.addAction(QIcon::fromTheme(QStringLiteral("media-mount")), i18nc("@action:inmenu", "Mount"));
}
if (teardownAction || ejectAction || mountAction) {
menu.addSeparator();
}
}
if (isTrash) {
configureTrashAction = menu.addAction(QIcon::fromTheme(QStringLiteral("configure")), i18nc("@action:inmenu", "Configure Trash..."));
}
if (!isDevice) {
editAction = menu.addAction(QIcon::fromTheme(QStringLiteral("edit-entry")), i18nc("@item:inmenu", "Edit..."));
}
QAction* removeAction = nullptr;
if (!isDevice && !item->isSystemItem()) {
removeAction = menu.addAction(QIcon::fromTheme(QStringLiteral("edit-delete")), i18nc("@item:inmenu", "Remove"));
}
QAction* hideAction = menu.addAction(QIcon::fromTheme(QStringLiteral("view-hidden")), i18nc("@item:inmenu", "Hide"));
hideAction->setCheckable(true);
hideAction->setChecked(item->isHidden());
buildGroupContextMenu(&menu, index);
QAction* action = menu.exec(pos.toPoint());
if (action) {
if (action == emptyTrashAction) {
Trash::empty(this);
} else if (action == configureTrashAction) {
DolphinSettingsDialog* settingsDialog = new DolphinSettingsDialog(item->url(), this);
settingsDialog->setCurrentPage(settingsDialog->trashSettings);
settingsDialog->setAttribute(Qt::WA_DeleteOnClose);
settingsDialog->show();
} else {
// The index might have changed if devices were added/removed while
// the context menu was open.
index = m_model->index(item);
if (index < 0) {
// The item is not in the model any more, probably because it was an
// external device that has been removed while the context menu was open.
return;
}
if (action == editAction) {
editEntry(index);
} else if (action == removeAction) {
m_model->deleteItem(index);
} else if (action == hideAction) {
item->setHidden(hideAction->isChecked());
if (!m_model->hiddenCount()) {
showHiddenEntries(false);
}
} else if (action == openInNewWindowAction) {
Dolphin::openNewWindow({KFilePlacesModel::convertedUrl(m_model->data(index).value("url").toUrl())}, this);
} else if (action == openInNewTabAction) {
triggerItem(index, TriggerItemModifier::ToNewTab);
} else if (action == mountAction) {
m_model->requestStorageSetup(index);
} else if (action == teardownAction) {
m_model->requestTearDown(index);
} else if (action == ejectAction) {
m_model->requestEject(index);
} else if (action == propertiesAction) {
KPropertiesDialog* dialog = new KPropertiesDialog(item->url(), this);
dialog->setAttribute(Qt::WA_DeleteOnClose);
dialog->show();
}
}
}
selectItem();
}
void PlacesPanel::slotViewContextMenuRequested(const QPointF& pos)
{
QMenu menu(this);
QAction* addAction = menu.addAction(QIcon::fromTheme(QStringLiteral("document-new")), i18nc("@item:inmenu", "Add Entry..."));
QAction* showAllAction = menu.addAction(i18nc("@item:inmenu", "Show Hidden Places"));
showAllAction->setCheckable(true);
showAllAction->setChecked(m_model->hiddenItemsShown());
showAllAction->setIcon(QIcon::fromTheme(m_model->hiddenItemsShown() ? QStringLiteral("view-visible") : QStringLiteral("view-hidden")));
showAllAction->setEnabled(m_model->hiddenCount());
buildGroupContextMenu(&menu, m_controller->indexCloseToMousePressedPosition());
QMenu* iconSizeSubMenu = new QMenu(i18nc("@item:inmenu", "Icon Size"), &menu);
struct IconSizeInfo
{
int size;
const char* context;
const char* text;
};
const int iconSizeCount = 4;
static const IconSizeInfo iconSizes[iconSizeCount] = {
{KIconLoader::SizeSmall, I18NC_NOOP("Small icon size", "Small (%1x%2)")},
{KIconLoader::SizeSmallMedium, I18NC_NOOP("Medium icon size", "Medium (%1x%2)")},
{KIconLoader::SizeMedium, I18NC_NOOP("Large icon size", "Large (%1x%2)")},
{KIconLoader::SizeLarge, I18NC_NOOP("Huge icon size", "Huge (%1x%2)")}
};
QHash<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) {
const QModelIndex index = indexAt(event->pos());
if (!index.isValid()) {
return;
}
const PlacesItem* destItem = m_model->placesItem(index);
if (destItem->isSearchOrTimelineUrl()) {
return;
}
if (m_model->storageSetupNeeded(index)) {
connect(m_model, &PlacesItemModel::storageSetupDone,
this, &PlacesPanel::slotItemDropEventStorageSetupDone);
m_itemDropEventIndex = index;
// Make a full copy of the Mime-Data
m_itemDropEventMimeData = new QMimeData;
m_itemDropEventMimeData->setText(event->mimeData()->text());
m_itemDropEventMimeData->setHtml(event->mimeData()->html());
m_itemDropEventMimeData->setUrls(event->mimeData()->urls());
m_itemDropEventMimeData->setImageData(event->mimeData()->imageData());
m_itemDropEventMimeData->setColorData(event->mimeData()->colorData());
m_itemDropEvent = new QDropEvent(event->pos().toPoint(),
event->possibleActions(),
m_itemDropEventMimeData,
event->buttons(),
event->modifiers());
m_model->requestStorageSetup(index);
return;
}
QUrl destUrl = destItem->url();
QDropEvent dropEvent(event->pos().toPoint(),
event->possibleActions(),
event->mimeData(),
event->buttons(),
event->modifiers());
slotUrlsDropped(destUrl, &dropEvent, this);
}
void PlacesPanel::slotItemDropEventStorageSetupDone(int index, bool success)
{
disconnect(m_model, &PlacesItemModel::storageSetupDone,
this, &PlacesPanel::slotItemDropEventStorageSetupDone);
if ((index == m_itemDropEventIndex) && m_itemDropEvent && m_itemDropEventMimeData) {
if (success) {
QUrl destUrl = m_model->placesItem(index)->url();
slotUrlsDropped(destUrl, m_itemDropEvent, this);
}
delete m_itemDropEventMimeData;
delete m_itemDropEvent;
m_itemDropEventIndex = -1;
m_itemDropEventMimeData = nullptr;
m_itemDropEvent = nullptr;
QPersistentModelIndex persistentIndex(index);
if (!m_pendingDragActivation.isValid() || m_pendingDragActivation != persistentIndex) {
m_pendingDragActivation = persistentIndex;
m_dragActivationTimer->start();
}
}
void PlacesPanel::slotAboveItemDropEvent(int index, QGraphicsSceneDragDropEvent* event)
void PlacesPanel::dragLeaveEvent(QDragLeaveEvent *event)
{
m_model->dropMimeDataBefore(index, event->mimeData());
KFilePlacesView::dragLeaveEvent(event);
if (m_dragActivationTimer) {
m_dragActivationTimer->stop();
m_pendingDragActivation = QPersistentModelIndex();
}
}
void PlacesPanel::slotConfigureTrash()
{
const QUrl url = currentIndex().data(KFilePlacesModel::UrlRole).toUrl();
DolphinSettingsDialog* settingsDialog = new DolphinSettingsDialog(url, this);
settingsDialog->setCurrentPage(settingsDialog->trashSettings);
settingsDialog->setAttribute(Qt::WA_DeleteOnClose);
settingsDialog->show();
}
void PlacesPanel::slotDragActivationTimeout()
{
if (!m_pendingDragActivation.isValid()) {
return;
}
auto *placesModel = static_cast<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) {
return;
}
auto *placesModel = static_cast<KFilePlacesModel *>(model());
const QUrl url = placesModel->url(index);
const Solid::Device device = placesModel->deviceForIndex(index);
if (success) {
Q_ASSERT(!m_model->storageSetupNeeded(index));
triggerItem(index, m_triggerStorageSetupModifier);
m_triggerStorageSetupModifier = TriggerItemModifier::None;
m_configureTrashAction->setVisible(url.scheme() == QLatin1String("trash"));
// show customContextMenuActions only on the view's context menu
if (!url.isValid() && !device.isValid()) {
addActions(m_customContextMenuActions);
} else {
setUrl(m_storageSetupFailedUrl);
m_storageSetupFailedUrl = QUrl();
}
}
void PlacesPanel::slotShowTooltip()
{
const QUrl url = m_model->data(m_hoveredIndex.value_or(-1)).value("url").value<QUrl>();
const QString text = url.toDisplayString(QUrl::PreferLocalFile);
QToolTip::showText(m_hoverPos, text);
}
void PlacesPanel::addEntry()
{
const int index = m_controller->selectionManager()->currentItem();
const QUrl url = m_model->data(index).value("url").toUrl();
const QString text = url.fileName().isEmpty() ? url.toDisplayString(QUrl::PreferLocalFile) : url.fileName();
QPointer<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);
}
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);
}
}
void PlacesPanel::triggerItem(int index, TriggerItemModifier modifier)
{
const PlacesItem* item = m_model->placesItem(index);
if (!item) {
return;
}
if (m_model->storageSetupNeeded(index)) {
m_triggerStorageSetupModifier = modifier;
m_storageSetupFailedUrl = url();
connect(m_model, &PlacesItemModel::storageSetupDone,
this, &PlacesPanel::slotStorageSetupDone);
m_model->requestStorageSetup(index);
} else {
m_triggerStorageSetupModifier = TriggerItemModifier::None;
const QUrl url = m_model->data(index).value("url").toUrl();
if (!url.isEmpty()) {
switch (modifier) {
case TriggerItemModifier::ToNewTab:
Q_EMIT placeActivatedInNewTab(KFilePlacesModel::convertedUrl(url));
break;
case TriggerItemModifier::ToNewActiveTab:
Q_EMIT placeActivatedInNewActiveTab(KFilePlacesModel::convertedUrl(url));
break;
case TriggerItemModifier::ToNewWindow:
Dolphin::openNewWindow({KFilePlacesModel::convertedUrl(url)}, this);
break;
case TriggerItemModifier::None:
Q_EMIT placeActivated(KFilePlacesModel::convertedUrl(url));
break;
const auto actions = this->actions();
for (QAction *action : actions) {
if (m_customContextMenuActions.contains(action)) {
removeAction(action);
}
}
}
}
void PlacesPanel::showHiddenEntries(bool shown)
void PlacesPanel::slotTearDownRequested(const QModelIndex &index)
{
m_model->setHiddenItemsShown(shown);
Q_EMIT showHiddenEntriesChanged(shown);
auto *placesModel = static_cast<KFilePlacesModel *>(model());
Solid::StorageAccess *storageAccess = placesModel->deviceForIndex(index).as<Solid::StorageAccess>();
if (!storageAccess) {
return;
}
m_deviceToTearDown = storageAccess;
// disconnect the Solid::StorageAccess::teardownRequested
// to prevent emitting PlacesPanel::storageTearDownExternallyRequested
// after we have emitted PlacesPanel::storageTearDownRequested
disconnect(storageAccess, &Solid::StorageAccess::teardownRequested, this, &PlacesPanel::slotTearDownRequestedExternally);
Q_EMIT storageTearDownRequested(storageAccess->filePath());
}
int PlacesPanel::hiddenListCount()
void PlacesPanel::slotTearDownRequestedExternally(const QString &udi)
{
if(!m_model) {
return 0;
}
return m_model->hiddenCount();
Q_UNUSED(udi);
auto *storageAccess = static_cast<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 {
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::slotRowsAboutToBeRemoved(const QModelIndex &parent, int first, int last)
{
auto *placesModel = static_cast<KFilePlacesModel *>(model());
for (int i = first; i <= last; ++i) {
const QModelIndex index = placesModel->index(i, 0, parent);
Solid::StorageAccess *storageAccess = placesModel->deviceForIndex(index).as<Solid::StorageAccess>();
if (!storageAccess) {
continue;
}
disconnect(storageAccess, &Solid::StorageAccess::teardownRequested, this, nullptr);
}
}
void PlacesPanel::connectDeviceSignals(const QModelIndex &index)
{
auto *placesModel = static_cast<KFilePlacesModel *>(model());
Solid::StorageAccess *storageAccess = placesModel->deviceForIndex(index).as<Solid::StorageAccess>();
if (!storageAccess) {
return;
}
connect(storageAccess, &Solid::StorageAccess::teardownRequested, this, &PlacesPanel::slotTearDownRequestedExternally);
}

View File

@ -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

View File

@ -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());
}

View File

@ -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

View File

@ -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"));
}
}

View File

@ -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

View File

@ -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"