2 * SPDX-FileCopyrightText: 2008-2012 Peter Penz <peter.penz19@gmail.com>
3 * SPDX-FileCopyrightText: 2021 Kai Uwe Broulik <kde@broulik.de>
5 * Based on KFilePlacesView from kdelibs:
6 * SPDX-FileCopyrightText: 2007 Kevin Ottens <ervin@kde.org>
7 * SPDX-FileCopyrightText: 2007 David Faure <faure@kde.org>
9 * SPDX-License-Identifier: GPL-2.0-or-later
12 #include "placespanel.h"
14 #include "dolphinplacesmodelsingleton.h"
15 #include "dolphin_generalsettings.h"
16 #include "dolphin_placespanelsettings.h"
18 #include "views/draganddrophelper.h"
19 #include "settings/dolphinsettingsdialog.h"
21 #include <KFilePlacesModel>
22 #include <KIO/DropJob>
24 #include <KListOpenFilesJob>
25 #include <KLocalizedString>
32 #include <Solid/StorageAccess>
34 PlacesPanel::PlacesPanel(QWidget
* parent
)
35 : KFilePlacesView(parent
)
37 setDropOnPlaceEnabled(true);
38 connect(this, &PlacesPanel::urlsDropped
,
39 this, &PlacesPanel::slotUrlsDropped
);
41 setAutoResizeItemsEnabled(false);
43 setTeardownFunction([this](const QModelIndex
&index
) {
44 slotTearDownRequested(index
);
47 m_configureTrashAction
= new QAction(QIcon::fromTheme(QStringLiteral("configure")), i18nc("@action:inmenu", "Configure Trash…"));
48 m_configureTrashAction
->setPriority(QAction::HighPriority
);
49 connect(m_configureTrashAction
, &QAction::triggered
, this, &PlacesPanel::slotConfigureTrash
);
50 addAction(m_configureTrashAction
);
52 connect(this, &PlacesPanel::contextMenuAboutToShow
, this, &PlacesPanel::slotContextMenuAboutToShow
);
54 connect(this, &PlacesPanel::iconSizeChanged
, this, [this](const QSize
&newSize
) {
55 int iconSize
= qMin(newSize
.width(), newSize
.height());
57 // Don't store 0 size, let's keep -1 for default/small/automatic
60 PlacesPanelSettings
* settings
= PlacesPanelSettings::self();
61 settings
->setIconSize(iconSize
);
66 PlacesPanel::~PlacesPanel() = default;
68 void PlacesPanel::setUrl(const QUrl
&url
)
70 // KFilePlacesView::setUrl no-ops when no model is set but we only set it in showEvent()
71 // Remember the URL and set it in showEvent
73 KFilePlacesView::setUrl(url
);
76 QList
<QAction
*> PlacesPanel::customContextMenuActions() const
78 return m_customContextMenuActions
;
81 void PlacesPanel::setCustomContextMenuActions(const QList
<QAction
*> &actions
)
83 m_customContextMenuActions
= actions
;
86 void PlacesPanel::proceedWithTearDown()
88 Q_ASSERT(m_deviceToTearDown
);
90 connect(m_deviceToTearDown
, &Solid::StorageAccess::teardownDone
,
91 this, &PlacesPanel::slotTearDownDone
);
92 m_deviceToTearDown
->teardown();
95 void PlacesPanel::readSettings()
97 if (GeneralSettings::autoExpandFolders()) {
98 if (!m_dragActivationTimer
) {
99 m_dragActivationTimer
= new QTimer(this);
100 m_dragActivationTimer
->setInterval(750);
101 m_dragActivationTimer
->setSingleShot(true);
102 connect(m_dragActivationTimer
, &QTimer::timeout
,
103 this, &PlacesPanel::slotDragActivationTimeout
);
106 delete m_dragActivationTimer
;
107 m_dragActivationTimer
= nullptr;
108 m_pendingDragActivation
= QPersistentModelIndex();
111 const int iconSize
= qMax(0, PlacesPanelSettings::iconSize());
112 setIconSize(QSize(iconSize
, iconSize
));
115 void PlacesPanel::showEvent(QShowEvent
* event
)
117 if (!event
->spontaneous() && !model()) {
120 auto *placesModel
= DolphinPlacesModelSingleton::instance().placesModel();
121 setModel(placesModel
);
123 connect(placesModel
, &KFilePlacesModel::errorMessage
, this, &PlacesPanel::errorMessage
);
125 connect(placesModel
, &QAbstractItemModel::rowsInserted
, this, &PlacesPanel::slotRowsInserted
);
126 connect(placesModel
, &QAbstractItemModel::rowsAboutToBeRemoved
, this, &PlacesPanel::slotRowsAboutToBeRemoved
);
128 for (int i
= 0; i
< model()->rowCount(); ++i
) {
129 connectDeviceSignals(model()->index(i
, 0, QModelIndex()));
135 KFilePlacesView::showEvent(event
);
138 void PlacesPanel::dragMoveEvent(QDragMoveEvent
*event
)
140 KFilePlacesView::dragMoveEvent(event
);
142 if (!m_dragActivationTimer
) {
146 const QModelIndex index
= indexAt(event
->pos());
147 if (!index
.isValid()) {
151 QPersistentModelIndex
persistentIndex(index
);
152 if (!m_pendingDragActivation
.isValid() || m_pendingDragActivation
!= persistentIndex
) {
153 m_pendingDragActivation
= persistentIndex
;
154 m_dragActivationTimer
->start();
158 void PlacesPanel::dragLeaveEvent(QDragLeaveEvent
*event
)
160 KFilePlacesView::dragLeaveEvent(event
);
162 if (m_dragActivationTimer
) {
163 m_dragActivationTimer
->stop();
164 m_pendingDragActivation
= QPersistentModelIndex();
168 void PlacesPanel::dropEvent(QDropEvent
*event
)
170 KFilePlacesView::dropEvent(event
);
172 if (m_dragActivationTimer
) {
173 m_dragActivationTimer
->stop();
174 m_pendingDragActivation
= QPersistentModelIndex();
178 void PlacesPanel::slotConfigureTrash()
180 const QUrl url
= currentIndex().data(KFilePlacesModel::UrlRole
).toUrl();
182 DolphinSettingsDialog
* settingsDialog
= new DolphinSettingsDialog(url
, this);
183 settingsDialog
->setCurrentPage(settingsDialog
->trashSettings
);
184 settingsDialog
->setAttribute(Qt::WA_DeleteOnClose
);
185 settingsDialog
->show();
188 void PlacesPanel::slotDragActivationTimeout()
190 if (!m_pendingDragActivation
.isValid()) {
194 auto *placesModel
= static_cast<KFilePlacesModel
*>(model());
195 Q_EMIT
placeActivated(KFilePlacesModel::convertedUrl(placesModel
->url(m_pendingDragActivation
)));
198 void PlacesPanel::slotUrlsDropped(const QUrl
& dest
, QDropEvent
* event
, QWidget
* parent
)
200 KIO::DropJob
*job
= DragAndDropHelper::dropUrls(dest
, event
, parent
);
202 connect(job
, &KIO::DropJob::result
, this, [this](KJob
*job
) { if (job
->error()) Q_EMIT
errorMessage(job
->errorString()); });
206 void PlacesPanel::slotContextMenuAboutToShow(const QModelIndex
&index
, QMenu
*menu
)
210 auto *placesModel
= static_cast<KFilePlacesModel
*>(model());
211 const QUrl url
= placesModel
->url(index
);
212 const Solid::Device device
= placesModel
->deviceForIndex(index
);
214 m_configureTrashAction
->setVisible(url
.scheme() == QLatin1String("trash"));
216 // show customContextMenuActions only on the view's context menu
217 if (!url
.isValid() && !device
.isValid()) {
218 addActions(m_customContextMenuActions
);
220 const auto actions
= this->actions();
221 for (QAction
*action
: actions
) {
222 if (m_customContextMenuActions
.contains(action
)) {
223 removeAction(action
);
229 void PlacesPanel::slotTearDownRequested(const QModelIndex
&index
)
231 auto *placesModel
= static_cast<KFilePlacesModel
*>(model());
233 Solid::StorageAccess
*storageAccess
= placesModel
->deviceForIndex(index
).as
<Solid::StorageAccess
>();
234 if (!storageAccess
) {
238 m_deviceToTearDown
= storageAccess
;
240 // disconnect the Solid::StorageAccess::teardownRequested
241 // to prevent emitting PlacesPanel::storageTearDownExternallyRequested
242 // after we have emitted PlacesPanel::storageTearDownRequested
243 disconnect(storageAccess
, &Solid::StorageAccess::teardownRequested
, this, &PlacesPanel::slotTearDownRequestedExternally
);
244 Q_EMIT
storageTearDownRequested(storageAccess
->filePath());
247 void PlacesPanel::slotTearDownRequestedExternally(const QString
&udi
)
250 auto *storageAccess
= static_cast<Solid::StorageAccess
*>(sender());
252 Q_EMIT
storageTearDownExternallyRequested(storageAccess
->filePath());
255 void PlacesPanel::slotTearDownDone(Solid::ErrorType error
, const QVariant
& errorData
)
257 if (error
&& errorData
.isValid()) {
258 if (error
== Solid::ErrorType::DeviceBusy
) {
259 KListOpenFilesJob
* listOpenFilesJob
= new KListOpenFilesJob(m_deviceToTearDown
->filePath());
260 connect(listOpenFilesJob
, &KIO::Job::result
, this, [this, listOpenFilesJob
](KJob
*) {
261 const KProcessList::KProcessInfoList blockingProcesses
= listOpenFilesJob
->processInfoList();
263 if (blockingProcesses
.isEmpty()) {
264 errorString
= i18n("One or more files on this device are open within an application.");
266 QStringList blockingApps
;
267 for (const auto& process
: blockingProcesses
) {
268 blockingApps
<< process
.name();
270 blockingApps
.removeDuplicates();
271 errorString
= xi18np("One or more files on this device are opened in application <application>\"%2\"</application>.",
272 "One or more files on this device are opened in following applications: <application>%2</application>.",
273 blockingApps
.count(), blockingApps
.join(i18nc("separator in list of apps blocking device unmount", ", ")));
275 Q_EMIT
errorMessage(errorString
);
277 listOpenFilesJob
->start();
279 Q_EMIT
errorMessage(errorData
.toString());
282 // No error; it must have been unmounted successfully
283 Q_EMIT
storageTearDownSuccessful();
285 disconnect(m_deviceToTearDown
, &Solid::StorageAccess::teardownDone
,
286 this, &PlacesPanel::slotTearDownDone
);
287 m_deviceToTearDown
= nullptr;
290 void PlacesPanel::slotRowsInserted(const QModelIndex
&parent
, int first
, int last
)
292 for (int i
= first
; i
<= last
; ++i
) {
293 connectDeviceSignals(model()->index(first
, 0, parent
));
297 void PlacesPanel::slotRowsAboutToBeRemoved(const QModelIndex
&parent
, int first
, int last
)
299 auto *placesModel
= static_cast<KFilePlacesModel
*>(model());
301 for (int i
= first
; i
<= last
; ++i
) {
302 const QModelIndex index
= placesModel
->index(i
, 0, parent
);
304 Solid::StorageAccess
*storageAccess
= placesModel
->deviceForIndex(index
).as
<Solid::StorageAccess
>();
305 if (!storageAccess
) {
309 disconnect(storageAccess
, &Solid::StorageAccess::teardownRequested
, this, nullptr);
313 void PlacesPanel::connectDeviceSignals(const QModelIndex
&index
)
315 auto *placesModel
= static_cast<KFilePlacesModel
*>(model());
317 Solid::StorageAccess
*storageAccess
= placesModel
->deviceForIndex(index
).as
<Solid::StorageAccess
>();
318 if (!storageAccess
) {
322 connect(storageAccess
, &Solid::StorageAccess::teardownRequested
, this, &PlacesPanel::slotTearDownRequestedExternally
);