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