]> cloud.milkyroute.net Git - dolphin.git/blob - src/panels/places/placespanel.cpp
Compile without foreach
[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 const auto actions = customContextMenuActions();
326 for (QAction* action : actions) {
327 menu.addAction(action);
328 }
329
330 QAction* action = menu.exec(pos.toPoint());
331 if (action) {
332 if (action == addAction) {
333 addEntry();
334 } else if (action == showAllAction) {
335 showHiddenEntries(showAllAction->isChecked());
336 } else if (iconSizeActionMap.contains(action)) {
337 m_view->setIconSize(iconSizeActionMap.value(action));
338 }
339 }
340
341 selectClosestItem();
342 }
343
344 QAction *PlacesPanel::buildGroupContextMenu(QMenu *menu, int index)
345 {
346 if (index == -1) {
347 return nullptr;
348 }
349
350 KFilePlacesModel::GroupType groupType = m_model->groupType(index);
351 QAction *hideGroupAction = menu->addAction(QIcon::fromTheme(QStringLiteral("view-hidden")), i18nc("@item:inmenu", "Hide Section '%1'", m_model->item(index)->group()));
352 hideGroupAction->setCheckable(true);
353 hideGroupAction->setChecked(m_model->isGroupHidden(groupType));
354
355 connect(hideGroupAction, &QAction::triggered, this, [this, groupType, hideGroupAction]{
356 m_model->setGroupHidden(groupType, hideGroupAction->isChecked());
357 if (!m_model->hiddenCount()) {
358 showHiddenEntries(false);
359 }
360 });
361
362 return hideGroupAction;
363 }
364
365 void PlacesPanel::slotItemDropEvent(int index, QGraphicsSceneDragDropEvent* event)
366 {
367 if (index < 0) {
368 return;
369 }
370
371 const PlacesItem* destItem = m_model->placesItem(index);
372
373 if (destItem->isSearchOrTimelineUrl()) {
374 return;
375 }
376
377 if (m_model->storageSetupNeeded(index)) {
378 connect(m_model, &PlacesItemModel::storageSetupDone,
379 this, &PlacesPanel::slotItemDropEventStorageSetupDone);
380
381 m_itemDropEventIndex = index;
382
383 // Make a full copy of the Mime-Data
384 m_itemDropEventMimeData = new QMimeData;
385 m_itemDropEventMimeData->setText(event->mimeData()->text());
386 m_itemDropEventMimeData->setHtml(event->mimeData()->html());
387 m_itemDropEventMimeData->setUrls(event->mimeData()->urls());
388 m_itemDropEventMimeData->setImageData(event->mimeData()->imageData());
389 m_itemDropEventMimeData->setColorData(event->mimeData()->colorData());
390
391 m_itemDropEvent = new QDropEvent(event->pos().toPoint(),
392 event->possibleActions(),
393 m_itemDropEventMimeData,
394 event->buttons(),
395 event->modifiers());
396
397 m_model->requestStorageSetup(index);
398 return;
399 }
400
401 QUrl destUrl = destItem->url();
402 QDropEvent dropEvent(event->pos().toPoint(),
403 event->possibleActions(),
404 event->mimeData(),
405 event->buttons(),
406 event->modifiers());
407
408 slotUrlsDropped(destUrl, &dropEvent, this);
409 }
410
411 void PlacesPanel::slotItemDropEventStorageSetupDone(int index, bool success)
412 {
413 disconnect(m_model, &PlacesItemModel::storageSetupDone,
414 this, &PlacesPanel::slotItemDropEventStorageSetupDone);
415
416 if ((index == m_itemDropEventIndex) && m_itemDropEvent && m_itemDropEventMimeData) {
417 if (success) {
418 QUrl destUrl = m_model->placesItem(index)->url();
419 slotUrlsDropped(destUrl, m_itemDropEvent, this);
420 }
421
422 delete m_itemDropEventMimeData;
423 delete m_itemDropEvent;
424
425 m_itemDropEventIndex = -1;
426 m_itemDropEventMimeData = nullptr;
427 m_itemDropEvent = nullptr;
428 }
429 }
430
431 void PlacesPanel::slotAboveItemDropEvent(int index, QGraphicsSceneDragDropEvent* event)
432 {
433 m_model->dropMimeDataBefore(index, event->mimeData());
434 }
435
436 void PlacesPanel::slotUrlsDropped(const QUrl& dest, QDropEvent* event, QWidget* parent)
437 {
438 KIO::DropJob *job = DragAndDropHelper::dropUrls(dest, event, parent);
439 if (job) {
440 connect(job, &KIO::DropJob::result, this, [this](KJob *job) { if (job->error()) emit errorMessage(job->errorString()); });
441 }
442 }
443
444 void PlacesPanel::slotStorageSetupDone(int index, bool success)
445 {
446 disconnect(m_model, &PlacesItemModel::storageSetupDone,
447 this, &PlacesPanel::slotStorageSetupDone);
448
449 if (m_triggerStorageSetupButton == Qt::NoButton) {
450 return;
451 }
452
453 if (success) {
454 Q_ASSERT(!m_model->storageSetupNeeded(index));
455 triggerItem(index, m_triggerStorageSetupButton);
456 m_triggerStorageSetupButton = Qt::NoButton;
457 } else {
458 setUrl(m_storageSetupFailedUrl);
459 m_storageSetupFailedUrl = QUrl();
460 }
461 }
462
463 void PlacesPanel::addEntry()
464 {
465 const int index = m_controller->selectionManager()->currentItem();
466 const QUrl url = m_model->data(index).value("url").toUrl();
467 const QString text = url.fileName().isEmpty() ? url.toDisplayString(QUrl::PreferLocalFile) : url.fileName();
468
469 QPointer<KFilePlaceEditDialog> dialog = new KFilePlaceEditDialog(true, url, text, QString(), true, false, KIconLoader::SizeMedium, this);
470 if (dialog->exec() == QDialog::Accepted) {
471 const QString appName = dialog->applicationLocal() ? QCoreApplication::applicationName() : QString();
472 m_model->createPlacesItem(dialog->label(), dialog->url(), dialog->icon(), appName);
473 }
474
475 delete dialog;
476 }
477
478 void PlacesPanel::editEntry(int index)
479 {
480 QHash<QByteArray, QVariant> data = m_model->data(index);
481 const QUrl url = data.value("url").toUrl();
482 const QString text = data.value("text").toString();
483 const QString iconName = data.value("iconName").toString();
484 const bool applicationLocal = !data.value("applicationName").toString().isEmpty();
485
486 QPointer<KFilePlaceEditDialog> dialog = new KFilePlaceEditDialog(true, url, text, iconName, true, applicationLocal, KIconLoader::SizeMedium, this);
487 if (dialog->exec() == QDialog::Accepted) {
488 PlacesItem* oldItem = m_model->placesItem(index);
489 if (oldItem) {
490 const QString appName = dialog->applicationLocal() ? QCoreApplication::applicationName() : QString();
491 oldItem->setApplicationName(appName);
492 oldItem->setText(dialog->label());
493 oldItem->setUrl(dialog->url());
494 oldItem->setIcon(dialog->icon());
495 m_model->refresh();
496 }
497 }
498
499 delete dialog;
500 }
501
502 void PlacesPanel::selectClosestItem()
503 {
504 const int index = m_model->closestItem(url());
505 KItemListSelectionManager* selectionManager = m_controller->selectionManager();
506 selectionManager->setCurrentItem(index);
507 selectionManager->clearSelection();
508 selectionManager->setSelected(index);
509 }
510
511 void PlacesPanel::triggerItem(int index, Qt::MouseButton button)
512 {
513 const PlacesItem* item = m_model->placesItem(index);
514 if (!item) {
515 return;
516 }
517
518 if (m_model->storageSetupNeeded(index)) {
519 m_triggerStorageSetupButton = button;
520 m_storageSetupFailedUrl = url();
521
522 connect(m_model, &PlacesItemModel::storageSetupDone,
523 this, &PlacesPanel::slotStorageSetupDone);
524
525 m_model->requestStorageSetup(index);
526 } else {
527 m_triggerStorageSetupButton = Qt::NoButton;
528
529 const QUrl url = m_model->data(index).value("url").toUrl();
530 if (!url.isEmpty()) {
531 if (button == Qt::MiddleButton) {
532 emit placeMiddleClicked(KFilePlacesModel::convertedUrl(url));
533 } else {
534 emit placeActivated(KFilePlacesModel::convertedUrl(url));
535 }
536 }
537 }
538 }
539
540 void PlacesPanel::showHiddenEntries(bool shown)
541 {
542 m_model->setHiddenItemsShown(shown);
543 emit showHiddenEntriesChanged(shown);
544 }
545
546 int PlacesPanel::hiddenListCount()
547 {
548 if(!m_model) {
549 return 0;
550 }
551 return m_model->hiddenCount();
552 }