File indexing completed on 2024-04-28 17:06:25

0001 /*
0002     SPDX-FileCopyrightText: 2000 Shie Erlich <krusader@users.sourceforge.net>
0003     SPDX-FileCopyrightText: 2000 Rafi Yanai <krusader@users.sourceforge.net>
0004     SPDX-FileCopyrightText: 2004-2022 Krusader Krew <https://krusader.org>
0005 
0006     SPDX-License-Identifier: GPL-2.0-or-later
0007 */
0008 
0009 #include "panelfunc.h"
0010 
0011 // QtCore
0012 #include <QDir>
0013 #include <QEventLoop>
0014 #include <QList>
0015 #include <QMimeData>
0016 #include <QTemporaryFile>
0017 #include <QUrl>
0018 // QtGui
0019 #include <QClipboard>
0020 #include <QDrag>
0021 // QtWidgets
0022 #include <QApplication>
0023 #include <QInputDialog>
0024 
0025 #include <KArchive/KTar>
0026 #include <KConfigCore/KDesktopFile>
0027 #include <KCoreAddons/KJobTrackerInterface>
0028 #include <KCoreAddons/KProcess>
0029 #include <KCoreAddons/KShell>
0030 #include <KCoreAddons/KUrlMimeData>
0031 #include <KI18n/KLocalizedString>
0032 #include <KIO/DesktopExecParser>
0033 #include <KIO/JobUiDelegate>
0034 
0035 #include <kio_version.h>
0036 #if KIO_VERSION >= QT_VERSION_CHECK(5, 71, 0)
0037 #include <KIO/CommandLauncherJob>
0038 #include <KIO/OpenUrlJob>
0039 #endif
0040 
0041 #include <KIOCore/KProtocolInfo>
0042 #include <KIOWidgets/KDesktopFileActions>
0043 #include <KIOWidgets/KOpenWithDialog>
0044 #include <KIOWidgets/KPropertiesDialog>
0045 #include <KIOWidgets/KRun>
0046 
0047 #include <KService/KApplicationTrader>
0048 #include <kservice_version.h>
0049 
0050 #include <KWidgetsAddons/KCursor>
0051 #include <KWidgetsAddons/KMessageBox>
0052 #include <KWidgetsAddons/KToggleAction>
0053 
0054 #include "../Archive/krarchandler.h"
0055 #include "../Archive/packjob.h"
0056 #include "../Dialogs/checksumdlg.h"
0057 #include "../Dialogs/krdialogs.h"
0058 #include "../Dialogs/krpleasewait.h"
0059 #include "../Dialogs/krspwidgets.h"
0060 #include "../Dialogs/packgui.h"
0061 #include "../FileSystem/fileitem.h"
0062 #include "../FileSystem/filesystemprovider.h"
0063 #include "../FileSystem/krpermhandler.h"
0064 #include "../FileSystem/sizecalculator.h"
0065 #include "../FileSystem/virtualfilesystem.h"
0066 #include "../KViewer/krviewer.h"
0067 #include "../MountMan/kmountman.h"
0068 #include "../abstractpanelmanager.h"
0069 #include "../compat.h"
0070 #include "../defaults.h"
0071 #include "../kractions.h"
0072 #include "../krglobal.h"
0073 #include "../krservices.h"
0074 #include "../krslots.h"
0075 #include "PanelView/krview.h"
0076 #include "PanelView/krviewitem.h"
0077 #include "dirhistoryqueue.h"
0078 #include "krcalcspacedialog.h"
0079 #include "krerrordisplay.h"
0080 #include "krsearchbar.h"
0081 #include "listpanel.h"
0082 #include "listpanelactions.h"
0083 
0084 QPointer<ListPanelFunc> ListPanelFunc::copyToClipboardOrigin;
0085 
0086 ListPanelFunc::ListPanelFunc(ListPanel *parent)
0087     : QObject(parent)
0088     , panel(parent)
0089     , fileSystemP(nullptr)
0090     , urlManuallyEntered(false)
0091     , _isPaused(true)
0092     , _refreshAfterPaused(true)
0093     , _quickSizeCalculator(nullptr)
0094 {
0095     history = new DirHistoryQueue(panel);
0096     delayTimer.setSingleShot(true);
0097     connect(&delayTimer, &QTimer::timeout, this, &ListPanelFunc::doRefresh);
0098 }
0099 
0100 ListPanelFunc::~ListPanelFunc()
0101 {
0102     if (fileSystemP) {
0103         fileSystemP->deleteLater();
0104     }
0105     delete history;
0106     if (_quickSizeCalculator)
0107         _quickSizeCalculator->deleteLater();
0108 }
0109 
0110 bool ListPanelFunc::isSyncing(const QUrl &url)
0111 {
0112     if (otherFunc()->otherFunc() == this && panel->otherPanel()->gui->syncBrowseButton->isChecked() && !otherFunc()->syncURL.isEmpty()
0113         && otherFunc()->syncURL == url)
0114         return true;
0115 
0116     return false;
0117 }
0118 
0119 void ListPanelFunc::openFileNameInternal(const QString &name, bool externallyExecutable)
0120 {
0121     if (name == "..") {
0122         dirUp();
0123         return;
0124     }
0125 
0126     FileItem *fileitem = files()->getFileItem(name);
0127     if (fileitem == nullptr)
0128         return;
0129 
0130     QUrl url = files()->getUrl(name);
0131 
0132     if (fileitem->isDir()) {
0133         panel->view->setNameToMakeCurrent(QString());
0134         openUrl(url);
0135         return;
0136     }
0137 
0138     QString mime = fileitem->getMime();
0139 
0140     QUrl arcPath = browsableArchivePath(name);
0141     if (!arcPath.isEmpty()) {
0142         bool browseAsDirectory = !externallyExecutable
0143             || (KConfigGroup(krConfig, "Archives").readEntry("ArchivesAsDirectories", _ArchivesAsDirectories)
0144                 && (KrArcHandler::arcSupported(mime) || KrServices::isoSupported(mime)));
0145         if (browseAsDirectory) {
0146             openUrl(arcPath);
0147             return;
0148         }
0149     }
0150 
0151     if (externallyExecutable) {
0152         if (mime == QLatin1String("application/x-desktop")) {
0153 #if KIO_VERSION >= QT_VERSION_CHECK(5, 71, 0)
0154             // KJob jobs will delete themselves when they finish (see kjob.h for more info)
0155             auto *job = new KIO::OpenUrlJob(url, this);
0156             job->start();
0157 #else
0158             KDesktopFileActions::runWithStartup(url, url.isLocalFile(), QByteArray());
0159 #endif
0160             return;
0161         }
0162         if (KRun::isExecutableFile(url, mime)) {
0163             runCommand(KShell::quoteArg(url.path()));
0164             return;
0165         }
0166 
0167         KService::Ptr service = KApplicationTrader::preferredService(mime);
0168         if (service) {
0169             runService(*service, QList<QUrl>() << url);
0170             return;
0171         }
0172 
0173         displayOpenWithDialog(QList<QUrl>() << url);
0174     }
0175 }
0176 
0177 QUrl ListPanelFunc::cleanPath(const QUrl &urlIn)
0178 {
0179     QUrl url = urlIn;
0180     url.setPath(QDir::cleanPath(url.path()));
0181     if (!url.isValid() || url.isRelative()) {
0182         if (url.url() == "~")
0183             url = QUrl::fromLocalFile(QDir::homePath());
0184         else if (!url.url().startsWith('/')) {
0185             // possible relative URL - translate to full URL
0186             url = files()->currentDirectory();
0187             url.setPath(url.path() + '/' + urlIn.path());
0188         }
0189     }
0190     url.setPath(QDir::cleanPath(url.path()));
0191     return url;
0192 }
0193 
0194 void ListPanelFunc::openUrl(const QUrl &url, const QString &nameToMakeCurrent, bool manuallyEntered)
0195 {
0196     qDebug() << "URL=" << url.toDisplayString() << "; name to current=" << nameToMakeCurrent;
0197     if (panel->syncBrowseButton->isChecked()) {
0198         // do sync-browse stuff....
0199         if (syncURL.isEmpty())
0200             syncURL = panel->otherPanel()->virtualPath();
0201 
0202         QString relative = QDir(panel->virtualPath().path() + '/').relativeFilePath(url.path());
0203         syncURL.setPath(QDir::cleanPath(syncURL.path() + '/' + relative));
0204         panel->otherPanel()->gui->setTabState(ListPanel::TabState::DEFAULT);
0205         otherFunc()->openUrlInternal(syncURL, nameToMakeCurrent, false, false);
0206     }
0207     openUrlInternal(url, nameToMakeCurrent, false, manuallyEntered);
0208 }
0209 
0210 void ListPanelFunc::immediateOpenUrl(const QUrl &url)
0211 {
0212     openUrlInternal(url, QString(), true, false);
0213 }
0214 
0215 void ListPanelFunc::openUrlInternal(const QUrl &url, const QString &nameToMakeCurrent, bool immediately, bool manuallyEntered)
0216 {
0217     const QUrl cleanUrl = cleanPath(url);
0218 
0219     if (panel->isLocked() && !files()->currentDirectory().matches(cleanUrl, QUrl::StripTrailingSlash)) {
0220         panel->_manager->newTab(url);
0221         urlManuallyEntered = false;
0222         return;
0223     }
0224 
0225     urlManuallyEntered = manuallyEntered;
0226 
0227     const QString currentItem = history->currentUrl().path() == cleanUrl.path() ? history->currentItem() : nameToMakeCurrent;
0228 
0229     panel->view->setNameToMakeCurrent(nameToMakeCurrent);
0230     history->add(cleanUrl, currentItem);
0231 
0232     if (immediately)
0233         doRefresh();
0234     else
0235         refresh();
0236 }
0237 
0238 void ListPanelFunc::refresh()
0239 {
0240     panel->cancelProgress();
0241     delayTimer.start(0); // to avoid qApp->processEvents() deadlock situation
0242 }
0243 
0244 void ListPanelFunc::doRefresh()
0245 {
0246     delayTimer.stop();
0247 
0248     if (_isPaused) {
0249         _refreshAfterPaused = true;
0250         // simulate refresh
0251         panel->slotStartUpdate(true);
0252         return;
0253     } else {
0254         _refreshAfterPaused = false;
0255     }
0256 
0257     const QUrl url = history->currentUrl();
0258 
0259     if (!url.isValid()) {
0260         panel->slotStartUpdate(true); // refresh the panel
0261         urlManuallyEntered = false;
0262         return;
0263     }
0264 
0265     panel->cancelProgress();
0266 
0267     // if we are not refreshing to current URL
0268     const bool isEqualUrl = files()->currentDirectory().matches(url, QUrl::StripTrailingSlash);
0269 
0270     if (!isEqualUrl) {
0271         panel->setCursor(Qt::WaitCursor);
0272         panel->view->clearSavedSelection();
0273     }
0274 
0275     if (panel->fileSystemError) {
0276         panel->fileSystemError->hide();
0277     }
0278 
0279     panel->setNavigatorUrl(url);
0280 
0281     // may get a new filesystem for this url
0282     FileSystem *fileSystem = FileSystemProvider::instance().getFilesystem(url, files());
0283     fileSystem->setParentWindow(krMainWindow);
0284     connect(fileSystem, &FileSystem::aboutToOpenDir, &krMtMan, &KMountMan::autoMount, Qt::DirectConnection);
0285     if (fileSystem != fileSystemP) {
0286         panel->view->setFiles(nullptr);
0287 
0288         // disconnect older signals
0289         disconnect(fileSystemP, nullptr, panel, nullptr);
0290 
0291         fileSystemP->deleteLater();
0292         fileSystemP = fileSystem; // v != 0 so this is safe
0293     } else {
0294         if (fileSystemP->isRefreshing()) {
0295             // TODO remove busy waiting here
0296             delayTimer.start(100); /* if filesystem is busy try refreshing later */
0297             return;
0298         }
0299     }
0300     // (re)connect filesystem signals
0301     disconnect(files(), nullptr, panel, nullptr);
0302     connect(files(), &DirListerInterface::scanDone, panel, &ListPanel::slotStartUpdate);
0303     connect(files(), &FileSystem::fileSystemInfoChanged, panel, &ListPanel::updateFilesystemStats);
0304     connect(files(), &FileSystem::refreshJobStarted, panel, &ListPanel::slotRefreshJobStarted);
0305     connect(files(), &FileSystem::error, panel, &ListPanel::slotFilesystemError);
0306 
0307     panel->view->setFiles(files());
0308 
0309     if (!isEqualUrl || !panel->view->getCurrentKrViewItem()) {
0310         // set current item after refresh from history, if there is none yet
0311         panel->view->setNameToMakeCurrent(history->currentItem());
0312     }
0313 
0314     // workaround for detecting panel deletion while filesystem is refreshing
0315     QPointer<ListPanel> panelSave = panel;
0316     // NOTE: this is blocking. Returns false on error or interruption (cancel requested or panel
0317     // was deleted)
0318     const bool scanned = fileSystemP->refresh(url);
0319     if (scanned) {
0320         // update the history and address bar, as the actual url might differ from the one requested
0321         history->setCurrentUrl(fileSystemP->currentDirectory());
0322         panel->setNavigatorUrl(fileSystemP->currentDirectory());
0323     } else if (!panelSave) {
0324         return;
0325     }
0326 
0327     panel->view->setNameToMakeCurrent(QString());
0328 
0329     panel->setCursor(Qt::ArrowCursor);
0330 
0331     // on local file system change the working directory
0332     if (files()->isLocal())
0333         QDir::setCurrent(KrServices::urlToLocalPath(files()->currentDirectory()));
0334 
0335     // see if the open url operation failed, and if so,
0336     // put the attempted url in the navigator bar and let the user change it
0337     if (!scanned) {
0338         if (isSyncing(url))
0339             panel->otherPanel()->gui->syncBrowseButton->setChecked(false);
0340         else if (urlManuallyEntered) {
0341             panel->setNavigatorUrl(url);
0342             if (panel == ACTIVE_PANEL)
0343                 panel->editLocation();
0344         }
0345     }
0346 
0347     if (otherFunc()->otherFunc() == this) // not true if our tab is not active
0348         otherFunc()->syncURL = QUrl();
0349 
0350     urlManuallyEntered = false;
0351 
0352     refreshActions();
0353 }
0354 
0355 void ListPanelFunc::setPaused(bool paused)
0356 {
0357     if (paused == _isPaused)
0358         return;
0359     _isPaused = paused;
0360 
0361     // TODO: disable refresh() in local file system when paused
0362 
0363     if (!_isPaused && _refreshAfterPaused)
0364         refresh();
0365 }
0366 
0367 void ListPanelFunc::redirectLink()
0368 {
0369     if (!files()->isLocal()) {
0370         KMessageBox::error(krMainWindow, i18n("You can edit links only on local file systems"));
0371         return;
0372     }
0373 
0374     FileItem *fileitem = files()->getFileItem(panel->getCurrentName());
0375     if (!fileitem)
0376         return;
0377 
0378     QString file = fileitem->getUrl().path();
0379     QString currentLink = fileitem->getSymDest();
0380     if (currentLink.isEmpty()) {
0381         KMessageBox::error(krMainWindow, i18n("The current file is not a link, so it cannot be redirected."));
0382         return;
0383     }
0384 
0385     // ask the user for a new destination
0386     bool ok = false;
0387     QString newLink =
0388         QInputDialog::getText(krMainWindow, i18n("Link Redirection"), i18n("Please enter the new link destination:"), QLineEdit::Normal, currentLink, &ok);
0389 
0390     // if the user canceled - quit
0391     if (!ok || newLink == currentLink)
0392         return;
0393     // delete the current link
0394     if (unlink(file.toLocal8Bit()) == -1) {
0395         KMessageBox::error(krMainWindow, i18n("Cannot remove old link: %1", file));
0396         return;
0397     }
0398     // try to create a new symlink
0399     if (symlink(newLink.toLocal8Bit(), file.toLocal8Bit()) == -1) {
0400         KMessageBox::/* --=={ Patch by Heiner <h.eichmann@gmx.de> }==-- */ error(krMainWindow, i18n("Failed to create a new link: %1", file));
0401         return;
0402     }
0403 }
0404 
0405 void ListPanelFunc::krlink(bool sym)
0406 {
0407     if (!files()->isLocal()) {
0408         KMessageBox::error(krMainWindow, i18n("You can create links only on local file systems"));
0409         return;
0410     }
0411 
0412     QString name = panel->getCurrentName();
0413 
0414     // ask the new link name..
0415     bool ok = false;
0416     QString linkName = QInputDialog::getText(krMainWindow, i18n("New Link"), i18n("Create a new link to: %1", name), QLineEdit::Normal, name, &ok);
0417 
0418     // if the user canceled - quit
0419     if (!ok || linkName == name)
0420         return;
0421 
0422     // if the name is already taken - quit
0423     if (files()->getFileItem(linkName) != nullptr) {
0424         KMessageBox::error(krMainWindow, i18n("A folder or a file with this name already exists."));
0425         return;
0426     }
0427 
0428     // make link name and target absolute path
0429     if (linkName.left(1) != "/")
0430         linkName = files()->currentDirectory().path() + '/' + linkName;
0431     name = files()->getUrl(name).path();
0432 
0433     if (sym) {
0434         if (symlink(name.toLocal8Bit(), linkName.toLocal8Bit()) == -1)
0435             KMessageBox::error(krMainWindow, i18n("Failed to create a new symlink '%1' to: '%2'", linkName, name));
0436     } else {
0437         if (link(name.toLocal8Bit(), linkName.toLocal8Bit()) == -1)
0438             KMessageBox::error(krMainWindow, i18n("Failed to create a new link '%1' to '%2'", linkName, name));
0439     }
0440 }
0441 
0442 void ListPanelFunc::view()
0443 {
0444     panel->searchBar->hideBarIfSearching();
0445 
0446     QString fileName = panel->getCurrentName();
0447     if (fileName.isNull())
0448         return;
0449 
0450     // if we're trying to view a directory, just exit
0451     FileItem *fileitem = files()->getFileItem(fileName);
0452     if (!fileitem || fileitem->isDir())
0453         return;
0454     if (!fileitem->isReadable()) {
0455         KMessageBox::error(nullptr, i18n("No permissions to view this file."));
0456         return;
0457     }
0458     // call KViewer.
0459     KrViewer::view(files()->getUrl(fileName));
0460     // nothing more to it!
0461 }
0462 
0463 void ListPanelFunc::viewDlg()
0464 {
0465     // ask the user for a url to view
0466     QUrl dest = KChooseDir::getFile(i18n("Enter a URL to view:"), QUrl(panel->getCurrentName()), panel->virtualPath());
0467     if (dest.isEmpty())
0468         return; // the user canceled
0469 
0470     KrViewer::view(dest); // view the file
0471 }
0472 
0473 void ListPanelFunc::terminal()
0474 {
0475     SLOTS->runTerminal(panel->lastLocalPath());
0476 }
0477 
0478 void ListPanelFunc::editFile(const QUrl &filePath)
0479 {
0480     panel->searchBar->hideBarIfSearching();
0481 
0482     QUrl editPath;
0483     if (!filePath.isEmpty()) {
0484         editPath = filePath;
0485     } else {
0486         const QString name = panel->getCurrentName();
0487         if (name.isNull())
0488             return;
0489         editPath = files()->getUrl(name);
0490     }
0491 
0492     if (editPath.isLocalFile()) {
0493         const KFileItem fileToEdit = KFileItem(editPath);
0494 
0495         if (fileToEdit.isDir()) {
0496             KMessageBox::error(krMainWindow, i18n("You cannot edit a folder"));
0497             return;
0498         }
0499 
0500         if (!fileToEdit.isReadable()) {
0501             KMessageBox::error(nullptr, i18n("No permissions to edit this file."));
0502             return;
0503         }
0504 
0505         KrViewer::edit(editPath);
0506     } else {
0507         KIO::StatJob *statJob = KIO::stat(editPath, KIO::HideProgressInfo);
0508         connect(statJob, &KIO::StatJob::result, this, &ListPanelFunc::slotStatEdit);
0509     }
0510 }
0511 
0512 void ListPanelFunc::askEditFile()
0513 {
0514     // ask the user for the filename to edit
0515     const QUrl filePath = KChooseDir::getFile(i18n("Enter the filename to edit:"), QUrl(panel->getCurrentName()), panel->virtualPath());
0516     if (filePath.isEmpty()) {
0517         return; // the user canceled
0518     }
0519 
0520     if (filePath.isLocalFile()) {
0521         // if the file exists, edit it instead of creating a new one
0522         QFile file(filePath.toLocalFile());
0523         if (file.exists()) {
0524             editFile(filePath);
0525             return;
0526         } else {
0527             // simply create a local file
0528             // also because KIO::CopyJob::setDefaultPermissions does not work
0529 #if QT_VERSION >= QT_VERSION_CHECK(5, 11, 0)
0530             file.open(QIODevice::NewOnly);
0531 #else
0532             file.open(QIODevice::WriteOnly);
0533 #endif
0534             file.close();
0535             slotFileCreated(nullptr, filePath);
0536             return;
0537         }
0538     } else {
0539         KIO::StatJob *statJob = KIO::stat(filePath, KIO::HideProgressInfo);
0540         connect(statJob, &KIO::StatJob::result, this, &ListPanelFunc::slotStatEdit);
0541     }
0542 }
0543 
0544 void ListPanelFunc::slotStatEdit(KJob *job)
0545 {
0546     if (!job)
0547         return;
0548 
0549     const KIO::StatJob *statJob = dynamic_cast<KIO::StatJob *>(job);
0550     const QUrl &url = statJob->url();
0551 
0552     if (job->error()) {
0553         if (job->error() == KIO::ERR_DOES_NOT_EXIST) {
0554             // create a new file
0555             auto *tempFile = new QTemporaryFile;
0556             tempFile->setAutoRemove(false); // done below
0557             tempFile->open(); // create file
0558 
0559             KIO::CopyJob *job = KIO::copy(QUrl::fromLocalFile(tempFile->fileName()), url);
0560             job->setUiDelegate(nullptr);
0561             job->setDefaultPermissions(true);
0562             connect(job, &KIO::CopyJob::result, this, [=](KJob *job) {
0563                 slotFileCreated(job, url);
0564             });
0565             connect(job, &KIO::CopyJob::result, tempFile, &QTemporaryFile::deleteLater);
0566             return;
0567         } else {
0568             KMessageBox::error(nullptr, job->errorString());
0569             return;
0570         }
0571     }
0572 
0573     if (statJob->statResult().isDir()) {
0574         KMessageBox::error(nullptr, i18n("You cannot edit a folder"));
0575         return;
0576     }
0577 
0578     KrViewer::edit(url);
0579 }
0580 
0581 void ListPanelFunc::slotFileCreated(KJob *job, const QUrl filePath)
0582 {
0583     if (!job || (!job->error() || job->error() == KIO::ERR_FILE_ALREADY_EXIST)) {
0584         KrViewer::edit(filePath);
0585 
0586         if (KIO::upUrl(filePath).matches(panel->virtualPath(), QUrl::StripTrailingSlash)) {
0587             refresh();
0588         }
0589         if (KIO::upUrl(filePath).matches(panel->otherPanel()->virtualPath(), QUrl::StripTrailingSlash)) {
0590             otherFunc()->refresh();
0591         }
0592     } else {
0593         KMessageBox::error(krMainWindow, job->errorString());
0594     }
0595 }
0596 
0597 void ListPanelFunc::copyFiles(bool enqueue, bool move)
0598 {
0599     panel->searchBar->hideBarIfSearching();
0600 
0601     const QStringList fileNames = panel->getSelectedNames();
0602     if (fileNames.isEmpty())
0603         return; // safety
0604 
0605     QUrl destination = panel->otherPanel()->virtualPath();
0606 
0607     bool fullDestPath = false;
0608     if (fileNames.count() == 1 && otherFunc()->files()->type() != FileSystem::FS_VIRTUAL) {
0609         FileItem *item = files()->getFileItem(fileNames[0]);
0610         if (item && !item->isDir()) {
0611             fullDestPath = true;
0612             // add original filename to destination
0613             destination.setPath(QDir(destination.path()).filePath(item->getUrl().fileName()));
0614         }
0615     }
0616     if (!fullDestPath) {
0617         destination = FileSystem::ensureTrailingSlash(destination);
0618     }
0619 
0620     const KConfigGroup group(krConfig, "Advanced");
0621     const bool showDialog = move ? group.readEntry("Confirm Move", _ConfirmMove) : group.readEntry("Confirm Copy", _ConfirmCopy);
0622 
0623     if (showDialog) {
0624         QString operationText;
0625         if (move) {
0626             operationText = fileNames.count() == 1 ? i18n("Move %1 to:", fileNames.first()) : i18np("Move %1 file to:", "Move %1 files to:", fileNames.count());
0627         } else {
0628             operationText = fileNames.count() == 1 ? i18n("Copy %1 to:", fileNames.first()) : i18np("Copy %1 file to:", "Copy %1 files to:", fileNames.count());
0629         }
0630 
0631         // ask the user for the copy/move dest
0632         const KChooseDir::ChooseResult result = KChooseDir::getCopyDir(operationText, destination, panel->virtualPath());
0633         destination = result.url;
0634         if (destination.isEmpty())
0635             return; // the user canceled
0636 
0637         enqueue = result.enqueue;
0638     }
0639 
0640     const JobMan::StartMode startMode = enqueue && krJobMan->isQueueModeEnabled() ? JobMan::Delay
0641         : !enqueue && !krJobMan->isQueueModeEnabled()                             ? JobMan::Start
0642                                                                                   : JobMan::Enqueue;
0643 
0644     const QList<QUrl> fileUrls = files()->getUrls(fileNames);
0645 
0646     if (move) {
0647         // after the delete return the cursor to the first unmarked file above the current item
0648         panel->prepareToDelete();
0649     }
0650 
0651     // make sure the user does not overwrite multiple files by mistake
0652     if (fileNames.count() > 1) {
0653         destination = FileSystem::ensureTrailingSlash(destination);
0654     }
0655 
0656     const KIO::CopyJob::CopyMode mode = move ? KIO::CopyJob::Move : KIO::CopyJob::Copy;
0657     FileSystemProvider::instance().startCopyFiles(fileUrls, destination, mode, true, startMode);
0658 
0659     if (KConfigGroup(krConfig, "Look&Feel").readEntry("UnselectBeforeOperation", _UnselectBeforeOperation)) {
0660         panel->view->saveSelection();
0661         panel->view->unselectAll();
0662     }
0663 }
0664 
0665 // called from SLOTS to begin the renaming process
0666 void ListPanelFunc::rename()
0667 {
0668     panel->searchBar->hideBarIfSearching();
0669     panel->view->renameCurrentItem();
0670 }
0671 
0672 // called by signal itemRenamed() from the view to complete the renaming process
0673 void ListPanelFunc::rename(const QString &oldname, const QString &newname)
0674 {
0675     if (oldname == newname)
0676         return; // do nothing
0677 
0678     // set current after rename
0679     panel->view->setNameToMakeCurrent(newname);
0680 
0681     // as always - the filesystem do the job
0682     files()->rename(oldname, newname);
0683 }
0684 
0685 void ListPanelFunc::mkdir()
0686 {
0687     QDialog dialog;
0688     dialog.setModal(true);
0689     dialog.setWindowTitle(i18n("New Folder"));
0690 
0691     QVBoxLayout layout;
0692     dialog.setLayout(&layout);
0693 
0694     QLabel comboBoxLabel(i18n("Folder's name:"));
0695     layout.addWidget(&comboBoxLabel);
0696 
0697     KrHistoryComboBox comboBox(&dialog);
0698     comboBox.setMaxCount(50);
0699     comboBox.setMinimumContentsLength(30); // Ensure that the window title is fully seen
0700     layout.addWidget(&comboBox);
0701 
0702     QDialogButtonBox buttonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel, &dialog);
0703     layout.addWidget(&buttonBox);
0704 
0705     layout.setSizeConstraint(QLayout::SetFixedSize);
0706 
0707     connect(&buttonBox, &QDialogButtonBox::accepted, &dialog, &QDialog::accept);
0708     connect(&buttonBox, &QDialogButtonBox::rejected, &dialog, &QDialog::reject);
0709 
0710     connect(&comboBox, QOverload<const QString &>::of(&KrHistoryComboBox::QCOMBOBOX_ACTIVATED), &comboBox, &KrHistoryComboBox::addToHistory);
0711 
0712     // ------------------------------------------------------------------------
0713     // load the history and completion list after creating the KrHistoryComboBox
0714 
0715     // in the configuration file: the group where the configuration is saved
0716     const QString confGroup = "Private";
0717 
0718     KConfigGroup group(krConfig, confGroup);
0719     const QString entryName = "NewFolder";
0720 
0721     // in the configuration file: the key where the completion list is saved
0722     const QString completionListKey = entryName + " Completion list";
0723 
0724     QStringList list = group.readEntry(completionListKey, QStringList());
0725     comboBox.completionObject()->setItems(list);
0726 
0727     // in the configuration file: the key where the history list is saved
0728     const QString historyListKey = entryName + " History list";
0729 
0730     list = group.readEntry(historyListKey, QStringList());
0731     comboBox.setHistoryItems(list);
0732     // ------------------------------------------------------------------------
0733 
0734     // the suggested name is the complete name for the folders,
0735     // while filenames are suggested without their extension
0736     QString suggestedName = panel->getCurrentName();
0737     if (!suggestedName.isEmpty() && !files()->getFileItem(suggestedName)->isDir())
0738         suggestedName = QFileInfo(suggestedName).completeBaseName();
0739 
0740     comboBox.setEditText(suggestedName);
0741     comboBox.lineEdit()->selectAll();
0742 
0743     // ask the name of the new folder
0744     const auto dialogResult = dialog.exec();
0745     const QString dirName = comboBox.currentText();
0746 
0747     if (dialogResult == QDialog::Accepted)
0748         comboBox.addToHistory(dirName);
0749 
0750     // save the history and completion list
0751     list = comboBox.completionObject()->items();
0752     group.writeEntry(completionListKey, list);
0753     list = comboBox.historyItems();
0754     group.writeEntry(historyListKey, list);
0755 
0756     if (dialogResult != QDialog::Accepted)
0757         return;
0758 
0759     const QString firstName = dirName.section('/', 0, 0, QString::SectionIncludeLeadingSep);
0760     // if the user canceled or the name was composed of slashes -> quit
0761     if (!dirName.startsWith('/') && firstName.isEmpty()) {
0762         return;
0763     }
0764 
0765     // notify the user about an existing folder if only a single directory was given
0766     if (!dirName.contains('/') && files()->getFileItem(firstName)) {
0767         // focus the existing directory
0768         panel->view->setCurrentItem(firstName);
0769         // show an error message
0770         KMessageBox::error(krMainWindow, i18n("A folder or a file with this name already exists."));
0771         return;
0772     }
0773 
0774     // focus new directory when next refresh happens
0775     panel->view->setNameToMakeCurrent(firstName);
0776 
0777     // create new directory (along with underlying directories if necessary)
0778     files()->mkDir(dirName);
0779 }
0780 
0781 void ListPanelFunc::defaultOrAlternativeDeleteFiles(bool invert)
0782 {
0783     const bool trash = KConfigGroup(krConfig, "General").readEntry("Move To Trash", _MoveToTrash);
0784     deleteFiles(trash != invert);
0785 }
0786 
0787 void ListPanelFunc::deleteFiles(bool moveToTrash)
0788 {
0789     panel->searchBar->hideBarIfSearching();
0790 
0791     const bool isVFS = files()->type() == FileSystem::FS_VIRTUAL;
0792     if (isVFS && files()->isRoot()) {
0793         // only virtual deletion possible
0794         removeVirtualFiles();
0795         return;
0796     }
0797 
0798     // first get the selected file names list
0799     const QStringList fileNames = panel->getSelectedNames();
0800     if (fileNames.isEmpty())
0801         return;
0802 
0803     // move to trash: only if possible
0804     moveToTrash = moveToTrash && files()->canMoveToTrash(fileNames);
0805 
0806     // now ask the user if he/she is sure:
0807 
0808     const QList<QUrl> confirmedUrls = confirmDeletion(files()->getUrls(fileNames), moveToTrash, isVFS, false);
0809 
0810     if (confirmedUrls.isEmpty())
0811         return; // nothing to delete
0812 
0813     // after the delete return the cursor to the first unmarked
0814     // file above the current item;
0815     panel->prepareToDelete();
0816 
0817     // let the filesystem do the job...
0818     files()->deleteFiles(confirmedUrls, moveToTrash);
0819 }
0820 
0821 QList<QUrl> ListPanelFunc::confirmDeletion(const QList<QUrl> &urls, bool moveToTrash, bool virtualFS, bool showPath)
0822 {
0823     QStringList files;
0824     for (const QUrl &fileUrl : urls) {
0825         files.append(showPath ? fileUrl.toDisplayString(QUrl::PreferLocalFile) : fileUrl.fileName());
0826     }
0827 
0828     const KConfigGroup advancedGroup(krConfig, "Advanced");
0829     if (advancedGroup.readEntry("Confirm Delete", _ConfirmDelete)) {
0830         QString s; // text
0831         KGuiItem b; // continue button
0832 
0833         if (moveToTrash) {
0834             s = i18np("Do you really want to move this item to the trash?", "Do you really want to move these %1 items to the trash?", files.count());
0835             b = KGuiItem(i18n("&Trash"));
0836         } else if (virtualFS) {
0837             s = i18np(
0838                 "<qt>Do you really want to delete this item <b>physically</b> (not just "
0839                 "removing it from the virtual items)?</qt>",
0840                 "<qt>Do you really want to delete these %1 items <b>physically</b> (not just "
0841                 "removing them from the virtual items)?</qt>",
0842                 files.count());
0843             b = KStandardGuiItem::del();
0844         } else {
0845             s = i18np("Do you really want to delete this item?", "Do you really want to delete these %1 items?", files.count());
0846             b = KStandardGuiItem::del();
0847         }
0848 
0849         // show message
0850         // note: i'm using continue and not yes/no because the yes/no has cancel as default button
0851         if (KMessageBox::warningContinueCancelList(krMainWindow, s, files, i18n("Warning"), b) != KMessageBox::Continue) {
0852             return QList<QUrl>();
0853         }
0854     }
0855 
0856     // we want to warn the user about non-empty dir
0857     const bool emptyDirVerify = advancedGroup.readEntry("Confirm Unempty Dir", _ConfirmUnemptyDir);
0858 
0859     QList<QUrl> urlsMarkedForDeletion;
0860     if (emptyDirVerify) {
0861         bool deleteAllIsChosen = false;
0862         for (const QUrl &url : urls) {
0863             // NOTE: we only support verifying local files for this safeguard option
0864             if (!url.isLocalFile() || deleteAllIsChosen) {
0865                 urlsMarkedForDeletion.append(url);
0866                 continue;
0867             }
0868 
0869             bool markForDeletion = true;
0870             const QString filePath = url.toLocalFile();
0871             QFileInfo fileInfo(filePath);
0872             if (fileInfo.isDir() && !fileInfo.isSymLink()) {
0873                 // read local dir
0874                 const QDir dir(filePath);
0875                 if (!dir.entryList(QDir::AllEntries | QDir::System | QDir::Hidden | QDir::NoDotAndDotDot).isEmpty()) {
0876                     // if the dir is not empty, show a confirmation dialog with buttons:
0877                     // * Skip (-> KMessageBox::Yes)
0878                     // * Delete All (-> KMessageBox::No)
0879                     // * Cancel (-> KMessageBox::Cancel)
0880                     const QString fileString = showPath ? filePath : url.fileName();
0881                     const KMessageBox::ButtonCode result = KMessageBox::warningYesNoCancel(
0882                         krMainWindow,
0883                         i18n("<qt><p>Folder <b>%1</b> is not empty.</p>", fileString)
0884                             + (moveToTrash ? i18n("<p>Skip this one or trash all?</p></qt>") : i18n("<p>Skip this one or delete all?</p></qt>")),
0885                         QString(),
0886                         KGuiItem(i18n("&Skip")),
0887                         KGuiItem(moveToTrash ? i18n("&Trash All") : i18n("&Delete All")));
0888 
0889                     // process user response
0890                     if (result == KMessageBox::Yes) {
0891                         // skip this dir
0892                         markForDeletion = false;
0893                     } else if (result == KMessageBox::No) {
0894                         // delete all
0895                         deleteAllIsChosen = true;
0896                     } else {
0897                         // cancel
0898                         return QList<QUrl>();
0899                     }
0900                 }
0901             }
0902 
0903             if (markForDeletion)
0904                 urlsMarkedForDeletion.append(url);
0905         }
0906     } else {
0907         urlsMarkedForDeletion = urls;
0908     }
0909 
0910     return urlsMarkedForDeletion;
0911 }
0912 
0913 void ListPanelFunc::removeVirtualFiles()
0914 {
0915     if (files()->type() != FileSystem::FS_VIRTUAL) {
0916         qWarning() << "filesystem not virtual";
0917         return;
0918     }
0919 
0920     const QStringList fileNames = panel->getSelectedNames();
0921     if (fileNames.isEmpty())
0922         return;
0923 
0924     const QString text = i18np("Do you really want to delete this virtual item (physical files stay untouched)?",
0925                                "Do you really want to delete these %1 virtual items (physical files stay "
0926                                "untouched)?",
0927                                fileNames.count());
0928     if (KMessageBox::warningContinueCancelList(krMainWindow, text, fileNames, i18n("Warning"), KStandardGuiItem::remove()) != KMessageBox::Continue)
0929         return;
0930 
0931     auto *fileSystem = dynamic_cast<VirtualFileSystem *>(files());
0932     fileSystem->remove(fileNames);
0933 }
0934 
0935 void ListPanelFunc::goInside(const QString &name)
0936 {
0937     openFileNameInternal(name, false);
0938 }
0939 
0940 void ListPanelFunc::runCommand(const QString &cmd)
0941 {
0942     qDebug() << "command=" << cmd;
0943     const QString workdir = panel->virtualPath().isLocalFile() ? panel->virtualPath().path() : QDir::homePath();
0944 
0945 #if KIO_VERSION >= QT_VERSION_CHECK(5, 71, 0)
0946     /* A note from kjob.h (KIO::CommandLauncherJob is a KJob):
0947      *
0948      * KJob and its subclasses are meant to be used
0949      * in a fire-and-forget way. Jobs will delete themselves
0950      * when they finish using deleteLater() (although this
0951      * behaviour can be changed), so a job instance will
0952      * disappear after the next event loop run.
0953      */
0954     auto *job = new KIO::CommandLauncherJob(cmd, this);
0955     job->setUiDelegate(new KDialogJobUiDelegate(KJobUiDelegate::AutoHandlingEnabled, krMainWindow));
0956     job->setWorkingDirectory(workdir);
0957     job->start();
0958 #else
0959     if (!KRun::runCommand(cmd, krMainWindow, workdir))
0960         KMessageBox::error(nullptr, i18n("Could not start %1", cmd));
0961 #endif
0962 }
0963 
0964 void ListPanelFunc::runService(const KService &service, const QList<QUrl> &urls)
0965 {
0966     qDebug() << "service name=" << service.name();
0967     KIO::DesktopExecParser parser(service, urls);
0968     QStringList args = parser.resultingArguments();
0969     if (!args.isEmpty())
0970         runCommand(KShell::joinArgs(args));
0971     else
0972         KMessageBox::error(nullptr, i18n("%1 cannot open %2", service.name(), KrServices::toStringList(urls).join(", ")));
0973 }
0974 
0975 void ListPanelFunc::displayOpenWithDialog(const QList<QUrl> &urls)
0976 {
0977     // NOTE: we are not using KRun::displayOpenWithDialog() because we want the commands working
0978     // directory to be the panel directory
0979     KOpenWithDialog dialog(urls, panel);
0980     dialog.hideRunInTerminal();
0981     if (dialog.exec()) {
0982         KService::Ptr service = dialog.service();
0983         if (!service)
0984             service = KService::Ptr(new KService(dialog.text(), dialog.text(), QString()));
0985         runService(*service, urls);
0986     }
0987 }
0988 
0989 QUrl ListPanelFunc::browsableArchivePath(const QString &filename)
0990 {
0991     FileItem *fileitem = files()->getFileItem(filename);
0992     QUrl url = files()->getUrl(filename);
0993     QString mime = fileitem->getMime();
0994 
0995     if (url.isLocalFile()) {
0996         QString protocol = krArcMan.registeredProtocol(mime);
0997         if (!protocol.isEmpty()) {
0998             url.setScheme(protocol);
0999             return url;
1000         }
1001     }
1002     return QUrl();
1003 }
1004 
1005 // this is done when you double click on a file
1006 void ListPanelFunc::execute(const QString &name)
1007 {
1008     openFileNameInternal(name, true);
1009 }
1010 
1011 void ListPanelFunc::pack()
1012 {
1013     const QStringList fileNames = panel->getSelectedNames();
1014     if (fileNames.isEmpty())
1015         return; // safety
1016 
1017     if (fileNames.count() == 0)
1018         return; // nothing to pack
1019 
1020     // choose the default name
1021     QString defaultName = panel->virtualPath().fileName();
1022     if (defaultName.isEmpty())
1023         defaultName = "pack";
1024     if (fileNames.count() == 1)
1025         defaultName = fileNames.first();
1026     // ask the user for archive name and packer
1027     new PackGUI(defaultName,
1028                 panel->otherPanel()->virtualPath().toDisplayString(QUrl::PreferLocalFile | QUrl::StripTrailingSlash),
1029                 fileNames.count(),
1030                 fileNames.first());
1031     if (PackGUI::type.isEmpty()) {
1032         return; // the user canceled
1033     }
1034 
1035     // check for partial URLs
1036     if (!PackGUI::destination.contains(":/") && !PackGUI::destination.startsWith('/')) {
1037         PackGUI::destination = panel->virtualPath().toDisplayString() + '/' + PackGUI::destination;
1038     }
1039 
1040     QString destDir = PackGUI::destination;
1041     if (!destDir.endsWith('/'))
1042         destDir += '/';
1043 
1044     bool packToOtherPanel = (destDir == FileSystem::ensureTrailingSlash(panel->otherPanel()->virtualPath()).toDisplayString(QUrl::PreferLocalFile));
1045 
1046     QUrl destURL = QUrl::fromUserInput(destDir + PackGUI::filename + '.' + PackGUI::type, QString(), QUrl::AssumeLocalFile);
1047     if (destURL.isLocalFile() && QFile::exists(destURL.path())) {
1048         QString msg =
1049             i18n("<qt><p>The archive <b>%1.%2</b> already exists. Do you want to overwrite it?</p><p>All data in the previous archive will be lost.</p></qt>",
1050                  PackGUI::filename,
1051                  PackGUI::type);
1052         if (PackGUI::type == "zip") {
1053             msg = i18n(
1054                 "<qt><p>The archive <b>%1.%2</b> already exists. Do you want to overwrite it?</p><p>Zip will replace identically named entries in the zip "
1055                 "archive or add entries for new names.</p></qt>",
1056                 PackGUI::filename,
1057                 PackGUI::type);
1058         }
1059         if (KMessageBox::warningContinueCancel(krMainWindow, msg, QString(), KStandardGuiItem::overwrite()) == KMessageBox::Cancel)
1060             return; // stop operation
1061     } else if (destURL.scheme() == QStringLiteral("virt")) {
1062         KMessageBox::error(krMainWindow, i18n("Cannot pack files onto a virtual destination."));
1063         return;
1064     }
1065 
1066     PackJob *job = PackJob::createPacker(files()->currentDirectory(), destURL, fileNames, PackGUI::type, PackGUI::extraProps);
1067     job->setUiDelegate(new KIO::JobUiDelegate());
1068     KIO::getJobTracker()->registerJob(job);
1069     job->uiDelegate()->setAutoErrorHandlingEnabled(true);
1070 
1071     if (packToOtherPanel)
1072         connect(job, &PackJob::result, panel->otherPanel()->func, &ListPanelFunc::refresh);
1073 }
1074 
1075 void ListPanelFunc::testArchive()
1076 {
1077     const QStringList fileNames = panel->getSelectedNames();
1078     if (fileNames.isEmpty())
1079         return; // safety
1080 
1081     TestArchiveJob *job = TestArchiveJob::testArchives(files()->currentDirectory(), fileNames);
1082     job->setUiDelegate(new KIO::JobUiDelegate());
1083     KIO::getJobTracker()->registerJob(job);
1084     job->uiDelegate()->setAutoErrorHandlingEnabled(true);
1085 }
1086 
1087 void ListPanelFunc::unpack()
1088 {
1089     const QStringList fileNames = panel->getSelectedNames();
1090     if (fileNames.isEmpty())
1091         return; // safety
1092 
1093     QString s;
1094     if (fileNames.count() == 1)
1095         s = i18n("Unpack %1 to:", fileNames[0]);
1096     else
1097         s = i18np("Unpack %1 file to:", "Unpack %1 files to:", fileNames.count());
1098 
1099     // ask the user for the copy dest
1100     QUrl dest = KChooseDir::getDir(s, panel->otherPanel()->virtualPath(), panel->virtualPath());
1101     if (dest.isEmpty())
1102         return; // the user canceled
1103 
1104     bool packToOtherPanel = (dest.matches(panel->otherPanel()->virtualPath(), QUrl::StripTrailingSlash));
1105 
1106     UnpackJob *job = UnpackJob::createUnpacker(files()->currentDirectory(), dest, fileNames);
1107     job->setUiDelegate(new KIO::JobUiDelegate());
1108     KIO::getJobTracker()->registerJob(job);
1109     job->uiDelegate()->setAutoErrorHandlingEnabled(true);
1110 
1111     if (packToOtherPanel)
1112         connect(job, &UnpackJob::result, panel->otherPanel()->func, &ListPanelFunc::refresh);
1113 }
1114 
1115 void ListPanelFunc::createChecksum()
1116 {
1117     if (!panel->func->files()->isLocal())
1118         return; // only local, non-virtual files are supported
1119 
1120     const KrViewItemList items = panel->view->getSelectedKrViewItems();
1121 
1122     QStringList fileNames;
1123     for (KrViewItem *item : items) {
1124         FileItem *file = panel->func->getFileItem(item);
1125         fileNames.append(file->getUrl().fileName());
1126     }
1127 
1128     if (fileNames.isEmpty())
1129         return; // nothing selected and no valid current file
1130 
1131     Checksum::startCreationWizard(panel->virtualPath().toLocalFile(), fileNames);
1132 }
1133 
1134 void ListPanelFunc::matchChecksum()
1135 {
1136     if (!panel->func->files()->isLocal())
1137         return; // only local, non-virtual files are supported
1138 
1139     FileItem *currentItem = files()->getFileItem(panel->getCurrentName());
1140     const QString checksumFilePath = currentItem ? currentItem->getUrl().toLocalFile() : QString();
1141 
1142     Checksum::startVerifyWizard(panel->virtualPath().toLocalFile(), checksumFilePath);
1143 }
1144 
1145 void ListPanelFunc::calcSpace()
1146 {
1147     QStringList fileNames;
1148     panel->view->getSelectedItems(&fileNames);
1149     if (fileNames.isEmpty()) {
1150         // current file is ".." dummy file
1151         panel->view->selectAllIncludingDirs();
1152         panel->view->getSelectedItems(&fileNames);
1153         panel->view->unselectAll();
1154     }
1155 
1156     SizeCalculator *sizeCalculator = createAndConnectSizeCalculator(files()->getUrls(fileNames));
1157     KrCalcSpaceDialog::showDialog(panel, sizeCalculator);
1158 }
1159 
1160 void ListPanelFunc::quickCalcSpace()
1161 {
1162     const QString currentName = panel->getCurrentName();
1163     if (currentName.isEmpty()) {
1164         // current file is ".." dummy, do a verbose calcSpace
1165         calcSpace();
1166         return;
1167     }
1168 
1169     if (!_quickSizeCalculator) {
1170         _quickSizeCalculator = createAndConnectSizeCalculator(QList<QUrl>());
1171         panel->connectQuickSizeCalculator(_quickSizeCalculator);
1172     }
1173 
1174     _quickSizeCalculator->add(files()->getUrl(currentName));
1175 }
1176 
1177 SizeCalculator *ListPanelFunc::createAndConnectSizeCalculator(const QList<QUrl> &urls)
1178 {
1179     auto *sizeCalculator = new SizeCalculator(urls);
1180     connect(sizeCalculator, &SizeCalculator::calculated, this, &ListPanelFunc::slotSizeCalculated);
1181     connect(sizeCalculator, &SizeCalculator::finished, panel, &ListPanel::slotUpdateTotals);
1182     connect(this, &ListPanelFunc::destroyed, sizeCalculator, &SizeCalculator::deleteLater);
1183     return sizeCalculator;
1184 }
1185 
1186 void ListPanelFunc::slotSizeCalculated(const QUrl &url, KIO::filesize_t size)
1187 {
1188     KrViewItem *item = panel->view->findItemByUrl(url);
1189     if (!item)
1190         return;
1191 
1192     item->setSize(size);
1193     item->redraw();
1194 }
1195 
1196 void ListPanelFunc::FTPDisconnect()
1197 {
1198     // you can disconnect only if connected!
1199     if (files()->isRemote()) {
1200         panel->_actions->actFTPDisconnect->setEnabled(false);
1201         panel->view->setNameToMakeCurrent(QString());
1202         openUrl(QUrl::fromLocalFile(panel->lastLocalPath()));
1203     }
1204 }
1205 
1206 void ListPanelFunc::newFTPconnection()
1207 {
1208     QUrl url = KrSpWidgets::newFTP();
1209     // if the user canceled - quit
1210     if (url.isEmpty())
1211         return;
1212 
1213     panel->_actions->actFTPDisconnect->setEnabled(true);
1214 
1215     qDebug() << "URL=" << url.toDisplayString();
1216 
1217     openUrl(url);
1218 }
1219 
1220 void ListPanelFunc::properties()
1221 {
1222     const QStringList names = panel->getSelectedNames();
1223     if (names.isEmpty()) {
1224         return; // no names...
1225     }
1226 
1227     KFileItemList fileItems;
1228 
1229     for (const QString &name : names) {
1230         FileItem *fileitem = files()->getFileItem(name);
1231         if (!fileitem) {
1232             continue;
1233         }
1234 
1235         fileItems.push_back(KFileItem(fileitem->getEntry(), files()->getUrl(name)));
1236     }
1237 
1238     if (fileItems.isEmpty())
1239         return;
1240 
1241     // Show the properties dialog
1242     auto *dialog = new KPropertiesDialog(fileItems, krMainWindow);
1243     connect(dialog, &KPropertiesDialog::applied, this, &ListPanelFunc::refresh);
1244     dialog->show();
1245 }
1246 
1247 void ListPanelFunc::refreshActions()
1248 {
1249     panel->updateButtons();
1250 
1251     if (ACTIVE_PANEL != panel)
1252         return;
1253 
1254     QString protocol = files()->currentDirectory().scheme();
1255     krRemoteEncoding->setEnabled(protocol == "ftp" || protocol == "sftp" || protocol == "fish" || protocol == "krarc");
1256     // krMultiRename->setEnabled( fileSystemType == FileSystem::FS_NORMAL );  // batch rename
1257     // krProperties ->setEnabled( fileSystemType == FileSystem::FS_NORMAL || fileSystemType == FileSystem::FS_FTP ); // file properties
1258 
1259     /*
1260       krUnpack->setEnabled(true);                            // unpack archive
1261       krTest->setEnabled(true);                              // test archive
1262       krSelect->setEnabled(true);                            // select a group by filter
1263       krSelectAll->setEnabled(true);                         // select all files
1264       krUnselect->setEnabled(true);                          // unselect by filter
1265       krUnselectAll->setEnabled( true);                      // remove all selections
1266       krInvert->setEnabled(true);                            // invert the selection
1267       krFTPConnect->setEnabled(true);                        // connect to an ftp
1268       krFTPNew->setEnabled(true);                            // create a new connection
1269       krAllFiles->setEnabled(true);                          // show all files in list
1270       krCustomFiles->setEnabled(true);                       // show a custom set of files
1271       krRoot->setEnabled(true);                              // go all the way up
1272           krExecFiles->setEnabled(true);                         // show only executables
1273     */
1274 
1275     panel->_actions->setViewActions[panel->panelType]->setChecked(true);
1276     panel->_actions->actFTPDisconnect->setEnabled(files()->isRemote()); // allow disconnecting a network session
1277     panel->_actions->actCreateChecksum->setEnabled(files()->isLocal());
1278     panel->_actions->actDirUp->setEnabled(!files()->isRoot());
1279     panel->_actions->actRoot->setEnabled(!panel->virtualPath().matches(QUrl::fromLocalFile(ROOT_DIR), QUrl::StripTrailingSlash));
1280     panel->_actions->actHome->setEnabled(!atHome());
1281     panel->_actions->actHistoryBackward->setEnabled(history->canGoBack());
1282     panel->_actions->actHistoryForward->setEnabled(history->canGoForward());
1283     panel->view->op()->emitRefreshActions();
1284 }
1285 
1286 FileSystem *ListPanelFunc::files()
1287 {
1288     if (!fileSystemP)
1289         fileSystemP = FileSystemProvider::instance().getFilesystem(QUrl::fromLocalFile(ROOT_DIR));
1290     return fileSystemP;
1291 }
1292 
1293 QUrl ListPanelFunc::virtualDirectory()
1294 {
1295     return _isPaused ? history->currentUrl() : files()->currentDirectory();
1296 }
1297 
1298 FileItem *ListPanelFunc::getFileItem(const QString &name)
1299 {
1300     return files()->getFileItem(name);
1301 }
1302 
1303 FileItem *ListPanelFunc::getFileItem(KrViewItem *item)
1304 {
1305     return files()->getFileItem(item->name());
1306 }
1307 
1308 void ListPanelFunc::clipboardChanged(QClipboard::Mode mode)
1309 {
1310     if (mode == QClipboard::Clipboard && this == copyToClipboardOrigin) {
1311         disconnect(QApplication::clipboard(), nullptr, this, nullptr);
1312         copyToClipboardOrigin = nullptr;
1313     }
1314 }
1315 
1316 void ListPanelFunc::copyToClipboard(bool move)
1317 {
1318     const QStringList fileNames = panel->getSelectedNames();
1319     if (fileNames.isEmpty())
1320         return; // safety
1321 
1322     QList<QUrl> fileUrls = files()->getUrls(fileNames);
1323     auto *mimeData = new QMimeData;
1324     mimeData->setData("application/x-kde-cutselection", move ? "1" : "0");
1325     mimeData->setUrls(fileUrls);
1326 
1327     if (copyToClipboardOrigin)
1328         disconnect(QApplication::clipboard(), nullptr, copyToClipboardOrigin, nullptr);
1329     copyToClipboardOrigin = this;
1330 
1331     QApplication::clipboard()->setMimeData(mimeData, QClipboard::Clipboard);
1332 
1333     connect(QApplication::clipboard(), &QClipboard::changed, this, &ListPanelFunc::clipboardChanged);
1334 }
1335 
1336 void ListPanelFunc::pasteFromClipboard()
1337 {
1338     QClipboard *cb = QApplication::clipboard();
1339 
1340     ListPanelFunc *origin = nullptr;
1341 
1342     if (copyToClipboardOrigin) {
1343         disconnect(QApplication::clipboard(), nullptr, copyToClipboardOrigin, nullptr);
1344         origin = copyToClipboardOrigin;
1345         copyToClipboardOrigin = nullptr;
1346     }
1347 
1348     bool move = false;
1349     const QMimeData *data = cb->mimeData();
1350     if (data->hasFormat("application/x-kde-cutselection")) {
1351         QByteArray a = data->data("application/x-kde-cutselection");
1352         if (!a.isEmpty())
1353             move = (a.at(0) == '1'); // true if 1
1354     }
1355 
1356     QList<QUrl> urls = data->urls();
1357     if (urls.isEmpty())
1358         return;
1359 
1360     if (origin && KConfigGroup(krConfig, "Look&Feel").readEntry("UnselectBeforeOperation", _UnselectBeforeOperation)) {
1361         origin->panel->view->saveSelection();
1362         for (KrViewItem *item = origin->panel->view->getFirst(); item != nullptr; item = origin->panel->view->getNext(item)) {
1363             if (urls.contains(item->getFileItem()->getUrl()))
1364                 item->setSelected(false);
1365         }
1366     }
1367 
1368     files()->addFiles(urls, move ? KIO::CopyJob::Move : KIO::CopyJob::Copy);
1369 }
1370 
1371 ListPanelFunc *ListPanelFunc::otherFunc()
1372 {
1373     return panel->otherPanel()->func;
1374 }
1375 
1376 void ListPanelFunc::historyGotoPos(int pos)
1377 {
1378     if (history->gotoPos(pos))
1379         refresh();
1380 }
1381 
1382 void ListPanelFunc::historyBackward()
1383 {
1384     if (history->goBack())
1385         refresh();
1386 }
1387 
1388 void ListPanelFunc::historyForward()
1389 {
1390     if (history->goForward())
1391         refresh();
1392 }
1393 
1394 void ListPanelFunc::dirUp()
1395 {
1396     openUrl(KIO::upUrl(files()->currentDirectory()), files()->currentDirectory().fileName());
1397 }
1398 
1399 void ListPanelFunc::home()
1400 {
1401     openUrl(QUrl::fromLocalFile(QDir::homePath()));
1402 }
1403 
1404 void ListPanelFunc::root()
1405 {
1406     openUrl(QUrl::fromLocalFile(ROOT_DIR));
1407 }
1408 
1409 void ListPanelFunc::cdToOtherPanel()
1410 {
1411     openUrl(panel->otherPanel()->virtualPath());
1412 }
1413 
1414 void ListPanelFunc::syncOtherPanel()
1415 {
1416     otherFunc()->openUrl(panel->virtualPath());
1417 }
1418 
1419 bool ListPanelFunc::atHome()
1420 {
1421     return QUrl::fromLocalFile(QDir::homePath()).matches(panel->virtualPath(), QUrl::StripTrailingSlash);
1422 }