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