]> cloud.milkyroute.net Git - dolphin.git/blob - src/panels/places/placespanel.cpp
Merge remote-tracking branch 'origin/master' into frameworks
[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
28 #include <KDebug>
29 #include <KDirNotify>
30 #include <QIcon>
31 #include <KIO/Job>
32 #include <KIO/EmptyTrashJob>
33 #include <KIO/JobUiDelegate>
34 #include <KJobWidgets>
35 #include <KLocale>
36 #include <KIconLoader>
37 #include <kitemviews/kitemlistcontainer.h>
38 #include <kitemviews/kitemlistcontroller.h>
39 #include <kitemviews/kitemlistselectionmanager.h>
40 #include <kitemviews/kstandarditem.h>
41 #include <QMenu>
42 #include <KMessageBox>
43 #include <KNotification>
44 #include "placesitem.h"
45 #include "placesitemeditdialog.h"
46 #include "placesitemlistgroupheader.h"
47 #include "placesitemlistwidget.h"
48 #include "placesitemmodel.h"
49 #include "placesview.h"
50 #include <views/draganddrophelper.h>
51 #include <QGraphicsSceneDragDropEvent>
52 #include <QVBoxLayout>
53 #include <QShowEvent>
54 #include <QMimeData>
55
56 PlacesPanel::PlacesPanel(QWidget* parent) :
57 Panel(parent),
58 m_controller(0),
59 m_model(0),
60 m_storageSetupFailedUrl(),
61 m_triggerStorageSetupButton(),
62 m_itemDropEventIndex(-1),
63 m_itemDropEventMimeData(0),
64 m_itemDropEvent(0)
65 {
66 }
67
68 PlacesPanel::~PlacesPanel()
69 {
70 }
71
72 bool PlacesPanel::urlChanged()
73 {
74 if (!url().isValid() || url().protocol().contains("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 selectClosestItem();
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
111 m_view = new PlacesView();
112 m_view->setWidgetCreator(new KItemListWidgetCreator<PlacesItemListWidget>());
113 m_view->setGroupHeaderCreator(new KItemListGroupHeaderCreator<PlacesItemListGroupHeader>());
114
115 m_controller = new KItemListController(m_model, m_view, this);
116 m_controller->setSelectionBehavior(KItemListController::SingleSelection);
117 m_controller->setSingleClickActivationEnforced(true);
118
119 readSettings();
120
121 connect(m_controller, &KItemListController::itemActivated, this, &PlacesPanel::slotItemActivated);
122 connect(m_controller, &KItemListController::itemMiddleClicked, this, &PlacesPanel::slotItemMiddleClicked);
123 connect(m_controller, &KItemListController::itemContextMenuRequested, this, &PlacesPanel::slotItemContextMenuRequested);
124 connect(m_controller, &KItemListController::viewContextMenuRequested, this, &PlacesPanel::slotViewContextMenuRequested);
125 connect(m_controller, &KItemListController::itemDropEvent, this, &PlacesPanel::slotItemDropEvent);
126 connect(m_controller, &KItemListController::aboveItemDropEvent, this, &PlacesPanel::slotAboveItemDropEvent);
127
128 KItemListContainer* container = new KItemListContainer(m_controller, this);
129 container->setEnabledFrame(false);
130
131 QVBoxLayout* layout = new QVBoxLayout(this);
132 layout->setMargin(0);
133 layout->addWidget(container);
134
135 selectClosestItem();
136 }
137
138 Panel::showEvent(event);
139 }
140
141 void PlacesPanel::slotItemActivated(int index)
142 {
143 triggerItem(index, Qt::LeftButton);
144 }
145
146 void PlacesPanel::slotItemMiddleClicked(int index)
147 {
148 triggerItem(index, Qt::MiddleButton);
149 }
150
151 void PlacesPanel::slotItemContextMenuRequested(int index, const QPointF& pos)
152 {
153 PlacesItem* item = m_model->placesItem(index);
154 if (!item) {
155 return;
156 }
157
158 QMenu menu(this);
159
160 QAction* emptyTrashAction = 0;
161 QAction* addAction = 0;
162 QAction* mainSeparator = 0;
163 QAction* editAction = 0;
164 QAction* teardownAction = 0;
165 QAction* ejectAction = 0;
166
167 const QString label = item->text();
168
169 const bool isDevice = !item->udi().isEmpty();
170 if (isDevice) {
171 ejectAction = m_model->ejectAction(index);
172 if (ejectAction) {
173 ejectAction->setParent(&menu);
174 menu.addAction(ejectAction);
175 }
176
177 teardownAction = m_model->teardownAction(index);
178 if (teardownAction) {
179 teardownAction->setParent(&menu);
180 menu.addAction(teardownAction);
181 }
182
183 if (teardownAction || ejectAction) {
184 mainSeparator = menu.addSeparator();
185 }
186 } else {
187 if (item->url() == KUrl("trash:/")) {
188 emptyTrashAction = menu.addAction(QIcon::fromTheme("trash-empty"), i18nc("@action:inmenu", "Empty Trash"));
189 emptyTrashAction->setEnabled(item->icon() == "user-trash-full");
190 menu.addSeparator();
191 }
192 addAction = menu.addAction(QIcon::fromTheme("document-new"), i18nc("@item:inmenu", "Add Entry..."));
193 mainSeparator = menu.addSeparator();
194 editAction = menu.addAction(QIcon::fromTheme("document-properties"), i18nc("@item:inmenu", "Edit '%1'...", label));
195 }
196
197 if (!addAction) {
198 addAction = menu.addAction(QIcon::fromTheme("document-new"), i18nc("@item:inmenu", "Add Entry..."));
199 }
200
201 QAction* openInNewTabAction = menu.addAction(i18nc("@item:inmenu", "Open '%1' in New Tab", label));
202 openInNewTabAction->setIcon(QIcon::fromTheme("tab-new"));
203
204 QAction* removeAction = 0;
205 if (!isDevice && !item->isSystemItem()) {
206 removeAction = menu.addAction(QIcon::fromTheme("edit-delete"), i18nc("@item:inmenu", "Remove '%1'", label));
207 }
208
209 QAction* hideAction = menu.addAction(i18nc("@item:inmenu", "Hide '%1'", label));
210 hideAction->setCheckable(true);
211 hideAction->setChecked(item->isHidden());
212
213 QAction* showAllAction = 0;
214 if (m_model->hiddenCount() > 0) {
215 if (!mainSeparator) {
216 mainSeparator = menu.addSeparator();
217 }
218 showAllAction = menu.addAction(i18nc("@item:inmenu", "Show All Entries"));
219 showAllAction->setCheckable(true);
220 showAllAction->setChecked(m_model->hiddenItemsShown());
221 }
222
223 menu.addSeparator();
224 QMenu* iconSizeSubMenu = new QMenu(i18nc("@item:inmenu", "Icon Size"), &menu);
225
226 struct IconSizeInfo
227 {
228 int size;
229 const char* context;
230 const char* text;
231 };
232
233 const int iconSizeCount = 4;
234 static const IconSizeInfo iconSizes[iconSizeCount] = {
235 {KIconLoader::SizeSmall, I18N_NOOP2_NOSTRIP("Small icon size", "Small (%1x%2)")},
236 {KIconLoader::SizeSmallMedium, I18N_NOOP2_NOSTRIP("Medium icon size", "Medium (%1x%2)")},
237 {KIconLoader::SizeMedium, I18N_NOOP2_NOSTRIP("Large icon size", "Large (%1x%2)")},
238 {KIconLoader::SizeLarge, I18N_NOOP2_NOSTRIP("Huge icon size", "Huge (%1x%2)")}
239 };
240
241 QMap<QAction*, int> iconSizeActionMap;
242 QActionGroup* iconSizeGroup = new QActionGroup(iconSizeSubMenu);
243
244 for (int i = 0; i < iconSizeCount; ++i) {
245 const int size = iconSizes[i].size;
246 const QString text = i18nc(iconSizes[i].context, iconSizes[i].text,
247 size, size);
248
249 QAction* action = iconSizeSubMenu->addAction(text);
250 iconSizeActionMap.insert(action, size);
251 action->setActionGroup(iconSizeGroup);
252 action->setCheckable(true);
253 action->setChecked(m_view->iconSize() == size);
254 }
255
256 menu.addMenu(iconSizeSubMenu);
257
258 menu.addSeparator();
259 foreach (QAction* action, customContextMenuActions()) {
260 menu.addAction(action);
261 }
262
263 QAction* action = menu.exec(pos.toPoint());
264 if (action) {
265 if (action == emptyTrashAction) {
266 emptyTrash();
267 } else if (action == addAction) {
268 addEntry();
269 } else if (action == showAllAction) {
270 m_model->setHiddenItemsShown(showAllAction->isChecked());
271 } else if (iconSizeActionMap.contains(action)) {
272 m_view->setIconSize(iconSizeActionMap.value(action));
273 } else {
274 // The index might have changed if devices were added/removed while
275 // the context menu was open.
276 index = m_model->index(item);
277 if (index < 0) {
278 // The item is not in the model any more, probably because it was an
279 // external device that has been removed while the context menu was open.
280 return;
281 }
282
283 if (action == editAction) {
284 editEntry(index);
285 } else if (action == removeAction) {
286 m_model->removeItem(index);
287 } else if (action == hideAction) {
288 item->setHidden(hideAction->isChecked());
289 } else if (action == openInNewTabAction) {
290 // TriggerItem does set up the storage first and then it will
291 // emit the slotItemMiddleClicked signal, because of Qt::MiddleButton.
292 triggerItem(index, Qt::MiddleButton);
293 } else if (action == teardownAction) {
294 m_model->requestTeardown(index);
295 } else if (action == ejectAction) {
296 m_model->requestEject(index);
297 }
298 }
299 }
300
301 selectClosestItem();
302 }
303
304 void PlacesPanel::slotViewContextMenuRequested(const QPointF& pos)
305 {
306 QMenu menu(this);
307
308 QAction* addAction = menu.addAction(QIcon::fromTheme("document-new"), i18nc("@item:inmenu", "Add Entry..."));
309
310 QAction* showAllAction = 0;
311 if (m_model->hiddenCount() > 0) {
312 showAllAction = menu.addAction(i18nc("@item:inmenu", "Show All Entries"));
313 showAllAction->setCheckable(true);
314 showAllAction->setChecked(m_model->hiddenItemsShown());
315 }
316
317 menu.addSeparator();
318 foreach (QAction* action, customContextMenuActions()) {
319 menu.addAction(action);
320 }
321
322 QAction* action = menu.exec(pos.toPoint());
323 if (action) {
324 if (action == addAction) {
325 addEntry();
326 } else if (action == showAllAction) {
327 m_model->setHiddenItemsShown(showAllAction->isChecked());
328 }
329 }
330
331 selectClosestItem();
332 }
333
334 void PlacesPanel::slotItemDropEvent(int index, QGraphicsSceneDragDropEvent* event)
335 {
336 if (index < 0) {
337 return;
338 }
339
340 const PlacesItem* destItem = m_model->placesItem(index);
341 const PlacesItem::GroupType group = destItem->groupType();
342 if (group == PlacesItem::SearchForType || group == PlacesItem::RecentlySavedType) {
343 return;
344 }
345
346 if (m_model->storageSetupNeeded(index)) {
347 connect(m_model, &PlacesItemModel::storageSetupDone,
348 this, &PlacesPanel::slotItemDropEventStorageSetupDone);
349
350 m_itemDropEventIndex = index;
351
352 // Make a full copy of the Mime-Data
353 m_itemDropEventMimeData = new QMimeData;
354 m_itemDropEventMimeData->setText(event->mimeData()->text());
355 m_itemDropEventMimeData->setHtml(event->mimeData()->html());
356 m_itemDropEventMimeData->setUrls(event->mimeData()->urls());
357 m_itemDropEventMimeData->setImageData(event->mimeData()->imageData());
358 m_itemDropEventMimeData->setColorData(event->mimeData()->colorData());
359
360 m_itemDropEvent = new QDropEvent(event->pos().toPoint(),
361 event->possibleActions(),
362 m_itemDropEventMimeData,
363 event->buttons(),
364 event->modifiers());
365
366 m_model->requestStorageSetup(index);
367 return;
368 }
369
370 KUrl destUrl = destItem->url();
371 QDropEvent dropEvent(event->pos().toPoint(),
372 event->possibleActions(),
373 event->mimeData(),
374 event->buttons(),
375 event->modifiers());
376
377 QString error;
378 DragAndDropHelper::dropUrls(KFileItem(), destUrl, &dropEvent, error);
379 if (!error.isEmpty()) {
380 emit errorMessage(error);
381 }
382 }
383
384 void PlacesPanel::slotItemDropEventStorageSetupDone(int index, bool success)
385 {
386 disconnect(m_model, &PlacesItemModel::storageSetupDone,
387 this, &PlacesPanel::slotItemDropEventStorageSetupDone);
388
389 if ((index == m_itemDropEventIndex) && m_itemDropEvent && m_itemDropEventMimeData) {
390 if (success) {
391 KUrl destUrl = m_model->placesItem(index)->url();
392
393 QString error;
394 DragAndDropHelper::dropUrls(KFileItem(), destUrl, m_itemDropEvent, error);
395 if (!error.isEmpty()) {
396 emit errorMessage(error);
397 }
398 }
399
400 delete m_itemDropEventMimeData;
401 delete m_itemDropEvent;
402
403 m_itemDropEventIndex = -1;
404 m_itemDropEventMimeData = 0;
405 m_itemDropEvent = 0;
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 KUrl& dest, QDropEvent* event, QWidget* parent)
415 {
416 Q_UNUSED(parent);
417 QString error;
418 DragAndDropHelper::dropUrls(KFileItem(), dest, event, error);
419 if (!error.isEmpty()) {
420 emit errorMessage(error);
421 }
422
423 }
424
425 void PlacesPanel::slotTrashUpdated(KJob* job)
426 {
427 if (job->error()) {
428 emit errorMessage(job->errorString());
429 }
430 // as long as KIO doesn't do this, do it ourselves
431 KNotification::event("Trash: emptied", QString(), QPixmap(), 0, KNotification::DefaultEvent);
432 }
433
434 void PlacesPanel::slotStorageSetupDone(int index, bool success)
435 {
436 disconnect(m_model, &PlacesItemModel::storageSetupDone,
437 this, &PlacesPanel::slotStorageSetupDone);
438
439 if (m_triggerStorageSetupButton == Qt::NoButton) {
440 return;
441 }
442
443 if (success) {
444 Q_ASSERT(!m_model->storageSetupNeeded(index));
445 triggerItem(index, m_triggerStorageSetupButton);
446 m_triggerStorageSetupButton = Qt::NoButton;
447 } else {
448 setUrl(m_storageSetupFailedUrl);
449 m_storageSetupFailedUrl = KUrl();
450 }
451 }
452
453 void PlacesPanel::emptyTrash()
454 {
455 KIO::JobUiDelegate uiDelegate;
456 uiDelegate.setWindow(window());
457 if (uiDelegate.askDeleteConfirmation(QList<QUrl>(), KIO::JobUiDelegate::EmptyTrash, KIO::JobUiDelegate::DefaultConfirmation)) {
458 KIO::Job* job = KIO::emptyTrash();
459 KJobWidgets::setWindow(job, window());
460 connect(job, &KIO::Job::result, this, &PlacesPanel::slotTrashUpdated);
461 }
462 }
463
464 void PlacesPanel::addEntry()
465 {
466 const int index = m_controller->selectionManager()->currentItem();
467 const KUrl url = m_model->data(index).value("url").value<KUrl>();
468
469 QPointer<PlacesItemEditDialog> dialog = new PlacesItemEditDialog(this);
470 dialog->setWindowTitle(i18nc("@title:window", "Add Places Entry"));
471 dialog->setAllowGlobal(true);
472 dialog->setUrl(url);
473 if (dialog->exec() == QDialog::Accepted) {
474 PlacesItem* item = m_model->createPlacesItem(dialog->text(), dialog->url(), dialog->icon());
475 m_model->appendItemToGroup(item);
476 }
477
478 delete dialog;
479 }
480
481 void PlacesPanel::editEntry(int index)
482 {
483 QHash<QByteArray, QVariant> data = m_model->data(index);
484
485 QPointer<PlacesItemEditDialog> dialog = new PlacesItemEditDialog(this);
486 dialog->setWindowTitle(i18nc("@title:window", "Edit Places Entry"));
487 dialog->setIcon(data.value("iconName").toString());
488 dialog->setText(data.value("text").toString());
489 dialog->setUrl(data.value("url").value<KUrl>());
490 dialog->setAllowGlobal(true);
491 if (dialog->exec() == QDialog::Accepted) {
492 PlacesItem* oldItem = m_model->placesItem(index);
493 if (oldItem) {
494 oldItem->setText(dialog->text());
495 oldItem->setUrl(dialog->url());
496 oldItem->setIcon(dialog->icon());
497 }
498 }
499
500 delete dialog;
501 }
502
503 void PlacesPanel::selectClosestItem()
504 {
505 const int index = m_model->closestItem(url());
506 KItemListSelectionManager* selectionManager = m_controller->selectionManager();
507 selectionManager->setCurrentItem(index);
508 selectionManager->clearSelection();
509 selectionManager->setSelected(index);
510 }
511
512 void PlacesPanel::triggerItem(int index, Qt::MouseButton button)
513 {
514 const PlacesItem* item = m_model->placesItem(index);
515 if (!item) {
516 return;
517 }
518
519 if (m_model->storageSetupNeeded(index)) {
520 m_triggerStorageSetupButton = button;
521 m_storageSetupFailedUrl = url();
522
523 connect(m_model, &PlacesItemModel::storageSetupDone,
524 this, &PlacesPanel::slotStorageSetupDone);
525
526 m_model->requestStorageSetup(index);
527 } else {
528 m_triggerStorageSetupButton = Qt::NoButton;
529
530 const KUrl url = m_model->data(index).value("url").value<KUrl>();
531 if (!url.isEmpty()) {
532 if (button == Qt::MiddleButton) {
533 emit placeMiddleClicked(PlacesItemModel::convertedUrl(url));
534 } else {
535 emit placeActivated(PlacesItemModel::convertedUrl(url));
536 }
537 }
538 }
539 }
540
541