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