File indexing completed on 2024-10-13 03:38:17
0001 /* 0002 SPDX-FileCopyrightText: 2008, 2009, 2015 David Faure <faure@kde.org> 0003 0004 SPDX-License-Identifier: LGPL-2.0-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL 0005 */ 0006 0007 #include "kfilecopytomenu.h" 0008 #include "kfilecopytomenu_p.h" 0009 0010 #include <QAction> 0011 #include <QDir> 0012 #include <QFileDialog> 0013 #include <QIcon> 0014 #include <QMimeDatabase> 0015 #include <QMimeType> 0016 0017 #include "../utils_p.h" 0018 0019 #include <KIO/CopyJob> 0020 #include <KIO/FileUndoManager> 0021 #include <KIO/JobUiDelegate> 0022 0023 #include <KJobWidgets> 0024 #include <KLocalizedString> 0025 #include <KSharedConfig> 0026 #include <KStringHandler> 0027 0028 #ifdef Q_OS_WIN 0029 #include "windows.h" 0030 #endif 0031 0032 static constexpr int s_maxRecentDirs = 10; // Hardcoded max size 0033 0034 KFileCopyToMenuPrivate::KFileCopyToMenuPrivate(KFileCopyToMenu *qq, QWidget *parentWidget) 0035 : q(qq) 0036 , m_urls() 0037 , m_parentWidget(parentWidget) 0038 , m_readOnly(false) 0039 , m_autoErrorHandling(false) 0040 { 0041 } 0042 0043 //// 0044 0045 KFileCopyToMenu::KFileCopyToMenu(QWidget *parentWidget) 0046 : QObject(parentWidget) 0047 , d(new KFileCopyToMenuPrivate(this, parentWidget)) 0048 { 0049 } 0050 0051 KFileCopyToMenu::~KFileCopyToMenu() = default; 0052 0053 void KFileCopyToMenu::setUrls(const QList<QUrl> &urls) 0054 { 0055 d->m_urls = urls; 0056 } 0057 0058 void KFileCopyToMenu::setReadOnly(bool ro) 0059 { 0060 d->m_readOnly = ro; 0061 } 0062 0063 void KFileCopyToMenu::setAutoErrorHandlingEnabled(bool b) 0064 { 0065 d->m_autoErrorHandling = b; 0066 } 0067 0068 void KFileCopyToMenu::addActionsTo(QMenu *menu) const 0069 { 0070 QMenu *mainCopyMenu = new KFileCopyToMainMenu(menu, d.get(), Copy); 0071 mainCopyMenu->setTitle(i18nc("@title:menu", "Copy To")); 0072 mainCopyMenu->menuAction()->setObjectName(QStringLiteral("copyTo_submenu")); // for the unittest 0073 menu->addMenu(mainCopyMenu); 0074 0075 if (!d->m_readOnly) { 0076 QMenu *mainMoveMenu = new KFileCopyToMainMenu(menu, d.get(), Move); 0077 mainMoveMenu->setTitle(i18nc("@title:menu", "Move To")); 0078 mainMoveMenu->menuAction()->setObjectName(QStringLiteral("moveTo_submenu")); // for the unittest 0079 menu->addMenu(mainMoveMenu); 0080 } 0081 } 0082 0083 //// 0084 0085 KFileCopyToMainMenu::KFileCopyToMainMenu(QMenu *parent, KFileCopyToMenuPrivate *_d, MenuType menuType) 0086 : QMenu(parent) 0087 , m_menuType(menuType) 0088 , m_actionGroup(static_cast<QWidget *>(nullptr)) 0089 , d(_d) 0090 , m_recentDirsGroup(KSharedConfig::openConfig(), m_menuType == Copy ? QStringLiteral("kuick-copy") : QStringLiteral("kuick-move")) 0091 { 0092 connect(this, &KFileCopyToMainMenu::aboutToShow, this, &KFileCopyToMainMenu::slotAboutToShow); 0093 connect(&m_actionGroup, &QActionGroup::triggered, this, &KFileCopyToMainMenu::slotTriggered); 0094 } 0095 0096 void KFileCopyToMainMenu::slotAboutToShow() 0097 { 0098 clear(); 0099 KFileCopyToDirectoryMenu *subMenu; 0100 // Home Folder 0101 subMenu = new KFileCopyToDirectoryMenu(this, this, QDir::homePath()); 0102 subMenu->setTitle(i18nc("@title:menu", "Home Folder")); 0103 subMenu->setIcon(QIcon::fromTheme(QStringLiteral("go-home"))); 0104 QAction *act = addMenu(subMenu); 0105 act->setObjectName(QStringLiteral("home")); 0106 0107 // Root Folder 0108 #ifndef Q_OS_WIN 0109 subMenu = new KFileCopyToDirectoryMenu(this, this, QDir::rootPath()); 0110 subMenu->setTitle(i18nc("@title:menu", "Root Folder")); 0111 subMenu->setIcon(QIcon::fromTheme(QStringLiteral("folder-red"))); 0112 act = addMenu(subMenu); 0113 act->setObjectName(QStringLiteral("root")); 0114 #else 0115 const QFileInfoList drives = QDir::drives(); 0116 for (const QFileInfo &info : drives) { 0117 QString driveIcon = QStringLiteral("drive-harddisk"); 0118 const uint type = GetDriveTypeW((wchar_t *)info.absoluteFilePath().utf16()); 0119 switch (type) { 0120 case DRIVE_REMOVABLE: 0121 driveIcon = QStringLiteral("drive-removable-media"); 0122 break; 0123 case DRIVE_FIXED: 0124 driveIcon = QStringLiteral("drive-harddisk"); 0125 break; 0126 case DRIVE_REMOTE: 0127 driveIcon = QStringLiteral("network-server"); 0128 break; 0129 case DRIVE_CDROM: 0130 driveIcon = QStringLiteral("drive-optical"); 0131 break; 0132 case DRIVE_RAMDISK: 0133 case DRIVE_UNKNOWN: 0134 case DRIVE_NO_ROOT_DIR: 0135 default: 0136 driveIcon = QStringLiteral("drive-harddisk"); 0137 } 0138 subMenu = new KFileCopyToDirectoryMenu(this, this, info.absoluteFilePath()); 0139 subMenu->setTitle(info.absoluteFilePath()); 0140 subMenu->setIcon(QIcon::fromTheme(driveIcon)); 0141 addMenu(subMenu); 0142 } 0143 #endif 0144 0145 // Browse... action, shows a file dialog 0146 QAction *browseAction = new QAction(i18nc("@title:menu in Copy To or Move To submenu", "Browse..."), this); 0147 browseAction->setObjectName(QStringLiteral("browse")); 0148 connect(browseAction, &QAction::triggered, this, &KFileCopyToMainMenu::slotBrowse); 0149 addAction(browseAction); 0150 0151 addSeparator(); // Qt handles removing it automatically if it's last in the menu, nice. 0152 0153 // Recent Destinations 0154 const QStringList recentDirs = m_recentDirsGroup.readPathEntry("Paths", QStringList()); 0155 for (const QString &recentDir : recentDirs) { 0156 const QUrl url = QUrl::fromLocalFile(recentDir); 0157 const QString text = KStringHandler::csqueeze(url.toDisplayString(QUrl::PreferLocalFile), 60); // shorten very long paths (#61386) 0158 QAction *act = new QAction(text, this); 0159 act->setObjectName(recentDir); 0160 act->setData(url); 0161 m_actionGroup.addAction(act); 0162 addAction(act); 0163 } 0164 } 0165 0166 void KFileCopyToMainMenu::slotBrowse() 0167 { 0168 const QUrl dest = QFileDialog::getExistingDirectoryUrl(d->m_parentWidget ? d->m_parentWidget : this); 0169 if (!dest.isEmpty()) { 0170 copyOrMoveTo(dest); 0171 } 0172 } 0173 0174 void KFileCopyToMainMenu::slotTriggered(QAction *action) 0175 { 0176 const QUrl url = action->data().toUrl(); 0177 Q_ASSERT(!url.isEmpty()); 0178 copyOrMoveTo(url); 0179 } 0180 0181 void KFileCopyToMainMenu::copyOrMoveTo(const QUrl &dest) 0182 { 0183 // Insert into the recent destinations list 0184 QStringList recentDirs = m_recentDirsGroup.readPathEntry("Paths", QStringList()); 0185 const QString niceDest = dest.toDisplayString(QUrl::PreferLocalFile); 0186 if (!recentDirs.contains(niceDest)) { // don't change position if already there, moving stuff is bad usability 0187 recentDirs.prepend(niceDest); 0188 if (recentDirs.size() > s_maxRecentDirs) { 0189 recentDirs.erase(recentDirs.begin() + s_maxRecentDirs, recentDirs.end()); 0190 } 0191 m_recentDirsGroup.writePathEntry("Paths", recentDirs); 0192 } 0193 0194 // #199549: add a trailing slash to avoid unexpected results when the 0195 // dest doesn't exist anymore: it was creating a file with the name of 0196 // the now non-existing dest. 0197 QUrl dirDest = dest; 0198 Utils::appendSlashToPath(dirDest); 0199 0200 // And now let's do the copy or move -- with undo/redo support. 0201 KIO::CopyJob *job = m_menuType == Copy ? KIO::copy(d->m_urls, dirDest) : KIO::move(d->m_urls, dirDest); 0202 KIO::FileUndoManager::self()->recordCopyJob(job); 0203 KJobWidgets::setWindow(job, d->m_parentWidget ? d->m_parentWidget : this); 0204 if (job->uiDelegate()) { 0205 job->uiDelegate()->setAutoErrorHandlingEnabled(d->m_autoErrorHandling); 0206 } 0207 connect(job, &KIO::CopyJob::result, this, [this](KJob *job) { 0208 Q_EMIT d->q->error(job->error(), job->errorString()); 0209 }); 0210 } 0211 0212 //// 0213 0214 KFileCopyToDirectoryMenu::KFileCopyToDirectoryMenu(QMenu *parent, KFileCopyToMainMenu *mainMenu, const QString &path) 0215 : QMenu(parent) 0216 , m_mainMenu(mainMenu) 0217 , m_path(Utils::slashAppended(path)) 0218 { 0219 connect(this, &KFileCopyToDirectoryMenu::aboutToShow, this, &KFileCopyToDirectoryMenu::slotAboutToShow); 0220 } 0221 0222 void KFileCopyToDirectoryMenu::slotAboutToShow() 0223 { 0224 clear(); 0225 QAction *act = new QAction(m_mainMenu->menuType() == Copy ? i18nc("@title:menu", "Copy Here") : i18nc("@title:menu", "Move Here"), this); 0226 act->setData(QUrl::fromLocalFile(m_path)); 0227 act->setEnabled(QFileInfo(m_path).isWritable()); 0228 m_mainMenu->actionGroup().addAction(act); 0229 addAction(act); 0230 0231 addSeparator(); // Qt handles removing it automatically if it's last in the menu, nice. 0232 0233 // List directory 0234 // All we need is sub folder names, their permissions, their icon. 0235 // KDirLister or KIO::listDir would fetch much more info, and would be async, 0236 // and we only care about local directories so we use QDir directly. 0237 QDir dir(m_path); 0238 const QStringList entries = dir.entryList(QDir::Dirs | QDir::NoDotAndDotDot, QDir::LocaleAware); 0239 const QMimeDatabase db; 0240 const QMimeType dirMime = db.mimeTypeForName(QStringLiteral("inode/directory")); 0241 for (const QString &subDir : entries) { 0242 QString subPath = m_path + subDir; 0243 KFileCopyToDirectoryMenu *subMenu = new KFileCopyToDirectoryMenu(this, m_mainMenu, subPath); 0244 QString menuTitle(subDir); 0245 // Replace '&' by "&&" to make sure that '&' inside the directory name is displayed 0246 // correctly and not misinterpreted as an indicator for a keyboard shortcut 0247 subMenu->setTitle(menuTitle.replace(QLatin1Char('&'), QLatin1String("&&"))); 0248 const QString iconName = dirMime.iconName(); 0249 subMenu->setIcon(QIcon::fromTheme(iconName)); 0250 if (QFileInfo(subPath).isSymLink()) { 0251 QFont font = subMenu->menuAction()->font(); 0252 font.setItalic(true); 0253 subMenu->menuAction()->setFont(font); 0254 } 0255 addMenu(subMenu); 0256 } 0257 } 0258 0259 #include "moc_kfilecopytomenu.cpp" 0260 #include "moc_kfilecopytomenu_p.cpp"