]> cloud.milkyroute.net Git - dolphin.git/blob - src/panels/places/placespanel.cpp
ba3451bd5e603f353e38fa4ec01f5e6590cceaf0
[dolphin.git] / src / panels / places / placespanel.cpp
1 /*
2 * SPDX-FileCopyrightText: 2008-2012 Peter Penz <peter.penz19@gmail.com>
3 * SPDX-FileCopyrightText: 2021 Kai Uwe Broulik <kde@broulik.de>
4 *
5 * Based on KFilePlacesView from kdelibs:
6 * SPDX-FileCopyrightText: 2007 Kevin Ottens <ervin@kde.org>
7 * SPDX-FileCopyrightText: 2007 David Faure <faure@kde.org>
8 *
9 * SPDX-License-Identifier: GPL-2.0-or-later
10 */
11
12 #include "placespanel.h"
13
14 #include "dolphin_generalsettings.h"
15 #include "dolphin_placespanelsettings.h"
16 #include "dolphinplacesmodelsingleton.h"
17 #include "settings/dolphinsettingsdialog.h"
18 #include "views/draganddrophelper.h"
19
20 #include <KFilePlacesModel>
21 #include <KIO/DropJob>
22 #include <KIO/Job>
23 #include <KLocalizedString>
24 #include <KProtocolManager>
25
26 #include <QIcon>
27 #include <QMenu>
28 #include <QMimeData>
29 #include <QShowEvent>
30
31 #include <Solid/StorageAccess>
32
33 PlacesPanel::PlacesPanel(QWidget *parent)
34 : KFilePlacesView(parent)
35 {
36 setDropOnPlaceEnabled(true);
37 connect(this, &PlacesPanel::urlsDropped, this, &PlacesPanel::slotUrlsDropped);
38
39 setAutoResizeItemsEnabled(false);
40
41 setTeardownFunction([this](const QModelIndex &index) {
42 slotTearDownRequested(index);
43 });
44
45 m_openInSplitView = new QAction(QIcon::fromTheme(QStringLiteral("view-right-new")), i18nc("@action:inmenu", "Open in Split View"));
46 m_openInSplitView->setPriority(QAction::HighPriority);
47 connect(m_openInSplitView, &QAction::triggered, this, [this]() {
48 const QUrl url = currentIndex().data(KFilePlacesModel::UrlRole).toUrl();
49 Q_EMIT openInSplitViewRequested(url);
50 });
51 addAction(m_openInSplitView);
52
53 m_configureTrashAction = new QAction(QIcon::fromTheme(QStringLiteral("configure")), i18nc("@action:inmenu", "Configure Trash…"));
54 m_configureTrashAction->setPriority(QAction::HighPriority);
55 connect(m_configureTrashAction, &QAction::triggered, this, &PlacesPanel::slotConfigureTrash);
56 addAction(m_configureTrashAction);
57
58 connect(this, &PlacesPanel::contextMenuAboutToShow, this, &PlacesPanel::slotContextMenuAboutToShow);
59
60 connect(this, &PlacesPanel::iconSizeChanged, this, [](const QSize &newSize) {
61 int iconSize = qMin(newSize.width(), newSize.height());
62 if (iconSize == 0) {
63 // Don't store 0 size, let's keep -1 for default/small/automatic
64 iconSize = -1;
65 }
66 PlacesPanelSettings *settings = PlacesPanelSettings::self();
67 settings->setIconSize(iconSize);
68 settings->save();
69 });
70 }
71
72 PlacesPanel::~PlacesPanel() = default;
73
74 void PlacesPanel::setUrl(const QUrl &url)
75 {
76 // KFilePlacesView::setUrl no-ops when no model is set but we only set it in showEvent()
77 // Remember the URL and set it in showEvent
78 m_url = url;
79 KFilePlacesView::setUrl(url);
80 }
81
82 QList<QAction *> PlacesPanel::customContextMenuActions() const
83 {
84 return m_customContextMenuActions;
85 }
86
87 void PlacesPanel::setCustomContextMenuActions(const QList<QAction *> &actions)
88 {
89 m_customContextMenuActions = actions;
90 }
91
92 void PlacesPanel::proceedWithTearDown()
93 {
94 if (m_indexToTearDown.isValid()) {
95 auto *placesModel = static_cast<KFilePlacesModel *>(model());
96 placesModel->requestTeardown(m_indexToTearDown);
97 } else {
98 qWarning() << "Places entry to tear down is no longer valid";
99 }
100 }
101
102 void PlacesPanel::readSettings()
103 {
104 if (GeneralSettings::autoExpandFolders()) {
105 setDragAutoActivationDelay(750);
106 } else {
107 setDragAutoActivationDelay(0);
108 }
109
110 const int iconSize = qMax(0, PlacesPanelSettings::iconSize());
111 setIconSize(QSize(iconSize, iconSize));
112 }
113
114 void PlacesPanel::showEvent(QShowEvent *event)
115 {
116 if (!event->spontaneous() && !model()) {
117 readSettings();
118
119 auto *placesModel = DolphinPlacesModelSingleton::instance().placesModel();
120 setModel(placesModel);
121
122 connect(placesModel, &KFilePlacesModel::errorMessage, this, &PlacesPanel::errorMessage);
123 connect(placesModel, &KFilePlacesModel::teardownDone, this, &PlacesPanel::slotTearDownDone);
124
125 connect(placesModel, &QAbstractItemModel::rowsInserted, this, &PlacesPanel::slotRowsInserted);
126 connect(placesModel, &QAbstractItemModel::rowsAboutToBeRemoved, this, &PlacesPanel::slotRowsAboutToBeRemoved);
127
128 for (int i = 0; i < model()->rowCount(); ++i) {
129 connectDeviceSignals(model()->index(i, 0, QModelIndex()));
130 }
131
132 setUrl(m_url);
133 }
134
135 KFilePlacesView::showEvent(event);
136 }
137
138 static bool isInternalDrag(const QMimeData *mimeData)
139 {
140 const auto formats = mimeData->formats();
141 for (const auto &format : formats) {
142 // from KFilePlacesModel::_k_internalMimetype
143 if (format.startsWith(QLatin1String("application/x-kfileplacesmodel-"))) {
144 return true;
145 }
146 }
147 return false;
148 }
149
150 void PlacesPanel::dragMoveEvent(QDragMoveEvent *event)
151 {
152 const QModelIndex index = indexAt(event->position().toPoint());
153 if (index.isValid()) {
154 auto *placesModel = static_cast<KFilePlacesModel *>(model());
155
156 // Reject drag ontop of a non-writable protocol
157 // We don't know whether we're dropping inbetween or ontop of a place
158 // so still allow internal drag events so that re-arranging still works.
159 if (!isInternalDrag(event->mimeData())) {
160 const QUrl url = placesModel->url(index);
161 if (!url.isValid() || !KProtocolManager::supportsWriting(url)) {
162 event->setDropAction(Qt::IgnoreAction);
163 } else {
164 DragAndDropHelper::updateDropAction(event, url);
165 }
166 }
167 }
168
169 KFilePlacesView::dragMoveEvent(event);
170 }
171
172 void PlacesPanel::slotConfigureTrash()
173 {
174 const QUrl url = currentIndex().data(KFilePlacesModel::UrlRole).toUrl();
175
176 DolphinSettingsDialog *settingsDialog = new DolphinSettingsDialog(url, this);
177 settingsDialog->setCurrentPage(settingsDialog->trashSettings);
178 settingsDialog->setAttribute(Qt::WA_DeleteOnClose);
179 settingsDialog->show();
180 }
181
182 void PlacesPanel::slotUrlsDropped(const QUrl &dest, QDropEvent *event, QWidget *parent)
183 {
184 KIO::DropJob *job = DragAndDropHelper::dropUrls(dest, event, parent);
185 if (job) {
186 connect(job, &KIO::DropJob::result, this, [this](KJob *job) {
187 if (job->error() && job->error() != KIO::ERR_USER_CANCELED) {
188 Q_EMIT errorMessage(job->errorString());
189 }
190 });
191 }
192 }
193
194 void PlacesPanel::slotContextMenuAboutToShow(const QModelIndex &index, QMenu *menu)
195 {
196 Q_UNUSED(menu);
197
198 auto *placesModel = static_cast<KFilePlacesModel *>(model());
199 const QUrl url = placesModel->url(index);
200 const Solid::Device device = placesModel->deviceForIndex(index);
201
202 m_configureTrashAction->setVisible(url.scheme() == QLatin1String("trash"));
203 m_openInSplitView->setVisible(url.isValid());
204
205 // show customContextMenuActions only on the view's context menu
206 if (!url.isValid() && !device.isValid()) {
207 addActions(m_customContextMenuActions);
208 } else {
209 const auto actions = this->actions();
210 for (QAction *action : actions) {
211 if (m_customContextMenuActions.contains(action)) {
212 removeAction(action);
213 }
214 }
215 }
216 }
217
218 void PlacesPanel::slotTearDownRequested(const QModelIndex &index)
219 {
220 auto *placesModel = static_cast<KFilePlacesModel *>(model());
221
222 Solid::StorageAccess *storageAccess = placesModel->deviceForIndex(index).as<Solid::StorageAccess>();
223 if (!storageAccess) {
224 return;
225 }
226
227 m_indexToTearDown = QPersistentModelIndex(index);
228
229 // disconnect the Solid::StorageAccess::teardownRequested
230 // to prevent emitting PlacesPanel::storageTearDownExternallyRequested
231 // after we have emitted PlacesPanel::storageTearDownRequested
232 disconnect(storageAccess, &Solid::StorageAccess::teardownRequested, this, &PlacesPanel::slotTearDownRequestedExternally);
233 Q_EMIT storageTearDownRequested(storageAccess->filePath());
234 }
235
236 void PlacesPanel::slotTearDownRequestedExternally(const QString &udi)
237 {
238 Q_UNUSED(udi);
239 auto *storageAccess = static_cast<Solid::StorageAccess *>(sender());
240
241 Q_EMIT storageTearDownExternallyRequested(storageAccess->filePath());
242 }
243
244 void PlacesPanel::slotTearDownDone(const QModelIndex &index, Solid::ErrorType error, const QVariant &errorData)
245 {
246 Q_UNUSED(errorData); // All error handling is currently done in frameworks.
247
248 if (index == m_indexToTearDown) {
249 if (error == Solid::ErrorType::NoError) {
250 // No error; it must have been unmounted successfully
251 Q_EMIT storageTearDownSuccessful();
252 }
253 m_indexToTearDown = QPersistentModelIndex();
254 }
255 }
256
257 void PlacesPanel::slotRowsInserted(const QModelIndex &parent, int first, int last)
258 {
259 for (int i = first; i <= last; ++i) {
260 connectDeviceSignals(model()->index(first, 0, parent));
261 }
262 }
263
264 void PlacesPanel::slotRowsAboutToBeRemoved(const QModelIndex &parent, int first, int last)
265 {
266 auto *placesModel = static_cast<KFilePlacesModel *>(model());
267
268 for (int i = first; i <= last; ++i) {
269 const QModelIndex index = placesModel->index(i, 0, parent);
270
271 Solid::StorageAccess *storageAccess = placesModel->deviceForIndex(index).as<Solid::StorageAccess>();
272 if (!storageAccess) {
273 continue;
274 }
275
276 disconnect(storageAccess, &Solid::StorageAccess::teardownRequested, this, nullptr);
277 }
278 }
279
280 void PlacesPanel::connectDeviceSignals(const QModelIndex &index)
281 {
282 auto *placesModel = static_cast<KFilePlacesModel *>(model());
283
284 Solid::StorageAccess *storageAccess = placesModel->deviceForIndex(index).as<Solid::StorageAccess>();
285 if (!storageAccess) {
286 return;
287 }
288
289 connect(storageAccess, &Solid::StorageAccess::teardownRequested, this, &PlacesPanel::slotTearDownRequestedExternally);
290 }
291
292 #include "moc_placespanel.cpp"