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 }