]> cloud.milkyroute.net Git - dolphin.git/blob - src/panels/places/placespanel.cpp
GIT_SILENT Sync po/docbooks with svn
[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 const QUrl url = placesModel->url(index);
160 if (url.isValid() && !isInternalDrag(event->mimeData()) && !KProtocolManager::supportsWriting(url)) {
161 event->setDropAction(Qt::IgnoreAction);
162 }
163 }
164
165 KFilePlacesView::dragMoveEvent(event);
166 }
167
168 void PlacesPanel::slotConfigureTrash()
169 {
170 const QUrl url = currentIndex().data(KFilePlacesModel::UrlRole).toUrl();
171
172 DolphinSettingsDialog *settingsDialog = new DolphinSettingsDialog(url, this);
173 settingsDialog->setCurrentPage(settingsDialog->trashSettings);
174 settingsDialog->setAttribute(Qt::WA_DeleteOnClose);
175 settingsDialog->show();
176 }
177
178 void PlacesPanel::slotUrlsDropped(const QUrl &dest, QDropEvent *event, QWidget *parent)
179 {
180 KIO::DropJob *job = DragAndDropHelper::dropUrls(dest, event, parent);
181 if (job) {
182 connect(job, &KIO::DropJob::result, this, [this](KJob *job) {
183 if (job->error() && job->error() != KIO::ERR_USER_CANCELED) {
184 Q_EMIT errorMessage(job->errorString());
185 }
186 });
187 }
188 }
189
190 void PlacesPanel::slotContextMenuAboutToShow(const QModelIndex &index, QMenu *menu)
191 {
192 Q_UNUSED(menu);
193
194 auto *placesModel = static_cast<KFilePlacesModel *>(model());
195 const QUrl url = placesModel->url(index);
196 const Solid::Device device = placesModel->deviceForIndex(index);
197
198 m_configureTrashAction->setVisible(url.scheme() == QLatin1String("trash"));
199 m_openInSplitView->setVisible(url.isValid());
200
201 // show customContextMenuActions only on the view's context menu
202 if (!url.isValid() && !device.isValid()) {
203 addActions(m_customContextMenuActions);
204 } else {
205 const auto actions = this->actions();
206 for (QAction *action : actions) {
207 if (m_customContextMenuActions.contains(action)) {
208 removeAction(action);
209 }
210 }
211 }
212 }
213
214 void PlacesPanel::slotTearDownRequested(const QModelIndex &index)
215 {
216 auto *placesModel = static_cast<KFilePlacesModel *>(model());
217
218 Solid::StorageAccess *storageAccess = placesModel->deviceForIndex(index).as<Solid::StorageAccess>();
219 if (!storageAccess) {
220 return;
221 }
222
223 m_indexToTearDown = QPersistentModelIndex(index);
224
225 // disconnect the Solid::StorageAccess::teardownRequested
226 // to prevent emitting PlacesPanel::storageTearDownExternallyRequested
227 // after we have emitted PlacesPanel::storageTearDownRequested
228 disconnect(storageAccess, &Solid::StorageAccess::teardownRequested, this, &PlacesPanel::slotTearDownRequestedExternally);
229 Q_EMIT storageTearDownRequested(storageAccess->filePath());
230 }
231
232 void PlacesPanel::slotTearDownRequestedExternally(const QString &udi)
233 {
234 Q_UNUSED(udi);
235 auto *storageAccess = static_cast<Solid::StorageAccess *>(sender());
236
237 Q_EMIT storageTearDownExternallyRequested(storageAccess->filePath());
238 }
239
240 void PlacesPanel::slotTearDownDone(const QModelIndex &index, Solid::ErrorType error, const QVariant &errorData)
241 {
242 Q_UNUSED(errorData); // All error handling is currently done in frameworks.
243
244 if (index == m_indexToTearDown) {
245 if (error == Solid::ErrorType::NoError) {
246 // No error; it must have been unmounted successfully
247 Q_EMIT storageTearDownSuccessful();
248 }
249 m_indexToTearDown = QPersistentModelIndex();
250 }
251 }
252
253 void PlacesPanel::slotRowsInserted(const QModelIndex &parent, int first, int last)
254 {
255 for (int i = first; i <= last; ++i) {
256 connectDeviceSignals(model()->index(first, 0, parent));
257 }
258 }
259
260 void PlacesPanel::slotRowsAboutToBeRemoved(const QModelIndex &parent, int first, int last)
261 {
262 auto *placesModel = static_cast<KFilePlacesModel *>(model());
263
264 for (int i = first; i <= last; ++i) {
265 const QModelIndex index = placesModel->index(i, 0, parent);
266
267 Solid::StorageAccess *storageAccess = placesModel->deviceForIndex(index).as<Solid::StorageAccess>();
268 if (!storageAccess) {
269 continue;
270 }
271
272 disconnect(storageAccess, &Solid::StorageAccess::teardownRequested, this, nullptr);
273 }
274 }
275
276 void PlacesPanel::connectDeviceSignals(const QModelIndex &index)
277 {
278 auto *placesModel = static_cast<KFilePlacesModel *>(model());
279
280 Solid::StorageAccess *storageAccess = placesModel->deviceForIndex(index).as<Solid::StorageAccess>();
281 if (!storageAccess) {
282 return;
283 }
284
285 connect(storageAccess, &Solid::StorageAccess::teardownRequested, this, &PlacesPanel::slotTearDownRequestedExternally);
286 }
287
288 #include "moc_placespanel.cpp"