]> cloud.milkyroute.net Git - dolphin.git/blob - src/panels/places/placespanel.cpp
Show home folder if needed after unmounting mounted disk
[dolphin.git] / src / panels / places / placespanel.cpp
1 /*
2 * SPDX-FileCopyrightText: 2008-2012 Peter Penz <peter.penz19@gmail.com>
3 *
4 * Based on KFilePlacesView from kdelibs:
5 * SPDX-FileCopyrightText: 2007 Kevin Ottens <ervin@kde.org>
6 * SPDX-FileCopyrightText: 2007 David Faure <faure@kde.org>
7 *
8 * SPDX-License-Identifier: GPL-2.0-or-later
9 */
10
11 #include "placespanel.h"
12
13 #include "dolphin_generalsettings.h"
14 #include "global.h"
15 #include "kitemviews/kitemlistcontainer.h"
16 #include "kitemviews/kitemlistcontroller.h"
17 #include "kitemviews/kitemlistselectionmanager.h"
18 #include "kitemviews/kstandarditem.h"
19 #include "placesitem.h"
20 #include "placesitemlistgroupheader.h"
21 #include "placesitemlistwidget.h"
22 #include "placesitemmodel.h"
23 #include "placesview.h"
24 #include "trash/dolphintrash.h"
25 #include "views/draganddrophelper.h"
26
27 #include <KFilePlaceEditDialog>
28 #include <KFilePlacesModel>
29 #include <KIO/DropJob>
30 #include <KIO/EmptyTrashJob>
31 #include <KIO/Job>
32 #include <KIconLoader>
33 #include <KLocalizedString>
34 #include <KMountPoint>
35 #include <KPropertiesDialog>
36
37 #include <QGraphicsSceneDragDropEvent>
38 #include <QIcon>
39 #include <QMenu>
40 #include <QMimeData>
41 #include <QVBoxLayout>
42
43 PlacesPanel::PlacesPanel(QWidget* parent) :
44 Panel(parent),
45 m_controller(nullptr),
46 m_model(nullptr),
47 m_view(nullptr),
48 m_storageSetupFailedUrl(),
49 m_triggerStorageSetupButton(),
50 m_itemDropEventIndex(-1),
51 m_itemDropEventMimeData(nullptr),
52 m_itemDropEvent(nullptr)
53 {
54 }
55
56 PlacesPanel::~PlacesPanel()
57 {
58 }
59
60 void PlacesPanel::proceedWithTearDown()
61 {
62 m_model->proceedWithTearDown();
63 }
64
65 bool PlacesPanel::urlChanged()
66 {
67 if (!url().isValid() || url().scheme().contains(QLatin1String("search"))) {
68 // Skip results shown by a search, as possible identical
69 // directory names are useless without parent-path information.
70 return false;
71 }
72
73 if (m_controller) {
74 selectClosestItem();
75 }
76
77 return true;
78 }
79
80 void PlacesPanel::readSettings()
81 {
82 if (m_controller) {
83 const int delay = GeneralSettings::autoExpandFolders() ? 750 : -1;
84 m_controller->setAutoActivationDelay(delay);
85 }
86 }
87
88 void PlacesPanel::showEvent(QShowEvent* event)
89 {
90 if (event->spontaneous()) {
91 Panel::showEvent(event);
92 return;
93 }
94
95 if (!m_controller) {
96 // Postpone the creating of the controller to the first show event.
97 // This assures that no performance and memory overhead is given when the folders panel is not
98 // used at all and stays invisible.
99 m_model = new PlacesItemModel(this);
100 m_model->setGroupedSorting(true);
101 connect(m_model, &PlacesItemModel::errorMessage,
102 this, &PlacesPanel::errorMessage);
103 connect(m_model, &PlacesItemModel::storageTearDownRequested,
104 this, &PlacesPanel::storageTearDownRequested);
105 connect(m_model, &PlacesItemModel::storageTearDownExternallyRequested,
106 this, &PlacesPanel::storageTearDownExternallyRequested);
107 connect(m_model, &PlacesItemModel::storageTearDownSuccessful,
108 this, &PlacesPanel::storageTearDownSuccessful);
109
110 m_view = new PlacesView();
111 m_view->setWidgetCreator(new KItemListWidgetCreator<PlacesItemListWidget>());
112 m_view->setGroupHeaderCreator(new KItemListGroupHeaderCreator<PlacesItemListGroupHeader>());
113
114 m_controller = new KItemListController(m_model, m_view, this);
115 m_controller->setSelectionBehavior(KItemListController::SingleSelection);
116 m_controller->setSingleClickActivationEnforced(true);
117
118 readSettings();
119
120 connect(m_controller, &KItemListController::itemActivated, this, &PlacesPanel::slotItemActivated);
121 connect(m_controller, &KItemListController::itemMiddleClicked, this, &PlacesPanel::slotItemMiddleClicked);
122 connect(m_controller, &KItemListController::itemContextMenuRequested, this, &PlacesPanel::slotItemContextMenuRequested);
123 connect(m_controller, &KItemListController::viewContextMenuRequested, this, &PlacesPanel::slotViewContextMenuRequested);
124 connect(m_controller, &KItemListController::itemDropEvent, this, &PlacesPanel::slotItemDropEvent);
125 connect(m_controller, &KItemListController::aboveItemDropEvent, this, &PlacesPanel::slotAboveItemDropEvent);
126
127 KItemListContainer* container = new KItemListContainer(m_controller, this);
128 container->setEnabledFrame(false);
129
130 QVBoxLayout* layout = new QVBoxLayout(this);
131 layout->setContentsMargins(0, 0, 0, 0);
132 layout->addWidget(container);
133
134 selectClosestItem();
135 }
136
137 Panel::showEvent(event);
138 }
139
140 void PlacesPanel::slotItemActivated(int index)
141 {
142 triggerItem(index, Qt::LeftButton);
143 }
144
145 void PlacesPanel::slotItemMiddleClicked(int index)
146 {
147 triggerItem(index, Qt::MiddleButton);
148 }
149
150 void PlacesPanel::slotItemContextMenuRequested(int index, const QPointF& pos)
151 {
152 PlacesItem* item = m_model->placesItem(index);
153 if (!item) {
154 return;
155 }
156
157 QMenu menu(this);
158
159 QAction* emptyTrashAction = nullptr;
160 QAction* editAction = nullptr;
161 QAction* teardownAction = nullptr;
162 QAction* ejectAction = nullptr;
163 QAction* mountAction = nullptr;
164
165 const bool isDevice = !item->udi().isEmpty();
166 const bool isTrash = (item->url().scheme() == QLatin1String("trash"));
167 if (isTrash) {
168 emptyTrashAction = menu.addAction(QIcon::fromTheme(QStringLiteral("trash-empty")), i18nc("@action:inmenu", "Empty Trash"));
169 emptyTrashAction->setEnabled(item->icon() == QLatin1String("user-trash-full"));
170 menu.addSeparator();
171 }
172
173 QAction* openInNewTabAction = menu.addAction(QIcon::fromTheme(QStringLiteral("tab-new")), i18nc("@item:inmenu", "Open in New Tab"));
174 QAction* openInNewWindowAction = menu.addAction(QIcon::fromTheme(QStringLiteral("window-new")), i18nc("@item:inmenu", "Open in New Window"));
175 QAction* propertiesAction = nullptr;
176 if (item->url().isLocalFile()) {
177 propertiesAction = menu.addAction(QIcon::fromTheme(QStringLiteral("document-properties")), i18nc("@action:inmenu", "Properties"));
178 }
179 if (!isDevice && !isTrash) {
180 menu.addSeparator();
181 }
182
183 if (isDevice) {
184 ejectAction = m_model->ejectAction(index);
185 if (ejectAction) {
186 ejectAction->setParent(&menu);
187 menu.addAction(ejectAction);
188 }
189
190 teardownAction = m_model->teardownAction(index);
191 if (teardownAction) {
192 // Disable teardown option for root and home partitions
193 bool teardownEnabled = item->url() != QUrl::fromLocalFile(QDir::rootPath());
194 if (teardownEnabled) {
195 KMountPoint::Ptr mountPoint = KMountPoint::currentMountPoints().findByPath(QDir::homePath());
196 if (mountPoint && item->url() == QUrl::fromLocalFile(mountPoint->mountPoint())) {
197 teardownEnabled = false;
198 }
199 }
200 teardownAction->setEnabled(teardownEnabled);
201
202 teardownAction->setParent(&menu);
203 menu.addAction(teardownAction);
204 }
205
206 if (item->storageSetupNeeded()) {
207 mountAction = menu.addAction(QIcon::fromTheme(QStringLiteral("media-mount")), i18nc("@action:inmenu", "Mount"));
208 }
209
210 if (teardownAction || ejectAction || mountAction) {
211 menu.addSeparator();
212 }
213 }
214
215 if (!isDevice) {
216 editAction = menu.addAction(QIcon::fromTheme(QStringLiteral("edit-entry")), i18nc("@item:inmenu", "Edit..."));
217 }
218
219 QAction* removeAction = nullptr;
220 if (!isDevice && !item->isSystemItem()) {
221 removeAction = menu.addAction(QIcon::fromTheme(QStringLiteral("edit-delete")), i18nc("@item:inmenu", "Remove"));
222 }
223
224 QAction* hideAction = menu.addAction(QIcon::fromTheme(QStringLiteral("view-hidden")), i18nc("@item:inmenu", "Hide"));
225 hideAction->setCheckable(true);
226 hideAction->setChecked(item->isHidden());
227
228 buildGroupContextMenu(&menu, index);
229
230 QAction* action = menu.exec(pos.toPoint());
231 if (action) {
232 if (action == emptyTrashAction) {
233 Trash::empty(this);
234 } else {
235 // The index might have changed if devices were added/removed while
236 // the context menu was open.
237 index = m_model->index(item);
238 if (index < 0) {
239 // The item is not in the model any more, probably because it was an
240 // external device that has been removed while the context menu was open.
241 return;
242 }
243
244 if (action == editAction) {
245 editEntry(index);
246 } else if (action == removeAction) {
247 m_model->deleteItem(index);
248 } else if (action == hideAction) {
249 item->setHidden(hideAction->isChecked());
250 if (!m_model->hiddenCount()) {
251 showHiddenEntries(false);
252 }
253 } else if (action == openInNewWindowAction) {
254 Dolphin::openNewWindow({KFilePlacesModel::convertedUrl(m_model->data(index).value("url").toUrl())}, this);
255 } else if (action == openInNewTabAction) {
256 // TriggerItem does set up the storage first and then it will
257 // emit the slotItemMiddleClicked signal, because of Qt::MiddleButton.
258 triggerItem(index, Qt::MiddleButton);
259 } else if (action == mountAction) {
260 m_model->requestStorageSetup(index);
261 } else if (action == teardownAction) {
262 m_model->requestTearDown(index);
263 } else if (action == ejectAction) {
264 m_model->requestEject(index);
265 } else if (action == propertiesAction) {
266 KPropertiesDialog* dialog = new KPropertiesDialog(item->url(), this);
267 dialog->setAttribute(Qt::WA_DeleteOnClose);
268 dialog->show();
269 }
270 }
271 }
272
273 selectClosestItem();
274 }
275
276 void PlacesPanel::slotViewContextMenuRequested(const QPointF& pos)
277 {
278 QMenu menu(this);
279
280 QAction* addAction = menu.addAction(QIcon::fromTheme(QStringLiteral("document-new")), i18nc("@item:inmenu", "Add Entry..."));
281
282 QAction* showAllAction = menu.addAction(i18nc("@item:inmenu", "Show Hidden Places"));
283 showAllAction->setCheckable(true);
284 showAllAction->setChecked(m_model->hiddenItemsShown());
285 showAllAction->setIcon(QIcon::fromTheme(m_model->hiddenItemsShown() ? QStringLiteral("view-visible") : QStringLiteral("view-hidden")));
286 showAllAction->setEnabled(m_model->hiddenCount());
287
288 buildGroupContextMenu(&menu, m_controller->indexCloseToMousePressedPosition());
289
290 QMenu* iconSizeSubMenu = new QMenu(i18nc("@item:inmenu", "Icon Size"), &menu);
291
292 struct IconSizeInfo
293 {
294 int size;
295 const char* context;
296 const char* text;
297 };
298
299 const int iconSizeCount = 4;
300 static const IconSizeInfo iconSizes[iconSizeCount] = {
301 {KIconLoader::SizeSmall, I18NC_NOOP("Small icon size", "Small (%1x%2)")},
302 {KIconLoader::SizeSmallMedium, I18NC_NOOP("Medium icon size", "Medium (%1x%2)")},
303 {KIconLoader::SizeMedium, I18NC_NOOP("Large icon size", "Large (%1x%2)")},
304 {KIconLoader::SizeLarge, I18NC_NOOP("Huge icon size", "Huge (%1x%2)")}
305 };
306
307 QHash<QAction*, int> iconSizeActionMap;
308 QActionGroup* iconSizeGroup = new QActionGroup(iconSizeSubMenu);
309
310 for (int i = 0; i < iconSizeCount; ++i) {
311 const int size = iconSizes[i].size;
312 const QString text = i18nc(iconSizes[i].context, iconSizes[i].text,
313 size, size);
314
315 QAction* action = iconSizeSubMenu->addAction(text);
316 iconSizeActionMap.insert(action, size);
317 action->setActionGroup(iconSizeGroup);
318 action->setCheckable(true);
319 action->setChecked(m_view->iconSize() == size);
320 }
321
322 menu.addMenu(iconSizeSubMenu);
323
324 menu.addSeparator();
325 foreach (QAction* action, customContextMenuActions()) {
326 menu.addAction(action);
327 }
328
329 QAction* action = menu.exec(pos.toPoint());
330 if (action) {
331 if (action == addAction) {
332 addEntry();
333 } else if (action == showAllAction) {
334 showHiddenEntries(showAllAction->isChecked());
335 } else if (iconSizeActionMap.contains(action)) {
336 m_view->setIconSize(iconSizeActionMap.value(action));
337 }
338 }
339
340 selectClosestItem();
341 }
342
343 QAction *PlacesPanel::buildGroupContextMenu(QMenu *menu, int index)
344 {
345 if (index == -1) {
346 return nullptr;
347 }
348
349 KFilePlacesModel::GroupType groupType = m_model->groupType(index);
350 QAction *hideGroupAction = menu->addAction(QIcon::fromTheme(QStringLiteral("view-hidden")), i18nc("@item:inmenu", "Hide Section '%1'", m_model->item(index)->group()));
351 hideGroupAction->setCheckable(true);
352 hideGroupAction->setChecked(m_model->isGroupHidden(groupType));
353
354 connect(hideGroupAction, &QAction::triggered, this, [this, groupType, hideGroupAction]{
355 m_model->setGroupHidden(groupType, hideGroupAction->isChecked());
356 if (!m_model->hiddenCount()) {
357 showHiddenEntries(false);
358 }
359 });
360
361 return hideGroupAction;
362 }
363
364 void PlacesPanel::slotItemDropEvent(int index, QGraphicsSceneDragDropEvent* event)
365 {
366 if (index < 0) {
367 return;
368 }
369
370 const PlacesItem* destItem = m_model->placesItem(index);
371
372 if (destItem->isSearchOrTimelineUrl()) {
373 return;
374 }
375
376 if (m_model->storageSetupNeeded(index)) {
377 connect(m_model, &PlacesItemModel::storageSetupDone,
378 this, &PlacesPanel::slotItemDropEventStorageSetupDone);
379
380 m_itemDropEventIndex = index;
381
382 // Make a full copy of the Mime-Data
383 m_itemDropEventMimeData = new QMimeData;
384 m_itemDropEventMimeData->setText(event->mimeData()->text());
385 m_itemDropEventMimeData->setHtml(event->mimeData()->html());
386 m_itemDropEventMimeData->setUrls(event->mimeData()->urls());
387 m_itemDropEventMimeData->setImageData(event->mimeData()->imageData());
388 m_itemDropEventMimeData->setColorData(event->mimeData()->colorData());
389
390 m_itemDropEvent = new QDropEvent(event->pos().toPoint(),
391 event->possibleActions(),
392 m_itemDropEventMimeData,
393 event->buttons(),
394 event->modifiers());
395
396 m_model->requestStorageSetup(index);
397 return;
398 }
399
400 QUrl destUrl = destItem->url();
401 QDropEvent dropEvent(event->pos().toPoint(),
402 event->possibleActions(),
403 event->mimeData(),
404 event->buttons(),
405 event->modifiers());
406
407 slotUrlsDropped(destUrl, &dropEvent, this);
408 }
409
410 void PlacesPanel::slotItemDropEventStorageSetupDone(int index, bool success)
411 {
412 disconnect(m_model, &PlacesItemModel::storageSetupDone,
413 this, &PlacesPanel::slotItemDropEventStorageSetupDone);
414
415 if ((index == m_itemDropEventIndex) && m_itemDropEvent && m_itemDropEventMimeData) {
416 if (success) {
417 QUrl destUrl = m_model->placesItem(index)->url();
418 slotUrlsDropped(destUrl, m_itemDropEvent, this);
419 }
420
421 delete m_itemDropEventMimeData;
422 delete m_itemDropEvent;
423
424 m_itemDropEventIndex = -1;
425 m_itemDropEventMimeData = nullptr;
426 m_itemDropEvent = nullptr;
427 }
428 }
429
430 void PlacesPanel::slotAboveItemDropEvent(int index, QGraphicsSceneDragDropEvent* event)
431 {
432 m_model->dropMimeDataBefore(index, event->mimeData());
433 }
434
435 void PlacesPanel::slotUrlsDropped(const QUrl& dest, QDropEvent* event, QWidget* parent)
436 {
437 KIO::DropJob *job = DragAndDropHelper::dropUrls(dest, event, parent);
438 if (job) {
439 connect(job, &KIO::DropJob::result, this, [this](KJob *job) { if (job->error()) emit errorMessage(job->errorString()); });
440 }
441 }
442
443 void PlacesPanel::slotStorageSetupDone(int index, bool success)
444 {
445 disconnect(m_model, &PlacesItemModel::storageSetupDone,
446 this, &PlacesPanel::slotStorageSetupDone);
447
448 if (m_triggerStorageSetupButton == Qt::NoButton) {
449 return;
450 }
451
452 if (success) {
453 Q_ASSERT(!m_model->storageSetupNeeded(index));
454 triggerItem(index, m_triggerStorageSetupButton);
455 m_triggerStorageSetupButton = Qt::NoButton;
456 } else {
457 setUrl(m_storageSetupFailedUrl);
458 m_storageSetupFailedUrl = QUrl();
459 }
460 }
461
462 void PlacesPanel::addEntry()
463 {
464 const int index = m_controller->selectionManager()->currentItem();
465 const QUrl url = m_model->data(index).value("url").toUrl();
466 const QString text = url.fileName().isEmpty() ? url.toDisplayString(QUrl::PreferLocalFile) : url.fileName();
467
468 QPointer<KFilePlaceEditDialog> dialog = new KFilePlaceEditDialog(true, url, text, QString(), true, false, KIconLoader::SizeMedium, this);
469 if (dialog->exec() == QDialog::Accepted) {
470 const QString appName = dialog->applicationLocal() ? QCoreApplication::applicationName() : QString();
471 m_model->createPlacesItem(dialog->label(), dialog->url(), dialog->icon(), appName);
472 }
473
474 delete dialog;
475 }
476
477 void PlacesPanel::editEntry(int index)
478 {
479 QHash<QByteArray, QVariant> data = m_model->data(index);
480 const QUrl url = data.value("url").toUrl();
481 const QString text = data.value("text").toString();
482 const QString iconName = data.value("iconName").toString();
483 const bool applicationLocal = !data.value("applicationName").toString().isEmpty();
484
485 QPointer<KFilePlaceEditDialog> dialog = new KFilePlaceEditDialog(true, url, text, iconName, true, applicationLocal, KIconLoader::SizeMedium, this);
486 if (dialog->exec() == QDialog::Accepted) {
487 PlacesItem* oldItem = m_model->placesItem(index);
488 if (oldItem) {
489 const QString appName = dialog->applicationLocal() ? QCoreApplication::applicationName() : QString();
490 oldItem->setApplicationName(appName);
491 oldItem->setText(dialog->label());
492 oldItem->setUrl(dialog->url());
493 oldItem->setIcon(dialog->icon());
494 m_model->refresh();
495 }
496 }
497
498 delete dialog;
499 }
500
501 void PlacesPanel::selectClosestItem()
502 {
503 const int index = m_model->closestItem(url());
504 KItemListSelectionManager* selectionManager = m_controller->selectionManager();
505 selectionManager->setCurrentItem(index);
506 selectionManager->clearSelection();
507 selectionManager->setSelected(index);
508 }
509
510 void PlacesPanel::triggerItem(int index, Qt::MouseButton button)
511 {
512 const PlacesItem* item = m_model->placesItem(index);
513 if (!item) {
514 return;
515 }
516
517 if (m_model->storageSetupNeeded(index)) {
518 m_triggerStorageSetupButton = button;
519 m_storageSetupFailedUrl = url();
520
521 connect(m_model, &PlacesItemModel::storageSetupDone,
522 this, &PlacesPanel::slotStorageSetupDone);
523
524 m_model->requestStorageSetup(index);
525 } else {
526 m_triggerStorageSetupButton = Qt::NoButton;
527
528 const QUrl url = m_model->data(index).value("url").toUrl();
529 if (!url.isEmpty()) {
530 if (button == Qt::MiddleButton) {
531 emit placeMiddleClicked(KFilePlacesModel::convertedUrl(url));
532 } else {
533 emit placeActivated(KFilePlacesModel::convertedUrl(url));
534 }
535 }
536 }
537 }
538
539 void PlacesPanel::showHiddenEntries(bool shown)
540 {
541 m_model->setHiddenItemsShown(shown);
542 emit showHiddenEntriesChanged(shown);
543 }
544
545 int PlacesPanel::hiddenListCount()
546 {
547 if(!m_model) {
548 return 0;
549 }
550 return m_model->hiddenCount();
551 }