]> cloud.milkyroute.net Git - dolphin.git/blob - src/dolphincontextmenu.cpp
Remove unused includes
[dolphin.git] / src / dolphincontextmenu.cpp
1 /*
2 * SPDX-FileCopyrightText: 2006 Peter Penz (peter.penz@gmx.at) and Cvetoslav Ludmiloff
3 *
4 * SPDX-License-Identifier: GPL-2.0-or-later
5 */
6
7 #include "dolphincontextmenu.h"
8
9 #include "dolphin_generalsettings.h"
10 #include "dolphin_contextmenusettings.h"
11 #include "dolphinmainwindow.h"
12 #include "dolphinnewfilemenu.h"
13 #include "dolphinplacesmodelsingleton.h"
14 #include "dolphinremoveaction.h"
15 #include "dolphinviewcontainer.h"
16 #include "global.h"
17 #include "trash/dolphintrash.h"
18 #include "views/dolphinview.h"
19 #include "views/viewmodecontroller.h"
20
21 #include <KActionCollection>
22 #include <KFileItemListProperties>
23 #include <KHamburgerMenu>
24 #include <KIO/EmptyTrashJob>
25 #include <KIO/JobUiDelegate>
26 #include <KIO/Paste>
27 #include <KIO/RestoreJob>
28 #include <KJobWidgets>
29 #include <KLocalizedString>
30 #include <KNewFileMenu>
31 #include <KStandardAction>
32 #include <kio_version.h>
33
34 #include <QApplication>
35 #include <QClipboard>
36 #include <QKeyEvent>
37
38 DolphinContextMenu::DolphinContextMenu(DolphinMainWindow* parent,
39 const KFileItem& fileInfo,
40 const KFileItemList &selectedItems,
41 const QUrl& baseUrl,
42 KFileItemActions *fileItemActions) :
43 QMenu(parent),
44 m_mainWindow(parent),
45 m_fileInfo(fileInfo),
46 m_baseUrl(baseUrl),
47 m_baseFileItem(nullptr),
48 m_selectedItems(selectedItems),
49 m_selectedItemsProperties(nullptr),
50 m_context(NoContext),
51 m_copyToMenu(parent),
52 m_removeAction(nullptr),
53 m_fileItemActions(fileItemActions)
54 {
55 QApplication::instance()->installEventFilter(this);
56
57 addAllActions();
58 }
59
60 DolphinContextMenu::~DolphinContextMenu()
61 {
62 delete m_baseFileItem;
63 m_baseFileItem = nullptr;
64 delete m_selectedItemsProperties;
65 m_selectedItemsProperties = nullptr;
66 }
67
68 void DolphinContextMenu::addAllActions()
69 {
70 static_cast<KHamburgerMenu *>(m_mainWindow->actionCollection()->
71 action(QStringLiteral("hamburger_menu")))->addToMenu(this);
72
73 // get the context information
74 const auto scheme = m_baseUrl.scheme();
75 if (scheme == QLatin1String("trash")) {
76 m_context |= TrashContext;
77 } else if (scheme.contains(QLatin1String("search"))) {
78 m_context |= SearchContext;
79 } else if (scheme.contains(QLatin1String("timeline"))) {
80 m_context |= TimelineContext;
81 }
82
83 if (!m_fileInfo.isNull() && !m_selectedItems.isEmpty()) {
84 m_context |= ItemContext;
85 // TODO: handle other use cases like devices + desktop files
86 }
87
88 // open the corresponding popup for the context
89 if (m_context & TrashContext) {
90 if (m_context & ItemContext) {
91 addTrashItemContextMenu();
92 } else {
93 addTrashContextMenu();
94 }
95 } else if (m_context & ItemContext) {
96 addItemContextMenu();
97 } else {
98 addViewportContextMenu();
99 }
100 }
101
102 bool DolphinContextMenu::eventFilter(QObject* object, QEvent* event)
103 {
104 Q_UNUSED(object)
105
106 if(event->type() == QEvent::KeyPress || event->type() == QEvent::KeyRelease) {
107 QKeyEvent* keyEvent = static_cast<QKeyEvent*>(event);
108
109 if (m_removeAction && keyEvent->key() == Qt::Key_Shift) {
110 if (event->type() == QEvent::KeyPress) {
111 m_removeAction->update(DolphinRemoveAction::ShiftState::Pressed);
112 } else {
113 m_removeAction->update(DolphinRemoveAction::ShiftState::Released);
114 }
115 }
116 }
117
118 return false;
119 }
120
121 void DolphinContextMenu::addTrashContextMenu()
122 {
123 Q_ASSERT(m_context & TrashContext);
124
125 QAction *emptyTrashAction = addAction(QIcon::fromTheme(QStringLiteral("trash-empty")), i18nc("@action:inmenu", "Empty Trash"), [this](){
126 Trash::empty(m_mainWindow);
127 });
128 emptyTrashAction->setEnabled(!Trash::isEmpty());
129
130 QAction* propertiesAction = m_mainWindow->actionCollection()->action(QStringLiteral("properties"));
131 addAction(propertiesAction);
132 }
133
134 void DolphinContextMenu::addTrashItemContextMenu()
135 {
136 Q_ASSERT(m_context & TrashContext);
137 Q_ASSERT(m_context & ItemContext);
138
139 addAction(QIcon::fromTheme("restoration"), i18nc("@action:inmenu", "Restore"), [this](){
140 QList<QUrl> selectedUrls;
141 selectedUrls.reserve(m_selectedItems.count());
142 for (const KFileItem &item : qAsConst(m_selectedItems)) {
143 selectedUrls.append(item.url());
144 }
145
146 KIO::RestoreJob *job = KIO::restoreFromTrash(selectedUrls);
147 KJobWidgets::setWindow(job, m_mainWindow);
148 job->uiDelegate()->setAutoErrorHandlingEnabled(true);
149 });
150
151 QAction* deleteAction = m_mainWindow->actionCollection()->action(KStandardAction::name(KStandardAction::DeleteFile));
152 addAction(deleteAction);
153
154 QAction* propertiesAction = m_mainWindow->actionCollection()->action(QStringLiteral("properties"));
155 addAction(propertiesAction);
156 }
157
158 void DolphinContextMenu::addDirectoryItemContextMenu()
159 {
160 // insert 'Open in new window' and 'Open in new tab' entries
161 const KFileItemListProperties& selectedItemsProps = selectedItemsProperties();
162 if (ContextMenuSettings::showOpenInNewTab()) {
163 addAction(m_mainWindow->actionCollection()->action(QStringLiteral("open_in_new_tab")));
164 }
165 if (ContextMenuSettings::showOpenInNewWindow()) {
166 addAction(m_mainWindow->actionCollection()->action(QStringLiteral("open_in_new_window")));
167 }
168
169 // Insert 'Open With' entries
170 addOpenWithActions();
171
172 // set up 'Create New' menu
173 DolphinNewFileMenu* newFileMenu = new DolphinNewFileMenu(m_mainWindow->actionCollection(), m_mainWindow);
174 newFileMenu->checkUpToDate();
175 #if KIO_VERSION >= QT_VERSION_CHECK(5, 97, 0)
176 newFileMenu->setWorkingDirectory(m_fileInfo.url());
177 #else
178 newFileMenu->setPopupFiles(QList<QUrl>() << m_fileInfo.url());
179 #endif
180 newFileMenu->setEnabled(selectedItemsProps.supportsWriting());
181 connect(newFileMenu, &DolphinNewFileMenu::fileCreated, newFileMenu, &DolphinNewFileMenu::deleteLater);
182 connect(newFileMenu, &DolphinNewFileMenu::directoryCreated, newFileMenu, &DolphinNewFileMenu::deleteLater);
183
184 QMenu* menu = newFileMenu->menu();
185 menu->setTitle(i18nc("@title:menu Create new folder, file, link, etc.", "Create New"));
186 menu->setIcon(QIcon::fromTheme(QStringLiteral("list-add")));
187 addMenu(menu);
188
189 addSeparator();
190 }
191
192 void DolphinContextMenu::addItemContextMenu()
193 {
194 Q_ASSERT(!m_fileInfo.isNull());
195
196 const KFileItemListProperties& selectedItemsProps = selectedItemsProperties();
197
198 m_fileItemActions->setItemListProperties(selectedItemsProps);
199
200 if (m_selectedItems.count() == 1) {
201 // single files
202 if (m_fileInfo.isDir()) {
203 addDirectoryItemContextMenu();
204 } else if (m_context & TimelineContext || m_context & SearchContext) {
205 addOpenWithActions();
206
207 addAction(QIcon::fromTheme(QStringLiteral("document-open-folder")),
208 i18nc("@action:inmenu",
209 "Open Path"),
210 [this](){
211 m_mainWindow->changeUrl(KIO::upUrl(m_fileInfo.url()));
212 m_mainWindow->activeViewContainer()->view()->markUrlsAsSelected({m_fileInfo.url()});
213 m_mainWindow->activeViewContainer()->view()->markUrlAsCurrent(m_fileInfo.url());
214 });
215
216 addAction(QIcon::fromTheme(QStringLiteral("window-new")),
217 i18nc("@action:inmenu",
218 "Open Path in New Window"),
219 [this](){
220 Dolphin::openNewWindow({m_fileInfo.url()}, m_mainWindow, Dolphin::OpenNewWindowFlag::Select);
221 });
222
223 addAction(QIcon::fromTheme(QStringLiteral("tab-new")),
224 i18nc("@action:inmenu",
225 "Open Path in New Tab"),
226 [this](){
227 m_mainWindow->openNewTab(KIO::upUrl(m_fileInfo.url()));
228 });
229
230 addSeparator();
231 } else {
232 // Insert 'Open With" entries
233 addOpenWithActions();
234 }
235 if (m_fileInfo.isLink()) {
236 addAction(m_mainWindow->actionCollection()->action(QStringLiteral("show_target")));
237 addSeparator();
238 }
239 } else {
240 // multiple files
241 bool selectionHasOnlyDirs = true;
242 for (const auto &item : qAsConst(m_selectedItems)) {
243 const QUrl& url = DolphinView::openItemAsFolderUrl(item);
244 if (url.isEmpty()) {
245 selectionHasOnlyDirs = false;
246 break;
247 }
248 }
249
250 if (selectionHasOnlyDirs && ContextMenuSettings::showOpenInNewTab()) {
251 // insert 'Open in new tab' entry
252 addAction(m_mainWindow->actionCollection()->action(QStringLiteral("open_in_new_tabs")));
253 }
254 // Insert 'Open With" entries
255 addOpenWithActions();
256 }
257
258 insertDefaultItemActions(selectedItemsProps);
259
260 addAdditionalActions(selectedItemsProps);
261
262 // insert 'Copy To' and 'Move To' sub menus
263 if (ContextMenuSettings::showCopyMoveMenu()) {
264 m_copyToMenu.setUrls(m_selectedItems.urlList());
265 m_copyToMenu.setReadOnly(!selectedItemsProps.supportsWriting());
266 m_copyToMenu.setAutoErrorHandlingEnabled(true);
267 m_copyToMenu.addActionsTo(this);
268 }
269
270 // insert 'Properties...' entry
271 addSeparator();
272 QAction* propertiesAction = m_mainWindow->actionCollection()->action(QStringLiteral("properties"));
273 addAction(propertiesAction);
274 }
275
276 void DolphinContextMenu::addViewportContextMenu()
277 {
278 const KFileItemListProperties baseUrlProperties(KFileItemList() << baseFileItem());
279 m_fileItemActions->setItemListProperties(baseUrlProperties);
280
281 // Set up and insert 'Create New' menu
282 KNewFileMenu* newFileMenu = m_mainWindow->newFileMenu();
283 newFileMenu->checkUpToDate();
284 #if KIO_VERSION >= QT_VERSION_CHECK(5, 97, 0)
285 newFileMenu->setWorkingDirectory(m_baseUrl);
286 #else
287 newFileMenu->setPopupFiles(QList<QUrl>() << m_baseUrl);
288 #endif
289 addMenu(newFileMenu->menu());
290
291 // Show "open with" menu items even if the dir is empty, because there are legitimate
292 // use cases for this, such as opening an empty dir in Kate or VSCode or something
293 addOpenWithActions();
294
295 QAction* pasteAction = createPasteAction();
296 if (pasteAction) {
297 addAction(pasteAction);
298 }
299
300 // Insert 'Add to Places' entry if it's not already in the places panel
301 if (ContextMenuSettings::showAddToPlaces() &&
302 !placeExists(m_mainWindow->activeViewContainer()->url())) {
303 addAction(m_mainWindow->actionCollection()->action(QStringLiteral("add_to_places")));
304 }
305 addSeparator();
306
307 // Insert 'Sort By' and 'View Mode'
308 if (ContextMenuSettings::showSortBy()) {
309 addAction(m_mainWindow->actionCollection()->action(QStringLiteral("sort")));
310 }
311 if (ContextMenuSettings::showViewMode()) {
312 addAction(m_mainWindow->actionCollection()->action(QStringLiteral("view_mode")));
313 }
314 if (ContextMenuSettings::showSortBy() || ContextMenuSettings::showViewMode()) {
315 addSeparator();
316 }
317
318 addAdditionalActions(baseUrlProperties);
319
320 addSeparator();
321
322 QAction* propertiesAction = m_mainWindow->actionCollection()->action(QStringLiteral("properties"));
323 addAction(propertiesAction);
324 }
325
326 void DolphinContextMenu::insertDefaultItemActions(const KFileItemListProperties& properties)
327 {
328 const KActionCollection* collection = m_mainWindow->actionCollection();
329
330 // Insert 'Cut', 'Copy', 'Copy Location' and 'Paste'
331 addAction(collection->action(KStandardAction::name(KStandardAction::Cut)));
332 addAction(collection->action(KStandardAction::name(KStandardAction::Copy)));
333 if (ContextMenuSettings::showCopyLocation()) {
334 QAction* copyPathAction = collection->action(QString("copy_location"));
335 copyPathAction->setEnabled(m_selectedItems.size() == 1);
336 addAction(copyPathAction);
337 }
338 QAction* pasteAction = createPasteAction();
339 if (pasteAction) {
340 addAction(pasteAction);
341 }
342
343 // Insert 'Duplicate Here'
344 if (ContextMenuSettings::showDuplicateHere()) {
345 addAction(m_mainWindow->actionCollection()->action(QStringLiteral("duplicate")));
346 }
347
348 // Insert 'Rename'
349 addAction(collection->action(KStandardAction::name(KStandardAction::RenameFile)));
350
351 // Insert 'Add to Places' entry if appropriate
352 if (ContextMenuSettings::showAddToPlaces() &&
353 m_selectedItems.count() == 1 &&
354 m_fileInfo.isDir() &&
355 !placeExists(m_fileInfo.url())) {
356 addAction(m_mainWindow->actionCollection()->action(QStringLiteral("add_to_places")));
357 }
358
359 addSeparator();
360
361 // Insert 'Move to Trash' and/or 'Delete'
362 const bool showDeleteAction = (KSharedConfig::openConfig()->group("KDE").readEntry("ShowDeleteCommand", false) ||
363 !properties.isLocal());
364 const bool showMoveToTrashAction = (properties.isLocal() &&
365 properties.supportsMoving());
366
367 if (showDeleteAction && showMoveToTrashAction) {
368 delete m_removeAction;
369 m_removeAction = nullptr;
370 addAction(m_mainWindow->actionCollection()->action(KStandardAction::name(KStandardAction::MoveToTrash)));
371 addAction(m_mainWindow->actionCollection()->action(KStandardAction::name(KStandardAction::DeleteFile)));
372 } else if (showDeleteAction && !showMoveToTrashAction) {
373 addAction(m_mainWindow->actionCollection()->action(KStandardAction::name(KStandardAction::DeleteFile)));
374 } else {
375 if (!m_removeAction) {
376 m_removeAction = new DolphinRemoveAction(this, m_mainWindow->actionCollection());
377 }
378 addAction(m_removeAction);
379 m_removeAction->update();
380 }
381 }
382
383 bool DolphinContextMenu::placeExists(const QUrl& url) const
384 {
385 const KFilePlacesModel* placesModel = DolphinPlacesModelSingleton::instance().placesModel();
386
387 const auto& matchedPlaces = placesModel->match(placesModel->index(0,0), KFilePlacesModel::UrlRole, url, 1, Qt::MatchExactly);
388
389 return !matchedPlaces.isEmpty();
390 }
391
392 QAction* DolphinContextMenu::createPasteAction()
393 {
394 QAction* action = nullptr;
395 KFileItem destItem;
396 if (!m_fileInfo.isNull() && m_selectedItems.count() <= 1) {
397 destItem = m_fileInfo;
398 } else {
399 destItem = baseFileItem();
400 }
401
402 if (!destItem.isNull() && destItem.isDir()) {
403 const QMimeData *mimeData = QApplication::clipboard()->mimeData();
404 bool canPaste;
405 const QString text = KIO::pasteActionText(mimeData, &canPaste, destItem);
406 if (canPaste) {
407 if (destItem == m_fileInfo) {
408 // if paste destination is a selected folder
409 action = new QAction(QIcon::fromTheme(QStringLiteral("edit-paste")), text, this);
410 connect(action, &QAction::triggered, m_mainWindow, &DolphinMainWindow::pasteIntoFolder);
411 } else {
412 action = m_mainWindow->actionCollection()->action(KStandardAction::name(KStandardAction::Paste));
413 }
414 }
415 }
416
417 return action;
418 }
419
420 KFileItemListProperties& DolphinContextMenu::selectedItemsProperties() const
421 {
422 if (!m_selectedItemsProperties) {
423 m_selectedItemsProperties = new KFileItemListProperties(m_selectedItems);
424 }
425 return *m_selectedItemsProperties;
426 }
427
428 KFileItem DolphinContextMenu::baseFileItem()
429 {
430 if (!m_baseFileItem) {
431 const DolphinView* view = m_mainWindow->activeViewContainer()->view();
432 KFileItem baseItem = view->rootItem();
433 if (baseItem.isNull() || baseItem.url() != m_baseUrl) {
434 m_baseFileItem = new KFileItem(m_baseUrl);
435 } else {
436 m_baseFileItem = new KFileItem(baseItem);
437 }
438 }
439 return *m_baseFileItem;
440 }
441
442 void DolphinContextMenu::addOpenWithActions()
443 {
444 // insert 'Open With...' action or sub menu
445 m_fileItemActions->insertOpenWithActionsTo(nullptr, this, QStringList{qApp->desktopFileName()});
446 }
447
448 void DolphinContextMenu::addAdditionalActions(const KFileItemListProperties &props)
449 {
450 addSeparator();
451
452 QList<QAction *> additionalActions;
453 if (props.isLocal() && ContextMenuSettings::showOpenTerminal()) {
454 additionalActions << m_mainWindow->actionCollection()->action(QStringLiteral("open_terminal_here"));
455 }
456 m_fileItemActions->addActionsTo(this, KFileItemActions::MenuActionSource::All, additionalActions);
457
458 const DolphinView* view = m_mainWindow->activeViewContainer()->view();
459 const QList<QAction*> versionControlActions = view->versionControlActions(m_selectedItems);
460 if (!versionControlActions.isEmpty()) {
461 addActions(versionControlActions);
462 addSeparator();
463 }
464 }
465