]> cloud.milkyroute.net Git - dolphin.git/blob - src/dolphincontextmenu.cpp
- Fixed crash when showing "Open With..." items of the context menu: the model index...
[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 "dolphinsettings.h"
25 #include "dolphinview.h"
26 #include "editbookmarkdialog.h"
27
28 #include <assert.h>
29
30 #include <kactioncollection.h>
31 #include <kbookmarkmanager.h>
32 #include <kbookmark.h>
33 #include <kdesktopfile.h>
34 #include <kglobal.h>
35 #include <kiconloader.h>
36 #include <kio/netaccess.h>
37 #include <kmenu.h>
38 #include <kmimetypetrader.h>
39 #include <klocale.h>
40 #include <kpropertiesdialog.h>
41 #include <krun.h>
42 #include <kstandardaction.h>
43 #include <kstandarddirs.h>
44
45 #include <QDir>
46
47 DolphinContextMenu::DolphinContextMenu(DolphinView* parent,
48 KFileItem* fileInfo,
49 const QPoint& pos) :
50 m_dolphinView(parent),
51 m_fileInfo(fileInfo),
52 m_pos(pos)
53 {
54 }
55
56 void DolphinContextMenu::open()
57 {
58 if (m_fileInfo == 0) {
59 openViewportContextMenu();
60 }
61 else {
62 openItemContextMenu();
63 }
64 }
65
66 DolphinContextMenu::~DolphinContextMenu()
67 {
68 }
69
70 void DolphinContextMenu::openViewportContextMenu()
71 {
72 // Parts of the following code have been taken
73 // from the class KonqOperations located in
74 // libqonq/konq_operations.h of Konqueror.
75 // (Copyright (C) 2000 David Faure <faure@kde.org>)
76
77 assert(m_fileInfo == 0);
78
79 DolphinMainWindow* dolphin = m_dolphinView->mainWindow();
80 KMenu* popup = new KMenu(m_dolphinView);
81
82 // setup 'Create New' menu
83 KMenu* createNewMenu = new KMenu(i18n("Create New"));
84 createNewMenu->setIcon(SmallIcon("filenew"));
85
86 QAction* createFolderAction = dolphin->actionCollection()->action("create_folder");
87 if (createFolderAction != 0) {
88 createNewMenu->addAction(createFolderAction);
89 }
90
91 QLinkedListIterator<QAction*> fileGrouptIt(dolphin->fileGroupActions());
92 while (fileGrouptIt.hasNext()) {
93 createNewMenu->addAction(fileGrouptIt.next());
94 }
95
96 popup->addMenu(createNewMenu);
97 popup->addSeparator();
98
99 QAction* pasteAction = dolphin->actionCollection()->action(KStandardAction::stdName(KStandardAction::Paste));
100 popup->addAction(pasteAction);
101
102 // setup 'View Mode' menu
103 KMenu* viewModeMenu = new KMenu(i18n("View Mode"));
104
105 QAction* iconsMode = dolphin->actionCollection()->action("icons");
106 viewModeMenu->addAction(iconsMode);
107
108 QAction* detailsMode = dolphin->actionCollection()->action("details");
109 viewModeMenu->addAction(detailsMode);
110
111 QAction* previewsMode = dolphin->actionCollection()->action("previews");
112 viewModeMenu->addAction(previewsMode);
113
114 popup->addMenu(viewModeMenu);
115 popup->addSeparator();
116
117 QAction* bookmarkAction = popup->addAction(i18n("Bookmark this folder"));
118 popup->addSeparator();
119
120 QAction* propertiesAction = popup->addAction(i18n("Properties..."));
121
122 QAction* activatedAction = popup->exec(m_pos);
123 if (activatedAction == propertiesAction) {
124 new KPropertiesDialog(dolphin->activeView()->url());
125 }
126 else if (activatedAction == bookmarkAction) {
127 const KUrl& url = dolphin->activeView()->url();
128 KBookmark bookmark = EditBookmarkDialog::getBookmark(i18n("Add folder as bookmark"),
129 url.fileName(),
130 url,
131 "bookmark");
132 if (!bookmark.isNull()) {
133 KBookmarkManager* manager = DolphinSettings::instance().bookmarkManager();
134 KBookmarkGroup root = manager->root();
135 root.addBookmark(manager, bookmark);
136 manager->emitChanged(root);
137 }
138 }
139
140 popup->deleteLater();
141 }
142
143 void DolphinContextMenu::openItemContextMenu()
144 {
145 // Parts of the following code have been taken
146 // from the class KonqOperations located in
147 // libkonq/konq_operations.h of Konqueror.
148 // (Copyright (C) 2000 David Faure <faure@kde.org>)
149
150 assert(m_fileInfo != 0);
151
152 KMenu* popup = new KMenu(m_dolphinView);
153 DolphinMainWindow* dolphin = m_dolphinView->mainWindow();
154 const KUrl::List urls = m_dolphinView->selectedUrls();
155
156 // insert 'Cut', 'Copy' and 'Paste'
157 const KStandardAction::StandardAction actionNames[] = {
158 KStandardAction::Cut,
159 KStandardAction::Copy,
160 KStandardAction::Paste
161 };
162
163 const int count = sizeof(actionNames) / sizeof(KStandardAction::StandardAction);
164 for (int i = 0; i < count; ++i) {
165 QAction* action = dolphin->actionCollection()->action(KStandardAction::stdName(actionNames[i]));
166 if (action != 0) {
167 popup->addAction(action);
168 }
169 }
170 popup->addSeparator();
171
172 // insert 'Rename'
173 QAction* renameAction = dolphin->actionCollection()->action("rename");
174 popup->addAction(renameAction);
175
176 // insert 'Move to Trash' for local Urls, otherwise insert 'Delete'
177 const KUrl& url = dolphin->activeView()->url();
178 if (url.isLocalFile()) {
179 QAction* moveToTrashAction = dolphin->actionCollection()->action("move_to_trash");
180 popup->addAction(moveToTrashAction);
181 }
182 else {
183 QAction* deleteAction = dolphin->actionCollection()->action("delete");
184 popup->addAction(deleteAction);
185 }
186
187 // insert 'Bookmark this folder...' entry
188 // urls is a list of selected items, so insert boolmark menu if
189 // urls contains only one item, i.e. no multiple selection made
190 QAction* bookmarkAction = 0;
191 if (m_fileInfo->isDir() && (urls.count() == 1)) {
192 bookmarkAction = popup->addAction(i18n("Bookmark this folder"));
193 }
194
195 // Insert 'Open With...' sub menu
196 QVector<KService::Ptr> openWithVector;
197 const QList<QAction*> openWithActions = insertOpenWithItems(popup, openWithVector);
198
199 // Insert 'Actions' sub menu
200 QVector<KDEDesktopMimeType::Service> actionsVector;
201 const QList<QAction*> serviceActions = insertActionItems(popup, actionsVector);
202 popup->addSeparator();
203
204 // insert 'Properties...' entry
205 QAction* propertiesAction = dolphin->actionCollection()->action("properties");
206 popup->addAction(propertiesAction);
207
208 QAction* activatedAction = popup->exec(m_pos);
209
210 if ((bookmarkAction!= 0) && (activatedAction == bookmarkAction)) {
211 const KUrl selectedUrl(m_fileInfo->url());
212 KBookmark bookmark = EditBookmarkDialog::getBookmark(i18n("Add folder as bookmark"),
213 selectedUrl.fileName(),
214 selectedUrl,
215 "bookmark");
216 if (!bookmark.isNull()) {
217 KBookmarkManager* manager = DolphinSettings::instance().bookmarkManager();
218 KBookmarkGroup root = manager->root();
219 root.addBookmark(manager, bookmark);
220 manager->emitChanged(root);
221 }
222 }
223 else if (serviceActions.contains(activatedAction)) {
224 // one of the 'Actions' items has been selected
225 int id = serviceActions.indexOf(activatedAction);
226 KDEDesktopMimeType::executeService(urls, actionsVector[id]);
227 }
228 else if (openWithActions.contains(activatedAction)) {
229 // one of the 'Open With' items has been selected
230 if (openWithActions.last() == activatedAction) {
231 // the item 'Other...' has been selected
232 KRun::displayOpenWithDialog(urls, m_dolphinView);
233 }
234 else {
235 int id = openWithActions.indexOf(activatedAction);
236 KService::Ptr servicePtr = openWithVector[id];
237 KRun::run(*servicePtr, urls, m_dolphinView);
238 }
239 }
240
241 openWithVector.clear();
242 actionsVector.clear();
243 popup->deleteLater();
244 }
245
246 QList<QAction*> DolphinContextMenu::insertOpenWithItems(KMenu* popup,
247 QVector<KService::Ptr>& openWithVector)
248 {
249 // Prepare 'Open With' sub menu. Usually a sub menu is created, where all applications
250 // are listed which are registered to open the item. As last entry "Other..." will be
251 // attached which allows to select a custom application. If no applications are registered
252 // no sub menu is created at all, only "Open With..." will be offered.
253 const KFileItemList list = m_dolphinView->selectedItems();
254
255 bool insertOpenWithItems = true;
256 const QString contextMimeType(m_fileInfo->mimetype());
257
258 QListIterator<KFileItem*> mimeIt(list);
259 while (insertOpenWithItems && mimeIt.hasNext()) {
260 KFileItem* item = mimeIt.next();
261 insertOpenWithItems = (contextMimeType == item->mimetype());
262 }
263
264 QList<QAction*> openWithActions;
265 if (insertOpenWithItems) {
266 // fill the 'Open with' sub menu with application types
267 const KMimeType::Ptr mimePtr = KMimeType::findByUrl(m_fileInfo->url());
268 KService::List offers = KMimeTypeTrader::self()->query(mimePtr->name(),
269 "Application",
270 "Type == 'Application'");
271 if (offers.count() > 0) {
272 KService::List::Iterator it;
273 KMenu* openWithMenu = new KMenu(i18n("Open With"));
274 for(it = offers.begin(); it != offers.end(); ++it) {
275 // The offer list from the KTrader returns duplicate
276 // application entries. Although this seems to be a configuration
277 // problem outside the scope of Dolphin, duplicated entries just
278 // will be skipped here.
279 const QString appName((*it)->name());
280 if (!containsEntry(openWithMenu, appName)) {
281 const KIcon icon((*it)->icon());
282 QAction *action = openWithMenu->addAction(icon, appName);
283 openWithVector.append(*it);
284 openWithActions << action;
285 }
286 }
287
288 openWithMenu->addSeparator();
289 QAction* action = openWithMenu->addAction(i18n("&Other..."));
290
291 openWithActions << action;
292 popup->addSeparator();
293 popup->addMenu(openWithMenu);
294 }
295 else {
296 // No applications are registered, hence just offer
297 // a "Open With..." item instead of a sub menu containing
298 // only one entry.
299 QAction* action = popup->addAction(i18n("Open With..."));
300 openWithActions << action;
301 }
302 }
303 else {
304 // At least one of the selected items has a different MIME type. In this case
305 // just show a disabled "Open With..." entry.
306 popup->addSeparator();
307 QAction* action = popup->addAction(i18n("Open With..."));
308 action->setEnabled(false);
309 }
310
311 return openWithActions;
312 }
313
314 QList<QAction*> DolphinContextMenu::insertActionItems(KMenu* popup,
315 QVector<KDEDesktopMimeType::Service>& actionsVector)
316 {
317 KMenu* actionsMenu = new KMenu(i18n("Actions"));
318
319 QList<QAction*> serviceActions;
320
321 QStringList dirs = KGlobal::dirs()->findDirs("data", "dolphin/servicemenus/");
322
323 KMenu* menu = 0;
324 for (QStringList::ConstIterator dirIt = dirs.begin(); dirIt != dirs.end(); ++dirIt) {
325 QDir dir(*dirIt);
326 QStringList filters;
327 filters << "*.desktop";
328 dir.setNameFilters(filters);
329 QStringList entries = dir.entryList(QDir::Files);
330
331 for (QStringList::ConstIterator entryIt = entries.begin(); entryIt != entries.end(); ++entryIt) {
332 KSimpleConfig cfg(*dirIt + *entryIt, true);
333 cfg.setDesktopGroup();
334 if ((cfg.hasKey("Actions") || cfg.hasKey("X-KDE-GetActionMenu")) && cfg.hasKey("ServiceTypes")) {
335 //const QStringList types = cfg.readListEntry("ServiceTypes");
336 QStringList types;
337 types = cfg.readEntry("ServiceTypes", types);
338 for (QStringList::ConstIterator it = types.begin(); it != types.end(); ++it) {
339 // check whether the mime type is equal or whether the
340 // mimegroup (e. g. image/*) is supported
341
342 bool insert = false;
343 if ((*it) == "all/allfiles") {
344 // The service type is valid for all files, but not for directories.
345 // Check whether the selected items only consist of files...
346 const KFileItemList list = m_dolphinView->selectedItems();
347
348 QListIterator<KFileItem*> mimeIt(list);
349 insert = true;
350 while (insert && mimeIt.hasNext()) {
351 KFileItem* item = mimeIt.next();
352 insert = !item->isDir();
353 }
354 }
355
356 if (!insert) {
357 // Check whether the MIME types of all selected files match
358 // to the mimetype of the service action. As soon as one MIME
359 // type does not match, no service menu is shown at all.
360 const KFileItemList list = m_dolphinView->selectedItems();
361
362 QListIterator<KFileItem*> mimeIt(list);
363 insert = true;
364 while (insert && mimeIt.hasNext()) {
365 KFileItem* item = mimeIt.next();
366 const QString mimeType(item->mimetype());
367 const QString mimeGroup(mimeType.left(mimeType.indexOf('/')));
368
369 insert = (*it == mimeType) ||
370 ((*it).right(1) == "*") &&
371 ((*it).left((*it).indexOf('/')) == mimeGroup);
372 }
373 }
374
375 if (insert) {
376 menu = actionsMenu;
377
378 const QString submenuName = cfg.readEntry( "X-KDE-Submenu" );
379 if (!submenuName.isEmpty()) {
380 menu = new KMenu(submenuName);
381 actionsMenu->addMenu(menu);
382 }
383
384 Q3ValueList<KDEDesktopMimeType::Service> userServices =
385 KDEDesktopMimeType::userDefinedServices(*dirIt + *entryIt, true);
386
387 Q3ValueList<KDEDesktopMimeType::Service>::Iterator serviceIt;
388 for (serviceIt = userServices.begin(); serviceIt != userServices.end(); ++serviceIt) {
389 KDEDesktopMimeType::Service service = (*serviceIt);
390 if (!service.m_strIcon.isEmpty()) {
391 QAction* action = menu->addAction(SmallIcon(service.m_strIcon),
392 service.m_strName);
393 serviceActions << action;
394 }
395 else {
396 QAction *action = menu->addAction(service.m_strName);
397 serviceActions << action;
398 }
399 actionsVector.append(service);
400 }
401 }
402 }
403 }
404 }
405 }
406
407 const int itemsCount = actionsMenu->actions().count();
408 if (itemsCount == 0) {
409 // no actions are available at all, hence show the "Actions"
410 // submenu disabled
411 actionsMenu->setEnabled(false);
412 }
413
414 if (itemsCount == 1) {
415 // Exactly one item is available. Instead of showing a sub menu with
416 // only one item, show the item directly in the root menu.
417 if (menu == actionsMenu) {
418 // The item is an action, hence show the action in the root menu.
419 const QList<QAction*> actions = actionsMenu->actions();
420 assert(actions.count() == 1);
421
422 const QString text = actions[0]->text();
423 const QIcon icon = actions[0]->icon();
424 if (icon.isNull()) {
425 QAction* action = popup->addAction(text);
426 serviceActions.clear();
427 serviceActions << action;
428 }
429 else {
430 QAction* action = popup->addAction(icon, text);
431 serviceActions.clear();
432 serviceActions << action;
433 }
434 }
435 else {
436 // The item is a sub menu, hence show the sub menu in the root menu.
437 popup->addMenu(menu);
438 }
439 actionsMenu->deleteLater();
440 actionsMenu = 0;
441 }
442 else {
443 popup->addMenu(actionsMenu);
444 }
445
446 return serviceActions;
447 }
448
449 bool DolphinContextMenu::containsEntry(const KMenu* menu,
450 const QString& entryName) const
451 {
452 assert(menu != 0);
453
454 const QList<QAction*> list = menu->actions();
455 const uint count = list.count();
456 for (uint i = 0; i < count; ++i) {
457 const QAction* action = list.at(i);
458 if (action->text() == entryName) {
459 return true;
460 }
461 }
462
463 return false;
464 }