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