diff --git a/src/widgets/accessmanagerreply_p.cpp b/src/widgets/accessmanagerreply_p.cpp index bfdad442..4ac9abde 100644 --- a/src/widgets/accessmanagerreply_p.cpp +++ b/src/widgets/accessmanagerreply_p.cpp @@ -1,525 +1,525 @@ /* * This file is part of the KDE project. * * Copyright (C) 2008 Alex Merry * Copyright (C) 2008 - 2009 Urs Wolfer * Copyright (C) 2009 - 2012 Dawit Alemayehu * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library 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 * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. * */ #include "accessmanagerreply_p.h" #include "accessmanager.h" #include "job.h" #include "scheduler.h" #include "kio_widgets_debug.h" #include #include #include #include #include #define QL1S(x) QLatin1String(x) #define QL1C(x) QLatin1Char(x) namespace KDEPrivate { AccessManagerReply::AccessManagerReply(const QNetworkAccessManager::Operation op, const QNetworkRequest &request, KIO::SimpleJob *kioJob, bool emitReadyReadOnMetaDataChange, QObject *parent) : QNetworkReply(parent), m_offset(0), m_metaDataRead(false), m_ignoreContentDisposition(false), m_emitReadyReadOnMetaDataChange(emitReadyReadOnMetaDataChange), m_kioJob(kioJob) { setRequest(request); setOpenMode(QIODevice::ReadOnly); setUrl(request.url()); setOperation(op); setError(NoError, QString()); if (!request.sslConfiguration().isNull()) { setSslConfiguration(request.sslConfiguration()); } connect(kioJob, SIGNAL(redirection(KIO::Job*,QUrl)), SLOT(slotRedirection(KIO::Job*,QUrl))); - connect(kioJob, SIGNAL(percent(KJob*,ulong)), SLOT(slotPercent(KJob*,ulong))); + connect(kioJob, QOverload::of(&KJob::percent), this, &AccessManagerReply::slotPercent); if (qobject_cast(kioJob)) { - connect(kioJob, SIGNAL(result(KJob*)), SLOT(slotStatResult(KJob*))); + connect(kioJob, &KJob::result, this, &AccessManagerReply::slotStatResult); } else { - connect(kioJob, SIGNAL(result(KJob*)), SLOT(slotResult(KJob*))); + connect(kioJob, &KJob::result, this, &AccessManagerReply::slotResult); connect(kioJob, SIGNAL(data(KIO::Job*,QByteArray)), SLOT(slotData(KIO::Job*,QByteArray))); connect(kioJob, SIGNAL(mimetype(KIO::Job*,QString)), SLOT(slotMimeType(KIO::Job*,QString))); } } AccessManagerReply::AccessManagerReply(const QNetworkAccessManager::Operation op, const QNetworkRequest &request, const QByteArray &data, const QUrl &url, const KIO::MetaData &metaData, QObject *parent) : QNetworkReply(parent), m_data(data), m_offset(0), m_ignoreContentDisposition(false), m_emitReadyReadOnMetaDataChange(false) { setRequest(request); setOpenMode(QIODevice::ReadOnly); setUrl((url.isValid() ? url : request.url())); setOperation(op); setHeaderFromMetaData(metaData); if (!request.sslConfiguration().isNull()) { setSslConfiguration(request.sslConfiguration()); } setError(NoError, QString()); emitFinished(true, Qt::QueuedConnection); } AccessManagerReply::AccessManagerReply(const QNetworkAccessManager::Operation op, const QNetworkRequest &request, QNetworkReply::NetworkError errorCode, const QString &errorMessage, QObject *parent) : QNetworkReply(parent), m_offset(0) { setRequest(request); setOpenMode(QIODevice::ReadOnly); setUrl(request.url()); setOperation(op); setError(static_cast(errorCode), errorMessage); if (error() != QNetworkReply::NoError) { QMetaObject::invokeMethod(this, "error", Qt::QueuedConnection, Q_ARG(QNetworkReply::NetworkError, error())); } emitFinished(true, Qt::QueuedConnection); } AccessManagerReply::~AccessManagerReply() { } void AccessManagerReply::abort() { if (m_kioJob) { m_kioJob.data()->disconnect(this); } m_kioJob.clear(); m_data.clear(); m_offset = 0; m_metaDataRead = false; } qint64 AccessManagerReply::bytesAvailable() const { return (QNetworkReply::bytesAvailable() + m_data.length() - m_offset); } qint64 AccessManagerReply::readData(char *data, qint64 maxSize) { const qint64 length = qMin(qint64(m_data.length() - m_offset), maxSize); if (length <= 0) { return 0; } memcpy(data, m_data.constData() + m_offset, length); m_offset += length; if (m_data.length() == m_offset) { m_data.clear(); m_offset = 0; } return length; } bool AccessManagerReply::ignoreContentDisposition(const KIO::MetaData &metaData) { if (m_ignoreContentDisposition) { return true; } if (!metaData.contains(QStringLiteral("content-disposition-type"))) { return true; } bool ok = false; const int statusCode = attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(&ok); if (!ok || statusCode < 200 || statusCode > 299) { return true; } return false; } void AccessManagerReply::setHeaderFromMetaData(const KIO::MetaData &_metaData) { if (_metaData.isEmpty()) { return; } KIO::MetaData metaData(_metaData); // Set the encryption attribute and values... QSslConfiguration sslConfig; const bool isEncrypted = KIO::Integration::sslConfigFromMetaData(metaData, sslConfig); setAttribute(QNetworkRequest::ConnectionEncryptedAttribute, isEncrypted); if (isEncrypted) { setSslConfiguration(sslConfig); } // Set the raw header information... const QStringList httpHeaders(metaData.value(QStringLiteral("HTTP-Headers")).split(QL1C('\n'), QString::SkipEmptyParts)); if (httpHeaders.isEmpty()) { if (metaData.contains(QStringLiteral("charset"))) { QString mimeType = header(QNetworkRequest::ContentTypeHeader).toString(); mimeType += QStringLiteral(" ; charset="); mimeType += metaData.value(QStringLiteral("charset")); //qDebug() << "changed content-type to" << mimeType; setHeader(QNetworkRequest::ContentTypeHeader, mimeType.toUtf8()); } } else { Q_FOREACH (const QString &httpHeader, httpHeaders) { int index = httpHeader.indexOf(QL1C(':')); // Handle HTTP status line... if (index == -1) { // Except for the status line, all HTTP header must be an nvpair of // type ":" if (!httpHeader.startsWith(QLatin1String("HTTP/"), Qt::CaseInsensitive)) { continue; } QStringList statusLineAttrs(httpHeader.split(QL1C(' '), QString::SkipEmptyParts)); if (statusLineAttrs.count() > 1) { setAttribute(QNetworkRequest::HttpStatusCodeAttribute, statusLineAttrs.at(1)); } if (statusLineAttrs.count() > 2) { setAttribute(QNetworkRequest::HttpReasonPhraseAttribute, statusLineAttrs.at(2)); } continue; } const QString headerName = httpHeader.left(index); QString headerValue = httpHeader.mid(index + 1); // Ignore cookie header since it is handled by the http ioslave. if (headerName.startsWith(QLatin1String("set-cookie"), Qt::CaseInsensitive)) { continue; } if (headerName.startsWith(QLatin1String("content-disposition"), Qt::CaseInsensitive) && ignoreContentDisposition(metaData)) { continue; } // Without overriding the corrected mime-type sent by kio_http, add // back the "charset=" portion of the content-type header if present. if (headerName.startsWith(QLatin1String("content-type"), Qt::CaseInsensitive)) { QString mimeType(header(QNetworkRequest::ContentTypeHeader).toString()); if (m_ignoreContentDisposition) { // If the server returned application/octet-stream, try to determine the // real content type from the disposition filename. if (mimeType == QStringLiteral("application/octet-stream")) { const QString fileName(metaData.value(QStringLiteral("content-disposition-filename"))); QMimeDatabase db; QMimeType mime = db.mimeTypeForFile((fileName.isEmpty() ? url().path() : fileName), QMimeDatabase::MatchExtension); mimeType = mime.name(); } metaData.remove(QStringLiteral("content-disposition-type")); metaData.remove(QStringLiteral("content-disposition-filename")); } if (!headerValue.contains(mimeType, Qt::CaseInsensitive)) { index = headerValue.indexOf(QL1C(';')); if (index == -1) { headerValue = mimeType; } else { headerValue.replace(0, index, mimeType); } //qDebug() << "Changed mime-type from" << mimeType << "to" << headerValue; } } setRawHeader(headerName.trimmed().toUtf8(), headerValue.trimmed().toUtf8()); } } // Set the returned meta data as attribute... setAttribute(static_cast(KIO::AccessManager::MetaData), metaData.toVariant()); } void AccessManagerReply::setIgnoreContentDisposition(bool on) { //qDebug() << on; m_ignoreContentDisposition = on; } void AccessManagerReply::putOnHold() { if (!m_kioJob || isFinished()) { return; } //qDebug() << m_kioJob << m_data; m_kioJob.data()->disconnect(this); m_kioJob.data()->putOnHold(); m_kioJob.clear(); KIO::Scheduler::publishSlaveOnHold(); } bool AccessManagerReply::isLocalRequest(const QUrl &url) { const QString scheme(url.scheme()); return (KProtocolInfo::isKnownProtocol(scheme) && KProtocolInfo::protocolClass(scheme).compare(QStringLiteral(":local"), Qt::CaseInsensitive) == 0); } void AccessManagerReply::readHttpResponseHeaders(KIO::Job *job) { if (!job || m_metaDataRead) { return; } KIO::MetaData metaData(job->metaData()); if (metaData.isEmpty()) { // Allow handling of local resources such as man pages and file url... if (isLocalRequest(url())) { setHeader(QNetworkRequest::ContentLengthHeader, job->totalAmount(KJob::Bytes)); setAttribute(QNetworkRequest::HttpStatusCodeAttribute, QStringLiteral("200")); emit metaDataChanged(); } return; } setHeaderFromMetaData(metaData); m_metaDataRead = true; emit metaDataChanged(); } int AccessManagerReply::jobError(KJob *kJob) { const int errCode = kJob->error(); switch (errCode) { case 0: break; // No error; case KIO::ERR_SLAVE_DEFINED: case KIO::ERR_NO_CONTENT: // Sent by a 204 response is not an error condition. setError(QNetworkReply::NoError, kJob->errorText()); //qDebug() << "0 -> QNetworkReply::NoError"; break; case KIO::ERR_IS_DIRECTORY: // This error condition can happen if you click on an ftp link that points // to a directory instead of a file, e.g. ftp://ftp.kde.org/pub setHeader(QNetworkRequest::ContentTypeHeader, QByteArrayLiteral("inode/directory")); setError(QNetworkReply::NoError, kJob->errorText()); break; case KIO::ERR_CANNOT_CONNECT: setError(QNetworkReply::ConnectionRefusedError, kJob->errorText()); //qDebug() << "KIO::ERR_CANNOT_CONNECT -> QNetworkReply::ConnectionRefusedError"; break; case KIO::ERR_UNKNOWN_HOST: setError(QNetworkReply::HostNotFoundError, kJob->errorText()); //qDebug() << "KIO::ERR_UNKNOWN_HOST -> QNetworkReply::HostNotFoundError"; break; case KIO::ERR_SERVER_TIMEOUT: setError(QNetworkReply::TimeoutError, kJob->errorText()); //qDebug() << "KIO::ERR_SERVER_TIMEOUT -> QNetworkReply::TimeoutError"; break; case KIO::ERR_USER_CANCELED: case KIO::ERR_ABORTED: setError(QNetworkReply::OperationCanceledError, kJob->errorText()); //qDebug() << "KIO::ERR_ABORTED -> QNetworkReply::OperationCanceledError"; break; case KIO::ERR_UNKNOWN_PROXY_HOST: setError(QNetworkReply::ProxyNotFoundError, kJob->errorText()); //qDebug() << "KIO::UNKNOWN_PROXY_HOST -> QNetworkReply::ProxyNotFoundError"; break; case KIO::ERR_ACCESS_DENIED: setError(QNetworkReply::ContentAccessDenied, kJob->errorText()); //qDebug() << "KIO::ERR_ACCESS_DENIED -> QNetworkReply::ContentAccessDenied"; break; case KIO::ERR_WRITE_ACCESS_DENIED: setError(QNetworkReply::ContentOperationNotPermittedError, kJob->errorText()); //qDebug() << "KIO::ERR_WRITE_ACCESS_DENIED -> QNetworkReply::ContentOperationNotPermittedError"; break; case KIO::ERR_DOES_NOT_EXIST: setError(QNetworkReply::ContentNotFoundError, kJob->errorText()); //qDebug() << "KIO::ERR_DOES_NOT_EXIST -> QNetworkReply::ContentNotFoundError"; break; case KIO::ERR_CANNOT_AUTHENTICATE: setError(QNetworkReply::AuthenticationRequiredError, kJob->errorText()); //qDebug() << "KIO::ERR_CANNOT_AUTHENTICATE -> QNetworkReply::AuthenticationRequiredError"; break; case KIO::ERR_UNSUPPORTED_PROTOCOL: case KIO::ERR_NO_SOURCE_PROTOCOL: setError(QNetworkReply::ProtocolUnknownError, kJob->errorText()); //qDebug() << "KIO::ERR_UNSUPPORTED_PROTOCOL -> QNetworkReply::ProtocolUnknownError"; break; case KIO::ERR_CONNECTION_BROKEN: setError(QNetworkReply::RemoteHostClosedError, kJob->errorText()); //qDebug() << "KIO::ERR_CONNECTION_BROKEN -> QNetworkReply::RemoteHostClosedError"; break; case KIO::ERR_UNSUPPORTED_ACTION: setError(QNetworkReply::ProtocolInvalidOperationError, kJob->errorText()); //qDebug() << "KIO::ERR_UNSUPPORTED_ACTION -> QNetworkReply::ProtocolInvalidOperationError"; break; default: setError(QNetworkReply::UnknownNetworkError, kJob->errorText()); //qDebug() << KIO::rawErrorDetail(errCode, QString()) << "-> QNetworkReply::UnknownNetworkError"; } return errCode; } void AccessManagerReply::slotData(KIO::Job *kioJob, const QByteArray &data) { Q_UNUSED(kioJob); if (data.isEmpty()) { return; } qint64 newSizeWithOffset = m_data.size() + data.size(); if (newSizeWithOffset <= m_data.capacity()) { // Already enough space } else if (newSizeWithOffset - m_offset <= m_data.capacity()) { // We get enough space with ::remove. m_data.remove(0, m_offset); m_offset = 0; } else { // We have to resize the array, which implies an expensive memmove. // Do it ourselves to save m_offset bytes. QByteArray newData; // Leave some free space to avoid that every slotData call results in // a reallocation. qNextPowerOfTwo is what QByteArray does internally. newData.reserve(qNextPowerOfTwo(newSizeWithOffset - m_offset)); newData.append(m_data.constData() + m_offset, m_data.size() - m_offset); m_data = newData; m_offset = 0; } m_data += data; emit readyRead(); } void AccessManagerReply::slotMimeType(KIO::Job *kioJob, const QString &mimeType) { //qDebug() << kioJob << mimeType; setHeader(QNetworkRequest::ContentTypeHeader, mimeType.toUtf8()); readHttpResponseHeaders(kioJob); if (m_emitReadyReadOnMetaDataChange) { emit readyRead(); } } void AccessManagerReply::slotResult(KJob *kJob) { const int errcode = jobError(kJob); const QUrl redirectUrl = attribute(QNetworkRequest::RedirectionTargetAttribute).toUrl(); if (!redirectUrl.isValid()) { setAttribute(static_cast(KIO::AccessManager::KioError), errcode); if (errcode && errcode != KIO::ERR_NO_CONTENT) { emit error(error()); } } // Make sure HTTP response headers are always set. if (!m_metaDataRead) { readHttpResponseHeaders(qobject_cast(kJob)); } emitFinished(true); } void AccessManagerReply::slotStatResult(KJob *kJob) { if (jobError(kJob)) { emit error(error()); emitFinished(true); return; } KIO::StatJob *statJob = qobject_cast(kJob); Q_ASSERT(statJob); KIO::UDSEntry entry = statJob->statResult(); QString mimeType = entry.stringValue(KIO::UDSEntry::UDS_MIME_TYPE); if (mimeType.isEmpty() && entry.isDir()) { mimeType = QStringLiteral("inode/directory"); } if (!mimeType.isEmpty()) { setHeader(QNetworkRequest::ContentTypeHeader, mimeType.toUtf8()); } emitFinished(true); } void AccessManagerReply::slotRedirection(KIO::Job *job, const QUrl &u) { if (!KUrlAuthorized::authorizeUrlAction(QStringLiteral("redirect"), url(), u)) { qCWarning(KIO_WIDGETS) << "Redirection from" << url() << "to" << u << "REJECTED by policy!"; setError(QNetworkReply::ContentAccessDenied, u.toString()); emit error(error()); return; } setAttribute(QNetworkRequest::RedirectionTargetAttribute, QUrl(u)); if (job->queryMetaData(QStringLiteral("redirect-to-get")) == QL1S("true")) { setOperation(QNetworkAccessManager::GetOperation); } } void AccessManagerReply::slotPercent(KJob *job, unsigned long percent) { qulonglong bytesTotal = job->totalAmount(KJob::Bytes); qulonglong bytesProcessed = (bytesTotal * percent) / 100; if (operation() == QNetworkAccessManager::PutOperation || operation() == QNetworkAccessManager::PostOperation) { emit uploadProgress(bytesProcessed, bytesTotal); return; } emit downloadProgress(bytesProcessed, bytesTotal); } void AccessManagerReply::emitFinished(bool state, Qt::ConnectionType type) { setFinished(state); emit QMetaObject::invokeMethod(this, "finished", type); } } diff --git a/src/widgets/clipboardupdater.cpp b/src/widgets/clipboardupdater.cpp index 4789e9c4..03ac6b62 100644 --- a/src/widgets/clipboardupdater.cpp +++ b/src/widgets/clipboardupdater.cpp @@ -1,190 +1,190 @@ /* This file is part of the KDE libraries Copyright (C) 2013 Dawit Alemayehu This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library 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 Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "clipboardupdater_p.h" #include "jobclasses.h" #include "copyjob.h" #include "deletejob.h" #include #include "../pathhelpers_p.h" #include #include #include using namespace KIO; static void overwriteUrlsInClipboard(KJob *job) { CopyJob *copyJob = qobject_cast(job); FileCopyJob *fileCopyJob = qobject_cast(job); if (!copyJob && !fileCopyJob) { return; } QList newUrls; if (copyJob) { Q_FOREACH (const QUrl &url, copyJob->srcUrls()) { QUrl dUrl = copyJob->destUrl().adjusted(QUrl::StripTrailingSlash); dUrl.setPath(concatPaths(dUrl.path(), url.fileName())); newUrls.append(dUrl); } } else if (fileCopyJob) { newUrls << fileCopyJob->destUrl(); } QMimeData *mime = new QMimeData(); mime->setUrls(newUrls); QGuiApplication::clipboard()->setMimeData(mime); } static void updateUrlsInClipboard(KJob *job) { CopyJob *copyJob = qobject_cast(job); FileCopyJob *fileCopyJob = qobject_cast(job); if (!copyJob && !fileCopyJob) { return; } QClipboard *clipboard = QGuiApplication::clipboard(); auto mimeData = clipboard->mimeData(); if (!mimeData) { return; } QList clipboardUrls = KUrlMimeData::urlsFromMimeData(mimeData); bool update = false; if (copyJob) { Q_FOREACH (const QUrl &url, copyJob->srcUrls()) { const int index = clipboardUrls.indexOf(url); if (index > -1) { QUrl dUrl = copyJob->destUrl().adjusted(QUrl::StripTrailingSlash); dUrl.setPath(concatPaths(dUrl.path(), url.fileName())); clipboardUrls.replace(index, dUrl); update = true; } } } else if (fileCopyJob) { const int index = clipboardUrls.indexOf(fileCopyJob->srcUrl()); if (index > -1) { clipboardUrls.replace(index, fileCopyJob->destUrl()); update = true; } } if (update) { QMimeData *mime = new QMimeData(); mime->setUrls(clipboardUrls); clipboard->setMimeData(mime); } } static void removeUrlsFromClipboard(KJob *job) { SimpleJob *simpleJob = qobject_cast(job); DeleteJob *deleteJob = qobject_cast(job); if (!simpleJob && !deleteJob) { return; } QList deletedUrls; if (simpleJob) { deletedUrls << simpleJob->url(); } else if (deleteJob) { deletedUrls << deleteJob->urls(); } if (deletedUrls.isEmpty()) { return; } QClipboard *clipboard = QGuiApplication::clipboard(); auto mimeData = clipboard->mimeData(); if (!mimeData) { return; } QList clipboardUrls = KUrlMimeData::urlsFromMimeData(mimeData); quint32 removedCount = 0; Q_FOREACH (const QUrl &url, deletedUrls) { removedCount += clipboardUrls.removeAll(url); } if (removedCount > 0) { QMimeData *mime = new QMimeData(); if (!clipboardUrls.isEmpty()) { mime->setUrls(clipboardUrls); } clipboard->setMimeData(mime); } } void ClipboardUpdater::slotResult(KJob *job) { if (job->error()) { return; } switch (m_mode) { case JobUiDelegateExtension::UpdateContent: updateUrlsInClipboard(job); break; case JobUiDelegateExtension::OverwriteContent: overwriteUrlsInClipboard(job); break; case JobUiDelegateExtension::RemoveContent: removeUrlsFromClipboard(job); break; } } void ClipboardUpdater::setMode(JobUiDelegateExtension::ClipboardUpdaterMode mode) { m_mode = mode; } void ClipboardUpdater::update(const QUrl &srcUrl, const QUrl &destUrl) { QClipboard *clipboard = QGuiApplication::clipboard(); auto mimeData = clipboard->mimeData(); if (mimeData && mimeData->hasUrls()) { QList clipboardUrls = KUrlMimeData::urlsFromMimeData(clipboard->mimeData()); const int index = clipboardUrls.indexOf(srcUrl); if (index > -1) { clipboardUrls.replace(index, destUrl); QMimeData *mime = new QMimeData(); mime->setUrls(clipboardUrls); clipboard->setMimeData(mime); } } } ClipboardUpdater::ClipboardUpdater(Job *job, JobUiDelegateExtension::ClipboardUpdaterMode mode) : QObject(job), m_mode(mode) { Q_ASSERT(job); - connect(job, SIGNAL(result(KJob*)), this, SLOT(slotResult(KJob*))); + connect(job, &KJob::result, this, &ClipboardUpdater::slotResult); } diff --git a/src/widgets/delegateanimationhandler.cpp b/src/widgets/delegateanimationhandler.cpp index 4929556e..f01a8b8f 100644 --- a/src/widgets/delegateanimationhandler.cpp +++ b/src/widgets/delegateanimationhandler.cpp @@ -1,442 +1,443 @@ /* This file is part of the KDE project Copyright © 2007 Fredrik Höglund This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library 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 Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "delegateanimationhandler_p.h" #include #include #include #include #include #include #include "kdirmodel.h" #include #include "moc_delegateanimationhandler_p.cpp" namespace KIO { // Needed because state() is a protected method class ProtectedAccessor : public QAbstractItemView { Q_OBJECT public: bool draggingState() const { return state() == DraggingState; } }; // Debug output is disabled by default, use kdebugdialog to enable it //static int animationDebugArea() { static int s_area = KDebug::registerArea("kio (delegateanimationhandler)", false); // return s_area; } // --------------------------------------------------------------------------- CachedRendering::CachedRendering(QStyle::State state, const QSize &size, const QModelIndex &index, qreal devicePixelRatio) : state(state), regular(QPixmap(size*devicePixelRatio)), hover(QPixmap(size*devicePixelRatio)), valid(true), validityIndex(index) { regular.setDevicePixelRatio(devicePixelRatio); hover.setDevicePixelRatio(devicePixelRatio); regular.fill(Qt::transparent); hover.fill(Qt::transparent); if (index.model()) { - connect(index.model(), SIGNAL(dataChanged(QModelIndex,QModelIndex)), - SLOT(dataChanged(QModelIndex,QModelIndex))); - connect(index.model(), SIGNAL(modelReset()), SLOT(modelReset())); + connect(index.model(), &QAbstractItemModel::dataChanged, + this, &CachedRendering::dataChanged); + connect(index.model(), &QAbstractItemModel::modelReset, + this, &CachedRendering::modelReset); } } void CachedRendering::dataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight) { if (validityIndex.row() >= topLeft.row() && validityIndex.column() >= topLeft.column() && validityIndex.row() <= bottomRight.row() && validityIndex.column() <= bottomRight.column()) { valid = false; } } void CachedRendering::modelReset() { valid = false; } // --------------------------------------------------------------------------- AnimationState::AnimationState(const QModelIndex &index) : index(index), direction(QTimeLine::Forward), animating(false), jobAnimation(false), progress(0.0), m_fadeProgress(1.0), m_jobAnimationAngle(0.0), renderCache(nullptr), fadeFromRenderCache(nullptr) { creationTime.start(); } AnimationState::~AnimationState() { delete renderCache; delete fadeFromRenderCache; } bool AnimationState::update() { const qreal runtime = (direction == QTimeLine::Forward ? 150 : 250); // milliseconds const qreal increment = 1000. / runtime / 1000.; const qreal delta = increment * time.restart(); if (direction == QTimeLine::Forward) { progress = qMin(qreal(1.0), progress + delta); animating = (progress < 1.0); } else { progress = qMax(qreal(0.0), progress - delta); animating = (progress > 0.0); } if (fadeFromRenderCache) { //Icon fading goes always forwards m_fadeProgress = qMin(qreal(1.0), m_fadeProgress + delta); animating |= (m_fadeProgress < 1.0); if (m_fadeProgress == 1) { setCachedRenderingFadeFrom(nullptr); } } if (jobAnimation) { m_jobAnimationAngle += 1.0; if (m_jobAnimationAngle == 360) { m_jobAnimationAngle = 0; } if (index.model()->data(index, KDirModel::HasJobRole).toBool()) { animating = true; //there is a job here still... return false; } else { animating = false; //there's no job here anymore, return true so we stop painting this. return true; } } else { return !animating; } } qreal AnimationState::hoverProgress() const { #ifndef M_PI_2 #define M_PI_2 1.57079632679489661923 #endif return qRound(255.0 * std::sin(progress * M_PI_2)) / 255.0; } qreal AnimationState::fadeProgress() const { return qRound(255.0 * std::sin(m_fadeProgress * M_PI_2)) / 255.0; } qreal AnimationState::jobAnimationAngle() const { return m_jobAnimationAngle; } bool AnimationState::hasJobAnimation() const { return jobAnimation; } void AnimationState::setJobAnimation(bool value) { jobAnimation = value; } // --------------------------------------------------------------------------- static const int switchIconInterval = 1000; ///@todo Eventually configurable interval? DelegateAnimationHandler::DelegateAnimationHandler(QObject *parent) : QObject(parent) { iconSequenceTimer.setSingleShot(true); iconSequenceTimer.setInterval(switchIconInterval); - connect(&iconSequenceTimer, SIGNAL(timeout()), SLOT(sequenceTimerTimeout()));; + connect(&iconSequenceTimer, &QTimer::timeout, this, &DelegateAnimationHandler::sequenceTimerTimeout);; } DelegateAnimationHandler::~DelegateAnimationHandler() { timer.stop(); QMapIterator i(animationLists); while (i.hasNext()) { i.next(); qDeleteAll(*i.value()); delete i.value(); } animationLists.clear(); } void DelegateAnimationHandler::sequenceTimerTimeout() { QAbstractItemModel *model = const_cast(sequenceModelIndex.model()); QAbstractProxyModel *proxy = qobject_cast(model); QModelIndex index = sequenceModelIndex; if (proxy) { index = proxy->mapToSource(index); model = proxy->sourceModel(); } KDirModel *dirModel = dynamic_cast(model); if (dirModel) { //qDebug() << "requesting" << currentSequenceIndex; dirModel->requestSequenceIcon(index, currentSequenceIndex); iconSequenceTimer.start(); // Some upper-bound interval is needed, in case items are not generated } } void DelegateAnimationHandler::gotNewIcon(const QModelIndex &index) { Q_UNUSED(index); //qDebug() << currentSequenceIndex; if (sequenceModelIndex.isValid() && currentSequenceIndex) { iconSequenceTimer.start(); } // if(index ==sequenceModelIndex) //Leads to problems ++currentSequenceIndex; } void DelegateAnimationHandler::setSequenceIndex(int sequenceIndex) { //qDebug() << sequenceIndex; if (sequenceIndex > 0) { currentSequenceIndex = sequenceIndex; iconSequenceTimer.start(); } else { currentSequenceIndex = 0; sequenceTimerTimeout(); //Set the icon back to the standard one currentSequenceIndex = 0; //currentSequenceIndex was incremented, set it back to 0 iconSequenceTimer.stop(); } } void DelegateAnimationHandler::eventuallyStartIteration(const QModelIndex &index) { // if (KGlobalSettings::graphicEffectsLevel() & KGlobalSettings::SimpleAnimationEffects) { ///Think about it. if (sequenceModelIndex.isValid()) { setSequenceIndex(0); // Stop old iteration, and reset the icon for the old iteration } // Start sequence iteration sequenceModelIndex = index; setSequenceIndex(1); // } } AnimationState *DelegateAnimationHandler::animationState(const QStyleOption &option, const QModelIndex &index, const QAbstractItemView *view) { // We can't do animations reliably when an item is being dragged, since that // item will be drawn in two locations at the same time and hovered in one and // not the other. We can't tell them apart because they both have the same index. if (!view || static_cast(view)->draggingState()) { return nullptr; } AnimationState *state = findAnimationState(view, index); bool hover = option.state & QStyle::State_MouseOver; // If the cursor has entered an item if (!state && hover) { state = new AnimationState(index); addAnimationState(state, view); if (!fadeInAddTime.isValid() || (fadeInAddTime.isValid() && fadeInAddTime.elapsed() > 300)) { startAnimation(state); } else { state->animating = false; state->progress = 1.0; state->direction = QTimeLine::Forward; } fadeInAddTime.restart(); eventuallyStartIteration(index); } else if (state) { // If the cursor has exited an item if (!hover && (!state->animating || state->direction == QTimeLine::Forward)) { state->direction = QTimeLine::Backward; if (state->creationTime.elapsed() < 200) { state->progress = 0.0; } startAnimation(state); // Stop sequence iteration if (index == sequenceModelIndex) { setSequenceIndex(0); sequenceModelIndex = QPersistentModelIndex(); } } else if (hover && state->direction == QTimeLine::Backward) { // This is needed to handle the case where an item is dragged within // the view, and dropped in a different location. State_MouseOver will // initially not be set causing a "hover out" animation to start. // This reverses the direction as soon as we see the bit being set. state->direction = QTimeLine::Forward; if (!state->animating) { startAnimation(state); } eventuallyStartIteration(index); } } else if (!state && index.model()->data(index, KDirModel::HasJobRole).toBool()) { state = new AnimationState(index); addAnimationState(state, view); startAnimation(state); state->setJobAnimation(true); } return state; } AnimationState *DelegateAnimationHandler::findAnimationState(const QAbstractItemView *view, const QModelIndex &index) const { // Try to find a list of animation states for the view AnimationList *list = animationLists.value(view); if (list) { foreach (AnimationState *state, *list) if (state->index == index) { return state; } } return nullptr; } void DelegateAnimationHandler::addAnimationState(AnimationState *state, const QAbstractItemView *view) { AnimationList *list = animationLists.value(view); // If this is the first time we've seen this view if (!list) { - connect(view, SIGNAL(destroyed(QObject*)), SLOT(viewDeleted(QObject*))); + connect(view, &QObject::destroyed, this, &DelegateAnimationHandler::viewDeleted); list = new AnimationList; animationLists.insert(view, list); } list->append(state); } void DelegateAnimationHandler::restartAnimation(AnimationState *state) { startAnimation(state); } void DelegateAnimationHandler::startAnimation(AnimationState *state) { state->time.start(); state->animating = true; if (!timer.isActive()) { timer.start(1000 / 30, this); // 30 fps } } int DelegateAnimationHandler::runAnimations(AnimationList *list, const QAbstractItemView *view) { int activeAnimations = 0; QRegion region; QMutableLinkedListIterator i(*list); while (i.hasNext()) { AnimationState *state = i.next(); if (!state->animating) { continue; } // We need to make sure the index is still valid, since it could be removed // while the animation is running. if (state->index.isValid()) { bool finished = state->update(); region += view->visualRect(state->index); if (!finished) { activeAnimations++; continue; } } // If the direction is Forward, the state object needs to stick around // after the animation has finished, so we know that we've already done // a "hover in" for the index. if (state->direction == QTimeLine::Backward || !state->index.isValid()) { delete state; i.remove(); } } // Trigger a repaint of the animated indexes if (!region.isEmpty()) { const_cast(view)->viewport()->update(region); } return activeAnimations; } void DelegateAnimationHandler::viewDeleted(QObject *view) { AnimationList *list = animationLists.take(static_cast(view)); qDeleteAll(*list); delete list; } void DelegateAnimationHandler::timerEvent(QTimerEvent *) { int activeAnimations = 0; AnimationListsIterator i(animationLists); while (i.hasNext()) { i.next(); AnimationList *list = i.value(); const QAbstractItemView *view = i.key(); activeAnimations += runAnimations(list, view); } if (activeAnimations == 0 && timer.isActive()) { timer.stop(); } } } #include "delegateanimationhandler.moc" diff --git a/src/widgets/fileundomanager.cpp b/src/widgets/fileundomanager.cpp index b865b52f..20562090 100644 --- a/src/widgets/fileundomanager.cpp +++ b/src/widgets/fileundomanager.cpp @@ -1,790 +1,790 @@ /* This file is part of the KDE project Copyright (C) 2000 Simon Hausmann Copyright (C) 2006, 2008 David Faure This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library 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 Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "fileundomanager.h" #include "fileundomanager_p.h" #include "clipboardupdater_p.h" #include "fileundomanager_adaptor.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include using namespace KIO; #if 0 static const char *undoStateToString(UndoState state) { static const char *const s_undoStateToString[] = { "MAKINGDIRS", "MOVINGFILES", "STATINGFILE", "REMOVINGDIRS", "REMOVINGLINKS" }; return s_undoStateToString[state]; } #endif static QDataStream &operator<<(QDataStream &stream, const KIO::BasicOperation &op) { stream << op.m_valid << (qint8)op.m_type << op.m_renamed << op.m_src << op.m_dst << op.m_target << qint64(op.m_mtime.toMSecsSinceEpoch() / 1000); return stream; } static QDataStream &operator>>(QDataStream &stream, BasicOperation &op) { qint8 type; qint64 mtime; stream >> op.m_valid >> type >> op.m_renamed >> op.m_src >> op.m_dst >> op.m_target >> mtime; op.m_type = static_cast(type); op.m_mtime = QDateTime::fromMSecsSinceEpoch(1000 * mtime); return stream; } static QDataStream &operator<<(QDataStream &stream, const UndoCommand &cmd) { stream << cmd.m_valid << (qint8)cmd.m_type << cmd.m_opStack << cmd.m_src << cmd.m_dst; return stream; } static QDataStream &operator>>(QDataStream &stream, UndoCommand &cmd) { qint8 type; stream >> cmd.m_valid >> type >> cmd.m_opStack >> cmd.m_src >> cmd.m_dst; cmd.m_type = static_cast(type); return stream; } /** * checklist: * copy dir -> overwrite -> works * move dir -> overwrite -> works * copy dir -> rename -> works * move dir -> rename -> works * * copy dir -> works * move dir -> works * * copy files -> works * move files -> works (TODO: optimize (change FileCopyJob to use the renamed arg for copyingDone) * * copy files -> overwrite -> works (sorry for your overwritten file...) * move files -> overwrite -> works (sorry for your overwritten file...) * * copy files -> rename -> works * move files -> rename -> works * * -> see also fileundomanagertest, which tests some of the above (but not renaming). * */ class KIO::UndoJob : public KIO::Job { Q_OBJECT public: UndoJob(bool showProgressInfo) : KIO::Job() { if (showProgressInfo) { KIO::getJobTracker()->registerJob(this); } d_ptr->m_privilegeExecutionEnabled = true; d_ptr->m_operationType = d_ptr->Other; d_ptr->m_caption = i18n("Undo Changes"); d_ptr->m_message = i18n("Undoing this operation requires root privileges. Do you want to continue?"); } virtual ~UndoJob() {} virtual void kill(bool) { FileUndoManager::self()->d->stopUndo(true); KIO::Job::doKill(); } void emitCreatingDir(const QUrl &dir) { emit description(this, i18n("Creating directory"), qMakePair(i18n("Directory"), dir.toDisplayString())); } void emitMoving(const QUrl &src, const QUrl &dest) { emit description(this, i18n("Moving"), qMakePair(i18nc("The source of a file operation", "Source"), src.toDisplayString()), qMakePair(i18nc("The destination of a file operation", "Destination"), dest.toDisplayString())); } void emitDeleting(const QUrl &url) { emit description(this, i18n("Deleting"), qMakePair(i18n("File"), url.toDisplayString())); } void emitResult() { KIO::Job::emitResult(); } }; CommandRecorder::CommandRecorder(FileUndoManager::CommandType op, const QList &src, const QUrl &dst, KIO::Job *job) : QObject(job) { m_cmd.m_type = op; m_cmd.m_valid = true; m_cmd.m_serialNumber = FileUndoManager::self()->newCommandSerialNumber(); m_cmd.m_src = src; m_cmd.m_dst = dst; - connect(job, SIGNAL(result(KJob*)), - this, SLOT(slotResult(KJob*))); - if (qobject_cast(job)) { - connect(job, SIGNAL(copyingDone(KIO::Job*,QUrl,QUrl,QDateTime,bool,bool)), - this, SLOT(slotCopyingDone(KIO::Job*,QUrl,QUrl,QDateTime,bool,bool))); - connect(job, SIGNAL(copyingLinkDone(KIO::Job*,QUrl,QString,QUrl)), - this, SLOT(slotCopyingLinkDone(KIO::Job*,QUrl,QString,QUrl))); + connect(job, &KJob::result, + this, &CommandRecorder::slotResult); + if (auto *copyJob = qobject_cast(job)) { + connect(copyJob, &KIO::CopyJob::copyingDone, + this, &CommandRecorder::slotCopyingDone); + connect(copyJob, &KIO::CopyJob::copyingLinkDone, + this, &CommandRecorder::slotCopyingLinkDone); } else if (KIO::MkpathJob *mkpathJob = qobject_cast(job)) { connect(mkpathJob, &KIO::MkpathJob::directoryCreated, this, &CommandRecorder::slotDirectoryCreated); } else if (KIO::BatchRenameJob *batchRenameJob = qobject_cast(job)) { connect(batchRenameJob, &KIO::BatchRenameJob::fileRenamed, this, &CommandRecorder::slotBatchRenamingDone); } } CommandRecorder::~CommandRecorder() { } void CommandRecorder::slotResult(KJob *job) { if (job->error()) { return; } FileUndoManager::self()->d->addCommand(m_cmd); } void CommandRecorder::slotCopyingDone(KIO::Job *, const QUrl &from, const QUrl &to, const QDateTime &mtime, bool directory, bool renamed) { BasicOperation op; op.m_valid = true; op.m_type = directory ? BasicOperation::Directory : BasicOperation::File; op.m_renamed = renamed; op.m_src = from; op.m_dst = to; op.m_mtime = mtime; m_cmd.m_opStack.prepend(op); } // TODO merge the signals? void CommandRecorder::slotCopyingLinkDone(KIO::Job *, const QUrl &from, const QString &target, const QUrl &to) { BasicOperation op; op.m_valid = true; op.m_type = BasicOperation::Link; op.m_renamed = false; op.m_src = from; op.m_target = target; op.m_dst = to; op.m_mtime = QDateTime(); m_cmd.m_opStack.prepend(op); } void CommandRecorder::slotDirectoryCreated(const QUrl &dir) { BasicOperation op; op.m_valid = true; op.m_type = BasicOperation::Directory; op.m_renamed = false; op.m_src = QUrl(); op.m_dst = dir; op.m_mtime = QDateTime(); m_cmd.m_opStack.prepend(op); } void CommandRecorder::slotBatchRenamingDone(const QUrl &from, const QUrl &to) { BasicOperation op; op.m_valid = true; op.m_type = BasicOperation::Directory; op.m_renamed = true; op.m_src = from; op.m_dst = to; op.m_mtime = QDateTime(); m_cmd.m_opStack.prepend(op); } //// class KIO::FileUndoManagerSingleton { public: FileUndoManager self; }; Q_GLOBAL_STATIC(KIO::FileUndoManagerSingleton, globalFileUndoManager) FileUndoManager *FileUndoManager::self() { return &globalFileUndoManager()->self; } // m_nextCommandIndex is initialized to a high number so that konqueror can // assign low numbers to closed items loaded "on-demand" from a config file // in KonqClosedWindowsManager::readConfig and thus maintaining the real // order of the undo items. FileUndoManagerPrivate::FileUndoManagerPrivate(FileUndoManager *qq) : m_uiInterface(new FileUndoManager::UiInterface()), m_undoJob(nullptr), m_nextCommandIndex(1000), q(qq) { (void) new KIOFileUndoManagerAdaptor(this); const QString dbusPath = QStringLiteral("/FileUndoManager"); const QString dbusInterface = QStringLiteral("org.kde.kio.FileUndoManager"); QDBusConnection dbus = QDBusConnection::sessionBus(); dbus.registerObject(dbusPath, this); dbus.connect(QString(), dbusPath, dbusInterface, QStringLiteral("lock"), this, SLOT(slotLock())); dbus.connect(QString(), dbusPath, dbusInterface, QStringLiteral("pop"), this, SLOT(slotPop())); dbus.connect(QString(), dbusPath, dbusInterface, QStringLiteral("push"), this, SLOT(slotPush(QByteArray))); dbus.connect(QString(), dbusPath, dbusInterface, QStringLiteral("unlock"), this, SLOT(slotUnlock())); } FileUndoManager::FileUndoManager() { d = new FileUndoManagerPrivate(this); d->m_lock = false; d->m_currentJob = nullptr; } FileUndoManager::~FileUndoManager() { delete d; } void FileUndoManager::recordJob(CommandType op, const QList &src, const QUrl &dst, KIO::Job *job) { // This records what the job does and calls addCommand when done (void) new CommandRecorder(op, src, dst, job); emit jobRecordingStarted(op); } void FileUndoManager::recordCopyJob(KIO::CopyJob *copyJob) { CommandType commandType; switch (copyJob->operationMode()) { case CopyJob::Copy: commandType = Copy; break; case CopyJob::Move: commandType = Move; break; case CopyJob::Link: default: // prevent "wrong" compiler warning because of possibly uninitialized variable commandType = Link; break; } recordJob(commandType, copyJob->srcUrls(), copyJob->destUrl(), copyJob); } void FileUndoManagerPrivate::addCommand(const UndoCommand &cmd) { pushCommand(cmd); emit q->jobRecordingFinished(cmd.m_type); } bool FileUndoManager::undoAvailable() const { return (d->m_commands.count() > 0) && !d->m_lock; } QString FileUndoManager::undoText() const { if (d->m_commands.isEmpty()) { return i18n("Und&o"); } FileUndoManager::CommandType t = d->m_commands.last().m_type; switch (t) { case FileUndoManager::Copy: return i18n("Und&o: Copy"); case FileUndoManager::Link: return i18n("Und&o: Link"); case FileUndoManager::Move: return i18n("Und&o: Move"); case FileUndoManager::Rename: return i18n("Und&o: Rename"); case FileUndoManager::Trash: return i18n("Und&o: Trash"); case FileUndoManager::Mkdir: return i18n("Und&o: Create Folder"); case FileUndoManager::Mkpath: return i18n("Und&o: Create Folder(s)"); case FileUndoManager::Put: return i18n("Und&o: Create File"); case FileUndoManager::BatchRename: return i18n("Und&o: Batch Rename"); } /* NOTREACHED */ return QString(); } quint64 FileUndoManager::newCommandSerialNumber() { return ++(d->m_nextCommandIndex); } quint64 FileUndoManager::currentCommandSerialNumber() const { if (!d->m_commands.isEmpty()) { const UndoCommand &cmd = d->m_commands.last(); assert(cmd.m_valid); return cmd.m_serialNumber; } else { return 0; } } void FileUndoManager::undo() { // Make a copy of the command to undo before slotPop() pops it. UndoCommand cmd = d->m_commands.last(); assert(cmd.m_valid); d->m_current = cmd; const CommandType commandType = cmd.m_type; BasicOperation::Stack &opStack = d->m_current.m_opStack; // Note that opStack is empty for simple operations like Mkdir. // Let's first ask for confirmation if we need to delete any file (#99898) QList itemsToDelete; BasicOperation::Stack::Iterator it = opStack.begin(); for (; it != opStack.end(); ++it) { BasicOperation::Type type = (*it).m_type; const auto destination = (*it).m_dst; if (type == BasicOperation::File && commandType == FileUndoManager::Copy) { if (destination.isLocalFile() && !QFileInfo::exists(destination.toLocalFile())) { continue; } itemsToDelete.append(destination); } else if (commandType == FileUndoManager::Mkpath) { itemsToDelete.append(destination); } } if (commandType == FileUndoManager::Mkdir || commandType == FileUndoManager::Put) { itemsToDelete.append(d->m_current.m_dst); } if (!itemsToDelete.isEmpty()) { if (!d->m_uiInterface->confirmDeletion(itemsToDelete)) { return; } } else if (commandType == FileUndoManager::Copy) { d->slotPop(); return; } d->slotPop(); d->slotLock(); d->m_dirCleanupStack.clear(); d->m_dirStack.clear(); d->m_dirsToUpdate.clear(); d->m_undoState = MOVINGFILES; // Let's have a look at the basic operations we need to undo. // While we're at it, collect all links that should be deleted. it = opStack.begin(); while (it != opStack.end()) { // don't cache end() here, erase modifies it bool removeBasicOperation = false; BasicOperation::Type type = (*it).m_type; if (type == BasicOperation::Directory && !(*it).m_renamed) { // If any directory has to be created/deleted, we'll start with that d->m_undoState = MAKINGDIRS; // Collect all the dirs that have to be created in case of a move undo. if (d->m_current.isMoveCommand()) { d->m_dirStack.push((*it).m_src); } // Collect all dirs that have to be deleted // from the destination in both cases (copy and move). d->m_dirCleanupStack.prepend((*it).m_dst); removeBasicOperation = true; } else if (type == BasicOperation::Link) { d->m_fileCleanupStack.prepend((*it).m_dst); removeBasicOperation = !d->m_current.isMoveCommand(); } if (removeBasicOperation) { it = opStack.erase(it); } else { ++it; } } if (commandType == FileUndoManager::Put) { d->m_fileCleanupStack.append(d->m_current.m_dst); } //qDebug() << "starting with" << undoStateToString(d->m_undoState); d->m_undoJob = new UndoJob(d->m_uiInterface->showProgressInfo()); QMetaObject::invokeMethod(d, "undoStep", Qt::QueuedConnection); } void FileUndoManagerPrivate::stopUndo(bool step) { m_current.m_opStack.clear(); m_dirCleanupStack.clear(); m_fileCleanupStack.clear(); m_undoState = REMOVINGDIRS; m_undoJob = nullptr; if (m_currentJob) { m_currentJob->kill(); } m_currentJob = nullptr; if (step) { undoStep(); } } void FileUndoManagerPrivate::slotResult(KJob *job) { m_currentJob = nullptr; if (job->error()) { m_uiInterface->jobError(static_cast(job)); delete m_undoJob; stopUndo(false); } else if (m_undoState == STATINGFILE) { BasicOperation op = m_current.m_opStack.last(); //qDebug() << "stat result for " << op.m_dst; KIO::StatJob *statJob = static_cast(job); const QDateTime mtime = QDateTime::fromMSecsSinceEpoch(1000 * statJob->statResult().numberValue(KIO::UDSEntry::UDS_MODIFICATION_TIME, -1)); if (mtime != op.m_mtime) { //qDebug() << op.m_dst << " was modified after being copied!"; QDateTime srcTime = op.m_mtime.toLocalTime(); QDateTime destTime = mtime.toLocalTime(); if (!m_uiInterface->copiedFileWasModified(op.m_src, op.m_dst, srcTime, destTime)) { stopUndo(false); } } } undoStep(); } void FileUndoManagerPrivate::addDirToUpdate(const QUrl &url) { if (!m_dirsToUpdate.contains(url)) { m_dirsToUpdate.prepend(url); } } void FileUndoManagerPrivate::undoStep() { m_currentJob = nullptr; if (m_undoState == MAKINGDIRS) { stepMakingDirectories(); } if (m_undoState == MOVINGFILES || m_undoState == STATINGFILE) { stepMovingFiles(); } if (m_undoState == REMOVINGLINKS) { stepRemovingLinks(); } if (m_undoState == REMOVINGDIRS) { stepRemovingDirectories(); } if (m_currentJob) { if (m_uiInterface) { KJobWidgets::setWindow(m_currentJob, m_uiInterface->parentWidget()); } - QObject::connect(m_currentJob, SIGNAL(result(KJob*)), - this, SLOT(slotResult(KJob*))); + QObject::connect(m_currentJob, &KJob::result, + this, &FileUndoManagerPrivate::slotResult); } } void FileUndoManagerPrivate::stepMakingDirectories() { if (!m_dirStack.isEmpty()) { QUrl dir = m_dirStack.pop(); //qDebug() << "creatingDir" << dir; m_currentJob = KIO::mkdir(dir); m_currentJob->setParentJob(m_undoJob); m_undoJob->emitCreatingDir(dir); } else { m_undoState = MOVINGFILES; } } // Misnamed method: It moves files back, but it also // renames directories back, recreates symlinks, // deletes copied files, and restores trashed files. void FileUndoManagerPrivate::stepMovingFiles() { if (!m_current.m_opStack.isEmpty()) { BasicOperation op = m_current.m_opStack.last(); BasicOperation::Type type = op.m_type; assert(op.m_valid); if (type == BasicOperation::Directory) { if (op.m_renamed) { //qDebug() << "rename" << op.m_dst << op.m_src; m_currentJob = KIO::rename(op.m_dst, op.m_src, KIO::HideProgressInfo); m_undoJob->emitMoving(op.m_dst, op.m_src); } else { assert(0); // this should not happen! } } else if (type == BasicOperation::Link) { //qDebug() << "symlink" << op.m_target << op.m_src; m_currentJob = KIO::symlink(op.m_target, op.m_src, KIO::Overwrite | KIO::HideProgressInfo); } else if (m_current.m_type == FileUndoManager::Copy) { if (m_undoState == MOVINGFILES) { // dest not stat'ed yet // Before we delete op.m_dst, let's check if it was modified (#20532) //qDebug() << "stat" << op.m_dst; m_currentJob = KIO::stat(op.m_dst, KIO::HideProgressInfo); m_undoState = STATINGFILE; // temporarily return; // no pop() yet, we'll finish the work in slotResult } else { // dest was stat'ed, and the deletion was approved in slotResult m_currentJob = KIO::file_delete(op.m_dst, KIO::HideProgressInfo); m_undoJob->emitDeleting(op.m_dst); m_undoState = MOVINGFILES; } } else if (m_current.isMoveCommand() || m_current.m_type == FileUndoManager::Trash) { //qDebug() << "file_move" << op.m_dst << op.m_src; m_currentJob = KIO::file_move(op.m_dst, op.m_src, -1, KIO::Overwrite | KIO::HideProgressInfo); m_currentJob->uiDelegateExtension()->createClipboardUpdater(m_currentJob, JobUiDelegateExtension::UpdateContent); m_undoJob->emitMoving(op.m_dst, op.m_src); } if (m_currentJob) { m_currentJob->setParentJob(m_undoJob); } m_current.m_opStack.removeLast(); // The above KIO jobs are lowlevel, they don't trigger KDirNotify notification // So we need to do it ourselves (but schedule it to the end of the undo, to compress them) QUrl url = op.m_dst.adjusted(QUrl::RemoveFilename | QUrl::StripTrailingSlash); addDirToUpdate(url); url = op.m_src.adjusted(QUrl::RemoveFilename | QUrl::StripTrailingSlash); addDirToUpdate(url); } else { m_undoState = REMOVINGLINKS; } } void FileUndoManagerPrivate::stepRemovingLinks() { //qDebug() << "REMOVINGLINKS"; if (!m_fileCleanupStack.isEmpty()) { QUrl file = m_fileCleanupStack.pop(); //qDebug() << "file_delete" << file; m_currentJob = KIO::file_delete(file, KIO::HideProgressInfo); m_currentJob->setParentJob(m_undoJob); m_undoJob->emitDeleting(file); QUrl url = file.adjusted(QUrl::RemoveFilename | QUrl::StripTrailingSlash); addDirToUpdate(url); } else { m_undoState = REMOVINGDIRS; if (m_dirCleanupStack.isEmpty() && m_current.m_type == FileUndoManager::Mkdir) { m_dirCleanupStack << m_current.m_dst; } } } void FileUndoManagerPrivate::stepRemovingDirectories() { if (!m_dirCleanupStack.isEmpty()) { QUrl dir = m_dirCleanupStack.pop(); //qDebug() << "rmdir" << dir; m_currentJob = KIO::rmdir(dir); m_currentJob->setParentJob(m_undoJob); m_undoJob->emitDeleting(dir); addDirToUpdate(dir); } else { m_current.m_valid = false; m_currentJob = nullptr; if (m_undoJob) { //qDebug() << "deleting undojob"; m_undoJob->emitResult(); m_undoJob = nullptr; } QList::ConstIterator it = m_dirsToUpdate.constBegin(); for (; it != m_dirsToUpdate.constEnd(); ++it) { //qDebug() << "Notifying FilesAdded for " << *it; org::kde::KDirNotify::emitFilesAdded((*it)); } emit q->undoJobFinished(); slotUnlock(); } } // const ref doesn't work due to QDataStream void FileUndoManagerPrivate::slotPush(QByteArray data) { QDataStream strm(&data, QIODevice::ReadOnly); UndoCommand cmd; strm >> cmd; pushCommand(cmd); } void FileUndoManagerPrivate::pushCommand(const UndoCommand &cmd) { m_commands.append(cmd); emit q->undoAvailable(true); emit q->undoTextChanged(q->undoText()); } void FileUndoManagerPrivate::slotPop() { m_commands.removeLast(); emit q->undoAvailable(q->undoAvailable()); emit q->undoTextChanged(q->undoText()); } void FileUndoManagerPrivate::slotLock() { // assert(!m_lock); m_lock = true; emit q->undoAvailable(q->undoAvailable()); } void FileUndoManagerPrivate::slotUnlock() { // assert(m_lock); m_lock = false; emit q->undoAvailable(q->undoAvailable()); } QByteArray FileUndoManagerPrivate::get() const { QByteArray data; QDataStream stream(&data, QIODevice::WriteOnly); stream << m_commands; return data; } void FileUndoManager::setUiInterface(UiInterface *ui) { delete d->m_uiInterface; d->m_uiInterface = ui; } FileUndoManager::UiInterface *FileUndoManager::uiInterface() const { return d->m_uiInterface; } //// class Q_DECL_HIDDEN FileUndoManager::UiInterface::UiInterfacePrivate { public: UiInterfacePrivate() : m_parentWidget(nullptr), m_showProgressInfo(true) {} QWidget *m_parentWidget; bool m_showProgressInfo; }; FileUndoManager::UiInterface::UiInterface() : d(new UiInterfacePrivate) { } FileUndoManager::UiInterface::~UiInterface() { delete d; } void FileUndoManager::UiInterface::jobError(KIO::Job *job) { job->uiDelegate()->showErrorMessage(); } bool FileUndoManager::UiInterface::copiedFileWasModified(const QUrl &src, const QUrl &dest, const QDateTime &srcTime, const QDateTime &destTime) { Q_UNUSED(srcTime); // not sure it should appear in the msgbox // Possible improvement: only show the time if date is today const QString timeStr = destTime.toString(Qt::DefaultLocaleShortDate); return KMessageBox::warningContinueCancel( d->m_parentWidget, i18n("The file %1 was copied from %2, but since then it has apparently been modified at %3.\n" "Undoing the copy will delete the file, and all modifications will be lost.\n" "Are you sure you want to delete %4?", dest.toDisplayString(QUrl::PreferLocalFile), src.toDisplayString(QUrl::PreferLocalFile), timeStr, dest.toDisplayString(QUrl::PreferLocalFile)), i18n("Undo File Copy Confirmation"), KStandardGuiItem::cont(), KStandardGuiItem::cancel(), QString(), KMessageBox::Options(KMessageBox::Notify) | KMessageBox::Dangerous) == KMessageBox::Continue; } bool FileUndoManager::UiInterface::confirmDeletion(const QList &files) { KIO::JobUiDelegate uiDelegate; uiDelegate.setWindow(d->m_parentWidget); // Because undo can happen with an accidental Ctrl-Z, we want to always confirm. return uiDelegate.askDeleteConfirmation(files, KIO::JobUiDelegate::Delete, KIO::JobUiDelegate::ForceConfirmation); } QWidget *FileUndoManager::UiInterface::parentWidget() const { return d->m_parentWidget; } void FileUndoManager::UiInterface::setParentWidget(QWidget *parentWidget) { d->m_parentWidget = parentWidget; } void FileUndoManager::UiInterface::setShowProgressInfo(bool b) { d->m_showProgressInfo = b; } bool FileUndoManager::UiInterface::showProgressInfo() const { return d->m_showProgressInfo; } void FileUndoManager::UiInterface::virtual_hook(int, void *) { } #include "moc_fileundomanager_p.cpp" #include "moc_fileundomanager.cpp" #include "fileundomanager.moc" diff --git a/src/widgets/jobuidelegate.cpp b/src/widgets/jobuidelegate.cpp index 45570b37..fb4c4535 100644 --- a/src/widgets/jobuidelegate.cpp +++ b/src/widgets/jobuidelegate.cpp @@ -1,421 +1,421 @@ /* This file is part of the KDE libraries Copyright (C) 2000 Stephan Kulow David Faure Copyright (C) 2006 Kevin Ottens Copyright (C) 2013 Dawit Alemayehu This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library 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 Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "jobuidelegate.h" #include #include "kio_widgets_debug.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "kio/scheduler.h" class Q_DECL_HIDDEN KIO::JobUiDelegate::Private { public: }; KIO::JobUiDelegate::JobUiDelegate() : d(new Private()) { } KIO::JobUiDelegate::~JobUiDelegate() { delete d; } /* Returns the top most window associated with widget. Unlike QWidget::window(), this function does its best to find and return the main application window associated with the given widget. If widget itself is a dialog or its parent is a dialog, and that dialog has a parent widget then this function will iterate through all those widgets to find the top most window, which most of the time is the main window of the application. By contrast, QWidget::window() would simply return the first file dialog it encountered since it is the "next ancestor widget that has (or could have) a window-system frame". */ static QWidget *topLevelWindow(QWidget *widget) { QWidget *w = widget; while (w && w->parentWidget()) { w = w->parentWidget(); } return (w ? w->window() : nullptr); } class JobUiDelegateStatic : public QObject { Q_OBJECT public: void registerWindow(QWidget *wid) { if (!wid) { return; } QWidget *window = topLevelWindow(wid); QObject *obj = static_cast(window); if (!m_windowList.contains(obj)) { // We must store the window Id because by the time // the destroyed signal is emitted we can no longer // access QWidget::winId() (already destructed) WId windowId = window->winId(); m_windowList.insert(obj, windowId); - connect(window, SIGNAL(destroyed(QObject*)), - this, SLOT(slotUnregisterWindow(QObject*))); + connect(window, &QObject::destroyed, + this, &JobUiDelegateStatic::slotUnregisterWindow); QDBusInterface(QStringLiteral("org.kde.kded5"), QStringLiteral("/kded"), QStringLiteral("org.kde.kded5")). call(QDBus::NoBlock, QStringLiteral("registerWindowId"), qlonglong(windowId)); } } public Q_SLOTS: void slotUnregisterWindow(QObject *obj) { if (!obj) { return; } QMap::Iterator it = m_windowList.find(obj); if (it == m_windowList.end()) { return; } WId windowId = it.value(); - disconnect(it.key(), SIGNAL(destroyed(QObject*)), - this, SLOT(slotUnregisterWindow(QObject*))); + disconnect(it.key(), &QObject::destroyed, + this, &JobUiDelegateStatic::slotUnregisterWindow); m_windowList.erase(it); QDBusInterface(QStringLiteral("org.kde.kded5"), QStringLiteral("/kded"), QStringLiteral("org.kde.kded5")). call(QDBus::NoBlock, QStringLiteral("unregisterWindowId"), qlonglong(windowId)); } private: QMap m_windowList; }; Q_GLOBAL_STATIC(JobUiDelegateStatic, s_static) void KIO::JobUiDelegate::setWindow(QWidget *window) { KDialogJobUiDelegate::setWindow(window); s_static()->registerWindow(window); } void KIO::JobUiDelegate::unregisterWindow(QWidget *window) { s_static()->slotUnregisterWindow(window); } KIO::RenameDialog_Result KIO::JobUiDelegate::askFileRename(KJob *job, const QString &caption, const QUrl &src, const QUrl &dest, KIO::RenameDialog_Options options, QString &newDest, KIO::filesize_t sizeSrc, KIO::filesize_t sizeDest, const QDateTime &ctimeSrc, const QDateTime &ctimeDest, const QDateTime &mtimeSrc, const QDateTime &mtimeDest) { //qDebug() << "job=" << job; // We now do it in process, so that opening the rename dialog // doesn't start uiserver for nothing if progressId=0 (e.g. F2 in konq) KIO::RenameDialog dlg(KJobWidgets::window(job), caption, src, dest, options, sizeSrc, sizeDest, ctimeSrc, ctimeDest, mtimeSrc, mtimeDest); dlg.setWindowModality(Qt::WindowModal); - connect(job, SIGNAL(finished(KJob*)), &dlg, SLOT(reject())); // #192976 + connect(job, &KJob::finished, &dlg, &QDialog::reject); // #192976 KIO::RenameDialog_Result res = static_cast(dlg.exec()); if (res == R_AUTO_RENAME) { newDest = dlg.autoDestUrl().path(); } else { newDest = dlg.newDestUrl().path(); } return res; } KIO::SkipDialog_Result KIO::JobUiDelegate::askSkip(KJob *job, KIO::SkipDialog_Options options, const QString &error_text) { KIO::SkipDialog dlg(KJobWidgets::window(job), options, error_text); dlg.setWindowModality(Qt::WindowModal); - connect(job, SIGNAL(finished(KJob*)), &dlg, SLOT(reject())); // #192976 + connect(job, &KJob::finished, &dlg, &QDialog::reject); // #192976 return static_cast(dlg.exec()); } bool KIO::JobUiDelegate::askDeleteConfirmation(const QList &urls, DeletionType deletionType, ConfirmationType confirmationType) { QString keyName; bool ask = (confirmationType == ForceConfirmation); if (!ask) { KSharedConfigPtr kioConfig = KSharedConfig::openConfig(QStringLiteral("kiorc"), KConfig::NoGlobals); // The default value for confirmations is true for delete and false // for trash. If you change this, please also update: // dolphin/src/settings/general/confirmationssettingspage.cpp bool defaultValue = true; switch (deletionType) { case Delete: keyName = QStringLiteral("ConfirmDelete"); break; case Trash: keyName = QStringLiteral("ConfirmTrash"); defaultValue = false; break; case EmptyTrash: keyName = QStringLiteral("ConfirmEmptyTrash"); break; } ask = kioConfig->group("Confirmations").readEntry(keyName, defaultValue); } if (ask) { QStringList prettyList; Q_FOREACH (const QUrl &url, urls) { if (url.scheme() == QLatin1String("trash")) { QString path = url.path(); // HACK (#98983): remove "0-foo". Note that it works better than // displaying KFileItem::name(), for files under a subdir. path.remove(QRegExp(QStringLiteral("^/[0-9]*-"))); prettyList.append(path); } else { prettyList.append(url.toDisplayString(QUrl::PreferLocalFile)); } } int result; QWidget *widget = window(); const KMessageBox::Options options(KMessageBox::Notify | KMessageBox::WindowModal); switch (deletionType) { case Delete: if (prettyList.count() == 1) { result = KMessageBox::warningContinueCancel( widget, xi18nc("@info", "Do you really want to permanently delete this item?%1This action cannot be undone.", prettyList.first()), QString(), KStandardGuiItem::del(), KStandardGuiItem::cancel(), keyName, options); } else { result = KMessageBox::warningContinueCancelList( widget, xi18ncp("@info", "Do you really want to permanently delete this item?This action cannot be undone.", "Do you really want to permanently delete these %1 items?This action cannot be undone.", prettyList.count()), prettyList, i18n("Delete Files"), KStandardGuiItem::del(), KStandardGuiItem::cancel(), keyName, options); } break; case EmptyTrash: result = KMessageBox::warningContinueCancel( widget, xi18nc("@info", "Do you want to permanently delete all items from the Trash?This action cannot be undone."), QString(), KGuiItem(i18nc("@action:button", "Empty Trash"), QIcon::fromTheme(QStringLiteral("user-trash"))), KStandardGuiItem::cancel(), keyName, options); break; case Trash: default: if (prettyList.count() == 1) { result = KMessageBox::warningContinueCancel( widget, xi18nc("@info", "Do you really want to move this item to the Trash?%1", prettyList.first()), QString(), KGuiItem(i18n("Move to Trash"), QStringLiteral("user-trash")), KStandardGuiItem::cancel(), keyName, options); } else { result = KMessageBox::warningContinueCancelList( widget, i18np("Do you really want to move this item to the trash?", "Do you really want to move these %1 items to the trash?", prettyList.count()), prettyList, i18n("Move to Trash"), KGuiItem(i18n("Move to Trash"), QStringLiteral("user-trash")), KStandardGuiItem::cancel(), keyName, options); } } if (!keyName.isEmpty()) { // Check kmessagebox setting... erase & copy to konquerorrc. KSharedConfig::Ptr config = KSharedConfig::openConfig(); KConfigGroup notificationGroup(config, "Notification Messages"); if (!notificationGroup.readEntry(keyName, true)) { notificationGroup.writeEntry(keyName, true); notificationGroup.sync(); KSharedConfigPtr kioConfig = KSharedConfig::openConfig(QStringLiteral("kiorc"), KConfig::NoGlobals); kioConfig->group("Confirmations").writeEntry(keyName, false); } } return (result == KMessageBox::Continue); } return true; } int KIO::JobUiDelegate::requestMessageBox(KIO::JobUiDelegate::MessageBoxType type, const QString &text, const QString &caption, const QString &buttonYes, const QString &buttonNo, const QString &iconYes, const QString &iconNo, const QString &dontAskAgainName, const KIO::MetaData &sslMetaData) { int result = -1; //qDebug() << type << text << "caption=" << caption; KConfig config(QStringLiteral("kioslaverc")); KMessageBox::setDontShowAgainConfig(&config); const KGuiItem buttonYesGui(buttonYes, iconYes); const KGuiItem buttonNoGui(buttonNo, iconNo); KMessageBox::Options options(KMessageBox::Notify | KMessageBox::WindowModal); switch (type) { case QuestionYesNo: result = KMessageBox::questionYesNo( window(), text, caption, buttonYesGui, buttonNoGui, dontAskAgainName, options); break; case WarningYesNo: result = KMessageBox::warningYesNo( window(), text, caption, buttonYesGui, buttonNoGui, dontAskAgainName, options | KMessageBox::Dangerous); break; case WarningYesNoCancel: result = KMessageBox::warningYesNoCancel( window(), text, caption, buttonYesGui, buttonNoGui, KStandardGuiItem::cancel(), dontAskAgainName, options); break; case WarningContinueCancel: result = KMessageBox::warningContinueCancel( window(), text, caption, buttonYesGui, KStandardGuiItem::cancel(), dontAskAgainName, options); break; case Information: KMessageBox::information(window(), text, caption, dontAskAgainName, options); result = 1; // whatever break; case SSLMessageBox: { QPointer kid(new KSslInfoDialog(window())); //### this is boilerplate code and appears in khtml_part.cpp almost unchanged! const QStringList sl = sslMetaData.value(QStringLiteral("ssl_peer_chain")).split(QLatin1Char('\x01'), QString::SkipEmptyParts); QList certChain; bool decodedOk = true; foreach (const QString &s, sl) { certChain.append(QSslCertificate(s.toLatin1())); //or is it toLocal8Bit or whatever? if (certChain.last().isNull()) { decodedOk = false; break; } } if (decodedOk) { result = 1; // whatever kid->setSslInfo(certChain, sslMetaData.value(QStringLiteral("ssl_peer_ip")), text, // the URL sslMetaData.value(QStringLiteral("ssl_protocol_version")), sslMetaData.value(QStringLiteral("ssl_cipher")), sslMetaData.value(QStringLiteral("ssl_cipher_used_bits")).toInt(), sslMetaData.value(QStringLiteral("ssl_cipher_bits")).toInt(), KSslInfoDialog::errorsFromString(sslMetaData.value(QStringLiteral("ssl_cert_errors")))); kid->exec(); } else { result = -1; KMessageBox::information(window(), i18n("The peer SSL certificate chain appears to be corrupt."), i18n("SSL"), QString(), options); } // KSslInfoDialog deletes itself (Qt::WA_DeleteOnClose). delete kid; break; } default: qCWarning(KIO_WIDGETS) << "Unknown type" << type; result = 0; break; } KMessageBox::setDontShowAgainConfig(nullptr); return result; } KIO::ClipboardUpdater *KIO::JobUiDelegate::createClipboardUpdater(Job *job, ClipboardUpdaterMode mode) { if (qobject_cast(qApp)) { return new KIO::ClipboardUpdater(job, mode); } return nullptr; } void KIO::JobUiDelegate::updateUrlInClipboard(const QUrl &src, const QUrl &dest) { if (qobject_cast(qApp)) { KIO::ClipboardUpdater::update(src, dest); } } class KIOWidgetJobUiDelegateFactory : public KIO::JobUiDelegateFactory { public: KJobUiDelegate *createDelegate() const override { return new KIO::JobUiDelegate; } }; Q_GLOBAL_STATIC(KIOWidgetJobUiDelegateFactory, globalUiDelegateFactory) Q_GLOBAL_STATIC(KIO::JobUiDelegate, globalUiDelegate) // Simply linking to this library, creates a GUI job delegate and delegate extension for all KIO jobs static void registerJobUiDelegate() { KIO::setDefaultJobUiDelegateFactory(globalUiDelegateFactory()); KIO::setDefaultJobUiDelegateExtension(globalUiDelegate()); } Q_CONSTRUCTOR_FUNCTION(registerJobUiDelegate) #include "jobuidelegate.moc" diff --git a/src/widgets/joburlcache.cpp b/src/widgets/joburlcache.cpp index 2929f869..4510e69c 100644 --- a/src/widgets/joburlcache.cpp +++ b/src/widgets/joburlcache.cpp @@ -1,64 +1,64 @@ /***************************************************************************** * This file is part of the KDE libraries * * Copyright (C) 2009 by Shaun Reich * * * * This library is free software; you can redistribute it and/or modify * * it under the terms of the GNU Lesser General Public License as published * * by the Free Software Foundation; either version 2 of the License or (at * * your option) any later version. * * * * This library 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 * * Library General Public License for more details. * * * * You should have received a copy of the GNU Lesser General Public License * * along with this library; see the file COPYING.LIB. * * If not, see . * *****************************************************************************/ #include "joburlcache_p.h" #include "kuiserver_interface.h" class JobUrlCacheSingleton { public: JobUrlCache instance; }; Q_GLOBAL_STATIC(JobUrlCacheSingleton, s_jobUrlCache) JobUrlCache &JobUrlCache::instance() { return s_jobUrlCache()->instance; } JobUrlCache::JobUrlCache() : QObject(nullptr) { org::kde::kuiserver *interface = new org::kde::kuiserver(QStringLiteral("org.kde.kuiserver"), QStringLiteral("/JobViewServer"), QDBusConnection::sessionBus(), this); //connect to receive updates about the job urls - connect(interface, SIGNAL(jobUrlsChanged(QStringList)), - this, SLOT(slotJobUrlsChanged(QStringList))); + connect(interface, &OrgKdeKuiserverInterface::jobUrlsChanged, + this, &JobUrlCache::slotJobUrlsChanged); //force signal emission interface->emitJobUrlsChanged(); } JobUrlCache::~JobUrlCache() { } void JobUrlCache::slotJobUrlsChanged(const QStringList &urlList) { m_destUrls = urlList; emit jobUrlsChanged(urlList); } void JobUrlCache::requestJobUrlsChanged() { emit jobUrlsChanged(m_destUrls); } #include "moc_joburlcache_p.cpp" diff --git a/src/widgets/kacleditwidget.cpp b/src/widgets/kacleditwidget.cpp index c28f3c49..67f7a7c0 100644 --- a/src/widgets/kacleditwidget.cpp +++ b/src/widgets/kacleditwidget.cpp @@ -1,1190 +1,1190 @@ /*************************************************************************** * Copyright (C) 2005 by Sean Harmer * * 2005 - 2007 Till Adam * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU Library General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * This program 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 this program; if not, write to the * * Free Software Foundation, Inc., * * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * ***************************************************************************/ #include "kacleditwidget.h" #include "kacleditwidget_p.h" #include "kio_widgets_debug.h" #if HAVE_POSIX_ACL #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #if HAVE_ACL_LIBACL_H # include #endif extern "C" { #include #include } #include static struct { const char *label; const char *pixmapName; QPixmap *pixmap; } s_itemAttributes[] = { { I18N_NOOP("Owner"), "user-grey", nullptr }, { I18N_NOOP("Owning Group"), "group-grey", nullptr }, { I18N_NOOP("Others"), "others-grey", nullptr }, { I18N_NOOP("Mask"), "mask", nullptr }, { I18N_NOOP("Named User"), "user", nullptr }, { I18N_NOOP("Named Group"), "group", nullptr }, }; class KACLEditWidget::KACLEditWidgetPrivate { public: KACLEditWidgetPrivate() { } // slots void _k_slotUpdateButtons(); KACLListView *m_listView; QPushButton *m_AddBtn; QPushButton *m_EditBtn; QPushButton *m_DelBtn; }; KACLEditWidget::KACLEditWidget(QWidget *parent) : QWidget(parent), d(new KACLEditWidgetPrivate) { QHBoxLayout *hbox = new QHBoxLayout(this); hbox->setMargin(0); d->m_listView = new KACLListView(this); hbox->addWidget(d->m_listView); connect(d->m_listView->selectionModel(), SIGNAL(selectionChanged(QItemSelection,QItemSelection)), this, SLOT(_k_slotUpdateButtons())); QVBoxLayout *vbox = new QVBoxLayout(); hbox->addLayout(vbox); d->m_AddBtn = new QPushButton(i18n("Add Entry..."), this); vbox->addWidget(d->m_AddBtn); d->m_AddBtn->setObjectName(QStringLiteral("add_entry_button")); - connect(d->m_AddBtn, SIGNAL(clicked()), d->m_listView, SLOT(slotAddEntry())); + connect(d->m_AddBtn, &QAbstractButton::clicked, d->m_listView, &KACLListView::slotAddEntry); d->m_EditBtn = new QPushButton(i18n("Edit Entry..."), this); vbox->addWidget(d->m_EditBtn); d->m_EditBtn->setObjectName(QStringLiteral("edit_entry_button")); - connect(d->m_EditBtn, SIGNAL(clicked()), d->m_listView, SLOT(slotEditEntry())); + connect(d->m_EditBtn, &QAbstractButton::clicked, d->m_listView, &KACLListView::slotEditEntry); d->m_DelBtn = new QPushButton(i18n("Delete Entry"), this); vbox->addWidget(d->m_DelBtn); d->m_DelBtn->setObjectName(QStringLiteral("delete_entry_button")); - connect(d->m_DelBtn, SIGNAL(clicked()), d->m_listView, SLOT(slotRemoveEntry())); + connect(d->m_DelBtn, &QAbstractButton::clicked, d->m_listView, &KACLListView::slotRemoveEntry); vbox->addItem(new QSpacerItem(10, 10, QSizePolicy::Fixed, QSizePolicy::Expanding)); d->_k_slotUpdateButtons(); } KACLEditWidget::~KACLEditWidget() { delete d; } void KACLEditWidget::KACLEditWidgetPrivate::_k_slotUpdateButtons() { bool atLeastOneIsNotDeletable = false; bool atLeastOneIsNotAllowedToChangeType = false; int selectedCount = 0; QList selected = m_listView->selectedItems(); QListIterator it(selected); while (it.hasNext()) { KACLListViewItem *item = static_cast(it.next()); ++selectedCount; if (!item->isDeletable()) { atLeastOneIsNotDeletable = true; } if (!item->isAllowedToChangeType()) { atLeastOneIsNotAllowedToChangeType = true; } } m_EditBtn->setEnabled(selectedCount && !atLeastOneIsNotAllowedToChangeType); m_DelBtn->setEnabled(selectedCount && !atLeastOneIsNotDeletable); } KACL KACLEditWidget::getACL() const { return d->m_listView->getACL(); } KACL KACLEditWidget::getDefaultACL() const { return d->m_listView->getDefaultACL(); } void KACLEditWidget::setACL(const KACL &acl) { d->m_listView->setACL(acl); } void KACLEditWidget::setDefaultACL(const KACL &acl) { d->m_listView->setDefaultACL(acl); } void KACLEditWidget::setAllowDefaults(bool value) { d->m_listView->setAllowDefaults(value); } KACLListViewItem::KACLListViewItem(QTreeWidget *parent, KACLListView::EntryType _type, unsigned short _value, bool defaults, const QString &_qualifier) : QTreeWidgetItem(parent), type(_type), value(_value), isDefault(defaults), qualifier(_qualifier), isPartial(false) { m_pACLListView = qobject_cast(parent); repaint(); } KACLListViewItem::~ KACLListViewItem() { } QString KACLListViewItem::key() const { QString key; if (!isDefault) { key = QLatin1Char('A'); } else { key = QLatin1Char('B'); } switch (type) { case KACLListView::User: key += QLatin1Char('A'); break; case KACLListView::Group: key += QLatin1Char('B'); break; case KACLListView::Others: key += QLatin1Char('C'); break; case KACLListView::Mask: key += QLatin1Char('D'); break; case KACLListView::NamedUser: key += QLatin1Char('E') + text(1); break; case KACLListView::NamedGroup: key += QLatin1Char('F') + text(1); break; default: key += text(0); break; } return key; } bool KACLListViewItem::operator< (const QTreeWidgetItem &other) const { return key() < static_cast(other).key(); } #if 0 void KACLListViewItem::paintCell(QPainter *p, const QColorGroup &cg, int column, int width, int alignment) { if (isDefault) { setForeground(QColor(0, 0, 255)); } if (isPartial) { QFont font = p->font(); font.setItalic(true); setForeground(QColor(100, 100, 100)); p->setFont(font); } QTreeWidgetItem::paintCell(p, mycg, column, width, alignment); KACLListViewItem *below = 0; if (itemBelow()) { below = static_cast(itemBelow()); } const bool lastUser = type == KACLListView::NamedUser && below && below->type == KACLListView::NamedGroup; const bool lastNonDefault = !isDefault && below && below->isDefault; if (type == KACLListView::Mask || lastUser || lastNonDefault) { p->setPen(QPen(Qt::gray, 0, Qt::DotLine)); if (type == KACLListView::Mask) { p->drawLine(0, 0, width - 1, 0); } p->drawLine(0, height() - 1, width - 1, height() - 1); } } #endif void KACLListViewItem::updatePermPixmaps() { unsigned int partialPerms = value; if (value & ACL_READ) { setIcon(2, m_pACLListView->getYesPixmap()); } else if (partialPerms & ACL_READ) { setIcon(2, m_pACLListView->getYesPartialPixmap()); } else { setIcon(2, QIcon()); } if (value & ACL_WRITE) { setIcon(3, m_pACLListView->getYesPixmap()); } else if (partialPerms & ACL_WRITE) { setIcon(3, m_pACLListView->getYesPartialPixmap()); } else { setIcon(3, QIcon()); } if (value & ACL_EXECUTE) { setIcon(4, m_pACLListView->getYesPixmap()); } else if (partialPerms & ACL_EXECUTE) { setIcon(4, m_pACLListView->getYesPartialPixmap()); } else { setIcon(4, QIcon()); } } void KACLListViewItem::repaint() { int idx = 0; switch (type) { case KACLListView::User: idx = KACLListView::OWNER_IDX; break; case KACLListView::Group: idx = KACLListView::GROUP_IDX; break; case KACLListView::Others: idx = KACLListView::OTHERS_IDX; break; case KACLListView::Mask: idx = KACLListView::MASK_IDX; break; case KACLListView::NamedUser: idx = KACLListView::NAMED_USER_IDX; break; case KACLListView::NamedGroup: idx = KACLListView::NAMED_GROUP_IDX; break; default: idx = KACLListView::OWNER_IDX; break; } setText(0, i18n(s_itemAttributes[idx].label)); setIcon(0, *s_itemAttributes[idx].pixmap); if (isDefault) { setText(0, text(0) + i18n(" (Default)")); } setText(1, qualifier); // Set the pixmaps for which of the perms are set updatePermPixmaps(); } void KACLListViewItem::calcEffectiveRights() { QString strEffective = QStringLiteral("---"); // Do we need to worry about the mask entry? It applies to named users, // owning group, and named groups if (m_pACLListView->hasMaskEntry() && (type == KACLListView::NamedUser || type == KACLListView::Group || type == KACLListView::NamedGroup) && !isDefault) { strEffective[0] = (m_pACLListView->maskPermissions() & value & ACL_READ) ? 'r' : '-'; strEffective[1] = (m_pACLListView->maskPermissions() & value & ACL_WRITE) ? 'w' : '-'; strEffective[2] = (m_pACLListView->maskPermissions() & value & ACL_EXECUTE) ? 'x' : '-'; /* // What about any partial perms? if ( maskPerms & partialPerms & ACL_READ || // Partial perms on entry maskPartialPerms & perms & ACL_READ || // Partial perms on mask maskPartialPerms & partialPerms & ACL_READ ) // Partial perms on mask and entry strEffective[0] = 'R'; if ( maskPerms & partialPerms & ACL_WRITE || // Partial perms on entry maskPartialPerms & perms & ACL_WRITE || // Partial perms on mask maskPartialPerms & partialPerms & ACL_WRITE ) // Partial perms on mask and entry strEffective[1] = 'W'; if ( maskPerms & partialPerms & ACL_EXECUTE || // Partial perms on entry maskPartialPerms & perms & ACL_EXECUTE || // Partial perms on mask maskPartialPerms & partialPerms & ACL_EXECUTE ) // Partial perms on mask and entry strEffective[2] = 'X'; */ } else { // No, the effective value are just the value in this entry strEffective[0] = (value & ACL_READ) ? 'r' : '-'; strEffective[1] = (value & ACL_WRITE) ? 'w' : '-'; strEffective[2] = (value & ACL_EXECUTE) ? 'x' : '-'; /* // What about any partial perms? if ( partialPerms & ACL_READ ) strEffective[0] = 'R'; if ( partialPerms & ACL_WRITE ) strEffective[1] = 'W'; if ( partialPerms & ACL_EXECUTE ) strEffective[2] = 'X'; */ } setText(5, strEffective); } bool KACLListViewItem::isDeletable() const { bool isMaskAndDeletable = false; if (type == KACLListView::Mask) { if (!isDefault && m_pACLListView->maskCanBeDeleted()) { isMaskAndDeletable = true; } else if (isDefault && m_pACLListView->defaultMaskCanBeDeleted()) { isMaskAndDeletable = true; } } return type != KACLListView::User && type != KACLListView::Group && type != KACLListView::Others && (type != KACLListView::Mask || isMaskAndDeletable); } bool KACLListViewItem::isAllowedToChangeType() const { return type != KACLListView::User && type != KACLListView::Group && type != KACLListView::Others && type != KACLListView::Mask; } void KACLListViewItem::togglePerm(acl_perm_t perm) { value ^= perm; // Toggle the perm if (type == KACLListView::Mask && !isDefault) { m_pACLListView->setMaskPermissions(value); } calcEffectiveRights(); updatePermPixmaps(); /* // If the perm is in the partial perms then remove it. i.e. Once // a user changes a partial perm it then applies to all selected files. if ( m_pEntry->m_partialPerms & perm ) m_pEntry->m_partialPerms ^= perm; m_pEntry->setPartialEntry( false ); // Make sure that all entries have their effective rights calculated if // we are changing the ACL_MASK entry. if ( type == Mask ) { m_pACLListView->setMaskPartialPermissions( m_pEntry->m_partialPerms ); m_pACLListView->setMaskPermissions( value ); m_pACLListView->calculateEffectiveRights(); } */ } EditACLEntryDialog::EditACLEntryDialog(KACLListView *listView, KACLListViewItem *item, const QStringList &users, const QStringList &groups, const QStringList &defaultUsers, const QStringList &defaultGroups, int allowedTypes, int allowedDefaultTypes, bool allowDefaults) : QDialog(listView), m_listView(listView), m_item(item), m_users(users), m_groups(groups), m_defaultUsers(defaultUsers), m_defaultGroups(defaultGroups), m_allowedTypes(allowedTypes), m_allowedDefaultTypes(allowedDefaultTypes), m_defaultCB(nullptr) { setObjectName(QStringLiteral("edit_entry_dialog")); setModal(true); setWindowTitle(i18n("Edit ACL Entry")); QVBoxLayout *mainLayout = new QVBoxLayout; setLayout(mainLayout); QGroupBox *gb = new QGroupBox(i18n("Entry Type"), this); QVBoxLayout *gbLayout = new QVBoxLayout(gb); m_buttonGroup = new QButtonGroup(this); if (allowDefaults) { m_defaultCB = new QCheckBox(i18n("Default for new files in this folder"), this); m_defaultCB->setObjectName(QStringLiteral("defaultCB")); mainLayout->addWidget(m_defaultCB); - connect(m_defaultCB, SIGNAL(toggled(bool)), - this, SLOT(slotUpdateAllowedUsersAndGroups())); - connect(m_defaultCB, SIGNAL(toggled(bool)), - this, SLOT(slotUpdateAllowedTypes())); + connect(m_defaultCB, &QAbstractButton::toggled, + this, &EditACLEntryDialog::slotUpdateAllowedUsersAndGroups); + connect(m_defaultCB, &QAbstractButton::toggled, + this, &EditACLEntryDialog::slotUpdateAllowedTypes); } QRadioButton *ownerType = new QRadioButton(i18n("Owner"), gb); ownerType->setObjectName(QStringLiteral("ownerType")); gbLayout->addWidget(ownerType); m_buttonGroup->addButton(ownerType); m_buttonIds.insert(ownerType, KACLListView::User); QRadioButton *owningGroupType = new QRadioButton(i18n("Owning Group"), gb); owningGroupType->setObjectName(QStringLiteral("owningGroupType")); gbLayout->addWidget(owningGroupType); m_buttonGroup->addButton(owningGroupType); m_buttonIds.insert(owningGroupType, KACLListView::Group); QRadioButton *othersType = new QRadioButton(i18n("Others"), gb); othersType->setObjectName(QStringLiteral("othersType")); gbLayout->addWidget(othersType); m_buttonGroup->addButton(othersType); m_buttonIds.insert(othersType, KACLListView::Others); QRadioButton *maskType = new QRadioButton(i18n("Mask"), gb); maskType->setObjectName(QStringLiteral("maskType")); gbLayout->addWidget(maskType); m_buttonGroup->addButton(maskType); m_buttonIds.insert(maskType, KACLListView::Mask); QRadioButton *namedUserType = new QRadioButton(i18n("Named user"), gb); namedUserType->setObjectName(QStringLiteral("namesUserType")); gbLayout->addWidget(namedUserType); m_buttonGroup->addButton(namedUserType); m_buttonIds.insert(namedUserType, KACLListView::NamedUser); QRadioButton *namedGroupType = new QRadioButton(i18n("Named group"), gb); namedGroupType->setObjectName(QStringLiteral("namedGroupType")); gbLayout->addWidget(namedGroupType); m_buttonGroup->addButton(namedGroupType); m_buttonIds.insert(namedGroupType, KACLListView::NamedGroup); mainLayout->addWidget(gb); - connect(m_buttonGroup, SIGNAL(buttonClicked(QAbstractButton*)), - this, SLOT(slotSelectionChanged(QAbstractButton*))); + connect(m_buttonGroup, QOverload::of(&QButtonGroup::buttonClicked), + this, &EditACLEntryDialog::slotSelectionChanged); m_widgetStack = new QStackedWidget(this); mainLayout->addWidget(m_widgetStack); // users box QWidget *usersBox = new QWidget(m_widgetStack); QHBoxLayout *usersLayout = new QHBoxLayout(usersBox); usersBox->setLayout(usersLayout); m_widgetStack->addWidget(usersBox); QLabel *usersLabel = new QLabel(i18n("User: "), usersBox); m_usersCombo = new KComboBox(usersBox); m_usersCombo->setEditable(false); m_usersCombo->setObjectName(QStringLiteral("users")); usersLabel->setBuddy(m_usersCombo); usersLayout->addWidget(usersLabel); usersLayout->addWidget(m_usersCombo); // groups box QWidget *groupsBox = new QWidget(m_widgetStack); QHBoxLayout *groupsLayout = new QHBoxLayout(usersBox); groupsBox->setLayout(groupsLayout); m_widgetStack->addWidget(groupsBox); QLabel *groupsLabel = new QLabel(i18n("Group: "), groupsBox); m_groupsCombo = new KComboBox(groupsBox); m_groupsCombo->setEditable(false); m_groupsCombo->setObjectName(QStringLiteral("groups")); groupsLabel->setBuddy(m_groupsCombo); groupsLayout->addWidget(groupsLabel); groupsLayout->addWidget(m_groupsCombo); if (m_item) { m_buttonIds.key(m_item->type)->setChecked(true); if (m_defaultCB) { m_defaultCB->setChecked(m_item->isDefault); } slotUpdateAllowedTypes(); slotSelectionChanged(m_buttonIds.key(m_item->type)); slotUpdateAllowedUsersAndGroups(); if (m_item->type == KACLListView::NamedUser) { m_usersCombo->setItemText(m_usersCombo->currentIndex(), m_item->qualifier); } else if (m_item->type == KACLListView::NamedGroup) { m_groupsCombo->setItemText(m_groupsCombo->currentIndex(), m_item->qualifier); } } else { // new entry, preselect "named user", arguably the most common one m_buttonIds.key(KACLListView::NamedUser)->setChecked(true); slotUpdateAllowedTypes(); slotSelectionChanged(m_buttonIds.key(KACLListView::NamedUser)); slotUpdateAllowedUsersAndGroups(); } QDialogButtonBox *buttonBox = new QDialogButtonBox(this); buttonBox->setStandardButtons(QDialogButtonBox::Ok | QDialogButtonBox::Cancel); - connect(buttonBox, SIGNAL(accepted()), this, SLOT(slotOk())); - connect(buttonBox, SIGNAL(rejected()), this, SLOT(reject())); + connect(buttonBox, &QDialogButtonBox::accepted, this, &EditACLEntryDialog::slotOk); + connect(buttonBox, &QDialogButtonBox::rejected, this, &QDialog::reject); mainLayout->addWidget(buttonBox); adjustSize(); } void EditACLEntryDialog::slotUpdateAllowedTypes() { int allowedTypes = m_allowedTypes; if (m_defaultCB && m_defaultCB->isChecked()) { allowedTypes = m_allowedDefaultTypes; } for (int i = 1; i < KACLListView::AllTypes; i = i * 2) { if (allowedTypes & i) { m_buttonIds.key(i)->show(); } else { m_buttonIds.key(i)->hide(); } } } void EditACLEntryDialog::slotUpdateAllowedUsersAndGroups() { const QString oldUser = m_usersCombo->currentText(); const QString oldGroup = m_groupsCombo->currentText(); m_usersCombo->clear(); m_groupsCombo->clear(); if (m_defaultCB && m_defaultCB->isChecked()) { m_usersCombo->addItems(m_defaultUsers); if (m_defaultUsers.contains(oldUser)) { m_usersCombo->setItemText(m_usersCombo->currentIndex(), oldUser); } m_groupsCombo->addItems(m_defaultGroups); if (m_defaultGroups.contains(oldGroup)) { m_groupsCombo->setItemText(m_groupsCombo->currentIndex(), oldGroup); } } else { m_usersCombo->addItems(m_users); if (m_users.contains(oldUser)) { m_usersCombo->setItemText(m_usersCombo->currentIndex(), oldUser); } m_groupsCombo->addItems(m_groups); if (m_groups.contains(oldGroup)) { m_groupsCombo->setItemText(m_groupsCombo->currentIndex(), oldGroup); } } } void EditACLEntryDialog::slotOk() { KACLListView::EntryType type = static_cast(m_buttonIds[m_buttonGroup->checkedButton()]); qCWarning(KIO_WIDGETS) << "Type 2: " << type; QString qualifier; if (type == KACLListView::NamedUser) { qualifier = m_usersCombo->currentText(); } if (type == KACLListView::NamedGroup) { qualifier = m_groupsCombo->currentText(); } if (!m_item) { m_item = new KACLListViewItem(m_listView, type, ACL_READ | ACL_WRITE | ACL_EXECUTE, false, qualifier); } else { m_item->type = type; m_item->qualifier = qualifier; } if (m_defaultCB) { m_item->isDefault = m_defaultCB->isChecked(); } m_item->repaint(); QDialog::accept(); } void EditACLEntryDialog::slotSelectionChanged(QAbstractButton *button) { switch (m_buttonIds[ button ]) { case KACLListView::User: case KACLListView::Group: case KACLListView::Others: case KACLListView::Mask: m_widgetStack->setEnabled(false); break; case KACLListView::NamedUser: m_widgetStack->setEnabled(true); m_widgetStack->setCurrentIndex(0 /* User */); break; case KACLListView::NamedGroup: m_widgetStack->setEnabled(true); m_widgetStack->setCurrentIndex(1 /* Group */); break; default: break; } } KACLListView::KACLListView(QWidget *parent) : QTreeWidget(parent), m_hasMask(false), m_allowDefaults(false) { // Add the columns setColumnCount(6); QStringList headers; headers << i18n("Type"); headers << i18n("Name"); headers << i18nc("read permission", "r"); headers << i18nc("write permission", "w"); headers << i18nc("execute permission", "x"); headers << i18n("Effective"); setHeaderLabels(headers); setSortingEnabled(false); setSelectionMode(QAbstractItemView::ExtendedSelection); header()->setSectionResizeMode(QHeaderView::ResizeToContents); setRootIsDecorated(false); // Load the avatars for (int i = 0; i < LAST_IDX; ++i) { s_itemAttributes[i].pixmap = new QPixmap(QLatin1String(":/images/") + QLatin1String(s_itemAttributes[i].pixmapName)); } m_yesPixmap = new QPixmap(QStringLiteral(":/images/yes.png")); m_yesPartialPixmap = new QPixmap(QStringLiteral(":/images/yespartial.png")); // fill the lists of all legal users and groups struct passwd *user = nullptr; setpwent(); while ((user = getpwent()) != nullptr) { m_allUsers << QString::fromLatin1(user->pw_name); } endpwent(); struct group *gr = nullptr; setgrent(); while ((gr = getgrent()) != nullptr) { m_allGroups << QString::fromLatin1(gr->gr_name); } endgrent(); m_allUsers.sort(); m_allGroups.sort(); - connect(this, SIGNAL(itemClicked(QTreeWidgetItem*,int)), - this, SLOT(slotItemClicked(QTreeWidgetItem*,int))); + connect(this, &QTreeWidget::itemClicked, + this, &KACLListView::slotItemClicked); connect(this, &KACLListView::itemDoubleClicked, this, &KACLListView::slotItemDoubleClicked); } KACLListView::~KACLListView() { for (int i = 0; i < LAST_IDX; ++i) { delete s_itemAttributes[i].pixmap; } delete m_yesPixmap; delete m_yesPartialPixmap; } QStringList KACLListView::allowedUsers(bool defaults, KACLListViewItem *allowedItem) { QStringList allowedUsers = m_allUsers; QTreeWidgetItemIterator it(this); while (*it) { const KACLListViewItem *item = static_cast(*it); ++it; if (item->type != NamedUser || item->isDefault != defaults) { continue; } if (allowedItem && item == allowedItem && allowedItem->isDefault == defaults) { continue; } allowedUsers.removeAll(item->qualifier); } return allowedUsers; } QStringList KACLListView::allowedGroups(bool defaults, KACLListViewItem *allowedItem) { QStringList allowedGroups = m_allGroups; QTreeWidgetItemIterator it(this); while (*it) { const KACLListViewItem *item = static_cast(*it); ++it; if (item->type != NamedGroup || item->isDefault != defaults) { continue; } if (allowedItem && item == allowedItem && allowedItem->isDefault == defaults) { continue; } allowedGroups.removeAll(item->qualifier); } return allowedGroups; } void KACLListView::fillItemsFromACL(const KACL &pACL, bool defaults) { // clear out old entries of that ilk QTreeWidgetItemIterator it(this); while (KACLListViewItem *item = static_cast(*it)) { ++it; if (item->isDefault == defaults) { delete item; } } new KACLListViewItem(this, User, pACL.ownerPermissions(), defaults); new KACLListViewItem(this, Group, pACL.owningGroupPermissions(), defaults); new KACLListViewItem(this, Others, pACL.othersPermissions(), defaults); bool hasMask = false; unsigned short mask = pACL.maskPermissions(hasMask); if (hasMask) { new KACLListViewItem(this, Mask, mask, defaults); } // read all named user entries const ACLUserPermissionsList &userList = pACL.allUserPermissions(); ACLUserPermissionsConstIterator itu = userList.begin(); while (itu != userList.end()) { new KACLListViewItem(this, NamedUser, (*itu).second, defaults, (*itu).first); ++itu; } // and now all named groups const ACLUserPermissionsList &groupList = pACL.allGroupPermissions(); ACLUserPermissionsConstIterator itg = groupList.begin(); while (itg != groupList.end()) { new KACLListViewItem(this, NamedGroup, (*itg).second, defaults, (*itg).first); ++itg; } } void KACLListView::setACL(const KACL &acl) { if (!acl.isValid()) { return; } // Remove any entries left over from displaying a previous ACL m_ACL = acl; fillItemsFromACL(m_ACL); m_mask = acl.maskPermissions(m_hasMask); calculateEffectiveRights(); } void KACLListView::setDefaultACL(const KACL &acl) { if (!acl.isValid()) { return; } m_defaultACL = acl; fillItemsFromACL(m_defaultACL, true); calculateEffectiveRights(); } KACL KACLListView::itemsToACL(bool defaults) const { KACL newACL(0); bool atLeastOneEntry = false; ACLUserPermissionsList users; ACLGroupPermissionsList groups; QTreeWidgetItemIterator it(const_cast(this)); while (QTreeWidgetItem *qlvi = *it) { ++it; const KACLListViewItem *item = static_cast(qlvi); if (item->isDefault != defaults) { continue; } atLeastOneEntry = true; switch (item->type) { case User: newACL.setOwnerPermissions(item->value); break; case Group: newACL.setOwningGroupPermissions(item->value); break; case Others: newACL.setOthersPermissions(item->value); break; case Mask: newACL.setMaskPermissions(item->value); break; case NamedUser: users.append(qMakePair(item->text(1), item->value)); break; case NamedGroup: groups.append(qMakePair(item->text(1), item->value)); break; default: break; } } if (atLeastOneEntry) { newACL.setAllUserPermissions(users); newACL.setAllGroupPermissions(groups); if (newACL.isValid()) { return newACL; } } return KACL(); } KACL KACLListView::getACL() { return itemsToACL(false); } KACL KACLListView::getDefaultACL() { return itemsToACL(true); } void KACLListView::contentsMousePressEvent(QMouseEvent * /*e*/) { /* QTreeWidgetItem *clickedItem = itemAt( e->pos() ); if ( !clickedItem ) return; // if the click is on an as yet unselected item, select it first if ( !clickedItem->isSelected() ) QAbstractItemView::contentsMousePressEvent( e ); if ( !currentItem() ) return; int column = header()->sectionAt( e->x() ); acl_perm_t perm; switch ( column ) { case 2: perm = ACL_READ; break; case 3: perm = ACL_WRITE; break; case 4: perm = ACL_EXECUTE; break; default: return QTreeWidget::contentsMousePressEvent( e ); } KACLListViewItem* referenceItem = static_cast( clickedItem ); unsigned short referenceHadItSet = referenceItem->value & perm; QTreeWidgetItemIterator it( this ); while ( KACLListViewItem* item = static_cast( *it ) ) { ++it; if ( !item->isSelected() ) continue; // toggle those with the same value as the clicked item, leave the others if ( referenceHadItSet == ( item->value & perm ) ) item->togglePerm( perm ); } */ } void KACLListView::slotItemClicked(QTreeWidgetItem *pItem, int col) { if (!pItem) { return; } QTreeWidgetItemIterator it(this); while (KACLListViewItem *item = static_cast(*it)) { ++it; if (!item->isSelected()) { continue; } switch (col) { case 2: item->togglePerm(ACL_READ); break; case 3: item->togglePerm(ACL_WRITE); break; case 4: item->togglePerm(ACL_EXECUTE); break; default: ; // Do nothing } } /* // Has the user changed one of the required entries in a default ACL? if ( m_pACL->aclType() == ACL_TYPE_DEFAULT && ( col == 2 || col == 3 || col == 4 ) && ( pACLItem->entryType() == ACL_USER_OBJ || pACLItem->entryType() == ACL_GROUP_OBJ || pACLItem->entryType() == ACL_OTHER ) ) { // Mark the required entries as no longer being partial entries. // That is, they will get applied to all selected directories. KACLListViewItem* pUserObj = findACLEntryByType( this, ACL_USER_OBJ ); pUserObj->entry()->setPartialEntry( false ); KACLListViewItem* pGroupObj = findACLEntryByType( this, ACL_GROUP_OBJ ); pGroupObj->entry()->setPartialEntry( false ); KACLListViewItem* pOther = findACLEntryByType( this, ACL_OTHER ); pOther->entry()->setPartialEntry( false ); update(); } */ } void KACLListView::slotItemDoubleClicked(QTreeWidgetItem *item, int column) { if (!item) { return; } // avoid conflict with clicking to toggle permission if (column >= 2 && column <= 4) { return; } KACLListViewItem *aclListItem = static_cast(item); if (!aclListItem->isAllowedToChangeType()) { return; } setCurrentItem(item); slotEditEntry(); } void KACLListView::calculateEffectiveRights() { QTreeWidgetItemIterator it(this); KACLListViewItem *pItem; while ((pItem = dynamic_cast(*it)) != nullptr) { ++it; pItem->calcEffectiveRights(); } } unsigned short KACLListView::maskPermissions() const { return m_mask; } void KACLListView::setMaskPermissions(unsigned short maskPerms) { m_mask = maskPerms; calculateEffectiveRights(); } acl_perm_t KACLListView::maskPartialPermissions() const { // return m_pMaskEntry->m_partialPerms; return 0; } void KACLListView::setMaskPartialPermissions(acl_perm_t /*maskPartialPerms*/) { //m_pMaskEntry->m_partialPerms = maskPartialPerms; calculateEffectiveRights(); } bool KACLListView::hasDefaultEntries() const { QTreeWidgetItemIterator it(const_cast(this)); while (*it) { const KACLListViewItem *item = static_cast(*it); ++it; if (item->isDefault) { return true; } } return false; } const KACLListViewItem *KACLListView::findDefaultItemByType(EntryType type) const { return findItemByType(type, true); } const KACLListViewItem *KACLListView::findItemByType(EntryType type, bool defaults) const { QTreeWidgetItemIterator it(const_cast(this)); while (*it) { const KACLListViewItem *item = static_cast(*it); ++it; if (item->isDefault == defaults && item->type == type) { return item; } } return nullptr; } unsigned short KACLListView::calculateMaskValue(bool defaults) const { // KACL auto-adds the relevant maks entries, so we can simply query bool dummy; return itemsToACL(defaults).maskPermissions(dummy); } void KACLListView::slotAddEntry() { int allowedTypes = NamedUser | NamedGroup; if (!m_hasMask) { allowedTypes |= Mask; } int allowedDefaultTypes = NamedUser | NamedGroup; if (!findDefaultItemByType(Mask)) { allowedDefaultTypes |= Mask; } if (!hasDefaultEntries()) { allowedDefaultTypes |= User | Group; } EditACLEntryDialog dlg(this, nullptr, allowedUsers(false), allowedGroups(false), allowedUsers(true), allowedGroups(true), allowedTypes, allowedDefaultTypes, m_allowDefaults); dlg.exec(); KACLListViewItem *item = dlg.item(); if (!item) { return; // canceled } if (item->type == Mask && !item->isDefault) { m_hasMask = true; m_mask = item->value; } if (item->isDefault && !hasDefaultEntries()) { // first default entry, fill in what is needed if (item->type != User) { unsigned short v = findDefaultItemByType(User)->value; new KACLListViewItem(this, User, v, true); } if (item->type != Group) { unsigned short v = findDefaultItemByType(Group)->value; new KACLListViewItem(this, Group, v, true); } if (item->type != Others) { unsigned short v = findDefaultItemByType(Others)->value; new KACLListViewItem(this, Others, v, true); } } const KACLListViewItem *defaultMaskItem = findDefaultItemByType(Mask); if (item->isDefault && !defaultMaskItem) { unsigned short v = calculateMaskValue(true); new KACLListViewItem(this, Mask, v, true); } if (!item->isDefault && !m_hasMask && (item->type == Group || item->type == NamedUser || item->type == NamedGroup)) { // auto-add a mask entry unsigned short v = calculateMaskValue(false); new KACLListViewItem(this, Mask, v, false); m_hasMask = true; m_mask = v; } calculateEffectiveRights(); sortItems(sortColumn(), Qt::AscendingOrder); setCurrentItem(item); // QTreeWidget doesn't seem to emit, in this case, and we need to update // the buttons... if (topLevelItemCount() == 1) { emit currentItemChanged(item, item); } } void KACLListView::slotEditEntry() { QTreeWidgetItem *current = currentItem(); if (!current) { return; } KACLListViewItem *item = static_cast(current); int allowedTypes = item->type | NamedUser | NamedGroup; bool itemWasMask = item->type == Mask; if (!m_hasMask || itemWasMask) { allowedTypes |= Mask; } int allowedDefaultTypes = item->type | NamedUser | NamedGroup; if (!findDefaultItemByType(Mask)) { allowedDefaultTypes |= Mask; } if (!hasDefaultEntries()) { allowedDefaultTypes |= User | Group; } EditACLEntryDialog dlg(this, item, allowedUsers(false, item), allowedGroups(false, item), allowedUsers(true, item), allowedGroups(true, item), allowedTypes, allowedDefaultTypes, m_allowDefaults); dlg.exec(); if (itemWasMask && item->type != Mask) { m_hasMask = false; m_mask = 0; } if (!itemWasMask && item->type == Mask) { m_mask = item->value; m_hasMask = true; } calculateEffectiveRights(); sortItems(sortColumn(), Qt::AscendingOrder); } void KACLListView::slotRemoveEntry() { QTreeWidgetItemIterator it(this, QTreeWidgetItemIterator::Selected); while (*it) { KACLListViewItem *item = static_cast(*it); ++it; /* First check if it's a mask entry and if so, make sure that there is * either no name user or group entry, which means the mask can be * removed, or don't remove it, but reset it. That is allowed. */ if (item->type == Mask) { bool itemWasDefault = item->isDefault; if (!itemWasDefault && maskCanBeDeleted()) { m_hasMask = false; m_mask = 0; delete item; } else if (itemWasDefault && defaultMaskCanBeDeleted()) { delete item; } else { item->value = 0; item->repaint(); } if (!itemWasDefault) { calculateEffectiveRights(); } } else { // for the base permissions, disable them, which is what libacl does if (!item->isDefault && (item->type == User || item->type == Group || item->type == Others)) { item->value = 0; item->repaint(); } else { delete item; } } } } bool KACLListView::maskCanBeDeleted() const { return !findItemByType(NamedUser) && !findItemByType(NamedGroup); } bool KACLListView::defaultMaskCanBeDeleted() const { return !findDefaultItemByType(NamedUser) && !findDefaultItemByType(NamedGroup); } #include "moc_kacleditwidget.cpp" #include "moc_kacleditwidget_p.cpp" #endif diff --git a/src/widgets/kbuildsycocaprogressdialog.cpp b/src/widgets/kbuildsycocaprogressdialog.cpp index 1d4bc203..a5df22ca 100644 --- a/src/widgets/kbuildsycocaprogressdialog.cpp +++ b/src/widgets/kbuildsycocaprogressdialog.cpp @@ -1,76 +1,77 @@ /* This file is part of the KDE project Copyright (C) 2003 Waldo Bastian This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License version 2 as published by the Free Software Foundation. This library 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 Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kbuildsycocaprogressdialog.h" #include #include #include #include #include #include class KBuildSycocaProgressDialogPrivate { public: explicit KBuildSycocaProgressDialogPrivate(KBuildSycocaProgressDialog *parent) : m_parent(parent) { } KBuildSycocaProgressDialog * const m_parent; }; void KBuildSycocaProgressDialog::rebuildKSycoca(QWidget *parent) { KBuildSycocaProgressDialog dlg(parent, i18n("Updating System Configuration"), i18n("Updating system configuration.")); // FIXME HACK: kdelibs 4 doesn't evaluate mimeapps.list at query time; refresh // its cache as well. QDBusInterface kbuildsycoca4(QStringLiteral("org.kde.kded"), QStringLiteral("/kbuildsycoca"), QStringLiteral("org.kde.kbuildsycoca")); if (kbuildsycoca4.isValid()) { kbuildsycoca4.call(QDBus::NoBlock, QStringLiteral("recreate")); } else { QProcess::startDetached(QStringLiteral("kbuildsycoca4"), QStringList()); } QProcess *proc = new QProcess(&dlg); proc->start(QStringLiteral(KBUILDSYCOCA_EXENAME), QStringList()); - QObject::connect(proc, SIGNAL(finished(int)), &dlg, SLOT(close())); + QObject::connect(proc, QOverload::of(&QProcess::finished), + &dlg, &QWidget::close); dlg.exec(); } KBuildSycocaProgressDialog::KBuildSycocaProgressDialog(QWidget *_parent, const QString &_caption, const QString &text) : QProgressDialog(_parent) , d(new KBuildSycocaProgressDialogPrivate(this)) { setWindowTitle(_caption); setModal(true); setLabelText(text); setRange(0, 0); setAutoClose(false); } KBuildSycocaProgressDialog::~KBuildSycocaProgressDialog() { delete d; } #include "moc_kbuildsycocaprogressdialog.cpp" diff --git a/src/widgets/kfileitemactions.cpp b/src/widgets/kfileitemactions.cpp index e00c3013..d9bdcb2f 100644 --- a/src/widgets/kfileitemactions.cpp +++ b/src/widgets/kfileitemactions.cpp @@ -1,822 +1,825 @@ /* This file is part of the KDE project Copyright (C) 1998-2009 David Faure This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License or ( at your option ) version 3 or, at the discretion of KDE e.V. ( which shall act as a proxy as in section 14 of the GPLv3 ), any later version. This library 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 Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kfileitemactions.h" #include "kfileitemactions_p.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include static bool KIOSKAuthorizedAction(const KConfigGroup &cfg) { if (!cfg.hasKey("X-KDE-AuthorizeAction")) { return true; } const QStringList list = cfg.readEntry("X-KDE-AuthorizeAction", QStringList()); for (QStringList::ConstIterator it = list.constBegin(); it != list.constEnd(); ++it) { if (!KAuthorized::authorize((*it).trimmed())) { return false; } } return true; } static bool mimeTypeListContains(const QStringList &list, const KFileItem &item) { const QString itemMimeType = item.mimetype(); foreach (const QString &i, list) { if (i == itemMimeType || i == QLatin1String("all/all")) { return true; } if (item.isFile() && (i == QLatin1String("allfiles") || i == QLatin1String("all/allfiles") || i == QLatin1String("application/octet-stream"))) { return true; } if (item.currentMimeType().inherits(i)) { return true; } const int iSlashPos = i.indexOf(QLatin1Char(QLatin1Char('/'))); Q_ASSERT(iSlashPos > 0); const QStringRef iSubType = i.midRef(iSlashPos+1); if (iSubType == QLatin1String("*")) { const int itemSlashPos = itemMimeType.indexOf(QLatin1Char('/')); Q_ASSERT(itemSlashPos > 0); const QStringRef iTopLevelType = i.midRef(0, iSlashPos); const QStringRef itemTopLevelType = itemMimeType.midRef(0, itemSlashPos); if (itemTopLevelType == iTopLevelType) { return true; } } } return false; } // This helper class stores the .desktop-file actions and the servicemenus // in order to support X-KDE-Priority and X-KDE-Submenu. namespace KIO { class PopupServices { public: ServiceList &selectList(const QString &priority, const QString &submenuName); ServiceList builtin; ServiceList user, userToplevel, userPriority; QMap userSubmenus, userToplevelSubmenus, userPrioritySubmenus; }; ServiceList &PopupServices::selectList(const QString &priority, const QString &submenuName) { // we use the categories .desktop entry to define submenus // if none is defined, we just pop it in the main menu if (submenuName.isEmpty()) { if (priority == QLatin1String("TopLevel")) { return userToplevel; } else if (priority == QLatin1String("Important")) { return userPriority; } } else if (priority == QLatin1String("TopLevel")) { return userToplevelSubmenus[submenuName]; } else if (priority == QLatin1String("Important")) { return userPrioritySubmenus[submenuName]; } else { return userSubmenus[submenuName]; } return user; } } // namespace //// KFileItemActionsPrivate::KFileItemActionsPrivate(KFileItemActions *qq) : QObject(), q(qq), m_executeServiceActionGroup(static_cast(nullptr)), m_runApplicationActionGroup(static_cast(nullptr)), m_parentWidget(nullptr), m_config(QStringLiteral("kservicemenurc"), KConfig::NoGlobals) { - QObject::connect(&m_executeServiceActionGroup, SIGNAL(triggered(QAction*)), - this, SLOT(slotExecuteService(QAction*))); - QObject::connect(&m_runApplicationActionGroup, SIGNAL(triggered(QAction*)), - this, SLOT(slotRunApplication(QAction*))); + QObject::connect(&m_executeServiceActionGroup, &QActionGroup::triggered, + this, &KFileItemActionsPrivate::slotExecuteService); + QObject::connect(&m_runApplicationActionGroup, &QActionGroup::triggered, + this, &KFileItemActionsPrivate::slotRunApplication); } KFileItemActionsPrivate::~KFileItemActionsPrivate() { } int KFileItemActionsPrivate::insertServicesSubmenus(const QMap &submenus, QMenu *menu, bool isBuiltin) { int count = 0; QMap::ConstIterator it; for (it = submenus.begin(); it != submenus.end(); ++it) { if (it.value().isEmpty()) { //avoid empty sub-menus continue; } QMenu *actionSubmenu = new QMenu(menu); actionSubmenu->setTitle(it.key()); actionSubmenu->menuAction()->setObjectName(QStringLiteral("services_submenu")); // for the unittest menu->addMenu(actionSubmenu); count += insertServices(it.value(), actionSubmenu, isBuiltin); } return count; } int KFileItemActionsPrivate::insertServices(const ServiceList &list, QMenu *menu, bool isBuiltin) { int count = 0; ServiceList::const_iterator it = list.begin(); for (; it != list.end(); ++it) { if ((*it).isSeparator()) { const QList actions = menu->actions(); if (!actions.isEmpty() && !actions.last()->isSeparator()) { menu->addSeparator(); } continue; } if (isBuiltin || !(*it).noDisplay()) { QAction *act = new QAction(q); act->setObjectName(QStringLiteral("menuaction")); // for the unittest QString text = (*it).text(); text.replace(QLatin1Char('&'), QLatin1String("&&")); act->setText(text); if (!(*it).icon().isEmpty()) { act->setIcon(QIcon::fromTheme((*it).icon())); } act->setData(QVariant::fromValue(*it)); m_executeServiceActionGroup.addAction(act); menu->addAction(act); // Add to toplevel menu ++count; } } return count; } void KFileItemActionsPrivate::slotExecuteService(QAction *act) { KServiceAction serviceAction = act->data().value(); if (KAuthorized::authorizeAction(serviceAction.name())) { KDesktopFileActions::executeService(m_props.urlList(), serviceAction); } } //// KFileItemActions::KFileItemActions(QObject *parent) : QObject(parent), d(new KFileItemActionsPrivate(this)) { } KFileItemActions::~KFileItemActions() { delete d; } void KFileItemActions::setItemListProperties(const KFileItemListProperties &itemListProperties) { d->m_props = itemListProperties; d->m_mimeTypeList.clear(); const KFileItemList items = d->m_props.items(); KFileItemList::const_iterator kit = items.constBegin(); const KFileItemList::const_iterator kend = items.constEnd(); for (; kit != kend; ++kit) { if (!d->m_mimeTypeList.contains((*kit).mimetype())) { d->m_mimeTypeList << (*kit).mimetype(); } } } int KFileItemActions::addServiceActionsTo(QMenu *mainMenu) { const KFileItemList items = d->m_props.items(); const KFileItem firstItem = items.first(); const QString protocol = firstItem.url().scheme(); // assumed to be the same for all items const bool isLocal = !firstItem.localPath().isEmpty(); const bool isSingleLocal = items.count() == 1 && isLocal; const QList urlList = d->m_props.urlList(); KIO::PopupServices s; // 1 - Look for builtin and user-defined services if (isSingleLocal && (d->m_props.mimeType() == QLatin1String("application/x-desktop") || // .desktop file d->m_props.mimeType() == QLatin1String("inode/blockdevice"))) { // dev file // get builtin services, like mount/unmount const QString path = firstItem.localPath(); s.builtin = KDesktopFileActions::builtinServices(QUrl::fromLocalFile(path)); KDesktopFile desktopFile(path); KConfigGroup cfg = desktopFile.desktopGroup(); const QString priority = cfg.readEntry("X-KDE-Priority"); const QString submenuName = cfg.readEntry("X-KDE-Submenu"); #if 0 if (cfg.readEntry("Type") == "Link") { d->m_url = cfg.readEntry("URL"); // TODO: Do we want to make all the actions apply on the target // of the .desktop file instead of the .desktop file itself? } #endif ServiceList &list = s.selectList(priority, submenuName); list = KDesktopFileActions::userDefinedServices(path, desktopFile, true /*isLocal*/); } // 2 - Look for "servicemenus" bindings (user-defined services) // first check the .directory if this is a directory if (d->m_props.isDirectory() && isSingleLocal) { QString dotDirectoryFile = QUrl::fromLocalFile(firstItem.localPath()).path().append(QLatin1String("/.directory")); if (QFile::exists(dotDirectoryFile)) { const KDesktopFile desktopFile(dotDirectoryFile); const KConfigGroup cfg = desktopFile.desktopGroup(); if (KIOSKAuthorizedAction(cfg)) { const QString priority = cfg.readEntry("X-KDE-Priority"); const QString submenuName = cfg.readEntry("X-KDE-Submenu"); ServiceList &list = s.selectList(priority, submenuName); list += KDesktopFileActions::userDefinedServices(dotDirectoryFile, desktopFile, true); } } } const KConfigGroup showGroup = d->m_config.group("Show"); const QMimeDatabase db; const KService::List entries = KServiceTypeTrader::self()->query(QStringLiteral("KonqPopupMenu/Plugin")); KService::List::const_iterator eEnd = entries.end(); for (KService::List::const_iterator it2 = entries.begin(); it2 != eEnd; ++it2) { QString file = QStandardPaths::locate(QStandardPaths::GenericDataLocation, QLatin1String("kservices5/") + (*it2)->entryPath()); KDesktopFile desktopFile(file); const KConfigGroup cfg = desktopFile.desktopGroup(); if (!KIOSKAuthorizedAction(cfg)) { continue; } if (cfg.hasKey("X-KDE-ShowIfRunning")) { const QString app = cfg.readEntry("X-KDE-ShowIfRunning"); if (QDBusConnection::sessionBus().interface()->isServiceRegistered(app)) { continue; } } if (cfg.hasKey("X-KDE-ShowIfDBusCall")) { QString calldata = cfg.readEntry("X-KDE-ShowIfDBusCall"); const QStringList parts = calldata.split(QLatin1Char(' ')); const QString &app = parts.at(0); const QString &obj = parts.at(1); QString interface = parts.at(2); QString method; int pos = interface.lastIndexOf(QLatin1Char('.')); if (pos != -1) { method = interface.mid(pos + 1); interface.truncate(pos); } //if (!QDBus::sessionBus().busService()->nameHasOwner(app)) // continue; //app does not exist so cannot send call QDBusMessage reply = QDBusInterface(app, obj, interface). call(method, QUrl::toStringList(urlList)); if (reply.arguments().count() < 1 || reply.arguments().at(0).type() != QVariant::Bool || !reply.arguments().at(0).toBool()) { continue; } } if (cfg.hasKey("X-KDE-Protocol")) { const QString theProtocol = cfg.readEntry("X-KDE-Protocol"); if (theProtocol.startsWith(QLatin1Char('!'))) { const QString excludedProtocol = theProtocol.mid(1); if (excludedProtocol == protocol) { continue; } } else if (protocol != theProtocol) { continue; } } else if (cfg.hasKey("X-KDE-Protocols")) { const QStringList protocols = cfg.readEntry("X-KDE-Protocols", QStringList()); if (!protocols.contains(protocol)) { continue; } } else if (protocol == QLatin1String("trash")) { // Require servicemenus for the trash to ask for protocol=trash explicitly. // Trashed files aren't supposed to be available for actions. // One might want a servicemenu for trash.desktop itself though. continue; } if (cfg.hasKey("X-KDE-Require")) { const QStringList capabilities = cfg.readEntry("X-KDE-Require", QStringList()); if (capabilities.contains(QStringLiteral("Write")) && !d->m_props.supportsWriting()) { continue; } } if (cfg.hasKey("X-KDE-RequiredNumberOfUrls")) { const QStringList requiredNumberOfUrls = cfg.readEntry("X-KDE-RequiredNumberOfUrls", QStringList()); bool matchesAtLeastOneCriterion = false; for (const QString &criterion : requiredNumberOfUrls) { const int number = criterion.toInt(); if (number < 1) { continue; } if (urlList.count() == number) { matchesAtLeastOneCriterion = true; break; } } if (!matchesAtLeastOneCriterion) { continue; } } if (cfg.hasKey("Actions") || cfg.hasKey("X-KDE-GetActionMenu")) { // Like KService, we support ServiceTypes, X-KDE-ServiceTypes, and MimeType. const QStringList types = cfg.readEntry("ServiceTypes", QStringList()) << cfg.readEntry("X-KDE-ServiceTypes", QStringList()) << cfg.readXdgListEntry("MimeType"); if (types.isEmpty()) { continue; } const QStringList excludeTypes = cfg.readEntry("ExcludeServiceTypes", QStringList()); const bool ok = std::all_of(items.constBegin(), items.constEnd(), [&types, &excludeTypes, this](const KFileItem &i) { return mimeTypeListContains(types, i) && !mimeTypeListContains(excludeTypes, i); }); if (ok) { const QString priority = cfg.readEntry("X-KDE-Priority"); const QString submenuName = cfg.readEntry("X-KDE-Submenu"); ServiceList &list = s.selectList(priority, submenuName); const ServiceList userServices = KDesktopFileActions::userDefinedServices(*(*it2), isLocal, urlList); foreach (const KServiceAction &action, userServices) { if (showGroup.readEntry(action.name(), true)) { list += action; } } } } } QMenu *actionMenu = mainMenu; int userItemCount = 0; if (s.user.count() + s.userSubmenus.count() + s.userPriority.count() + s.userPrioritySubmenus.count() > 1) { // we have more than one item, so let's make a submenu actionMenu = new QMenu(i18nc("@title:menu", "&Actions"), mainMenu); actionMenu->menuAction()->setObjectName(QStringLiteral("actions_submenu")); // for the unittest mainMenu->addMenu(actionMenu); } userItemCount += d->insertServicesSubmenus(s.userPrioritySubmenus, actionMenu, false); userItemCount += d->insertServices(s.userPriority, actionMenu, false); // see if we need to put a separator between our priority items and our regular items if (userItemCount > 0 && (s.user.count() > 0 || s.userSubmenus.count() > 0 || s.builtin.count() > 0) && !actionMenu->actions().constLast()->isSeparator()) { actionMenu->addSeparator(); } userItemCount += d->insertServicesSubmenus(s.userSubmenus, actionMenu, false); userItemCount += d->insertServices(s.user, actionMenu, false); userItemCount += d->insertServices(s.builtin, mainMenu, true); userItemCount += d->insertServicesSubmenus(s.userToplevelSubmenus, mainMenu, false); userItemCount += d->insertServices(s.userToplevel, mainMenu, false); return userItemCount; } int KFileItemActions::addPluginActionsTo(QMenu *mainMenu) { QString commonMimeType = d->m_props.mimeType(); if (commonMimeType.isEmpty() && d->m_props.isFile()) { commonMimeType = QStringLiteral("application/octet-stream"); } QStringList addedPlugins; int itemCount = 0; const KConfigGroup showGroup = d->m_config.group("Show"); const KService::List fileItemPlugins = KMimeTypeTrader::self()->query(commonMimeType, QStringLiteral("KFileItemAction/Plugin"), QStringLiteral("exist Library")); for(const auto &service : fileItemPlugins) { if (!showGroup.readEntry(service->desktopEntryName(), true)) { // The plugin has been disabled continue; } KAbstractFileItemActionPlugin *abstractPlugin = service->createInstance(); if (abstractPlugin) { abstractPlugin->setParent(mainMenu); auto actions = abstractPlugin->actions(d->m_props, d->m_parentWidget); itemCount += actions.count(); mainMenu->addActions(actions); addedPlugins.append(service->desktopEntryName()); } } const QMimeDatabase db; const auto jsonPlugins = KPluginLoader::findPlugins(QStringLiteral("kf5/kfileitemaction"), [&db, commonMimeType](const KPluginMetaData& metaData) { if (!metaData.serviceTypes().contains(QStringLiteral("KFileItemAction/Plugin"))) { return false; } auto mimeType = db.mimeTypeForName(commonMimeType); foreach (const auto& supportedMimeType, metaData.mimeTypes()) { if (mimeType.inherits(supportedMimeType)) { return true; } } return false; }); foreach (const auto& jsonMetadata, jsonPlugins) { // The plugin has been disabled if (!showGroup.readEntry(jsonMetadata.pluginId(), true)) { continue; } // The plugin also has a .desktop file and has already been added. if (addedPlugins.contains(jsonMetadata.pluginId())) { continue; } KPluginFactory *factory = KPluginLoader(jsonMetadata.fileName()).factory(); if (!factory) { continue; } KAbstractFileItemActionPlugin* abstractPlugin = factory->create(); if (abstractPlugin) { abstractPlugin->setParent(this); auto actions = abstractPlugin->actions(d->m_props, d->m_parentWidget); itemCount += actions.count(); mainMenu->addActions(actions); addedPlugins.append(jsonMetadata.pluginId()); } } return itemCount; } // static KService::List KFileItemActions::associatedApplications(const QStringList &mimeTypeList, const QString &traderConstraint) { if (!KAuthorized::authorizeAction(QStringLiteral("openwith")) || mimeTypeList.isEmpty()) { return KService::List(); } const KService::List firstOffers = KMimeTypeTrader::self()->query(mimeTypeList.first(), QStringLiteral("Application"), traderConstraint); QList rankings; QStringList serviceList; // This section does two things. First, it determines which services are common to all the given mimetypes. // Second, it ranks them based on their preference level in the associated applications list. // The more often a service appear near the front of the list, the LOWER its score. rankings.reserve(firstOffers.count()); serviceList.reserve(firstOffers.count()); for (int i = 0; i < firstOffers.count(); ++i) { KFileItemActionsPrivate::ServiceRank tempRank; tempRank.service = firstOffers[i]; tempRank.score = i; rankings << tempRank; serviceList << tempRank.service->storageId(); } for (int j = 1; j < mimeTypeList.count(); ++j) { QStringList subservice; // list of services that support this mimetype const KService::List offers = KMimeTypeTrader::self()->query(mimeTypeList[j], QStringLiteral("Application"), traderConstraint); subservice.reserve(offers.count()); for (int i = 0; i != offers.count(); ++i) { const QString serviceId = offers[i]->storageId(); subservice << serviceId; const int idPos = serviceList.indexOf(serviceId); if (idPos != -1) { rankings[idPos].score += i; } // else: we ignore the services that didn't support the previous mimetypes } // Remove services which supported the previous mimetypes but don't support this one for (int i = 0; i < serviceList.count(); ++i) { if (!subservice.contains(serviceList[i])) { serviceList.removeAt(i); rankings.removeAt(i); --i; } } // Nothing left -> there is no common application for these mimetypes if (rankings.isEmpty()) { return KService::List(); } } std::sort(rankings.begin(), rankings.end(), KFileItemActionsPrivate::lessRank); KService::List result; Q_FOREACH (const KFileItemActionsPrivate::ServiceRank &tempRank, rankings) { result << tempRank.service; } return result; } // KMimeTypeTrader::preferredService doesn't take a constraint static KService::Ptr preferredService(const QString &mimeType, const QString &constraint) { const KService::List services = KMimeTypeTrader::self()->query(mimeType, QStringLiteral("Application"), constraint); return !services.isEmpty() ? services.first() : KService::Ptr(); } void KFileItemActions::addOpenWithActionsTo(QMenu *topMenu, const QString &traderConstraint) { if (!KAuthorized::authorizeAction(QStringLiteral("openwith"))) { return; } d->m_traderConstraint = traderConstraint; KService::List offers = associatedApplications(d->m_mimeTypeList, traderConstraint); //// Ok, we have everything, now insert const KFileItemList items = d->m_props.items(); const KFileItem firstItem = items.first(); const bool isLocal = firstItem.url().isLocalFile(); // "Open With..." for folders is really not very useful, especially for remote folders. // (media:/something, or trash:/, or ftp://...) if (!d->m_props.isDirectory() || isLocal) { QAction *runAct = new QAction(this); QString runActionName; const QStringList serviceIdList = d->listPreferredServiceIds(d->m_mimeTypeList, traderConstraint); //qDebug() << "serviceIdList=" << serviceIdList; // When selecting files with multiple mimetypes, offer either "open with " // or a generic (if there are any apps associated). if (d->m_mimeTypeList.count() > 1 && !serviceIdList.isEmpty() && !(serviceIdList.count() == 1 && serviceIdList.first().isEmpty())) { // empty means "no apps associated" if (serviceIdList.count() == 1) { const KService::Ptr app = preferredService(d->m_mimeTypeList.first(), traderConstraint); runActionName = i18n("&Open with %1", app->name()); runAct->setIcon(QIcon::fromTheme(app->icon())); // Remove that app from the offers list (#242731) for (int i = 0; i < offers.count(); ++i) { if (offers[i]->storageId() == app->storageId()) { offers.removeAt(i); break; } } } else { runActionName = i18n("&Open"); } runAct->setText(runActionName); d->m_traderConstraint = traderConstraint; d->m_fileOpenList = d->m_props.items(); - QObject::connect(runAct, SIGNAL(triggered()), d, SLOT(slotRunPreferredApplications())); + QObject::connect(runAct, &QAction::triggered, + d, &KFileItemActionsPrivate::slotRunPreferredApplications); topMenu->addAction(runAct); } if (!offers.isEmpty()) { QMenu *menu = topMenu; // Show the top app inline for files, but not folders if (!d->m_props.isDirectory()) { QAction *act = d->createAppAction(offers.takeFirst(), true); menu->addAction(act); } // If there are still more apps, show them in a sub-menu if (!offers.isEmpty()) { // submenu 'open with' menu = new QMenu(i18nc("@title:menu", "&Open With"), topMenu); menu->menuAction()->setObjectName(QStringLiteral("openWith_submenu")); // for the unittest topMenu->addMenu(menu); // Add other apps to the sub-menu KService::List::ConstIterator it = offers.constBegin(); for (; it != offers.constEnd(); it++) { QAction *act = d->createAppAction(*it, // no submenu -> prefix single offer menu == topMenu); menu->addAction(act); } topMenu->addSeparator(); } QString openWithActionName; if (menu != topMenu) { // submenu menu->addSeparator(); openWithActionName = i18nc("@action:inmenu Open With", "&Other Application..."); } else { openWithActionName = i18nc("@title:menu", "&Open With..."); } QAction *openWithAct = new QAction(this); openWithAct->setText(openWithActionName); openWithAct->setObjectName(QStringLiteral("openwith_browse")); // for the unittest - QObject::connect(openWithAct, SIGNAL(triggered()), d, SLOT(slotOpenWithDialog())); + QObject::connect(openWithAct, &QAction::triggered, + d, &KFileItemActionsPrivate::slotOpenWithDialog); menu->addAction(openWithAct); menu->addSeparator(); } else { // no app offers -> Open With... QAction *act = new QAction(this); act->setText(i18nc("@title:menu", "&Open With...")); act->setObjectName(QStringLiteral("openwith")); // for the unittest - QObject::connect(act, SIGNAL(triggered()), d, SLOT(slotOpenWithDialog())); + QObject::connect(act, &QAction::triggered, + d, &KFileItemActionsPrivate::slotOpenWithDialog); topMenu->addAction(act); } } } void KFileItemActionsPrivate::slotRunPreferredApplications() { const KFileItemList fileItems = m_fileOpenList; const QStringList mimeTypeList = listMimeTypes(fileItems); const QStringList serviceIdList = listPreferredServiceIds(mimeTypeList, m_traderConstraint); foreach (const QString& serviceId, serviceIdList) { KFileItemList serviceItems; foreach (const KFileItem &item, fileItems) { const KService::Ptr serv = preferredService(item.mimetype(), m_traderConstraint); const QString preferredServiceId = serv ? serv->storageId() : QString(); if (preferredServiceId == serviceId) { serviceItems << item; } } if (serviceId.isEmpty()) { // empty means: no associated app for this mimetype openWithByMime(serviceItems); continue; } const KService::Ptr servicePtr = KService::serviceByStorageId(serviceId); if (!servicePtr) { KRun::displayOpenWithDialog(serviceItems.urlList(), m_parentWidget); continue; } KRun::runService(*servicePtr, serviceItems.urlList(), m_parentWidget); } } void KFileItemActions::runPreferredApplications(const KFileItemList &fileOpenList, const QString &traderConstraint) { d->m_fileOpenList = fileOpenList; d->m_traderConstraint = traderConstraint; d->slotRunPreferredApplications(); } void KFileItemActionsPrivate::openWithByMime(const KFileItemList &fileItems) { const QStringList mimeTypeList = listMimeTypes(fileItems); foreach (const QString& mimeType, mimeTypeList) { KFileItemList mimeItems; foreach (const KFileItem &item, fileItems) { if (item.mimetype() == mimeType) { mimeItems << item; } } KRun::displayOpenWithDialog(mimeItems.urlList(), m_parentWidget); } } void KFileItemActionsPrivate::slotRunApplication(QAction *act) { // Is it an application, from one of the "Open With" actions KService::Ptr app = act->data().value(); Q_ASSERT(app); if (app) { KRun::runService(*app, m_props.urlList(), m_parentWidget); } } void KFileItemActionsPrivate::slotOpenWithDialog() { // The item 'Other...' or 'Open With...' has been selected emit q->openWithDialogAboutToBeShown(); KRun::displayOpenWithDialog(m_props.urlList(), m_parentWidget); } QStringList KFileItemActionsPrivate::listMimeTypes(const KFileItemList &items) { QStringList mimeTypeList; foreach (const KFileItem &item, items) { if (!mimeTypeList.contains(item.mimetype())) { mimeTypeList << item.mimetype(); } } return mimeTypeList; } QStringList KFileItemActionsPrivate::listPreferredServiceIds(const QStringList &mimeTypeList, const QString &traderConstraint) { QStringList serviceIdList; Q_FOREACH (const QString &mimeType, mimeTypeList) { const KService::Ptr serv = preferredService(mimeType, traderConstraint); const QString newOffer = serv ? serv->storageId() : QString(); serviceIdList << newOffer; } serviceIdList.removeDuplicates(); return serviceIdList; } QAction *KFileItemActionsPrivate::createAppAction(const KService::Ptr &service, bool singleOffer) { QString actionName(service->name().replace(QLatin1Char('&'), QLatin1String("&&"))); if (singleOffer) { actionName = i18n("Open &with %1", actionName); } else { actionName = i18nc("@item:inmenu Open With, %1 is application name", "%1", actionName); } QAction *act = new QAction(q); act->setObjectName(QStringLiteral("openwith")); // for the unittest act->setIcon(QIcon::fromTheme(service->icon())); act->setText(actionName); act->setData(QVariant::fromValue(service)); m_runApplicationActionGroup.addAction(act); return act; } QAction *KFileItemActions::preferredOpenWithAction(const QString &traderConstraint) { const KService::List offers = associatedApplications(d->m_mimeTypeList, traderConstraint); if (offers.isEmpty()) { return nullptr; } return d->createAppAction(offers.first(), true); } void KFileItemActions::setParentWidget(QWidget *widget) { d->m_parentWidget = widget; } diff --git a/src/widgets/kfileitemactions_p.h b/src/widgets/kfileitemactions_p.h index 550bb63d..c21e9afd 100644 --- a/src/widgets/kfileitemactions_p.h +++ b/src/widgets/kfileitemactions_p.h @@ -1,94 +1,95 @@ /* This file is part of the KDE project Copyright (C) 1998-2009 David Faure This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License or ( at your option ) version 3 or, at the discretion of KDE e.V. ( which shall act as a proxy as in section 14 of the GPLv3 ), any later version. This library 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 Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef KFILEITEMACTIONS_P_H #define KFILEITEMACTIONS_P_H #include #include #include #include #include #include #include class KFileItemActions; typedef QList ServiceList; class KFileItemActionsPrivate : public QObject { Q_OBJECT + friend class KFileItemActions; public: explicit KFileItemActionsPrivate(KFileItemActions *qq); ~KFileItemActionsPrivate(); int insertServicesSubmenus(const QMap &list, QMenu *menu, bool isBuiltin); int insertServices(const ServiceList &list, QMenu *menu, bool isBuiltin); // For "open with" KService::List associatedApplications(const QString &traderConstraint); QAction *createAppAction(const KService::Ptr &service, bool singleOffer); struct ServiceRank { int score; KService::Ptr service; }; // Inline function for sorting lists of ServiceRank static bool lessRank(const ServiceRank &id1, const ServiceRank &id2) { return id1.score < id2.score; } QStringList listMimeTypes(const KFileItemList &items); QStringList listPreferredServiceIds(const QStringList &mimeTypeList, const QString &traderConstraint); public Q_SLOTS: void slotRunPreferredApplications(); private: void openWithByMime(const KFileItemList &fileItems); private Q_SLOTS: // For servicemenus void slotExecuteService(QAction *act); // For "open with" applications void slotRunApplication(QAction *act); void slotOpenWithDialog(); public: KFileItemActions *const q; KFileItemListProperties m_props; QStringList m_mimeTypeList; QString m_traderConstraint; KFileItemList m_fileOpenList; QActionGroup m_executeServiceActionGroup; QActionGroup m_runApplicationActionGroup; QWidget *m_parentWidget; KConfig m_config; }; Q_DECLARE_METATYPE(KService::Ptr) Q_DECLARE_METATYPE(KServiceAction) #endif /* KFILEITEMACTIONS_P_H */ diff --git a/src/widgets/kopenwithdialog.cpp b/src/widgets/kopenwithdialog.cpp index 23708e2e..193f14bf 100644 --- a/src/widgets/kopenwithdialog.cpp +++ b/src/widgets/kopenwithdialog.cpp @@ -1,1164 +1,1164 @@ /* This file is part of the KDE libraries Copyright (C) 1997 Torben Weis Copyright (C) 1999 Dirk Mueller Portions copyright (C) 1999 Preston Brown Copyright (C) 2007 Pino Toscano This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library 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 Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kopenwithdialog.h" #include "kopenwithdialog_p.h" #include "kio_widgets_debug.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include inline void writeEntry(KConfigGroup &group, const char *key, const KCompletion::CompletionMode &aValue, KConfigBase::WriteConfigFlags flags = KConfigBase::Normal) { group.writeEntry(key, int(aValue), flags); } namespace KDEPrivate { class AppNode { public: AppNode() : isDir(false), parent(nullptr), fetched(false) { } ~AppNode() { qDeleteAll(children); } QString icon; QString text; QString entryPath; QString exec; bool isDir; AppNode *parent; bool fetched; QList children; }; static bool AppNodeLessThan(KDEPrivate::AppNode *n1, KDEPrivate::AppNode *n2) { if (n1->isDir) { if (n2->isDir) { return n1->text.compare(n2->text, Qt::CaseInsensitive) < 0; } else { return true; } } else { if (n2->isDir) { return false; } else { return n1->text.compare(n2->text, Qt::CaseInsensitive) < 0; } } } } class KApplicationModelPrivate { public: explicit KApplicationModelPrivate(KApplicationModel *qq) : q(qq), root(new KDEPrivate::AppNode()) { } ~KApplicationModelPrivate() { delete root; } void fillNode(const QString &entryPath, KDEPrivate::AppNode *node); KApplicationModel * const q; KDEPrivate::AppNode *root; }; void KApplicationModelPrivate::fillNode(const QString &_entryPath, KDEPrivate::AppNode *node) { KServiceGroup::Ptr root = KServiceGroup::group(_entryPath); if (!root || !root->isValid()) { return; } const KServiceGroup::List list = root->entries(); for (KServiceGroup::List::ConstIterator it = list.begin(); it != list.end(); ++it) { QString icon; QString text; QString entryPath; QString exec; bool isDir = false; const KSycocaEntry::Ptr p = (*it); if (p->isType(KST_KService)) { const KService::Ptr service(static_cast(p.data())); if (service->noDisplay()) { continue; } icon = service->icon(); text = service->name(); exec = service->exec(); entryPath = service->entryPath(); } else if (p->isType(KST_KServiceGroup)) { const KServiceGroup::Ptr serviceGroup(static_cast(p.data())); if (serviceGroup->noDisplay() || serviceGroup->childCount() == 0) { continue; } icon = serviceGroup->icon(); text = serviceGroup->caption(); entryPath = serviceGroup->entryPath(); isDir = true; } else { qCWarning(KIO_WIDGETS) << "KServiceGroup: Unexpected object in list!"; continue; } KDEPrivate::AppNode *newnode = new KDEPrivate::AppNode(); newnode->icon = icon; newnode->text = text; newnode->entryPath = entryPath; newnode->exec = exec; newnode->isDir = isDir; newnode->parent = node; node->children.append(newnode); } qStableSort(node->children.begin(), node->children.end(), KDEPrivate::AppNodeLessThan); } KApplicationModel::KApplicationModel(QObject *parent) : QAbstractItemModel(parent), d(new KApplicationModelPrivate(this)) { d->fillNode(QString(), d->root); const int nRows = rowCount(); for (int i = 0; i < nRows; i++) { fetchAll(index(i, 0)); } } KApplicationModel::~KApplicationModel() { delete d; } bool KApplicationModel::canFetchMore(const QModelIndex &parent) const { if (!parent.isValid()) { return false; } KDEPrivate::AppNode *node = static_cast(parent.internalPointer()); return node->isDir && !node->fetched; } int KApplicationModel::columnCount(const QModelIndex &parent) const { Q_UNUSED(parent) return 1; } QVariant KApplicationModel::data(const QModelIndex &index, int role) const { if (!index.isValid()) { return QVariant(); } KDEPrivate::AppNode *node = static_cast(index.internalPointer()); switch (role) { case Qt::DisplayRole: return node->text; case Qt::DecorationRole: if (!node->icon.isEmpty()) { return QIcon::fromTheme(node->icon); } break; default: ; } return QVariant(); } void KApplicationModel::fetchMore(const QModelIndex &parent) { if (!parent.isValid()) { return; } KDEPrivate::AppNode *node = static_cast(parent.internalPointer()); if (!node->isDir) { return; } emit layoutAboutToBeChanged(); d->fillNode(node->entryPath, node); node->fetched = true; emit layoutChanged(); } void KApplicationModel::fetchAll(const QModelIndex &parent) { if (!parent.isValid() || !canFetchMore(parent)) { return; } fetchMore(parent); int childCount = rowCount(parent); for (int i = 0; i < childCount; i++) { const QModelIndex &child = parent.child(i, 0); // Recursively call the function for each child node. fetchAll(child); } } bool KApplicationModel::hasChildren(const QModelIndex &parent) const { if (!parent.isValid()) { return true; } KDEPrivate::AppNode *node = static_cast(parent.internalPointer()); return node->isDir; } QVariant KApplicationModel::headerData(int section, Qt::Orientation orientation, int role) const { if (orientation != Qt::Horizontal || section != 0) { return QVariant(); } switch (role) { case Qt::DisplayRole: return i18n("Known Applications"); default: return QVariant(); } } QModelIndex KApplicationModel::index(int row, int column, const QModelIndex &parent) const { if (row < 0 || column != 0) { return QModelIndex(); } KDEPrivate::AppNode *node = d->root; if (parent.isValid()) { node = static_cast(parent.internalPointer()); } if (row >= node->children.count()) { return QModelIndex(); } else { return createIndex(row, 0, node->children.at(row)); } } QModelIndex KApplicationModel::parent(const QModelIndex &index) const { if (!index.isValid()) { return QModelIndex(); } KDEPrivate::AppNode *node = static_cast(index.internalPointer()); if (node->parent->parent) { int id = node->parent->parent->children.indexOf(node->parent); if (id >= 0 && id < node->parent->parent->children.count()) { return createIndex(id, 0, node->parent); } else { return QModelIndex(); } } else { return QModelIndex(); } } int KApplicationModel::rowCount(const QModelIndex &parent) const { if (!parent.isValid()) { return d->root->children.count(); } KDEPrivate::AppNode *node = static_cast(parent.internalPointer()); return node->children.count(); } QString KApplicationModel::entryPathFor(const QModelIndex &index) const { if (!index.isValid()) { return QString(); } KDEPrivate::AppNode *node = static_cast(index.internalPointer()); return node->entryPath; } QString KApplicationModel::execFor(const QModelIndex &index) const { if (!index.isValid()) { return QString(); } KDEPrivate::AppNode *node = static_cast(index.internalPointer()); return node->exec; } bool KApplicationModel::isDirectory(const QModelIndex &index) const { if (!index.isValid()) { return false; } KDEPrivate::AppNode *node = static_cast(index.internalPointer()); return node->isDir; } QTreeViewProxyFilter::QTreeViewProxyFilter(QObject *parent) : QSortFilterProxyModel(parent) { } bool QTreeViewProxyFilter::filterAcceptsRow(int sourceRow, const QModelIndex &parent) const { QModelIndex index = sourceModel()->index(sourceRow, 0, parent); if (!index.isValid()) { return false; } // Match the regexp only on leaf nodes if (!sourceModel()->hasChildren(index) && index.data().toString().contains(filterRegExp())) { return true; } //Show the non-leaf node also if the regexp matches one of its children int rows = sourceModel()->rowCount(index); for (int crow = 0; crow < rows; crow++) { if (filterAcceptsRow(crow, index)) { return true; } } return false; } class KApplicationViewPrivate { public: KApplicationViewPrivate() : appModel(nullptr), m_proxyModel(nullptr) { } KApplicationModel *appModel; QSortFilterProxyModel *m_proxyModel; }; KApplicationView::KApplicationView(QWidget *parent) : QTreeView(parent), d(new KApplicationViewPrivate) { setHeaderHidden(true); } KApplicationView::~KApplicationView() { delete d; } void KApplicationView::setModels(KApplicationModel *model, QSortFilterProxyModel *proxyModel) { if (d->appModel) { - disconnect(selectionModel(), SIGNAL(selectionChanged(QItemSelection,QItemSelection)), - this, SLOT(slotSelectionChanged(QItemSelection,QItemSelection))); + disconnect(selectionModel(), &QItemSelectionModel::selectionChanged, + this, &KApplicationView::slotSelectionChanged); } QTreeView::setModel(proxyModel); // Here we set the proxy model d->m_proxyModel = proxyModel; // Also store it in a member property to avoid many casts later d->appModel = model; if (d->appModel) { - connect(selectionModel(), SIGNAL(selectionChanged(QItemSelection,QItemSelection)), - this, SLOT(slotSelectionChanged(QItemSelection,QItemSelection))); + connect(selectionModel(), &QItemSelectionModel::selectionChanged, + this, &KApplicationView::slotSelectionChanged); } } QSortFilterProxyModel* KApplicationView::proxyModel() { return d->m_proxyModel; } bool KApplicationView::isDirSel() const { if (d->appModel) { QModelIndex index = selectionModel()->currentIndex(); index = d->m_proxyModel->mapToSource(index); return d->appModel->isDirectory(index); } return false; } void KApplicationView::currentChanged(const QModelIndex ¤t, const QModelIndex &previous) { QTreeView::currentChanged(current, previous); if (d->appModel) { QModelIndex sourceCurrent = d->m_proxyModel->mapToSource(current); if(!d->appModel->isDirectory(sourceCurrent)) { QString exec = d->appModel->execFor(sourceCurrent); if (!exec.isEmpty()) { emit highlighted(d->appModel->entryPathFor(sourceCurrent), exec); } } } } void KApplicationView::slotSelectionChanged(const QItemSelection &selected, const QItemSelection &deselected) { Q_UNUSED(deselected) QItemSelection sourceSelected = d->m_proxyModel->mapSelectionToSource(selected); const QModelIndexList indexes = sourceSelected.indexes(); if (indexes.count() == 1) { QString exec = d->appModel->execFor(indexes.at(0)); emit this->selected(d->appModel->entryPathFor(indexes.at(0)), exec); } } /*************************************************************** * * KOpenWithDialog * ***************************************************************/ class KOpenWithDialogPrivate { public: explicit KOpenWithDialogPrivate(KOpenWithDialog *qq) : q(qq), saveNewApps(false) { } KOpenWithDialog * const q; /** * Determine mime type from URLs */ void setMimeType(const QList &_urls); void addToMimeAppsList(const QString &serviceId); /** * Create a dialog that asks for a application to open a given * URL(s) with. * * @param text appears as a label on top of the entry box. * @param value is the initial value of the line */ void init(const QString &text, const QString &value); /** * Called by checkAccept() in order to save the history of the combobox */ void saveComboboxHistory(); /** * Process the choices made by the user, and return true if everything is OK. * Called by KOpenWithDialog::accept(), i.e. when clicking on OK or typing Return. */ bool checkAccept(); // slots void _k_slotDbClick(); void _k_slotFileSelected(); bool saveNewApps; bool m_terminaldirty; KService::Ptr curService; KApplicationView *view; KUrlRequester *edit; QString m_command; QLabel *label; QString qMimeType; QString qMimeTypeComment; QCheckBox *terminal; QCheckBox *remember; QCheckBox *nocloseonexit; KService::Ptr m_pService; QDialogButtonBox *buttonBox; }; KOpenWithDialog::KOpenWithDialog(const QList &_urls, QWidget *parent) : QDialog(parent), d(new KOpenWithDialogPrivate(this)) { setObjectName(QStringLiteral("openwith")); setModal(true); setWindowTitle(i18n("Open With")); QString text; if (_urls.count() == 1) { text = i18n("Select the program that should be used to open %1. " "If the program is not listed, enter the name or click " "the browse button.", _urls.first().fileName().toHtmlEscaped()); } else // Should never happen ?? { text = i18n("Choose the name of the program with which to open the selected files."); } d->setMimeType(_urls); d->init(text, QString()); } KOpenWithDialog::KOpenWithDialog(const QList &_urls, const QString &_text, const QString &_value, QWidget *parent) : QDialog(parent), d(new KOpenWithDialogPrivate(this)) { setObjectName(QStringLiteral("openwith")); setModal(true); QString text = _text; if (text.isEmpty() && !_urls.isEmpty()) { if (_urls.count() == 1) { const QString fileName = KStringHandler::csqueeze(_urls.first().fileName()); text = i18n("Select the program you want to use to open the file
%1
", fileName.toHtmlEscaped()); } else { text = i18np("Select the program you want to use to open the file.", "Select the program you want to use to open the %1 files.", _urls.count()); } } setWindowTitle(i18n("Choose Application")); d->setMimeType(_urls); d->init(text, _value); } KOpenWithDialog::KOpenWithDialog(const QString &mimeType, const QString &value, QWidget *parent) : QDialog(parent), d(new KOpenWithDialogPrivate(this)) { setObjectName(QStringLiteral("openwith")); setModal(true); setWindowTitle(i18n("Choose Application for %1", mimeType)); QString text = i18n("Select the program for the file type: %1. " "If the program is not listed, enter the name or click " "the browse button.", mimeType); d->qMimeType = mimeType; QMimeDatabase db; d->qMimeTypeComment = db.mimeTypeForName(mimeType).comment(); d->init(text, value); if (d->remember) { d->remember->hide(); } } KOpenWithDialog::KOpenWithDialog(QWidget *parent) : QDialog(parent), d(new KOpenWithDialogPrivate(this)) { setObjectName(QStringLiteral("openwith")); setModal(true); setWindowTitle(i18n("Choose Application")); QString text = i18n("Select a program. " "If the program is not listed, enter the name or click " "the browse button."); d->qMimeType.clear(); d->init(text, QString()); } void KOpenWithDialogPrivate::setMimeType(const QList &_urls) { if (_urls.count() == 1) { QMimeDatabase db; QMimeType mime = db.mimeTypeForUrl(_urls.first()); qMimeType = mime.name(); if (mime.isDefault()) { qMimeType.clear(); } else { qMimeTypeComment = mime.comment(); } } else { qMimeType.clear(); } } void KOpenWithDialogPrivate::init(const QString &_text, const QString &_value) { bool bReadOnly = !KAuthorized::authorize(QStringLiteral("shell_access")); m_terminaldirty = false; view = nullptr; m_pService = nullptr; curService = nullptr; QBoxLayout *topLayout = new QVBoxLayout; q->setLayout(topLayout); label = new QLabel(_text, q); label->setWordWrap(true); topLayout->addWidget(label); if (!bReadOnly) { // init the history combo and insert it into the URL-Requester KHistoryComboBox *combo = new KHistoryComboBox(); combo->setToolTip(i18n("Type to filter the applications below, or specify the name of a command.\nPress down arrow to navigate the results.")); KLineEdit *lineEdit = new KLineEdit(q); lineEdit->setClearButtonEnabled(true); combo->setLineEdit(lineEdit); combo->setSizeAdjustPolicy(QComboBox::AdjustToMinimumContentsLengthWithIcon); combo->setDuplicatesEnabled(false); KConfigGroup cg(KSharedConfig::openConfig(), QStringLiteral("Open-with settings")); int max = cg.readEntry("Maximum history", 15); combo->setMaxCount(max); int mode = cg.readEntry("CompletionMode", int(KCompletion::CompletionNone)); combo->setCompletionMode(static_cast(mode)); const QStringList list = cg.readEntry("History", QStringList()); combo->setHistoryItems(list, true); edit = new KUrlRequester(combo, q); edit->installEventFilter(q); } else { edit = new KUrlRequester(q); edit->lineEdit()->setReadOnly(true); edit->button()->hide(); } edit->setText(_value); edit->setWhatsThis(i18n( "Following the command, you can have several place holders which will be replaced " "with the actual values when the actual program is run:\n" "%f - a single file name\n" "%F - a list of files; use for applications that can open several local files at once\n" "%u - a single URL\n" "%U - a list of URLs\n" "%d - the directory of the file to open\n" "%D - a list of directories\n" "%i - the icon\n" "%m - the mini-icon\n" "%c - the comment")); topLayout->addWidget(edit); if (edit->comboBox()) { KUrlCompletion *comp = new KUrlCompletion(KUrlCompletion::ExeCompletion); edit->comboBox()->setCompletionObject(comp); edit->comboBox()->setAutoDeleteCompletionObject(true); } - QObject::connect(edit, SIGNAL(textChanged(QString)), q, SLOT(slotTextChanged())); + QObject::connect(edit, &KUrlRequester::textChanged, q, &KOpenWithDialog::slotTextChanged); QObject::connect(edit, SIGNAL(urlSelected(QUrl)), q, SLOT(_k_slotFileSelected())); QTreeViewProxyFilter *proxyModel = new QTreeViewProxyFilter(view); KApplicationModel *appModel = new KApplicationModel(proxyModel); proxyModel->setSourceModel(appModel); proxyModel->setFilterKeyColumn(0); proxyModel->setFilterCaseSensitivity(Qt::CaseInsensitive); view = new KApplicationView(q); view->setModels(appModel, proxyModel); topLayout->addWidget(view); topLayout->setStretchFactor(view, 1); - QObject::connect(view, SIGNAL(selected(QString,QString)), - q, SLOT(slotSelected(QString,QString))); - QObject::connect(view, SIGNAL(highlighted(QString,QString)), - q, SLOT(slotHighlighted(QString,QString))); + QObject::connect(view, &KApplicationView::selected, + q, &KOpenWithDialog::slotSelected); + QObject::connect(view, &KApplicationView::highlighted, + q, &KOpenWithDialog::slotHighlighted); QObject::connect(view, SIGNAL(doubleClicked(QModelIndex)), q, SLOT(_k_slotDbClick())); if (!qMimeType.isNull()) { remember = new QCheckBox(i18n("&Remember application association for all files of type\n\"%1\" (%2)", qMimeTypeComment, qMimeType)); // remember->setChecked(true); topLayout->addWidget(remember); } else { remember = nullptr; } //Advanced options KCollapsibleGroupBox *dialogExtension = new KCollapsibleGroupBox(q); dialogExtension->setTitle(i18n("Terminal options")); QVBoxLayout *dialogExtensionLayout = new QVBoxLayout; dialogExtensionLayout->setMargin(0); terminal = new QCheckBox(i18n("Run in &terminal"), q); if (bReadOnly) { terminal->hide(); } - QObject::connect(terminal, SIGNAL(toggled(bool)), q, SLOT(slotTerminalToggled(bool))); + QObject::connect(terminal, &QAbstractButton::toggled, q, &KOpenWithDialog::slotTerminalToggled); dialogExtensionLayout->addWidget(terminal); QStyleOptionButton checkBoxOption; checkBoxOption.initFrom(terminal); int checkBoxIndentation = terminal->style()->pixelMetric(QStyle::PM_IndicatorWidth, &checkBoxOption, terminal); checkBoxIndentation += terminal->style()->pixelMetric(QStyle::PM_CheckBoxLabelSpacing, &checkBoxOption, terminal); QBoxLayout *nocloseonexitLayout = new QHBoxLayout(); nocloseonexitLayout->setMargin(0); QSpacerItem *spacer = new QSpacerItem(checkBoxIndentation, 0, QSizePolicy::Fixed, QSizePolicy::Minimum); nocloseonexitLayout->addItem(spacer); nocloseonexit = new QCheckBox(i18n("&Do not close when command exits"), q); nocloseonexit->setChecked(false); nocloseonexit->setDisabled(true); // check to see if we use konsole if not disable the nocloseonexit // because we don't know how to do this on other terminal applications KConfigGroup confGroup(KSharedConfig::openConfig(), QStringLiteral("General")); QString preferredTerminal = confGroup.readPathEntry("TerminalApplication", QStringLiteral("konsole")); if (bReadOnly || preferredTerminal != QStringLiteral("konsole")) { nocloseonexit->hide(); } nocloseonexitLayout->addWidget(nocloseonexit); dialogExtensionLayout->addLayout(nocloseonexitLayout); dialogExtension->setLayout(dialogExtensionLayout); topLayout->addWidget(dialogExtension); buttonBox = new QDialogButtonBox(q); buttonBox->setStandardButtons(QDialogButtonBox::Ok | QDialogButtonBox::Cancel); - q->connect(buttonBox, SIGNAL(accepted()), q, SLOT(accept())); - q->connect(buttonBox, SIGNAL(rejected()), q, SLOT(reject())); + q->connect(buttonBox, &QDialogButtonBox::accepted, q, &QDialog::accept); + q->connect(buttonBox, &QDialogButtonBox::rejected, q, &QDialog::reject); topLayout->addWidget(buttonBox); q->setMinimumSize(q->minimumSizeHint()); //edit->setText( _value ); // The resize is what caused "can't click on items before clicking on Name header" in previous versions. // Probably due to the resizeEvent handler using width(). q->resize( q->minimumWidth(), 0.6*QApplication::desktop()->availableGeometry().height()); edit->setFocus(); q->slotTextChanged(); } // ---------------------------------------------------------------------- KOpenWithDialog::~KOpenWithDialog() { delete d; } // ---------------------------------------------------------------------- void KOpenWithDialog::slotSelected(const QString & /*_name*/, const QString &_exec) { d->buttonBox->button(QDialogButtonBox::Ok)->setEnabled(!_exec.isEmpty()); } // ---------------------------------------------------------------------- void KOpenWithDialog::slotHighlighted(const QString &entryPath, const QString &) { d->curService = KService::serviceByDesktopPath(entryPath); if (d->curService && !d->m_terminaldirty) { // ### indicate that default value was restored d->terminal->setChecked(d->curService->terminal()); QString terminalOptions = d->curService->terminalOptions(); d->nocloseonexit->setChecked((terminalOptions.contains(QLatin1String("--noclose")))); d->m_terminaldirty = false; // slotTerminalToggled changed it } } // ---------------------------------------------------------------------- void KOpenWithDialog::slotTextChanged() { // Forget about the service only when the selection is empty // otherwise changing text but hitting the same result clears curService bool selectionEmpty = !d->view->currentIndex().isValid(); if (d->curService && selectionEmpty) { d->curService = nullptr; } d->buttonBox->button(QDialogButtonBox::Ok)->setEnabled(!d->edit->text().isEmpty() || d->curService); //Update the filter regexp with the new text in the lineedit d->view->proxyModel()->setFilterFixedString(d->edit->text()); //Expand all the nodes when the search string is 3 characters long //If the search string doesn't match anything there will be no nodes to expand if (d->edit->text().size() > 2) { d->view->expandAll(); //Automatically select the first result (first leaf node) when the filter has match QModelIndex leafNodeIdx = d->view->model()->index(0, 0); while (d->view->model()->hasChildren(leafNodeIdx)) { leafNodeIdx = leafNodeIdx.child(0,0); } d->view->setCurrentIndex(leafNodeIdx); } else { d->view->collapseAll(); d->view->setCurrentIndex(d->view->rootIndex()); // Unset and deselect all the elements d->curService = nullptr; } } // ---------------------------------------------------------------------- void KOpenWithDialog::slotTerminalToggled(bool) { // ### indicate that default value was overridden d->m_terminaldirty = true; d->nocloseonexit->setDisabled(!d->terminal->isChecked()); } // ---------------------------------------------------------------------- void KOpenWithDialogPrivate::_k_slotDbClick() { // check if a directory is selected if (view->isDirSel()) { return; } q->accept(); } void KOpenWithDialogPrivate::_k_slotFileSelected() { // quote the path to avoid unescaped whitespace, backslashes, etc. edit->setText(KShell::quoteArg(edit->text())); } void KOpenWithDialog::setSaveNewApplications(bool b) { d->saveNewApps = b; } static QString simplifiedExecLineFromService(const QString &fullExec) { QString exec = fullExec; exec.remove(QStringLiteral("%u"), Qt::CaseInsensitive); exec.remove(QStringLiteral("%f"), Qt::CaseInsensitive); exec.remove(QStringLiteral("-caption %c")); exec.remove(QStringLiteral("-caption \"%c\"")); exec.remove(QStringLiteral("%i")); exec.remove(QStringLiteral("%m")); return exec.simplified(); } void KOpenWithDialogPrivate::addToMimeAppsList(const QString &serviceId /*menu id or storage id*/) { KSharedConfig::Ptr profile = KSharedConfig::openConfig(QStringLiteral("mimeapps.list"), KConfig::NoGlobals, QStandardPaths::GenericConfigLocation); // Save the default application according to mime-apps-spec 1.0 KConfigGroup defaultApp(profile, "Default Applications"); defaultApp.writeXdgListEntry(qMimeType, QStringList(serviceId)); KConfigGroup addedApps(profile, "Added Associations"); QStringList apps = addedApps.readXdgListEntry(qMimeType); apps.removeAll(serviceId); apps.prepend(serviceId); // make it the preferred app addedApps.writeXdgListEntry(qMimeType, apps); profile->sync(); // Also make sure the "auto embed" setting for this mimetype is off KSharedConfig::Ptr fileTypesConfig = KSharedConfig::openConfig(QStringLiteral("filetypesrc"), KConfig::NoGlobals); fileTypesConfig->group("EmbedSettings").writeEntry(QStringLiteral("embed-") + qMimeType, false); fileTypesConfig->sync(); // qDebug() << "rebuilding ksycoca..."; // kbuildsycoca is the one reading mimeapps.list, so we need to run it now KBuildSycocaProgressDialog::rebuildKSycoca(q); // could be nullptr if the user canceled the dialog... m_pService = KService::serviceByStorageId(serviceId); } bool KOpenWithDialogPrivate::checkAccept() { const QString typedExec(edit->text()); QString fullExec(typedExec); QString serviceName; QString initialServiceName; QString preferredTerminal; QString configPath; QString serviceExec; m_pService = curService; if (!m_pService) { // No service selected - check the command line // Find out the name of the service from the command line, removing args and paths serviceName = KIO::DesktopExecParser::executableName(typedExec); if (serviceName.isEmpty()) { KMessageBox::error(q, i18n("Could not extract executable name from '%1', please type a valid program name.", serviceName)); return false; } initialServiceName = serviceName; // Also remember the executableName with a path, if any, for the // check that the executable exists. // qDebug() << "initialServiceName=" << initialServiceName; int i = 1; // We have app, app-2, app-3... Looks better for the user. bool ok = false; // Check if there's already a service by that name, with the same Exec line do { // qDebug() << "looking for service" << serviceName; KService::Ptr serv = KService::serviceByDesktopName(serviceName); ok = !serv; // ok if no such service yet // also ok if we find the exact same service (well, "kwrite" == "kwrite %U") if (serv && !serv->noDisplay() /* #297720 */) { if (serv->isApplication()) { /*// qDebug() << "typedExec=" << typedExec << "serv->exec=" << serv->exec() << "simplifiedExecLineFromService=" << simplifiedExecLineFromService(fullExec);*/ serviceExec = simplifiedExecLineFromService(serv->exec()); if (typedExec == serviceExec) { ok = true; m_pService = serv; // qDebug() << "OK, found identical service: " << serv->entryPath(); } else { // qDebug() << "Exec line differs, service says:" << serviceExec; configPath = serv->entryPath(); serviceExec = serv->exec(); } } else { // qDebug() << "Found, but not an application:" << serv->entryPath(); } } if (!ok) { // service was found, but it was different -> keep looking ++i; serviceName = initialServiceName + QLatin1Char('-') + QString::number(i); } } while (!ok); } if (m_pService) { // Existing service selected serviceName = m_pService->name(); initialServiceName = serviceName; fullExec = m_pService->exec(); } else { const QString binaryName = KIO::DesktopExecParser::executablePath(typedExec); // qDebug() << "binaryName=" << binaryName; // Ensure that the typed binary name actually exists (#81190) if (QStandardPaths::findExecutable(binaryName).isEmpty()) { KMessageBox::error(q, i18n("'%1' not found, please type a valid program name.", binaryName)); return false; } } if (terminal->isChecked()) { KConfigGroup confGroup(KSharedConfig::openConfig(), QStringLiteral("General")); preferredTerminal = confGroup.readPathEntry("TerminalApplication", QStringLiteral("konsole")); m_command = preferredTerminal; // only add --noclose when we are sure it is konsole we're using if (preferredTerminal == QStringLiteral("konsole") && nocloseonexit->isChecked()) { m_command += QStringLiteral(" --noclose"); } m_command += QStringLiteral(" -e "); m_command += edit->text(); // qDebug() << "Setting m_command to" << m_command; } if (m_pService && terminal->isChecked() != m_pService->terminal()) { m_pService = nullptr; // It's not exactly this service we're running } const bool bRemember = remember && remember->isChecked(); // qDebug() << "bRemember=" << bRemember << "service found=" << m_pService; if (m_pService) { if (bRemember) { // Associate this app with qMimeType in mimeapps.list Q_ASSERT(!qMimeType.isEmpty()); // we don't show the remember checkbox otherwise addToMimeAppsList(m_pService->storageId()); } } else { const bool createDesktopFile = bRemember || saveNewApps; if (!createDesktopFile) { // Create temp service if (configPath.isEmpty()) { m_pService = new KService(initialServiceName, fullExec, QString()); } else { if (!typedExec.contains(QLatin1String("%u"), Qt::CaseInsensitive) && !typedExec.contains(QLatin1String("%f"), Qt::CaseInsensitive)) { int index = serviceExec.indexOf(QLatin1String("%u"), 0, Qt::CaseInsensitive); if (index == -1) { index = serviceExec.indexOf(QLatin1String("%f"), 0, Qt::CaseInsensitive); } if (index > -1) { fullExec += QLatin1Char(' '); fullExec += serviceExec.midRef(index, 2); } } // qDebug() << "Creating service with Exec=" << fullExec; m_pService = new KService(configPath); m_pService->setExec(fullExec); } if (terminal->isChecked()) { m_pService->setTerminal(true); // only add --noclose when we are sure it is konsole we're using if (preferredTerminal == QLatin1String("konsole") && nocloseonexit->isChecked()) { m_pService->setTerminalOptions(QStringLiteral("--noclose")); } } } else { // If we got here, we can't seem to find a service for what they wanted. Create one. QString menuId; #ifdef Q_OS_WIN32 // on windows, do not use the complete path, but only the default name. serviceName = QFileInfo(serviceName).fileName(); #endif QString newPath = KService::newServicePath(false /* ignored argument */, serviceName, &menuId); // qDebug() << "Creating new service" << serviceName << "(" << newPath << ")" << "menuId=" << menuId; KDesktopFile desktopFile(newPath); KConfigGroup cg = desktopFile.desktopGroup(); cg.writeEntry("Type", "Application"); cg.writeEntry("Name", initialServiceName); cg.writeEntry("Exec", fullExec); cg.writeEntry("NoDisplay", true); // don't make it appear in the K menu if (terminal->isChecked()) { cg.writeEntry("Terminal", true); // only add --noclose when we are sure it is konsole we're using if (preferredTerminal == QLatin1String("konsole") && nocloseonexit->isChecked()) { cg.writeEntry("TerminalOptions", "--noclose"); } } if (!qMimeType.isEmpty()) { cg.writeXdgListEntry("MimeType", QStringList() << qMimeType); } cg.sync(); if (!qMimeType.isEmpty()) { addToMimeAppsList(menuId); } else { m_pService = new KService(newPath); } } } saveComboboxHistory(); return true; } bool KOpenWithDialog::eventFilter(QObject *object, QEvent *event) { // Detect DownArrow to navigate the results in the QTreeView if (object == d->edit && event->type() == QEvent::ShortcutOverride) { QKeyEvent *keyEvent = static_cast(event); if (keyEvent->key() == Qt::Key_Down) { KHistoryComboBox *combo = static_cast(d->edit->comboBox()); // FIXME: Disable arrow down in CompletionPopup and CompletionPopupAuto only when the dropdown list is shown. // When popup completion mode is used the down arrow is used to navigate the dropdown list of results if (combo->completionMode() != KCompletion::CompletionPopup && combo->completionMode() != KCompletion::CompletionPopupAuto) { QModelIndex leafNodeIdx = d->view->model()->index(0, 0); // Check if we have at least one result or the focus is passed to the empty QTreeView if (d->view->model()->hasChildren(leafNodeIdx)) { d->view->setFocus(Qt::OtherFocusReason); QApplication::sendEvent(d->view, keyEvent); return true; } } } } return QDialog::eventFilter(object, event); } void KOpenWithDialog::accept() { if (d->checkAccept()) { QDialog::accept(); } } QString KOpenWithDialog::text() const { if (!d->m_command.isEmpty()) { return d->m_command; } else { return d->edit->text(); } } void KOpenWithDialog::hideNoCloseOnExit() { // uncheck the checkbox because the value could be used when "Run in Terminal" is selected d->nocloseonexit->setChecked(false); d->nocloseonexit->hide(); } void KOpenWithDialog::hideRunInTerminal() { d->terminal->hide(); hideNoCloseOnExit(); } KService::Ptr KOpenWithDialog::service() const { return d->m_pService; } void KOpenWithDialogPrivate::saveComboboxHistory() { KHistoryComboBox *combo = static_cast(edit->comboBox()); if (combo) { combo->addToHistory(edit->text()); KConfigGroup cg(KSharedConfig::openConfig(), QStringLiteral("Open-with settings")); cg.writeEntry("History", combo->historyItems()); writeEntry(cg, "CompletionMode", combo->completionMode()); // don't store the completion-list, as it contains all of KUrlCompletion's // executables cg.sync(); } } #include "moc_kopenwithdialog.cpp" #include "moc_kopenwithdialog_p.cpp" diff --git a/src/widgets/kpropertiesdialog.cpp b/src/widgets/kpropertiesdialog.cpp index 34bd7790..6eededcc 100644 --- a/src/widgets/kpropertiesdialog.cpp +++ b/src/widgets/kpropertiesdialog.cpp @@ -1,3924 +1,3929 @@ /* This file is part of the KDE project Copyright (C) 1998, 1999 Torben Weis Copyright (c) 1999, 2000 Preston Brown Copyright (c) 2000 Simon Hausmann Copyright (c) 2000 David Faure Copyright (c) 2003 Waldo Bastian This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library 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 Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ /* * kpropertiesdialog.cpp * View/Edit Properties of files, locally or remotely * * some FilePermissionsPropsPlugin-changes by * Henner Zeller * some layout management by * Bertrand Leconte * the rest of the layout management, bug fixes, adaptation to libkio, * template feature by * David Faure * More layout, cleanups, and fixes by * Preston Brown * Plugin capability, cleanups and port to KDialog by * Simon Hausmann * KDesktopPropsPlugin by * Waldo Bastian */ #include "kpropertiesdialog.h" #include "kpropertiesdialog_p.h" #include "kio_widgets_debug.h" #include "../pathhelpers_p.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #if HAVE_POSIX_ACL extern "C" { # include # include } #endif #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "ui_checksumswidget.h" #include "ui_kpropertiesdesktopbase.h" #include "ui_kpropertiesdesktopadvbase.h" #if HAVE_POSIX_ACL #include "kacleditwidget.h" #endif #include #include #ifdef Q_OS_WIN #include #include #include #ifdef __GNUC__ # warning TODO: port completely to win32 #endif #endif using namespace KDEPrivate; static QString nameFromFileName(QString nameStr) { if (nameStr.endsWith(QLatin1String(".desktop"))) { nameStr.truncate(nameStr.length() - 8); } if (nameStr.endsWith(QLatin1String(".kdelnk"))) { nameStr.truncate(nameStr.length() - 7); } // Make it human-readable (%2F => '/', ...) nameStr = KIO::decodeFileName(nameStr); return nameStr; } const mode_t KFilePermissionsPropsPlugin::fperm[3][4] = { {S_IRUSR, S_IWUSR, S_IXUSR, S_ISUID}, {S_IRGRP, S_IWGRP, S_IXGRP, S_ISGID}, {S_IROTH, S_IWOTH, S_IXOTH, S_ISVTX} }; class Q_DECL_HIDDEN KPropertiesDialog::KPropertiesDialogPrivate { public: explicit KPropertiesDialogPrivate(KPropertiesDialog *qq) : q(qq) , m_aborted(false) , fileSharePage(nullptr) { } ~KPropertiesDialogPrivate() { } /** * Common initialization for all constructors */ void init(); /** * Inserts all pages in the dialog. */ void insertPages(); KPropertiesDialog * const q; bool m_aborted; QWidget *fileSharePage; /** * The URL of the props dialog (when shown for only one file) */ QUrl m_singleUrl; /** * List of items this props dialog is shown for */ KFileItemList m_items; /** * For templates */ QString m_defaultName; QUrl m_currentDir; /** * List of all plugins inserted ( first one first ) */ QList m_pageList; }; KPropertiesDialog::KPropertiesDialog(const KFileItem &item, QWidget *parent) : KPageDialog(parent), d(new KPropertiesDialogPrivate(this)) { setWindowTitle(i18n("Properties for %1", KIO::decodeFileName(item.name()))); Q_ASSERT(!item.isNull()); d->m_items.append(item); d->m_singleUrl = item.url(); Q_ASSERT(!d->m_singleUrl.isEmpty()); d->init(); } KPropertiesDialog::KPropertiesDialog(const QString &title, QWidget *parent) : KPageDialog(parent), d(new KPropertiesDialogPrivate(this)) { setWindowTitle(i18n("Properties for %1", title)); d->init(); } KPropertiesDialog::KPropertiesDialog(const KFileItemList &_items, QWidget *parent) : KPageDialog(parent), d(new KPropertiesDialogPrivate(this)) { if (_items.count() > 1) { setWindowTitle(i18np("Properties for 1 item", "Properties for %1 Selected Items", _items.count())); } else { setWindowTitle(i18n("Properties for %1", KIO::decodeFileName(_items.first().name()))); } Q_ASSERT(!_items.isEmpty()); d->m_singleUrl = _items.first().url(); Q_ASSERT(!d->m_singleUrl.isEmpty()); d->m_items = _items; d->init(); } KPropertiesDialog::KPropertiesDialog(const QUrl &_url, QWidget *parent) : KPageDialog(parent), d(new KPropertiesDialogPrivate(this)) { setWindowTitle(i18n("Properties for %1", KIO::decodeFileName(_url.fileName()))); d->m_singleUrl = _url; KIO::StatJob *job = KIO::stat(_url); KJobWidgets::setWindow(job, parent); job->exec(); KIO::UDSEntry entry = job->statResult(); d->m_items.append(KFileItem(entry, _url)); d->init(); } KPropertiesDialog::KPropertiesDialog(const QList& urls, QWidget* parent) : KPageDialog(parent), d(new KPropertiesDialogPrivate(this)) { if (urls.count() > 1) { setWindowTitle(i18np("Properties for 1 item", "Properties for %1 Selected Items", urls.count())); } else { setWindowTitle(i18n("Properties for %1", KIO::decodeFileName(urls.first().fileName()))); } Q_ASSERT(!urls.isEmpty()); d->m_singleUrl = urls.first(); Q_ASSERT(!d->m_singleUrl.isEmpty()); foreach (const QUrl& url, urls) { KIO::StatJob *job = KIO::stat(url); KJobWidgets::setWindow(job, parent); job->exec(); KIO::UDSEntry entry = job->statResult(); d->m_items.append(KFileItem(entry, url)); } d->init(); } KPropertiesDialog::KPropertiesDialog(const QUrl &_tempUrl, const QUrl &_currentDir, const QString &_defaultName, QWidget *parent) : KPageDialog(parent), d(new KPropertiesDialogPrivate(this)) { setWindowTitle(i18n("Properties for %1", KIO::decodeFileName(_tempUrl.fileName()))); d->m_singleUrl = _tempUrl; d->m_defaultName = _defaultName; d->m_currentDir = _currentDir; Q_ASSERT(!d->m_singleUrl.isEmpty()); // Create the KFileItem for the _template_ file, in order to read from it. d->m_items.append(KFileItem(d->m_singleUrl)); d->init(); } #ifdef Q_OS_WIN bool showWin32FilePropertyDialog(const QString &fileName) { QString path_ = QDir::toNativeSeparators(QFileInfo(fileName).absoluteFilePath()); #ifndef _WIN32_WCE SHELLEXECUTEINFOW execInfo; #else SHELLEXECUTEINFO execInfo; #endif memset(&execInfo, 0, sizeof(execInfo)); execInfo.cbSize = sizeof(execInfo); #ifndef _WIN32_WCE execInfo.fMask = SEE_MASK_INVOKEIDLIST | SEE_MASK_NOCLOSEPROCESS | SEE_MASK_FLAG_NO_UI; #else execInfo.fMask = SEE_MASK_NOCLOSEPROCESS | SEE_MASK_FLAG_NO_UI; #endif const QString verb(QLatin1String("properties")); execInfo.lpVerb = (LPCWSTR)verb.utf16(); execInfo.lpFile = (LPCWSTR)path_.utf16(); #ifndef _WIN32_WCE return ShellExecuteExW(&execInfo); #else return ShellExecuteEx(&execInfo); //There is no native file property dialog in wince // return false; #endif } #endif bool KPropertiesDialog::showDialog(const KFileItem &item, QWidget *parent, bool modal) { // TODO: do we really want to show the win32 property dialog? // This means we lose metainfo, support for .desktop files, etc. (DF) #ifdef Q_OS_WIN QString localPath = item.localPath(); if (!localPath.isEmpty()) { return showWin32FilePropertyDialog(localPath); } #endif KPropertiesDialog *dlg = new KPropertiesDialog(item, parent); if (modal) { dlg->exec(); } else { dlg->show(); } return true; } bool KPropertiesDialog::showDialog(const QUrl &_url, QWidget *parent, bool modal) { #ifdef Q_OS_WIN if (_url.isLocalFile()) { return showWin32FilePropertyDialog(_url.toLocalFile()); } #endif KPropertiesDialog *dlg = new KPropertiesDialog(_url, parent); if (modal) { dlg->exec(); } else { dlg->show(); } return true; } bool KPropertiesDialog::showDialog(const KFileItemList &_items, QWidget *parent, bool modal) { if (_items.count() == 1) { const KFileItem item = _items.first(); if (item.entry().count() == 0 && item.localPath().isEmpty()) // this remote item wasn't listed by a slave // Let's stat to get more info on the file { return KPropertiesDialog::showDialog(item.url(), parent, modal); } else { return KPropertiesDialog::showDialog(_items.first(), parent, modal); } } KPropertiesDialog *dlg = new KPropertiesDialog(_items, parent); if (modal) { dlg->exec(); } else { dlg->show(); } return true; } bool KPropertiesDialog::showDialog(const QList &urls, QWidget* parent, bool modal) { KPropertiesDialog *dlg = new KPropertiesDialog(urls, parent); if (modal) { dlg->exec(); } else { dlg->show(); } return true; } void KPropertiesDialog::KPropertiesDialogPrivate::init() { q->setFaceType(KPageDialog::Tabbed); insertPages(); KConfigGroup group(KSharedConfig::openConfig(), "KPropertiesDialog"); KWindowConfig::restoreWindowSize(q->windowHandle(), group); } void KPropertiesDialog::showFileSharingPage() { if (d->fileSharePage) { // FIXME: this showFileSharingPage thingy looks broken! (tokoe) // showPage( pageIndex( d->fileSharePage)); } } void KPropertiesDialog::setFileSharingPage(QWidget *page) { d->fileSharePage = page; } void KPropertiesDialog::setFileNameReadOnly(bool ro) { foreach (KPropertiesDialogPlugin *it, d->m_pageList) { if (auto *filePropsPlugin = qobject_cast(it)) { filePropsPlugin->setFileNameReadOnly(ro); } else if (auto *urlPropsPlugin = qobject_cast(it)) { urlPropsPlugin->setFileNameReadOnly(ro); } } } KPropertiesDialog::~KPropertiesDialog() { qDeleteAll(d->m_pageList); delete d; KConfigGroup group(KSharedConfig::openConfig(), "KPropertiesDialog"); KWindowConfig::saveWindowSize(windowHandle(), group, KConfigBase::Persistent); } void KPropertiesDialog::insertPlugin(KPropertiesDialogPlugin *plugin) { - connect(plugin, SIGNAL(changed()), - plugin, SLOT(setDirty())); + connect(plugin, &KPropertiesDialogPlugin::changed, + plugin, QOverload<>::of(&KPropertiesDialogPlugin::setDirty)); d->m_pageList.append(plugin); } QUrl KPropertiesDialog::url() const { return d->m_singleUrl; } KFileItem &KPropertiesDialog::item() { return d->m_items.first(); } KFileItemList KPropertiesDialog::items() const { return d->m_items; } QUrl KPropertiesDialog::currentDir() const { return d->m_currentDir; } QString KPropertiesDialog::defaultName() const { return d->m_defaultName; } bool KPropertiesDialog::canDisplay(const KFileItemList &_items) { // TODO: cache the result of those calls. Currently we parse .desktop files far too many times return KFilePropsPlugin::supports(_items) || KFilePermissionsPropsPlugin::supports(_items) || KDesktopPropsPlugin::supports(_items) || KUrlPropsPlugin::supports(_items) || KDevicePropsPlugin::supports(_items) /* || KPreviewPropsPlugin::supports( _items )*/; } void KPropertiesDialog::slotOk() { accept(); } void KPropertiesDialog::accept() { QList::const_iterator pageListIt; d->m_aborted = false; KFilePropsPlugin *filePropsPlugin = qobject_cast(d->m_pageList.first()); // If any page is dirty, then set the main one (KFilePropsPlugin) as // dirty too. This is what makes it possible to save changes to a global // desktop file into a local one. In other cases, it doesn't hurt. for (pageListIt = d->m_pageList.constBegin(); pageListIt != d->m_pageList.constEnd(); ++pageListIt) { if ((*pageListIt)->isDirty() && filePropsPlugin) { filePropsPlugin->setDirty(); break; } } // Apply the changes in the _normal_ order of the tabs now // This is because in case of renaming a file, KFilePropsPlugin will call // KPropertiesDialog::rename, so other tab will be ok with whatever order // BUT for file copied from templates, we need to do the renaming first ! for (pageListIt = d->m_pageList.constBegin(); pageListIt != d->m_pageList.constEnd() && !d->m_aborted; ++pageListIt) { if ((*pageListIt)->isDirty()) { // qDebug() << "applying changes for " << (*pageListIt)->metaObject()->className(); (*pageListIt)->applyChanges(); // applyChanges may change d->m_aborted. } else { // qDebug() << "skipping page " << (*pageListIt)->metaObject()->className(); } } if (!d->m_aborted && filePropsPlugin) { filePropsPlugin->postApplyChanges(); } if (!d->m_aborted) { emit applied(); emit propertiesClosed(); deleteLater(); // somewhat like Qt::WA_DeleteOnClose would do. KPageDialog::accept(); } // else, keep dialog open for user to fix the problem. } void KPropertiesDialog::slotCancel() { reject(); } void KPropertiesDialog::reject() { emit canceled(); emit propertiesClosed(); deleteLater(); KPageDialog::reject(); } void KPropertiesDialog::KPropertiesDialogPrivate::insertPages() { if (m_items.isEmpty()) { return; } if (KFilePropsPlugin::supports(m_items)) { KPropertiesDialogPlugin *p = new KFilePropsPlugin(q); q->insertPlugin(p); } if (KFilePermissionsPropsPlugin::supports(m_items)) { KPropertiesDialogPlugin *p = new KFilePermissionsPropsPlugin(q); q->insertPlugin(p); } if (KChecksumsPlugin::supports(m_items)) { KPropertiesDialogPlugin *p = new KChecksumsPlugin(q); q->insertPlugin(p); } if (KDesktopPropsPlugin::supports(m_items)) { KPropertiesDialogPlugin *p = new KDesktopPropsPlugin(q); q->insertPlugin(p); } if (KUrlPropsPlugin::supports(m_items)) { KPropertiesDialogPlugin *p = new KUrlPropsPlugin(q); q->insertPlugin(p); } if (KDevicePropsPlugin::supports(m_items)) { KPropertiesDialogPlugin *p = new KDevicePropsPlugin(q); q->insertPlugin(p); } // if ( KPreviewPropsPlugin::supports( m_items ) ) { // KPropertiesDialogPlugin *p = new KPreviewPropsPlugin(q); // q->insertPlugin(p); // } //plugins if (m_items.count() != 1) { return; } const KFileItem item = m_items.first(); const QString mimetype = item.mimetype(); if (mimetype.isEmpty()) { return; } QString query = QStringLiteral( "(((not exist [X-KDE-Protocol]) and (not exist [X-KDE-Protocols])) or ([X-KDE-Protocol] == '%1') or ('%1' in [X-KDE-Protocols]))" ).arg(item.url().scheme()); // qDebug() << "trader query: " << query; const KService::List offers = KMimeTypeTrader::self()->query(mimetype, QStringLiteral("KPropertiesDialog/Plugin"), query); foreach (const KService::Ptr &ptr, offers) { KPropertiesDialogPlugin *plugin = ptr->createInstance(q); if (!plugin) { continue; } plugin->setObjectName(ptr->name()); q->insertPlugin(plugin); } } void KPropertiesDialog::updateUrl(const QUrl &_newUrl) { Q_ASSERT(d->m_items.count() == 1); // qDebug() << "KPropertiesDialog::updateUrl (pre)" << _newUrl; QUrl newUrl = _newUrl; emit saveAs(d->m_singleUrl, newUrl); // qDebug() << "KPropertiesDialog::updateUrl (post)" << newUrl; d->m_singleUrl = newUrl; d->m_items.first().setUrl(newUrl); Q_ASSERT(!d->m_singleUrl.isEmpty()); // If we have an Desktop page, set it dirty, so that a full file is saved locally // Same for a URL page (because of the Name= hack) foreach (KPropertiesDialogPlugin *it, d->m_pageList) { if (qobject_cast(it) || qobject_cast(it)) { //qDebug() << "Setting page dirty"; it->setDirty(); break; } } } void KPropertiesDialog::rename(const QString &_name) { Q_ASSERT(d->m_items.count() == 1); // qDebug() << "KPropertiesDialog::rename " << _name; QUrl newUrl; // if we're creating from a template : use currentdir if (!d->m_currentDir.isEmpty()) { newUrl = d->m_currentDir; newUrl.setPath(concatPaths(newUrl.path(), _name)); } else { // It's a directory, so strip the trailing slash first newUrl = d->m_singleUrl.adjusted(QUrl::StripTrailingSlash); // Now change the filename newUrl = newUrl.adjusted(QUrl::RemoveFilename); // keep trailing slash newUrl.setPath(concatPaths(newUrl.path(), _name)); } updateUrl(newUrl); } void KPropertiesDialog::abortApplying() { d->m_aborted = true; } class Q_DECL_HIDDEN KPropertiesDialogPlugin::KPropertiesDialogPluginPrivate { public: KPropertiesDialogPluginPrivate() { } ~KPropertiesDialogPluginPrivate() { } bool m_bDirty; int fontHeight; }; KPropertiesDialogPlugin::KPropertiesDialogPlugin(KPropertiesDialog *_props) : QObject(_props), d(new KPropertiesDialogPluginPrivate) { properties = _props; d->fontHeight = 2 * properties->fontMetrics().height(); d->m_bDirty = false; } KPropertiesDialogPlugin::~KPropertiesDialogPlugin() { delete d; } #ifndef KIOWIDGETS_NO_DEPRECATED bool KPropertiesDialogPlugin::isDesktopFile(const KFileItem &_item) { return _item.isDesktopFile(); } #endif void KPropertiesDialogPlugin::setDirty(bool b) { d->m_bDirty = b; } void KPropertiesDialogPlugin::setDirty() { d->m_bDirty = true; } bool KPropertiesDialogPlugin::isDirty() const { return d->m_bDirty; } void KPropertiesDialogPlugin::applyChanges() { qCWarning(KIO_WIDGETS) << "applyChanges() not implemented in page !"; } int KPropertiesDialogPlugin::fontHeight() const { return d->fontHeight; } /////////////////////////////////////////////////////////////////////////////// class KFilePropsPlugin::KFilePropsPluginPrivate { public: KFilePropsPluginPrivate() { dirSizeJob = nullptr; dirSizeUpdateTimer = nullptr; m_lined = nullptr; m_capacityBar = nullptr; m_linkTargetLineEdit = nullptr; } ~KFilePropsPluginPrivate() { if (dirSizeJob) { dirSizeJob->kill(); } } KIO::DirectorySizeJob *dirSizeJob; QTimer *dirSizeUpdateTimer; QFrame *m_frame; bool bMultiple; bool bIconChanged; bool bKDesktopMode; bool bDesktopFile; KCapacityBar *m_capacityBar; QString mimeType; QString oldFileName; KLineEdit *m_lined; QLabel *m_fileNameLabel = nullptr; QGridLayout *m_grid = nullptr; QWidget *iconArea; QLabel *m_sizeLabel; QPushButton *m_sizeDetermineButton; QPushButton *m_sizeStopButton; KLineEdit *m_linkTargetLineEdit; QString m_sRelativePath; bool m_bFromTemplate; /** * The initial filename */ QString oldName; }; KFilePropsPlugin::KFilePropsPlugin(KPropertiesDialog *_props) : KPropertiesDialogPlugin(_props), d(new KFilePropsPluginPrivate) { d->bMultiple = (properties->items().count() > 1); d->bIconChanged = false; d->bDesktopFile = KDesktopPropsPlugin::supports(properties->items()); // qDebug() << "KFilePropsPlugin::KFilePropsPlugin bMultiple=" << d->bMultiple; // We set this data from the first item, and we'll // check that the other items match against it, resetting when not. bool isLocal; const KFileItem item = properties->item(); QUrl url = item.mostLocalUrl(isLocal); bool isReallyLocal = item.url().isLocalFile(); bool bDesktopFile = item.isDesktopFile(); mode_t mode = item.mode(); bool hasDirs = item.isDir() && !item.isLink(); bool hasRoot = url.path() == QLatin1String("/"); QString iconStr = item.iconName(); QString directory = properties->url().adjusted(QUrl::RemoveFilename | QUrl::StripTrailingSlash).path(); QString protocol = properties->url().scheme(); d->bKDesktopMode = protocol == QLatin1String("desktop") || properties->currentDir().scheme() == QLatin1String("desktop"); QString mimeComment = item.mimeComment(); d->mimeType = item.mimetype(); KIO::filesize_t totalSize = item.size(); QString magicMimeComment; QMimeDatabase db; if (isLocal) { QMimeType magicMimeType = db.mimeTypeForFile(url.toLocalFile(), QMimeDatabase::MatchContent); if (magicMimeType.isValid() && !magicMimeType.isDefault()) { magicMimeComment = magicMimeType.comment(); } } #ifdef Q_OS_WIN if (isReallyLocal) { directory = QDir::toNativeSeparators(directory.mid(1)); } #endif // Those things only apply to 'single file' mode QString filename; bool isTrash = false; d->m_bFromTemplate = false; // And those only to 'multiple' mode uint iDirCount = hasDirs ? 1 : 0; uint iFileCount = 1 - iDirCount; d->m_frame = new QFrame(); properties->addPage(d->m_frame, i18nc("@title:tab File properties", "&General")); QVBoxLayout *vbl = new QVBoxLayout(d->m_frame); vbl->setMargin(0); vbl->setObjectName(QStringLiteral("vbl")); QGridLayout *grid = new QGridLayout(); // unknown rows d->m_grid = grid; grid->setColumnStretch(0, 0); grid->setColumnStretch(1, 0); grid->setColumnStretch(2, 1); const int spacingHint = d->m_frame->style()->pixelMetric(QStyle::PM_DefaultLayoutSpacing); grid->addItem(new QSpacerItem(spacingHint, 0), 0, 1); vbl->addLayout(grid); int curRow = 0; if (!d->bMultiple) { QString path; if (!d->m_bFromTemplate) { isTrash = (properties->url().scheme() == QLatin1String("trash")); // Extract the full name, but without file: for local files path = properties->url().toDisplayString(QUrl::PreferLocalFile); } else { path = concatPaths(properties->currentDir().path(), properties->defaultName()); directory = properties->currentDir().toDisplayString(QUrl::PreferLocalFile); } if (d->bDesktopFile) { determineRelativePath(path); } // Extract the file name only filename = properties->defaultName(); if (filename.isEmpty()) { // no template const QFileInfo finfo(item.name()); // this gives support for UDS_NAME, e.g. for kio_trash or kio_system filename = finfo.fileName(); // Make sure only the file's name is displayed (#160964). } else { d->m_bFromTemplate = true; setDirty(); // to enforce that the copy happens } d->oldFileName = filename; // Make it human-readable filename = nameFromFileName(filename); if (d->bKDesktopMode && d->bDesktopFile) { KDesktopFile config(url.toLocalFile()); if (config.desktopGroup().hasKey("Name")) { filename = config.readName(); } } d->oldName = filename; } else { // Multiple items: see what they have in common const KFileItemList items = properties->items(); KFileItemList::const_iterator kit = items.begin(); const KFileItemList::const_iterator kend = items.end(); for (++kit /*no need to check the first one again*/; kit != kend; ++kit) { const QUrl url = (*kit).url(); // qDebug() << "KFilePropsPlugin::KFilePropsPlugin " << url.toDisplayString(); // The list of things we check here should match the variables defined // at the beginning of this method. if (url.isLocalFile() != isLocal) { isLocal = false; // not all local } if (bDesktopFile && (*kit).isDesktopFile() != bDesktopFile) { bDesktopFile = false; // not all desktop files } if ((*kit).mode() != mode) { mode = (mode_t)0; } if (KIO::iconNameForUrl(url) != iconStr) { iconStr = QStringLiteral("document-multiple"); } if (url.adjusted(QUrl::RemoveFilename | QUrl::StripTrailingSlash).path() != directory) { directory.clear(); } if (url.scheme() != protocol) { protocol.clear(); } if (!mimeComment.isNull() && (*kit).mimeComment() != mimeComment) { mimeComment.clear(); } if (isLocal && !magicMimeComment.isNull()) { QMimeType magicMimeType = db.mimeTypeForFile(url.toLocalFile(), QMimeDatabase::MatchContent); if (magicMimeType.isValid() && magicMimeType.comment() != magicMimeComment) { magicMimeComment.clear(); } } if (isLocal && url.path() == QLatin1String("/")) { hasRoot = true; } if ((*kit).isDir() && !(*kit).isLink()) { iDirCount++; hasDirs = true; } else { iFileCount++; totalSize += (*kit).size(); } } } if (!isReallyLocal && !protocol.isEmpty()) { directory += QLatin1String(" (") + protocol + QLatin1Char(')'); } if (!isTrash && (bDesktopFile || ((mode & QT_STAT_MASK) == QT_STAT_DIR)) && !d->bMultiple // not implemented for multiple && enableIconButton()) { // #56857 KIconButton *iconButton = new KIconButton(d->m_frame); int bsize = 66 + 2 * iconButton->style()->pixelMetric(QStyle::PM_ButtonMargin); iconButton->setFixedSize(bsize, bsize); iconButton->setIconSize(48); iconButton->setStrictIconSize(false); if (bDesktopFile && isLocal) { const KDesktopFile config(url.toLocalFile()); if (config.hasDeviceType()) { iconButton->setIconType(KIconLoader::Desktop, KIconLoader::Device); } else { iconButton->setIconType(KIconLoader::Desktop, KIconLoader::Application); } } else { iconButton->setIconType(KIconLoader::Desktop, KIconLoader::Place); } iconButton->setIcon(iconStr); d->iconArea = iconButton; - connect(iconButton, SIGNAL(iconChanged(QString)), - this, SLOT(slotIconChanged())); + connect(iconButton, &KIconButton::iconChanged, + this, &KFilePropsPlugin::slotIconChanged); } else { QLabel *iconLabel = new QLabel(d->m_frame); iconLabel->setAlignment(Qt::AlignCenter); int bsize = 66 + 2 * iconLabel->style()->pixelMetric(QStyle::PM_ButtonMargin); iconLabel->setFixedSize(bsize, bsize); iconLabel->setPixmap(QIcon::fromTheme(iconStr).pixmap(48)); d->iconArea = iconLabel; } grid->addWidget(d->iconArea, curRow, 0, Qt::AlignCenter); KFileItemListProperties itemList(KFileItemList() << item); if (d->bMultiple || isTrash || hasRoot || !(d->m_bFromTemplate || itemList.supportsMoving())) { setFileNameReadOnly(true); if (d->bMultiple) { d->m_fileNameLabel->setText(KIO::itemsSummaryString(iFileCount + iDirCount, iFileCount, iDirCount, 0, false)); } } else { d->m_lined = new KLineEdit(d->m_frame); d->m_lined->setObjectName(QStringLiteral("KFilePropsPlugin::nameLineEdit")); d->m_lined->setText(filename); d->m_lined->setFocus(); // Enhanced rename: Don't highlight the file extension. QString extension = db.suffixForFileName(filename); if (!extension.isEmpty()) { d->m_lined->setSelection(0, filename.length() - extension.length() - 1); } else { int lastDot = filename.lastIndexOf(QLatin1Char('.')); if (lastDot > 0) { d->m_lined->setSelection(0, lastDot); } } - connect(d->m_lined, SIGNAL(textChanged(QString)), - this, SLOT(nameFileChanged(QString))); + connect(d->m_lined, &QLineEdit::textChanged, + this, &KFilePropsPlugin::nameFileChanged); grid->addWidget(d->m_lined, curRow, 2); } ++curRow; KSeparator *sep = new KSeparator(Qt::Horizontal, d->m_frame); grid->addWidget(sep, curRow, 0, 1, 3); ++curRow; QLabel *l; if (!mimeComment.isEmpty() && !isTrash) { l = new QLabel(i18n("Type:"), d->m_frame); grid->addWidget(l, curRow, 0, Qt::AlignRight | Qt::AlignTop); QFrame *box = new QFrame(d->m_frame); QVBoxLayout *boxLayout = new QVBoxLayout(box); boxLayout->setSpacing(2); // without that spacing the button literally “sticks” to the label ;) boxLayout->setMargin(0); l = new QLabel(mimeComment, box); l->setTextInteractionFlags(Qt::TextSelectableByMouse | Qt::TextSelectableByKeyboard); grid->addWidget(box, curRow++, 2); QPushButton *button = new QPushButton(box); button->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed); // Minimum still makes the button grow to the entire layout width button->setIcon(QIcon::fromTheme(QStringLiteral("configure"))); boxLayout->addWidget(l); boxLayout->addWidget(button); if (d->mimeType == QLatin1String("application/octet-stream")) { button->setText(i18n("Create New File Type")); } else { button->setText(i18n("File Type Options")); } - connect(button, SIGNAL(clicked()), SLOT(slotEditFileType())); + connect(button, &QAbstractButton::clicked, this, &KFilePropsPlugin::slotEditFileType); if (!KAuthorized::authorizeAction(QStringLiteral("editfiletype"))) { button->hide(); } } if (!magicMimeComment.isEmpty() && magicMimeComment != mimeComment) { l = new QLabel(i18n("Contents:"), d->m_frame); grid->addWidget(l, curRow, 0, Qt::AlignRight); l = new QLabel(magicMimeComment, d->m_frame); grid->addWidget(l, curRow++, 2); } if (!directory.isEmpty()) { l = new QLabel(i18n("Location:"), d->m_frame); grid->addWidget(l, curRow, 0, Qt::AlignRight); l = new KSqueezedTextLabel(directory, d->m_frame); // force the layout direction to be always LTR l->setLayoutDirection(Qt::LeftToRight); // but if we are in RTL mode, align the text to the right // otherwise the text is on the wrong side of the dialog if (properties->layoutDirection() == Qt::RightToLeft) { l->setAlignment(Qt::AlignRight); } l->setTextInteractionFlags(Qt::TextSelectableByMouse | Qt::TextSelectableByKeyboard); grid->addWidget(l, curRow++, 2); } l = new QLabel(i18n("Size:"), d->m_frame); grid->addWidget(l, curRow, 0, Qt::AlignRight | Qt::AlignTop); d->m_sizeLabel = new QLabel(d->m_frame); d->m_sizeLabel->setTextInteractionFlags(Qt::TextSelectableByMouse | Qt::TextSelectableByKeyboard); grid->addWidget(d->m_sizeLabel, curRow++, 2); if (!hasDirs) { // Only files [and symlinks] d->m_sizeLabel->setText(QStringLiteral("%1 (%2)").arg(KIO::convertSize(totalSize), QLocale().toString(totalSize))); d->m_sizeDetermineButton = nullptr; d->m_sizeStopButton = nullptr; } else { // Directory QHBoxLayout *sizelay = new QHBoxLayout(); grid->addLayout(sizelay, curRow++, 2); // buttons d->m_sizeDetermineButton = new QPushButton(i18n("Calculate"), d->m_frame); d->m_sizeStopButton = new QPushButton(i18n("Stop"), d->m_frame); - connect(d->m_sizeDetermineButton, SIGNAL(clicked()), this, SLOT(slotSizeDetermine())); - connect(d->m_sizeStopButton, SIGNAL(clicked()), this, SLOT(slotSizeStop())); + connect(d->m_sizeDetermineButton, &QAbstractButton::clicked, this, &KFilePropsPlugin::slotSizeDetermine); + connect(d->m_sizeStopButton, &QAbstractButton::clicked, this, &KFilePropsPlugin::slotSizeStop); sizelay->addWidget(d->m_sizeDetermineButton, 0); sizelay->addWidget(d->m_sizeStopButton, 0); sizelay->addStretch(10); // so that the buttons don't grow horizontally // auto-launch for local dirs only, and not for '/' if (isLocal && !hasRoot) { d->m_sizeDetermineButton->setText(i18n("Refresh")); slotSizeDetermine(); } else { d->m_sizeStopButton->setEnabled(false); } } if (!d->bMultiple && item.isLink()) { l = new QLabel(i18n("Points to:"), d->m_frame); grid->addWidget(l, curRow, 0, Qt::AlignRight); d->m_linkTargetLineEdit = new KLineEdit(item.linkDest(), d->m_frame); grid->addWidget(d->m_linkTargetLineEdit, curRow++, 2); - connect(d->m_linkTargetLineEdit, SIGNAL(textChanged(QString)), this, SLOT(setDirty())); + connect(d->m_linkTargetLineEdit, &QLineEdit::textChanged, + this, QOverload<>::of(&KFilePropsPlugin::setDirty)); } if (!d->bMultiple) { // Dates for multiple don't make much sense... QDateTime dt = item.time(KFileItem::CreationTime); if (!dt.isNull()) { l = new QLabel(i18n("Created:"), d->m_frame); grid->addWidget(l, curRow, 0, Qt::AlignRight); l = new QLabel(dt.toString(Qt::DefaultLocaleLongDate), d->m_frame); grid->addWidget(l, curRow++, 2); } dt = item.time(KFileItem::ModificationTime); if (!dt.isNull()) { l = new QLabel(i18n("Modified:"), d->m_frame); grid->addWidget(l, curRow, 0, Qt::AlignRight); l = new QLabel(dt.toString(Qt::DefaultLocaleLongDate), d->m_frame); l->setTextInteractionFlags(Qt::TextSelectableByMouse | Qt::TextSelectableByKeyboard); grid->addWidget(l, curRow++, 2); } dt = item.time(KFileItem::AccessTime); if (!dt.isNull()) { l = new QLabel(i18n("Accessed:"), d->m_frame); grid->addWidget(l, curRow, 0, Qt::AlignRight); l = new QLabel(dt.toString(Qt::DefaultLocaleLongDate), d->m_frame); l->setTextInteractionFlags(Qt::TextSelectableByMouse | Qt::TextSelectableByKeyboard); grid->addWidget(l, curRow++, 2); } } if (hasDirs) { // only for directories sep = new KSeparator(Qt::Horizontal, d->m_frame); grid->addWidget(sep, curRow, 0, 1, 3); ++curRow; if (isLocal) { KMountPoint::Ptr mp = KMountPoint::currentMountPoints().findByPath(url.toLocalFile()); l = new QLabel(i18n("File System:"), d->m_frame); grid->addWidget(l, curRow, 0, Qt::AlignRight); l = new QLabel(d->m_frame); grid->addWidget(l, curRow++, 2); l->setTextInteractionFlags(Qt::TextSelectableByMouse | Qt::TextSelectableByKeyboard); l->setText(mp->mountType()); if (mp) { l = new QLabel(i18n("Mounted on:"), d->m_frame); grid->addWidget(l, curRow, 0, Qt::AlignRight); l = new KSqueezedTextLabel(mp->mountPoint(), d->m_frame); l->setTextInteractionFlags(Qt::TextSelectableByMouse | Qt::TextSelectableByKeyboard); grid->addWidget(l, curRow++, 2); l = new QLabel(i18n("Mounted from:"), d->m_frame); grid->addWidget(l, curRow, 0, Qt::AlignRight); l = new QLabel(d->m_frame); grid->addWidget(l, curRow++, 2); l->setTextInteractionFlags(Qt::TextSelectableByMouse | Qt::TextSelectableByKeyboard); l->setText(mp->mountedFrom()); } } l = new QLabel(i18n("Device usage:"), d->m_frame); grid->addWidget(l, curRow, 0, Qt::AlignRight); d->m_capacityBar = new KCapacityBar(KCapacityBar::DrawTextOutline, d->m_frame); d->m_capacityBar->setText(i18nc("@info:status", "Unknown size")); grid->addWidget(d->m_capacityBar, curRow++, 2); KIO::FileSystemFreeSpaceJob *job = KIO::fileSystemFreeSpace(url); connect(job, &KIO::FileSystemFreeSpaceJob::result, this, &KFilePropsPlugin::slotFreeSpaceResult); } vbl->addStretch(1); } bool KFilePropsPlugin::enableIconButton() const { bool iconEnabled = false; const KFileItem item = properties->item(); // If the current item is a directory, check if it's writable, // so we can create/update a .directory // Current item is a file, same thing: check if it is writable if (item.isWritable()) { iconEnabled = true; } return iconEnabled; } // QString KFilePropsPlugin::tabName () const // { // return i18n ("&General"); // } void KFilePropsPlugin::setFileNameReadOnly(bool ro) { Q_ASSERT(ro); // false isn't supported if (ro && !d->m_fileNameLabel) { Q_ASSERT(!d->m_bFromTemplate); delete d->m_lined; d->m_lined = nullptr; d->m_fileNameLabel = new QLabel(d->m_frame); d->m_fileNameLabel->setTextInteractionFlags(Qt::TextSelectableByMouse | Qt::TextSelectableByKeyboard); d->m_fileNameLabel->setText(d->oldName); // will get overwritten if d->bMultiple d->m_grid->addWidget(d->m_fileNameLabel, 0, 2); } } void KFilePropsPlugin::slotEditFileType() { QString mime; if (d->mimeType == QLatin1String("application/octet-stream")) { const int pos = d->oldFileName.lastIndexOf(QLatin1Char('.')); if (pos != -1) { mime = QLatin1Char('*') + d->oldFileName.mid(pos); } else { mime = QStringLiteral("*"); } } else { mime = d->mimeType; } KMimeTypeEditor::editMimeType(mime, properties->window()); } void KFilePropsPlugin::slotIconChanged() { d->bIconChanged = true; emit changed(); } void KFilePropsPlugin::nameFileChanged(const QString &text) { properties->buttonBox()->button(QDialogButtonBox::Ok)->setEnabled(!text.isEmpty()); emit changed(); } static QString relativeAppsLocation(const QString &file) { const QString canonical = QFileInfo(file).canonicalFilePath(); Q_FOREACH (const QString &base, QStandardPaths::standardLocations(QStandardPaths::ApplicationsLocation)) { QDir base_dir = QDir(base); if (base_dir.exists() && canonical.startsWith(base_dir.canonicalPath())) { return canonical.mid(base.length() + 1); } } return QString(); // return empty if the file is not in apps } void KFilePropsPlugin::determineRelativePath(const QString &path) { // now let's make it relative d->m_sRelativePath = relativeAppsLocation(path); } void KFilePropsPlugin::slotFreeSpaceResult(KIO::Job *job, KIO::filesize_t size, KIO::filesize_t available) { if (!job->error()) { const quint64 used = size - available; const int percentUsed = qRound(100.0 * qreal(used) / qreal(size)); d->m_capacityBar->setText( i18nc("Available space out of total partition size (percent used)", "%1 free of %2 (%3% used)", KIO::convertSize(available), KIO::convertSize(size), percentUsed)); d->m_capacityBar->setValue(percentUsed); } else { d->m_capacityBar->setText(i18nc("@info:status", "Unknown size")); d->m_capacityBar->setValue(0); } } void KFilePropsPlugin::slotDirSizeUpdate() { KIO::filesize_t totalSize = d->dirSizeJob->totalSize(); KIO::filesize_t totalFiles = d->dirSizeJob->totalFiles(); KIO::filesize_t totalSubdirs = d->dirSizeJob->totalSubdirs(); d->m_sizeLabel->setText( i18n("Calculating... %1 (%2)\n%3, %4", KIO::convertSize(totalSize), QLocale().toString(totalSize), i18np("1 file", "%1 files", totalFiles), i18np("1 sub-folder", "%1 sub-folders", totalSubdirs))); } void KFilePropsPlugin::slotDirSizeFinished(KJob *job) { if (job->error()) { d->m_sizeLabel->setText(job->errorString()); } else { KIO::filesize_t totalSize = d->dirSizeJob->totalSize(); KIO::filesize_t totalFiles = d->dirSizeJob->totalFiles(); KIO::filesize_t totalSubdirs = d->dirSizeJob->totalSubdirs(); d->m_sizeLabel->setText(QStringLiteral("%1 (%2)\n%3, %4") .arg(KIO::convertSize(totalSize), QLocale().toString(totalSize), i18np("1 file", "%1 files", totalFiles), i18np("1 sub-folder", "%1 sub-folders", totalSubdirs))); } d->m_sizeStopButton->setEnabled(false); // just in case you change something and try again :) d->m_sizeDetermineButton->setText(i18n("Refresh")); d->m_sizeDetermineButton->setEnabled(true); d->dirSizeJob = nullptr; delete d->dirSizeUpdateTimer; d->dirSizeUpdateTimer = nullptr; } void KFilePropsPlugin::slotSizeDetermine() { d->m_sizeLabel->setText(i18n("Calculating...")); // qDebug() << "properties->item()=" << properties->item() << "URL=" << properties->item().url(); d->dirSizeJob = KIO::directorySize(properties->items()); d->dirSizeUpdateTimer = new QTimer(this); - connect(d->dirSizeUpdateTimer, SIGNAL(timeout()), - SLOT(slotDirSizeUpdate())); + connect(d->dirSizeUpdateTimer, &QTimer::timeout, + this, &KFilePropsPlugin::slotDirSizeUpdate); d->dirSizeUpdateTimer->start(500); - connect(d->dirSizeJob, SIGNAL(result(KJob*)), - SLOT(slotDirSizeFinished(KJob*))); + connect(d->dirSizeJob, &KJob::result, + this, &KFilePropsPlugin::slotDirSizeFinished); d->m_sizeStopButton->setEnabled(true); d->m_sizeDetermineButton->setEnabled(false); // also update the "Free disk space" display if (d->m_capacityBar) { const KFileItem item = properties->item(); KIO::FileSystemFreeSpaceJob *job = KIO::fileSystemFreeSpace(item.url()); connect(job, &KIO::FileSystemFreeSpaceJob::result, this, &KFilePropsPlugin::slotFreeSpaceResult); } } void KFilePropsPlugin::slotSizeStop() { if (d->dirSizeJob) { KIO::filesize_t totalSize = d->dirSizeJob->totalSize(); d->m_sizeLabel->setText(i18n("At least %1", KIO::convertSize(totalSize))); d->dirSizeJob->kill(); d->dirSizeJob = nullptr; } if (d->dirSizeUpdateTimer) { d->dirSizeUpdateTimer->stop(); } d->m_sizeStopButton->setEnabled(false); d->m_sizeDetermineButton->setEnabled(true); } KFilePropsPlugin::~KFilePropsPlugin() { delete d; } bool KFilePropsPlugin::supports(const KFileItemList & /*_items*/) { return true; } void KFilePropsPlugin::applyChanges() { if (d->dirSizeJob) { slotSizeStop(); } // qDebug() << "KFilePropsPlugin::applyChanges"; if (d->m_lined) { QString n = d->m_lined->text(); // Remove trailing spaces (#4345) while (! n.isEmpty() && n[n.length() - 1].isSpace()) { n.truncate(n.length() - 1); } if (n.isEmpty()) { KMessageBox::sorry(properties, i18n("The new file name is empty.")); properties->abortApplying(); return; } // Do we need to rename the file ? // qDebug() << "oldname = " << d->oldName; // qDebug() << "newname = " << n; if (d->oldName != n || d->m_bFromTemplate) { // true for any from-template file - KIO::Job *job = nullptr; + KIO::CopyJob *job = nullptr; QUrl oldurl = properties->url(); QString newFileName = KIO::encodeFileName(n); if (d->bDesktopFile && !newFileName.endsWith(QLatin1String(".desktop")) && !newFileName.endsWith(QLatin1String(".kdelnk"))) { newFileName += QLatin1String(".desktop"); } // Tell properties. Warning, this changes the result of properties->url() ! properties->rename(newFileName); // Update also relative path (for apps) if (!d->m_sRelativePath.isEmpty()) { determineRelativePath(properties->url().toLocalFile()); } // qDebug() << "New URL = " << properties->url(); // qDebug() << "old = " << oldurl.url(); // Don't remove the template !! if (!d->m_bFromTemplate) { // (normal renaming) job = KIO::moveAs(oldurl, properties->url()); } else { // Copying a template job = KIO::copyAs(oldurl, properties->url()); } - connect(job, SIGNAL(result(KJob*)), - SLOT(slotCopyFinished(KJob*))); - connect(job, SIGNAL(renamed(KIO::Job*,QUrl,QUrl)), - SLOT(slotFileRenamed(KIO::Job*,QUrl,QUrl))); + connect(job, &KJob::result, + this, &KFilePropsPlugin::slotCopyFinished); + connect(job, &KIO::CopyJob::renamed, + this, &KFilePropsPlugin::slotFileRenamed); // wait for job QEventLoop eventLoop; - connect(this, SIGNAL(leaveModality()), - &eventLoop, SLOT(quit())); + connect(this, &KFilePropsPlugin::leaveModality, + &eventLoop, &QEventLoop::quit); eventLoop.exec(QEventLoop::ExcludeUserInputEvents); return; } properties->updateUrl(properties->url()); // Update also relative path (for apps) if (!d->m_sRelativePath.isEmpty()) { determineRelativePath(properties->url().toLocalFile()); } } // No job, keep going slotCopyFinished(nullptr); } void KFilePropsPlugin::slotCopyFinished(KJob *job) { // qDebug() << "KFilePropsPlugin::slotCopyFinished"; if (job) { // allow apply() to return emit leaveModality(); if (job->error()) { job->uiDelegate()->showErrorMessage(); // Didn't work. Revert the URL to the old one properties->updateUrl(static_cast(job)->srcUrls().constFirst()); properties->abortApplying(); // Don't apply the changes to the wrong file ! return; } } Q_ASSERT(!properties->item().isNull()); Q_ASSERT(!properties->item().url().isEmpty()); // Save the file locally if (d->bDesktopFile && !d->m_sRelativePath.isEmpty()) { // qDebug() << "KFilePropsPlugin::slotCopyFinished " << d->m_sRelativePath; const QString newPath = QStandardPaths::writableLocation(QStandardPaths::ApplicationsLocation) + QLatin1Char('/') + d->m_sRelativePath; const QUrl newURL = QUrl::fromLocalFile(newPath); // qDebug() << "KFilePropsPlugin::slotCopyFinished path=" << newURL; properties->updateUrl(newURL); } if (d->bKDesktopMode && d->bDesktopFile) { // Renamed? Update Name field // Note: The desktop ioslave does this as well, but not when // the file is copied from a template. if (d->m_bFromTemplate) { KIO::StatJob *job = KIO::stat(properties->url()); job->exec(); KIO::UDSEntry entry = job->statResult(); KFileItem item(entry, properties->url()); KDesktopFile config(item.localPath()); KConfigGroup cg = config.desktopGroup(); QString nameStr = nameFromFileName(properties->url().fileName()); cg.writeEntry("Name", nameStr); cg.writeEntry("Name", nameStr, KConfigGroup::Persistent | KConfigGroup::Localized); } } if (d->m_linkTargetLineEdit && !d->bMultiple) { const KFileItem item = properties->item(); const QString newTarget = d->m_linkTargetLineEdit->text(); if (newTarget != item.linkDest()) { // qDebug() << "Updating target of symlink to" << newTarget; KIO::Job *job = KIO::symlink(newTarget, item.url(), KIO::Overwrite); job->uiDelegate()->setAutoErrorHandlingEnabled(true); job->exec(); } } // "Link to Application" templates need to be made executable // Instead of matching against a filename we check if the destination // is an Application now. if (d->m_bFromTemplate) { // destination is not necessarily local, use the src template KDesktopFile templateResult(static_cast(job)->srcUrls().constFirst().toLocalFile()); if (templateResult.hasApplicationType()) { // We can either stat the file and add the +x bit or use the larger chmod() job // with a umask designed to only touch u+x. This is only one KIO job, so let's // do that. KFileItem appLink(properties->item()); KFileItemList fileItemList; fileItemList << appLink; // first 0100 adds u+x, second 0100 only allows chmod to change u+x KIO::Job *chmodJob = KIO::chmod(fileItemList, 0100, 0100, QString(), QString(), KIO::HideProgressInfo); chmodJob->exec(); } } } void KFilePropsPlugin::applyIconChanges() { KIconButton *iconButton = qobject_cast(d->iconArea); if (!iconButton || !d->bIconChanged) { return; } // handle icon changes - only local files (or pseudo-local) for now // TODO: Use KTempFile and KIO::file_copy with overwrite = true QUrl url = properties->url(); KIO::StatJob *job = KIO::mostLocalUrl(url); KJobWidgets::setWindow(job, properties); job->exec(); url = job->mostLocalUrl(); if (url.isLocalFile()) { QString path; if ((properties->item().mode() & QT_STAT_MASK) == QT_STAT_DIR) { path = url.toLocalFile() + QLatin1String("/.directory"); // don't call updateUrl because the other tabs (i.e. permissions) // apply to the directory, not the .directory file. } else { path = url.toLocalFile(); } // Get the default image QMimeDatabase db; QString str = db.mimeTypeForFile(url.toLocalFile(), QMimeDatabase::MatchExtension).iconName(); // Is it another one than the default ? QString sIcon; if (str != iconButton->icon()) { sIcon = iconButton->icon(); } // (otherwise write empty value) // qDebug() << "**" << path << "**"; // If default icon and no .directory file -> don't create one if (!sIcon.isEmpty() || QFile::exists(path)) { KDesktopFile cfg(path); // qDebug() << "sIcon = " << (sIcon); // qDebug() << "str = " << (str); cfg.desktopGroup().writeEntry("Icon", sIcon); cfg.sync(); cfg.reparseConfiguration(); if (cfg.desktopGroup().readEntry("Icon") != sIcon) { KMessageBox::sorry(nullptr, i18n("Could not save properties. You do not " "have sufficient access to write to %1.", path)); } } } } void KFilePropsPlugin::slotFileRenamed(KIO::Job *, const QUrl &, const QUrl &newUrl) { // This is called in case of an existing local file during the copy/move operation, // if the user chooses Rename. properties->updateUrl(newUrl); } void KFilePropsPlugin::postApplyChanges() { // Save the icon only after applying the permissions changes (#46192) applyIconChanges(); const KFileItemList items = properties->items(); const QList lst = items.urlList(); org::kde::KDirNotify::emitFilesChanged(QList(lst)); } class KFilePermissionsPropsPlugin::KFilePermissionsPropsPluginPrivate { public: KFilePermissionsPropsPluginPrivate() { } ~KFilePermissionsPropsPluginPrivate() { } QFrame *m_frame; QCheckBox *cbRecursive; QLabel *explanationLabel; KComboBox *ownerPermCombo, *groupPermCombo, *othersPermCombo; QCheckBox *extraCheckbox; mode_t partialPermissions; KFilePermissionsPropsPlugin::PermissionsMode pmode; bool canChangePermissions; bool isIrregular; bool hasExtendedACL; KACL extendedACL; KACL defaultACL; bool fileSystemSupportsACLs; KComboBox *grpCombo; KLineEdit *usrEdit; KLineEdit *grpEdit; // Old permissions mode_t permissions; // Old group QString strGroup; // Old owner QString strOwner; }; #define UniOwner (S_IRUSR|S_IWUSR|S_IXUSR) #define UniGroup (S_IRGRP|S_IWGRP|S_IXGRP) #define UniOthers (S_IROTH|S_IWOTH|S_IXOTH) #define UniRead (S_IRUSR|S_IRGRP|S_IROTH) #define UniWrite (S_IWUSR|S_IWGRP|S_IWOTH) #define UniExec (S_IXUSR|S_IXGRP|S_IXOTH) #define UniSpecial (S_ISUID|S_ISGID|S_ISVTX) // synced with PermissionsTarget const mode_t KFilePermissionsPropsPlugin::permissionsMasks[3] = {UniOwner, UniGroup, UniOthers}; const mode_t KFilePermissionsPropsPlugin::standardPermissions[4] = { 0, UniRead, UniRead | UniWrite, (mode_t) - 1 }; // synced with PermissionsMode and standardPermissions const char *const KFilePermissionsPropsPlugin::permissionsTexts[4][4] = { { I18N_NOOP("No Access"), I18N_NOOP("Can Only View"), I18N_NOOP("Can View & Modify"), nullptr }, { I18N_NOOP("No Access"), I18N_NOOP("Can Only View Content"), I18N_NOOP("Can View & Modify Content"), nullptr }, { nullptr, nullptr, nullptr, nullptr}, // no texts for links { I18N_NOOP("No Access"), I18N_NOOP("Can Only View/Read Content"), I18N_NOOP("Can View/Read & Modify/Write"), nullptr } }; KFilePermissionsPropsPlugin::KFilePermissionsPropsPlugin(KPropertiesDialog *_props) : KPropertiesDialogPlugin(_props), d(new KFilePermissionsPropsPluginPrivate) { d->cbRecursive = nullptr; d->grpCombo = nullptr; d->grpEdit = nullptr; d->usrEdit = nullptr; bool isLocal = properties->url().isLocalFile(); bool isTrash = (properties->url().scheme() == QLatin1String("trash")); KUser myself(KUser::UseEffectiveUID); const bool IamRoot = myself.isSuperUser(); const KFileItem item = properties->item(); bool isLink = item.isLink(); bool isDir = item.isDir(); // all dirs bool hasDir = item.isDir(); // at least one dir d->permissions = item.permissions(); // common permissions to all files d->partialPermissions = d->permissions; // permissions that only some files have (at first we take everything) d->isIrregular = isIrregular(d->permissions, isDir, isLink); d->strOwner = item.user(); d->strGroup = item.group(); d->hasExtendedACL = item.ACL().isExtended() || item.defaultACL().isValid(); d->extendedACL = item.ACL(); d->defaultACL = item.defaultACL(); d->fileSystemSupportsACLs = false; if (properties->items().count() > 1) { // Multiple items: see what they have in common const KFileItemList items = properties->items(); KFileItemList::const_iterator it = items.begin(); const KFileItemList::const_iterator kend = items.end(); for (++it /*no need to check the first one again*/; it != kend; ++it) { if (!d->isIrregular) d->isIrregular |= isIrregular((*it).permissions(), (*it).isDir() == isDir, (*it).isLink() == isLink); d->hasExtendedACL = d->hasExtendedACL || (*it).hasExtendedACL(); if ((*it).isLink() != isLink) { isLink = false; } if ((*it).isDir() != isDir) { isDir = false; } hasDir |= (*it).isDir(); if ((*it).permissions() != d->permissions) { d->permissions &= (*it).permissions(); d->partialPermissions |= (*it).permissions(); } if ((*it).user() != d->strOwner) { d->strOwner.clear(); } if ((*it).group() != d->strGroup) { d->strGroup.clear(); } } } if (isLink) { d->pmode = PermissionsOnlyLinks; } else if (isDir) { d->pmode = PermissionsOnlyDirs; } else if (hasDir) { d->pmode = PermissionsMixed; } else { d->pmode = PermissionsOnlyFiles; } // keep only what's not in the common permissions d->partialPermissions = d->partialPermissions & ~d->permissions; bool isMyFile = false; if (isLocal && !d->strOwner.isEmpty()) { // local files, and all owned by the same person if (myself.isValid()) { isMyFile = (d->strOwner == myself.loginName()); } else { qCWarning(KIO_WIDGETS) << "I don't exist ?! geteuid=" << KUserId::currentEffectiveUserId().toString(); } } else { //We don't know, for remote files, if they are ours or not. //So we let the user change permissions, and //KIO::chmod will tell, if he had no right to do it. isMyFile = true; } d->canChangePermissions = (isMyFile || IamRoot) && (!isLink); // create GUI d->m_frame = new QFrame(); properties->addPage(d->m_frame, i18n("&Permissions")); QBoxLayout *box = new QVBoxLayout(d->m_frame); box->setMargin(0); QWidget *l; QLabel *lbl; QGroupBox *gb; QGridLayout *gl; QPushButton *pbAdvancedPerm = nullptr; /* Group: Access Permissions */ gb = new QGroupBox(i18n("Access Permissions"), d->m_frame); box->addWidget(gb); gl = new QGridLayout(gb); gl->setColumnStretch(1, 1); l = d->explanationLabel = new QLabel(gb); if (isLink) d->explanationLabel->setText(i18np("This file is a link and does not have permissions.", "All files are links and do not have permissions.", properties->items().count())); else if (!d->canChangePermissions) { d->explanationLabel->setText(i18n("Only the owner can change permissions.")); } gl->addWidget(l, 0, 0, 1, 2); lbl = new QLabel(i18n("O&wner:"), gb); gl->addWidget(lbl, 1, 0, Qt::AlignRight); l = d->ownerPermCombo = new KComboBox(gb); lbl->setBuddy(l); gl->addWidget(l, 1, 1); - connect(l, SIGNAL(activated(int)), this, SIGNAL(changed())); + connect(d->ownerPermCombo, QOverload::of(&QComboBox::activated), + this, &KPropertiesDialogPlugin::changed); l->setWhatsThis(i18n("Specifies the actions that the owner is allowed to do.")); lbl = new QLabel(i18n("Gro&up:"), gb); gl->addWidget(lbl, 2, 0, Qt::AlignRight); l = d->groupPermCombo = new KComboBox(gb); lbl->setBuddy(l); gl->addWidget(l, 2, 1); - connect(l, SIGNAL(activated(int)), this, SIGNAL(changed())); + connect(d->groupPermCombo, QOverload::of(&QComboBox::activated), + this, &KPropertiesDialogPlugin::changed); l->setWhatsThis(i18n("Specifies the actions that the members of the group are allowed to do.")); lbl = new QLabel(i18n("O&thers:"), gb); gl->addWidget(lbl, 3, 0, Qt::AlignRight); l = d->othersPermCombo = new KComboBox(gb); lbl->setBuddy(l); gl->addWidget(l, 3, 1); - connect(l, SIGNAL(activated(int)), this, SIGNAL(changed())); + connect(d->othersPermCombo, QOverload::of(&QComboBox::activated), + this, &KPropertiesDialogPlugin::changed); l->setWhatsThis(i18n("Specifies the actions that all users, who are neither " "owner nor in the group, are allowed to do.")); if (!isLink) { l = d->extraCheckbox = new QCheckBox(hasDir ? i18n("Only own&er can rename and delete folder content") : i18n("Is &executable"), gb); - connect(d->extraCheckbox, SIGNAL(clicked()), this, SIGNAL(changed())); + connect(d->extraCheckbox, &QAbstractButton::clicked, this, &KPropertiesDialogPlugin::changed); gl->addWidget(l, 4, 1); l->setWhatsThis(hasDir ? i18n("Enable this option to allow only the folder's owner to " "delete or rename the contained files and folders. Other " "users can only add new files, which requires the 'Modify " "Content' permission.") : i18n("Enable this option to mark the file as executable. This only makes " "sense for programs and scripts. It is required when you want to " "execute them.")); QLayoutItem *spacer = new QSpacerItem(0, 20, QSizePolicy::Minimum, QSizePolicy::Expanding); gl->addItem(spacer, 5, 0, 1, 3); pbAdvancedPerm = new QPushButton(i18n("A&dvanced Permissions"), gb); gl->addWidget(pbAdvancedPerm, 6, 0, 1, 2, Qt::AlignRight); - connect(pbAdvancedPerm, SIGNAL(clicked()), this, SLOT(slotShowAdvancedPermissions())); + connect(pbAdvancedPerm, &QAbstractButton::clicked, + this, &KFilePermissionsPropsPlugin::slotShowAdvancedPermissions); } else { d->extraCheckbox = nullptr; } /**** Group: Ownership ****/ gb = new QGroupBox(i18n("Ownership"), d->m_frame); box->addWidget(gb); gl = new QGridLayout(gb); gl->addItem(new QSpacerItem(0, 10), 0, 0); /*** Set Owner ***/ l = new QLabel(i18n("User:"), gb); gl->addWidget(l, 1, 0, Qt::AlignRight); /* GJ: Don't autocomplete more than 1000 users. This is a kind of random * value. Huge sites having 10.000+ user have a fair chance of using NIS, * (possibly) making this unacceptably slow. * OTOH, it is nice to offer this functionality for the standard user. */ int maxEntries = 1000; /* File owner: For root, offer a KLineEdit with autocompletion. * For a user, who can never chown() a file, offer a QLabel. */ if (IamRoot && isLocal) { d->usrEdit = new KLineEdit(gb); KCompletion *kcom = d->usrEdit->completionObject(); kcom->setOrder(KCompletion::Sorted); QStringList userNames = KUser::allUserNames(maxEntries); kcom->setItems(userNames); d->usrEdit->setCompletionMode((userNames.size() < maxEntries) ? KCompletion::CompletionAuto : KCompletion::CompletionNone); d->usrEdit->setText(d->strOwner); gl->addWidget(d->usrEdit, 1, 1); - connect(d->usrEdit, SIGNAL(textChanged(QString)), - this, SIGNAL(changed())); + connect(d->usrEdit, &QLineEdit::textChanged, + this, &KPropertiesDialogPlugin::changed); } else { l = new QLabel(d->strOwner, gb); gl->addWidget(l, 1, 1); } /*** Set Group ***/ QStringList groupList; KUser user(KUser::UseEffectiveUID); const bool isMyGroup = user.groupNames().contains(d->strGroup); /* add the group the file currently belongs to .. * .. if it is not there already */ if (!isMyGroup) { groupList += d->strGroup; } l = new QLabel(i18n("Group:"), gb); gl->addWidget(l, 2, 0, Qt::AlignRight); /* Set group: if possible to change: * - Offer a KLineEdit for root, since he can change to any group. * - Offer a KComboBox for a normal user, since he can change to a fixed * (small) set of groups only. * If not changeable: offer a QLabel. */ if (IamRoot && isLocal) { d->grpEdit = new KLineEdit(gb); KCompletion *kcom = new KCompletion; kcom->setItems(groupList); d->grpEdit->setCompletionObject(kcom, true); d->grpEdit->setAutoDeleteCompletionObject(true); d->grpEdit->setCompletionMode(KCompletion::CompletionAuto); d->grpEdit->setText(d->strGroup); gl->addWidget(d->grpEdit, 2, 1); - connect(d->grpEdit, SIGNAL(textChanged(QString)), - this, SIGNAL(changed())); + connect(d->grpEdit, &QLineEdit::textChanged, + this, &KPropertiesDialogPlugin::changed); } else if ((groupList.count() > 1) && isMyFile && isLocal) { d->grpCombo = new KComboBox(gb); d->grpCombo->setObjectName(QStringLiteral("combogrouplist")); d->grpCombo->addItems(groupList); d->grpCombo->setCurrentIndex(groupList.indexOf(d->strGroup)); gl->addWidget(d->grpCombo, 2, 1); - connect(d->grpCombo, SIGNAL(activated(int)), - this, SIGNAL(changed())); + connect(d->grpCombo, QOverload::of(&QComboBox::activated), + this, &KPropertiesDialogPlugin::changed); } else { l = new QLabel(d->strGroup, gb); gl->addWidget(l, 2, 1); } gl->setColumnStretch(2, 10); // "Apply recursive" checkbox if (hasDir && !isLink && !isTrash) { d->cbRecursive = new QCheckBox(i18n("Apply changes to all subfolders and their contents"), d->m_frame); - connect(d->cbRecursive, SIGNAL(clicked()), this, SIGNAL(changed())); + connect(d->cbRecursive, &QAbstractButton::clicked, this, &KPropertiesDialogPlugin::changed); box->addWidget(d->cbRecursive); } updateAccessControls(); if (isTrash) { //don't allow to change properties for file into trash enableAccessControls(false); if (pbAdvancedPerm) { pbAdvancedPerm->setEnabled(false); } } box->addStretch(10); } #if HAVE_POSIX_ACL static bool fileSystemSupportsACL(const QByteArray &path) { bool fileSystemSupportsACLs = false; #ifdef Q_OS_FREEBSD struct statfs buf; fileSystemSupportsACLs = (statfs(path.data(), &buf) == 0) && (buf.f_flags & MNT_ACLS); #elif defined Q_OS_MACOS fileSystemSupportsACLs = getxattr(path.data(), "system.posix_acl_access", nullptr, 0, 0, XATTR_NOFOLLOW) >= 0 || errno == ENODATA; #else fileSystemSupportsACLs = getxattr(path.data(), "system.posix_acl_access", nullptr, 0) >= 0 || errno == ENODATA; #endif return fileSystemSupportsACLs; } #endif void KFilePermissionsPropsPlugin::slotShowAdvancedPermissions() { bool isDir = (d->pmode == PermissionsOnlyDirs) || (d->pmode == PermissionsMixed); QDialog dlg(properties); dlg.setModal(true); dlg.setWindowTitle(i18n("Advanced Permissions")); QLabel *l, *cl[3]; QGroupBox *gb; QGridLayout *gl; QVBoxLayout *vbox = new QVBoxLayout; dlg.setLayout(vbox); // Group: Access Permissions gb = new QGroupBox(i18n("Access Permissions"), &dlg); vbox->addWidget(gb); gl = new QGridLayout(gb); gl->addItem(new QSpacerItem(0, 10), 0, 0); QVector theNotSpecials; l = new QLabel(i18n("Class"), gb); gl->addWidget(l, 1, 0); theNotSpecials.append(l); QString readWhatsThis; QString readLabel; if (isDir) { readLabel = i18n("Show\nEntries"); readWhatsThis = i18n("This flag allows viewing the content of the folder."); } else { readLabel = i18n("Read"); readWhatsThis = i18n("The Read flag allows viewing the content of the file."); } QString writeWhatsThis; QString writeLabel; if (isDir) { writeLabel = i18n("Write\nEntries"); writeWhatsThis = i18n("This flag allows adding, renaming and deleting of files. " "Note that deleting and renaming can be limited using the Sticky flag."); } else { writeLabel = i18n("Write"); writeWhatsThis = i18n("The Write flag allows modifying the content of the file."); } QString execLabel; QString execWhatsThis; if (isDir) { execLabel = i18nc("Enter folder", "Enter"); execWhatsThis = i18n("Enable this flag to allow entering the folder."); } else { execLabel = i18n("Exec"); execWhatsThis = i18n("Enable this flag to allow executing the file as a program."); } // GJ: Add space between normal and special modes QSize size = l->sizeHint(); size.setWidth(size.width() + 15); l->setFixedSize(size); gl->addWidget(l, 1, 3); l = new QLabel(i18n("Special"), gb); gl->addWidget(l, 1, 4, 1, 1); QString specialWhatsThis; if (isDir) specialWhatsThis = i18n("Special flag. Valid for the whole folder, the exact " "meaning of the flag can be seen in the right hand column."); else specialWhatsThis = i18n("Special flag. The exact meaning of the flag can be seen " "in the right hand column."); l->setWhatsThis(specialWhatsThis); cl[0] = new QLabel(i18n("User"), gb); gl->addWidget(cl[0], 2, 0); theNotSpecials.append(cl[0]); cl[1] = new QLabel(i18n("Group"), gb); gl->addWidget(cl[1], 3, 0); theNotSpecials.append(cl[1]); cl[2] = new QLabel(i18n("Others"), gb); gl->addWidget(cl[2], 4, 0); theNotSpecials.append(cl[2]); QString setUidWhatsThis; if (isDir) setUidWhatsThis = i18n("If this flag is set, the owner of this folder will be " "the owner of all new files."); else setUidWhatsThis = i18n("If this file is an executable and the flag is set, it will " "be executed with the permissions of the owner."); QString setGidWhatsThis; if (isDir) setGidWhatsThis = i18n("If this flag is set, the group of this folder will be " "set for all new files."); else setGidWhatsThis = i18n("If this file is an executable and the flag is set, it will " "be executed with the permissions of the group."); QString stickyWhatsThis; if (isDir) stickyWhatsThis = i18n("If the Sticky flag is set on a folder, only the owner " "and root can delete or rename files. Otherwise everybody " "with write permissions can do this."); else stickyWhatsThis = i18n("The Sticky flag on a file is ignored on Linux, but may " "be used on some systems"); mode_t aPermissions, aPartialPermissions; mode_t dummy1, dummy2; if (!d->isIrregular) { switch (d->pmode) { case PermissionsOnlyFiles: getPermissionMasks(aPartialPermissions, dummy1, aPermissions, dummy2); break; case PermissionsOnlyDirs: case PermissionsMixed: getPermissionMasks(dummy1, aPartialPermissions, dummy2, aPermissions); break; case PermissionsOnlyLinks: aPermissions = UniRead | UniWrite | UniExec | UniSpecial; aPartialPermissions = 0; break; } } else { aPermissions = d->permissions; aPartialPermissions = d->partialPermissions; } // Draw Checkboxes QCheckBox *cba[3][4]; for (int row = 0; row < 3; ++row) { for (int col = 0; col < 4; ++col) { QCheckBox *cb = new QCheckBox(gb); if (col != 3) { theNotSpecials.append(cb); } cba[row][col] = cb; cb->setChecked(aPermissions & fperm[row][col]); if (aPartialPermissions & fperm[row][col]) { cb->setTristate(); cb->setCheckState(Qt::PartiallyChecked); } else if (d->cbRecursive && d->cbRecursive->isChecked()) { cb->setTristate(); } cb->setEnabled(d->canChangePermissions); gl->addWidget(cb, row + 2, col + 1); switch (col) { case 0: cb->setText(readLabel); cb->setWhatsThis(readWhatsThis); break; case 1: cb->setText(writeLabel); cb->setWhatsThis(writeWhatsThis); break; case 2: cb->setText(execLabel); cb->setWhatsThis(execWhatsThis); break; case 3: switch (row) { case 0: cb->setText(i18n("Set UID")); cb->setWhatsThis(setUidWhatsThis); break; case 1: cb->setText(i18n("Set GID")); cb->setWhatsThis(setGidWhatsThis); break; case 2: cb->setText(i18nc("File permission", "Sticky")); cb->setWhatsThis(stickyWhatsThis); break; } break; } } } gl->setColumnStretch(6, 10); #if HAVE_POSIX_ACL KACLEditWidget *extendedACLs = nullptr; // FIXME make it work with partial entries if (properties->items().count() == 1) { QByteArray path = QFile::encodeName(properties->item().url().toLocalFile()); d->fileSystemSupportsACLs = fileSystemSupportsACL(path); } if (d->fileSystemSupportsACLs) { std::for_each(theNotSpecials.begin(), theNotSpecials.end(), std::mem_fun(&QWidget::hide)); extendedACLs = new KACLEditWidget(&dlg); extendedACLs->setEnabled(d->canChangePermissions); vbox->addWidget(extendedACLs); if (d->extendedACL.isValid() && d->extendedACL.isExtended()) { extendedACLs->setACL(d->extendedACL); } else { extendedACLs->setACL(KACL(aPermissions)); } if (d->defaultACL.isValid()) { extendedACLs->setDefaultACL(d->defaultACL); } if (properties->items().constFirst().isDir()) { extendedACLs->setAllowDefaults(true); } } #endif QDialogButtonBox *buttonBox = new QDialogButtonBox(&dlg); buttonBox->setStandardButtons(QDialogButtonBox::Ok | QDialogButtonBox::Cancel); - connect(buttonBox, SIGNAL(accepted()), &dlg, SLOT(accept())); - connect(buttonBox, SIGNAL(rejected()), &dlg, SLOT(reject())); + connect(buttonBox, &QDialogButtonBox::accepted, &dlg, &QDialog::accept); + connect(buttonBox, &QDialogButtonBox::rejected, &dlg, &QDialog::reject); vbox->addWidget(buttonBox); if (dlg.exec() != QDialog::Accepted) { return; } mode_t andPermissions = mode_t(~0); mode_t orPermissions = 0; for (int row = 0; row < 3; ++row) for (int col = 0; col < 4; ++col) { switch (cba[row][col]->checkState()) { case Qt::Checked: orPermissions |= fperm[row][col]; //fall through case Qt::Unchecked: andPermissions &= ~fperm[row][col]; break; case Qt::PartiallyChecked: break; } } d->isIrregular = false; const KFileItemList items = properties->items(); KFileItemList::const_iterator it = items.begin(); const KFileItemList::const_iterator kend = items.end(); for (; it != kend; ++it) { if (isIrregular(((*it).permissions() & andPermissions) | orPermissions, (*it).isDir(), (*it).isLink())) { d->isIrregular = true; break; } } d->permissions = orPermissions; d->partialPermissions = andPermissions; #if HAVE_POSIX_ACL // override with the acls, if present if (extendedACLs) { d->extendedACL = extendedACLs->getACL(); d->defaultACL = extendedACLs->getDefaultACL(); d->hasExtendedACL = d->extendedACL.isExtended() || d->defaultACL.isValid(); d->permissions = d->extendedACL.basePermissions(); d->permissions |= (andPermissions | orPermissions) & (S_ISUID | S_ISGID | S_ISVTX); } #endif updateAccessControls(); emit changed(); } // QString KFilePermissionsPropsPlugin::tabName () const // { // return i18n ("&Permissions"); // } KFilePermissionsPropsPlugin::~KFilePermissionsPropsPlugin() { delete d; } bool KFilePermissionsPropsPlugin::supports(const KFileItemList & /*_items*/) { return true; } // sets a combo box in the Access Control frame void KFilePermissionsPropsPlugin::setComboContent(QComboBox *combo, PermissionsTarget target, mode_t permissions, mode_t partial) { combo->clear(); if (d->isIrregular) { //#176876 return; } if (d->pmode == PermissionsOnlyLinks) { combo->addItem(i18n("Link")); combo->setCurrentIndex(0); return; } mode_t tMask = permissionsMasks[target]; int textIndex; for (textIndex = 0; standardPermissions[textIndex] != (mode_t) - 1; textIndex++) { if ((standardPermissions[textIndex]&tMask) == (permissions & tMask & (UniRead | UniWrite))) { break; } } Q_ASSERT(standardPermissions[textIndex] != (mode_t) - 1); // must not happen, would be irreglar for (int i = 0; permissionsTexts[(int)d->pmode][i]; i++) { combo->addItem(i18n(permissionsTexts[(int)d->pmode][i])); } if (partial & tMask & ~UniExec) { combo->addItem(i18n("Varying (No Change)")); combo->setCurrentIndex(3); } else { combo->setCurrentIndex(textIndex); } } // permissions are irregular if they cant be displayed in a combo box. bool KFilePermissionsPropsPlugin::isIrregular(mode_t permissions, bool isDir, bool isLink) { if (isLink) { // links are always ok return false; } mode_t p = permissions; if (p & (S_ISUID | S_ISGID)) { // setuid/setgid -> irregular return true; } if (isDir) { p &= ~S_ISVTX; // ignore sticky on dirs // check supported flag combinations mode_t p0 = p & UniOwner; if ((p0 != 0) && (p0 != (S_IRUSR | S_IXUSR)) && (p0 != UniOwner)) { return true; } p0 = p & UniGroup; if ((p0 != 0) && (p0 != (S_IRGRP | S_IXGRP)) && (p0 != UniGroup)) { return true; } p0 = p & UniOthers; if ((p0 != 0) && (p0 != (S_IROTH | S_IXOTH)) && (p0 != UniOthers)) { return true; } return false; } if (p & S_ISVTX) { // sticky on file -> irregular return true; } // check supported flag combinations mode_t p0 = p & UniOwner; bool usrXPossible = !p0; // true if this file could be an executable if (p0 & S_IXUSR) { if ((p0 == S_IXUSR) || (p0 == (S_IWUSR | S_IXUSR))) { return true; } usrXPossible = true; } else if (p0 == S_IWUSR) { return true; } p0 = p & UniGroup; bool grpXPossible = !p0; // true if this file could be an executable if (p0 & S_IXGRP) { if ((p0 == S_IXGRP) || (p0 == (S_IWGRP | S_IXGRP))) { return true; } grpXPossible = true; } else if (p0 == S_IWGRP) { return true; } if (p0 == 0) { grpXPossible = true; } p0 = p & UniOthers; bool othXPossible = !p0; // true if this file could be an executable if (p0 & S_IXOTH) { if ((p0 == S_IXOTH) || (p0 == (S_IWOTH | S_IXOTH))) { return true; } othXPossible = true; } else if (p0 == S_IWOTH) { return true; } // check that there either all targets are executable-compatible, or none return (p & UniExec) && !(usrXPossible && grpXPossible && othXPossible); } // enables/disabled the widgets in the Access Control frame void KFilePermissionsPropsPlugin::enableAccessControls(bool enable) { d->ownerPermCombo->setEnabled(enable); d->groupPermCombo->setEnabled(enable); d->othersPermCombo->setEnabled(enable); if (d->extraCheckbox) { d->extraCheckbox->setEnabled(enable); } if (d->cbRecursive) { d->cbRecursive->setEnabled(enable); } } // updates all widgets in the Access Control frame void KFilePermissionsPropsPlugin::updateAccessControls() { setComboContent(d->ownerPermCombo, PermissionsOwner, d->permissions, d->partialPermissions); setComboContent(d->groupPermCombo, PermissionsGroup, d->permissions, d->partialPermissions); setComboContent(d->othersPermCombo, PermissionsOthers, d->permissions, d->partialPermissions); switch (d->pmode) { case PermissionsOnlyLinks: enableAccessControls(false); break; case PermissionsOnlyFiles: enableAccessControls(d->canChangePermissions && !d->isIrregular && !d->hasExtendedACL); if (d->canChangePermissions) d->explanationLabel->setText(d->isIrregular || d->hasExtendedACL ? i18np("This file uses advanced permissions", "These files use advanced permissions.", properties->items().count()) : QString()); if (d->partialPermissions & UniExec) { d->extraCheckbox->setTristate(); d->extraCheckbox->setCheckState(Qt::PartiallyChecked); } else { d->extraCheckbox->setTristate(false); d->extraCheckbox->setChecked(d->permissions & UniExec); } break; case PermissionsOnlyDirs: enableAccessControls(d->canChangePermissions && !d->isIrregular && !d->hasExtendedACL); // if this is a dir, and we can change permissions, don't dis-allow // recursive, we can do that for ACL setting. if (d->cbRecursive) { d->cbRecursive->setEnabled(d->canChangePermissions && !d->isIrregular); } if (d->canChangePermissions) d->explanationLabel->setText(d->isIrregular || d->hasExtendedACL ? i18np("This folder uses advanced permissions.", "These folders use advanced permissions.", properties->items().count()) : QString()); if (d->partialPermissions & S_ISVTX) { d->extraCheckbox->setTristate(); d->extraCheckbox->setCheckState(Qt::PartiallyChecked); } else { d->extraCheckbox->setTristate(false); d->extraCheckbox->setChecked(d->permissions & S_ISVTX); } break; case PermissionsMixed: enableAccessControls(d->canChangePermissions && !d->isIrregular && !d->hasExtendedACL); if (d->canChangePermissions) d->explanationLabel->setText(d->isIrregular || d->hasExtendedACL ? i18n("These files use advanced permissions.") : QString()); if (d->partialPermissions & S_ISVTX) { d->extraCheckbox->setTristate(); d->extraCheckbox->setCheckState(Qt::PartiallyChecked); } else { d->extraCheckbox->setTristate(false); d->extraCheckbox->setChecked(d->permissions & S_ISVTX); } break; } } // gets masks for files and dirs from the Access Control frame widgets void KFilePermissionsPropsPlugin::getPermissionMasks(mode_t &andFilePermissions, mode_t &andDirPermissions, mode_t &orFilePermissions, mode_t &orDirPermissions) { andFilePermissions = mode_t(~UniSpecial); andDirPermissions = mode_t(~(S_ISUID | S_ISGID)); orFilePermissions = 0; orDirPermissions = 0; if (d->isIrregular) { return; } mode_t m = standardPermissions[d->ownerPermCombo->currentIndex()]; if (m != (mode_t) - 1) { orFilePermissions |= m & UniOwner; if ((m & UniOwner) && ((d->pmode == PermissionsMixed) || ((d->pmode == PermissionsOnlyFiles) && (d->extraCheckbox->checkState() == Qt::PartiallyChecked)))) { andFilePermissions &= ~(S_IRUSR | S_IWUSR); } else { andFilePermissions &= ~(S_IRUSR | S_IWUSR | S_IXUSR); if ((m & S_IRUSR) && (d->extraCheckbox->checkState() == Qt::Checked)) { orFilePermissions |= S_IXUSR; } } orDirPermissions |= m & UniOwner; if (m & S_IRUSR) { orDirPermissions |= S_IXUSR; } andDirPermissions &= ~(S_IRUSR | S_IWUSR | S_IXUSR); } m = standardPermissions[d->groupPermCombo->currentIndex()]; if (m != (mode_t) - 1) { orFilePermissions |= m & UniGroup; if ((m & UniGroup) && ((d->pmode == PermissionsMixed) || ((d->pmode == PermissionsOnlyFiles) && (d->extraCheckbox->checkState() == Qt::PartiallyChecked)))) { andFilePermissions &= ~(S_IRGRP | S_IWGRP); } else { andFilePermissions &= ~(S_IRGRP | S_IWGRP | S_IXGRP); if ((m & S_IRGRP) && (d->extraCheckbox->checkState() == Qt::Checked)) { orFilePermissions |= S_IXGRP; } } orDirPermissions |= m & UniGroup; if (m & S_IRGRP) { orDirPermissions |= S_IXGRP; } andDirPermissions &= ~(S_IRGRP | S_IWGRP | S_IXGRP); } m = d->othersPermCombo->currentIndex() >= 0 ? standardPermissions[d->othersPermCombo->currentIndex()] : (mode_t) - 1; if (m != (mode_t) - 1) { orFilePermissions |= m & UniOthers; if ((m & UniOthers) && ((d->pmode == PermissionsMixed) || ((d->pmode == PermissionsOnlyFiles) && (d->extraCheckbox->checkState() == Qt::PartiallyChecked)))) { andFilePermissions &= ~(S_IROTH | S_IWOTH); } else { andFilePermissions &= ~(S_IROTH | S_IWOTH | S_IXOTH); if ((m & S_IROTH) && (d->extraCheckbox->checkState() == Qt::Checked)) { orFilePermissions |= S_IXOTH; } } orDirPermissions |= m & UniOthers; if (m & S_IROTH) { orDirPermissions |= S_IXOTH; } andDirPermissions &= ~(S_IROTH | S_IWOTH | S_IXOTH); } if (((d->pmode == PermissionsMixed) || (d->pmode == PermissionsOnlyDirs)) && (d->extraCheckbox->checkState() != Qt::PartiallyChecked)) { andDirPermissions &= ~S_ISVTX; if (d->extraCheckbox->checkState() == Qt::Checked) { orDirPermissions |= S_ISVTX; } } } void KFilePermissionsPropsPlugin::applyChanges() { mode_t orFilePermissions; mode_t orDirPermissions; mode_t andFilePermissions; mode_t andDirPermissions; if (!d->canChangePermissions) { return; } if (!d->isIrregular) getPermissionMasks(andFilePermissions, andDirPermissions, orFilePermissions, orDirPermissions); else { orFilePermissions = d->permissions; andFilePermissions = d->partialPermissions; orDirPermissions = d->permissions; andDirPermissions = d->partialPermissions; } QString owner, group; if (d->usrEdit) { owner = d->usrEdit->text(); } if (d->grpEdit) { group = d->grpEdit->text(); } else if (d->grpCombo) { group = d->grpCombo->currentText(); } if (owner == d->strOwner) { owner.clear(); // no change } if (group == d->strGroup) { group.clear(); } bool recursive = d->cbRecursive && d->cbRecursive->isChecked(); bool permissionChange = false; KFileItemList files, dirs; const KFileItemList items = properties->items(); KFileItemList::const_iterator it = items.begin(); const KFileItemList::const_iterator kend = items.end(); for (; it != kend; ++it) { if ((*it).isDir()) { dirs.append(*it); if ((*it).permissions() != (((*it).permissions() & andDirPermissions) | orDirPermissions)) { permissionChange = true; } } else if ((*it).isFile()) { files.append(*it); if ((*it).permissions() != (((*it).permissions() & andFilePermissions) | orFilePermissions)) { permissionChange = true; } } } const bool ACLChange = (d->extendedACL != properties->item().ACL()); const bool defaultACLChange = (d->defaultACL != properties->item().defaultACL()); if (owner.isEmpty() && group.isEmpty() && !recursive && !permissionChange && !ACLChange && !defaultACLChange) { return; } KIO::Job *job; if (files.count() > 0) { job = KIO::chmod(files, orFilePermissions, ~andFilePermissions, owner, group, false); if (ACLChange && d->fileSystemSupportsACLs) { job->addMetaData(QStringLiteral("ACL_STRING"), d->extendedACL.isValid() ? d->extendedACL.asString() : QStringLiteral("ACL_DELETE")); } if (defaultACLChange && d->fileSystemSupportsACLs) { job->addMetaData(QStringLiteral("DEFAULT_ACL_STRING"), d->defaultACL.isValid() ? d->defaultACL.asString() : QStringLiteral("ACL_DELETE")); } - connect(job, SIGNAL(result(KJob*)), - SLOT(slotChmodResult(KJob*))); + connect(job, &KJob::result, + this, &KFilePermissionsPropsPlugin::slotChmodResult); QEventLoop eventLoop; - connect(this, SIGNAL(leaveModality()), - &eventLoop, SLOT(quit())); + connect(this, &KFilePermissionsPropsPlugin::leaveModality, + &eventLoop, &QEventLoop::quit); eventLoop.exec(QEventLoop::ExcludeUserInputEvents); } if (dirs.count() > 0) { job = KIO::chmod(dirs, orDirPermissions, ~andDirPermissions, owner, group, recursive); if (ACLChange && d->fileSystemSupportsACLs) { job->addMetaData(QStringLiteral("ACL_STRING"), d->extendedACL.isValid() ? d->extendedACL.asString() : QStringLiteral("ACL_DELETE")); } if (defaultACLChange && d->fileSystemSupportsACLs) { job->addMetaData(QStringLiteral("DEFAULT_ACL_STRING"), d->defaultACL.isValid() ? d->defaultACL.asString() : QStringLiteral("ACL_DELETE")); } - connect(job, SIGNAL(result(KJob*)), - SLOT(slotChmodResult(KJob*))); + connect(job, &KJob::result, + this, &KFilePermissionsPropsPlugin::slotChmodResult); QEventLoop eventLoop; - connect(this, SIGNAL(leaveModality()), - &eventLoop, SLOT(quit())); + connect(this, &KFilePermissionsPropsPlugin::leaveModality, + &eventLoop, &QEventLoop::quit); eventLoop.exec(QEventLoop::ExcludeUserInputEvents); } } void KFilePermissionsPropsPlugin::slotChmodResult(KJob *job) { // qDebug() << "KFilePermissionsPropsPlugin::slotChmodResult"; if (job->error()) { job->uiDelegate()->showErrorMessage(); } // allow apply() to return emit leaveModality(); } class KChecksumsPlugin::KChecksumsPluginPrivate { public: KChecksumsPluginPrivate() { } ~KChecksumsPluginPrivate() { } QWidget m_widget; Ui::ChecksumsWidget m_ui; QFileSystemWatcher fileWatcher; QString m_md5; QString m_sha1; QString m_sha256; }; KChecksumsPlugin::KChecksumsPlugin(KPropertiesDialog *dialog) : KPropertiesDialogPlugin(dialog), d(new KChecksumsPluginPrivate) { d->m_ui.setupUi(&d->m_widget); properties->addPage(&d->m_widget, i18nc("@title:tab", "C&hecksums")); d->m_ui.md5CopyButton->hide(); d->m_ui.sha1CopyButton->hide(); d->m_ui.sha256CopyButton->hide(); connect(d->m_ui.lineEdit, &QLineEdit::textChanged, this, [=](const QString &text) { slotVerifyChecksum(text.toLower()); }); connect(d->m_ui.md5Button, &QPushButton::clicked, this, &KChecksumsPlugin::slotShowMd5); connect(d->m_ui.sha1Button, &QPushButton::clicked, this, &KChecksumsPlugin::slotShowSha1); connect(d->m_ui.sha256Button, &QPushButton::clicked, this, &KChecksumsPlugin::slotShowSha256); d->fileWatcher.addPath(properties->item().localPath()); connect(&d->fileWatcher, &QFileSystemWatcher::fileChanged, this, &KChecksumsPlugin::slotInvalidateCache); auto clipboard = QApplication::clipboard(); connect(d->m_ui.md5CopyButton, &QPushButton::clicked, this, [=]() { clipboard->setText(d->m_md5); }); connect(d->m_ui.sha1CopyButton, &QPushButton::clicked, this, [=]() { clipboard->setText(d->m_sha1); }); connect(d->m_ui.sha256CopyButton, &QPushButton::clicked, this, [=]() { clipboard->setText(d->m_sha256); }); connect(d->m_ui.pasteButton, &QPushButton::clicked, this, [=]() { d->m_ui.lineEdit->setText(clipboard->text()); }); setDefaultState(); } KChecksumsPlugin::~KChecksumsPlugin() { delete d; } bool KChecksumsPlugin::supports(const KFileItemList &items) { if (items.count() != 1) { return false; } const KFileItem item = items.first(); return item.isFile() && !item.localPath().isEmpty() && item.isReadable() && !item.isDesktopFile() && !item.isLink(); } void KChecksumsPlugin::slotInvalidateCache() { d->m_md5 = QString(); d->m_sha1 = QString(); d->m_sha256 = QString(); } void KChecksumsPlugin::slotShowMd5() { auto label = new QLabel(i18nc("@action:button", "Calculating..."), &d->m_widget); label->setTextInteractionFlags(Qt::TextSelectableByMouse | Qt::TextSelectableByKeyboard); d->m_ui.calculateWidget->layout()->replaceWidget(d->m_ui.md5Button, label); d->m_ui.md5Button->hide(); showChecksum(QCryptographicHash::Md5, label, d->m_ui.md5CopyButton); } void KChecksumsPlugin::slotShowSha1() { auto label = new QLabel(i18nc("@action:button", "Calculating..."), &d->m_widget); label->setTextInteractionFlags(Qt::TextSelectableByMouse | Qt::TextSelectableByKeyboard); d->m_ui.calculateWidget->layout()->replaceWidget(d->m_ui.sha1Button, label); d->m_ui.sha1Button->hide(); showChecksum(QCryptographicHash::Sha1, label, d->m_ui.sha1CopyButton); } void KChecksumsPlugin::slotShowSha256() { auto label = new QLabel(i18nc("@action:button", "Calculating..."), &d->m_widget); label->setTextInteractionFlags(Qt::TextSelectableByMouse | Qt::TextSelectableByKeyboard); d->m_ui.calculateWidget->layout()->replaceWidget(d->m_ui.sha256Button, label); d->m_ui.sha256Button->hide(); showChecksum(QCryptographicHash::Sha256, label, d->m_ui.sha256CopyButton); } void KChecksumsPlugin::slotVerifyChecksum(const QString &input) { auto algorithm = detectAlgorithm(input); // Input is not a supported hash algorithm. if (algorithm == QCryptographicHash::Md4) { if (input.isEmpty()) { setDefaultState(); } else { setInvalidChecksumState(); } return; } const QString checksum = cachedChecksum(algorithm); // Checksum already in cache. if (!checksum.isEmpty()) { const bool isMatch = (checksum == input); if (isMatch) { setMatchState(); } else { setMismatchState(); } return; } // Calculate checksum in another thread. auto futureWatcher = new QFutureWatcher(this); connect(futureWatcher, &QFutureWatcher::finished, this, [=]() { const QString checksum = futureWatcher->result(); futureWatcher->deleteLater(); cacheChecksum(checksum, algorithm); switch (algorithm) { case QCryptographicHash::Md5: slotShowMd5(); break; case QCryptographicHash::Sha1: slotShowSha1(); break; case QCryptographicHash::Sha256: slotShowSha256(); break; default: break; } const bool isMatch = (checksum == input); if (isMatch) { setMatchState(); } else { setMismatchState(); } }); // Notify the user about the background computation. setVerifyState(); auto future = QtConcurrent::run(&KChecksumsPlugin::computeChecksum, algorithm, properties->item().localPath()); futureWatcher->setFuture(future); } bool KChecksumsPlugin::isMd5(const QString &input) { QRegularExpression regex(QStringLiteral("^[a-f0-9]{32}$")); return regex.match(input).hasMatch(); } bool KChecksumsPlugin::isSha1(const QString &input) { QRegularExpression regex(QStringLiteral("^[a-f0-9]{40}$")); return regex.match(input).hasMatch(); } bool KChecksumsPlugin::isSha256(const QString &input) { QRegularExpression regex(QStringLiteral("^[a-f0-9]{64}$")); return regex.match(input).hasMatch(); } QString KChecksumsPlugin::computeChecksum(QCryptographicHash::Algorithm algorithm, const QString &path) { QFile file(path); if (!file.open(QIODevice::ReadOnly)) { return QString(); } QCryptographicHash hash(algorithm); hash.addData(&file); return QString::fromLatin1(hash.result().toHex()); } QCryptographicHash::Algorithm KChecksumsPlugin::detectAlgorithm(const QString &input) { if (isMd5(input)) { return QCryptographicHash::Md5; } if (isSha1(input)) { return QCryptographicHash::Sha1; } if (isSha256(input)) { return QCryptographicHash::Sha256; } // Md4 used as negative error code. return QCryptographicHash::Md4; } void KChecksumsPlugin::setDefaultState() { QColor defaultColor = d->m_widget.palette().color(QPalette::Base); QPalette palette = d->m_widget.palette(); palette.setColor(QPalette::Base, defaultColor); d->m_ui.feedbackLabel->hide(); d->m_ui.lineEdit->setPalette(palette); d->m_ui.lineEdit->setToolTip(QString()); } void KChecksumsPlugin::setInvalidChecksumState() { KColorScheme colorScheme(QPalette::Active, KColorScheme::View); QColor warningColor = colorScheme.background(KColorScheme::NegativeBackground).color(); QPalette palette = d->m_widget.palette(); palette.setColor(QPalette::Base, warningColor); d->m_ui.feedbackLabel->setText(i18n("Invalid checksum.")); d->m_ui.feedbackLabel->show(); d->m_ui.lineEdit->setPalette(palette); d->m_ui.lineEdit->setToolTip(i18nc("@info:tooltip", "The given input is not a valid MD5, SHA1 or SHA256 checksum.")); } void KChecksumsPlugin::setMatchState() { KColorScheme colorScheme(QPalette::Active, KColorScheme::View); QColor positiveColor = colorScheme.background(KColorScheme::PositiveBackground).color(); QPalette palette = d->m_widget.palette(); palette.setColor(QPalette::Base, positiveColor); d->m_ui.feedbackLabel->setText(i18n("Checksums match.")); d->m_ui.feedbackLabel->show(); d->m_ui.lineEdit->setPalette(palette); d->m_ui.lineEdit->setToolTip(i18nc("@info:tooltip", "The computed checksum and the expected checksum match.")); } void KChecksumsPlugin::setMismatchState() { KColorScheme colorScheme(QPalette::Active, KColorScheme::View); QColor warningColor = colorScheme.background(KColorScheme::NegativeBackground).color(); QPalette palette = d->m_widget.palette(); palette.setColor(QPalette::Base, warningColor); d->m_ui.feedbackLabel->setText(i18n("

Checksums do not match.

" "This may be due to a faulty download. Try re-downloading the file.
" "If the verification still fails, contact the source of the file.")); d->m_ui.feedbackLabel->show(); d->m_ui.lineEdit->setPalette(palette); d->m_ui.lineEdit->setToolTip(i18nc("@info:tooltip", "The computed checksum and the expected checksum differ.")); } void KChecksumsPlugin::setVerifyState() { // Users can paste a checksum at any time, so reset to default. setDefaultState(); d->m_ui.feedbackLabel->setText(i18nc("notify the user about a computation in the background", "Verifying checksum...")); d->m_ui.feedbackLabel->show(); } void KChecksumsPlugin::showChecksum(QCryptographicHash::Algorithm algorithm, QLabel *label, QPushButton *copyButton) { const QString checksum = cachedChecksum(algorithm); // Checksum in cache, nothing else to do. if (!checksum.isEmpty()) { label->setText(checksum); return; } // Calculate checksum in another thread. auto futureWatcher = new QFutureWatcher(this); connect(futureWatcher, &QFutureWatcher::finished, this, [=]() { const QString checksum = futureWatcher->result(); futureWatcher->deleteLater(); label->setText(checksum); cacheChecksum(checksum, algorithm); copyButton->show(); }); auto future = QtConcurrent::run(&KChecksumsPlugin::computeChecksum, algorithm, properties->item().localPath()); futureWatcher->setFuture(future); } QString KChecksumsPlugin::cachedChecksum(QCryptographicHash::Algorithm algorithm) const { switch (algorithm) { case QCryptographicHash::Md5: return d->m_md5; case QCryptographicHash::Sha1: return d->m_sha1; case QCryptographicHash::Sha256: return d->m_sha256; default: break; } return QString(); } void KChecksumsPlugin::cacheChecksum(const QString &checksum, QCryptographicHash::Algorithm algorithm) { switch (algorithm) { case QCryptographicHash::Md5: d->m_md5 = checksum; break; case QCryptographicHash::Sha1: d->m_sha1 = checksum; break; case QCryptographicHash::Sha256: d->m_sha256 = checksum; break; default: return; } } class KUrlPropsPlugin::KUrlPropsPluginPrivate { public: KUrlPropsPluginPrivate() { } ~KUrlPropsPluginPrivate() { } QFrame *m_frame; KUrlRequester *URLEdit; QString URLStr; bool fileNameReadOnly = false; }; KUrlPropsPlugin::KUrlPropsPlugin(KPropertiesDialog *_props) : KPropertiesDialogPlugin(_props), d(new KUrlPropsPluginPrivate) { d->m_frame = new QFrame(); properties->addPage(d->m_frame, i18n("U&RL")); QVBoxLayout *layout = new QVBoxLayout(d->m_frame); layout->setMargin(0); QLabel *l; l = new QLabel(d->m_frame); l->setObjectName(QStringLiteral("Label_1")); l->setText(i18n("URL:")); layout->addWidget(l, Qt::AlignRight); d->URLEdit = new KUrlRequester(d->m_frame); layout->addWidget(d->URLEdit); KIO::StatJob *job = KIO::mostLocalUrl(properties->url()); KJobWidgets::setWindow(job, properties); job->exec(); QUrl url = job->mostLocalUrl(); if (url.isLocalFile()) { QString path = url.toLocalFile(); QFile f(path); if (!f.open(QIODevice::ReadOnly)) { return; } f.close(); KDesktopFile config(path); const KConfigGroup dg = config.desktopGroup(); d->URLStr = dg.readPathEntry("URL", QString()); if (!d->URLStr.isEmpty()) { d->URLEdit->setUrl(QUrl(d->URLStr)); } } - connect(d->URLEdit, SIGNAL(textChanged(QString)), - this, SIGNAL(changed())); + connect(d->URLEdit, &KUrlRequester::textChanged, + this, &KPropertiesDialogPlugin::changed); layout->addStretch(1); } KUrlPropsPlugin::~KUrlPropsPlugin() { delete d; } void KUrlPropsPlugin::setFileNameReadOnly(bool ro) { d->fileNameReadOnly = ro; } // QString KUrlPropsPlugin::tabName () const // { // return i18n ("U&RL"); // } bool KUrlPropsPlugin::supports(const KFileItemList &_items) { if (_items.count() != 1) { return false; } const KFileItem item = _items.first(); // check if desktop file if (!item.isDesktopFile()) { return false; } // open file and check type bool isLocal; QUrl url = item.mostLocalUrl(isLocal); if (!isLocal) { return false; } KDesktopFile config(url.toLocalFile()); return config.hasLinkType(); } void KUrlPropsPlugin::applyChanges() { KIO::StatJob *job = KIO::mostLocalUrl(properties->url()); KJobWidgets::setWindow(job, properties); job->exec(); const QUrl url = job->mostLocalUrl(); if (!url.isLocalFile()) { KMessageBox::sorry(nullptr, i18n("Could not save properties. Only entries on local file systems are supported.")); return; } QString path = url.toLocalFile(); QFile f(path); if (!f.open(QIODevice::ReadWrite)) { KMessageBox::sorry(nullptr, i18n("Could not save properties. You do not have " "sufficient access to write to %1.", path)); return; } f.close(); KDesktopFile config(path); KConfigGroup dg = config.desktopGroup(); dg.writeEntry("Type", QStringLiteral("Link")); dg.writePathEntry("URL", d->URLEdit->url().toString()); // Users can't create a Link .desktop file with a Name field, // but distributions can. Update the Name field in that case, // if the file name could have been changed. if (!d->fileNameReadOnly && dg.hasKey("Name")) { const QString nameStr = nameFromFileName(properties->url().fileName()); dg.writeEntry("Name", nameStr); dg.writeEntry("Name", nameStr, KConfigBase::Persistent | KConfigBase::Localized); } } /* ---------------------------------------------------- * * KDevicePropsPlugin * * -------------------------------------------------- */ class KDevicePropsPlugin::KDevicePropsPluginPrivate { public: KDevicePropsPluginPrivate() { } ~KDevicePropsPluginPrivate() { } bool isMounted() const { const QString dev = device->currentText(); return !dev.isEmpty() && KMountPoint::currentMountPoints().findByDevice(dev); } QFrame *m_frame; QStringList mountpointlist; QLabel *m_freeSpaceText; QLabel *m_freeSpaceLabel; QProgressBar *m_freeSpaceBar; KComboBox *device; QLabel *mountpoint; QCheckBox *readonly; QStringList m_devicelist; }; KDevicePropsPlugin::KDevicePropsPlugin(KPropertiesDialog *_props) : KPropertiesDialogPlugin(_props), d(new KDevicePropsPluginPrivate) { d->m_frame = new QFrame(); properties->addPage(d->m_frame, i18n("De&vice")); QStringList devices; const KMountPoint::List mountPoints = KMountPoint::possibleMountPoints(); for (KMountPoint::List::ConstIterator it = mountPoints.begin(); it != mountPoints.end(); ++it) { const KMountPoint::Ptr mp = (*it); QString mountPoint = mp->mountPoint(); QString device = mp->mountedFrom(); // qDebug()<<"mountPoint :"<mountType() :"<mountType(); if ((mountPoint != QLatin1String("-")) && (mountPoint != QLatin1String("none")) && !mountPoint.isEmpty() && device != QLatin1String("none")) { devices.append(device + QLatin1String(" (") + mountPoint + QLatin1String(")")); d->m_devicelist.append(device); d->mountpointlist.append(mountPoint); } } QGridLayout *layout = new QGridLayout(d->m_frame); layout->setMargin(0); layout->setColumnStretch(1, 1); QLabel *label; label = new QLabel(d->m_frame); label->setText(devices.count() == 0 ? i18n("Device (/dev/fd0):") : // old style i18n("Device:")); // new style (combobox) layout->addWidget(label, 0, 0, Qt::AlignRight); d->device = new KComboBox(d->m_frame); d->device->setObjectName(QStringLiteral("ComboBox_device")); d->device->setEditable(true); d->device->addItems(devices); layout->addWidget(d->device, 0, 1); - connect(d->device, SIGNAL(activated(int)), - this, SLOT(slotActivated(int))); + connect(d->device, QOverload::of(&QComboBox::activated), + this, &KDevicePropsPlugin::slotActivated); d->readonly = new QCheckBox(d->m_frame); d->readonly->setObjectName(QStringLiteral("CheckBox_readonly")); d->readonly->setText(i18n("Read only")); layout->addWidget(d->readonly, 1, 1); label = new QLabel(d->m_frame); label->setText(i18n("File system:")); layout->addWidget(label, 2, 0, Qt::AlignRight); QLabel *fileSystem = new QLabel(d->m_frame); layout->addWidget(fileSystem, 2, 1); label = new QLabel(d->m_frame); label->setText(devices.count() == 0 ? i18n("Mount point (/mnt/floppy):") : // old style i18n("Mount point:")); // new style (combobox) layout->addWidget(label, 3, 0, Qt::AlignRight); d->mountpoint = new QLabel(d->m_frame); d->mountpoint->setObjectName(QStringLiteral("LineEdit_mountpoint")); layout->addWidget(d->mountpoint, 3, 1); // show disk free d->m_freeSpaceText = new QLabel(i18n("Device usage:"), d->m_frame); layout->addWidget(d->m_freeSpaceText, 4, 0, Qt::AlignRight); d->m_freeSpaceLabel = new QLabel(d->m_frame); layout->addWidget(d->m_freeSpaceLabel, 4, 1); d->m_freeSpaceBar = new QProgressBar(d->m_frame); d->m_freeSpaceBar->setObjectName(QStringLiteral("freeSpaceBar")); layout->addWidget(d->m_freeSpaceBar, 5, 0, 1, 2); // we show it in the slot when we know the values d->m_freeSpaceText->hide(); d->m_freeSpaceLabel->hide(); d->m_freeSpaceBar->hide(); KSeparator *sep = new KSeparator(Qt::Horizontal, d->m_frame); layout->addWidget(sep, 6, 0, 1, 2); layout->setRowStretch(7, 1); KIO::StatJob *job = KIO::mostLocalUrl(_props->url()); KJobWidgets::setWindow(job, _props); job->exec(); QUrl url = job->mostLocalUrl(); if (!url.isLocalFile()) { return; } QString path = url.toLocalFile(); QFile f(path); if (!f.open(QIODevice::ReadOnly)) { return; } f.close(); const KDesktopFile _config(path); const KConfigGroup config = _config.desktopGroup(); QString deviceStr = config.readEntry("Dev"); QString mountPointStr = config.readEntry("MountPoint"); bool ro = config.readEntry("ReadOnly", false); fileSystem->setText(config.readEntry("FSType")); d->device->setEditText(deviceStr); if (!deviceStr.isEmpty()) { // Set default options for this device (first matching entry) int index = d->m_devicelist.indexOf(deviceStr); if (index != -1) { //qDebug() << "found it" << index; slotActivated(index); } } if (!mountPointStr.isEmpty()) { d->mountpoint->setText(mountPointStr); updateInfo(); } d->readonly->setChecked(ro); - connect(d->device, SIGNAL(activated(int)), - this, SIGNAL(changed())); - connect(d->device, SIGNAL(textChanged(QString)), - this, SIGNAL(changed())); - connect(d->readonly, SIGNAL(toggled(bool)), - this, SIGNAL(changed())); + connect(d->device, QOverload::of(&QComboBox::activated), + this, &KPropertiesDialogPlugin::changed); + connect(d->device, &QComboBox::currentTextChanged, + this, &KPropertiesDialogPlugin::changed); + connect(d->readonly, &QAbstractButton::toggled, + this, &KPropertiesDialogPlugin::changed); - connect(d->device, SIGNAL(textChanged(QString)), - this, SLOT(slotDeviceChanged())); + connect(d->device, &QComboBox::currentTextChanged, + this, &KDevicePropsPlugin::slotDeviceChanged); } KDevicePropsPlugin::~KDevicePropsPlugin() { delete d; } // QString KDevicePropsPlugin::tabName () const // { // return i18n ("De&vice"); // } void KDevicePropsPlugin::updateInfo() { // we show it in the slot when we know the values d->m_freeSpaceText->hide(); d->m_freeSpaceLabel->hide(); d->m_freeSpaceBar->hide(); if (!d->mountpoint->text().isEmpty() && d->isMounted()) { KDiskFreeSpaceInfo info = KDiskFreeSpaceInfo::freeSpaceInfo(d->mountpoint->text()); slotFoundMountPoint(info.mountPoint(), info.size() / 1024, info.used() / 1024, info.available() / 1024); } } void KDevicePropsPlugin::slotActivated(int index) { // index can be more than the number of known devices, when the user types // a "custom" device. if (index < d->m_devicelist.count()) { // Update mountpoint so that it matches the device that was selected in the combo d->device->setEditText(d->m_devicelist[index]); d->mountpoint->setText(d->mountpointlist[index]); } updateInfo(); } void KDevicePropsPlugin::slotDeviceChanged() { // Update mountpoint so that it matches the typed device int index = d->m_devicelist.indexOf(d->device->currentText()); if (index != -1) { d->mountpoint->setText(d->mountpointlist[index]); } else { d->mountpoint->setText(QString()); } updateInfo(); } void KDevicePropsPlugin::slotFoundMountPoint(const QString &, quint64 kibSize, quint64 /*kibUsed*/, quint64 kibAvail) { d->m_freeSpaceText->show(); d->m_freeSpaceLabel->show(); const int percUsed = kibSize != 0 ? (100 - (int)(100.0 * kibAvail / kibSize)) : 100; d->m_freeSpaceLabel->setText( i18nc("Available space out of total partition size (percent used)", "%1 free of %2 (%3% used)", KIO::convertSizeFromKiB(kibAvail), KIO::convertSizeFromKiB(kibSize), percUsed)); d->m_freeSpaceBar->setRange(0, 100); d->m_freeSpaceBar->setValue(percUsed); d->m_freeSpaceBar->show(); } bool KDevicePropsPlugin::supports(const KFileItemList &_items) { if (_items.count() != 1) { return false; } const KFileItem item = _items.first(); // check if desktop file if (!item.isDesktopFile()) { return false; } // open file and check type bool isLocal; QUrl url = item.mostLocalUrl(isLocal); if (!isLocal) { return false; } KDesktopFile config(url.toLocalFile()); return config.hasDeviceType(); } void KDevicePropsPlugin::applyChanges() { KIO::StatJob *job = KIO::mostLocalUrl(properties->url()); KJobWidgets::setWindow(job, properties); job->exec(); const QUrl url = job->mostLocalUrl(); if (!url.isLocalFile()) { return; } const QString path = url.toLocalFile(); QFile f(path); if (!f.open(QIODevice::ReadWrite)) { KMessageBox::sorry(nullptr, i18n("Could not save properties. You do not have sufficient " "access to write to %1.", path)); return; } f.close(); KDesktopFile _config(path); KConfigGroup config = _config.desktopGroup(); config.writeEntry("Type", QStringLiteral("FSDevice")); config.writeEntry("Dev", d->device->currentText()); config.writeEntry("MountPoint", d->mountpoint->text()); config.writeEntry("ReadOnly", d->readonly->isChecked()); config.sync(); } /* ---------------------------------------------------- * * KDesktopPropsPlugin * * -------------------------------------------------- */ class KDesktopPropsPlugin::KDesktopPropsPluginPrivate { public: KDesktopPropsPluginPrivate() : w(new Ui_KPropertiesDesktopBase) , m_frame(new QFrame()) { } ~KDesktopPropsPluginPrivate() { delete w; } Ui_KPropertiesDesktopBase *w; QWidget *m_frame; QString m_origCommandStr; QString m_terminalOptionStr; QString m_suidUserStr; QString m_dbusStartupType; QString m_dbusServiceName; QString m_origDesktopFile; bool m_terminalBool; bool m_suidBool; bool m_hasDiscreteGpuBool; bool m_runOnDiscreteGpuBool; bool m_startupBool; }; KDesktopPropsPlugin::KDesktopPropsPlugin(KPropertiesDialog *_props) : KPropertiesDialogPlugin(_props), d(new KDesktopPropsPluginPrivate) { QMimeDatabase db; d->w->setupUi(d->m_frame); properties->addPage(d->m_frame, i18n("&Application")); bool bKDesktopMode = properties->url().scheme() == QLatin1String("desktop") || properties->currentDir().scheme() == QLatin1String("desktop"); if (bKDesktopMode) { // Hide Name entry d->w->nameEdit->hide(); d->w->nameLabel->hide(); } d->w->pathEdit->setMode(KFile::Directory | KFile::LocalOnly); d->w->pathEdit->lineEdit()->setAcceptDrops(false); - connect(d->w->nameEdit, SIGNAL(textChanged(QString)), this, SIGNAL(changed())); - connect(d->w->genNameEdit, SIGNAL(textChanged(QString)), this, SIGNAL(changed())); - connect(d->w->commentEdit, SIGNAL(textChanged(QString)), this, SIGNAL(changed())); - connect(d->w->commandEdit, SIGNAL(textChanged(QString)), this, SIGNAL(changed())); - connect(d->w->pathEdit, SIGNAL(textChanged(QString)), this, SIGNAL(changed())); + connect(d->w->nameEdit, &QLineEdit::textChanged, this, &KPropertiesDialogPlugin::changed); + connect(d->w->genNameEdit, &QLineEdit::textChanged, this, &KPropertiesDialogPlugin::changed); + connect(d->w->commentEdit, &QLineEdit::textChanged, this, &KPropertiesDialogPlugin::changed); + connect(d->w->commandEdit, &QLineEdit::textChanged, this, &KPropertiesDialogPlugin::changed); + connect(d->w->pathEdit, &KUrlRequester::textChanged, this, &KPropertiesDialogPlugin::changed); - connect(d->w->browseButton, SIGNAL(clicked()), this, SLOT(slotBrowseExec())); - connect(d->w->addFiletypeButton, SIGNAL(clicked()), this, SLOT(slotAddFiletype())); - connect(d->w->delFiletypeButton, SIGNAL(clicked()), this, SLOT(slotDelFiletype())); - connect(d->w->advancedButton, SIGNAL(clicked()), this, SLOT(slotAdvanced())); + connect(d->w->browseButton, &QAbstractButton::clicked, this, &KDesktopPropsPlugin::slotBrowseExec); + connect(d->w->addFiletypeButton, &QAbstractButton::clicked, this, &KDesktopPropsPlugin::slotAddFiletype); + connect(d->w->delFiletypeButton, &QAbstractButton::clicked, this, &KDesktopPropsPlugin::slotDelFiletype); + connect(d->w->advancedButton, &QAbstractButton::clicked, this, &KDesktopPropsPlugin::slotAdvanced); enum DiscreteGpuCheck { NotChecked, Present, Absent }; static DiscreteGpuCheck s_gpuCheck = NotChecked; if (s_gpuCheck == NotChecked) { // Check whether we have a discrete gpu bool hasDiscreteGpu = false; QDBusInterface iface(QStringLiteral("org.kde.Solid.PowerManagement"), QStringLiteral("/org/kde/Solid/PowerManagement"), QStringLiteral("org.kde.Solid.PowerManagement"), QDBusConnection::sessionBus()); if (iface.isValid()) { QDBusReply reply = iface.call(QStringLiteral("hasDualGpu")); if (reply.isValid()) { hasDiscreteGpu = reply.value(); } } s_gpuCheck = hasDiscreteGpu ? Present : Absent; } d->m_hasDiscreteGpuBool = s_gpuCheck == Present; // now populate the page KIO::StatJob *job = KIO::mostLocalUrl(_props->url()); KJobWidgets::setWindow(job, _props); job->exec(); QUrl url = job->mostLocalUrl(); if (!url.isLocalFile()) { return; } d->m_origDesktopFile = url.toLocalFile(); QFile f(d->m_origDesktopFile); if (!f.open(QIODevice::ReadOnly)) { return; } f.close(); KDesktopFile _config(d->m_origDesktopFile); KConfigGroup config = _config.desktopGroup(); QString nameStr = _config.readName(); QString genNameStr = _config.readGenericName(); QString commentStr = _config.readComment(); QString commandStr = config.readEntry("Exec", QString()); d->m_origCommandStr = commandStr; QString pathStr = config.readEntry("Path", QString()); // not readPathEntry, see kservice.cpp d->m_terminalBool = config.readEntry("Terminal", false); d->m_terminalOptionStr = config.readEntry("TerminalOptions"); d->m_suidBool = config.readEntry("X-KDE-SubstituteUID", false); d->m_suidUserStr = config.readEntry("X-KDE-Username"); if (d->m_hasDiscreteGpuBool) { d->m_runOnDiscreteGpuBool = config.readEntry("X-KDE-RunOnDiscreteGpu", false); } if (config.hasKey("StartupNotify")) { d->m_startupBool = config.readEntry("StartupNotify", true); } else { d->m_startupBool = config.readEntry("X-KDE-StartupNotify", true); } d->m_dbusStartupType = config.readEntry("X-DBUS-StartupType").toLower(); // ### should there be a GUI for this setting? // At least we're copying it over to the local file, to avoid side effects (#157853) d->m_dbusServiceName = config.readEntry("X-DBUS-ServiceName"); const QStringList mimeTypes = config.readXdgListEntry("MimeType"); if (nameStr.isEmpty() || bKDesktopMode) { // We'll use the file name if no name is specified // because we _need_ a Name for a valid file. // But let's do it in apply, not here, so that we pick up the right name. setDirty(); } if (!bKDesktopMode) { d->w->nameEdit->setText(nameStr); } d->w->genNameEdit->setText(genNameStr); d->w->commentEdit->setText(commentStr); d->w->commandEdit->setText(commandStr); d->w->pathEdit->lineEdit()->setText(pathStr); // was: d->w->filetypeList->setFullWidth(true); // d->w->filetypeList->header()->setStretchEnabled(true, d->w->filetypeList->columns()-1); for (QStringList::ConstIterator it = mimeTypes.begin(); it != mimeTypes.end();) { QMimeType p = db.mimeTypeForName(*it); ++it; QString preference; if (it != mimeTypes.end()) { bool numeric; (*it).toInt(&numeric); if (numeric) { preference = *it; ++it; } } if (p.isValid()) { QTreeWidgetItem *item = new QTreeWidgetItem(); item->setText(0, p.name()); item->setText(1, p.comment()); item->setText(2, preference); d->w->filetypeList->addTopLevelItem(item); } } d->w->filetypeList->resizeColumnToContents(0); } KDesktopPropsPlugin::~KDesktopPropsPlugin() { delete d; } void KDesktopPropsPlugin::slotAddFiletype() { QMimeDatabase db; KMimeTypeChooserDialog dlg(i18n("Add File Type for %1", properties->url().fileName()), i18n("Select one or more file types to add:"), QStringList(), // no preselected mimetypes QString(), QStringList(), KMimeTypeChooser::Comments | KMimeTypeChooser::Patterns, d->m_frame); if (dlg.exec() == QDialog::Accepted) { foreach (const QString &mimetype, dlg.chooser()->mimeTypes()) { QMimeType p = db.mimeTypeForName(mimetype); if (!p.isValid()) { continue; } bool found = false; int count = d->w->filetypeList->topLevelItemCount(); for (int i = 0; !found && i < count; ++i) { if (d->w->filetypeList->topLevelItem(i)->text(0) == mimetype) { found = true; } } if (!found) { QTreeWidgetItem *item = new QTreeWidgetItem(); item->setText(0, p.name()); item->setText(1, p.comment()); d->w->filetypeList->addTopLevelItem(item); } d->w->filetypeList->resizeColumnToContents(0); } } emit changed(); } void KDesktopPropsPlugin::slotDelFiletype() { QTreeWidgetItem *cur = d->w->filetypeList->currentItem(); if (cur) { delete cur; emit changed(); } } void KDesktopPropsPlugin::checkCommandChanged() { if (KIO::DesktopExecParser::executableName(d->w->commandEdit->text()) != KIO::DesktopExecParser::executableName(d->m_origCommandStr)) { d->m_origCommandStr = d->w->commandEdit->text(); d->m_dbusStartupType.clear(); // Reset d->m_dbusServiceName.clear(); } } void KDesktopPropsPlugin::applyChanges() { // qDebug(); KIO::StatJob *job = KIO::mostLocalUrl(properties->url()); KJobWidgets::setWindow(job, properties); job->exec(); const QUrl url = job->mostLocalUrl(); if (!url.isLocalFile()) { KMessageBox::sorry(nullptr, i18n("Could not save properties. Only entries on local file systems are supported.")); return; } const QString path(url.toLocalFile()); // make sure the directory exists QDir().mkpath(QFileInfo(path).absolutePath()); QFile f(path); if (!f.open(QIODevice::ReadWrite)) { KMessageBox::sorry(nullptr, i18n("Could not save properties. You do not have " "sufficient access to write to %1.", path)); return; } f.close(); // If the command is changed we reset certain settings that are strongly // coupled to the command. checkCommandChanged(); KDesktopFile origConfig(d->m_origDesktopFile); QScopedPointer _config(origConfig.copyTo(path)); KConfigGroup config = _config->desktopGroup(); config.writeEntry("Type", QStringLiteral("Application")); config.writeEntry("Comment", d->w->commentEdit->text()); config.writeEntry("Comment", d->w->commentEdit->text(), KConfigGroup::Persistent | KConfigGroup::Localized); // for compat config.writeEntry("GenericName", d->w->genNameEdit->text()); config.writeEntry("GenericName", d->w->genNameEdit->text(), KConfigGroup::Persistent | KConfigGroup::Localized); // for compat config.writeEntry("Exec", d->w->commandEdit->text()); config.writeEntry("Path", d->w->pathEdit->lineEdit()->text()); // not writePathEntry, see kservice.cpp // Write mimeTypes QStringList mimeTypes; int count = d->w->filetypeList->topLevelItemCount(); for (int i = 0; i < count; ++i) { QTreeWidgetItem *item = d->w->filetypeList->topLevelItem(i); QString preference = item->text(2); mimeTypes.append(item->text(0)); if (!preference.isEmpty()) { mimeTypes.append(preference); } } // qDebug() << mimeTypes; config.writeXdgListEntry("MimeType", mimeTypes); if (!d->w->nameEdit->isHidden()) { QString nameStr = d->w->nameEdit->text(); config.writeEntry("Name", nameStr); config.writeEntry("Name", nameStr, KConfigGroup::Persistent | KConfigGroup::Localized); } config.writeEntry("Terminal", d->m_terminalBool); config.writeEntry("TerminalOptions", d->m_terminalOptionStr); config.writeEntry("X-KDE-SubstituteUID", d->m_suidBool); config.writeEntry("X-KDE-Username", d->m_suidUserStr); if (d->m_hasDiscreteGpuBool) { config.writeEntry("X-KDE-RunOnDiscreteGpu", d->m_runOnDiscreteGpuBool); } config.writeEntry("StartupNotify", d->m_startupBool); config.writeEntry("X-DBUS-StartupType", d->m_dbusStartupType); config.writeEntry("X-DBUS-ServiceName", d->m_dbusServiceName); config.sync(); // KSycoca update needed? bool updateNeeded = !relativeAppsLocation(path).isEmpty(); if (updateNeeded) { KBuildSycocaProgressDialog::rebuildKSycoca(d->m_frame); } } void KDesktopPropsPlugin::slotBrowseExec() { QUrl f = QFileDialog::getOpenFileUrl(d->m_frame); if (f.isEmpty()) { return; } if (!f.isLocalFile()) { KMessageBox::sorry(d->m_frame, i18n("Only executables on local file systems are supported.")); return; } QString path = f.toLocalFile(); path = KShell::quoteArg(path); d->w->commandEdit->setText(path); } void KDesktopPropsPlugin::slotAdvanced() { QDialog dlg(d->m_frame); dlg.setObjectName(QStringLiteral("KPropertiesDesktopAdv")); dlg.setModal(true); dlg.setWindowTitle(i18n("Advanced Options for %1", properties->url().fileName())); Ui_KPropertiesDesktopAdvBase w; QWidget *mainWidget = new QWidget(&dlg); w.setupUi(mainWidget); QDialogButtonBox *buttonBox = new QDialogButtonBox(&dlg); buttonBox->setStandardButtons(QDialogButtonBox::Ok | QDialogButtonBox::Cancel); - connect(buttonBox, SIGNAL(accepted()), &dlg, SLOT(accept())); - connect(buttonBox, SIGNAL(rejected()), &dlg, SLOT(reject())); + connect(buttonBox, &QDialogButtonBox::accepted, &dlg, &QDialog::accept); + connect(buttonBox, &QDialogButtonBox::rejected, &dlg, &QDialog::reject); QVBoxLayout *layout = new QVBoxLayout; layout->addWidget(mainWidget); layout->addWidget(buttonBox); dlg.setLayout(layout); // If the command is changed we reset certain settings that are strongly // coupled to the command. checkCommandChanged(); // check to see if we use konsole if not do not add the nocloseonexit // because we don't know how to do this on other terminal applications KConfigGroup confGroup(KSharedConfig::openConfig(), QStringLiteral("General")); QString preferredTerminal = confGroup.readPathEntry("TerminalApplication", QStringLiteral("konsole")); bool terminalCloseBool = false; if (preferredTerminal == QLatin1String("konsole")) { terminalCloseBool = d->m_terminalOptionStr.contains(QStringLiteral("--noclose")); w.terminalCloseCheck->setChecked(terminalCloseBool); d->m_terminalOptionStr.remove(QStringLiteral("--noclose")); } else { w.terminalCloseCheck->hide(); } w.terminalCheck->setChecked(d->m_terminalBool); w.terminalEdit->setText(d->m_terminalOptionStr); w.terminalCloseCheck->setEnabled(d->m_terminalBool); w.terminalEdit->setEnabled(d->m_terminalBool); w.terminalEditLabel->setEnabled(d->m_terminalBool); w.suidCheck->setChecked(d->m_suidBool); w.suidEdit->setText(d->m_suidUserStr); w.suidEdit->setEnabled(d->m_suidBool); w.suidEditLabel->setEnabled(d->m_suidBool); if (d->m_hasDiscreteGpuBool) { w.discreteGpuCheck->setChecked(d->m_runOnDiscreteGpuBool); } else { w.discreteGpuGroupBox->hide(); } w.startupInfoCheck->setChecked(d->m_startupBool); if (d->m_dbusStartupType == QLatin1String("unique")) { w.dbusCombo->setCurrentIndex(2); } else if (d->m_dbusStartupType == QLatin1String("multi")) { w.dbusCombo->setCurrentIndex(1); } else if (d->m_dbusStartupType == QLatin1String("wait")) { w.dbusCombo->setCurrentIndex(3); } else { w.dbusCombo->setCurrentIndex(0); } // Provide username completion up to 1000 users. const int maxEntries = 1000; QStringList userNames = KUser::allUserNames(maxEntries); if (userNames.size() < maxEntries) { KCompletion *kcom = new KCompletion; kcom->setOrder(KCompletion::Sorted); w.suidEdit->setCompletionObject(kcom, true); w.suidEdit->setAutoDeleteCompletionObject(true); w.suidEdit->setCompletionMode(KCompletion::CompletionAuto); kcom->setItems(userNames); } - connect(w.terminalEdit, SIGNAL(textChanged(QString)), - this, SIGNAL(changed())); - connect(w.terminalCloseCheck, SIGNAL(toggled(bool)), - this, SIGNAL(changed())); - connect(w.terminalCheck, SIGNAL(toggled(bool)), - this, SIGNAL(changed())); - connect(w.suidCheck, SIGNAL(toggled(bool)), - this, SIGNAL(changed())); - connect(w.suidEdit, SIGNAL(textChanged(QString)), - this, SIGNAL(changed())); - connect(w.discreteGpuCheck, SIGNAL(toggled(bool)), - this, SIGNAL(changed())); - connect(w.startupInfoCheck, SIGNAL(toggled(bool)), - this, SIGNAL(changed())); - connect(w.dbusCombo, SIGNAL(activated(int)), - this, SIGNAL(changed())); + connect(w.terminalEdit, &QLineEdit::textChanged, + this, &KPropertiesDialogPlugin::changed); + connect(w.terminalCloseCheck, &QAbstractButton::toggled, + this, &KPropertiesDialogPlugin::changed); + connect(w.terminalCheck, &QAbstractButton::toggled, + this, &KPropertiesDialogPlugin::changed); + connect(w.suidCheck, &QAbstractButton::toggled, + this, &KPropertiesDialogPlugin::changed); + connect(w.suidEdit, &QLineEdit::textChanged, + this, &KPropertiesDialogPlugin::changed); + connect(w.discreteGpuCheck, &QAbstractButton::toggled, + this, &KPropertiesDialogPlugin::changed); + connect(w.startupInfoCheck, &QAbstractButton::toggled, + this, &KPropertiesDialogPlugin::changed); + connect(w.dbusCombo, QOverload::of(&QComboBox::activated), + this, &KPropertiesDialogPlugin::changed); if (dlg.exec() == QDialog::Accepted) { d->m_terminalOptionStr = w.terminalEdit->text().trimmed(); d->m_terminalBool = w.terminalCheck->isChecked(); d->m_suidBool = w.suidCheck->isChecked(); d->m_suidUserStr = w.suidEdit->text().trimmed(); if (d->m_hasDiscreteGpuBool) { d->m_runOnDiscreteGpuBool = w.discreteGpuCheck->isChecked(); } d->m_startupBool = w.startupInfoCheck->isChecked(); if (w.terminalCloseCheck->isChecked()) { d->m_terminalOptionStr.append(QLatin1String(" --noclose")); } switch (w.dbusCombo->currentIndex()) { case 1: d->m_dbusStartupType = QStringLiteral("multi"); break; case 2: d->m_dbusStartupType = QStringLiteral("unique"); break; case 3: d->m_dbusStartupType = QStringLiteral("wait"); break; default: d->m_dbusStartupType = QStringLiteral("none"); break; } } } bool KDesktopPropsPlugin::supports(const KFileItemList &_items) { if (_items.count() != 1) { return false; } const KFileItem item = _items.first(); // check if desktop file if (!item.isDesktopFile()) { return false; } // open file and check type bool isLocal; QUrl url = item.mostLocalUrl(isLocal); if (!isLocal) { return false; } KDesktopFile config(url.toLocalFile()); return config.hasApplicationType() && KAuthorized::authorize(QStringLiteral("run_desktop_files")) && KAuthorized::authorize(QStringLiteral("shell_access")); } #include "moc_kpropertiesdialog.cpp" #include "moc_kpropertiesdialog_p.cpp" diff --git a/src/widgets/krun.cpp b/src/widgets/krun.cpp index 6cad542d..05f81435 100644 --- a/src/widgets/krun.cpp +++ b/src/widgets/krun.cpp @@ -1,1642 +1,1642 @@ /* This file is part of the KDE libraries Copyright (C) 2000 Torben Weis Copyright (C) 2006 David Faure Copyright (C) 2009 Michael Pyne This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library 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 Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "krun.h" #include "krun_p.h" #include // HAVE_X11 #include "kio_widgets_debug.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "kio/job.h" #include "kio/global.h" #include "kio/scheduler.h" #include "kopenwithdialog.h" #include "krecentdocument.h" #include "kdesktopfileactions.h" #include "executablefileopendialog_p.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #if HAVE_X11 #include #elif defined(Q_OS_WIN) #include #endif #include KRun::KRunPrivate::KRunPrivate(KRun *parent) : q(parent), m_showingDialog(false) { } void KRun::KRunPrivate::startTimer() { m_timer->start(0); } // --------------------------------------------------------------------------- static QString schemeHandler(const QString &protocol) { // We have up to two sources of data, for protocols not handled by kioslaves (so called "helper") : // 1) the exec line of the .protocol file, if there's one // 2) the application associated with x-scheme-handler/ if there's one // If both exist, then: // A) if the .protocol file says "launch an application", then the new-style handler-app has priority // B) but if the .protocol file is for a kioslave (e.g. kio_http) then this has priority over // firefox or chromium saying x-scheme-handler/http. Gnome people want to send all HTTP urls // to a webbrowser, but we want mimetype-determination-in-calling-application by default // (the user can configure a BrowserApplication though) const KService::Ptr service = KMimeTypeTrader::self()->preferredService(QLatin1String("x-scheme-handler/") + protocol); if (service) { return service->exec(); // for helper protocols, the handler app has priority over the hardcoded one (see A above) } Q_ASSERT(KProtocolInfo::isHelperProtocol(protocol)); return KProtocolInfo::exec(protocol); } // --------------------------------------------------------------------------- bool KRun::isExecutableFile(const QUrl &url, const QString &mimetype) { if (!url.isLocalFile()) { return false; } QFileInfo file(url.toLocalFile()); if (file.isExecutable()) { // Got a prospective file to run QMimeDatabase db; QMimeType mimeType = db.mimeTypeForName(mimetype); if (mimeType.inherits(QStringLiteral("application/x-executable")) || #ifdef Q_OS_WIN mimeType.inherits(QLatin1String("application/x-ms-dos-executable")) || #endif mimeType.inherits(QStringLiteral("application/x-executable-script")) || mimeType.inherits(QStringLiteral("application/x-sharedlib")) ) { return true; } } return false; } void KRun::handleInitError(int kioErrorCode, const QString &errorMsg) { Q_UNUSED(kioErrorCode); d->m_showingDialog = true; KMessageBox::error(d->m_window, errorMsg); d->m_showingDialog = false; } void KRun::handleError(KJob *job) { Q_ASSERT(job); if (job) { d->m_showingDialog = true; job->uiDelegate()->showErrorMessage(); d->m_showingDialog = false; } } #ifndef KIOWIDGETS_NO_DEPRECATED bool KRun::runUrl(const QUrl &url, const QString &mimetype, QWidget *window, bool tempFile, bool runExecutables, const QString &suggestedFileName, const QByteArray &asn) { RunFlags flags = tempFile ? KRun::DeleteTemporaryFiles : RunFlags(); if (runExecutables) { flags |= KRun::RunExecutables; } return runUrl(url, mimetype, window, flags, suggestedFileName, asn); } #endif // This is called by foundMimeType, since it knows the mimetype of the URL bool KRun::runUrl(const QUrl &u, const QString &_mimetype, QWidget *window, RunFlags flags, const QString &suggestedFileName, const QByteArray &asn) { const bool runExecutables = flags.testFlag(KRun::RunExecutables); const bool tempFile = flags.testFlag(KRun::DeleteTemporaryFiles); bool noRun = false; bool noAuth = false; if (_mimetype == QLatin1String("inode/directory-locked")) { KMessageBox::error(window, i18n("Unable to enter %1.\nYou do not have access rights to this location.", u.toDisplayString().toHtmlEscaped())); return false; } else if (_mimetype == QLatin1String("application/x-desktop")) { if (u.isLocalFile() && runExecutables) { return KDesktopFileActions::runWithStartup(u, true, asn); } } else if (isExecutableFile(u, _mimetype)) { if (u.isLocalFile() && runExecutables) { if (KAuthorized::authorize(QStringLiteral("shell_access"))) { return (KRun::runCommand(KShell::quoteArg(u.toLocalFile()), QString(), QString(), window, asn, u.adjusted(QUrl::RemoveFilename).toLocalFile())); // just execute the url as a command // ## TODO implement deleting the file if tempFile==true } else { noAuth = true; } } else if (_mimetype == QLatin1String("application/x-executable")) { noRun = true; } } else if (isExecutable(_mimetype)) { if (!runExecutables) { noRun = true; } if (!KAuthorized::authorize(QStringLiteral("shell_access"))) { noAuth = true; } } if (noRun) { KMessageBox::sorry(window, i18n("The file %1 is an executable program. " "For safety it will not be started.", u.toDisplayString().toHtmlEscaped())); return false; } if (noAuth) { KMessageBox::error(window, i18n("You do not have permission to run %1.", u.toDisplayString().toHtmlEscaped())); return false; } QList lst; lst.append(u); KService::Ptr offer = KMimeTypeTrader::self()->preferredService(_mimetype); if (!offer) { #ifdef Q_OS_WIN // As KDE on windows doesn't know about the windows default applications offers will be empty in nearly all cases. // So we use QDesktopServices::openUrl to let windows decide how to open the file return QDesktopServices::openUrl(u); #else // Open-with dialog // TODO : pass the mimetype as a parameter, to show it (comment field) in the dialog ! // Hmm, in fact KOpenWithDialog::setServiceType already guesses the mimetype from the first URL of the list... return displayOpenWithDialog(lst, window, tempFile, suggestedFileName, asn); #endif } return KRun::runService(*offer, lst, window, tempFile, suggestedFileName, asn); } bool KRun::displayOpenWithDialog(const QList &lst, QWidget *window, bool tempFiles, const QString &suggestedFileName, const QByteArray &asn) { if (!KAuthorized::authorizeAction(QStringLiteral("openwith"))) { KMessageBox::sorry(window, i18n("You are not authorized to select an application to open this file.")); return false; } #ifdef Q_OS_WIN KConfigGroup cfgGroup(KSharedConfig::openConfig(), "KOpenWithDialog Settings"); if (cfgGroup.readEntry("Native", true)) { return KRun::KRunPrivate::displayNativeOpenWithDialog(lst, window, tempFiles, suggestedFileName, asn); } #endif KOpenWithDialog dialog(lst, QString(), QString(), window); dialog.setWindowModality(Qt::WindowModal); if (dialog.exec()) { KService::Ptr service = dialog.service(); if (!service) { //qDebug() << "No service set, running " << dialog.text(); service = KService::Ptr(new KService(QString() /*name*/, dialog.text(), QString() /*icon*/)); } return KRun::runService(*service, lst, window, tempFiles, suggestedFileName, asn); } return false; } #ifndef KIOWIDGETS_NO_DEPRECATED void KRun::shellQuote(QString &_str) { // Credits to Walter, says Bernd G. :) if (_str.isEmpty()) { // Don't create an explicit empty parameter return; } const QChar q = QLatin1Char('\''); _str.replace(q, QLatin1String("'\\''")).prepend(q).append(q); } #endif QStringList KRun::processDesktopExec(const KService &_service, const QList &_urls, bool tempFiles, const QString &suggestedFileName) { KIO::DesktopExecParser parser(_service, _urls); parser.setUrlsAreTempFiles(tempFiles); parser.setSuggestedFileName(suggestedFileName); return parser.resultingArguments(); } #ifndef KIOWIDGETS_NO_DEPRECATED QString KRun::binaryName(const QString &execLine, bool removePath) { return removePath ? KIO::DesktopExecParser::executableName(execLine) : KIO::DesktopExecParser::executablePath(execLine); } #endif static qint64 runCommandInternal(KProcess *proc, const KService *service, const QString &executable, const QString &userVisibleName, const QString &iconName, QWidget *window, const QByteArray &asn) { if (window) { window = window->topLevelWidget(); } if (service && !service->entryPath().isEmpty() && !KDesktopFile::isAuthorizedDesktopFile(service->entryPath())) { qCWarning(KIO_WIDGETS) << "No authorization to execute " << service->entryPath(); KMessageBox::sorry(window, i18n("You are not authorized to execute this file.")); delete proc; return 0; } QString bin = KIO::DesktopExecParser::executableName(executable); #if HAVE_X11 // Startup notification doesn't work with QT/E, service isn't needed without Startup notification static bool isX11 = QGuiApplication::platformName() == QStringLiteral("xcb"); if (isX11) { bool silent; QByteArray wmclass; KStartupInfoId id; bool startup_notify = (asn != "0" && KRun::checkStartupNotify(QString() /*unused*/, service, &silent, &wmclass)); if (startup_notify) { id.initId(asn); id.setupStartupEnv(); KStartupInfoData data; data.setHostname(); data.setBin(bin); if (!userVisibleName.isEmpty()) { data.setName(userVisibleName); } else if (service && !service->name().isEmpty()) { data.setName(service->name()); } data.setDescription(i18n("Launching %1", data.name())); if (!iconName.isEmpty()) { data.setIcon(iconName); } else if (service && !service->icon().isEmpty()) { data.setIcon(service->icon()); } if (!wmclass.isEmpty()) { data.setWMClass(wmclass); } if (silent) { data.setSilent(KStartupInfoData::Yes); } data.setDesktop(KWindowSystem::currentDesktop()); // QTBUG-59017 Calling winId() on an embedded widget will break interaction // with it on high-dpi multi-screen setups (cf. also Bug 363548), hence using // its parent window instead if (window && window->window()) { data.setLaunchedBy(window->window()->winId()); } if (service && !service->entryPath().isEmpty()) { data.setApplicationId(service->entryPath()); } KStartupInfo::sendStartup(id, data); } qint64 pid = KProcessRunner::run(proc, executable, id); if (startup_notify && pid) { KStartupInfoData data; data.addPid(pid); KStartupInfo::sendChange(id, data); KStartupInfo::resetStartupEnv(); } return pid; } #else Q_UNUSED(userVisibleName); Q_UNUSED(iconName); #endif return KProcessRunner::run(proc, bin, KStartupInfoId()); } // This code is also used in klauncher. bool KRun::checkStartupNotify(const QString & /*binName*/, const KService *service, bool *silent_arg, QByteArray *wmclass_arg) { bool silent = false; QByteArray wmclass; if (service && service->property(QStringLiteral("StartupNotify")).isValid()) { silent = !service->property(QStringLiteral("StartupNotify")).toBool(); wmclass = service->property(QStringLiteral("StartupWMClass")).toString().toLatin1(); } else if (service && service->property(QStringLiteral("X-KDE-StartupNotify")).isValid()) { silent = !service->property(QStringLiteral("X-KDE-StartupNotify")).toBool(); wmclass = service->property(QStringLiteral("X-KDE-WMClass")).toString().toLatin1(); } else { // non-compliant app if (service) { if (service->isApplication()) { // doesn't have .desktop entries needed, start as non-compliant wmclass = "0"; // krazy:exclude=doublequote_chars } else { return false; // no startup notification at all } } else { #if 0 // Create startup notification even for apps for which there shouldn't be any, // just without any visual feedback. This will ensure they'll be positioned on the proper // virtual desktop, and will get user timestamp from the ASN ID. wmclass = '0'; silent = true; #else // That unfortunately doesn't work, when the launched non-compliant application // launches another one that is compliant and there is any delay inbetween (bnc:#343359) return false; #endif } } if (silent_arg) { *silent_arg = silent; } if (wmclass_arg) { *wmclass_arg = wmclass; } return true; } static qint64 runApplicationImpl(const KService &_service, const QList &_urls, QWidget *window, KRun::RunFlags flags, const QString &suggestedFileName, const QByteArray &asn) { QList urlsToRun = _urls; if ((_urls.count() > 1) && !_service.allowMultipleFiles()) { // We need to launch the application N times. That sucks. // We ignore the result for application 2 to N. // For the first file we launch the application in the // usual way. The reported result is based on this // application. QList::ConstIterator it = _urls.begin(); while (++it != _urls.end()) { QList singleUrl; singleUrl.append(*it); runApplicationImpl(_service, singleUrl, window, flags, suggestedFileName, QByteArray()); } urlsToRun.clear(); urlsToRun.append(_urls.first()); } KIO::DesktopExecParser execParser(_service, urlsToRun); execParser.setUrlsAreTempFiles(flags & KRun::DeleteTemporaryFiles); execParser.setSuggestedFileName(suggestedFileName); const QStringList args = execParser.resultingArguments(); if (args.isEmpty()) { KMessageBox::sorry(window, i18n("Error processing Exec field in %1", _service.entryPath())); return 0; } //qDebug() << "runTempService: KProcess args=" << args; KProcess * proc = new KProcess; *proc << args; enum DiscreteGpuCheck { NotChecked, Present, Absent }; static DiscreteGpuCheck s_gpuCheck = NotChecked; if (_service.runOnDiscreteGpu() && s_gpuCheck == NotChecked) { // Check whether we have a discrete gpu bool hasDiscreteGpu = false; QDBusInterface iface(QStringLiteral("org.kde.Solid.PowerManagement"), QStringLiteral("/org/kde/Solid/PowerManagement"), QStringLiteral("org.kde.Solid.PowerManagement"), QDBusConnection::sessionBus()); if (iface.isValid()) { QDBusReply reply = iface.call(QStringLiteral("hasDualGpu")); if (reply.isValid()) { hasDiscreteGpu = reply.value(); } } s_gpuCheck = hasDiscreteGpu ? Present : Absent; } if (_service.runOnDiscreteGpu() && s_gpuCheck == Present) { proc->setEnv(QStringLiteral("DRI_PRIME"), QStringLiteral("1")); } QString path(_service.path()); if (path.isEmpty() && !_urls.isEmpty() && _urls.first().isLocalFile()) { path = _urls.first().adjusted(QUrl::RemoveFilename).toLocalFile(); } proc->setWorkingDirectory(path); return runCommandInternal(proc, &_service, KIO::DesktopExecParser::executablePath(_service.exec()), _service.name(), _service.icon(), window, asn); } // WARNING: don't call this from DesktopExecParser, since klauncher uses that too... // TODO: make this async, see the job->exec() in there... static QList resolveURLs(const QList &_urls, const KService &_service) { // Check which protocols the application supports. // This can be a list of actual protocol names, or just KIO for KDE apps. QStringList appSupportedProtocols = KIO::DesktopExecParser::supportedProtocols(_service); QList urls(_urls); if (!appSupportedProtocols.contains(QStringLiteral("KIO"))) { for (QList::Iterator it = urls.begin(); it != urls.end(); ++it) { const QUrl url = *it; bool supported = KIO::DesktopExecParser::isProtocolInSupportedList(url, appSupportedProtocols); //qDebug() << "Looking at url=" << url << " supported=" << supported; if (!supported && KProtocolInfo::protocolClass(url.scheme()) == QLatin1String(":local")) { // Maybe we can resolve to a local URL? KIO::StatJob *job = KIO::mostLocalUrl(url); if (job->exec()) { // ## nasty nested event loop! const QUrl localURL = job->mostLocalUrl(); if (localURL != url) { *it = localURL; //qDebug() << "Changed to" << localURL; } } } } } return urls; } // Simple QDialog that resizes the given text edit after being shown to more // or less fit the enclosed text. class SecureMessageDialog : public QDialog { Q_OBJECT public: SecureMessageDialog(QWidget *parent) : QDialog(parent), m_textEdit(nullptr) { } void setTextEdit(QPlainTextEdit *textEdit) { m_textEdit = textEdit; } protected: void showEvent(QShowEvent *e) override { // Now that we're shown, use our width to calculate a good // bounding box for the text, and resize m_textEdit appropriately. QDialog::showEvent(e); if (!m_textEdit) { return; } QSize fudge(20, 24); // About what it sounds like :-/ // Form rect with a lot of height for bounding. Use no more than // 5 lines. QRect curRect(m_textEdit->rect()); QFontMetrics metrics(fontMetrics()); curRect.setHeight(5 * metrics.lineSpacing()); curRect.setWidth(qMax(curRect.width(), 300)); // At least 300 pixels ok? QString text(m_textEdit->toPlainText()); curRect = metrics.boundingRect(curRect, Qt::TextWordWrap | Qt::TextSingleLine, text); // Scroll bars interfere. If we don't think there's enough room, enable // the vertical scrollbar however. m_textEdit->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); if (curRect.height() < m_textEdit->height()) { // then we've got room m_textEdit->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff); m_textEdit->setMaximumHeight(curRect.height() + fudge.height()); } m_textEdit->setMinimumSize(curRect.size() + fudge); m_textEdit->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Minimum); updateGeometry(); } private: QPlainTextEdit *m_textEdit; }; // Helper function to make the given .desktop file executable by ensuring // that a #!/usr/bin/env xdg-open line is added if necessary and the file has // the +x bit set for the user. Returns false if either fails. static bool makeFileExecutable(const QString &fileName) { // Open the file and read the first two characters, check if it's // #!. If not, create a new file, prepend appropriate lines, and copy // over. QFile desktopFile(fileName); if (!desktopFile.open(QFile::ReadOnly)) { qCWarning(KIO_WIDGETS) << "Error opening service" << fileName << desktopFile.errorString(); return false; } QByteArray header = desktopFile.peek(2); // First two chars of file if (header.size() == 0) { qCWarning(KIO_WIDGETS) << "Error inspecting service" << fileName << desktopFile.errorString(); return false; // Some kind of error } if (header != "#!") { // Add header QSaveFile saveFile; saveFile.setFileName(fileName); if (!saveFile.open(QIODevice::WriteOnly)) { qCWarning(KIO_WIDGETS) << "Unable to open replacement file for" << fileName << saveFile.errorString(); return false; } QByteArray shebang("#!/usr/bin/env xdg-open\n"); if (saveFile.write(shebang) != shebang.size()) { qCWarning(KIO_WIDGETS) << "Error occurred adding header for" << fileName << saveFile.errorString(); saveFile.cancelWriting(); return false; } // Now copy the one into the other and then close and reopen desktopFile QByteArray desktopData(desktopFile.readAll()); if (desktopData.isEmpty()) { qCWarning(KIO_WIDGETS) << "Unable to read service" << fileName << desktopFile.errorString(); saveFile.cancelWriting(); return false; } if (saveFile.write(desktopData) != desktopData.size()) { qCWarning(KIO_WIDGETS) << "Error copying service" << fileName << saveFile.errorString(); saveFile.cancelWriting(); return false; } desktopFile.close(); if (!saveFile.commit()) { // Figures.... qCWarning(KIO_WIDGETS) << "Error committing changes to service" << fileName << saveFile.errorString(); return false; } if (!desktopFile.open(QFile::ReadOnly)) { qCWarning(KIO_WIDGETS) << "Error re-opening service" << fileName << desktopFile.errorString(); return false; } } // Add header // corresponds to owner on unix, which will have to do since if the user // isn't the owner we can't change perms anyways. if (!desktopFile.setPermissions(QFile::ExeUser | desktopFile.permissions())) { qCWarning(KIO_WIDGETS) << "Unable to change permissions for" << fileName << desktopFile.errorString(); return false; } // whew return true; } // Helper function to make a .desktop file executable if prompted by the user. // returns true if KRun::run() should continue with execution, false if user declined // to make the file executable or we failed to make it executable. static bool makeServiceExecutable(const KService &service, QWidget *window) { if (!KAuthorized::authorize(QStringLiteral("run_desktop_files"))) { qCWarning(KIO_WIDGETS) << "No authorization to execute " << service.entryPath(); KMessageBox::sorry(window, i18n("You are not authorized to execute this service.")); return false; // Don't circumvent the Kiosk } SecureMessageDialog *baseDialog = new SecureMessageDialog(window); baseDialog->setWindowTitle(i18nc("Warning about executing unknown .desktop file", "Warning")); QVBoxLayout *topLayout = new QVBoxLayout; baseDialog->setLayout(topLayout); // Dialog will have explanatory text with a disabled lineedit with the // Exec= to make it visually distinct. QWidget *baseWidget = new QWidget(baseDialog); QHBoxLayout *mainLayout = new QHBoxLayout(baseWidget); QLabel *iconLabel = new QLabel(baseWidget); QPixmap warningIcon(KIconLoader::global()->loadIcon(QStringLiteral("dialog-warning"), KIconLoader::NoGroup, KIconLoader::SizeHuge)); mainLayout->addWidget(iconLabel); iconLabel->setPixmap(warningIcon); QVBoxLayout *contentLayout = new QVBoxLayout; QString warningMessage = i18nc("program name follows in a line edit below", "This will start the program:"); QLabel *message = new QLabel(warningMessage, baseWidget); contentLayout->addWidget(message); // We can use KStandardDirs::findExe to resolve relative pathnames // but that gets rid of the command line arguments. QString program = QFileInfo(service.exec()).canonicalFilePath(); if (program.isEmpty()) { // e.g. due to command line arguments program = service.exec(); } QPlainTextEdit *textEdit = new QPlainTextEdit(baseWidget); textEdit->setPlainText(program); textEdit->setReadOnly(true); contentLayout->addWidget(textEdit); QLabel *footerLabel = new QLabel(i18n("If you do not trust this program, click Cancel")); contentLayout->addWidget(footerLabel); contentLayout->addStretch(0); // Don't allow the text edit to expand mainLayout->addLayout(contentLayout); topLayout->addWidget(baseWidget); baseDialog->setTextEdit(textEdit); QDialogButtonBox *buttonBox = new QDialogButtonBox(baseDialog); buttonBox->setStandardButtons(QDialogButtonBox::Ok | QDialogButtonBox::Cancel); KGuiItem::assign(buttonBox->button(QDialogButtonBox::Ok), KStandardGuiItem::cont()); buttonBox->button(QDialogButtonBox::Cancel)->setDefault(true); buttonBox->button(QDialogButtonBox::Cancel)->setFocus(); - QObject::connect(buttonBox, SIGNAL(accepted()), baseDialog, SLOT(accept())); - QObject::connect(buttonBox, SIGNAL(rejected()), baseDialog, SLOT(reject())); + QObject::connect(buttonBox, &QDialogButtonBox::accepted, baseDialog, &QDialog::accept); + QObject::connect(buttonBox, &QDialogButtonBox::rejected, baseDialog, &QDialog::reject); topLayout->addWidget(buttonBox); // Constrain maximum size. Minimum size set in // the dialog's show event. QSize screenSize = QApplication::desktop()->screen()->size(); baseDialog->resize(screenSize.width() / 4, 50); baseDialog->setMaximumHeight(screenSize.height() / 3); baseDialog->setMaximumWidth(screenSize.width() / 10 * 8); int result = baseDialog->exec(); if (result != QDialog::Accepted) { return false; } // Assume that service is an absolute path since we're being called (relative paths // would have been allowed unless Kiosk said no, therefore we already know where the // .desktop file is. Now add a header to it if it doesn't already have one // and add the +x bit. if (!::makeFileExecutable(service.entryPath())) { QString serviceName = service.name(); if (serviceName.isEmpty()) { serviceName = service.genericName(); } KMessageBox::sorry( window, i18n("Unable to make the service %1 executable, aborting execution", serviceName) ); return false; } return true; } bool KRun::run(const KService &_service, const QList &_urls, QWidget *window, bool tempFiles, const QString &suggestedFileName, const QByteArray &asn) { return runService(_service, _urls, window, tempFiles, suggestedFileName, asn) != 0; } qint64 KRun::runApplication(const KService &service, const QList &urls, QWidget *window, RunFlags flags, const QString &suggestedFileName, const QByteArray &asn) { if (!service.entryPath().isEmpty() && !KDesktopFile::isAuthorizedDesktopFile(service.entryPath()) && !::makeServiceExecutable(service, window)) { return 0; } if ((flags & DeleteTemporaryFiles) == 0) { // Remember we opened those urls, for the "recent documents" menu in kicker for (const QUrl &url : urls) { KRecentDocument::add(url, service.desktopEntryName()); } } return runApplicationImpl(service, urls, window, flags, suggestedFileName, asn); } qint64 KRun::runService(const KService &_service, const QList &_urls, QWidget *window, bool tempFiles, const QString &suggestedFileName, const QByteArray &asn) { if (!_service.entryPath().isEmpty() && !KDesktopFile::isAuthorizedDesktopFile(_service.entryPath()) && !::makeServiceExecutable(_service, window)) { return 0; } if (!tempFiles) { // Remember we opened those urls, for the "recent documents" menu in kicker for (const QUrl &url : _urls) { KRecentDocument::add(url, _service.desktopEntryName()); } } bool useKToolInvocation = !(tempFiles || _service.entryPath().isEmpty() || !suggestedFileName.isEmpty()); if (useKToolInvocation) { // Is klauncher installed? Let's try to start it, if it fails, then we won't use it. static int klauncherAvailable = -1; if (klauncherAvailable == -1) { KToolInvocation::ensureKdeinitRunning(); QDBusConnectionInterface *dbusDaemon = QDBusConnection::sessionBus().interface(); klauncherAvailable = dbusDaemon->isServiceRegistered(QStringLiteral("org.kde.klauncher5")); } if (klauncherAvailable == 0) { useKToolInvocation = false; } } if (!useKToolInvocation) { return runApplicationImpl(_service, _urls, window, tempFiles ? RunFlags(DeleteTemporaryFiles) : RunFlags(), suggestedFileName, asn); } // Resolve urls if needed, depending on what the app supports const QList urls = resolveURLs(_urls, _service); //qDebug() << "Running" << _service.entryPath() << _urls << "using klauncher"; QString error; int pid = 0; //TODO KF6: change KToolInvokation to take a qint64* QByteArray myasn = asn; // startServiceByDesktopPath() doesn't take QWidget*, add it to the startup info now if (window) { if (myasn.isEmpty()) { myasn = KStartupInfo::createNewStartupId(); } if (myasn != "0") { KStartupInfoId id; id.initId(myasn); KStartupInfoData data; // QTBUG-59017 Calling winId() on an embedded widget will break interaction // with it on high-dpi multi-screen setups (cf. also Bug 363548), hence using // its parent window instead if (window->window()) { data.setLaunchedBy(window->window()->winId()); } KStartupInfo::sendChange(id, data); } } int i = KToolInvocation::startServiceByDesktopPath( _service.entryPath(), QUrl::toStringList(urls), &error, nullptr, &pid, myasn ); if (i != 0) { //qDebug() << error; KMessageBox::sorry(window, error); return 0; } //qDebug() << "startServiceByDesktopPath worked fine"; return pid; } bool KRun::run(const QString &_exec, const QList &_urls, QWidget *window, const QString &_name, const QString &_icon, const QByteArray &asn) { KService::Ptr service(new KService(_name, _exec, _icon)); return runService(*service, _urls, window, false, QString(), asn); } bool KRun::runCommand(const QString &cmd, QWidget *window, const QString &workingDirectory) { if (cmd.isEmpty()) { qCWarning(KIO_WIDGETS) << "Command was empty, nothing to run"; return false; } const QStringList args = KShell::splitArgs(cmd); if (args.isEmpty()) { qCWarning(KIO_WIDGETS) << "Command could not be parsed."; return false; } const QString bin = args.first(); return KRun::runCommand(cmd, bin, bin /*iconName*/, window, QByteArray(), workingDirectory); } bool KRun::runCommand(const QString &cmd, const QString &execName, const QString &iconName, QWidget *window, const QByteArray &asn) { return runCommand(cmd, execName, iconName, window, asn, QString()); } bool KRun::runCommand(const QString &cmd, const QString &execName, const QString &iconName, QWidget *window, const QByteArray &asn, const QString &workingDirectory) { //qDebug() << "runCommand " << cmd << "," << execName; KProcess *proc = new KProcess; proc->setShellCommand(cmd); if (!workingDirectory.isEmpty()) { proc->setWorkingDirectory(workingDirectory); } QString bin = KIO::DesktopExecParser::executableName(execName); KService::Ptr service = KService::serviceByDesktopName(bin); return runCommandInternal(proc, service.data(), execName /*executable to check for in slotProcessExited*/, execName /*user-visible name*/, iconName, window, asn) != 0; } KRun::KRun(const QUrl &url, QWidget *window, bool showProgressInfo, const QByteArray &asn) : d(new KRunPrivate(this)) { d->m_timer = new QTimer(this); d->m_timer->setObjectName(QStringLiteral("KRun::timer")); d->m_timer->setSingleShot(true); d->init(url, window, showProgressInfo, asn); } void KRun::KRunPrivate::init(const QUrl &url, QWidget *window, bool showProgressInfo, const QByteArray &asn) { m_bFault = false; m_bAutoDelete = true; m_bProgressInfo = showProgressInfo; m_bFinished = false; m_job = nullptr; m_strURL = url; m_bScanFile = false; m_bIsDirectory = false; m_runExecutables = true; m_window = window; m_asn = asn; q->setEnableExternalBrowser(true); // Start the timer. This means we will return to the event // loop and do initialization afterwards. // Reason: We must complete the constructor before we do anything else. m_bCheckPrompt = false; m_bInit = true; - q->connect(m_timer, SIGNAL(timeout()), q, SLOT(slotTimeout())); + q->connect(m_timer, &QTimer::timeout, q, &KRun::slotTimeout); startTimer(); //qDebug() << "new KRun" << q << url << "timer=" << m_timer; } void KRun::init() { //qDebug() << "INIT called"; if (!d->m_strURL.isValid() || d->m_strURL.scheme().isEmpty()) { const QString error = !d->m_strURL.isValid() ? d->m_strURL.errorString() : d->m_strURL.toString(); handleInitError(KIO::ERR_MALFORMED_URL, i18n("Malformed URL\n%1", error)); qCWarning(KIO_WIDGETS) << "Malformed URL:" << error; d->m_bFault = true; d->m_bFinished = true; d->startTimer(); return; } if (!KUrlAuthorized::authorizeUrlAction(QStringLiteral("open"), QUrl(), d->m_strURL)) { QString msg = KIO::buildErrorString(KIO::ERR_ACCESS_DENIED, d->m_strURL.toDisplayString()); handleInitError(KIO::ERR_ACCESS_DENIED, msg); d->m_bFault = true; d->m_bFinished = true; d->startTimer(); return; } if (!d->m_externalBrowser.isEmpty() && d->m_strURL.scheme().startsWith(QLatin1String("http"))) { if (d->runExecutable(d->m_externalBrowser)) { return; } } else if (d->m_strURL.isLocalFile() && (d->m_strURL.host().isEmpty() || (d->m_strURL.host() == QLatin1String("localhost")) || (d->m_strURL.host().compare(QHostInfo::localHostName(), Qt::CaseInsensitive) == 0))) { const QString localPath = d->m_strURL.toLocalFile(); if (!QFile::exists(localPath)) { handleInitError(KIO::ERR_DOES_NOT_EXIST, i18n("Unable to run the command specified. " "The file or folder %1 does not exist.", localPath.toHtmlEscaped())); d->m_bFault = true; d->m_bFinished = true; d->startTimer(); return; } QMimeDatabase db; QMimeType mime = db.mimeTypeForUrl(d->m_strURL); //qDebug() << "MIME TYPE is " << mime.name(); if (!d->m_externalBrowser.isEmpty() && ( mime.inherits(QStringLiteral("text/html")) || mime.inherits(QStringLiteral("application/xhtml+xml")))) { if (d->runExecutable(d->m_externalBrowser)) { return; } } else if (mime.isDefault() && !QFileInfo(localPath).isReadable()) { // Unknown mimetype because the file is unreadable, no point in showing an open-with dialog (#261002) const QString msg = KIO::buildErrorString(KIO::ERR_ACCESS_DENIED, localPath); handleInitError(KIO::ERR_ACCESS_DENIED, msg); d->m_bFault = true; d->m_bFinished = true; d->startTimer(); return; } else { mimeTypeDetermined(mime.name()); return; } } else if (KIO::DesktopExecParser::hasSchemeHandler(d->m_strURL)) { //qDebug() << "Using scheme handler"; const QString exec = schemeHandler(d->m_strURL.scheme()); if (exec.isEmpty()) { mimeTypeDetermined(KProtocolManager::defaultMimetype(d->m_strURL)); return; } else { if (run(exec, QList() << d->m_strURL, d->m_window, QString(), QString(), d->m_asn)) { d->m_bFinished = true; d->startTimer(); return; } } } #if 0 // removed for KF5 (for portability). Reintroduce a bool or flag if useful. // Did we already get the information that it is a directory ? if ((d->m_mode & QT_STAT_MASK) == QT_STAT_DIR) { mimeTypeDetermined("inode/directory"); return; } #endif // Let's see whether it is a directory if (!KProtocolManager::supportsListing(d->m_strURL)) { // No support for listing => it can't be a directory (example: http) if (!KProtocolManager::supportsReading(d->m_strURL)) { // No support for reading files either => we can't do anything (example: mailto URL, with no associated app) handleInitError(KIO::ERR_UNSUPPORTED_ACTION, i18n("Could not find any application or handler for %1", d->m_strURL.toDisplayString())); d->m_bFault = true; d->m_bFinished = true; d->startTimer(); return; } scanFile(); return; } //qDebug() << "Testing directory (stating)"; // It may be a directory or a file, let's stat KIO::JobFlags flags = d->m_bProgressInfo ? KIO::DefaultFlags : KIO::HideProgressInfo; KIO::StatJob *job = KIO::stat(d->m_strURL, KIO::StatJob::SourceSide, 0 /* no details */, flags); KJobWidgets::setWindow(job, d->m_window); - connect(job, SIGNAL(result(KJob*)), - this, SLOT(slotStatResult(KJob*))); + connect(job, &KJob::result, + this, &KRun::slotStatResult); d->m_job = job; //qDebug() << "Job" << job << "is about stating" << d->m_strURL; } KRun::~KRun() { //qDebug() << this; d->m_timer->stop(); killJob(); //qDebug() << this << "done"; delete d; } bool KRun::KRunPrivate::runExecutable(const QString &_exec) { QList urls; urls.append(m_strURL); if (_exec.startsWith(QLatin1Char('!'))) { QString exec = _exec.mid(1); // Literal command exec += QLatin1String(" %u"); if (q->run(exec, urls, m_window, QString(), QString(), m_asn)) { m_bFinished = true; startTimer(); return true; } } else { KService::Ptr service = KService::serviceByStorageId(_exec); if (service && q->runService(*service, urls, m_window, false, QString(), m_asn)) { m_bFinished = true; startTimer(); return true; } } return false; } void KRun::KRunPrivate::showPrompt() { ExecutableFileOpenDialog *dialog = new ExecutableFileOpenDialog(q->window()); dialog->setAttribute(Qt::WA_DeleteOnClose); connect(dialog, &ExecutableFileOpenDialog::finished, q, [this, dialog](int result){ onDialogFinished(result, dialog->isDontAskAgainChecked()); }); dialog->show(); } bool KRun::KRunPrivate::isPromptNeeded() { if (m_strURL == QUrl(QStringLiteral("remote:/x-wizard_service.desktop"))) { return false; } const QMimeDatabase db; const QMimeType mime = db.mimeTypeForUrl(m_strURL); const bool isFileExecutable = (isExecutableFile(m_strURL, mime.name()) || mime.inherits(QStringLiteral("application/x-desktop"))); const bool isTextFile = mime.inherits(QStringLiteral("text/plain")); if (isFileExecutable && isTextFile) { KConfigGroup cfgGroup(KSharedConfig::openConfig(QStringLiteral("kiorc")), "Executable scripts"); const QString value = cfgGroup.readEntry("behaviourOnLaunch", "alwaysAsk"); if (value == QLatin1String("alwaysAsk")) { return true; } else { q->setRunExecutables(value == QLatin1String("execute")); } } return false; } void KRun::KRunPrivate::onDialogFinished(int result, bool isDontAskAgainSet) { if (result == ExecutableFileOpenDialog::Rejected) { m_bFinished = true; m_bInit = false; startTimer(); return; } q->setRunExecutables(result == ExecutableFileOpenDialog::ExecuteFile); if (isDontAskAgainSet) { QString output = result == ExecutableFileOpenDialog::OpenFile ? QStringLiteral("open") : QStringLiteral("execute"); KConfigGroup cfgGroup(KSharedConfig::openConfig(QStringLiteral("kiorc")), "Executable scripts"); cfgGroup.writeEntry("behaviourOnLaunch", output); } startTimer(); } void KRun::scanFile() { //qDebug() << d->m_strURL; // First, let's check for well-known extensions // Not when there is a query in the URL, in any case. if (!d->m_strURL.hasQuery()) { QMimeDatabase db; QMimeType mime = db.mimeTypeForUrl(d->m_strURL); if (!mime.isDefault() || d->m_strURL.isLocalFile()) { //qDebug() << "Scanfile: MIME TYPE is " << mime.name(); mimeTypeDetermined(mime.name()); return; } } // No mimetype found, and the URL is not local (or fast mode not allowed). // We need to apply the 'KIO' method, i.e. either asking the server or // getting some data out of the file, to know what mimetype it is. if (!KProtocolManager::supportsReading(d->m_strURL)) { qCWarning(KIO_WIDGETS) << "#### NO SUPPORT FOR READING!"; d->m_bFault = true; d->m_bFinished = true; d->startTimer(); return; } //qDebug() << this << "Scanning file" << d->m_strURL; KIO::JobFlags flags = d->m_bProgressInfo ? KIO::DefaultFlags : KIO::HideProgressInfo; KIO::TransferJob *job = KIO::get(d->m_strURL, KIO::NoReload /*reload*/, flags); KJobWidgets::setWindow(job, d->m_window); - connect(job, SIGNAL(result(KJob*)), - this, SLOT(slotScanFinished(KJob*))); - connect(job, SIGNAL(mimetype(KIO::Job*,QString)), - this, SLOT(slotScanMimeType(KIO::Job*,QString))); + connect(job, &KJob::result, + this, &KRun::slotScanFinished); + connect(job, QOverload::of(&KIO::TransferJob::mimetype), + this, &KRun::slotScanMimeType); d->m_job = job; //qDebug() << "Job" << job << "is about getting from" << d->m_strURL; } // When arriving in that method there are 6 possible states: // must_show_prompt, must_init, must_scan_file, found_dir, done+error or done+success. void KRun::slotTimeout() { if (d->m_bCheckPrompt) { d->m_bCheckPrompt = false; if (d->isPromptNeeded()) { d->showPrompt(); return; } } if (d->m_bInit) { d->m_bInit = false; init(); return; } if (d->m_bFault) { emit error(); } if (d->m_bFinished) { emit finished(); } else { if (d->m_bScanFile) { d->m_bScanFile = false; scanFile(); return; } else if (d->m_bIsDirectory) { d->m_bIsDirectory = false; mimeTypeDetermined(QStringLiteral("inode/directory")); return; } } if (d->m_bAutoDelete) { deleteLater(); return; } } void KRun::slotStatResult(KJob *job) { d->m_job = nullptr; const int errCode = job->error(); if (errCode) { // ERR_NO_CONTENT is not an error, but an indication no further // actions needs to be taken. if (errCode != KIO::ERR_NO_CONTENT) { qCWarning(KIO_WIDGETS) << this << "ERROR" << job->error() << job->errorString(); handleError(job); //qDebug() << this << " KRun returning from showErrorDialog, starting timer to delete us"; d->m_bFault = true; } d->m_bFinished = true; // will emit the error and autodelete this d->startTimer(); } else { //qDebug() << "Finished"; KIO::StatJob *statJob = qobject_cast(job); if (!statJob) { qFatal("Fatal Error: job is a %s, should be a StatJob", typeid(*job).name()); } // Update our URL in case of a redirection setUrl(statJob->url()); const KIO::UDSEntry entry = statJob->statResult(); const mode_t mode = entry.numberValue(KIO::UDSEntry::UDS_FILE_TYPE); if ((mode & QT_STAT_MASK) == QT_STAT_DIR) { d->m_bIsDirectory = true; // it's a dir } else { d->m_bScanFile = true; // it's a file } d->m_localPath = entry.stringValue(KIO::UDSEntry::UDS_LOCAL_PATH); // mimetype already known? (e.g. print:/manager) const QString knownMimeType = entry.stringValue(KIO::UDSEntry::UDS_MIME_TYPE); if (!knownMimeType.isEmpty()) { mimeTypeDetermined(knownMimeType); d->m_bFinished = true; } // We should have found something assert(d->m_bScanFile || d->m_bIsDirectory); // Start the timer. Once we get the timer event this // protocol server is back in the pool and we can reuse it. // This gives better performance than starting a new slave d->startTimer(); } } void KRun::slotScanMimeType(KIO::Job *, const QString &mimetype) { if (mimetype.isEmpty()) { qCWarning(KIO_WIDGETS) << "get() didn't emit a mimetype! Probably a kioslave bug, please check the implementation of" << url().scheme(); } mimeTypeDetermined(mimetype); d->m_job = nullptr; } void KRun::slotScanFinished(KJob *job) { d->m_job = nullptr; const int errCode = job->error(); if (errCode) { // ERR_NO_CONTENT is not an error, but an indication no further // actions needs to be taken. if (errCode != KIO::ERR_NO_CONTENT) { qCWarning(KIO_WIDGETS) << this << "ERROR (stat):" << job->error() << ' ' << job->errorString(); handleError(job); d->m_bFault = true; } d->m_bFinished = true; // will emit the error and autodelete this d->startTimer(); } } void KRun::mimeTypeDetermined(const QString &mimeType) { // foundMimeType reimplementations might show a dialog box; // make sure some timer doesn't kill us meanwhile (#137678, #156447) Q_ASSERT(!d->m_showingDialog); d->m_showingDialog = true; foundMimeType(mimeType); d->m_showingDialog = false; // We cannot assume that we're finished here. Some reimplementations // start a KIO job and call setFinished only later. } void KRun::foundMimeType(const QString &type) { //qDebug() << "Resulting mime type is " << type; QMimeDatabase db; KIO::TransferJob *job = qobject_cast(d->m_job); if (job) { // Update our URL in case of a redirection setUrl(job->url()); job->putOnHold(); KIO::Scheduler::publishSlaveOnHold(); d->m_job = nullptr; } Q_ASSERT(!d->m_bFinished); // Support for preferred service setting, see setPreferredService if (!d->m_preferredService.isEmpty()) { //qDebug() << "Attempting to open with preferred service: " << d->m_preferredService; KService::Ptr serv = KService::serviceByDesktopName(d->m_preferredService); if (serv && serv->hasMimeType(type)) { QList lst; lst.append(d->m_strURL); if (KRun::runService(*serv, lst, d->m_window, false, QString(), d->m_asn)) { setFinished(true); return; } /// Note: if that service failed, we'll go to runUrl below to /// maybe find another service, even though an error dialog box was /// already displayed. That's good if runUrl tries another service, /// but it's not good if it tries the same one :} } } // Resolve .desktop files from media:/, remote:/, applications:/ etc. QMimeType mime = db.mimeTypeForName(type); if (!mime.isValid()) { qCWarning(KIO_WIDGETS) << "Unknown mimetype " << type; } else if (mime.inherits(QStringLiteral("application/x-desktop")) && !d->m_localPath.isEmpty()) { d->m_strURL = QUrl::fromLocalFile(d->m_localPath); } KRun::RunFlags runFlags; if (d->m_runExecutables) { runFlags |= KRun::RunExecutables; } if (!KRun::runUrl(d->m_strURL, type, d->m_window, runFlags, d->m_suggestedFileName, d->m_asn)) { d->m_bFault = true; } setFinished(true); } void KRun::killJob() { if (d->m_job) { //qDebug() << this << "m_job=" << d->m_job; d->m_job->kill(); d->m_job = nullptr; } } void KRun::abort() { if (d->m_bFinished) { return; } //qDebug() << this << "m_showingDialog=" << d->m_showingDialog; killJob(); // If we're showing an error message box, the rest will be done // after closing the msgbox -> don't autodelete nor emit signals now. if (d->m_showingDialog) { return; } d->m_bFault = true; d->m_bFinished = true; d->m_bInit = false; d->m_bScanFile = false; // will emit the error and autodelete this d->startTimer(); } QWidget *KRun::window() const { return d->m_window; } bool KRun::hasError() const { return d->m_bFault; } bool KRun::hasFinished() const { return d->m_bFinished; } bool KRun::autoDelete() const { return d->m_bAutoDelete; } void KRun::setAutoDelete(bool b) { d->m_bAutoDelete = b; } void KRun::setEnableExternalBrowser(bool b) { if (b) { d->m_externalBrowser = KConfigGroup(KSharedConfig::openConfig(), "General").readEntry("BrowserApplication"); } else { d->m_externalBrowser.clear(); } } void KRun::setPreferredService(const QString &desktopEntryName) { d->m_preferredService = desktopEntryName; } void KRun::setRunExecutables(bool b) { d->m_runExecutables = b; } void KRun::setSuggestedFileName(const QString &fileName) { d->m_suggestedFileName = fileName; } void KRun::setShowScriptExecutionPrompt(bool showPrompt) { d->m_bCheckPrompt = showPrompt; } QString KRun::suggestedFileName() const { return d->m_suggestedFileName; } bool KRun::isExecutable(const QString &serviceType) { return (serviceType == QLatin1String("application/x-desktop") || serviceType == QLatin1String("application/x-executable") || /* See https://bugs.freedesktop.org/show_bug.cgi?id=97226 */ serviceType == QLatin1String("application/x-sharedlib") || serviceType == QLatin1String("application/x-ms-dos-executable") || serviceType == QLatin1String("application/x-shellscript")); } void KRun::setUrl(const QUrl &url) { d->m_strURL = url; } QUrl KRun::url() const { return d->m_strURL; } void KRun::setError(bool error) { d->m_bFault = error; } void KRun::setProgressInfo(bool progressInfo) { d->m_bProgressInfo = progressInfo; } bool KRun::progressInfo() const { return d->m_bProgressInfo; } void KRun::setFinished(bool finished) { d->m_bFinished = finished; if (finished) { d->startTimer(); } } void KRun::setJob(KIO::Job *job) { d->m_job = job; } KIO::Job *KRun::job() { return d->m_job; } #ifndef KIOWIDGETS_NO_DEPRECATED QTimer &KRun::timer() { return *d->m_timer; } #endif #ifndef KIOWIDGETS_NO_DEPRECATED void KRun::setDoScanFile(bool scanFile) { d->m_bScanFile = scanFile; } #endif #ifndef KIOWIDGETS_NO_DEPRECATED bool KRun::doScanFile() const { return d->m_bScanFile; } #endif #ifndef KIOWIDGETS_NO_DEPRECATED void KRun::setIsDirecory(bool isDirectory) { d->m_bIsDirectory = isDirectory; } #endif bool KRun::isDirectory() const { return d->m_bIsDirectory; } #ifndef KIOWIDGETS_NO_DEPRECATED void KRun::setInitializeNextAction(bool initialize) { d->m_bInit = initialize; } #endif #ifndef KIOWIDGETS_NO_DEPRECATED bool KRun::initializeNextAction() const { return d->m_bInit; } #endif bool KRun::isLocalFile() const { return d->m_strURL.isLocalFile(); } /****************/ qint64 KProcessRunner::run(KProcess *p, const QString &executable, const KStartupInfoId &id) { return (new KProcessRunner(p, executable, id))->pid(); } KProcessRunner::KProcessRunner(KProcess *p, const QString &executable, const KStartupInfoId &id) : id(id) { m_pid = 0; process = p; m_executable = executable; - connect(process, SIGNAL(finished(int,QProcess::ExitStatus)), - this, SLOT(slotProcessExited(int,QProcess::ExitStatus))); + connect(process, QOverload::of(&QProcess::finished), + this, &KProcessRunner::slotProcessExited); process->start(); if (!process->waitForStarted()) { //qDebug() << "wait for started failed, exitCode=" << process->exitCode() // << "exitStatus=" << process->exitStatus(); // Note that exitCode is 255 here (the first time), and 0 later on (bug?). slotProcessExited(255, process->exitStatus()); } else { m_pid = process->processId(); } } KProcessRunner::~KProcessRunner() { delete process; } qint64 KProcessRunner::pid() const { return m_pid; } void KProcessRunner::terminateStartupNotification() { #if HAVE_X11 if (!id.isNull()) { KStartupInfoData data; data.addPid(m_pid); // announce this pid for the startup notification has finished data.setHostname(); KStartupInfo::sendFinish(id, data); } #endif } void KProcessRunner::slotProcessExited(int exitCode, QProcess::ExitStatus exitStatus) { //qDebug() << m_executable << "exitCode=" << exitCode << "exitStatus=" << exitStatus; Q_UNUSED(exitStatus) terminateStartupNotification(); // do this before the messagebox if (exitCode != 0 && !m_executable.isEmpty()) { // Let's see if the error is because the exe doesn't exist. // When this happens, waitForStarted returns false, but not if kioexec // was involved, then we come here, that's why the code is here. // // We'll try to find the executable relatively to current directory, // (or with a full path, if m_executable is absolute), and then in the PATH. if (!QFile(m_executable).exists() && QStandardPaths::findExecutable(m_executable).isEmpty()) { const QString &error = i18n("Could not find the program '%1'", m_executable); if (qApp) { QTimer::singleShot(0, qApp, [=]() { QEventLoopLocker locker; KMessageBox::sorry(nullptr, error); }); } else { qWarning() << error; } } else { //qDebug() << process->readAllStandardError(); } } deleteLater(); } #include "moc_krun.cpp" #include "moc_krun_p.cpp" #include "krun.moc" diff --git a/src/widgets/ksslinfodialog.cpp b/src/widgets/ksslinfodialog.cpp index 77d4b801..dc6c889e 100644 --- a/src/widgets/ksslinfodialog.cpp +++ b/src/widgets/ksslinfodialog.cpp @@ -1,242 +1,242 @@ /* This file is part of the KDE project * * Copyright (C) 2000,2001 George Staikos * Copyright (C) 2000 Malte Starostik * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library 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 * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "ksslinfodialog.h" #include "ui_sslinfo.h" #include "ksslcertificatebox.h" #include #include #include #include #include #include #include // BarIcon #include "ktcpsocket.h" class Q_DECL_HIDDEN KSslInfoDialog::KSslInfoDialogPrivate { public: QList certificateChain; QList > certificateErrors; bool isMainPartEncrypted; bool auxPartsEncrypted; Ui::SslInfo ui; KSslCertificateBox *subject; KSslCertificateBox *issuer; }; KSslInfoDialog::KSslInfoDialog(QWidget *parent) : QDialog(parent), d(new KSslInfoDialogPrivate) { setWindowTitle(i18n("KDE SSL Information")); setAttribute(Qt::WA_DeleteOnClose); QVBoxLayout *layout = new QVBoxLayout; setLayout(layout); QWidget *mainWidget = new QWidget(this); d->ui.setupUi(mainWidget); layout->addWidget(mainWidget); d->subject = new KSslCertificateBox(d->ui.certParties); d->issuer = new KSslCertificateBox(d->ui.certParties); d->ui.certParties->addTab(d->subject, i18nc("The receiver of the SSL certificate", "Subject")); d->ui.certParties->addTab(d->issuer, i18nc("The authority that issued the SSL certificate", "Issuer")); d->isMainPartEncrypted = true; d->auxPartsEncrypted = true; updateWhichPartsEncrypted(); QDialogButtonBox *buttonBox = new QDialogButtonBox(this); buttonBox->setStandardButtons(QDialogButtonBox::Close); - connect(buttonBox, SIGNAL(accepted()), this, SLOT(accept())); - connect(buttonBox, SIGNAL(rejected()), this, SLOT(reject())); + connect(buttonBox, &QDialogButtonBox::accepted, this, &QDialog::accept); + connect(buttonBox, &QDialogButtonBox::rejected, this, &QDialog::reject); layout->addWidget(buttonBox); #if 0 if (QSslSocket::supportsSsl()) { if (d->m_secCon) { d->pixmap->setPixmap(BarIcon("security-high")); d->info->setText(i18n("Current connection is secured with SSL.")); } else { d->pixmap->setPixmap(BarIcon("security-low")); d->info->setText(i18n("Current connection is not secured with SSL.")); } } else { d->pixmap->setPixmap(BarIcon("security-low")); d->info->setText(i18n("SSL support is not available in this build of KDE.")); } #endif } KSslInfoDialog::~KSslInfoDialog() { delete d; } void KSslInfoDialog::setMainPartEncrypted(bool mainEncrypted) { d->isMainPartEncrypted = mainEncrypted; updateWhichPartsEncrypted(); } void KSslInfoDialog::setAuxiliaryPartsEncrypted(bool auxEncrypted) { d->auxPartsEncrypted = auxEncrypted; updateWhichPartsEncrypted(); } void KSslInfoDialog::updateWhichPartsEncrypted() { if (d->isMainPartEncrypted) { if (d->auxPartsEncrypted) { d->ui.encryptionIndicator->setPixmap(BarIcon(QStringLiteral("security-high"))); d->ui.explanation->setText(i18n("Current connection is secured with SSL.")); } else { d->ui.encryptionIndicator->setPixmap(BarIcon(QStringLiteral("security-medium"))); d->ui.explanation->setText(i18n("The main part of this document is secured " "with SSL, but some parts are not.")); } } else { if (d->auxPartsEncrypted) { d->ui.encryptionIndicator->setPixmap(BarIcon(QStringLiteral("security-medium"))); d->ui.explanation->setText(i18n("Some of this document is secured with SSL, " "but the main part is not.")); } else { d->ui.encryptionIndicator->setPixmap(BarIcon(QStringLiteral("security-low"))); d->ui.explanation->setText(i18n("Current connection is not secured with SSL.")); } } } void KSslInfoDialog::setSslInfo(const QList &certificateChain, const QString &ip, const QString &host, const QString &sslProtocol, const QString &cipher, int usedBits, int bits, const QList > &validationErrors) { d->certificateChain = certificateChain; d->certificateErrors = validationErrors; d->ui.certSelector->clear(); for (int i = 0; i < certificateChain.size(); i++) { const QSslCertificate &cert = certificateChain[i]; QString name; static const QSslCertificate::SubjectInfo si[] = { QSslCertificate::CommonName, QSslCertificate::Organization, QSslCertificate::OrganizationalUnitName }; for (int j = 0; j < 3 && name.isEmpty(); j++) { name = cert.subjectInfo(si[j]).join(QStringLiteral(", ")); } d->ui.certSelector->addItem(name); } if (certificateChain.size() < 2) { d->ui.certSelector->setEnabled(false); } - connect(d->ui.certSelector, SIGNAL(currentIndexChanged(int)), - this, SLOT(displayFromChain(int))); + connect(d->ui.certSelector, QOverload::of(&QComboBox::currentIndexChanged), + this, &KSslInfoDialog::displayFromChain); if (d->certificateChain.isEmpty()) { d->certificateChain.append(QSslCertificate()); } displayFromChain(0); d->ui.ip->setText(ip); d->ui.address->setText(host); d->ui.sslVersion->setText(sslProtocol); const QStringList cipherInfo = cipher.split(QLatin1Char('\n'), QString::SkipEmptyParts); if (cipherInfo.size() >= 4) { d->ui.encryption->setText(i18nc("%1, using %2 bits of a %3 bit key", "%1, %2 %3", cipherInfo[0], i18ncp("Part of: %1, using %2 bits of a %3 bit key", "using %1 bit", "using %1 bits", usedBits), i18ncp("Part of: %1, using %2 bits of a %3 bit key", "of a %1 bit key", "of a %1 bit key", bits))); d->ui.details->setText(QStringLiteral("Auth = %1, Kx = %2, MAC = %3") .arg(cipherInfo[1], cipherInfo[2], cipherInfo[3])); } else { d->ui.encryption->setText(QString()); d->ui.details->setText(QString()); } } void KSslInfoDialog::displayFromChain(int i) { const QSslCertificate &cert = d->certificateChain[i]; QString trusted; if (!d->certificateErrors[i].isEmpty()) { trusted = i18nc("The certificate is not trusted", "NO, there were errors:"); foreach (KSslError::Error e, d->certificateErrors[i]) { KSslError classError(e); trusted.append(QLatin1Char('\n')); trusted.append(classError.errorString()); } } else { trusted = i18nc("The certificate is trusted", "Yes"); } d->ui.trusted->setText(trusted); QString vp = i18nc("%1 is the effective date of the certificate, %2 is the expiry date", "%1 to %2", cert.effectiveDate().toString(), cert.expiryDate().toString()); d->ui.validityPeriod->setText(vp); d->ui.serial->setText(QString::fromUtf8(cert.serialNumber())); d->ui.digest->setText(QString::fromUtf8(cert.digest().toHex())); d->ui.sha1Digest->setText(QString::fromUtf8(cert.digest(QCryptographicHash::Sha1).toHex())); d->subject->setCertificate(cert, KSslCertificateBox::Subject); d->issuer->setCertificate(cert, KSslCertificateBox::Issuer); } //static QList > KSslInfoDialog::errorsFromString(const QString &es) { QStringList sl = es.split(QLatin1Char('\n'), QString::KeepEmptyParts); QList > ret; foreach (const QString &s, sl) { QList certErrors; QStringList sl2 = s.split(QLatin1Char('\t'), QString::SkipEmptyParts); foreach (const QString &s2, sl2) { bool didConvert; KSslError::Error error = static_cast(s2.toInt(&didConvert)); if (didConvert) { certErrors.append(error); } } ret.append(certErrors); } return ret; } diff --git a/src/widgets/kurlrequesterdialog.cpp b/src/widgets/kurlrequesterdialog.cpp index aca62186..0c14e27a 100644 --- a/src/widgets/kurlrequesterdialog.cpp +++ b/src/widgets/kurlrequesterdialog.cpp @@ -1,143 +1,143 @@ /* This file is part of the KDE libraries Copyright (C) 2000 Wilco Greven library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library 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 Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kurlrequesterdialog.h" #include #include #include #include #include #include #include #include #include class KUrlRequesterDialogPrivate { public: explicit KUrlRequesterDialogPrivate(KUrlRequesterDialog *qq) : q(qq) { } KUrlRequesterDialog * const q; void initDialog(const QString &text, const QUrl &url); // slots void _k_slotTextChanged(const QString &); KUrlRequester *urlRequester; QDialogButtonBox *buttonBox; }; KUrlRequesterDialog::KUrlRequesterDialog(const QUrl &urlName, QWidget *parent) : QDialog(parent), d(new KUrlRequesterDialogPrivate(this)) { d->initDialog(i18n("Location:"), urlName); } KUrlRequesterDialog::KUrlRequesterDialog(const QUrl &urlName, const QString &_text, QWidget *parent) : QDialog(parent), d(new KUrlRequesterDialogPrivate(this)) { d->initDialog(_text, urlName); } KUrlRequesterDialog::~KUrlRequesterDialog() { delete d; } void KUrlRequesterDialogPrivate::initDialog(const QString &text, const QUrl &urlName) { QVBoxLayout *topLayout = new QVBoxLayout; q->setLayout(topLayout); QLabel *label = new QLabel(text, q); topLayout->addWidget(label); urlRequester = new KUrlRequester(urlName, q); urlRequester->setMinimumWidth(urlRequester->sizeHint().width() * 3); topLayout->addWidget(urlRequester); urlRequester->setFocus(); QObject::connect(urlRequester->lineEdit(), SIGNAL(textChanged(QString)), q, SLOT(_k_slotTextChanged(QString))); /* KFile::Mode mode = static_cast( KFile::File | KFile::ExistingOnly ); urlRequester_->setMode( mode ); */ buttonBox = new QDialogButtonBox(q); buttonBox->setStandardButtons(QDialogButtonBox::Ok | QDialogButtonBox::Cancel); - QObject::connect(buttonBox, SIGNAL(accepted()), q, SLOT(accept())); - QObject::connect(buttonBox, SIGNAL(rejected()), q, SLOT(reject())); + QObject::connect(buttonBox, &QDialogButtonBox::accepted, q, &QDialog::accept); + QObject::connect(buttonBox, &QDialogButtonBox::rejected, q, &QDialog::reject); topLayout->addWidget(buttonBox); _k_slotTextChanged(urlName.toString()); } void KUrlRequesterDialogPrivate::_k_slotTextChanged(const QString &text) { bool state = !text.trimmed().isEmpty(); buttonBox->button(QDialogButtonBox::Ok)->setEnabled(state); } QUrl KUrlRequesterDialog::selectedUrl() const { if (result() == QDialog::Accepted) { return d->urlRequester->url(); } else { return QUrl(); } } QUrl KUrlRequesterDialog::getUrl(const QUrl &dir, QWidget *parent, const QString &caption) { KUrlRequesterDialog dlg(dir, parent); dlg.setWindowTitle(caption.isEmpty() ? i18n("Open") : caption); dlg.exec(); const QUrl &url = dlg.selectedUrl(); if (url.isValid()) { KRecentDocument::add(url); } return url; } #ifndef KIOWIDGETS_NO_DEPRECATED QFileDialog *KUrlRequesterDialog::fileDialog() { return d->urlRequester->fileDialog(); } #endif KUrlRequester *KUrlRequesterDialog::urlRequester() { return d->urlRequester; } #include "moc_kurlrequesterdialog.cpp" diff --git a/src/widgets/pastedialog.cpp b/src/widgets/pastedialog.cpp index 20138af1..beea2b8c 100644 --- a/src/widgets/pastedialog.cpp +++ b/src/widgets/pastedialog.cpp @@ -1,97 +1,97 @@ /* This file is part of the KDE libraries Copyright (C) 2005 David Faure This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License version 2 as published by the Free Software Foundation. This library 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 Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "pastedialog_p.h" #include #include #include #include #include #include #include #include KIO::PasteDialog::PasteDialog(const QString &caption, const QString &label, const QString &value, const QStringList &items, QWidget *parent, bool clipboard) : QDialog(parent) { setWindowTitle(caption); setModal(true); QVBoxLayout *topLayout = new QVBoxLayout; setLayout(topLayout); QFrame *frame = new QFrame(this); topLayout->addWidget(frame); QVBoxLayout *layout = new QVBoxLayout(frame); m_label = new QLabel(label, frame); layout->addWidget(m_label); m_lineEdit = new QLineEdit(value, frame); layout->addWidget(m_lineEdit); m_lineEdit->setFocus(); m_label->setBuddy(m_lineEdit); layout->addWidget(new QLabel(i18n("Data format:"), frame)); m_comboBox = new KComboBox(frame); m_comboBox->addItems(items); layout->addWidget(m_comboBox); layout->addStretch(); QDialogButtonBox *buttonBox = new QDialogButtonBox(this); buttonBox->setStandardButtons(QDialogButtonBox::Ok | QDialogButtonBox::Cancel); - connect(buttonBox, SIGNAL(accepted()), this, SLOT(accept())); - connect(buttonBox, SIGNAL(rejected()), this, SLOT(reject())); + connect(buttonBox, &QDialogButtonBox::accepted, this, &QDialog::accept); + connect(buttonBox, &QDialogButtonBox::rejected, this, &QDialog::reject); topLayout->addWidget(buttonBox); //connect( m_lineEdit, SIGNAL(textChanged(QString)), // SLOT(slotEditTextChanged(QString)) ); //connect( this, SIGNAL(user1Clicked()), m_lineEdit, SLOT(clear()) ); //slotEditTextChanged( value ); setMinimumWidth(350); m_clipboardChanged = false; if (clipboard) - connect(QApplication::clipboard(), SIGNAL(dataChanged()), - this, SLOT(slotClipboardDataChanged())); + connect(QApplication::clipboard(), &QClipboard::dataChanged, + this, &PasteDialog::slotClipboardDataChanged); } void KIO::PasteDialog::slotClipboardDataChanged() { m_clipboardChanged = true; } QString KIO::PasteDialog::lineEditText() const { return m_lineEdit->text(); } int KIO::PasteDialog::comboItem() const { return m_comboBox->currentIndex(); } diff --git a/src/widgets/renamedialog.cpp b/src/widgets/renamedialog.cpp index 9931cb20..5f209367 100644 --- a/src/widgets/renamedialog.cpp +++ b/src/widgets/renamedialog.cpp @@ -1,675 +1,675 @@ /* This file is part of the KDE libraries Copyright (C) 2000 Stephan Kulow 1999 - 2008 David Faure 2001, 2006 Holger Freyther This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library 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 Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kio/renamedialog.h" #include "kio_widgets_debug.h" #include "../pathhelpers_p.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #if 0 #include #endif #include using namespace KIO; static QLabel *createLabel(QWidget *parent, const QString &text, bool containerTitle = false) { QLabel *label = new QLabel(parent); if (containerTitle) { QFont font = label->font(); font.setBold(true); label->setFont(font); } label->setAlignment(Qt::AlignHCenter); label->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::Fixed); label->setText(text); return label; } static QLabel *createDateLabel(QWidget *parent, const KFileItem &item) { const QString text = i18n("Date: %1", item.timeString(KFileItem::ModificationTime)); return createLabel(parent, text); } static QLabel *createSizeLabel(QWidget *parent, const KFileItem &item) { const QString text = i18n("Size: %1", KIO::convertSize(item.size())); return createLabel(parent, text); } static KSqueezedTextLabel *createSqueezedLabel(QWidget *parent, const QString &text) { KSqueezedTextLabel *label = new KSqueezedTextLabel(text, parent); label->setAlignment(Qt::AlignHCenter); label->setSizePolicy(QSizePolicy::Ignored, QSizePolicy::Fixed); return label; } /** @internal */ class Q_DECL_HIDDEN RenameDialog::RenameDialogPrivate { public: RenameDialogPrivate() { bCancel = nullptr; bRename = bSkip = bOverwrite = nullptr; bResume = bSuggestNewName = nullptr; bApplyAll = nullptr; m_pLineEdit = nullptr; m_srcPendingPreview = false; m_destPendingPreview = false; m_srcPreview = nullptr; m_destPreview = nullptr; m_srcArea = nullptr; m_destArea = nullptr; } void setRenameBoxText(const QString &fileName) { // sets the text in file name line edit box, selecting the filename (but not the extension if there is one). QMimeDatabase db; const QString extension = db.suffixForFileName(fileName); m_pLineEdit->setText(fileName); if (!extension.isEmpty()) { const int selectionLength = fileName.length() - extension.length() - 1; m_pLineEdit->setSelection(0, selectionLength); } else { m_pLineEdit->selectAll(); } } QPushButton *bCancel; QPushButton *bRename; QPushButton *bSkip; QPushButton *bOverwrite; QPushButton *bResume; QPushButton *bSuggestNewName; QCheckBox *bApplyAll; QLineEdit *m_pLineEdit; QUrl src; QUrl dest; bool m_srcPendingPreview; bool m_destPendingPreview; QLabel *m_srcPreview; QLabel *m_destPreview; QScrollArea *m_srcArea; QScrollArea *m_destArea; KFileItem srcItem; KFileItem destItem; }; RenameDialog::RenameDialog(QWidget *parent, const QString &_caption, const QUrl &_src, const QUrl &_dest, RenameDialog_Options _options, KIO::filesize_t sizeSrc, KIO::filesize_t sizeDest, const QDateTime &ctimeSrc, const QDateTime &ctimeDest, const QDateTime &mtimeSrc, const QDateTime &mtimeDest) : QDialog(parent), d(new RenameDialogPrivate) { setObjectName(QStringLiteral("KIO::RenameDialog")); d->src = _src; d->dest = _dest; setWindowTitle(_caption); d->bCancel = new QPushButton(this); KGuiItem::assign(d->bCancel, KStandardGuiItem::cancel()); - connect(d->bCancel, SIGNAL(clicked()), this, SLOT(cancelPressed())); + connect(d->bCancel, &QAbstractButton::clicked, this, &RenameDialog::cancelPressed); if (_options & RenameDialog_MultipleItems) { d->bApplyAll = new QCheckBox(i18n("Appl&y to All"), this); d->bApplyAll->setToolTip((_options & RenameDialog_IsDirectory) ? i18n("When this is checked the button pressed will be applied to all subsequent folder conflicts for the remainder of the current job.\nUnless you press Skip you will still be prompted in case of a conflict with an existing file in the directory.") : i18n("When this is checked the button pressed will be applied to all subsequent conflicts for the remainder of the current job.")); - connect(d->bApplyAll, SIGNAL(clicked()), this, SLOT(applyAllPressed())); + connect(d->bApplyAll, &QAbstractButton::clicked, this, &RenameDialog::applyAllPressed); } if (!(_options & RenameDialog_NoRename)) { d->bRename = new QPushButton(i18n("&Rename"), this); d->bRename->setEnabled(false); d->bSuggestNewName = new QPushButton(i18n("Suggest New &Name"), this); - connect(d->bSuggestNewName, SIGNAL(clicked()), this, SLOT(suggestNewNamePressed())); - connect(d->bRename, SIGNAL(clicked()), this, SLOT(renamePressed())); + connect(d->bSuggestNewName, &QAbstractButton::clicked, this, &RenameDialog::suggestNewNamePressed); + connect(d->bRename, &QAbstractButton::clicked, this, &RenameDialog::renamePressed); } if ((_options & RenameDialog_MultipleItems) && (_options & RenameDialog_Skip)) { d->bSkip = new QPushButton(i18n("&Skip"), this); d->bSkip->setToolTip((_options & RenameDialog_IsDirectory) ? i18n("Do not copy or move this folder, skip to the next item instead") : i18n("Do not copy or move this file, skip to the next item instead")); - connect(d->bSkip, SIGNAL(clicked()), this, SLOT(skipPressed())); + connect(d->bSkip, &QAbstractButton::clicked, this, &RenameDialog::skipPressed); } if (_options & RenameDialog_Overwrite) { const QString text = (_options & RenameDialog_IsDirectory) ? i18nc("Write files into an existing folder", "&Write Into") : i18n("&Overwrite"); d->bOverwrite = new QPushButton(text, this); d->bOverwrite->setToolTip(i18n("Files and folders will be copied into the existing directory, alongside its existing contents.\nYou will be prompted again in case of a conflict with an existing file in the directory.")); - connect(d->bOverwrite, SIGNAL(clicked()), this, SLOT(overwritePressed())); + connect(d->bOverwrite, &QAbstractButton::clicked, this, &RenameDialog::overwritePressed); } if (_options & RenameDialog_Resume) { d->bResume = new QPushButton(i18n("&Resume"), this); - connect(d->bResume, SIGNAL(clicked()), this, SLOT(resumePressed())); + connect(d->bResume, &QAbstractButton::clicked, this, &RenameDialog::resumePressed); } QVBoxLayout *pLayout = new QVBoxLayout(this); pLayout->addStrut(400); // makes dlg at least that wide // User tries to overwrite a file with itself ? if (_options & RenameDialog_OverwriteItself) { QLabel *lb = new QLabel(i18n("This action would overwrite '%1' with itself.\n" "Please enter a new file name:", KStringHandler::csqueeze(d->src.toDisplayString(QUrl::PreferLocalFile), 100)), this); lb->setTextFormat(Qt::PlainText); d->bRename->setText(i18n("C&ontinue")); pLayout->addWidget(lb); } else if (_options & RenameDialog_Overwrite) { if (d->src.isLocalFile()) { d->srcItem = KFileItem(d->src); } else { UDSEntry srcUds; srcUds.fastInsert(UDSEntry::UDS_NAME, d->src.fileName()); srcUds.fastInsert(UDSEntry::UDS_MODIFICATION_TIME, mtimeSrc.toMSecsSinceEpoch() / 1000); srcUds.fastInsert(UDSEntry::UDS_CREATION_TIME, ctimeSrc.toMSecsSinceEpoch() / 1000); srcUds.fastInsert(UDSEntry::UDS_SIZE, sizeSrc); d->srcItem = KFileItem(srcUds, d->src); } if (d->dest.isLocalFile()) { d->destItem = KFileItem(d->dest); } else { UDSEntry destUds; destUds.fastInsert(UDSEntry::UDS_NAME, d->dest.fileName()); destUds.fastInsert(UDSEntry::UDS_MODIFICATION_TIME, mtimeDest.toMSecsSinceEpoch() / 1000); destUds.fastInsert(UDSEntry::UDS_CREATION_TIME, ctimeDest.toMSecsSinceEpoch() / 1000); destUds.fastInsert(UDSEntry::UDS_SIZE, sizeDest); d->destItem = KFileItem(destUds, d->dest); } d->m_srcPreview = createLabel(parent, QString()); d->m_destPreview = createLabel(parent, QString()); d->m_srcPreview->setMinimumHeight(KIconLoader::SizeEnormous); d->m_destPreview->setMinimumHeight(KIconLoader::SizeEnormous); d->m_srcPreview->setAlignment(Qt::AlignCenter); d->m_destPreview->setAlignment(Qt::AlignCenter); d->m_srcPendingPreview = true; d->m_destPendingPreview = true; // widget d->m_srcArea = createContainerLayout(parent, d->srcItem, d->m_srcPreview); d->m_destArea = createContainerLayout(parent, d->destItem, d->m_destPreview); - connect(d->m_srcArea->verticalScrollBar(), SIGNAL(valueChanged(int)), d->m_destArea->verticalScrollBar(), SLOT(setValue(int))); - connect(d->m_destArea->verticalScrollBar(), SIGNAL(valueChanged(int)), d->m_srcArea->verticalScrollBar(), SLOT(setValue(int))); - connect(d->m_srcArea->horizontalScrollBar(), SIGNAL(valueChanged(int)), d->m_destArea->horizontalScrollBar(), SLOT(setValue(int))); - connect(d->m_destArea->horizontalScrollBar(), SIGNAL(valueChanged(int)), d->m_srcArea->horizontalScrollBar(), SLOT(setValue(int))); + connect(d->m_srcArea->verticalScrollBar(), &QAbstractSlider::valueChanged, d->m_destArea->verticalScrollBar(), &QAbstractSlider::setValue); + connect(d->m_destArea->verticalScrollBar(), &QAbstractSlider::valueChanged, d->m_srcArea->verticalScrollBar(), &QAbstractSlider::setValue); + connect(d->m_srcArea->horizontalScrollBar(), &QAbstractSlider::valueChanged, d->m_destArea->horizontalScrollBar(), &QAbstractSlider::setValue); + connect(d->m_destArea->horizontalScrollBar(), &QAbstractSlider::valueChanged, d->m_srcArea->horizontalScrollBar(), &QAbstractSlider::setValue); // create layout QGridLayout *gridLayout = new QGridLayout(); pLayout->addLayout(gridLayout); int gridRow = 0; QLabel *titleLabel = new QLabel(i18n("This action will overwrite the destination."), this); gridLayout->addWidget(titleLabel, gridRow, 0, 1, 2); // takes the complete first line if (mtimeDest > mtimeSrc) { QLabel *warningLabel = new QLabel(i18n("Warning, the destination is more recent."), this); gridLayout->addWidget(warningLabel, ++gridRow, 0, 1, 2); } gridLayout->setRowMinimumHeight(++gridRow, 15); // spacer QLabel *srcTitle = createLabel(parent, i18n("Source"), true); gridLayout->addWidget(srcTitle, ++gridRow, 0); QLabel *destTitle = createLabel(parent, i18n("Destination"), true); gridLayout->addWidget(destTitle, gridRow, 1); QLabel *srcUrlLabel = createSqueezedLabel(parent, d->src.toDisplayString(QUrl::PreferLocalFile)); srcUrlLabel->setTextFormat(Qt::PlainText); gridLayout->addWidget(srcUrlLabel, ++gridRow, 0); QLabel *destUrlLabel = createSqueezedLabel(parent, d->dest.toDisplayString(QUrl::PreferLocalFile)); destUrlLabel->setTextFormat(Qt::PlainText); gridLayout->addWidget(destUrlLabel, gridRow, 1); // The labels containing previews or icons gridLayout->addWidget(d->m_srcArea, ++gridRow, 0); gridLayout->addWidget(d->m_destArea, gridRow, 1); QLabel *srcDateLabel = createDateLabel(parent, d->srcItem); gridLayout->addWidget(srcDateLabel, ++gridRow, 0); QLabel *destDateLabel = createDateLabel(parent, d->destItem); gridLayout->addWidget(destDateLabel, gridRow, 1); QLabel *srcSizeLabel = createSizeLabel(parent, d->srcItem); gridLayout->addWidget(srcSizeLabel, ++gridRow, 0); QLabel *destSizeLabel = createSizeLabel(parent, d->destItem); gridLayout->addWidget(destSizeLabel, gridRow, 1); } else { // This is the case where we don't want to allow overwriting, the existing // file must be preserved (e.g. when renaming). QString sentence1; if (mtimeDest < mtimeSrc) { sentence1 = i18n("An older item named '%1' already exists.", d->dest.toDisplayString(QUrl::PreferLocalFile)); } else if (mtimeDest == mtimeSrc) { sentence1 = i18n("A similar file named '%1' already exists.", d->dest.toDisplayString(QUrl::PreferLocalFile)); } else { sentence1 = i18n("A more recent item named '%1' already exists.", d->dest.toDisplayString(QUrl::PreferLocalFile)); } QLabel *lb = new KSqueezedTextLabel(sentence1, this); lb->setTextFormat(Qt::PlainText); pLayout->addWidget(lb); } if ((_options != RenameDialog_OverwriteItself) && (_options != RenameDialog_NoRename)) { if (_options == RenameDialog_Overwrite) { pLayout->addSpacing(15); // spacer } QLabel *lb2 = new QLabel(i18n("Rename:"), this); pLayout->addWidget(lb2); } QHBoxLayout *layout2 = new QHBoxLayout(); pLayout->addLayout(layout2); d->m_pLineEdit = new QLineEdit(this); layout2->addWidget(d->m_pLineEdit); if (d->bRename) { const QString fileName = d->dest.fileName(); d->setRenameBoxText(KIO::decodeFileName(fileName)); - connect(d->m_pLineEdit, SIGNAL(textChanged(QString)), - SLOT(enableRenameButton(QString))); + connect(d->m_pLineEdit, &QLineEdit::textChanged, + this, &RenameDialog::enableRenameButton); d->m_pLineEdit->setFocus(); } else { d->m_pLineEdit->hide(); } if (d->bSuggestNewName) { layout2->addWidget(d->bSuggestNewName); setTabOrder(d->m_pLineEdit, d->bSuggestNewName); } KSeparator *separator = new KSeparator(this); pLayout->addWidget(separator); QHBoxLayout *layout = new QHBoxLayout(); pLayout->addLayout(layout); layout->addStretch(1); if (d->bApplyAll) { layout->addWidget(d->bApplyAll); setTabOrder(d->bApplyAll, d->bCancel); } if (d->bRename) { layout->addWidget(d->bRename); setTabOrder(d->bRename, d->bCancel); } if (d->bSkip) { layout->addWidget(d->bSkip); setTabOrder(d->bSkip, d->bCancel); } if (d->bOverwrite) { layout->addWidget(d->bOverwrite); setTabOrder(d->bOverwrite, d->bCancel); } if (d->bResume) { layout->addWidget(d->bResume); setTabOrder(d->bResume, d->bCancel); } d->bCancel->setDefault(true); layout->addWidget(d->bCancel); resize(sizeHint()); #if 1 // without kfilemetadata // don't wait for kfilemetadata, but wait until the layouting is done if (_options & RenameDialog_Overwrite) { QMetaObject::invokeMethod(this, "resizePanels", Qt::QueuedConnection); } #endif } RenameDialog::~RenameDialog() { delete d; } void RenameDialog::enableRenameButton(const QString &newDest) { if (newDest != KIO::decodeFileName(d->dest.fileName()) && !newDest.isEmpty()) { d->bRename->setEnabled(true); d->bRename->setDefault(true); if (d->bOverwrite) { d->bOverwrite->setEnabled(false); // prevent confusion (#83114) } } else { d->bRename->setEnabled(false); if (d->bOverwrite) { d->bOverwrite->setEnabled(true); } } } QUrl RenameDialog::newDestUrl() { const QString fileName = d->m_pLineEdit->text(); QUrl newDest = d->dest.adjusted(QUrl::RemoveFilename); // keeps trailing slash newDest.setPath(newDest.path() + KIO::encodeFileName(fileName)); return newDest; } QUrl RenameDialog::autoDestUrl() const { const QUrl destDirectory = d->dest.adjusted(QUrl::RemoveFilename | QUrl::StripTrailingSlash); const QString newName = KIO::suggestName(destDirectory, d->dest.fileName()); QUrl newDest(destDirectory); newDest.setPath(concatPaths(newDest.path(), newName)); return newDest; } void RenameDialog::cancelPressed() { done(Result_Cancel); } // Rename void RenameDialog::renamePressed() { if (d->m_pLineEdit->text().isEmpty()) { return; } if (d->bApplyAll && d->bApplyAll->isChecked()) { done(Result_AutoRename); } else { const QUrl u = newDestUrl(); if (!u.isValid()) { KMessageBox::error(this, i18n("Malformed URL\n%1", u.errorString())); qCWarning(KIO_WIDGETS) << u.errorString(); return; } done(Result_Rename); } } #ifndef KIOWIDGETS_NO_DEPRECATED QString RenameDialog::suggestName(const QUrl &baseURL, const QString &oldName) { return KIO::suggestName(baseURL, oldName); } #endif // Propose button clicked void RenameDialog::suggestNewNamePressed() { /* no name to play with */ if (d->m_pLineEdit->text().isEmpty()) { return; } QUrl destDirectory = d->dest.adjusted(QUrl::RemoveFilename | QUrl::StripTrailingSlash); d->setRenameBoxText(KIO::suggestName(destDirectory, d->m_pLineEdit->text())); } void RenameDialog::skipPressed() { if (d->bApplyAll && d->bApplyAll->isChecked()) { done(Result_AutoSkip); } else { done(Result_Skip); } } void RenameDialog::autoSkipPressed() { done(Result_AutoSkip); } void RenameDialog::overwritePressed() { if (d->bApplyAll && d->bApplyAll->isChecked()) { done(Result_OverwriteAll); } else { done(Result_Overwrite); } } void RenameDialog::overwriteAllPressed() { done(Result_OverwriteAll); } void RenameDialog::resumePressed() { if (d->bApplyAll && d->bApplyAll->isChecked()) { done(Result_ResumeAll); } else { done(Result_Resume); } } void RenameDialog::resumeAllPressed() { done(Result_ResumeAll); } void RenameDialog::applyAllPressed() { if (d->bApplyAll && d->bApplyAll->isChecked()) { d->m_pLineEdit->setText(KIO::decodeFileName(d->dest.fileName())); d->m_pLineEdit->setEnabled(false); if (d->bRename) { d->bRename->setEnabled(true); } if (d->bSuggestNewName) { d->bSuggestNewName->setEnabled(false); } } else { d->m_pLineEdit->setEnabled(true); if (d->bRename) { d->bRename->setEnabled(false); } if (d->bSuggestNewName) { d->bSuggestNewName->setEnabled(true); } } } void RenameDialog::showSrcIcon(const KFileItem &fileitem) { // The preview job failed, show a standard file icon. d->m_srcPendingPreview = false; const int size = d->m_srcPreview->height(); const QPixmap pix = KIconLoader::global()->loadMimeTypeIcon(fileitem.iconName(), KIconLoader::Desktop, size); d->m_srcPreview->setPixmap(pix); } void RenameDialog::showDestIcon(const KFileItem &fileitem) { // The preview job failed, show a standard file icon. d->m_destPendingPreview = false; const int size = d->m_destPreview->height(); const QPixmap pix = KIconLoader::global()->loadMimeTypeIcon(fileitem.iconName(), KIconLoader::Desktop, size); d->m_destPreview->setPixmap(pix); } void RenameDialog::showSrcPreview(const KFileItem &fileitem, const QPixmap &pixmap) { Q_UNUSED(fileitem); if (d->m_srcPendingPreview) { d->m_srcPreview->setPixmap(pixmap); d->m_srcPendingPreview = false; } } void RenameDialog::showDestPreview(const KFileItem &fileitem, const QPixmap &pixmap) { Q_UNUSED(fileitem); if (d->m_destPendingPreview) { d->m_destPreview->setPixmap(pixmap); d->m_destPendingPreview = false; } } void RenameDialog::resizePanels() { Q_ASSERT(d->m_srcArea != nullptr); Q_ASSERT(d->m_destArea != nullptr); Q_ASSERT(d->m_srcPreview != nullptr); Q_ASSERT(d->m_destPreview != nullptr); // using QDesktopWidget geometry as Kephal isn't accessible here in kdelibs const QSize screenSize = QApplication::desktop()->availableGeometry(this).size(); QSize halfSize = d->m_srcArea->widget()->sizeHint().expandedTo(d->m_destArea->widget()->sizeHint()); const QSize currentSize = d->m_srcArea->size().expandedTo(d->m_destArea->size()); const int maxHeightPossible = screenSize.height() - (size().height() - currentSize.height()); QSize maxHalfSize = QSize(screenSize.width() / qreal(2.1), maxHeightPossible * qreal(0.9)); if (halfSize.height() > maxHalfSize.height() && halfSize.width() <= maxHalfSize.width() + d->m_srcArea->verticalScrollBar()->width()) { halfSize.rwidth() += d->m_srcArea->verticalScrollBar()->width(); maxHalfSize.rwidth() += d->m_srcArea->verticalScrollBar()->width(); } d->m_srcArea->setMinimumSize(halfSize.boundedTo(maxHalfSize)); d->m_destArea->setMinimumSize(halfSize.boundedTo(maxHalfSize)); KIO::PreviewJob *srcJob = KIO::filePreview(KFileItemList() << d->srcItem, QSize(d->m_srcPreview->width() * qreal(0.9), d->m_srcPreview->height())); srcJob->setScaleType(KIO::PreviewJob::Unscaled); KIO::PreviewJob *destJob = KIO::filePreview(KFileItemList() << d->destItem, QSize(d->m_destPreview->width() * qreal(0.9), d->m_destPreview->height())); destJob->setScaleType(KIO::PreviewJob::Unscaled); - connect(srcJob, SIGNAL(gotPreview(KFileItem,QPixmap)), - this, SLOT(showSrcPreview(KFileItem,QPixmap))); - connect(destJob, SIGNAL(gotPreview(KFileItem,QPixmap)), - this, SLOT(showDestPreview(KFileItem,QPixmap))); - connect(srcJob, SIGNAL(failed(KFileItem)), - this, SLOT(showSrcIcon(KFileItem))); - connect(destJob, SIGNAL(failed(KFileItem)), - this, SLOT(showDestIcon(KFileItem))); + connect(srcJob, &PreviewJob::gotPreview, + this, &RenameDialog::showSrcPreview); + connect(destJob, &PreviewJob::gotPreview, + this, &RenameDialog::showDestPreview); + connect(srcJob, &PreviewJob::failed, + this, &RenameDialog::showSrcIcon); + connect(destJob, &PreviewJob::failed, + this, &RenameDialog::showDestIcon); } QScrollArea *RenameDialog::createContainerLayout(QWidget *parent, const KFileItem &item, QLabel *preview) { KFileItemList itemList; itemList << item; #if 0 // PENDING // KFileMetaDataWidget was deprecated for a Nepomuk widget, which is itself deprecated... // If we still want metadata shown, we need a plugin that fetches data from KFileMetaData::ExtractorCollection KFileMetaDataWidget *metaWidget = new KFileMetaDataWidget(this); metaWidget->setReadOnly(true); metaWidget->setItems(itemList); // ### This is going to call resizePanels twice! Need to split it up to do preview job only once on each side connect(metaWidget, SIGNAL(metaDataRequestFinished(KFileItemList)), this, SLOT(resizePanels())); #endif // Encapsulate the MetaDataWidgets inside a container with stretch at the bottom. // This prevents that the meta data widgets get vertically stretched // in the case where the height of m_metaDataArea > m_metaDataWidget. QWidget *widgetContainer = new QWidget(parent); QVBoxLayout *containerLayout = new QVBoxLayout(widgetContainer); containerLayout->setContentsMargins(0, 0, 0, 0); containerLayout->setSpacing(0); containerLayout->addWidget(preview); #if 0 // PENDING containerLayout->addWidget(metaWidget); #endif containerLayout->addStretch(1); QScrollArea *metaDataArea = new QScrollArea(parent); metaDataArea->setWidget(widgetContainer); metaDataArea->setWidgetResizable(true); metaDataArea->setFrameShape(QFrame::NoFrame); return metaDataArea; } diff --git a/src/widgets/skipdialog.cpp b/src/widgets/skipdialog.cpp index f718be9d..340e06f8 100644 --- a/src/widgets/skipdialog.cpp +++ b/src/widgets/skipdialog.cpp @@ -1,91 +1,91 @@ /* This file is part of the KDE libraries Copyright (C) 2000 David Faure This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License version 2 as published by the Free Software Foundation. This library 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 Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kio/skipdialog.h" #include #include #include #include #include #include #include #include #include using namespace KIO; SkipDialog::SkipDialog(QWidget *parent, KIO::SkipDialog_Options options, const QString &_error_text) : QDialog(parent), d(nullptr) { setWindowTitle(i18n("Information")); QVBoxLayout *layout = new QVBoxLayout; setLayout(layout); layout->addWidget(new QLabel(_error_text, this)); QDialogButtonBox *buttonBox = new QDialogButtonBox(this); layout->addWidget(buttonBox); QPushButton *retryButton = new QPushButton(i18n("Retry")); - connect(retryButton, SIGNAL(clicked()), SLOT(retryPressed())); + connect(retryButton, &QAbstractButton::clicked, this, &SkipDialog::retryPressed); buttonBox->addButton(retryButton, QDialogButtonBox::ActionRole); if (options & SkipDialog_MultipleItems) { QPushButton *skipButton = new QPushButton(i18n("Skip")); - connect(skipButton, SIGNAL(clicked()), SLOT(skipPressed())); + connect(skipButton, &QAbstractButton::clicked, this, &SkipDialog::skipPressed); buttonBox->addButton(skipButton, QDialogButtonBox::ActionRole); QPushButton *autoSkipButton = new QPushButton(i18n("AutoSkip")); - connect(autoSkipButton, SIGNAL(clicked()), SLOT(autoSkipPressed())); + connect(autoSkipButton, &QAbstractButton::clicked, this, &SkipDialog::autoSkipPressed); buttonBox->addButton(autoSkipButton, QDialogButtonBox::ActionRole); } buttonBox->addButton(QDialogButtonBox::Cancel); - connect(buttonBox, SIGNAL(rejected()), SLOT(cancelPressed())); + connect(buttonBox, &QDialogButtonBox::rejected, this, &SkipDialog::cancelPressed); resize(sizeHint()); } SkipDialog::~SkipDialog() { } void SkipDialog::cancelPressed() { done(KIO::Result_Cancel); } void SkipDialog::skipPressed() { done(KIO::Result_Skip); } void SkipDialog::autoSkipPressed() { done(KIO::Result_AutoSkip); } void SkipDialog::retryPressed() { done(KIO::Result_Retry); }