KIO
kfileitemactions.cpp
Go to the documentation of this file.
00001 /* This file is part of the KDE project 00002 Copyright (C) 1998-2009 David Faure <faure@kde.org> 00003 00004 This library is free software; you can redistribute it and/or modify 00005 it under the terms of the GNU Library General Public License as published 00006 by the Free Software Foundation; either version 2 of the License or 00007 ( at your option ) version 3 or, at the discretion of KDE e.V. 00008 ( which shall act as a proxy as in section 14 of the GPLv3 ), any later version. 00009 00010 This library is distributed in the hope that it will be useful, 00011 but WITHOUT ANY WARRANTY; without even the implied warranty of 00012 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 00013 Library General Public License for more details. 00014 00015 You should have received a copy of the GNU Library General Public License 00016 along with this library; see the file COPYING.LIB. If not, write to 00017 the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, 00018 Boston, MA 02110-1301, USA. 00019 */ 00020 00021 #include "kfileitemactions.h" 00022 #include "kfileitemactions_p.h" 00023 #include <kaction.h> 00024 #include <krun.h> 00025 #include <kmimetypetrader.h> 00026 #include <kdebug.h> 00027 #include <kdesktopfileactions.h> 00028 #include <kmenu.h> 00029 #include <klocale.h> 00030 #include <kauthorized.h> 00031 #include <kconfiggroup.h> 00032 #include <kdesktopfile.h> 00033 #include <kglobal.h> 00034 #include <kicon.h> 00035 #include <kstandarddirs.h> 00036 #include <kservicetypetrader.h> 00037 #include <QFile> 00038 #include <QtAlgorithms> 00039 00040 #include <QtDBus/QtDBus> 00041 00042 static bool KIOSKAuthorizedAction(const KConfigGroup& cfg) 00043 { 00044 if (!cfg.hasKey("X-KDE-AuthorizeAction")) { 00045 return true; 00046 } 00047 const QStringList list = cfg.readEntry("X-KDE-AuthorizeAction", QStringList()); 00048 for(QStringList::ConstIterator it = list.constBegin(); 00049 it != list.constEnd(); ++it) { 00050 if (!KAuthorized::authorize((*it).trimmed())) { 00051 return false; 00052 } 00053 } 00054 return true; 00055 } 00056 00057 // This helper class stores the .desktop-file actions and the servicemenus 00058 // in order to support X-KDE-Priority and X-KDE-Submenu. 00059 namespace KIO { 00060 class PopupServices 00061 { 00062 public: 00063 ServiceList& selectList(const QString& priority, const QString& submenuName); 00064 00065 ServiceList builtin; 00066 ServiceList user, userToplevel, userPriority; 00067 QMap<QString, ServiceList> userSubmenus, userToplevelSubmenus, userPrioritySubmenus; 00068 }; 00069 00070 ServiceList& PopupServices::selectList(const QString& priority, const QString& submenuName) 00071 { 00072 // we use the categories .desktop entry to define submenus 00073 // if none is defined, we just pop it in the main menu 00074 if (submenuName.isEmpty()) { 00075 if (priority == "TopLevel") { 00076 return userToplevel; 00077 } else if (priority == "Important") { 00078 return userPriority; 00079 } 00080 } else if (priority == "TopLevel") { 00081 return userToplevelSubmenus[submenuName]; 00082 } else if (priority == "Important") { 00083 return userPrioritySubmenus[submenuName]; 00084 } else { 00085 return userSubmenus[submenuName]; 00086 } 00087 return user; 00088 } 00089 } // namespace 00090 00092 00093 KFileItemActionsPrivate::KFileItemActionsPrivate() 00094 : QObject(), 00095 m_executeServiceActionGroup(static_cast<QWidget *>(0)), 00096 m_runApplicationActionGroup(static_cast<QWidget *>(0)), 00097 m_parentWidget(0) 00098 { 00099 QObject::connect(&m_executeServiceActionGroup, SIGNAL(triggered(QAction*)), 00100 this, SLOT(slotExecuteService(QAction*))); 00101 QObject::connect(&m_runApplicationActionGroup, SIGNAL(triggered(QAction*)), 00102 this, SLOT(slotRunApplication(QAction*))); 00103 } 00104 00105 KFileItemActionsPrivate::~KFileItemActionsPrivate() 00106 { 00107 qDeleteAll(m_ownActions); 00108 } 00109 00110 int KFileItemActionsPrivate::insertServicesSubmenus(const QMap<QString, ServiceList>& submenus, 00111 QMenu* menu, 00112 bool isBuiltin) 00113 { 00114 int count = 0; 00115 QMap<QString, ServiceList>::ConstIterator it; 00116 for (it = submenus.begin(); it != submenus.end(); ++it) { 00117 if (it.value().isEmpty()) { 00118 //avoid empty sub-menus 00119 continue; 00120 } 00121 00122 QMenu* actionSubmenu = new KMenu(menu); 00123 actionSubmenu->setTitle(it.key()); 00124 actionSubmenu->menuAction()->setObjectName("services_submenu"); // for the unittest 00125 menu->addMenu(actionSubmenu); 00126 count += insertServices(it.value(), actionSubmenu, isBuiltin); 00127 } 00128 00129 return count; 00130 } 00131 00132 int KFileItemActionsPrivate::insertServices(const ServiceList& list, 00133 QMenu* menu, 00134 bool isBuiltin) 00135 { 00136 int count = 0; 00137 ServiceList::const_iterator it = list.begin(); 00138 for(; it != list.end(); ++it) { 00139 if ((*it).isSeparator()) { 00140 const QList<QAction*> actions = menu->actions(); 00141 if (!actions.isEmpty() && !actions.last()->isSeparator()) { 00142 menu->addSeparator(); 00143 } 00144 continue; 00145 } 00146 00147 if (isBuiltin || !(*it).noDisplay()) { 00148 KAction *act = new KAction(m_parentWidget); 00149 m_ownActions.append(act); 00150 act->setObjectName("menuaction"); // for the unittest 00151 QString text = (*it).text(); 00152 text.replace('&',"&&"); 00153 act->setText(text); 00154 if (!(*it).icon().isEmpty()) { 00155 act->setIcon(KIcon((*it).icon())); 00156 } 00157 act->setData(QVariant::fromValue(*it)); 00158 m_executeServiceActionGroup.addAction(act); 00159 00160 menu->addAction(act); // Add to toplevel menu 00161 ++count; 00162 } 00163 } 00164 00165 return count; 00166 } 00167 00168 void KFileItemActionsPrivate::slotExecuteService(QAction* act) 00169 { 00170 KServiceAction serviceAction = act->data().value<KServiceAction>(); 00171 if (KAuthorized::authorizeKAction(serviceAction.name())) { 00172 KDesktopFileActions::executeService(m_props.urlList(), serviceAction); 00173 } 00174 } 00175 00177 00178 KFileItemActions::KFileItemActions(QObject* parent) 00179 : QObject(parent), d(new KFileItemActionsPrivate) 00180 { 00181 } 00182 00183 00184 KFileItemActions::~KFileItemActions() 00185 { 00186 delete d; 00187 } 00188 00189 void KFileItemActions::setItemListProperties(const KFileItemListProperties& itemListProperties) 00190 { 00191 d->m_props = itemListProperties; 00192 00193 d->m_mimeTypeList.clear(); 00194 const KFileItemList items = d->m_props.items(); 00195 KFileItemList::const_iterator kit = items.constBegin(); 00196 const KFileItemList::const_iterator kend = items.constEnd(); 00197 for (; kit != kend; ++kit) { 00198 if (!d->m_mimeTypeList.contains((*kit).mimetype())) 00199 d->m_mimeTypeList << (*kit).mimetype(); 00200 } 00201 } 00202 00203 int KFileItemActions::addServiceActionsTo(QMenu* mainMenu) 00204 { 00205 const KFileItemList items = d->m_props.items(); 00206 const KFileItem firstItem = items.first(); 00207 const QString protocol = firstItem.url().protocol(); // assumed to be the same for all items 00208 const bool isLocal = !firstItem.localPath().isEmpty(); 00209 const bool isSingleLocal = items.count() == 1 && isLocal; 00210 const KUrl::List urlList = d->m_props.urlList(); 00211 00212 KIO::PopupServices s; 00213 00214 // 1 - Look for builtin and user-defined services 00215 if (isSingleLocal && (d->m_props.mimeType() == "application/x-desktop" || // .desktop file 00216 d->m_props.mimeType() == "inode/blockdevice")) { // dev file 00217 // get builtin services, like mount/unmount 00218 const QString path = firstItem.localPath(); 00219 s.builtin = KDesktopFileActions::builtinServices(path); 00220 KDesktopFile desktopFile(path); 00221 KConfigGroup cfg = desktopFile.desktopGroup(); 00222 const QString priority = cfg.readEntry("X-KDE-Priority"); 00223 const QString submenuName = cfg.readEntry("X-KDE-Submenu"); 00224 #if 0 00225 if (cfg.readEntry("Type") == "Link") { 00226 d->m_url = cfg.readEntry("URL"); 00227 // TODO: Do we want to make all the actions apply on the target 00228 // of the .desktop file instead of the .desktop file itself? 00229 } 00230 #endif 00231 ServiceList& list = s.selectList(priority, submenuName); 00232 list = KDesktopFileActions::userDefinedServices(path, desktopFile, true /*isLocal*/); 00233 } 00234 00235 // 2 - Look for "servicemenus" bindings (user-defined services) 00236 00237 // first check the .directory if this is a directory 00238 if (d->m_props.isDirectory() && isSingleLocal) { 00239 QString dotDirectoryFile = KUrl::fromPath(firstItem.localPath()).path(KUrl::AddTrailingSlash).append(".directory"); 00240 if (QFile::exists(dotDirectoryFile)) { 00241 const KDesktopFile desktopFile(dotDirectoryFile); 00242 const KConfigGroup cfg = desktopFile.desktopGroup(); 00243 00244 if (KIOSKAuthorizedAction(cfg)) { 00245 const QString priority = cfg.readEntry("X-KDE-Priority"); 00246 const QString submenuName = cfg.readEntry("X-KDE-Submenu"); 00247 ServiceList& list = s.selectList(priority, submenuName); 00248 list += KDesktopFileActions::userDefinedServices(dotDirectoryFile, desktopFile, true); 00249 } 00250 } 00251 } 00252 00253 const KConfig config("kservicemenurc", KConfig::NoGlobals); 00254 const KConfigGroup showGroup = config.group("Show"); 00255 00256 const QString commonMimeType = d->m_props.mimeType(); 00257 const QString commonMimeGroup = d->m_props.mimeGroup(); 00258 const KMimeType::Ptr mimeTypePtr = commonMimeType.isEmpty() ? KMimeType::Ptr() : KMimeType::mimeType(commonMimeType); 00259 const KService::List entries = KServiceTypeTrader::self()->query("KonqPopupMenu/Plugin"); 00260 KService::List::const_iterator eEnd = entries.end(); 00261 for (KService::List::const_iterator it2 = entries.begin(); it2 != eEnd; ++it2) { 00262 QString file = KStandardDirs::locate("services", (*it2)->entryPath()); 00263 KDesktopFile desktopFile(file); 00264 const KConfigGroup cfg = desktopFile.desktopGroup(); 00265 00266 if (!KIOSKAuthorizedAction(cfg)) { 00267 continue; 00268 } 00269 00270 if (cfg.hasKey("X-KDE-ShowIfRunning")) { 00271 const QString app = cfg.readEntry("X-KDE-ShowIfRunning"); 00272 if (QDBusConnection::sessionBus().interface()->isServiceRegistered(app)) { 00273 continue; 00274 } 00275 } 00276 if (cfg.hasKey("X-KDE-ShowIfDBusCall")) { 00277 QString calldata = cfg.readEntry("X-KDE-ShowIfDBusCall"); 00278 const QStringList parts = calldata.split(' '); 00279 const QString &app = parts.at(0); 00280 const QString &obj = parts.at(1); 00281 QString interface = parts.at(2); 00282 QString method; 00283 int pos = interface.lastIndexOf(QLatin1Char('.')); 00284 if (pos != -1) { 00285 method = interface.mid(pos + 1); 00286 interface.truncate(pos); 00287 } 00288 00289 //if (!QDBus::sessionBus().busService()->nameHasOwner(app)) 00290 // continue; //app does not exist so cannot send call 00291 00292 QDBusMessage reply = QDBusInterface(app, obj, interface). 00293 call(method, urlList.toStringList()); 00294 if (reply.arguments().count() < 1 || reply.arguments().at(0).type() != QVariant::Bool || !reply.arguments().at(0).toBool()) { 00295 continue; 00296 } 00297 00298 } 00299 if (cfg.hasKey("X-KDE-Protocol")) { 00300 const QString theProtocol = cfg.readEntry("X-KDE-Protocol"); 00301 if (theProtocol.startsWith('!')) { 00302 const QString excludedProtocol = theProtocol.mid(1); 00303 if (excludedProtocol == protocol) { 00304 continue; 00305 } 00306 } else if (protocol != theProtocol) { 00307 continue; 00308 } 00309 } 00310 else if (cfg.hasKey("X-KDE-Protocols")) { 00311 const QStringList protocols = cfg.readEntry("X-KDE-Protocols", QStringList()); 00312 if (!protocols.contains(protocol)) { 00313 continue; 00314 } 00315 } 00316 else if (protocol == "trash") { 00317 // Require servicemenus for the trash to ask for protocol=trash explicitly. 00318 // Trashed files aren't supposed to be available for actions. 00319 // One might want a servicemenu for trash.desktop itself though. 00320 continue; 00321 } 00322 00323 if (cfg.hasKey("X-KDE-Require")) { 00324 const QStringList capabilities = cfg.readEntry("X-KDE-Require" , QStringList()); 00325 if (capabilities.contains("Write") && !d->m_props.supportsWriting()) { 00326 continue; 00327 } 00328 } 00329 if (cfg.hasKey("Actions") || cfg.hasKey("X-KDE-GetActionMenu")) { 00330 // Like KService, we support ServiceTypes, X-KDE-ServiceTypes, and MimeType. 00331 QStringList types = cfg.readEntry("ServiceTypes", QStringList()); 00332 types += cfg.readEntry("X-KDE-ServiceTypes", QStringList()); 00333 types += cfg.readXdgListEntry("MimeType"); 00334 //kDebug() << file << types; 00335 00336 if (types.isEmpty()) { 00337 continue; 00338 } 00339 const QStringList excludeTypes = cfg.readEntry("ExcludeServiceTypes" , QStringList()); 00340 bool ok = false; 00341 00342 // check for exact matches or a typeglob'd mimetype if we have a mimetype 00343 for (QStringList::ConstIterator it = types.constBegin(); 00344 it != types.constEnd() && !ok; 00345 ++it) 00346 { 00347 // first check if we have an all mimetype 00348 bool checkTheMimetypes = false; 00349 if (*it == "all/all" || 00350 *it == "allfiles" /*compat with KDE up to 3.0.3*/) { 00351 checkTheMimetypes = true; 00352 } 00353 00354 // next, do we match all files? 00355 if (!ok && 00356 !d->m_props.isDirectory() && 00357 *it == "all/allfiles") { 00358 checkTheMimetypes = true; 00359 } 00360 00361 // if we have a mimetype, see if we have an exact or a type globbed match 00362 if (!ok && ( 00363 (mimeTypePtr && mimeTypePtr->is(*it)) || 00364 (!commonMimeGroup.isEmpty() && 00365 ((*it).right(1) == "*" && 00366 (*it).left((*it).indexOf('/')) == commonMimeGroup)))) { 00367 checkTheMimetypes = true; 00368 } 00369 00370 if (checkTheMimetypes) { 00371 ok = true; 00372 for (QStringList::ConstIterator itex = excludeTypes.constBegin(); itex != excludeTypes.constEnd(); ++itex) { 00373 if(((*itex).endsWith('*') && (*itex).left((*itex).indexOf('/')) == commonMimeGroup) || 00374 ((*itex) == commonMimeType)) { 00375 ok = false; 00376 break; 00377 } 00378 } 00379 } 00380 } 00381 00382 if (ok) { 00383 const QString priority = cfg.readEntry("X-KDE-Priority"); 00384 const QString submenuName = cfg.readEntry("X-KDE-Submenu"); 00385 00386 ServiceList& list = s.selectList(priority, submenuName); 00387 const ServiceList userServices = KDesktopFileActions::userDefinedServices(*(*it2), isLocal, urlList); 00388 foreach (const KServiceAction& action, userServices) { 00389 if (showGroup.readEntry(action.name(), true)) { 00390 list += action; 00391 } 00392 } 00393 } 00394 } 00395 } 00396 00397 00398 00399 QMenu* actionMenu = mainMenu; 00400 int userItemCount = 0; 00401 if (s.user.count() + s.userSubmenus.count() + 00402 s.userPriority.count() + s.userPrioritySubmenus.count() > 1) { 00403 // we have more than one item, so let's make a submenu 00404 actionMenu = new KMenu(i18nc("@title:menu", "&Actions"), mainMenu); 00405 actionMenu->menuAction()->setObjectName("actions_submenu"); // for the unittest 00406 mainMenu->addMenu(actionMenu); 00407 } 00408 00409 userItemCount += d->insertServicesSubmenus(s.userPrioritySubmenus, actionMenu, false); 00410 userItemCount += d->insertServices(s.userPriority, actionMenu, false); 00411 00412 // see if we need to put a separator between our priority items and our regular items 00413 if (userItemCount > 0 && 00414 (s.user.count() > 0 || 00415 s.userSubmenus.count() > 0 || 00416 s.builtin.count() > 0) && 00417 !actionMenu->actions().last()->isSeparator()) { 00418 actionMenu->addSeparator(); 00419 } 00420 userItemCount += d->insertServicesSubmenus(s.userSubmenus, actionMenu, false); 00421 userItemCount += d->insertServices(s.user, actionMenu, false); 00422 userItemCount += d->insertServices(s.builtin, mainMenu, true); 00423 userItemCount += d->insertServicesSubmenus(s.userToplevelSubmenus, mainMenu, false); 00424 userItemCount += d->insertServices(s.userToplevel, mainMenu, false); 00425 return userItemCount; 00426 } 00427 00428 00429 // static 00430 KService::List KFileItemActions::associatedApplications(const QStringList& mimeTypeList, const QString& traderConstraint) 00431 { 00432 if (!KAuthorized::authorizeKAction("openwith") || mimeTypeList.isEmpty()) { 00433 return KService::List(); 00434 } 00435 00436 const KService::List firstOffers = KMimeTypeTrader::self()->query(mimeTypeList.first(), "Application", traderConstraint); 00437 00438 QList<KFileItemActionsPrivate::ServiceRank> rankings; 00439 QStringList serviceList; 00440 00441 // This section does two things. First, it determines which services are common to all the given mimetypes. 00442 // Second, it ranks them based on their preference level in the associated applications list. 00443 // The more often a service appear near the front of the list, the LOWER its score. 00444 00445 for (int i = 0; i < firstOffers.count(); ++i) { 00446 KFileItemActionsPrivate::ServiceRank tempRank; 00447 tempRank.service = firstOffers[i]; 00448 tempRank.score = i; 00449 rankings << tempRank; 00450 serviceList << tempRank.service->storageId(); 00451 } 00452 00453 for (int j = 1; j < mimeTypeList.count(); ++j) { 00454 QStringList subservice; // list of services that support this mimetype 00455 const KService::List offers = KMimeTypeTrader::self()->query(mimeTypeList[j], "Application", traderConstraint); 00456 for (int i = 0; i != offers.count(); ++i) { 00457 const QString serviceId = offers[i]->storageId(); 00458 subservice << serviceId; 00459 const int idPos = serviceList.indexOf(serviceId); 00460 if (idPos != -1) { 00461 rankings[idPos].score += i; 00462 } // else: we ignore the services that didn't support the previous mimetypes 00463 } 00464 00465 // Remove services which supported the previous mimetypes but don't support this one 00466 for (int i = 0; i < serviceList.count(); ++i) { 00467 if (!subservice.contains(serviceList[i])) { 00468 serviceList.removeAt(i); 00469 rankings.removeAt(i); 00470 --i; 00471 } 00472 } 00473 // Nothing left -> there is no common application for these mimetypes 00474 if (rankings.isEmpty()) { 00475 return KService::List(); 00476 } 00477 } 00478 00479 qSort(rankings.begin(), rankings.end(), KFileItemActionsPrivate::lessRank); 00480 00481 KService::List result; 00482 Q_FOREACH(const KFileItemActionsPrivate::ServiceRank& tempRank, rankings) { 00483 result << tempRank.service; 00484 } 00485 00486 return result; 00487 } 00488 00489 // KMimeTypeTrader::preferredService doesn't take a constraint 00490 static KService::Ptr preferredService(const QString& mimeType, const QString& constraint) 00491 { 00492 const KService::List services = KMimeTypeTrader::self()->query(mimeType, QString::fromLatin1("Application"), constraint); 00493 return !services.isEmpty() ? services.first() : KService::Ptr(); 00494 } 00495 00496 void KFileItemActions::addOpenWithActionsTo(QMenu* topMenu, const QString& traderConstraint) 00497 { 00498 if (!KAuthorized::authorizeKAction("openwith")) { 00499 return; 00500 } 00501 00502 d->m_traderConstraint = traderConstraint; 00503 KService::List offers = associatedApplications(d->m_mimeTypeList, traderConstraint); 00504 00506 00507 const KFileItemList items = d->m_props.items(); 00508 const KFileItem firstItem = items.first(); 00509 const bool isLocal = firstItem.url().isLocalFile(); 00510 // "Open With..." for folders is really not very useful, especially for remote folders. 00511 // (media:/something, or trash:/, or ftp://...) 00512 if (!d->m_props.isDirectory() || isLocal) { 00513 if (!topMenu->actions().isEmpty()) { 00514 topMenu->addSeparator(); 00515 } 00516 00517 KAction *runAct = new KAction(d->m_parentWidget); 00518 QString runActionName; 00519 00520 00521 const QStringList serviceIdList = d->listPreferredServiceIds(d->m_mimeTypeList, traderConstraint); 00522 //kDebug(7010) << "serviceIdList=" << serviceIdList; 00523 00524 // When selecting files with multiple mimetypes, offer either "open with <app for all>" 00525 // or a generic <open> (if there are any apps associated). 00526 if (d->m_mimeTypeList.count() > 1 00527 && !serviceIdList.isEmpty() 00528 && !(serviceIdList.count()==1 && serviceIdList.first().isEmpty())) { // empty means "no apps associated" 00529 00530 d->m_ownActions.append(runAct); 00531 00532 if (serviceIdList.count() == 1) { 00533 const KService::Ptr app = preferredService(d->m_mimeTypeList.first(), traderConstraint); 00534 runActionName = i18n("&Open with %1", app->name()); 00535 runAct->setIcon(KIcon(app->icon())); 00536 00537 // Remove that app from the offers list (#242731) 00538 for (int i = 0; i < offers.count() ; ++i) { 00539 if (offers[i]->storageId() == app->storageId()) { 00540 offers.removeAt(i); 00541 break; 00542 } 00543 } 00544 } else { 00545 runActionName = i18n("&Open"); 00546 } 00547 00548 runAct->setText(runActionName); 00549 00550 d->m_traderConstraint = traderConstraint; 00551 d->m_fileOpenList = d->m_props.items(); 00552 QObject::connect(runAct, SIGNAL(triggered()), d, SLOT(slotRunPreferredApplications())); 00553 topMenu->addAction(runAct); 00554 } 00555 00556 if (!offers.isEmpty()) { 00557 QMenu* menu = topMenu; 00558 00559 if (offers.count() > 1) { // submenu 'open with' 00560 menu = new QMenu(i18nc("@title:menu", "&Open With"), topMenu); 00561 menu->menuAction()->setObjectName("openWith_submenu"); // for the unittest 00562 topMenu->addMenu(menu); 00563 } 00564 //kDebug() << offers.count() << "offers" << topMenu << menu; 00565 00566 KService::List::ConstIterator it = offers.constBegin(); 00567 for(; it != offers.constEnd(); it++) { 00568 KAction* act = d->createAppAction(*it, 00569 // no submenu -> prefix single offer 00570 menu == topMenu); 00571 menu->addAction(act); 00572 } 00573 00574 QString openWithActionName; 00575 if (menu != topMenu) { // submenu 00576 menu->addSeparator(); 00577 openWithActionName = i18nc("@action:inmenu Open With", "&Other..."); 00578 } else { 00579 openWithActionName = i18nc("@title:menu", "&Open With..."); 00580 } 00581 KAction *openWithAct = new KAction(d->m_parentWidget); 00582 d->m_ownActions.append(openWithAct); 00583 openWithAct->setText(openWithActionName); 00584 QObject::connect(openWithAct, SIGNAL(triggered()), d, SLOT(slotOpenWithDialog())); 00585 menu->addAction(openWithAct); 00586 } 00587 else // no app offers -> Open With... 00588 { 00589 KAction *act = new KAction(d->m_parentWidget); 00590 d->m_ownActions.append(act); 00591 act->setText(i18nc("@title:menu", "&Open With...")); 00592 QObject::connect(act, SIGNAL(triggered()), d, SLOT(slotOpenWithDialog())); 00593 topMenu->addAction(act); 00594 } 00595 00596 } 00597 } 00598 00599 void KFileItemActionsPrivate::slotRunPreferredApplications() 00600 { 00601 const KFileItemList fileItems = m_fileOpenList; 00602 00603 const QStringList mimeTypeList = listMimeTypes(fileItems); 00604 const QStringList serviceIdList = listPreferredServiceIds(mimeTypeList, m_traderConstraint); 00605 00606 foreach (const QString serviceId, serviceIdList) { 00607 KFileItemList serviceItems; 00608 foreach (const KFileItem& item, fileItems) { 00609 const KService::Ptr serv = preferredService(item.mimetype(), m_traderConstraint); 00610 const QString preferredServiceId = serv ? serv->storageId() : QString(); 00611 if (preferredServiceId == serviceId) { 00612 serviceItems << item; 00613 } 00614 } 00615 00616 if (serviceId.isEmpty()) { // empty means: no associated app for this mimetype 00617 openWithByMime(serviceItems); 00618 continue; 00619 } 00620 00621 const KService::Ptr servicePtr = KService::serviceByStorageId(serviceId); 00622 if (servicePtr.isNull()) { 00623 KRun::displayOpenWithDialog(serviceItems.urlList(), m_parentWidget); 00624 continue; 00625 } 00626 KRun::run(*servicePtr, serviceItems.urlList(), m_parentWidget); 00627 } 00628 } 00629 00630 void KFileItemActions::runPreferredApplications(const KFileItemList& fileOpenList, const QString& traderConstraint) 00631 { 00632 d->m_fileOpenList = fileOpenList; 00633 d->m_traderConstraint = traderConstraint; 00634 d->slotRunPreferredApplications(); 00635 } 00636 00637 void KFileItemActionsPrivate::openWithByMime(const KFileItemList& fileItems) 00638 { 00639 const QStringList mimeTypeList = listMimeTypes(fileItems); 00640 foreach (const QString mimeType, mimeTypeList) { 00641 KFileItemList mimeItems; 00642 foreach (const KFileItem& item, fileItems) { 00643 if (item.mimetype() == mimeType) { 00644 mimeItems << item; 00645 } 00646 } 00647 KRun::displayOpenWithDialog(mimeItems.urlList(), m_parentWidget); 00648 } 00649 } 00650 00651 void KFileItemActionsPrivate::slotRunApplication(QAction* act) 00652 { 00653 // Is it an application, from one of the "Open With" actions 00654 KService::Ptr app = act->data().value<KService::Ptr>(); 00655 Q_ASSERT(app); 00656 if (app) { 00657 KRun::run(*app, m_props.urlList(), m_parentWidget); 00658 } 00659 } 00660 00661 void KFileItemActionsPrivate::slotOpenWithDialog() 00662 { 00663 // The item 'Other...' or 'Open With...' has been selected 00664 KRun::displayOpenWithDialog(m_props.urlList(), m_parentWidget); 00665 } 00666 00667 QStringList KFileItemActionsPrivate::listMimeTypes(const KFileItemList& items) 00668 { 00669 QStringList mimeTypeList; 00670 foreach (const KFileItem& item, items) { 00671 if (!mimeTypeList.contains(item.mimetype())) 00672 mimeTypeList << item.mimetype(); 00673 } 00674 return mimeTypeList; 00675 } 00676 00677 QStringList KFileItemActionsPrivate::listPreferredServiceIds(const QStringList& mimeTypeList, const QString& traderConstraint) 00678 { 00679 QStringList serviceIdList; 00680 Q_FOREACH(const QString& mimeType, mimeTypeList) { 00681 const KService::Ptr serv = preferredService(mimeType, traderConstraint); 00682 const QString newOffer = serv ? serv->storageId() : QString(); 00683 serviceIdList << newOffer; 00684 } 00685 serviceIdList.removeDuplicates(); 00686 return serviceIdList; 00687 } 00688 00689 KAction* KFileItemActionsPrivate::createAppAction(const KService::Ptr& service, bool singleOffer) 00690 { 00691 QString actionName(service->name().replace('&', "&&")); 00692 if (singleOffer) { 00693 actionName = i18n("Open &with %1", actionName); 00694 } else { 00695 actionName = i18nc("@item:inmenu Open With, %1 is application name", "%1", actionName); 00696 } 00697 00698 KAction *act = new KAction(m_parentWidget); 00699 m_ownActions.append(act); 00700 act->setIcon(KIcon(service->icon())); 00701 act->setText(actionName); 00702 act->setData(QVariant::fromValue(service)); 00703 m_runApplicationActionGroup.addAction(act); 00704 return act; 00705 } 00706 00707 KAction* KFileItemActions::preferredOpenWithAction(const QString& traderConstraint) 00708 { 00709 const KService::List offers = associatedApplications(d->m_mimeTypeList, traderConstraint); 00710 if (offers.isEmpty()) { 00711 return 0; 00712 } 00713 return d->createAppAction(offers.first(), true); 00714 } 00715 00716 void KFileItemActions::setParentWidget(QWidget* widget) 00717 { 00718 d->m_parentWidget = widget; 00719 }
KDE 4.7 API Reference