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