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