diff --git a/krusader/Archive/abstractthreadedjob.cpp b/krusader/Archive/abstractthreadedjob.cpp index 69b2ab9b..1070bef2 100644 --- a/krusader/Archive/abstractthreadedjob.cpp +++ b/krusader/Archive/abstractthreadedjob.cpp @@ -1,664 +1,664 @@ /***************************************************************************** * Copyright (C) 2009 Csaba Karai * * Copyright (C) 2009-2018 Krusader Krew [https://krusader.org] * * * * This file is part of Krusader [https://krusader.org]. * * * * Krusader is free software: you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation, either version 2 of the License, or * * (at your option) any later version. * * * * Krusader is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with Krusader. If not, see [http://www.gnu.org/licenses/]. * *****************************************************************************/ #include "abstractthreadedjob.h" // QtCore #include #include #include #include #include #include // QtWidgets #include #include #include #include #include "krarchandler.h" #include "../krglobal.h" #include "../krservices.h" #include "../FileSystem/filesystemprovider.h" extern KRarcHandler arcHandler; AbstractThreadedJob::AbstractThreadedJob() : KIO::Job(), _locker(), _waiter(), _stack(), _maxProgressValue(0), _currentProgress(0), _exiting(false), _jobThread(nullptr) { } void AbstractThreadedJob::startAbstractJobThread(AbstractJobThread * jobThread) { _jobThread = jobThread; _jobThread->setJob(this); _jobThread->moveToThread(_jobThread); _jobThread->start(); } AbstractThreadedJob::~AbstractThreadedJob() { _exiting = true; if (_jobThread) { _jobThread->abort(); _locker.lock(); _waiter.wakeAll(); _locker.unlock(); _jobThread->wait(); delete _jobThread; } } bool AbstractThreadedJob::event(QEvent *e) { if (e->type() == QEvent::User) { auto *event = (UserEvent*) e; switch (event->command()) { case CMD_SUCCESS: { emitResult(); } break; case CMD_ERROR: { auto error = event->args()[ 0 ].value(); QString errorText = event->args()[ 1 ].value(); setError(error); setErrorText(errorText); emitResult(); } break; case CMD_INFO: { QString info = event->args()[ 0 ].value(); QString arg1 = event->args()[ 1 ].value(); QString arg2 = event->args()[ 2 ].value(); QString arg3 = event->args()[ 3 ].value(); QString arg4 = event->args()[ 4 ].value(); _title = info; emit description(this, info, qMakePair(arg1, arg2), qMakePair(arg3, arg4)); } break; case CMD_RESET: { QString info = event->args()[ 0 ].value(); QString arg1 = event->args()[ 1 ].value(); QString arg2 = event->args()[ 2 ].value(); QString arg3 = event->args()[ 3 ].value(); QString arg4 = event->args()[ 4 ].value(); _title = info; setProcessedAmount(KJob::Bytes, 0); setTotalAmount(KJob::Bytes, 0); emitSpeed(0); emit description(this, info, qMakePair(arg1, arg2), qMakePair(arg3, arg4)); } break; case CMD_UPLOAD_FILES: case CMD_DOWNLOAD_FILES: { QList sources = KrServices::toUrlList(event->args()[ 0 ].value()); QUrl dest = event->args()[ 1 ].value(); KIO::Job *job = KIO::copy(sources, dest, KIO::HideProgressInfo); addSubjob(job); job->setUiDelegate(new KIO::JobUiDelegate()); connect(job, &KIO::Job::result, this, &AbstractThreadedJob::slotDownloadResult); connect(job, SIGNAL(processedAmount(KJob*,KJob::Unit,qulonglong)), this, SLOT(slotProcessedAmount(KJob*,KJob::Unit,qulonglong))); connect(job, SIGNAL(totalAmount(KJob*,KJob::Unit,qulonglong)), this, SLOT(slotTotalAmount(KJob*,KJob::Unit,qulonglong))); connect(job, SIGNAL(speed(KJob*,ulong)), this, SLOT(slotSpeed(KJob*,ulong))); connect(job, SIGNAL(description(KJob*,QString,QPair,QPair)), this, SLOT(slotDescription(KJob*,QString,QPair,QPair))); } break; case CMD_MAXPROGRESSVALUE: { auto maxValue = event->args()[ 0 ].value(); _maxProgressValue = maxValue; _currentProgress = 0; } break; case CMD_ADD_PROGRESS: { auto progress = event->args()[ 0 ].value(); _currentProgress += progress; if (_maxProgressValue != 0) { setPercent(100 * _currentProgress / _maxProgressValue); int elapsed = _time.isNull() ? 1 : _time.secsTo(QTime::currentTime()); if (elapsed != 0 && event->args().count() > 1) { _time = QTime::currentTime(); QString progressString = (event->args()[ 1 ].value()); emit description(this, _title, qMakePair(progressString, QString("%1/%2").arg(_currentProgress).arg(_maxProgressValue)), qMakePair(QString(), QString()) ); } } } break; case CMD_GET_PASSWORD: { QString path = event->args()[ 0 ].value(); QString password = KRarcHandler::getPassword(path); auto *resultResp = new QList (); (*resultResp) << password; addEventResponse(resultResp); } break; case CMD_MESSAGE: { QString message = event->args()[ 0 ].value(); auto *ui = dynamic_cast(uiDelegate()); KMessageBox::information(ui ? ui->window() : nullptr, message); auto *resultResp = new QList (); addEventResponse(resultResp); } break; } return true; } else { return KIO::Job::event(e); } } void AbstractThreadedJob::addEventResponse(QList * obj) { _locker.lock(); _stack.push(obj); _waiter.wakeOne(); _locker.unlock(); } QList * AbstractThreadedJob::getEventResponse(UserEvent * event) { _locker.lock(); QApplication::postEvent(this, event); _waiter.wait(&_locker); if (_exiting) return nullptr; QList *resp = _stack.pop(); _locker.unlock(); return resp; } void AbstractThreadedJob::sendEvent(UserEvent * event) { QApplication::postEvent(this, event); } void AbstractThreadedJob::slotDownloadResult(KJob* job) { auto *resultResp = new QList (); if (job) { (*resultResp) << QVariant(job->error()); (*resultResp) << QVariant(job->errorText()); } else { (*resultResp) << QVariant(KJob::UserDefinedError); (*resultResp) << QVariant(QString(i18n("Internal error, undefined in result signal"))); } addEventResponse(resultResp); } void AbstractThreadedJob::slotProcessedAmount(KJob *, KJob::Unit unit, qulonglong xu) { setProcessedAmount(unit, xu); } void AbstractThreadedJob::slotTotalAmount(KJob *, KJob::Unit unit, qulonglong xu) { setTotalAmount(unit, xu); } void AbstractThreadedJob::slotSpeed(KJob *, unsigned long spd) { emitSpeed(spd); } void AbstractThreadedJob::slotDescription(KJob *, const QString &title, const QPair &field1, const QPair &field2) { QString mytitle = title; if (!_title.isNull()) mytitle = _title; emit description(this, mytitle, field1, field2); } class AbstractJobObserver : public KRarcObserver { protected: AbstractJobThread * _jobThread; public: explicit AbstractJobObserver(AbstractJobThread * thread): _jobThread(thread) {} ~AbstractJobObserver() override = default; void processEvents() Q_DECL_OVERRIDE { usleep(1000); qApp->processEvents(); } void subJobStarted(const QString & jobTitle, int count) Q_DECL_OVERRIDE { _jobThread->sendReset(jobTitle); _jobThread->sendMaxProgressValue(count); } void subJobStopped() Q_DECL_OVERRIDE { } bool wasCancelled() Q_DECL_OVERRIDE { return _jobThread->_exited; } void error(const QString & error) Q_DECL_OVERRIDE { _jobThread->sendError(KIO::ERR_NO_CONTENT, error); } void detailedError(const QString & error, const QString & details) Q_DECL_OVERRIDE { _jobThread->sendError(KIO::ERR_NO_CONTENT, error + '\n' + details); } void incrementProgress(int c) Q_DECL_OVERRIDE { _jobThread->sendAddProgress(c, _jobThread->_progressTitle); } }; AbstractJobThread::AbstractJobThread() : _job(nullptr), _downloadTempDir(nullptr), _observer(nullptr), _tempFile(nullptr), _tempDir(nullptr), _exited(false) { } AbstractJobThread::~AbstractJobThread() { if (_downloadTempDir) { delete _downloadTempDir; _downloadTempDir = nullptr; } if (_observer) { delete _observer; _observer = nullptr; } if (_tempFile) { delete _tempFile; _tempFile = nullptr; } } void AbstractJobThread::run() { QTimer::singleShot(0, this, &AbstractJobThread::slotStart); QPointer threadLoop = new QEventLoop(this); _loop = threadLoop; threadLoop->exec(); _loop = nullptr; delete threadLoop; } void AbstractJobThread::terminate() { if (_loop && !_exited) { _loop->quit(); _exited = true; } } void AbstractJobThread::abort() { terminate(); } QList AbstractJobThread::remoteUrls(const QUrl &baseUrl, const QStringList & files) { QList urlList; foreach(const QString &name, files) { QUrl url = baseUrl; url = url.adjusted(QUrl::StripTrailingSlash); url.setPath(url.path() + '/' + (name)); urlList << url; } return urlList; } QUrl AbstractJobThread::downloadIfRemote(const QUrl &baseUrl, const QStringList & files) { // download remote URL-s if necessary if (!baseUrl.isLocalFile()) { sendInfo(i18n("Downloading remote files")); _downloadTempDir = new QTemporaryDir(); QList urlList = remoteUrls(baseUrl, files); QUrl dest(_downloadTempDir->path()); QList args; args << KrServices::toStringList(urlList); args << dest; auto * downloadEvent = new UserEvent(CMD_DOWNLOAD_FILES, args); QList * result = _job->getEventResponse(downloadEvent); if (result == nullptr) return QUrl(); auto errorCode = (*result)[ 0 ].value(); QString errorText = (*result)[ 1 ].value(); delete result; if (errorCode) { sendError(errorCode, errorText); return QUrl(); } else { return dest; } } else { return baseUrl; } } QString AbstractJobThread::tempFileIfRemote(const QUrl &kurl, const QString &type) { if (kurl.isLocalFile()) { return kurl.path(); } _tempFile = new QTemporaryFile(QDir::tempPath() + QLatin1String("/krusader_XXXXXX.") + type); _tempFile->open(); _tempFileName = _tempFile->fileName(); _tempFile->close(); // necessary to create the filename QFile::remove(_tempFileName); _tempFileTarget = kurl; return _tempFileName; } QString AbstractJobThread::tempDirIfRemote(const QUrl &kurl) { if (kurl.isLocalFile()) { return kurl.adjusted(QUrl::StripTrailingSlash).path(); } _tempDir = new QTemporaryDir(); _tempDirTarget = kurl; return _tempDirName = _tempDir->path(); } void AbstractJobThread::sendSuccess() { terminate(); QList args; auto * errorEvent = new UserEvent(CMD_SUCCESS, args); _job->sendEvent(errorEvent); } void AbstractJobThread::sendError(int errorCode, const QString& message) { terminate(); QList args; args << errorCode; args << message; auto * errorEvent = new UserEvent(CMD_ERROR, args); _job->sendEvent(errorEvent); } void AbstractJobThread::sendInfo(const QString& message, const QString& a1, const QString& a2, const QString& a3, const QString& a4) { QList args; args << message; args << a1; args << a2; args << a3; args << a4; auto * infoEvent = new UserEvent(CMD_INFO, args); _job->sendEvent(infoEvent); } void AbstractJobThread::sendReset(const QString& message, const QString& a1, const QString& a2, const QString& a3, const QString& a4) { QList args; args << message; args << a1; args << a2; args << a3; args << a4; auto * infoEvent = new UserEvent(CMD_RESET, args); _job->sendEvent(infoEvent); } void AbstractJobThread::sendMaxProgressValue(qulonglong value) { QList args; args << value; auto * infoEvent = new UserEvent(CMD_MAXPROGRESSVALUE, args); _job->sendEvent(infoEvent); } void AbstractJobThread::sendAddProgress(qulonglong value, const QString &progress) { QList args; args << value; if (!progress.isNull()) args << progress; auto * infoEvent = new UserEvent(CMD_ADD_PROGRESS, args); _job->sendEvent(infoEvent); } void countFiles(const QString &path, unsigned long &totalFiles, bool &stop) { const QDir dir(path); if (!dir.exists()) { totalFiles++; // assume it's a file return; } - for (const QString name : dir.entryList()) { + for (const QString& name : dir.entryList()) { if (stop) return; if (name == QStringLiteral(".") || name == QStringLiteral("..")) continue; countFiles(dir.absoluteFilePath(name), totalFiles, stop); } } void AbstractJobThread::countLocalFiles(const QUrl &baseUrl, const QStringList &names, unsigned long &totalFiles) { sendReset(i18n("Counting files")); FileSystem *calcSpaceFileSystem = FileSystemProvider::instance().getFilesystem(baseUrl); calcSpaceFileSystem->scanDir(baseUrl); - for (const QString name : names) { + for (const QString& name : names) { if (_exited) return; const QString path = calcSpaceFileSystem->getUrl(name).toLocalFile(); if (!QFileInfo(path).exists()) return; countFiles(path, totalFiles, _exited); } delete calcSpaceFileSystem; } KRarcObserver * AbstractJobThread::observer() { if (_observer) return _observer; _observer = new AbstractJobObserver(this); return _observer; } bool AbstractJobThread::uploadTempFiles() { if (_tempFile != nullptr || _tempDir != nullptr) { sendInfo(i18n("Uploading to remote destination")); if (_tempFile) { QList urlList; urlList << QUrl::fromLocalFile(_tempFileName); QList args; args << KrServices::toStringList(urlList); args << _tempFileTarget; auto * uploadEvent = new UserEvent(CMD_UPLOAD_FILES, args); QList * result = _job->getEventResponse(uploadEvent); if (result == nullptr) return false; auto errorCode = (*result)[ 0 ].value(); QString errorText = (*result)[ 1 ].value(); delete result; if (errorCode) { sendError(errorCode, errorText); return false; } } if (_tempDir) { QList urlList; QDir tempDir(_tempDirName); QStringList list = tempDir.entryList(); foreach(const QString &name, list) { if (name == "." || name == "..") continue; QUrl url = QUrl::fromLocalFile(_tempDirName).adjusted(QUrl::StripTrailingSlash); url.setPath(url.path() + '/' + (name)); urlList << url; } QList args; args << KrServices::toStringList(urlList); args << _tempDirTarget; auto * uploadEvent = new UserEvent(CMD_UPLOAD_FILES, args); QList * result = _job->getEventResponse(uploadEvent); if (result == nullptr) return false; auto errorCode = (*result)[ 0 ].value(); QString errorText = (*result)[ 1 ].value(); delete result; if (errorCode) { sendError(errorCode, errorText); return false; } } } return true; } QString AbstractJobThread::getPassword(const QString &path) { QList args; args << path; auto * getPasswdEvent = new UserEvent(CMD_GET_PASSWORD, args); QList * result = _job->getEventResponse(getPasswdEvent); if (result == nullptr) return QString(); QString password = (*result)[ 0 ].value(); if (password.isNull()) password = QString(""); delete result; return password; } void AbstractJobThread::sendMessage(const QString &message) { QList args; args << message; auto * getPasswdEvent = new UserEvent(CMD_MESSAGE, args); QList * result = _job->getEventResponse(getPasswdEvent); if (result == nullptr) return; delete result; } //! Gets some archive information that is needed in several cases. /*! \param path A path to the archive. \param type The type of the archive. \param password The password of the archive. \param arcName The name of the archive. \param sourceFolder A QUrl, which may be remote, of the folder where the archive is. \return If the archive information has been obtained. */ bool AbstractJobThread::getArchiveInformation(QString &path, QString &type, QString &password, QString &arcName, const QUrl &sourceFolder) { // Safety checks (though the user shouldn't have been able to select something named "" or "..") if (arcName.isEmpty()) return false; if (arcName == "..") return false; QUrl url = sourceFolder.adjusted(QUrl::StripTrailingSlash); url.setPath(url.path() + '/' + arcName); path = url.adjusted(QUrl::StripTrailingSlash).path(); QMimeDatabase db; QMimeType mt = db.mimeTypeForUrl(url); QString mime = mt.isValid() ? mt.name() : QString(); bool encrypted = false; type = arcHandler.getType(encrypted, path, mime); // Check that the archive is supported if (!KRarcHandler::arcSupported(type)) { sendError(KIO::ERR_NO_CONTENT, i18nc("%1=archive filename", "%1, unsupported archive type.", arcName)); return false; } password = encrypted ? getPassword(path) : QString(); return true; } diff --git a/krusader/Dialogs/checksumdlg.cpp b/krusader/Dialogs/checksumdlg.cpp index 1033cca2..6fcace1f 100644 --- a/krusader/Dialogs/checksumdlg.cpp +++ b/krusader/Dialogs/checksumdlg.cpp @@ -1,576 +1,576 @@ /***************************************************************************** * Copyright (C) 2005 Shie Erlich * * Copyright (C) 2007-2008 Csaba Karai * * Copyright (C) 2008 Jonas Bähr * * Copyright (C) 2005-2018 Krusader Krew [https://krusader.org] * * * * This file is part of Krusader [https://krusader.org]. * * * * Krusader is free software: you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation, either version 2 of the License, or * * (at your option) any later version. * * * * Krusader is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with Krusader. If not, see [http://www.gnu.org/licenses/]. * *****************************************************************************/ #include "checksumdlg.h" #include "../krglobal.h" #include "../icon.h" #include "../krservices.h" #include "../krusader.h" #include "../GUI/krlistwidget.h" #include "../GUI/krtreewidget.h" // QtCore #include #include #include #include #include #include // QtWidgets #include #include #include #include #include #include // krazy:exclude=includes #include #include #include void Checksum::startCreationWizard(const QString &path, const QStringList &files) { if (files.isEmpty()) return; QDialog *dialog = new CHECKSUM_::CreateWizard(path, files); dialog->show(); } void Checksum::startVerifyWizard(const QString &path, const QString &checksumFile) { QDialog *dialog = new CHECKSUM_::VerifyWizard(path, checksumFile); dialog->show(); } namespace CHECKSUM_ { bool stopListFiles; // async operation invoked by QtConcurrent::run in creation wizard QStringList listFiles(const QString &path, const QStringList &fileNames) { const QDir baseDir(path); QStringList allFiles; - for (const QString fileName : fileNames) { + for (const QString& fileName : fileNames) { if (stopListFiles) return QStringList(); QDir subDir = QDir(baseDir.filePath(fileName)); if (subDir.exists()) { subDir.setFilter(QDir::Files); QDirIterator it(subDir, QDirIterator::Subdirectories | QDirIterator::FollowSymlinks); while (it.hasNext()) { if (stopListFiles) return QStringList(); allFiles << baseDir.relativeFilePath(it.next()); } } else { // assume this is a file allFiles << fileName; } } return allFiles; } // ------------- Checksum Process ChecksumProcess::ChecksumProcess(QObject *parent, const QString &path) : KProcess(parent), m_tmpOutFile(QDir::tempPath() + QLatin1String("/krusader_XXXXXX.stdout")), m_tmpErrFile(QDir::tempPath() + QLatin1String("/krusader_XXXXXX.stderr")) { m_tmpOutFile.open(); // necessary to create the filename m_tmpErrFile.open(); // necessary to create the filename setOutputChannelMode(KProcess::SeparateChannels); // without this the next 2 lines have no effect! setStandardOutputFile(m_tmpOutFile.fileName()); setStandardErrorFile(m_tmpErrFile.fileName()); setWorkingDirectory(path); #if QT_VERSION >= QT_VERSION_CHECK(5, 6, 0) connect(this, &ChecksumProcess::errorOccurred, this, &ChecksumProcess::slotError); #endif connect(this, static_cast(&QProcess::finished), this, &ChecksumProcess::slotFinished); } ChecksumProcess::~ChecksumProcess() { disconnect(this, nullptr, this, nullptr); // QProcess emits finished() on destruction close(); } void ChecksumProcess::slotError(QProcess::ProcessError error) { if (error == QProcess::FailedToStart) { KMessageBox::error(nullptr, i18n("Could not start %1.", program().join(" "))); } } void ChecksumProcess::slotFinished(int, QProcess::ExitStatus exitStatus) { if (exitStatus != QProcess::NormalExit) { KMessageBox::error(nullptr, i18n("There was an error while running %1.", program().join(" "))); return; } // parse result files if (!KrServices::fileToStringList(&m_tmpOutFile, m_outputLines) || !KrServices::fileToStringList(&m_tmpErrFile, m_errorLines)) { KMessageBox::error(nullptr, i18n("Error reading stdout or stderr")); return; } emit resultReady(); } // ------------- Generic Checksum Wizard ChecksumWizard::ChecksumWizard(const QString &path) : QWizard(krApp), m_path(path), m_process(nullptr) { setAttribute(Qt::WA_DeleteOnClose); // init the dictionary - pity it has to be manually m_checksumTools.insert("md5", "md5sum"); m_checksumTools.insert("sha1", "sha1sum"); m_checksumTools.insert("sha256", "sha256sum"); m_checksumTools.insert("sha224", "sha224sum"); m_checksumTools.insert("sha384", "sha384sum"); m_checksumTools.insert("sha512", "sha512sum"); connect(this, &QWizard::currentIdChanged, this, &ChecksumWizard::slotCurrentIdChanged); } ChecksumWizard::~ChecksumWizard() { if (m_process) { delete m_process; } } void ChecksumWizard::slotCurrentIdChanged(int id) { if (id == m_introId) { onIntroPage(); } else if (id == m_progressId) { if (m_process) { // we are coming from the result page; delete m_process; m_process = nullptr; restart(); } else { button(QWizard::BackButton)->hide(); button(QWizard::NextButton)->hide(); onProgressPage(); } } else if (id == m_resultId) { onResultPage(); } } QWizardPage *ChecksumWizard::createProgressPage(const QString &title) { auto *page = new QWizardPage; page->setTitle(title); page->setPixmap(QWizard::LogoPixmap, Icon("process-working").pixmap(32)); page->setSubTitle(i18n("Please wait...")); auto *mainLayout = new QVBoxLayout; page->setLayout(mainLayout); // "busy" indicator auto *bar = new QProgressBar(); bar->setRange(0,0); mainLayout->addWidget(bar); return page; } bool ChecksumWizard::checkExists(const QString& type) { if (!KrServices::cmdExist(m_checksumTools[type])) { KMessageBox::error( this, i18n("Krusader cannot find a checksum tool that handles %1 on your system. " "Please check the Dependencies page in Krusader's settings.", type)); return false; } return true; } void ChecksumWizard::runProcess(const QString &type, const QStringList &args) { Q_ASSERT(m_process == nullptr); m_process = new ChecksumProcess(this, m_path); m_process->setProgram(KrServices::fullPathName(m_checksumTools[type]), args); // show next page (with results) (only) when process is done connect(m_process, &ChecksumProcess::resultReady, this, &QWizard::next); // run the process m_process->start(); } void ChecksumWizard::addChecksumLine(KrTreeWidget *tree, const QString &line) { auto *item = new QTreeWidgetItem(tree); const int hashLength = line.indexOf(' '); // delimiter is either " " or " *" item->setText(0, line.left(hashLength)); QString fileName = line.mid(hashLength + 2); if (fileName.endsWith('\n')) fileName.chop(1); item->setText(1, fileName); } // ------------- Create Wizard CreateWizard::CreateWizard(const QString &path, const QStringList &_files) : ChecksumWizard(path), m_fileNames(_files), m_listFilesWatcher() { m_introId = addPage(createIntroPage()); m_progressId = addPage(createProgressPage(i18n("Creating Checksums"))); m_resultId = addPage(createResultPage()); setButton(QWizard::FinishButton, QDialogButtonBox(QDialogButtonBox::Save).button(QDialogButtonBox::Save)); connect(&m_listFilesWatcher, &QFutureWatcher::resultReadyAt, this, &CreateWizard::createChecksums); } QWizardPage *CreateWizard::createIntroPage() { auto *page = new QWizardPage; page->setTitle(i18n("Create Checksums")); page->setPixmap(QWizard::LogoPixmap, Icon("document-edit-sign").pixmap(32)); page->setSubTitle(i18n("About to calculate checksum for the following files or directories:")); auto *mainLayout = new QVBoxLayout; page->setLayout(mainLayout); // file list auto *listWidget = new KrListWidget; listWidget->addItems(m_fileNames); mainLayout->addWidget(listWidget); // checksum method auto *hLayout = new QHBoxLayout; QLabel *methodLabel = new QLabel(i18n("Select the checksum method:")); hLayout->addWidget(methodLabel); m_methodBox = new KComboBox; // -- fill the combo with available methods - for (const QString type: m_checksumTools.keys()) + for (const QString& type: m_checksumTools.keys()) m_methodBox->addItem(type); m_methodBox->setFocus(); hLayout->addWidget(m_methodBox); mainLayout->addLayout(hLayout); return page; } QWizardPage *CreateWizard::createResultPage() { auto *page = new QWizardPage; page->setTitle(i18n("Checksum Results")); auto *mainLayout = new QVBoxLayout; page->setLayout(mainLayout); m_hashesTreeWidget = new KrTreeWidget(this); m_hashesTreeWidget->setAllColumnsShowFocus(true); m_hashesTreeWidget->setHeaderLabels(QStringList() << i18n("Hash") << i18n("File")); mainLayout->addWidget(m_hashesTreeWidget); m_errorLabel = new QLabel(i18n("Errors received:")); mainLayout->addWidget(m_errorLabel); m_errorListWidget = new KrListWidget; mainLayout->addWidget(m_errorListWidget); m_onePerFileBox = new QCheckBox(i18n("Save one checksum file for each source file")); m_onePerFileBox->setChecked(false); mainLayout->addWidget(m_onePerFileBox); return page; } void CreateWizard::onIntroPage() { button(QWizard::NextButton)->show(); } void CreateWizard::onProgressPage() { // first, get all files (recurse in directories) - async stopListFiles = false; // QFuture cannot cancel QtConcurrent::run connect(this, &CreateWizard::finished, this, [=]() { stopListFiles = true; }); QFuture listFuture = QtConcurrent::run(listFiles, m_path, m_fileNames); m_listFilesWatcher.setFuture(listFuture); } void CreateWizard::createChecksums() { const QString type = m_methodBox->currentText(); if (!checkExists(type)) { button(QWizard::BackButton)->show(); return; } const QStringList &allFiles = m_listFilesWatcher.result(); if (allFiles.isEmpty()) { KMessageBox::error(this, i18n("No files found")); button(QWizard::BackButton)->show(); return; } runProcess(type, allFiles); // set suggested filename m_suggestedFilePath = QDir(m_path).filePath( (m_fileNames.count() > 1 ? "checksum." : (m_fileNames[0] + '.')) + type); } void CreateWizard::onResultPage() { // hash tools display errors into stderr, so we'll use that to determine the result of the job const QStringList outputLines = m_process->stdOutput(); const QStringList errorLines = m_process->errOutput(); bool errors = !errorLines.isEmpty(); bool successes = !outputLines.isEmpty(); QWizardPage *page = currentPage(); page->setPixmap(QWizard::LogoPixmap, Icon(errors || !successes ? "dialog-error" : "dialog-information").pixmap(32)); page->setSubTitle(errors || !successes ? i18n("Errors were detected while creating the checksums") : i18n("Checksums were created successfully")); m_hashesTreeWidget->clear(); m_hashesTreeWidget->setVisible(successes); if (successes) { - for (const QString line : outputLines) + for (const QString& line : outputLines) addChecksumLine(m_hashesTreeWidget, line); //m_hashesTreeWidget->sortItems(1, Qt::AscendingOrder); } m_errorLabel->setVisible(errors); m_errorListWidget->setVisible(errors); m_errorListWidget->clear(); m_errorListWidget->addItems(errorLines); m_onePerFileBox->setEnabled(outputLines.size() > 1); button(QWizard::FinishButton)->setEnabled(successes); } bool CreateWizard::savePerFile() { const QString type = m_suggestedFilePath.mid(m_suggestedFilePath.lastIndexOf('.')); krApp->startWaiting(i18n("Saving checksum files..."), 0); - for (const QString line : m_process->stdOutput()) { + for (const QString& line : m_process->stdOutput()) { const QString filename = line.mid(line.indexOf(' ') + 2) + type; if (!saveChecksumFile(QStringList() << line, filename)) { KMessageBox::error(this, i18n("Errors occurred while saving multiple checksums. Stopping")); krApp->stopWait(); return false; } } krApp->stopWait(); return true; } bool CreateWizard::saveChecksumFile(const QStringList &data, const QString &filename) { QString filePath = filename.isEmpty() ? m_suggestedFilePath : filename; if (filename.isEmpty() || QFile::exists(filePath)) { filePath = QFileDialog::getSaveFileName(this, QString(), filePath); if (filePath.isEmpty()) return false; // user pressed cancel } QFile file(filePath); if (file.open(QIODevice::WriteOnly)) { QTextStream stream(&file); - for (const QString line : data) + for (const QString& line : data) stream << line << "\n"; file.close(); } if (file.error() != QFile::NoError) { KMessageBox::detailedError(this, i18n("Error saving file %1", filePath), file.errorString()); return false; } return true; } void CreateWizard::accept() { const bool saved = m_onePerFileBox->isChecked() ? savePerFile() : saveChecksumFile(m_process->stdOutput()); if (saved) QWizard::accept(); } // ------------- Verify Wizard VerifyWizard::VerifyWizard(const QString &path, const QString &inputFile) : ChecksumWizard(path) { m_checksumFile = isSupported(inputFile) ? inputFile : path; m_introId = addPage(createIntroPage()); // m_checksumFile must already be set m_progressId = addPage(createProgressPage(i18n("Verifying Checksums"))); m_resultId = addPage(createResultPage()); } void VerifyWizard::slotChecksumPathChanged(const QString &path) { m_hashesTreeWidget->clear(); button(QWizard::NextButton)->setEnabled(false); if (!isSupported(path)) return; m_checksumFile = path; // parse and display checksum file content; only for the user, parsed values are not used m_hashesTreeWidget->clear(); QFile file(m_checksumFile); if (file.open(QFile::ReadOnly)) { QTextStream inStream(&file); while (!inStream.atEnd()) { addChecksumLine(m_hashesTreeWidget, file.readLine()); } } file.close(); button(QWizard::NextButton)->setEnabled(true); } QWizardPage *VerifyWizard::createIntroPage() { auto *page = new QWizardPage; page->setTitle(i18n("Verify Checksum File")); page->setPixmap(QWizard::LogoPixmap, Icon("document-edit-verify").pixmap(32)); page->setSubTitle(i18n("About to verify the following checksum file")); auto *mainLayout = new QVBoxLayout; page->setLayout(mainLayout); // checksum file auto *hLayout = new QHBoxLayout; QLabel *checksumFileLabel = new QLabel(i18n("Checksum file:")); hLayout->addWidget(checksumFileLabel); auto *checksumFileReq = new KUrlRequester; QString typesFilter; - for (const QString ext: m_checksumTools.keys()) + for (const QString& ext: m_checksumTools.keys()) typesFilter += ("*." + ext + ' '); checksumFileReq->setFilter(typesFilter); checksumFileReq->setText(m_checksumFile); checksumFileReq->setFocus(); connect(checksumFileReq, &KUrlRequester::textChanged, this, &VerifyWizard::slotChecksumPathChanged); hLayout->addWidget(checksumFileReq); mainLayout->addLayout(hLayout); // content of checksum file m_hashesTreeWidget = new KrTreeWidget(page); m_hashesTreeWidget->setAllColumnsShowFocus(true); m_hashesTreeWidget->setHeaderLabels(QStringList() << i18n("Hash") << i18n("File")); mainLayout->addWidget(m_hashesTreeWidget); return page; } QWizardPage *VerifyWizard::createResultPage() { auto *page = new QWizardPage; page->setTitle(i18n("Verify Result")); auto *mainLayout = new QVBoxLayout; page->setLayout(mainLayout); m_outputLabel = new QLabel(i18n("Result output:")); mainLayout->addWidget(m_outputLabel); m_outputListWidget = new KrListWidget; mainLayout->addWidget(m_outputListWidget); return page; } void VerifyWizard::onIntroPage() { // cannot do this in constructor: NextButton->hide() is overridden slotChecksumPathChanged(m_checksumFile); } void VerifyWizard::onProgressPage() { // verify checksum file... const QString extension = QFileInfo(m_checksumFile).suffix(); if (!checkExists(extension)) { button(QWizard::BackButton)->show(); return; } runProcess(extension, QStringList() << "--strict" << "-c" << m_checksumFile); } void VerifyWizard::onResultPage() { // better not only trust error output const bool errors = m_process->exitCode() != 0 || !m_process->errOutput().isEmpty(); QWizardPage *page = currentPage(); page->setPixmap(QWizard::LogoPixmap, Icon(errors ? "dialog-error" : "dialog-information").pixmap(32)); page->setSubTitle(errors ? i18n("Errors were detected while verifying the checksums") : i18n("Checksums were verified successfully")); // print everything, errors first m_outputListWidget->clear(); m_outputListWidget->addItems(m_process->errOutput() + m_process->stdOutput()); button(QWizard::FinishButton)->setEnabled(!errors); } bool VerifyWizard::isSupported(const QString &path) { const QFileInfo fileInfo(path); return fileInfo.isFile() && m_checksumTools.keys().contains(fileInfo.suffix()); } } // NAMESPACE CHECKSUM_ diff --git a/krusader/Dialogs/kurllistrequester.cpp b/krusader/Dialogs/kurllistrequester.cpp index d4be7957..f5dfacbe 100644 --- a/krusader/Dialogs/kurllistrequester.cpp +++ b/krusader/Dialogs/kurllistrequester.cpp @@ -1,187 +1,187 @@ /***************************************************************************** * Copyright (C) 2005 Csaba Karai * * Copyright (C) 2005-2018 Krusader Krew [https://krusader.org] * * * * This file is part of Krusader [https://krusader.org]. * * * * Krusader is free software: you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation, either version 2 of the License, or * * (at your option) any later version. * * * * Krusader is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with Krusader. If not, see [http://www.gnu.org/licenses/]. * *****************************************************************************/ #include "kurllistrequester.h" #include "../FileSystem/filesystem.h" #include "../icon.h" // QtGui #include #include #include // QtWidgets #include #include #include #include #include #include #define DELETE_ITEM_ID 100 KURLListRequester::KURLListRequester(Mode requestMode, QWidget *parent) : QWidget(parent), mode(requestMode) { // Creating the widget auto *urlListRequesterGrid = new QGridLayout(this); urlListRequesterGrid->setSpacing(0); urlListRequesterGrid->setContentsMargins(0, 0, 0, 0); urlLineEdit = new KLineEdit(this); urlListRequesterGrid->addWidget(urlLineEdit, 0, 0); urlAddBtn = new QToolButton(this); urlAddBtn->setText(""); urlAddBtn->setIcon(Icon("arrow-down")); urlListRequesterGrid->addWidget(urlAddBtn, 0, 1); urlBrowseBtn = new QToolButton(this); urlBrowseBtn->setText(""); urlBrowseBtn->setIcon(Icon("folder")); urlListRequesterGrid->addWidget(urlBrowseBtn, 0, 2); urlListBox = new KrListWidget(this); urlListBox->setSelectionMode(QAbstractItemView::ExtendedSelection); urlListRequesterGrid->addWidget(urlListBox, 1, 0, 1, 3); // add shell completion completion.setMode(KUrlCompletion::FileCompletion); urlLineEdit->setCompletionObject(&completion); // connection table connect(urlAddBtn, &QToolButton::clicked, this, &KURLListRequester::slotAdd); connect(urlBrowseBtn, &QToolButton::clicked, this, &KURLListRequester::slotBrowse); connect(urlLineEdit, &KLineEdit::returnPressed, this, &KURLListRequester::slotAdd); connect(urlListBox, &KrListWidget::itemRightClicked, this, &KURLListRequester::slotRightClicked); connect(urlLineEdit, &KLineEdit::textChanged, this, &KURLListRequester::changed); } void KURLListRequester::slotAdd() { QString text = urlLineEdit->text().simplified(); if (text.length()) { QString error; emit checkValidity(text, error); if (!error.isNull()) KMessageBox::error(this, error); else { urlListBox->addItem(text); urlLineEdit->clear(); emit changed(); } } } void KURLListRequester::slotBrowse() { QUrl url; switch (mode) { case RequestFiles: url = QFileDialog::getOpenFileUrl(this); break; case RequestDirs: url = QFileDialog::getExistingDirectoryUrl(this); break; } if (!url.isEmpty()) urlLineEdit->setText(url.toDisplayString(QUrl::PreferLocalFile)); urlLineEdit->setFocus(); } void KURLListRequester::keyPressEvent(QKeyEvent *e) { if (e->key() == Qt::Key_Delete) { if (urlListBox->hasFocus()) { deleteSelectedItems(); return; } } QWidget::keyPressEvent(e); } void KURLListRequester::deleteSelectedItems() { QList selectedItems = urlListBox->selectedItems(); for (QListWidgetItem *item : selectedItems) delete item; emit changed(); } void KURLListRequester::slotRightClicked(QListWidgetItem *item, const QPoint &pos) { if (item == nullptr) return; QMenu popupMenu(this); QAction * menuAction = popupMenu.addAction(i18n("Delete")); if (menuAction == popupMenu.exec(pos)) { if (item->isSelected()) deleteSelectedItems(); else { delete item; emit changed(); } } } QList KURLListRequester::urlList() { QStringList lines; const QString lineEditText = urlLineEdit->text().simplified(); if (!lineEditText.isEmpty()) { lines.append(lineEditText); } for (int i = 0; i != urlListBox->count(); i++) { QListWidgetItem *item = urlListBox->item(i); lines.append(item->text().simplified()); } QList urls; for (QString text : lines) { QString error; emit checkValidity(text, error); if (error.isNull()) urls.append(QUrl::fromUserInput(text, QString(), QUrl::AssumeLocalFile)); } return urls; } void KURLListRequester::setUrlList(const QList &urlList) { urlLineEdit->clear(); urlListBox->clear(); - for (const QUrl url : urlList) { + for (const QUrl& url : urlList) { urlListBox->addItem(url.toDisplayString(QUrl::PreferLocalFile)); } emit changed(); } diff --git a/krusader/Dialogs/newftpgui.cpp b/krusader/Dialogs/newftpgui.cpp index 996216c5..24a306d6 100644 --- a/krusader/Dialogs/newftpgui.cpp +++ b/krusader/Dialogs/newftpgui.cpp @@ -1,203 +1,203 @@ /***************************************************************************** * Copyright (C) 2002 Shie Erlich * * Copyright (C) 2002 Rafi Yanai * * Copyright (C) 2009 Fathi Boudra * * Copyright (C) 2004-2018 Krusader Krew [https://krusader.org] * * * * This file is part of Krusader [https://krusader.org]. * * * * Krusader is free software: you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation, either version 2 of the License, or * * (at your option) any later version. * * * * Krusader is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with Krusader. If not, see [http://www.gnu.org/licenses/]. * *****************************************************************************/ #include "newftpgui.h" // QtCore #include #include // QtGui #include // QtWidgets #include #include #include #include #include #include #include #include #include #include "../krglobal.h" #include "../icon.h" #define SIZE_MINIMUM QSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed) const QStringList sProtocols = QStringList() << QStringLiteral("ftp") << QStringLiteral("ftps") << QStringLiteral("sftp") << QStringLiteral("fish") << QStringLiteral("nfs") << QStringLiteral("smb") << QStringLiteral("webdav") << QStringLiteral("svn") << QStringLiteral("svn+file") << QStringLiteral("svn+http") << QStringLiteral("svn+https") << QStringLiteral("svn+ssh"); /** * Constructs a newFTPGUI which is a child of 'parent', * with the name 'name' and widget flags set to 'f' */ newFTPGUI::newFTPGUI(QWidget* parent) : QDialog(parent) { setModal(true); setWindowTitle(i18n("New Network Connection")); resize(500, 240); auto *mainLayout = new QVBoxLayout; setLayout(mainLayout); QSizePolicy policy(QSizePolicy::Preferred, QSizePolicy::Preferred); policy.setHeightForWidth(sizePolicy().hasHeightForWidth()); setSizePolicy(policy); iconLabel = new QLabel(this); iconLabel->setPixmap(Icon("network-wired").pixmap(32)); iconLabel->setSizePolicy(SIZE_MINIMUM); aboutLabel = new QLabel(i18n("About to connect to..."), this); QFont font(aboutLabel->font()); font.setBold(true); aboutLabel->setFont(font); protocolLabel = new QLabel(i18n("Protocol:"), this); hostLabel = new QLabel(i18n("Host:"), this); portLabel = new QLabel(i18n("Port:"), this); prefix = new KComboBox(this); prefix->setObjectName(QString::fromUtf8("protocol")); prefix->setSizePolicy(SIZE_MINIMUM); url = new KHistoryComboBox(this); url->setMaxCount(50); url->setMinimumContentsLength(10); const QStringList availableProtocols = KProtocolInfo::protocols(); - for (const QString protocol : sProtocols) { + for (const QString& protocol : sProtocols) { if (availableProtocols.contains(protocol)) prefix->addItem(protocol + QStringLiteral("://")); } // load the history and completion list after creating the history combo KConfigGroup group(krConfig, "Private"); QStringList list = group.readEntry("newFTP Completion list", QStringList()); url->completionObject()->setItems(list); list = group.readEntry("newFTP History list", QStringList()); url->setHistoryItems(list); // Select last used protocol const QString lastUsedProtocol = group.readEntry("newFTP Protocol", QString()); if(!lastUsedProtocol.isEmpty()) { prefix->setCurrentItem(lastUsedProtocol); } port = new QSpinBox(this); port->setMaximum(65535); port->setValue(21); port->setSizePolicy(SIZE_MINIMUM); usernameLabel = new QLabel(i18n("Username:"), this); username = new KLineEdit(this); passwordLabel = new QLabel(i18n("Password:"), this); password = new KLineEdit(this); password->setEchoMode(QLineEdit::Password); auto *horizontalLayout = new QHBoxLayout(); horizontalLayout->addWidget(iconLabel); horizontalLayout->addWidget(aboutLabel); auto *gridLayout = new QGridLayout(); gridLayout->addWidget(protocolLabel, 0, 0, 1, 1); gridLayout->addWidget(hostLabel, 0, 1, 1, 1); gridLayout->addWidget(portLabel, 0, 2, 1, 1); gridLayout->addWidget(prefix, 1, 0, 1, 1); gridLayout->addWidget(url, 1, 1, 1, 1); gridLayout->addWidget(port, 1, 2, 1, 1); gridLayout->addWidget(usernameLabel, 2, 0, 1, 1); gridLayout->addWidget(username, 3, 0, 1, 3); gridLayout->addWidget(passwordLabel, 4, 0, 1, 1); gridLayout->addWidget(password, 5, 0, 1, 3); auto *widgetLayout = new QGridLayout(); widgetLayout->addLayout(horizontalLayout, 0, 0, 1, 1); widgetLayout->addLayout(gridLayout, 1, 0, 1, 1); mainLayout->addLayout(widgetLayout); QDialogButtonBox *buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok|QDialogButtonBox::Cancel); mainLayout->addWidget(buttonBox); QPushButton *okButton = buttonBox->button(QDialogButtonBox::Ok); okButton->setText(i18n("&Connect")); okButton->setDefault(true); okButton->setShortcut(Qt::CTRL | Qt::Key_Return); connect(buttonBox, &QDialogButtonBox::accepted, this, &newFTPGUI::accept); connect(buttonBox, &QDialogButtonBox::rejected, this, &newFTPGUI::reject); connect(prefix, QOverload::of(&KComboBox::activated), this, &newFTPGUI::slotTextChanged); connect(url, QOverload::of(&KHistoryComboBox::activated), url, &KHistoryComboBox::addToHistory); if(!lastUsedProtocol.isEmpty()) { // update the port field slotTextChanged(lastUsedProtocol); } setTabOrder(url, username); setTabOrder(username, password); setTabOrder(password, prefix); } /** * Destroys the object and frees any allocated resources */ newFTPGUI::~newFTPGUI() { // no need to delete child widgets, Qt does it all for us } void newFTPGUI::slotTextChanged(const QString &string) { if (string.startsWith(QLatin1String("ftp")) || string.startsWith(QLatin1String("sftp")) || string.startsWith(QLatin1String("fish"))) { if (port->value() == 21 || port->value() == 22) { port->setValue(string.startsWith(QLatin1String("ftp")) ? 21 : 22); } port->setEnabled(true); } else { port->setEnabled(false); } } /** * Main event handler. Reimplemented to handle application font changes */ bool newFTPGUI::event(QEvent *ev) { bool ret = QDialog::event(ev); if (ev->type() == QEvent::ApplicationFontChange) { QFont font(aboutLabel->font()); font.setBold(true); aboutLabel->setFont(font); } return ret; } diff --git a/krusader/FileSystem/defaultfilesystem.cpp b/krusader/FileSystem/defaultfilesystem.cpp index 37ab5e93..a151df96 100644 --- a/krusader/FileSystem/defaultfilesystem.cpp +++ b/krusader/FileSystem/defaultfilesystem.cpp @@ -1,412 +1,412 @@ /***************************************************************************** * Copyright (C) 2000 Rafi Yanai * * Copyright (C) 2004-2018 Krusader Krew [https://krusader.org] * * * * This file is part of Krusader [https://krusader.org]. * * * * Krusader is free software: you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation, either version 2 of the License, or * * (at your option) any later version. * * * * Krusader is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with Krusader. If not, see [http://www.gnu.org/licenses/]. * *****************************************************************************/ #include "defaultfilesystem.h" // QtCore #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "fileitem.h" #include "../defaults.h" #include "../krglobal.h" #include "../krservices.h" #include "../JobMan/krjob.h" DefaultFileSystem::DefaultFileSystem(): FileSystem(), _watcher() { _type = FS_DEFAULT; } void DefaultFileSystem::copyFiles(const QList &urls, const QUrl &destination, KIO::CopyJob::CopyMode mode, bool showProgressInfo, JobMan::StartMode startMode) { // resolve relative path before resolving symlinks const QUrl dest = resolveRelativePath(destination); KIO::JobFlags flags = showProgressInfo ? KIO::DefaultFlags : KIO::HideProgressInfo; KrJob *krJob = KrJob::createCopyJob(mode, urls, dest, flags); // destination can be a full path with filename when copying/moving a single file const QUrl destDir = dest.adjusted(QUrl::RemoveFilename); connect(krJob, &KrJob::started, this, [=](KIO::Job *job) { connectJobToDestination(job, destDir); }); if (mode == KIO::CopyJob::Move) { // notify source about removed files connect(krJob, &KrJob::started, this, [=](KIO::Job *job) { connectJobToSources(job, urls); }); } krJobMan->manageJob(krJob, startMode); } void DefaultFileSystem::dropFiles(const QUrl &destination, QDropEvent *event) { qDebug() << "destination=" << destination; // resolve relative path before resolving symlinks const QUrl dest = resolveRelativePath(destination); KIO::DropJob *job = KIO::drop(event, dest); #if KIO_VERSION >= QT_VERSION_CHECK(5, 30, 0) // NOTE: a DropJob "starts" with showing a menu. If the operation is chosen (copy/move/link) // the actual CopyJob starts automatically - we cannot manage the start of the CopyJob (see // documentation for KrJob) connect(job, &KIO::DropJob::copyJobStarted, this, [=](KIO::CopyJob *kJob) { connectJobToDestination(job, dest); // now we have to refresh the destination KrJob *krJob = KrJob::createDropJob(job, kJob); krJobMan->manageStartedJob(krJob, kJob); if (kJob->operationMode() == KIO::CopyJob::Move) { // notify source about removed files connectJobToSources(kJob, kJob->srcUrls()); } }); #else // NOTE: DropJob does not provide information about the actual user choice // (move/copy/link/abort). We have to assume the worst (move) connectJobToDestination(job, dest); connectJobToSources(job, KUrlMimeData::urlsFromMimeData(event->mimeData())); #endif } void DefaultFileSystem::addFiles(const QList &fileUrls, KIO::CopyJob::CopyMode mode, const QString &dir) { QUrl destination(_currentDirectory); if (!dir.isEmpty()) { destination.setPath(QDir::cleanPath(destination.path() + '/' + dir)); const QString scheme = destination.scheme(); if (scheme == "tar" || scheme == "zip" || scheme == "krarc") { if (QDir(destination.path()).exists()) // if we get out from the archive change the protocol destination.setScheme("file"); } } destination = ensureTrailingSlash(destination); // destination is always a directory copyFiles(fileUrls, destination, mode); } void DefaultFileSystem::mkDir(const QString &name) { KJob *job; if (name.contains('/')) { job = KIO::mkpath(getUrl(name)); } else { job = KIO::mkdir(getUrl(name)); } connectJobToDestination(job, currentDirectory()); } void DefaultFileSystem::rename(const QString &oldName, const QString &newName) { const QUrl oldUrl = getUrl(oldName); const QUrl newUrl = getUrl(newName); KIO::Job *job = KIO::moveAs(oldUrl, newUrl, KIO::HideProgressInfo); connectJobToDestination(job, currentDirectory()); KIO::FileUndoManager::self()->recordJob(KIO::FileUndoManager::Rename, {oldUrl}, newUrl, job); } QUrl DefaultFileSystem::getUrl(const QString& name) const { // NOTE: on non-local fs file URL does not have to be path + name! FileItem *fileItem = getFileItem(name); if (fileItem) return fileItem->getUrl(); QUrl absoluteUrl(_currentDirectory); if (name.startsWith('/')) { absoluteUrl.setPath(name); } else { absoluteUrl.setPath(absoluteUrl.path() + '/' + name); } return absoluteUrl; } void DefaultFileSystem::updateFilesystemInfo() { if (!KConfigGroup(krConfig, "Look&Feel").readEntry("ShowSpaceInformation", true)) { _mountPoint = ""; emit fileSystemInfoChanged(i18n("Space information disabled"), "", 0, 0); return; } // TODO get space info for trash:/ with KIO spaceInfo job if (!_currentDirectory.isLocalFile()) { _mountPoint = ""; emit fileSystemInfoChanged(i18n("No space information on non-local filesystems"), "", 0, 0); return; } const QString path = _currentDirectory.path(); const KDiskFreeSpaceInfo info = KDiskFreeSpaceInfo::freeSpaceInfo(path); if (!info.isValid()) { _mountPoint = ""; emit fileSystemInfoChanged(i18n("Space information unavailable"), "", 0, 0); return; } _mountPoint = info.mountPoint(); const KMountPoint::Ptr mountPoint = KMountPoint::currentMountPoints().findByPath(path); const QString fsType = mountPoint ? mountPoint->mountType() : ""; emit fileSystemInfoChanged("", fsType, info.size(), info.available()); } // ==== protected ==== bool DefaultFileSystem::refreshInternal(const QUrl &directory, bool onlyScan) { qDebug() << "refresh internal to URL=" << directory.toDisplayString(); if (!KProtocolManager::supportsListing(directory)) { emit error(i18n("Protocol not supported by Krusader:\n%1", directory.url())); return false; } delete _watcher; // stop watching the old dir if (directory.isLocalFile()) { qDebug() << "start local refresh to URL=" << directory.toDisplayString(); // we could read local directories with KIO but using Qt is a lot faster! return refreshLocal(directory, onlyScan); } _currentDirectory = cleanUrl(directory); // start the listing job KIO::ListJob *job = KIO::listDir(_currentDirectory, KIO::HideProgressInfo, showHiddenFiles()); connect(job, &KIO::ListJob::entries, this, &DefaultFileSystem::slotAddFiles); connect(job, &KIO::ListJob::redirection, this, &DefaultFileSystem::slotRedirection); connect(job, &KIO::ListJob::permanentRedirection, this, &DefaultFileSystem::slotRedirection); connect(job, &KIO::Job::result, this, &DefaultFileSystem::slotListResult); // ensure connection credentials are asked only once if(!parentWindow.isNull()) { auto *ui = dynamic_cast(job->uiDelegate()); ui->setWindow(parentWindow); } emit refreshJobStarted(job); _listError = false; // ugly: we have to wait here until the list job is finished QEventLoop eventLoop; connect(job, &KJob::finished, &eventLoop, &QEventLoop::quit); eventLoop.exec(); // blocking until quit() return !_listError; } // ==== protected slots ==== void DefaultFileSystem::slotListResult(KJob *job) { qDebug() << "got list result"; if (job && job->error()) { // we failed to refresh _listError = true; qDebug() << "error=" << job->errorString() << "; text=" << job->errorText(); emit error(job->errorString()); // display error message (in panel) } } void DefaultFileSystem::slotAddFiles(KIO::Job *, const KIO::UDSEntryList& entries) { - for (const KIO::UDSEntry entry : entries) { + for (const KIO::UDSEntry& entry : entries) { FileItem *fileItem = FileSystem::createFileItemFromKIO(entry, _currentDirectory); if (fileItem) { addFileItem(fileItem); } } } void DefaultFileSystem::slotRedirection(KIO::Job *job, const QUrl &url) { qDebug() << "redirection to URL=" << url.toDisplayString(); // some protocols (zip, tar) send redirect to local URL without scheme const QUrl newUrl = preferLocalUrl(url); if (newUrl.scheme() != _currentDirectory.scheme()) { // abort and start over again, // some protocols (iso, zip, tar) do this on transition to local fs job->kill(); _isRefreshing = false; refresh(newUrl); return; } _currentDirectory = cleanUrl(newUrl); } void DefaultFileSystem::slotWatcherCreated(const QString& path) { qDebug() << "path created (doing nothing): " << path; } void DefaultFileSystem::slotWatcherDirty(const QString& path) { qDebug() << "path dirty: " << path; if (path == realPath()) { // this happens // 1. if a directory was created/deleted/renamed inside this directory. // 2. during and after a file operation (create/delete/rename/touch) inside this directory // KDirWatcher doesn't reveal the name of changed directories and we have to refresh. // (QFileSystemWatcher in Qt5.7 can't help here either) refresh(); return; } const QString name = QUrl::fromLocalFile(path).fileName(); FileItem *fileItem = getFileItem(name); if (!fileItem) { qWarning() << "file not found (unexpected), path=" << path; // this happens at least for cifs mounted filesystems: when a new file is created, a dirty // signal with its file path but no other signals are sent (buggy behaviour of KDirWatch) refresh(); return; } // we have an updated file.. FileItem *newFileItem = createLocalFileItem(name); addFileItem(newFileItem); emit updatedFileItem(newFileItem); delete fileItem; } void DefaultFileSystem::slotWatcherDeleted(const QString& path) { qDebug() << "path deleted: " << path; if (path != _currentDirectory.toLocalFile()) { // ignore deletion of files here, a 'dirty' signal will be send anyway return; } // the current directory was deleted. Try a refresh, which will fail. An error message will // be emitted and the empty (non-existing) directory remains. refresh(); } bool DefaultFileSystem::refreshLocal(const QUrl &directory, bool onlyScan) { const QString path = KrServices::urlToLocalPath(directory); #ifdef Q_WS_WIN if (!path.contains("/")) { // change C: to C:/ path = path + QString("/"); } #endif // check if the new directory exists if (!QDir(path).exists()) { emit error(i18n("The folder %1 does not exist.", path)); return false; } // mount if needed emit aboutToOpenDir(path); // set the current directory... _currentDirectory = directory; _currentDirectory.setPath(QDir::cleanPath(_currentDirectory.path())); // Note: we are using low-level Qt functions here. // It's around twice as fast as using the QDir class. QT_DIR* dir = QT_OPENDIR(path.toLocal8Bit()); if (!dir) { emit error(i18n("Cannot open the folder %1.", path)); return false; } // change directory to the new directory const QString savedDir = QDir::currentPath(); if (!QDir::setCurrent(path)) { emit error(i18nc("%1=folder path", "Access to %1 denied", path)); QT_CLOSEDIR(dir); return false; } QT_DIRENT* dirEnt; QString name; const bool showHidden = showHiddenFiles(); while ((dirEnt = QT_READDIR(dir)) != nullptr) { name = QString::fromLocal8Bit(dirEnt->d_name); // show hidden files? if (!showHidden && name.left(1) == ".") continue; // we don't need the "." and ".." entries if (name == "." || name == "..") continue; FileItem* temp = createLocalFileItem(name); addFileItem(temp); } // clean up QT_CLOSEDIR(dir); QDir::setCurrent(savedDir); if (!onlyScan) { // start watching the new dir for file changes _watcher = new KDirWatch(this); // if the current dir is a link path the watcher needs to watch the real path - and signal // parameters will be the real path _watcher->addDir(realPath(), KDirWatch::WatchFiles); connect(_watcher.data(), &KDirWatch::dirty, this, &DefaultFileSystem::slotWatcherDirty); // NOTE: not connecting 'created' signal. A 'dirty' is send after that anyway //connect(_watcher.data(), &KDirWatch::created, this, &DefaultFileSystem::slotWatcherCreated); connect(_watcher.data(), &KDirWatch::deleted, this, &DefaultFileSystem::slotWatcherDeleted); _watcher->startScan(false); } return true; } FileItem *DefaultFileSystem::createLocalFileItem(const QString &name) { return FileSystem::createLocalFileItem(name, _currentDirectory.path()); } QString DefaultFileSystem::realPath() { // NOTE: current dir must exist return QDir(_currentDirectory.toLocalFile()).canonicalPath(); } QUrl DefaultFileSystem::resolveRelativePath(const QUrl &url) { // if e.g. "/tmp/bin" is a link to "/bin", // resolve "/tmp/bin/.." to "/tmp" and not "/" return url.adjusted(QUrl::NormalizePathSegments); } diff --git a/krusader/FileSystem/filesystem.cpp b/krusader/FileSystem/filesystem.cpp index eb63a265..890bd799 100644 --- a/krusader/FileSystem/filesystem.cpp +++ b/krusader/FileSystem/filesystem.cpp @@ -1,334 +1,334 @@ /***************************************************************************** * Copyright (C) 2000 Shie Erlich * * Copyright (C) 2000 Rafi Yanai * * Copyright (C) 2004-2018 Krusader Krew [https://krusader.org] * * * * This file is part of Krusader [https://krusader.org]. * * * * Krusader is free software: you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation, either version 2 of the License, or * * (at your option) any later version. * * * * Krusader is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with Krusader. If not, see [http://www.gnu.org/licenses/]. * *****************************************************************************/ #include "filesystem.h" #include // QtCore #include #include #include // QtWidgets #include #include #include #include #include "fileitem.h" #include "krpermhandler.h" #include "../defaults.h" #include "../krglobal.h" #include "../JobMan/jobman.h" #include "../JobMan/krjob.h" FileSystem::FileSystem() : DirListerInterface(nullptr), _isRefreshing(false) {} FileSystem::~FileSystem() { clear(_fileItems); // please don't remove this line. This informs the view about deleting the file items. emit cleared(); } QList FileSystem::getUrls(const QStringList &names) const { QList urls; - for (const QString name : names) { + for (const QString& name : names) { urls.append(getUrl(name)); } return urls; } FileItem *FileSystem::getFileItem(const QString &name) const { return _fileItems.contains(name) ? _fileItems.value(name) : 0; } KIO::filesize_t FileSystem::totalSize() const { KIO::filesize_t temp = 0; for (FileItem *item : _fileItems.values()) { if (!item->isDir() && item->getName() != "." && item->getName() != "..") { temp += item->getSize(); } } return temp; } QUrl FileSystem::ensureTrailingSlash(const QUrl &url) { if (url.path().endsWith('/')) { return url; } QUrl adjustedUrl(url); adjustedUrl.setPath(adjustedUrl.path() + '/'); return adjustedUrl; } QUrl FileSystem::preferLocalUrl(const QUrl &url){ if (url.isEmpty() || !url.scheme().isEmpty()) return url; QUrl adjustedUrl = url; adjustedUrl.setScheme("file"); return adjustedUrl; } bool FileSystem::scanOrRefresh(const QUrl &directory, bool onlyScan) { qDebug() << "from current dir=" << _currentDirectory.toDisplayString() << "; to=" << directory.toDisplayString(); if (_isRefreshing) { // NOTE: this does not happen (unless async)"; return false; } // workaround for krarc: find out if transition to local fs is wanted and adjust URL manually QUrl url = directory; if (_currentDirectory.scheme() == "krarc" && url.scheme() == "krarc" && QDir(url.path()).exists()) { url.setScheme("file"); } const bool dirChange = !url.isEmpty() && cleanUrl(url) != _currentDirectory; const QUrl toRefresh = dirChange ? url.adjusted(QUrl::NormalizePathSegments) : _currentDirectory; if (!toRefresh.isValid()) { emit error(i18n("Malformed URL:\n%1", toRefresh.toDisplayString())); return false; } _isRefreshing = true; FileItemDict tempFileItems(_fileItems); // old file items are still used during refresh _fileItems.clear(); if (dirChange) // show an empty directory while loading the new one and clear selection emit cleared(); const bool refreshed = refreshInternal(toRefresh, onlyScan); _isRefreshing = false; if (!refreshed) { // cleanup and abort if (!dirChange) emit cleared(); clear(tempFileItems); return false; } emit scanDone(dirChange); clear(tempFileItems); updateFilesystemInfo(); return true; } void FileSystem::deleteFiles(const QList &urls, bool moveToTrash) { KrJob *krJob = KrJob::createDeleteJob(urls, moveToTrash); connect(krJob, &KrJob::started, this, [=](KIO::Job *job) { connectJobToSources(job, urls); }); if (moveToTrash) { // update destination: the trash bin (in case a panel/tab is showing it) connect(krJob, &KrJob::started, this, [=](KIO::Job *job) { // Note: the "trash" protocol should always have only one "/" after the "scheme:" part connect(job, &KIO::Job::result, this, [=]() { emit fileSystemChanged(QUrl("trash:/"), false); }); }); } krJobMan->manageJob(krJob); } void FileSystem::connectJobToSources(KJob *job, const QList& urls) { if (!urls.isEmpty()) { // TODO we assume that all files were in the same directory and only emit one signal for // the directory of the first file URL (all subdirectories of parent are notified) const QUrl url = urls.first().adjusted(QUrl::RemoveFilename); connect(job, &KIO::Job::result, this, [=]() { emit fileSystemChanged(url, true); }); } } void FileSystem::connectJobToDestination(KJob *job, const QUrl &destination) { connect(job, &KIO::Job::result, this, [=]() { emit fileSystemChanged(destination, false); }); // (additional) direct refresh if on local fs because watcher is too slow const bool refresh = cleanUrl(destination) == _currentDirectory && isLocal(); connect(job, &KIO::Job::result, this, [=](KJob* job) { slotJobResult(job, refresh); }); } bool FileSystem::showHiddenFiles() { const KConfigGroup gl(krConfig, "Look&Feel"); return gl.readEntry("Show Hidden", _ShowHidden); } void FileSystem::addFileItem(FileItem *item) { _fileItems.insert(item->getName(), item); } FileItem *FileSystem::createLocalFileItem(const QString &name, const QString &directory, bool virt) { const QDir dir = QDir(directory); const QString path = dir.filePath(name); const QByteArray pathByteArray = path.toLocal8Bit(); const QString fileItemName = virt ? path : name; const QUrl fileItemUrl = QUrl::fromLocalFile(path); // read file status; in case of error create a "broken" file item QT_STATBUF stat_p; memset(&stat_p, 0, sizeof(stat_p)); if (QT_LSTAT(pathByteArray.data(), &stat_p) < 0) return FileItem::createBroken(fileItemName, fileItemUrl); const KIO::filesize_t size = stat_p.st_size; bool isDir = S_ISDIR(stat_p.st_mode); const bool isLink = S_ISLNK(stat_p.st_mode); // for links, read link destination and determine whether it's broken or not QString linkDestination; bool brokenLink = false; if (isLink) { linkDestination = readLinkSafely(pathByteArray.data()); if (linkDestination.isNull()) { brokenLink = true; } else { const QFileInfo linkFile(dir, linkDestination); if (!linkFile.exists()) brokenLink = true; else if (linkFile.isDir()) isDir = true; } } // create normal file item return new FileItem(fileItemName, fileItemUrl, isDir, size, stat_p.st_mode, stat_p.st_mtime, stat_p.st_ctime, stat_p.st_atime, stat_p.st_uid, stat_p.st_gid, QString(), QString(), isLink, linkDestination, brokenLink); } QString FileSystem::readLinkSafely(const char *path) { // inspired by the areadlink_with_size function from gnulib, which is used for coreutils // idea: start with a small buffer and gradually increase it as we discover it wasn't enough QT_OFF_T bufferSize = 1024; // start with 1 KiB QT_OFF_T maxBufferSize = std::numeric_limits::max(); while (true) { // try to read the link std::unique_ptr buffer(new char[bufferSize]); auto nBytesRead = readlink(path, buffer.get(), bufferSize); // should never happen, asserted by the readlink if (nBytesRead > bufferSize) { return QString(); } // read failure if (nBytesRead < 0) { qDebug() << "Failed to read the link " << path; return QString(); } // read success if (nBytesRead < bufferSize || nBytesRead == maxBufferSize) { return QString::fromLocal8Bit(buffer.get(), nBytesRead); } // increase the buffer and retry again // bufferSize < maxBufferSize is implied from previous checks if (bufferSize <= maxBufferSize / 2) { bufferSize *= 2; } else { bufferSize = maxBufferSize; } } } FileItem *FileSystem::createFileItemFromKIO(const KIO::UDSEntry &entry, const QUrl &directory, bool virt) { const KFileItem kfi(entry, directory, true, true); const QString name = kfi.text(); // ignore un-needed entries if (name.isEmpty() || name == "." || name == "..") { return nullptr; } const QString localPath = kfi.localPath(); const QUrl url = !localPath.isEmpty() ? QUrl::fromLocalFile(localPath) : kfi.url(); const QString fname = virt ? url.toDisplayString() : name; // get file statistics... const time_t mtime = kfi.time(KFileItem::ModificationTime).toTime_t(); const time_t ctime = kfi.time(KFileItem::CreationTime).toTime_t(); // "Creation"? its "Changed" const time_t atime = kfi.time(KFileItem::AccessTime).toTime_t(); const mode_t mode = kfi.mode() | kfi.permissions(); // NOTE: we could get the mimetype (and file icon) from the kfileitem here but this is very // slow. Instead, the file item class has it's own (faster) way to determine the file type. // NOTE: "broken link" flag is always false, checking link destination existence is // considered to be too expensive return new FileItem(fname, url, kfi.isDir(), kfi.size(), mode, mtime, ctime, atime, (uid_t) -1, (gid_t) -1, kfi.user(), kfi.group(), kfi.isLink(), kfi.linkDest(), false, kfi.ACL().asString(), kfi.defaultACL().asString()); } void FileSystem::slotJobResult(KJob *job, bool refresh) { if (job->error() && job->uiDelegate()) { // show errors for modifying operations as popup (works always) job->uiDelegate()->showErrorMessage(); } if (refresh) { FileSystem::refresh(); } } void FileSystem::clear(FileItemDict &fileItems) { QHashIterator lit(fileItems); while (lit.hasNext()) { delete lit.next().value(); } fileItems.clear(); } diff --git a/krusader/FileSystem/filesystemprovider.cpp b/krusader/FileSystem/filesystemprovider.cpp index b440e2b8..ac5b960e 100644 --- a/krusader/FileSystem/filesystemprovider.cpp +++ b/krusader/FileSystem/filesystemprovider.cpp @@ -1,220 +1,220 @@ /***************************************************************************** * Copyright (C) 2003 Shie Erlich * * Copyright (C) 2003 Rafi Yanai * * Copyright (C) 2004-2018 Krusader Krew [https://krusader.org] * * * * This file is part of Krusader [https://krusader.org]. * * * * Krusader is free software: you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation, either version 2 of the License, or * * (at your option) any later version. * * * * Krusader is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with Krusader. If not, see [http://www.gnu.org/licenses/]. * *****************************************************************************/ #include "filesystemprovider.h" #ifdef HAVE_POSIX_ACL #include #ifdef HAVE_NON_POSIX_ACL_EXTENSIONS #include #endif #endif // QtCore #include #include #include #include "defaultfilesystem.h" #include "fileitem.h" #include "virtualfilesystem.h" #include "../krservices.h" #include "../JobMan/jobman.h" FileSystemProvider::FileSystemProvider() : _defaultFileSystem(nullptr), _virtFileSystem(nullptr) {} FileSystem *FileSystemProvider::getFilesystem(const QUrl &url, FileSystem *oldFilesystem) { const FileSystem::FS_TYPE type = getFilesystemType(url); return oldFilesystem && oldFilesystem->type() == type ? oldFilesystem : createFilesystem(type); } void FileSystemProvider::startCopyFiles(const QList &urls, const QUrl &destination, KIO::CopyJob::CopyMode mode, bool showProgressInfo, JobMan::StartMode startMode) { FileSystem *fs = getFilesystemInstance(destination); fs->copyFiles(urls, destination, mode, showProgressInfo, startMode); } void FileSystemProvider::startDropFiles(QDropEvent *event, const QUrl &destination) { FileSystem *fs = getFilesystemInstance(destination); fs->dropFiles(destination, event); } void FileSystemProvider::startDeleteFiles(const QList &urls, bool moveToTrash) { if (urls.isEmpty()) return; // assume all URLs use the same filesystem FileSystem *fs = getFilesystemInstance(urls.first()); fs->deleteFiles(urls, moveToTrash); } void FileSystemProvider::refreshFilesystems(const QUrl &directory, bool removed) { qDebug() << "changed=" << directory.toDisplayString(); QMutableListIterator> it(_fileSystems); while (it.hasNext()) { if (it.next().isNull()) { it.remove(); } } QString mountPoint = ""; if (directory.isLocalFile()) { KMountPoint::Ptr kMountPoint = KMountPoint::currentMountPoints().findByPath(directory.path()); if (kMountPoint) mountPoint = kMountPoint->mountPoint(); } - for(QPointer fileSystemPointer: _fileSystems) { + for(const QPointer& fileSystemPointer: _fileSystems) { FileSystem *fs = fileSystemPointer.data(); // refresh all filesystems currently showing this directory // and always refresh filesystems showing a virtual directory; it can contain files from // various places, we don't know if they were (re)moved. Refreshing is also fast enough. const QUrl fileSystemDir = fs->currentDirectory(); if ((!fs->hasAutoUpdate() && (fileSystemDir == FileSystem::cleanUrl(directory) || (fs->type() == FileSystem::FS_VIRTUAL && !fs->isRoot()))) // also refresh if a parent directory was (re)moved (not detected by file watcher) || (removed && directory.isParentOf(fileSystemDir))) { fs->refresh(); // ..or refresh filesystem info if mount point is the same (for free space update) } else if (!mountPoint.isEmpty() && mountPoint == fs->mountPoint()) { fs->updateFilesystemInfo(); } } } FileSystem *FileSystemProvider::getFilesystemInstance(const QUrl &directory) { const FileSystem::FS_TYPE type = getFilesystemType(directory); switch (type) { case FileSystem::FS_VIRTUAL: if (!_virtFileSystem) _virtFileSystem = createFilesystem(type); return _virtFileSystem; break; default: if (!_defaultFileSystem) _defaultFileSystem = createFilesystem(type); return _defaultFileSystem; } } FileSystem *FileSystemProvider::createFilesystem(FileSystem::FS_TYPE type) { FileSystem *newFilesystem; switch (type) { case (FileSystem::FS_VIRTUAL): newFilesystem = new VirtualFileSystem(); break; default: newFilesystem = new DefaultFileSystem(); } QPointer fileSystemPointer(newFilesystem); _fileSystems.append(fileSystemPointer); connect(newFilesystem, &FileSystem::fileSystemChanged, this, &FileSystemProvider::refreshFilesystems); return newFilesystem; } // ==== static ==== FileSystemProvider &FileSystemProvider::instance() { static FileSystemProvider instance; return instance; } FileSystem::FS_TYPE FileSystemProvider::getFilesystemType(const QUrl &url) { return url.scheme() == QStringLiteral("virt") ? FileSystem::FS_VIRTUAL : FileSystem::FS_DEFAULT; } void FileSystemProvider::getACL(FileItem *file, QString &acl, QString &defAcl) { Q_UNUSED(file); acl.clear(); defAcl.clear(); #ifdef HAVE_POSIX_ACL QString fileName = FileSystem::cleanUrl(file->getUrl()).path(); #ifdef HAVE_NON_POSIX_ACL_EXTENSIONS if (acl_extended_file(fileName)) { #endif acl = getACL(fileName, ACL_TYPE_ACCESS); if (file->isDir()) defAcl = getACL(fileName, ACL_TYPE_DEFAULT); #ifdef HAVE_NON_POSIX_ACL_EXTENSIONS } #endif #endif } QString FileSystemProvider::getACL(const QString & path, int type) { Q_UNUSED(path); Q_UNUSED(type); #ifdef HAVE_POSIX_ACL acl_t acl = nullptr; // do we have an acl for the file, and/or a default acl for the dir, if it is one? if ((acl = acl_get_file(path.toLocal8Bit(), type)) != nullptr) { bool aclExtended = false; #ifdef HAVE_NON_POSIX_ACL_EXTENSIONS aclExtended = acl_equiv_mode(acl, 0); #else acl_entry_t entry; int ret = acl_get_entry(acl, ACL_FIRST_ENTRY, &entry); while (ret == 1) { acl_tag_t currentTag; acl_get_tag_type(entry, ¤tTag); if (currentTag != ACL_USER_OBJ && currentTag != ACL_GROUP_OBJ && currentTag != ACL_OTHER) { aclExtended = true; break; } ret = acl_get_entry(acl, ACL_NEXT_ENTRY, &entry); } #endif if (!aclExtended) { acl_free(acl); acl = nullptr; } } if (acl == nullptr) return QString(); char *aclString = acl_to_text(acl, nullptr); QString ret = QString::fromLatin1(aclString); acl_free((void*)aclString); acl_free(acl); return ret; #else return QString(); #endif } diff --git a/krusader/FileSystem/virtualfilesystem.cpp b/krusader/FileSystem/virtualfilesystem.cpp index 227cf468..ec0f5dc1 100644 --- a/krusader/FileSystem/virtualfilesystem.cpp +++ b/krusader/FileSystem/virtualfilesystem.cpp @@ -1,355 +1,355 @@ /***************************************************************************** * Copyright (C) 2003 Shie Erlich * * Copyright (C) 2003 Rafi Yanai * * Copyright (C) 2004-2018 Krusader Krew [https://krusader.org] * * * * This file is part of Krusader [https://krusader.org]. * * * * Krusader is free software: you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation, either version 2 of the License, or * * (at your option) any later version. * * * * Krusader is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with Krusader. If not, see [http://www.gnu.org/licenses/]. * *****************************************************************************/ #include "virtualfilesystem.h" // QtCore #include #include #include // QtWidgets #include #include #include #include #include #include #include #include #include #include "fileitem.h" #include "../defaults.h" #include "../krglobal.h" #include "../krservices.h" #define VIRTUALFILESYSTEM_DB "virtualfilesystem.db" QHash *> VirtualFileSystem::_virtFilesystemDict; QHash VirtualFileSystem::_metaInfoDict; VirtualFileSystem::VirtualFileSystem() : FileSystem() { if (_virtFilesystemDict.isEmpty()) { restore(); } _type = FS_VIRTUAL; } void VirtualFileSystem::copyFiles(const QList &urls, const QUrl &destination, KIO::CopyJob::CopyMode /*mode*/, bool /*showProgressInfo*/, JobMan::StartMode /*startMode*/) { const QString dir = QDir(destination.path()).absolutePath().remove('/'); if (dir.isEmpty()) { showError(i18n("You cannot copy files directly to the 'virt:/' folder.\n" "You can create a sub folder and copy your files into it.")); return; } if (!_virtFilesystemDict.contains(dir)) { mkDirInternal(dir); } QList *urlList = _virtFilesystemDict[dir]; for (const QUrl &fileUrl : urls) { if (!urlList->contains(fileUrl)) { urlList->push_back(fileUrl); } } emit fileSystemChanged(QUrl("virt:///" + dir), false); // may call refresh() } void VirtualFileSystem::dropFiles(const QUrl &destination, QDropEvent *event) { const QList &urls = KUrlMimeData::urlsFromMimeData(event->mimeData()); // dropping on virtual filesystem is always copy operation copyFiles(urls, destination); } void VirtualFileSystem::addFiles(const QList &fileUrls, KIO::CopyJob::CopyMode /*mode*/, const QString &dir) { QUrl destination(_currentDirectory); if (!dir.isEmpty()) { destination.setPath(QDir::cleanPath(destination.path() + '/' + dir)); } copyFiles(fileUrls, destination); } void VirtualFileSystem::remove(const QStringList &fileNames) { const QString parentDir = currentDir(); if (parentDir == "/") { // remove virtual directory for (const QString &filename : fileNames) { _virtFilesystemDict["/"]->removeAll(QUrl(QStringLiteral("virt:/") + filename)); delete _virtFilesystemDict[filename]; _virtFilesystemDict.remove(filename); _metaInfoDict.remove(filename); } } else { // remove the URLs from the collection - for (const QString name : fileNames) { + for (const QString& name : fileNames) { if (_virtFilesystemDict.find(parentDir) != _virtFilesystemDict.end()) { QList *urlList = _virtFilesystemDict[parentDir]; urlList->removeAll(getUrl(name)); } } } emit fileSystemChanged(currentDirectory(), true); // will call refresh() } QUrl VirtualFileSystem::getUrl(const QString &name) const { FileItem *item = getFileItem(name); if (!item) { return QUrl(); // not found } return item->getUrl(); } void VirtualFileSystem::mkDir(const QString &name) { if (currentDir() != "/") { showError(i18n("Creating new folders is allowed only in the 'virt:/' folder.")); return; } mkDirInternal(name); emit fileSystemChanged(currentDirectory(), false); // will call refresh() } void VirtualFileSystem::rename(const QString &fileName, const QString &newName) { FileItem *item = getFileItem(fileName); if (!item) return; // not found if (currentDir() == "/") { // rename virtual directory _virtFilesystemDict["/"]->append(QUrl(QStringLiteral("virt:/") + newName)); _virtFilesystemDict["/"]->removeAll(QUrl(QStringLiteral("virt:/") + fileName)); _virtFilesystemDict.insert(newName, _virtFilesystemDict.take(fileName)); refresh(); return; } // newName can be a (local) path or a full url QUrl dest(newName); if (dest.scheme().isEmpty()) dest.setScheme("file"); // add the new url to the list // the list is refreshed, only existing files remain - // so we don't have to worry if the job was successful _virtFilesystemDict[currentDir()]->append(dest); KIO::Job *job = KIO::moveAs(item->getUrl(), dest, KIO::HideProgressInfo); connect(job, &KIO::Job::result, this, [=](KJob* job) { slotJobResult(job, false); }); connect(job, &KIO::Job::result, this, [=]() { emit fileSystemChanged(currentDirectory(), false); }); } bool VirtualFileSystem::canMoveToTrash(const QStringList &fileNames) const { if (isRoot()) return false; - for (const QString fileName : fileNames) { + for (const QString& fileName : fileNames) { if (!getUrl(fileName).isLocalFile()) { return false; } } return true; } void VirtualFileSystem::setMetaInformation(const QString &info) { _metaInfoDict[currentDir()] = info; } // ==== protected ==== bool VirtualFileSystem::refreshInternal(const QUrl &directory, bool onlyScan) { _currentDirectory = cleanUrl(directory); _currentDirectory.setHost(""); // remove invalid subdirectories _currentDirectory.setPath('/' + _currentDirectory.path().remove('/')); if (!_virtFilesystemDict.contains(currentDir())) { if (onlyScan) { return false; // virtual dir does not exist } else { // Silently creating non-existing directories here. The search and locate tools // expect this. And the user can enter some directory and it will be created. mkDirInternal(currentDir()); save(); // infinite loop possible // emit fileSystemChanged(currentDirectory()); return true; } } QList *urlList = _virtFilesystemDict[currentDir()]; if (!onlyScan) { const QString metaInfo = _metaInfoDict[currentDir()]; emit fileSystemInfoChanged(metaInfo.isEmpty() ? i18n("Virtual filesystem") : metaInfo, "", 0, 0); } QMutableListIterator it(*urlList); while (it.hasNext()) { const QUrl url = it.next(); FileItem *item = createFileItem(url); if (!item) { // remove URL from the list for a file that no longer exists it.remove(); } else { addFileItem(item); } } save(); return true; } // ==== private ==== void VirtualFileSystem::mkDirInternal(const QString &name) { // clean path, consistent with currentDir() QString dirName = name; dirName = dirName.remove('/'); if (dirName.isEmpty()) dirName = '/'; _virtFilesystemDict.insert(dirName, new QList()); _virtFilesystemDict["/"]->append(QUrl(QStringLiteral("virt:/") + dirName)); } void VirtualFileSystem::save() { KConfig *db = &VirtualFileSystem::getVirtDB(); db->deleteGroup("virt_db"); KConfigGroup group(db, "virt_db"); QHashIterator *> it(_virtFilesystemDict); while (it.hasNext()) { it.next(); QList *urlList = it.value(); QList::iterator url; QStringList entry; for (url = urlList->begin(); url != urlList->end(); ++url) { entry.append((*url).toDisplayString()); } // KDE 4.0 workaround: 'Item_' prefix is added as KConfig fails on 1 char names (such as /) group.writeEntry("Item_" + it.key(), entry); group.writeEntry("MetaInfo_" + it.key(), _metaInfoDict[it.key()]); } db->sync(); } void VirtualFileSystem::restore() { KConfig *db = &VirtualFileSystem::getVirtDB(); const KConfigGroup dbGrp(db, "virt_db"); const QMap map = db->entryMap("virt_db"); QMapIterator it(map); while (it.hasNext()) { it.next(); // KDE 4.0 workaround: check and remove 'Item_' prefix if (!it.key().startsWith(QLatin1String("Item_"))) continue; const QString key = it.key().mid(5); const QList urlList = KrServices::toUrlList(dbGrp.readEntry(it.key(), QStringList())); _virtFilesystemDict.insert(key, new QList(urlList)); _metaInfoDict.insert(key, dbGrp.readEntry("MetaInfo_" + key, QString())); } if (!_virtFilesystemDict["/"]) { // insert root element if missing for some reason _virtFilesystemDict.insert("/", new QList()); } } FileItem *VirtualFileSystem::createFileItem(const QUrl &url) { if (url.scheme() == "virt") { // return a virtual directory in root QString path = url.path().mid(1); if (path.isEmpty()) path = '/'; return FileItem::createVirtualDir(path, url); } const QUrl directory = url.adjusted(QUrl::RemoveFilename); if (url.isLocalFile()) { QFileInfo file(url.path()); return file.exists() ? FileSystem::createLocalFileItem(url.fileName(), directory.path(), true) : nullptr; } KIO::StatJob *statJob = KIO::stat(url, KIO::HideProgressInfo); connect(statJob, &KIO::Job::result, this, &VirtualFileSystem::slotStatResult); // ugly: we have to wait here until the stat job is finished QEventLoop eventLoop; connect(statJob, &KJob::finished, &eventLoop, &QEventLoop::quit); eventLoop.exec(); // blocking until quit() if (_fileEntry.count() == 0) { return nullptr; // stat job failed } if (!_fileEntry.contains(KIO::UDSEntry::UDS_MODIFICATION_TIME)) { // TODO this also happens for FTP directories return nullptr; // file not found } return FileSystem::createFileItemFromKIO(_fileEntry, directory, true); } KConfig &VirtualFileSystem::getVirtDB() { //virtualfilesystem_db = new KConfig("data",VIRTUALFILESYSTEM_DB,KConfig::NoGlobals); static KConfig db(VIRTUALFILESYSTEM_DB, KConfig::CascadeConfig, QStandardPaths::AppDataLocation); return db; } void VirtualFileSystem::slotStatResult(KJob *job) { _fileEntry = job->error() ? KIO::UDSEntry() : dynamic_cast(job)->statResult(); } void VirtualFileSystem::showError(const QString &error) { QWidget *window = QApplication::activeWindow(); KMessageBox::sorry(window, error); // window can be null, is allowed } diff --git a/krusader/Panel/panelcontextmenu.cpp b/krusader/Panel/panelcontextmenu.cpp index 10f5aa53..1e680b5a 100644 --- a/krusader/Panel/panelcontextmenu.cpp +++ b/krusader/Panel/panelcontextmenu.cpp @@ -1,433 +1,433 @@ /***************************************************************************** * Copyright (C) 2003 Shie Erlich * * Copyright (C) 2003 Rafi Yanai * * Copyright (C) 2004-2018 Krusader Krew [https://krusader.org] * * * * This file is part of Krusader [https://krusader.org]. * * * * Krusader is free software: you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation, either version 2 of the License, or * * (at your option) any later version. * * * * Krusader is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with Krusader. If not, see [http://www.gnu.org/licenses/]. * *****************************************************************************/ #include "panelcontextmenu.h" // QtGui #include #include #include #include #include #include #include #include #include #include #include #include "krpreviewpopup.h" #include "listpanel.h" #include "listpanelactions.h" #include "panelfunc.h" #include "PanelView/krview.h" #include "PanelView/krviewitem.h" #include "../defaults.h" #include "../icon.h" #include "../krservices.h" #include "../krslots.h" #include "../krusader.h" #include "../krusaderview.h" #include "../panelmanager.h" #include "../Archive/krarchandler.h" #include "../FileSystem/fileitem.h" #include "../FileSystem/filesystem.h" #include "../FileSystem/krtrashhandler.h" #include "../MountMan/kmountman.h" #include "../UserAction/useractionpopupmenu.h" void PanelContextMenu::run(const QPoint &pos, KrPanel *panel) { PanelContextMenu menu(panel); QAction * res = menu.exec(pos); int result = -1; if (res && res->data().canConvert()) result = res->data().toInt(); menu.performAction(result); } /** * Copied from dolphin/src/dolphincontextmenu.cpp and modified to add only compress and extract submenus. */ void PanelContextMenu::addCompressAndExtractPluginActions() { KFileItemListProperties props(_items); QVector jsonPlugins = KPluginLoader::findPlugins("kf5/kfileitemaction", [=](const KPluginMetaData& metaData) { return metaData.pluginId() == "compressfileitemaction" || metaData.pluginId() == "extractfileitemaction"; }); foreach (const KPluginMetaData &jsonMetadata, jsonPlugins) { auto* abstractPlugin = KPluginLoader(jsonMetadata.fileName()) .factory()->create(); if (abstractPlugin) { abstractPlugin->setParent(this); addActions(abstractPlugin->actions(props, this)); } } } PanelContextMenu::PanelContextMenu(KrPanel *krPanel, QWidget *parent) : QMenu(parent), panel(krPanel) { // selected file names const QStringList fileNames = panel->gui->getSelectedNames(); // file items QList files; - for (const QString fileName : fileNames) { + for (const QString& fileName : fileNames) { files.append(panel->func->files()->getFileItem(fileName)); } // KFileItems for (FileItem *file : files) { _items.append(KFileItem(file->getUrl(), file->getMime(), file->getMode())); } if (files.empty()) { addCreateNewMenu(); addSeparator(); addEmptyMenuEntries(); return; } const bool multipleSelections = files.size() > 1; QSet protocols; for (FileItem *file : files) { protocols.insert(file->getUrl().scheme()); } const bool inTrash = protocols.contains("trash"); const bool trashOnly = inTrash && protocols.count() == 1; FileItem *file = files.first(); // ------------ the OPEN/BROWSE option - open preferred service QAction * openAct = addAction(i18n("Open/Run")); openAct->setData(QVariant(OPEN_ID)); if (!multipleSelections) { // meaningful only if one file is selected KrViewItemList viewItems; panel->view->getSelectedKrViewItems(&viewItems); openAct->setIcon(viewItems.first()->icon()); openAct->setText(file->isExecutable() && !file->isDir() ? i18n("Run") : i18n("Open")); // open in a new tab (if folder) if (file->isDir()) { QAction * openTab = addAction(i18n("Open in New Tab")); openTab->setData(QVariant(OPEN_TAB_ID)); openTab->setIcon(Icon("tab-new")); openTab->setText(i18n("Open in New Tab")); } // if the file can be browsed as archive... if (!panel->func->browsableArchivePath(file->getName()).isEmpty() // ...but user disabled archive browsing... && (!KConfigGroup(krConfig, "Archives") .readEntry("ArchivesAsDirectories", _ArchivesAsDirectories) // ...or the file is not a standard archive (e.g. odt, docx, etc.)... || !KRarcHandler::arcSupported(file->getMime()))) { // ...it will not be browsed as a directory by default, but add an option for it QAction *browseAct = addAction(i18n("Browse")); browseAct->setData(QVariant(BROWSE_ID)); browseAct->setIcon(Icon()); browseAct->setText(i18n("Browse Archive")); } addSeparator(); } // ------------- Preview - local filesystem only ? if (panel->func->files()->isLocal()) { // create the preview popup KrPreviewPopup preview; preview.setUrls(panel->func->files()->getUrls(fileNames)); QAction *previewAction = addMenu(&preview); previewAction->setData(QVariant(PREVIEW_ID)); previewAction->setText(i18n("Preview")); previewAction->setIcon(Icon("document-print-preview")); } // -------------- Open with: try to find-out which apps can open the file QSet uniqueMimeTypes; for (FileItem *file : files) uniqueMimeTypes.insert(file->getMime()); const QStringList mimeTypes = uniqueMimeTypes.toList(); offers = mimeTypes.count() == 1 ? KMimeTypeTrader::self()->query(mimeTypes.first()) : KFileItemActions::associatedApplications(mimeTypes, QString()); if (!offers.isEmpty()) { auto *openWithMenu = new QMenu(this); for (int i = 0; i < offers.count(); ++i) { QExplicitlySharedDataPointer service = offers[i]; if (service->isValid() && service->isApplication()) { openWithMenu->addAction(Icon(service->icon()), service->name())->setData(QVariant(SERVICE_LIST_ID + i)); } } openWithMenu->addSeparator(); if (!multipleSelections && file->isDir()) openWithMenu->addAction(Icon("utilities-terminal"), i18n("Terminal"))->setData(QVariant(OPEN_TERM_ID)); openWithMenu->addAction(i18n("Other..."))->setData(QVariant(CHOOSE_ID)); QAction *openWithAction = addMenu(openWithMenu); openWithAction->setText(i18n("Open With")); openWithAction->setIcon(Icon("document-open")); addSeparator(); } // --------------- user actions QAction *userAction = new UserActionPopupMenu(file->getUrl(), this); userAction->setText(i18n("User Actions")); addAction(userAction); // --------------- compress/extract actions // workaround for Bug 372999: application freezes very long time if many files are selected if (_items.length() < 1000) // add compress and extract plugins (if available) addCompressAndExtractPluginActions(); // --------------- KDE file item actions // NOTE: design and usability problem here. Services disabled in kservicemenurc settings won't // be added to the menu. But Krusader does not provide a way do change these settings (only // Dolphin does). auto *fileItemActions = new KFileItemActions(this); fileItemActions->setItemListProperties(KFileItemListProperties(_items)); fileItemActions->setParentWidget(MAIN_VIEW); fileItemActions->addServiceActionsTo(this); addSeparator(); // ------------- 'create new' submenu addCreateNewMenu(); addSeparator(); // ---------- COPY addAction(panel->gui->actions()->actCopyF5); // ------- MOVE addAction(panel->gui->actions()->actMoveF6); // ------- RENAME - only one file if (!multipleSelections && !inTrash) { addAction(panel->gui->actions()->actRenameF2); } // -------- MOVE TO TRASH if (KConfigGroup(krConfig, "General").readEntry("Move To Trash", _MoveToTrash) && panel->func->files()->canMoveToTrash(fileNames)) { addAction(Icon("user-trash"), i18n("Move to Trash"))->setData(QVariant(TRASH_ID)); } // -------- DELETE addAction(Icon("edit-delete"), i18n("Delete"))->setData(QVariant(DELETE_ID)); // -------- SHRED - only one file /* if ( panel->func->files() ->getType() == filesystem:fileSystemM_NORMAL && !fileitem->isDir() && !multipleSelections ) addAction( i18n( "Shred" ) )->setData( QVariant( SHRED_ID ) );*/ // ---------- link handling // create new shortcut or redirect links - only on local directories: if (panel->func->files()->isLocal()) { addSeparator(); auto *linkMenu = new QMenu(this); linkMenu->addAction(i18n("New Symlink..."))->setData(QVariant(NEW_SYMLINK_ID)); linkMenu->addAction(i18n("New Hardlink..."))->setData(QVariant(NEW_LINK_ID)); if (file->isSymLink()) { linkMenu->addAction(i18n("Redirect Link..."))->setData(QVariant(REDIRECT_LINK_ID)); } QAction *linkAction = addMenu(linkMenu); linkAction->setText(i18n("Link Handling")); linkAction->setIcon(Icon("insert-link")); } addSeparator(); // ---------- calculate space if (panel->func->files()->isLocal() && (file->isDir() || multipleSelections)) addAction(panel->gui->actions()->actCalculate); // ---------- mount/umount/eject if (panel->func->files()->isLocal() && file->isDir() && !multipleSelections) { const QString selectedDirectoryPath = file->getUrl().path(); if (krMtMan.getStatus(selectedDirectoryPath) == KMountMan::MOUNTED) addAction(i18n("Unmount"))->setData(QVariant(UNMOUNT_ID)); else if (krMtMan.getStatus(selectedDirectoryPath) == KMountMan::NOT_MOUNTED) addAction(i18n("Mount"))->setData(QVariant(MOUNT_ID)); if (krMtMan.ejectable(selectedDirectoryPath)) addAction(i18n("Eject"))->setData(QVariant(EJECT_ID)); } // --------- send by mail if (KrServices::supportedTools().contains("MAIL") && !file->isDir()) { addAction(Icon("mail-send"), i18n("Send by Email"))->setData(QVariant(SEND_BY_EMAIL_ID)); } // --------- empty trash if (trashOnly) { addAction(i18n("Restore"))->setData(QVariant(RESTORE_TRASHED_FILE_ID)); addAction(i18n("Empty Trash"))->setData(QVariant(EMPTY_TRASH_ID)); } #ifdef SYNCHRONIZER_ENABLED // --------- synchronize if (panel->view->numSelected()) { addAction(i18n("Synchronize Selected Files..."))->setData(QVariant(SYNC_SELECTED_ID)); } #endif // --------- copy/paste addSeparator(); addAction(panel->gui->actions()->actCut); addAction(panel->gui->actions()->actCopy); addAction(panel->gui->actions()->actPaste); addSeparator(); // --------- properties addAction(panel->gui->actions()->actProperties); } void PanelContextMenu::addEmptyMenuEntries() { addAction(panel->gui->actions()->actPaste); } void PanelContextMenu::addCreateNewMenu() { auto *createNewMenu = new QMenu(this); createNewMenu->addAction(Icon("folder"), i18n("Folder..."))->setData(QVariant(MKDIR_ID)); createNewMenu->addAction(Icon("text-plain"), i18n("Text File..."))->setData(QVariant(NEW_TEXT_FILE_ID)); QAction *newMenuAction = addMenu(createNewMenu); newMenuAction->setText(i18n("Create New")); newMenuAction->setIcon(Icon("document-new")); } void PanelContextMenu::performAction(int id) { const QUrl singleURL = _items.isEmpty() ? QUrl() : _items.first().url(); switch (id) { case - 1 : // the user clicked outside of the menu return; case OPEN_TAB_ID : // assuming only 1 file is selected (otherwise we won't get here) panel->manager()->newTab(singleURL, panel); break; case OPEN_ID : foreach(const KFileItem &fi, _items) panel->func->execute(fi.name()); break; case BROWSE_ID : panel->func->goInside(singleURL.fileName()); break; case COPY_ID : panel->func->copyFiles(); break; case MOVE_ID : panel->func->moveFiles(); break; case TRASH_ID : panel->func->deleteFiles(true); break; case DELETE_ID : panel->func->deleteFiles(false); break; case EJECT_ID : krMtMan.eject(singleURL.adjusted(QUrl::StripTrailingSlash).path()); break; // case SHRED_ID : // if ( KMessageBox::warningContinueCancel( krApp, // i18n("Do you really want to shred %1? Once shred, the file is gone forever.", item->name()), // QString(), KStandardGuiItem::cont(), KStandardGuiItem::cancel(), "Shred" ) == KMessageBox::Continue ) // KShred::shred( panel->func->files() ->getFile( item->name() ).adjusted(QUrl::RemoveTrailingSlash).path() ); // break; case OPEN_KONQ_ID : KToolInvocation::startServiceByDesktopName("konqueror", singleURL.toDisplayString(QUrl::PreferLocalFile)); break; case CHOOSE_ID : // open-with dialog panel->func->displayOpenWithDialog(_items.urlList()); break; case MOUNT_ID : krMtMan.mount(singleURL.adjusted(QUrl::StripTrailingSlash).path()); break; case NEW_LINK_ID : panel->func->krlink(false); break; case NEW_SYMLINK_ID : panel->func->krlink(true); break; case REDIRECT_LINK_ID : panel->func->redirectLink(); break; case EMPTY_TRASH_ID : KrTrashHandler::emptyTrash(); break; case RESTORE_TRASHED_FILE_ID : KrTrashHandler::restoreTrashedFiles(_items.urlList()); break; case UNMOUNT_ID : krMtMan.unmount(singleURL.adjusted(QUrl::StripTrailingSlash).path()); break; case SEND_BY_EMAIL_ID : { SLOTS->sendFileByEmail(_items.urlList()); break; } case MKDIR_ID : panel->func->mkdir(); break; case NEW_TEXT_FILE_ID: panel->func->editNew(); break; #ifdef SYNCHRONIZER_ENABLED case SYNC_SELECTED_ID : { QStringList selectedNames; - for (const KFileItem item : _items) { + for (const KFileItem& item : _items) { selectedNames.append(item.name()); } KrViewItemList otherItems; panel->otherPanel()->view->getSelectedKrViewItems(&otherItems); for (KrViewItem *otherItem : otherItems) { const QString& name = otherItem->name(); if (!selectedNames.contains(name)) { selectedNames.append(name); } } SLOTS->slotSynchronizeDirs(selectedNames); } break; #endif case OPEN_TERM_ID : SLOTS->runTerminal(singleURL.path()); break; } // check if something from the open-with-offered-services was selected if (id >= SERVICE_LIST_ID) { const QStringList names = panel->gui->getSelectedNames(); panel->func->runService(*(offers[ id - SERVICE_LIST_ID ]), panel->func->files()->getUrls(names)); } } diff --git a/krusader/Panel/panelfunc.cpp b/krusader/Panel/panelfunc.cpp index 280b3469..b2aba1dc 100644 --- a/krusader/Panel/panelfunc.cpp +++ b/krusader/Panel/panelfunc.cpp @@ -1,1291 +1,1291 @@ /***************************************************************************** * Copyright (C) 2000 Shie Erlich * * Copyright (C) 2000 Rafi Yanai * * Copyright (C) 2004-2018 Krusader Krew [https://krusader.org] * * * * This file is part of Krusader [https://krusader.org]. * * * * Krusader is free software: you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation, either version 2 of the License, or * * (at your option) any later version. * * * * Krusader is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with Krusader. If not, see [http://www.gnu.org/licenses/]. * *****************************************************************************/ #include "panelfunc.h" // QtCore #include #include #include #include #include #include // QtGui #include #include // QtWidgets #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "dirhistoryqueue.h" #include "krcalcspacedialog.h" #include "krerrordisplay.h" #include "krsearchbar.h" #include "listpanel.h" #include "listpanelactions.h" #include "PanelView/krview.h" #include "PanelView/krviewitem.h" #include "../krglobal.h" #include "../krslots.h" #include "../kractions.h" #include "../defaults.h" #include "../abstractpanelmanager.h" #include "../krservices.h" #include "../Archive/krarchandler.h" #include "../Archive/packjob.h" #include "../FileSystem/fileitem.h" #include "../FileSystem/virtualfilesystem.h" #include "../FileSystem/krpermhandler.h" #include "../FileSystem/filesystemprovider.h" #include "../FileSystem/sizecalculator.h" #include "../Dialogs/packgui.h" #include "../Dialogs/krdialogs.h" #include "../Dialogs/krpleasewait.h" #include "../Dialogs/krspwidgets.h" #include "../Dialogs/checksumdlg.h" #include "../KViewer/krviewer.h" #include "../MountMan/kmountman.h" QPointer ListPanelFunc::copyToClipboardOrigin; ListPanelFunc::ListPanelFunc(ListPanel *parent) : QObject(parent), panel(parent), fileSystemP(nullptr), urlManuallyEntered(false), _isPaused(true), _refreshAfterPaused(true), _quickSizeCalculator(nullptr) { history = new DirHistoryQueue(panel); delayTimer.setSingleShot(true); connect(&delayTimer, &QTimer::timeout, this, &ListPanelFunc::doRefresh); } ListPanelFunc::~ListPanelFunc() { if (fileSystemP) { fileSystemP->deleteLater(); } delete history; if (_quickSizeCalculator) _quickSizeCalculator->deleteLater(); } bool ListPanelFunc::isSyncing(const QUrl &url) { if(otherFunc()->otherFunc() == this && panel->otherPanel()->gui->syncBrowseButton->isChecked() && !otherFunc()->syncURL.isEmpty() && otherFunc()->syncURL == url) return true; return false; } void ListPanelFunc::openFileNameInternal(const QString &name, bool externallyExecutable) { if (name == "..") { dirUp(); return; } FileItem *fileitem = files()->getFileItem(name); if (fileitem == nullptr) return; QUrl url = files()->getUrl(name); if (fileitem->isDir()) { panel->view->setNameToMakeCurrent(QString()); openUrl(url); return; } QString mime = fileitem->getMime(); QUrl arcPath = browsableArchivePath(name); if (!arcPath.isEmpty()) { bool browseAsDirectory = !externallyExecutable || (KConfigGroup(krConfig, "Archives").readEntry("ArchivesAsDirectories", _ArchivesAsDirectories) && (KRarcHandler::arcSupported(mime) || KrServices::isoSupported(mime))); if (browseAsDirectory) { openUrl(arcPath); return; } } if (externallyExecutable) { if (KRun::isExecutableFile(url, mime)) { runCommand(KShell::quoteArg(url.path())); return; } KService::Ptr service = KMimeTypeTrader::self()->preferredService(mime); if(service) { runService(*service, QList() << url); return; } displayOpenWithDialog(QList() << url); } } QUrl ListPanelFunc::cleanPath(const QUrl &urlIn) { QUrl url = urlIn; url.setPath(QDir::cleanPath(url.path())); if (!url.isValid() || url.isRelative()) { if (url.url() == "~") url = QUrl::fromLocalFile(QDir::homePath()); else if (!url.url().startsWith('/')) { // possible relative URL - translate to full URL url = files()->currentDirectory(); url.setPath(url.path() + '/' + urlIn.path()); } } url.setPath(QDir::cleanPath(url.path())); return url; } void ListPanelFunc::openUrl(const QUrl &url, const QString& nameToMakeCurrent, bool manuallyEntered) { qDebug() << "URL=" << url.toDisplayString() << "; name to current=" << nameToMakeCurrent; if (panel->syncBrowseButton->isChecked()) { //do sync-browse stuff.... if(syncURL.isEmpty()) syncURL = panel->otherPanel()->virtualPath(); QString relative = QDir(panel->virtualPath().path() + '/').relativeFilePath(url.path()); syncURL.setPath(QDir::cleanPath(syncURL.path() + '/' + relative)); panel->otherPanel()->gui->setTabState(ListPanel::TabState::DEFAULT); otherFunc()->openUrlInternal(syncURL, nameToMakeCurrent, false, false); } openUrlInternal(url, nameToMakeCurrent, false, manuallyEntered); } void ListPanelFunc::immediateOpenUrl(const QUrl &url) { openUrlInternal(url, QString(), true, false); } void ListPanelFunc::openUrlInternal(const QUrl &url, const QString& nameToMakeCurrent, bool immediately, bool manuallyEntered) { const QUrl cleanUrl = cleanPath(url); if (panel->isLocked() && !files()->currentDirectory().matches(cleanUrl, QUrl::StripTrailingSlash)) { panel->_manager->newTab(url); urlManuallyEntered = false; return; } urlManuallyEntered = manuallyEntered; const QString currentItem = history->currentUrl().path() == cleanUrl.path() ? history->currentItem() : nameToMakeCurrent; history->add(cleanUrl, currentItem); if(immediately) doRefresh(); else refresh(); } void ListPanelFunc::refresh() { panel->cancelProgress(); delayTimer.start(0); // to avoid qApp->processEvents() deadlock situation } void ListPanelFunc::doRefresh() { delayTimer.stop(); if (_isPaused) { _refreshAfterPaused = true; // simulate refresh panel->slotStartUpdate(true); return; } else { _refreshAfterPaused = false; } const QUrl url = history->currentUrl(); if(!url.isValid()) { panel->slotStartUpdate(true); // refresh the panel urlManuallyEntered = false; return; } panel->cancelProgress(); // if we are not refreshing to current URL const bool isEqualUrl = files()->currentDirectory().matches(url, QUrl::StripTrailingSlash); if (!isEqualUrl) { panel->setCursor(Qt::WaitCursor); panel->view->clearSavedSelection(); } if (panel->fileSystemError) { panel->fileSystemError->hide(); } panel->setNavigatorUrl(url); // may get a new filesystem for this url FileSystem *fileSystem = FileSystemProvider::instance().getFilesystem(url, files()); fileSystem->setParentWindow(krMainWindow); connect(fileSystem, &FileSystem::aboutToOpenDir, &krMtMan, &KMountMan::autoMount, Qt::DirectConnection); if (fileSystem != fileSystemP) { panel->view->setFiles(nullptr); // disconnect older signals disconnect(fileSystemP, nullptr, panel, nullptr); fileSystemP->deleteLater(); fileSystemP = fileSystem; // v != 0 so this is safe } else { if (fileSystemP->isRefreshing()) { // TODO remove busy waiting here delayTimer.start(100); /* if filesystem is busy try refreshing later */ return; } } // (re)connect filesystem signals disconnect(files(), nullptr, panel, nullptr); connect(files(), &DirListerInterface::scanDone, panel, &ListPanel::slotStartUpdate); connect(files(), &FileSystem::fileSystemInfoChanged, panel, &ListPanel::updateFilesystemStats); connect(files(), &FileSystem::refreshJobStarted, panel, &ListPanel::slotRefreshJobStarted); connect(files(), &FileSystem::error, panel, &ListPanel::slotFilesystemError); panel->view->setFiles(files()); if (!isEqualUrl || !panel->view->getCurrentKrViewItem()) { // set current item after refresh from history, if there is none yet panel->view->setNameToMakeCurrent(history->currentItem()); } // workaround for detecting panel deletion while filesystem is refreshing QPointer panelSave = panel; // NOTE: this is blocking. Returns false on error or interruption (cancel requested or panel // was deleted) const bool scanned = fileSystemP->refresh(url); if (scanned) { // update the history and address bar, as the actual url might differ from the one requested history->setCurrentUrl(fileSystemP->currentDirectory()); panel->setNavigatorUrl(fileSystemP->currentDirectory()); } else if (!panelSave) { return; } panel->view->setNameToMakeCurrent(QString()); panel->setCursor(Qt::ArrowCursor); // on local file system change the working directory if (files()->isLocal()) QDir::setCurrent(KrServices::urlToLocalPath(files()->currentDirectory())); // see if the open url operation failed, and if so, // put the attempted url in the navigator bar and let the user change it if (!scanned) { if(isSyncing(url)) panel->otherPanel()->gui->syncBrowseButton->setChecked(false); else if(urlManuallyEntered) { panel->setNavigatorUrl(url); if(panel == ACTIVE_PANEL) panel->editLocation(); } } if(otherFunc()->otherFunc() == this) // not true if our tab is not active otherFunc()->syncURL = QUrl(); urlManuallyEntered = false; refreshActions(); } void ListPanelFunc::setPaused(bool paused) { if (paused == _isPaused) return; _isPaused = paused; // TODO: disable refresh() in local file system when paused if (!_isPaused && _refreshAfterPaused) refresh(); } void ListPanelFunc::redirectLink() { if (!files()->isLocal()) { KMessageBox::sorry(krMainWindow, i18n("You can edit links only on local file systems")); return; } FileItem *fileitem = files()->getFileItem(panel->getCurrentName()); if (!fileitem) return; QString file = fileitem->getUrl().path(); QString currentLink = fileitem->getSymDest(); if (currentLink.isEmpty()) { KMessageBox::sorry(krMainWindow, i18n("The current file is not a link, so it cannot be redirected.")); return; } // ask the user for a new destination bool ok = false; QString newLink = QInputDialog::getText(krMainWindow, i18n("Link Redirection"), i18n("Please enter the new link destination:"), QLineEdit::Normal, currentLink, &ok); // if the user canceled - quit if (!ok || newLink == currentLink) return; // delete the current link if (unlink(file.toLocal8Bit()) == -1) { KMessageBox::sorry(krMainWindow, i18n("Cannot remove old link: %1", file)); return; } // try to create a new symlink if (symlink(newLink.toLocal8Bit(), file.toLocal8Bit()) == -1) { KMessageBox:: /* --=={ Patch by Heiner }==-- */sorry(krMainWindow, i18n("Failed to create a new link: %1", file)); return; } } void ListPanelFunc::krlink(bool sym) { if (!files()->isLocal()) { KMessageBox::sorry(krMainWindow, i18n("You can create links only on local file systems")); return; } QString name = panel->getCurrentName(); // ask the new link name.. bool ok = false; QString linkName = QInputDialog::getText(krMainWindow, i18n("New Link"), i18n("Create a new link to: %1", name), QLineEdit::Normal, name, &ok); // if the user canceled - quit if (!ok || linkName == name) return; // if the name is already taken - quit if (files()->getFileItem(linkName) != nullptr) { KMessageBox::sorry(krMainWindow, i18n("A folder or a file with this name already exists.")); return; } // make link name and target absolute path if (linkName.left(1) != "/") linkName = files()->currentDirectory().path() + '/' + linkName; name = files()->getUrl(name).path(); if (sym) { if (symlink(name.toLocal8Bit(), linkName.toLocal8Bit()) == -1) KMessageBox::sorry(krMainWindow, i18n("Failed to create a new symlink '%1' to: '%2'", linkName, name)); } else { if (link(name.toLocal8Bit(), linkName.toLocal8Bit()) == -1) KMessageBox::sorry(krMainWindow, i18n("Failed to create a new link '%1' to '%2'", linkName, name)); } } void ListPanelFunc::view() { panel->searchBar->hideBarIfSearching(); QString fileName = panel->getCurrentName(); if (fileName.isNull()) return; // if we're trying to view a directory, just exit FileItem *fileitem = files()->getFileItem(fileName); if (!fileitem || fileitem->isDir()) return; if (!fileitem->isReadable()) { KMessageBox::sorry(nullptr, i18n("No permissions to view this file.")); return; } // call KViewer. KrViewer::view(files()->getUrl(fileName)); // nothing more to it! } void ListPanelFunc::viewDlg() { // ask the user for a url to view QUrl dest = KChooseDir::getFile(i18n("Enter a URL to view:"), QUrl(panel->getCurrentName()), panel->virtualPath()); if (dest.isEmpty()) return ; // the user canceled KrViewer::view(dest); // view the file } void ListPanelFunc::terminal() { SLOTS->runTerminal(panel->lastLocalPath()); } void ListPanelFunc::edit() { panel->searchBar->hideBarIfSearching(); KFileItem tmp; if (fileToCreate.isEmpty()) { QString name = panel->getCurrentName(); if (name.isNull()) return; fileToCreate = files()->getUrl(name); } tmp = KFileItem(fileToCreate); if (tmp.isDir()) { KMessageBox::sorry(krMainWindow, i18n("You cannot edit a folder")); fileToCreate = QUrl(); return; } if (!tmp.isReadable()) { KMessageBox::sorry(nullptr, i18n("No permissions to edit this file.")); fileToCreate = QUrl(); return; } KrViewer::edit(fileToCreate); fileToCreate = QUrl(); } void ListPanelFunc::editNew() { if(!fileToCreate.isEmpty()) return; // ask the user for the filename to edit fileToCreate = KChooseDir::getFile(i18n("Enter the filename to edit:"), QUrl(panel->getCurrentName()), panel->virtualPath()); if(fileToCreate.isEmpty()) return ; // the user canceled // if the file exists, edit it instead of creating a new one QFile f(fileToCreate.toLocalFile()); if(f.exists()) { edit(); } else { auto *tempFile = new QTemporaryFile; tempFile->open(); KIO::CopyJob *job = KIO::copy(QUrl::fromLocalFile(tempFile->fileName()), fileToCreate); job->setUiDelegate(nullptr); job->setDefaultPermissions(true); connect(job, &KIO::CopyJob::result, this, &ListPanelFunc::slotFileCreated); connect(job, &KIO::CopyJob::result, tempFile, &QTemporaryFile::deleteLater); } } void ListPanelFunc::slotFileCreated(KJob *job) { if(!job->error() || job->error() == KIO::ERR_FILE_ALREADY_EXIST) { KrViewer::edit(fileToCreate); if(KIO::upUrl(fileToCreate).matches(panel->virtualPath(), QUrl::StripTrailingSlash)) refresh(); else if(KIO::upUrl(fileToCreate).matches(panel->otherPanel()->virtualPath(), QUrl::StripTrailingSlash)) otherFunc()->refresh(); } else KMessageBox::sorry(krMainWindow, job->errorString()); fileToCreate = QUrl(); } void ListPanelFunc::copyFiles(bool enqueue, bool move) { panel->searchBar->hideBarIfSearching(); const QStringList fileNames = panel->getSelectedNames(); if (fileNames.isEmpty()) return ; // safety QUrl destination = panel->otherPanel()->virtualPath(); bool fullDestPath = false; if (fileNames.count() == 1 && otherFunc()->files()->type() != FileSystem::FS_VIRTUAL) { FileItem *item = files()->getFileItem(fileNames[0]); if (item && !item->isDir()) { fullDestPath = true; // add original filename to destination destination.setPath(QDir(destination.path()).filePath(item->getUrl().fileName())); } } if (!fullDestPath) { destination = FileSystem::ensureTrailingSlash(destination); } const KConfigGroup group(krConfig, "Advanced"); const bool showDialog = move ? group.readEntry("Confirm Move", _ConfirmMove) : group.readEntry("Confirm Copy", _ConfirmCopy); if (showDialog) { QString operationText; if (move) { operationText = fileNames.count() == 1 ? i18n("Move %1 to:", fileNames.first()) : i18np("Move %1 file to:", "Move %1 files to:", fileNames.count()); } else { operationText = fileNames.count() == 1 ? i18n("Copy %1 to:", fileNames.first()) : i18np("Copy %1 file to:", "Copy %1 files to:", fileNames.count()); } // ask the user for the copy/move dest const KChooseDir::ChooseResult result = KChooseDir::getCopyDir(operationText, destination, panel->virtualPath()); destination = result.url; if (destination.isEmpty()) return ; // the user canceled enqueue = result.enqueue; } const JobMan::StartMode startMode = enqueue && krJobMan->isQueueModeEnabled() ? JobMan::Delay : !enqueue && !krJobMan->isQueueModeEnabled() ? JobMan::Start : JobMan::Enqueue; const QList fileUrls = files()->getUrls(fileNames); if (move) { // after the delete return the cursor to the first unmarked file above the current item panel->prepareToDelete(); } // make sure the user does not overwrite multiple files by mistake if (fileNames.count() > 1) { destination = FileSystem::ensureTrailingSlash(destination); } const KIO::CopyJob::CopyMode mode = move ? KIO::CopyJob::Move : KIO::CopyJob::Copy; FileSystemProvider::instance().startCopyFiles(fileUrls, destination, mode, true, startMode); if(KConfigGroup(krConfig, "Look&Feel").readEntry("UnselectBeforeOperation", _UnselectBeforeOperation)) { panel->view->saveSelection(); panel->view->unselectAll(); } } // called from SLOTS to begin the renaming process void ListPanelFunc::rename() { panel->searchBar->hideBarIfSearching(); panel->view->renameCurrentItem(); } // called by signal itemRenamed() from the view to complete the renaming process void ListPanelFunc::rename(const QString &oldname, const QString &newname) { if (oldname == newname) return ; // do nothing // set current after rename panel->view->setNameToMakeCurrent(newname); // as always - the filesystem do the job files()->rename(oldname, newname); } void ListPanelFunc::mkdir() { // ask the new dir name.. // suggested name is the complete name for the directories // while filenames are suggested without their extension QString suggestedName = panel->getCurrentName(); if (!suggestedName.isEmpty() && !files()->getFileItem(suggestedName)->isDir()) suggestedName = QFileInfo(suggestedName).completeBaseName(); const QString dirName = QInputDialog::getText(krMainWindow, i18n("New folder"), i18n("Folder's name:"), QLineEdit::Normal, suggestedName); const QString firstName = dirName.section('/', 0, 0, QString::SectionIncludeLeadingSep); // if the user canceled or the name was composed of slashes - quit if (!dirName.startsWith('/') && firstName.isEmpty()) { return; } // notify user about existing folder if only single directory was given if (!dirName.contains('/') && files()->getFileItem(firstName)) { // focus the existing directory panel->view->setCurrentItem(firstName); // show error message KMessageBox::sorry(krMainWindow, i18n("A folder or a file with this name already exists.")); return; } // focus new directory when next refresh happens panel->view->setNameToMakeCurrent(firstName); // create new directory (along with underlying directories if necessary) files()->mkDir(dirName); } void ListPanelFunc::defaultOrAlternativeDeleteFiles(bool invert) { const bool trash = KConfigGroup(krConfig, "General").readEntry("Move To Trash", _MoveToTrash); deleteFiles(trash != invert); } void ListPanelFunc::deleteFiles(bool moveToTrash) { panel->searchBar->hideBarIfSearching(); const bool isVFS = files()->type() == FileSystem::FS_VIRTUAL; if (isVFS && files()->isRoot()) { // only virtual deletion possible removeVirtualFiles(); return; } // first get the selected file names list const QStringList fileNames = panel->getSelectedNames(); if (fileNames.isEmpty()) return; // move to trash: only if possible moveToTrash = moveToTrash && files()->canMoveToTrash(fileNames); // now ask the user if he/she is sure: const QList confirmedUrls = confirmDeletion( files()->getUrls(fileNames), moveToTrash, isVFS, false); if (confirmedUrls.isEmpty()) return; // nothing to delete // after the delete return the cursor to the first unmarked // file above the current item; panel->prepareToDelete(); // let the filesystem do the job... files()->deleteFiles(confirmedUrls, moveToTrash); } QList ListPanelFunc::confirmDeletion(const QList &urls, bool moveToTrash, bool virtualFS, bool showPath) { QStringList files; - for (QUrl fileUrl : urls) { + for (const QUrl& fileUrl : urls) { files.append(showPath ? fileUrl.toDisplayString(QUrl::PreferLocalFile) : fileUrl.fileName()); } const KConfigGroup advancedGroup(krConfig, "Advanced"); if (advancedGroup.readEntry("Confirm Delete", _ConfirmDelete)) { QString s; // text KGuiItem b; // continue button if (moveToTrash) { 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()); b = KGuiItem(i18n("&Trash")); } else if (virtualFS) { s = i18np("Do you really want to delete this item physically (not just " "removing it from the virtual items)?", "Do you really want to delete these %1 items physically (not just " "removing them from the virtual items)?", files.count()); b = KStandardGuiItem::del(); } else { s = i18np("Do you really want to delete this item?", "Do you really want to delete these %1 items?", files.count()); b = KStandardGuiItem::del(); } // show message // note: i'm using continue and not yes/no because the yes/no has cancel as default button if (KMessageBox::warningContinueCancelList(krMainWindow, s, files, i18n("Warning"), b) != KMessageBox::Continue) { return QList(); } } // we want to warn the user about non empty dir const bool emptyDirVerify = advancedGroup.readEntry("Confirm Unempty Dir", _ConfirmUnemptyDir); QList toDelete; if (emptyDirVerify) { QSet confirmedFiles = urls.toSet(); - for (QUrl fileUrl : urls) { + for (const QUrl& fileUrl : urls) { if (!fileUrl.isLocalFile()) { continue; // TODO only local fs supported } const QString filePath = fileUrl.toLocalFile(); QFileInfo fileInfo(filePath); if (fileInfo.isDir() && !fileInfo.isSymLink()) { // read local dir... const QDir dir(filePath); if (!dir.entryList(QDir::AllEntries | QDir::System | QDir::Hidden | QDir::NoDotAndDotDot).isEmpty()) { // ...is not empty, ask user const QString fileString = showPath ? filePath : fileUrl.fileName(); const KMessageBox::ButtonCode result = KMessageBox::warningYesNoCancel( krMainWindow, i18n("

Folder %1 is not empty.

", fileString) + (moveToTrash ? i18n("

Skip this one or trash all?

") : i18n("

Skip this one or delete all?

")), QString(), KGuiItem(i18n("&Skip")), KGuiItem(moveToTrash ? i18n("&Trash All") : i18n("&Delete All"))); if (result == KMessageBox::Yes) { confirmedFiles.remove(fileUrl); // skip this dir } else if (result == KMessageBox::No) { break; // accept all remaining } else { return QList(); // cancel } } } } toDelete = confirmedFiles.toList(); } else { toDelete = urls; } return toDelete; } void ListPanelFunc::removeVirtualFiles() { if (files()->type() != FileSystem::FS_VIRTUAL) { qWarning() << "filesystem not virtual"; return; } const QStringList fileNames = panel->getSelectedNames(); if (fileNames.isEmpty()) return; const QString text = i18np("Do you really want to delete this virtual item (physical files stay untouched)?", "Do you really want to delete these %1 virtual items (physical files stay " "untouched)?", fileNames.count()); if (KMessageBox::warningContinueCancelList(krMainWindow, text, fileNames, i18n("Warning"), KStandardGuiItem::remove()) != KMessageBox::Continue) return; auto *fileSystem = dynamic_cast(files()); fileSystem->remove(fileNames); } void ListPanelFunc::goInside(const QString& name) { openFileNameInternal(name, false); } void ListPanelFunc::runCommand(const QString& cmd) { qDebug() << "command=" << cmd; const QString workdir = panel->virtualPath().isLocalFile() ? panel->virtualPath().path() : QDir::homePath(); if(!KRun::runCommand(cmd, krMainWindow, workdir)) KMessageBox::error(nullptr, i18n("Could not start %1", cmd)); } void ListPanelFunc::runService(const KService &service, const QList& urls) { qDebug() << "service name=" << service.name(); KIO::DesktopExecParser parser(service, urls); QStringList args = parser.resultingArguments(); if (!args.isEmpty()) runCommand(KShell::joinArgs(args)); else KMessageBox::error(nullptr, i18n("%1 cannot open %2", service.name(), KrServices::toStringList(urls).join(", "))); } void ListPanelFunc::displayOpenWithDialog(const QList& urls) { // NOTE: we are not using KRun::displayOpenWithDialog() because we want the commands working // directory to be the panel directory KOpenWithDialog dialog(urls, panel); dialog.hideRunInTerminal(); if (dialog.exec()) { KService::Ptr service = dialog.service(); if(!service) service = KService::Ptr(new KService(dialog.text(), dialog.text(), QString())); runService(*service, urls); } } QUrl ListPanelFunc::browsableArchivePath(const QString &filename) { FileItem *fileitem = files()->getFileItem(filename); QUrl url = files()->getUrl(filename); QString mime = fileitem->getMime(); if(url.isLocalFile()) { QString protocol = KrServices::registeredProtocol(mime); if(!protocol.isEmpty()) { url.setScheme(protocol); return url; } } return QUrl(); } // this is done when you double click on a file void ListPanelFunc::execute(const QString& name) { openFileNameInternal(name, true); } void ListPanelFunc::pack() { const QStringList fileNames = panel->getSelectedNames(); if (fileNames.isEmpty()) return ; // safety if (fileNames.count() == 0) return ; // nothing to pack // choose the default name QString defaultName = panel->virtualPath().fileName(); if (defaultName.isEmpty()) defaultName = "pack"; if (fileNames.count() == 1) defaultName = fileNames.first(); // ask the user for archive name and packer new PackGUI(defaultName, panel->otherPanel()->virtualPath().toDisplayString(QUrl::PreferLocalFile | QUrl::StripTrailingSlash), fileNames.count(), fileNames.first()); if (PackGUI::type.isEmpty()) { return ; // the user canceled } // check for partial URLs if (!PackGUI::destination.contains(":/") && !PackGUI::destination.startsWith('/')) { PackGUI::destination = panel->virtualPath().toDisplayString() + '/' + PackGUI::destination; } QString destDir = PackGUI::destination; if (!destDir.endsWith('/')) destDir += '/'; bool packToOtherPanel = (destDir == FileSystem::ensureTrailingSlash(panel->otherPanel()->virtualPath()).toDisplayString(QUrl::PreferLocalFile)); QUrl destURL = QUrl::fromUserInput(destDir + PackGUI::filename + '.' + PackGUI::type, QString(), QUrl::AssumeLocalFile); if (destURL.isLocalFile() && QFile::exists(destURL.path())) { QString msg = i18n("

The archive %1.%2 already exists. Do you want to overwrite it?

All data in the previous archive will be lost.

", PackGUI::filename, PackGUI::type); if (PackGUI::type == "zip") { msg = i18n("

The archive %1.%2 already exists. Do you want to overwrite it?

Zip will replace identically named entries in the zip archive or add entries for new names.

", PackGUI::filename, PackGUI::type); } if (KMessageBox::warningContinueCancel(krMainWindow, msg, QString(), KStandardGuiItem::overwrite()) == KMessageBox::Cancel) return ; // stop operation } else if (destURL.scheme() == QStringLiteral("virt")) { KMessageBox::error(krMainWindow, i18n("Cannot pack files onto a virtual destination.")); return; } PackJob * job = PackJob::createPacker(files()->currentDirectory(), destURL, fileNames, PackGUI::type, PackGUI::extraProps); job->setUiDelegate(new KIO::JobUiDelegate()); KIO::getJobTracker()->registerJob(job); job->uiDelegate()->setAutoErrorHandlingEnabled(true); if (packToOtherPanel) connect(job, &PackJob::result, panel->otherPanel()->func, &ListPanelFunc::refresh); } void ListPanelFunc::testArchive() { const QStringList fileNames = panel->getSelectedNames(); if (fileNames.isEmpty()) return ; // safety TestArchiveJob * job = TestArchiveJob::testArchives(files()->currentDirectory(), fileNames); job->setUiDelegate(new KIO::JobUiDelegate()); KIO::getJobTracker()->registerJob(job); job->uiDelegate()->setAutoErrorHandlingEnabled(true); } void ListPanelFunc::unpack() { const QStringList fileNames = panel->getSelectedNames(); if (fileNames.isEmpty()) return ; // safety QString s; if (fileNames.count() == 1) s = i18n("Unpack %1 to:", fileNames[0]); else s = i18np("Unpack %1 file to:", "Unpack %1 files to:", fileNames.count()); // ask the user for the copy dest QUrl dest = KChooseDir::getDir(s, panel->otherPanel()->virtualPath(), panel->virtualPath()); if (dest.isEmpty()) return ; // the user canceled bool packToOtherPanel = (dest.matches(panel->otherPanel()->virtualPath(), QUrl::StripTrailingSlash)); UnpackJob * job = UnpackJob::createUnpacker(files()->currentDirectory(), dest, fileNames); job->setUiDelegate(new KIO::JobUiDelegate()); KIO::getJobTracker()->registerJob(job); job->uiDelegate()->setAutoErrorHandlingEnabled(true); if (packToOtherPanel) connect(job, &UnpackJob::result, panel->otherPanel()->func, &ListPanelFunc::refresh); } void ListPanelFunc::createChecksum() { if (!panel->func->files()->isLocal()) return; // only local, non-virtual files are supported KrViewItemList items; panel->view->getSelectedKrViewItems(&items); QStringList fileNames; for (KrViewItem *item : items) { FileItem *file = panel->func->getFileItem(item); fileNames.append(file->getUrl().fileName()); } if (fileNames.isEmpty()) return; // nothing selected and no valid current file Checksum::startCreationWizard(panel->virtualPath().toLocalFile(), fileNames); } void ListPanelFunc::matchChecksum() { if (!panel->func->files()->isLocal()) return; // only local, non-virtual files are supported FileItem *currentItem = files()->getFileItem(panel->getCurrentName()); const QString checksumFilePath = currentItem ? currentItem->getUrl().toLocalFile() : QString(); Checksum::startVerifyWizard(panel->virtualPath().toLocalFile(), checksumFilePath); } void ListPanelFunc::calcSpace() { QStringList fileNames; panel->view->getSelectedItems(&fileNames); if (fileNames.isEmpty()) { // current file is ".." dummy file panel->view->selectAllIncludingDirs(); panel->view->getSelectedItems(&fileNames); panel->view->unselectAll(); } SizeCalculator *sizeCalculator = createAndConnectSizeCalculator(files()->getUrls(fileNames)); KrCalcSpaceDialog::showDialog(panel, sizeCalculator); } void ListPanelFunc::quickCalcSpace() { const QString currentName = panel->getCurrentName(); if (currentName.isEmpty()) { // current file is ".." dummy, do a verbose calcSpace calcSpace(); return; } if (!_quickSizeCalculator) { _quickSizeCalculator = createAndConnectSizeCalculator(QList()); panel->connectQuickSizeCalculator(_quickSizeCalculator); } _quickSizeCalculator->add(files()->getUrl(currentName)); } SizeCalculator *ListPanelFunc::createAndConnectSizeCalculator(const QList &urls) { auto *sizeCalculator = new SizeCalculator(urls); connect(sizeCalculator, &SizeCalculator::calculated, this, &ListPanelFunc::slotSizeCalculated); connect(sizeCalculator, &SizeCalculator::finished, panel, &ListPanel::slotUpdateTotals); connect(this, &ListPanelFunc::destroyed, sizeCalculator, &SizeCalculator::deleteLater); return sizeCalculator; } void ListPanelFunc::slotSizeCalculated(const QUrl &url, KIO::filesize_t size) { KrViewItem *item = panel->view->findItemByUrl(url); if (!item) return; item->setSize(size); item->redraw(); } void ListPanelFunc::FTPDisconnect() { // you can disconnect only if connected! if (files()->isRemote()) { panel->_actions->actFTPDisconnect->setEnabled(false); panel->view->setNameToMakeCurrent(QString()); openUrl(QUrl::fromLocalFile(panel->lastLocalPath())); } } void ListPanelFunc::newFTPconnection() { QUrl url = KRSpWidgets::newFTP(); // if the user canceled - quit if (url.isEmpty()) return; panel->_actions->actFTPDisconnect->setEnabled(true); qDebug() << "URL=" << url.toDisplayString(); openUrl(url); } void ListPanelFunc::properties() { const QStringList names = panel->getSelectedNames(); if (names.isEmpty()) { return ; // no names... } KFileItemList fileItems; - for (const QString name : names) { + for (const QString& name : names) { FileItem *fileitem = files()->getFileItem(name); if (!fileitem) { continue; } fileItems.push_back(KFileItem(fileitem->getEntry(), files()->getUrl(name))); } if (fileItems.isEmpty()) return; // Show the properties dialog auto *dialog = new KPropertiesDialog(fileItems, krMainWindow); connect(dialog, &KPropertiesDialog::applied, this, &ListPanelFunc::refresh); dialog->show(); } void ListPanelFunc::refreshActions() { panel->updateButtons(); if(ACTIVE_PANEL != panel) return; QString protocol = files()->currentDirectory().scheme(); krRemoteEncoding->setEnabled(protocol == "ftp" || protocol == "sftp" || protocol == "fish" || protocol == "krarc"); //krMultiRename->setEnabled( fileSystemType == FileSystem::FS_NORMAL ); // batch rename //krProperties ->setEnabled( fileSystemType == FileSystem::FS_NORMAL || fileSystemType == FileSystem::FS_FTP ); // file properties /* krUnpack->setEnabled(true); // unpack archive krTest->setEnabled(true); // test archive krSelect->setEnabled(true); // select a group by filter krSelectAll->setEnabled(true); // select all files krUnselect->setEnabled(true); // unselect by filter krUnselectAll->setEnabled( true); // remove all selections krInvert->setEnabled(true); // invert the selection krFTPConnect->setEnabled(true); // connect to an ftp krFTPNew->setEnabled(true); // create a new connection krAllFiles->setEnabled(true); // show all files in list krCustomFiles->setEnabled(true); // show a custom set of files krRoot->setEnabled(true); // go all the way up krExecFiles->setEnabled(true); // show only executables */ panel->_actions->setViewActions[panel->panelType]->setChecked(true); panel->_actions->actFTPDisconnect->setEnabled(files()->isRemote()); // allow disconnecting a network session panel->_actions->actCreateChecksum->setEnabled(files()->isLocal()); panel->_actions->actDirUp->setEnabled(!files()->isRoot()); panel->_actions->actRoot->setEnabled(!panel->virtualPath().matches(QUrl::fromLocalFile(ROOT_DIR), QUrl::StripTrailingSlash)); panel->_actions->actHome->setEnabled(!atHome()); panel->_actions->actHistoryBackward->setEnabled(history->canGoBack()); panel->_actions->actHistoryForward->setEnabled(history->canGoForward()); panel->view->op()->emitRefreshActions(); } FileSystem* ListPanelFunc::files() { if (!fileSystemP) fileSystemP = FileSystemProvider::instance().getFilesystem(QUrl::fromLocalFile(ROOT_DIR)); return fileSystemP; } QUrl ListPanelFunc::virtualDirectory() { return _isPaused ? history->currentUrl() : files()->currentDirectory(); } FileItem *ListPanelFunc::getFileItem(const QString &name) { return files()->getFileItem(name); } FileItem *ListPanelFunc::getFileItem(KrViewItem *item) { return files()->getFileItem(item->name()); } void ListPanelFunc::clipboardChanged(QClipboard::Mode mode) { if (mode == QClipboard::Clipboard && this == copyToClipboardOrigin) { disconnect(QApplication::clipboard(), nullptr, this, nullptr); copyToClipboardOrigin = nullptr; } } void ListPanelFunc::copyToClipboard(bool move) { const QStringList fileNames = panel->getSelectedNames(); if (fileNames.isEmpty()) return ; // safety QList fileUrls = files()->getUrls(fileNames); auto *mimeData = new QMimeData; mimeData->setData("application/x-kde-cutselection", move ? "1" : "0"); mimeData->setUrls(fileUrls); if (copyToClipboardOrigin) disconnect(QApplication::clipboard(), nullptr, copyToClipboardOrigin, nullptr); copyToClipboardOrigin = this; QApplication::clipboard()->setMimeData(mimeData, QClipboard::Clipboard); connect(QApplication::clipboard(), &QClipboard::changed, this, &ListPanelFunc::clipboardChanged); } void ListPanelFunc::pasteFromClipboard() { QClipboard * cb = QApplication::clipboard(); ListPanelFunc *origin = nullptr; if (copyToClipboardOrigin) { disconnect(QApplication::clipboard(), nullptr, copyToClipboardOrigin, nullptr); origin = copyToClipboardOrigin; copyToClipboardOrigin = nullptr; } bool move = false; const QMimeData *data = cb->mimeData(); if (data->hasFormat("application/x-kde-cutselection")) { QByteArray a = data->data("application/x-kde-cutselection"); if (!a.isEmpty()) move = (a.at(0) == '1'); // true if 1 } QList urls = data->urls(); if (urls.isEmpty()) return; if(origin && KConfigGroup(krConfig, "Look&Feel").readEntry("UnselectBeforeOperation", _UnselectBeforeOperation)) { origin->panel->view->saveSelection(); for(KrViewItem *item = origin->panel->view->getFirst(); item != nullptr; item = origin->panel->view->getNext(item)) { if (urls.contains(item->getFileItem()->getUrl())) item->setSelected(false); } } files()->addFiles(urls, move ? KIO::CopyJob::Move : KIO::CopyJob::Copy); } ListPanelFunc* ListPanelFunc::otherFunc() { return panel->otherPanel()->func; } void ListPanelFunc::historyGotoPos(int pos) { if(history->gotoPos(pos)) refresh(); } void ListPanelFunc::historyBackward() { if(history->goBack()) refresh(); } void ListPanelFunc::historyForward() { if(history->goForward()) refresh(); } void ListPanelFunc::dirUp() { openUrl(KIO::upUrl(files()->currentDirectory()), files()->currentDirectory().fileName()); } void ListPanelFunc::home() { openUrl(QUrl::fromLocalFile(QDir::homePath())); } void ListPanelFunc::root() { openUrl(QUrl::fromLocalFile(ROOT_DIR)); } void ListPanelFunc::cdToOtherPanel() { openUrl(panel->otherPanel()->virtualPath()); } void ListPanelFunc::syncOtherPanel() { otherFunc()->openUrl(panel->virtualPath()); } bool ListPanelFunc::atHome() { return QUrl::fromLocalFile(QDir::homePath()).matches(panel->virtualPath(), QUrl::StripTrailingSlash); } diff --git a/krusader/Synchronizer/synchronizerdirlist.cpp b/krusader/Synchronizer/synchronizerdirlist.cpp index 24a2d928..a897e9cb 100644 --- a/krusader/Synchronizer/synchronizerdirlist.cpp +++ b/krusader/Synchronizer/synchronizerdirlist.cpp @@ -1,181 +1,181 @@ /***************************************************************************** * Copyright (C) 2006 Csaba Karai * * Copyright (C) 2006-2018 Krusader Krew [https://krusader.org] * * * * This file is part of Krusader [https://krusader.org]. * * * * Krusader is free software: you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation, either version 2 of the License, or * * (at your option) any later version. * * * * Krusader is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with Krusader. If not, see [http://www.gnu.org/licenses/]. * *****************************************************************************/ #include "synchronizerdirlist.h" #ifdef HAVE_POSIX_ACL #include #ifdef HAVE_NON_POSIX_ACL_EXTENSIONS #include #endif #endif #include // QtCore #include // QtWidgets #include #include #include #include #include #include "../FileSystem/filesystem.h" #include "../FileSystem/krpermhandler.h" #include "../krservices.h" SynchronizerDirList::SynchronizerDirList(QWidget *w, bool hidden) : QObject(), QHash(), fileIterator(nullptr), parentWidget(w), busy(false), result(false), ignoreHidden(hidden), currentUrl() { } SynchronizerDirList::~SynchronizerDirList() { if (fileIterator) delete fileIterator; QHashIterator< QString, FileItem *> lit(*this); while (lit.hasNext()) delete lit.next().value(); } FileItem *SynchronizerDirList::search(const QString &name, bool ignoreCase) { if (!ignoreCase) { if (!contains(name)) return nullptr; return (*this)[ name ]; } QHashIterator iter(*this); iter.toFront(); QString file = name.toLower(); while (iter.hasNext()) { FileItem *item = iter.next().value(); if (file == item->getName().toLower()) return item; } return nullptr; } FileItem *SynchronizerDirList::first() { if (fileIterator == nullptr) fileIterator = new QHashIterator (*this); fileIterator->toFront(); if (fileIterator->hasNext()) return fileIterator->next().value(); return nullptr; } FileItem *SynchronizerDirList::next() { if (fileIterator == nullptr) fileIterator = new QHashIterator (*this); if (fileIterator->hasNext()) return fileIterator->next().value(); return nullptr; } bool SynchronizerDirList::load(const QString &urlIn, bool wait) { if (busy) return false; currentUrl = urlIn; const QUrl url = QUrl::fromUserInput(urlIn, QString(), QUrl::AssumeLocalFile); QHashIterator< QString, FileItem *> lit(*this); while (lit.hasNext()) delete lit.next().value(); clear(); if (fileIterator) { delete fileIterator; fileIterator = nullptr; } if (url.isLocalFile()) { const QString dir = FileSystem::ensureTrailingSlash(url).path(); QT_DIR* qdir = QT_OPENDIR(dir.toLocal8Bit()); if (!qdir) { KMessageBox::error(parentWidget, i18n("Cannot open the folder %1.", dir), i18n("Error")); emit finished(result = false); return false; } QT_DIRENT* dirEnt; while ((dirEnt = QT_READDIR(qdir)) != nullptr) { const QString name = QString::fromLocal8Bit(dirEnt->d_name); if (name == "." || name == "..") continue; if (ignoreHidden && name.startsWith('.')) continue; FileItem *item = FileSystem::createLocalFileItem(name, dir); insert(name, item); } QT_CLOSEDIR(qdir); emit finished(result = true); return true; } else { KIO::ListJob *job = KIO::listDir(KrServices::escapeFileUrl(url), KIO::HideProgressInfo, true); connect(job, &KIO::ListJob::entries, this, &SynchronizerDirList::slotEntries); connect(job, &KIO::ListJob::result, this, &SynchronizerDirList::slotListResult); busy = true; if (!wait) return true; while (busy) qApp->processEvents(); return result; } } void SynchronizerDirList::slotEntries(KIO::Job *job, const KIO::UDSEntryList& entries) { auto *listJob = dynamic_cast(job); - for (const KIO::UDSEntry entry : entries) { + for (const KIO::UDSEntry& entry : entries) { FileItem *item = FileSystem::createFileItemFromKIO(entry, listJob->url()); if (item) { insert(item->getName(), item); } } } void SynchronizerDirList::slotListResult(KJob *job) { busy = false; if (job && job->error()) { job->uiDelegate()->showErrorMessage(); emit finished(result = false); return; } emit finished(result = true); } diff --git a/krusader/UserAction/expander.cpp b/krusader/UserAction/expander.cpp index d77386b7..239c0197 100644 --- a/krusader/UserAction/expander.cpp +++ b/krusader/UserAction/expander.cpp @@ -1,1222 +1,1222 @@ /***************************************************************************** * Copyright (C) 2004 Jonas Bähr * * Copyright (C) 2004 Shie Erlich * * Copyright (C) 2004-2018 Krusader Krew [https://krusader.org] * * * * This file is part of Krusader [https://krusader.org]. * * * * Krusader is free software: you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation, either version 2 of the License, or * * (at your option) any later version. * * * * Krusader is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with Krusader. If not, see [http://www.gnu.org/licenses/]. * *****************************************************************************/ #include "expander.h" #include "../krusader.h" #include "../krusaderview.h" #include "../panelmanager.h" #include "../Panel/listpanel.h" #include "../Panel/panelfunc.h" #include "../Panel/PanelView/krview.h" #include "../Search/krsearchdialog.h" #include "../GUI/profilemanager.h" #include "../FileSystem/filesystemprovider.h" #include "../KViewer/krviewer.h" #include "../krservices.h" #ifdef SYNCHRONIZER_ENABLED #include "../Synchronizer/synchronizergui.h" #endif #ifdef __KJSEMBED__ #include "../KrJS/krjs.h" #endif // QtCore #include #include #include #include #include // QtGui #include // QtWidgets #include #include #include #include #include #include using namespace std; #define NEED_PANEL if (panel==0) { panelMissingError(_expression,exp); return QString(); } inline void exp_placeholder::setError(Expander& exp, const Error& e) { exp.setError(e); } inline QStringList exp_placeholder::splitEach(const TagString& s) { return Expander::splitEach(s); } inline exp_placeholder::exp_placeholder() = default; void exp_placeholder::panelMissingError(const QString &s, Expander& exp) { exp.setError(Error(Error::exp_S_FATAL, Error::exp_C_ARGUMENT, i18n("Needed panel specification missing in expander %1", s))); } QStringList exp_placeholder::fileList(const KrPanel* const panel, const QString& type, const QString& mask, const bool omitPath, const bool useUrl, Expander& exp, const QString& error) { QStringList items; if (type.isEmpty() || type == "all") panel->view->getItemsByMask(mask, &items); else if (type == "files") panel->view->getItemsByMask(mask, &items, false, true); else if (type == "dirs") panel->view->getItemsByMask(mask, &items, true, false); else if (type == "selected") panel->view->getSelectedItems(&items); else { setError(exp, Error(Error::exp_S_FATAL, Error::exp_C_ARGUMENT, i18n("Expander: Bad argument to %1: %2 is not valid item specifier", error, type))); return QStringList(); } if (!omitPath) { // add the current path // translate to urls using filesystem QList list = panel->func->files()->getUrls(items); items.clear(); // parse everything to a single qstring foreach(const QUrl &url, list) { items.push_back(useUrl ? url.url() : url.path()); } } return items; } namespace { class exp_simpleplaceholder : public exp_placeholder { public: EXP_FUNC Q_DECL_OVERRIDE; virtual TagString expFunc(const KrPanel*, const QStringList&, const bool&, Expander&) const = 0; }; #define PLACEHOLDER_CLASS(name) \ class name : public exp_placeholder { \ public: \ name(); \ virtual TagString expFunc ( const KrPanel*, const TagStringList&, const bool&, Expander& ) const Q_DECL_OVERRIDE; \ }; #define SIMPLE_PLACEHOLDER_CLASS(name) \ class name : public exp_simpleplaceholder { \ public: \ using exp_simpleplaceholder::expFunc; \ name(); \ virtual TagString expFunc ( const KrPanel*, const QStringList&, const bool&, Expander& ) const Q_DECL_OVERRIDE; \ }; /** * expands %_Path% ('_' is replaced by 'a', 'o', 'r' or 'l' to indicate the active, other, right or left panel) with the path of the specified panel */ SIMPLE_PLACEHOLDER_CLASS(exp_Path) /** * expands %_Count% ('_' is replaced by 'a', 'o', 'r' or 'l' to indicate the active, other, right or left panel) with the number of items, which type is specified by the first Parameter */ SIMPLE_PLACEHOLDER_CLASS(exp_Count) /** * expands %_Filter% ('_' is replaced by 'a', 'o', 'r' or 'l' to indicate the active, other, right or left panel) with the correspondent filter (ie: "*.cpp") */ SIMPLE_PLACEHOLDER_CLASS(exp_Filter) /** * expands %_Current% ('_' is replaced by 'a', 'o', 'r' or 'l' to indicate the active, other, right or left panel) with the current item ( != the selected ones) */ SIMPLE_PLACEHOLDER_CLASS(exp_Current); /** * expands %_List% ('_' is replaced by 'a', 'o', 'r' or 'l' to indicate the active, other, right or left panel) with a list of items, which type is specified by the first Parameter */ SIMPLE_PLACEHOLDER_CLASS(exp_List); /** * expands %_ListFile% ('_' is replaced by 'a', 'o', 'r' or 'l' to indicate * the active, other, right or left panel) with the name of a temporary file, * containing a list of items, which type is specified by the first Parameter */ SIMPLE_PLACEHOLDER_CLASS(exp_ListFile); /** * expands %_Ask% ('_' is necessary because there is no panel needed) * with the return of an input-dialog */ SIMPLE_PLACEHOLDER_CLASS(exp_Ask); /** * This copies it's first Parameter to the clipboard */ PLACEHOLDER_CLASS(exp_Clipboard); /** * This selects all items by the mask given with the first Parameter */ SIMPLE_PLACEHOLDER_CLASS(exp_Select); /** * This changes the panel'spath to the value given with the first Parameter. */ SIMPLE_PLACEHOLDER_CLASS(exp_Goto); /** * This is equal to 'cp '. */ PLACEHOLDER_CLASS(exp_Copy); /** * This is equal to 'mv '. */ PLACEHOLDER_CLASS(exp_Move); #ifdef SYNCHRONIZER_ENABLED /** * This opens the synchronizer with a given profile */ SIMPLE_PLACEHOLDER_CLASS(exp_Sync); #endif /** * This opens the searchmodule with a given profile */ SIMPLE_PLACEHOLDER_CLASS(exp_NewSearch); /** * This loads the panel-profile with a given name */ SIMPLE_PLACEHOLDER_CLASS(exp_Profile); /** * This is setting marks in the string where he is later split up for each {all, selected, files, dirs} */ SIMPLE_PLACEHOLDER_CLASS(exp_Each); /** * This sets the sorting on a specific column */ SIMPLE_PLACEHOLDER_CLASS(exp_ColSort); /** * This sets relation between the left and right panel */ SIMPLE_PLACEHOLDER_CLASS(exp_PanelSize); #ifdef __KJSEMBED__ /** * This sets relation between the left and right panel */ SIMPLE_PLACEHOLDER_CLASS(exp_Script); #endif /** * This loads a file in the internal viewer */ SIMPLE_PLACEHOLDER_CLASS(exp_View); //////////////////////////////////////////////////////////// //////////////////////// utils //////////////////////// //////////////////////////////////////////////////////////// /** * escapes everything that confuses bash in filenames * @param s String to manipulate * @return escaped string */ QString bashquote(QString s) { /* // we _can_not_ use this function because it _encloses_ the sting in single-quotes! // In this case quotes strings could not be concatenated anymore return KrServices::quote(s); */ static const QString evilstuff = "\\\"'`()[]{}!?;$&<>| \t\r\n"; // stuff that should get escaped for (auto i : evilstuff) s.replace(i, ('\\' + i)); return s; } QString separateAndQuote(QStringList list, const QString& separator, const bool quote) { if (quote) transform(list.begin(), list.end(), list.begin(), bashquote); // QLineEdit::text() always escapes special characters, revert this for newline and tab QString decodedSeparator = separator; decodedSeparator.replace("\\n", "\n").replace("\\t", "\t"); return list.join(decodedSeparator); } ///////////////////////////////////////////////////////////////////////////////////////////////// /////////////////////////////////// expander classes //////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////////////// exp_Path::exp_Path() { _expression = "Path"; _description = i18n("Panel's Path..."); _needPanel = true; addParameter(exp_parameter(i18n("Automatically escape spaces"), "__yes", false)); } TagString exp_Path::expFunc(const KrPanel* panel, const QStringList& parameter, const bool& useUrl, Expander& exp) const { NEED_PANEL QString result; if (useUrl) result = panel->func->files()->currentDirectory().url() + '/'; else result = panel->func->files()->currentDirectory().path() + '/'; if (parameter.count() > 0 && parameter[0].toLower() == "no") // don't escape spaces return TagString(result); else return TagString(bashquote(result)); } exp_Count::exp_Count() { _expression = "Count"; _description = i18n("Number of..."); _needPanel = true; addParameter(exp_parameter(i18n("Count:"), "__choose:All;Files;Dirs;Selected", false)); } TagString exp_Count::expFunc(const KrPanel* panel, const QStringList& parameter, const bool&, Expander& exp) const { NEED_PANEL int n = -1; if (parameter.count() == 0 || parameter[ 0 ].isEmpty() || parameter[ 0 ].toLower() == "all") n = panel->view->numDirs() + panel->view->numFiles(); else if (parameter[ 0 ].toLower() == "files") n = panel->view->numFiles(); else if (parameter[ 0 ].toLower() == "dirs") n = panel->view->numDirs(); else if (parameter[ 0 ].toLower() == "selected") n = panel->view->numSelected(); else { setError(exp, Error(Error::exp_S_FATAL, Error::exp_C_ARGUMENT, i18n("Expander: Bad argument to Count: %1 is not valid item specifier", parameter[0]))); return QString(); } return TagString(QString("%1").arg(n)); } exp_Filter::exp_Filter() { _expression = "Filter"; _description = i18n("Filter Mask (*.h, *.cpp, etc.)"); _needPanel = true; } TagString exp_Filter::expFunc(const KrPanel* panel, const QStringList&, const bool&, Expander& exp) const { NEED_PANEL return panel->view->filterMask().nameFilter(); } exp_Current::exp_Current() { _expression = "Current"; _description = i18n("Current File (!= Selected File)..."); _needPanel = true; addParameter(exp_parameter(i18n("Omit the current path (optional)"), "__no", false)); addParameter(exp_parameter(i18n("Automatically escape spaces"), "__yes", false)); } TagString exp_Current::expFunc(const KrPanel* panel, const QStringList& parameter, const bool& useUrl, Expander& exp) const { NEED_PANEL QString item = panel->view->getCurrentItem(); if (item == "..") { // if ".." is current, treat this as nothing is current return QString(); } QString result; if (parameter.count() > 0 && parameter[0].toLower() == "yes") // omit the current path result = item; else { const QUrl url = panel->func->files()->getUrl(item); result = useUrl ? url.url() : url.path(); } const bool escapeSpaces = parameter.count() < 2 || parameter[1].toLower() != "no"; return escapeSpaces ? bashquote(result) : result; } exp_List::exp_List() { _expression = "List"; _description = i18n("Item List of..."); _needPanel = true; addParameter(exp_parameter(i18n("Which items:"), "__choose:All;Files;Dirs;Selected", false)); addParameter(exp_parameter(i18n("Separator between the items (optional):"), " ", false)); addParameter(exp_parameter(i18n("Omit the current path (optional)"), "__no", false)); addParameter(exp_parameter(i18n("Mask (optional, all but 'Selected'):"), "__select", false)); addParameter(exp_parameter(i18n("Automatically escape spaces"), "__yes", false)); } TagString exp_List::expFunc(const KrPanel* panel, const QStringList& parameter, const bool& useUrl, Expander& exp) const { NEED_PANEL // get selected items from view QStringList items; QString mask; if (parameter.count() <= 3 || parameter[3].isEmpty()) mask = '*'; else mask = parameter[3]; return separateAndQuote( fileList(panel, parameter.isEmpty() ? QString() : parameter[0].toLower(), mask, parameter.count() > 2 ? parameter[2].toLower() == "yes" : false, useUrl, exp, "List"), parameter.count() > 1 ? parameter[1] : " ", parameter.count() > 4 ? parameter[4].toLower() == "yes" : true); } exp_ListFile::exp_ListFile() { _expression = "ListFile"; _description = i18n("Filename of an Item List..."); _needPanel = true; addParameter(exp_parameter(i18n("Which items:"), "__choose:All;Files;Dirs;Selected", false)); addParameter(exp_parameter(i18n("Separator between the items (optional)"), "\n", false)); addParameter(exp_parameter(i18n("Omit the current path (optional)"), "__no", false)); addParameter(exp_parameter(i18n("Mask (optional, all but 'Selected'):"), "__select", false)); addParameter(exp_parameter(i18n("Automatically escape spaces"), "__no", false)); } TagString exp_ListFile::expFunc(const KrPanel* panel, const QStringList& parameter, const bool& useUrl, Expander& exp) const { NEED_PANEL // get selected items from view QStringList items; QString mask; if (parameter.count() <= 3 || parameter[3].isEmpty()) mask = '*'; else mask = parameter[3]; QTemporaryFile tmpFile(QDir::tempPath() + QLatin1String("/krusader_XXXXXX.itemlist")); tmpFile.setAutoRemove(false); if (!tmpFile.open()) { setError(exp, Error(Error::exp_S_FATAL, Error::exp_C_WORLD, i18n("Expander: temporary file could not be opened (%1)", tmpFile.errorString()))); return QString(); } QTextStream stream(&tmpFile); stream << separateAndQuote( fileList(panel, parameter.isEmpty() ? QString() : parameter[0].toLower(), mask, parameter.count() > 2 ? parameter[2].toLower() == "yes" : false, useUrl, exp, "ListFile"), parameter.count() > 1 ? parameter[1] : "\n", parameter.count() > 4 ? parameter[4].toLower() == "yes" : true) << "\n"; tmpFile.close(); return tmpFile.fileName(); } exp_Select::exp_Select() { _expression = "Select"; _description = i18n("Manipulate the Selection..."); _needPanel = true; addParameter(exp_parameter(i18n("Selection mask:"), "__select", true)); addParameter(exp_parameter(i18n("Manipulate in which way:"), "__choose:Set;Add;Remove", false)); } TagString exp_Select::expFunc(const KrPanel* panel, const QStringList& parameter, const bool& , Expander& exp) const { NEED_PANEL KRQuery mask; if (parameter.count() <= 0 || parameter[0].isEmpty()) mask = KRQuery("*"); else mask = KRQuery(parameter[0]); if (parameter.count() > 1 && parameter[1].toLower() == "list-add") panel->view->select(mask); else if (parameter.count() > 1 && parameter[1].toLower() == "list-remove") panel->view->unselect(mask); else { // parameter[1].toLower() == "set" or isEmpty() or whatever panel->view->unselect(KRQuery("*")); panel->view->select(mask); } return QString(); // this doesn't return anything, that's normal! } exp_Goto::exp_Goto() { _expression = "Goto"; _description = i18n("Jump to a Location..."); _needPanel = true; addParameter(exp_parameter(i18n("Choose a path:"), "__goto", true)); addParameter(exp_parameter(i18n("Open location in a new tab"), "__no", false)); } TagString exp_Goto::expFunc(const KrPanel* panel, const QStringList& parameter, const bool&, Expander& exp) const { NEED_PANEL bool newTab = false; if (parameter.count() > 1 && parameter[1].toLower() == "yes") newTab = true; if (parameter.count() == 0) { setError(exp, Error(Error::exp_S_FATAL, Error::exp_C_ARGUMENT, i18n("Expander: at least 1 parameter is required for Goto."))); return QString(); } QUrl url = QUrl::fromUserInput(parameter[0], QString(), QUrl::AssumeLocalFile); if (newTab) { if (panel == LEFT_PANEL) MAIN_VIEW->leftManager()->slotNewTab(url); else MAIN_VIEW->rightManager()->slotNewTab(url); } else { panel->func->openUrl(url, ""); panel->gui->slotFocusOnMe(); } return QString(); // this doesn't return anything, that's normal! } /* exp_Search::exp_Search() { _expression = "Search"; _description = i18n("Search for files"); _needPanel = true; addParameter( new exp_parameter( i18n("please choose the setting"), "__searchprofile", true ) ); addParameter( new exp_parameter( i18n("open the search in a new tab"), "__yes", false ) ); //TODO: add this also to panel-dependent as soon as filesystem support the display of search-results } */ exp_Ask::exp_Ask() { _expression = "Ask"; _description = i18n("Ask Parameter from User..."); _needPanel = false; addParameter(exp_parameter(i18n("Question:"), "Where do you want do go today?", true)); addParameter(exp_parameter(i18n("Preset (optional):"), "", false)); addParameter(exp_parameter(i18n("Caption (optional):"), "", false)); } TagString exp_Ask::expFunc(const KrPanel*, const QStringList& parameter, const bool&, Expander& exp) const { QString caption, preset, result; if (parameter.count() == 0) { setError(exp, Error(Error::exp_S_FATAL, Error::exp_C_ARGUMENT, i18n("Expander: at least 1 parameter is required for Ask."))); return QString(); } if (parameter.count() <= 2 || parameter[2].isEmpty()) caption = i18n("User Action"); else caption = parameter[2]; if (parameter.count() <= 1 || parameter[1].isEmpty()) preset.clear(); else preset = parameter[1]; bool ok; result = QInputDialog::getText(krMainWindow, caption, parameter[0], QLineEdit::Normal, preset, &ok); if (ok) return result; else { // user cancelled setError(exp, Error(Error::exp_S_ERROR, Error::exp_C_USER)); return QString(); } } exp_Clipboard::exp_Clipboard() { _expression = "Clipboard"; _description = i18n("Copy to Clipboard..."); _needPanel = false; addParameter(exp_parameter(i18n("What to copy:"), "__placeholder", true)); addParameter(exp_parameter(i18n("Append to current clipboard content with this separator (optional):"), "", false)); } TagString exp_Clipboard::expFunc(const KrPanel*, const TagStringList& parameter, const bool&, Expander& exp) const { // qDebug() << "Expander::exp_Clipboard, parameter[0]: '" << parameter[0] << "', Clipboard: " << QApplication::clipboard()->text(); if (parameter.count() == 0) { setError(exp, Error(Error::exp_S_FATAL, Error::exp_C_ARGUMENT, i18n("Expander: at least 1 parameter is required for Clipboard."))); return QString(); } QStringList lst = splitEach(parameter[0]); if (parameter.count() > 1 && !parameter[1].isSimple()) { setError(exp, Error(Error::exp_S_FATAL, Error::exp_C_SYNTAX, i18n("Expander: %Each% may not be in the second argument of %Clipboard%"))); return QString(); } if (parameter.count() <= 1 || parameter[1].string().isEmpty() || QApplication::clipboard()->text().isEmpty()) QApplication::clipboard()->setText(lst.join("\n")); else QApplication::clipboard()->setText(QApplication::clipboard()->text() + parameter[1].string() + lst.join("\n")); return QString(); // this doesn't return anything, that's normal! } exp_Copy::exp_Copy() { _expression = "Copy"; _description = i18n("Copy a File/Folder..."); _needPanel = false; addParameter(exp_parameter(i18n("What to copy:"), "__placeholder", true)); addParameter(exp_parameter(i18n("Where to copy:"), "__placeholder", true)); } TagString exp_Copy::expFunc(const KrPanel*, const TagStringList& parameter, const bool&, Expander& exp) const { if (parameter.count() < 2) { setError(exp, Error(Error::exp_S_FATAL, Error::exp_C_ARGUMENT, i18n("Expander: at least 2 parameter is required for Copy."))); return QString(); } // basically the parameter can already be used as URL, but since QUrl has problems with ftp-proxy-urls (like ftp://username@proxyusername@url...) this is necessary: const QStringList sourceList = splitEach(parameter[0]); QList sourceURLs; - for (const QString source : sourceList) { + for (const QString& source : sourceList) { sourceURLs.append(QUrl::fromUserInput(source, QString(), QUrl::AssumeLocalFile)); } if (!parameter[1].isSimple()) { setError(exp, Error(Error::exp_S_FATAL, Error::exp_C_SYNTAX, i18n("Expander: %Each% may not be in the second argument of %Copy%"))); return QString(); } // or transform(...) ? const QUrl dest = QUrl::fromUserInput(parameter[1].string(), QString(), QUrl::AssumeLocalFile); if (!dest.isValid() || find_if(sourceURLs.constBegin(), sourceURLs.constEnd(), not1(mem_fun_ref(&QUrl::isValid))) != sourceURLs.constEnd()) { setError(exp, Error(Error::exp_S_FATAL, Error::exp_C_ARGUMENT, i18n("Expander: invalid URLs in %_Copy(\"src\", \"dest\")%"))); return QString(); } FileSystemProvider::instance().startCopyFiles(sourceURLs, dest); return QString(); // this doesn't return everything, that's normal! } exp_Move::exp_Move() { _expression = "Move"; _description = i18n("Move/Rename a File/Folder..."); _needPanel = false; addParameter(exp_parameter(i18n("What to move/rename:"), "__placeholder", true)); addParameter(exp_parameter(i18n("New target/name:"), "__placeholder", true)); } TagString exp_Move::expFunc(const KrPanel*, const TagStringList& parameter, const bool& , Expander& exp) const { if (parameter.count() < 2) { setError(exp, Error(Error::exp_S_FATAL, Error::exp_C_ARGUMENT, i18n("Expander: at least 2 parameter is required for Move."))); return QString(); } // basically the parameter can already be used as URL, but since QUrl has problems with ftp-proxy-urls (like ftp://username@proxyusername@url...) this is necessary: QStringList lst = splitEach(parameter[0]); if (!parameter[1].isSimple()) { setError(exp, Error(Error::exp_S_FATAL, Error::exp_C_SYNTAX, i18n("%Each% may not be in the second argument of %Move%"))); return QString(); } QList src; for (QStringList::const_iterator it = lst.constBegin(), end = lst.constEnd();it != end;++it) src.push_back(QUrl::fromUserInput(*it, QString(), QUrl::AssumeLocalFile)); // or transform(...) ? QUrl dest = QUrl::fromUserInput(parameter[1].string(), QString(), QUrl::AssumeLocalFile); if (!dest.isValid() || find_if(src.constBegin(), src.constEnd(), not1(mem_fun_ref(&QUrl::isValid))) != src.constEnd()) { setError(exp, Error(Error::exp_S_FATAL, Error::exp_C_ARGUMENT, i18n("Expander: invalid URLs in %_Move(\"src\", \"dest\")%"))); return QString(); } FileSystemProvider::instance().startCopyFiles(src, dest, KIO::CopyJob::Move); return QString(); // this doesn't return anything, that's normal! } #ifdef SYNCHRONIZER_ENABLED exp_Sync::exp_Sync() { _expression = "Sync"; _description = i18n("Load a Synchronizer Profile..."); _needPanel = false; addParameter(exp_parameter(i18n("Choose a profile:"), "__syncprofile", true)); } TagString exp_Sync::expFunc(const KrPanel*, const QStringList& parameter, const bool&, Expander& exp) const { if (parameter.count() == 0 || parameter[0].isEmpty()) { setError(exp, Error(Error::exp_S_FATAL, Error::exp_C_ARGUMENT, i18n("Expander: no profile specified for %_Sync(profile)%"))); return QString(); } SynchronizerGUI *synchronizerDialog = new SynchronizerGUI(MAIN_VIEW, parameter[0]); synchronizerDialog->show(); // destroyed on close return QString(); // this doesn't return everything, that's normal! } #endif exp_NewSearch::exp_NewSearch() { _expression = "NewSearch"; _description = i18n("Load a Searchmodule Profile..."); _needPanel = false; addParameter(exp_parameter(i18n("Choose a profile:"), "__searchprofile", true)); } TagString exp_NewSearch::expFunc(const KrPanel*, const QStringList& parameter, const bool&, Expander& exp) const { if (parameter.count() == 0 || parameter[0].isEmpty()) { setError(exp, Error(Error::exp_S_FATAL, Error::exp_C_ARGUMENT, i18n("Expander: no profile specified for %_NewSearch(profile)%"))); return QString(); } new KrSearchDialog(parameter[0], krApp); return QString(); // this doesn't return everything, that's normal! } exp_Profile::exp_Profile() { _expression = "Profile"; _description = i18n("Load a Panel Profile..."); _needPanel = false; addParameter(exp_parameter(i18n("Choose a profile:"), "__panelprofile", true)); } TagString exp_Profile::expFunc(const KrPanel*, const QStringList& parameter, const bool&, Expander& exp) const { if (parameter.count() == 0 || parameter[0].isEmpty()) { setError(exp, Error(Error::exp_S_FATAL, Error::exp_C_ARGUMENT, i18n("Expander: no profile specified for %_Profile(profile)%; abort..."))); return QString(); } MAIN_VIEW->profiles(parameter[0]); return QString(); // this doesn't return everything, that's normal! } exp_Each::exp_Each() { _expression = "Each"; _description = i18n("Separate Program Call for Each..."); _needPanel = true; addParameter(exp_parameter(i18n("Which items:"), "__choose:All;Files;Dirs;Selected", false)); addParameter(exp_parameter(i18n("Omit the current path (optional)"), "__no", false)); addParameter(exp_parameter(i18n("Mask (optional, all but 'Selected'):"), "__select", false)); addParameter(exp_parameter(i18n("Automatically escape spaces"), "__yes", false)); } TagString exp_Each::expFunc(const KrPanel* panel, const QStringList& parameter, const bool& useUrl, Expander& exp) const { NEED_PANEL QString mask; if (parameter.count() <= 2 || parameter[2].isEmpty()) mask = '*'; else mask = parameter[2]; TagString ret; QStringList l = fileList(panel, parameter.empty() ? QString() : parameter[0].toLower(), mask, parameter.count() > 1 && parameter[1].toLower() == "yes", useUrl, exp, "Each"); if (!(parameter.count() <= 3 || parameter[3].toLower() != "yes")) transform(l.begin(), l.end(), l.begin(), bashquote); ret.insertTag(0, l); return ret; } exp_ColSort::exp_ColSort() { _expression = "ColSort"; _description = i18n("Set Sorting for This Panel..."); _needPanel = true; addParameter(exp_parameter(i18n("Choose a column:"), "__choose:Name;Ext;Type;Size;Modified;Perms;rwx;Owner;Group", true)); addParameter(exp_parameter(i18n("Choose a sort sequence:"), "__choose:Toggle;Asc;Desc", false)); } TagString exp_ColSort::expFunc(const KrPanel* panel, const QStringList& parameter, const bool&, Expander& exp) const { NEED_PANEL if (parameter.count() == 0 || parameter[0].isEmpty()) { setError(exp, Error(Error::exp_S_FATAL, Error::exp_C_ARGUMENT, i18n("Expander: no column specified for %_ColSort(column)%"))); return QString(); } KrViewProperties::ColumnType oldColumn = panel->view->properties()->sortColumn; KrViewProperties::ColumnType column = oldColumn; if (parameter[0].toLower() == "name") { column = KrViewProperties::Name; } else if (parameter[0].toLower() == "ext") { column = KrViewProperties::Ext; } else if (parameter[0].toLower() == "type") { column = KrViewProperties::Type; } else if (parameter[0].toLower() == "size") { column = KrViewProperties::Size; } else if (parameter[0].toLower() == "modified") { column = KrViewProperties::Modified; } else if (parameter[0].toLower() == "changed") { column = KrViewProperties::Changed; } else if (parameter[0].toLower() == "accessed") { column = KrViewProperties::Accessed; } else if (parameter[0].toLower() == "perms") { column = KrViewProperties::Permissions; } else if (parameter[0].toLower() == "rwx") { column = KrViewProperties::KrPermissions; } else if (parameter[0].toLower() == "owner") { column = KrViewProperties::Owner; } else if (parameter[0].toLower() == "group") { column = KrViewProperties::Group; } else { setError(exp, Error(Error::exp_S_WARNING, Error::exp_C_ARGUMENT, i18n("Expander: unknown column specified for %_ColSort(%1)%", parameter[0]))); return QString(); } bool descending = panel->view->properties()->sortOptions & KrViewProperties::Descending; if (parameter.count() <= 1 || (parameter[1].toLower() != "asc" && parameter[1].toLower() != "desc")) { // no sortdir parameter if(column == oldColumn) // reverse direction if column is unchanged descending = !descending; else // otherwise set to ascending descending = false; } else { // sortdir specified if (parameter[1].toLower() == "asc") descending = false; else // == desc descending = true; } panel->view->setSortMode(column, descending); return QString(); // this doesn't return anything, that's normal! } exp_PanelSize::exp_PanelSize() { _expression = "PanelSize"; _description = i18n("Set Relation Between the Panels..."); _needPanel = true; addParameter(exp_parameter(i18n("Set the new size in percent:"), "__int:0;100;5;50", true)); } TagString exp_PanelSize::expFunc(const KrPanel* panel, const QStringList& parameter, const bool&, Expander& exp) const { NEED_PANEL int newSize; if (parameter.count() == 0 || parameter[0].isEmpty()) newSize = 50; //default is 50% else newSize = parameter[0].toInt(); if (newSize < 0 || newSize > 100) { setError(exp, Error(Error::exp_S_FATAL, Error::exp_C_ARGUMENT, i18n("Expander: Value %1 out of range for %_PanelSize(percent)%. The first parameter has to be >0 and <100", newSize))); return QString(); } MAIN_VIEW->setPanelSize(panel->isLeft(), newSize); return QString(); // this doesn't return everything, that's normal! } #ifdef __KJSEMBED__ exp_Script::exp_Script() { _expression = "Script"; _description = i18n("Execute a JavaScript Extension..."); _needPanel = false; addParameter(exp_parameter(i18n("Location of the script"), "", true)); addParameter(exp_parameter(i18n("Set some variables for the execution (optional).\ni.e. \"return=return_var;foo=bar\", consult the handbook for more information"), "", false)); } TagString exp_Script::expFunc(const KrPanel*, const QStringList& parameter, const bool&, Expander& exp) const { if (parameter.count() == 0 || parameter[0].isEmpty()) { setError(exp, Error(Error::exp_S_FATAL, Error::exp_C_ARGUMENT, i18n("Expander: no script specified for %_Script(script)%"))); return QString(); } QString filename = parameter[0]; if (filename.find('/') && QUrl::isRelativeUrl(filename)) { // this return the local version of the file if this exists. else the global one is returnd filename = locate("data", "krusader/js/" + filename); } if (! krJS) krJS = new KrJS(); KJS::ExecState *exec = krJS->globalExec(); QString jsReturn; if (parameter[1].toLower() == "yes") // to stay compatible with the old-style parameter jsReturn = "cmd"; else { QStringList jsVariables = parameter[1].split(';'); QString jsVariable, jsValue; for (QStringList::Iterator it = jsVariables.begin(); it != jsVariables.end(); ++it) { jsVariable = (*it).section('=', 0, 0).trimmed(); jsValue = (*it).section('=', 1); if (jsVariable == "return") jsReturn = jsValue.trimmed(); else krJS->putValue(jsVariable, KJSEmbed::convertToValue(exec, jsValue)); } } krJS->runFile(filename); if (! jsReturn.isEmpty()) return krJS->getValue(jsReturn).toString(krJS->globalExec()).qstring(); else return QString(); } #endif exp_View::exp_View() { _expression = "View"; _description = i18n("View File with Krusader's Internal Viewer..."); _needPanel = false; addParameter(exp_parameter(i18n("Which file to view (normally '%aCurrent%'):"), "__placeholder", true)); addParameter(exp_parameter(i18n("Choose a view mode:"), "__choose:generic;text;hex", false)); //addParameter( exp_parameter( i18n("Choose a window-mode"), "__choose:tab;window;panel", false ) ); //TODO: window-mode 'panel' should open the file in the third-hand viewer addParameter(exp_parameter(i18n("Choose a window mode:"), "__choose:tab;window", false)); } TagString exp_View::expFunc(const KrPanel*, const QStringList& parameter, const bool&, Expander& exp) const { if (parameter.count() == 0 || parameter[0].isEmpty()) { setError(exp, Error(Error::exp_S_FATAL, Error::exp_C_ARGUMENT, i18n("Expander: no file to view in %_View(filename)%"))); return QString(); } QString viewMode, windowMode; if (parameter.count() <= 1 || parameter[1].isEmpty()) viewMode = "generic"; else viewMode = parameter[1]; if (parameter.count() <= 2 || parameter[2].isEmpty()) windowMode = "tab"; else windowMode = parameter[2]; KrViewer::Mode mode = KrViewer::Generic; if (viewMode == "text") mode = KrViewer::Text; else if (viewMode == "hex") mode = KrViewer::Hex; QUrl url = QUrl::fromUserInput(parameter[0], QString(), QUrl::AssumeLocalFile); KrViewer::view(url, mode, (windowMode == "window")); //TODO: Call the viewer with viewMode and windowMode. Filename is in parameter[0]. // It would be nice if parameter[0] could also be a space-separated filename-list (provided if the first parameter is %aList(selected)%) return QString(); // this doesn't return everything, that's normal! } ///////////////////////////////////////////////////////////////////////////////////////////////// ////////////////////////////// end of expander classes //////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////////////// TagString exp_simpleplaceholder::expFunc(const KrPanel* p, const TagStringList& parameter, const bool& useUrl, Expander& exp) const { QStringList lst; for (const auto & it : parameter) if (it.isSimple()) lst.push_back(it.string()); else { setError(exp, Error(Error::exp_S_FATAL, Error::exp_C_SYNTAX, i18n("%Each% is not allowed in parameter to %1", description()))); return QString(); } return expFunc(p, lst, useUrl, exp); } } KrPanel* Expander::getPanel(const char panelIndicator, const exp_placeholder* pl, Expander& exp) { switch (panelIndicator) { case 'a': return ACTIVE_PANEL; case 'o': return OTHER_PANEL; case 'l': return LEFT_PANEL; case 'r': return RIGHT_PANEL; case '_': return nullptr; default: exp.setError(Error(Error::exp_S_FATAL, Error::exp_C_SYNTAX, i18n("Expander: Bad panel specifier %1 in placeholder %2", panelIndicator, pl->description()))); return nullptr; } } void Expander::expand(const QString& stringToExpand, bool useUrl) { TagString result = expandCurrent(stringToExpand, useUrl); if (error()) return; if (!result.isSimple()) resultList = splitEach(result); else resultList.append(result.string()); // qWarning() << resultList[0]; } TagString Expander::expandCurrent(const QString& stringToExpand, bool useUrl) { TagString result; QString exp; TagString tmpResult; int begin, end, i; // int brackets = 0; // bool inQuotes = false; int idx = 0; while (idx < stringToExpand.length()) { if ((begin = stringToExpand.indexOf('%', idx)) == -1) break; if ((end = findEnd(stringToExpand, begin)) == -1) { // xgettext:no-c-format setError(Error(Error::exp_S_FATAL, Error::exp_C_SYNTAX, i18n("Error: unterminated % in Expander"))); return QString(); } result += stringToExpand.mid(idx, begin - idx); // copy until the start of %exp% // get the expression, and expand it using the correct expander function exp = stringToExpand.mid(begin + 1, end - begin - 1); // qDebug() << "------------- exp: '" << exp << "'"; if (exp.isEmpty()) result += QString(QChar('%')); else { TagStringList parameter = separateParameter(&exp, useUrl); if (error()) return QString(); char panelIndicator = exp.toLower()[0].toLatin1(); exp.replace(0, 1, ""); for (i = 0; i < placeholderCount(); ++i) if (exp == placeholder(i)->expression()) { // qDebug() << "---------------------------------------"; tmpResult = placeholder(i)->expFunc(getPanel(panelIndicator, placeholder(i), *this), parameter, useUrl, *this); if (error()) { return QString(); } else result += tmpResult; // qDebug() << "---------------------------------------"; break; } if (i == placeholderCount()) { // didn't find an expander setError(Error(Error::exp_S_FATAL, Error::exp_C_SYNTAX, i18n("Error: unrecognized %%%1%2%% in Expander", panelIndicator, exp))); return QString(); } } //else idx = end + 1; } // copy the rest of the string result += stringToExpand.mid(idx); // qDebug() << "============== result '" << result << "'"; return result; } QStringList Expander::splitEach(TagString stringToSplit) { if (stringToSplit.isSimple()) { // qWarning() << stringToSplit.string(); QStringList l; l << stringToSplit.string(); return l; } pair pl = *stringToSplit.tagsBegin(); stringToSplit.eraseTag(stringToSplit.tagsBegin()); QStringList ret; for (QStringList::const_iterator it = pl.second.constBegin(), end = pl.second.constEnd();it != end;++it) { TagString s = stringToSplit; s.insert(pl.first, *it); ret += splitEach(s); } return ret; // qDebug() << "stringToSplit: " << stringToSplit; } TagStringList Expander::separateParameter(QString* const exp, bool useUrl) { TagStringList parameter; QStringList parameter1; QString result; int begin, end; if ((begin = exp->indexOf('(')) != -1) { if ((end = exp->lastIndexOf(')')) == -1) { setError(Error(Error::exp_S_FATAL, Error::exp_C_SYNTAX, i18n("Error: missing ')' in Expander"))); return TagStringList(); } result = exp->mid(begin + 1, end - begin - 1); *exp = exp->left(begin); bool inQuotes = false; int idx = 0; begin = 0; while (idx < result.length()) { if (result[ idx ].toLatin1() == '\\') { if (result[ idx+1 ].toLatin1() == '"') result.replace(idx, 1, ""); } if (result[ idx ].toLatin1() == '"') inQuotes = !inQuotes; if (result[ idx ].toLatin1() == ',' && !inQuotes) { parameter1.append(result.mid(begin, idx - begin)); begin = idx + 1; // qWarning() << " ---- parameter: " << parameter.join(";"); } idx++; } parameter1.append(result.mid(begin, idx - begin)); //don't forget the last one for (auto & it : parameter1) { it = it.trimmed(); if (it.left(1) == "\"") it = it.mid(1, it.length() - 2); parameter.push_back(expandCurrent(it, useUrl)); if (error()) return TagStringList(); } } // qWarning() << "------- exp: " << *exp << " ---- parameter: " << parameter.join(";"); return parameter; } int Expander::findEnd(const QString& str, int start) { int end = str.indexOf('%', start + 1); if (end == -1) return end; int bracket = str.indexOf('(', start + 1); if (end < bracket || bracket == -1) return end; int idx = bracket + 1; bool inQuotes = false; int depth = 1; while (idx < str.length()) { switch (str[ idx ].toLatin1()) { case '\\': idx ++; break; case '"': inQuotes = !inQuotes; break; case '(': if (!inQuotes) depth++; break; case ')': if (!inQuotes) --depth; break; case '%': if (depth == 0) return idx; } //switch idx++; } //while // failsafe return -1; } QList& Expander::_placeholder() { static QList ret; if(!ret.count()) { ret << new exp_View; ret << new exp_PanelSize; ret << new exp_ColSort; ret << new exp_Each; ret << new exp_Profile; ret << new exp_NewSearch; #ifdef SYNCHRONIZER_ENABLED ret << new exp_Sync; #endif ret << new exp_Move; ret << new exp_Copy; ret << new exp_Goto; ret << new exp_Select; ret << new exp_Clipboard; ret << new exp_Ask; ret << new exp_ListFile; ret << new exp_List; ret << new exp_Current; ret << new exp_Filter; ret << new exp_Count; ret << new exp_Path; #ifdef __KJSEMBED__ ret << new exp_Script; #endif } return ret; } diff --git a/krusader/icon.cpp b/krusader/icon.cpp index ea2bf69d..7dc28dd8 100644 --- a/krusader/icon.cpp +++ b/krusader/icon.cpp @@ -1,311 +1,311 @@ /***************************************************************************** * Copyright (C) 2018 Nikita Melnichenko * * Copyright (C) 2018 Krusader Krew [https://krusader.org] * * * * This file is part of Krusader [https://krusader.org]. * * * * Krusader is free software: you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation, either version 2 of the License, or * * (at your option) any later version. * * * * Krusader is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with Krusader. If not, see [http://www.gnu.org/licenses/]. * *****************************************************************************/ #include "icon.h" #include "krglobal.h" // QtCore #include #include #include #include // QtGui #include #include #include #include #include #include static const int cacheSize = 500; static const char *missingIconPath = ":/icons/icon-missing.svgz"; static inline QStringList getThemeFallbackList() { QStringList themes; // add user fallback theme if set if (krConfig) { const KConfigGroup group(krConfig, QStringLiteral("Startup")); QString userFallbackTheme = group.readEntry("Fallback Icon Theme", QString()); if (!userFallbackTheme.isEmpty()) { themes << userFallbackTheme; } } // Breeze and Oxygen are weak dependencies of Krusader, // i.e. each of the themes provide a complete set of icons used in the interface const QString breeze(Icon::isLightWindowThemeActive() ? "breeze" : "breeze-dark"); themes << breeze << "oxygen"; return themes; } class IconEngine : public QIconEngine { public: IconEngine(QString iconName, QIcon fallbackIcon, QStringList overlays = QStringList()) : _iconName(std::move(iconName)), _fallbackIcon(std::move(fallbackIcon)), _overlays(std::move(overlays)) { _themeFallbackList = getThemeFallbackList(); } void paint(QPainter *painter, const QRect &rect, QIcon::Mode mode, QIcon::State state) override; QPixmap pixmap(const QSize &size, QIcon::Mode mode, QIcon::State state) override; IconEngine *clone() const override { return new IconEngine(*this); } private: QString _iconName; QStringList _themeFallbackList; QIcon _fallbackIcon; QStringList _overlays; }; Icon::Icon() : QIcon() { } Icon::Icon(QString name, QStringList overlays) : QIcon(new IconEngine(std::move(name), QIcon(missingIconPath), std::move(overlays))) { } Icon::Icon(QString name, QIcon fallbackIcon, QStringList overlays) : QIcon(new IconEngine(std::move(name), std::move(fallbackIcon), std::move(overlays))) { } struct IconSearchResult { QIcon icon; ///< icon returned by search; null icon if not found QString originalThemeName; ///< original theme name if theme is modified by search IconSearchResult(QIcon icon, QString originalThemeName) : icon(std::move(icon)), originalThemeName(std::move(originalThemeName)) {} }; // Search icon in the configured themes. // If this call modifies active theme, the original theme name will be specified in the result. static inline IconSearchResult searchIcon(const QString& iconName, QStringList themeFallbackList) { if (QDir::isAbsolutePath(iconName)) { // a path is used - directly load the icon return IconSearchResult(QIcon(iconName), QString()); } else if (QIcon::hasThemeIcon(iconName)) { // current theme has the icon - load seamlessly return IconSearchResult(QIcon::fromTheme(iconName), QString()); } else if (KIconLoader::global()->hasIcon(iconName)) { // KF icon loader does a wider search and helps with mime icons return IconSearchResult(KDE::icon(iconName), QString()); } else { // search the icon in fallback themes auto currentTheme = QIcon::themeName(); - for (auto fallbackThemeName : themeFallbackList) { + for (const auto& fallbackThemeName : themeFallbackList) { QIcon::setThemeName(fallbackThemeName); if (QIcon::hasThemeIcon(iconName)) { return IconSearchResult(QIcon::fromTheme(iconName), currentTheme); } } QIcon::setThemeName(currentTheme); // not found return IconSearchResult(QIcon(), QString()); } } bool Icon::exists(const QString& iconName) { static QCache cache(cacheSize); static QString cachedTheme; // invalidate cache if system theme is changed if (cachedTheme != QIcon::themeName()) { cache.clear(); cachedTheme = QIcon::themeName(); } // return cached result when possible if (cache.contains(iconName)) { return *cache.object(iconName); } auto searchResult = searchIcon(iconName, getThemeFallbackList()); if (!searchResult.originalThemeName.isNull()) { QIcon::setThemeName(searchResult.originalThemeName); } auto *result = new bool(!searchResult.icon.isNull()); // update the cache; the cache takes ownership over the result cache.insert(iconName, result); return *result; } void Icon::applyOverlays(QPixmap *pixmap, QStringList overlays) { auto iconLoader = KIconLoader::global(); // Since KIconLoader loadIcon is not virtual method, we can't redefine loadIcon // that is called by drawOverlays. The best we can do is to go over the overlays // and ensure they exist from the icon loader point of view. // If not, we replace the overlay with "emblem-unreadable" which should be available // per freedesktop icon name specification: // https://specifications.freedesktop.org/icon-naming-spec/icon-naming-spec-latest.html QStringList fixedOverlays; - for (auto overlay : overlays) { + for (const auto& overlay : overlays) { if (overlay.isEmpty() || iconLoader->hasIcon(overlay)) { fixedOverlays << overlay; } else { fixedOverlays << "emblem-unreadable"; } } iconLoader->drawOverlays(fixedOverlays, *pixmap, KIconLoader::Desktop); } bool Icon::isLightWindowThemeActive() { const QColor textColor = QPalette().brush(QPalette::Text).color(); return (textColor.red() + textColor.green() + textColor.blue()) / 3 < 128; } class IconCacheKey { public: IconCacheKey(const QString &name, const QStringList& overlays, const QSize &size, QIcon::Mode mode, QIcon::State state) : name(name), overlays(overlays), size(size), mode(mode), state(state) { auto repr = QString("%1 [%2] %3x%4 %5 %6").arg(name).arg(overlays.join(';')) .arg(size.width()).arg(size.height()) .arg((int)mode).arg((int)state); _hash = qHash(repr); } bool operator ==(const IconCacheKey &x) const { return name == x.name && overlays == x.overlays && size == x.size && mode == x.mode && state == x.state; } uint hash() const { return _hash; } QString name; QStringList overlays; QSize size; QIcon::Mode mode; QIcon::State state; private: uint _hash; }; uint qHash(const IconCacheKey &key) { return key.hash(); } QPixmap IconEngine::pixmap(const QSize &size, QIcon::Mode mode, QIcon::State state) { static QCache cache(cacheSize); static QString cachedTheme; QString systemTheme = QIcon::themeName(); // [WORKAROUND] If system theme is Breeze, pick light or dark variant of the theme explicitly // This type of selection works implicitly when QIcon::fromTheme is used, // however after QIcon::setThemeName it stops working for unknown reason. if (systemTheme == "breeze" || systemTheme == "breeze-dark") { const QString pickedSystemTheme(Icon::isLightWindowThemeActive() ? "breeze" : "breeze-dark"); if (systemTheme != pickedSystemTheme) { qDebug() << "System icon theme variant changed:" << systemTheme << "->" << pickedSystemTheme; systemTheme = pickedSystemTheme; QIcon::setThemeName(systemTheme); } } // invalidate cache if system theme is changed if (cachedTheme != systemTheme) { if (!cachedTheme.isEmpty()) { qDebug() << "System icon theme changed:" << cachedTheme << "->" << systemTheme; } cache.clear(); cachedTheme = systemTheme; } // an empty icon name is a special case - we don't apply any fallback if (_iconName.isEmpty()) { return QPixmap(); } auto key = IconCacheKey(_iconName, _overlays, size, mode, state); // return cached icon when possible if (cache.contains(key)) { return *cache.object(key); } // search icon and extract pixmap auto pixmap = new QPixmap; auto searchResult = searchIcon(_iconName, _themeFallbackList); if (!searchResult.icon.isNull()) { *pixmap = searchResult.icon.pixmap(size, mode, state); } if (!searchResult.originalThemeName.isNull()) { QIcon::setThemeName(searchResult.originalThemeName); } // can't find the icon neither in system theme nor in fallback themes - load fallback icon if (pixmap->isNull()) { qWarning() << "Unable to find icon" << _iconName << "of size" << size << "in any configured theme"; *pixmap = _fallbackIcon.pixmap(size, mode, state); } // apply overlays in a safe manner Icon::applyOverlays(pixmap, _overlays); // update the cache; the cache takes ownership over the pixmap cache.insert(key, pixmap); return *pixmap; } void IconEngine::paint(QPainter *painter, const QRect &rect, QIcon::Mode mode, QIcon::State state) { QSize pixmapSize = rect.size(); QPixmap px = pixmap(pixmapSize, mode, state); painter->drawPixmap(rect, px); } diff --git a/krusader/krslots.cpp b/krusader/krslots.cpp index 1622afd6..ecadf3d4 100644 --- a/krusader/krslots.cpp +++ b/krusader/krslots.cpp @@ -1,748 +1,748 @@ /***************************************************************************** * Copyright (C) 2001 Shie Erlich * * Copyright (C) 2001 Rafi Yanai * * Copyright (C) 2004-2018 Krusader Krew [https://krusader.org] * * * * This file is part of Krusader [https://krusader.org]. * * * * Krusader is free software: you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation, either version 2 of the License, or * * (at your option) any later version. * * * * Krusader is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with Krusader. If not, see [http://www.gnu.org/licenses/]. * *****************************************************************************/ #include "krslots.h" // QtCore #include #include #include #include #include #include // QtGui #include #include // QtWidgets #include #include #include #include #include #include #include #ifdef __KJSEMBED__ #include #include "KrJS/krjs.h" #endif #include "defaults.h" #include "icon.h" #include "kractions.h" #include "krservices.h" #include "krtrashhandler.h" #include "krusader.h" #include "krusaderview.h" #include "panelmanager.h" #include "ActionMan/actionman.h" #include "BookMan/krbookmarkbutton.h" #include "BookMan/krbookmarkhandler.h" #include "Dialogs/krdialogs.h" #include "Dialogs/krspecialwidgets.h" #include "Dialogs/krspwidgets.h" #include "DiskUsage/diskusagegui.h" #include "FileSystem/fileitem.h" #include "FileSystem/filesystem.h" #include "FileSystem/krquery.h" #include "GUI/dirhistorybutton.h" #include "GUI/kcmdline.h" #include "GUI/kfnkeys.h" #include "GUI/krusaderstatus.h" #include "GUI/mediabutton.h" #include "GUI/terminaldock.h" #include "KViewer/krviewer.h" #include "Konfigurator/konfigurator.h" #include "Locate/locate.h" #include "MountMan/kmountman.h" #include "Panel/PanelView/krselectionmode.h" #include "Panel/PanelView/krview.h" #include "Panel/PanelView/krviewfactory.h" #include "Panel/PanelView/krviewitem.h" #include "Panel/listpanel.h" #include "Panel/panelfunc.h" #include "Panel/sidebar.h" #include "Search/krsearchdialog.h" #include "Search/krsearchmod.h" #include "Splitter/combiner.h" #include "Splitter/splitter.h" #include "Splitter/splittergui.h" #ifdef SYNCHRONIZER_ENABLED #include "Synchronizer/synchronizergui.h" #endif #define ACTIVE_VIEW _mainWindow->activeView() KRslots::KRslots(QObject *parent) : QObject(parent), _mainWindow(krApp) { } void KRslots::sendFileByEmail(const QList &urls) { if (urls.count() == 0) { KMessageBox::error(nullptr, i18n("No selected files to send.")); return; } QString mailProg; QStringList lst = KrServices::supportedTools(); if (lst.contains("MAIL")) mailProg = lst[lst.indexOf("MAIL") + 1]; else { KMessageBox::error(nullptr, i18n("Krusader cannot find a supported mail client. Please install one to your path. Hint: Krusader supports KMail.")); return; } QString subject, separator; foreach(const QUrl &url, urls) { subject += separator + url.fileName(); separator = ','; } subject = i18np("Sending file: %2", "Sending files: %2", urls.count(), subject); KProcess proc; QString executable = QUrl::fromLocalFile(mailProg).fileName(); if (executable == QStringLiteral("kmail")) { proc << mailProg << "--subject" << subject; foreach(const QUrl &url2, urls) proc << "--attach" << url2.toDisplayString(); } else if (executable == QStringLiteral("thunderbird")) { QString param = "attachment=\'"; separator = ""; foreach(const QUrl &url2, urls) { param += separator + url2.toDisplayString(); separator = ','; } param += "\',subject=\'" + subject + "\'"; proc << mailProg << "--compose" << param; } else if (executable == QStringLiteral("evolution")) { QString param = "mailto:?cc=&subject=" + subject + "&attach="; separator = ""; foreach(const QUrl &url2, urls) { param += separator + url2.toDisplayString(); separator = "&attach="; } proc << mailProg << param + ""; } if (!proc.startDetached()) KMessageBox::error(nullptr, i18n("Error executing %1.", mailProg)); } void KRslots::compareContent() { const QStringList lstLeft = LEFT_PANEL->getSelectedNames(); const QStringList lstRight = RIGHT_PANEL->getSelectedNames(); const QStringList lstActive = ACTIVE_PANEL->gui->isLeft() ? lstLeft : lstRight; QUrl name1, name2; if (lstLeft.count() == 1 && lstRight.count() == 1) { // first, see if we've got exactly 1 selected file in each panel: name1 = LEFT_PANEL->func->files()->getUrl(lstLeft[0]); name2 = RIGHT_PANEL->func->files()->getUrl(lstRight[0]); } else if (lstActive.count() == 2) { // next try: are in the current panel exactly 2 files selected? name1 = ACTIVE_PANEL->func->files()->getUrl(lstActive[0]); name2 = ACTIVE_PANEL->func->files()->getUrl(lstActive[1]); } else if (ACTIVE_PANEL->otherPanel()->func->files()->getFileItem(ACTIVE_VIEW->getCurrentItem())) { // next try: is in the other panel a file with the same name? name1 = ACTIVE_PANEL->func->files()->getUrl(ACTIVE_VIEW->getCurrentItem()); name2 = ACTIVE_PANEL->otherPanel()->func->files()->getUrl(ACTIVE_VIEW->getCurrentItem()); } else { // if we got here, then we can't be sure what file to diff KMessageBox::sorry(nullptr, "" + i18n("Do not know which files to compare.") + "

" + i18n("To compare two files by content, you can either:
  • Select one file in the left panel, and one in the right panel.
  • Select exactly two files in the active panel.
  • Make sure there is a file in the other panel, with the same name as the current file in the active panel.
") + "
"); return; } // else implied: all ok, let's call an external program to compare files // but if any of the files isn't local, download it first compareContent(name1, name2); } bool downloadToTemp(const QUrl &url, QString &dest) { QTemporaryFile tmpFile; tmpFile.setAutoRemove(false); if (tmpFile.open()) { dest = tmpFile.fileName(); KIO::Job* job = KIO::file_copy(url, QUrl::fromLocalFile(dest), -1, KIO::Overwrite | KIO::HideProgressInfo); if(!job->exec()) { KMessageBox::error(krApp, i18n("Krusader is unable to download %1", url.fileName())); return false; } return true; } return false; } void KRslots::compareContent(const QUrl& url1, const QUrl& url2) { QString diffProg; QStringList lst = KrServices::supportedTools(); if (lst.contains("DIFF")) diffProg = lst[lst.indexOf("DIFF") + 1]; else { KMessageBox::error(nullptr, i18n("Krusader cannot find any of the supported diff-frontends. Please install one to your path. Hint: Krusader supports Kompare, KDiff3 and Xxdiff.")); return; } QString tmp1; QString tmp2; // kdiff3 sucks with spaces if (QUrl::fromLocalFile(diffProg).fileName() == "kdiff3" && !url1.toDisplayString().contains(" ") && !url2.toDisplayString().contains(" ")) { tmp1 = url1.toDisplayString(); tmp2 = url2.toDisplayString(); } else { if (!url1.isLocalFile()) { if (!downloadToTemp(url1, tmp1)) { return; } } else tmp1 = url1.path(); if (!url2.isLocalFile()) { if (!downloadToTemp(url2, tmp2)) { if (tmp1 != url1.path()) QFile::remove(tmp1); return; } } else tmp2 = url2.path(); } KrProcess *p = new KrProcess(tmp1 != url1.path() ? tmp1 : QString(), tmp2 != url2.path() ? tmp2 : QString()); *p << diffProg << tmp1 << tmp2; p->start(); if (!p->waitForStarted()) KMessageBox::error(nullptr, i18n("Error executing %1.", diffProg)); } // GUI toggle slots void KRslots::toggleFnkeys() { if (MAIN_VIEW->fnKeys()->isVisible()) MAIN_VIEW->fnKeys()->hide(); else MAIN_VIEW->fnKeys()->show(); } void KRslots::toggleCmdline() { if (MAIN_VIEW->cmdLine()->isVisible()) MAIN_VIEW->cmdLine()->hide(); else MAIN_VIEW->cmdLine()->show(); } void KRslots::updateStatusbarVisibility() { krApp->statusBar()->setVisible(KrActions::actShowStatusBar->isChecked()); } void KRslots::toggleTerminal() { MAIN_VIEW->setTerminalEmulator(KrActions::actToggleTerminal->isChecked()); } void KRslots::insertFileName(bool fullPath) { QString filename = ACTIVE_VIEW->getCurrentItem(); if (filename.isEmpty()) { return; } if (fullPath) { const QString path = FileSystem::ensureTrailingSlash(ACTIVE_PANEL->virtualPath()) .toDisplayString(QUrl::PreferLocalFile); filename = path + filename; } filename = KrServices::quote(filename); if (MAIN_VIEW->cmdLine()->isVisible() || !MAIN_VIEW->terminalDock()->isTerminalVisible()) { QString current = MAIN_VIEW->cmdLine()->text(); if (current.length() != 0 && !current.endsWith(' ')) current += ' '; MAIN_VIEW->cmdLine()->setText(current + filename); MAIN_VIEW->cmdLine()->setFocus(); } else if (MAIN_VIEW->terminalDock()->isTerminalVisible()) { filename = ' ' + filename + ' '; MAIN_VIEW->terminalDock()->sendInput(filename, false); MAIN_VIEW->terminalDock()->setFocus(); } } void KRslots::refresh(const QUrl &u) { ACTIVE_FUNC->openUrl(u); } void KRslots::runKonfigurator(bool firstTime) { auto *konfigurator = new Konfigurator(firstTime); connect(konfigurator, &Konfigurator::configChanged, this, &KRslots::configChanged); //FIXME - no need to exec konfigurator->exec(); delete konfigurator; } void KRslots::configChanged(bool isGUIRestartNeeded) { krConfig->sync(); if (isGUIRestartNeeded) { krApp->setUpdatesEnabled(false); KConfigGroup group(krConfig, "Look&Feel"); FileItem::loadUserDefinedFolderIcons(group.readEntry("Load User Defined Folder Icons", _UserDefinedFolderIcons)); bool leftActive = ACTIVE_PANEL->gui->isLeft(); MAIN_VIEW->leftManager()->slotRecreatePanels(); MAIN_VIEW->rightManager()->slotRecreatePanels(); if(leftActive) LEFT_PANEL->slotFocusOnMe(); else RIGHT_PANEL->slotFocusOnMe(); MAIN_VIEW->fnKeys()->updateShortcuts(); KrSelectionMode::resetSelectionHandler(); krApp->setUpdatesEnabled(true); } krApp->setTray(); // really ugly, but reload the Fn keys just in case - csaba: any better idea? MAIN_VIEW->fnKeys()->updateShortcuts(); const bool showHidden = KConfigGroup(krConfig, "Look&Feel") .readEntry("Show Hidden", KrActions::actToggleHidden->isChecked()); if (showHidden != KrActions::actToggleHidden->isChecked()) { KrActions::actToggleHidden->setChecked(showHidden); MAIN_VIEW->leftManager()->reloadConfig(); MAIN_VIEW->rightManager()->reloadConfig(); } } void KRslots::showHiddenFiles(bool show) { KConfigGroup group(krConfig, "Look&Feel"); group.writeEntry("Show Hidden", show); MAIN_VIEW->leftManager()->reloadConfig(); MAIN_VIEW->rightManager()->reloadConfig(); } void KRslots::swapPanels() { QUrl leftURL = LEFT_PANEL->virtualPath(); QUrl rightURL = RIGHT_PANEL->virtualPath(); LEFT_PANEL->func->openUrl(rightURL); RIGHT_PANEL->func->openUrl(leftURL); } void KRslots::toggleSwapSides() { MAIN_VIEW->swapSides(); } void KRslots::search() { if (KrSearchDialog::SearchDialog != nullptr) { KConfigGroup group(krConfig, "Search"); if (group.readEntry("Window Maximized", false)) KrSearchDialog::SearchDialog->showMaximized(); else KrSearchDialog::SearchDialog->showNormal(); KrSearchDialog::SearchDialog->raise(); KrSearchDialog::SearchDialog->activateWindow(); } else KrSearchDialog::SearchDialog = new KrSearchDialog(); } void KRslots::locate() { if (!KrServices::cmdExist("locate")) { KMessageBox::error(krApp, i18n("Cannot find the 'locate' command. Please install the " "findutils-locate package of GNU, or set its dependencies in " "Konfigurator")); return; } if (LocateDlg::LocateDialog != nullptr) { LocateDlg::LocateDialog->showNormal(); LocateDlg::LocateDialog->raise(); LocateDlg::LocateDialog->activateWindow(); LocateDlg::LocateDialog->reset(); } else LocateDlg::LocateDialog = new LocateDlg(krApp); } void KRslots::runTerminal(const QString & dir) { KProcess proc; proc.setWorkingDirectory(dir); KConfigGroup group(krConfig, "General"); QString term = group.readEntry("Terminal", _Terminal); QStringList sepdArgs = KShell::splitArgs(term, KShell::TildeExpand); if (sepdArgs.isEmpty()) { KMessageBox::error(krMainWindow, i18nc("Arg is a string containing the bad quoting.", "Bad quoting in terminal command:\n%1", term)); return; } for (int i = 0; i < sepdArgs.size(); i++) { if (sepdArgs[i] == "%d") { sepdArgs[i] = dir; } } proc << sepdArgs; if (!proc.startDetached()) KMessageBox::sorry(krApp, i18n("Error executing %1.", term)); } void KRslots::homeTerminal() { runTerminal(QDir::homePath()); } void KRslots::multiRename() { QStringList lst = KrServices::supportedTools(); int i = lst.indexOf("RENAME"); if (i == -1) { KMessageBox::sorry(krApp, i18n("Cannot find a batch rename tool.\nYou can get KRename at %1", QLatin1String("https://www.kde.org/applications/utilities/krename/"))); return; } QString pathToRename = lst[i+1]; const QStringList names = ACTIVE_PANEL->gui->getSelectedNames(); if (names.isEmpty()) { return; } KProcess proc; proc << pathToRename; - for (const QString name: names) { + for (const QString& name: names) { FileItem *file = ACTIVE_FUNC->files()->getFileItem(name); if (!file) continue; const QUrl url = file->getUrl(); // KRename only supports the recursive option combined with a local directory path if (file->isDir() && url.scheme() == "file") { proc << "-r" << url.path(); } else { proc << url.toString(); } } if (!proc.startDetached()) KMessageBox::error(nullptr, i18n("Error executing '%1'.", proc.program().join(" "))); } void KRslots::rootKrusader() { if (KMessageBox::warningContinueCancel( krApp, i18n("Improper operations in root mode can damage your operating system. " "

Furthermore, running UI applications as root is insecure and can " "allow attackers to gain root access."), QString(), KStandardGuiItem::cont(), KStandardGuiItem::cancel(), "Confirm Root Mode", KMessageBox::Notify | KMessageBox::Dangerous) != KMessageBox::Continue) return; if (!KrServices::isExecutable(KDESU_PATH)) { KMessageBox::sorry(krApp, i18n("Cannot start root mode Krusader, %1 not found or not executable. " "Please verify that kde-cli-tools are installed.", QString(KDESU_PATH))); return; } KProcess proc; proc << KDESU_PATH << "-c" << QApplication::instance()->applicationFilePath() + " --left=" + KrServices::quote(LEFT_PANEL->virtualPath().toDisplayString(QUrl::PreferLocalFile)) + " --right=" + KrServices::quote(RIGHT_PANEL->virtualPath().toDisplayString(QUrl::PreferLocalFile)); if (!proc.startDetached()) KMessageBox::error(nullptr, i18n("Error executing %1.", proc.program()[0])); } void KRslots::slotSplit() { const QStringList list = ACTIVE_PANEL->gui->getSelectedNames(); QString name; // first, see if we've got exactly 1 selected file, if not, try the current one if (list.count() == 1) name = list[0]; if (name.isEmpty()) { // if we got here, then one of the panel can't be sure what file to diff KMessageBox::error(nullptr, i18n("Do not know which file to split.")); return; } QUrl fileURL = ACTIVE_FUNC->files()->getUrl(name); if (fileURL.isEmpty()) return; if (ACTIVE_FUNC->files()->getFileItem(name)->isDir()) { KMessageBox::sorry(krApp, i18n("You cannot split a folder.")); return; } const QUrl destDir = ACTIVE_PANEL->otherPanel()->virtualPath(); SplitterGUI splitterGUI(MAIN_VIEW, fileURL, destDir); if (splitterGUI.exec() == QDialog::Accepted) { bool splitToOtherPanel = splitterGUI.getDestinationDir().matches(ACTIVE_PANEL->otherPanel()->virtualPath(), QUrl::StripTrailingSlash); Splitter split(MAIN_VIEW, fileURL, splitterGUI.getDestinationDir(), splitterGUI.overWriteFiles()); split.split(splitterGUI.getSplitSize()); if (splitToOtherPanel) ACTIVE_PANEL->otherPanel()->func->refresh(); } } void KRslots::slotCombine() { const QStringList list = ACTIVE_PANEL->gui->getSelectedNames(); if (list.isEmpty()) { KMessageBox::error(nullptr, i18n("Do not know which files to combine.")); return; } QUrl baseURL; bool unixStyle = false; bool windowsStyle = false; QString commonName; int commonLength = 0; /* checking splitter names */ for (const auto & it : list) { QUrl url = ACTIVE_FUNC->files()->getUrl(it); if (url.isEmpty()) return; if (ACTIVE_FUNC->files()->getFileItem(it)->isDir()) { KMessageBox::sorry(krApp, i18n("You cannot combine a folder.")); return; } if (!unixStyle) { QString name = url.fileName(); int extPos = name.lastIndexOf('.'); QString ext = name.mid(extPos + 1); name.truncate(extPos); url = url.adjusted(QUrl::RemoveFilename); url.setPath(url.path() + name); bool isExtInt; ext.toInt(&isExtInt, 10); if (extPos < 1 || ext.isEmpty() || (ext != "crc" && !isExtInt)) { if (windowsStyle) { KMessageBox::error(nullptr, i18n("Not a split file: %1.", url.toDisplayString(QUrl::PreferLocalFile))); return; } unixStyle = true; } else { if (ext != "crc") windowsStyle = true; if (baseURL.isEmpty()) baseURL = url; else if (baseURL != url) { KMessageBox::error(nullptr, i18n("Select only one split file.")); return; } } } if (unixStyle) { bool error = true; do { const QString& shortName = it; QChar lastChar = shortName.at(shortName.length() - 1); if (lastChar.isLetter()) { char fillLetter = (lastChar.toUpper() == lastChar) ? 'A' : 'a'; if (commonName.isNull()) { commonLength = shortName.length(); commonName = shortName; while (commonName.length()) { QString shorter = commonName.left(commonName.length() - 1); QString testFile = shorter.leftJustified(commonLength, fillLetter); if (ACTIVE_FUNC->files()->getFileItem(testFile) == nullptr) break; else { commonName = shorter; baseURL = ACTIVE_PANEL->virtualPath().adjusted(QUrl::StripTrailingSlash); baseURL.setPath(baseURL.path() + '/' + (testFile)); } } error = (commonName == shortName); } else if (commonLength == shortName.length() && shortName.startsWith(commonName)) error = false; } } while (false); if (error) { KMessageBox::error(nullptr, i18n("Not a split file: %1.", url.toDisplayString(QUrl::PreferLocalFile))); return; } } } // ask the user for the copy dest QUrl dest = KChooseDir::getDir(i18n("Combining %1.* to folder:", baseURL.toDisplayString(QUrl::PreferLocalFile)), ACTIVE_PANEL->otherPanel()->virtualPath(), ACTIVE_PANEL->virtualPath()); if (dest.isEmpty()) return ; // the user canceled bool combineToOtherPanel = (dest.matches(ACTIVE_PANEL->otherPanel()->virtualPath(), QUrl::StripTrailingSlash)); Combiner combine(MAIN_VIEW, baseURL, dest, unixStyle); combine.combine(); if (combineToOtherPanel) ACTIVE_PANEL->otherPanel()->func->refresh(); } void KRslots::manageUseractions() { ActionMan actionMan(MAIN_VIEW); } #ifdef SYNCHRONIZER_ENABLED void KRslots::slotSynchronizeDirs(QStringList selected) { SynchronizerGUI *synchronizerDialog = new SynchronizerGUI(MAIN_VIEW, LEFT_PANEL->virtualPath(), RIGHT_PANEL->virtualPath(), std::move(selected)); synchronizerDialog->show(); // destroyed on close } #endif void KRslots::compareSetup() { for (int i = 0; KrActions::compareArray[i] != nullptr; i++) if ((*KrActions::compareArray[i])->isChecked()) { KConfigGroup group(krConfig, "Private"); group.writeEntry("Compare Mode", i); break; } } /** called by actions actExec* to choose the built-in command line mode */ void KRslots::execTypeSetup() { for (int i = 0; KrActions::execTypeArray[i] != nullptr; i++) if ((*KrActions::execTypeArray[i])->isChecked()) { if (*KrActions::execTypeArray[i] == KrActions::actExecTerminalEmbedded) { // if commands are to be executed in the TE, it must be loaded MAIN_VIEW->terminalDock()->initialise(); } KConfigGroup grp(krConfig, "Private"); grp.writeEntry("Command Execution Mode", i); break; } } void KRslots::slotDiskUsage() { DiskUsageGUI *diskUsageDialog = new DiskUsageGUI(ACTIVE_PANEL->virtualPath()); diskUsageDialog->askDirAndShow(); } void KRslots::applicationStateChanged() { if (MAIN_VIEW == nullptr) { /* CRASH FIX: it's possible that the method is called after destroying the main view */ return; } if(qApp->applicationState() == Qt::ApplicationActive || qApp->applicationState() == Qt::ApplicationInactive) { LEFT_PANEL->panelVisible(); RIGHT_PANEL->panelVisible(); } else { LEFT_PANEL->panelHidden(); RIGHT_PANEL->panelHidden(); } } void KRslots::emptyTrash() { KrTrashHandler::emptyTrash(); } #define OPEN_ID 100001 #define EMPTY_TRASH_ID 100002 void KRslots::trashPopupMenu() { QMenu trashMenu(krApp); QAction * act = trashMenu.addAction(Icon("document-open"), i18n("Open trash bin")); act->setData(QVariant(OPEN_ID)); act = trashMenu.addAction(Icon("trash-empty"), i18n("Empty trash bin")); act->setData(QVariant(EMPTY_TRASH_ID)); int result = -1; QAction *res = trashMenu.exec(QCursor::pos()); if (res && res->data().canConvert ()) result = res->data().toInt(); if (result == OPEN_ID) { ACTIVE_FUNC->openUrl(QUrl(QStringLiteral("trash:/"))); } else if (result == EMPTY_TRASH_ID) { KrTrashHandler::emptyTrash(); } } //shows the JavaScript-Console void KRslots::jsConsole() { #ifdef __KJSEMBED__ if (! krJS) krJS = new KrJS(); krJS->view()->show(); #endif } void KRslots::addBookmark() { krBookMan->bookmarkCurrent(ACTIVE_PANEL->virtualPath()); } void KRslots::cmdlinePopup() { MAIN_VIEW->cmdLine()->popup(); }