]> cloud.milkyroute.net Git - dolphin.git/blob - src/panels/places/placespanel.cpp
Output of licensedigger + manual cleanup afterwards.
[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
108 m_view = new PlacesView();
109 m_view->setWidgetCreator(new KItemListWidgetCreator<PlacesItemListWidget>());
110 m_view->setGroupHeaderCreator(new KItemListGroupHeaderCreator<PlacesItemListGroupHeader>());
111
112 m_controller = new KItemListController(m_model, m_view, this);
113 m_controller->setSelectionBehavior(KItemListController::SingleSelection);
114 m_controller->setSingleClickActivationEnforced(true);
115
116 readSettings();
117
118 connect(m_controller, &KItemListController::itemActivated, this, &PlacesPanel::slotItemActivated);
119 connect(m_controller, &KItemListController::itemMiddleClicked, this, &PlacesPanel::slotItemMiddleClicked);
120 connect(m_controller, &KItemListController::itemContextMenuRequested, this, &PlacesPanel::slotItemContextMenuRequested);
121 connect(m_controller, &KItemListController::viewContextMenuRequested, this, &PlacesPanel::slotViewContextMenuRequested);
122 connect(m_controller, &KItemListController::itemDropEvent, this, &PlacesPanel::slotItemDropEvent);
123 connect(m_controller, &KItemListController::aboveItemDropEvent, this, &PlacesPanel::slotAboveItemDropEvent);
124
125 KItemListContainer* container = new KItemListContainer(m_controller, this);
126 container->setEnabledFrame(false);
127
128 QVBoxLayout* layout = new QVBoxLayout(this);
129 layout->setContentsMargins(0, 0, 0, 0);
130 layout->addWidget(container);
131
132 selectClosestItem();
133 }
134
135 Panel::showEvent(event);
136 }
137
138 void PlacesPanel::slotItemActivated(int index)
139 {
140 triggerItem(index, Qt::LeftButton);
141 }
142
143 void PlacesPanel::slotItemMiddleClicked(int index)
144 {
145 triggerItem(index, Qt::MiddleButton);
146 }
147
148 void PlacesPanel::slotItemContextMenuRequested(int index, const QPointF& pos)
149 {
150 PlacesItem* item = m_model->placesItem(index);
151 if (!item) {
152 return;
153 }
154
155 QMenu menu(this);
156
157 QAction* emptyTrashAction = nullptr;
158 QAction* editAction = nullptr;
159 QAction* teardownAction = nullptr;
160 QAction* ejectAction = nullptr;
161 QAction* mountAction = nullptr;
162
163 const bool isDevice = !item->udi().isEmpty();
164 const bool isTrash = (item->url().scheme() == QLatin1String("trash"));
165 if (isTrash) {
166 emptyTrashAction = menu.addAction(QIcon::fromTheme(QStringLiteral("trash-empty")), i18nc("@action:inmenu", "Empty Trash"));
167 emptyTrashAction->setEnabled(item->icon() == QLatin1String("user-trash-full"));
168 menu.addSeparator();
169 }
170
171 QAction* openInNewTabAction = menu.addAction(QIcon::fromTheme(QStringLiteral("tab-new")), i18nc("@item:inmenu", "Open in New Tab"));
172 QAction* openInNewWindowAction = menu.addAction(QIcon::fromTheme(QStringLiteral("window-new")), i18nc("@item:inmenu", "Open in New Window"));
173 QAction* propertiesAction = nullptr;
174 if (item->url().isLocalFile()) {
175 propertiesAction = menu.addAction(QIcon::fromTheme(QStringLiteral("document-properties")), i18nc("@action:inmenu", "Properties"));
176 }
177 if (!isDevice && !isTrash) {
178 menu.addSeparator();
179 }
180
181 if (isDevice) {
182 ejectAction = m_model->ejectAction(index);
183 if (ejectAction) {
184 ejectAction->setParent(&menu);
185 menu.addAction(ejectAction);
186 }
187
188 teardownAction = m_model->teardownAction(index);
189 if (teardownAction) {
190 // Disable teardown option for root and home partitions
191 bool teardownEnabled = item->url() != QUrl::fromLocalFile(QDir::rootPath());
192 if (teardownEnabled) {
193 KMountPoint::Ptr mountPoint = KMountPoint::currentMountPoints().findByPath(QDir::homePath());
194 if (mountPoint && item->url() == QUrl::fromLocalFile(mountPoint->mountPoint())) {
195 teardownEnabled = false;
196 }
197 }
198 teardownAction->setEnabled(teardownEnabled);
199
200 teardownAction->setParent(&menu);
201 menu.addAction(teardownAction);
202 }
203
204 if (item->storageSetupNeeded()) {
205 mountAction = menu.addAction(QIcon::fromTheme(QStringLiteral("media-mount")), i18nc("@action:inmenu", "Mount"));
206 }
207
208 if (teardownAction || ejectAction || mountAction) {
209 menu.addSeparator();
210 }
211 }
212
213 if (!isDevice) {
214 editAction = menu.addAction(QIcon::fromTheme(QStringLiteral("edit-entry")), i18nc("@item:inmenu", "Edit..."));
215 }
216
217 QAction* removeAction = nullptr;
218 if (!isDevice && !item->isSystemItem()) {
219 removeAction = menu.addAction(QIcon::fromTheme(QStringLiteral("edit-delete")), i18nc("@item:inmenu", "Remove"));
220 }
221
222 QAction* hideAction = menu.addAction(QIcon::fromTheme(QStringLiteral("view-hidden")), i18nc("@item:inmenu", "Hide"));
223 hideAction->setCheckable(true);
224 hideAction->setChecked(item->isHidden());
225
226 buildGroupContextMenu(&menu, index);
227
228 QAction* action = menu.exec(pos.toPoint());
229 if (action) {
230 if (action == emptyTrashAction) {
231 Trash::empty(this);
232 } else {
233 // The index might have changed if devices were added/removed while
234 // the context menu was open.
235 index = m_model->index(item);
236 if (index < 0) {
237 // The item is not in the model any more, probably because it was an
238 // external device that has been removed while the context menu was open.
239 return;
240 }
241
242 if (action == editAction) {
243 editEntry(index);
244 } else if (action == removeAction) {
245 m_model->deleteItem(index);
246 } else if (action == hideAction) {
247 item->setHidden(hideAction->isChecked());
248 if (!m_model->hiddenCount()) {
249 showHiddenEntries(false);
250 }
251 } else if (action == openInNewWindowAction) {
252 Dolphin::openNewWindow({KFilePlacesModel::convertedUrl(m_model->data(index).value("url").toUrl())}, this);
253 } else if (action == openInNewTabAction) {
254 // TriggerItem does set up the storage first and then it will
255 // emit the slotItemMiddleClicked signal, because of Qt::MiddleButton.
256 triggerItem(index, Qt::MiddleButton);
257 } else if (action == mountAction) {
258 m_model->requestStorageSetup(index);
259 } else if (action == teardownAction) {
260 m_model->requestTearDown(index);
261 } else if (action == ejectAction) {
262 m_model->requestEject(index);
263 } else if (action == propertiesAction) {
264 KPropertiesDialog* dialog = new KPropertiesDialog(item->url(), this);
265 dialog->setAttribute(Qt::WA_DeleteOnClose);
266 dialog->show();
267 }
268 }
269 }
270
271 selectClosestItem();
272 }
273
274 void PlacesPanel::slotViewContextMenuRequested(const QPointF& pos)
275 {
276 QMenu menu(this);
277
278 QAction* addAction = menu.addAction(QIcon::fromTheme(QStringLiteral("document-new")), i18nc("@item:inmenu", "Add Entry..."));
279
280 QAction* showAllAction = menu.addAction(i18nc("@item:inmenu", "Show Hidden Places"));
281 showAllAction->setCheckable(true);
282 showAllAction->setChecked(m_model->hiddenItemsShown());
283 showAllAction->setIcon(QIcon::fromTheme(m_model->hiddenItemsShown() ? QStringLiteral("view-visible") : QStringLiteral("view-hidden")));
284 showAllAction->setEnabled(m_model->hiddenCount());
285
286 buildGroupContextMenu(&menu, m_controller->indexCloseToMousePressedPosition());
287
288 QMenu* iconSizeSubMenu = new QMenu(i18nc("@item:inmenu", "Icon Size"), &menu);
289
290 struct IconSizeInfo
291 {
292 int size;
293 const char* context;
294 const char* text;
295 };
296
297 const int iconSizeCount = 4;
298 static const IconSizeInfo iconSizes[iconSizeCount] = {
299 {KIconLoader::SizeSmall, I18NC_NOOP("Small icon size", "Small (%1x%2)")},
300 {KIconLoader::SizeSmallMedium, I18NC_NOOP("Medium icon size", "Medium (%1x%2)")},
301 {KIconLoader::SizeMedium, I18NC_NOOP("Large icon size", "Large (%1x%2)")},
302 {KIconLoader::SizeLarge, I18NC_NOOP("Huge icon size", "Huge (%1x%2)")}
303 };
304
305 QHash<QAction*, int> iconSizeActionMap;
306 QActionGroup* iconSizeGroup = new QActionGroup(iconSizeSubMenu);
307
308 for (int i = 0; i < iconSizeCount; ++i) {
309 const int size = iconSizes[i].size;
310 const QString text = i18nc(iconSizes[i].context, iconSizes[i].text,
311 size, size);
312
313 QAction* action = iconSizeSubMenu->addAction(text);
314 iconSizeActionMap.insert(action, size);
315 action->setActionGroup(iconSizeGroup);
316 action->setCheckable(true);
317 action->setChecked(m_view->iconSize() == size);
318 }
319
320 menu.addMenu(iconSizeSubMenu);
321
322 menu.addSeparator();
323 foreach (QAction* action, customContextMenuActions()) {
324 menu.addAction(action);
325 }
326
327 QAction* action = menu.exec(pos.toPoint());
328 if (action) {
329 if (action == addAction) {
330 addEntry();
331 } else if (action == showAllAction) {
332 showHiddenEntries(showAllAction->isChecked());
333 } else if (iconSizeActionMap.contains(action)) {
334 m_view->setIconSize(iconSizeActionMap.value(action));
335 }
336 }
337
338 selectClosestItem();
339 }
340
341 QAction *PlacesPanel::buildGroupContextMenu(QMenu *menu, int index)
342 {
343 if (index == -1) {
344 return nullptr;
345 }
346
347 KFilePlacesModel::GroupType groupType = m_model->groupType(index);
348 QAction *hideGroupAction = menu->addAction(QIcon::fromTheme(QStringLiteral("view-hidden")), i18nc("@item:inmenu", "Hide Section '%1'", m_model->item(index)->group()));
349 hideGroupAction->setCheckable(true);
350 hideGroupAction->setChecked(m_model->isGroupHidden(groupType));
351
352 connect(hideGroupAction, &QAction::triggered, this, [this, groupType, hideGroupAction]{
353 m_model->setGroupHidden(groupType, hideGroupAction->isChecked());
354 if (!m_model->hiddenCount()) {
355 showHiddenEntries(false);
356 }
357 });
358
359 return hideGroupAction;
360 }
361
362 void PlacesPanel::slotItemDropEvent(int index, QGraphicsSceneDragDropEvent* event)
363 {
364 if (index < 0) {
365 return;
366 }
367
368 const PlacesItem* destItem = m_model->placesItem(index);
369
370 if (destItem->isSearchOrTimelineUrl()) {
371 return;
372 }
373
374 if (m_model->storageSetupNeeded(index)) {
375 connect(m_model, &PlacesItemModel::storageSetupDone,
376 this, &PlacesPanel::slotItemDropEventStorageSetupDone);
377
378 m_itemDropEventIndex = index;
379
380 // Make a full copy of the Mime-Data
381 m_itemDropEventMimeData = new QMimeData;
382 m_itemDropEventMimeData->setText(event->mimeData()->text());
383 m_itemDropEventMimeData->setHtml(event->mimeData()->html());
384 m_itemDropEventMimeData->setUrls(event->mimeData()->urls());
385 m_itemDropEventMimeData->setImageData(event->mimeData()->imageData());
386 m_itemDropEventMimeData->setColorData(event->mimeData()->colorData());
387
388 m_itemDropEvent = new QDropEvent(event->pos().toPoint(),
389 event->possibleActions(),
390 m_itemDropEventMimeData,
391 event->buttons(),
392 event->modifiers());
393
394 m_model->requestStorageSetup(index);
395 return;
396 }
397
398 QUrl destUrl = destItem->url();
399 QDropEvent dropEvent(event->pos().toPoint(),
400 event->possibleActions(),
401 event->mimeData(),
402 event->buttons(),
403 event->modifiers());
404
405 slotUrlsDropped(destUrl, &dropEvent, this);
406 }
407
408 void PlacesPanel::slotItemDropEventStorageSetupDone(int index, bool success)
409 {
410 disconnect(m_model, &PlacesItemModel::storageSetupDone,
411 this, &PlacesPanel::slotItemDropEventStorageSetupDone);
412
413 if ((index == m_itemDropEventIndex) && m_itemDropEvent && m_itemDropEventMimeData) {
414 if (success) {
415 QUrl destUrl = m_model->placesItem(index)->url();
416 slotUrlsDropped(destUrl, m_itemDropEvent, this);
417 }
418
419 delete m_itemDropEventMimeData;
420 delete m_itemDropEvent;
421
422 m_itemDropEventIndex = -1;
423 m_itemDropEventMimeData = nullptr;
424 m_itemDropEvent = nullptr;
425 }
426 }
427
428 void PlacesPanel::slotAboveItemDropEvent(int index, QGraphicsSceneDragDropEvent* event)
429 {
430 m_model->dropMimeDataBefore(index, event->mimeData());
431 }
432
433 void PlacesPanel::slotUrlsDropped(const QUrl& dest, QDropEvent* event, QWidget* parent)
434 {
435 KIO::DropJob *job = DragAndDropHelper::dropUrls(dest, event, parent);
436 if (job) {
437 connect(job, &KIO::DropJob::result, this, [this](KJob *job) { if (job->error()) emit errorMessage(job->errorString()); });
438 }
439 }
440
441 void PlacesPanel::slotStorageSetupDone(int index, bool success)
442 {
443 disconnect(m_model, &PlacesItemModel::storageSetupDone,
444 this, &PlacesPanel::slotStorageSetupDone);
445
446 if (m_triggerStorageSetupButton == Qt::NoButton) {
447 return;
448 }
449
450 if (success) {
451 Q_ASSERT(!m_model->storageSetupNeeded(index));
452 triggerItem(index, m_triggerStorageSetupButton);
453 m_triggerStorageSetupButton = Qt::NoButton;
454 } else {
455 setUrl(m_storageSetupFailedUrl);
456 m_storageSetupFailedUrl = QUrl();
457 }
458 }
459
460 void PlacesPanel::addEntry()
461 {
462 const int index = m_controller->selectionManager()->currentItem();
463 const QUrl url = m_model->data(index).value("url").toUrl();
464 const QString text = url.fileName().isEmpty() ? url.toDisplayString(QUrl::PreferLocalFile) : url.fileName();
465
466 QPointer<KFilePlaceEditDialog> dialog = new KFilePlaceEditDialog(true, url, text, QString(), true, false, KIconLoader::SizeMedium, this);
467 if (dialog->exec() == QDialog::Accepted) {
468 const QString appName = dialog->applicationLocal() ? QCoreApplication::applicationName() : QString();
469 m_model->createPlacesItem(dialog->label(), dialog->url(), dialog->icon(), appName);
470 }
471
472 delete dialog;
473 }
474
475 void PlacesPanel::editEntry(int index)
476 {
477 QHash<QByteArray, QVariant> data = m_model->data(index);
478 const QUrl url = data.value("url").toUrl();
479 const QString text = data.value("text").toString();
480 const QString iconName = data.value("iconName").toString();
481 const bool applicationLocal = !data.value("applicationName").toString().isEmpty();
482
483 QPointer<KFilePlaceEditDialog> dialog = new KFilePlaceEditDialog(true, url, text, iconName, true, applicationLocal, KIconLoader::SizeMedium, this);
484 if (dialog->exec() == QDialog::Accepted) {
485 PlacesItem* oldItem = m_model->placesItem(index);
486 if (oldItem) {
487 const QString appName = dialog->applicationLocal() ? QCoreApplication::applicationName() : QString();
488 oldItem->setApplicationName(appName);
489 oldItem->setText(dialog->label());
490 oldItem->setUrl(dialog->url());
491 oldItem->setIcon(dialog->icon());
492 m_model->refresh();
493 }
494 }
495
496 delete dialog;
497 }
498
499 void PlacesPanel::selectClosestItem()
500 {
501 const int index = m_model->closestItem(url());
502 KItemListSelectionManager* selectionManager = m_controller->selectionManager();
503 selectionManager->setCurrentItem(index);
504 selectionManager->clearSelection();
505 selectionManager->setSelected(index);
506 }
507
508 void PlacesPanel::triggerItem(int index, Qt::MouseButton button)
509 {
510 const PlacesItem* item = m_model->placesItem(index);
511 if (!item) {
512 return;
513 }
514
515 if (m_model->storageSetupNeeded(index)) {
516 m_triggerStorageSetupButton = button;
517 m_storageSetupFailedUrl = url();
518
519 connect(m_model, &PlacesItemModel::storageSetupDone,
520 this, &PlacesPanel::slotStorageSetupDone);
521
522 m_model->requestStorageSetup(index);
523 } else {
524 m_triggerStorageSetupButton = Qt::NoButton;
525
526 const QUrl url = m_model->data(index).value("url").toUrl();
527 if (!url.isEmpty()) {
528 if (button == Qt::MiddleButton) {
529 emit placeMiddleClicked(KFilePlacesModel::convertedUrl(url));
530 } else {
531 emit placeActivated(KFilePlacesModel::convertedUrl(url));
532 }
533 }
534 }
535 }
536
537 void PlacesPanel::showHiddenEntries(bool shown)
538 {
539 m_model->setHiddenItemsShown(shown);
540 emit showHiddenEntriesChanged(shown);
541 }
542
543 int PlacesPanel::hiddenListCount()
544 {
545 if(!m_model) {
546 return 0;
547 }
548 return m_model->hiddenCount();
549 }