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"