KIO

kfilecopytomenu.cpp
1/*
2 SPDX-FileCopyrightText: 2008, 2009, 2015 David Faure <faure@kde.org>
3
4 SPDX-License-Identifier: LGPL-2.0-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
5*/
6
7#include "kfilecopytomenu.h"
8#include "kfilecopytomenu_p.h"
9
10#include <QAction>
11#include <QDir>
12#include <QFileDialog>
13#include <QIcon>
14#include <QMimeDatabase>
15#include <QMimeType>
16
17#include "../utils_p.h"
18
19#include <KIO/CopyJob>
20#include <KIO/FileUndoManager>
21#include <KIO/JobUiDelegate>
22
23#include <KJobWidgets>
24#include <KLocalizedString>
25#include <KSharedConfig>
26#include <KStringHandler>
27
28#ifdef Q_OS_WIN
29#include "windows.h"
30#endif
31
32static constexpr int s_maxRecentDirs = 10; // Hardcoded max size
33
34KFileCopyToMenuPrivate::KFileCopyToMenuPrivate(KFileCopyToMenu *qq, QWidget *parentWidget)
35 : q(qq)
36 , m_urls()
37 , m_parentWidget(parentWidget)
38 , m_readOnly(false)
39 , m_autoErrorHandling(false)
40{
41}
42
43////
44
46 : QObject(parentWidget)
47 , d(new KFileCopyToMenuPrivate(this, parentWidget))
48{
49}
50
52
54{
55 d->m_urls = urls;
56}
57
59{
60 d->m_readOnly = ro;
61}
62
64{
65 d->m_autoErrorHandling = b;
66}
67
69{
70 QMenu *mainCopyMenu = new KFileCopyToMainMenu(menu, d.get(), Copy);
71 mainCopyMenu->setTitle(i18nc("@title:menu", "Copy To"));
72 mainCopyMenu->menuAction()->setObjectName(QStringLiteral("copyTo_submenu")); // for the unittest
73 menu->addMenu(mainCopyMenu);
74
75 if (!d->m_readOnly) {
76 QMenu *mainMoveMenu = new KFileCopyToMainMenu(menu, d.get(), Move);
77 mainMoveMenu->setTitle(i18nc("@title:menu", "Move To"));
78 mainMoveMenu->menuAction()->setObjectName(QStringLiteral("moveTo_submenu")); // for the unittest
79 menu->addMenu(mainMoveMenu);
80 }
81}
82
83////
84
85KFileCopyToMainMenu::KFileCopyToMainMenu(QMenu *parent, KFileCopyToMenuPrivate *_d, MenuType menuType)
86 : QMenu(parent)
87 , m_menuType(menuType)
88 , m_actionGroup(static_cast<QWidget *>(nullptr))
89 , d(_d)
90 , m_recentDirsGroup(KSharedConfig::openConfig(), m_menuType == Copy ? QStringLiteral("kuick-copy") : QStringLiteral("kuick-move"))
91{
92 connect(this, &KFileCopyToMainMenu::aboutToShow, this, &KFileCopyToMainMenu::slotAboutToShow);
93 connect(&m_actionGroup, &QActionGroup::triggered, this, &KFileCopyToMainMenu::slotTriggered);
94}
95
96void KFileCopyToMainMenu::slotAboutToShow()
97{
98 clear();
99 KFileCopyToDirectoryMenu *subMenu;
100 // Home Folder
101 subMenu = new KFileCopyToDirectoryMenu(this, this, QDir::homePath());
102 subMenu->setTitle(i18nc("@title:menu", "Home Folder"));
103 subMenu->setIcon(QIcon::fromTheme(QStringLiteral("go-home")));
104 QAction *act = addMenu(subMenu);
105 act->setObjectName(QStringLiteral("home"));
106
107 // Root Folder
108#ifndef Q_OS_WIN
109 subMenu = new KFileCopyToDirectoryMenu(this, this, QDir::rootPath());
110 subMenu->setTitle(i18nc("@title:menu", "Root Folder"));
111 subMenu->setIcon(QIcon::fromTheme(QStringLiteral("folder-red")));
112 act = addMenu(subMenu);
113 act->setObjectName(QStringLiteral("root"));
114#else
115 const QFileInfoList drives = QDir::drives();
116 for (const QFileInfo &info : drives) {
117 QString driveIcon = QStringLiteral("drive-harddisk");
118 const uint type = GetDriveTypeW((wchar_t *)info.absoluteFilePath().utf16());
119 switch (type) {
120 case DRIVE_REMOVABLE:
121 driveIcon = QStringLiteral("drive-removable-media");
122 break;
123 case DRIVE_FIXED:
124 driveIcon = QStringLiteral("drive-harddisk");
125 break;
126 case DRIVE_REMOTE:
127 driveIcon = QStringLiteral("network-server");
128 break;
129 case DRIVE_CDROM:
130 driveIcon = QStringLiteral("drive-optical");
131 break;
132 case DRIVE_RAMDISK:
133 case DRIVE_UNKNOWN:
134 case DRIVE_NO_ROOT_DIR:
135 default:
136 driveIcon = QStringLiteral("drive-harddisk");
137 }
138 subMenu = new KFileCopyToDirectoryMenu(this, this, info.absoluteFilePath());
139 subMenu->setTitle(info.absoluteFilePath());
140 subMenu->setIcon(QIcon::fromTheme(driveIcon));
141 addMenu(subMenu);
142 }
143#endif
144
145 // Browse... action, shows a file dialog
146 QAction *browseAction = new QAction(i18nc("@title:menu in Copy To or Move To submenu", "Browse..."), this);
147 browseAction->setObjectName(QStringLiteral("browse"));
148 connect(browseAction, &QAction::triggered, this, &KFileCopyToMainMenu::slotBrowse);
149 addAction(browseAction);
150
151 addSeparator(); // Qt handles removing it automatically if it's last in the menu, nice.
152
153 // Recent Destinations
154 const QStringList recentDirs = m_recentDirsGroup.readPathEntry("Paths", QStringList());
155 for (const QString &recentDir : recentDirs) {
156 const QUrl url = QUrl::fromLocalFile(recentDir);
157 const QString text = KStringHandler::csqueeze(url.toDisplayString(QUrl::PreferLocalFile), 60); // shorten very long paths (#61386)
158 QAction *act = new QAction(text, this);
159 act->setObjectName(recentDir);
160 act->setData(url);
161 m_actionGroup.addAction(act);
162 addAction(act);
163 }
164}
165
166void KFileCopyToMainMenu::slotBrowse()
167{
168 const QUrl dest = QFileDialog::getExistingDirectoryUrl(d->m_parentWidget ? d->m_parentWidget : this);
169 if (!dest.isEmpty()) {
170 copyOrMoveTo(dest);
171 }
172}
173
174void KFileCopyToMainMenu::slotTriggered(QAction *action)
175{
176 const QUrl url = action->data().toUrl();
177 Q_ASSERT(!url.isEmpty());
178 copyOrMoveTo(url);
179}
180
181void KFileCopyToMainMenu::copyOrMoveTo(const QUrl &dest)
182{
183 // Insert into the recent destinations list
184 QStringList recentDirs = m_recentDirsGroup.readPathEntry("Paths", QStringList());
185 const QString niceDest = dest.toDisplayString(QUrl::PreferLocalFile);
186 if (!recentDirs.contains(niceDest)) { // don't change position if already there, moving stuff is bad usability
187 recentDirs.prepend(niceDest);
188 if (recentDirs.size() > s_maxRecentDirs) {
189 recentDirs.erase(recentDirs.begin() + s_maxRecentDirs, recentDirs.end());
190 }
191 m_recentDirsGroup.writePathEntry("Paths", recentDirs);
192 }
193
194 // #199549: add a trailing slash to avoid unexpected results when the
195 // dest doesn't exist anymore: it was creating a file with the name of
196 // the now non-existing dest.
197 QUrl dirDest = dest;
198 Utils::appendSlashToPath(dirDest);
199
200 // And now let's do the copy or move -- with undo/redo support.
201 KIO::CopyJob *job = m_menuType == Copy ? KIO::copy(d->m_urls, dirDest) : KIO::move(d->m_urls, dirDest);
203 KJobWidgets::setWindow(job, d->m_parentWidget ? d->m_parentWidget : this);
204 if (job->uiDelegate()) {
205 job->uiDelegate()->setAutoErrorHandlingEnabled(d->m_autoErrorHandling);
206 }
207 connect(job, &KIO::CopyJob::result, this, [this](KJob *job) {
208 Q_EMIT d->q->error(job->error(), job->errorString());
209 });
210}
211
212////
213
214KFileCopyToDirectoryMenu::KFileCopyToDirectoryMenu(QMenu *parent, KFileCopyToMainMenu *mainMenu, const QString &path)
215 : QMenu(parent)
216 , m_mainMenu(mainMenu)
217 , m_path(Utils::slashAppended(path))
218{
219 connect(this, &KFileCopyToDirectoryMenu::aboutToShow, this, &KFileCopyToDirectoryMenu::slotAboutToShow);
220}
221
222void KFileCopyToDirectoryMenu::slotAboutToShow()
223{
224 clear();
225 QAction *act = new QAction(m_mainMenu->menuType() == Copy ? i18nc("@title:menu", "Copy Here") : i18nc("@title:menu", "Move Here"), this);
226 act->setData(QUrl::fromLocalFile(m_path));
227 act->setEnabled(QFileInfo(m_path).isWritable());
228 m_mainMenu->actionGroup().addAction(act);
229 addAction(act);
230
231 addSeparator(); // Qt handles removing it automatically if it's last in the menu, nice.
232
233 // List directory
234 // All we need is sub folder names, their permissions, their icon.
235 // KDirLister or KIO::listDir would fetch much more info, and would be async,
236 // and we only care about local directories so we use QDir directly.
237 QDir dir(m_path);
239 const QMimeDatabase db;
240 const QMimeType dirMime = db.mimeTypeForName(QStringLiteral("inode/directory"));
241 for (const QString &subDir : entries) {
242 QString subPath = m_path + subDir;
243 KFileCopyToDirectoryMenu *subMenu = new KFileCopyToDirectoryMenu(this, m_mainMenu, subPath);
244 QString menuTitle(subDir);
245 // Replace '&' by "&&" to make sure that '&' inside the directory name is displayed
246 // correctly and not misinterpreted as an indicator for a keyboard shortcut
247 subMenu->setTitle(menuTitle.replace(QLatin1Char('&'), QLatin1String("&&")));
248 const QString iconName = dirMime.iconName();
249 subMenu->setIcon(QIcon::fromTheme(iconName));
250 if (QFileInfo(subPath).isSymLink()) {
251 QFont font = subMenu->menuAction()->font();
252 font.setItalic(true);
253 subMenu->menuAction()->setFont(font);
254 }
255 addMenu(subMenu);
256 }
257}
258
259#include "moc_kfilecopytomenu.cpp"
260#include "moc_kfilecopytomenu_p.cpp"
This class adds "Copy To" and "Move To" submenus to a popupmenu.
KFileCopyToMenu(QWidget *parentWidget)
Creates a KFileCopyToMenu instance Note that this instance (and the widget) must stay alive for at le...
void setAutoErrorHandlingEnabled(bool b)
Enables or disables automatic error handling with message boxes.
void setReadOnly(bool ro)
If setReadOnly(true) is called, the "Move To" submenu will not appear.
~KFileCopyToMenu() override
Destructor.
void setUrls(const QList< QUrl > &urls)
Sets the URLs which the actions apply to.
void addActionsTo(QMenu *menu) const
Generate the actions and submenus, and adds them to the menu.
CopyJob is used to move, copy or symlink files and directories.
Definition copyjob.h:41
void recordCopyJob(KIO::CopyJob *copyJob)
Record this CopyJob while it's happening and add a command for it so that the user can undo it.
static FileUndoManager * self()
void setAutoErrorHandlingEnabled(bool enable)
virtual QString errorString() const
int error() const
void result(KJob *job)
KJobUiDelegate * uiDelegate() const
QString i18nc(const char *context, const char *text, const TYPE &arg...)
Type type(const QSqlDatabase &db)
A namespace for KIO globals.
KIOCORE_EXPORT CopyJob * move(const QUrl &src, const QUrl &dest, JobFlags flags=DefaultFlags)
Moves a file or directory src to the given destination dest.
Definition copyjob.cpp:2626
KIOCORE_EXPORT CopyJob * copy(const QUrl &src, const QUrl &dest, JobFlags flags=DefaultFlags)
Copy a file or directory src into the destination dest, which can be a file (including the final file...
Definition copyjob.cpp:2604
void setWindow(QObject *job, QWidget *widget)
QString path(const QString &relativePath)
KIOCORE_EXPORT QString dir(const QString &fileClass)
Returns the most recently used directory associated with this file-class.
KGuiItem clear()
KCOREADDONS_EXPORT QString csqueeze(const QString &str, int maxlen=40)
QVariant data() const const
void setEnabled(bool)
void setData(const QVariant &data)
void triggered(bool checked)
QFileInfoList drives()
QString homePath()
QString rootPath()
QUrl getExistingDirectoryUrl(QWidget *parent, const QString &caption, const QUrl &dir, Options options, const QStringList &supportedSchemes)
void setItalic(bool enable)
QIcon fromTheme(const QString &name)
iterator begin()
iterator end()
iterator erase(const_iterator begin, const_iterator end)
void prepend(parameter_type value)
qsizetype size() const const
QAction * addMenu(QMenu *menu)
QAction * menuAction() const const
void setTitle(const QString &title)
QMimeType mimeTypeForName(const QString &nameOrAlias) const const
QMetaObject::Connection connect(const QObject *sender, PointerToMemberFunction signal, Functor functor)
void setObjectName(QAnyStringView name)
bool contains(QLatin1StringView str, Qt::CaseSensitivity cs) const const
QFuture< ArgsType< Signal > > connect(Sender *sender, Signal signal)
PreferLocalFile
QUrl fromLocalFile(const QString &localFile)
bool isEmpty() const const
QString toDisplayString(FormattingOptions options) const const
QUrl toUrl() const const
This file is part of the KDE documentation.
Documentation copyright © 1996-2024 The KDE developers.
Generated on Tue Mar 26 2024 11:18:52 by doxygen 1.10.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.