1 /*
2  * SPDX-FileCopyrightText: 2006-2010 Peter Penz <peter.penz19@gmail.com>
3  * SPDX-FileCopyrightText: 2006 Cvetoslav Ludmiloff <ludmiloff@gmail.com>
4  *
5  * SPDX-License-Identifier: GPL-2.0-or-later
6  */
7 
8 #include "treeviewcontextmenu.h"
9 
10 #include "folderspanel.h"
11 #include "global.h"
12 
13 #include <KConfigGroup>
14 #include <KFileItemListProperties>
15 #include <KIO/CopyJob>
16 #include <KIO/DeleteJob>
17 #include <KIO/FileUndoManager>
18 #include <KIO/JobUiDelegate>
19 #include <KIO/Paste>
20 #include <KIO/PasteJob>
21 #include <KJobWidgets>
22 #include <KLocalizedString>
23 #include <KPropertiesDialog>
24 #include <KSharedConfig>
25 #include <KUrlMimeData>
26 
27 #include <QApplication>
28 #include <QClipboard>
29 #include <QMenu>
30 #include <QMimeData>
31 #include <QPointer>
32 
TreeViewContextMenu(FoldersPanel * parent,const KFileItem & fileInfo)33 TreeViewContextMenu::TreeViewContextMenu(FoldersPanel* parent,
34                                          const KFileItem& fileInfo) :
35     QObject(parent),
36     m_parent(parent),
37     m_fileItem(fileInfo)
38 {
39 }
40 
~TreeViewContextMenu()41 TreeViewContextMenu::~TreeViewContextMenu()
42 {
43 }
44 
open(const QPoint & pos)45 void TreeViewContextMenu::open(const QPoint& pos)
46 {
47     QMenu* popup = new QMenu(m_parent);
48 
49     if (!m_fileItem.isNull()) {
50         KFileItemListProperties capabilities(KFileItemList() << m_fileItem);
51 
52         // insert 'Cut', 'Copy' and 'Paste'
53         QAction* cutAction = new QAction(QIcon::fromTheme(QStringLiteral("edit-cut")), i18nc("@action:inmenu", "Cut"), this);
54         cutAction->setEnabled(capabilities.supportsMoving());
55         connect(cutAction, &QAction::triggered, this, &TreeViewContextMenu::cut);
56 
57         QAction* copyAction = new QAction(QIcon::fromTheme(QStringLiteral("edit-copy")), i18nc("@action:inmenu", "Copy"), this);
58         connect(copyAction, &QAction::triggered, this, &TreeViewContextMenu::copy);
59 
60         const QMimeData *mimeData = QApplication::clipboard()->mimeData();
61         bool canPaste;
62         const QString text = KIO::pasteActionText(mimeData, &canPaste, m_fileItem);
63         QAction* pasteAction = new QAction(QIcon::fromTheme(QStringLiteral("edit-paste")), text, this);
64         connect(pasteAction, &QAction::triggered, this, &TreeViewContextMenu::paste);
65         pasteAction->setEnabled(canPaste);
66 
67         popup->addAction(cutAction);
68         popup->addAction(copyAction);
69         popup->addAction(pasteAction);
70         popup->addSeparator();
71 
72         // insert 'Rename'
73         QAction* renameAction = new QAction(i18nc("@action:inmenu", "Rename..."), this);
74         renameAction->setEnabled(capabilities.supportsMoving());
75         renameAction->setIcon(QIcon::fromTheme(QStringLiteral("edit-rename")));
76         connect(renameAction, &QAction::triggered, this, &TreeViewContextMenu::rename);
77         popup->addAction(renameAction);
78 
79         // insert 'Move to Trash' and (optionally) 'Delete'
80         KSharedConfig::Ptr globalConfig = KSharedConfig::openConfig(QStringLiteral("kdeglobals"), KConfig::IncludeGlobals);
81         KConfigGroup configGroup(globalConfig, "KDE");
82         bool showDeleteCommand = configGroup.readEntry("ShowDeleteCommand", false);
83 
84         const QUrl url = m_fileItem.url();
85         if (url.isLocalFile()) {
86             QAction* moveToTrashAction = new QAction(QIcon::fromTheme(QStringLiteral("user-trash")),
87                                                     i18nc("@action:inmenu", "Move to Trash"), this);
88             const bool enableMoveToTrash = capabilities.isLocal() && capabilities.supportsMoving();
89             moveToTrashAction->setEnabled(enableMoveToTrash);
90             connect(moveToTrashAction, &QAction::triggered, this, &TreeViewContextMenu::moveToTrash);
91             popup->addAction(moveToTrashAction);
92         } else {
93             showDeleteCommand = true;
94         }
95 
96         if (showDeleteCommand) {
97             QAction* deleteAction = new QAction(QIcon::fromTheme(QStringLiteral("edit-delete")), i18nc("@action:inmenu", "Delete"), this);
98             deleteAction->setEnabled(capabilities.supportsDeleting());
99             connect(deleteAction, &QAction::triggered, this, &TreeViewContextMenu::deleteItem);
100             popup->addAction(deleteAction);
101         }
102 
103         popup->addSeparator();
104     }
105 
106     // insert 'Show Hidden Files'
107     QAction* showHiddenFilesAction = new QAction(i18nc("@action:inmenu", "Show Hidden Files"), this);
108     showHiddenFilesAction->setCheckable(true);
109     showHiddenFilesAction->setChecked(m_parent->showHiddenFiles());
110     popup->addAction(showHiddenFilesAction);
111     connect(showHiddenFilesAction, &QAction::toggled, this, &TreeViewContextMenu::setShowHiddenFiles);
112 
113     if (!m_fileItem.isNull()) {
114         // insert 'Limit to Home Directory'
115         const QUrl url = m_fileItem.url();
116         const bool enableLimitToHomeDirectory = url.isLocalFile();
117         QAction* limitFoldersPanelToHomeAction = new QAction(i18nc("@action:inmenu", "Limit to Home Directory"), this);
118         limitFoldersPanelToHomeAction->setCheckable(true);
119         limitFoldersPanelToHomeAction->setEnabled(enableLimitToHomeDirectory);
120         limitFoldersPanelToHomeAction->setChecked(m_parent->limitFoldersPanelToHome());
121         popup->addAction(limitFoldersPanelToHomeAction);
122         connect(limitFoldersPanelToHomeAction, &QAction::toggled, this, &TreeViewContextMenu::setLimitFoldersPanelToHome);
123     }
124 
125     // insert 'Automatic Scrolling'
126     QAction* autoScrollingAction = new QAction(i18nc("@action:inmenu", "Automatic Scrolling"), this);
127     autoScrollingAction->setCheckable(true);
128     autoScrollingAction->setChecked(m_parent->autoScrolling());
129     // TODO: Temporary disabled. Horizontal autoscrolling will be implemented later either
130     // in KItemViews or manually as part of the FoldersPanel
131     //popup->addAction(autoScrollingAction);
132     connect(autoScrollingAction, &QAction::toggled, this, &TreeViewContextMenu::setAutoScrolling);
133 
134     if (!m_fileItem.isNull()) {
135         // insert 'Properties' entry
136         QAction* propertiesAction = new QAction(i18nc("@action:inmenu", "Properties"), this);
137         propertiesAction->setIcon(QIcon::fromTheme(QStringLiteral("document-properties")));
138         connect(propertiesAction, &QAction::triggered, this, &TreeViewContextMenu::showProperties);
139         popup->addAction(propertiesAction);
140     }
141 
142     const QList<QAction*> customActions = m_parent->customContextMenuActions();
143     if (!customActions.isEmpty()) {
144         popup->addSeparator();
145         for (QAction* action : customActions) {
146             popup->addAction(action);
147         }
148     }
149 
150     QPointer<QMenu> popupPtr = popup;
151     popup->exec(pos);
152     if (popupPtr.data()) {
153         popupPtr.data()->deleteLater();
154     }
155 }
156 
populateMimeData(QMimeData * mimeData,bool cut)157 void TreeViewContextMenu::populateMimeData(QMimeData* mimeData, bool cut)
158 {
159     QList<QUrl> kdeUrls;
160     kdeUrls.append(m_fileItem.url());
161     QList<QUrl> mostLocalUrls;
162     bool dummy;
163     mostLocalUrls.append(m_fileItem.mostLocalUrl(&dummy));
164     KIO::setClipboardDataCut(mimeData, cut);
165     KUrlMimeData::setUrls(kdeUrls, mostLocalUrls, mimeData);
166 }
167 
cut()168 void TreeViewContextMenu::cut()
169 {
170     QMimeData* mimeData = new QMimeData();
171     populateMimeData(mimeData, true);
172     QApplication::clipboard()->setMimeData(mimeData);
173 }
174 
copy()175 void TreeViewContextMenu::copy()
176 {
177     QMimeData* mimeData = new QMimeData();
178     populateMimeData(mimeData, false);
179     QApplication::clipboard()->setMimeData(mimeData);
180 }
181 
paste()182 void TreeViewContextMenu::paste()
183 {
184     KIO::PasteJob *job = KIO::paste(QApplication::clipboard()->mimeData(), m_fileItem.url());
185     KJobWidgets::setWindow(job, m_parent);
186 }
187 
rename()188 void TreeViewContextMenu::rename()
189 {
190     m_parent->rename(m_fileItem);
191 }
192 
moveToTrash()193 void TreeViewContextMenu::moveToTrash()
194 {
195     const QList<QUrl> list{m_fileItem.url()};
196     KIO::JobUiDelegate uiDelegate;
197     uiDelegate.setWindow(m_parent);
198     if (uiDelegate.askDeleteConfirmation(list, KIO::JobUiDelegate::Trash, KIO::JobUiDelegate::DefaultConfirmation)) {
199         KIO::Job* job = KIO::trash(list);
200         KIO::FileUndoManager::self()->recordJob(KIO::FileUndoManager::Trash, list, QUrl(QStringLiteral("trash:/")), job);
201         KJobWidgets::setWindow(job, m_parent);
202         job->uiDelegate()->setAutoErrorHandlingEnabled(true);
203     }
204 }
205 
deleteItem()206 void TreeViewContextMenu::deleteItem()
207 {
208     const QList<QUrl> list{m_fileItem.url()};
209     KIO::JobUiDelegate uiDelegate;
210     uiDelegate.setWindow(m_parent);
211     if (uiDelegate.askDeleteConfirmation(list, KIO::JobUiDelegate::Delete, KIO::JobUiDelegate::DefaultConfirmation)) {
212         KIO::Job* job = KIO::del(list);
213         KJobWidgets::setWindow(job, m_parent);
214         job->uiDelegate()->setAutoErrorHandlingEnabled(true);
215     }
216 }
217 
showProperties()218 void TreeViewContextMenu::showProperties()
219 {
220     KPropertiesDialog* dialog = new KPropertiesDialog(m_fileItem.url(), m_parent);
221     dialog->setAttribute(Qt::WA_DeleteOnClose);
222     dialog->show();
223 }
224 
setShowHiddenFiles(bool show)225 void TreeViewContextMenu::setShowHiddenFiles(bool show)
226 {
227     m_parent->setShowHiddenFiles(show);
228 }
229 
setLimitFoldersPanelToHome(bool enable)230 void TreeViewContextMenu::setLimitFoldersPanelToHome(bool enable)
231 {
232     m_parent->setLimitFoldersPanelToHome(enable);
233 }
234 
setAutoScrolling(bool enable)235 void TreeViewContextMenu::setAutoScrolling(bool enable)
236 {
237     m_parent->setAutoScrolling(enable);
238 }
239 
240