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