00001
00002
00003
00004
00005
00006
00007
00008
00009
00010
00011
00012
00013
00014
00015
00016
00017
00018
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 <kservice.h>
00037 #include <kservicetypetrader.h>
00038 #include <QFile>
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
00058
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
00073
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 }
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
00119 continue;
00120 }
00121
00122 QMenu* actionSubmenu = new KMenu(menu);
00123 actionSubmenu->setTitle(it.key());
00124 actionSubmenu->menuAction()->setObjectName("services_submenu");
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");
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);
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 KDesktopFileActions::executeService(m_props.urlList(), serviceAction);
00172 }
00173
00175
00176 KFileItemActions::KFileItemActions(QObject* parent)
00177 : QObject(parent), d(new KFileItemActionsPrivate)
00178 {
00179 }
00180
00181
00182 KFileItemActions::~KFileItemActions()
00183 {
00184 delete d;
00185 }
00186
00187 void KFileItemActions::setItemListProperties(const KFileItemListProperties& itemListProperties)
00188 {
00189 d->m_props = itemListProperties;
00190 }
00191
00192 int KFileItemActions::addServiceActionsTo(QMenu* mainMenu)
00193 {
00194 const KFileItemList items = d->m_props.items();
00195 const KFileItem firstItem = items.first();
00196 const QString protocol = firstItem.url().protocol();
00197 const bool isLocal = !firstItem.localPath().isEmpty();
00198 const bool isSingleLocal = items.count() == 1 && isLocal;
00199 const KUrl::List urlList = d->m_props.urlList();
00200
00201 KIO::PopupServices s;
00202
00203
00204 if (isSingleLocal && d->m_props.mimeType() == "application/x-desktop") {
00205
00206 const QString path = firstItem.localPath();
00207 s.builtin = KDesktopFileActions::builtinServices(path);
00208 KDesktopFile desktopFile(path);
00209 KConfigGroup cfg = desktopFile.desktopGroup();
00210 const QString priority = cfg.readEntry("X-KDE-Priority");
00211 const QString submenuName = cfg.readEntry("X-KDE-Submenu");
00212 #if 0
00213 if (cfg.readEntry("Type") == "Link") {
00214 d->m_url = cfg.readEntry("URL");
00215
00216
00217 }
00218 #endif
00219 ServiceList& list = s.selectList(priority, submenuName);
00220 list = KDesktopFileActions::userDefinedServices(path, desktopFile, true );
00221 }
00222
00223
00224
00225
00226 if (d->m_props.isDirectory() && isSingleLocal) {
00227 QString dotDirectoryFile = KUrl::fromPath(firstItem.localPath()).path(KUrl::AddTrailingSlash).append(".directory");
00228 if (QFile::exists(dotDirectoryFile)) {
00229 const KDesktopFile desktopFile(dotDirectoryFile);
00230 const KConfigGroup cfg = desktopFile.desktopGroup();
00231
00232 if (KIOSKAuthorizedAction(cfg)) {
00233 const QString priority = cfg.readEntry("X-KDE-Priority");
00234 const QString submenuName = cfg.readEntry("X-KDE-Submenu");
00235 ServiceList& list = s.selectList(priority, submenuName);
00236 list += KDesktopFileActions::userDefinedServices(dotDirectoryFile, desktopFile, true);
00237 }
00238 }
00239 }
00240
00241 const KConfig config("kservicemenurc", KConfig::NoGlobals);
00242 const KConfigGroup showGroup = config.group("Show");
00243
00244 const QString commonMimeType = d->m_props.mimeType();
00245 const QString commonMimeGroup = d->m_props.mimeGroup();
00246 const KMimeType::Ptr mimeTypePtr = commonMimeType.isEmpty() ? KMimeType::Ptr() : KMimeType::mimeType(commonMimeType);
00247 const KService::List entries = KServiceTypeTrader::self()->query("KonqPopupMenu/Plugin");
00248 KService::List::const_iterator eEnd = entries.end();
00249 for (KService::List::const_iterator it2 = entries.begin(); it2 != eEnd; ++it2) {
00250 QString file = KStandardDirs::locate("services", (*it2)->entryPath());
00251 KDesktopFile desktopFile(file);
00252 const KConfigGroup cfg = desktopFile.desktopGroup();
00253
00254 if (!KIOSKAuthorizedAction(cfg)) {
00255 continue;
00256 }
00257
00258 if (cfg.hasKey("X-KDE-ShowIfRunning")) {
00259 const QString app = cfg.readEntry("X-KDE-ShowIfRunning");
00260 if (QDBusConnection::sessionBus().interface()->isServiceRegistered(app)) {
00261 continue;
00262 }
00263 }
00264 if (cfg.hasKey("X-KDE-ShowIfDBusCall")) {
00265 QString calldata = cfg.readEntry("X-KDE-ShowIfDBusCall");
00266 QStringList parts = calldata.split(' ');
00267 const QString &app = parts.at(0);
00268 const QString &obj = parts.at(1);
00269 QString interface = parts.at(2);
00270 QString method;
00271 int pos = interface.lastIndexOf(QLatin1Char('.'));
00272 if (pos != -1) {
00273 method = interface.mid(pos + 1);
00274 interface.truncate(pos);
00275 }
00276
00277
00278
00279
00280 QDBusMessage reply = QDBusInterface(app, obj, interface).
00281 call(method, urlList.toStringList());
00282 if (reply.arguments().count() < 1 || reply.arguments().at(0).type() != QVariant::Bool || !reply.arguments().at(0).toBool()) {
00283 continue;
00284 }
00285
00286 }
00287 if (cfg.hasKey("X-KDE-Protocol")) {
00288 const QString protocol = cfg.readEntry("X-KDE-Protocol");
00289 if (protocol.startsWith('!')) {
00290 const QString excludedProtocol = protocol.mid(1);
00291 if (excludedProtocol == protocol) {
00292 continue;
00293 }
00294 } else if (protocol != protocol) {
00295 continue;
00296 }
00297 }
00298 else if (cfg.hasKey("X-KDE-Protocols")) {
00299 const QStringList protocols = cfg.readEntry("X-KDE-Protocols", QStringList());
00300 if (!protocols.contains(protocol)) {
00301 continue;
00302 }
00303 }
00304 else if (protocol == "trash") {
00305
00306
00307
00308 continue;
00309 }
00310
00311 if (cfg.hasKey("X-KDE-Require")) {
00312 const QStringList capabilities = cfg.readEntry("X-KDE-Require" , QStringList());
00313 if (capabilities.contains("Write") && !d->m_props.supportsWriting()) {
00314 continue;
00315 }
00316 }
00317 if (cfg.hasKey("Actions") || cfg.hasKey("X-KDE-GetActionMenu")) {
00318
00319 QStringList types = cfg.readEntry("ServiceTypes", QStringList());
00320 types += cfg.readEntry("X-KDE-ServiceTypes", QStringList());
00321 types += cfg.readXdgListEntry("MimeType");
00322
00323
00324 if (types.isEmpty()) {
00325 continue;
00326 }
00327 const QStringList excludeTypes = cfg.readEntry("ExcludeServiceTypes" , QStringList());
00328 bool ok = false;
00329
00330
00331 for (QStringList::ConstIterator it = types.constBegin();
00332 it != types.constEnd() && !ok;
00333 ++it)
00334 {
00335
00336 bool checkTheMimetypes = false;
00337 if (*it == "all/all" ||
00338 *it == "allfiles" ) {
00339 checkTheMimetypes = true;
00340 }
00341
00342
00343 if (!ok &&
00344 !d->m_props.isDirectory() &&
00345 *it == "all/allfiles") {
00346 checkTheMimetypes = true;
00347 }
00348
00349
00350 if (!ok && (
00351 (mimeTypePtr && mimeTypePtr->is(*it)) ||
00352 (!commonMimeGroup.isEmpty() &&
00353 ((*it).right(1) == "*" &&
00354 (*it).left((*it).indexOf('/')) == commonMimeGroup)))) {
00355 checkTheMimetypes = true;
00356 }
00357
00358 if (checkTheMimetypes) {
00359 ok = true;
00360 for (QStringList::ConstIterator itex = excludeTypes.constBegin(); itex != excludeTypes.constEnd(); ++itex) {
00361 if(((*itex).endsWith('*') && (*itex).left((*itex).indexOf('/')) == commonMimeGroup) ||
00362 ((*itex) == commonMimeType)) {
00363 ok = false;
00364 break;
00365 }
00366 }
00367 }
00368 }
00369
00370 if (ok) {
00371 const QString priority = cfg.readEntry("X-KDE-Priority");
00372 const QString submenuName = cfg.readEntry("X-KDE-Submenu");
00373
00374 ServiceList& list = s.selectList(priority, submenuName);
00375 const ServiceList userServices = KDesktopFileActions::userDefinedServices(*(*it2), isLocal, urlList);
00376 foreach (const KServiceAction& action, userServices) {
00377 if (showGroup.readEntry(action.name(), true)) {
00378 list += action;
00379 }
00380 }
00381 }
00382 }
00383 }
00384
00385
00386
00387 QMenu* actionMenu = mainMenu;
00388 int userItemCount = 0;
00389 if (s.user.count() + s.userSubmenus.count() +
00390 s.userPriority.count() + s.userPrioritySubmenus.count() > 1) {
00391
00392 actionMenu = new KMenu(i18nc("@title:menu", "&Actions"), mainMenu);
00393 actionMenu->menuAction()->setObjectName("actions_submenu");
00394 mainMenu->addMenu(actionMenu);
00395 }
00396
00397 userItemCount += d->insertServicesSubmenus(s.userPrioritySubmenus, actionMenu, false);
00398 userItemCount += d->insertServices(s.userPriority, actionMenu, false);
00399
00400
00401 if (userItemCount > 0 &&
00402 (s.user.count() > 0 ||
00403 s.userSubmenus.count() > 0 ||
00404 s.builtin.count() > 0) &&
00405 !actionMenu->actions().last()->isSeparator()) {
00406 actionMenu->addSeparator();
00407 }
00408 userItemCount += d->insertServicesSubmenus(s.userSubmenus, actionMenu, false);
00409 userItemCount += d->insertServices(s.user, actionMenu, false);
00410 userItemCount += d->insertServices(s.builtin, mainMenu, true);
00411 userItemCount += d->insertServicesSubmenus(s.userToplevelSubmenus, mainMenu, false);
00412 userItemCount += d->insertServices(s.userToplevel, mainMenu, false);
00413 return userItemCount;
00414 }
00415
00416
00417 KService::List KFileItemActionsPrivate::associatedApplications(const QString& traderConstraint)
00418 {
00419 if (!KAuthorized::authorizeKAction("openwith")) {
00420 return KService::List();
00421 }
00422
00423 const KFileItemList items = m_props.items();
00424 QStringList mimeTypeList;
00425 KFileItemList::const_iterator kit = items.constBegin();
00426 const KFileItemList::const_iterator kend = items.constEnd();
00427 for (; kit != kend; ++kit) {
00428 if (!mimeTypeList.contains((*kit).mimetype()))
00429 mimeTypeList << (*kit).mimetype();
00430 }
00431
00432 QString constraint = traderConstraint;
00433 const QString subConstraint = " and '%1' in ServiceTypes";
00434
00435 QStringList::ConstIterator it = mimeTypeList.constBegin();
00436 const QStringList::ConstIterator end = mimeTypeList.constEnd();
00437 Q_ASSERT(it != end);
00438 QString firstMimeType = *it;
00439 ++it;
00440 for (; it != end ; ++it) {
00441 constraint += subConstraint.arg(*it);
00442 }
00443
00444 KService::List offers = KMimeTypeTrader::self()->query(firstMimeType, "Application", constraint);
00445
00446 QSet<QString> seenTexts;
00447 for (KService::List::iterator it = offers.begin(); it != offers.end();) {
00448 bool skipThisEntry = false;
00449
00450
00451
00452 const KService::Ptr service = (*it);
00453 const QString appName(service->name());
00454 if (!seenTexts.contains(appName)) {
00455 seenTexts.insert(appName);
00456 } else {
00457 skipThisEntry = true;
00458 }
00459
00460 if (!skipThisEntry) {
00461
00462
00463
00464
00465 const QString onlyShowIn = service->property("OnlyShowIn", QVariant::String).toString();
00466 if (!onlyShowIn.isEmpty()) {
00467 const QStringList aList = onlyShowIn.split(';', QString::SkipEmptyParts);
00468 if (!aList.contains("KDE")) {
00469 skipThisEntry = true;
00470 }
00471 }
00472 const QString notShowIn = service->property("NotShowIn", QVariant::String).toString();
00473 if (!notShowIn.isEmpty()) {
00474 const QStringList aList = notShowIn.split(';', QString::SkipEmptyParts);
00475 if (aList.contains("KDE")) {
00476 skipThisEntry = true;
00477 }
00478 }
00479 }
00480
00481 if (skipThisEntry) {
00482 it = offers.erase(it);
00483 } else {
00484 ++it;
00485 }
00486 }
00487 return offers;
00488 }
00489
00490 void KFileItemActions::addOpenWithActionsTo(QMenu* topMenu, const QString& traderConstraint)
00491 {
00492 const KService::List offers = d->associatedApplications(traderConstraint);
00493
00495
00496 const KFileItemList items = d->m_props.items();
00497 const KFileItem firstItem = items.first();
00498 const bool isLocal = firstItem.url().isLocalFile();
00499
00500
00501 if (!d->m_props.isDirectory() || isLocal) {
00502 if (!topMenu->actions().isEmpty()) {
00503 topMenu->addSeparator();
00504 }
00505
00506 if (!offers.isEmpty()) {
00507 QMenu* menu = topMenu;
00508
00509 if (offers.count() > 1) {
00510 menu = new QMenu(i18nc("@title:menu", "&Open With"), topMenu);
00511 menu->menuAction()->setObjectName("openWith_submenu");
00512 topMenu->addMenu(menu);
00513 }
00514
00515
00516 KService::List::ConstIterator it = offers.constBegin();
00517 for(; it != offers.constEnd(); it++) {
00518 KAction* act = d->createAppAction(*it,
00519
00520 menu == topMenu);
00521 menu->addAction(act);
00522 }
00523
00524 QString openWithActionName;
00525 if (menu != topMenu) {
00526 menu->addSeparator();
00527 openWithActionName = i18nc("@action:inmenu Open With", "&Other...");
00528 } else {
00529 openWithActionName = i18nc("@title:menu", "&Open With...");
00530 }
00531 KAction *openWithAct = new KAction(d->m_parentWidget);
00532 d->m_ownActions.append(openWithAct);
00533 openWithAct->setText(openWithActionName);
00534 QObject::connect(openWithAct, SIGNAL(triggered()), d, SLOT(slotOpenWithDialog()));
00535 menu->addAction(openWithAct);
00536 }
00537 else
00538 {
00539 KAction *act = new KAction(d->m_parentWidget);
00540 d->m_ownActions.append(act);
00541 act->setText(i18nc("@title:menu", "&Open With..."));
00542 QObject::connect(act, SIGNAL(triggered()), d, SLOT(slotOpenWithDialog()));
00543 topMenu->addAction(act);
00544 }
00545
00546 }
00547 }
00548
00549 void KFileItemActionsPrivate::slotRunApplication(QAction* act)
00550 {
00551
00552 KService::Ptr app = act->data().value<KService::Ptr>();
00553 Q_ASSERT(app);
00554 if (app) {
00555 KRun::run(*app, m_props.urlList(), m_parentWidget);
00556 }
00557 }
00558
00559 void KFileItemActionsPrivate::slotOpenWithDialog()
00560 {
00561
00562 KRun::displayOpenWithDialog(m_props.urlList(), m_parentWidget);
00563 }
00564
00565 KAction* KFileItemActionsPrivate::createAppAction(const KService::Ptr& service, bool singleOffer)
00566 {
00567 QString actionName(service->name().replace('&', "&&"));
00568 if (singleOffer) {
00569 actionName = i18n("Open &with %1", actionName);
00570 }
00571
00572 KAction *act = new KAction(m_parentWidget);
00573 m_ownActions.append(act);
00574 act->setIcon(KIcon(service->icon()));
00575 act->setText(actionName);
00576 act->setData(QVariant::fromValue(service));
00577 m_runApplicationActionGroup.addAction(act);
00578 return act;
00579 }
00580
00581 KAction* KFileItemActions::preferredOpenWithAction(const QString& traderConstraint)
00582 {
00583 const KService::List offers = d->associatedApplications(traderConstraint);
00584 if (offers.isEmpty()) {
00585 return 0;
00586 }
00587 return d->createAppAction(offers.first(), true);
00588 }
00589
00590 void KFileItemActions::setParentWidget(QWidget* widget)
00591 {
00592 d->m_parentWidget = widget;
00593 }