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