]> cloud.milkyroute.net Git - dolphin.git/blob - src/dolphincontextmenu.cpp
Cut the ropes between DolphinMainWindow and DolphinNewFileMenu. Error handling is...
[dolphin.git] / src / dolphincontextmenu.cpp
1 /***************************************************************************
2 * Copyright (C) 2006 by Peter Penz (peter.penz@gmx.at) and *
3 * Cvetoslav Ludmiloff *
4 * *
5 * This program is free software; you can redistribute it and/or modify *
6 * it under the terms of the GNU General Public License as published by *
7 * the Free Software Foundation; either version 2 of the License, or *
8 * (at your option) any later version. *
9 * *
10 * This program is distributed in the hope that it will be useful, *
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of *
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
13 * GNU General Public License for more details. *
14 * *
15 * You should have received a copy of the GNU General Public License *
16 * along with this program; if not, write to the *
17 * Free Software Foundation, Inc., *
18 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA *
19 ***************************************************************************/
20
21 #include "dolphincontextmenu.h"
22
23 #include "dolphinmainwindow.h"
24 #include "dolphinnewfilemenu.h"
25 #include "dolphinviewcontainer.h"
26 #include "dolphin_generalsettings.h"
27 #include "dolphinremoveaction.h"
28
29 #include <KActionCollection>
30 #include <KDesktopFile>
31 #include <kfileitemactionplugin.h>
32 #include <kabstractfileitemactionplugin.h>
33 #include <KFileItemActions>
34 #include <KFileItemListProperties>
35 #include <KGlobal>
36 #include <KIconLoader>
37 #include <KIO/NetAccess>
38 #include <KMenu>
39 #include <KMenuBar>
40 #include <KMessageBox>
41 #include <KMimeTypeTrader>
42 #include <KNewFileMenu>
43 #include <konqmimedata.h>
44 #include <konq_operations.h>
45 #include <KService>
46 #include <KLocale>
47 #include <KPropertiesDialog>
48 #include <KStandardAction>
49 #include <KStandardDirs>
50 #include <KToolBar>
51
52 #include <panels/places/placesitem.h>
53 #include <panels/places/placesitemmodel.h>
54
55 #include <QApplication>
56 #include <QClipboard>
57 #include <QDir>
58
59 #include "views/dolphinview.h"
60 #include "views/viewmodecontroller.h"
61
62 DolphinContextMenu::DolphinContextMenu(DolphinMainWindow* parent,
63 const QPoint& pos,
64 const KFileItem& fileInfo,
65 const KUrl& baseUrl) :
66 KMenu(parent),
67 m_pos(pos),
68 m_mainWindow(parent),
69 m_fileInfo(fileInfo),
70 m_baseUrl(baseUrl),
71 m_baseFileItem(0),
72 m_selectedItems(),
73 m_selectedItemsProperties(0),
74 m_context(NoContext),
75 m_copyToMenu(parent),
76 m_customActions(),
77 m_command(None),
78 m_removeAction(0)
79 {
80 // The context menu either accesses the URLs of the selected items
81 // or the items itself. To increase the performance both lists are cached.
82 const DolphinView* view = m_mainWindow->activeViewContainer()->view();
83 m_selectedItems = view->selectedItems();
84 }
85
86 DolphinContextMenu::~DolphinContextMenu()
87 {
88 delete m_selectedItemsProperties;
89 m_selectedItemsProperties = 0;
90 }
91
92 void DolphinContextMenu::setCustomActions(const QList<QAction*>& actions)
93 {
94 m_customActions = actions;
95 }
96
97 DolphinContextMenu::Command DolphinContextMenu::open()
98 {
99 // get the context information
100 if (m_baseUrl.protocol() == QLatin1String("trash")) {
101 m_context |= TrashContext;
102 }
103
104 if (!m_fileInfo.isNull() && !m_selectedItems.isEmpty()) {
105 m_context |= ItemContext;
106 // TODO: handle other use cases like devices + desktop files
107 }
108
109 // open the corresponding popup for the context
110 if (m_context & TrashContext) {
111 if (m_context & ItemContext) {
112 openTrashItemContextMenu();
113 } else {
114 openTrashContextMenu();
115 }
116 } else if (m_context & ItemContext) {
117 openItemContextMenu();
118 } else {
119 Q_ASSERT(m_context == NoContext);
120 openViewportContextMenu();
121 }
122
123 return m_command;
124 }
125
126 void DolphinContextMenu::keyPressEvent(QKeyEvent *ev)
127 {
128 if (m_removeAction && ev->key() == Qt::Key_Shift) {
129 m_removeAction->update();
130 }
131 KMenu::keyPressEvent(ev);
132 }
133
134 void DolphinContextMenu::keyReleaseEvent(QKeyEvent *ev)
135 {
136 if (m_removeAction && ev->key() == Qt::Key_Shift) {
137 m_removeAction->update();
138 }
139 KMenu::keyReleaseEvent(ev);
140 }
141
142 void DolphinContextMenu::openTrashContextMenu()
143 {
144 Q_ASSERT(m_context & TrashContext);
145
146 QAction* emptyTrashAction = new QAction(KIcon("trash-empty"), i18nc("@action:inmenu", "Empty Trash"), this);
147 KConfig trashConfig("trashrc", KConfig::SimpleConfig);
148 emptyTrashAction->setEnabled(!trashConfig.group("Status").readEntry("Empty", true));
149 addAction(emptyTrashAction);
150
151 addCustomActions();
152
153 QAction* propertiesAction = m_mainWindow->actionCollection()->action("properties");
154 addAction(propertiesAction);
155
156 addShowMenuBarAction();
157
158 if (exec(m_pos) == emptyTrashAction) {
159 KonqOperations::emptyTrash(m_mainWindow);
160 }
161 }
162
163 void DolphinContextMenu::openTrashItemContextMenu()
164 {
165 Q_ASSERT(m_context & TrashContext);
166 Q_ASSERT(m_context & ItemContext);
167
168 QAction* restoreAction = new QAction(i18nc("@action:inmenu", "Restore"), m_mainWindow);
169 addAction(restoreAction);
170
171 QAction* deleteAction = m_mainWindow->actionCollection()->action("delete");
172 addAction(deleteAction);
173
174 QAction* propertiesAction = m_mainWindow->actionCollection()->action("properties");
175 addAction(propertiesAction);
176
177 if (exec(m_pos) == restoreAction) {
178 KUrl::List selectedUrls;
179 foreach (const KFileItem &item, m_selectedItems) {
180 selectedUrls.append(item.url());
181 }
182
183 KonqOperations::restoreTrashedItems(selectedUrls, m_mainWindow);
184 }
185 }
186
187 void DolphinContextMenu::openItemContextMenu()
188 {
189 Q_ASSERT(!m_fileInfo.isNull());
190
191 QAction* openParentInNewWindowAction = 0;
192 QAction* openParentInNewTabAction = 0;
193 QAction* addToPlacesAction = 0;
194 const KFileItemListProperties& selectedItemsProps = selectedItemsProperties();
195
196 if (m_selectedItems.count() == 1) {
197 if (m_fileInfo.isDir()) {
198 // setup 'Create New' menu
199 DolphinNewFileMenu* newFileMenu = new DolphinNewFileMenu(m_mainWindow->actionCollection(), this);
200 const DolphinView* view = m_mainWindow->activeViewContainer()->view();
201 newFileMenu->setViewShowsHiddenFiles(view->hiddenFilesShown());
202 newFileMenu->checkUpToDate();
203 newFileMenu->setPopupFiles(m_fileInfo.url());
204 newFileMenu->setEnabled(selectedItemsProps.supportsWriting());
205 connect(newFileMenu, SIGNAL(fileCreated(KUrl)), newFileMenu, SLOT(deleteLater()));
206 connect(newFileMenu, SIGNAL(directoryCreated(KUrl)), newFileMenu, SLOT(deleteLater()));
207 connect(newFileMenu, SIGNAL(errorMessage(QString)), this, SIGNAL(errorMessage(QString)));
208
209 KMenu* menu = newFileMenu->menu();
210 menu->setTitle(i18nc("@title:menu Create new folder, file, link, etc.", "Create New"));
211 menu->setIcon(KIcon("document-new"));
212 addMenu(menu);
213 addSeparator();
214
215 // insert 'Open in new window' and 'Open in new tab' entries
216 addAction(m_mainWindow->actionCollection()->action("open_in_new_window"));
217 addAction(m_mainWindow->actionCollection()->action("open_in_new_tab"));
218
219 // insert 'Add to Places' entry
220 if (!placeExists(m_fileInfo.url())) {
221 addToPlacesAction = addAction(KIcon("bookmark-new"),
222 i18nc("@action:inmenu Add selected folder to places",
223 "Add to Places"));
224 }
225
226 addSeparator();
227 } else if (m_baseUrl.protocol().contains("search")) {
228 openParentInNewWindowAction = new QAction(KIcon("window-new"),
229 i18nc("@action:inmenu",
230 "Open Path in New Window"),
231 this);
232 addAction(openParentInNewWindowAction);
233
234 openParentInNewTabAction = new QAction(KIcon("tab-new"),
235 i18nc("@action:inmenu",
236 "Open Path in New Tab"),
237 this);
238 addAction(openParentInNewTabAction);
239
240 addSeparator();
241 } else if (!DolphinView::openItemAsFolderUrl(m_fileInfo).isEmpty()) {
242 // insert 'Open in new window' and 'Open in new tab' entries
243 addAction(m_mainWindow->actionCollection()->action("open_in_new_window"));
244 addAction(m_mainWindow->actionCollection()->action("open_in_new_tab"));
245
246 addSeparator();
247 }
248 } else {
249 bool selectionHasOnlyDirs = true;
250 foreach (const KFileItem& item, m_selectedItems) {
251 const KUrl& url = DolphinView::openItemAsFolderUrl(item);
252 if (url.isEmpty()) {
253 selectionHasOnlyDirs = false;
254 break;
255 }
256 }
257
258 if (selectionHasOnlyDirs) {
259 // insert 'Open in new tab' entry
260 addAction(m_mainWindow->actionCollection()->action("open_in_new_tabs"));
261 addSeparator();
262 }
263 }
264
265 insertDefaultItemActions(selectedItemsProps);
266
267 addSeparator();
268
269 KFileItemActions fileItemActions;
270 fileItemActions.setItemListProperties(selectedItemsProps);
271 addServiceActions(fileItemActions);
272
273 addFileItemPluginActions();
274
275 addVersionControlPluginActions();
276
277 // insert 'Copy To' and 'Move To' sub menus
278 if (GeneralSettings::showCopyMoveMenu()) {
279 m_copyToMenu.setItems(m_selectedItems);
280 m_copyToMenu.setReadOnly(!selectedItemsProps.supportsWriting());
281 m_copyToMenu.addActionsTo(this);
282 }
283
284 // insert 'Properties...' entry
285 QAction* propertiesAction = m_mainWindow->actionCollection()->action("properties");
286 addAction(propertiesAction);
287
288 QAction* activatedAction = exec(m_pos);
289 if (activatedAction) {
290 if (activatedAction == addToPlacesAction) {
291 const KUrl selectedUrl(m_fileInfo.url());
292 if (selectedUrl.isValid()) {
293 PlacesItemModel model;
294 const QString text = selectedUrl.fileName();
295 PlacesItem* item = model.createPlacesItem(text, selectedUrl);
296 model.appendItemToGroup(item);
297 }
298 } else if (activatedAction == openParentInNewWindowAction) {
299 m_command = OpenParentFolderInNewWindow;
300 } else if (activatedAction == openParentInNewTabAction) {
301 m_command = OpenParentFolderInNewTab;
302 }
303 }
304 }
305
306 void DolphinContextMenu::openViewportContextMenu()
307 {
308 // setup 'Create New' menu
309 KNewFileMenu* newFileMenu = m_mainWindow->newFileMenu();
310 const DolphinView* view = m_mainWindow->activeViewContainer()->view();
311 newFileMenu->setViewShowsHiddenFiles(view->hiddenFilesShown());
312 newFileMenu->checkUpToDate();
313 newFileMenu->setPopupFiles(m_baseUrl);
314 addMenu(newFileMenu->menu());
315 addSeparator();
316
317 // Insert 'New Window' and 'New Tab' entries. Don't use "open_in_new_window" and
318 // "open_in_new_tab" here, as the current selection should get ignored.
319 addAction(m_mainWindow->actionCollection()->action("new_window"));
320 addAction(m_mainWindow->actionCollection()->action("new_tab"));
321
322 // Insert 'Add to Places' entry if exactly one item is selected
323 QAction* addToPlacesAction = 0;
324 if (!placeExists(m_mainWindow->activeViewContainer()->url())) {
325 addToPlacesAction = addAction(KIcon("bookmark-new"),
326 i18nc("@action:inmenu Add current folder to places", "Add to Places"));
327 }
328
329 addSeparator();
330
331 QAction* pasteAction = createPasteAction();
332 addAction(pasteAction);
333 addSeparator();
334
335 // Insert service actions
336 const KFileItemListProperties baseUrlProperties(KFileItemList() << baseFileItem());
337 KFileItemActions fileItemActions;
338 fileItemActions.setItemListProperties(baseUrlProperties);
339 addServiceActions(fileItemActions);
340
341 addFileItemPluginActions();
342
343 addVersionControlPluginActions();
344
345 addCustomActions();
346
347 QAction* propertiesAction = m_mainWindow->actionCollection()->action("properties");
348 addAction(propertiesAction);
349
350 addShowMenuBarAction();
351
352 QAction* action = exec(m_pos);
353 if (addToPlacesAction && (action == addToPlacesAction)) {
354 const DolphinViewContainer* container = m_mainWindow->activeViewContainer();
355 if (container->url().isValid()) {
356 PlacesItemModel model;
357 PlacesItem* item = model.createPlacesItem(container->placesText(),
358 container->url());
359 model.appendItemToGroup(item);
360 }
361 }
362 }
363
364 void DolphinContextMenu::insertDefaultItemActions(const KFileItemListProperties& properties)
365 {
366 const KActionCollection* collection = m_mainWindow->actionCollection();
367
368 // Insert 'Cut', 'Copy' and 'Paste'
369 addAction(collection->action(KStandardAction::name(KStandardAction::Cut)));
370 addAction(collection->action(KStandardAction::name(KStandardAction::Copy)));
371 addAction(createPasteAction());
372
373 addSeparator();
374
375 // Insert 'Rename'
376 QAction* renameAction = collection->action("rename");
377 addAction(renameAction);
378
379 // Insert 'Move to Trash' and/or 'Delete'
380 if (properties.supportsDeleting()) {
381 const bool showDeleteAction = (KGlobal::config()->group("KDE").readEntry("ShowDeleteCommand", false) ||
382 !properties.isLocal());
383 const bool showMoveToTrashAction = (properties.isLocal() &&
384 properties.supportsMoving());
385
386 if (showDeleteAction && showMoveToTrashAction) {
387 delete m_removeAction;
388 m_removeAction = 0;
389 addAction(m_mainWindow->actionCollection()->action("move_to_trash"));
390 addAction(m_mainWindow->actionCollection()->action("delete"));
391 } else if (showDeleteAction && !showMoveToTrashAction) {
392 addAction(m_mainWindow->actionCollection()->action("delete"));
393 } else {
394 if (!m_removeAction) {
395 m_removeAction = new DolphinRemoveAction(this, m_mainWindow->actionCollection());
396 }
397 addAction(m_removeAction);
398 m_removeAction->update();
399 }
400 }
401 }
402
403 void DolphinContextMenu::addShowMenuBarAction()
404 {
405 const KActionCollection* ac = m_mainWindow->actionCollection();
406 QAction* showMenuBar = ac->action(KStandardAction::name(KStandardAction::ShowMenubar));
407 if (!m_mainWindow->menuBar()->isVisible() && !m_mainWindow->toolBar()->isVisible()) {
408 addSeparator();
409 addAction(showMenuBar);
410 }
411 }
412
413 bool DolphinContextMenu::placeExists(const KUrl& url) const
414 {
415 PlacesItemModel model;
416
417 const int count = model.count();
418 for (int i = 0; i < count; ++i) {
419 const KUrl placeUrl = model.placesItem(i)->url();
420 if (placeUrl.equals(url, KUrl::CompareWithoutTrailingSlash)) {
421 return true;
422 }
423 }
424
425 return false;
426 }
427
428 QAction* DolphinContextMenu::createPasteAction()
429 {
430 QAction* action = 0;
431 const bool isDir = !m_fileInfo.isNull() && m_fileInfo.isDir();
432 if (isDir && (m_selectedItems.count() == 1)) {
433 action = new QAction(KIcon("edit-paste"), i18nc("@action:inmenu", "Paste Into Folder"), this);
434 const QMimeData* mimeData = QApplication::clipboard()->mimeData();
435 const KUrl::List pasteData = KUrl::List::fromMimeData(mimeData);
436 action->setEnabled(!pasteData.isEmpty() && selectedItemsProperties().supportsWriting());
437 connect(action, SIGNAL(triggered()), m_mainWindow, SLOT(pasteIntoFolder()));
438 } else {
439 action = m_mainWindow->actionCollection()->action(KStandardAction::name(KStandardAction::Paste));
440 }
441
442 return action;
443 }
444
445 KFileItemListProperties& DolphinContextMenu::selectedItemsProperties() const
446 {
447 if (!m_selectedItemsProperties) {
448 m_selectedItemsProperties = new KFileItemListProperties(m_selectedItems);
449 }
450 return *m_selectedItemsProperties;
451 }
452
453 KFileItem DolphinContextMenu::baseFileItem()
454 {
455 if (!m_baseFileItem) {
456 m_baseFileItem = new KFileItem(KFileItem::Unknown, KFileItem::Unknown, m_baseUrl);
457 }
458 return *m_baseFileItem;
459 }
460
461 void DolphinContextMenu::addServiceActions(KFileItemActions& fileItemActions)
462 {
463 fileItemActions.setParentWidget(m_mainWindow);
464
465 // insert 'Open With...' action or sub menu
466 fileItemActions.addOpenWithActionsTo(this, "DesktopEntryName != 'dolphin'");
467
468 // insert 'Actions' sub menu
469 fileItemActions.addServiceActionsTo(this);
470 }
471
472 void DolphinContextMenu::addFileItemPluginActions()
473 {
474 KFileItemListProperties props;
475 if (m_selectedItems.isEmpty()) {
476 props.setItems(KFileItemList() << baseFileItem());
477 } else {
478 props = selectedItemsProperties();
479 }
480
481 QString commonMimeType = props.mimeType();
482 if (commonMimeType.isEmpty()) {
483 commonMimeType = QLatin1String("application/octet-stream");
484 }
485
486 const KService::List pluginServices = KMimeTypeTrader::self()->query(commonMimeType, "KFileItemAction/Plugin", "exist Library");
487 if (pluginServices.isEmpty()) {
488 return;
489 }
490
491 const KConfig config("kservicemenurc", KConfig::NoGlobals);
492 const KConfigGroup showGroup = config.group("Show");
493
494 foreach (const KSharedPtr<KService>& service, pluginServices) {
495 if (!showGroup.readEntry(service->desktopEntryName(), true)) {
496 // The plugin has been disabled
497 continue;
498 }
499
500 // Old API (kdelibs-4.6.0 only)
501 KFileItemActionPlugin* plugin = service->createInstance<KFileItemActionPlugin>();
502 if (plugin) {
503 plugin->setParent(this);
504 addActions(plugin->actions(props, m_mainWindow));
505 }
506 // New API (kdelibs >= 4.6.1)
507 KAbstractFileItemActionPlugin* abstractPlugin = service->createInstance<KAbstractFileItemActionPlugin>();
508 if (abstractPlugin) {
509 abstractPlugin->setParent(this);
510 addActions(abstractPlugin->actions(props, m_mainWindow));
511 }
512 }
513 }
514
515 void DolphinContextMenu::addVersionControlPluginActions()
516 {
517 const DolphinView* view = m_mainWindow->activeViewContainer()->view();
518 const QList<QAction*> versionControlActions = view->versionControlActions(m_selectedItems);
519 if (!versionControlActions.isEmpty()) {
520 foreach (QAction* action, versionControlActions) {
521 addAction(action);
522 }
523 addSeparator();
524 }
525 }
526
527 void DolphinContextMenu::addCustomActions()
528 {
529 foreach (QAction* action, m_customActions) {
530 addAction(action);
531 }
532 }
533
534 #include "dolphincontextmenu.moc"