File indexing completed on 2024-04-14 05:38:40
0001 /* 0002 * SPDX-FileCopyrightText: 2006 Peter Penz (peter.penz@gmx.at) and Cvetoslav Ludmiloff 0003 * 0004 * SPDX-License-Identifier: GPL-2.0-or-later 0005 */ 0006 0007 #include "dolphincontextmenu.h" 0008 0009 #include "dolphin_contextmenusettings.h" 0010 #include "dolphinmainwindow.h" 0011 #include "dolphinnewfilemenu.h" 0012 #include "dolphinplacesmodelsingleton.h" 0013 #include "dolphinremoveaction.h" 0014 #include "dolphinviewcontainer.h" 0015 #include "global.h" 0016 #include "trash/dolphintrash.h" 0017 #include "views/dolphinview.h" 0018 0019 #include <KActionCollection> 0020 #include <KFileItemListProperties> 0021 #include <KHamburgerMenu> 0022 #include <KIO/EmptyTrashJob> 0023 #include <KIO/JobUiDelegate> 0024 #include <KIO/Paste> 0025 #include <KIO/RestoreJob> 0026 #include <KJobWidgets> 0027 #include <KLocalizedString> 0028 #include <KNewFileMenu> 0029 #include <KStandardAction> 0030 0031 #include <QApplication> 0032 #include <QClipboard> 0033 #include <QKeyEvent> 0034 0035 DolphinContextMenu::DolphinContextMenu(DolphinMainWindow *parent, 0036 const KFileItem &fileInfo, 0037 const KFileItemList &selectedItems, 0038 const QUrl &baseUrl, 0039 KFileItemActions *fileItemActions) 0040 : QMenu(parent) 0041 , m_mainWindow(parent) 0042 , m_fileInfo(fileInfo) 0043 , m_baseUrl(baseUrl) 0044 , m_baseFileItem(nullptr) 0045 , m_selectedItems(selectedItems) 0046 , m_selectedItemsProperties(nullptr) 0047 , m_context(NoContext) 0048 , m_copyToMenu(parent) 0049 , m_removeAction(nullptr) 0050 , m_fileItemActions(fileItemActions) 0051 { 0052 QApplication::instance()->installEventFilter(this); 0053 0054 addAllActions(); 0055 } 0056 0057 DolphinContextMenu::~DolphinContextMenu() 0058 { 0059 delete m_baseFileItem; 0060 m_baseFileItem = nullptr; 0061 delete m_selectedItemsProperties; 0062 m_selectedItemsProperties = nullptr; 0063 } 0064 0065 void DolphinContextMenu::addAllActions() 0066 { 0067 static_cast<KHamburgerMenu *>(m_mainWindow->actionCollection()->action(QStringLiteral("hamburger_menu")))->addToMenu(this); 0068 0069 // get the context information 0070 const auto scheme = m_baseUrl.scheme(); 0071 if (scheme == QLatin1String("trash")) { 0072 m_context |= TrashContext; 0073 } else if (scheme.contains(QLatin1String("search"))) { 0074 m_context |= SearchContext; 0075 } else if (scheme.contains(QLatin1String("timeline"))) { 0076 m_context |= TimelineContext; 0077 } else if (scheme == QStringLiteral("recentlyused")) { 0078 m_context |= RecentlyUsedContext; 0079 } 0080 0081 if (!m_fileInfo.isNull() && !m_selectedItems.isEmpty()) { 0082 m_context |= ItemContext; 0083 // TODO: handle other use cases like devices + desktop files 0084 } 0085 0086 // open the corresponding popup for the context 0087 if (m_context & TrashContext) { 0088 if (m_context & ItemContext) { 0089 addTrashItemContextMenu(); 0090 } else { 0091 addTrashContextMenu(); 0092 } 0093 } else if (m_context & ItemContext) { 0094 addItemContextMenu(); 0095 } else { 0096 addViewportContextMenu(); 0097 } 0098 } 0099 0100 bool DolphinContextMenu::eventFilter(QObject *object, QEvent *event) 0101 { 0102 Q_UNUSED(object) 0103 0104 if (event->type() == QEvent::KeyPress || event->type() == QEvent::KeyRelease) { 0105 QKeyEvent *keyEvent = static_cast<QKeyEvent *>(event); 0106 0107 if (m_removeAction && keyEvent->key() == Qt::Key_Shift) { 0108 if (event->type() == QEvent::KeyPress) { 0109 m_removeAction->update(DolphinRemoveAction::ShiftState::Pressed); 0110 } else { 0111 m_removeAction->update(DolphinRemoveAction::ShiftState::Released); 0112 } 0113 } 0114 } 0115 0116 return false; 0117 } 0118 0119 void DolphinContextMenu::addTrashContextMenu() 0120 { 0121 Q_ASSERT(m_context & TrashContext); 0122 0123 QAction *emptyTrashAction = addAction(QIcon::fromTheme(QStringLiteral("trash-empty")), i18nc("@action:inmenu", "Empty Trash"), [this]() { 0124 Trash::empty(m_mainWindow); 0125 }); 0126 emptyTrashAction->setEnabled(!Trash::isEmpty()); 0127 0128 QAction *propertiesAction = m_mainWindow->actionCollection()->action(QStringLiteral("properties")); 0129 addAction(propertiesAction); 0130 } 0131 0132 void DolphinContextMenu::addTrashItemContextMenu() 0133 { 0134 Q_ASSERT(m_context & TrashContext); 0135 Q_ASSERT(m_context & ItemContext); 0136 0137 addAction(QIcon::fromTheme("restoration"), i18nc("@action:inmenu", "Restore"), [this]() { 0138 QList<QUrl> selectedUrls; 0139 selectedUrls.reserve(m_selectedItems.count()); 0140 for (const KFileItem &item : std::as_const(m_selectedItems)) { 0141 selectedUrls.append(item.url()); 0142 } 0143 0144 KIO::RestoreJob *job = KIO::restoreFromTrash(selectedUrls); 0145 KJobWidgets::setWindow(job, m_mainWindow); 0146 job->uiDelegate()->setAutoErrorHandlingEnabled(true); 0147 }); 0148 0149 QAction *deleteAction = m_mainWindow->actionCollection()->action(KStandardAction::name(KStandardAction::DeleteFile)); 0150 addAction(deleteAction); 0151 0152 QAction *propertiesAction = m_mainWindow->actionCollection()->action(QStringLiteral("properties")); 0153 addAction(propertiesAction); 0154 } 0155 0156 void DolphinContextMenu::addDirectoryItemContextMenu() 0157 { 0158 // insert 'Open in new window' and 'Open in new tab' entries 0159 const KFileItemListProperties &selectedItemsProps = selectedItemsProperties(); 0160 if (ContextMenuSettings::showOpenInNewTab()) { 0161 addAction(m_mainWindow->actionCollection()->action(QStringLiteral("open_in_new_tab"))); 0162 } 0163 if (ContextMenuSettings::showOpenInNewWindow()) { 0164 addAction(m_mainWindow->actionCollection()->action(QStringLiteral("open_in_new_window"))); 0165 } 0166 0167 if (ContextMenuSettings::showOpenInSplitView()) { 0168 addAction(m_mainWindow->actionCollection()->action(QStringLiteral("open_in_split_view"))); 0169 } 0170 0171 // Insert 'Open With' entries 0172 addOpenWithActions(); 0173 0174 // set up 'Create New' menu 0175 DolphinNewFileMenu *newFileMenu = new DolphinNewFileMenu(m_mainWindow->actionCollection()->action(QStringLiteral("create_dir")), m_mainWindow); 0176 newFileMenu->checkUpToDate(); 0177 newFileMenu->setWorkingDirectory(m_fileInfo.url()); 0178 newFileMenu->setEnabled(selectedItemsProps.supportsWriting()); 0179 connect(newFileMenu, &DolphinNewFileMenu::fileCreated, newFileMenu, &DolphinNewFileMenu::deleteLater); 0180 connect(newFileMenu, &DolphinNewFileMenu::directoryCreated, newFileMenu, &DolphinNewFileMenu::deleteLater); 0181 0182 QMenu *menu = newFileMenu->menu(); 0183 menu->setTitle(i18nc("@title:menu Create new folder, file, link, etc.", "Create New")); 0184 menu->setIcon(QIcon::fromTheme(QStringLiteral("list-add"))); 0185 addMenu(menu); 0186 0187 addSeparator(); 0188 } 0189 0190 void DolphinContextMenu::addOpenParentFolderActions() 0191 { 0192 addAction(QIcon::fromTheme(QStringLiteral("document-open-folder")), i18nc("@action:inmenu", "Open Path"), [this]() { 0193 const QUrl url = m_fileInfo.targetUrl(); 0194 const QUrl parentUrl = KIO::upUrl(url); 0195 m_mainWindow->changeUrl(parentUrl); 0196 m_mainWindow->activeViewContainer()->view()->markUrlsAsSelected({url}); 0197 m_mainWindow->activeViewContainer()->view()->markUrlAsCurrent(url); 0198 }); 0199 0200 addAction(QIcon::fromTheme(QStringLiteral("tab-new")), i18nc("@action:inmenu", "Open Path in New Tab"), [this]() { 0201 m_mainWindow->openNewTab(KIO::upUrl(m_fileInfo.targetUrl())); 0202 }); 0203 0204 addAction(QIcon::fromTheme(QStringLiteral("window-new")), i18nc("@action:inmenu", "Open Path in New Window"), [this]() { 0205 Dolphin::openNewWindow({m_fileInfo.targetUrl()}, m_mainWindow, Dolphin::OpenNewWindowFlag::Select); 0206 }); 0207 } 0208 0209 void DolphinContextMenu::addItemContextMenu() 0210 { 0211 Q_ASSERT(!m_fileInfo.isNull()); 0212 0213 const KFileItemListProperties &selectedItemsProps = selectedItemsProperties(); 0214 0215 m_fileItemActions->setItemListProperties(selectedItemsProps); 0216 0217 if (m_selectedItems.count() == 1) { 0218 // single files 0219 if (m_fileInfo.isDir()) { 0220 addDirectoryItemContextMenu(); 0221 } else if (m_context & TimelineContext || m_context & SearchContext || m_context & RecentlyUsedContext) { 0222 addOpenWithActions(); 0223 0224 addOpenParentFolderActions(); 0225 0226 addSeparator(); 0227 } else { 0228 // Insert 'Open With" entries 0229 addOpenWithActions(); 0230 } 0231 if (m_fileInfo.isLink()) { 0232 addAction(m_mainWindow->actionCollection()->action(QStringLiteral("show_target"))); 0233 addSeparator(); 0234 } 0235 } else { 0236 // multiple files 0237 bool selectionHasOnlyDirs = true; 0238 for (const auto &item : std::as_const(m_selectedItems)) { 0239 const QUrl &url = DolphinView::openItemAsFolderUrl(item); 0240 if (url.isEmpty()) { 0241 selectionHasOnlyDirs = false; 0242 break; 0243 } 0244 } 0245 0246 if (selectionHasOnlyDirs && ContextMenuSettings::showOpenInNewTab()) { 0247 // insert 'Open in new tab' entry 0248 addAction(m_mainWindow->actionCollection()->action(QStringLiteral("open_in_new_tabs"))); 0249 } 0250 // Insert 'Open With" entries 0251 addOpenWithActions(); 0252 } 0253 0254 insertDefaultItemActions(selectedItemsProps); 0255 0256 addAdditionalActions(selectedItemsProps); 0257 0258 // insert 'Copy To' and 'Move To' sub menus 0259 if (ContextMenuSettings::showCopyMoveMenu()) { 0260 m_copyToMenu.setUrls(m_selectedItems.urlList()); 0261 m_copyToMenu.setReadOnly(!selectedItemsProps.supportsWriting()); 0262 m_copyToMenu.setAutoErrorHandlingEnabled(true); 0263 m_copyToMenu.addActionsTo(this); 0264 } 0265 0266 if (m_mainWindow->isSplitViewEnabledInCurrentTab()) { 0267 if (ContextMenuSettings::showCopyToOtherSplitView()) { 0268 addAction(m_mainWindow->actionCollection()->action(QStringLiteral("copy_to_inactive_split_view"))); 0269 } 0270 0271 if (ContextMenuSettings::showMoveToOtherSplitView()) { 0272 addAction(m_mainWindow->actionCollection()->action(QStringLiteral("move_to_inactive_split_view"))); 0273 } 0274 } 0275 0276 // insert 'Properties...' entry 0277 addSeparator(); 0278 QAction *propertiesAction = m_mainWindow->actionCollection()->action(QStringLiteral("properties")); 0279 addAction(propertiesAction); 0280 } 0281 0282 void DolphinContextMenu::addViewportContextMenu() 0283 { 0284 const KFileItemListProperties baseUrlProperties(KFileItemList() << baseFileItem()); 0285 m_fileItemActions->setItemListProperties(baseUrlProperties); 0286 0287 // Set up and insert 'Create New' menu 0288 KNewFileMenu *newFileMenu = m_mainWindow->newFileMenu(); 0289 newFileMenu->checkUpToDate(); 0290 newFileMenu->setWorkingDirectory(m_baseUrl); 0291 addMenu(newFileMenu->menu()); 0292 0293 // Show "open with" menu items even if the dir is empty, because there are legitimate 0294 // use cases for this, such as opening an empty dir in Kate or VSCode or something 0295 addOpenWithActions(); 0296 0297 QAction *pasteAction = createPasteAction(); 0298 if (pasteAction) { 0299 addAction(pasteAction); 0300 } 0301 0302 // Insert 'Add to Places' entry if it's not already in the places panel 0303 if (ContextMenuSettings::showAddToPlaces() && !placeExists(m_mainWindow->activeViewContainer()->url())) { 0304 addAction(m_mainWindow->actionCollection()->action(QStringLiteral("add_to_places"))); 0305 } 0306 addSeparator(); 0307 0308 // Insert 'Sort By' and 'View Mode' 0309 if (ContextMenuSettings::showSortBy()) { 0310 addAction(m_mainWindow->actionCollection()->action(QStringLiteral("sort"))); 0311 } 0312 if (ContextMenuSettings::showViewMode()) { 0313 addAction(m_mainWindow->actionCollection()->action(QStringLiteral("view_mode"))); 0314 } 0315 if (ContextMenuSettings::showSortBy() || ContextMenuSettings::showViewMode()) { 0316 addSeparator(); 0317 } 0318 0319 addAdditionalActions(baseUrlProperties); 0320 0321 addSeparator(); 0322 0323 QAction *propertiesAction = m_mainWindow->actionCollection()->action(QStringLiteral("properties")); 0324 addAction(propertiesAction); 0325 } 0326 0327 void DolphinContextMenu::insertDefaultItemActions(const KFileItemListProperties &properties) 0328 { 0329 const KActionCollection *collection = m_mainWindow->actionCollection(); 0330 0331 // Insert 'Cut', 'Copy', 'Copy Location' and 'Paste' 0332 addAction(collection->action(KStandardAction::name(KStandardAction::Cut))); 0333 addAction(collection->action(KStandardAction::name(KStandardAction::Copy))); 0334 if (ContextMenuSettings::showCopyLocation()) { 0335 QAction *copyPathAction = collection->action(QString("copy_location")); 0336 copyPathAction->setEnabled(m_selectedItems.size() == 1); 0337 addAction(copyPathAction); 0338 } 0339 QAction *pasteAction = createPasteAction(); 0340 if (pasteAction) { 0341 addAction(pasteAction); 0342 } 0343 0344 // Insert 'Duplicate Here' 0345 if (ContextMenuSettings::showDuplicateHere()) { 0346 addAction(m_mainWindow->actionCollection()->action(QStringLiteral("duplicate"))); 0347 } 0348 0349 // Insert 'Rename' 0350 addAction(collection->action(KStandardAction::name(KStandardAction::RenameFile))); 0351 0352 // Insert 'Add to Places' entry if appropriate 0353 if (ContextMenuSettings::showAddToPlaces() && m_selectedItems.count() == 1 && m_fileInfo.isDir() && !placeExists(m_fileInfo.url())) { 0354 addAction(m_mainWindow->actionCollection()->action(QStringLiteral("add_to_places"))); 0355 } 0356 0357 addSeparator(); 0358 0359 // Insert 'Move to Trash' and/or 'Delete' 0360 const bool showDeleteAction = (KSharedConfig::openConfig()->group(QStringLiteral("KDE")).readEntry("ShowDeleteCommand", false) || !properties.isLocal()); 0361 const bool showMoveToTrashAction = (properties.isLocal() && properties.supportsMoving()); 0362 0363 if (showDeleteAction && showMoveToTrashAction) { 0364 delete m_removeAction; 0365 m_removeAction = nullptr; 0366 addAction(m_mainWindow->actionCollection()->action(KStandardAction::name(KStandardAction::MoveToTrash))); 0367 addAction(m_mainWindow->actionCollection()->action(KStandardAction::name(KStandardAction::DeleteFile))); 0368 } else if (showDeleteAction && !showMoveToTrashAction) { 0369 addAction(m_mainWindow->actionCollection()->action(KStandardAction::name(KStandardAction::DeleteFile))); 0370 } else { 0371 if (!m_removeAction) { 0372 m_removeAction = new DolphinRemoveAction(this, m_mainWindow->actionCollection()); 0373 } 0374 addAction(m_removeAction); 0375 m_removeAction->update(); 0376 } 0377 } 0378 0379 bool DolphinContextMenu::placeExists(const QUrl &url) const 0380 { 0381 const KFilePlacesModel *placesModel = DolphinPlacesModelSingleton::instance().placesModel(); 0382 0383 const auto &matchedPlaces = placesModel->match(placesModel->index(0, 0), KFilePlacesModel::UrlRole, url, 1, Qt::MatchExactly); 0384 0385 return !matchedPlaces.isEmpty(); 0386 } 0387 0388 QAction *DolphinContextMenu::createPasteAction() 0389 { 0390 QAction *action = nullptr; 0391 KFileItem destItem; 0392 if (!m_fileInfo.isNull() && m_selectedItems.count() <= 1) { 0393 destItem = m_fileInfo; 0394 } else { 0395 destItem = baseFileItem(); 0396 } 0397 0398 if (!destItem.isNull() && destItem.isDir()) { 0399 const QMimeData *mimeData = QApplication::clipboard()->mimeData(); 0400 bool canPaste; 0401 const QString text = KIO::pasteActionText(mimeData, &canPaste, destItem); 0402 if (canPaste) { 0403 if (destItem == m_fileInfo) { 0404 // if paste destination is a selected folder 0405 action = new QAction(QIcon::fromTheme(QStringLiteral("edit-paste")), text, this); 0406 connect(action, &QAction::triggered, m_mainWindow, &DolphinMainWindow::pasteIntoFolder); 0407 } else { 0408 action = m_mainWindow->actionCollection()->action(KStandardAction::name(KStandardAction::Paste)); 0409 } 0410 } 0411 } 0412 0413 return action; 0414 } 0415 0416 KFileItemListProperties &DolphinContextMenu::selectedItemsProperties() const 0417 { 0418 if (!m_selectedItemsProperties) { 0419 m_selectedItemsProperties = new KFileItemListProperties(m_selectedItems); 0420 } 0421 return *m_selectedItemsProperties; 0422 } 0423 0424 KFileItem DolphinContextMenu::baseFileItem() 0425 { 0426 if (!m_baseFileItem) { 0427 const DolphinView *view = m_mainWindow->activeViewContainer()->view(); 0428 KFileItem baseItem = view->rootItem(); 0429 if (baseItem.isNull() || baseItem.url() != m_baseUrl) { 0430 m_baseFileItem = new KFileItem(m_baseUrl); 0431 } else { 0432 m_baseFileItem = new KFileItem(baseItem); 0433 } 0434 } 0435 return *m_baseFileItem; 0436 } 0437 0438 void DolphinContextMenu::addOpenWithActions() 0439 { 0440 // insert 'Open With...' action or sub menu 0441 m_fileItemActions->insertOpenWithActionsTo(nullptr, this, QStringList{qApp->desktopFileName()}); 0442 } 0443 0444 void DolphinContextMenu::addAdditionalActions(const KFileItemListProperties &props) 0445 { 0446 addSeparator(); 0447 0448 QList<QAction *> additionalActions; 0449 if (props.isLocal() && ContextMenuSettings::showOpenTerminal()) { 0450 additionalActions << m_mainWindow->actionCollection()->action(QStringLiteral("open_terminal_here")); 0451 } 0452 m_fileItemActions->addActionsTo(this, KFileItemActions::MenuActionSource::All, additionalActions); 0453 0454 const DolphinView *view = m_mainWindow->activeViewContainer()->view(); 0455 const QList<QAction *> versionControlActions = view->versionControlActions(m_selectedItems); 0456 if (!versionControlActions.isEmpty()) { 0457 addActions(versionControlActions); 0458 addSeparator(); 0459 } 0460 } 0461 0462 #include "moc_dolphincontextmenu.cpp"