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