File indexing completed on 2024-04-28 17:06:23
0001 /* 0002 SPDX-FileCopyrightText: 2003 Shie Erlich <erlich@users.sourceforge.net> 0003 SPDX-FileCopyrightText: 2003 Rafi Yanai <yanai@users.sourceforge.net> 0004 SPDX-FileCopyrightText: 2004-2022 Krusader Krew <https://krusader.org> 0005 0006 SPDX-License-Identifier: GPL-2.0-or-later 0007 */ 0008 0009 #include "panelcontextmenu.h" 0010 0011 // QtGui 0012 #include <QPixmap> 0013 0014 #include <KCoreAddons/KPluginMetaData> 0015 #include <KCoreAddons/KProcess> 0016 #include <KI18n/KLocalizedString> 0017 #include <KIOCore/KFileItem> 0018 #include <KIOCore/KFileItemListProperties> 0019 #include <KIOWidgets/KAbstractFileItemActionPlugin> 0020 #include <KIOWidgets/KRun> 0021 #include <KService/KApplicationTrader> 0022 #include <KWidgetsAddons/KMessageBox> 0023 #include <KXmlGui/KActionCollection> 0024 #include <kio_version.h> 0025 #include <kservice_version.h> 0026 0027 #include "../Archive/krarchandler.h" 0028 #include "../FileSystem/fileitem.h" 0029 #include "../FileSystem/filesystem.h" 0030 #include "../FileSystem/krtrashhandler.h" 0031 #include "../MountMan/kmountman.h" 0032 #include "../UserAction/useractionpopupmenu.h" 0033 #include "../defaults.h" 0034 #include "../icon.h" 0035 #include "../krservices.h" 0036 #include "../krslots.h" 0037 #include "../krusader.h" 0038 #include "../krusaderview.h" 0039 #include "../panelmanager.h" 0040 #include "PanelView/krview.h" 0041 #include "PanelView/krviewitem.h" 0042 #include "krpreviewpopup.h" 0043 #include "listpanel.h" 0044 #include "listpanelactions.h" 0045 #include "panelfunc.h" 0046 0047 PanelContextMenu *PanelContextMenu::run(const QPoint &pos, KrPanel *panel) 0048 { 0049 auto menu = new PanelContextMenu(panel); 0050 QAction *res = menu->exec(pos); 0051 int result = res && res->data().canConvert<int>() ? res->data().toInt() : -1; 0052 menu->performAction(result); 0053 return menu; 0054 } 0055 0056 /** 0057 * Copied from dolphin/src/dolphincontextmenu.cpp and modified to add only compress and extract submenus. 0058 */ 0059 void PanelContextMenu::addCompressAndExtractPluginActions() 0060 { 0061 KFileItemListProperties props(_items); 0062 0063 QVector<KPluginMetaData> jsonPlugins = KPluginLoader::findPlugins("kf5/kfileitemaction", [=](const KPluginMetaData &metaData) { 0064 return metaData.pluginId() == "compressfileitemaction" || metaData.pluginId() == "extractfileitemaction"; 0065 }); 0066 0067 foreach (const KPluginMetaData &jsonMetadata, jsonPlugins) { 0068 auto *abstractPlugin = KPluginLoader(jsonMetadata.fileName()).factory()->create<KAbstractFileItemActionPlugin>(); 0069 if (abstractPlugin) { 0070 abstractPlugin->setParent(this); 0071 addActions(abstractPlugin->actions(props, this)); 0072 } 0073 } 0074 } 0075 0076 PanelContextMenu::PanelContextMenu(KrPanel *krPanel, QWidget *parent) 0077 : QMenu(parent) 0078 , panel(krPanel) 0079 { 0080 // selected file names 0081 const QStringList fileNames = panel->gui->getSelectedNames(); 0082 0083 // file items 0084 QList<FileItem *> files; 0085 for (const QString &fileName : fileNames) { 0086 files.append(panel->func->files()->getFileItem(fileName)); 0087 } 0088 0089 // KFileItems 0090 bool allFilesAreDirs = true; 0091 for (FileItem *file : files) { 0092 _items.append(KFileItem(file->getUrl(), file->getMime(), file->getMode())); 0093 allFilesAreDirs &= file->isDir(); 0094 } 0095 0096 if (files.empty()) { 0097 addCreateNewMenu(); 0098 addSeparator(); 0099 addEmptyMenuEntries(); 0100 return; 0101 } 0102 0103 const bool multipleSelections = files.size() > 1; 0104 0105 QSet<QString> protocols; 0106 for (FileItem *file : files) { 0107 protocols.insert(file->getUrl().scheme()); 0108 } 0109 const bool inTrash = protocols.contains("trash"); 0110 const bool trashOnly = inTrash && protocols.count() == 1; 0111 0112 FileItem *file = files.first(); 0113 0114 // ------------ the OPEN/BROWSE option - open preferred service 0115 0116 // open/run - if not only multiple dirs are selected 0117 if (!(multipleSelections && allFilesAreDirs)) { 0118 QAction *openAction = new QAction(this); 0119 openAction->setData(QVariant(OPEN_ID)); 0120 if (multipleSelections) { 0121 openAction->setText(i18n("Open/Run Files")); 0122 } else { 0123 openAction->setText(file->isExecutable() && !file->isDir() ? i18n("Run") : i18n("Open")); 0124 const KrViewItemList viewItems = panel->view->getSelectedKrViewItems(); 0125 openAction->setIcon(viewItems.first()->icon()); 0126 } 0127 addAction(openAction); 0128 } 0129 0130 // open in a new tab (if only folder(s) are selected) 0131 if (allFilesAreDirs) { 0132 QAction *openTab = addAction(multipleSelections ? i18n("Open in New Tabs") : i18n("Open in New Tab")); 0133 openTab->setData(QVariant(OPEN_TAB_ID)); 0134 openTab->setIcon(Icon("tab-new")); 0135 } 0136 0137 // browse archive - if one file is selected and the file can be browsed as archive... 0138 if (!multipleSelections 0139 && !panel->func->browsableArchivePath(file->getName()).isEmpty() 0140 // ...but user disabled archive browsing... 0141 && (!KConfigGroup(krConfig, "Archives").readEntry("ArchivesAsDirectories", _ArchivesAsDirectories) 0142 // ...or the file is not a standard archive (e.g. odt, docx, etc.)... 0143 || !KrArcHandler::arcSupported(file->getMime()))) { 0144 // ...it will not be browsed as a directory by default, but add an option for it 0145 QAction *browseAct = addAction(i18n("Browse Archive")); 0146 browseAct->setData(QVariant(BROWSE_ID)); 0147 browseAct->setIcon(Icon("archive-insert-directory")); 0148 } 0149 0150 // ------------- Preview - local filesystem only ? 0151 if (panel->func->files()->isLocal()) { 0152 // create the preview popup 0153 KrPreviewPopup preview; 0154 preview.setUrls(panel->func->files()->getUrls(fileNames)); 0155 QAction *previewAction = addMenu(&preview); 0156 previewAction->setData(QVariant(PREVIEW_ID)); 0157 previewAction->setText(i18n("Preview")); 0158 previewAction->setIcon(Icon("document-print-preview")); 0159 } 0160 0161 // -------------- Open with: try to find-out which apps can open the file 0162 QSet<QString> uniqueMimeTypes; 0163 for (FileItem *file : files) 0164 uniqueMimeTypes.insert(file->getMime()); 0165 const QStringList mimeTypes = uniqueMimeTypes.values(); 0166 0167 #if KSERVICE_VERSION >= QT_VERSION_CHECK(5, 83, 0) 0168 offers = mimeTypes.count() == 1 ? KApplicationTrader::queryByMimeType(mimeTypes.first()) : KFileItemActions::associatedApplications(mimeTypes); 0169 #elif KSERVICE_VERSION >= QT_VERSION_CHECK(5, 68, 0) 0170 offers = mimeTypes.count() == 1 ? KApplicationTrader::queryByMimeType(mimeTypes.first()) : KFileItemActions::associatedApplications(mimeTypes, QString()); 0171 #else 0172 offers = mimeTypes.count() == 1 ? KMimeTypeTrader::self()->query(mimeTypes.first()) : KFileItemActions::associatedApplications(mimeTypes, QString()); 0173 #endif 0174 0175 if (!offers.isEmpty()) { 0176 auto *openWithMenu = new QMenu(this); 0177 for (int i = 0; i < offers.count(); ++i) { 0178 QExplicitlySharedDataPointer<KService> service = offers[i]; 0179 if (service->isValid() && service->isApplication()) { 0180 openWithMenu->addAction(Icon(service->icon()), service->name())->setData(QVariant(SERVICE_LIST_ID + i)); 0181 } 0182 } 0183 openWithMenu->addSeparator(); 0184 if (!multipleSelections && file->isDir()) { 0185 openWithMenu->addAction(Icon("utilities-terminal"), i18n("Terminal"))->setData(QVariant(OPEN_TERM_ID)); 0186 } 0187 openWithMenu->addAction(i18n("Other..."))->setData(QVariant(CHOOSE_ID)); 0188 QAction *openWithAction = addMenu(openWithMenu); 0189 openWithAction->setText(i18n("Open With")); 0190 openWithAction->setIcon(Icon("document-open")); 0191 } 0192 0193 addSeparator(); 0194 0195 // --------------- user actions 0196 QAction *userAction = new UserActionPopupMenu(file->getUrl(), this); 0197 userAction->setText(i18n("User Actions")); 0198 addAction(userAction); 0199 0200 // --------------- compress/extract actions 0201 // workaround for Bug 372999: application freezes very long time if many files are selected 0202 if (_items.length() < 1000) 0203 // add compress and extract plugins (if available) 0204 addCompressAndExtractPluginActions(); 0205 0206 // --------------- KDE file item actions 0207 // NOTE: design and usability problem here. Services disabled in kservicemenurc settings won't 0208 // be added to the menu. But Krusader does not provide a way do change these settings (only 0209 // Dolphin does). 0210 auto *fileItemActions = new KFileItemActions(this); 0211 fileItemActions->setItemListProperties(KFileItemListProperties(_items)); 0212 fileItemActions->setParentWidget(MAIN_VIEW); 0213 #if KIO_VERSION >= QT_VERSION_CHECK(5, 79, 0) 0214 fileItemActions->addActionsTo(this); 0215 #else 0216 fileItemActions->addServiceActionsTo(this); 0217 #endif 0218 0219 addSeparator(); 0220 0221 // ------------- 'create new' submenu 0222 addCreateNewMenu(); 0223 addSeparator(); 0224 0225 // ---------- COPY 0226 addAction(panel->gui->actions()->actCopyF5); 0227 // ------- MOVE 0228 addAction(panel->gui->actions()->actMoveF6); 0229 // ------- RENAME - only one file 0230 if (!multipleSelections && !inTrash) { 0231 addAction(panel->gui->actions()->actRenameF2); 0232 } 0233 0234 // -------- MOVE TO TRASH 0235 if (KConfigGroup(krConfig, "General").readEntry("Move To Trash", _MoveToTrash) && panel->func->files()->canMoveToTrash(fileNames)) { 0236 addAction(Icon("user-trash"), i18n("Move to Trash"))->setData(QVariant(TRASH_ID)); 0237 } 0238 // -------- DELETE 0239 addAction(Icon("edit-delete"), i18n("Delete"))->setData(QVariant(DELETE_ID)); 0240 // -------- SHRED - only one file 0241 /* if ( panel->func->files() ->getType() == filesystem:fileSystemM_NORMAL && 0242 !fileitem->isDir() && !multipleSelections ) 0243 addAction( i18n( "Shred" ) )->setData( QVariant( SHRED_ID ) );*/ 0244 0245 // ---------- link handling 0246 // create new shortcut or redirect links - only on local directories: 0247 if (panel->func->files()->isLocal()) { 0248 addSeparator(); 0249 auto *linkMenu = new QMenu(this); 0250 linkMenu->addAction(i18n("New Symlink..."))->setData(QVariant(NEW_SYMLINK_ID)); 0251 linkMenu->addAction(i18n("New Hardlink..."))->setData(QVariant(NEW_LINK_ID)); 0252 if (file->isSymLink()) { 0253 linkMenu->addAction(i18n("Redirect Link..."))->setData(QVariant(REDIRECT_LINK_ID)); 0254 } 0255 QAction *linkAction = addMenu(linkMenu); 0256 linkAction->setText(i18n("Link Handling")); 0257 linkAction->setIcon(Icon("insert-link")); 0258 } 0259 addSeparator(); 0260 0261 // ---------- calculate space 0262 if (panel->func->files()->isLocal() && (file->isDir() || multipleSelections)) 0263 addAction(panel->gui->actions()->actCalculate); 0264 0265 // ---------- mount/umount/eject 0266 if (panel->func->files()->isLocal() && file->isDir() && !multipleSelections) { 0267 const QString selectedDirectoryPath = file->getUrl().path(); 0268 if (krMtMan.getStatus(selectedDirectoryPath) == KMountMan::MOUNTED) 0269 addAction(i18n("Unmount"))->setData(QVariant(UNMOUNT_ID)); 0270 else if (krMtMan.getStatus(selectedDirectoryPath) == KMountMan::NOT_MOUNTED) 0271 addAction(i18n("Mount"))->setData(QVariant(MOUNT_ID)); 0272 if (krMtMan.ejectable(selectedDirectoryPath)) 0273 addAction(i18n("Eject"))->setData(QVariant(EJECT_ID)); 0274 } 0275 0276 // --------- send by mail 0277 if (KrServices::supportedTools().contains("MAIL") && !file->isDir()) { 0278 addAction(Icon("mail-send"), i18n("Send by Email"))->setData(QVariant(SEND_BY_EMAIL_ID)); 0279 } 0280 0281 // --------- empty trash 0282 if (trashOnly) { 0283 addAction(i18n("Restore"))->setData(QVariant(RESTORE_TRASHED_FILE_ID)); 0284 addAction(i18n("Empty Trash"))->setData(QVariant(EMPTY_TRASH_ID)); 0285 } 0286 0287 #ifdef SYNCHRONIZER_ENABLED 0288 // --------- synchronize 0289 if (panel->view->numSelected()) { 0290 addAction(i18n("Synchronize Selected Files..."))->setData(QVariant(SYNC_SELECTED_ID)); 0291 } 0292 #endif 0293 0294 // --------- copy/paste 0295 addSeparator(); 0296 addAction(panel->gui->actions()->actCut); 0297 addAction(panel->gui->actions()->actCopy); 0298 addAction(panel->gui->actions()->actPaste); 0299 addSeparator(); 0300 0301 // --------- properties 0302 addAction(panel->gui->actions()->actProperties); 0303 } 0304 0305 void PanelContextMenu::addEmptyMenuEntries() 0306 { 0307 addAction(panel->gui->actions()->actPaste); 0308 } 0309 0310 void PanelContextMenu::addCreateNewMenu() 0311 { 0312 auto *createNewMenu = new QMenu(this); 0313 createNewMenu->addAction(panel->gui->actions()->actNewFolderF7); 0314 createNewMenu->addAction(panel->gui->actions()->actNewFileShiftF4); 0315 0316 QAction *newMenuAction = addMenu(createNewMenu); 0317 newMenuAction->setText(i18n("Create New")); 0318 newMenuAction->setIcon(Icon("document-new")); 0319 } 0320 0321 void PanelContextMenu::performAction(int id) 0322 { 0323 const QUrl singleURL = _items.isEmpty() ? QUrl() : _items.first().url(); 0324 0325 switch (id) { 0326 case -1: // the user clicked outside of the menu 0327 return; 0328 case OPEN_TAB_ID: 0329 for (const KFileItem &fileItem : _items) { 0330 panel->manager()->duplicateTab(fileItem.url(), panel); 0331 } 0332 break; 0333 case OPEN_ID: 0334 for (const KFileItem &fileItem : _items) { 0335 // do not open dirs if multiple files are selected 0336 if (_items.size() == 1 || !fileItem.isDir()) { 0337 panel->func->execute(fileItem.name()); 0338 } 0339 } 0340 break; 0341 case BROWSE_ID: 0342 panel->func->goInside(singleURL.fileName()); 0343 break; 0344 case TRASH_ID: 0345 panel->func->deleteFiles(true); 0346 break; 0347 case DELETE_ID: 0348 panel->func->deleteFiles(false); 0349 break; 0350 case EJECT_ID: 0351 krMtMan.eject(singleURL.adjusted(QUrl::StripTrailingSlash).path()); 0352 break; 0353 case CHOOSE_ID: // open-with dialog 0354 panel->func->displayOpenWithDialog(_items.urlList()); 0355 break; 0356 case MOUNT_ID: 0357 krMtMan.mount(singleURL.adjusted(QUrl::StripTrailingSlash).path()); 0358 break; 0359 case NEW_LINK_ID: 0360 panel->func->krlink(false); 0361 break; 0362 case NEW_SYMLINK_ID: 0363 panel->func->krlink(true); 0364 break; 0365 case REDIRECT_LINK_ID: 0366 panel->func->redirectLink(); 0367 break; 0368 case EMPTY_TRASH_ID: 0369 KrTrashHandler::emptyTrash(); 0370 break; 0371 case RESTORE_TRASHED_FILE_ID: 0372 KrTrashHandler::restoreTrashedFiles(_items.urlList()); 0373 break; 0374 case UNMOUNT_ID: 0375 krMtMan.unmount(singleURL.adjusted(QUrl::StripTrailingSlash).path()); 0376 break; 0377 case SEND_BY_EMAIL_ID: { 0378 SLOTS->sendFileByEmail(_items.urlList()); 0379 break; 0380 } 0381 #ifdef SYNCHRONIZER_ENABLED 0382 case SYNC_SELECTED_ID: { 0383 QStringList selectedNames; 0384 for (const KFileItem &item : _items) { 0385 selectedNames.append(item.name()); 0386 } 0387 const KrViewItemList otherItems = panel->otherPanel()->view->getSelectedKrViewItems(); 0388 for (KrViewItem *otherItem : otherItems) { 0389 const QString &name = otherItem->name(); 0390 if (!selectedNames.contains(name)) { 0391 selectedNames.append(name); 0392 } 0393 } 0394 SLOTS->slotSynchronizeDirs(selectedNames); 0395 } break; 0396 #endif 0397 case OPEN_TERM_ID: 0398 SLOTS->runTerminal(singleURL.path()); 0399 break; 0400 } 0401 0402 // check if something from the open-with-offered-services was selected 0403 if (id >= SERVICE_LIST_ID) { 0404 const QStringList names = panel->gui->getSelectedNames(); 0405 panel->func->runService(*(offers[id - SERVICE_LIST_ID]), panel->func->files()->getUrls(names)); 0406 } 0407 }