File indexing completed on 2024-04-28 04:58:11
0001 /* This file is part of the KDE project 0002 SPDX-FileCopyrightText: 1998-2008 David Faure <faure@kde.org> 0003 SPDX-FileCopyrightText: 2001 Holger Freyther <freyther@yahoo.com> 0004 0005 SPDX-License-Identifier: LGPL-2.0-or-later 0006 */ 0007 0008 #include "konq_popupmenu.h" 0009 0010 #include <KActionCollection> 0011 0012 #include <kfileitemlistproperties.h> 0013 #include "kfileitemactions.h" 0014 #include "kabstractfileitemactionplugin.h" 0015 #include "kpropertiesdialog.h" 0016 0017 #include <klocalizedstring.h> 0018 #include <kbookmarkmanager.h> 0019 #include <kbookmarkdialog.h> 0020 #include <KIO/OpenUrlJob> 0021 #include <KIO/JobUiDelegate> 0022 #include <kprotocolmanager.h> 0023 #include <knewfilemenu.h> 0024 #include <kconfiggroup.h> 0025 #include <KSharedConfig> 0026 #include <kdesktopfile.h> 0027 #include <kauthorized.h> 0028 #include <kacceleratormanager.h> 0029 #include <KIO/EmptyTrashJob> 0030 #include <KIO/JobUiDelegate> 0031 #include <KIO/RestoreJob> 0032 #include <KIO/CommandLauncherJob> 0033 #include <kio_version.h> 0034 #include <KIO/OpenUrlJob> 0035 #include <KTerminalLauncherJob> 0036 #include <KFileCopyToMenu> 0037 #include <KJobWidgets> 0038 #include <KJobUiDelegate> 0039 #include <KMimeTypeEditor> 0040 #include <KPluginMetaData> 0041 #include <KIO/JobUiDelegateFactory> 0042 0043 #include <QIcon> 0044 #include <QFileInfo> 0045 #include <QMimeDatabase> 0046 0047 /* 0048 Test cases: 0049 iconview file: background 0050 iconview file: file (with and without servicemenus) 0051 iconview file: directory 0052 iconview remote protocol (e.g. ftp: or fish:) 0053 iconview trash:/ 0054 sidebar directory tree 0055 sidebar Devices / Hard Disc 0056 khtml background 0057 khtml link 0058 khtml image (www.kde.org RMB on K logo) 0059 khtmlimage (same as above, then choose View image, then RMB) 0060 selected text in khtml 0061 embedded katepart 0062 folder on the desktop 0063 trash link on the desktop 0064 trashed file or directory 0065 application .desktop file 0066 Then the same after uninstalling kdeaddons/konq-plugins (arkplugin in particular) 0067 */ 0068 0069 class KonqPopupMenuPrivate 0070 { 0071 public: 0072 KonqPopupMenuPrivate(KonqPopupMenu *qq, KActionCollection &actions, QWidget *parentWidget) 0073 : q(qq), 0074 m_parentWidget(parentWidget), 0075 m_popupFlags(KonqPopupMenu::DefaultPopupItems), 0076 m_pMenuNew(nullptr), 0077 m_copyToMenu(parentWidget), 0078 m_bookmarkManager(nullptr), 0079 m_actions(actions) 0080 { 0081 } 0082 0083 ~KonqPopupMenuPrivate() 0084 { 0085 qDeleteAll(m_ownActions); 0086 } 0087 0088 void addNamedAction(const char *name); 0089 void addGroup(KonqPopupMenu::ActionGroup group); 0090 void populate(); 0091 void aboutToShow(); 0092 0093 void slotPopupNewDir(); 0094 void slotPopupNewView(); 0095 void slotPopupEmptyTrashBin(); 0096 void slotConfigTrashBin(); 0097 void slotPopupRestoreTrashedItems(); 0098 void slotPopupAddToBookmark(); 0099 void slotPopupMimeType(); 0100 void slotPopupProperties(); 0101 void slotShowOriginalFile(); 0102 0103 KonqPopupMenu *q; 0104 QWidget *m_parentWidget; 0105 QString m_urlTitle; 0106 KonqPopupMenu::Flags m_popupFlags; 0107 KNewFileMenu *m_pMenuNew; 0108 QUrl m_sViewURL; 0109 KFileItemListProperties m_popupItemProperties; 0110 KFileItemActions m_menuActions; 0111 KFileCopyToMenu m_copyToMenu; 0112 KBookmarkManager *m_bookmarkManager; 0113 KActionCollection &m_actions; 0114 QList<QAction *> m_ownActions; 0115 KonqPopupMenu::ActionGroupMap m_actionGroups; 0116 }; 0117 0118 ////////////////// 0119 0120 KonqPopupMenu::KonqPopupMenu(const KFileItemList &items, 0121 const QUrl &viewURL, 0122 KActionCollection &actions, 0123 Flags popupFlags, 0124 QWidget *parentWidget) 0125 : QMenu(parentWidget), 0126 d(new KonqPopupMenuPrivate(this, actions, parentWidget)) 0127 { 0128 d->m_sViewURL = viewURL; 0129 d->m_popupItemProperties.setItems(items); 0130 d->m_menuActions.setParentWidget(parentWidget); 0131 d->m_popupFlags = popupFlags; 0132 0133 connect(this, &QMenu::aboutToShow, this, [this]() { d->aboutToShow(); }); 0134 } 0135 0136 void KonqPopupMenuPrivate::addNamedAction(const char *name) 0137 { 0138 QAction *act = m_actions.action(QString::fromLatin1(name)); 0139 if (act) { 0140 q->addAction(act); 0141 } 0142 } 0143 0144 void KonqPopupMenuPrivate::aboutToShow() 0145 { 0146 populate(); 0147 KAcceleratorManager::manage(q); 0148 } 0149 0150 void KonqPopupMenuPrivate::populate() 0151 { 0152 Q_ASSERT(m_popupItemProperties.items().count() >= 1); 0153 0154 bool bTrashIncluded = false; 0155 0156 const KFileItemList lstItems = m_popupItemProperties.items(); 0157 QList<QUrl> lstUrls; 0158 lstUrls.reserve(lstItems.count()); 0159 for (const KFileItem &item : lstItems) { 0160 const QUrl url = item.url(); 0161 lstUrls.append(url); 0162 if (!bTrashIncluded && ((url.scheme() == QLatin1String("trash") && url.path().length() <= 1))) { 0163 bTrashIncluded = true; 0164 } 0165 } 0166 0167 const bool isDirectory = m_popupItemProperties.isDirectory(); 0168 const bool sReading = m_popupItemProperties.supportsReading(); 0169 bool sDeleting = (m_popupFlags & KonqPopupMenu::NoDeletion) == 0 0170 && m_popupItemProperties.supportsDeleting(); 0171 const bool sWriting = m_popupItemProperties.supportsWriting(); 0172 const bool sMoving = sDeleting && m_popupItemProperties.supportsMoving(); 0173 0174 QUrl url = m_sViewURL.adjusted(QUrl::NormalizePathSegments); 0175 0176 bool isTrashLink = false; 0177 bool isCurrentTrash = false; 0178 bool currentDir = false; 0179 bool isSymLink = false; 0180 bool isSymLinkInSameDir = false; // true for "ln -s foo bar", false for links to foo/sub or /foo 0181 0182 //check if url is current directory 0183 if (lstItems.count() == 1) { 0184 const KFileItem firstPopupItem = lstItems.constFirst(); 0185 if (firstPopupItem.isLink()) { 0186 isSymLink = true; 0187 isSymLinkInSameDir = !firstPopupItem.linkDest().contains(QLatin1Char('/')); 0188 } 0189 const QUrl firstPopupURL(firstPopupItem.url().adjusted(QUrl::NormalizePathSegments)); 0190 currentDir = (firstPopupURL.matches(url, QUrl::StripTrailingSlash)); 0191 if (firstPopupItem.isDesktopFile()) { 0192 KDesktopFile desktopFile(firstPopupItem.localPath()); 0193 const KConfigGroup cfg = desktopFile.desktopGroup(); 0194 isTrashLink = cfg.readEntry("Type") == QLatin1String("Link") 0195 && cfg.readEntry("URL") == QLatin1String("trash:/"); 0196 } 0197 0198 if (isTrashLink) { 0199 sDeleting = false; 0200 } 0201 0202 // isCurrentTrash: popup on trash:/ itself, or on the trash.desktop link 0203 isCurrentTrash = (firstPopupURL.scheme() == QLatin1String("trash") && firstPopupURL.path().length() <= 1) 0204 || isTrashLink; 0205 } 0206 0207 const bool isIntoTrash = (url.scheme() == QLatin1String("trash")) && !isCurrentTrash; // trashed file, not trash:/ itself 0208 const bool bIsLink = (m_popupFlags & KonqPopupMenu::IsLink); 0209 0210 addGroup(KonqPopupMenu::TopActions); // used e.g. for ShowMenuBar. includes a separator at the end 0211 0212 QAction *act; 0213 QAction *actNewWindow = nullptr; 0214 0215 // Either 'newview' is in the actions we're given (probably in the tabhandling group) 0216 // or we need to insert it ourselves (e.g. for the desktop). 0217 // In the first case, actNewWindow must remain 0. 0218 if (((m_popupFlags & KonqPopupMenu::ShowNewWindow) != 0) && sReading) { 0219 const QString openStr = i18n("&Open"); 0220 actNewWindow = new QAction(m_parentWidget /*for status tips*/); 0221 m_ownActions.append(actNewWindow); 0222 actNewWindow->setIcon(QIcon::fromTheme(QStringLiteral("window-new"))); 0223 actNewWindow->setText(openStr); 0224 QObject::connect(actNewWindow, &QAction::triggered, [this]() { 0225 slotPopupNewView(); 0226 }); 0227 } 0228 0229 if (isDirectory && sWriting && !isCurrentTrash) { // A dir, and we can create things into it 0230 const bool mkdirRequested = m_popupFlags & KonqPopupMenu::ShowCreateDirectory; 0231 if ((currentDir || mkdirRequested) && m_pMenuNew) { // Current dir -> add the "new" menu 0232 // As requested by KNewFileMenu : 0233 m_pMenuNew->checkUpToDate(); 0234 m_pMenuNew->setWorkingDirectory(url); 0235 0236 q->addAction(m_pMenuNew); 0237 q->addSeparator(); 0238 } else if (mkdirRequested) { 0239 QAction *actNewDir = new QAction(m_parentWidget); 0240 m_ownActions.append(actNewDir); 0241 actNewDir->setIcon(QIcon::fromTheme(QStringLiteral("folder-new"))); 0242 actNewDir->setText(i18n("Create &Folder...")); 0243 QObject::connect(actNewDir, &QAction::triggered, [this]() { 0244 slotPopupNewDir(); 0245 }); 0246 q->addAction(actNewDir); 0247 q->addSeparator(); 0248 } 0249 } else if (isIntoTrash) { 0250 // Trashed item, offer restoring 0251 act = new QAction(m_parentWidget /*for status tips*/); 0252 m_ownActions.append(act); 0253 act->setText(i18n("&Restore")); 0254 //PORT QT5 act->setHelpText(i18n("Restores this file or directory, back to the location where it was deleted from initially")); 0255 QObject::connect(act, &QAction::triggered, [this]() { 0256 slotPopupRestoreTrashedItems(); 0257 }); 0258 q->addAction(act); 0259 } 0260 0261 if (!currentDir && isSymLink && !isSymLinkInSameDir) { 0262 // #65151: offer to open the target's parent dir 0263 act = new QAction(m_parentWidget); 0264 m_ownActions.append(act); 0265 act->setText(isDirectory ? i18n("Show Original Directory") : i18n("Show Original File")); 0266 //PORT TO QT5 act->setHelpText(i18n("Opens a new file manager window showing the target of this link, in its parent directory.")); 0267 QObject::connect(act, &QAction::triggered, [this]() { 0268 slotShowOriginalFile(); 0269 }); 0270 q->addAction(act); 0271 } 0272 0273 // "open in new window" is either provided by us, or by the tabhandling group 0274 if (actNewWindow) { 0275 q->addAction(actNewWindow); 0276 q->addSeparator(); 0277 } 0278 addGroup(KonqPopupMenu::TabHandlingActions); // includes a separator at the end 0279 0280 if (m_popupFlags & KonqPopupMenu::ShowUrlOperations) { 0281 if (!currentDir && sReading) { 0282 if (sDeleting) { 0283 addNamedAction("cut"); 0284 } 0285 addNamedAction("copy"); 0286 } 0287 0288 if (isDirectory && sWriting) { 0289 if (currentDir) { 0290 addNamedAction("paste"); 0291 } else { 0292 addNamedAction("pasteto"); 0293 } 0294 } 0295 } 0296 if (isCurrentTrash) { 0297 act = new QAction(m_parentWidget); 0298 m_ownActions.append(act); 0299 act->setIcon(QIcon::fromTheme(QStringLiteral("trash-empty"))); 0300 act->setText(i18n("&Empty Trash Bin")); 0301 KConfig trashConfig(QStringLiteral("trashrc"), KConfig::SimpleConfig); 0302 act->setEnabled(!trashConfig.group("Status").readEntry("Empty", true)); 0303 QObject::connect(act, &QAction::triggered, [this]() { 0304 slotPopupEmptyTrashBin(); 0305 }); 0306 q->addAction(act); 0307 } 0308 if (isCurrentTrash) { 0309 act = new QAction(m_parentWidget); 0310 m_ownActions.append(act); 0311 act->setIcon(QIcon::fromTheme(QStringLiteral("trash-empty"))); 0312 act->setText(i18n("&Configure Trash Bin")); 0313 QObject::connect(act, &QAction::triggered, [this]() { 0314 slotConfigTrashBin(); 0315 }); 0316 q->addAction(act); 0317 } 0318 0319 // This is used by KHTML, see khtml_popupmenu.rc (copy, selectAll, searchProvider etc.) 0320 // and by DolphinPart (rename, trash, delete) 0321 addGroup(KonqPopupMenu::EditActions); 0322 0323 if (m_popupFlags & KonqPopupMenu::ShowTextSelectionItems) { 0324 // OK, we have to stop here. 0325 0326 // Anything else that is provided by the part 0327 addGroup(KonqPopupMenu::CustomActions); 0328 return; 0329 } 0330 0331 if (!isCurrentTrash && !isIntoTrash && (m_popupFlags & KonqPopupMenu::ShowBookmark)) { 0332 QString caption; 0333 if (currentDir) { 0334 const bool httpPage = m_sViewURL.scheme().startsWith(QLatin1String("http"), Qt::CaseInsensitive); 0335 if (httpPage) { 0336 caption = i18n("&Bookmark This Page"); 0337 } else { 0338 caption = i18n("&Bookmark This Location"); 0339 } 0340 } else if (isDirectory) { 0341 caption = i18n("&Bookmark This Folder"); 0342 } else if (bIsLink) { 0343 caption = i18n("&Bookmark This Link"); 0344 } else { 0345 caption = i18n("&Bookmark This File"); 0346 } 0347 0348 act = new QAction(m_parentWidget); 0349 m_ownActions.append(act); 0350 act->setObjectName(QStringLiteral("bookmark_add")); // for unittest 0351 act->setIcon(QIcon::fromTheme(QStringLiteral("bookmark-new"))); 0352 act->setText(caption); 0353 QObject::connect(act, &QAction::triggered, [this]() { 0354 slotPopupAddToBookmark(); 0355 }); 0356 if (lstItems.count() > 1) { 0357 act->setEnabled(false); 0358 } 0359 if (KAuthorized::authorizeAction(QStringLiteral("bookmarks"))) { 0360 q->addAction(act); 0361 } 0362 if (bIsLink) { 0363 addGroup(KonqPopupMenu::LinkActions); // see khtml 0364 } 0365 } 0366 0367 // "Open With" actions 0368 0369 m_menuActions.setItemListProperties(m_popupItemProperties); 0370 0371 if (sReading) { 0372 const QStringList excludedEntries = { 0373 QStringLiteral("kfmclient"), 0374 QStringLiteral("kfmclient_dir"), 0375 QStringLiteral("kfmclient_html") 0376 }; 0377 m_menuActions.insertOpenWithActionsTo(nullptr, q, excludedEntries); 0378 0379 QList<QAction *> previewActions = m_actionGroups.value(KonqPopupMenu::PreviewActions); 0380 if (!previewActions.isEmpty()) { 0381 if (previewActions.count() == 1) { 0382 q->addAction(previewActions.first()); 0383 } else { 0384 QMenu *subMenu = new QMenu(i18n("Preview In"), q); 0385 subMenu->menuAction()->setObjectName(QStringLiteral("preview_submenu")); // for the unittest 0386 q->addMenu(subMenu); 0387 subMenu->addActions(previewActions); 0388 } 0389 } 0390 } 0391 0392 // Second block, builtin + user 0393 QList<QAction *> additionalActions; 0394 if (isDirectory && m_popupItemProperties.items().count() == 1) { 0395 QAction *openTerminalHere = new QAction(QIcon::fromTheme("utilities-terminal"), i18n("Open Terminal Here"), m_parentWidget); 0396 openTerminalHere->setObjectName("open-terminal-here"); 0397 QObject::connect(openTerminalHere, &QAction::triggered, q, [this]() { 0398 const QString localPath = m_popupItemProperties.urlList().constFirst().toLocalFile(); 0399 // 5.84 because the header wasn't usable in 5.83 0400 auto *job = new KTerminalLauncherJob(QString{}); 0401 job->setWorkingDirectory(localPath); 0402 job->start(); 0403 }); 0404 additionalActions << openTerminalHere; 0405 } 0406 m_menuActions.addActionsTo(q, KFileItemActions::MenuActionSource::Services, additionalActions); 0407 0408 q->addSeparator(); 0409 0410 // Use the Dolphin setting for showing the "Copy To" and "Move To" actions 0411 KSharedConfig::Ptr dolphin = KSharedConfig::openConfig(QStringLiteral("dolphinrc")); 0412 0413 // CopyTo/MoveTo menus 0414 if (m_popupFlags & KonqPopupMenu::ShowUrlOperations && 0415 KConfigGroup(dolphin, "General").readEntry("ShowCopyMoveMenu", false)) { 0416 0417 m_copyToMenu.setUrls(lstUrls); 0418 m_copyToMenu.setReadOnly(sMoving == false); 0419 m_copyToMenu.addActionsTo(q); 0420 q->addSeparator(); 0421 } 0422 0423 if (!isCurrentTrash && !isIntoTrash && sReading && (m_popupFlags & KonqPopupMenu::NoPlugins) == 0) { 0424 m_menuActions.addActionsTo(q, KFileItemActions::MenuActionSource::Plugins); 0425 } 0426 0427 if ((m_popupFlags & KonqPopupMenu::ShowProperties) && KPropertiesDialog::canDisplay(lstItems)) { 0428 act = new QAction(m_parentWidget); 0429 m_ownActions.append(act); 0430 act->setObjectName(QStringLiteral("properties")); // for unittest 0431 act->setText(i18n("&Properties")); 0432 QObject::connect(act, &QAction::triggered, [this]() { 0433 slotPopupProperties(); 0434 }); 0435 q->addAction(act); 0436 } 0437 0438 while (!q->actions().isEmpty() && 0439 q->actions().last()->isSeparator()) { 0440 delete q->actions().last(); 0441 } 0442 0443 // Anything else that is provided by the part 0444 addGroup(KonqPopupMenu::CustomActions); 0445 0446 QObject::connect(&m_menuActions, &KFileItemActions::openWithDialogAboutToBeShown, q, &KonqPopupMenu::openWithDialogAboutToBeShown); 0447 } 0448 0449 KonqPopupMenu::~KonqPopupMenu() 0450 { 0451 delete d; 0452 } 0453 0454 void KonqPopupMenu::setNewFileMenu(KNewFileMenu *newMenu) 0455 { 0456 d->m_pMenuNew = newMenu; 0457 } 0458 0459 void KonqPopupMenu::setBookmarkManager(KBookmarkManager *manager) 0460 { 0461 d->m_bookmarkManager = manager; 0462 } 0463 0464 void KonqPopupMenu::setActionGroups(const KonqPopupMenu::ActionGroupMap &actionGroups) 0465 { 0466 d->m_actionGroups = actionGroups; 0467 } 0468 0469 void KonqPopupMenu::setURLTitle(const QString &urlTitle) 0470 { 0471 d->m_urlTitle = urlTitle; 0472 } 0473 0474 void KonqPopupMenuPrivate::slotPopupNewView() 0475 { 0476 for (const QUrl &url: m_popupItemProperties.urlList()) { 0477 KIO::OpenUrlJob *job = new KIO::OpenUrlJob(url); 0478 job->setUiDelegate(KIO::createDefaultJobUiDelegate(KJobUiDelegate::AutoHandlingEnabled, m_parentWidget)); 0479 job->start(); 0480 } 0481 } 0482 0483 void KonqPopupMenuPrivate::slotPopupNewDir() 0484 { 0485 m_pMenuNew->createDirectory(); 0486 } 0487 0488 void KonqPopupMenuPrivate::slotPopupEmptyTrashBin() 0489 { 0490 KJobUiDelegate* baseUiDelegate = KIO::createDefaultJobUiDelegate(KJobUiDelegate::Flags{}, m_parentWidget); 0491 KIO::JobUiDelegate* uiDelegate = qobject_cast<KIO::JobUiDelegate*>(baseUiDelegate); 0492 uiDelegate->setWindow(m_parentWidget); 0493 if (uiDelegate && uiDelegate->askDeleteConfirmation(QList<QUrl>(), KIO::JobUiDelegate::EmptyTrash, KIO::JobUiDelegate::DefaultConfirmation)) { 0494 KIO::Job *job = KIO::emptyTrash(); 0495 KJobWidgets::setWindow(job, m_parentWidget); 0496 job->uiDelegate()->setAutoErrorHandlingEnabled(true); // or connect to the result signal 0497 } 0498 } 0499 0500 void KonqPopupMenuPrivate::slotConfigTrashBin() 0501 { 0502 KIO::CommandLauncherJob *job = new KIO::CommandLauncherJob(QStringLiteral("kcmshell5 kcmtrash")); 0503 job->setUiDelegate(new KDialogJobUiDelegate(KJobUiDelegate::AutoHandlingEnabled, m_parentWidget)); 0504 job->start(); 0505 } 0506 0507 void KonqPopupMenuPrivate::slotPopupRestoreTrashedItems() 0508 { 0509 KIO::RestoreJob *job = KIO::restoreFromTrash(m_popupItemProperties.urlList()); 0510 KJobWidgets::setWindow(job, m_parentWidget); 0511 job->uiDelegate()->setAutoErrorHandlingEnabled(true); 0512 } 0513 0514 void KonqPopupMenuPrivate::slotPopupAddToBookmark() 0515 { 0516 KBookmarkGroup root; 0517 if (m_popupItemProperties.urlList().count() == 1) { 0518 const QUrl url = m_popupItemProperties.urlList().first(); 0519 const QString title = m_urlTitle.isEmpty() ? url.toDisplayString() : m_urlTitle; 0520 KBookmarkDialog dlg(m_bookmarkManager, m_parentWidget); 0521 dlg.addBookmark(title, url, QString()); 0522 } else { 0523 root = m_bookmarkManager->root(); 0524 for(const QUrl &url: m_popupItemProperties.urlList()) { 0525 root.addBookmark(url.toDisplayString(), url, QString()); 0526 } 0527 m_bookmarkManager->emitChanged(root); 0528 } 0529 } 0530 0531 void KonqPopupMenuPrivate::slotPopupMimeType() 0532 { 0533 KMimeTypeEditor::editMimeType(m_popupItemProperties.mimeType(), m_parentWidget); 0534 } 0535 0536 void KonqPopupMenuPrivate::slotPopupProperties() 0537 { 0538 KPropertiesDialog::showDialog(m_popupItemProperties.items(), m_parentWidget, false); 0539 } 0540 0541 void KonqPopupMenuPrivate::addGroup(KonqPopupMenu::ActionGroup group) 0542 { 0543 q->addActions(m_actionGroups.value(group)); 0544 } 0545 0546 void KonqPopupMenuPrivate::slotShowOriginalFile() 0547 { 0548 const KFileItem item = m_popupItemProperties.items().first(); 0549 QUrl destUrl = QUrl::fromLocalFile(item.linkDest()); 0550 0551 if (!destUrl.isValid()) { 0552 return; 0553 } 0554 0555 // Now destUrl points to the target file, let's go up to parent dir 0556 destUrl = destUrl.adjusted(QUrl::RemoveFilename); 0557 0558 KIO::OpenUrlJob *job = new KIO::OpenUrlJob(destUrl, QStringLiteral("inode/directory")); 0559 job->setUiDelegate(KIO::createDefaultJobUiDelegate(KJobUiDelegate::AutoHandlingEnabled, m_parentWidget)); 0560 job->start(); 0561 }