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