Advance Wayland and KDE package bring-up

Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
This commit is contained in:
2026-04-14 10:51:06 +01:00
parent 51f3c21121
commit cf12defd28
15214 changed files with 20594243 additions and 269 deletions
@@ -0,0 +1,198 @@
#include (ConfigureChecks.cmake)
set_package_properties(ACL PROPERTIES DESCRIPTION "LibACL" URL "ftp://oss.sgi.com/projects/xfs/cmd_tars"
TYPE RECOMMENDED PURPOSE "Support for manipulating access control lists")
configure_file(config-kiowidgets.h.cmake ${CMAKE_CURRENT_BINARY_DIR}/config-kiowidgets.h)
add_library(KF6KIOWidgets)
add_library(KF6::KIOWidgets ALIAS KF6KIOWidgets)
set_target_properties(KF6KIOWidgets PROPERTIES
VERSION ${KIO_VERSION}
SOVERSION ${KIO_SOVERSION}
EXPORT_NAME KIOWidgets
)
set(kiowidgets_dbus_SRCS)
if(HAVE_QTDBUS)
qt_add_dbus_adaptor(kiowidgets_dbus_SRCS org.kde.kio.FileUndoManager.xml fileundomanager_p.h KIO::FileUndoManagerPrivate fileundomanager_adaptor KIOFileUndoManagerAdaptor)
qt_add_dbus_interface(kiowidgets_dbus_SRCS org.kde.kuiserver.xml kuiserver_interface)
endif()
target_sources(KF6KIOWidgets PRIVATE
kacleditwidget.cpp
kurlrequesterdialog.cpp
kurlcombobox.cpp
kfileitemactions.cpp
imagefilter.cpp
kopenwithdialog.cpp
kfile.cpp
pastedialog.cpp
clipboardupdater.cpp
kabstractfileitemactionplugin.cpp
kurlrequester.cpp
kshellcompletion.cpp
kurlcompletion.cpp
renamedialog.cpp
ksslcertificatebox.cpp
ksslinfodialog.cpp
skipdialog.cpp
jobuidelegate.cpp
kdirlister.cpp
executablefileopendialog.cpp
kurifiltersearchprovideractions.cpp
widgetsuntrustedprogramhandler.cpp
widgetsopenwithhandler.cpp
widgetsopenorexecutefilehandler.cpp
widgetsaskuseractionhandler.cpp
deleteortrashjob.cpp
fileundomanager.cpp
paste.cpp
pastejob.cpp
joburlcache.cpp
kdirmodel.cpp
dropjob.cpp
kbuildsycocaprogressdialog.cpp
renamefiledialog.cpp
kfileitemdelegate.cpp
delegateanimationhandler.cpp
kpropertiesdialog.cpp
kpropertiesdialogplugin.cpp
kpropertiesdialogbuiltin_p.cpp
sslui.cpp
)
if (HAVE_QTDBUS)
target_sources(KF6KIOWidgets PRIVATE
kdynamicjobtracker.cpp
${kiowidgets_dbus_SRCS}
)
endif()
ecm_qt_declare_logging_category(KF6KIOWidgets
HEADER kio_widgets_debug.h
IDENTIFIER KIO_WIDGETS
CATEGORY_NAME kf.kio.widgets
OLD_CATEGORY_NAMES kf5.kio.widgets
DESCRIPTION "KIOWidgets (KIO)"
EXPORT KIO
)
ecm_qt_export_logging_category(
IDENTIFIER category
CATEGORY_NAME kf.kio.widgets.kdirmodel
OLD_CATEGORY_NAMES kf5.kio.kdirmodel
DESCRIPTION "KDirModel (KIO)"
EXPORT KIO
)
ki18n_wrap_ui(KF6KIOWidgets
checksumswidget.ui
certificateparty.ui
sslinfo.ui
kpropertiesdesktopadvbase.ui
kpropertiesdesktopbase.ui
kfilepropspluginwidget.ui
)
ecm_generate_export_header(KF6KIOWidgets
BASE_NAME KIOWidgets
GROUP_BASE_NAME KF
VERSION ${KF_VERSION}
USE_VERSION_HEADER
VERSION_BASE_NAME KIO
DEPRECATED_BASE_VERSION 0
DEPRECATION_VERSIONS 5.0
EXCLUDE_DEPRECATED_BEFORE_AND_AT ${EXCLUDE_DEPRECATED_BEFORE_AND_AT}
)
target_include_directories(KF6KIOWidgets INTERFACE "$<INSTALL_INTERFACE:${KDE_INSTALL_INCLUDEDIR_KF}/KIOWidgets>")
target_link_libraries(KF6KIOWidgets
PUBLIC
KF6::KIOGui
KF6::KIOCore
KF6::JobWidgets
KF6::Service
KF6::Solid
Qt6::Network # SSL
KF6::Completion # KUrlCompletion uses KCompletion
KF6::WidgetsAddons # keditlistwidget
PRIVATE
Qt6::Concurrent
KF6::I18n
KF6::ConfigGui
KF6::GuiAddons # KIconUtils
KF6::IconThemes # KIconLoader
KF6::IconWidgets # KIconButton
KF6::WindowSystem # KStartupInfo
KF6::ColorScheme
)
if (HAVE_QTDBUS)
target_link_libraries(KF6KIOWidgets PRIVATE ${DBUS_LIB})
endif()
if(ACL_FOUND)
target_link_libraries(KF6KIOWidgets PRIVATE ${ACL_LIBS})
endif()
# Headers not prefixed with KIO/
ecm_generate_headers(KIOWidgets_HEADERS
HEADER_NAMES
KPropertiesDialog
KPropertiesDialogPlugin
KUrlRequesterDialog
KUrlComboBox
KFileItemActions
KFileItemDelegate
KOpenWithDialog
KAbstractFileItemActionPlugin
KBuildSycocaProgressDialog
KFile
KUrlRequester
KSslCertificateBox
KSslInfoDialog
KDirLister
KDirModel
KShellCompletion
KUrlCompletion
REQUIRED_HEADERS KIOWidgets_HEADERS
)
# Headers prefixed with KIO/
ecm_generate_headers(KIOWidgets_CamelCase_HEADERS
HEADER_NAMES
DeleteOrTrashJob
SslUi
DropJob
PasteJob
RenameDialog
SkipDialog
JobUiDelegate
FileUndoManager
Paste
KUriFilterSearchProviderActions # KF6: fix and move to non-KIO prefixed install folder
RenameFileDialog
WidgetsAskUserActionHandler
PREFIX KIO
REQUIRED_HEADERS KIO_namespaced_widgets_HEADERS
)
install(FILES ${KIOWidgets_CamelCase_HEADERS} DESTINATION ${KDE_INSTALL_INCLUDEDIR_KF}/KIOWidgets/KIO COMPONENT Devel)
install(TARGETS KF6KIOWidgets EXPORT KF6KIOTargets ${KF_INSTALL_TARGETS_DEFAULT_ARGS})
install(FILES
${KIO_namespaced_widgets_HEADERS}
DESTINATION ${KDE_INSTALL_INCLUDEDIR_KF}/KIOWidgets/kio COMPONENT Devel)
install(FILES
${KIOWidgets_HEADERS}
${CMAKE_CURRENT_BINARY_DIR}/kiowidgets_export.h
DESTINATION ${KDE_INSTALL_INCLUDEDIR_KF}/KIOWidgets COMPONENT Devel)
# make available to ecm_add_qch in parent folder
set(KIOWidgets_QCH_SOURCES ${KIOWidgets_HEADERS} ${KIO_namespaced_widgets_HEADERS} PARENT_SCOPE)
@@ -0,0 +1,151 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>CertificateParty</class>
<widget class="QWidget" name="CertificateParty">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>390</width>
<height>214</height>
</rect>
</property>
<layout class="QGridLayout">
<item row="0" column="0">
<layout class="QFormLayout" name="formLayout">
<property name="fieldGrowthPolicy">
<enum>QFormLayout::ExpandingFieldsGrow</enum>
</property>
<property name="horizontalSpacing">
<number>4</number>
</property>
<property name="verticalSpacing">
<number>0</number>
</property>
<item row="0" column="0">
<widget class="QLabel" name="commonNameTag">
<property name="text">
<string>Common name:</string>
</property>
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QLabel" name="commonName">
<property name="text">
<string>Acme Co.</string>
</property>
<property name="textInteractionFlags">
<set>Qt::TextSelectableByMouse</set>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QLabel" name="organizationTag">
<property name="text">
<string>Organization:</string>
</property>
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QLabel" name="organization">
<property name="text">
<string>Acme Sundry Products Company</string>
</property>
<property name="textInteractionFlags">
<set>Qt::TextSelectableByMouse</set>
</property>
</widget>
</item>
<item row="2" column="0">
<widget class="QLabel" name="organizationalUnitTag">
<property name="text">
<string>Organizational unit:</string>
</property>
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
</widget>
</item>
<item row="2" column="1">
<widget class="QLabel" name="organizationalUnit">
<property name="text">
<string>Fraud Department</string>
</property>
<property name="textInteractionFlags">
<set>Qt::TextSelectableByMouse</set>
</property>
</widget>
</item>
<item row="3" column="0">
<widget class="QLabel" name="countryTag">
<property name="text">
<string>Country:</string>
</property>
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
</widget>
</item>
<item row="3" column="1">
<widget class="QLabel" name="country">
<property name="text">
<string>Canada</string>
</property>
<property name="textInteractionFlags">
<set>Qt::TextSelectableByMouse</set>
</property>
</widget>
</item>
<item row="4" column="0">
<widget class="QLabel" name="stateTag">
<property name="text">
<string>State:</string>
</property>
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
</widget>
</item>
<item row="4" column="1">
<widget class="QLabel" name="state">
<property name="text">
<string>Quebec</string>
</property>
<property name="textInteractionFlags">
<set>Qt::TextSelectableByMouse</set>
</property>
</widget>
</item>
<item row="5" column="0">
<widget class="QLabel" name="cityTag">
<property name="text">
<string>City:</string>
</property>
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
</widget>
</item>
<item row="5" column="1">
<widget class="QLabel" name="city">
<property name="text">
<string>Lakeridge Meadows</string>
</property>
<property name="textInteractionFlags">
<set>Qt::TextSelectableByMouse</set>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</widget>
<resources/>
<connections/>
</ui>
@@ -0,0 +1,264 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>ChecksumsWidget</class>
<widget class="QWidget" name="ChecksumsWidget">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>485</width>
<height>463</height>
</rect>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QLabel" name="label">
<property name="text">
<string>Copy and paste a checksum in the field below.&lt;br/&gt;A checksum is usually provided by the website you downloaded this file from.</string>
</property>
<property name="wordWrap">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<widget class="QLineEdit" name="lineEdit">
<property name="placeholderText">
<string extracomment="MD5, SHA1, SHA256 and SHA512 are hashing algorithms">Expected checksum (MD5, SHA1, SHA256 or SHA512)...</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="pasteButton">
<property name="toolTip">
<string>Click to paste the checksum from the clipboard to the input field.</string>
</property>
<property name="text">
<string>Paste</string>
</property>
<property name="icon">
<iconset theme="edit-paste"/>
</property>
</widget>
</item>
</layout>
</item>
<item>
<widget class="QLabel" name="feedbackLabel">
<property name="text">
<string notr="true">Feedback</string>
</property>
</widget>
</item>
<item>
<widget class="KSeparator" name="kseparator"/>
</item>
<item>
<widget class="QWidget" name="calculateWidget" native="true">
<layout class="QFormLayout" name="formLayout">
<item row="0" column="0">
<widget class="QLabel" name="md5Label">
<property name="text">
<string extracomment="MD5 is the hashing algorithm">MD5:</string>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QLabel" name="sha1Label">
<property name="text">
<string extracomment="SHA1 is the hashing algorithm">SHA1:</string>
</property>
</widget>
</item>
<item row="2" column="0">
<widget class="QLabel" name="sha256Label">
<property name="text">
<string extracomment="SHA256 is the hashing algorithm">SHA256:</string>
</property>
</widget>
</item>
<item row="3" column="0">
<widget class="QLabel" name="sha512Label">
<property name="text">
<string extracomment="SHA512 is the hashing algorithm">SHA512:</string>
</property>
</widget>
</item>
<item row="0" column="1">
<layout class="QHBoxLayout" name="horizontalLayout_1">
<item>
<widget class="QPushButton" name="md5Button">
<property name="text">
<string>Calculate</string>
</property>
</widget>
</item>
<item>
<spacer name="horizontalSpacerMd5">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QPushButton" name="md5CopyButton">
<property name="toolTip">
<string>Click to copy the checksum to the clipboard.</string>
</property>
<property name="text">
<string>Copy</string>
</property>
<property name="icon">
<iconset theme="edit-copy"/>
</property>
</widget>
</item>
</layout>
</item>
<item row="1" column="1">
<layout class="QHBoxLayout" name="horizontalLayout_2">
<item>
<widget class="QPushButton" name="sha1Button">
<property name="text">
<string>Calculate</string>
</property>
</widget>
</item>
<item>
<spacer name="horizontalSpacerSha1">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QPushButton" name="sha1CopyButton">
<property name="toolTip">
<string>Click to copy the checksum to the clipboard.</string>
</property>
<property name="text">
<string>Copy</string>
</property>
<property name="icon">
<iconset theme="edit-copy"/>
</property>
</widget>
</item>
</layout>
</item>
<item row="2" column="1">
<layout class="QHBoxLayout" name="horizontalLayout_3">
<item>
<widget class="QPushButton" name="sha256Button">
<property name="text">
<string>Calculate</string>
</property>
</widget>
</item>
<item>
<spacer name="horizontalSpacerSha256">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QPushButton" name="sha256CopyButton">
<property name="toolTip">
<string>Click to copy the checksum to the clipboard.</string>
</property>
<property name="text">
<string>Copy</string>
</property>
<property name="icon">
<iconset theme="edit-copy"/>
</property>
</widget>
</item>
</layout>
</item>
<item row="3" column="1">
<layout class="QHBoxLayout" name="horizontalLayout_4">
<item>
<widget class="QPushButton" name="sha512Button">
<property name="text">
<string>Calculate</string>
</property>
</widget>
</item>
<item>
<spacer name="horizontalSpacerSha512">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QPushButton" name="sha512CopyButton">
<property name="toolTip">
<string>Click to copy the checksum to the clipboard.</string>
</property>
<property name="text">
<string>Copy</string>
</property>
<property name="icon">
<iconset theme="edit-copy"/>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</widget>
</item>
<item>
<spacer name="verticalSpacer">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>40</height>
</size>
</property>
</spacer>
</item>
</layout>
</widget>
<customwidgets>
<customwidget>
<class>KSeparator</class>
<extends>QFrame</extends>
<header>kseparator.h</header>
</customwidget>
</customwidgets>
<resources/>
<connections/>
</ui>
@@ -0,0 +1,184 @@
/*
This file is part of the KDE libraries
SPDX-FileCopyrightText: 2013 Dawit Alemayehu <adawit@kde.org>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#include "../utils_p.h"
#include "clipboardupdater_p.h"
#include "copyjob.h"
#include "deletejob.h"
#include "filecopyjob.h"
#include "simplejob.h"
#include <KUrlMimeData>
#include <QClipboard>
#include <QGuiApplication>
#include <QMimeData>
using namespace KIO;
static void overwriteUrlsInClipboard(KJob *job)
{
CopyJob *copyJob = qobject_cast<CopyJob *>(job);
FileCopyJob *fileCopyJob = qobject_cast<FileCopyJob *>(job);
if (!copyJob && !fileCopyJob) {
return;
}
QList<QUrl> newUrls;
if (copyJob) {
const auto srcUrls = copyJob->srcUrls();
newUrls.reserve(srcUrls.size());
for (const QUrl &url : srcUrls) {
QUrl dUrl = copyJob->destUrl().adjusted(QUrl::StripTrailingSlash);
dUrl.setPath(Utils::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<CopyJob *>(job);
FileCopyJob *fileCopyJob = qobject_cast<FileCopyJob *>(job);
if (!copyJob && !fileCopyJob) {
return;
}
QClipboard *clipboard = QGuiApplication::clipboard();
auto mimeData = clipboard->mimeData();
if (!mimeData) {
return;
}
QList<QUrl> clipboardUrls = KUrlMimeData::urlsFromMimeData(mimeData);
bool update = false;
if (copyJob) {
const QList<QUrl> urls = copyJob->srcUrls();
for (const QUrl &url : urls) {
const int index = clipboardUrls.indexOf(url);
if (index > -1) {
QUrl dUrl = copyJob->destUrl().adjusted(QUrl::StripTrailingSlash);
dUrl.setPath(Utils::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<SimpleJob *>(job);
DeleteJob *deleteJob = qobject_cast<DeleteJob *>(job);
if (!simpleJob && !deleteJob) {
return;
}
QList<QUrl> 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<QUrl> clipboardUrls = KUrlMimeData::urlsFromMimeData(mimeData);
quint32 removedCount = 0;
for (const QUrl &url : std::as_const(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<QUrl> 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, &KJob::result, this, &ClipboardUpdater::slotResult);
}
#include "moc_clipboardupdater_p.cpp"
@@ -0,0 +1,65 @@
/*
This file is part of the KDE libraries
SPDX-FileCopyrightText: 2013 Dawit Alemayehu <adawit@kde.org>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#ifndef KIO_CLIPBOARDUPDATER_P_H
#define KIO_CLIPBOARDUPDATER_P_H
#include <QObject>
#include <jobuidelegateextension.h>
class KJob;
class QUrl;
namespace KIO
{
class Job;
class JobUiDelegate;
/**
* Updates the clipboard when it is affected by KIO operations.
*
* UpdateContent updates clipboard urls that were modified. This mode should
* be the one preferred by default because it will not change the contents
* of the clipboard if the urls modified by the job are not found in the
* clipboard.
*
* OverwriteContent blindly replaces all urls in the clipboard with the ones
* from the job. This mode should not be used unless you are 100% certain that
* the urls in the clipboard are actually there for the purposes of carrying
* out the specified job. This mode for example is used by the KIO::pasteClipboard
* job when a user performs a cut+paste operation.
*
* This class also sets @ref job as its parent object. As such, when @ref job
* is deleted the instance of ClipboardUpdater you create will also be deleted
* as well.
*/
class ClipboardUpdater : public QObject
{
Q_OBJECT
public:
/**
* Convenience function that allows renaming of a single url in the clipboard.
*/
static void update(const QUrl &srcUrl, const QUrl &destUrl);
/**
* Sets the mode.
*/
void setMode(JobUiDelegateExtension::ClipboardUpdaterMode m);
private Q_SLOTS:
void slotResult(KJob *job);
private:
explicit ClipboardUpdater(Job *job, JobUiDelegateExtension::ClipboardUpdaterMode mode);
friend class JobUiDelegate;
JobUiDelegateExtension::ClipboardUpdaterMode m_mode;
};
}
#endif
@@ -0,0 +1,14 @@
#cmakedefine01 HAVE_GETGROUPLIST
/* Defined if system has POSIX ACL support. */
#cmakedefine01 HAVE_POSIX_ACL
/* Defined if acl/libacl.h exists */
#cmakedefine01 HAVE_ACL_LIBACL_H
/* Defined if sys/extattr.h exists */
#cmakedefine01 HAVE_SYS_EXTATTR_H
/* Defined if sys/mount.h exists */
#cmakedefine01 HAVE_SYS_MOUNT_H
/* Defined if sys/stat.h exists */
#cmakedefine01 HAVE_SYS_STAT_H
/* Defined if sys/xattr.h exists */
#cmakedefine01 HAVE_SYS_XATTR_H
@@ -0,0 +1,433 @@
/*
This file is part of the KDE project
SPDX-FileCopyrightText: 2007 Fredrik Höglund <fredrik@kde.org>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#include "delegateanimationhandler_p.h"
#include <QAbstractItemView>
#include <QDebug>
#include <QPersistentModelIndex>
#include <QTime>
#include "kdirmodel.h"
#include <QAbstractProxyModel>
#include <cmath>
#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(), &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;
}
}
static constexpr double s_mPI2 = 1.57079632679489661923;
qreal AnimationState::hoverProgress() const
{
return qRound(255.0 * std::sin(progress * s_mPI2)) / 255.0;
}
qreal AnimationState::fadeProgress() const
{
return qRound(255.0 * std::sin(m_fadeProgress * s_mPI2)) / 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, &QTimer::timeout, this, &DelegateAnimationHandler::sequenceTimerTimeout);
;
}
DelegateAnimationHandler::~DelegateAnimationHandler()
{
timer.stop();
QMapIterator<const QAbstractItemView *, AnimationList *> i(animationLists);
while (i.hasNext()) {
i.next();
qDeleteAll(*i.value());
delete i.value();
}
animationLists.clear();
}
void DelegateAnimationHandler::sequenceTimerTimeout()
{
QAbstractItemModel *model = const_cast<QAbstractItemModel *>(sequenceModelIndex.model());
QAbstractProxyModel *proxy = qobject_cast<QAbstractProxyModel *>(model);
QModelIndex index = sequenceModelIndex;
if (proxy) {
index = proxy->mapToSource(index);
model = proxy->sourceModel();
}
KDirModel *dirModel = dynamic_cast<KDirModel *>(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<const ProtectedAccessor *>(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
const AnimationList *list = animationLists.value(view);
if (list) {
auto it = std::find_if(list->cbegin(), list->cend(), [&index](AnimationState *state) {
return state->index == index;
});
if (it != list->cend()) {
return *it;
}
}
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, &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;
QMutableListIterator<AnimationState *> 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<QAbstractItemView *>(view)->viewport()->update(region);
}
return activeAnimations;
}
void DelegateAnimationHandler::viewDeleted(QObject *view)
{
AnimationList *list = animationLists.take(static_cast<QAbstractItemView *>(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"
@@ -0,0 +1,160 @@
/*
This file is part of the KDE project
SPDX-FileCopyrightText: 2007 Fredrik Höglund <fredrik@kde.org>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#ifndef DELEGATEANIMATIONHANDLER_P_H
#define DELEGATEANIMATIONHANDLER_P_H
#include <QBasicTimer>
#include <QElapsedTimer>
#include <QMap>
#include <QPersistentModelIndex>
#include <QStyle>
#include <QTime>
#include <QTimeLine>
#include <QTimer>
class QAbstractItemView;
namespace KIO
{
class CachedRendering : public QObject
{
Q_OBJECT
public:
CachedRendering(QStyle::State state, const QSize &size, const QModelIndex &validityIndex, qreal devicePixelRatio = 1.0);
bool checkValidity(QStyle::State current) const
{
return state == current && valid;
}
QStyle::State state;
QPixmap regular;
QPixmap hover;
bool valid;
QPersistentModelIndex validityIndex;
private Q_SLOTS:
void dataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight);
void modelReset();
};
class AnimationState
{
public:
~AnimationState();
AnimationState(const AnimationState &) = delete;
AnimationState &operator=(const AnimationState &) = delete;
// Progress of the mouse hovering animation
qreal hoverProgress() const;
// Progress of the icon fading animation
qreal fadeProgress() const;
// Angle of the painter, to paint the animation for a file job on an item
qreal jobAnimationAngle() const;
void setJobAnimation(bool value);
bool hasJobAnimation() const;
CachedRendering *cachedRendering() const
{
return renderCache;
}
// The previous render-cache is deleted, if there was one
void setCachedRendering(CachedRendering *rendering)
{
delete renderCache;
renderCache = rendering;
}
// Returns current cached rendering, and removes it from this state.
// The caller has the ownership.
CachedRendering *takeCachedRendering()
{
CachedRendering *ret = renderCache;
renderCache = nullptr;
return ret;
}
CachedRendering *cachedRenderingFadeFrom() const
{
return fadeFromRenderCache;
}
// The previous render-cache is deleted, if there was one
void setCachedRenderingFadeFrom(CachedRendering *rendering)
{
delete fadeFromRenderCache;
fadeFromRenderCache = rendering;
if (rendering) {
m_fadeProgress = 0;
} else {
m_fadeProgress = 1;
}
}
private:
explicit AnimationState(const QModelIndex &index);
bool update();
QPersistentModelIndex index;
QTimeLine::Direction direction;
bool animating;
bool jobAnimation;
qreal progress;
qreal m_fadeProgress;
qreal m_jobAnimationAngle;
QElapsedTimer time;
QElapsedTimer creationTime;
CachedRendering *renderCache;
CachedRendering *fadeFromRenderCache;
friend class DelegateAnimationHandler;
};
class DelegateAnimationHandler : public QObject
{
Q_OBJECT
typedef QList<AnimationState *> AnimationList;
typedef QMapIterator<const QAbstractItemView *, AnimationList *> AnimationListsIterator;
typedef QMutableMapIterator<const QAbstractItemView *, AnimationList *> MutableAnimationListsIterator;
public:
explicit DelegateAnimationHandler(QObject *parent = nullptr);
~DelegateAnimationHandler() override;
AnimationState *animationState(const QStyleOption &option, const QModelIndex &index, const QAbstractItemView *view);
void restartAnimation(AnimationState *state);
void gotNewIcon(const QModelIndex &index);
private Q_SLOTS:
void viewDeleted(QObject *view);
void sequenceTimerTimeout();
private:
void eventuallyStartIteration(const QModelIndex &index);
AnimationState *findAnimationState(const QAbstractItemView *view, const QModelIndex &index) const;
void addAnimationState(AnimationState *state, const QAbstractItemView *view);
void startAnimation(AnimationState *state);
int runAnimations(AnimationList *list, const QAbstractItemView *view);
void timerEvent(QTimerEvent *event) override;
void setSequenceIndex(int arg1);
private:
QMap<const QAbstractItemView *, AnimationList *> animationLists;
QElapsedTimer fadeInAddTime;
QBasicTimer timer;
// Icon sequence handling:
QPersistentModelIndex sequenceModelIndex;
QTimer iconSequenceTimer;
int currentSequenceIndex;
};
}
#endif
@@ -0,0 +1,154 @@
/*
This file is part of the KDE libraries
SPDX-FileCopyrightText: 2022 Ahmad Samir <a.samirh78@gmail.com>
SPDX-License-Identifier: LGPL-2.0-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
*/
#include "deleteortrashjob.h"
#include "fileundomanager.h"
#include "widgetsaskuseractionhandler.h"
#include <kio/copyjob.h>
#include <kio/deletejob.h>
#include <kio/emptytrashjob.h>
#include <kio/job.h>
#include <kio/jobuidelegatefactory.h>
#include <kio_widgets_debug.h>
#include <KJobWidgets>
namespace KIO
{
using AskIface = AskUserActionInterface;
class DeleteOrTrashJobPrivate
{
public:
DeleteOrTrashJobPrivate(const QList<QUrl> &urls, //
AskIface::DeletionType deletionType,
AskIface::ConfirmationType confirm,
QObject *parent,
DeleteOrTrashJob *qq)
: q(qq)
, m_urls(urls)
, m_delType(deletionType)
, m_confirm(confirm)
, m_parentWindow(qobject_cast<QWidget *>(parent))
{
// trashing an already trashed file is deleting it, BUG 459545
if (m_delType == AskIface::Trash && m_urls.first().scheme() == QStringLiteral("trash")) {
m_delType = AskIface::Delete;
}
}
void slotAskUser(bool allowDelete, const QList<QUrl> &urls, AskIface::DeletionType delType, QWidget *parentWindow);
DeleteOrTrashJob *q = nullptr;
QList<QUrl> m_urls;
AskIface::DeletionType m_delType;
AskIface::ConfirmationType m_confirm;
QWidget *m_parentWindow = nullptr;
QMetaObject::Connection m_handlerConnection;
};
void DeleteOrTrashJobPrivate::slotAskUser(bool allowDelete, const QList<QUrl> &urls, AskIface::DeletionType delType, QWidget *parentWindow)
{
if (!allowDelete) {
q->setError(KIO::ERR_USER_CANCELED);
q->emitResult();
return;
}
KIO::Job *job = nullptr;
switch (delType) {
case AskIface::Trash:
Q_ASSERT(!urls.isEmpty());
job = KIO::trash(urls);
using UndoMananger = KIO::FileUndoManager;
UndoMananger::self()->recordJob(UndoMananger::Trash, urls, QUrl(QStringLiteral("trash:/")), job);
break;
case AskIface::DeleteInsteadOfTrash:
case AskIface::Delete:
Q_ASSERT(!urls.isEmpty());
job = KIO::del(urls);
break;
case AskIface::EmptyTrash:
job = KIO::emptyTrash();
break;
}
if (job) {
KJobWidgets::setWindow(job, parentWindow);
// showErrorMessage() is used in slotResult() instead of AutoErrorHandling,
// because if Trashing fails (e.g. due to size constraints), we'll re-ask the
// user about deleting instead of Trashing, in which case we don't want to
// show the "File is too large to Trash" error message
job->uiDelegate()->setAutoErrorHandlingEnabled(false);
q->addSubjob(job);
}
}
DeleteOrTrashJob::DeleteOrTrashJob(const QList<QUrl> &urls, //
AskIface::DeletionType deletionType,
AskIface::ConfirmationType confirm,
QObject *parent)
: KCompositeJob(parent)
, d(new DeleteOrTrashJobPrivate{urls, deletionType, confirm, parent, this})
{
}
DeleteOrTrashJob::~DeleteOrTrashJob() = default;
void DeleteOrTrashJob::start()
{
auto *askHandler = KIO::delegateExtension<AskIface *>(this);
if (!askHandler) {
auto *uiDelegate = new KJobUiDelegate(KJobUiDelegate::AutoErrorHandlingEnabled);
auto *widgetAskHandler = new WidgetsAskUserActionHandler(uiDelegate);
widgetAskHandler->setWindow(d->m_parentWindow);
setUiDelegate(uiDelegate);
askHandler = widgetAskHandler;
}
Q_ASSERT(askHandler);
auto askFunc = [this](bool allowDelete, //
const QList<QUrl> &urls,
AskIface::DeletionType deletionType,
QWidget *window) {
d->slotAskUser(allowDelete, urls, deletionType, window);
};
// Make it a unique connection, as the same UI delegate could get re-used
// if e.g. Trashing failed and we're re-asking the user about deleting instead
// of Trashing
disconnect(d->m_handlerConnection);
d->m_handlerConnection = connect(askHandler, &AskIface::askUserDeleteResult, this, askFunc);
askHandler->askUserDelete(d->m_urls, d->m_delType, d->m_confirm, d->m_parentWindow);
}
void DeleteOrTrashJob::slotResult(KJob *job)
{
const int errCode = job->error();
if (errCode == KIO::ERR_TRASH_FILE_TOO_LARGE) {
removeSubjob(job);
d->m_delType = AskIface::DeleteInsteadOfTrash;
start();
return;
}
if (errCode) {
setError(errCode);
// We're a KJob, not a KIO::Job, so build the error string here
setErrorText(KIO::buildErrorString(errCode, job->errorText()));
job->uiDelegate()->showErrorMessage();
}
emitResult();
}
} // namespace KIO
#include "moc_deleteortrashjob.cpp"
@@ -0,0 +1,80 @@
/*
This file is part of the KDE libraries
SPDX-FileCopyrightText: 2022 Ahmad Samir <a.samirh78@gmail.com>
SPDX-License-Identifier: LGPL-2.0-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
*/
#ifndef DELETEORTRASHJOB_H
#define DELETEORTRASHJOB_H
#include <KIO/AskUserActionInterface>
#include <kiowidgets_export.h>
#include <KCompositeJob>
#include <memory>
namespace KIO
{
class DeleteOrTrashJobPrivate;
/**
* @class DeleteOrTrashJob deleteortrashjob.h <KIO/DeleteOrTrashJob>
*
* This job asks the user for confirmation to delete or move to Trash
* a list of URLs; or if the job is constructed with
* AskUserActionInterface::EmptyTrash, to empty the Trash.
*
* A KIO::WidgetAskUserActionHandler will be used by default, unless a
* KJobUiDelegate that implements KIO::AskUserActionInterface is set with
* setUiDelegate().
*
* In the case of moving items to Trash, this job records the
* operation using KIO::FileUndoManager.
*
* To start the job after constructing it, you must call start().
*
* @since 5.100
*/
class KIOWIDGETS_EXPORT DeleteOrTrashJob : public KCompositeJob
{
Q_OBJECT
public:
/**
* Creates a DeleteOrTrashJob.
* @param urls the list of urls to delete, move to Trash, or an empty list
* in the case of AskUserActionInterface::EmptyTrash (in the latter case,
* the list of urls is ignored)
* @param deletionType one of AskUserActionInterface::DeletionType
* @param confirm one of AskUserActionInterface::ConfirmationType
* @param parent parent object, e.g. a QWidget for widget-based applications
*/
explicit DeleteOrTrashJob(const QList<QUrl> &urls,
AskUserActionInterface::DeletionType deletionType,
AskUserActionInterface::ConfirmationType confirm,
QObject *parent);
/**
* Destructor
*
* Note that jobs auto-delete themselves after emitting result
*/
~DeleteOrTrashJob() override;
/**
* You must call this to actually start the job.
*/
void start() override;
private:
void slotResult(KJob *job) override;
friend DeleteOrTrashJobPrivate;
std::unique_ptr<DeleteOrTrashJobPrivate> d;
};
} // namespace KIO
#endif // DELETEORTRASHJOB_H
@@ -0,0 +1,696 @@
/*
This file is part of the KDE libraries
SPDX-FileCopyrightText: 2014 David Faure <faure@kde.org>
SPDX-License-Identifier: LGPL-2.0-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
*/
#include "dropjob.h"
#include "job_p.h"
#include "jobuidelegate.h"
#include "jobuidelegateextension.h"
#include "kio_widgets_debug.h"
#include "pastejob.h"
#include "pastejob_p.h"
#include <KConfigGroup>
#include <KCoreDirLister>
#include <KDesktopFile>
#include <KFileItem>
#include <KFileItemListProperties>
#include <KIO/ApplicationLauncherJob>
#include <KIO/CopyJob>
#include <KIO/DndPopupMenuPlugin>
#include <KIO/FileUndoManager>
#include <KJobWidgets>
#include <KJobWindows>
#include <KLocalizedString>
#include <KPluginFactory>
#include <KPluginMetaData>
#include <KProtocolManager>
#include <KService>
#include <KUrlMimeData>
#ifdef WITH_QTDBUS
#include <QDBusConnection>
#include <QDBusPendingCall>
#endif
#include <QDropEvent>
#include <QFileInfo>
#include <QMenu>
#include <QMimeData>
#include <QProcess>
#include <QTimer>
#include <QWindow>
using namespace KIO;
Q_DECLARE_METATYPE(Qt::DropAction)
namespace KIO
{
class DropMenu;
}
class KIO::DropMenu : public QMenu
{
Q_OBJECT
public:
explicit DropMenu(QWidget *parent = nullptr);
~DropMenu() override;
void addCancelAction();
void addExtraActions(const QList<QAction *> &appActions, const QList<QAction *> &pluginActions);
private:
QList<QAction *> m_appActions;
QList<QAction *> m_pluginActions;
QAction *m_lastSeparator;
QAction *m_extraActionsSeparator;
QAction *m_cancelAction;
};
static const QString s_applicationSlashXDashKDEDashArkDashDnDExtractDashService = //
QStringLiteral("application/x-kde-ark-dndextract-service");
static const QString s_applicationSlashXDashKDEDashArkDashDnDExtractDashPath = //
QStringLiteral("application/x-kde-ark-dndextract-path");
class KIO::DropJobPrivate : public KIO::JobPrivate
{
public:
DropJobPrivate(const QDropEvent *dropEvent, const QUrl &destUrl, DropJobFlags dropjobFlags, JobFlags flags)
: JobPrivate()
, m_mimeData(dropEvent->mimeData()) // Extract everything from the dropevent, since it will be deleted before the job starts
, m_urls(KUrlMimeData::urlsFromMimeData(m_mimeData, KUrlMimeData::PreferLocalUrls, &m_metaData))
, m_dropAction(dropEvent->dropAction())
, m_relativePos(dropEvent->position().toPoint())
, m_keyboardModifiers(dropEvent->modifiers())
, m_hasArkFormat(m_mimeData->hasFormat(s_applicationSlashXDashKDEDashArkDashDnDExtractDashService)
&& m_mimeData->hasFormat(s_applicationSlashXDashKDEDashArkDashDnDExtractDashPath))
, m_destUrl(destUrl)
, m_destItem(KCoreDirLister::cachedItemForUrl(destUrl))
, m_flags(flags)
, m_dropjobFlags(dropjobFlags)
, m_triggered(false)
{
// Check for the drop of a bookmark -> we want a Link action
if (m_mimeData->hasFormat(QStringLiteral("application/x-xbel"))) {
m_keyboardModifiers |= Qt::KeyboardModifiers(Qt::ControlModifier | Qt::ShiftModifier);
m_dropAction = Qt::LinkAction;
}
if (m_destItem.isNull() && m_destUrl.isLocalFile()) {
m_destItem = KFileItem(m_destUrl);
}
if (m_hasArkFormat) {
m_remoteArkDBusClient = QString::fromUtf8(m_mimeData->data(s_applicationSlashXDashKDEDashArkDashDnDExtractDashService));
m_remoteArkDBusPath = QString::fromUtf8(m_mimeData->data(s_applicationSlashXDashKDEDashArkDashDnDExtractDashPath));
}
if (!(m_flags & KIO::NoPrivilegeExecution)) {
m_privilegeExecutionEnabled = true;
switch (m_dropAction) {
case Qt::CopyAction:
m_operationType = Copy;
break;
case Qt::MoveAction:
m_operationType = Move;
break;
case Qt::LinkAction:
m_operationType = Symlink;
break;
default:
m_operationType = Other;
break;
}
}
}
bool destIsDirectory() const
{
if (!m_destItem.isNull()) {
return m_destItem.isDir();
}
// We support local dir, remote dir, local desktop file, local executable.
// So for remote URLs, we just assume they point to a directory, the user will get an error from KIO::copy if not.
return true;
}
void handleCopyToDirectory();
void slotDropActionDetermined(int error);
void handleDropToDesktopFile();
void handleDropToExecutable();
void fillPopupMenu(KIO::DropMenu *popup);
void addPluginActions(KIO::DropMenu *popup, const KFileItemListProperties &itemProps);
void doCopyToDirectory();
QWindow *transientParent();
QPointer<const QMimeData> m_mimeData;
const QList<QUrl> m_urls;
QMap<QString, QString> m_metaData;
Qt::DropAction m_dropAction;
QPoint m_relativePos;
Qt::KeyboardModifiers m_keyboardModifiers;
bool m_hasArkFormat;
QString m_remoteArkDBusClient;
QString m_remoteArkDBusPath;
QUrl m_destUrl;
KFileItem m_destItem; // null for remote URLs not found in the dirlister cache
const JobFlags m_flags;
const DropJobFlags m_dropjobFlags;
QList<QAction *> m_appActions;
QList<QAction *> m_pluginActions;
bool m_triggered; // Tracks whether an action has been triggered in the popup menu.
QSet<KIO::DropMenu *> m_menus;
Q_DECLARE_PUBLIC(DropJob)
void slotStart();
void slotTriggered(QAction *);
void slotAboutToHide();
static inline DropJob *newJob(const QDropEvent *dropEvent, const QUrl &destUrl, DropJobFlags dropjobFlags, JobFlags flags)
{
DropJob *job = new DropJob(*new DropJobPrivate(dropEvent, destUrl, dropjobFlags, flags));
job->setUiDelegate(KIO::createDefaultJobUiDelegate());
// Note: never KIO::getJobTracker()->registerJob here.
// We don't want a progress dialog during the copy/move/link popup, it would in fact close
// the popup
return job;
}
};
DropMenu::DropMenu(QWidget *parent)
: QMenu(parent)
, m_extraActionsSeparator(nullptr)
{
m_cancelAction = new QAction(i18n("C&ancel") + QLatin1Char('\t') + QKeySequence(Qt::Key_Escape).toString(QKeySequence::NativeText), this);
m_cancelAction->setIcon(QIcon::fromTheme(QStringLiteral("process-stop")));
m_lastSeparator = new QAction(this);
m_lastSeparator->setSeparator(true);
}
DropMenu::~DropMenu()
{
}
void DropMenu::addExtraActions(const QList<QAction *> &appActions, const QList<QAction *> &pluginActions)
{
removeAction(m_lastSeparator);
removeAction(m_cancelAction);
removeAction(m_extraActionsSeparator);
for (QAction *action : std::as_const(m_appActions)) {
removeAction(action);
}
for (QAction *action : std::as_const(m_pluginActions)) {
removeAction(action);
}
m_appActions = appActions;
m_pluginActions = pluginActions;
if (!m_appActions.isEmpty() || !m_pluginActions.isEmpty()) {
QAction *firstExtraAction = m_appActions.value(0, m_pluginActions.value(0, nullptr));
if (firstExtraAction && !firstExtraAction->isSeparator()) {
if (!m_extraActionsSeparator) {
m_extraActionsSeparator = new QAction(this);
m_extraActionsSeparator->setSeparator(true);
}
addAction(m_extraActionsSeparator);
}
addActions(appActions);
addActions(pluginActions);
}
addAction(m_lastSeparator);
addAction(m_cancelAction);
}
DropJob::DropJob(DropJobPrivate &dd)
: Job(dd)
{
Q_D(DropJob);
QTimer::singleShot(0, this, [d]() {
d->slotStart();
});
}
DropJob::~DropJob()
{
}
void DropJobPrivate::slotStart()
{
Q_Q(DropJob);
#ifdef WITH_QTDBUS
if (m_hasArkFormat) {
QDBusMessage message = QDBusMessage::createMethodCall(m_remoteArkDBusClient,
m_remoteArkDBusPath,
QStringLiteral("org.kde.ark.DndExtract"),
QStringLiteral("extractSelectedFilesTo"));
message.setArguments({m_destUrl.toDisplayString(QUrl::PreferLocalFile)});
const auto pending = QDBusConnection::sessionBus().asyncCall(message);
auto watcher = std::make_shared<QDBusPendingCallWatcher>(pending);
QObject::connect(watcher.get(), &QDBusPendingCallWatcher::finished, q, [this, watcher] {
Q_Q(DropJob);
if (watcher->isError()) {
q->setError(KIO::ERR_UNKNOWN);
}
q->emitResult();
});
return;
}
#endif
if (!m_urls.isEmpty()) {
if (destIsDirectory()) {
handleCopyToDirectory();
} else { // local file
const QString destFile = m_destUrl.toLocalFile();
if (KDesktopFile::isDesktopFile(destFile)) {
handleDropToDesktopFile();
} else if (QFileInfo(destFile).isExecutable()) {
handleDropToExecutable();
} else {
// should not happen, if KDirModel::flags is correct
q->setError(KIO::ERR_ACCESS_DENIED);
q->emitResult();
}
}
} else if (m_mimeData) {
// Dropping raw data
KIO::PasteJob *job = KIO::PasteJobPrivate::newJob(m_mimeData, m_destUrl, KIO::HideProgressInfo, false /*not clipboard*/);
QObject::connect(job, &KIO::PasteJob::itemCreated, q, &KIO::DropJob::itemCreated);
q->addSubjob(job);
}
}
void DropJobPrivate::fillPopupMenu(KIO::DropMenu *popup)
{
Q_Q(DropJob);
// Check what the source can do
// TODO: Determining the MIME type of the source URLs is difficult for remote URLs,
// we would need to KIO::stat each URL in turn, asynchronously....
KFileItemList fileItems;
fileItems.reserve(m_urls.size());
for (const QUrl &url : m_urls) {
fileItems.append(KFileItem(url));
}
const bool allSourcesAreHttpUrls = std::ranges::all_of(m_urls, [](const auto &url) {
return url.scheme().startsWith(QStringLiteral("http"), Qt::CaseInsensitive);
});
const KFileItemListProperties itemProps(fileItems);
Q_EMIT q->popupMenuAboutToShow(itemProps);
const bool sReading = itemProps.supportsReading();
// For http URLs, even though technically the protocol supports deleting,
// this never makes sense for a drag operation.
const bool sDeleting = allSourcesAreHttpUrls ? false : itemProps.supportsDeleting();
const bool sMoving = itemProps.supportsMoving();
const int separatorLength = QCoreApplication::translate("QShortcut", "+").size();
QString seq = QKeySequence(Qt::ShiftModifier).toString(QKeySequence::NativeText);
seq.chop(separatorLength); // chop superfluous '+'
QAction *popupMoveAction = new QAction(i18n("&Move Here") + QLatin1Char('\t') + seq, popup);
popupMoveAction->setIcon(QIcon::fromTheme(QStringLiteral("edit-move"), QIcon::fromTheme(QStringLiteral("go-jump"))));
popupMoveAction->setData(QVariant::fromValue(Qt::MoveAction));
seq = QKeySequence(Qt::ControlModifier).toString(QKeySequence::NativeText);
seq.chop(separatorLength);
const QString copyActionName = allSourcesAreHttpUrls ? i18nc("@action:inmenu Download contents of URL here", "&Download Here") : i18n("&Copy Here");
const QIcon copyActionIcon = QIcon::fromTheme(allSourcesAreHttpUrls ? QStringLiteral("download") : QStringLiteral("edit-copy"));
QAction *popupCopyAction = new QAction(copyActionName + QLatin1Char('\t') + seq, popup);
popupCopyAction->setIcon(copyActionIcon);
popupCopyAction->setData(QVariant::fromValue(Qt::CopyAction));
seq = QKeySequence(Qt::ControlModifier | Qt::ShiftModifier).toString(QKeySequence::NativeText);
seq.chop(separatorLength);
QAction *popupLinkAction = new QAction(i18n("&Link Here") + QLatin1Char('\t') + seq, popup);
popupLinkAction->setIcon(QIcon::fromTheme(QStringLiteral("edit-link")));
popupLinkAction->setData(QVariant::fromValue(Qt::LinkAction));
if (sMoving || (sReading && sDeleting)) {
const bool equalDestination = std::all_of(m_urls.cbegin(), m_urls.cend(), [this](const QUrl &src) {
return m_destUrl.matches(src.adjusted(QUrl::RemoveFilename), QUrl::StripTrailingSlash);
});
if (!equalDestination) {
popup->addAction(popupMoveAction);
}
}
if (sReading) {
popup->addAction(popupCopyAction);
}
popup->addAction(popupLinkAction);
addPluginActions(popup, itemProps);
}
void DropJobPrivate::addPluginActions(KIO::DropMenu *popup, const KFileItemListProperties &itemProps)
{
const QList<KPluginMetaData> plugin_offers = KPluginMetaData::findPlugins(QStringLiteral("kf6/kio_dnd"));
for (const KPluginMetaData &data : plugin_offers) {
if (auto plugin = KPluginFactory::instantiatePlugin<KIO::DndPopupMenuPlugin>(data).plugin) {
const auto actions = plugin->setup(itemProps, m_destUrl);
for (auto action : actions) {
action->setParent(popup);
}
m_pluginActions += actions;
}
}
popup->addExtraActions(m_appActions, m_pluginActions);
}
void DropJob::setApplicationActions(const QList<QAction *> &actions)
{
Q_D(DropJob);
d->m_appActions = actions;
for (KIO::DropMenu *menu : std::as_const(d->m_menus)) {
menu->addExtraActions(d->m_appActions, d->m_pluginActions);
}
}
void DropJob::showMenu(const QPoint &p, QAction *atAction)
{
Q_D(DropJob);
if (!(d->m_dropjobFlags & KIO::ShowMenuManually)) {
return;
}
for (KIO::DropMenu *menu : std::as_const(d->m_menus)) {
if (QWindow *transientParent = d->transientParent()) {
if (menu->winId()) {
menu->windowHandle()->setTransientParent(transientParent);
}
}
menu->popup(p, atAction);
}
}
void DropJobPrivate::slotTriggered(QAction *action)
{
Q_Q(DropJob);
if (m_appActions.contains(action) || m_pluginActions.contains(action)) {
q->emitResult();
return;
}
const QVariant data = action->data();
if (!data.canConvert<Qt::DropAction>()) {
q->setError(KIO::ERR_USER_CANCELED);
q->emitResult();
return;
}
m_dropAction = data.value<Qt::DropAction>();
doCopyToDirectory();
}
void DropJobPrivate::slotAboutToHide()
{
Q_Q(DropJob);
// QMenu emits aboutToHide before triggered.
// So we need to give the menu time in case it needs to emit triggered.
// If it does, the cleanup will be done by slotTriggered.
QTimer::singleShot(0, q, [=, this]() {
if (!m_triggered) {
q->setError(KIO::ERR_USER_CANCELED);
q->emitResult();
}
});
}
void DropJobPrivate::handleCopyToDirectory()
{
Q_Q(DropJob);
// Process m_dropAction as set by Qt at the time of the drop event
if (!KProtocolManager::supportsWriting(m_destUrl)) {
slotDropActionDetermined(KIO::ERR_CANNOT_WRITE);
return;
}
if (!m_destItem.isNull() && !m_destItem.isWritable() && (m_flags & KIO::NoPrivilegeExecution)) {
slotDropActionDetermined(KIO::ERR_WRITE_ACCESS_DENIED);
return;
}
bool allItemsAreFromTrash = true;
bool containsTrashRoot = false;
for (const QUrl &url : m_urls) {
const bool local = url.isLocalFile();
if (!local /*optimization*/ && url.scheme() == QLatin1String("trash")) {
if (url.path().isEmpty() || url.path() == QLatin1String("/")) {
containsTrashRoot = true;
}
} else {
allItemsAreFromTrash = false;
}
if (url.matches(m_destUrl, QUrl::StripTrailingSlash)) {
slotDropActionDetermined(KIO::ERR_DROP_ON_ITSELF);
return;
}
}
const bool trashing = m_destUrl.scheme() == QLatin1String("trash");
if (trashing) {
if (allItemsAreFromTrash) {
qCDebug(KIO_WIDGETS) << "Dropping items from trash to trash";
slotDropActionDetermined(KIO::ERR_DROP_ON_ITSELF);
return;
}
m_dropAction = Qt::MoveAction;
auto *askUserInterface = KIO::delegateExtension<AskUserActionInterface *>(q);
// No UI Delegate set for this job, or a delegate that doesn't implement
// AskUserActionInterface, then just proceed with the job without asking.
// This is useful for non-interactive usage, (which doesn't actually apply
// here as a DropJob is always interactive), but this is useful for unittests,
// which are typically non-interactive.
if (!askUserInterface) {
slotDropActionDetermined(KJob::NoError);
return;
}
QObject::connect(askUserInterface, &KIO::AskUserActionInterface::askUserDeleteResult, q, [this](bool allowDelete) {
if (allowDelete) {
slotDropActionDetermined(KJob::NoError);
} else {
slotDropActionDetermined(KIO::ERR_USER_CANCELED);
}
});
askUserInterface->askUserDelete(m_urls, KIO::AskUserActionInterface::Trash, KIO::AskUserActionInterface::DefaultConfirmation, KJobWidgets::window(q));
return;
}
// If we can't determine the action below, we use ERR::UNKNOWN as we need to ask
// the user via a popup menu.
int err = KIO::ERR_UNKNOWN;
const bool implicitCopy = m_destUrl.scheme() == QLatin1String("stash");
if (implicitCopy) {
m_dropAction = Qt::CopyAction;
err = KJob::NoError; // Ok
} else if (containsTrashRoot) {
// Dropping a link to the trash: don't move the full contents, just make a link (#319660)
m_dropAction = Qt::LinkAction;
err = KJob::NoError; // Ok
} else if (allItemsAreFromTrash) {
// No point in asking copy/move/link when using dragging from the trash, just move the file out.
m_dropAction = Qt::MoveAction;
err = KJob::NoError; // Ok
} else if (m_keyboardModifiers & (Qt::ControlModifier | Qt::ShiftModifier | Qt::AltModifier)) {
// Qt determined m_dropAction from the modifiers already
err = KJob::NoError; // Ok
}
slotDropActionDetermined(err);
}
QWindow *DropJobPrivate::transientParent()
{
Q_Q(DropJob);
if (QWidget *widget = KJobWidgets::window(q)) {
QWidget *window = widget->window();
Q_ASSERT(window);
return window->windowHandle();
}
if (QWindow *window = KJobWindows::window(q)) {
return window;
}
return nullptr;
}
void DropJobPrivate::slotDropActionDetermined(int error)
{
Q_Q(DropJob);
if (error == KJob::NoError) {
doCopyToDirectory();
return;
}
// There was an error, handle it
if (error == KIO::ERR_UNKNOWN) {
KIO::DropMenu *menu = new KIO::DropMenu();
QObject::connect(menu, &QMenu::aboutToHide, menu, &QObject::deleteLater);
// If the user clicks outside the menu, it will be destroyed without emitting the triggered signal.
QObject::connect(menu, &QMenu::aboutToHide, q, [this]() {
slotAboutToHide();
});
fillPopupMenu(menu);
QObject::connect(menu, &QMenu::triggered, q, [this](QAction *action) {
m_triggered = true;
slotTriggered(action);
});
if (!(m_dropjobFlags & KIO::ShowMenuManually)) {
if (QWindow *parent = transientParent()) {
if (menu->winId()) {
menu->windowHandle()->setTransientParent(parent);
}
}
auto *window = KJobWidgets::window(q);
menu->popup(window ? window->mapToGlobal(m_relativePos) : QCursor::pos());
}
m_menus.insert(menu);
QObject::connect(menu, &QObject::destroyed, q, [this, menu]() {
m_menus.remove(menu);
});
} else {
q->setError(error);
q->emitResult();
}
}
void DropJobPrivate::doCopyToDirectory()
{
Q_Q(DropJob);
KIO::CopyJob *job = nullptr;
switch (m_dropAction) {
case Qt::MoveAction:
job = KIO::move(m_urls, m_destUrl, m_flags);
KIO::FileUndoManager::self()->recordJob(m_destUrl.scheme() == QLatin1String("trash") ? KIO::FileUndoManager::Trash : KIO::FileUndoManager::Move,
m_urls,
m_destUrl,
job);
break;
case Qt::CopyAction:
job = KIO::copy(m_urls, m_destUrl, m_flags);
KIO::FileUndoManager::self()->recordCopyJob(job);
break;
case Qt::LinkAction:
job = KIO::link(m_urls, m_destUrl, m_flags);
KIO::FileUndoManager::self()->recordCopyJob(job);
break;
default:
qCWarning(KIO_WIDGETS) << "Unknown drop action" << int(m_dropAction);
q->setError(KIO::ERR_UNSUPPORTED_ACTION);
q->emitResult();
return;
}
Q_ASSERT(job);
job->setParentJob(q);
job->setMetaData(m_metaData);
QObject::connect(job, &KIO::CopyJob::copyingDone, q, [q](KIO::Job *, const QUrl &, const QUrl &to) {
Q_EMIT q->itemCreated(to);
});
QObject::connect(job, &KIO::CopyJob::copyingLinkDone, q, [q](KIO::Job *, const QUrl &, const QString &, const QUrl &to) {
Q_EMIT q->itemCreated(to);
});
q->addSubjob(job);
Q_EMIT q->copyJobStarted(job);
}
void DropJobPrivate::handleDropToDesktopFile()
{
Q_Q(DropJob);
const QString urlKey = QStringLiteral("URL");
const QString destFile = m_destUrl.toLocalFile();
const KDesktopFile desktopFile(destFile);
const KConfigGroup desktopGroup = desktopFile.desktopGroup();
if (desktopFile.hasApplicationType()) {
// Drop to application -> start app with urls as argument
KService::Ptr service(new KService(destFile));
// Can't use setParentJob() because ApplicationLauncherJob isn't a KIO::Job,
// instead pass q as parent so that KIO::delegateExtension() can find a delegate
KIO::ApplicationLauncherJob *job = new KIO::ApplicationLauncherJob(service, q);
job->setUrls(m_urls);
QObject::connect(job, &KJob::result, q, [=]() {
if (job->error()) {
q->setError(KIO::ERR_CANNOT_LAUNCH_PROCESS);
q->setErrorText(destFile);
}
q->emitResult();
});
job->start();
} else if (desktopFile.hasLinkType() && desktopGroup.hasKey(urlKey)) {
// Drop to link -> adjust destination directory
m_destUrl = QUrl::fromUserInput(desktopGroup.readPathEntry(urlKey, QString()));
handleCopyToDirectory();
} else {
if (desktopFile.hasDeviceType()) {
qCWarning(KIO_WIDGETS) << "Not re-implemented; please email kde-frameworks-devel@kde.org if you need this.";
// take code from libkonq's old konq_operations.cpp
// for now, fallback
}
// Some other kind of .desktop file (service, servicetype...)
q->setError(KIO::ERR_UNSUPPORTED_ACTION);
q->emitResult();
}
}
void DropJobPrivate::handleDropToExecutable()
{
Q_Q(DropJob);
// Launch executable for each of the files
QStringList args;
args.reserve(m_urls.size());
for (const QUrl &url : std::as_const(m_urls)) {
args << url.toLocalFile(); // assume local files
}
QProcess::startDetached(m_destUrl.toLocalFile(), args);
q->emitResult();
}
void DropJob::slotResult(KJob *job)
{
if (job->error()) {
KIO::Job::slotResult(job); // will set the error and emit result(this)
return;
}
removeSubjob(job);
emitResult();
}
DropJob *KIO::drop(const QDropEvent *dropEvent, const QUrl &destUrl, JobFlags flags)
{
return DropJobPrivate::newJob(dropEvent, destUrl, KIO::DropJobDefaultFlags, flags);
}
DropJob *KIO::drop(const QDropEvent *dropEvent, const QUrl &destUrl, DropJobFlags dropjobFlags, JobFlags flags)
{
return DropJobPrivate::newJob(dropEvent, destUrl, dropjobFlags, flags);
}
#include "dropjob.moc"
#include "moc_dropjob.cpp"
@@ -0,0 +1,159 @@
/*
This file is part of the KDE libraries
SPDX-FileCopyrightText: 2014 David Faure <faure@kde.org>
SPDX-License-Identifier: LGPL-2.0-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
*/
#ifndef DROPJOB_H
#define DROPJOB_H
#include <QUrl>
#include "kiowidgets_export.h"
#include <kio/job_base.h>
class QAction;
class QDropEvent;
class KFileItemListProperties;
namespace KIO
{
/**
* Special flag of DropJob in addition to KIO::JobFlag
*
* @see DropJobFlags
* @since 5.67
*/
enum DropJobFlag {
DropJobDefaultFlags = 0,
ShowMenuManually = 1, ///< show the menu manually with DropJob::showMenu
};
/**
* Stores a combination of #DropJobFlag values.
*/
Q_DECLARE_FLAGS(DropJobFlags, DropJobFlag)
Q_DECLARE_OPERATORS_FOR_FLAGS(DropJobFlags)
class CopyJob;
class DropJobPrivate;
/**
* @class KIO::DropJob dropjob.h <KIO/DropJob>
*
* A KIO job that handles dropping into a file-manager-like view.
* @see KIO::drop
*
* The popupmenu that can appear on drop, can be customized with plugins,
* see KIO::DndPopupMenuPlugin.
*
* @since 5.6
*/
class KIOWIDGETS_EXPORT DropJob : public Job
{
Q_OBJECT
public:
~DropJob() override;
/**
* Allows the application to set additional actions in the drop popup menu.
* For instance, the application handling the desktop might want to add
* "set as wallpaper" if the dropped url is an image file.
* This can be called upfront, or for convenience, when popupMenuAboutToShow is emitted.
*/
void setApplicationActions(const QList<QAction *> &actions);
/**
* Allows the application to show the menu manually.
* DropJob instance has to be created with the KIO::ShowMenuManually flag
*
* @since 5.67
*/
void showMenu(const QPoint &p, QAction *atAction = nullptr);
Q_SIGNALS:
/**
* Signals that a file or directory was created.
*/
void itemCreated(const QUrl &url);
/**
* Emitted when a copy job was started as subjob after user selection.
*
* You can use @p job to monitor the progress of the copy/move/link operation. Note that a
* CopyJob isn't always started by DropJob. For instance dropping files onto an executable will
* simply launch the executable.
*
* @param job the job started for moving, copying or symlinking files
* @since 5.30
*/
void copyJobStarted(KIO::CopyJob *job);
/**
* Signals that the popup menu is about to be shown.
* Applications can use the information provided about the dropped URLs
* (e.g. the MIME type) to decide whether to call setApplicationActions.
* @param itemProps properties of the dropped items
*/
void popupMenuAboutToShow(const KFileItemListProperties &itemProps);
protected Q_SLOTS:
void slotResult(KJob *job) override;
protected:
KIOWIDGETS_NO_EXPORT explicit DropJob(DropJobPrivate &dd);
private:
Q_DECLARE_PRIVATE(DropJob)
};
/**
* Drops the clipboard contents.
*
* If the mime data contains URLs, a popup appears to choose between
* Move, Copy, Link and Cancel
* which is then implemented by the job, using KIO::move, KIO::copy or KIO::link
* Additional actions provided by the application or by plugins can be shown in the popup.
*
* If the mime data contains data other than URLs, it is saved into a file after asking
* the user to choose a filename and the preferred data format.
*
* This job takes care of recording the subjob in the FileUndoManager, and emits
* itemCreated for every file or directory being created, so that the view can select
* these items.
*
* @param dropEvent the drop event, from which the job will extract mimeData, dropAction, etc.
The application should take care of calling dropEvent->acceptProposedAction().
* @param destUrl The URL of the target file or directory
* @param flags passed to the sub job
*
* @return A pointer to the job handling the operation.
* @warning Don't forget to call KJobWidgets::setWindow() on this job, otherwise the popup
* menu won't be properly positioned with Wayland compositors.
* @since 5.4
*/
KIOWIDGETS_EXPORT DropJob *drop(const QDropEvent *dropEvent, const QUrl &destUrl, JobFlags flags = DefaultFlags);
/**
* Similar to KIO::drop
*
* @param dropEvent the drop event, from which the job will extract mimeData, dropAction, etc.
The application should take care of calling dropEvent->acceptProposedAction().
* @param destUrl The URL of the target file or directory
* @param dropjobFlags Show the menu immediately or manually.
* @param flags passed to the sub job
*
* @return A pointer to the job handling the operation.
* @warning Don't forget to call DropJob::showMenu on this job, otherwise the popup will never be shown
*
* @since 5.67
*/
KIOWIDGETS_EXPORT DropJob *drop(const QDropEvent *dropEvent,
const QUrl &destUrl,
DropJobFlags dropjobFlags,
JobFlags flags = DefaultFlags); // TODO KF6: merge with DropJobFlags dropjobFlag = DropJobDefaultFlags
}
#endif
@@ -0,0 +1,76 @@
/*
This file is part of the KDE libraries
SPDX-FileCopyrightText: 2014 Arjun A.K. <arjunak234@gmail.com>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#include "executablefileopendialog_p.h"
#include <QCheckBox>
#include <QDialogButtonBox>
#include <QLabel>
#include <QPushButton>
#include <QVBoxLayout>
#include <KLocalizedString>
ExecutableFileOpenDialog::ExecutableFileOpenDialog(ExecutableFileOpenDialog::Mode mode, QWidget *parent)
: QDialog(parent)
{
QLabel *label = new QLabel(i18n("What do you wish to do with this file?"), this);
m_dontAskAgain = new QCheckBox(this);
m_dontAskAgain->setText(i18n("Do not ask again"));
QDialogButtonBox *buttonBox = new QDialogButtonBox(QDialogButtonBox::Cancel, this);
connect(buttonBox, &QDialogButtonBox::rejected, this, &ExecutableFileOpenDialog::reject);
QVBoxLayout *layout = new QVBoxLayout(this);
layout->addWidget(label);
layout->addWidget(m_dontAskAgain);
layout->addWidget(buttonBox);
QPushButton *executeButton = new QPushButton(i18n("&Execute"), this);
executeButton->setIcon(QIcon::fromTheme(QStringLiteral("system-run")));
if (mode == OnlyExecute) {
connect(executeButton, &QPushButton::clicked, this, &ExecutableFileOpenDialog::executeFile);
} else if (mode == OpenAsExecute) {
connect(executeButton, &QPushButton::clicked, this, &ExecutableFileOpenDialog::openFile);
} else { // mode == OpenOrExecute
connect(executeButton, &QPushButton::clicked, this, &ExecutableFileOpenDialog::executeFile);
QPushButton *openButton = new QPushButton(i18n("&Open"), this);
openButton->setIcon(QIcon::fromTheme(QStringLiteral("document-preview")));
buttonBox->addButton(openButton, QDialogButtonBox::AcceptRole);
connect(openButton, &QPushButton::clicked, this, &ExecutableFileOpenDialog::openFile);
}
// Add Execute button last so that Open is first in the button box
buttonBox->addButton(executeButton, QDialogButtonBox::AcceptRole);
buttonBox->button(QDialogButtonBox::Cancel)->setFocus();
}
ExecutableFileOpenDialog::ExecutableFileOpenDialog(QWidget *parent)
: ExecutableFileOpenDialog(ExecutableFileOpenDialog::OpenOrExecute, parent)
{
}
bool ExecutableFileOpenDialog::isDontAskAgainChecked() const
{
return m_dontAskAgain->isChecked();
}
void ExecutableFileOpenDialog::executeFile()
{
done(ExecuteFile);
}
void ExecutableFileOpenDialog::openFile()
{
done(OpenFile);
}
#include "moc_executablefileopendialog_p.cpp"
@@ -0,0 +1,51 @@
/*
This file is part of the KDE libraries
SPDX-FileCopyrightText: 2014 Arjun A.K. <arjunak234@gmail.com>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#ifndef EXECUTABLEFILEOPENDIALOG_H
#define EXECUTABLEFILEOPENDIALOG_H
#include <QDialog>
class QCheckBox;
/**
* @brief Dialog shown when opening an executable file
*/
class ExecutableFileOpenDialog : public QDialog
{
Q_OBJECT
public:
enum ReturnCode {
OpenFile = 42,
ExecuteFile,
};
enum Mode {
// For executable scripts
OpenOrExecute,
// For native binary executables
OnlyExecute,
// For *.exe files, open with WINE is like execute the file
// In this case, openAsExecute is true, we hide "Open" button and connect
// "Execute" button to OpenFile action.
OpenAsExecute,
};
explicit ExecutableFileOpenDialog(Mode mode, QWidget *parent = nullptr);
explicit ExecutableFileOpenDialog(QWidget *parent = nullptr);
bool isDontAskAgainChecked() const;
private:
void executeFile();
void openFile();
QCheckBox *m_dontAskAgain;
};
#endif // EXECUTABLEFILEOPENDIALOG_H
@@ -0,0 +1,778 @@
/*
This file is part of the KDE project
SPDX-FileCopyrightText: 2000 Simon Hausmann <hausmann@kde.org>
SPDX-FileCopyrightText: 2006, 2008 David Faure <faure@kde.org>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#include "fileundomanager.h"
#include "askuseractioninterface.h"
#include "clipboardupdater_p.h"
#ifdef WITH_QTDBUS
#include "fileundomanager_adaptor.h"
#endif
#include "fileundomanager_p.h"
#include "kio_widgets_debug.h"
#include <job_p.h>
#include <kdirnotify.h>
#include <kio/batchrenamejob.h>
#include <kio/copyjob.h>
#include <kio/filecopyjob.h>
#include <kio/jobuidelegate.h>
#include <kio/mkdirjob.h>
#include <kio/mkpathjob.h>
#include <kio/statjob.h>
#include <KJobTrackerInterface>
#include <KJobWidgets>
#include <KLocalizedString>
#include <KMessageBox>
#ifdef WITH_QTDBUS
#include <QDBusConnection>
#endif
#include <QDateTime>
#include <QFileInfo>
#include <QLocale>
using namespace KIO;
static const char *undoStateToString(UndoState state)
{
static const char *const s_undoStateToString[] = {"MAKINGDIRS", "MOVINGFILES", "STATINGFILE", "REMOVINGDIRS", "REMOVINGLINKS"};
return s_undoStateToString[state];
}
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<BasicOperation::Type>(type);
op.m_mtime = QDateTime::fromSecsSinceEpoch(mtime, QTimeZone::UTC);
return stream;
}
static QDataStream &operator<<(QDataStream &stream, const UndoCommand &cmd)
{
stream << cmd.m_valid << (qint8)cmd.m_type << cmd.m_opQueue << cmd.m_src << cmd.m_dst;
return stream;
}
static QDataStream &operator>>(QDataStream &stream, UndoCommand &cmd)
{
qint8 type;
stream >> cmd.m_valid >> type >> cmd.m_opQueue >> cmd.m_src >> cmd.m_dst;
cmd.m_type = static_cast<FileUndoManager::CommandType>(type);
return stream;
}
QDebug operator<<(QDebug dbg, const BasicOperation &op)
{
if (op.m_valid) {
static const char *s_types[] = {"File", "Link", "Directory"};
dbg << "BasicOperation: type" << s_types[op.m_type] << "src" << op.m_src << "dest" << op.m_dst << "target" << op.m_target << "renamed" << op.m_renamed;
} else {
dbg << "Invalid BasicOperation";
}
return dbg;
}
/**
* 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_title = i18n("Undo Changes");
d_ptr->m_message = i18n("Undoing this operation requires root privileges. Do you want to continue?");
}
~UndoJob() override = default;
virtual void kill(bool) // TODO should be doKill
{
FileUndoManager::self()->d->stopUndo(true);
KIO::Job::doKill();
}
void emitCreatingDir(const QUrl &dir)
{
Q_EMIT description(this, i18n("Creating directory"), qMakePair(i18n("Directory"), dir.toDisplayString()));
}
void emitMovingOrRenaming(const QUrl &src, const QUrl &dest, FileUndoManager::CommandType cmdType)
{
static const QString srcMsg(i18nc("The source of a file operation", "Source"));
static const QString destMsg(i18nc("The destination of a file operation", "Destination"));
Q_EMIT description(this, //
cmdType == FileUndoManager::Move ? i18n("Moving") : i18n("Renaming"),
{srcMsg, src.toDisplayString()},
{destMsg, dest.toDisplayString()});
}
void emitDeleting(const QUrl &url)
{
Q_EMIT description(this, i18n("Deleting"), qMakePair(i18n("File"), url.toDisplayString()));
}
void emitResult()
{
KIO::Job::emitResult();
}
};
CommandRecorder::CommandRecorder(FileUndoManager::CommandType op, const QList<QUrl> &src, const QUrl &dst, KIO::Job *job)
: QObject(job)
, m_cmd(op, src, dst, FileUndoManager::self()->newCommandSerialNumber())
{
connect(job, &KJob::result, this, &CommandRecorder::slotResult);
if (auto *copyJob = qobject_cast<KIO::CopyJob *>(job)) {
connect(copyJob, &KIO::CopyJob::copyingDone, this, &CommandRecorder::slotCopyingDone);
connect(copyJob, &KIO::CopyJob::copyingLinkDone, this, &CommandRecorder::slotCopyingLinkDone);
} else if (auto *mkpathJob = qobject_cast<KIO::MkpathJob *>(job)) {
connect(mkpathJob, &KIO::MkpathJob::directoryCreated, this, &CommandRecorder::slotDirectoryCreated);
} else if (auto *batchRenameJob = qobject_cast<KIO::BatchRenameJob *>(job)) {
connect(batchRenameJob, &KIO::BatchRenameJob::fileRenamed, this, &CommandRecorder::slotBatchRenamingDone);
}
}
void CommandRecorder::slotResult(KJob *job)
{
const int err = job->error();
if (err) {
if (err != KIO::ERR_USER_CANCELED) {
qCDebug(KIO_WIDGETS) << "CommandRecorder::slotResult:" << job->errorString() << " - no undo command will be added";
}
return;
}
// For CopyJob, don't add an undo command unless the job actually did something,
// e.g. if user selected to skip all, there is nothing to undo.
// Note: this doesn't apply to other job types, e.g. for Mkdir m_opQueue is
// expected to be empty
if (qobject_cast<KIO::CopyJob *>(job)) {
if (!m_cmd.m_opQueue.isEmpty()) {
FileUndoManager::self()->d->addCommand(m_cmd);
}
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)
{
const BasicOperation::Type type = directory ? BasicOperation::Directory : BasicOperation::File;
m_cmd.m_opQueue.enqueue(BasicOperation(type, renamed, from, to, mtime));
}
void CommandRecorder::slotCopyingLinkDone(KIO::Job *, const QUrl &from, const QString &target, const QUrl &to)
{
m_cmd.m_opQueue.enqueue(BasicOperation(BasicOperation::Link, false, from, to, {}, target));
}
void CommandRecorder::slotDirectoryCreated(const QUrl &dir)
{
m_cmd.m_opQueue.enqueue(BasicOperation(BasicOperation::Directory, false, QUrl{}, dir, {}));
}
void CommandRecorder::slotBatchRenamingDone(const QUrl &from, const QUrl &to)
{
m_cmd.m_opQueue.enqueue(BasicOperation(BasicOperation::Item, true, from, to, {}));
}
////
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_nextCommandIndex(1000)
, q(qq)
{
#ifdef WITH_QTDBUS
(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()));
#endif
}
FileUndoManager::FileUndoManager()
: d(new FileUndoManagerPrivate(this))
{
}
FileUndoManager::~FileUndoManager() = default;
void FileUndoManager::recordJob(CommandType op, const QList<QUrl> &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);
Q_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:
commandType = Link;
break;
default:
Q_UNREACHABLE();
}
recordJob(commandType, copyJob->srcUrls(), copyJob->destUrl(), copyJob);
}
void FileUndoManagerPrivate::addCommand(const UndoCommand &cmd)
{
pushCommand(cmd);
Q_EMIT q->jobRecordingFinished(cmd.m_type);
}
bool FileUndoManager::isUndoAvailable() const
{
return !d->m_commands.isEmpty() && !d->m_lock;
}
QString FileUndoManager::undoText() const
{
if (d->m_commands.isEmpty()) {
return i18n("Und&o");
}
FileUndoManager::CommandType t = d->m_commands.top().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.top();
Q_ASSERT(cmd.m_valid);
return cmd.m_serialNumber;
}
return 0;
}
void FileUndoManager::undo()
{
Q_ASSERT(!d->m_commands.isEmpty()); // forgot to record before calling undo?
// Make a copy of the command to undo before slotPop() pops it.
UndoCommand cmd = d->m_commands.last();
Q_ASSERT(cmd.m_valid);
d->m_currentCmd = cmd;
const CommandType commandType = cmd.m_type;
// Note that m_opQueue is empty for simple operations like Mkdir.
const auto &opQueue = d->m_currentCmd.m_opQueue;
// Let's first ask for confirmation if we need to delete any file (#99898)
QList<QUrl> itemsToDelete;
for (auto it = opQueue.crbegin(); it != opQueue.crend(); ++it) {
const BasicOperation &op = *it;
const auto destination = op.m_dst;
if (op.m_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_currentCmd.m_dst);
}
if (!itemsToDelete.isEmpty()) {
AskUserActionInterface *askUserInterface = nullptr;
d->m_uiInterface->virtual_hook(UiInterface::HookGetAskUserActionInterface, &askUserInterface);
if (askUserInterface) {
if (!d->m_connectedToAskUserInterface) {
d->m_connectedToAskUserInterface = true;
QObject::connect(askUserInterface, &KIO::AskUserActionInterface::askUserDeleteResult, this, [this](bool allowDelete) {
if (allowDelete) {
d->startUndo();
}
});
}
// Because undo can happen with an accidental Ctrl-Z, we want to always confirm.
askUserInterface->askUserDelete(itemsToDelete,
KIO::AskUserActionInterface::Delete,
KIO::AskUserActionInterface::ForceConfirmation,
d->m_uiInterface->parentWidget());
return;
}
}
d->startUndo();
}
void FileUndoManagerPrivate::startUndo()
{
slotPop();
slotLock();
m_dirCleanupStack.clear();
m_dirStack.clear();
m_dirsToUpdate.clear();
m_undoState = MOVINGFILES;
// Let's have a look at the basic operations we need to undo.
auto &opQueue = m_currentCmd.m_opQueue;
for (auto it = opQueue.rbegin(); it != opQueue.rend(); ++it) {
const 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
m_undoState = MAKINGDIRS;
// Collect all the dirs that have to be created in case of a move undo.
if (m_currentCmd.isMoveOrRename()) {
m_dirStack.push((*it).m_src);
}
// Collect all dirs that have to be deleted
// from the destination in both cases (copy and move).
m_dirCleanupStack.prepend((*it).m_dst);
} else if (type == BasicOperation::Link) {
m_fileCleanupStack.prepend((*it).m_dst);
}
}
auto isBasicOperation = [this](const BasicOperation &op) {
return (op.m_type == BasicOperation::Directory && !op.m_renamed) //
|| (op.m_type == BasicOperation::Link && !m_currentCmd.isMoveOrRename());
};
opQueue.erase(std::remove_if(opQueue.begin(), opQueue.end(), isBasicOperation), opQueue.end());
const FileUndoManager::CommandType commandType = m_currentCmd.m_type;
if (commandType == FileUndoManager::Put) {
m_fileCleanupStack.append(m_currentCmd.m_dst);
}
qCDebug(KIO_WIDGETS) << "starting with" << undoStateToString(m_undoState);
m_undoJob = new UndoJob(m_uiInterface->showProgressInfo());
auto undoFunc = [this]() {
undoStep();
};
QMetaObject::invokeMethod(this, undoFunc, Qt::QueuedConnection);
}
void FileUndoManagerPrivate::stopUndo(bool step)
{
m_currentCmd.m_opQueue.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()) {
qWarning() << job->errorString();
m_uiInterface->jobError(static_cast<KIO::Job *>(job));
delete m_undoJob;
stopUndo(false);
} else if (m_undoState == STATINGFILE) {
const BasicOperation op = m_currentCmd.m_opQueue.head();
// qDebug() << "stat result for " << op.m_dst;
KIO::StatJob *statJob = static_cast<KIO::StatJob *>(job);
const QDateTime mtime = QDateTime::fromSecsSinceEpoch(statJob->statResult().numberValue(KIO::UDSEntry::UDS_MODIFICATION_TIME, -1), QTimeZone::UTC);
if (mtime != op.m_mtime) {
qCDebug(KIO_WIDGETS) << op.m_dst << "was modified after being copied. Initial timestamp" << mtime << "now" << op.m_mtime;
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, &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_currentCmd.m_opQueue.isEmpty()) {
m_undoState = REMOVINGLINKS;
return;
}
const BasicOperation op = m_currentCmd.m_opQueue.head();
Q_ASSERT(op.m_valid);
if (op.m_type == BasicOperation::Directory || op.m_type == BasicOperation::Item) {
Q_ASSERT(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->emitMovingOrRenaming(op.m_dst, op.m_src, m_currentCmd.m_type);
} else if (op.m_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_currentCmd.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_currentCmd.isMoveOrRename() || m_currentCmd.m_type == FileUndoManager::Trash) {
m_currentJob = KIO::file_move(op.m_dst, op.m_src, -1, KIO::HideProgressInfo);
m_currentJob->uiDelegateExtension()->createClipboardUpdater(m_currentJob, JobUiDelegateExtension::UpdateContent);
m_undoJob->emitMovingOrRenaming(op.m_dst, op.m_src, m_currentCmd.m_type);
}
if (m_currentJob) {
m_currentJob->setParentJob(m_undoJob);
}
m_currentCmd.m_opQueue.dequeue();
// 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);
}
void FileUndoManagerPrivate::stepRemovingLinks()
{
// qDebug() << "REMOVINGLINKS";
if (!m_fileCleanupStack.isEmpty()) {
const 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);
const QUrl url = file.adjusted(QUrl::RemoveFilename | QUrl::StripTrailingSlash);
addDirToUpdate(url);
} else {
m_undoState = REMOVINGDIRS;
if (m_dirCleanupStack.isEmpty() && m_currentCmd.m_type == FileUndoManager::Mkdir) {
m_dirCleanupStack << m_currentCmd.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_currentCmd.m_valid = false;
m_currentJob = nullptr;
if (m_undoJob) {
// qDebug() << "deleting undojob";
m_undoJob->emitResult();
m_undoJob = nullptr;
}
#ifdef WITH_QTDBUS
for (const QUrl &url : std::as_const(m_dirsToUpdate)) {
// qDebug() << "Notifying FilesAdded for " << url;
org::kde::KDirNotify::emitFilesAdded(url);
}
#endif
Q_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.push(cmd);
Q_EMIT q->undoAvailable(true);
Q_EMIT q->undoTextChanged(q->undoText());
}
void FileUndoManagerPrivate::slotPop()
{
m_commands.pop();
Q_EMIT q->undoAvailable(q->isUndoAvailable());
Q_EMIT q->undoTextChanged(q->undoText());
}
void FileUndoManagerPrivate::slotLock()
{
// Q_ASSERT(!m_lock);
m_lock = true;
Q_EMIT q->undoAvailable(q->isUndoAvailable());
}
void FileUndoManagerPrivate::slotUnlock()
{
// Q_ASSERT(m_lock);
m_lock = false;
Q_EMIT q->undoAvailable(q->isUndoAvailable());
}
QByteArray FileUndoManagerPrivate::get() const
{
QByteArray data;
QDataStream stream(&data, QIODevice::WriteOnly);
stream << m_commands;
return data;
}
void FileUndoManager::setUiInterface(UiInterface *ui)
{
d->m_uiInterface.reset(ui);
}
FileUndoManager::UiInterface *FileUndoManager::uiInterface() const
{
return d->m_uiInterface.get();
}
////
class Q_DECL_HIDDEN FileUndoManager::UiInterface::UiInterfacePrivate
{
public:
QPointer<QWidget> m_parentWidget;
bool m_showProgressInfo = true;
};
FileUndoManager::UiInterface::UiInterface()
: d(new UiInterfacePrivate)
{
}
FileUndoManager::UiInterface::~UiInterface() = default;
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 = QLocale().toString(destTime, QLocale::ShortFormat);
const QString msg = 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));
const auto result = KMessageBox::warningContinueCancel(d->m_parentWidget,
msg,
i18n("Undo File Copy Confirmation"),
KStandardGuiItem::cont(),
KStandardGuiItem::cancel(),
QString(),
KMessageBox::Options(KMessageBox::Notify) | KMessageBox::Dangerous);
return result == KMessageBox::Continue;
}
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 id, void *data)
{
if (id == HookGetAskUserActionInterface) {
auto *p = static_cast<AskUserActionInterface **>(data);
static KJobUiDelegate *delegate = KIO::createDefaultJobUiDelegate();
static auto *askUserInterface = delegate ? delegate->findChild<AskUserActionInterface *>(QString(), Qt::FindDirectChildrenOnly) : nullptr;
*p = askUserInterface;
}
}
#include "fileundomanager.moc"
#include "moc_fileundomanager.cpp"
#include "moc_fileundomanager_p.cpp"
@@ -0,0 +1,221 @@
/*
This file is part of the KDE project
SPDX-FileCopyrightText: 2000 Simon Hausmann <hausmann@kde.org>
SPDX-FileCopyrightText: 2006, 2008 David Faure <faure@kde.org>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#ifndef KIO_FILEUNDOMANAGER_H
#define KIO_FILEUNDOMANAGER_H
#include <QObject>
#include <QUrl>
#include "kiowidgets_export.h"
#include <memory>
class QDateTime;
namespace KIO
{
class Job;
class CopyJob;
class FileUndoManagerPrivate;
class FileUndoManagerSingleton;
class CommandRecorder;
class UndoCommand;
class UndoJob;
/**
* @class KIO::FileUndoManager fileundomanager.h <KIO/FileUndoManager>
*
* FileUndoManager: makes it possible to undo kio jobs.
* This class is a singleton, use self() to access its only instance.
*/
class KIOWIDGETS_EXPORT FileUndoManager : public QObject
{
Q_OBJECT
public:
/**
* @return the FileUndoManager instance
*/
static FileUndoManager *self();
/**
* Interface for the gui handling of FileUndoManager.
* This includes three events currently:
* - error when undoing a job
* - (until KF 5.78) confirm deletion before undoing a copy job
* - confirm deletion when the copied file has been modified afterwards
*
* By default UiInterface shows message boxes in all three cases;
* applications can reimplement this interface to provide different user interfaces.
*/
class KIOWIDGETS_EXPORT UiInterface
{
public:
UiInterface();
virtual ~UiInterface();
/**
* Sets whether to show progress info when running the KIO jobs for undoing.
*/
void setShowProgressInfo(bool b);
/**
* @returns whether progress info dialogs are shown while undoing.
*/
bool showProgressInfo() const;
/**
* Sets the parent widget to use for message boxes.
*/
void setParentWidget(QWidget *parentWidget);
/**
* @return the parent widget passed to the last call to undo(parentWidget), or @c nullptr.
*/
QWidget *parentWidget() const;
/**
* Called when an undo job errors; default implementation displays a message box.
*/
virtual void jobError(KIO::Job *job);
/**
* Called when dest was modified since it was copied from src.
* Note that this is called after confirmDeletion.
* Return true if we should proceed with deleting dest.
*/
virtual bool copiedFileWasModified(const QUrl &src, const QUrl &dest, const QDateTime &srcTime, const QDateTime &destTime);
// TODO KF6 replace hook with virtual AskUserActionInterface* askUserActionInterface(); // (does not take ownership)
enum {
HookGetAskUserActionInterface = 1
};
/**
* \internal, for future extensions
*/
virtual void virtual_hook(int id, void *data);
private:
class UiInterfacePrivate;
UiInterfacePrivate *d;
};
/**
* Set a new UiInterface implementation.
* This deletes the previous one.
* @param ui the UiInterface instance, which becomes owned by the undo manager.
*/
void setUiInterface(UiInterface *ui);
/**
* @return the UiInterface instance passed to setUiInterface.
* This is useful for calling setParentWidget on it. Never delete it!
*/
UiInterface *uiInterface() const;
/**
* The type of job.
*/
enum CommandType {
Copy,
Move,
Rename,
Link,
Mkdir,
Trash,
Put, ///< Represents the creation of a file from data in memory. Used when pasting data from clipboard or drag-n-drop. @since 4.7
Mkpath, ///< Represents a KIO::mkpath() job. @since 5.4
BatchRename ///< Represents a KIO::batchRename() job. Used when renaming multiple files. @since 5.42
};
/**
* Record this job while it's happening and add a command for it so that the user can undo it.
* The signal jobRecordingStarted() is emitted.
* @param op the type of job - which is also the type of command that will be created for it
* @param src list of source urls. This is empty for Mkdir, Mkpath, Put operations.
* @param dst destination url
* @param job the job to record
*/
void recordJob(CommandType op, const QList<QUrl> &src, const QUrl &dst, KIO::Job *job);
/**
* Record this CopyJob while it's happening and add a command for it so that the user can undo it.
* The signal jobRecordingStarted() is emitted.
*/
void recordCopyJob(KIO::CopyJob *copyJob);
/**
* @return true if undo is possible. Usually used for enabling/disabling the undo action.
*
* @since 5.79
*/
bool isUndoAvailable() const;
/**
* @return the current text for the undo action.
*/
QString undoText() const;
/**
* These two functions are useful when wrapping FileUndoManager and adding custom commands.
* Each command has a unique ID. You can get a new serial number for a custom command
* with newCommandSerialNumber(), and then when you want to undo, check if the command
* FileUndoManager would undo is newer or older than your custom command.
*/
quint64 newCommandSerialNumber();
quint64 currentCommandSerialNumber() const;
public Q_SLOTS:
/**
* Undoes the last command
* Remember to call uiInterface()->setParentWidget(parentWidget) first,
* if you have multiple mainwindows.
*
* This operation is asynchronous.
* undoJobFinished will be emitted once the undo is complete.
*/
void undo(); // TODO pass QWindow*, for askUserInterface->askUserDelete and error handling etc.
Q_SIGNALS:
/// Emitted when the value of isUndoAvailable() changes
void undoAvailable(bool avail);
/// Emitted when the value of undoText() changes
void undoTextChanged(const QString &text);
/// Emitted when an undo job finishes. Used for unit testing.
void undoJobFinished();
/**
* Emitted when a job recording has been started by FileUndoManager::recordJob()
* or FileUndoManager::recordCopyJob(). After the job recording has been finished,
* the signal jobRecordingFinished() will be emitted.
*/
void jobRecordingStarted(CommandType op);
/**
* Emitted when a job that has been recorded by FileUndoManager::recordJob()
* or FileUndoManager::recordCopyJob has been finished. The command
* is now available for an undo-operation.
*/
void jobRecordingFinished(FileUndoManager::CommandType op);
private:
KIOWIDGETS_NO_EXPORT FileUndoManager();
KIOWIDGETS_NO_EXPORT ~FileUndoManager() override;
friend class FileUndoManagerSingleton;
friend class UndoJob;
friend class CommandRecorder;
friend class FileUndoManagerPrivate;
std::unique_ptr<FileUndoManagerPrivate> d;
};
} // namespace
#endif
@@ -0,0 +1,191 @@
/*
This file is part of the KDE project
SPDX-FileCopyrightText: 2000 Simon Hausmann <hausmann@kde.org>
SPDX-FileCopyrightText: 2006, 2008 David Faure <faure@kde.org>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#ifndef FILEUNDOMANAGER_P_H
#define FILEUNDOMANAGER_P_H
#include "fileundomanager.h"
#include <QDateTime>
#include <QQueue>
#include <QStack>
class KJob;
namespace KIO
{
class FileUndoManagerAdaptor;
struct BasicOperation {
enum Type {
File,
Link,
Directory,
/**
* Used with BatchRenameJob, it doesn't stat the files/dirs it's renaming,
* so the file/dir distinction isn't available
*/
Item,
};
// for QDataStream deserialization
BasicOperation()
{
}
BasicOperation(Type type, bool renamed, const QUrl &src, const QUrl &dst, const QDateTime &mtime, const QString &target = {})
: m_valid(true)
, m_renamed(renamed)
, m_type(type)
, m_src(src)
, m_dst(dst)
, m_target(target)
, m_mtime(mtime)
{
}
bool m_valid = false;
bool m_renamed;
Type m_type : 2;
QUrl m_src;
QUrl m_dst;
QString m_target;
QDateTime m_mtime;
};
class UndoCommand
{
public:
UndoCommand() = default;
UndoCommand(FileUndoManager::CommandType type, const QList<QUrl> &src, const QUrl &dst, qint64 serialNumber)
: m_valid(true)
, m_type(type)
, m_src(src)
, m_dst(dst)
, m_serialNumber(serialNumber)
{
}
// TODO: is ::TRASH missing?
bool isMoveOrRename() const
{
return m_type == FileUndoManager::Move || m_type == FileUndoManager::Rename;
}
bool m_valid = false;
FileUndoManager::CommandType m_type;
QQueue<BasicOperation> m_opQueue;
QList<QUrl> m_src;
QUrl m_dst;
quint64 m_serialNumber = 0;
};
// This class listens to a job, collects info while it's running (for copyjobs)
// and when the job terminates, on success, it calls addCommand in the undomanager.
class CommandRecorder : public QObject
{
Q_OBJECT
public:
CommandRecorder(FileUndoManager::CommandType op, const QList<QUrl> &src, const QUrl &dst, KIO::Job *job);
private Q_SLOTS:
void slotResult(KJob *job);
void slotCopyingDone(KIO::Job *, const QUrl &from, const QUrl &to, const QDateTime &, bool directory, bool renamed);
void slotCopyingLinkDone(KIO::Job *, const QUrl &from, const QString &target, const QUrl &to);
void slotDirectoryCreated(const QUrl &url);
void slotBatchRenamingDone(const QUrl &from, const QUrl &to);
private:
UndoCommand m_cmd;
};
enum UndoState {
MAKINGDIRS = 0,
MOVINGFILES,
STATINGFILE,
REMOVINGDIRS,
REMOVINGLINKS,
};
// The private class is, exceptionally, a real QObject
// so that it can be the class with the DBUS adaptor forwarding its signals.
class FileUndoManagerPrivate : public QObject
{
Q_OBJECT
public:
explicit FileUndoManagerPrivate(FileUndoManager *qq);
~FileUndoManagerPrivate() override = default;
void pushCommand(const UndoCommand &cmd);
void addDirToUpdate(const QUrl &url);
void startUndo();
void stepMakingDirectories();
void stepMovingFiles();
void stepRemovingLinks();
void stepRemovingDirectories();
/// called by FileUndoManagerAdaptor
QByteArray get() const;
friend class UndoJob;
/// called by UndoJob
void stopUndo(bool step);
friend class UndoCommandRecorder;
/// called by UndoCommandRecorder
void addCommand(const UndoCommand &cmd);
QStack<UndoCommand> m_commands;
KIO::Job *m_currentJob = nullptr;
QStack<QUrl> m_dirStack;
QStack<QUrl> m_dirCleanupStack;
QStack<QUrl> m_fileCleanupStack; // files and links
QList<QUrl> m_dirsToUpdate;
std::unique_ptr<FileUndoManager::UiInterface> m_uiInterface;
UndoJob *m_undoJob = nullptr;
quint64 m_nextCommandIndex = 0;
FileUndoManager *const q;
UndoCommand m_currentCmd;
UndoState m_undoState;
bool m_lock = false;
bool m_connectedToAskUserInterface = false;
// DBUS interface
Q_SIGNALS:
/// DBUS signal
void push(const QByteArray &command);
/// DBUS signal
void pop();
/// DBUS signal
void lock();
/// DBUS signal
void unlock();
public Q_SLOTS:
// Those four slots are connected to DBUS signals
void slotPush(QByteArray);
void slotPop();
void slotLock();
void slotUnlock();
void undoStep();
void slotResult(KJob *);
};
} // namespace
#endif /* FILEUNDOMANAGER_P_H */
@@ -0,0 +1,220 @@
// krazy:excludeall=copyright (email of Maxim is missing)
/*
This file is a part of the KDE project
SPDX-FileCopyrightText: 2006 Zack Rusin <zack@kde.org>
SPDX-FileCopyrightText: 2006-2007, 2008 Fredrik Höglund <fredrik@kde.org>
The stack blur algorithm was invented by Mario Klingemann <mario@quasimondo.com>
This implementation is based on the version in Anti-Grain Geometry Version 2.4,
SPDX-FileCopyrightText: 2002-2005 Maxim Shemanarev <http://www.antigrain.com>
SPDX-License-Identifier: BSD-2-Clause
*/
#include "imagefilter_p.h"
#include <QColor>
#include <QImage>
#include <QPainter>
#include <cmath>
#include <string.h>
using namespace KIO;
static const quint32 stack_blur8_mul[255] = {
512, 512, 456, 512, 328, 456, 335, 512, 405, 328, 271, 456, 388, 335, 292, 512, 454, 405, 364, 328, 298, 271, 496, 456, 420, 388, 360, 335, 312,
292, 273, 512, 482, 454, 428, 405, 383, 364, 345, 328, 312, 298, 284, 271, 259, 496, 475, 456, 437, 420, 404, 388, 374, 360, 347, 335, 323, 312,
302, 292, 282, 273, 265, 512, 497, 482, 468, 454, 441, 428, 417, 405, 394, 383, 373, 364, 354, 345, 337, 328, 320, 312, 305, 298, 291, 284, 278,
271, 265, 259, 507, 496, 485, 475, 465, 456, 446, 437, 428, 420, 412, 404, 396, 388, 381, 374, 367, 360, 354, 347, 341, 335, 329, 323, 318, 312,
307, 302, 297, 292, 287, 282, 278, 273, 269, 265, 261, 512, 505, 497, 489, 482, 475, 468, 461, 454, 447, 441, 435, 428, 422, 417, 411, 405, 399,
394, 389, 383, 378, 373, 368, 364, 359, 354, 350, 345, 341, 337, 332, 328, 324, 320, 316, 312, 309, 305, 301, 298, 294, 291, 287, 284, 281, 278,
274, 271, 268, 265, 262, 259, 257, 507, 501, 496, 491, 485, 480, 475, 470, 465, 460, 456, 451, 446, 442, 437, 433, 428, 424, 420, 416, 412, 408,
404, 400, 396, 392, 388, 385, 381, 377, 374, 370, 367, 363, 360, 357, 354, 350, 347, 344, 341, 338, 335, 332, 329, 326, 323, 320, 318, 315, 312,
310, 307, 304, 302, 299, 297, 294, 292, 289, 287, 285, 282, 280, 278, 275, 273, 271, 269, 267, 265, 263, 261, 259};
static const quint32 stack_blur8_shr[255] = {
9, 11, 12, 13, 13, 14, 14, 15, 15, 15, 15, 16, 16, 16, 16, 17, 17, 17, 17, 17, 17, 17, 18, 18, 18, 18, 18, 18, 18, 18, 18, 19, 19, 19, 19, 19, 19,
19, 19, 19, 19, 19, 19, 19, 19, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21,
21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22,
22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23,
23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 24, 24, 24, 24,
24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24,
24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24};
inline static void blurHorizontal(QImage &image, unsigned int *stack, int div, int radius)
{
quint32 *const pixels = reinterpret_cast<quint32 *>(image.bits());
quint32 pixel = 0;
const int width = image.width();
const int height = image.height();
const int wm = width - 1;
const unsigned int mul_sum = stack_blur8_mul[radius];
const unsigned int shr_sum = stack_blur8_shr[radius];
for (int y = 0; y < height; y++) {
unsigned int sum = 0;
unsigned int sum_in = 0;
unsigned int sum_out = 0;
const int yw = y * width;
pixel = pixels[yw];
for (int i = 0; i <= radius; i++) {
stack[i] = qAlpha(pixel);
sum += stack[i] * (i + 1);
sum_out += stack[i];
}
for (int i = 1; i <= radius; i++) {
pixel = pixels[yw + qMin(i, wm)];
unsigned int *stackpix = &stack[i + radius];
*stackpix = qAlpha(pixel);
sum += *stackpix * (radius + 1 - i);
sum_in += *stackpix;
}
int stackindex = radius;
for (int x = 0, i = yw; x < width; x++) {
pixels[i++] = (((sum * mul_sum) >> shr_sum) << 24) & 0xff000000;
sum -= sum_out;
int stackstart = stackindex + div - radius;
if (stackstart >= div) {
stackstart -= div;
}
unsigned int *stackpix = &stack[stackstart];
sum_out -= *stackpix;
pixel = pixels[yw + qMin(x + radius + 1, wm)];
*stackpix = qAlpha(pixel);
sum_in += *stackpix;
sum += sum_in;
if (++stackindex >= div) {
stackindex = 0;
}
stackpix = &stack[stackindex];
sum_out += *stackpix;
sum_in -= *stackpix;
} // for (x = 0, ...)
} // for (y = 0, ...)
}
inline static void blurVertical(QImage &image, unsigned int *stack, int div, int radius)
{
int stackindex;
int stackstart;
quint32 *const pixels = reinterpret_cast<quint32 *>(image.bits());
quint32 pixel;
int w = image.width();
int h = image.height();
int hm = h - 1;
int mul_sum = stack_blur8_mul[radius];
int shr_sum = stack_blur8_shr[radius];
unsigned int sum;
unsigned int sum_in;
unsigned int sum_out;
for (int x = 0; x < w; x++) {
sum = 0;
sum_in = 0;
sum_out = 0;
pixel = pixels[x];
for (int i = 0; i <= radius; i++) {
stack[i] = qAlpha(pixel);
sum += stack[i] * (i + 1);
sum_out += stack[i];
}
for (int i = 1; i <= radius; i++) {
pixel = pixels[qMin(i, hm) * w + x];
unsigned int *stackpix = &stack[i + radius];
*stackpix = qAlpha(pixel);
sum += *stackpix * (radius + 1 - i);
sum_in += *stackpix;
}
stackindex = radius;
for (int y = 0, i = x; y < h; y++, i += w) {
pixels[i] = (((sum * mul_sum) >> shr_sum) << 24) & 0xff000000;
sum -= sum_out;
stackstart = stackindex + div - radius;
if (stackstart >= div) {
stackstart -= div;
}
unsigned int *stackpix = &stack[stackstart];
sum_out -= *stackpix;
pixel = pixels[qMin(y + radius + 1, hm) * w + x];
*stackpix = qAlpha(pixel);
sum_in += *stackpix;
sum += sum_in;
if (++stackindex >= div) {
stackindex = 0;
}
stackpix = &stack[stackindex];
sum_out += *stackpix;
sum_in -= *stackpix;
} // for (y = 0, ...)
} // for (x = 0, ...)
}
static void stackBlur(QImage &image, float radius)
{
radius = qRound(radius);
int div = int(radius * 2) + 1;
unsigned int *stack = new unsigned int[div];
blurHorizontal(image, stack, div, radius);
blurVertical(image, stack, div, radius);
delete[] stack;
}
void ImageFilter::shadowBlur(QImage &image, float radius, const QColor &color)
{
if (radius < 0) {
return;
}
if (radius > 0) {
stackBlur(image, radius);
}
// Correct the color and opacity of the shadow
QPainter p(&image);
p.setCompositionMode(QPainter::CompositionMode_SourceIn);
p.fillRect(image.rect(), color);
}
@@ -0,0 +1,35 @@
// krazy:exclude=copyright (email of Maxim is missing)
/*
This file is a part of the KDE project
SPDX-FileCopyrightText: 2006 Zack Rusin <zack@kde.org>
SPDX-FileCopyrightText: 2006-2007, 2008 Fredrik Höglund <fredrik@kde.org>
The stack blur algorithm was invented by Mario Klingemann <mario@quasimondo.com>
This implementation is based on the version in Anti-Grain Geometry Version 2.4,
SPDX-FileCopyrightText: 2002-2005 Maxim Shemanarev <http://www.antigrain.com>
SPDX-License-Identifier: BSD-2-Clause
*/
#ifndef KIO_IMAGEFILTER_P_H
#define KIO_IMAGEFILTER_P_H
#include "kiowidgets_export.h"
class QImage;
class QColor;
namespace KIO
{
class KIOWIDGETS_EXPORT ImageFilter
{
public:
// Blurs the alpha channel of the image and recolors it to the specified color.
// The image must have transparent padding on all sides, or the shadow will be clipped.
static void shadowBlur(QImage &image, float radius, const QColor &color);
};
}
#endif
@@ -0,0 +1,365 @@
/*
This file is part of the KDE libraries
SPDX-FileCopyrightText: 2000 Stephan Kulow <coolo@kde.org>
SPDX-FileCopyrightText: 2000 David Faure <faure@kde.org>
SPDX-FileCopyrightText: 2006 Kevin Ottens <ervin@kde.org>
SPDX-FileCopyrightText: 2013 Dawit Alemayehu <adawit@kde.org>
SPDX-FileCopyrightText: 2022 Harald Sitter <sitter@kde.org>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#include "jobuidelegate.h"
#include "kio_widgets_debug.h"
#include "kiogui_export.h"
#include "widgetsaskuseractionhandler.h"
#include "widgetsopenorexecutefilehandler.h"
#include "widgetsopenwithhandler.h"
#include "widgetsuntrustedprogramhandler.h"
#include <kio/jobuidelegatefactory.h>
#include <KConfigGroup>
#include <KJob>
#include <KJobWidgets>
#include <KLocalizedString>
#include <KMessageBox>
#include <KSharedConfig>
#include <clipboardupdater_p.h>
#include <ksslinfodialog.h>
#ifdef WITH_QTDBUS
#include <QDBusInterface>
#endif
#include <QGuiApplication>
#include <QIcon>
#include <QPointer>
#include <QRegularExpression>
#include <QUrl>
#include <QWidget>
class KIO::JobUiDelegatePrivate
{
public:
JobUiDelegatePrivate(KIO::JobUiDelegate *qq, const QList<QObject *> &ifaces)
{
for (auto iface : ifaces) {
iface->setParent(qq);
if (auto obj = qobject_cast<UntrustedProgramHandlerInterface *>(iface)) {
m_untrustedProgramHandler = obj;
} else if (auto obj = qobject_cast<OpenWithHandlerInterface *>(iface)) {
m_openWithHandler = obj;
} else if (auto obj = qobject_cast<OpenOrExecuteFileInterface *>(iface)) {
m_openOrExecuteFileHandler = obj;
} else if (auto obj = qobject_cast<AskUserActionInterface *>(iface)) {
m_askUserActionHandler = obj;
}
}
if (!m_untrustedProgramHandler) {
m_untrustedProgramHandler = new WidgetsUntrustedProgramHandler(qq);
}
if (!m_openWithHandler) {
m_openWithHandler = new WidgetsOpenWithHandler(qq);
}
if (!m_openOrExecuteFileHandler) {
m_openOrExecuteFileHandler = new WidgetsOpenOrExecuteFileHandler(qq);
}
if (!m_askUserActionHandler) {
m_askUserActionHandler = new WidgetsAskUserActionHandler(qq);
}
}
UntrustedProgramHandlerInterface *m_untrustedProgramHandler = nullptr;
OpenWithHandlerInterface *m_openWithHandler = nullptr;
OpenOrExecuteFileInterface *m_openOrExecuteFileHandler = nullptr;
AskUserActionInterface *m_askUserActionHandler = nullptr;
};
KIO::JobUiDelegate::~JobUiDelegate() = default;
/*
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<QObject *>(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, &QObject::destroyed, this, &JobUiDelegateStatic::slotUnregisterWindow);
#ifdef WITH_QTDBUS
QDBusInterface(QStringLiteral("org.kde.kded6"), QStringLiteral("/kded"), QStringLiteral("org.kde.kded6"))
.call(QDBus::NoBlock, QStringLiteral("registerWindowId"), qlonglong(windowId));
#endif
}
}
public Q_SLOTS:
void slotUnregisterWindow(QObject *obj)
{
if (!obj) {
return;
}
QMap<QObject *, WId>::Iterator it = m_windowList.find(obj);
if (it == m_windowList.end()) {
return;
}
WId windowId = it.value();
disconnect(it.key(), &QObject::destroyed, this, &JobUiDelegateStatic::slotUnregisterWindow);
m_windowList.erase(it);
#ifdef WITH_QTDBUS
QDBusInterface(QStringLiteral("org.kde.kded6"), QStringLiteral("/kded"), QStringLiteral("org.kde.kded6"))
.call(QDBus::NoBlock, QStringLiteral("unregisterWindowId"), qlonglong(windowId));
#endif
}
private:
QMap<QObject *, WId> m_windowList;
};
Q_GLOBAL_STATIC(JobUiDelegateStatic, s_static)
void KIO::JobUiDelegate::setWindow(QWidget *window)
{
KDialogJobUiDelegate::setWindow(window);
if (auto obj = qobject_cast<WidgetsUntrustedProgramHandler *>(d->m_openWithHandler)) {
obj->setWindow(window);
}
if (auto obj = qobject_cast<WidgetsOpenWithHandler *>(d->m_untrustedProgramHandler)) {
obj->setWindow(window);
}
if (auto obj = qobject_cast<WidgetsOpenOrExecuteFileHandler *>(d->m_openOrExecuteFileHandler)) {
obj->setWindow(window);
}
if (auto obj = qobject_cast<WidgetsAskUserActionHandler *>(d->m_askUserActionHandler)) {
obj->setWindow(window);
}
s_static()->registerWindow(window);
}
void KIO::JobUiDelegate::unregisterWindow(QWidget *window)
{
s_static()->slotUnregisterWindow(window);
}
bool KIO::JobUiDelegate::askDeleteConfirmation(const QList<QUrl> &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(QStringLiteral("Confirmations")).readEntry(keyName, defaultValue);
}
if (ask) {
QStringList prettyList;
prettyList.reserve(urls.size());
for (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(QRegularExpression(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?<nl/><filename>%1</filename><nl/><nl/><emphasis strong='true'>This action "
"cannot be undone.</emphasis>",
prettyList.first()),
i18n("Delete Permanently"),
KGuiItem(i18nc("@action:button", "Delete Permanently"), QStringLiteral("edit-delete")),
KStandardGuiItem::cancel(),
keyName,
options);
} else {
result = KMessageBox::warningContinueCancelList(
widget,
xi18ncp(
"@info",
"Do you really want to permanently delete this item?<nl/><nl/><emphasis strong='true'>This action cannot be undone.</emphasis>",
"Do you really want to permanently delete these %1 items?<nl/><nl/><emphasis strong='true'>This action cannot be undone.</emphasis>",
prettyList.count()),
prettyList,
i18n("Delete Permanently"),
KGuiItem(i18nc("@action:button", "Delete Permanently"), QStringLiteral("edit-delete")),
KStandardGuiItem::cancel(),
keyName,
options);
}
break;
case EmptyTrash:
result = KMessageBox::warningContinueCancel(
widget,
xi18nc("@info",
"Do you want to permanently delete all items from the Trash?<nl/><nl/><emphasis strong='true'>This action cannot be undone.</emphasis>"),
i18n("Delete Permanently"),
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?<nl/><filename>%1</filename>", prettyList.first()),
i18n("Move to Trash"),
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, QStringLiteral("Notification Messages"));
if (!notificationGroup.readEntry(keyName, true)) {
notificationGroup.writeEntry(keyName, true);
notificationGroup.sync();
KSharedConfigPtr kioConfig = KSharedConfig::openConfig(QStringLiteral("kiorc"), KConfig::NoGlobals);
kioConfig->group(QStringLiteral("Confirmations")).writeEntry(keyName, false);
}
}
return (result == KMessageBox::Continue);
}
return true;
}
KIO::ClipboardUpdater *KIO::JobUiDelegate::createClipboardUpdater(Job *job, ClipboardUpdaterMode mode)
{
if (qobject_cast<QGuiApplication *>(qApp)) {
return new KIO::ClipboardUpdater(job, mode);
}
return nullptr;
}
void KIO::JobUiDelegate::updateUrlInClipboard(const QUrl &src, const QUrl &dest)
{
if (qobject_cast<QGuiApplication *>(qApp)) {
KIO::ClipboardUpdater::update(src, dest);
}
}
KIO::JobUiDelegate::JobUiDelegate(KJobUiDelegate::Flags flags, QWidget *window, const QList<QObject *> &ifaces)
: KDialogJobUiDelegate(flags, window)
, d(new JobUiDelegatePrivate(this, ifaces))
{
// TODO KF6: change the API to accept QWindows rather than QWidgets (this also carries through to the Interfaces)
if (window) {
s_static()->registerWindow(window);
setWindow(window);
}
}
class KIOWidgetJobUiDelegateFactory : public KIO::JobUiDelegateFactory
{
public:
using KIO::JobUiDelegateFactory::JobUiDelegateFactory;
KJobUiDelegate *createDelegate() const override
{
return new KIO::JobUiDelegate;
}
KJobUiDelegate *createDelegate(KJobUiDelegate::Flags flags, QWidget *window) const override
{
return new KIO::JobUiDelegate(flags, window);
}
static void registerJobUiDelegate()
{
static KIOWidgetJobUiDelegateFactory factory;
KIO::setDefaultJobUiDelegateFactory(&factory);
static KIO::JobUiDelegate delegate;
KIO::setDefaultJobUiDelegateExtension(&delegate);
}
};
// Simply linking to this library, creates a GUI job delegate and delegate extension for all KIO jobs
static void registerJobUiDelegate()
{
// Inside the factory class so it is a friend of the delegate and can construct it.
KIOWidgetJobUiDelegateFactory::registerJobUiDelegate();
}
Q_CONSTRUCTOR_FUNCTION(registerJobUiDelegate)
#include "jobuidelegate.moc"
#include "moc_jobuidelegate.cpp"
@@ -0,0 +1,111 @@
/*
This file is part of the KDE libraries
SPDX-FileCopyrightText: 2000 Stephan Kulow <coolo@kde.org>
SPDX-FileCopyrightText: 2000 David Faure <faure@kde.org>
SPDX-FileCopyrightText: 2006 Kevin Ottens <ervin@kde.org>
SPDX-FileCopyrightText: 2013 Dawit Alemayehu <adawit@kde.org>
SPDX-FileCopyrightText: 2022 Harald Sitter <sitter@kde.org>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#ifndef KIO_JOBUIDELEGATE_H
#define KIO_JOBUIDELEGATE_H
#include <KDialogJobUiDelegate>
#include <kio/askuseractioninterface.h>
#include <kio/global.h>
#include <kio/jobuidelegateextension.h>
#include <kio/renamedialog.h>
#include <kio/skipdialog.h>
class KJob;
class KDirOperator;
class KIOWidgetJobUiDelegateFactory;
namespace KIO
{
class JobUiDelegatePrivate;
class FileUndoManager;
class Job;
/**
* @class KIO::JobUiDelegate jobuidelegate.h <KIO/JobUiDelegate>
*
* A UI delegate tuned to be used with KIO Jobs.
*/
class KIOWIDGETS_EXPORT JobUiDelegate : public KDialogJobUiDelegate, public JobUiDelegateExtension
{
Q_OBJECT
// Allow the factory to construct. Everyone else needs to go through the factory or derive!
friend class ::KIOWidgetJobUiDelegateFactory;
// KIO internals don't need to derive either
friend class KIO::FileUndoManager;
protected:
friend class ::KDirOperator;
/**
* Constructs a new KIO Job UI delegate.
* @param flags allows to enable automatic error/warning handling
* @param window the window associated with this delegate, see setWindow.
* @param ifaces Interface instances such as OpenWithHandlerInterface to replace the default interfaces
* @since 5.98
*/
explicit JobUiDelegate(KJobUiDelegate::Flags flags = AutoHandlingDisabled, QWidget *window = nullptr, const QList<QObject *> &ifaces = {});
public:
/**
* Destroys the KIO Job UI delegate.
*/
~JobUiDelegate() override;
public:
/**
* Associate this job with a window given by @p window.
* @param window the window to associate to
* @see window()
*/
void setWindow(QWidget *window) override;
/**
* Unregister the given window from kded.
* This is normally done automatically when the window is destroyed.
*
* This method is useful for instance when keeping a hidden window
* around to make it faster to reuse later.
* @since 5.2
*/
static void unregisterWindow(QWidget *window);
/**
* Ask for confirmation before deleting/trashing @p urls.
*
* Note that this method is not called automatically by KIO jobs. It's the application's
* responsibility to ask the user for confirmation before calling KIO::del() or KIO::trash().
*
* @param urls the urls about to be deleted/trashed
* @param deletionType the type of deletion (Delete for real deletion, Trash otherwise)
* @param confirmation see ConfirmationType. Normally set to DefaultConfirmation.
* Note: the window passed to setWindow is used as the parent for the message box.
* @return true if confirmed
*/
bool askDeleteConfirmation(const QList<QUrl> &urls, DeletionType deletionType, ConfirmationType confirmationType) override;
/**
* Creates a clipboard updater
*/
ClipboardUpdater *createClipboardUpdater(Job *job, ClipboardUpdaterMode mode) override;
/**
* Update URL in clipboard, if present
*/
void updateUrlInClipboard(const QUrl &src, const QUrl &dest) override;
private:
std::unique_ptr<JobUiDelegatePrivate> const d;
};
}
#endif
@@ -0,0 +1,57 @@
/*
This file is part of the KDE libraries
SPDX-FileCopyrightText: 2009 Shaun Reich <shaun.reich@kdemail.net>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#include "joburlcache_p.h"
#ifdef WITH_QTDBUS
#include "kuiserver_interface.h"
#endif
class JobUrlCacheSingleton
{
public:
JobUrlCache instance;
};
Q_GLOBAL_STATIC(JobUrlCacheSingleton, s_jobUrlCache)
JobUrlCache &JobUrlCache::instance()
{
return s_jobUrlCache()->instance;
}
JobUrlCache::JobUrlCache()
: QObject(nullptr)
{
#ifdef WITH_QTDBUS
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, &OrgKdeKuiserverInterface::jobUrlsChanged, this, &JobUrlCache::slotJobUrlsChanged);
// force signal emission
interface->emitJobUrlsChanged();
#endif
}
JobUrlCache::~JobUrlCache()
{
}
void JobUrlCache::slotJobUrlsChanged(const QStringList &urlList)
{
m_destUrls = urlList;
Q_EMIT jobUrlsChanged(urlList);
}
void JobUrlCache::requestJobUrlsChanged()
{
Q_EMIT jobUrlsChanged(m_destUrls);
}
#include "moc_joburlcache_p.cpp"
@@ -0,0 +1,41 @@
/*
This file is part of the KDE libraries
SPDX-FileCopyrightText: 2009 Shaun Reich <shaun.reich@kdemail.net>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#ifndef JOBURLCACHE_H
#define JOBURLCACHE_H
#include <QObject>
#include <QStringList>
class JobUrlCache : public QObject
{
Q_OBJECT
public:
static JobUrlCache &instance();
void requestJobUrlsChanged();
Q_SIGNALS:
void jobUrlsChanged(const QStringList &);
private Q_SLOTS:
/**
* Connected to kuiserver's signal...
* @p urlList the dest url list
*/
void slotJobUrlsChanged(const QStringList &urlList);
private:
JobUrlCache();
~JobUrlCache() override;
QStringList m_destUrls;
friend class JobUrlCacheSingleton;
};
#endif // JOBURLCACHE_H
@@ -0,0 +1,19 @@
/*
This file is part of the KDE project
SPDX-FileCopyrightText: 2010 Sebastian Trueg <trueg@kde.org>
SPDX-License-Identifier: LGPL-2.0-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
*/
#include "kabstractfileitemactionplugin.h"
KAbstractFileItemActionPlugin::KAbstractFileItemActionPlugin(QObject *parent)
: QObject(parent)
{
}
KAbstractFileItemActionPlugin::~KAbstractFileItemActionPlugin()
{
}
#include "moc_kabstractfileitemactionplugin.cpp"
@@ -0,0 +1,105 @@
/*
This file is part of the KDE project
SPDX-FileCopyrightText: 2010 Sebastian Trueg <trueg@kde.org>
Based on konq_popupmenuplugin.h
SPDX-FileCopyrightText: 2008 David Faure <faure@kde.org>
SPDX-License-Identifier: LGPL-2.0-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
*/
#ifndef KABSTRACTFILEITEMACTION_PLUGIN_H
#define KABSTRACTFILEITEMACTION_PLUGIN_H
#include "kiowidgets_export.h"
#include <QObject>
class QAction;
class QMenu;
class QWidget;
class KFileItemListProperties;
/**
* @class KAbstractFileItemActionPlugin kabstractfileitemactionplugin.h <KAbstractFileItemActionPlugin>
*
* @brief Base class for KFileItemAction plugins.
*
* KFileItemAction plugins allow dynamic features to be added to the context
* menus for files and directories when browsing.
*
* Most filetype-based popup menu items can be implemented using servicemenus
* linked to MIME types, and that should be the preferred way of doing this.
* However, complex scenarios such as showing submenus with a variable number of
* actions or only showing an item if exactly two files are selected need to be
* implemented as a KFileItemAction plugin.
*
* To create such a plugin, subclass KAbstractFileItemActionPlugin and implement
* actions() to return the actions to want to add to the context menu. Then
* create a plugin in the usual KPluginFactory based way:
* \code
* K_PLUGIN_CLASS_WITH_JSON(MyActionPlugin, "myactionplugin.json")
* #include <thisfile.moc>
* \endcode
*
* A desktop file is necessary to register the plugin with the KDE plugin system:
*
* \code
* [Desktop Entry]
* Type=Service
* Name=My fancy action plugin
* X-KDE-Library=myactionplugin
* X-KDE-ServiceTypes=KFileItemAction/Plugin
* MimeType=some/mimetype;
* \endcode
*
* Note the \p KFileItemAction/Plugin service type which is used by
* KFileItemActions::addServicePluginActionsTo() to load all available plugins
* and the \p MimeType field which specifies for which types of file items
* the setup() method should be called.
*
* The desktop file contents must also be compiled into the plugin as JSON data.
* The following CMake code builds and installs the plugin:
* \code
* kcoreaddons_add_plugin(myactionplugin SOURCES myactionplugin.cpp INSTALL_NAMESPACE "kf5/kfileitemaction")
* kcoreaddons_desktop_to_json(myactionplugin myactionplugin.desktop) # generate the json file
*
* target_link_libraries(myactionplugin KF5::KIOWidgets)
* \endcode
*
* @note the plugin should be installed in the "kf5/kfileitemaction" subfolder of $QT_PLUGIN_PATH.
* @note If the plugin has a lower priority and should show up in the "Actions" submenu,
* you can set the X-KDE-Show-In-Submenu property to true.
*
* @author Sebastian Trueg <trueg@kde.org>
*
* @since 4.6.1
*/
class KIOWIDGETS_EXPORT KAbstractFileItemActionPlugin : public QObject
{
Q_OBJECT
public:
explicit KAbstractFileItemActionPlugin(QObject *parent);
~KAbstractFileItemActionPlugin() override;
/**
* Implement the actions method in the plugin in order to create actions.
*
* @param fileItemInfos Information about the selected file items.
* @param parentWidget To be used as parent for the returned QActions
*
* @return A list of actions to be added to a contextual menu for the file
* items.
*/
virtual QList<QAction *> actions(const KFileItemListProperties &fileItemInfos, QWidget *parentWidget) = 0;
Q_SIGNALS:
/**
* Emits an error which will be displayed to the user
* @since 5.82
*/
void error(const QString &errorMessage);
};
#endif
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,42 @@
/*
SPDX-FileCopyrightText: 2005 Sean Harmer <sh@rama.homelinux.org>
SPDX-FileCopyrightText: 2005-2007 Till Adam <adam@kde.org>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#ifndef KACLEDITWIDGET_H
#define KACLEDITWIDGET_H
#include <config-kiowidgets.h>
#if HAVE_POSIX_ACL || defined(Q_MOC_RUN)
#include <QWidget>
#include <kacl.h>
#include <memory>
/// @internal
class KACLEditWidget : public QWidget
{
Q_OBJECT
public:
explicit KACLEditWidget(QWidget *parent = nullptr);
~KACLEditWidget() override;
KACL getACL() const;
KACL getDefaultACL() const;
void setACL(const KACL &);
void setDefaultACL(const KACL &);
void setAllowDefaults(bool value);
private:
class KACLEditWidgetPrivate;
std::unique_ptr<KACLEditWidgetPrivate> const d;
Q_DISABLE_COPY(KACLEditWidget)
};
#endif
#endif
@@ -0,0 +1,194 @@
/*
SPDX-FileCopyrightText: 2005 Sean Harmer <sh@rama.homelinux.org>
SPDX-FileCopyrightText: 2005-2007 Till Adam <adam@kde.org>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#ifndef KACLEDITWIDGET_P_H
#define KACLEDITWIDGET_P_H
#include <config-kiowidgets.h>
#if HAVE_POSIX_ACL || defined(Q_MOC_RUN)
#include <kacl.h>
#include <sys/acl.h>
#include <QDialog>
#include <QHash>
#include <QTreeWidget>
#include <QComboBox>
#include <kfileitem.h>
class KACLListViewItem;
class QButtonGroup;
class KACLListView;
class QStackedWidget;
class QCheckBox;
class QAbstractButton;
class QColorGroup;
/**
@author Sean Harmer
*/
class KACLListView : public QTreeWidget
{
Q_OBJECT
friend class KACLListViewItem;
public:
enum Types {
OWNER_IDX = 0,
GROUP_IDX,
OTHERS_IDX,
MASK_IDX,
NAMED_USER_IDX,
NAMED_GROUP_IDX,
LAST_IDX,
};
enum EntryType {
User = 1,
Group = 2,
Others = 4,
Mask = 8,
NamedUser = 16,
NamedGroup = 32,
AllTypes = 63,
};
explicit KACLListView(QWidget *parent = nullptr);
~KACLListView() override;
bool hasMaskEntry() const
{
return m_hasMask;
}
bool hasDefaultEntries() const;
bool allowDefaults() const
{
return m_allowDefaults;
}
void setAllowDefaults(bool v)
{
m_allowDefaults = v;
}
unsigned short maskPermissions() const;
void setMaskPermissions(unsigned short maskPerms);
acl_perm_t maskPartialPermissions() const;
void setMaskPartialPermissions(acl_perm_t maskPerms);
bool maskCanBeDeleted() const;
bool defaultMaskCanBeDeleted() const;
const KACLListViewItem *findDefaultItemByType(EntryType type) const;
const KACLListViewItem *findItemByType(EntryType type, bool defaults = false) const;
unsigned short calculateMaskValue(bool defaults) const;
void calculateEffectiveRights();
QStringList allowedUsers(bool defaults, KACLListViewItem *allowedItem = nullptr);
QStringList allowedGroups(bool defaults, KACLListViewItem *allowedItem = nullptr);
KACL getACL();
KACL getDefaultACL();
public Q_SLOTS:
void slotAddEntry();
void slotEditEntry();
void slotRemoveEntry();
void setACL(const KACL &anACL);
void setDefaultACL(const KACL &anACL);
QSize sizeHint() const override;
protected Q_SLOTS:
void slotItemClicked(QTreeWidgetItem *pItem, int col);
void slotItemDoubleClicked(QTreeWidgetItem *item, int col);
protected:
void contentsMousePressEvent(QMouseEvent *e);
private:
void fillItemsFromACL(const KACL &pACL, bool defaults = false);
KACL itemsToACL(bool defaults) const;
KACL m_ACL;
KACL m_defaultACL;
unsigned short m_mask;
bool m_hasMask;
bool m_allowDefaults;
QStringList m_allUsers;
QStringList m_allGroups;
};
class EditACLEntryDialog : public QDialog
{
Q_OBJECT
public:
EditACLEntryDialog(KACLListView *listView,
KACLListViewItem *item,
const QStringList &users,
const QStringList &groups,
const QStringList &defaultUsers,
const QStringList &defaultGroups,
int allowedTypes = KACLListView::AllTypes,
int allowedDefaultTypes = KACLListView::AllTypes,
bool allowDefault = false);
KACLListViewItem *item() const
{
return m_item;
}
public Q_SLOTS:
void slotOk();
void slotSelectionChanged(QAbstractButton *);
private Q_SLOTS:
void slotUpdateAllowedUsersAndGroups();
void slotUpdateAllowedTypes();
private:
KACLListView *m_listView;
KACLListViewItem *m_item;
QStringList m_users;
QStringList m_groups;
QStringList m_defaultUsers;
QStringList m_defaultGroups;
int m_allowedTypes;
int m_allowedDefaultTypes;
QButtonGroup *m_buttonGroup;
QComboBox *m_usersCombo;
QComboBox *m_groupsCombo;
QStackedWidget *m_widgetStack;
QCheckBox *m_defaultCB;
QHash<QAbstractButton *, int> m_buttonIds;
};
class KACLListViewItem : public QTreeWidgetItem
{
public:
KACLListViewItem(QTreeWidget *parent, KACLListView::EntryType type, unsigned short value, bool defaultEntry, const QString &qualifier = QString());
~KACLListViewItem() override;
QString key() const;
bool operator<(const QTreeWidgetItem &other) const override;
void calcEffectiveRights();
bool isDeletable() const;
bool isAllowedToChangeType() const;
void togglePerm(acl_perm_t perm);
void updatePermissionIcons();
void repaint();
KACLListView::EntryType type;
unsigned short value;
bool isDefault;
QString qualifier;
bool isPartial;
private:
KACLListView *m_pACLListView;
};
#endif
#endif
@@ -0,0 +1,59 @@
/*
This file is part of the KDE project
SPDX-FileCopyrightText: 2003 Waldo Bastian <bastian@kde.org>
SPDX-License-Identifier: LGPL-2.0-only
*/
#include "kbuildsycocaprogressdialog.h"
#include "kio_widgets_debug.h"
#include <KLocalizedString>
#include <KSycoca>
#include <QDialogButtonBox>
#include <QProcess>
#include <QStandardPaths>
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…"));
const QString exec = QStandardPaths::findExecutable(QStringLiteral(KBUILDSYCOCA_EXENAME));
if (exec.isEmpty()) {
qCWarning(KIO_WIDGETS) << "Could not find kbuildsycoca executable:" << KBUILDSYCOCA_EXENAME;
return;
}
QProcess *proc = new QProcess(&dlg);
proc->start(exec, QStringList());
QObject::connect(proc, &QProcess::finished, &dlg, &QWidget::close);
dlg.exec();
}
KBuildSycocaProgressDialog::KBuildSycocaProgressDialog(QWidget *_parent, const QString &title, const QString &text)
: QProgressDialog(_parent)
, d(new KBuildSycocaProgressDialogPrivate(this))
{
setWindowTitle(title);
setModal(true);
setLabelText(text);
setRange(0, 0);
setAutoClose(false);
QDialogButtonBox *dialogButtonBox = new QDialogButtonBox(QDialogButtonBox::Cancel, this);
setCancelButton(dialogButtonBox->button(QDialogButtonBox::Cancel));
}
KBuildSycocaProgressDialog::~KBuildSycocaProgressDialog() = default;
#include "moc_kbuildsycocaprogressdialog.cpp"
@@ -0,0 +1,42 @@
/*
This file is part of the KDE project
SPDX-FileCopyrightText: 2003 Waldo Bastian <bastian@kde.org>
SPDX-License-Identifier: LGPL-2.0-only
*/
#ifndef KBUILDSYCOCAPROGRESSDIALOG_H
#define KBUILDSYCOCAPROGRESSDIALOG_H
#include "kiowidgets_export.h"
#include <QProgressDialog>
#include <QTimer>
#include <memory>
class KBuildSycocaProgressDialogPrivate;
/**
* @class KBuildSycocaProgressDialog kbuildsycocaprogressdialog.h <KBuildSycocaProgressDialog>
*
* Progress dialog while ksycoca is being rebuilt (by kbuildsycoca).
* Usage: KBuildSycocaProgressDialog::rebuildKSycoca(parentWidget)
*/
class KIOWIDGETS_EXPORT KBuildSycocaProgressDialog : public QProgressDialog
{
Q_OBJECT
public:
/**
* Rebuild KSycoca and show a progress dialog while doing so.
* @param parent Parent widget for the progress dialog
*/
static void rebuildKSycoca(QWidget *parent);
private:
KIOWIDGETS_NO_EXPORT KBuildSycocaProgressDialog(QWidget *parent, const QString &title, const QString &text);
KIOWIDGETS_NO_EXPORT ~KBuildSycocaProgressDialog() override;
private:
std::unique_ptr<KBuildSycocaProgressDialogPrivate> const d;
};
#endif
@@ -0,0 +1,60 @@
/*
This file is part of the KDE project
SPDX-FileCopyrightText: 1998, 1999 Torben Weis <weis@kde.org>
SPDX-FileCopyrightText: 2000 Carsten Pfeiffer <pfeiffer@kde.org>
SPDX-FileCopyrightText: 2003-2005 David Faure <faure@kde.org>
SPDX-FileCopyrightText: 2001-2006 Michael Brade <brade@kde.org>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#include "kdirlister.h"
#include <KJobUiDelegate>
#include <KJobWidgets>
#include <kio/listjob.h>
#include <QWidget>
class KDirListerPrivate
{
public:
KDirListerPrivate()
{
}
QWidget *m_window = nullptr; // Main window this lister is associated with
};
KDirLister::KDirLister(QObject *parent)
: KCoreDirLister(parent)
, d(new KDirListerPrivate)
{
}
KDirLister::~KDirLister()
{
}
bool KDirLister::autoErrorHandlingEnabled() const
{
return KCoreDirLister::autoErrorHandlingEnabled();
}
void KDirLister::setMainWindow(QWidget *window)
{
d->m_window = window;
}
QWidget *KDirLister::mainWindow()
{
return d->m_window;
}
void KDirLister::jobStarted(KIO::ListJob *job)
{
if (d->m_window) {
KJobWidgets::setWindow(job, d->m_window);
}
}
#include "moc_kdirlister.cpp"
@@ -0,0 +1,73 @@
/*
This file is part of the KDE project
SPDX-FileCopyrightText: 1999 David Faure <faure@kde.org>
SPDX-FileCopyrightText: 2001, 2002, 2004-2006 Michael Brade <brade@kde.org>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#ifndef kdirlister_h
#define kdirlister_h
#include "kiowidgets_export.h"
#include <kcoredirlister.h>
class QWidget;
class KDirListerPrivate;
/**
* @class KDirLister kdirlister.h <KDirLister>
*
* Subclass of KCoreDirLister which uses QWidgets to show error messages
* and to associate jobs with windows.
*/
class KIOWIDGETS_EXPORT KDirLister : public KCoreDirLister
{
Q_OBJECT
public:
/**
* Create a directory lister.
*/
explicit KDirLister(QObject *parent = nullptr);
/**
* Destroy the directory lister.
*/
~KDirLister() override;
/**
* Check whether auto error handling is enabled.
* If enabled, it will show an error dialog to the user when an
* error occurs. It is turned on by default.
* @return true if auto error handling is enabled, false otherwise
* @see setAutoErrorHandlingEnabled()
*/
bool autoErrorHandlingEnabled() const; // KF6 remove, already provided by KCoreDirLister
/**
* Pass the main window this object is associated with
* this is used for caching authentication data
* @param window the window to associate with, @c nullptr to disassociate
*/
void setMainWindow(QWidget *window);
/**
* Returns the main window associated with this object.
* @return the associated main window, or @c nullptr if there is none
*/
QWidget *mainWindow();
protected:
/**
* Reimplemented to associate a window with new jobs
* @reimp
*/
void jobStarted(KIO::ListJob *) override;
private:
friend class KDirListerPrivate;
std::unique_ptr<KDirListerPrivate> d;
};
#endif
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,300 @@
/*
This file is part of the KDE project
SPDX-FileCopyrightText: 2006 David Faure <faure@kde.org>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#ifndef KDIRMODEL_H
#define KDIRMODEL_H
#include "kiowidgets_export.h"
#include <QAbstractItemModel>
#include <kfileitem.h>
#include <memory>
class KDirLister;
class KDirModelPrivate;
class JobUrlCache;
/**
* @class KDirModel kdirmodel.h <KDirModel>
*
* @short A model for a KIO-based directory tree.
*
* KDirModel implements the QAbstractItemModel interface (for use with Qt's model/view widgets)
* around the directory listing for one directory or a tree of directories.
*
* Note that there are some cases when using QPersistentModelIndexes from this model will not give
* expected results. QPersistentIndexes will remain valid and updated if its siblings are added or
* removed. However, if the QPersistentIndex or one of its ancestors is moved, the QPersistentIndex will become
* invalid. For example, if a file or directory is renamed after storing a QPersistentModelIndex for it,
* the index (along with any stored children) will become invalid even though it is still in the model. The reason
* for this is that moves of files and directories are treated as separate insert and remove actions.
*
* @see KDirSortFilterProxyModel
*
* @author David Faure
* Based on work by Hamish Rodda and Pascal Letourneau
*/
class KIOWIDGETS_EXPORT KDirModel : public QAbstractItemModel
{
Q_OBJECT
public:
/**
* @param parent parent qobject
*/
explicit KDirModel(QObject *parent = nullptr);
~KDirModel() override;
/**
* Flags for the openUrl() method
* @see OpenUrlFlags
* @since 5.69
*/
enum OpenUrlFlag {
NoFlags = 0x0, ///< No additional flags specified.
Reload = 0x1, ///< Indicates whether to use the cache or to reread
///< the directory from the disk.
///< Use only when opening a dir not yet listed by our dirLister()
///< without using the cache. Otherwise use dirLister()->updateDirectory().
ShowRoot = 0x2, ///< Display a root node for the URL being opened.
};
/**
* Stores a combination of #OpenUrlFlag values.
*/
Q_DECLARE_FLAGS(OpenUrlFlags, OpenUrlFlag)
Q_FLAG(OpenUrlFlags)
/**
* Display the contents of @p url in the model.
* Apart from the support for the ShowRoot flag, this is equivalent to dirLister()->openUrl(url, flags)
* @param url the URL of the directory whose contents should be listed.
* Unless ShowRoot is set, the item for this directory will NOT be shown, the model starts at its children.
* @param flags see OpenUrlFlag
* @since 5.69
*/
Q_INVOKABLE void openUrl(const QUrl &url, OpenUrlFlags flags = NoFlags);
/**
* Set the directory lister to use by this model, instead of the default KDirLister created internally.
* The model takes ownership.
*/
void setDirLister(KDirLister *dirLister);
/**
* Return the directory lister used by this model.
*/
KDirLister *dirLister() const;
/**
* Return the fileitem for a given index. This is O(1), i.e. fast.
*/
KFileItem itemForIndex(const QModelIndex &index) const;
/**
* Return the index for a given kfileitem. This can be slow.
*/
Q_INVOKABLE QModelIndex indexForItem(const KFileItem &) const;
/**
* Return the index for a given url. This can be slow.
*/
Q_INVOKABLE QModelIndex indexForUrl(const QUrl &url) const;
/**
* @short Lists subdirectories using fetchMore() as needed until the given @p url exists in the model.
*
* When the model is used by a treeview, call KDirLister::openUrl with the base url of the tree,
* then the treeview will take care of calling fetchMore() when the user opens directories.
* However if you want the tree to show a given URL (i.e. open the tree recursively until that URL),
* call expandToUrl().
* Note that this is asynchronous; the necessary listing of subdirectories will take time so
* the model will not immediately have this url available.
* The model emits the signal expand() when an index has become available; this can be connected
* to the treeview in order to let it open that index.
* @param url the url of a subdirectory of the directory model (or a file in a subdirectory)
*/
Q_INVOKABLE void expandToUrl(const QUrl &url);
/**
* Notify the model that the item at this index has changed.
* For instance because KMimeTypeResolver called determineMimeType on it.
* This makes the model emit its dataChanged signal at this index, so that views repaint.
* Note that for most things (renaming, changing size etc.), KDirLister's signals tell the model already.
*/
Q_INVOKABLE void itemChanged(const QModelIndex &index);
/**
* Forget all previews (optimization for turning previews off).
* The items will again have their default appearance (not controlled by the model).
* @since 5.28
*/
Q_INVOKABLE void clearAllPreviews();
/**
* Useful "default" columns. Views can use a proxy to have more control over this.
*/
enum ModelColumns {
Name = 0,
Size,
ModifiedTime,
Permissions,
Owner,
Group,
Type,
ColumnCount,
};
/// Possible return value for data(ChildCountRole), meaning the item isn't a directory,
/// or we haven't calculated its child count yet
enum {
ChildCountUnknown = -1
};
enum AdditionalRoles {
// Note: use printf "0x%08X\n" $(($RANDOM*$RANDOM))
// to define additional roles.
FileItemRole = 0x07A263FF, ///< returns the KFileItem for a given index. roleName is "fileItem".
ChildCountRole = 0x2C4D0A40, ///< returns the number of items in a directory, or ChildCountUnknown. roleName is "childCount".
HasJobRole = 0x01E555A5, ///< returns whether or not there is a job on an item (file/directory). roleName is "hasJob".
};
/**
* @see DropsAllowed
*/
enum DropsAllowedFlag {
NoDrops = 0,
DropOnDirectory = 1, ///< allow drops on any directory
DropOnAnyFile = 2, ///< allow drops on any file
DropOnLocalExecutable = 4, ///< allow drops on local executables, shell scripts and desktop files. Can be used with DropOnDirectory.
};
/**
* Stores a combination of #DropsAllowedFlag values.
*/
Q_DECLARE_FLAGS(DropsAllowed, DropsAllowedFlag)
Q_FLAG(DropsAllowed)
/// Set whether dropping onto items should be allowed, and for which kind of item
/// Drops are disabled by default.
Q_INVOKABLE void setDropsAllowed(DropsAllowed dropsAllowed);
/// Reimplemented from QAbstractItemModel. Returns true for empty directories.
bool canFetchMore(const QModelIndex &parent) const override;
/// Reimplemented from QAbstractItemModel. Returns ColumnCount.
int columnCount(const QModelIndex &parent = QModelIndex()) const override;
/// Reimplemented from QAbstractItemModel.
QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
/// Reimplemented from QAbstractItemModel. Not implemented yet.
bool dropMimeData(const QMimeData *data, Qt::DropAction action, int row, int column, const QModelIndex &parent) override;
/// Reimplemented from QAbstractItemModel. Lists the subdirectory.
void fetchMore(const QModelIndex &parent) override;
/// Reimplemented from QAbstractItemModel.
Qt::ItemFlags flags(const QModelIndex &index) const override;
/// Reimplemented from QAbstractItemModel. Returns true for directories.
bool hasChildren(const QModelIndex &parent = QModelIndex()) const override;
/// Reimplemented from QAbstractItemModel. Returns the column titles.
QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override;
/// Reimplemented from QAbstractItemModel. O(1)
QModelIndex index(int row, int column, const QModelIndex &parent = QModelIndex()) const override;
/// Reimplemented from QAbstractItemModel.
QMimeData *mimeData(const QModelIndexList &indexes) const override;
/// Reimplemented from QAbstractItemModel.
QStringList mimeTypes() const override;
/// Reimplemented from QAbstractItemModel.
QModelIndex parent(const QModelIndex &index) const override;
/// Reimplemented from QAbstractItemModel.
QModelIndex sibling(int row, int column, const QModelIndex &index) const override;
/// Reimplemented from QAbstractItemModel.
int rowCount(const QModelIndex &parent = QModelIndex()) const override;
/// Reimplemented from QAbstractItemModel.
/// Call this to set a new icon, e.g. a preview
bool setData(const QModelIndex &index, const QVariant &value, int role = Qt::EditRole) override;
/// Reimplemented from QAbstractItemModel. Not implemented. @see KDirSortFilterProxyModel
void sort(int column, Qt::SortOrder order = Qt::AscendingOrder) override;
/// Reimplemented from QAbstractItemModel.
/// @see AdditionalRoles
QHash<int, QByteArray> roleNames() const override;
/**
* Remove urls from the list if an ancestor is present on the list. This can
* be used to delete only the ancestor url and skip a potential error of a non-existent url.
*
* For example, for a list of "/home/foo/a", "/home/foo/a/a.txt", "/home/foo/a/a/a.txt", "/home/foo/a/b/b.txt",
* "home/foo/b/b.txt", this method will return the list "/home/foo/a", "/home/foo/b/b.txt".
*
* @return the list @p urls without parented urls inside.
*/
static QList<QUrl> simplifiedUrlList(const QList<QUrl> &urls);
/**
* This emits the needSequenceIcon signal, requesting another sequence icon
*
* If there is a KFilePreviewGenerator attached to this model, that generator will care
* about creating another preview.
*
* @param index Index of the item that should get another icon
* @param sequenceIndex Index in the sequence. If it is zero, the standard icon will be assigned.
* For higher indices, arbitrary different meaningful icons will be generated.
*/
void requestSequenceIcon(const QModelIndex &index, int sequenceIndex);
/**
* Enable/Disable the displaying of an animated overlay that is shown for any destination
* urls (in the view). When enabled, the animations (if any) will be drawn automatically.
*
* Only the files/folders that are visible and have jobs associated with them
* will display the animation.
* You would likely not want this enabled if you perform some kind of custom painting
* that takes up a whole item, and will just make this(and what you paint) look funky.
*
* Default is disabled.
*
* Note: KFileItemDelegate needs to have it's method called with the same
* value, when you make the call to this method.
*/
void setJobTransfersVisible(bool show);
/**
* Returns whether or not displaying job transfers has been enabled.
*/
bool jobTransfersVisible() const;
Qt::DropActions supportedDropActions() const override;
Q_SIGNALS:
/**
* Emitted for each subdirectory that is a parent of a url passed to expandToUrl
* This allows to asynchronously open a tree view down to a given directory.
* Also emitted for the final file, if expandToUrl is called with a file
* (for instance so that it can be selected).
*/
void expand(const QModelIndex &index);
/**
* Emitted when another icon sequence index is requested
* @param index Index of the item that should get another icon
* @param sequenceIndex Index in the sequence. If it is zero, the standard icon should be assigned.
* For higher indices, arbitrary different meaningful icons should be generated.
* This is usually slowly counted up while the user hovers the icon.
* If no meaningful alternative icons can be generated, this should be ignored.
*/
void needSequenceIcon(const QModelIndex &index, int sequenceIndex);
private:
// Make those private, they shouldn't be called by applications
bool insertRows(int, int, const QModelIndex & = QModelIndex()) override;
bool insertColumns(int, int, const QModelIndex & = QModelIndex()) override;
bool removeRows(int, int, const QModelIndex & = QModelIndex()) override;
bool removeColumns(int, int, const QModelIndex & = QModelIndex()) override;
private:
friend class KDirModelPrivate;
std::unique_ptr<KDirModelPrivate> const d;
};
Q_DECLARE_OPERATORS_FOR_FLAGS(KDirModel::DropsAllowed)
Q_DECLARE_OPERATORS_FOR_FLAGS(KDirModel::OpenUrlFlags)
#endif /* KDIRMODEL_H */
@@ -0,0 +1,253 @@
/*
SPDX-FileCopyrightText: 2008 Rob Scheepmaker <r.scheepmaker@student.utwente.nl>
SPDX-FileCopyrightText: 2010 Shaun Reich <shaun.reich@kdemail.net>
SPDX-FileCopyrightText: 2021 Kai Uwe Broulik <kde@broulik.de>
SPDX-License-Identifier: LGPL-2.1-or-later
*/
#include "kdynamicjobtracker_p.h"
#include "kio_widgets_debug.h"
#include "kuiserver_interface.h"
#include <KJobTrackerInterface>
#include <KUiServerJobTracker>
#include <KUiServerV2JobTracker>
#include <KWidgetJobTracker>
#include <kio/jobtracker.h>
#include <QApplication>
#include <QDBusConnection>
#include <QDBusConnectionInterface>
#include <QDBusServiceWatcher>
#include <QMap>
#include <QXmlStreamReader>
struct AllTrackers {
KUiServerJobTracker *kuiserverTracker;
KUiServerV2JobTracker *kuiserverV2Tracker;
KWidgetJobTracker *widgetTracker;
};
class KDynamicJobTrackerPrivate
{
public:
KDynamicJobTrackerPrivate()
{
}
~KDynamicJobTrackerPrivate()
{
delete kuiserverTracker;
delete kuiserverV2Tracker;
delete widgetTracker;
}
static bool hasDBusInterface(const QString &introspectionData, const QString &interface)
{
QXmlStreamReader xml(introspectionData);
while (!xml.atEnd() && !xml.hasError()) {
xml.readNext();
if (xml.tokenType() == QXmlStreamReader::StartElement && xml.name() == QLatin1String("interface")) {
if (xml.attributes().value(QLatin1String("name")) == interface) {
return true;
}
}
}
return false;
}
KUiServerJobTracker *kuiserverTracker = nullptr;
KUiServerV2JobTracker *kuiserverV2Tracker = nullptr;
KWidgetJobTracker *widgetTracker = nullptr;
QMap<KJob *, AllTrackers> trackers;
enum JobViewServerSupport {
NeedsChecking,
Error,
V2Supported,
V2NotSupported,
};
JobViewServerSupport jobViewServerSupport = NeedsChecking;
QDBusServiceWatcher *jobViewServerWatcher = nullptr;
};
KDynamicJobTracker::KDynamicJobTracker(QObject *parent)
: KJobTrackerInterface(parent)
, d(new KDynamicJobTrackerPrivate)
{
}
KDynamicJobTracker::~KDynamicJobTracker() = default;
void KDynamicJobTracker::registerJob(KJob *job)
{
if (d->trackers.contains(job)) {
return;
}
// only interested in finished() signal,
// so catching ourselves instead of using KJobTrackerInterface::registerJob()
connect(job, &KJob::finished, this, &KDynamicJobTracker::unregisterJob);
const bool canHaveWidgets = (qobject_cast<QApplication *>(qApp) != nullptr);
// always add an entry, even with no trackers used at all,
// so unregisterJob() will work as normal
AllTrackers &trackers = d->trackers[job];
trackers.kuiserverTracker = nullptr;
trackers.kuiserverV2Tracker = nullptr;
trackers.widgetTracker = nullptr;
auto useWidgetsFallback = [this, canHaveWidgets, &trackers, job] {
if (canHaveWidgets) {
// fallback to widget tracker only!
if (!d->widgetTracker) {
d->widgetTracker = new KWidgetJobTracker();
}
trackers.widgetTracker = d->widgetTracker;
trackers.widgetTracker->registerJob(job);
}
};
// do not try to use kuiserver on Windows/macOS
#if defined(Q_OS_WIN) || defined(Q_OS_MAC)
useWidgetsFallback();
return;
#endif
// do not try to query kuiserver if dbus is not available
if (!QDBusConnection::sessionBus().interface()) {
useWidgetsFallback();
return;
}
const QString kuiserverService = QStringLiteral("org.kde.kuiserver");
if (!d->jobViewServerWatcher) {
d->jobViewServerWatcher = new QDBusServiceWatcher(kuiserverService,
QDBusConnection::sessionBus(),
QDBusServiceWatcher::WatchForOwnerChange | QDBusServiceWatcher::WatchForUnregistration,
this);
connect(d->jobViewServerWatcher, &QDBusServiceWatcher::serviceOwnerChanged, this, [this] {
d->jobViewServerSupport = KDynamicJobTrackerPrivate::NeedsChecking;
});
}
if (d->jobViewServerSupport == KDynamicJobTrackerPrivate::NeedsChecking) {
// Unfortunately no DBus ObjectManager support in Qt DBus.
QDBusMessage msg = QDBusMessage::createMethodCall(kuiserverService,
QStringLiteral("/JobViewServer"),
QStringLiteral("org.freedesktop.DBus.Introspectable"),
QStringLiteral("Introspect"));
auto reply = QDBusConnection::sessionBus().call(msg);
if (reply.type() == QDBusMessage::ErrorMessage || reply.arguments().count() != 1) {
qCWarning(KIO_WIDGETS) << "Failed to check which JobView API is supported" << reply.errorMessage();
d->jobViewServerSupport = KDynamicJobTrackerPrivate::Error;
} else {
const QString introspectionData = reply.arguments().first().toString();
if (KDynamicJobTrackerPrivate::hasDBusInterface(introspectionData, QStringLiteral("org.kde.JobViewServerV2"))) {
d->jobViewServerSupport = KDynamicJobTrackerPrivate::V2Supported;
} else {
d->jobViewServerSupport = KDynamicJobTrackerPrivate::V2NotSupported;
}
}
org::kde::kuiserver interface(kuiserverService, QStringLiteral("/JobViewServer"), QDBusConnection::sessionBus(), this);
QDBusReply<bool> requiresTrackerReply = interface.requiresJobTracker();
if (!requiresTrackerReply.isValid() || requiresTrackerReply.value()) {
d->jobViewServerSupport = KDynamicJobTrackerPrivate::Error;
}
QDBusConnection::sessionBus().connect(kuiserverService,
QStringLiteral("/JobViewServer"),
QStringLiteral("org.kde.kuiserver"),
QStringLiteral("requiresJobTrackerChanged"),
this,
SLOT(handleRequiresJobTrackerChanged(bool)));
}
if (d->jobViewServerSupport == KDynamicJobTrackerPrivate::V2Supported) {
if (!d->kuiserverV2Tracker) {
d->kuiserverV2Tracker = new KUiServerV2JobTracker();
}
trackers.kuiserverV2Tracker = d->kuiserverV2Tracker;
trackers.kuiserverV2Tracker->registerJob(job);
return;
}
// No point in trying to set up V1 if calling the service above failed.
if (d->jobViewServerSupport != KDynamicJobTrackerPrivate::Error) {
if (!d->kuiserverTracker) {
d->kuiserverTracker = new KUiServerJobTracker();
}
trackers.kuiserverTracker = d->kuiserverTracker;
trackers.kuiserverTracker->registerJob(job);
}
// If kuiserver isn't available or it tells us a job tracker is required
// create a widget tracker.
if (d->jobViewServerSupport == KDynamicJobTrackerPrivate::Error) {
useWidgetsFallback();
}
}
void KDynamicJobTracker::unregisterJob(KJob *job)
{
job->disconnect(this);
QMap<KJob *, AllTrackers>::Iterator it = d->trackers.find(job);
if (it == d->trackers.end()) {
qCWarning(KIO_WIDGETS) << "Tried to unregister a kio job that hasn't been registered.";
return;
}
const AllTrackers &trackers = it.value();
KUiServerJobTracker *kuiserverTracker = trackers.kuiserverTracker;
KUiServerV2JobTracker *kuiserverV2Tracker = trackers.kuiserverV2Tracker;
KWidgetJobTracker *widgetTracker = trackers.widgetTracker;
if (kuiserverTracker) {
kuiserverTracker->unregisterJob(job);
}
if (kuiserverV2Tracker) {
kuiserverV2Tracker->unregisterJob(job);
}
if (widgetTracker) {
widgetTracker->unregisterJob(job);
}
d->trackers.erase(it);
}
void KDynamicJobTracker::handleRequiresJobTrackerChanged(bool req)
{
if (req) {
d->jobViewServerSupport = KDynamicJobTrackerPrivate::Error;
} else {
d->jobViewServerSupport = KDynamicJobTrackerPrivate::V2Supported;
}
}
Q_GLOBAL_STATIC(KDynamicJobTracker, globalJobTracker)
// Simply linking to this library, creates a GUI job tracker for all KIO jobs
static int registerDynamicJobTracker()
{
KIO::setJobTracker(globalJobTracker());
return 0; // something
}
Q_CONSTRUCTOR_FUNCTION(registerDynamicJobTracker)
#include "moc_kdynamicjobtracker_p.cpp"
@@ -0,0 +1,60 @@
/*
SPDX-FileCopyrightText: 2008 Rob Scheepmaker <r.scheepmaker@student.utwente.nl>
SPDX-License-Identifier: LGPL-2.1-or-later
*/
#ifndef KDYNAMICJOBTRACKER_H
#define KDYNAMICJOBTRACKER_H
#include <KJobTrackerInterface>
#include <memory>
class KDynamicJobTrackerPrivate;
/**
* This class implements a simple job tracker which registers any job to the KWidgetJobTracker if a
* kuiserver isn't available on the DBus, or to the KUiServerJobTracker, if a kuiserver is
* available. This way, we have the old dialogs as fallback when the user doesn't use a kuiserver
* applet or application.
*/
class KDynamicJobTracker : public KJobTrackerInterface
{
Q_OBJECT
public:
/**
* Creates a new KDynamicJobTracker
*
* @param parent the parent of this object.
*/
explicit KDynamicJobTracker(QObject *parent = nullptr);
/**
* Destroys this KDynamicJobTracker
*/
~KDynamicJobTracker() override;
public Q_SLOTS:
/**
* Register a new job in this tracker. This call will get forwarded to either KWidgetJobTracker
* or KUiServerJobTracker, depending on the availability of the Kuiserver.
*
* @param job the job to register
*/
void registerJob(KJob *job) override;
/**
* Unregister a job from the tracker it was registered to.
*
* @param job the job to unregister
*/
void unregisterJob(KJob *job) override;
private:
std::unique_ptr<KDynamicJobTrackerPrivate> const d;
Q_SLOT void handleRequiresJobTrackerChanged(bool);
};
#endif
@@ -0,0 +1,80 @@
/*
This file is part of the KDE libraries
SPDX-FileCopyrightText: 2006 David Faure <faure@kde.org>
SPDX-License-Identifier: LGPL-2.0-only
*/
#include "kfile.h"
bool KFile::isSortByName(const QDir::SortFlags &sort)
{
return (sort & QDir::Time) != QDir::Time && (sort & QDir::Size) != QDir::Size && (sort & QDir::Type) != QDir::Type;
}
bool KFile::isSortBySize(const QDir::SortFlags &sort)
{
return (sort & QDir::Size) == QDir::Size;
}
bool KFile::isSortByDate(const QDir::SortFlags &sort)
{
return (sort & QDir::Time) == QDir::Time;
}
bool KFile::isSortByType(const QDir::SortFlags &sort)
{
return (sort & QDir::Type) == QDir::Type;
}
bool KFile::isSortDirsFirst(const QDir::SortFlags &sort)
{
return (sort & QDir::DirsFirst) == QDir::DirsFirst;
}
bool KFile::isSortCaseInsensitive(const QDir::SortFlags &sort)
{
return (sort & QDir::IgnoreCase) == QDir::IgnoreCase;
}
bool KFile::isDefaultView(const FileView &view)
{
return (view & Default) == Default;
}
bool KFile::isSimpleView(const FileView &view)
{
return (view & Simple) == Simple;
}
bool KFile::isDetailView(const FileView &view)
{
return (view & Detail) == Detail;
}
bool KFile::isSeparateDirs(const FileView &view)
{
return (view & SeparateDirs) == SeparateDirs;
}
bool KFile::isPreviewContents(const FileView &view)
{
return (view & PreviewContents) == PreviewContents;
}
bool KFile::isPreviewInfo(const FileView &view)
{
return (view & PreviewInfo) == PreviewInfo;
}
bool KFile::isTreeView(const FileView &view)
{
return (view & Tree) == Tree;
}
bool KFile::isDetailTreeView(const FileView &view)
{
return (view & DetailTree) == DetailTree;
}
#include "moc_kfile.cpp"
@@ -0,0 +1,110 @@
/*
This file is part of the KDE libraries
SPDX-FileCopyrightText: 2000 Carsten Pfeiffer <pfeiffer@kde.org>
SPDX-License-Identifier: LGPL-2.0-only
*/
#ifndef KFILE_H
#define KFILE_H
#include <QDir>
#include "kiowidgets_export.h"
/**
* @class KFile kfile.h <KFile>
*
* KFile is a class which provides a namespace for some enumerated
* values associated with the kfile library. You will never need to
* construct a KFile object itself.
*/
class KIOWIDGETS_EXPORT KFile
{
Q_GADGET
public:
/**
* Modes of operation for the dialog.
* @li @p File - Get a single file name from the user.
* @li @p Directory - Get a directory name from the user.
* @li @p Files - Get multiple file names from the user.
* @li @p ExistingOnly - Never return a filename which does not exist yet
* @li @p LocalOnly - Don't return remote filenames
* @see Modes
*/
enum Mode {
File = 1,
Directory = 2,
Files = 4,
ExistingOnly = 8,
LocalOnly = 16,
ModeMax = 65536,
};
/**
* Stores a combination of #Mode values.
*/
Q_DECLARE_FLAGS(Modes, Mode)
Q_FLAG(Modes)
enum FileView {
Default = 0,
Simple = 1,
Detail = 2,
SeparateDirs = 4,
PreviewContents = 8,
PreviewInfo = 16,
Tree = 32,
DetailTree = 64,
FileViewMax = 65536,
};
enum SelectionMode {
Single = 1,
Multi = 2,
Extended = 4,
NoSelection = 8,
};
//
// some bittests
//
// sorting specific
static bool isSortByName(const QDir::SortFlags &sort);
static bool isSortBySize(const QDir::SortFlags &sort);
static bool isSortByDate(const QDir::SortFlags &sort);
static bool isSortByType(const QDir::SortFlags &sort);
static bool isSortDirsFirst(const QDir::SortFlags &sort);
static bool isSortCaseInsensitive(const QDir::SortFlags &sort);
// view specific
static bool isDefaultView(const FileView &view);
static bool isSimpleView(const FileView &view);
static bool isDetailView(const FileView &view);
static bool isSeparateDirs(const FileView &view);
static bool isPreviewContents(const FileView &view);
static bool isPreviewInfo(const FileView &view);
static bool isTreeView(const FileView &view);
static bool isDetailTreeView(const FileView &view);
private:
KFile() = delete;
};
Q_DECLARE_OPERATORS_FOR_FLAGS(KFile::Modes)
#endif // KFILE_H
@@ -0,0 +1,849 @@
/*
This file is part of the KDE project
SPDX-FileCopyrightText: 1998-2009 David Faure <faure@kde.org>
SPDX-FileCopyrightText: 2021 Alexander Lohnau <alexander.lohnau@gmx.de>
SPDX-License-Identifier: LGPL-2.0-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
*/
#include "kfileitemactions.h"
#include "kfileitemactions_p.h"
#include <KAbstractFileItemActionPlugin>
#include <KApplicationTrader>
#include <KAuthorized>
#include <KConfigGroup>
#include <KDesktopFile>
#include <KDesktopFileAction>
#include <KFileUtils>
#include <KIO/ApplicationLauncherJob>
#include <KIO/JobUiDelegate>
#include <KLocalizedString>
#include <KPluginFactory>
#include <KPluginMetaData>
#include <KSandbox>
#include <jobuidelegatefactory.h>
#include <kapplicationtrader.h>
#include <kdirnotify.h>
#include <kurlauthorized.h>
#include <QFile>
#include <QMenu>
#include <QMimeDatabase>
#include <QtAlgorithms>
#ifdef WITH_QTDBUS
#include <QDBusConnection>
#include <QDBusConnectionInterface>
#include <QDBusInterface>
#include <QDBusMessage>
#endif
#include <algorithm>
#include <kio_widgets_debug.h>
#include <set>
static bool KIOSKAuthorizedAction(const KConfigGroup &cfg)
{
const QStringList list = cfg.readEntry("X-KDE-AuthorizeAction", QStringList());
return std::all_of(list.constBegin(), list.constEnd(), [](const QString &action) {
return KAuthorized::authorize(action.trimmed());
});
}
static bool mimeTypeListContains(const QStringList &list, const KFileItem &item)
{
const QString itemMimeType = item.mimetype();
return std::any_of(list.cbegin(), list.cend(), [&](const QString &mt) {
if (mt == itemMimeType || mt == QLatin1String("all/all")) {
return true;
}
if (item.isFile() //
&& (mt == QLatin1String("allfiles") || mt == QLatin1String("all/allfiles") || mt == QLatin1String("application/octet-stream"))) {
return true;
}
if (item.currentMimeType().inherits(mt)) {
return true;
}
if (mt.endsWith(QLatin1String("/*"))) {
const int slashPos = mt.indexOf(QLatin1Char('/'));
const auto topLevelType = QStringView(mt).mid(0, slashPos);
return itemMimeType.startsWith(topLevelType);
}
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 user;
ServiceList userToplevel;
ServiceList userPriority;
QMap<QString, ServiceList> userSubmenus;
QMap<QString, ServiceList> userToplevelSubmenus;
QMap<QString, ServiceList> 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<QWidget *>(nullptr))
, m_runApplicationActionGroup(static_cast<QWidget *>(nullptr))
, m_parentWidget(nullptr)
, m_config(QStringLiteral("kservicemenurc"), KConfig::NoGlobals)
{
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<QString, ServiceList> &submenus, QMenu *menu)
{
int count = 0;
QMap<QString, ServiceList>::ConstIterator it;
for (it = submenus.begin(); it != submenus.end(); ++it) {
if (it.value().isEmpty()) {
// avoid empty sub-menus
continue;
}
QMenu *actionSubmenu = new QMenu(menu);
const int servicesAddedCount = insertServices(it.value(), actionSubmenu);
if (servicesAddedCount > 0) {
count += servicesAddedCount;
actionSubmenu->setTitle(it.key());
actionSubmenu->setIcon(QIcon::fromTheme(it.value().first().icon()));
actionSubmenu->menuAction()->setObjectName(QStringLiteral("services_submenu")); // for the unittest
menu->addMenu(actionSubmenu);
} else {
// avoid empty sub-menus
delete actionSubmenu;
}
}
return count;
}
int KFileItemActionsPrivate::insertServices(const ServiceList &list, QMenu *menu)
{
// Temporary storage for current group and all groups
ServiceList currentGroup;
std::vector<ServiceList> allGroups;
// Grouping
for (const KDesktopFileAction &serviceAction : std::as_const(list)) {
if (serviceAction.isSeparator()) {
if (!currentGroup.empty()) {
allGroups.push_back(currentGroup);
currentGroup.clear();
}
// Push back a dummy list to represent a separator for later
allGroups.push_back(ServiceList());
} else {
currentGroup.push_back(serviceAction);
}
}
// Don't forget to add the last group if it exists
if (!currentGroup.empty()) {
allGroups.push_back(currentGroup);
}
// Sort each group
for (ServiceList &group : allGroups) {
std::sort(group.begin(), group.end(), [](const KDesktopFileAction &a1, const KDesktopFileAction &a2) {
return a1.name() < a2.name();
});
}
int count = 0;
for (const ServiceList &group : allGroups) {
// Check if the group is a separator
if (group.empty()) {
const QList<QAction *> actions = menu->actions();
if (!actions.isEmpty() && !actions.last()->isSeparator()) {
menu->addSeparator();
}
continue;
}
// Insert sorted actions for current group
for (const KDesktopFileAction &serviceAction : group) {
QAction *act = new QAction(q);
act->setObjectName(QStringLiteral("menuaction")); // for the unittest
QString text = serviceAction.name();
text.replace(QLatin1Char('&'), QLatin1String("&&"));
act->setText(text);
if (!serviceAction.icon().isEmpty()) {
act->setIcon(QIcon::fromTheme(serviceAction.icon()));
}
act->setData(QVariant::fromValue(serviceAction));
m_executeServiceActionGroup.addAction(act);
menu->addAction(act); // Add to toplevel menu
++count;
}
}
return count;
}
void KFileItemActionsPrivate::slotExecuteService(QAction *act)
{
const KDesktopFileAction serviceAction = act->data().value<KDesktopFileAction>();
if (KAuthorized::authorizeAction(serviceAction.name())) {
auto *job = new KIO::ApplicationLauncherJob(serviceAction);
job->setUrls(m_props.urlList());
job->setUiDelegate(new KDialogJobUiDelegate(KJobUiDelegate::AutoHandlingEnabled, m_parentWidget));
job->start();
}
}
KFileItemActions::KFileItemActions(QObject *parent)
: QObject(parent)
, d(new KFileItemActionsPrivate(this))
{
}
KFileItemActions::~KFileItemActions() = default;
void KFileItemActions::setItemListProperties(const KFileItemListProperties &itemListProperties)
{
d->m_props = itemListProperties;
d->m_mimeTypeList.clear();
const KFileItemList items = d->m_props.items();
for (const KFileItem &item : items) {
if (!d->m_mimeTypeList.contains(item.mimetype())) {
d->m_mimeTypeList << item.mimetype();
}
}
}
void KFileItemActions::addActionsTo(QMenu *menu, MenuActionSources sources, const QList<QAction *> &additionalActions, const QStringList &excludeList)
{
QMenu *actionsMenu = menu;
if (sources & MenuActionSource::Services) {
actionsMenu = d->addServiceActionsTo(menu, additionalActions, excludeList).menu;
} else {
// Since we didn't call addServiceActionsTo(), we have to add additional actions manually
for (QAction *action : additionalActions) {
actionsMenu->addAction(action);
}
}
if (sources & MenuActionSource::Plugins) {
d->addPluginActionsTo(menu, actionsMenu, excludeList);
}
}
// static
KService::List KFileItemActions::associatedApplications(const QStringList &mimeTypeList)
{
return KFileItemActionsPrivate::associatedApplications(mimeTypeList, QStringList{});
}
static KService::Ptr preferredService(const QString &mimeType, const QStringList &excludedDesktopEntryNames)
{
KService::List services = KApplicationTrader::queryByMimeType(mimeType, [&](const KService::Ptr &serv) {
return !excludedDesktopEntryNames.contains(serv->desktopEntryName());
});
return services.isEmpty() ? KService::Ptr() : services.first();
}
void KFileItemActions::insertOpenWithActionsTo(QAction *before, QMenu *topMenu, const QStringList &excludedDesktopEntryNames)
{
d->insertOpenWithActionsTo(before, topMenu, excludedDesktopEntryNames);
}
void KFileItemActionsPrivate::slotRunPreferredApplications()
{
const KFileItemList fileItems = m_fileOpenList;
const QStringList mimeTypeList = listMimeTypes(fileItems);
const QStringList serviceIdList = listPreferredServiceIds(mimeTypeList, QStringList());
for (const QString &serviceId : serviceIdList) {
KFileItemList serviceItems;
for (const KFileItem &item : fileItems) {
const KService::Ptr serv = preferredService(item.mimetype(), QStringList());
const QString preferredServiceId = serv ? serv->storageId() : QString();
if (preferredServiceId == serviceId) {
serviceItems << item;
}
}
if (serviceId.isEmpty()) { // empty means: no associated app for this MIME type
openWithByMime(serviceItems);
continue;
}
const KService::Ptr servicePtr = KService::serviceByStorageId(serviceId); // can be nullptr
auto *job = new KIO::ApplicationLauncherJob(servicePtr);
job->setUrls(serviceItems.urlList());
job->setUiDelegate(KIO::createDefaultJobUiDelegate(KJobUiDelegate::AutoHandlingEnabled, m_parentWidget));
job->start();
}
}
void KFileItemActions::runPreferredApplications(const KFileItemList &fileOpenList)
{
d->m_fileOpenList = fileOpenList;
d->slotRunPreferredApplications();
}
void KFileItemActionsPrivate::openWithByMime(const KFileItemList &fileItems)
{
const QStringList mimeTypeList = listMimeTypes(fileItems);
for (const QString &mimeType : mimeTypeList) {
KFileItemList mimeItems;
for (const KFileItem &item : fileItems) {
if (item.mimetype() == mimeType) {
mimeItems << item;
}
}
// Show Open With dialog
auto *job = new KIO::ApplicationLauncherJob();
job->setUrls(mimeItems.urlList());
job->setUiDelegate(KIO::createDefaultJobUiDelegate(KJobUiDelegate::AutoHandlingEnabled, m_parentWidget));
job->start();
}
}
void KFileItemActionsPrivate::slotRunApplication(QAction *act)
{
// Is it an application, from one of the "Open With" actions?
KService::Ptr app = act->data().value<KService::Ptr>();
Q_ASSERT(app);
auto *job = new KIO::ApplicationLauncherJob(app);
job->setUrls(m_props.urlList());
job->setUiDelegate(KIO::createDefaultJobUiDelegate(KJobUiDelegate::AutoHandlingEnabled, m_parentWidget));
job->start();
}
void KFileItemActionsPrivate::slotOpenWithDialog()
{
// The item 'Other...' or 'Open With...' has been selected
Q_EMIT q->openWithDialogAboutToBeShown();
auto *job = new KIO::ApplicationLauncherJob();
job->setUrls(m_props.urlList());
job->setUiDelegate(KIO::createDefaultJobUiDelegate(KJobUiDelegate::AutoHandlingEnabled, m_parentWidget));
job->start();
}
QStringList KFileItemActionsPrivate::listMimeTypes(const KFileItemList &items)
{
QStringList mimeTypeList;
for (const KFileItem &item : items) {
if (!mimeTypeList.contains(item.mimetype())) {
mimeTypeList << item.mimetype();
}
}
return mimeTypeList;
}
QStringList KFileItemActionsPrivate::listPreferredServiceIds(const QStringList &mimeTypeList, const QStringList &excludedDesktopEntryNames)
{
QStringList serviceIdList;
serviceIdList.reserve(mimeTypeList.size());
for (const QString &mimeType : mimeTypeList) {
const KService::Ptr serv = preferredService(mimeType, excludedDesktopEntryNames);
serviceIdList << (serv ? serv->storageId() : QString()); // empty string means mimetype has no associated apps
}
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;
}
bool KFileItemActionsPrivate::shouldDisplayServiceMenu(const KConfigGroup &cfg, const QString &protocol) const
{
const QList<QUrl> urlList = m_props.urlList();
if (!KIOSKAuthorizedAction(cfg)) {
return false;
}
if (cfg.hasKey("X-KDE-Protocol")) {
const QString theProtocol = cfg.readEntry("X-KDE-Protocol");
if (theProtocol.startsWith(QLatin1Char('!'))) { // Is it excluded?
if (QStringView(theProtocol).mid(1) == protocol) {
return false;
}
} else if (protocol != theProtocol) {
return false;
}
} else if (cfg.hasKey("X-KDE-Protocols")) {
const QStringList protocols = cfg.readEntry("X-KDE-Protocols", QStringList());
if (!protocols.contains(protocol)) {
return false;
}
} 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.
return false;
}
const auto requiredNumbers = cfg.readEntry("X-KDE-RequiredNumberOfUrls", QList<int>());
if (!requiredNumbers.isEmpty() && !requiredNumbers.contains(urlList.count())) {
return false;
}
if (cfg.hasKey("X-KDE-MinNumberOfUrls")) {
const int minNumber = cfg.readEntry("X-KDE-MinNumberOfUrls").toInt();
if (urlList.count() < minNumber) {
return false;
}
}
if (cfg.hasKey("X-KDE-MaxNumberOfUrls")) {
const int maxNumber = cfg.readEntry("X-KDE-MaxNumberOfUrls").toInt();
if (urlList.count() > maxNumber) {
return false;
}
}
return true;
}
bool KFileItemActionsPrivate::checkTypesMatch(const KConfigGroup &cfg) const
{
QStringList types = cfg.readXdgListEntry("MimeType");
if (types.isEmpty()) {
types = cfg.readEntry("ServiceTypes", QStringList());
types.removeAll(QStringLiteral("KonqPopupMenu/Plugin"));
if (types.isEmpty()) {
return false;
}
}
const QStringList excludeTypes = cfg.readEntry("ExcludeServiceTypes", QStringList());
const KFileItemList items = m_props.items();
return std::all_of(items.constBegin(), items.constEnd(), [&types, &excludeTypes](const KFileItem &i) {
return mimeTypeListContains(types, i) && !mimeTypeListContains(excludeTypes, i);
});
}
KFileItemActionsPrivate::ServiceActionInfo
KFileItemActionsPrivate::addServiceActionsTo(QMenu *mainMenu, const QList<QAction *> &additionalActions, const QStringList &excludeList)
{
const KFileItemList items = 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();
KIO::PopupServices s;
// 2 - Look for "servicemenus" bindings (user-defined services)
// first check the .directory if this is a directory
const bool isSingleLocal = items.count() == 1 && isLocal;
if (m_props.isDirectory() && isSingleLocal) {
const 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 += desktopFile.actions();
}
}
}
const KConfigGroup showGroup = m_config.group(QStringLiteral("Show"));
const QMimeDatabase db;
const QStringList files = serviceMenuFilePaths();
for (const QString &file : files) {
const KDesktopFile desktopFile(file);
const KConfigGroup cfg = desktopFile.desktopGroup();
if (!shouldDisplayServiceMenu(cfg, protocol)) {
continue;
}
const QList<KDesktopFileAction> actions = desktopFile.actions();
if (!actions.isEmpty()) {
if (!checkTypesMatch(cfg)) {
continue;
}
const QString priority = cfg.readEntry("X-KDE-Priority");
const QString submenuName = cfg.readEntry("X-KDE-Submenu");
ServiceList &list = s.selectList(priority, submenuName);
std::copy_if(actions.cbegin(), actions.cend(), std::back_inserter(list), [&excludeList, &showGroup](const KDesktopFileAction &srvAction) {
return showGroup.readEntry(srvAction.actionsKey(), true) && !excludeList.contains(srvAction.actionsKey());
});
}
}
QMenu *actionMenu = mainMenu;
int userItemCount = 0;
if (s.user.count() + s.userSubmenus.count() + s.userPriority.count() + s.userPrioritySubmenus.count() + additionalActions.count() > 3) {
// we have more than three items, so let's make a submenu
actionMenu = new QMenu(i18nc("@title:menu", "&Actions"), mainMenu);
actionMenu->setIcon(QIcon::fromTheme(QStringLiteral("view-more-symbolic")));
actionMenu->menuAction()->setObjectName(QStringLiteral("actions_submenu")); // for the unittest
mainMenu->addMenu(actionMenu);
}
userItemCount += additionalActions.count();
for (QAction *action : additionalActions) {
actionMenu->addAction(action);
}
userItemCount += insertServicesSubmenus(s.userPrioritySubmenus, actionMenu);
userItemCount += insertServices(s.userPriority, actionMenu);
userItemCount += insertServicesSubmenus(s.userSubmenus, actionMenu);
userItemCount += insertServices(s.user, actionMenu);
userItemCount += insertServicesSubmenus(s.userToplevelSubmenus, mainMenu);
userItemCount += insertServices(s.userToplevel, mainMenu);
return {userItemCount, actionMenu};
}
int KFileItemActionsPrivate::addPluginActionsTo(QMenu *mainMenu, QMenu *actionsMenu, const QStringList &excludeList)
{
QString commonMimeType = m_props.mimeType();
if (commonMimeType.isEmpty() && m_props.isFile()) {
commonMimeType = QStringLiteral("application/octet-stream");
}
int itemCount = 0;
const KConfigGroup showGroup = m_config.group(QStringLiteral("Show"));
const QMimeDatabase db;
const auto jsonPlugins = KPluginMetaData::findPlugins(QStringLiteral("kf6/kfileitemaction"), [&db, commonMimeType](const KPluginMetaData &metaData) {
auto mimeType = db.mimeTypeForName(commonMimeType);
const QStringList list = metaData.mimeTypes();
return std::any_of(list.constBegin(), list.constEnd(), [mimeType](const QString &supportedMimeType) {
return mimeType.inherits(supportedMimeType);
});
});
for (const auto &jsonMetadata : jsonPlugins) {
// The plugin has been disabled
const QString pluginId = jsonMetadata.pluginId();
if (!showGroup.readEntry(pluginId, true) || excludeList.contains(pluginId)) {
continue;
}
KAbstractFileItemActionPlugin *abstractPlugin = m_loadedPlugins.value(pluginId);
if (!abstractPlugin) {
abstractPlugin = KPluginFactory::instantiatePlugin<KAbstractFileItemActionPlugin>(jsonMetadata, this).plugin;
m_loadedPlugins.insert(pluginId, abstractPlugin);
}
if (abstractPlugin) {
connect(abstractPlugin, &KAbstractFileItemActionPlugin::error, q, &KFileItemActions::error);
const QList<QAction *> actions = abstractPlugin->actions(m_props, m_parentWidget);
itemCount += actions.count();
if (jsonMetadata.value(QStringLiteral("X-KDE-Show-In-Submenu"), false)) {
actionsMenu->addActions(actions);
} else {
mainMenu->addActions(actions);
}
}
}
return itemCount;
}
KService::List KFileItemActionsPrivate::associatedApplications(const QStringList &mimeTypeList, const QStringList &excludedDesktopEntryNames)
{
if (!KAuthorized::authorizeAction(QStringLiteral("openwith")) || mimeTypeList.isEmpty()) {
return KService::List();
}
KService::List firstOffers = KApplicationTrader::queryByMimeType(mimeTypeList.first(), [excludedDesktopEntryNames](const KService::Ptr &service) {
return !excludedDesktopEntryNames.contains(service->desktopEntryName());
});
QList<KFileItemActionsPrivate::ServiceRank> rankings;
QStringList serviceList;
// This section does two things. First, it determines which services are common to all the given MIME types.
// 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 MIME type
KService::List offers = KApplicationTrader::queryByMimeType(mimeTypeList[j], [excludedDesktopEntryNames](const KService::Ptr &service) {
return !excludedDesktopEntryNames.contains(service->desktopEntryName());
});
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 MIME types
}
// Remove services which supported the previous MIME types 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 MIME types
if (rankings.isEmpty()) {
return KService::List();
}
}
std::sort(rankings.begin(), rankings.end(), KFileItemActionsPrivate::lessRank);
KService::List result;
result.reserve(rankings.size());
for (const KFileItemActionsPrivate::ServiceRank &tempRank : std::as_const(rankings)) {
result << tempRank.service;
}
return result;
}
void KFileItemActionsPrivate::insertOpenWithActionsTo(QAction *before, QMenu *topMenu, const QStringList &excludedDesktopEntryNames)
{
if (!KAuthorized::authorizeAction(QStringLiteral("openwith"))) {
return;
}
// TODO Overload with excludedDesktopEntryNames, but this method in public API and will be handled in a new MR
KService::List offers = associatedApplications(m_mimeTypeList, excludedDesktopEntryNames);
//// Ok, we have everything, now insert
const KFileItemList items = m_props.items();
const KFileItem &firstItem = items.first();
const bool isLocal = firstItem.url().isLocalFile();
const bool isDir = m_props.isDirectory();
// "Open With..." for folders is really not very useful, especially for remote folders.
// (media:/something, or trash:/, or ftp://...).
// Don't show "open with" actions for remote dirs only
if (isDir && !isLocal) {
return;
}
const auto makeOpenWithAction = [this, isDir] {
auto action = new QAction(this);
action->setText(isDir ? i18nc("@action:inmenu", "&Open Folder With…") : i18nc("@action:inmenu", "&Open With…"));
action->setIcon(QIcon::fromTheme(QStringLiteral("system-run")));
action->setObjectName(QStringLiteral("openwith_browse")); // For the unittest
return action;
};
#ifdef WITH_QTDBUS
if (KSandbox::isInside() && !m_fileOpenList.isEmpty()) {
auto openWithAction = makeOpenWithAction();
QObject::connect(openWithAction, &QAction::triggered, this, [this] {
const auto &items = m_fileOpenList;
for (const auto &fileItem : items) {
QDBusMessage message = QDBusMessage::createMethodCall(QLatin1String("org.freedesktop.portal.Desktop"),
QLatin1String("/org/freedesktop/portal/desktop"),
QLatin1String("org.freedesktop.portal.OpenURI"),
QLatin1String("OpenURI"));
message << QString() << fileItem.url() << QVariantMap{};
QDBusConnection::sessionBus().asyncCall(message);
}
});
topMenu->insertAction(before, openWithAction);
return;
}
if (KSandbox::isInside()) {
return;
}
#endif
QStringList serviceIdList = listPreferredServiceIds(m_mimeTypeList, excludedDesktopEntryNames);
// When selecting files with multiple MIME types, offer either "open with <app for all>"
// or a generic <open> (if there are any apps associated).
if (m_mimeTypeList.count() > 1 && !serviceIdList.isEmpty()
&& !(serviceIdList.count() == 1 && serviceIdList.first().isEmpty())) { // empty means "no apps associated"
QAction *runAct = new QAction(this);
if (serviceIdList.count() == 1) {
const KService::Ptr app = preferredService(m_mimeTypeList.first(), excludedDesktopEntryNames);
runAct->setText(isDir ? i18n("&Open folder with %1", app->name()) : 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 {
runAct->setText(i18n("&Open"));
}
QObject::connect(runAct, &QAction::triggered, this, &KFileItemActionsPrivate::slotRunPreferredApplications);
topMenu->insertAction(before, runAct);
m_fileOpenList = m_props.items();
}
auto openWithAct = makeOpenWithAction();
QObject::connect(openWithAct, &QAction::triggered, this, &KFileItemActionsPrivate::slotOpenWithDialog);
if (!offers.isEmpty()) {
// Show the top app inline for files, but not folders
if (!isDir) {
QAction *act = createAppAction(offers.takeFirst(), true);
topMenu->insertAction(before, act);
}
// If there are still more apps, show them in a sub-menu
if (!offers.isEmpty()) { // submenu 'open with'
QMenu *subMenu = new QMenu(isDir ? i18nc("@title:menu", "&Open Folder With") : i18nc("@title:menu", "&Open With"), topMenu);
subMenu->setIcon(QIcon::fromTheme(QStringLiteral("system-run")));
subMenu->menuAction()->setObjectName(QStringLiteral("openWith_submenu")); // For the unittest
// Add other apps to the sub-menu
for (const KServicePtr &service : std::as_const(offers)) {
QAction *act = createAppAction(service, false);
subMenu->addAction(act);
}
subMenu->addSeparator();
openWithAct->setText(i18nc("@action:inmenu Open With", "&Other Application…"));
subMenu->addAction(openWithAct);
topMenu->insertMenu(before, subMenu);
} else { // No other apps
topMenu->insertAction(before, openWithAct);
}
} else { // no app offers -> Open With...
openWithAct->setIcon(QIcon::fromTheme(QStringLiteral("system-run")));
openWithAct->setObjectName(QStringLiteral("openwith")); // For the unittest
topMenu->insertAction(before, openWithAct);
}
if (m_props.mimeType() == QLatin1String("application/x-desktop")) {
const QString path = firstItem.localPath();
const ServiceList services = KDesktopFile(path).actions();
for (const KDesktopFileAction &serviceAction : services) {
QAction *action = new QAction(this);
action->setText(serviceAction.name());
action->setIcon(QIcon::fromTheme(serviceAction.icon()));
connect(action, &QAction::triggered, this, [serviceAction] {
if (KAuthorized::authorizeAction(serviceAction.name())) {
auto *job = new KIO::ApplicationLauncherJob(serviceAction);
job->setUiDelegate(new KDialogJobUiDelegate(KJobUiDelegate::AutoHandlingEnabled, nullptr));
job->start();
}
});
topMenu->addAction(action);
}
}
topMenu->insertSeparator(before);
}
QStringList KFileItemActionsPrivate::serviceMenuFilePaths()
{
QStringList filePaths;
std::set<QString> uniqueFileNames;
// Load servicemenus from new install location
const QStringList paths =
QStandardPaths::locateAll(QStandardPaths::GenericDataLocation, QStringLiteral("kio/servicemenus"), QStandardPaths::LocateDirectory);
QStringList fromDisk = KFileUtils::findAllUniqueFiles(paths, QStringList(QStringLiteral("*.desktop")));
// Also search in kservices5 for compatibility with older existing files
const QStringList legacyPaths =
QStandardPaths::locateAll(QStandardPaths::GenericDataLocation, QStringLiteral("kservices5"), QStandardPaths::LocateDirectory);
const QStringList legacyFiles = KFileUtils::findAllUniqueFiles(legacyPaths, QStringList(QStringLiteral("*.desktop")));
for (const QString &path : legacyFiles) {
KDesktopFile file(path);
const QStringList serviceTypes = file.desktopGroup().readEntry("ServiceTypes", QStringList());
if (serviceTypes.contains(QStringLiteral("KonqPopupMenu/Plugin"))) {
fromDisk << path;
}
}
for (const QString &fileFromDisk : std::as_const(fromDisk)) {
if (auto [_, inserted] = uniqueFileNames.insert(fileFromDisk.split(QLatin1Char('/')).last()); inserted) {
filePaths << fileFromDisk;
}
}
return filePaths;
}
void KFileItemActions::setParentWidget(QWidget *widget)
{
d->m_parentWidget = widget;
}
#include "moc_kfileitemactions.cpp"
#include "moc_kfileitemactions_p.cpp"
@@ -0,0 +1,174 @@
/*
This file is part of the KDE project
SPDX-FileCopyrightText: 1998-2009 David Faure <faure@kde.org>
SPDX-License-Identifier: LGPL-2.0-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
*/
#ifndef KFILEITEMACTIONS_H
#define KFILEITEMACTIONS_H
#include "kiowidgets_export.h"
#include <KService>
#include <kfileitem.h>
#include <memory>
class KFileItemListProperties;
class QAction;
class QMenu;
class KFileItemActionsPrivate;
/**
* @class KFileItemActions kfileitemactions.h <KFileItemActions>
*
* This class creates and handles the actions for a url (or urls) in a popupmenu.
*
* This includes:
* @li "open with <application>" actions, but also
* @li user-defined actions for a .desktop file, defined in the file itself (see the desktop entry standard)
* @li servicemenus actions, defined in .desktop files and selected based on the MIME type of the url
*
* KFileItemActions respects Kiosk-based restrictions (see the KAuthorized
* namespace in the KConfig framework). In particular, the "action/openwith"
* action is checked when determining actions for opening files (see
* addOpenWithActionsTo()) and service-specific actions are checked before
* adding service actions to a menu (see addServiceActionsTo()).
*
* For user-defined actions in a .desktop file, the "X-KDE-AuthorizeAction" key
* can be used to determine which actions are checked before the user-defined
* action is allowed. The action is ignored if any of the listed actions are
* not authorized.
*
* @note: The builtin services like mount/unmount for old-style device desktop
* files (which mainly concerns CDROM and Floppy drives) have been deprecated
* since 5.82; those menu entries were hidden long before that, since the FSDevice
* .desktop template file hadn't been installed for quite a while.
*
*/
class KIOWIDGETS_EXPORT KFileItemActions : public QObject
{
Q_OBJECT
public:
/**
* Creates a KFileItemActions instance.
* Note that this instance must stay alive for at least as long as the popupmenu;
* it has the slots for the actions created by addOpenWithActionsTo/addServiceActionsTo.
*/
KFileItemActions(QObject *parent = nullptr);
/**
* Destructor
*/
~KFileItemActions() override;
/**
* Sets all the data for the next instance of the popupmenu.
* @see KFileItemListProperties
*/
void setItemListProperties(const KFileItemListProperties &itemList);
/**
* Set the parent widget for any dialogs being shown.
*
* This should normally be your mainwindow, not a popup menu,
* so that it still exists even after the popup is closed
* (e.g. error message from KRun) and so that QAction::setStatusTip
* can find a statusbar, too.
*/
void setParentWidget(QWidget *widget);
/**
* Generates the "Open With <Application>" actions, and inserts them in @p menu,
* before action @p before. If @p before is nullptr or doesn't exist in the menu
* the actions will be appended to the menu.
*
* All actions are created as children of the menu.
*
* No actions will be added if the "openwith" Kiosk action is not authorized
* (see KAuthorized::authorize()).
*
* @param before the "open with" actions will be inserted before this action; if this action
* is nullptr or isn't available in @p topMenu, the "open with" actions will be appended
* @param menu the QMenu where the actions will be added
* @param excludedDesktopEntryNames list of desktop entry names that will not be shown
*
* @since 5.82
*/
void insertOpenWithActionsTo(QAction *before, QMenu *topMenu, const QStringList &excludedDesktopEntryNames);
/**
* Returns the applications associated with all the given MIME types.
*
* This is basically a KApplicationTrader::query, but it supports multiple MIME types, and
* also cleans up "apparent" duplicates, such as different versions of the same
* application installed in parallel.
*
* The list is sorted according to the user preferences for the given MIME type(s).
* In case multiple MIME types appear in the URL list, the logic is:
* applications that on average appear earlier on the associated applications
* list for the given MIME types also appear earlier on the final applications list.
*
* Note that for a single MIME type there is no need to use this, you should use
* KApplicationTrader instead, e.g. query() or preferredService().
*
* This will return an empty list if the "openwith" Kiosk action is not
* authorized (see @c KAuthorized::authorize()).
*
* @param mimeTypeList the MIME types
* @return the sorted list of services.
* @since 5.83
*/
static KService::List associatedApplications(const QStringList &mimeTypeList);
enum class MenuActionSource {
Services = 0x1, ///< Add user defined actions and servicemenu actions (this used to include builtin
///< actions, which have been deprecated since 5.82 see class API documentation)
Plugins = 0x2, ///< Add actions implemented by plugins. See KAbstractFileItemActionPlugin base class.
All = Services | Plugins,
};
Q_DECLARE_FLAGS(MenuActionSources, MenuActionSource)
/**
* This methods adds additional actions to the menu.
* @param menu Menu to which the actions/submenus will be added.
* @param sources sources from which the actions should be fetched. By default all sources are used.
* @param additionalActions additional actions that should be added to the "Actions" submenu or
* top level menu if there are less than three entries in total.
* @param excludeList list of action names or plugin ids that should be excluded
* @since 5.77
*/
void addActionsTo(QMenu *menu,
MenuActionSources sources = MenuActionSource::All,
const QList<QAction *> &additionalActions = {},
const QStringList &excludeList = {});
Q_SIGNALS:
/**
* Emitted before the "Open With" dialog is shown
* This is used e.g in folderview to close the folder peek popups on invoking the "Open With" menu action
* @since 4.8.2
*/
void openWithDialogAboutToBeShown();
/**
* Forwards the errors from the KAbstractFileItemActionPlugin instances
* @since 5.82
*/
void error(const QString &errorMessage);
public Q_SLOTS:
/**
* Slot used to execute a list of files in their respective preferred application.
* @param fileOpenList the list of KFileItems to open.
* @since 5.83
*/
void runPreferredApplications(const KFileItemList &fileOpenList);
private:
std::unique_ptr<KFileItemActionsPrivate> const d;
friend class KFileItemActionsPrivate;
};
Q_DECLARE_OPERATORS_FOR_FLAGS(KFileItemActions::MenuActionSources)
#endif /* KFILEITEMACTIONS_H */
@@ -0,0 +1,99 @@
/*
This file is part of the KDE project
SPDX-FileCopyrightText: 1998-2009 David Faure <faure@kde.org>
SPDX-License-Identifier: LGPL-2.0-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
*/
#ifndef KFILEITEMACTIONS_P_H
#define KFILEITEMACTIONS_P_H
#include "kabstractfileitemactionplugin.h"
#include <KConfig>
#include <KDesktopFileAction>
#include <KService>
#include <kfileitem.h>
#include <kfileitemlistproperties.h>
#include <QActionGroup>
#include <QObject>
class KFileItemActions;
typedef QList<KDesktopFileAction> ServiceList;
class KFileItemActionsPrivate : public QObject
{
Q_OBJECT
friend class KFileItemActions;
public:
explicit KFileItemActionsPrivate(KFileItemActions *qq);
~KFileItemActionsPrivate() override;
int insertServicesSubmenus(const QMap<QString, ServiceList> &list, QMenu *menu);
int insertServices(const ServiceList &list, QMenu *menu);
// For "open with"
KService::List associatedApplications();
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 QStringList &excludedDesktopEntryNames);
struct ServiceActionInfo {
int userItemCount = 0;
QMenu *menu = nullptr;
};
ServiceActionInfo addServiceActionsTo(QMenu *mainMenu, const QList<QAction *> &additionalActions, const QStringList &excludeList);
int addPluginActionsTo(QMenu *mainMenu, QMenu *actionsMenu, const QStringList &excludeList);
void insertOpenWithActionsTo(QAction *before, QMenu *topMenu, const QStringList &excludedDesktopEntryNames);
static KService::List associatedApplications(const QStringList &mimeTypeList, const QStringList &excludedDesktopEntryNames);
QStringList serviceMenuFilePaths();
public Q_SLOTS:
void slotRunPreferredApplications();
private:
void openWithByMime(const KFileItemList &fileItems);
// Utility function which returns true if the service menu should be displayed
bool shouldDisplayServiceMenu(const KConfigGroup &cfg, const QString &protocol) const;
// Utility functions which returns true if the types for the service are set and the exclude types are not contained
bool checkTypesMatch(const KConfigGroup &cfg) const;
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;
KFileItemList m_fileOpenList;
QActionGroup m_executeServiceActionGroup;
QActionGroup m_runApplicationActionGroup;
QWidget *m_parentWidget;
KConfig m_config;
QHash<QString, KAbstractFileItemActionPlugin *> m_loadedPlugins;
};
Q_DECLARE_METATYPE(KService::Ptr)
Q_DECLARE_METATYPE(KServiceAction)
#endif /* KFILEITEMACTIONS_P_H */
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,419 @@
/*
This file is part of the KDE project
SPDX-FileCopyrightText: 2006-2007, 2008 Fredrik Höglund <fredrik@kde.org>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#ifndef KFILEITEMDELEGATE_H
#define KFILEITEMDELEGATE_H
#include "kiowidgets_export.h"
#include <QAbstractItemDelegate>
#include <QTextOption>
#include <memory>
class QAbstractItemModel;
class QAbstractItemView;
class QHelpEvent;
class QModelIndex;
class QPainter;
/**
* @class KFileItemDelegate kfileitemdelegate.h <KFileItemDelegate>
*
* KFileItemDelegate is intended to be used to provide a KDE file system
* view, when using one of the standard item views in Qt with KDirModel.
*
* While primarily intended to be used with KDirModel, it uses
* Qt::DecorationRole and Qt::DisplayRole for the icons and text labels,
* just like QItemDelegate, and can thus be used with any standard model.
*
* When used with KDirModel however, KFileItemDelegate can change the way
* the display and/or decoration roles are drawn, based on properties
* of the file items. For example, if the file item is a symbolic link,
* it will use an italic font to draw the file name.
*
* KFileItemDelegate also supports showing additional information about
* the file items below the icon labels.
*
* Which information should be shown, if any, is controlled by the
* @ref information property, which is a list that can be set by calling
* setShowInformation(), and read by calling showInformation().
* By default this list is empty.
*
* To use KFileItemDelegate, instantiate an object from the delegate,
* and call setItemDelegate() in one of the standard item views in Qt:
*
* @code
* QListView *listview = new QListView(this);
* KFileItemDelegate *delegate = new KFileItemDelegate(this);
* listview->setItemDelegate(delegate);
* @endcode
*/
class KIOWIDGETS_EXPORT KFileItemDelegate : public QAbstractItemDelegate
{
Q_OBJECT
/**
* This property holds which additional information (if any) should be shown below
* items in icon views.
*
* Access functions:
* @li void setShownformation(InformationList information)
* @li InformationList showInformation() const
*/
Q_PROPERTY(InformationList information READ showInformation WRITE setShowInformation)
/**
* This property holds the color used for the text shadow.
*
* The alpha value in the color determines the opacity of the shadow.
* Shadows are only rendered when the alpha value is non-zero.
* The default value for this property is Qt::transparent.
*
* Access functions:
* @li void setShadowColor(const QColor &color)
* @li QColor shadowColor() const
*/
Q_PROPERTY(QColor shadowColor READ shadowColor WRITE setShadowColor)
/**
* This property holds the horizontal and vertical offset for the text shadow.
* The default value for this property is (1, 1).
*
* Access functions:
* @li void setShadowOffset(const QPointF &offset)
* @li QPointF shadowOffset() const
*/
Q_PROPERTY(QPointF shadowOffset READ shadowOffset WRITE setShadowOffset)
/**
* This property holds the blur radius for the text shadow.
* The default value for this property is 2.
*
* Access functions:
* @li void setShadowBlur(qreal radius)
* @li qreal shadowBlur() const
*/
Q_PROPERTY(qreal shadowBlur READ shadowBlur WRITE setShadowBlur)
/**
* This property holds the maximum size that can be returned
* by KFileItemDelegate::sizeHint(). If the maximum size is empty,
* it will be ignored.
*/
Q_PROPERTY(QSize maximumSize READ maximumSize WRITE setMaximumSize)
/**
* This property determines whether a tooltip will be shown by the delegate
* if the display role is elided. This tooltip will contain the full display
* role information. The tooltip will only be shown if the Qt::ToolTipRole differs
* from Qt::DisplayRole, or if they match, showToolTipWhenElided flag is set and
* the display role information is elided.
*/
Q_PROPERTY(bool showToolTipWhenElided READ showToolTipWhenElided WRITE setShowToolTipWhenElided)
/**
* This property determines if there are KIO jobs on a destination URL visible, then
* they will have a small animation overlay displayed on them.
*/
Q_PROPERTY(bool jobTransfersVisible READ jobTransfersVisible WRITE setJobTransfersVisible)
public:
/**
* This enum defines the additional information that can be displayed below item
* labels in icon views.
*
* The information will only be shown for indexes for which the model provides
* a valid value for KDirModel::FileItemRole, and only when there's sufficient vertical
* space to display at least one line of the information, along with the display label.
*
* For the number of items to be shown for folders, the model must provide a valid
* value for KDirMode::ChildCountRole, in addition to KDirModel::FileItemRole.
*
* Note that KFileItemDelegate will not call KFileItem::determineMimeType() if
* KFileItem::isMimeTypeKnown() returns false, so if you want to display MIME types
* you should use a KMimeTypeResolver with the model and the view, to ensure that MIME
* types are resolved. If the MIME type isn't known, "Unknown" will be displayed until
* the MIME type has been successfully resolved.
*
* @see setShowInformation()
* @see showInformation()
* @see information
*/
enum Information {
NoInformation, ///< No additional information will be shown for items.
Size, ///< The file size for files, and the number of items for folders.
Permissions, ///< A UNIX permissions string, e.g.\ -rwxr-xr-x.
OctalPermissions, ///< The permissions as an octal value, e.g.\ 0644.
Owner, ///< The user name of the file owner, e.g.\ root
OwnerAndGroup, ///< The user and group that owns the file, e.g.\ root:root
CreationTime, ///< The date and time the file/folder was created.
ModificationTime, ///< The date and time the file/folder was last modified.
AccessTime, ///< The date and time the file/folder was last accessed.
MimeType, ///< The MIME type for the item, e.g.\ text/html.
FriendlyMimeType, ///< The descriptive name for the MIME type, e.g.\ HTML Document.
LinkDest, ///< The destination of a symbolic link. @since 4.5
LocalPathOrUrl, ///< The local path to the file or the URL in case it is not a local file. @since 4.5
Comment, ///< A simple comment that can be displayed to the user as is. @since 4.6
};
Q_ENUM(Information)
typedef QList<Information> InformationList;
/**
* Constructs a new KFileItemDelegate.
*
* @param parent The parent object for the delegate.
*/
explicit KFileItemDelegate(QObject *parent = nullptr);
/**
* Destroys the item delegate.
*/
~KFileItemDelegate() override;
/**
* Returns the nominal size for the item referred to by @p index, given the
* provided options.
*
* If the model provides a valid Qt::FontRole and/or Qt::TextAlignmentRole for the item,
* those will be used instead of the ones specified in the style options.
*
* This function is reimplemented from @ref QAbstractItemDelegate.
*
* @param option The style options that should be used when painting the item.
* @param index The index to the item for which to return the size hint.
*/
QSize sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const override;
/**
* Paints the item indicated by @p index, using @p painter.
*
* The item will be drawn in the rectangle specified by option.rect.
* The correct size for that rectangle can be obtained by calling
* @ref sizeHint().
*
* This function will use the following data values if the model provides
* them for the item, in place of the values in @p option:
*
* @li Qt::FontRole The font that should be used for the display role.
* @li Qt::TextAlignmentRole The alignment of the display role.
* @li Qt::ForegroundRole The text color for the display role.
* @li Qt::BackgroundRole The background color for the item.
*
* This function is reimplemented from @ref QAbstractItemDelegate.
*
* @param painter The painter with which to draw the item.
* @param option The style options that should be used when painting the item.
* @param index The index to the item that should be painted.
*/
void paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const override;
/**
* Reimplemented from @ref QAbstractItemDelegate.
*/
QWidget *createEditor(QWidget *parent, const QStyleOptionViewItem &option, const QModelIndex &index) const override;
/**
* Reimplemented from @ref QAbstractItemDelegate.
*/
bool editorEvent(QEvent *event, QAbstractItemModel *model, const QStyleOptionViewItem &option, const QModelIndex &index) override;
/**
* Reimplemented from @ref QAbstractItemDelegate.
*/
void setEditorData(QWidget *editor, const QModelIndex &index) const override;
/**
* Reimplemented from @ref QAbstractItemDelegate.
*/
void setModelData(QWidget *editor, QAbstractItemModel *model, const QModelIndex &index) const override;
/**
* Reimplemented from @ref QAbstractItemDelegate.
*/
void updateEditorGeometry(QWidget *editor, const QStyleOptionViewItem &option, const QModelIndex &index) const override;
/**
* Sets the list of information lines that are shown below the icon label in list views.
*
* You will typically construct the list like this:
* @code
* KFileItemDelegate::InformationList list;
* list << KFileItemDelegate::FriendlyMimeType << KFileItemDelegate::Size;
* delegate->setShowInformation(list);
* @endcode
*
* The information lines will be displayed in the list order.
* The delegate will first draw the item label, and then as many information
* lines as will fit in the available space.
*
* @param list A list of information items that should be shown
*/
void setShowInformation(const InformationList &list);
/**
* Sets a single information line that is shown below the icon label in list views.
*
* This is a convenience function for when you only want to show a single line
* of information.
*
* @param information The information that should be shown
*/
void setShowInformation(Information information);
/**
* Returns the file item information that should be shown below item labels in list views.
*/
InformationList showInformation() const;
/**
* Sets the color used for drawing the text shadow.
*
* To enable text shadows, set the shadow color to a non-transparent color.
* To disable text shadows, set the color to Qt::transparent.
*
* @see shadowColor()
*/
void setShadowColor(const QColor &color);
/**
* Returns the color used for the text shadow.
*
* @see setShadowColor()
*/
QColor shadowColor() const;
/**
* Sets the horizontal and vertical offset for the text shadow.
*
* @see shadowOffset()
*/
void setShadowOffset(const QPointF &offset);
/**
* Returns the offset used for the text shadow.
*
* @see setShadowOffset()
*/
QPointF shadowOffset() const;
/**
* Sets the blur radius for the text shadow.
*
* @see shadowBlur()
*/
void setShadowBlur(qreal radius);
/**
* Returns the blur radius for the text shadow.
*
* @see setShadowBlur()
*/
qreal shadowBlur() const;
/**
* Sets the maximum size for KFileItemDelegate::sizeHint().
*
* @see maximumSize()
*/
void setMaximumSize(const QSize &size);
/**
* Returns the maximum size for KFileItemDelegate::sizeHint().
*
* @see setMaximumSize()
*/
QSize maximumSize() const;
/**
* Sets whether a tooltip should be shown if the display role is
* elided containing the full display role information.
*
* @note The tooltip will only be shown if the Qt::ToolTipRole differs
* from Qt::DisplayRole, or if they match, showToolTipWhenElided
* flag is set and the display role information is elided.
* @see showToolTipWhenElided()
*/
void setShowToolTipWhenElided(bool showToolTip);
/**
* Returns whether a tooltip should be shown if the display role
* is elided containing the full display role information.
*
* @note The tooltip will only be shown if the Qt::ToolTipRole differs
* from Qt::DisplayRole, or if they match, showToolTipWhenElided
* flag is set and the display role information is elided.
* @see setShowToolTipWhenElided()
*/
bool showToolTipWhenElided() const;
/**
* Returns the rectangle of the icon that is aligned inside the decoration
* rectangle.
*/
QRect iconRect(const QStyleOptionViewItem &option, const QModelIndex &index) const;
/**
* When the contents text needs to be wrapped, @p wrapMode strategy
* will be followed.
*
*/
void setWrapMode(QTextOption::WrapMode wrapMode);
/**
* Returns the wrapping strategy followed to show text when it needs
* wrapping.
*
*/
QTextOption::WrapMode wrapMode() const;
/**
* Enable/Disable the displaying of an animated overlay that is shown for any destination
* urls (in the view). When enabled, the animations (if any) will be drawn automatically.
*
* Only the files/folders that are visible and have jobs associated with them
* will display the animation.
* You would likely not want this enabled if you perform some kind of custom painting
* that takes up a whole item, and will just make this(and what you paint) look funky.
*
* Default is disabled.
*
* Note: The model (KDirModel) needs to have it's method called with the same
* value, when you make the call to this method.
*/
void setJobTransfersVisible(bool jobTransfersVisible);
/**
* Returns whether or not the displaying of job transfers is enabled.
* @see setJobTransfersVisible()
*/
bool jobTransfersVisible() const;
/**
* Reimplemented from @ref QAbstractItemDelegate.
*/
bool eventFilter(QObject *object, QEvent *event) override;
public Q_SLOTS:
/**
* Reimplemented from @ref QAbstractItemDelegate.
*/
bool helpEvent(QHelpEvent *event, QAbstractItemView *view, const QStyleOptionViewItem &option, const QModelIndex &index) override;
/**
* Returns the shape of the item as a region.
* The returned region can be used for precise hit testing of the item.
*/
QRegion shape(const QStyleOptionViewItem &option, const QModelIndex &index);
private:
class Private;
std::unique_ptr<Private> const d; /// @internal
Q_DISABLE_COPY(KFileItemDelegate)
};
#endif // KFILEITEMDELEGATE_H
@@ -0,0 +1,551 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>KFilePropsPluginWidget</class>
<widget class="QWidget" name="KFilePropsPluginWidget">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>1053</width>
<height>976</height>
</rect>
</property>
<layout class="QVBoxLayout" name="verticalLayout_3">
<item>
<layout class="QGridLayout" name="gridLayout" columnstretch="0,1">
<property name="horizontalSpacing">
<number>24</number>
</property>
<property name="verticalSpacing">
<number>10</number>
</property>
<item row="7" column="1">
<widget class="QWidget" name="sizeBtnWidget" native="true">
<layout class="QHBoxLayout" name="horizontalLayout">
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item>
<layout class="QHBoxLayout" name="sizeButtonsLayout">
<item>
<widget class="QPushButton" name="calculateSizeBtn">
<property name="text">
<string>Calculate</string>
</property>
<property name="icon">
<iconset theme="view-refresh"/>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="stopCalculateSizeBtn">
<property name="text">
<string>Stop</string>
</property>
<property name="icon">
<iconset theme="dialog-cancel"/>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="sizeDetailsBtn">
<property name="text">
<string>Explore in Filelight</string>
</property>
<property name="icon">
<iconset theme="filelight"/>
</property>
</widget>
</item>
<item>
<spacer name="horizontalSpacer_2">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
</layout>
</item>
</layout>
</widget>
</item>
<item row="14" column="1">
<widget class="QLabel" name="fsLabel">
<property name="text">
<string/>
</property>
<property name="textFormat">
<enum>Qt::PlainText</enum>
</property>
<property name="textInteractionFlags">
<set>Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse</set>
</property>
</widget>
</item>
<item row="0" column="0">
<layout class="QHBoxLayout" name="iconLayout">
<item>
<spacer name="horizontalSpacer">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="KIconButton" name="iconButton">
<property name="sizePolicy">
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="iconSize">
<number>48</number>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="iconLabel">
<property name="text">
<string/>
</property>
<property name="alignment">
<set>Qt::AlignCenter</set>
</property>
</widget>
</item>
</layout>
</item>
<item row="1" column="0" colspan="2">
<widget class="KSeparator" name="kseparator_2"/>
</item>
<item row="16" column="0">
<widget class="QLabel" name="mountSrcLabel_Left">
<property name="text">
<string>Mounted From:</string>
</property>
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
</widget>
</item>
<item row="11" column="1">
<widget class="QLabel" name="modifiedTimeLabel">
<property name="text">
<string/>
</property>
<property name="textInteractionFlags">
<set>Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse</set>
</property>
</widget>
</item>
<item row="5" column="1">
<widget class="KSqueezedTextLabel" name="locationLabel">
<property name="layoutDirection">
<enum>Qt::LeftToRight</enum>
</property>
<property name="text">
<string/>
</property>
<property name="textFormat">
<enum>Qt::PlainText</enum>
</property>
<property name="textInteractionFlags">
<set>Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse</set>
</property>
</widget>
</item>
<item row="2" column="1">
<layout class="QVBoxLayout" name="mimeTypeLayout">
<item>
<widget class="QLabel" name="mimeCommentLabel">
<property name="text">
<string/>
</property>
<property name="textFormat">
<enum>Qt::PlainText</enum>
</property>
<property name="textInteractionFlags">
<set>Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse</set>
</property>
</widget>
</item>
</layout>
</item>
<item row="9" column="0" colspan="2">
<widget class="KMessageWidget" name="symlinkTargetMessageWidget">
<property name="wordWrap">
<bool>true</bool>
</property>
<property name="messageType">
<enum>KMessageWidget::Error</enum>
</property>
</widget>
</item>
<item row="6" column="1">
<widget class="QLabel" name="sizeLabel">
<property name="text">
<string/>
</property>
<property name="textFormat">
<enum>Qt::PlainText</enum>
</property>
<property name="textInteractionFlags">
<set>Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse</set>
</property>
</widget>
</item>
<item row="17" column="0">
<widget class="QLabel" name="freespaceLabel">
<property name="text">
<string>Free Space:</string>
</property>
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
</widget>
</item>
<item row="8" column="1">
<layout class="QHBoxLayout" name="symlinkLayout">
<item>
<widget class="QLineEdit" name="symlinkTargetEdit">
<property name="text">
<string/>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="symlinkTargetOpenDir">
<property name="sizePolicy">
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string/>
</property>
<property name="icon">
<iconset theme="go-jump"/>
</property>
</widget>
</item>
</layout>
</item>
<item row="2" column="0">
<widget class="QLabel" name="typeLabel">
<property name="text">
<string>Type:</string>
</property>
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTop|Qt::AlignTrailing</set>
</property>
</widget>
</item>
<item row="16" column="1">
<widget class="QLabel" name="mountSrcLabel">
<property name="text">
<string/>
</property>
<property name="textFormat">
<enum>Qt::PlainText</enum>
</property>
<property name="textInteractionFlags">
<set>Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse</set>
</property>
</widget>
</item>
<item row="5" column="0">
<widget class="QLabel" name="locationLabel_Left">
<property name="text">
<string>Location:</string>
</property>
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
</widget>
</item>
<item row="10" column="1">
<widget class="QLabel" name="createdTimeLabel">
<property name="text">
<string/>
</property>
<property name="textInteractionFlags">
<set>Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse</set>
</property>
</widget>
</item>
<item row="6" column="0">
<widget class="QLabel" name="sizeLabelLeft">
<property name="text">
<string>Size:</string>
</property>
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTop|Qt::AlignTrailing</set>
</property>
</widget>
</item>
<item row="4" column="1">
<layout class="QHBoxLayout" name="defaultHandlerLayout">
<item>
<widget class="QLabel" name="defaultHandlerIcon">
<property name="text">
<string/>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="defaultHandlerLabel">
<property name="text">
<string/>
</property>
<property name="textFormat">
<enum>Qt::PlainText</enum>
</property>
</widget>
</item>
<item>
<spacer name="horizontalSpacer_3">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QPushButton" name="configureMimeBtn">
<property name="sizePolicy">
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>Change...</string>
</property>
<property name="icon">
<iconset theme="configure"/>
</property>
</widget>
</item>
</layout>
</item>
<item row="3" column="0">
<widget class="QLabel" name="contentLabel">
<property name="text">
<string>Content:</string>
</property>
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
</widget>
</item>
<item row="15" column="1">
<widget class="KSqueezedTextLabel" name="mountPointLabel">
<property name="text">
<string/>
</property>
<property name="textFormat">
<enum>Qt::PlainText</enum>
</property>
<property name="textInteractionFlags">
<set>Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse</set>
</property>
</widget>
</item>
<item row="8" column="0">
<widget class="QLabel" name="symlinkTargetLabel">
<property name="text">
<string>Points to:</string>
</property>
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
</widget>
</item>
<item row="11" column="0">
<widget class="QLabel" name="modifiedTimeLabel_Left">
<property name="text">
<string>Modified:</string>
</property>
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
</widget>
</item>
<item row="10" column="0">
<widget class="QLabel" name="createdTimeLabel_Left">
<property name="text">
<string>Created:</string>
</property>
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
</widget>
</item>
<item row="14" column="0">
<widget class="QLabel" name="fsLabel_Left">
<property name="text">
<string>File System:</string>
</property>
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
</widget>
</item>
<item row="0" column="1">
<layout class="QHBoxLayout" name="fileNameLayout">
<item>
<widget class="QLineEdit" name="fileNameLineEdit"/>
</item>
<item>
<widget class="QLabel" name="fileNameLabel">
<property name="text">
<string/>
</property>
<property name="textFormat">
<enum>Qt::PlainText</enum>
</property>
<property name="textInteractionFlags">
<set>Qt::LinksAccessibleByMouse|Qt::TextSelectableByMouse</set>
</property>
</widget>
</item>
</layout>
</item>
<item row="17" column="1">
<widget class="KCapacityBar" name="capacityBar">
<property name="text">
<string>Unknown size</string>
</property>
<property name="drawTextMode">
<enum>KCapacityBar::DrawTextOutline</enum>
</property>
</widget>
</item>
<item row="3" column="1">
<widget class="QLabel" name="magicMimeCommentLabel">
<property name="text">
<string/>
</property>
<property name="textFormat">
<enum>Qt::PlainText</enum>
</property>
</widget>
</item>
<item row="13" column="0" colspan="2">
<widget class="KSeparator" name="fsSeparator"/>
</item>
<item row="15" column="0">
<widget class="QLabel" name="mountPointLabel_Left">
<property name="text">
<string>Mounted On:</string>
</property>
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
</widget>
</item>
<item row="12" column="1">
<widget class="QLabel" name="accessTimeLabel">
<property name="text">
<string/>
</property>
<property name="textInteractionFlags">
<set>Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse</set>
</property>
</widget>
</item>
<item row="12" column="0">
<widget class="QLabel" name="accessTimeLabel_Left">
<property name="text">
<string>Accessed:</string>
</property>
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
</widget>
</item>
<item row="4" column="0">
<widget class="QLabel" name="defaultHandlerLabel_Left">
<property name="text">
<string>Open With:</string>
</property>
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
</widget>
</item>
</layout>
</item>
<item>
<spacer name="verticalSpacer">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>40</height>
</size>
</property>
</spacer>
</item>
</layout>
</widget>
<customwidgets>
<customwidget>
<class>KIconButton</class>
<extends>QPushButton</extends>
<header>kiconbutton.h</header>
</customwidget>
<customwidget>
<class>KCapacityBar</class>
<extends>QWidget</extends>
<header>kcapacitybar.h</header>
</customwidget>
<customwidget>
<class>KMessageWidget</class>
<extends>QFrame</extends>
<header>kmessagewidget.h</header>
</customwidget>
<customwidget>
<class>KSeparator</class>
<extends>QFrame</extends>
<header>kseparator.h</header>
</customwidget>
<customwidget>
<class>KSqueezedTextLabel</class>
<extends>QLabel</extends>
<header>ksqueezedtextlabel.h</header>
</customwidget>
</customwidgets>
<resources/>
<connections/>
</ui>
@@ -0,0 +1,68 @@
[Desktop Entry]
Type=ServiceType
X-KDE-ServiceType=KonqPopupMenu/Plugin
Comment=Plugin for the Konqueror Popup Menu
Comment[ar]=ملحق لقائمة منبثقة لكونيكور
Comment[ast]=Plugin pal menú emerxente de Konqueror
Comment[az]=Konqueror Popup Menyu üçün plaqin
Comment[be]=Убудова выплыўнога меню для Konqueror
Comment[be@latin]=Ubudova vyplyŭnoha mieniu dlia Konqueror
Comment[bg]=Приставка за изскачащо меню на Konqueror
Comment[bs]=Dodatak za Konqueror iskačući meni
Comment[ca]=Connector per al menú emergent del Konqueror
Comment[ca@valencia]=Connector per al menú emergent de Konqueror
Comment[cs]=Modul pro kontextovou nabídku Konqueroru
Comment[cy]=Mewnbillgyn ar gyfer y Dewislen Uwchbicio Konqueror
Comment[da]=Plugin til Konquerors pop-op-menu
Comment[de]=Modul für das Aufklappmenü in Konqueror
Comment[el]=Πρόσθετο για το αναδυόμενο μενού του Konqueror
Comment[en_GB]=Plugin for the Konqueror Popup Menu
Comment[eo]=Kromaĵo por la Ŝprucmenuo de Konqueror
Comment[es]=Complemento para el menú emergente de Konqueror
Comment[et]=Konquerori hüpikmenüü plugin
Comment[eu]=Konquerorren menu gainerakorrarentzako plugina
Comment[fa]=وصله برای فهرست بالاپر کانکرر
Comment[fi]=Konquerorin ponnahdusvalikkoliitännäinen
Comment[fr]=Module externe pour le menu contextuel de Konqueror
Comment[gd]=Plugan airson priob-chlàir-taice Konqueror
Comment[gl]=Complemento para o menú emerxente de Konqueror.
Comment[he]=תוסף לתפריט הקופץ של Konqueror
Comment[hu]=Bővítőmodul a Konqueror felbukkanó menühöz
Comment[ia]=Plugin pro le Menu de Popup de Konqueror
Comment[id]=Plugin untuk Menu Sembulan Konqueror
Comment[ie]=Plugin por li menú contextual de Konqueror
Comment[is]=Viðbót fyrir sprettivalmynd Konqueror
Comment[it]=Estensione per il menu a comparsa di Konqueror
Comment[ja]=Konqueror ポップアップメニューのプラグイン
Comment[ka]=Konqueror-ის ამომხტარი მენიუს გაფართოება
Comment[ko]=Konqueror 팝업 메뉴 플러그인
Comment[lg]=Ekyongerwako kya Menyu Eboneka eya Konqueror
Comment[lt]=Konqueror iškylančiojo meniu įskiepis
Comment[lv]=„Konqueror“ izlecošās izvēlnes spraudnis
Comment[my]=ကွန်ကွာယာပေါ့အပ်မီနူးအတွက် ပလပ်ဂင်
Comment[nb]=Programtillegg for dra-og-slipp i Konquerors sprettoppmeny.
Comment[nl]=Plugin voor Konqueror's contextmenu
Comment[nn]=Tillegg til sprettoppmenyen i Konqueror
Comment[pa]=ਕੋਨਕਿਉਰੋਰ ਪਾਪਅੱਪ ਮੇਨੂ ਲਈ ਪਲੱਗਇਨ
Comment[pl]=Wtyczka do menu (otwieranego przyciskiem myszy) Konquerora
Comment[pt]='Plugin' do Menu de Contexto do Konqueror
Comment[pt_BR]=Plugin do Konqueror para menus de contexto
Comment[ru]=Расширение контекстного меню Konqueror
Comment[sa]=Plugin for the Konqueror Popup Menu
Comment[sk]=Modul pre kontextovú ponuku Konquerora
Comment[sl]=Vstavek za Konquerorjev pojavni meni
Comment[sq]=Shtojcë për Menu Flluskë Konqueror-i
Comment[sr]=Прикључак за К‑освајачев искачући мени
Comment[sr@ijekavian]=Прикључак за К‑освајачев искачући мени
Comment[sr@ijekavianlatin]=Priključak za Kosvajačev iskačući meni
Comment[sr@latin]=Priključak za Kosvajačev iskačući meni
Comment[sv]=Insticksprogram Konquerors popupmeny
Comment[ta]=கான்கொரரின் மேல்தோன்றும் பட்டிக்கான செருகுநிரல்
Comment[tg]=Васлкунак барои феҳристи пайдошавандаи Konqueror
Comment[tok]=ni li pana e ilo kin tawa ilo Konqueror
Comment[tr]=Konqueror Açılır Menüsü Eklentisi
Comment[uk]=Додаток контекстного меню Konqueror
Comment[vi]=Phần cài cắm cho trình đơn bật lên của Konqueror
Comment[x-test]=xxPlugin for the Konqueror Popup Menuxx
Comment[zh_CN]=Konqueror 的弹出菜单插件
Comment[zh_TW]=Konqueror 彈出式選單的外掛程式
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,143 @@
/*
This file is part of the KDE libraries
SPDX-FileCopyrightText: 2000 David Faure <faure@kde.org>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#ifndef OPENWITHDIALOG_H
#define OPENWITHDIALOG_H
#include "kiowidgets_export.h"
#include <KService>
#include <QDialog>
#include <QUrl>
class KOpenWithDialogPrivate;
/**
* @class KOpenWithDialog kopenwithdialog.h <KOpenWithDialog>
*
* "Open With" dialog box.
*
* @note To let the user choose an application and run it immediately,
* use simpler KRun::displayOpenWithDialog().
*
* If the Kiosk "shell_access" action is not authorized (see
* KAuthorized::authorize()), arbitrary commands are not allowed; instead, the
* user must browse to and choose an executable.
*
* @author David Faure <faure@kde.org>
*/
class KIOWIDGETS_EXPORT KOpenWithDialog : public QDialog
{
Q_OBJECT
public:
/**
* Create a dialog that asks for a application to open a given
* URL(s) with.
*
* @param urls the URLs that should be opened. The list can be empty,
* if the dialog is used to choose an application but not for some particular URLs.
* @param parent parent widget
*/
explicit KOpenWithDialog(const QList<QUrl> &urls, QWidget *parent = nullptr);
/**
* Create a dialog that asks for a application to open a given
* URL(s) with.
*
* @param urls is the URL that should be opened
* @param text appears as a label on top of the entry box. Leave empty for default text (since 5.20).
* @param value is the initial value of the line
* @param parent parent widget
*/
KOpenWithDialog(const QList<QUrl> &urls, const QString &text, const QString &value, QWidget *parent = nullptr);
/**
* Create a dialog to select a service for a given MIME type.
* Note that this dialog doesn't apply to URLs.
*
* @param mimeType the MIME type we want to choose an application for.
* @param value is the initial value of the line
* @param parent parent widget
*/
KOpenWithDialog(const QString &mimeType, const QString &value, QWidget *parent = nullptr);
/**
* Create a dialog that asks for a application for opening a given
* URL (or more than one), when we already know the MIME type of the URL(s).
*
* @param urls is the URLs that should be opened
* @param mimeType the MIME type of the URL
* @param text appears as a label on top of the entry box.
* @param value is the initial value of the line
* @param parent parent widget
* @since 5.71
*/
KOpenWithDialog(const QList<QUrl> &urls, const QString &mimeType, const QString &text, const QString &value, QWidget *parent = nullptr);
/**
* Create a dialog to select an application
* Note that this dialog doesn't apply to URLs.
*
* @param parent parent widget
*/
KOpenWithDialog(QWidget *parent = nullptr);
/**
* Destructor
*/
~KOpenWithDialog() override;
/**
* @return the text the user entered
*/
QString text() const;
/**
* Hide the "Do not &close when command exits" Checkbox
*/
void hideNoCloseOnExit();
/**
* Hide the "Run in &terminal" Checkbox
*/
void hideRunInTerminal();
/**
* @return the chosen service in the application tree
* Can be null, if the user typed some text and didn't select a service.
*/
KService::Ptr service() const;
/**
* Set whether a new .desktop file should be created if the user selects an
* application for which no corresponding .desktop file can be found.
*
* Regardless of this setting a new .desktop file may still be created if
* the user has chosen to remember the file association.
*
* The default is false: no .desktop files are created.
*/
void setSaveNewApplications(bool b);
public Q_SLOTS: // TODO KDE5: move all those slots to the private class!
void slotSelected(const QString &_name, const QString &_exec);
void slotHighlighted(const QString &_name, const QString &_exec);
void slotTextChanged();
void slotTerminalToggled(bool);
protected Q_SLOTS:
/**
* Reimplemented from QDialog::accept()
*/
void accept() override;
private:
bool eventFilter(QObject *object, QEvent *event) override;
friend class KOpenWithDialogPrivate;
std::unique_ptr<KOpenWithDialogPrivate> const d;
Q_DISABLE_COPY(KOpenWithDialog)
};
#endif
@@ -0,0 +1,100 @@
/*
This file is part of the KDE libraries
SPDX-FileCopyrightText: 2000 David Faure <faure@kde.org>
SPDX-FileCopyrightText: 2007 Pino Toscano <pino@kde.org>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#ifndef OPENWITHDIALOG_P_H
#define OPENWITHDIALOG_P_H
#include <QAbstractItemModel>
#include <QSortFilterProxyModel>
#include <QTreeView>
#include <memory>
class KApplicationModelPrivate;
/**
* @internal
*/
class KApplicationModel : public QAbstractItemModel
{
Q_OBJECT
public:
explicit KApplicationModel(QObject *parent = nullptr);
~KApplicationModel() override;
bool canFetchMore(const QModelIndex &parent) const override;
int columnCount(const QModelIndex &parent = QModelIndex()) const override;
QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
void fetchMore(const QModelIndex &parent) override;
// Qt::ItemFlags flags(const QModelIndex &index) const override;
bool hasChildren(const QModelIndex &parent = QModelIndex()) const override;
QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override;
QModelIndex index(int row, int column, const QModelIndex &parent = QModelIndex()) const override;
QModelIndex parent(const QModelIndex &index) const override;
int rowCount(const QModelIndex &parent = QModelIndex()) const override;
QString entryPathFor(const QModelIndex &index) const;
QString execFor(const QModelIndex &index) const;
bool isDirectory(const QModelIndex &index) const;
void fetchAll(const QModelIndex &parent);
private:
friend class KApplicationModelPrivate;
std::unique_ptr<KApplicationModelPrivate> const d;
Q_DISABLE_COPY(KApplicationModel)
};
/**
* @internal
*/
class QTreeViewProxyFilter : public QSortFilterProxyModel
{
Q_OBJECT
public:
explicit QTreeViewProxyFilter(QObject *parent = nullptr);
bool filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const override;
};
class KApplicationViewPrivate;
/**
* @internal
*/
class KApplicationView : public QTreeView
{
Q_OBJECT
public:
explicit KApplicationView(QWidget *parent = nullptr);
~KApplicationView() override;
void setModels(KApplicationModel *model, QSortFilterProxyModel *proxyModel);
QSortFilterProxyModel *proxyModel();
bool isDirSel() const;
Q_SIGNALS:
void selected(const QString &_name, const QString &_exec);
void highlighted(const QString &_name, const QString &_exec);
protected Q_SLOTS:
void currentChanged(const QModelIndex &current, const QModelIndex &previous) override;
private Q_SLOTS:
void slotSelectionChanged(const QItemSelection &selected, const QItemSelection &deselected);
private:
friend class KApplicationViewPrivate;
std::unique_ptr<KApplicationViewPrivate> const d;
Q_DISABLE_COPY(KApplicationView)
};
#endif
@@ -0,0 +1,254 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>KPropertiesDesktopAdvBase</class>
<widget class="QWidget" name="KPropertiesDesktopAdvBase">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>371</width>
<height>539</height>
</rect>
</property>
<layout class="QVBoxLayout">
<item>
<widget class="QGroupBox" name="buttonGroup2">
<property name="title">
<string comment="@title:group Title of a group that lets the user choose options about the terminal when launching a program">Terminal</string>
</property>
<layout class="QGridLayout">
<item row="1" column="0" rowspan="2">
<spacer>
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeType">
<enum>QSizePolicy::Minimum</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item row="0" column="0" colspan="3">
<widget class="QCheckBox" name="terminalCheck">
<property name="whatsThis">
<string>Check this option if the application you want to run is a text mode application or if you want the information that is provided by the terminal emulator window.</string>
</property>
<property name="text">
<string>Run in terminal</string>
</property>
</widget>
</item>
<item row="2" column="1">
<widget class="QLabel" name="terminalEditLabel">
<property name="text">
<string>Terminal options:</string>
</property>
<property name="buddy">
<cstring>terminalEdit</cstring>
</property>
</widget>
</item>
<item row="1" column="1" colspan="2">
<widget class="QCheckBox" name="terminalCloseCheck">
<property name="whatsThis">
<string>Check this option if the text mode application offers relevant information on exit. Keeping the terminal emulator open allows you to retrieve this information.</string>
</property>
<property name="text">
<string>Do not close when command exits</string>
</property>
</widget>
</item>
<item row="2" column="2">
<widget class="QLineEdit" name="terminalEdit"/>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QGroupBox" name="buttonGroup2_2">
<property name="title">
<string comment="@title:group Title of a group that lets the user choose which user to use when launching a program">User</string>
</property>
<layout class="QGridLayout">
<item row="0" column="0" colspan="3">
<widget class="QCheckBox" name="suidCheck">
<property name="whatsThis">
<string>Check this option if you want to run this application with a different user id. Every process has a different user id associated with it. This id code determines file access and other permissions. The password of the user is required to use this option.</string>
</property>
<property name="text">
<string>Run as a different user</string>
</property>
</widget>
</item>
<item row="1" column="0">
<spacer>
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeType">
<enum>QSizePolicy::Minimum</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item row="1" column="1">
<widget class="QLabel" name="suidEditLabel">
<property name="whatsThis">
<string>Enter the user name you want to run the application as.</string>
</property>
<property name="text">
<string>Username:</string>
</property>
<property name="buddy">
<cstring>suidEdit</cstring>
</property>
</widget>
</item>
<item row="1" column="2">
<widget class="KLineEdit" name="suidEdit">
<property name="whatsThis">
<string>Enter the user name you want to run the application as here.</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QGroupBox" name="discreteGpuGroupBox">
<property name="title">
<string>Discrete GPU</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QCheckBox" name="discreteGpuCheck">
<property name="text">
<string>Run using dedicated graphics card</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QGroupBox" name="buttonGroup4">
<property name="title">
<string comment="@title:group Title of a group that lets the user choose options regarding program startup">Startup</string>
</property>
<layout class="QGridLayout">
<item row="0" column="0" colspan="2">
<widget class="QCheckBox" name="startupInfoCheck">
<property name="whatsThis">
<string>Check this option if you want to make clear that your application has started. This visual feedback may appear as a busy cursor or in the taskbar.</string>
</property>
<property name="text">
<string>Enable launch feedback</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
</layout>
</widget>
<customwidgets>
<customwidget>
<class>KLineEdit</class>
<extends>QLineEdit</extends>
<header>klineedit.h</header>
</customwidget>
</customwidgets>
<resources/>
<connections>
<connection>
<sender>terminalCheck</sender>
<signal>toggled(bool)</signal>
<receiver>terminalCloseCheck</receiver>
<slot>setEnabled(bool)</slot>
<hints>
<hint type="sourcelabel">
<x>47</x>
<y>66</y>
</hint>
<hint type="destinationlabel">
<x>56</x>
<y>98</y>
</hint>
</hints>
</connection>
<connection>
<sender>terminalCheck</sender>
<signal>toggled(bool)</signal>
<receiver>terminalEdit</receiver>
<slot>setEnabled(bool)</slot>
<hints>
<hint type="sourcelabel">
<x>197</x>
<y>66</y>
</hint>
<hint type="destinationlabel">
<x>373</x>
<y>136</y>
</hint>
</hints>
</connection>
<connection>
<sender>terminalCheck</sender>
<signal>toggled(bool)</signal>
<receiver>terminalEditLabel</receiver>
<slot>setEnabled(bool)</slot>
<hints>
<hint type="sourcelabel">
<x>115</x>
<y>48</y>
</hint>
<hint type="destinationlabel">
<x>115</x>
<y>125</y>
</hint>
</hints>
</connection>
<connection>
<sender>suidCheck</sender>
<signal>toggled(bool)</signal>
<receiver>suidEdit</receiver>
<slot>setEnabled(bool)</slot>
<hints>
<hint type="sourcelabel">
<x>27</x>
<y>232</y>
</hint>
<hint type="destinationlabel">
<x>122</x>
<y>278</y>
</hint>
</hints>
</connection>
<connection>
<sender>suidCheck</sender>
<signal>toggled(bool)</signal>
<receiver>suidEditLabel</receiver>
<slot>setEnabled(bool)</slot>
<hints>
<hint type="sourcelabel">
<x>92</x>
<y>234</y>
</hint>
<hint type="destinationlabel">
<x>78</x>
<y>278</y>
</hint>
</hints>
</connection>
</connections>
</ui>
@@ -0,0 +1,305 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>KPropertiesDesktopBase</class>
<widget class="QWidget" name="KPropertiesDesktopBase">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>365</width>
<height>415</height>
</rect>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<layout class="QFormLayout" name="formLayout">
<item row="0" column="0">
<widget class="QLabel" name="nameLabel">
<property name="whatsThis">
<string>Type the name you want to give to this application here. This application will appear under this name in the applications menu and in the panel.</string>
</property>
<property name="text">
<string>&amp;Name:</string>
</property>
<property name="buddy">
<cstring>nameEdit</cstring>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QLineEdit" name="nameEdit">
<property name="whatsThis">
<string>Type the name you want to give to this application here. This application will appear under this name in the applications menu and in the panel.</string>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QLabel" name="textLabel2">
<property name="whatsThis">
<string>Type the description of this application, based on its use, here. Examples: a dial up application (KPPP) would be &quot;Dial up tool&quot;.</string>
</property>
<property name="text">
<string>&amp;Description:</string>
</property>
<property name="buddy">
<cstring>genNameEdit</cstring>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QLineEdit" name="genNameEdit">
<property name="whatsThis">
<string>Type the description of this application, based on its use, here. Examples: a dial up application (KPPP) would be &quot;Dial up tool&quot;.</string>
</property>
</widget>
</item>
<item row="2" column="0">
<widget class="QLabel" name="textLabel3">
<property name="whatsThis">
<string>Type any comment you think is useful here.</string>
</property>
<property name="text">
<string>Comm&amp;ent:</string>
</property>
<property name="buddy">
<cstring>commentEdit</cstring>
</property>
</widget>
</item>
<item row="2" column="1">
<widget class="QLineEdit" name="commentEdit">
<property name="whatsThis">
<string>Type any comment you think is useful here.</string>
</property>
</widget>
</item>
<item row="3" column="0">
<widget class="QLabel" name="labelenvvars">
<property name="text">
<string>Environment Variables:</string>
</property>
<property name="buddy">
<cstring>envarsEdit</cstring>
</property>
</widget>
</item>
<item row="3" column="1">
<widget class="QLineEdit" name="envarsEdit"/>
</item>
<item row="4" column="0">
<widget class="QLabel" name="textLabel4">
<property name="whatsThis">
<string>Type the command to start this application here.</string>
</property>
<property name="text">
<string>Program:</string>
</property>
<property name="buddy">
<cstring>programEdit</cstring>
</property>
</widget>
</item>
<item row="4" column="1">
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<widget class="QLineEdit" name="programEdit">
<property name="whatsThis">
<string>Type the command to start this application here.</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="browseButton">
<property name="whatsThis">
<string>Click here to browse your file system in order to find the desired executable.</string>
</property>
<property name="text">
<string>&amp;Browse...</string>
</property>
</widget>
</item>
</layout>
</item>
<item row="5" column="0">
<widget class="QLabel" name="argumentsLabel">
<property name="whatsThis">
<string>Type the arguments to use when starting this application here.
You can have several placeholders which will be replaced with the actual values when the actual program is run:
%f - a single filename
%F - a list of files (for apps that can open several files at once)
%u - a single URL
%U - a list of URLs
%i - the icon of the .desktop file
%c - the name of the .desktop file
%k - the location of the .desktop file</string>
</property>
<property name="text">
<string>&amp;Arguments:</string>
</property>
<property name="buddy">
<cstring>argumentsEdit</cstring>
</property>
</widget>
</item>
<item row="5" column="1">
<widget class="QLineEdit" name="argumentsEdit">
<property name="whatsThis">
<string>Type the arguments to use when starting this application here.
You can have several placeholders which will be replaced with the actual values when the actual program is run:
%f - a single filename
%F - a list of files (for apps that can open several files at once)
%u - a single URL
%U - a list of URLs
%i - the icon of the .desktop file
%c - the name of the .desktop file
%k - the location of the .desktop file</string>
</property>
</widget>
</item>
<item row="6" column="0">
<widget class="QLabel" name="textLabel5">
<property name="whatsThis">
<string>Sets the working directory for your application.</string>
</property>
<property name="text">
<string>&amp;Work path:</string>
</property>
<property name="buddy">
<cstring>pathEdit</cstring>
</property>
</widget>
</item>
<item row="6" column="1">
<widget class="KUrlRequester" name="pathEdit">
<property name="whatsThis">
<string>Sets the working directory for your application.</string>
</property>
</widget>
</item>
</layout>
</item>
<item>
<widget class="KSeparator" name="kseparator"/>
</item>
<item>
<widget class="QLabel" name="textLabel7">
<property name="whatsThis">
<string>&lt;qt&gt;&lt;p&gt;This list should show the types of file that your application can handle. This list is organized by &lt;u&gt;mimetypes&lt;/u&gt;.&lt;/p&gt;
&lt;p&gt;MIME, Multipurpose Internet (e)Mail Extension, is a standard protocol for identifying the type of data based on filename extensions and correspondent &lt;u&gt;mimetypes&lt;/u&gt;. Example: the &quot;bmp&quot; part that comes after the dot in flower.bmp indicates that it is a specific kind of image, &lt;u&gt;image/x-bmp&lt;/u&gt;. To know which application should open each type of file, the system should be informed about the abilities of each application to handle these extensions and mimetypes.&lt;/p&gt;
&lt;p&gt;If you want to associate this application with one or more mimetypes that are not in this list, click on the button &lt;b&gt;Add&lt;/b&gt; below. If there are one or more filetypes that this application cannot handle, you may want to remove them from the list clicking on the button &lt;b&gt;Remove&lt;/b&gt; below.&lt;/p&gt;&lt;/qt&gt;</string>
</property>
<property name="text">
<string>&amp;Supported file types:</string>
</property>
<property name="buddy">
<cstring>filetypeList</cstring>
</property>
</widget>
</item>
<item>
<widget class="QTreeWidget" name="filetypeList">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Expanding">
<horstretch>0</horstretch>
<verstretch>1</verstretch>
</sizepolicy>
</property>
<property name="whatsThis">
<string>&lt;qt&gt;&lt;p&gt;This list should show the types of file that your application can handle. This list is organized by &lt;u&gt;mimetypes&lt;/u&gt;.&lt;/p&gt;
&lt;p&gt;MIME, Multipurpose Internet (e)Mail Extension, is a standard protocol for identifying the type of data based on filename extensions and correspondent &lt;u&gt;mimetypes&lt;/u&gt;. Example: the &quot;bmp&quot; part that comes after the dot in flower.bmp indicates that it is a specific kind of image, &lt;u&gt;image/x-bmp&lt;/u&gt;. To know which application should open each type of file, the system should be informed about the abilities of each application to handle these extensions and mimetypes.&lt;/p&gt;
&lt;p&gt;If you want to associate this application with one or more mimetypes that are not in this list, click on the button &lt;b&gt;Add&lt;/b&gt; below. If there are one or more filetypes that this application cannot handle, you may want to remove them from the list clicking on the button &lt;b&gt;Remove&lt;/b&gt; below.&lt;/p&gt;&lt;/qt&gt;</string>
</property>
<property name="rootIsDecorated">
<bool>false</bool>
</property>
<property name="sortingEnabled">
<bool>true</bool>
</property>
<property name="allColumnsShowFocus">
<bool>true</bool>
</property>
<column>
<property name="text">
<string>Mimetype</string>
</property>
</column>
<column>
<property name="text">
<string>Description</string>
</property>
</column>
</widget>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_2">
<item>
<widget class="QPushButton" name="addFiletypeButton">
<property name="whatsThis">
<string>Click on this button if you want to add a type of file (mimetype) that your application can handle.</string>
</property>
<property name="text">
<string>Add...</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="delFiletypeButton">
<property name="whatsThis">
<string>If you want to remove a type of file (mimetype) that your application cannot handle, select the mimetype in the list above and click on this button.</string>
</property>
<property name="text">
<string>Remove</string>
</property>
</widget>
</item>
<item>
<spacer>
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeType">
<enum>QSizePolicy::MinimumExpanding</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>70</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QPushButton" name="advancedButton">
<property name="whatsThis">
<string>Click here to modify the way this application will run, launch feedback, D-Bus options or to run it as a different user.</string>
</property>
<property name="text">
<string>Ad&amp;vanced Options</string>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</widget>
<customwidgets>
<customwidget>
<class>KUrlRequester</class>
<extends>QFrame</extends>
<header>kurlrequester.h</header>
<container>1</container>
</customwidget>
<customwidget>
<class>KSeparator</class>
<extends>QFrame</extends>
<header>kseparator.h</header>
<container>1</container>
</customwidget>
</customwidgets>
<resources/>
<connections/>
</ui>
@@ -0,0 +1,607 @@
/*
This file is part of the KDE project
SPDX-FileCopyrightText: 1998, 1999 Torben Weis <weis@kde.org>
SPDX-FileCopyrightText: 1999, 2000 Preston Brown <pbrown@kde.org>
SPDX-FileCopyrightText: 2000 Simon Hausmann <hausmann@kde.org>
SPDX-FileCopyrightText: 2000 David Faure <faure@kde.org>
SPDX-FileCopyrightText: 2003 Waldo Bastian <bastian@kde.org>
SPDX-FileCopyrightText: 2021 Ahmad Samir <a.samirh78@gmail.com>
SPDX-FileCopyrightText: 2022 Harald Sitter <sitter@kde.org>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
/*
* kpropertiesdialog.cpp
* View/Edit Properties of files, locally or remotely
*
* some FilePermissionsPropsPlugin-changes by
* Henner Zeller <zeller@think.de>
* some layout management by
* Bertrand Leconte <B.Leconte@mail.dotcom.fr>
* the rest of the layout management, bug fixes, adaptation to libkio,
* template feature by
* David Faure <faure@kde.org>
* More layout, cleanups, and fixes by
* Preston Brown <pbrown@kde.org>
* Plugin capability, cleanups and port to KDialog by
* Simon Hausmann <hausmann@kde.org>
* KDesktopPropsPlugin by
* Waldo Bastian <bastian@kde.org>
*/
#include "kpropertiesdialog.h"
#include "../utils_p.h"
#include "kio_widgets_debug.h"
#include "kpropertiesdialogbuiltin_p.h"
#include <config-kiowidgets.h>
#include <kacl.h>
#include <kio/global.h>
#include <kio/statjob.h>
#include <kioglobal_p.h>
#include <KJobWidgets>
#include <KLocalizedString>
#include <KPluginFactory>
#include <KPluginMetaData>
#include <qplatformdefs.h>
#include <QDebug>
#include <QDir>
#include <QLayout>
#include <QList>
#include <QMimeData>
#include <QMimeDatabase>
#include <QRegularExpression>
#include <QStandardPaths>
#include <QUrl>
#include <algorithm>
#include <functional>
#include <vector>
#ifdef Q_OS_WIN
#include <process.h>
#include <qt_windows.h>
#include <shellapi.h>
#ifdef __GNUC__
#warning TODO: port completely to win32
#endif
#endif
using namespace KDEPrivate;
constexpr 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 KPropertiesDialogPrivate
{
public:
explicit KPropertiesDialogPrivate(KPropertiesDialog *qq)
: q(qq)
{
}
~KPropertiesDialogPrivate()
{
// qDeleteAll deletes the pages in order, this prevents crashes when closing the dialog
qDeleteAll(m_pages);
}
/**
* Common initialization for all constructors
*/
void init();
/**
* Inserts all pages in the dialog.
*/
void insertPages();
void insertPlugin(KPropertiesDialogPlugin *plugin)
{
q->connect(plugin, &KPropertiesDialogPlugin::changed, plugin, [plugin]() {
plugin->setDirty();
});
m_pages.push_back(plugin);
}
KPropertiesDialog *const q;
bool m_aborted = false;
KPageWidgetItem *fileSharePageItem = nullptr;
KFilePropsPlugin *m_filePropsPlugin = nullptr;
KFilePermissionsPropsPlugin *m_permissionsPropsPlugin = nullptr;
KDesktopPropsPlugin *m_desktopPropsPlugin = nullptr;
KUrlPropsPlugin *m_urlPropsPlugin = nullptr;
/**
* 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 )
*/
std::vector<KPropertiesDialogPlugin *> m_pages;
};
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))
{
d->m_singleUrl = _url.adjusted(QUrl::StripTrailingSlash);
setWindowTitle(i18n("Properties for %1", KIO::decodeFileName(d->m_singleUrl.fileName())));
KIO::StatJob *job = KIO::stat(d->m_singleUrl);
KJobWidgets::setWindow(job, parent);
job->exec();
KIO::UDSEntry entry = job->statResult();
d->m_items.append(KFileItem(entry, d->m_singleUrl));
d->init();
}
KPropertiesDialog::KPropertiesDialog(const QList<QUrl> &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());
d->m_items.reserve(urls.size());
for (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());
SHELLEXECUTEINFOW execInfo;
memset(&execInfo, 0, sizeof(execInfo));
execInfo.cbSize = sizeof(execInfo);
execInfo.fMask = SEE_MASK_INVOKEIDLIST | SEE_MASK_NOCLOSEPROCESS | SEE_MASK_FLAG_NO_UI;
const QString verb(QLatin1String("properties"));
execInfo.lpVerb = (LPCWSTR)verb.utf16();
execInfo.lpFile = (LPCWSTR)path_.utf16();
return ShellExecuteExW(&execInfo);
}
#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 worker
// 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<QUrl> &urls, QWidget *parent, bool modal)
{
KPropertiesDialog *dlg = new KPropertiesDialog(urls, parent);
if (modal) {
dlg->exec();
} else {
dlg->show();
}
return true;
}
void KPropertiesDialogPrivate::init()
{
q->setFaceType(KPageDialog::Tabbed);
insertPages();
// Ensure users can't make it so small where things break
q->setMinimumSize(q->sizeHint());
}
void KPropertiesDialog::showFileSharingPage()
{
if (d->fileSharePageItem) {
setCurrentPage(d->fileSharePageItem);
}
}
void KPropertiesDialog::setFileSharingPage(QWidget *page)
{
d->fileSharePageItem = addPage(page, i18nc("@title:tab", "Share"));
}
void KPropertiesDialog::setFileNameReadOnly(bool ro)
{
if (d->m_filePropsPlugin) {
d->m_filePropsPlugin->setFileNameReadOnly(ro);
}
if (d->m_urlPropsPlugin) {
d->m_urlPropsPlugin->setFileNameReadOnly(ro);
}
}
KPropertiesDialog::~KPropertiesDialog()
{
}
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
/* clang-format off */
return KFilePropsPlugin::supports(_items)
|| KFilePermissionsPropsPlugin::supports(_items)
|| KDesktopPropsPlugin::supports(_items)
|| KUrlPropsPlugin::supports(_items);
/* clang-format on */
}
void KPropertiesDialog::accept()
{
d->m_aborted = false;
auto acceptAndClose = [this]() {
Q_EMIT applied();
Q_EMIT propertiesClosed();
deleteLater(); // Somewhat like Qt::WA_DeleteOnClose would do.
KPageDialog::accept();
};
const bool isAnyDirty = std::any_of(d->m_pages.cbegin(), d->m_pages.cend(), [](const KPropertiesDialogPlugin *page) {
return page->isDirty();
});
if (!isAnyDirty) { // No point going further
acceptAndClose();
return;
}
// 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.
if (d->m_filePropsPlugin) {
d->m_filePropsPlugin->setDirty(true);
}
// Changes are applied in the following order:
// - KFilePropsPlugin changes, this is because in case of renaming an item or saving changes
// of a template or a .desktop file, the renaming or copying respectively, must be finished
// first, before applying the rest of the changes
// - KFilePermissionsPropsPlugin changes, e.g. if the item was read-only and was changed to
// read/write, this must be applied first for other changes to work
// - The rest of the changes from the other plugins/tabs
// - KFilePropsPlugin::postApplyChanges()
auto applyOtherChanges = [this, acceptAndClose]() {
Q_ASSERT(!d->m_filePropsPlugin->isDirty());
Q_ASSERT(!d->m_permissionsPropsPlugin->isDirty());
// Apply the changes for the rest of the plugins
for (auto *page : d->m_pages) {
if (d->m_aborted) {
break;
}
if (page->isDirty()) {
// qDebug() << "applying changes for " << page->metaObject()->className();
page->applyChanges();
}
/* else {
qDebug() << "skipping page " << page->metaObject()->className();
} */
}
if (!d->m_aborted && d->m_filePropsPlugin) {
d->m_filePropsPlugin->postApplyChanges();
}
if (!d->m_aborted) {
acceptAndClose();
} // Else, keep dialog open for user to fix the problem.
};
auto applyPermissionsChanges = [this, applyOtherChanges]() {
connect(d->m_permissionsPropsPlugin, &KFilePermissionsPropsPlugin::changesApplied, this, [applyOtherChanges]() {
applyOtherChanges();
});
d->m_permissionsPropsPlugin->applyChanges();
};
if (d->m_filePropsPlugin && d->m_filePropsPlugin->isDirty()) {
// changesApplied() is _not_ emitted if applying the changes was aborted
connect(d->m_filePropsPlugin, &KFilePropsPlugin::changesApplied, this, [this, applyPermissionsChanges, applyOtherChanges]() {
if (d->m_permissionsPropsPlugin && d->m_permissionsPropsPlugin->isDirty()) {
applyPermissionsChanges();
} else {
applyOtherChanges();
}
});
d->m_filePropsPlugin->applyChanges();
}
}
void KPropertiesDialog::reject()
{
Q_EMIT canceled();
Q_EMIT propertiesClosed();
deleteLater();
KPageDialog::reject();
}
void KPropertiesDialogPrivate::insertPages()
{
if (m_items.isEmpty()) {
return;
}
if (KFilePropsPlugin::supports(m_items)) {
m_filePropsPlugin = new KFilePropsPlugin(q);
insertPlugin(m_filePropsPlugin);
}
if (KFilePermissionsPropsPlugin::supports(m_items)) {
m_permissionsPropsPlugin = new KFilePermissionsPropsPlugin(q);
insertPlugin(m_permissionsPropsPlugin);
}
if (KChecksumsPlugin::supports(m_items)) {
KPropertiesDialogPlugin *p = new KChecksumsPlugin(q);
insertPlugin(p);
}
if (KDesktopPropsPlugin::supports(m_items)) {
m_desktopPropsPlugin = new KDesktopPropsPlugin(q);
insertPlugin(m_desktopPropsPlugin);
}
if (KUrlPropsPlugin::supports(m_items)) {
m_urlPropsPlugin = new KUrlPropsPlugin(q);
insertPlugin(m_urlPropsPlugin);
}
if (m_items.count() != 1) {
return;
}
const KFileItem item = m_items.first();
const QString mimetype = item.mimetype();
if (mimetype.isEmpty()) {
return;
}
const auto scheme = item.url().scheme();
const auto filter = [mimetype, scheme](const KPluginMetaData &metaData) {
const auto supportedProtocols = metaData.value(QStringLiteral("X-KDE-Protocols"), QStringList());
if (!supportedProtocols.isEmpty()) {
const auto none = std::none_of(supportedProtocols.cbegin(), supportedProtocols.cend(), [scheme](const auto &protocol) {
return !protocol.isEmpty() && protocol == scheme;
});
if (none) {
return false;
}
}
return metaData.mimeTypes().isEmpty() || metaData.supportsMimeType(mimetype);
};
const auto jsonPlugins = KPluginMetaData::findPlugins(QStringLiteral("kf6/propertiesdialog"), filter);
for (const auto &jsonMetadata : jsonPlugins) {
if (auto plugin = KPluginFactory::instantiatePlugin<KPropertiesDialogPlugin>(jsonMetadata, q).plugin) {
insertPlugin(plugin);
}
}
}
void KPropertiesDialog::updateUrl(const QUrl &_newUrl)
{
Q_ASSERT(d->m_items.count() == 1);
// qDebug() << "KPropertiesDialog::updateUrl (pre)" << _newUrl;
QUrl newUrl = _newUrl;
Q_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)
if (d->m_urlPropsPlugin) {
d->m_urlPropsPlugin->setDirty();
} else if (d->m_desktopPropsPlugin) {
d->m_desktopPropsPlugin->setDirty();
}
}
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(Utils::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(Utils::concatPaths(newUrl.path(), _name));
}
updateUrl(newUrl);
}
void KPropertiesDialog::abortApplying()
{
d->m_aborted = true;
}
#include "moc_kpropertiesdialog.cpp"
@@ -0,0 +1,328 @@
/*
This file is part of the KDE project
SPDX-FileCopyrightText: 1998, 1999 Torben Weis <weis@kde.org>
SPDX-FileCopyrightText: 1999, 2000 Preston Brown <pbrown@kde.org>
SPDX-FileCopyrightText: 2000 Simon Hausmann <hausmann@kde.org>
SPDX-FileCopyrightText: 2000 David Faure <faure@kde.org>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#ifndef KPROPERTIESDIALOG_H
#define KPROPERTIESDIALOG_H
#include <QString>
#include <QUrl>
#include "kiowidgets_export.h"
#include <kfileitem.h>
#include <KPageDialog>
#include <memory>
class KPropertiesDialogPrivate;
/**
* @class KPropertiesDialog kpropertiesdialog.h <KPropertiesDialog>
*
* The main properties dialog class.
* A Properties Dialog is a dialog which displays various information
* about a particular file or URL, or several files or URLs.
* This main class holds various related classes, which are instantiated in
* the form of tab entries in the tabbed dialog that this class provides.
* The various tabs themselves will let the user view, and sometimes change,
* information about the file or URL.
*
* \image html kpropertiesdialog.png "Example of KPropertiesDialog"
*
* The best way to display the properties dialog is to use showDialog().
* Otherwise, you should use (void)new KPropertiesDialog(...)
* It will take care of deleting itself when closed.
*
* If you are looking for more flexibility, see KFileMetaInfo and
* KFileMetaInfoWidget.
*
* This respects the "editfiletype", "run_desktop_files" and "shell_access"
* Kiosk action restrictions (see KAuthorized::authorize()).
*/
class KIOWIDGETS_EXPORT KPropertiesDialog : public KPageDialog
{
Q_OBJECT
public:
/**
* Determine whether there are any property pages available for the
* given file items.
* @param _items the list of items to check.
* @return true if there are any property pages, otherwise false.
*/
static bool canDisplay(const KFileItemList &_items);
/**
* Brings up a Properties dialog, as shown above.
* This is the normal constructor for
* file-manager type applications, where you have a KFileItem instance
* to work with. Normally you will use this
* method rather than the one below.
*
* @param item file item whose properties should be displayed.
* @param parent is the parent of the dialog widget.
*/
explicit KPropertiesDialog(const KFileItem &item, QWidget *parent = nullptr);
/**
* \overload
*
* You use this constructor for cases where you have a number of items,
* rather than a single item. Be careful which methods you use
* when passing a list of files or URLs, since some of them will only
* work on the first item in a list.
*
* @param _items list of file items whose properties should be displayed.
* @param parent is the parent of the dialog widget.
*/
explicit KPropertiesDialog(const KFileItemList &_items, QWidget *parent = nullptr);
/**
* Brings up a Properties dialog. Convenience constructor for
* non-file-manager applications, where you have a QUrl rather than a
* KFileItem or KFileItemList.
*
* @param url the URL whose properties should be displayed
* @param parent is the parent of the dialog widget.
*
* For local files with a known MIME type, simply create a KFileItem
* and pass it to the other constructor.
*/
explicit KPropertiesDialog(const QUrl &url, QWidget *parent = nullptr);
/**
* Brings up a Properties dialog. Convenience constructor for
* non-file-manager applications, where you have a list of QUrls rather
* than a KFileItemList.
*
* @param urls list of URLs whose properties should be displayed (must
* contain at least one non-empty URL)
* @param parent is the parent of the dialog widget.
*
* For local files with a known MIME type, simply create a KFileItemList
* and pass it to the other constructor.
*
* @since 5.10
*/
explicit KPropertiesDialog(const QList<QUrl> &urls, QWidget *parent = nullptr);
/**
* Creates a properties dialog for a new .desktop file (whose name
* is not known yet), based on a template. Special constructor for
* "File / New" in file-manager type applications.
*
* @param _tempUrl template used for reading only
* @param _currentDir directory where the file will be written to
* @param _defaultName something to put in the name field,
* like mimetype.desktop
* @param parent is the parent of the dialog widget.
*/
KPropertiesDialog(const QUrl &_tempUrl, const QUrl &_currentDir, const QString &_defaultName, QWidget *parent = nullptr);
/**
* Creates an empty properties dialog (for applications that want use
* a standard dialog, but for things not doable via the plugin-mechanism).
*
* @param title is the string display as the "filename" in the title of the dialog.
* @param parent is the parent of the dialog widget.
*/
explicit KPropertiesDialog(const QString &title, QWidget *parent = nullptr);
/**
* Cleans up the properties dialog and frees any associated resources,
* including the dialog itself. Note that when a properties dialog is
* closed it cleans up and deletes itself.
*/
~KPropertiesDialog() override;
/**
* Immediately displays a Properties dialog using constructor with
* the same parameters.
* On MS Windows, if @p item points to a local file, native (non modal) property
* dialog is displayed (@p parent and @p modal are ignored in this case).
*
* @return true on successful dialog displaying (can be false on win32).
*/
static bool showDialog(const KFileItem &item, QWidget *parent = nullptr, bool modal = true);
/**
* Immediately displays a Properties dialog using constructor with
* the same parameters.
* On MS Windows, if @p _url points to a local file, native (non modal) property
* dialog is displayed (@p parent and @p modal are ignored in this case).
*
* @return true on successful dialog displaying (can be false on win32).
*/
static bool showDialog(const QUrl &_url, QWidget *parent = nullptr, bool modal = true);
/**
* Immediately displays a Properties dialog using constructor with
* the same parameters.
* On MS Windows, if @p _items has one element and this element points
* to a local file, native (non modal) property dialog is displayed
* (@p parent and @p modal are ignored in this case).
*
* @return true on successful dialog displaying (can be false on win32).
*/
static bool showDialog(const KFileItemList &_items, QWidget *parent = nullptr, bool modal = true);
/**
* Immediately displays a Properties dialog using constructor with
* the same parameters.
*
* On MS Windows, if @p _urls has one element and this element points
* to a local file, native (non modal) property dialog is displayed
* (@p parent and @p modal are ignored in this case).
*
* @param urls list of URLs whose properties should be displayed (must
* contain at least one non-empty URL)
* @param parent is the parent of the dialog widget.
* @param modal tells the dialog whether it should be modal.
*
* @return true on successful dialog displaying (can be false on win32).
*
* @since 5.10
*/
static bool showDialog(const QList<QUrl> &urls, QWidget *parent = nullptr, bool modal = true);
/**
* The URL of the file that has its properties being displayed.
* This is only valid if the KPropertiesDialog was created/shown
* for one file or URL.
*
* @return the single URL.
*/
QUrl url() const;
/**
* @return the file item for which the dialog is shown
*
* Warning: this method returns the first item of the list.
* This means that you should use this only if you are sure the dialog is used
* for a single item. Otherwise, you probably want items() instead.
*/
KFileItem &item();
/**
* @return the items for which the dialog is shown
*/
KFileItemList items() const;
/**
* If the dialog is being built from a template, this method
* returns the current directory. If no template, it returns QString().
* See the template form of the constructor.
*
* @return the current directory or QString()
*/
QUrl currentDir() const;
/**
* If the dialog is being built from a template, this method
* returns the default name. If no template, it returns QString().
* See the template form of the constructor.
* @return the default name or QString()
*/
QString defaultName() const;
/**
* Updates the item URL (either called by rename or because
* a global apps/mimelnk desktop file is being saved)
* Can only be called if the dialog applies to a single file or URL.
* @param newUrl the new URL
*/
void updateUrl(const QUrl &newUrl);
/**
* Renames the item to the specified name. This can only be called if
* the dialog applies to a single file or URL.
* @param _name new filename, encoded.
* \see FilePropsDialogPlugin::applyChanges
*/
void rename(const QString &_name);
/**
* To abort applying changes.
*/
void abortApplying();
/**
* Shows the page that was previously set by
* setFileSharingPage(), or does nothing if no page
* was set yet.
* \see setFileSharingPage
*/
void showFileSharingPage();
/**
* Sets the file sharing page.
* This page is shown when calling showFileSharingPage().
*
* @param page the page to set
*
* \note This should only be called by KPropertiesDialog plugins.
* \see showFileSharingPage
*/
void setFileSharingPage(QWidget *page);
/**
* Call this to make the filename lineedit readonly, to prevent the user
* from renaming the file.
* \param ro true if the lineedit should be read only
*/
void setFileNameReadOnly(bool ro);
using KPageDialog::buttonBox;
public Q_SLOTS:
/**
* Called when the user presses 'Ok'.
* @since 5.25
*/
void accept() override;
/**
* Called when the user presses 'Cancel' or Esc.
* @since 5.25
*/
void reject() override;
Q_SIGNALS:
/**
* This signal is emitted when the Properties Dialog is closed (for
* example, with OK or Cancel buttons)
*/
void propertiesClosed();
/**
* This signal is emitted when the properties changes are applied (for
* example, with the OK button)
*/
void applied();
/**
* This signal is emitted when the properties changes are aborted (for
* example, with the Cancel button)
*/
void canceled();
/**
* Emitted before changes to @p oldUrl are saved as @p newUrl.
* The receiver may change @p newUrl to point to an alternative
* save location.
*/
void saveAs(const QUrl &oldUrl, QUrl &newUrl);
private:
std::unique_ptr<KPropertiesDialogPrivate> d;
Q_DISABLE_COPY(KPropertiesDialog)
};
#endif
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,248 @@
/*
This file is part of the KDE project
SPDX-FileCopyrightText: 1998, 1999 Torben Weis <weis@kde.org>
SPDX-FileCopyrightText: 1999, 2000 Preston Brown <pbrown@kde.org>
SPDX-FileCopyrightText: 2000 Simon Hausmann <hausmann@kde.org>
SPDX-FileCopyrightText: 2000 David Faure <faure@kde.org>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
/*
* This file holds the definitions for all classes used to
* display a properties dialog.
*/
#pragma once
#include "kpropertiesdialog.h"
#include "kpropertiesdialogplugin.h"
#include <QCryptographicHash>
class QComboBox;
class QLineEdit;
class KJob;
namespace KIO
{
class Job;
}
namespace KDEPrivate
{
/**
* 'General' plugin
* This plugin displays the name of the file, its size and access times.
* @internal
*/
class KFilePropsPlugin : public KPropertiesDialogPlugin
{
Q_OBJECT
public:
explicit KFilePropsPlugin(KPropertiesDialog *_props);
~KFilePropsPlugin() override;
/**
* Applies all changes made. This plugin must be always the first
* plugin in the dialog, since this function may rename the file which
* may confuse other applyChanges functions.
*/
void applyChanges() override;
/**
* Tests whether the files specified by _items need a 'General' plugin.
*/
static bool supports(const KFileItemList &_items);
/**
* Called after all plugins applied their changes
*/
void postApplyChanges();
void setFileNameReadOnly(bool ro);
protected Q_SLOTS:
void slotEditFileType();
void slotCopyFinished(KJob *);
void slotFileRenamed(KIO::Job *, const QUrl &, const QUrl &);
void slotDirSizeUpdate();
void slotDirSizeFinished(KJob *);
void slotFreeSpaceResult(KJob *);
void slotSizeStop();
void slotSizeDetermine();
void slotSizeDetails();
Q_SIGNALS:
void changesApplied();
private Q_SLOTS:
void nameFileChanged(const QString &text);
void slotIconChanged();
private:
bool enableIconButton() const;
void determineRelativePath(const QString &path);
void applyIconChanges();
void updateDefaultHandler(const QString &mimeType);
class KFilePropsPluginPrivate;
std::unique_ptr<KFilePropsPluginPrivate> d;
};
/**
* 'Permissions' plugin
* In this plugin you can modify permissions and change
* the owner of a file.
* @internal
*/
class KFilePermissionsPropsPlugin : public KPropertiesDialogPlugin
{
Q_OBJECT
public:
enum PermissionsMode {
PermissionsOnlyFiles = 0,
PermissionsOnlyDirs = 1,
PermissionsOnlyLinks = 2,
PermissionsMixed = 3,
};
enum PermissionsTarget {
PermissionsOwner = 0,
PermissionsGroup = 1,
PermissionsOthers = 2,
};
explicit KFilePermissionsPropsPlugin(KPropertiesDialog *_props);
~KFilePermissionsPropsPlugin() override;
void applyChanges() override;
/**
* Tests whether the file specified by _items needs a 'Permissions' plugin.
*/
static bool supports(const KFileItemList &_items);
private Q_SLOTS:
void slotShowAdvancedPermissions();
Q_SIGNALS:
void changesApplied();
private:
void setComboContent(QComboBox *combo, PermissionsTarget target, mode_t permissions, mode_t partial);
bool isIrregular(mode_t permissions, bool isDir, bool isLink);
void enableAccessControls(bool enable);
void updateAccessControls();
void getPermissionMasks(mode_t &andFilePermissions, mode_t &andDirPermissions, mode_t &orFilePermissions, mode_t &orDirPermissions);
static const mode_t permissionsMasks[3];
static const mode_t standardPermissions[4];
static const mode_t fperm[3][4];
class KFilePermissionsPropsPluginPrivate;
std::unique_ptr<KFilePermissionsPropsPluginPrivate> d;
};
class KChecksumsPlugin : public KPropertiesDialogPlugin
{
Q_OBJECT
public:
explicit KChecksumsPlugin(KPropertiesDialog *dialog);
~KChecksumsPlugin() override;
static bool supports(const KFileItemList &items);
private Q_SLOTS:
void slotInvalidateCache();
void slotShowMd5();
void slotShowSha1();
void slotShowSha256();
void slotShowSha512();
/**
* Compare @p input (required to be lowercase) with the checksum in cache.
*/
void slotVerifyChecksum(const QString &input);
private:
static bool isMd5(const QString &input);
static bool isSha1(const QString &input);
static bool isSha256(const QString &input);
static bool isSha512(const QString &input);
static QString computeChecksum(QCryptographicHash::Algorithm algorithm, const QString &path);
static QCryptographicHash::Algorithm detectAlgorithm(const QString &input);
void setDefaultState();
void setInvalidChecksumState();
void setMatchState();
void setMismatchState();
void setVerifyState();
void showChecksum(QCryptographicHash::Algorithm algorithm, QLineEdit *label, QPushButton *copyButton);
QString cachedChecksum(QCryptographicHash::Algorithm algorithm) const;
void cacheChecksum(const QString &checksum, QCryptographicHash::Algorithm algorithm);
class KChecksumsPluginPrivate;
std::unique_ptr<KChecksumsPluginPrivate> d;
};
/**
* Used to edit the files containing
* [Desktop Entry]
* URL=....
*
* Such files are used to represent a program in kicker and konqueror.
* @internal
*/
class KUrlPropsPlugin : public KPropertiesDialogPlugin
{
Q_OBJECT
public:
explicit KUrlPropsPlugin(KPropertiesDialog *_props);
~KUrlPropsPlugin() override;
void applyChanges() override;
void setFileNameReadOnly(bool ro);
static bool supports(const KFileItemList &_items);
private:
class KUrlPropsPluginPrivate;
std::unique_ptr<KUrlPropsPluginPrivate> d;
};
/**
* Used to edit the files containing
* [Desktop Entry]
* Type=Application
*
* Such files are used to represent a program in kicker and konqueror.
* @internal
*/
class KDesktopPropsPlugin : public KPropertiesDialogPlugin
{
Q_OBJECT
public:
explicit KDesktopPropsPlugin(KPropertiesDialog *_props);
~KDesktopPropsPlugin() override;
void applyChanges() override;
static bool supports(const KFileItemList &_items);
public Q_SLOTS:
void slotAddFiletype();
void slotDelFiletype();
void slotBrowseExec();
void slotAdvanced();
private:
void checkCommandChanged();
private:
class KDesktopPropsPluginPrivate;
std::unique_ptr<KDesktopPropsPluginPrivate> d;
};
}
@@ -0,0 +1,56 @@
/*
SPDX-FileCopyrightText: 2000 David Faure <faure@kde.org>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#include "kpropertiesdialogplugin.h"
#include "kio_widgets_debug.h"
#include "kpropertiesdialog.h"
class KPropertiesDialogPluginPrivate
{
public:
KPropertiesDialogPluginPrivate()
{
}
~KPropertiesDialogPluginPrivate()
{
}
bool m_bDirty;
int fontHeight;
};
KPropertiesDialogPlugin::KPropertiesDialogPlugin(QObject *_props)
: QObject(_props)
, properties(qobject_cast<KPropertiesDialog *>(_props))
, d(new KPropertiesDialogPluginPrivate)
{
Q_ASSERT(properties);
d->fontHeight = 2 * properties->fontMetrics().height();
d->m_bDirty = false;
}
KPropertiesDialogPlugin::~KPropertiesDialogPlugin() = default;
void KPropertiesDialogPlugin::setDirty(bool b)
{
d->m_bDirty = b;
}
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;
}
#include "moc_kpropertiesdialogplugin.cpp"
@@ -0,0 +1,86 @@
/*
SPDX-FileCopyrightText: 2000 David Faure <faure@kde.org>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#include "kiowidgets_export.h"
#include <QObject>
#ifndef KPROPERTIESDIALOGPLUGIN_H
#define KPROPERTIESDIALOGPLUGIN_H
#include "kiowidgets_export.h"
#include <QObject>
#include <kpropertiesdialog.h>
class KPropertiesDialogPluginPrivate;
/**
* A Plugin in the Properties dialog
* This is an abstract class. You must inherit from this class
* to build a new kind of tabbed page for the KPropertiesDialog.
* A plugin in itself is just a library containing code, not a dialog's page.
* It's up to the plugin to insert pages into the parent dialog.
*
* To make a plugin available, ensure it has embedded json metadata using
* K_PLUGIN_CLASS_WITH_JSON and install the plugin in the KDE_INSTALL_PLUGINDIR/kf6/propertiesdialog
* folder from the KDEInstallDirs CMake module.
*
* The metadata can contain the MIME types for which the plugin should be created.
* For instance:
* @verbatim
{
"KPlugin": {
"MimeTypes": ["text/html", "application/x-mymimetype"]
},
"X-KDE-Protocols": ["file"]
}
@endverbatim
* If the MIME types are empty or not specified, the plugin will be created for all MIME types.
*
* You can also include "X-KDE-Protocols" if you want that plugin for instance
* to be loaded only for local files.
*/
class KIOWIDGETS_EXPORT KPropertiesDialogPlugin : public QObject
{
Q_OBJECT
public:
/**
* Constructor whos parent will be cast to KPropertiesDialog
* To insert tabs into the properties dialog, use the add methods provided by
* KPageDialog (the properties dialog is a KPageDialog).
*/
KPropertiesDialogPlugin(QObject *parent);
~KPropertiesDialogPlugin() override;
/**
* Applies all changes to the file.
* This function is called when the user presses 'Ok'. The last plugin inserted
* is called first.
*/
virtual void applyChanges();
void setDirty(bool b = true);
bool isDirty() const;
Q_SIGNALS:
/**
* Emit this signal when the user changed anything in the plugin's tabs.
* The hosting PropertiesDialog will call applyChanges only if the
* PropsPlugin has emitted this signal or if you have called setDirty() before.
*/
void changed();
protected:
/**
* Pointer to the dialog
*/
KPropertiesDialog *const properties;
/**
* Returns the font height.
*/
int fontHeight() const;
private:
const std::unique_ptr<KPropertiesDialogPluginPrivate> d;
};
#endif
@@ -0,0 +1,279 @@
/*
This file is part of the KDE libraries
SPDX-FileCopyrightText: 2000 David Smith <dsmith@algonet.se>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#include "kshellcompletion.h"
#include <KCompletion>
#include <KCompletionMatches>
#include <stdlib.h>
class KShellCompletionPrivate
{
public:
KShellCompletionPrivate()
: m_word_break_char(QLatin1Char(' '))
, m_quote_char1(QLatin1Char('\"'))
, m_quote_char2(QLatin1Char('\''))
, m_escape_char(QLatin1Char('\\'))
{
}
void splitText(const QString &text, QString &text_start, QString &text_compl) const;
bool quoteText(QString *text, bool force, bool skip_last) const;
QString unquote(const QString &text) const;
QString m_text_start; // part of the text that was not completed
QString m_text_compl; // part of the text that was completed (unchanged)
QChar m_word_break_char;
QChar m_quote_char1;
QChar m_quote_char2;
QChar m_escape_char;
};
KShellCompletion::KShellCompletion()
: KUrlCompletion()
, d(new KShellCompletionPrivate)
{
}
KShellCompletion::~KShellCompletion() = default;
/*
* makeCompletion()
*
* Entry point for file name completion
*/
QString KShellCompletion::makeCompletion(const QString &text)
{
// Split text at the last unquoted space
//
d->splitText(text, d->m_text_start, d->m_text_compl);
// Remove quotes from the text to be completed
//
QString tmp = d->unquote(d->m_text_compl);
d->m_text_compl = tmp;
// Do exe-completion if there was no unquoted space
//
const bool is_exe_completion = !d->m_text_start.contains(d->m_word_break_char);
setMode(is_exe_completion ? ExeCompletion : FileCompletion);
// Make completion on the last part of text
//
return KUrlCompletion::makeCompletion(d->m_text_compl);
}
/*
* postProcessMatch, postProcessMatches
*
* Called by KCompletion before emitting match() and matches()
*
* Add add the part of the text that was not completed
* Add quotes when needed
*/
void KShellCompletion::postProcessMatch(QString *match) const
{
KUrlCompletion::postProcessMatch(match);
if (match->isNull()) {
return;
}
if (match->endsWith(QLatin1Char('/'))) {
d->quoteText(match, false, true); // don't quote the trailing '/'
} else {
d->quoteText(match, false, false); // quote the whole text
}
match->prepend(d->m_text_start);
}
void KShellCompletion::postProcessMatches(QStringList *matches) const
{
KUrlCompletion::postProcessMatches(matches);
for (QString &match : *matches) {
if (!match.isNull()) {
if (match.endsWith(QLatin1Char('/'))) {
d->quoteText(&match, false, true); // don't quote trailing '/'
} else {
d->quoteText(&match, false, false); // quote the whole text
}
match.prepend(d->m_text_start);
}
}
}
void KShellCompletion::postProcessMatches(KCompletionMatches *matches) const
{
KUrlCompletion::postProcessMatches(matches);
for (auto &match : *matches) {
QString &matchString = match.value();
if (!matchString.isNull()) {
if (matchString.endsWith(QLatin1Char('/'))) {
d->quoteText(&matchString, false, true); // don't quote trailing '/'
} else {
d->quoteText(&matchString, false, false); // quote the whole text
}
matchString.prepend(d->m_text_start);
}
}
}
/*
* splitText
*
* Split text at the last unquoted space
*
* text_start = [out] text at the left, including the space
* text_compl = [out] text at the right
*/
void KShellCompletionPrivate::splitText(const QString &text, QString &text_start, QString &text_compl) const
{
bool in_quote = false;
bool escaped = false;
QChar p_last_quote_char;
int last_unquoted_space = -1;
for (int pos = 0; pos < text.length(); pos++) {
if (escaped) {
escaped = false;
} else if (in_quote && text[pos] == p_last_quote_char) {
in_quote = false;
} else if (!in_quote && text[pos] == m_quote_char1) {
p_last_quote_char = m_quote_char1;
in_quote = true;
} else if (!in_quote && text[pos] == m_quote_char2) {
p_last_quote_char = m_quote_char2;
in_quote = true;
} else if (text[pos] == m_escape_char) {
escaped = true;
} else if (!in_quote && text[pos] == m_word_break_char) {
while (pos + 1 < text.length() && text[pos + 1] == m_word_break_char) {
pos++;
}
if (pos + 1 == text.length()) {
break;
}
last_unquoted_space = pos;
}
}
text_start = text.left(last_unquoted_space + 1);
// the last part without trailing blanks
text_compl = text.mid(last_unquoted_space + 1);
}
/*
* quoteText()
*
* Add quotations to 'text' if needed or if 'force' = true
* Returns true if quotes were added
*
* skip_last => ignore the last character (we add a space or '/' to all filenames)
*/
bool KShellCompletionPrivate::quoteText(QString *text, bool force, bool skip_last) const
{
int pos = 0;
if (!force) {
pos = text->indexOf(m_word_break_char);
if (skip_last && (pos == (int)(text->length()) - 1)) {
pos = -1;
}
}
if (!force && pos == -1) {
pos = text->indexOf(m_quote_char1);
if (skip_last && (pos == (int)(text->length()) - 1)) {
pos = -1;
}
}
if (!force && pos == -1) {
pos = text->indexOf(m_quote_char2);
if (skip_last && (pos == (int)(text->length()) - 1)) {
pos = -1;
}
}
if (!force && pos == -1) {
pos = text->indexOf(m_escape_char);
if (skip_last && (pos == (int)(text->length()) - 1)) {
pos = -1;
}
}
if (force || (pos >= 0)) {
// Escape \ in the string
text->replace(m_escape_char, QString(m_escape_char) + m_escape_char);
// Escape " in the string
text->replace(m_quote_char1, QString(m_escape_char) + m_quote_char1);
// " at the beginning
text->insert(0, m_quote_char1);
// " at the end
if (skip_last) {
text->insert(text->length() - 1, m_quote_char1);
} else {
text->insert(text->length(), m_quote_char1);
}
return true;
}
return false;
}
/*
* unquote
*
* Remove quotes and return the result in a new string
*
*/
QString KShellCompletionPrivate::unquote(const QString &text) const
{
bool in_quote = false;
bool escaped = false;
QChar p_last_quote_char;
QString result;
for (const QChar ch : text) {
if (escaped) {
escaped = false;
result.insert(result.length(), ch);
} else if (in_quote && ch == p_last_quote_char) {
in_quote = false;
} else if (!in_quote && ch == m_quote_char1) {
p_last_quote_char = m_quote_char1;
in_quote = true;
} else if (!in_quote && ch == m_quote_char2) {
p_last_quote_char = m_quote_char2;
in_quote = true;
} else if (ch == m_escape_char) {
escaped = true;
result.insert(result.length(), ch);
} else {
result.insert(result.length(), ch);
}
}
return result;
}
#include "moc_kshellcompletion.cpp"
@@ -0,0 +1,61 @@
/*
This file is part of the KDE libraries
SPDX-FileCopyrightText: 2000 David Smith <dsmith@algonet.se>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#ifndef KSHELLCOMPLETION_H
#define KSHELLCOMPLETION_H
#include <QString>
#include <QStringList>
#include "kurlcompletion.h"
#include <memory>
class KShellCompletionPrivate;
/**
* @class KShellCompletion kshellcompletion.h <KShellCompletion>
*
* This class does shell-like completion of file names.
* A string passed to makeCompletion() will be interpreted as a shell
* command line. Completion will be done on the last argument on the line.
* Returned matches consist of the first arguments (uncompleted) plus the
* completed last argument.
*
* @short Shell-like completion of file names
* @author David Smith <dsmith@algonet.se>
*/
class KIOWIDGETS_EXPORT KShellCompletion : public KUrlCompletion
{
Q_OBJECT
public:
/**
* Constructs a KShellCompletion object.
*/
KShellCompletion();
~KShellCompletion() override;
/**
* Finds completions to the given text.
* The first match is returned and emitted in the signal match().
* @param text the text to complete
* @return the first match, or QString() if not found
*/
QString makeCompletion(const QString &text) override;
protected:
// Called by KCompletion
void postProcessMatch(QString *match) const override;
void postProcessMatches(QStringList *matches) const override;
void postProcessMatches(KCompletionMatches *matches) const override;
private:
std::unique_ptr<KShellCompletionPrivate> const d;
};
#endif // KSHELLCOMPLETION_H
@@ -0,0 +1,63 @@
/*
This file is part of the KDE project
SPDX-FileCopyrightText: 2007 Andreas Hartmetz <ahartmetz@gmail.com>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#include "ksslcertificatebox.h"
#include "ui_certificateparty.h"
#include <QSslCertificate>
class KSslCertificateBoxPrivate
{
public:
Ui::CertificateParty ui;
};
KSslCertificateBox::KSslCertificateBox(QWidget *parent)
: QWidget(parent)
, d(new KSslCertificateBoxPrivate())
{
d->ui.setupUi(this);
// No fooling us with html tags
const QList<QLabel *> labels = findChildren<QLabel *>();
for (QLabel *label : labels) {
label->setTextFormat(Qt::PlainText);
}
}
KSslCertificateBox::~KSslCertificateBox() = default;
void KSslCertificateBox::setCertificate(const QSslCertificate &cert, CertificateParty party)
{
if (party == Subject) {
d->ui.commonName->setText(cert.subjectInfo(QSslCertificate::CommonName).join(QLatin1String(", ")));
d->ui.organization->setText(cert.subjectInfo(QSslCertificate::Organization).join(QLatin1String(", ")));
d->ui.organizationalUnit->setText(cert.subjectInfo(QSslCertificate::OrganizationalUnitName).join(QLatin1String(", ")));
d->ui.country->setText(cert.subjectInfo(QSslCertificate::CountryName).join(QLatin1String(", ")));
d->ui.state->setText(cert.subjectInfo(QSslCertificate::StateOrProvinceName).join(QLatin1String(", ")));
d->ui.city->setText(cert.subjectInfo(QSslCertificate::LocalityName).join(QLatin1String(", ")));
} else if (party == Issuer) {
d->ui.commonName->setText(cert.issuerInfo(QSslCertificate::CommonName).join(QLatin1String(", ")));
d->ui.organization->setText(cert.issuerInfo(QSslCertificate::Organization).join(QLatin1String(", ")));
d->ui.organizationalUnit->setText(cert.issuerInfo(QSslCertificate::OrganizationalUnitName).join(QLatin1String(", ")));
d->ui.country->setText(cert.issuerInfo(QSslCertificate::CountryName).join(QLatin1String(", ")));
d->ui.state->setText(cert.issuerInfo(QSslCertificate::StateOrProvinceName).join(QLatin1String(", ")));
d->ui.city->setText(cert.issuerInfo(QSslCertificate::LocalityName).join(QLatin1String(", ")));
}
}
void KSslCertificateBox::clear()
{
d->ui.commonName->clear();
d->ui.organization->clear();
d->ui.organizationalUnit->clear();
d->ui.country->clear();
d->ui.state->clear();
d->ui.city->clear();
}
#include "moc_ksslcertificatebox.cpp"
@@ -0,0 +1,39 @@
/*
This file is part of the KDE project
SPDX-FileCopyrightText: 2007 Andreas Hartmetz <ahartmetz@gmail.com>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#ifndef KSSLCERTIFICATEBOX_H
#define KSSLCERTIFICATEBOX_H
#include "kiowidgets_export.h"
#include <QWidget>
#include <memory>
class QSslCertificate;
class KSslCertificateBoxPrivate;
class KIOWIDGETS_EXPORT KSslCertificateBox : public QWidget
{
Q_OBJECT
public:
enum CertificateParty {
Subject = 0,
Issuer,
};
explicit KSslCertificateBox(QWidget *parent = nullptr);
~KSslCertificateBox() override;
void setCertificate(const QSslCertificate &cert, CertificateParty party);
void clear();
std::unique_ptr<KSslCertificateBoxPrivate> const d;
};
#endif // KSSLCERTIFICATEBOX_H
@@ -0,0 +1,200 @@
/*
This file is part of the KDE project
SPDX-FileCopyrightText: 2000, 2001 George Staikos <staikos@kde.org>
SPDX-FileCopyrightText: 2000 Malte Starostik <malte@kde.org>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#include "ksslinfodialog.h"
#include "ksslcertificatebox.h"
#include "ui_sslinfo.h"
#include <QDialogButtonBox>
#include <QSslCertificate>
#include <KIconLoader> // BarIcon
#include <KLocalizedString>
class Q_DECL_HIDDEN KSslInfoDialog::KSslInfoDialogPrivate
{
public:
QList<QSslCertificate> certificateChain;
QList<QList<QSslError::SslError>> 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(this);
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, &QDialogButtonBox::accepted, this, &QDialog::accept);
connect(buttonBox, &QDialogButtonBox::rejected, this, &QDialog::reject);
layout->addWidget(buttonBox);
}
KSslInfoDialog::~KSslInfoDialog() = default;
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(QIcon::fromTheme(QStringLiteral("security-high")).pixmap(KIconLoader::SizeSmallMedium));
d->ui.explanation->setText(i18n("Current connection is secured with SSL."));
} else {
d->ui.encryptionIndicator->setPixmap(QIcon::fromTheme(QStringLiteral("security-medium")).pixmap(KIconLoader::SizeSmallMedium));
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(QIcon::fromTheme(QStringLiteral("security-medium")).pixmap(KIconLoader::SizeSmallMedium));
d->ui.explanation->setText(
i18n("Some of this document is secured with SSL, "
"but the main part is not."));
} else {
d->ui.encryptionIndicator->setPixmap(QIcon::fromTheme(QStringLiteral("security-low")).pixmap(KIconLoader::SizeSmallMedium));
d->ui.explanation->setText(i18n("Current connection is not secured with SSL."));
}
}
}
void KSslInfoDialog::setSslInfo(const QList<QSslCertificate> &certificateChain,
const QString &ip,
const QString &host,
const QString &sslProtocol,
const QString &cipher,
int usedBits,
int bits,
const QList<QList<QSslError::SslError>> &validationErrors)
{
d->certificateChain = certificateChain;
d->certificateErrors = validationErrors;
d->ui.certSelector->clear();
for (const QSslCertificate &cert : certificateChain) {
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(QLatin1String(", "));
}
d->ui.certSelector->addItem(name);
}
if (certificateChain.size() < 2) {
d->ui.certSelector->setEnabled(false);
}
connect(d->ui.certSelector, &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'), Qt::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;
const QList<QSslError::SslError> errorsList = d->certificateErrors[i];
if (!errorsList.isEmpty()) {
trusted = i18nc("The certificate is not trusted", "NO, there were errors:");
for (QSslError::SslError e : errorsList) {
QSslError classError(e);
trusted += QLatin1Char('\n') + 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<QList<QSslError::SslError>> KSslInfoDialog::certificateErrorsFromString(const QString &errorsString)
{
const QStringList sl = errorsString.split(QLatin1Char('\n'), Qt::KeepEmptyParts);
QList<QList<QSslError::SslError>> ret;
ret.reserve(sl.size());
for (const QString &s : sl) {
QList<QSslError::SslError> certErrors;
const QStringList sl2 = s.split(QLatin1Char('\t'), Qt::SkipEmptyParts);
for (const QString &s2 : sl2) {
bool didConvert;
QSslError::SslError error = static_cast<QSslError::SslError>(s2.toInt(&didConvert));
if (didConvert) {
certErrors.append(error);
}
}
ret.append(certErrors);
}
return ret;
}
#include "moc_ksslinfodialog.cpp"
@@ -0,0 +1,91 @@
/*
This file is part of the KDE project
SPDX-FileCopyrightText: 2000-2003 George Staikos <staikos@kde.org>
SPDX-FileCopyrightText: 2000 Malte Starostik <malte@kde.org>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#ifndef _KSSLINFODIALOG_H
#define _KSSLINFODIALOG_H
#include <QDialog>
#include <QSslError>
#include "kiowidgets_export.h"
#include <memory>
/**
* KDE SSL Information Dialog
*
* This class creates a dialog that can be used to display information about
* an SSL session.
*
* There are NO GUARANTEES that KSslInfoDialog will remain binary compatible/
* Contact staikos@kde.org for details if needed.
*
* @author George Staikos <staikos@kde.org>
* @see KSSL
* @short KDE SSL Information Dialog
*/
class KIOWIDGETS_EXPORT KSslInfoDialog : public QDialog
{
Q_OBJECT
public:
/**
* Construct a KSSL Information Dialog
*
* @param parent the parent widget
*/
explicit KSslInfoDialog(QWidget *parent = nullptr);
/**
* Destroy this dialog
*/
~KSslInfoDialog() override;
/**
* Set information to display about the SSL connection.
*
* @param certificateChain the certificate chain leading from the certificate
* authority to the peer.
* @param ip the ip of the remote host
* @param host the remote hostname
* @param sslProtocol the version of SSL in use (SSLv2, SSLv3, TLSv1)
* @param cipher the cipher in use
* @param usedBits the used bits of the key
* @param bits the key size of the cipher in use
* @param validationErrors errors validating the certificates, if any
* @since 5.64
*/
void setSslInfo(const QList<QSslCertificate> &certificateChain,
const QString &ip,
const QString &host,
const QString &sslProtocol,
const QString &cipher,
int usedBits,
int bits,
const QList<QList<QSslError::SslError>> &validationErrors);
void setMainPartEncrypted(bool);
void setAuxiliaryPartsEncrypted(bool);
/**
* Converts certificate errors as provided in the "ssl_cert_errors" meta data
* to a list of QSslError::SslError values per certificate in the certificate chain.
* @since 5.65
*/
static QList<QList<QSslError::SslError>> certificateErrorsFromString(const QString &errorsString);
private:
KIOWIDGETS_NO_EXPORT void updateWhichPartsEncrypted();
class KSslInfoDialogPrivate;
std::unique_ptr<KSslInfoDialogPrivate> const d;
private Q_SLOTS:
KIOWIDGETS_NO_EXPORT void displayFromChain(int);
};
#endif
@@ -0,0 +1,113 @@
/*
SPDX-FileCopyrightText: 2015 Montel Laurent <montel@kde.org>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#include "kurifiltersearchprovideractions.h"
#include <KDialogJobUiDelegate>
#include <KIO/CommandLauncherJob>
#include <KLocalizedString>
#include <KStringHandler>
#include <QActionGroup>
#include <QDesktopServices>
#include <QIcon>
#include <QMenu>
#include <QStandardPaths>
#include <kurifilter.h>
using namespace KIO;
class KIO::WebShortcutsMenuManagerPrivate
{
public:
WebShortcutsMenuManagerPrivate()
{
}
QString mSelectedText;
};
KUriFilterSearchProviderActions::KUriFilterSearchProviderActions(QObject *parent)
: QObject(parent)
, d(new KIO::WebShortcutsMenuManagerPrivate)
{
}
KUriFilterSearchProviderActions::~KUriFilterSearchProviderActions() = default;
QString KUriFilterSearchProviderActions::selectedText() const
{
return d->mSelectedText;
}
void KUriFilterSearchProviderActions::setSelectedText(const QString &selectedText)
{
d->mSelectedText = selectedText;
}
void KUriFilterSearchProviderActions::slotConfigureWebShortcuts()
{
auto *job = new KIO::CommandLauncherJob(QStringLiteral("kcmshell6"), {QStringLiteral("webshortcuts")});
job->setUiDelegate(new KDialogJobUiDelegate(KJobUiDelegate::AutoHandlingEnabled, nullptr));
job->start();
}
void KUriFilterSearchProviderActions::addWebShortcutsToMenu(QMenu *menu)
{
if (d->mSelectedText.isEmpty()) {
return;
}
const QString searchText = d->mSelectedText.simplified();
if (searchText.isEmpty()) {
return;
}
KUriFilterData filterData(searchText);
filterData.setSearchFilteringOptions(KUriFilterData::RetrievePreferredSearchProvidersOnly);
if (KUriFilter::self()->filterSearchUri(filterData, KUriFilter::NormalTextFilter)) {
const QStringList searchProviders = filterData.preferredSearchProviders();
if (!searchProviders.isEmpty()) {
QMenu *webShortcutsMenu = new QMenu(menu);
webShortcutsMenu->setIcon(QIcon::fromTheme(QStringLiteral("preferences-web-browser-shortcuts")));
const QString squeezedText = KStringHandler::rsqueeze(searchText, 21);
webShortcutsMenu->setTitle(i18n("Search for '%1' with", squeezedText));
QActionGroup *actionGroup = new QActionGroup(this);
connect(actionGroup, &QActionGroup::triggered, this, &KUriFilterSearchProviderActions::slotHandleWebShortcutAction);
for (const QString &searchProvider : searchProviders) {
QAction *action = new QAction(i18nc("@action:inmenu Search for <text> with", "%1", searchProvider), webShortcutsMenu);
action->setIcon(QIcon::fromTheme(filterData.iconNameForPreferredSearchProvider(searchProvider)));
action->setData(filterData.queryForPreferredSearchProvider(searchProvider));
webShortcutsMenu->addAction(action);
actionGroup->addAction(action);
}
if (!QStandardPaths::findExecutable(QStringLiteral("kcmshell6")).isEmpty()) {
webShortcutsMenu->addSeparator();
QAction *action = new QAction(i18nc("@action:inmenu", "Configure Web Shortcuts…"), webShortcutsMenu);
action->setIcon(QIcon::fromTheme(QStringLiteral("configure")));
connect(action, &QAction::triggered, this, &KUriFilterSearchProviderActions::slotConfigureWebShortcuts);
webShortcutsMenu->addAction(action);
}
menu->addMenu(webShortcutsMenu);
}
}
}
void KUriFilterSearchProviderActions::slotHandleWebShortcutAction(QAction *action)
{
KUriFilterData filterData(action->data().toString());
if (KUriFilter::self()->filterSearchUri(filterData, KUriFilter::WebShortcutFilter)) {
QDesktopServices::openUrl(filterData.uri());
}
}
#include "moc_kurifiltersearchprovideractions.cpp"
@@ -0,0 +1,69 @@
/*
SPDX-FileCopyrightText: 2015 Montel Laurent <montel@kde.org>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#ifndef KURIFILTERSEARCHPROVIDERACTIONS_H
#define KURIFILTERSEARCHPROVIDERACTIONS_H
#include "kiowidgets_export.h"
#include <QObject>
#include <memory>
class QMenu;
class QAction;
namespace KIO
{
class WebShortcutsMenuManagerPrivate;
/**
* @class KUriFilterSearchProviderActions kurifiltersearchprovideractions.h <KIO/KUriFilterSearchProviderActions>
*
* This class is a manager for web shortcuts
*
* It will provide a list of web shortcuts against a selected text
*
* You can set the selected text with setSelectedText() function
*
* @since 5.16
*/
class KIOWIDGETS_EXPORT KUriFilterSearchProviderActions : public QObject
{
Q_OBJECT
public:
/**
* Constructs a webshorts menu manager.
*
* @param parent The QObject parent.
*/
explicit KUriFilterSearchProviderActions(QObject *parent = nullptr);
~KUriFilterSearchProviderActions() override;
/**
* @brief return the selected text
*/
QString selectedText() const;
/**
* @brief Set selected text
* @param selectedText the text to search for
*/
void setSelectedText(const QString &selectedText);
/**
* @brief addWebShortcutsToMenu Manage to add web shortcut actions to existing menu.
* @param menu menu to add shortcuts to
*/
void addWebShortcutsToMenu(QMenu *menu);
private Q_SLOTS:
KIOWIDGETS_NO_EXPORT void slotConfigureWebShortcuts();
KIOWIDGETS_NO_EXPORT void slotHandleWebShortcutAction(QAction *action);
private:
std::unique_ptr<WebShortcutsMenuManagerPrivate> const d;
};
}
#endif // WEBSHORTCUTSMENUMANAGER_H
@@ -0,0 +1,435 @@
/*
This file is part of the KDE libraries
SPDX-FileCopyrightText: 2000, 2001 Carsten Pfeiffer <pfeiffer@kde.org>
SPDX-License-Identifier: LGPL-2.0-only
*/
#include "kurlcombobox.h"
#include "../utils_p.h"
#include <QApplication>
#include <QDir>
#include <QDrag>
#include <QMimeData>
#include <QMouseEvent>
#include <KIconLoader>
#include <KLocalizedString>
#include <QDebug>
#include <kio/global.h>
#include <algorithm>
#include <vector>
class KUrlComboBoxPrivate
{
public:
KUrlComboBoxPrivate(KUrlComboBox *parent)
: m_parent(parent)
, dirIcon(QIcon::fromTheme(QStringLiteral("folder")))
{
}
struct KUrlComboItem {
KUrlComboItem(const QUrl &url, const QIcon &icon, const QString &text = QString())
: url(url)
, icon(icon)
, text(text)
{
}
QUrl url;
QIcon icon;
QString text; // if empty, calculated from the QUrl
};
void init(KUrlComboBox::Mode mode);
QString textForItem(const KUrlComboItem *item) const;
void insertUrlItem(const KUrlComboItem *);
QIcon getIcon(const QUrl &url) const;
void updateItem(const KUrlComboItem *item, int index, const QIcon &icon);
void slotActivated(int);
KUrlComboBox *const m_parent;
QIcon dirIcon;
bool urlAdded;
int myMaximum;
KUrlComboBox::Mode myMode;
QPoint m_dragPoint;
using KUrlComboItemList = std::vector<std::unique_ptr<const KUrlComboItem>>;
KUrlComboItemList itemList;
KUrlComboItemList defaultList;
QMap<int, const KUrlComboItem *> itemMapper;
QIcon opendirIcon;
};
QString KUrlComboBoxPrivate::textForItem(const KUrlComboItem *item) const
{
if (!item->text.isEmpty()) {
return item->text;
}
QUrl url = item->url;
if (myMode == KUrlComboBox::Directories) {
Utils::appendSlashToPath(url);
} else {
url = url.adjusted(QUrl::StripTrailingSlash);
}
if (url.isLocalFile()) {
return url.toLocalFile();
} else {
return url.toDisplayString();
}
}
KUrlComboBox::KUrlComboBox(Mode mode, QWidget *parent)
: KComboBox(parent)
, d(new KUrlComboBoxPrivate(this))
{
d->init(mode);
}
KUrlComboBox::KUrlComboBox(Mode mode, bool rw, QWidget *parent)
: KComboBox(rw, parent)
, d(new KUrlComboBoxPrivate(this))
{
d->init(mode);
}
KUrlComboBox::~KUrlComboBox() = default;
void KUrlComboBoxPrivate::init(KUrlComboBox::Mode mode)
{
myMode = mode;
urlAdded = false;
myMaximum = 10; // default
m_parent->setInsertPolicy(KUrlComboBox::NoInsert);
m_parent->setTrapReturnKey(true);
m_parent->setSizePolicy(QSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed));
m_parent->setLayoutDirection(Qt::LeftToRight);
if (m_parent->completionObject()) {
m_parent->completionObject()->setOrder(KCompletion::Sorted);
}
opendirIcon = QIcon::fromTheme(QStringLiteral("folder-open"));
m_parent->connect(m_parent, &KUrlComboBox::activated, m_parent, [this](int index) {
slotActivated(index);
});
}
QStringList KUrlComboBox::urls() const
{
// qDebug() << "::urls()";
QStringList list;
QString url;
for (int i = static_cast<int>(d->defaultList.size()); i < count(); i++) {
url = itemText(i);
if (!url.isEmpty()) {
if (Utils::isAbsoluteLocalPath(url)) {
list.append(QUrl::fromLocalFile(url).toString());
} else {
list.append(url);
}
}
}
return list;
}
void KUrlComboBox::addDefaultUrl(const QUrl &url, const QString &text)
{
addDefaultUrl(url, d->getIcon(url), text);
}
void KUrlComboBox::addDefaultUrl(const QUrl &url, const QIcon &icon, const QString &text)
{
d->defaultList.push_back(std::unique_ptr<KUrlComboBoxPrivate::KUrlComboItem>(new KUrlComboBoxPrivate::KUrlComboItem(url, icon, text)));
}
void KUrlComboBox::setDefaults()
{
clear();
d->itemMapper.clear();
for (const auto &item : d->defaultList) {
d->insertUrlItem(item.get());
}
}
void KUrlComboBox::setUrls(const QStringList &urls)
{
setUrls(urls, RemoveBottom);
}
void KUrlComboBox::setUrls(const QStringList &_urls, OverLoadResolving remove)
{
setDefaults();
d->itemList.clear();
d->urlAdded = false;
if (_urls.isEmpty()) {
return;
}
QStringList urls;
QStringList::ConstIterator it = _urls.constBegin();
// kill duplicates
while (it != _urls.constEnd()) {
if (!urls.contains(*it)) {
urls += *it;
}
++it;
}
// limit to myMaximum items
/* Note: overload is an (old) C++ keyword, some compilers (KCC) choke
on that, so call it Overload (capital 'O'). (matz) */
int Overload = urls.count() - d->myMaximum + static_cast<int>(d->defaultList.size());
while (Overload > 0) {
if (remove == RemoveBottom) {
if (!urls.isEmpty()) {
urls.removeLast();
}
} else {
if (!urls.isEmpty()) {
urls.removeFirst();
}
}
Overload--;
}
it = urls.constBegin();
while (it != urls.constEnd()) {
if ((*it).isEmpty()) {
++it;
continue;
}
QUrl u;
if (Utils::isAbsoluteLocalPath(*it)) {
u = QUrl::fromLocalFile(*it);
} else {
u.setUrl(*it);
}
// Don't restore if file doesn't exist anymore
if (u.isLocalFile() && !QFile::exists(u.toLocalFile())) {
++it;
continue;
}
std::unique_ptr<KUrlComboBoxPrivate::KUrlComboItem> item(new KUrlComboBoxPrivate::KUrlComboItem(u, d->getIcon(u)));
d->insertUrlItem(item.get());
d->itemList.push_back(std::move(item));
++it;
}
}
void KUrlComboBox::setUrl(const QUrl &url)
{
if (url.isEmpty()) {
return;
}
bool blocked = blockSignals(true);
// check for duplicates
auto mit = d->itemMapper.constBegin();
QString urlToInsert = url.toString(QUrl::StripTrailingSlash);
while (mit != d->itemMapper.constEnd()) {
Q_ASSERT(mit.value());
if (urlToInsert == mit.value()->url.toString(QUrl::StripTrailingSlash)) {
setCurrentIndex(mit.key());
if (d->myMode == Directories) {
d->updateItem(mit.value(), mit.key(), d->opendirIcon);
}
blockSignals(blocked);
return;
}
++mit;
}
// not in the combo yet -> create a new item and insert it
// first remove the old item
if (d->urlAdded) {
Q_ASSERT(!d->itemList.empty());
d->itemList.pop_back();
d->urlAdded = false;
}
setDefaults();
const int offset = qMax(0, static_cast<int>(d->itemList.size() + d->defaultList.size()) - d->myMaximum);
for (size_t i = offset; i < d->itemList.size(); ++i) {
d->insertUrlItem(d->itemList.at(i).get());
}
std::unique_ptr<KUrlComboBoxPrivate::KUrlComboItem> item(new KUrlComboBoxPrivate::KUrlComboItem(url, d->getIcon(url)));
const int id = count();
const QString text = d->textForItem(item.get());
if (d->myMode == Directories) {
KComboBox::insertItem(id, d->opendirIcon, text);
} else {
KComboBox::insertItem(id, item->icon, text);
}
d->itemMapper.insert(id, item.get());
d->itemList.push_back(std::move(item));
setCurrentIndex(id);
Q_ASSERT(!d->itemList.empty());
d->urlAdded = true;
blockSignals(blocked);
}
void KUrlComboBoxPrivate::slotActivated(int index)
{
auto item = itemMapper.value(index);
if (item) {
m_parent->setUrl(item->url);
Q_EMIT m_parent->urlActivated(item->url);
}
}
void KUrlComboBoxPrivate::insertUrlItem(const KUrlComboItem *item)
{
Q_ASSERT(item);
// qDebug() << "insertURLItem " << d->textForItem(item);
int id = m_parent->count();
m_parent->KComboBox::insertItem(id, item->icon, textForItem(item));
itemMapper.insert(id, item);
}
void KUrlComboBox::setMaxItems(int max)
{
d->myMaximum = max;
if (count() > d->myMaximum) {
int oldCurrent = currentIndex();
setDefaults();
const int offset = qMax(0, static_cast<int>(d->itemList.size() + d->defaultList.size()) - d->myMaximum);
for (size_t i = offset; i < d->itemList.size(); ++i) {
d->insertUrlItem(d->itemList.at(i).get());
}
if (count() > 0) { // restore the previous currentItem
if (oldCurrent >= count()) {
oldCurrent = count() - 1;
}
setCurrentIndex(oldCurrent);
}
}
}
int KUrlComboBox::maxItems() const
{
return d->myMaximum;
}
void KUrlComboBox::removeUrl(const QUrl &url, bool checkDefaultURLs)
{
auto mit = d->itemMapper.constBegin();
while (mit != d->itemMapper.constEnd()) {
if (url.toString(QUrl::StripTrailingSlash) == mit.value()->url.toString(QUrl::StripTrailingSlash)) {
auto removePredicate = [&mit](const std::unique_ptr<const KUrlComboBoxPrivate::KUrlComboItem> &item) {
return item.get() == mit.value();
};
d->itemList.erase(std::remove_if(d->itemList.begin(), d->itemList.end(), removePredicate), d->itemList.end());
if (checkDefaultURLs) {
d->defaultList.erase(std::remove_if(d->defaultList.begin(), d->defaultList.end(), removePredicate), d->defaultList.end());
}
}
++mit;
}
bool blocked = blockSignals(true);
setDefaults();
for (const auto &item : d->itemList) {
d->insertUrlItem(item.get());
}
blockSignals(blocked);
}
void KUrlComboBox::setCompletionObject(KCompletion *compObj, bool hsig)
{
if (compObj) {
// on a url combo box we want completion matches to be sorted. This way, if we are given
// a suggestion, we match the "best" one. For instance, if we have "foo" and "foobar",
// and we write "foo", the match is "foo" and never "foobar". (ereslibre)
compObj->setOrder(KCompletion::Sorted);
}
KComboBox::setCompletionObject(compObj, hsig);
}
void KUrlComboBox::mousePressEvent(QMouseEvent *event)
{
QStyleOptionComboBox comboOpt;
comboOpt.initFrom(this);
const int x0 =
QStyle::visualRect(layoutDirection(), rect(), style()->subControlRect(QStyle::CC_ComboBox, &comboOpt, QStyle::SC_ComboBoxEditField, this)).x();
const int frameWidth = style()->pixelMetric(QStyle::PM_DefaultFrameWidth, &comboOpt, this);
if (qRound(event->position().x()) < (x0 + KIconLoader::SizeSmall + frameWidth)) {
d->m_dragPoint = event->pos();
} else {
d->m_dragPoint = QPoint();
}
KComboBox::mousePressEvent(event);
}
void KUrlComboBox::mouseMoveEvent(QMouseEvent *event)
{
const int index = currentIndex();
auto item = d->itemMapper.value(index);
if (item && !d->m_dragPoint.isNull() && event->buttons() & Qt::LeftButton
&& (event->pos() - d->m_dragPoint).manhattanLength() > QApplication::startDragDistance()) {
QDrag *drag = new QDrag(this);
QMimeData *mime = new QMimeData();
mime->setUrls(QList<QUrl>() << item->url);
mime->setText(itemText(index));
if (!itemIcon(index).isNull()) {
drag->setPixmap(itemIcon(index).pixmap(KIconLoader::SizeMedium));
}
drag->setMimeData(mime);
drag->exec();
}
KComboBox::mouseMoveEvent(event);
}
QIcon KUrlComboBoxPrivate::getIcon(const QUrl &url) const
{
if (myMode == KUrlComboBox::Directories) {
return dirIcon;
} else {
return QIcon::fromTheme(KIO::iconNameForUrl(url));
}
}
// updates "item" with icon "icon"
// kdelibs4 used to also say "and sets the URL instead of text", but this breaks const-ness,
// now that it would require clearing the text, and I don't see the point since the URL was already in the text.
void KUrlComboBoxPrivate::updateItem(const KUrlComboItem *item, int index, const QIcon &icon)
{
m_parent->setItemIcon(index, icon);
m_parent->setItemText(index, textForItem(item));
}
#include "moc_kurlcombobox.cpp"
@@ -0,0 +1,203 @@
/*
This file is part of the KDE libraries
SPDX-FileCopyrightText: 2000 Carsten Pfeiffer <pfeiffer@kde.org>
SPDX-License-Identifier: LGPL-2.0-only
*/
#ifndef KURLCOMBOBOX_H
#define KURLCOMBOBOX_H
#include "kiowidgets_export.h"
#include <QIcon>
#include <QList>
#include <QMap>
#include <QStringList>
#include <KComboBox>
#include <memory>
class QUrl;
class KUrlComboBoxPrivate;
/**
* @class KUrlComboBox kurlcombobox.h <KUrlComboBox>
*
* This combobox shows a number of recent URLs/directories, as well as some
* default directories.
* It will manage the default dirs root-directory, home-directory and
* Desktop-directory, as well as a number of URLs set via setUrls()
* and one additional entry to be set via setUrl().
*
* This widget forces the layout direction to be Qt::LeftToRight instead
* of inheriting the layout direction like a normal widget. This means
* that even in RTL desktops the widget will be displayed in LTR mode,
* as generally URLs are LTR by nature.
*
* @short A combo box showing a number of recent URLs/directories
* @author Carsten Pfeiffer <pfeiffer@kde.org>
*/
class KIOWIDGETS_EXPORT KUrlComboBox : public KComboBox
{
Q_OBJECT
Q_PROPERTY(QStringList urls READ urls WRITE setUrls DESIGNABLE true)
Q_PROPERTY(int maxItems READ maxItems WRITE setMaxItems DESIGNABLE true)
public:
/**
* This enum describes which kind of items is shown in the combo box.
*/
enum Mode {
Files = -1,
Directories = 1,
Both = 0
};
/**
* This Enumeration is used in setUrl() to determine which items
* will be removed when the given list is larger than maxItems().
*
* @li RemoveTop means that items will be removed from top
* @li RemoveBottom means, that items will be removed from the bottom
*/
enum OverLoadResolving {
RemoveTop,
RemoveBottom
};
/**
* Constructs a KUrlComboBox.
* @param mode is either Files, Directories or Both and controls the
* following behavior:
* @li Files all inserted URLs will be treated as files, therefore the
* url shown in the combo will never show a trailing /
* the icon will be the one associated with the file's MIME type.
* @li Directories all inserted URLs will be treated as directories, will
* have a trailing slash in the combobox. The current
* directory will show the "open folder" icon, other
* directories the "folder" icon.
* @li Both Don't mess with anything, just show the url as given.
* @param parent The parent object of this widget.
*/
explicit KUrlComboBox(Mode mode, QWidget *parent = nullptr);
KUrlComboBox(Mode mode, bool rw, QWidget *parent = nullptr);
/**
* Destructs the combo box.
*/
~KUrlComboBox() override;
/**
* Sets the current url. This combo handles exactly one url additionally
* to the default items and those set via setUrls(). So you can call
* setUrl() as often as you want, it will always replace the previous one
* set via setUrl().
* If @p url is already in the combo, the last item will stay there
* and the existing item becomes the current item.
* The current item will always have the open-directory-pixmap as icon.
*
* Note that you won't receive any signals, e.g. textChanged(),
* returnPressed() or activated() upon calling this method.
*/
void setUrl(const QUrl &url);
/**
* Inserts @p urls into the combobox below the "default urls" (see
* addDefaultUrl).
*
* If the list of urls contains more items than maxItems, the first items
* will be stripped.
*/
void setUrls(const QStringList &urls);
/**
* Inserts @p urls into the combobox below the "default urls" (see
* addDefaultUrl).
*
* If the list of urls contains more items than maxItems, the @p remove
* parameter determines whether the first or last items will be stripped.
*/
void setUrls(const QStringList &urls, OverLoadResolving remove);
/**
* @returns a list of all urls currently handled. The list contains at most
* maxItems() items.
* Use this to save the list of urls in a config-file and reinsert them
* via setUrls() next time.
* Note that all default urls set via addDefaultUrl() are not
* returned, they will automatically be set via setUrls() or setUrl().
* You will always get fully qualified urls, i.e. with protocol like
* file:/
*/
QStringList urls() const;
/**
* Sets how many items should be handled and displayed by the combobox.
* @see maxItems
*/
void setMaxItems(int);
/**
* @returns the maximum of items the combobox handles.
* @see setMaxItems
*/
int maxItems() const;
/**
* Adds a url that will always be shown in the combobox, it can't be
* "rotated away". Default urls won't be returned in urls() and don't
* have to be set via setUrls().
* If you want to specify a special pixmap, use the overloaded method with
* the pixmap parameter.
* Default URLs will be inserted into the combobox by setDefaults()
*/
void addDefaultUrl(const QUrl &url, const QString &text = QString());
/**
* Adds a url that will always be shown in the combobox, it can't be
* "rotated away". Default urls won't be returned in urls() and don't
* have to be set via setUrls().
* If you don't need to specify a pixmap, use the overloaded method without
* the pixmap parameter.
* Default URLs will be inserted into the combobox by setDefaults()
*/
void addDefaultUrl(const QUrl &url, const QIcon &icon, const QString &text = QString());
/**
* Clears all items and inserts the default urls into the combo. Will be
* called implicitly upon the first call to setUrls() or setUrl()
* @see addDefaultUrl
*/
void setDefaults();
/**
* Removes any occurrence of @p url. If @p checkDefaultUrls is false
* default-urls won't be removed.
*/
void removeUrl(const QUrl &url, bool checkDefaultURLs = true);
/**
* Reimplemented from KComboBox (from KCompletion)
* @internal
*/
void setCompletionObject(KCompletion *compObj, bool hsig = true) override;
Q_SIGNALS:
/**
* Emitted when an item was clicked at.
* @param url is the url of the now current item.
*/
void urlActivated(const QUrl &url);
protected:
void mousePressEvent(QMouseEvent *event) override;
void mouseMoveEvent(QMouseEvent *event) override;
private:
friend class KUrlComboBoxPrivate;
std::unique_ptr<KUrlComboBoxPrivate> const d;
Q_DISABLE_COPY(KUrlComboBox)
};
#endif // KURLCOMBOBOX_H
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,194 @@
/*
This file is part of the KDE libraries
SPDX-FileCopyrightText: 2000 David Smith <dsmith@algonet.se>
This class was inspired by a previous KUrlCompletion by
SPDX-FileContributor: Henner Zeller <zeller@think.de>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#ifndef KURLCOMPLETION_H
#define KURLCOMPLETION_H
#include "kiowidgets_export.h"
#include <kio/udsentry.h>
#include <KCompletion>
#include <QString>
#include <QStringList>
#include <memory>
namespace KIO
{
class Job;
}
class KUrlCompletionPrivate;
/**
* @class KUrlCompletion kurlcompletion.h <KUrlCompletion>
*
* This class does completion of URLs including user directories (~user)
* and environment variables. Remote URLs are passed to KIO.
*
* @short Completion of a single URL
* @author David Smith <dsmith@algonet.se>
*/
class KIOWIDGETS_EXPORT KUrlCompletion : public KCompletion
{
Q_OBJECT
public:
/**
* Determines how completion is done.
* @li ExeCompletion - executables in $PATH or with full path.
* @li FileCompletion - all files with full path or in dir(), URLs
* are listed using KIO.
* @li DirCompletion - Same as FileCompletion but only returns directories.
*/
enum Mode {
ExeCompletion = 1,
FileCompletion,
DirCompletion
};
/**
* Constructs a KUrlCompletion object in FileCompletion mode.
*/
KUrlCompletion();
/**
* This overloaded constructor allows you to set the Mode to ExeCompletion
* or FileCompletion without using setMode. Default is FileCompletion.
*/
KUrlCompletion(Mode);
/**
* Destructs the KUrlCompletion object.
*/
~KUrlCompletion() override;
/**
* Finds completions to the given text.
*
* Remote URLs are listed with KIO. For performance reasons, local files
* are listed with KIO only if KURLCOMPLETION_LOCAL_KIO is set.
* The completion is done asynchronously if KIO is used.
*
* Returns the first match for user, environment, and local dir completion
* and QString() for asynchronous completion (KIO or threaded).
*
* @param text the text to complete
* @return the first match, or QString() if not found
*/
QString makeCompletion(const QString &text) override;
/**
* Sets the current directory (used as base for completion).
* Default = $HOME.
* @param dir the current directory, as a URL (use QUrl::fromLocalFile for local paths)
*/
virtual void setDir(const QUrl &dir);
/**
* Returns the current directory, as it was given in setDir
* @return the current directory, as a URL (use QUrl::toLocalFile for local paths)
*/
virtual QUrl dir() const;
/**
* Check whether asynchronous completion is in progress.
* @return true if asynchronous completion is in progress
*/
virtual bool isRunning() const;
/**
* Stops asynchronous completion.
*/
virtual void stop();
/**
* Returns the completion mode: exe or file completion (default FileCompletion).
* @return the completion mode
*/
virtual Mode mode() const;
/**
* Changes the completion mode: exe or file completion
* @param mode the new completion mode
*/
virtual void setMode(Mode mode);
/**
* Checks whether environment variables are completed and
* whether they are replaced internally while finding completions.
* Default is enabled.
* @return true if environment variables will be replaced
*/
virtual bool replaceEnv() const;
/**
* Enables/disables completion and replacement (internally) of
* environment variables in URLs. Default is enabled.
* @param replace true to replace environment variables
*/
virtual void setReplaceEnv(bool replace);
/**
* Returns whether ~username is completed and whether ~username
* is replaced internally with the user's home directory while
* finding completions. Default is enabled.
* @return true to replace tilde with the home directory
*/
virtual bool replaceHome() const;
/**
* Enables/disables completion of ~username and replacement
* (internally) of ~username with the user's home directory.
* Default is enabled.
* @param replace true to replace tilde with the home directory
*/
virtual void setReplaceHome(bool replace);
/**
* Replaces username and/or environment variables, depending on the
* current settings and returns the filtered url. Only works with
* local files, i.e. returns back the original string for non-local
* urls.
* @param text the text to process
* @return the path or URL resulting from this operation. If you
* want to convert it to a QUrl, use QUrl::fromUserInput.
*/
QString replacedPath(const QString &text) const;
/**
* @internal I'll let ossi add a real one to KShell :)
*/
static QString replacedPath(const QString &text, bool replaceHome, bool replaceEnv = true);
/**
* Sets the MIME type filters for the file dialog.
* @see QFileDialog::setMimeTypeFilters()
* @since 5.38
*/
void setMimeTypeFilters(const QStringList &mimeTypes);
/**
* Returns the MIME type filters for the file dialog.
* @see QFileDialog::mimeTypeFilters()
* @since 5.38
*/
QStringList mimeTypeFilters() const;
protected:
// Called by KCompletion, adds '/' to directories
void postProcessMatch(QString *match) const override;
void postProcessMatches(QStringList *matches) const override;
void postProcessMatches(KCompletionMatches *matches) const override;
private:
std::unique_ptr<KUrlCompletionPrivate> const d;
};
#endif // KURLCOMPLETION_H
@@ -0,0 +1,689 @@
/*
This file is part of the KDE libraries
SPDX-FileCopyrightText: 1999, 2000, 2001 Carsten Pfeiffer <pfeiffer@kde.org>
SPDX-FileCopyrightText: 2013 Teo Mrnjavac <teo@kde.org>
SPDX-License-Identifier: LGPL-2.0-only
*/
#include "kurlrequester.h"
#include "../utils_p.h"
#include "kio_widgets_debug.h"
#include <KComboBox>
#include <KDragWidgetDecorator>
#include <KLineEdit>
#include <KLocalizedString>
#include <kprotocolmanager.h>
#include <kurlcompletion.h>
#include <QAction>
#include <QApplication>
#include <QDrag>
#include <QEvent>
#include <QHBoxLayout>
#include <QKeySequence>
#include <QMenu>
#include <QMimeData>
class KUrlDragPushButton : public QPushButton
{
Q_OBJECT
public:
explicit KUrlDragPushButton(QWidget *parent)
: QPushButton(parent)
{
new DragDecorator(this);
}
~KUrlDragPushButton() override
{
}
void setURL(const QUrl &url)
{
m_urls.clear();
m_urls.append(url);
}
private:
class DragDecorator : public KDragWidgetDecoratorBase
{
public:
explicit DragDecorator(KUrlDragPushButton *button)
: KDragWidgetDecoratorBase(button)
, m_button(button)
{
}
protected:
QDrag *dragObject() override
{
if (m_button->m_urls.isEmpty()) {
return nullptr;
}
QDrag *drag = new QDrag(m_button);
QMimeData *mimeData = new QMimeData;
mimeData->setUrls(m_button->m_urls);
drag->setMimeData(mimeData);
return drag;
}
private:
KUrlDragPushButton *m_button;
};
QList<QUrl> m_urls;
};
class Q_DECL_HIDDEN KUrlRequester::KUrlRequesterPrivate
{
public:
explicit KUrlRequesterPrivate(KUrlRequester *parent)
: m_fileDialogModeWasDirAndFile(false)
, m_parent(parent)
, edit(nullptr)
, combo(nullptr)
, fileDialogMode(KFile::File | KFile::ExistingOnly | KFile::LocalOnly)
, fileDialogAcceptMode(QFileDialog::AcceptOpen)
{
}
~KUrlRequesterPrivate()
{
delete myCompletion;
delete myFileDialog;
}
void init();
void setText(const QString &text)
{
if (combo) {
if (combo->isEditable()) {
combo->setEditText(text);
} else {
int i = combo->findText(text);
if (i == -1) {
combo->addItem(text);
combo->setCurrentIndex(combo->count() - 1);
} else {
combo->setCurrentIndex(i);
}
}
} else {
edit->setText(text);
}
}
void connectSignals(KUrlRequester *receiver)
{
if (combo) {
connect(combo, &QComboBox::currentTextChanged, receiver, &KUrlRequester::textChanged);
connect(combo, &QComboBox::editTextChanged, receiver, &KUrlRequester::textEdited);
connect(combo, &KComboBox::returnPressed, receiver, &KUrlRequester::returnPressed);
} else if (edit) {
connect(edit, &QLineEdit::textChanged, receiver, &KUrlRequester::textChanged);
connect(edit, &QLineEdit::textEdited, receiver, &KUrlRequester::textEdited);
connect(edit, qOverload<>(&QLineEdit::returnPressed), receiver, [this]() {
m_parent->Q_EMIT returnPressed(QString{});
});
if (auto kline = qobject_cast<KLineEdit *>(edit)) {
connect(kline, &KLineEdit::returnKeyPressed, receiver, &KUrlRequester::returnPressed);
}
}
}
void setCompletionObject(KCompletion *comp)
{
if (combo) {
combo->setCompletionObject(comp);
} else {
edit->setCompletionObject(comp);
}
}
void updateCompletionStartDir(const QUrl &newStartDir)
{
myCompletion->setDir(newStartDir);
}
QString text() const
{
return combo ? combo->currentText() : edit->text();
}
/**
* replaces ~user or $FOO, if necessary
* if text() is a relative path, make it absolute using startDir()
*/
QUrl url() const
{
const QString txt = text();
KUrlCompletion *comp;
if (combo) {
comp = qobject_cast<KUrlCompletion *>(combo->completionObject());
} else {
comp = qobject_cast<KUrlCompletion *>(edit->completionObject());
}
QString enteredPath;
if (comp) {
enteredPath = comp->replacedPath(txt);
} else {
enteredPath = txt;
}
if (Utils::isAbsoluteLocalPath(enteredPath)) {
return QUrl::fromLocalFile(enteredPath);
}
const QUrl enteredUrl = QUrl(enteredPath); // absolute or relative
if (enteredUrl.isRelative() && !txt.isEmpty()) {
QUrl finalUrl(m_startDir);
finalUrl.setPath(Utils::concatPaths(finalUrl.path(), enteredPath));
return finalUrl;
} else {
return enteredUrl;
}
}
static void applyFileMode(QFileDialog *dlg, KFile::Modes m, QFileDialog::AcceptMode acceptMode)
{
QFileDialog::FileMode fileMode;
bool dirsOnly = false;
if (m & KFile::Directory) {
fileMode = QFileDialog::Directory;
if ((m & KFile::File) == 0 && (m & KFile::Files) == 0) {
dirsOnly = true;
}
} else if (m & KFile::Files && m & KFile::ExistingOnly) {
fileMode = QFileDialog::ExistingFiles;
} else if (m & KFile::File && m & KFile::ExistingOnly) {
fileMode = QFileDialog::ExistingFile;
} else {
fileMode = QFileDialog::AnyFile;
}
dlg->setFileMode(fileMode);
dlg->setAcceptMode(acceptMode);
dlg->setOption(QFileDialog::ShowDirsOnly, dirsOnly);
}
QUrl getDirFromFileDialog(const QUrl &openUrl) const
{
return QFileDialog::getExistingDirectoryUrl(m_parent, QString(), openUrl, QFileDialog::ShowDirsOnly);
}
void createFileDialog()
{
// Creates the fileDialog if it doesn't exist yet
QFileDialog *dlg = m_parent->fileDialog();
if (!url().isEmpty() && !url().isRelative()) {
QUrl u(url());
// If we won't be able to list it (e.g. http), then don't try :)
if (KProtocolManager::supportsListing(u)) {
dlg->selectUrl(u);
}
} else {
dlg->setDirectoryUrl(m_startDir);
}
dlg->setAcceptMode(fileDialogAcceptMode);
// Update the file dialog window modality
if (dlg->windowModality() != fileDialogModality) {
dlg->setWindowModality(fileDialogModality);
}
if (fileDialogModality == Qt::NonModal) {
dlg->show();
} else {
dlg->exec();
}
}
// slots
void slotUpdateUrl();
void slotOpenDialog();
void slotFileDialogAccepted();
QUrl m_startDir;
bool m_startDirCustomized;
bool m_fileDialogModeWasDirAndFile;
KUrlRequester *const m_parent; // TODO: rename to 'q'
KLineEdit *edit;
KComboBox *combo;
KFile::Modes fileDialogMode;
QFileDialog::AcceptMode fileDialogAcceptMode;
QStringList nameFilters;
QStringList mimeTypeFilters;
KEditListWidget::CustomEditor editor;
KUrlDragPushButton *myButton;
QFileDialog *myFileDialog;
KUrlCompletion *myCompletion;
Qt::WindowModality fileDialogModality;
};
KUrlRequester::KUrlRequester(QWidget *editWidget, QWidget *parent)
: QWidget(parent)
, d(new KUrlRequesterPrivate(this))
{
// must have this as parent
editWidget->setParent(this);
d->combo = qobject_cast<KComboBox *>(editWidget);
d->edit = qobject_cast<KLineEdit *>(editWidget);
if (d->edit) {
d->edit->setClearButtonEnabled(true);
}
d->init();
}
KUrlRequester::KUrlRequester(QWidget *parent)
: QWidget(parent)
, d(new KUrlRequesterPrivate(this))
{
d->init();
}
KUrlRequester::KUrlRequester(const QUrl &url, QWidget *parent)
: QWidget(parent)
, d(new KUrlRequesterPrivate(this))
{
d->init();
setUrl(url);
}
KUrlRequester::~KUrlRequester()
{
QWidget *widget = d->combo ? static_cast<QWidget *>(d->combo) : static_cast<QWidget *>(d->edit);
widget->removeEventFilter(this);
}
void KUrlRequester::KUrlRequesterPrivate::init()
{
myFileDialog = nullptr;
fileDialogModality = Qt::ApplicationModal;
if (!combo && !edit) {
edit = new KLineEdit(m_parent);
edit->setClearButtonEnabled(true);
}
QWidget *widget = combo ? static_cast<QWidget *>(combo) : static_cast<QWidget *>(edit);
QHBoxLayout *topLayout = new QHBoxLayout(m_parent);
topLayout->setContentsMargins(0, 0, 0, 0);
topLayout->setSpacing(-1); // use default spacing
topLayout->addWidget(widget);
myButton = new KUrlDragPushButton(m_parent);
myButton->setIcon(QIcon::fromTheme(QStringLiteral("document-open")));
int buttonSize = myButton->sizeHint().expandedTo(widget->sizeHint()).height();
myButton->setFixedSize(buttonSize, buttonSize);
myButton->setToolTip(i18n("Open file dialog"));
connect(myButton, &KUrlDragPushButton::pressed, m_parent, [this]() {
slotUpdateUrl();
});
widget->installEventFilter(m_parent);
m_parent->setFocusProxy(widget);
m_parent->setFocusPolicy(Qt::StrongFocus);
topLayout->addWidget(myButton);
connectSignals(m_parent);
connect(myButton, &KUrlDragPushButton::clicked, m_parent, [this]() {
slotOpenDialog();
});
m_startDir = QUrl::fromLocalFile(QDir::currentPath());
m_startDirCustomized = false;
myCompletion = new KUrlCompletion();
updateCompletionStartDir(m_startDir);
setCompletionObject(myCompletion);
QAction *openAction = new QAction(m_parent);
openAction->setShortcut(QKeySequence::Open);
m_parent->connect(openAction, &QAction::triggered, m_parent, [this]() {
slotOpenDialog();
});
}
void KUrlRequester::setUrl(const QUrl &url)
{
d->setText(url.toDisplayString(QUrl::PreferLocalFile));
}
void KUrlRequester::setText(const QString &text)
{
d->setText(text);
}
void KUrlRequester::setStartDir(const QUrl &startDir)
{
d->m_startDir = startDir;
d->m_startDirCustomized = true;
d->updateCompletionStartDir(startDir);
}
void KUrlRequester::changeEvent(QEvent *e)
{
if (e->type() == QEvent::WindowTitleChange) {
if (d->myFileDialog) {
d->myFileDialog->setWindowTitle(windowTitle());
}
}
QWidget::changeEvent(e);
}
QUrl KUrlRequester::url() const
{
return d->url();
}
QUrl KUrlRequester::startDir() const
{
return d->m_startDir;
}
QString KUrlRequester::text() const
{
return d->text();
}
void KUrlRequester::KUrlRequesterPrivate::slotOpenDialog()
{
if (myFileDialog) {
if (myFileDialog->isVisible()) {
// The file dialog is already being shown, raise it and exit
myFileDialog->raise();
myFileDialog->activateWindow();
return;
}
}
if (!m_fileDialogModeWasDirAndFile
&& (((fileDialogMode & KFile::Directory) && !(fileDialogMode & KFile::File)) ||
/* catch possible fileDialog()->setMode( KFile::Directory ) changes */
(myFileDialog && (myFileDialog->fileMode() == QFileDialog::Directory && myFileDialog->testOption(QFileDialog::ShowDirsOnly))))) {
const QUrl openUrl = (!m_parent->url().isEmpty() && !m_parent->url().isRelative()) ? m_parent->url() : m_startDir;
/* FIXME We need a new abstract interface for using KDirSelectDialog in a non-modal way */
QUrl newUrl;
if (fileDialogMode & KFile::LocalOnly) {
newUrl = QFileDialog::getExistingDirectoryUrl(m_parent, QString(), openUrl, QFileDialog::ShowDirsOnly, QStringList() << QStringLiteral("file"));
} else {
newUrl = getDirFromFileDialog(openUrl);
}
if (newUrl.isValid()) {
m_parent->setUrl(newUrl);
Q_EMIT m_parent->urlSelected(url());
}
} else {
Q_EMIT m_parent->openFileDialog(m_parent);
if (((fileDialogMode & KFile::Directory) && (fileDialogMode & KFile::File)) || m_fileDialogModeWasDirAndFile) {
QMenu *dirOrFileMenu = new QMenu();
QAction *fileAction = new QAction(QIcon::fromTheme(QStringLiteral("document-new")), i18n("File"));
QAction *dirAction = new QAction(QIcon::fromTheme(QStringLiteral("folder-new")), i18n("Directory"));
dirOrFileMenu->addAction(fileAction);
dirOrFileMenu->addAction(dirAction);
connect(fileAction, &QAction::triggered, [this]() {
fileDialogMode = KFile::File;
applyFileMode(m_parent->fileDialog(), fileDialogMode, fileDialogAcceptMode);
m_fileDialogModeWasDirAndFile = true;
createFileDialog();
});
connect(dirAction, &QAction::triggered, [this]() {
fileDialogMode = KFile::Directory;
applyFileMode(m_parent->fileDialog(), fileDialogMode, fileDialogAcceptMode);
m_fileDialogModeWasDirAndFile = true;
createFileDialog();
});
dirOrFileMenu->exec(m_parent->mapToGlobal(QPoint(m_parent->width(), m_parent->height())));
return;
}
createFileDialog();
}
}
void KUrlRequester::KUrlRequesterPrivate::slotFileDialogAccepted()
{
if (!myFileDialog) {
return;
}
const QUrl newUrl = myFileDialog->selectedUrls().constFirst();
if (newUrl.isValid()) {
m_parent->setUrl(newUrl);
Q_EMIT m_parent->urlSelected(url());
// remember url as defaultStartDir and update startdir for autocompletion
if (newUrl.isLocalFile() && !m_startDirCustomized) {
m_startDir = newUrl.adjusted(QUrl::RemoveFilename);
updateCompletionStartDir(m_startDir);
}
}
}
void KUrlRequester::setMode(KFile::Modes mode)
{
Q_ASSERT((mode & KFile::Files) == 0);
d->fileDialogMode = mode;
if ((mode & KFile::Directory) && !(mode & KFile::File)) {
d->myCompletion->setMode(KUrlCompletion::DirCompletion);
}
if (d->myFileDialog) {
d->applyFileMode(d->myFileDialog, mode, d->fileDialogAcceptMode);
}
}
KFile::Modes KUrlRequester::mode() const
{
return d->fileDialogMode;
}
void KUrlRequester::setAcceptMode(QFileDialog::AcceptMode mode)
{
d->fileDialogAcceptMode = mode;
if (d->myFileDialog) {
d->applyFileMode(d->myFileDialog, d->fileDialogMode, mode);
}
}
QFileDialog::AcceptMode KUrlRequester::acceptMode() const
{
return d->fileDialogAcceptMode;
}
QStringList KUrlRequester::nameFilters() const
{
return d->nameFilters;
}
void KUrlRequester::setNameFilters(const QStringList &filters)
{
d->nameFilters = filters;
if (d->myFileDialog) {
d->myFileDialog->setNameFilters(d->nameFilters);
}
}
void KUrlRequester::setNameFilter(const QString &filter)
{
if (filter.isEmpty()) {
setNameFilters(QStringList());
return;
}
// by default use ";;" as separator
// if not present, support alternatively "\n" (matching QFileDialog behaviour)
// if also not present split() will simply return the string passed in
QString separator = QStringLiteral(";;");
if (!filter.contains(separator)) {
separator = QStringLiteral("\n");
}
setNameFilters(filter.split(separator));
}
void KUrlRequester::setMimeTypeFilters(const QStringList &mimeTypes)
{
d->mimeTypeFilters = mimeTypes;
if (d->myFileDialog) {
d->myFileDialog->setMimeTypeFilters(d->mimeTypeFilters);
}
d->myCompletion->setMimeTypeFilters(d->mimeTypeFilters);
}
QStringList KUrlRequester::mimeTypeFilters() const
{
return d->mimeTypeFilters;
}
QFileDialog *KUrlRequester::fileDialog() const
{
if (d->myFileDialog
&& ((d->myFileDialog->fileMode() == QFileDialog::Directory && !(d->fileDialogMode & KFile::Directory))
|| (d->myFileDialog->fileMode() != QFileDialog::Directory && (d->fileDialogMode & KFile::Directory)))) {
delete d->myFileDialog;
d->myFileDialog = nullptr;
}
if (!d->myFileDialog) {
d->myFileDialog = new QFileDialog(window(), windowTitle());
if (!d->mimeTypeFilters.isEmpty()) {
d->myFileDialog->setMimeTypeFilters(d->mimeTypeFilters);
} else {
d->myFileDialog->setNameFilters(d->nameFilters);
}
d->applyFileMode(d->myFileDialog, d->fileDialogMode, d->fileDialogAcceptMode);
d->myFileDialog->setWindowModality(d->fileDialogModality);
connect(d->myFileDialog, &QFileDialog::accepted, this, [this]() {
d->slotFileDialogAccepted();
});
}
return d->myFileDialog;
}
void KUrlRequester::clear()
{
d->setText(QString());
}
KLineEdit *KUrlRequester::lineEdit() const
{
return d->edit;
}
KComboBox *KUrlRequester::comboBox() const
{
return d->combo;
}
void KUrlRequester::KUrlRequesterPrivate::slotUpdateUrl()
{
const QUrl visibleUrl = url();
QUrl u = visibleUrl;
if (visibleUrl.isRelative()) {
u = QUrl::fromLocalFile(QDir::currentPath() + QLatin1Char('/')).resolved(visibleUrl);
}
myButton->setURL(u);
}
bool KUrlRequester::eventFilter(QObject *obj, QEvent *ev)
{
if ((d->edit == obj) || (d->combo == obj)) {
if ((ev->type() == QEvent::FocusIn) || (ev->type() == QEvent::FocusOut))
// Forward focusin/focusout events to the urlrequester; needed by file form element in khtml
{
QApplication::sendEvent(this, ev);
}
}
return QWidget::eventFilter(obj, ev);
}
QPushButton *KUrlRequester::button() const
{
return d->myButton;
}
KUrlCompletion *KUrlRequester::completionObject() const
{
return d->myCompletion;
}
void KUrlRequester::setPlaceholderText(const QString &msg)
{
if (d->edit) {
d->edit->setPlaceholderText(msg);
}
}
QString KUrlRequester::placeholderText() const
{
if (d->edit) {
return d->edit->placeholderText();
} else {
return QString();
}
}
Qt::WindowModality KUrlRequester::fileDialogModality() const
{
return d->fileDialogModality;
}
void KUrlRequester::setFileDialogModality(Qt::WindowModality modality)
{
d->fileDialogModality = modality;
}
const KEditListWidget::CustomEditor &KUrlRequester::customEditor()
{
setSizePolicy(QSizePolicy(QSizePolicy::Preferred, QSizePolicy::Fixed));
KLineEdit *edit = d->edit;
if (!edit && d->combo) {
edit = qobject_cast<KLineEdit *>(d->combo->lineEdit());
}
#ifndef NDEBUG
if (!edit) {
qCWarning(KIO_WIDGETS) << "KUrlRequester's lineedit is not a KLineEdit!??\n";
}
#endif
d->editor.setRepresentationWidget(this);
d->editor.setLineEdit(edit);
return d->editor;
}
KUrlComboRequester::KUrlComboRequester(QWidget *parent)
: KUrlRequester(new KComboBox(false), parent)
, d(nullptr)
{
}
#include "kurlrequester.moc"
#include "moc_kurlrequester.cpp"
@@ -0,0 +1,354 @@
/*
This file is part of the KDE libraries
SPDX-FileCopyrightText: 1999, 2000, 2001 Carsten Pfeiffer <pfeiffer@kde.org>
SPDX-FileCopyrightText: 2013 Teo Mrnjavac <teo@kde.org>
SPDX-License-Identifier: LGPL-2.0-only
*/
#ifndef KURLREQUESTER_H
#define KURLREQUESTER_H
#include "kiowidgets_export.h"
#include <KEditListWidget>
#include <kfile.h>
#include <QFileDialog>
#include <QPushButton>
#include <QUrl>
#include <memory>
class KComboBox;
class KLineEdit;
class KUrlCompletion;
class QEvent;
class QString;
/**
* @class KUrlRequester kurlrequester.h <KUrlRequester>
*
* This class is a widget showing a lineedit and a button, which invokes a
* filedialog. File name completion is available in the lineedit.
*
* The default for the filedialog is to ask for one existing local file, i.e.
* the default mode is 'KFile::File | KFile::ExistingOnly | KFile::LocalOnly',
* which you can change by using setMode().
*
* The default filter is "*", i.e. show all files, which you can change by
* using setNameFilters() or setMimeTypeFilters().
*
* By default the start directory is the current working directory, or the
* last directory where a file has been selected previously, you can change
* this behavior by calling setStartDir().
*
* The default window modality for the file dialog is Qt::ApplicationModal
*
* \image html kurlrequester.png "KUrlRequester"
*
* @short A widget to request a filename/url from the user
* @author Carsten Pfeiffer <pfeiffer@kde.org>
*/
class KIOWIDGETS_EXPORT KUrlRequester : public QWidget
{
Q_OBJECT
Q_PROPERTY(QUrl url READ url WRITE setUrl NOTIFY textChanged USER true)
/// @since 5.108
Q_PROPERTY(QStringList nameFilters READ nameFilters WRITE setNameFilters)
Q_PROPERTY(KFile::Modes mode READ mode WRITE setMode)
Q_PROPERTY(QFileDialog::AcceptMode acceptMode READ acceptMode WRITE setAcceptMode)
Q_PROPERTY(QString placeholderText READ placeholderText WRITE setPlaceholderText)
Q_PROPERTY(QString text READ text WRITE setText NOTIFY textChanged)
Q_PROPERTY(Qt::WindowModality fileDialogModality READ fileDialogModality WRITE setFileDialogModality)
public:
/**
* Constructs a KUrlRequester widget.
*/
explicit KUrlRequester(QWidget *parent = nullptr);
/**
* Constructs a KUrlRequester widget with the initial URL @p url.
*/
explicit KUrlRequester(const QUrl &url, QWidget *parent = nullptr);
/**
* Special constructor, which creates a KUrlRequester widget with a custom
* edit-widget. The edit-widget can be either a KComboBox or a KLineEdit
* (or inherited thereof). Note: for geometry management reasons, the
* edit-widget is reparented to have the KUrlRequester as parent.
*/
KUrlRequester(QWidget *editWidget, QWidget *parent);
/**
* Destructs the KUrlRequester.
*/
~KUrlRequester() override;
/**
* @returns the current url in the lineedit. May be malformed, if the user
* entered something weird. For local files, ~user or environment variables
* are substituted, relative paths will be resolved against startDir()
*/
QUrl url() const;
/**
* @returns the current start dir
*/
QUrl startDir() const;
/**
* @returns the current text in the lineedit or combobox.
* This does not do the URL expansion that url() does, it's only provided
* for cases where KUrlRequester is used to enter URL-or-something-else,
* like KOpenWithDialog where you can type a full command with arguments.
*
*/
QString text() const;
/**
* Sets the mode of the file dialog.
*
* The default mode of the file dialog is 'KFile::File | KFile::ExistingOnly | KFile::LocalOnly',
* which you can change using this method.
*
* @note You can only select one file from the file dialog invoked
* by KUrlRequester, hence setting KFile::Files doesn't make
* much sense here.
*
* @param mode an OR'ed combination of KFile::Modes flags
*
* @see QFileDialog::setFileMode()
*/
void setMode(KFile::Modes mode);
/**
* Returns the current mode
* @see QFileDialog::fileMode()
*/
KFile::Modes mode() const;
/**
* Sets the open / save mode of the file dialog.
*
* The default is QFileDialog::AcceptOpen.
*
* @see QFileDialog::setAcceptMode()
* @since 5.33
*/
void setAcceptMode(QFileDialog::AcceptMode m);
/**
* Returns the current open / save mode
* @see QFileDialog::acceptMode()
* @since 5.33
*/
QFileDialog::AcceptMode acceptMode() const;
/**
* Sets the filters for the file dialog.
* @see QFileDialog::setNameFilters()
* @since 5.108
*/
void setNameFilters(const QStringList &filters);
/**
* Sets the filters for the file dialog.
* @see QFileDialog::setNameFilter()
* @since 5.108
*/
void setNameFilter(const QString &filter);
/**
* Returns the filters for the file dialog.
* @see QFileDialog::nameFilters()
* @since 5.108
*/
QStringList nameFilters() const;
/**
* Sets the MIME type filters for the file dialog.
* @see QFileDialog::setMimeTypeFilters()
* @since 5.31
*/
void setMimeTypeFilters(const QStringList &mimeTypes);
/**
* Returns the MIME type filters for the file dialog.
* @see QFileDialog::mimeTypeFilters()
* @since 5.31
*/
QStringList mimeTypeFilters() const;
/**
* @returns a pointer to the filedialog.
* You can use this to customize the dialog, e.g. to call setLocationLabel
* or other things which are not accessible in the KUrlRequester API.
*
* Never returns 0. This method creates the file dialog on demand.
*
* @deprecated since 5.0. The dialog will be created anyway when the user
* requests it, and will behave according to the properties of KUrlRequester.
*/
KIOWIDGETS_DEPRECATED_VERSION(5, 0, "See API docs")
virtual QFileDialog *fileDialog() const;
/**
* @returns a pointer to the lineedit, either the default one, or the
* special one, if you used the special constructor.
*
* It is provided so that you can e.g. set an own completion object
* (e.g. KShellCompletion) into it.
*/
KLineEdit *lineEdit() const;
/**
* @returns a pointer to the combobox, in case you have set one using the
* special constructor. Returns 0L otherwise.
*/
KComboBox *comboBox() const;
/**
* @returns a pointer to the pushbutton. It is provided so that you can
* specify an own pixmap or a text, if you really need to.
*/
QPushButton *button() const;
/**
* @returns the KUrlCompletion object used in the lineedit/combobox.
*/
KUrlCompletion *completionObject() const;
/**
* @returns an object, suitable for use with KEditListWidget. It allows you
* to put this KUrlRequester into a KEditListWidget.
* Basically, do it like this:
* \code
* KUrlRequester *req = new KUrlRequester( someWidget );
* [...]
* KEditListWidget *editListWidget = new KEditListWidget( req->customEditor(), someWidget );
* \endcode
*/
const KEditListWidget::CustomEditor &customEditor();
/**
* @return the message set with setPlaceholderText
* @since 5.0
*/
QString placeholderText() const;
/**
* This makes the KUrlRequester line edit display a grayed-out hinting text as long as
* the user didn't enter any text. It is often used as indication about
* the purpose of the line edit.
* @since 5.0
*/
void setPlaceholderText(const QString &msg);
/**
* @returns the window modality of the file dialog set with setFileDialogModality
*/
Qt::WindowModality fileDialogModality() const;
/**
* Set the window modality for the file dialog to @p modality
* Directory selection dialogs are always modal
*
* The default is Qt::ApplicationModal.
*
*/
void setFileDialogModality(Qt::WindowModality modality);
public Q_SLOTS:
/**
* Sets the url in the lineedit to @p url.
*/
void setUrl(const QUrl &url);
/**
* Sets the start dir @p startDir.
* The start dir is only used when the URL isn't set.
*/
void setStartDir(const QUrl &startDir);
/**
* Sets the current text in the lineedit or combobox.
* This is used for cases where KUrlRequester is used to
* enter URL-or-something-else, like KOpenWithDialog where you
* can type a full command with arguments.
*
* @see text
*/
void setText(const QString &text);
/**
* Clears the lineedit/combobox.
*/
void clear();
Q_SIGNALS:
// forwards from LineEdit
/**
* Emitted when the text in the lineedit changes.
* The parameter contains the contents of the lineedit.
*/
void textChanged(const QString &);
/**
* Emitted when the text in the lineedit was modified by the user.
* Unlike textChanged(), this signal is not emitted when the text is changed programmatically, for example, by calling setText().
* @since 5.21
*/
void textEdited(const QString &);
/**
* Emitted when return or enter was pressed in the lineedit.
* The parameter contains the contents of the lineedit.
*/
void returnPressed(const QString &text);
/**
* Emitted before the filedialog is going to open. Connect
* to this signal to "configure" the filedialog, e.g. set the
* filefilter, the mode, a preview-widget, etc. It's usually
* not necessary to set a URL for the filedialog, as it will
* get set properly from the editfield contents.
*
* If you use multiple KUrlRequesters, you can connect all of them
* to the same slot and use the given KUrlRequester pointer to know
* which one is going to open.
*/
void openFileDialog(KUrlRequester *);
/**
* Emitted when the user changed the URL via the file dialog.
* The parameter contains the contents of the lineedit.
*/
void urlSelected(const QUrl &);
protected:
void changeEvent(QEvent *e) override;
bool eventFilter(QObject *obj, QEvent *ev) override;
private:
class KUrlRequesterPrivate;
std::unique_ptr<KUrlRequesterPrivate> const d;
Q_DISABLE_COPY(KUrlRequester)
};
class KIOWIDGETS_EXPORT KUrlComboRequester : public KUrlRequester // krazy:exclude=dpointer (For use in Qt Designer)
{
Q_OBJECT
public:
/**
* Constructs a KUrlRequester widget with a combobox.
*/
explicit KUrlComboRequester(QWidget *parent = nullptr);
private:
class Private;
Private *const d;
};
#endif // KURLREQUESTER_H
@@ -0,0 +1,122 @@
/*
This file is part of the KDE libraries
SPDX-FileCopyrightText: 2000 Wilco Greven <greven@kde.org>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#include "kurlrequesterdialog.h"
#include <QDialogButtonBox>
#include <QFileDialog>
#include <QLabel>
#include <QVBoxLayout>
#include <KLineEdit>
#include <KLocalizedString>
#include <KStandardGuiItem>
#include <krecentdocument.h>
#include <kurlrequester.h>
class KUrlRequesterDialogPrivate
{
public:
explicit KUrlRequesterDialogPrivate(KUrlRequesterDialog *qq)
: q(qq)
{
}
KUrlRequesterDialog *const q;
void initDialog(const QString &text, const QUrl &url);
// slots
void 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() = default;
void KUrlRequesterDialogPrivate::initDialog(const QString &text, const QUrl &urlName)
{
QVBoxLayout *topLayout = new QVBoxLayout(q);
QLabel *label = new QLabel(text, q);
label->setWordWrap(true);
topLayout->addWidget(label);
urlRequester = new KUrlRequester(urlName, q);
urlRequester->setMinimumWidth(urlRequester->sizeHint().width() * 3);
topLayout->addWidget(urlRequester);
urlRequester->setFocus();
QObject::connect(urlRequester->lineEdit(), &KLineEdit::textChanged, q, [this](const QString &text) {
slotTextChanged(text);
});
/*
KFile::Mode mode = static_cast<KFile::Mode>( KFile::File |
KFile::ExistingOnly );
urlRequester_->setMode( mode );
*/
buttonBox = new QDialogButtonBox(q);
buttonBox->setStandardButtons(QDialogButtonBox::Ok | QDialogButtonBox::Cancel);
QObject::connect(buttonBox, &QDialogButtonBox::accepted, q, &QDialog::accept);
QObject::connect(buttonBox, &QDialogButtonBox::rejected, q, &QDialog::reject);
topLayout->addWidget(buttonBox);
slotTextChanged(urlName.toString());
}
void KUrlRequesterDialogPrivate::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 &title)
{
KUrlRequesterDialog dlg(dir, parent);
dlg.setWindowTitle(title.isEmpty() ? i18n("Open") : title);
dlg.exec();
const QUrl &url = dlg.selectedUrl();
if (url.isValid()) {
KRecentDocument::add(url);
}
return url;
}
KUrlRequester *KUrlRequesterDialog::urlRequester()
{
return d->urlRequester;
}
#include "moc_kurlrequesterdialog.cpp"
@@ -0,0 +1,86 @@
/*
This file is part of the KDE libraries
SPDX-FileCopyrightText: 2000 Wilco Greven <greven@kde.org>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#ifndef KURLREQUESTERDIALOG_H
#define KURLREQUESTERDIALOG_H
#include "kiowidgets_export.h"
#include <QDialog>
#include <QUrl>
#include <memory>
class KUrlRequester;
class QFileDialog;
class KUrlRequesterDialogPrivate;
/**
* @class KUrlRequesterDialog kurlrequesterdialog.h <KUrlRequesterDialog>
*
* Dialog in which a user can enter a filename or url. It is a dialog
* encapsulating KUrlRequester.
*
* @short Simple dialog to enter a filename/url.
* @author Wilco Greven <greven@kde.org>
*/
class KIOWIDGETS_EXPORT KUrlRequesterDialog : public QDialog
{
Q_OBJECT
public:
/**
* Constructs a KUrlRequesterDialog.
*
* @param url The url of the directory to start in. Use QString()
* to start in the current working directory, or the last
* directory where a file has been selected.
* @param parent The parent object of this widget.
*/
explicit KUrlRequesterDialog(const QUrl &url, QWidget *parent = nullptr);
/**
* Constructs a KUrlRequesterDialog.
*
* @param url The url of the directory to start in. Use QString()
* to start in the current working directory, or the last
* directory where a file has been selected.
* @param text Text of the label
* @param parent The parent object of this widget.
*/
KUrlRequesterDialog(const QUrl &url, const QString &text, QWidget *parent);
/**
* Destructs the dialog.
*/
~KUrlRequesterDialog() override;
/**
* Returns the fully qualified filename.
*/
QUrl selectedUrl() const;
/**
* Creates a modal dialog, executes it and returns the selected URL.
*
* @param url This specifies the initial path of the input line.
* @param parent The widget the dialog will be centered on initially.
* @param title The title to use for the dialog.
*/
static QUrl getUrl(const QUrl &url = QUrl(), QWidget *parent = nullptr, const QString &title = QString());
/**
* Returns a pointer to the KUrlRequester.
*/
KUrlRequester *urlRequester();
private:
friend class KUrlRequesterDialogPrivate;
std::unique_ptr<KUrlRequesterDialogPrivate> const d;
Q_DISABLE_COPY(KUrlRequesterDialog)
};
#endif // KURLREQUESTERDIALOG_H
@@ -0,0 +1,15 @@
<!DOCTYPE node PUBLIC "-//freedesktop//DTD D-BUS Object Introspection 1.0//EN"
"http://www.freedesktop.org/standards/dbus/1.0/introspect.dtd">
<node>
<interface name="org.kde.libkonq.FileUndoManager">
<method name="get">
<arg name="commands" type="ay" direction="out"/>
</method>
<signal name="lock"/>
<signal name="pop"/>
<signal name="push">
<arg name="command" type="ay" direction="out"/>
</signal>
<signal name="unlock"/>
</interface>
</node>
@@ -0,0 +1,28 @@
<!DOCTYPE node PUBLIC "-//freedesktop//DTD D-BUS Object Introspection 1.0//EN" "http://www.freedesktop.org/standards/dbus/1.0/introspect.dtd">
<node>
<interface name="org.kde.kuiserver">
<method name="registerService">
<arg name="service" type="s" direction="in"/>
<arg name="objectPath" type="s" direction="in"/>
</method>
<method name="emitJobUrlsChanged">
</method>
<method name="requiresJobTracker">
<arg type="b" direction="out"/>
</method>
<signal name="jobUrlsChanged">
<arg type="as" direction="out"/>
<annotation name="org.qtproject.QtDBus.QtTypeName.Out0" value="QList&lt;QString&gt;"/>
</signal>
<signal name="requiresJobTrackerChanged">
<arg type="b" direction="out"/>
</signal>
</interface>
</node>
@@ -0,0 +1,285 @@
/*
This file is part of the KDE libraries
SPDX-FileCopyrightText: 2000 David Faure <faure@kde.org>
SPDX-License-Identifier: LGPL-2.0-only
*/
#include "paste.h"
#include "kio_widgets_debug.h"
#include "../utils_p.h"
#include "kio/copyjob.h"
#include "kio/deletejob.h"
#include "kio/global.h"
#include "kio/renamedialog.h"
#include "kio/statjob.h"
#include "pastedialog_p.h"
#include <kdirnotify.h>
#include <kfileitem.h>
#include <kfileitemlistproperties.h>
#include <kio/storedtransferjob.h>
#include <KJobWidgets>
#include <KLocalizedString>
#include <KMessageBox>
#include <KUrlMimeData>
#include <QApplication>
#include <QClipboard>
#include <QDebug>
#include <QFileInfo>
#include <QInputDialog>
#include <QMimeData>
#include <QMimeDatabase>
#include <QTemporaryFile>
static QUrl getDestinationUrl(const QUrl &srcUrl, const QUrl &destUrl, QWidget *widget)
{
KIO::StatJob *job = KIO::stat(destUrl, destUrl.isLocalFile() ? KIO::HideProgressInfo : KIO::DefaultFlags);
job->setDetails(KIO::StatBasic);
job->setSide(KIO::StatJob::DestinationSide);
KJobWidgets::setWindow(job, widget);
// Check for existing destination file.
// When we were using CopyJob, we couldn't let it do that (would expose
// an ugly tempfile name as the source URL)
// And now we're using a put job anyway, no destination checking included.
if (job->exec()) {
KIO::RenameDialog dlg(widget, i18n("File Already Exists"), srcUrl, destUrl, KIO::RenameDialog_Overwrite);
KIO::RenameDialog_Result res = static_cast<KIO::RenameDialog_Result>(dlg.exec());
if (res == KIO::Result_Rename) {
return dlg.newDestUrl();
} else if (res == KIO::Result_Cancel) {
return QUrl();
} else if (res == KIO::Result_Overwrite) {
return destUrl;
}
}
return destUrl;
}
static QUrl getNewFileName(const QUrl &u, const QString &text, const QString &suggestedFileName, QWidget *widget)
{
bool ok;
QString dialogText(text);
if (dialogText.isEmpty()) {
dialogText = i18n("Filename for clipboard content:");
}
QString file = QInputDialog::getText(widget, QString(), dialogText, QLineEdit::Normal, suggestedFileName, &ok);
if (!ok) {
return QUrl();
}
QUrl myurl(u);
myurl.setPath(Utils::concatPaths(myurl.path(), file));
return getDestinationUrl(u, myurl, widget);
}
static KIO::Job *putDataAsyncTo(const QUrl &url, const QByteArray &data, QWidget *widget, KIO::JobFlags flags)
{
KIO::Job *job = KIO::storedPut(data, url, -1, flags);
QObject::connect(job, &KIO::Job::result, [url](KJob *job) {
if (job->error() == KJob::NoError) {
#ifdef WITH_QTDBUS
org::kde::KDirNotify::emitFilesAdded(url.adjusted(QUrl::RemoveFilename | QUrl::StripTrailingSlash));
#endif
}
});
KJobWidgets::setWindow(job, widget);
return job;
}
static QByteArray chooseFormatAndUrl(const QUrl &u,
const QMimeData *mimeData,
const QStringList &formats,
const QString &text,
const QString &suggestedFileName,
QWidget *widget,
bool clipboard,
QUrl *newUrl)
{
QMimeDatabase db;
QStringList formatLabels;
formatLabels.reserve(formats.size());
for (int i = 0; i < formats.size(); ++i) {
const QString &fmt = formats[i];
QMimeType mime = db.mimeTypeForName(fmt);
if (mime.isValid()) {
formatLabels.append(i18n("%1 (%2)", mime.comment(), fmt));
} else {
formatLabels.append(fmt);
}
}
QString dialogText(text);
if (dialogText.isEmpty()) {
dialogText = i18n("Filename for clipboard content:");
}
KIO::PasteDialog dlg(QString(), dialogText, suggestedFileName, formatLabels, widget);
if (dlg.exec() != QDialog::Accepted) {
return QByteArray();
}
const QString chosenFormat = formats[dlg.comboItem()];
if (clipboard && !qApp->clipboard()->mimeData()->hasFormat(chosenFormat)) {
KMessageBox::information(widget,
i18n("The clipboard has changed since you used 'paste': "
"the chosen data format is no longer applicable. "
"Please copy again what you wanted to paste."));
return QByteArray();
}
const QString result = dlg.lineEditText();
// qDebug() << " result=" << result << " chosenFormat=" << chosenFormat;
*newUrl = u;
newUrl->setPath(Utils::concatPaths(newUrl->path(), result));
const QUrl destUrl = getDestinationUrl(u, *newUrl, widget);
*newUrl = destUrl;
// In Qt3, the result of clipboard()->mimeData() only existed until the next
// event loop run (see dlg.exec() above), so we re-fetched it.
// TODO: This should not be necessary with Qt5; remove this conditional
// and test that it still works.
if (clipboard) {
mimeData = QApplication::clipboard()->mimeData();
}
const QByteArray ba = mimeData->data(chosenFormat);
return ba;
}
static QStringList extractFormats(const QMimeData *mimeData)
{
QStringList formats;
const QStringList allFormats = mimeData->formats();
for (const QString &format : allFormats) {
if (format == QLatin1String("application/x-qiconlist")) { // Q3IconView and kde4's libkonq
continue;
}
if (format == QLatin1String("application/x-kde-cutselection")) { // see isClipboardDataCut
continue;
}
if (format == QLatin1String("application/x-kde-suggestedfilename")) {
continue;
}
if (format == QLatin1String("application/x-kde-onlyReplaceEmpty")) { // Prevents emptying Klipper via selection
continue;
}
if (format.startsWith(QLatin1String("application/x-qt-"))) { // Qt-internal
continue;
}
if (format.startsWith(QLatin1String("x-kmail-drag/"))) { // app-internal
continue;
}
if (!format.contains(QLatin1Char('/'))) { // e.g. TARGETS, MULTIPLE, TIMESTAMP
continue;
}
formats.append(format);
}
return formats;
}
KIOWIDGETS_EXPORT bool KIO::canPasteMimeData(const QMimeData *data)
{
return data->hasText() || !extractFormats(data).isEmpty();
}
KIO::Job *pasteMimeDataImpl(const QMimeData *mimeData, const QUrl &destUrl, const QString &dialogText, QWidget *widget, bool clipboard)
{
QByteArray ba;
const QString suggestedFilename = QString::fromUtf8(mimeData->data(QStringLiteral("application/x-kde-suggestedfilename")));
// Now check for plain text
// We don't want to display a MIME type choice for a QTextDrag, those MIME type look ugly.
if (mimeData->hasText()) {
ba = mimeData->text().toLocal8Bit(); // encoding OK?
} else {
auto formats = extractFormats(mimeData);
const auto firstFormat = formats.value(0);
// Remove formats that shouldn't be exposed to the user
erase_if(formats, [](const QString &string) -> bool {
return string.startsWith(u"application/x-kde-");
});
if (formats.isEmpty() && firstFormat.isEmpty()) {
return nullptr;
} else if (formats.size() > 1) {
QUrl newUrl;
ba = chooseFormatAndUrl(destUrl, mimeData, formats, dialogText, suggestedFilename, widget, clipboard, &newUrl);
if (ba.isEmpty() || newUrl.isEmpty()) {
return nullptr;
}
return putDataAsyncTo(newUrl, ba, widget, KIO::Overwrite);
}
ba = mimeData->data(firstFormat);
}
if (ba.isEmpty()) {
return nullptr;
}
const QUrl newUrl = getNewFileName(destUrl, dialogText, suggestedFilename, widget);
if (newUrl.isEmpty()) {
return nullptr;
}
return putDataAsyncTo(newUrl, ba, widget, KIO::Overwrite);
}
KIOWIDGETS_EXPORT QString KIO::pasteActionText(const QMimeData *mimeData, bool *enable, const KFileItem &destItem)
{
bool canPasteData = false;
QList<QUrl> urls;
// mimeData can be 0 according to https://bugs.kde.org/show_bug.cgi?id=335053
if (mimeData) {
canPasteData = KIO::canPasteMimeData(mimeData);
urls = KUrlMimeData::urlsFromMimeData(mimeData);
} else {
qCWarning(KIO_WIDGETS) << "QApplication::clipboard()->mimeData() is 0!";
}
QString text;
if (!urls.isEmpty() || canPasteData) {
// disable the paste action if no writing is supported
if (!destItem.isNull()) {
if (destItem.url().isEmpty()) {
*enable = false;
} else {
*enable = destItem.isWritable();
}
} else {
*enable = false;
}
if (urls.count() == 1 && urls.first().isLocalFile()) {
const bool isDir = QFileInfo(urls.first().toLocalFile()).isDir();
text = isDir ? i18nc("@action:inmenu", "Paste One Folder") : i18nc("@action:inmenu", "Paste One File");
} else if (!urls.isEmpty()) {
text = i18ncp("@action:inmenu", "Paste One Item", "Paste %1 Items", urls.count());
} else {
text = i18nc("@action:inmenu", "Paste Clipboard Contents…");
}
} else {
*enable = false;
text = i18nc("@action:inmenu", "Paste");
}
return text;
}
KIOWIDGETS_EXPORT void KIO::setClipboardDataCut(QMimeData *mimeData, bool cut)
{
const QByteArray cutSelectionData = cut ? "1" : "0";
mimeData->setData(QStringLiteral("application/x-kde-cutselection"), cutSelectionData);
}
KIOWIDGETS_EXPORT bool KIO::isClipboardDataCut(const QMimeData *mimeData)
{
const QByteArray a = mimeData->data(QStringLiteral("application/x-kde-cutselection"));
return (!a.isEmpty() && a.at(0) == '1');
}
@@ -0,0 +1,60 @@
/*
This file is part of the KDE libraries
SPDX-FileCopyrightText: 2000-2005 David Faure <faure@kde.org>
SPDX-License-Identifier: LGPL-2.0-only
*/
#ifndef KIO_PASTE_H
#define KIO_PASTE_H
#include "kiowidgets_export.h"
#include <QString>
class QWidget;
class QUrl;
class QMimeData;
class KFileItem;
namespace KIO
{
class Job;
class CopyJob;
/**
* Returns true if pasteMimeData will find any interesting format in @p data.
* You can use this method to enable/disable the paste action appropriately.
* @since 5.0 (was called canPasteMimeSource before)
*/
KIOWIDGETS_EXPORT bool canPasteMimeData(const QMimeData *data);
/**
* Returns the text to use for the Paste action, when the application supports
* pasting files, urls, and clipboard data, using pasteClipboard().
* @param mimeData the mime data, usually QApplication::clipboard()->mimeData().
* @param enable output parameter, to be passed to QAction::setEnabled.
* The pointer must be non-null, and in return the function will always set its value.
* @param destItem item representing the directory into which the clipboard data
* or items would be pasted. Used to find out about permissions in that directory.
* @return a string suitable for QAction::setText
* @since 5.4
*/
KIOWIDGETS_EXPORT QString pasteActionText(const QMimeData *mimeData, bool *enable, const KFileItem &destItem);
/**
* Add the information whether the files were cut, into the mimedata.
* @param mimeData pointer to the mimeData object to be populated. Must not be null.
* @param cut if true, the user selected "cut" (saved as application/x-kde-cutselection in the mimedata).
* @since 5.2
*/
KIOWIDGETS_EXPORT void setClipboardDataCut(QMimeData *mimeData, bool cut);
/**
* Returns true if the URLs in @p mimeData were cut by the user.
* This should be called when pasting, to choose between moving and copying.
* @since 5.2
*/
KIOWIDGETS_EXPORT bool isClipboardDataCut(const QMimeData *mimeData);
}
#endif
@@ -0,0 +1,69 @@
/*
This file is part of the KDE libraries
SPDX-FileCopyrightText: 2005 David Faure <faure@kde.org>
SPDX-License-Identifier: LGPL-2.0-only
*/
#include "pastedialog_p.h"
#include <KLocalizedString>
#include <QApplication>
#include <QClipboard>
#include <QComboBox>
#include <QDialogButtonBox>
#include <QLabel>
#include <QLineEdit>
#include <QVBoxLayout>
KIO::PasteDialog::PasteDialog(const QString &title, const QString &label, const QString &value, const QStringList &items, QWidget *parent)
: QDialog(parent)
{
setWindowTitle(title);
setModal(true);
QVBoxLayout *topLayout = new QVBoxLayout(this);
QFrame *frame = new QFrame(this);
topLayout->addWidget(frame);
QVBoxLayout *layout = new QVBoxLayout(frame);
m_label = new QLabel(label, frame);
m_label->setWordWrap(true);
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 QComboBox(frame);
m_comboBox->addItems(items);
layout->addWidget(m_comboBox);
layout->addStretch();
QDialogButtonBox *buttonBox = new QDialogButtonBox(this);
buttonBox->setStandardButtons(QDialogButtonBox::Ok | QDialogButtonBox::Cancel);
connect(buttonBox, &QDialogButtonBox::accepted, this, &QDialog::accept);
connect(buttonBox, &QDialogButtonBox::rejected, this, &QDialog::reject);
topLayout->addWidget(buttonBox);
setMinimumWidth(350);
}
QString KIO::PasteDialog::lineEditText() const
{
return m_lineEdit->text();
}
int KIO::PasteDialog::comboItem() const
{
return m_comboBox->currentIndex();
}
#include "moc_pastedialog_p.cpp"
@@ -0,0 +1,40 @@
/*
This file is part of the KDE libraries
SPDX-FileCopyrightText: 2005 David Faure <faure@kde.org>
SPDX-License-Identifier: LGPL-2.0-only
*/
#ifndef PASTEDIALOG_H
#define PASTEDIALOG_H
#include <QDialog>
class QComboBox;
class QLineEdit;
class QLabel;
namespace KIO
{
/**
* @internal
* Internal class used by paste.h. DO NOT USE.
*/
class PasteDialog : public QDialog
{
Q_OBJECT
public:
PasteDialog(const QString &title, const QString &label, const QString &value, const QStringList &items, QWidget *parent);
QString lineEditText() const;
int comboItem() const;
private:
QLabel *m_label;
QLineEdit *m_lineEdit;
QComboBox *m_comboBox;
};
} // namespace
#endif /* PASTEDIALOG_H */
@@ -0,0 +1,108 @@
/*
This file is part of the KDE libraries
SPDX-FileCopyrightText: 2014 David Faure <faure@kde.org>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#include "pastejob.h"
#include "pastejob_p.h"
#include "paste.h"
#include <QMimeData>
#include <QTimer>
#include <KIO/CopyJob>
#include <KIO/FileUndoManager>
#include <KJobWidgets>
#include <KLocalizedString>
#include <KUrlMimeData>
using namespace KIO;
extern KIO::Job *pasteMimeDataImpl(const QMimeData *mimeData, const QUrl &destUrl, const QString &dialogText, QWidget *widget, bool clipboard);
PasteJob::PasteJob(PasteJobPrivate &dd)
: Job(dd)
{
Q_D(PasteJob);
QTimer::singleShot(0, this, [d]() {
d->slotStart();
});
}
PasteJob::~PasteJob()
{
}
void PasteJobPrivate::slotStart()
{
Q_Q(PasteJob);
if (!m_mimeData) {
q->setError(KIO::ERR_NO_CONTENT);
q->emitResult();
return;
}
const bool move = KIO::isClipboardDataCut(m_mimeData);
KIO::Job *job = nullptr;
KIO::CopyJob *copyJob = nullptr;
if (m_mimeData->hasUrls()) {
const QList<QUrl> urls = KUrlMimeData::urlsFromMimeData(m_mimeData, KUrlMimeData::PreferLocalUrls);
if (!urls.isEmpty()) {
if (move) {
copyJob = KIO::move(urls, m_destDir, m_flags);
} else {
copyJob = KIO::copy(urls, m_destDir, m_flags);
}
QObject::connect(copyJob, &KIO::CopyJob::copyingDone, q, [this](KIO::Job *job, const QUrl &src, const QUrl &dest) {
slotCopyingDone(job, src, dest);
});
QObject::connect(copyJob, &KIO::CopyJob::copyingLinkDone, q, [this](KIO::Job *job, const QUrl &from, const QString &target, const QUrl &to) {
slotCopyingLinkDone(job, from, target, to);
});
KIO::FileUndoManager::self()->recordJob(move ? KIO::FileUndoManager::Move : KIO::FileUndoManager::Copy, QList<QUrl>(), m_destDir, copyJob);
job = copyJob;
}
} else {
const QString dialogText = m_clipboard ? i18n("Filename for clipboard content:") : i18n("Filename for dropped contents:");
job = pasteMimeDataImpl(m_mimeData, m_destDir, dialogText, KJobWidgets::window(q), m_clipboard);
if (KIO::SimpleJob *simpleJob = qobject_cast<KIO::SimpleJob *>(job)) {
KIO::FileUndoManager::self()->recordJob(KIO::FileUndoManager::Put, QList<QUrl>(), simpleJob->url(), job);
}
}
if (job) {
q->addSubjob(job);
if (copyJob) {
Q_EMIT q->copyJobStarted(copyJob);
}
} else {
q->setError(KIO::ERR_NO_CONTENT);
q->emitResult();
}
}
void PasteJob::slotResult(KJob *job)
{
if (job->error()) {
KIO::Job::slotResult(job); // will set the error and emit result(this)
return;
}
KIO::SimpleJob *simpleJob = qobject_cast<KIO::SimpleJob *>(job);
if (simpleJob) {
Q_EMIT itemCreated(simpleJob->url());
}
removeSubjob(job);
emitResult();
}
PasteJob *KIO::paste(const QMimeData *mimeData, const QUrl &destDir, JobFlags flags)
{
return PasteJobPrivate::newJob(mimeData, destDir, flags, true /*clipboard*/);
}
#include "moc_pastejob.cpp"
@@ -0,0 +1,91 @@
/*
This file is part of the KDE libraries
SPDX-FileCopyrightText: 2014 David Faure <faure@kde.org>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#ifndef PASTEJOB_H
#define PASTEJOB_H
#include <QUrl>
#include "kiowidgets_export.h"
#include <kio/job_base.h>
class QMimeData;
namespace KIO
{
class CopyJob;
class PasteJobPrivate;
/**
* @class KIO::PasteJob pastejob.h <KIO/PasteJob>
*
* A KIO job that handles pasting the clipboard contents.
*
* If the clipboard contains URLs, they are copied to the destination URL.
* If the clipboard contains data, it is saved into a file after asking
* the user to choose a filename and the preferred data format.
*
* @see KIO::pasteClipboard
* @since 5.4
*/
class KIOWIDGETS_EXPORT PasteJob : public Job
{
Q_OBJECT
public:
~PasteJob() override;
Q_SIGNALS:
/**
* Signals that a file or directory was created.
*/
void itemCreated(const QUrl &url);
/**
* Emitted when a copy job was started as subjob as part of pasting. Note that a
* CopyJob isn't always started by PasteJob. For instance pasting image content will create a file.
*
* You can use @p job to monitor the progress of the copy/move/link operation.
*
* @param job the job started for moving, copying or symlinking files
* @since 6.0
*/
void copyJobStarted(KIO::CopyJob *job);
protected Q_SLOTS:
void slotResult(KJob *job) override;
protected:
KIOWIDGETS_NO_EXPORT explicit PasteJob(PasteJobPrivate &dd);
private:
Q_DECLARE_PRIVATE(PasteJob)
};
/**
* Pastes the clipboard contents.
*
* If the clipboard contains URLs, they are copied (or moved) to the destination URL,
* using a KIO::CopyJob subjob.
* Otherwise, the data from the clipboard is saved into a file using KIO::storedPut,
* after asking the user to choose a filename and the preferred data format.
*
* This takes care of recording the subjob in the FileUndoManager, and emits
* itemCreated for every file or directory being created, so that the view can select
* these items.
*
* @param mimeData the MIME data to paste, usually QApplication::clipboard()->mimeData()
* @param destDir The URL of the target directory
* @param flags passed to the sub job
*
* @return A pointer to the job handling the operation.
* @since 5.4
*/
KIOWIDGETS_EXPORT PasteJob *paste(const QMimeData *mimeData, const QUrl &destDir, JobFlags flags = DefaultFlags);
}
#endif
@@ -0,0 +1,64 @@
/*
This file is part of the KDE libraries
SPDX-FileCopyrightText: 2014 David Faure <faure@kde.org>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#ifndef PASTEJOB_P_H
#define PASTEJOB_P_H
#include "pastejob.h"
#include <job_p.h>
#include <QMimeData>
namespace KIO
{
class DropJobPrivate;
class PasteJobPrivate : public KIO::JobPrivate
{
public:
// Used by KIO::PasteJob (clipboard=true) and KIO::DropJob (clipboard=false)
PasteJobPrivate(const QMimeData *mimeData, const QUrl &destDir, JobFlags flags, bool clipboard)
: JobPrivate()
, m_mimeData(mimeData)
, m_destDir(destDir)
, m_flags(flags)
, m_clipboard(clipboard)
{
}
friend class KIO::DropJobPrivate;
QPointer<const QMimeData> m_mimeData;
QUrl m_destDir;
JobFlags m_flags;
bool m_clipboard;
Q_DECLARE_PUBLIC(PasteJob)
void slotStart();
void slotCopyingDone(KIO::Job *, const QUrl &, const QUrl &to)
{
Q_EMIT q_func()->itemCreated(to);
}
void slotCopyingLinkDone(KIO::Job *, const QUrl &, const QString &, const QUrl &to)
{
Q_EMIT q_func()->itemCreated(to);
}
static inline PasteJob *newJob(const QMimeData *mimeData, const QUrl &destDir, JobFlags flags, bool clipboard)
{
PasteJob *job = new PasteJob(*new PasteJobPrivate(mimeData, destDir, flags, clipboard));
job->setUiDelegate(KIO::createDefaultJobUiDelegate());
// Note: never KIO::getJobTracker()->registerJob here
// The progress information comes from the underlying job (so we don't have to forward it here).
return job;
}
};
}
#endif
@@ -0,0 +1,779 @@
/*
This file is part of the KDE libraries
SPDX-FileCopyrightText: 2000 Stephan Kulow <coolo@kde.org>
SPDX-FileCopyrightText: 1999-2008 David Faure <faure@kde.org>
SPDX-FileCopyrightText: 2001, 2006 Holger Freyther <freyther@kde.org>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#include "kio/renamedialog.h"
#include "../utils_p.h"
#include "kio_widgets_debug.h"
#include "kshell.h"
#include <QApplication>
#include <QCheckBox>
#include <QDate>
#include <QLabel>
#include <QLayout>
#include <QLineEdit>
#include <QMenu>
#include <QMimeDatabase>
#include <QPixmap>
#include <QPushButton>
#include <QScreen>
#include <QScrollArea>
#include <QScrollBar>
#include <QToolButton>
#include <KFileUtils>
#include <KGuiItem>
#include <KIconLoader>
#include <KLocalizedString>
#include <KMessageBox>
#include <KSeparator>
#include <KSqueezedTextLabel>
#include <KStandardGuiItem>
#include <KStringHandler>
#include <kfileitem.h>
#include <kio/udsentry.h>
#include <previewjob.h>
using namespace KIO;
static QLabel *createLabel(QWidget *parent, const QString &text, bool containerTitle = false)
{
auto *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 bool hasDate = item.entry().contains(KIO::UDSEntry::UDS_MODIFICATION_TIME);
const QString text = hasDate ? i18n("Date: %1", item.timeString(KFileItem::ModificationTime)) : QString();
QLabel *dateLabel = createLabel(parent, text);
dateLabel->setAlignment(Qt::AlignLeft | Qt::AlignTop);
return dateLabel;
}
static QLabel *createSizeLabel(QWidget *parent, const KFileItem &item)
{
const bool hasSize = item.entry().contains(KIO::UDSEntry::UDS_SIZE);
const QString text = hasSize ? i18n("Size: %1", KIO::convertSize(item.size())) : QString();
QLabel *sizeLabel = createLabel(parent, text);
sizeLabel->setAlignment(Qt::AlignLeft | Qt::AlignBottom);
return sizeLabel;
}
static KSqueezedTextLabel *createSqueezedLabel(QWidget *parent, const QString &text)
{
auto *label = new KSqueezedTextLabel(text, parent);
label->setAlignment(Qt::AlignHCenter);
label->setSizePolicy(QSizePolicy::Ignored, QSizePolicy::Fixed);
return label;
}
enum CompareFilesResult {
Identical,
PartiallyIdentical,
Different,
};
static CompareFilesResult compareFiles(const QString &filepath, const QString &secondFilePath)
{
const qint64 bufferSize = 4096; // 4kb
QFile f(filepath);
QFile f2(secondFilePath);
const auto fileSize = f.size();
if (fileSize != f2.size()) {
return CompareFilesResult::Different;
}
if (!f.open(QFile::ReadOnly)) {
qCWarning(KIO_WIDGETS) << "Could not open file for comparison:" << f.fileName();
return CompareFilesResult::Different;
}
if (!f2.open(QFile::ReadOnly)) {
f.close();
qCWarning(KIO_WIDGETS) << "Could not open file for comparison:" << f2.fileName();
return CompareFilesResult::Different;
}
QByteArray buffer(bufferSize, 0);
QByteArray buffer2(bufferSize, 0);
auto seekFillBuffer = [bufferSize](qint64 pos, QFile &f, QByteArray &buffer) {
auto ioresult = f.seek(pos);
if (ioresult) {
const int bytesRead = f.read(buffer.data(), bufferSize);
ioresult = bytesRead != -1;
}
if (!ioresult) {
qCWarning(KIO_WIDGETS) << "Could not read file for comparison:" << f.fileName();
return false;
}
return true;
};
// compare at the beginning of the files
bool result = seekFillBuffer(0, f, buffer);
result = result && seekFillBuffer(0, f2, buffer2);
result = result && buffer == buffer2;
if (result && fileSize > 2 * bufferSize) {
// compare the contents in the middle of the files
result = seekFillBuffer(fileSize / 2 - bufferSize / 2, f, buffer);
result = result && seekFillBuffer(fileSize / 2 - bufferSize / 2, f2, buffer2);
result = result && buffer == buffer2;
}
if (result && fileSize > bufferSize) {
// compare the contents at the end of the files
result = seekFillBuffer(fileSize - bufferSize, f, buffer);
result = result && seekFillBuffer(fileSize - bufferSize, f2, buffer2);
result = result && buffer == buffer2;
}
if (!result) {
return CompareFilesResult::Different;
}
if (fileSize <= bufferSize * 3) {
// for files smaller than bufferSize * 3, we in fact compared fully the files
return CompareFilesResult::Identical;
} else {
return CompareFilesResult::PartiallyIdentical;
}
}
/** @internal */
class Q_DECL_HIDDEN RenameDialog::RenameDialogPrivate
{
public:
RenameDialogPrivate()
{
}
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 = nullptr;
QPushButton *bRename = nullptr;
QPushButton *bSkip = nullptr;
QToolButton *bOverwrite = nullptr;
QAction *bOverwriteWhenOlder = nullptr;
QPushButton *bResume = nullptr;
QPushButton *bSuggestNewName = nullptr;
QCheckBox *bApplyAll = nullptr;
QLineEdit *m_pLineEdit = nullptr;
QUrl src;
QUrl dest;
bool m_srcPendingPreview = false;
bool m_destPendingPreview = false;
QLabel *m_srcPreview = nullptr;
QLabel *m_destPreview = nullptr;
QLabel *m_srcDateLabel = nullptr;
QLabel *m_destDateLabel = nullptr;
KFileItem srcItem;
KFileItem destItem;
};
RenameDialog::RenameDialog(QWidget *parent,
const QString &title,
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(title);
d->bCancel = new QPushButton(this);
KGuiItem::assign(d->bCancel, KStandardGuiItem::cancel());
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_DestIsDirectory) ? i18n("When this is checked the button pressed will be applied to all "
"subsequent folder conflicts for the remainder of the current job.\n"
"Unless 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, &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, &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_DestIsDirectory) ? 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, &QAbstractButton::clicked, this, &RenameDialog::skipPressed);
}
if (_options & RenameDialog_Overwrite) {
d->bOverwrite = new QToolButton(this);
d->bOverwrite->setText(KStandardGuiItem::overwrite().text());
d->bOverwrite->setIcon(QIcon::fromTheme(KStandardGuiItem::overwrite().iconName()));
d->bOverwrite->setToolButtonStyle(Qt::ToolButtonTextBesideIcon);
if (_options & RenameDialog_DestIsDirectory) {
d->bOverwrite->setText(i18nc("Write files into an existing folder", "&Write Into"));
d->bOverwrite->setIcon(QIcon());
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."));
} else if ((_options & RenameDialog_MultipleItems) && mtimeSrc.isValid() && mtimeDest.isValid()) {
d->bOverwriteWhenOlder = new QAction(QIcon::fromTheme(KStandardGuiItem::overwrite().iconName()),
i18nc("Overwrite files into an existing folder when files are older", "&Overwrite older files"),
this);
d->bOverwriteWhenOlder->setEnabled(false);
d->bOverwriteWhenOlder->setToolTip(
i18n("Destination files which have older modification times will be overwritten by the source, skipped otherwise."));
connect(d->bOverwriteWhenOlder, &QAction::triggered, this, &RenameDialog::overwriteWhenOlderPressed);
auto *overwriteMenu = new QMenu();
overwriteMenu->addAction(d->bOverwriteWhenOlder);
d->bOverwrite->setMenu(overwriteMenu);
d->bOverwrite->setPopupMode(QToolButton::MenuButtonPopup);
}
connect(d->bOverwrite, &QAbstractButton::clicked, this, &RenameDialog::overwritePressed);
}
if (_options & RenameDialog_Resume) {
d->bResume = new QPushButton(i18n("&Resume"), this);
connect(d->bResume, &QAbstractButton::clicked, this, &RenameDialog::resumePressed);
}
auto *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) {
auto *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.reserve(4);
srcUds.fastInsert(UDSEntry::UDS_NAME, d->src.fileName());
if (mtimeSrc.isValid()) {
srcUds.fastInsert(UDSEntry::UDS_MODIFICATION_TIME, mtimeSrc.toMSecsSinceEpoch() / 1000);
}
if (ctimeSrc.isValid()) {
srcUds.fastInsert(UDSEntry::UDS_CREATION_TIME, ctimeSrc.toMSecsSinceEpoch() / 1000);
}
if (sizeSrc != KIO::filesize_t(-1)) {
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.reserve(4);
destUds.fastInsert(UDSEntry::UDS_NAME, d->dest.fileName());
if (mtimeDest.isValid()) {
destUds.fastInsert(UDSEntry::UDS_MODIFICATION_TIME, mtimeDest.toMSecsSinceEpoch() / 1000);
}
if (ctimeDest.isValid()) {
destUds.fastInsert(UDSEntry::UDS_CREATION_TIME, ctimeDest.toMSecsSinceEpoch() / 1000);
}
if (sizeDest != KIO::filesize_t(-1)) {
destUds.fastInsert(UDSEntry::UDS_SIZE, sizeDest);
}
d->destItem = KFileItem(destUds, d->dest);
}
d->m_srcPreview = createLabel(this, QString());
d->m_destPreview = createLabel(this, QString());
d->m_srcPreview->setMinimumHeight(KIconLoader::SizeHuge);
d->m_srcPreview->setMinimumWidth(KIconLoader::SizeHuge);
d->m_destPreview->setMinimumHeight(KIconLoader::SizeHuge);
d->m_destPreview->setMinimumWidth(KIconLoader::SizeHuge);
d->m_srcPreview->setAlignment(Qt::AlignCenter);
d->m_destPreview->setAlignment(Qt::AlignCenter);
d->m_srcPendingPreview = true;
d->m_destPendingPreview = true;
// create layout
auto *gridLayout = new QGridLayout();
pLayout->addLayout(gridLayout);
int gridRow = 0;
auto question = i18n("Would you like to overwrite the destination?");
if (d->srcItem.isDir() && d->destItem.isDir()) {
question = i18n("Would you like to merge the contents of '%1' into '%2'?",
KShell::tildeCollapse(d->src.toDisplayString(QUrl::PreferLocalFile)),
KShell::tildeCollapse(d->dest.toDisplayString(QUrl::PreferLocalFile)));
}
auto *questionLabel = new QLabel(question, this);
questionLabel->setAlignment(Qt::AlignHCenter);
gridLayout->addWidget(questionLabel, gridRow, 0, 1, 4); // takes the complete first line
QLabel *srcTitle = createLabel(this, i18n("Source"), true);
gridLayout->addWidget(srcTitle, ++gridRow, 0, 1, 2);
QLabel *destTitle = createLabel(this, i18n("Destination"), true);
gridLayout->addWidget(destTitle, gridRow, 2, 1, 2);
// The labels containing src and dest path
QLabel *srcUrlLabel = createSqueezedLabel(this, d->src.toDisplayString(QUrl::PreferLocalFile));
srcUrlLabel->setTextFormat(Qt::PlainText);
gridLayout->addWidget(srcUrlLabel, ++gridRow, 0, 1, 2);
QLabel *destUrlLabel = createSqueezedLabel(this, d->dest.toDisplayString(QUrl::PreferLocalFile));
destUrlLabel->setTextFormat(Qt::PlainText);
gridLayout->addWidget(destUrlLabel, gridRow, 2, 1, 2);
gridRow++;
// src container (preview, size, date)
QLabel *srcSizeLabel = createSizeLabel(this, d->srcItem);
d->m_srcDateLabel = createDateLabel(this, d->srcItem);
QWidget *srcContainer = createContainerWidget(d->m_srcPreview, srcSizeLabel, d->m_srcDateLabel);
gridLayout->addWidget(srcContainer, gridRow, 0, 1, 2);
// dest container (preview, size, date)
QLabel *destSizeLabel = createSizeLabel(this, d->destItem);
d->m_destDateLabel = createDateLabel(this, d->destItem);
QWidget *destContainer = createContainerWidget(d->m_destPreview, destSizeLabel, d->m_destDateLabel);
gridLayout->addWidget(destContainer, gridRow, 2, 1, 2);
// Verdicts
auto *hbox_verdicts = new QHBoxLayout();
pLayout->addLayout(hbox_verdicts);
hbox_verdicts->addStretch(1);
if (mtimeSrc > mtimeDest) {
hbox_verdicts->addWidget(createLabel(this, i18n("The source is <b>more recent</b>.")));
} else if (mtimeDest > mtimeSrc) {
hbox_verdicts->addWidget(createLabel(this, i18n("The source is <b>older</b>.")));
};
if (d->srcItem.entry().contains(KIO::UDSEntry::UDS_SIZE) && d->destItem.entry().contains(KIO::UDSEntry::UDS_SIZE)
&& d->srcItem.size() != d->destItem.size()) {
QString text;
KIO::filesize_t diff = 0;
if (d->destItem.size() > d->srcItem.size()) {
diff = d->destItem.size() - d->srcItem.size();
text = i18n("The source is <b>smaller by %1</b>.", KIO::convertSize(diff));
} else {
diff = d->srcItem.size() - d->destItem.size();
text = i18n("The source is <b>bigger by %1</b>.", KIO::convertSize(diff));
}
hbox_verdicts->addWidget(createLabel(this, text));
}
// check files contents for local files
if ((d->dest.isLocalFile() && !(_options & RenameDialog_DestIsDirectory)) && (d->src.isLocalFile() && !(_options & RenameDialog_SourceIsDirectory))
&& (d->srcItem.size() == d->destItem.size())) {
const CompareFilesResult CompareFilesResult = compareFiles(d->src.toLocalFile(), d->dest.toLocalFile());
QString text;
switch (CompareFilesResult) {
case CompareFilesResult::Identical:
text = i18n("The files are <b>identical</b>.");
break;
case CompareFilesResult::PartiallyIdentical:
text = i18n("The files <b>seem identical</b>.");
break;
case CompareFilesResult::Different:
text = i18n("The files are <b>different</b>.");
break;
}
QLabel *filesIdenticalLabel = createLabel(this, text);
if (CompareFilesResult == CompareFilesResult::PartiallyIdentical) {
auto *pixmapLabel = new QLabel(this);
pixmapLabel->setPixmap(QIcon::fromTheme(QStringLiteral("help-about")).pixmap(QSize(16, 16)));
pixmapLabel->setToolTip(
i18n("The files are likely to be identical: they have the same size and their contents are the same at the beginning, middle and end."));
pixmapLabel->setCursor(Qt::WhatsThisCursor);
auto *hbox = new QHBoxLayout();
hbox->addWidget(filesIdenticalLabel);
hbox->addWidget(pixmapLabel);
hbox_verdicts->addLayout(hbox);
} else {
hbox_verdicts->addWidget(filesIdenticalLabel);
}
}
hbox_verdicts->addStretch(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
}
auto *lb2 = new QLabel(i18n("Rename:"), this);
pLayout->addWidget(lb2);
}
auto *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, &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);
}
auto *layout = new QHBoxLayout();
pLayout->addLayout(layout);
layout->setContentsMargins(0, 10, 0, 0); // add some space above the bottom row with buttons
layout->addStretch(1);
if (d->bApplyAll) {
layout->addWidget(d->bApplyAll);
setTabOrder(d->bApplyAll, d->bCancel);
}
if (d->bSkip) {
layout->addWidget(d->bSkip);
setTabOrder(d->bSkip, d->bCancel);
}
if (d->bRename) {
layout->addWidget(d->bRename);
setTabOrder(d->bRename, 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, &KIO::RenameDialog::resizePanels, Qt::QueuedConnection);
}
#endif
}
RenameDialog::~RenameDialog() = default;
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 = KFileUtils::suggestName(destDirectory, d->dest.fileName());
QUrl newDest(destDirectory);
newDest.setPath(Utils::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);
}
}
// 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(KFileUtils::suggestName(destDirectory, d->m_pLineEdit->text()));
}
void RenameDialog::skipPressed()
{
if (d->bApplyAll && d->bApplyAll->isChecked()) {
done(Result_AutoSkip);
} else {
done(Result_Skip);
}
}
void RenameDialog::overwritePressed()
{
if (d->bApplyAll && d->bApplyAll->isChecked()) {
done(Result_OverwriteAll);
} else {
done(Result_Overwrite);
}
}
void RenameDialog::overwriteWhenOlderPressed()
{
if (d->bApplyAll && d->bApplyAll->isChecked()) {
done(Result_OverwriteWhenOlder);
}
}
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()
{
const bool applyAll = d->bApplyAll && d->bApplyAll->isChecked();
if (applyAll) {
d->m_pLineEdit->setText(KIO::decodeFileName(d->dest.fileName()));
d->m_pLineEdit->setEnabled(false);
} else {
d->m_pLineEdit->setEnabled(true);
}
if (d->bRename) {
d->bRename->setEnabled(applyAll);
}
if (d->bSuggestNewName) {
d->bSuggestNewName->setEnabled(!applyAll);
}
if (d->bOverwriteWhenOlder) {
d->bOverwriteWhenOlder->setEnabled(applyAll);
}
}
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 = QIcon::fromTheme(fileitem.iconName(), QIcon::fromTheme(QStringLiteral("application-octet-stream"))).pixmap(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 = QIcon::fromTheme(fileitem.iconName(), QIcon::fromTheme(QStringLiteral("application-octet-stream"))).pixmap(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_srcPreview != nullptr);
Q_ASSERT(d->m_destPreview != nullptr);
// Force keep the same (max) width of date width for src and dest
int destDateWidth = d->m_destDateLabel->width();
int srcDateWidth = d->m_srcDateLabel->width();
int minDateWidth = std::max(destDateWidth, srcDateWidth);
d->m_srcDateLabel->setMinimumWidth(minDateWidth);
d->m_destDateLabel->setMinimumWidth(minDateWidth);
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, &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);
}
QWidget *RenameDialog::createContainerWidget(QLabel *preview, QLabel *SizeLabel, QLabel *DateLabel)
{
auto *widgetContainer = new QWidget();
auto *containerLayout = new QHBoxLayout(widgetContainer);
containerLayout->addStretch(1);
containerLayout->addWidget(preview);
auto *detailsLayout = new QVBoxLayout(widgetContainer);
detailsLayout->addStretch(1);
detailsLayout->addWidget(SizeLabel);
detailsLayout->addWidget(DateLabel);
detailsLayout->addStretch(1);
containerLayout->addLayout(detailsLayout);
containerLayout->addStretch(1);
return widgetContainer;
}
#include "moc_renamedialog.cpp"
@@ -0,0 +1,113 @@
/*
This file is part of the KDE libraries
SPDX-FileCopyrightText: 2000 Stephan Kulow <coolo@kde.org>
SPDX-FileCopyrightText: 1999-2008 David Faure <faure@kde.org>
SPDX-FileCopyrightText: 2001 Holger Freyther <freyther@kde.org>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#ifndef KIO_RENAMEDIALOG_H
#define KIO_RENAMEDIALOG_H
#include <QDateTime>
#include <QDialog>
#include <QString>
#include <kio/jobuidelegateextension.h>
#include "kiowidgets_export.h"
#include <kio/global.h>
#include <memory>
class QScrollArea;
class QLabel;
class QPixmap;
class KFileItem;
class KSqueezedTextLabel;
namespace KIO
{
/**
* @class KIO::RenameDialog renamedialog.h <KIO/RenameDialog>
*
* The dialog shown when a CopyJob realizes that a destination file already exists,
* and wants to offer the user with the choice to either Rename, Overwrite, Skip;
* this dialog is also used when a .part file exists and the user can choose to
* Resume a previous download.
*/
class KIOWIDGETS_EXPORT RenameDialog : public QDialog
{
Q_OBJECT
public:
/**
* Construct a "rename" dialog to let the user know that @p src is about to overwrite @p dest.
*
* @param parent parent widget (often 0)
* @param title the title for the dialog box
* @param src the url to the file/dir we're trying to copy, as it's part of the text message
* @param dest the path to destination file/dir, i.e. the one that already exists
* @param options parameters for the dialog (which buttons to show...),
* @param sizeSrc size of source file
* @param sizeDest size of destination file
* @param ctimeSrc creation time of source file
* @param ctimeDest creation time of destination file
* @param mtimeSrc modification time of source file
* @param mtimeDest modification time of destination file
*/
RenameDialog(QWidget *parent,
const QString &title,
const QUrl &src,
const QUrl &dest,
RenameDialog_Options options,
KIO::filesize_t sizeSrc = KIO::filesize_t(-1),
KIO::filesize_t sizeDest = KIO::filesize_t(-1),
const QDateTime &ctimeSrc = QDateTime(),
const QDateTime &ctimeDest = QDateTime(),
const QDateTime &mtimeSrc = QDateTime(),
const QDateTime &mtimeDest = QDateTime());
~RenameDialog() override;
/**
* @return the new destination
* valid only if RENAME was chosen
*/
QUrl newDestUrl();
/**
* @return an automatically renamed destination
* valid always
*/
QUrl autoDestUrl() const;
public Q_SLOTS:
void cancelPressed();
void renamePressed();
void skipPressed();
void overwritePressed();
void overwriteAllPressed();
void overwriteWhenOlderPressed();
void resumePressed();
void resumeAllPressed();
void suggestNewNamePressed();
protected Q_SLOTS:
void enableRenameButton(const QString &);
private Q_SLOTS:
KIOWIDGETS_NO_EXPORT void applyAllPressed();
KIOWIDGETS_NO_EXPORT void showSrcIcon(const KFileItem &);
KIOWIDGETS_NO_EXPORT void showDestIcon(const KFileItem &);
KIOWIDGETS_NO_EXPORT void showSrcPreview(const KFileItem &, const QPixmap &);
KIOWIDGETS_NO_EXPORT void showDestPreview(const KFileItem &, const QPixmap &);
KIOWIDGETS_NO_EXPORT void resizePanels();
private:
KIOWIDGETS_NO_EXPORT QWidget *createContainerWidget(QLabel *preview, QLabel *SizeLabel, QLabel *DateLabel);
class RenameDialogPrivate;
std::unique_ptr<RenameDialogPrivate> const d;
};
}
#endif
@@ -0,0 +1,226 @@
/*
This file is part of the KDE libraries
SPDX-FileCopyrightText: 2006-2010 Peter Penz <peter.penz@gmx.at>
SPDX-FileCopyrightText: 2020 Méven Car <meven.car@kdemail.net>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#include "renamefiledialog.h"
#include <KGuiItem>
#include <KIO/BatchRenameJob>
#include <KIO/CopyJob>
#include <KIO/FileUndoManager>
#include <KJobUiDelegate>
#include <KJobWidgets>
#include <KLocalizedString>
#include <QDialogButtonBox>
#include <QHBoxLayout>
#include <QLabel>
#include <QLineEdit>
#include <QMimeDatabase>
#include <QPushButton>
#include <QShowEvent>
#include <QSpinBox>
namespace KIO
{
class Q_DECL_HIDDEN RenameFileDialog::RenameFileDialogPrivate
{
public:
RenameFileDialogPrivate(const KFileItemList &items)
: lineEdit(nullptr)
, items(items)
, spinBox(nullptr)
, renameOneItem(false)
, allExtensionsDifferent(true)
{
}
QList<QUrl> renamedItems;
QLineEdit *lineEdit;
KFileItemList items;
QSpinBox *spinBox;
QPushButton *okButton;
bool renameOneItem;
bool allExtensionsDifferent;
};
RenameFileDialog::RenameFileDialog(const KFileItemList &items, QWidget *parent)
: QDialog(parent)
, d(new RenameFileDialogPrivate(items))
{
setMinimumWidth(320);
const int itemCount = items.count();
Q_ASSERT(itemCount >= 1);
d->renameOneItem = (itemCount == 1);
setWindowTitle(d->renameOneItem ? i18nc("@title:window", "Rename Item") : i18nc("@title:window", "Rename Items"));
QDialogButtonBox *buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel);
QVBoxLayout *mainLayout = new QVBoxLayout(this);
d->okButton = buttonBox->button(QDialogButtonBox::Ok);
d->okButton->setDefault(true);
d->okButton->setShortcut(QKeySequence(Qt::CTRL | Qt::Key_Return));
connect(buttonBox, &QDialogButtonBox::accepted, this, &RenameFileDialog::slotAccepted);
connect(buttonBox, &QDialogButtonBox::rejected, this, &RenameFileDialog::reject);
connect(buttonBox, &QDialogButtonBox::rejected, this, &QObject::deleteLater);
d->okButton->setDefault(true);
KGuiItem::assign(d->okButton, KGuiItem(i18nc("@action:button", "&Rename"), QStringLiteral("dialog-ok-apply")));
QWidget *page = new QWidget(this);
mainLayout->addWidget(page);
mainLayout->addWidget(buttonBox);
QVBoxLayout *topLayout = new QVBoxLayout(page);
QLabel *editLabel = nullptr;
QString newName;
if (d->renameOneItem) {
newName = items.first().name();
editLabel = new QLabel(xi18nc("@label:textbox", "Rename the item <filename>%1</filename> to:", newName), page);
editLabel->setTextFormat(Qt::PlainText);
} else {
newName = i18nc("This a template for new filenames, # is replaced by a number later, must be the end character", "New name #");
editLabel = new QLabel(i18ncp("@label:textbox", "Rename the %1 selected item to:", "Rename the %1 selected items to:", itemCount), page);
}
d->lineEdit = new QLineEdit(page);
mainLayout->addWidget(d->lineEdit);
connect(d->lineEdit, &QLineEdit::textChanged, this, &RenameFileDialog::slotTextChanged);
int selectionLength = newName.length();
if (d->renameOneItem) {
// If the current item is a directory, select the whole file name.
if (!items.first().isDir()) {
QMimeDatabase db;
const QString extension = db.suffixForFileName(items.first().name());
if (extension.length() > 0) {
// Don't select the extension
selectionLength -= extension.length() + 1;
}
}
} else {
// Don't select the # character
--selectionLength;
}
d->lineEdit->setText(newName);
d->lineEdit->setSelection(0, selectionLength);
topLayout->addWidget(editLabel);
topLayout->addWidget(d->lineEdit);
if (!d->renameOneItem) {
QMimeDatabase db;
QSet<QString> extensions;
for (const KFileItem &item : std::as_const(d->items)) {
const QString extension = db.suffixForFileName(item.name());
if (extensions.contains(extension)) {
d->allExtensionsDifferent = false;
break;
}
extensions.insert(extension);
}
QLabel *infoLabel = new QLabel(i18nc("@info", "# will be replaced by ascending numbers starting with:"), page);
mainLayout->addWidget(infoLabel);
d->spinBox = new QSpinBox(page);
d->spinBox->setMinimum(0);
d->spinBox->setMaximum(1'000'000'000);
d->spinBox->setSingleStep(1);
d->spinBox->setValue(1);
d->spinBox->setDisplayIntegerBase(10);
QHBoxLayout *horizontalLayout = new QHBoxLayout;
horizontalLayout->setContentsMargins(0, 0, 0, 0);
horizontalLayout->addWidget(infoLabel);
horizontalLayout->addWidget(d->spinBox);
topLayout->addLayout(horizontalLayout);
}
d->lineEdit->setFocus();
}
RenameFileDialog::~RenameFileDialog()
{
}
void RenameFileDialog::slotAccepted()
{
QWidget *widget = parentWidget();
if (!widget) {
widget = this;
}
const QList<QUrl> srcList = d->items.urlList();
const QString newName = d->lineEdit->text();
KIO::FileUndoManager::CommandType cmdType;
KIO::Job *job = nullptr;
if (d->renameOneItem) {
Q_ASSERT(d->items.count() == 1);
cmdType = KIO::FileUndoManager::Rename;
const QUrl oldUrl = d->items.constFirst().url();
QUrl newUrl = oldUrl.adjusted(QUrl::RemoveFilename);
newUrl.setPath(newUrl.path() + KIO::encodeFileName(newName));
d->renamedItems << newUrl;
job = KIO::moveAs(oldUrl, newUrl, KIO::HideProgressInfo);
} else {
d->renamedItems.reserve(d->items.count());
cmdType = KIO::FileUndoManager::BatchRename;
job = KIO::batchRename(srcList, newName, d->spinBox->value(), QLatin1Char('#'));
connect(qobject_cast<KIO::BatchRenameJob *>(job), &KIO::BatchRenameJob::fileRenamed, this, &RenameFileDialog::slotFileRenamed);
}
KJobWidgets::setWindow(job, widget);
const QUrl parentUrl = srcList.first().adjusted(QUrl::RemoveFilename | QUrl::StripTrailingSlash);
KIO::FileUndoManager::self()->recordJob(cmdType, srcList, parentUrl, job);
connect(job, &KJob::result, this, &RenameFileDialog::slotResult);
connect(job, &KJob::result, this, &QObject::deleteLater);
accept();
}
void RenameFileDialog::slotTextChanged(const QString &newName)
{
bool enable = !newName.isEmpty() && (newName != QLatin1String("..")) && (newName != QLatin1String("."));
if (enable && !d->renameOneItem) {
const int count = newName.count(QLatin1Char('#'));
if (count == 0) {
// Renaming multiple files without '#' will only work if all extensions are different.
enable = d->allExtensionsDifferent;
} else {
// Ensure that the new name contains exactly one # (or a connected sequence of #'s)
const int first = newName.indexOf(QLatin1Char('#'));
const int last = newName.lastIndexOf(QLatin1Char('#'));
enable = (last - first + 1 == count);
}
}
d->okButton->setEnabled(enable);
}
void RenameFileDialog::slotFileRenamed(const QUrl &oldUrl, const QUrl &newUrl)
{
Q_UNUSED(oldUrl)
d->renamedItems << newUrl;
}
void RenameFileDialog::slotResult(KJob *job)
{
if (!job->error()) {
Q_EMIT renamingFinished(d->renamedItems);
} else {
Q_EMIT error(job);
}
}
} // namespace KIO
#include "moc_renamefiledialog.cpp"
@@ -0,0 +1,68 @@
/*
This file is part of the KDE libraries
SPDX-FileCopyrightText: 2006-2010 Peter Penz <peter.penz@gmx.at>
SPDX-FileCopyrightText: 2020 Méven Car <meven.car@kdemail.net>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#ifndef RENAMEFILEDIALOG_H
#define RENAMEFILEDIALOG_H
#include <KFileItem>
#include "kiowidgets_export.h"
#include <QDialog>
#include <QString>
class QLineEdit;
class QSpinBox;
class QPushButton;
class KJob;
namespace KIO
{
class RenameFileDialogPrivate;
/**
* @class KIO::RenameFileDialog renamefiledialog.h <KIO/RenameFileDialog>
*
* @brief Dialog for renaming a variable number of files.
*
* The dialog deletes itself when accepted or rejected.
*
* @since 5.67
*/
// TODO KF6 : rename the class RenameFileDialog to RenameDialog and the class RenameDialog to RenameFileOverwrittenDialog or similar.
class KIOWIDGETS_EXPORT RenameFileDialog : public QDialog
{
Q_OBJECT
public:
/**
* Constructs the Dialog to rename file(s)
*
* @param parent the parent QWidget
* @param items a non-empty list of items to rename
*/
explicit RenameFileDialog(const KFileItemList &items, QWidget *parent);
~RenameFileDialog() override;
Q_SIGNALS:
void renamingFinished(const QList<QUrl> &urls);
void error(KJob *error);
private Q_SLOTS:
KIOWIDGETS_NO_EXPORT void slotAccepted();
KIOWIDGETS_NO_EXPORT void slotTextChanged(const QString &newName);
KIOWIDGETS_NO_EXPORT void slotFileRenamed(const QUrl &oldUrl, const QUrl &newUrl);
KIOWIDGETS_NO_EXPORT void slotResult(KJob *job);
private:
class RenameFileDialogPrivate;
RenameFileDialogPrivate *const d;
};
} // namespace KIO
#endif
@@ -0,0 +1,111 @@
/*
This file is part of the KDE libraries
SPDX-FileCopyrightText: 2000 David Faure <faure@kde.org>
SPDX-License-Identifier: LGPL-2.0-only
*/
#include "kio/skipdialog.h"
#include <assert.h>
#include <stdio.h>
#include <QDialogButtonBox>
#include <QLabel>
#include <QPushButton>
#include <QVBoxLayout>
#include <QWidget>
#include <KGuiItem>
#include <KLocalizedString>
#include <KStandardGuiItem>
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(this);
auto *label = new QLabel(_error_text, this);
label->setWordWrap(true);
layout->addWidget(label);
QDialogButtonBox *buttonBox = new QDialogButtonBox(this);
layout->addWidget(buttonBox);
const bool isMultiple = options & SkipDialog_MultipleItems;
const bool isInvalidChars = options & SkipDialog_Replace_Invalid_Chars;
const bool hideRetry = options & SkipDialog_Hide_Retry;
// Retrying to e.g. copy a file with "*" in the name to a fat32
// partition will always fail
if (isInvalidChars) {
QPushButton *replaceCharButton = new QPushButton(i18n("Replace"));
connect(replaceCharButton, &QAbstractButton::clicked, this, [this]() {
done(KIO::Result_ReplaceInvalidChars);
});
buttonBox->addButton(replaceCharButton, QDialogButtonBox::ActionRole);
} else if (!hideRetry) {
QPushButton *retryButton = new QPushButton(i18n("Retry"));
connect(retryButton, &QAbstractButton::clicked, this, &SkipDialog::retryPressed);
buttonBox->addButton(retryButton, QDialogButtonBox::ActionRole);
}
if (isMultiple) {
if (isInvalidChars) {
QPushButton *autoReplaceButton = new QPushButton(i18n("Replace All"));
connect(autoReplaceButton, &QAbstractButton::clicked, this, [this]() {
done(KIO::Result_ReplaceAllInvalidChars);
});
buttonBox->addButton(autoReplaceButton, QDialogButtonBox::ActionRole);
}
QPushButton *skipButton = new QPushButton(i18n("Skip"));
connect(skipButton, &QAbstractButton::clicked, this, &SkipDialog::skipPressed);
buttonBox->addButton(skipButton, QDialogButtonBox::ActionRole);
QPushButton *autoSkipButton = new QPushButton(i18n("Skip All"));
connect(autoSkipButton, &QAbstractButton::clicked, this, &SkipDialog::autoSkipPressed);
buttonBox->addButton(autoSkipButton, QDialogButtonBox::ActionRole);
}
auto *cancelBtn = buttonBox->addButton(QDialogButtonBox::Cancel);
// If it's one item and the Retry button is hidden, replace the Cancel
// button text with OK
if (hideRetry && !isMultiple) {
KGuiItem::assign(cancelBtn, KStandardGuiItem::ok());
}
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);
}
#include "moc_skipdialog.cpp"
@@ -0,0 +1,41 @@
/*
This file is part of the KDE libraries
SPDX-FileCopyrightText: 2000 David Faure <faure@kde.org>
SPDX-License-Identifier: LGPL-2.0-only
*/
#ifndef KIO_SKIPDIALOG_H
#define KIO_SKIPDIALOG_H
#include "kiowidgets_export.h"
#include <QDialog>
#include <kio/jobuidelegateextension.h>
class QWidget;
namespace KIO
{
class SkipDialogPrivate;
/**
* @internal
*/
class KIOWIDGETS_EXPORT SkipDialog : public QDialog
{
Q_OBJECT
public:
SkipDialog(QWidget *parent, KIO::SkipDialog_Options options, const QString &_error_text);
~SkipDialog() override;
private Q_SLOTS:
KIOWIDGETS_NO_EXPORT void cancelPressed();
KIOWIDGETS_NO_EXPORT void skipPressed();
KIOWIDGETS_NO_EXPORT void autoSkipPressed();
KIOWIDGETS_NO_EXPORT void retryPressed();
private:
SkipDialogPrivate *const d;
};
}
#endif
@@ -0,0 +1,271 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>SslInfo</class>
<widget class="QWidget" name="SslInfo">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>474</width>
<height>510</height>
</rect>
</property>
<layout class="QGridLayout">
<item row="0" column="0">
<widget class="QLabel" name="encryptionIndicator">
<property name="text">
<string>[padlock]</string>
</property>
</widget>
</item>
<item row="0" column="1" colspan="2">
<widget class="QLabel" name="explanation">
<property name="text">
<string notr="true">Bruce Schneier secure</string>
</property>
<property name="textInteractionFlags">
<set>Qt::TextSelectableByMouse</set>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QLabel" name="addressTag">
<property name="text">
<string comment="Web page address">Address:</string>
</property>
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="KSqueezedTextLabel" name="address">
<property name="text">
<string notr="true">KSqueezedTextLabel</string>
</property>
<property name="textInteractionFlags">
<set>Qt::TextSelectableByMouse</set>
</property>
</widget>
</item>
<item row="2" column="0">
<widget class="QLabel" name="ipTag">
<property name="text">
<string>IP address:</string>
</property>
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
</widget>
</item>
<item row="2" column="1">
<widget class="QLabel" name="ip">
<property name="text">
<string notr="true">is not there</string>
</property>
<property name="textInteractionFlags">
<set>Qt::TextSelectableByMouse</set>
</property>
</widget>
</item>
<item row="3" column="0">
<widget class="QLabel" name="encryptionTag">
<property name="text">
<string>Encryption:</string>
</property>
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
</widget>
</item>
<item row="3" column="1" colspan="2">
<widget class="QLabel" name="encryption">
<property name="text">
<string notr="true">SnakeOilCrypt 3000</string>
</property>
<property name="textInteractionFlags">
<set>Qt::TextSelectableByMouse</set>
</property>
</widget>
</item>
<item row="4" column="0">
<widget class="QLabel" name="detailsTag">
<property name="text">
<string>Details:</string>
</property>
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
</widget>
</item>
<item row="4" column="1" colspan="2">
<widget class="QLabel" name="details">
<property name="text">
<string notr="true">Kx = DH, Auth = RSA, MAC = SHA1</string>
</property>
<property name="textInteractionFlags">
<set>Qt::TextSelectableByMouse</set>
</property>
</widget>
</item>
<item row="5" column="0">
<widget class="QLabel" name="sslVersionTag">
<property name="text">
<string>SSL version:</string>
</property>
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
</widget>
</item>
<item row="5" column="1">
<widget class="QLabel" name="sslVersion">
<property name="text">
<string notr="true">ElboniaTLS v0.0.0</string>
</property>
<property name="textInteractionFlags">
<set>Qt::TextSelectableByMouse</set>
</property>
</widget>
</item>
<item row="6" column="0">
<widget class="QLabel" name="certSelectorTag">
<property name="text">
<string>Certificate chain:</string>
</property>
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
</widget>
</item>
<item row="6" column="1">
<widget class="QComboBox" name="certSelector"/>
</item>
<item row="6" column="2">
<spacer>
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>239</width>
<height>16</height>
</size>
</property>
</spacer>
</item>
<item row="7" column="0" colspan="3">
<widget class="QTabWidget" name="certParties"/>
</item>
<item row="8" column="0">
<widget class="QLabel" name="trustedTag">
<property name="text">
<string>Trusted:</string>
</property>
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
</widget>
</item>
<item row="8" column="1">
<widget class="QLabel" name="trusted">
<property name="text">
<string notr="true">Maybe... no.</string>
</property>
<property name="textInteractionFlags">
<set>Qt::TextSelectableByMouse</set>
</property>
</widget>
</item>
<item row="9" column="0">
<widget class="QLabel" name="validityPeriodTag">
<property name="text">
<string>Validity period:</string>
</property>
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
</widget>
</item>
<item row="9" column="1" colspan="2">
<widget class="QLabel" name="validityPeriod">
<property name="text">
<string notr="true">August 34 2004 to Undecimber 0 2008</string>
</property>
<property name="textInteractionFlags">
<set>Qt::TextSelectableByMouse</set>
</property>
</widget>
</item>
<item row="10" column="0">
<widget class="QLabel" name="serialTag">
<property name="text">
<string>Serial number:</string>
</property>
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
</widget>
</item>
<item row="10" column="1">
<widget class="QLabel" name="serial">
<property name="text">
<string notr="true">23</string>
</property>
<property name="textInteractionFlags">
<set>Qt::TextSelectableByMouse</set>
</property>
</widget>
</item>
<item row="11" column="0">
<widget class="QLabel" name="digestTag">
<property name="text">
<string>MD5 digest:</string>
</property>
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
</widget>
</item>
<item row="11" column="1">
<widget class="QLabel" name="digest">
<property name="text">
<string/>
</property>
<property name="textInteractionFlags">
<set>Qt::TextSelectableByMouse</set>
</property>
</widget>
</item>
<item row="12" column="0">
<widget class="QLabel" name="sha1DigestTag">
<property name="text">
<string>SHA1 digest:</string>
</property>
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
</widget>
</item>
<item row="12" column="1">
<widget class="QLabel" name="sha1Digest">
<property name="text">
<string notr="true">B4:DB:00:2E</string>
</property>
<property name="textInteractionFlags">
<set>Qt::TextSelectableByMouse</set>
</property>
</widget>
</item>
</layout>
</widget>
<customwidgets>
<customwidget>
<class>KSqueezedTextLabel</class>
<extends>QLabel</extends>
<header>ksqueezedtextlabel.h</header>
</customwidget>
</customwidgets>
<resources/>
<connections/>
</ui>
@@ -0,0 +1,117 @@
/*
This file is part of the KDE project
SPDX-FileCopyrightText: 2009 Andreas Hartmetz <ahartmetz@gmail.com>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#include "sslui.h"
#include <KLocalizedString>
#include <KMessageBox>
#include <ksslcertificatemanager.h>
#include <ksslerroruidata_p.h>
#include <ksslinfodialog.h>
bool KIO::SslUi::askIgnoreSslErrors(const KSslErrorUiData &uiData, RulesStorage storedRules)
{
const KSslErrorUiData::Private *ud = KSslErrorUiData::Private::get(&uiData);
if (ud->sslErrors.isEmpty()) {
return true;
}
const QList<QSslError> fatalErrors = KSslCertificateManager::nonIgnorableErrors(ud->sslErrors);
if (!fatalErrors.isEmpty()) {
// TODO message "sorry, fatal error, you can't override it"
return false;
}
if (ud->certificateChain.isEmpty()) {
// SSL without certificates is quite useless and should never happen
KMessageBox::error(nullptr,
i18n("The remote host did not send any SSL certificates.\n"
"Aborting because the identity of the host cannot be established."));
return false;
}
KSslCertificateManager *const cm = KSslCertificateManager::self();
KSslCertificateRule rule(ud->certificateChain.first(), ud->host);
if (storedRules & RecallRules) {
rule = cm->rule(ud->certificateChain.first(), ud->host);
// remove previously seen and acknowledged errors
const QList<QSslError> remainingErrors = rule.filterErrors(ud->sslErrors);
if (remainingErrors.isEmpty()) {
// qDebug() << "Error list empty after removing errors to be ignored. Continuing.";
return true;
}
}
// ### We don't ask to permanently reject the certificate
QString message = i18n("The server failed the authenticity check (%1).\n\n", ud->host);
for (const QSslError &err : std::as_const(ud->sslErrors)) {
message.append(err.errorString() + QLatin1Char('\n'));
}
message = message.trimmed();
int msgResult;
do {
msgResult = KMessageBox::warningTwoActionsCancel(nullptr,
message,
i18n("Server Authentication"),
KGuiItem(i18n("&Details"), QStringLiteral("help-about")),
KGuiItem(i18n("Co&ntinue"), QStringLiteral("arrow-right")));
if (msgResult == KMessageBox::PrimaryAction) {
// Details was chosen - show the certificate and error details
QList<QList<QSslError::SslError>> meh; // parallel list to cert list :/
meh.reserve(ud->certificateChain.size());
for (const QSslCertificate &cert : std::as_const(ud->certificateChain)) {
QList<QSslError::SslError> errors;
for (const QSslError &error : std::as_const(ud->sslErrors)) {
if (error.certificate() == cert) {
// we keep only the error code enum here
errors.append(error.error());
}
}
meh.append(errors);
}
KSslInfoDialog *dialog = new KSslInfoDialog();
dialog->setSslInfo(ud->certificateChain, ud->ip, ud->host, ud->sslProtocol, ud->cipher, ud->usedBits, ud->bits, meh);
dialog->exec();
} else if (msgResult == KMessageBox::Cancel) {
return false;
}
// fall through on KMessageBox::SecondaryAction
} while (msgResult == KMessageBox::PrimaryAction);
if (storedRules & StoreRules) {
// Save the user's choice to ignore the SSL errors.
msgResult = KMessageBox::warningTwoActions(nullptr,
i18n("Would you like to accept this "
"certificate forever without "
"being prompted?"),
i18n("Server Authentication"),
KGuiItem(i18n("&Forever"), QStringLiteral("flag-green")),
KGuiItem(i18n("&Current Session only"), QStringLiteral("chronometer")));
QDateTime ruleExpiry = QDateTime::currentDateTime();
if (msgResult == KMessageBox::PrimaryAction) {
// accept forever ("for a very long time")
ruleExpiry = ruleExpiry.addYears(1000);
} else {
// accept "for a short time", half an hour.
ruleExpiry = ruleExpiry.addSecs(30 * 60);
}
// TODO special cases for wildcard domain name in the certificate!
// rule = KSslCertificateRule(d->socket.peerCertificateChain().first(), whatever);
rule.setExpiryDateTime(ruleExpiry);
rule.setIgnoredErrors(ud->sslErrors);
cm->setRule(rule);
}
return true;
}
@@ -0,0 +1,43 @@
/*
This file is part of the KDE project
SPDX-FileCopyrightText: 2009 Andreas Hartmetz <ahartmetz@gmail.com>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#ifndef _KSSLUI_H
#define _KSSLUI_H
#include "kiowidgets_export.h"
#include <ksslerroruidata.h>
namespace KIO
{
/** UI methods for handling SSL errors. */
namespace SslUi
{
/** Error rule storage behavior. */
enum RulesStorage {
RecallRules = 1, ///< apply stored certificate rules (typically ignored errors)
StoreRules = 2, ///< make new ignore rules from the user's choice and store them
RecallAndStoreRules = 3, ///< apply stored rules and store new rules
};
/**
* If there are errors while establishing an SSL encrypted connection to a peer, usually due to
* certificate issues, and since this poses a security issue, we need confirmation from the user about
* how they wish to proceed.
*
* This function provides a dialog asking the user if they wish to abort the connection or ignore
* the SSL errors that occurred and continue connecting. And in case of the latter whether to remember
* the decision in the future or ignore the error temporarily.
*
* @p uiData the KSslErrorUiData object constructed from the socket that is trying to establish the
* encrypted connection
* @p storedRules see RulesStorage Enum
*/
bool KIOWIDGETS_EXPORT askIgnoreSslErrors(const KSslErrorUiData &uiData, RulesStorage storedRules = RecallAndStoreRules);
}
}
#endif
@@ -0,0 +1,561 @@
/*
This file is part of the KDE libraries
SPDX-FileCopyrightText: 2020 Ahmad Samir <a.samirh78@gmail.com>
SPDX-License-Identifier: LGPL-2.0-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
*/
#include "widgetsaskuseractionhandler.h"
#include <KConfig>
#include <KConfigGroup>
#include <KGuiItem>
#include <KIO/WorkerBase>
#include <KJob>
#include <KJobWidgets>
#include <KLocalizedString>
#include <KMessageDialog>
#include <KSharedConfig>
#include <KSslInfoDialog>
#include <KStandardGuiItem>
#include <kio_widgets_debug.h>
#include <QApplication>
#include <QDialogButtonBox>
#include <QPointer>
#include <QRegularExpression>
#include <QUrl>
class KIO::WidgetsAskUserActionHandlerPrivate
{
public:
explicit WidgetsAskUserActionHandlerPrivate(WidgetsAskUserActionHandler *qq)
: q(qq)
{
}
// Creates a KSslInfoDialog or falls back to a generic Information dialog
void sslMessageBox(const QString &text, const KIO::MetaData &metaData, QWidget *parent);
bool gotPersistentUserReply(KIO::AskUserActionInterface::MessageDialogType type, const KConfigGroup &cg, const QString &dontAskAgainName);
void savePersistentUserReply(KIO::AskUserActionInterface::MessageDialogType type, KConfigGroup &cg, const QString &dontAskAgainName, int result);
WidgetsAskUserActionHandler *const q;
QPointer<QWidget> m_parentWidget = nullptr;
QWidget *getParentWidget(KJob *job);
QWidget *getParentWidget(QWidget *widget);
};
bool KIO::WidgetsAskUserActionHandlerPrivate::gotPersistentUserReply(KIO::AskUserActionInterface::MessageDialogType type,
const KConfigGroup &cg,
const QString &dontAskAgainName)
{
// storage values matching the logic of FrameworkIntegration's KMessageBoxDontAskAgainConfigStorage
switch (type) {
case KIO::AskUserActionInterface::QuestionTwoActions:
case KIO::AskUserActionInterface::QuestionTwoActionsCancel:
case KIO::AskUserActionInterface::WarningTwoActions:
case KIO::AskUserActionInterface::WarningTwoActionsCancel: {
// storage holds "true" if persistent reply is "Yes", "false" for persistent "No",
// otherwise no persistent reply is present
const QString value = cg.readEntry(dontAskAgainName, QString());
if ((value.compare(QLatin1String("yes"), Qt::CaseInsensitive) == 0) || (value.compare(QLatin1String("true"), Qt::CaseInsensitive) == 0)) {
Q_EMIT q->messageBoxResult(KIO::WorkerBase::PrimaryAction);
return true;
}
if ((value.compare(QLatin1String("no"), Qt::CaseInsensitive) == 0) || (value.compare(QLatin1String("false"), Qt::CaseInsensitive) == 0)) {
Q_EMIT q->messageBoxResult(KIO::WorkerBase::SecondaryAction);
return true;
}
break;
}
case KIO::AskUserActionInterface::WarningContinueCancel: {
// storage holds "false" if persistent reply is "Continue"
// otherwise no persistent reply is present
const bool value = cg.readEntry(dontAskAgainName, true);
if (value == false) {
Q_EMIT q->messageBoxResult(KIO::WorkerBase::Continue);
return true;
}
break;
}
default:
break;
}
return false;
}
void KIO::WidgetsAskUserActionHandlerPrivate::savePersistentUserReply(KIO::AskUserActionInterface::MessageDialogType type,
KConfigGroup &cg,
const QString &dontAskAgainName,
int result)
{
// see gotPersistentUserReply for values stored and why
switch (type) {
case KIO::AskUserActionInterface::QuestionTwoActions:
case KIO::AskUserActionInterface::QuestionTwoActionsCancel:
case KIO::AskUserActionInterface::WarningTwoActions:
case KIO::AskUserActionInterface::WarningTwoActionsCancel:
cg.writeEntry(dontAskAgainName, result == KIO::WorkerBase::PrimaryAction);
cg.sync();
break;
case KIO::AskUserActionInterface::WarningContinueCancel:
cg.writeEntry(dontAskAgainName, false);
cg.sync();
break;
default:
break;
}
}
QWidget *KIO::WidgetsAskUserActionHandlerPrivate::getParentWidget(KJob *job)
{
QWidget *parentWidget = nullptr;
if (job) {
auto parentWindow = KJobWidgets::window(job);
if (parentWindow) {
parentWidget = parentWindow;
}
}
return getParentWidget(parentWidget);
}
QWidget *KIO::WidgetsAskUserActionHandlerPrivate::getParentWidget(QWidget *widget)
{
QWidget *parentWidget = widget;
if (!parentWidget) {
parentWidget = this->m_parentWidget;
}
if (!parentWidget) {
parentWidget = qApp->activeWindow();
}
return parentWidget;
}
KIO::WidgetsAskUserActionHandler::WidgetsAskUserActionHandler(QObject *parent)
: KIO::AskUserActionInterface(parent)
, d(new WidgetsAskUserActionHandlerPrivate(this))
{
}
KIO::WidgetsAskUserActionHandler::~WidgetsAskUserActionHandler()
{
}
void KIO::WidgetsAskUserActionHandler::askUserRename(KJob *job,
const QString &title,
const QUrl &src,
const QUrl &dest,
KIO::RenameDialog_Options options,
KIO::filesize_t sizeSrc,
KIO::filesize_t sizeDest,
const QDateTime &ctimeSrc,
const QDateTime &ctimeDest,
const QDateTime &mtimeSrc,
const QDateTime &mtimeDest)
{
QMetaObject::invokeMethod(qGuiApp, [=, this] {
auto *dlg = new KIO::RenameDialog(d->getParentWidget(job), title, src, dest, options, sizeSrc, sizeDest, ctimeSrc, ctimeDest, mtimeSrc, mtimeDest);
dlg->setAttribute(Qt::WA_DeleteOnClose);
dlg->setWindowModality(Qt::WindowModal);
connect(job, &KJob::finished, dlg, &QDialog::reject);
connect(dlg, &QDialog::finished, this, [this, job, dlg](const int exitCode) {
KIO::RenameDialog_Result result = static_cast<RenameDialog_Result>(exitCode);
const QUrl newUrl = result == Result_AutoRename ? dlg->autoDestUrl() : dlg->newDestUrl();
Q_EMIT askUserRenameResult(result, newUrl, job);
});
dlg->show();
});
}
void KIO::WidgetsAskUserActionHandler::askUserSkip(KJob *job, KIO::SkipDialog_Options options, const QString &errorText)
{
QMetaObject::invokeMethod(qGuiApp, [=, this] {
auto *dlg = new KIO::SkipDialog(d->getParentWidget(job), options, errorText);
dlg->setAttribute(Qt::WA_DeleteOnClose);
dlg->setWindowModality(Qt::WindowModal);
connect(job, &KJob::finished, dlg, &QDialog::reject);
connect(dlg, &QDialog::finished, this, [this, job](const int exitCode) {
Q_EMIT askUserSkipResult(static_cast<KIO::SkipDialog_Result>(exitCode), job);
});
dlg->show();
});
}
struct ProcessAskDeleteResult {
QStringList prettyList;
KMessageDialog::Type dialogType = KMessageDialog::QuestionTwoActions;
KGuiItem acceptButton;
QString text;
QIcon icon;
QString title = i18n("Delete Permanently");
bool isSingleUrl = false;
};
using AskIface = KIO::AskUserActionInterface;
static ProcessAskDeleteResult processAskDelete(const QList<QUrl> &urls, AskIface::DeletionType deletionType)
{
ProcessAskDeleteResult res;
res.prettyList.reserve(urls.size());
std::transform(urls.cbegin(), urls.cend(), std::back_inserter(res.prettyList), [](const auto &url) {
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.
static const QRegularExpression re(QStringLiteral("^/[0-9]+-"));
path.remove(re);
return path;
} else {
return url.toDisplayString(QUrl::PreferLocalFile);
}
});
const int urlCount = res.prettyList.size();
res.isSingleUrl = urlCount == 1;
switch (deletionType) {
case AskIface::Delete: {
res.dialogType = KMessageDialog::QuestionTwoActions; // Using Question* so the Delete button is pre-selected. Bug 462845
res.icon = QIcon::fromTheme(QStringLiteral("dialog-warning"));
if (res.isSingleUrl) {
res.text = xi18nc("@info",
"Do you really want to permanently delete this item?<nl/><nl/>"
"<filename>%1</filename><nl/><nl/>"
"<emphasis strong='true'>This action cannot be undone.</emphasis>",
res.prettyList.at(0));
} else {
res.text = xi18ncp("@info",
"Do you really want to permanently delete this %1 item?<nl/><nl/>"
"<emphasis strong='true'>This action cannot be undone.</emphasis>",
"Do you really want to permanently delete these %1 items?<nl/><nl/>"
"<emphasis strong='true'>This action cannot be undone.</emphasis>",
urlCount);
}
res.acceptButton = KGuiItem(i18nc("@action:button", "Delete Permanently"), QStringLiteral("edit-delete"));
break;
}
case AskIface::DeleteInsteadOfTrash: {
res.dialogType = KMessageDialog::WarningTwoActions;
if (res.isSingleUrl) {
res.text = xi18nc("@info",
"Moving this item to Trash failed as it is too large."
" Permanently delete it instead?<nl/><nl/>"
"<filename>%1</filename><nl/><nl/>"
"<emphasis strong='true'>This action cannot be undone.</emphasis>",
res.prettyList.at(0));
} else {
res.text = xi18ncp("@info",
"Moving this %1 item to Trash failed as it is too large."
" Permanently delete it instead?<nl/>"
"<emphasis strong='true'>This action cannot be undone.</emphasis>",
"Moving these %1 items to Trash failed as they are too large."
" Permanently delete them instead?<nl/><nl/>"
"<emphasis strong='true'>This action cannot be undone.</emphasis>",
urlCount);
}
res.acceptButton = KGuiItem(i18nc("@action:button", "Delete Permanently"), QStringLiteral("edit-delete"));
break;
}
case AskIface::EmptyTrash: {
res.dialogType = KMessageDialog::QuestionTwoActions; // Using Question* so the Delete button is pre-selected.
res.icon = QIcon::fromTheme(QStringLiteral("dialog-warning"));
res.text = xi18nc("@info",
"Do you want to permanently delete all items from the Trash?<nl/><nl/>"
"<emphasis strong='true'>This action cannot be undone.</emphasis>");
res.acceptButton = KGuiItem(i18nc("@action:button", "Empty Trash"), QStringLiteral("user-trash"));
break;
}
case AskIface::Trash: {
if (res.isSingleUrl) {
res.text = xi18nc("@info",
"Do you really want to move this item to the Trash?<nl/>"
"<filename>%1</filename>",
res.prettyList.at(0));
} else {
res.text =
xi18ncp("@info", "Do you really want to move this %1 item to the Trash?", "Do you really want to move these %1 items to the Trash?", urlCount);
}
res.title = i18n("Move to Trash");
res.acceptButton = KGuiItem(res.title, QStringLiteral("user-trash"));
break;
}
default:
break;
}
return res;
}
void KIO::WidgetsAskUserActionHandler::askUserDelete(const QList<QUrl> &urls, DeletionType deletionType, ConfirmationType confirmationType, QWidget *parent)
{
QString keyName;
bool ask = (confirmationType == ForceConfirmation);
if (!ask) {
// 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 DeleteInsteadOfTrash:
case Delete:
keyName = QStringLiteral("ConfirmDelete");
break;
case Trash:
keyName = QStringLiteral("ConfirmTrash");
defaultValue = false;
break;
case EmptyTrash:
keyName = QStringLiteral("ConfirmEmptyTrash");
break;
}
KSharedConfigPtr kioConfig = KSharedConfig::openConfig(QStringLiteral("kiorc"), KConfig::NoGlobals);
ask = kioConfig->group(QStringLiteral("Confirmations")).readEntry(keyName, defaultValue);
}
if (!ask) {
Q_EMIT askUserDeleteResult(true, urls, deletionType, parent);
return;
}
QMetaObject::invokeMethod(qGuiApp, [=, this] {
const auto &[prettyList, dialogType, acceptButton, text, icon, title, singleUrl] = processAskDelete(urls, deletionType);
KMessageDialog *dlg = new KMessageDialog(dialogType, text, parent);
dlg->setAttribute(Qt::WA_DeleteOnClose);
dlg->setCaption(title);
dlg->setIcon(icon);
dlg->setButtons(acceptButton, KStandardGuiItem::cancel());
if (!singleUrl) {
dlg->setListWidgetItems(prettyList);
}
dlg->setDontAskAgainText(i18nc("@option:checkbox", "Do not ask again"));
// If we get here, !ask must be false
dlg->setDontAskAgainChecked(!ask);
connect(dlg, &QDialog::finished, this, [=, this](const int buttonCode) {
const bool isDelete = (buttonCode == KMessageDialog::PrimaryAction);
Q_EMIT askUserDeleteResult(isDelete, urls, deletionType, parent);
if (isDelete) {
KSharedConfigPtr kioConfig = KSharedConfig::openConfig(QStringLiteral("kiorc"), KConfig::NoGlobals);
KConfigGroup cg = kioConfig->group(QStringLiteral("Confirmations"));
cg.writeEntry(keyName, !dlg->isDontAskAgainChecked());
cg.sync();
}
});
dlg->setWindowModality(Qt::WindowModal);
dlg->show();
});
}
void KIO::WidgetsAskUserActionHandler::requestUserMessageBox(MessageDialogType type,
const QString &text,
const QString &title,
const QString &primaryActionText,
const QString &secondaryActionText,
const QString &primaryActionIconName,
const QString &secondaryActionIconName,
const QString &dontAskAgainName,
const QString &details,
QWidget *parent)
{
if (d->gotPersistentUserReply(type,
KSharedConfig::openConfig(QStringLiteral("kioslaverc"))->group(QStringLiteral("Notification Messages")),
dontAskAgainName)) {
return;
}
const KGuiItem primaryActionButton(primaryActionText, primaryActionIconName);
const KGuiItem secondaryActionButton(secondaryActionText, secondaryActionIconName);
// It's "Do not ask again" every where except with Information
QString dontAskAgainText = i18nc("@option:check", "Do not ask again");
KMessageDialog::Type dlgType;
bool hasCancelButton = false;
switch (type) {
case AskUserActionInterface::QuestionTwoActions:
dlgType = KMessageDialog::QuestionTwoActions;
break;
case AskUserActionInterface::QuestionTwoActionsCancel:
dlgType = KMessageDialog::QuestionTwoActionsCancel;
hasCancelButton = true;
break;
case AskUserActionInterface::WarningTwoActions:
dlgType = KMessageDialog::WarningTwoActions;
break;
case AskUserActionInterface::WarningTwoActionsCancel:
dlgType = KMessageDialog::WarningTwoActionsCancel;
hasCancelButton = true;
break;
case AskUserActionInterface::WarningContinueCancel:
dlgType = KMessageDialog::WarningContinueCancel;
hasCancelButton = true;
break;
case AskUserActionInterface::Information:
dlgType = KMessageDialog::Information;
dontAskAgainText = i18nc("@option:check", "Do not show this message again");
break;
case AskUserActionInterface::Error:
dlgType = KMessageDialog::Error;
dontAskAgainText = QString{}; // No dontAskAgain checkbox
break;
default:
qCWarning(KIO_WIDGETS) << "Unknown message dialog type" << type;
return;
}
QMetaObject::invokeMethod(qGuiApp, [=, this]() {
auto cancelButton = hasCancelButton ? KStandardGuiItem::cancel() : KGuiItem();
auto *dialog = new KMessageDialog(dlgType, text, d->getParentWidget(parent));
dialog->setAttribute(Qt::WA_DeleteOnClose);
dialog->setCaption(title);
dialog->setIcon(QIcon{});
dialog->setButtons(primaryActionButton, secondaryActionButton, cancelButton);
dialog->setDetails(details);
dialog->setDontAskAgainText(dontAskAgainText);
dialog->setDontAskAgainChecked(false);
dialog->setOpenExternalLinks(true); // Allow opening external links in the text labels
connect(dialog, &QDialog::finished, this, [=, this](const int result) {
KIO::WorkerBase::ButtonCode btnCode;
switch (result) {
case KMessageDialog::PrimaryAction:
if (dlgType == KMessageDialog::WarningContinueCancel) {
btnCode = KIO::WorkerBase::Continue;
} else {
btnCode = KIO::WorkerBase::PrimaryAction;
}
break;
case KMessageDialog::SecondaryAction:
btnCode = KIO::WorkerBase::SecondaryAction;
break;
case KMessageDialog::Cancel:
btnCode = KIO::WorkerBase::Cancel;
break;
case KMessageDialog::Ok:
btnCode = KIO::WorkerBase::Ok;
break;
default:
qCWarning(KIO_WIDGETS) << "Unknown message dialog result" << result;
return;
}
Q_EMIT messageBoxResult(btnCode);
if ((result != KMessageDialog::Cancel) && dialog->isDontAskAgainChecked()) {
KSharedConfigPtr reqMsgConfig = KSharedConfig::openConfig(QStringLiteral("kioslaverc"));
KConfigGroup cg = reqMsgConfig->group(QStringLiteral("Notification Messages"));
d->savePersistentUserReply(type, cg, dontAskAgainName, result);
}
});
dialog->show();
});
}
void KIO::WidgetsAskUserActionHandler::setWindow(QWidget *window)
{
d->m_parentWidget = window;
}
void KIO::WidgetsAskUserActionHandler::askIgnoreSslErrors(const QVariantMap &sslErrorData, QWidget *parent)
{
QWidget *parentWidget = d->getParentWidget(parent);
QString message = i18n("The server failed the authenticity check (%1).\n\n", sslErrorData[QLatin1String("hostname")].toString());
message += sslErrorData[QLatin1String("sslError")].toString();
auto *dialog = new KMessageDialog(KMessageDialog::WarningTwoActionsCancel, message, parentWidget);
dialog->setAttribute(Qt::WA_DeleteOnClose);
dialog->setCaption(i18n("Server Authentication"));
dialog->setIcon(QIcon{});
dialog->setButtons(KGuiItem{i18n("&Details"), QStringLiteral("documentinfo")}, KStandardGuiItem::cont(), KStandardGuiItem::cancel());
connect(dialog, &KMessageDialog::finished, this, [this, parentWidget, sslErrorData](int result) {
if (result == KMessageDialog::PrimaryAction) {
showSslDetails(sslErrorData, parentWidget);
} else if (result == KMessageDialog::SecondaryAction) {
// continue();
Q_EMIT askIgnoreSslErrorsResult(1);
} else if (result == KMessageDialog::Cancel) {
// cancel();
Q_EMIT askIgnoreSslErrorsResult(0);
}
});
dialog->show();
}
void KIO::WidgetsAskUserActionHandler::showSslDetails(const QVariantMap &sslErrorData, QWidget *parentWidget)
{
const QStringList sslList = sslErrorData[QLatin1String("peerCertChain")].toStringList();
QList<QSslCertificate> certChain;
bool decodedOk = true;
for (const QString &str : sslList) {
certChain.append(QSslCertificate(str.toUtf8()));
if (certChain.last().isNull()) {
decodedOk = false;
break;
}
}
QMetaObject::invokeMethod(qGuiApp, [=, this] {
if (decodedOk) { // Use KSslInfoDialog
KSslInfoDialog *ksslDlg = new KSslInfoDialog(parentWidget);
ksslDlg->setSslInfo(
certChain,
QString(),
sslErrorData[QLatin1String("hostname")].toString(),
sslErrorData[QLatin1String("protocol")].toString(),
sslErrorData[QLatin1String("cipher")].toString(),
sslErrorData[QLatin1String("usedBits")].toInt(),
sslErrorData[QLatin1String("bits")].toInt(),
KSslInfoDialog::certificateErrorsFromString(sslErrorData[QLatin1String("certificateErrors")].toStringList().join(QLatin1Char('\n'))));
// KSslInfoDialog deletes itself by setting Qt::WA_DeleteOnClose
QObject::connect(ksslDlg, &QDialog::finished, this, [this, sslErrorData, parentWidget]() {
// KSslInfoDialog only has one button, QDialogButtonBox::Close
askIgnoreSslErrors(sslErrorData, parentWidget);
});
ksslDlg->show();
return;
}
// Fallback to a generic message box
auto *dialog = new KMessageDialog(KMessageDialog::Information, i18n("The peer SSL certificate chain appears to be corrupt."), parentWidget);
dialog->setAttribute(Qt::WA_DeleteOnClose);
dialog->setCaption(i18n("SSL"));
dialog->setButtons(KStandardGuiItem::ok());
QObject::connect(dialog, &QDialog::finished, this, [this](const int result) {
Q_EMIT askIgnoreSslErrorsResult(result == KMessageDialog::Ok ? 1 : 0);
});
dialog->show();
});
}
#include "moc_widgetsaskuseractionhandler.cpp"
@@ -0,0 +1,94 @@
/*
This file is part of the KDE libraries
SPDX-FileCopyrightText: 2020 Ahmad Samir <a.samirh78@gmail.com>
SPDX-License-Identifier: LGPL-2.0-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
*/
#ifndef WIDGETSASKUSERACTIONHANDLER_H
#define WIDGETSASKUSERACTIONHANDLER_H
#include "kiowidgets_export.h"
#include <kio/askuseractioninterface.h>
#include <kio/global.h>
#include <kio/jobuidelegateextension.h>
#include <kio/renamedialog.h>
#include <kio/skipdialog.h>
namespace KIO
{
// TODO KF6: Handle this the same way we end up handling WidgetsUntrustedProgramHandler.
/**
* @class KIO::WidgetsAskUserActionHandler widgetsaskuseractionhandler.h <KIO/WidgetsAskUserActionHandler>
*
* This implements KIO::AskUserActionInterface.
* @see KIO::AskUserActionInterface()
*
* @sa KIO::JobUiDelegateExtension()
*
* @since 5.78
* @note This header wasn't installed until 5.98
*/
class WidgetsAskUserActionHandlerPrivate;
class KIOWIDGETS_EXPORT WidgetsAskUserActionHandler : public AskUserActionInterface
{
Q_OBJECT
public:
explicit WidgetsAskUserActionHandler(QObject *parent = nullptr);
~WidgetsAskUserActionHandler() override;
/**
* @copydoc KIO::AskUserActionInterface::askUserRename()
*/
void askUserRename(KJob *job,
const QString &title,
const QUrl &src,
const QUrl &dest,
KIO::RenameDialog_Options options,
KIO::filesize_t sizeSrc = KIO::filesize_t(-1),
KIO::filesize_t sizeDest = KIO::filesize_t(-1),
const QDateTime &ctimeSrc = {},
const QDateTime &ctimeDest = {},
const QDateTime &mtimeSrc = {},
const QDateTime &mtimeDest = {}) override;
/**
* @copydoc KIO::AskUserActionInterface::askUserSkip()
*/
void askUserSkip(KJob *job, KIO::SkipDialog_Options options, const QString &error_text) override;
/**
* @copydoc KIO::AskUserActionInterface::askUserDelete()
*/
void askUserDelete(const QList<QUrl> &urls, DeletionType deletionType, ConfirmationType confirmationType, QWidget *parent = nullptr) override;
/**
* @copydoc KIO::AskUserActionInterface::requestUserMessageBox()
*/
void requestUserMessageBox(MessageDialogType type,
const QString &text,
const QString &title,
const QString &primaryActionText,
const QString &secondaryActionText,
const QString &primaryActionIconName = {},
const QString &secondaryActionIconName = {},
const QString &dontAskAgainName = {},
const QString &details = {},
QWidget *parent = nullptr) override;
void askIgnoreSslErrors(const QVariantMap &sslErrorData, QWidget *parent) override;
void setWindow(QWidget *window);
private:
void showSslDetails(const QVariantMap &sslErrorData, QWidget *parentWidget);
std::unique_ptr<WidgetsAskUserActionHandlerPrivate> d;
};
} // namespace KIO
#endif // WIDGETSASKUSERACTIONHANDLER_H
@@ -0,0 +1,94 @@
/*
This file is part of the KDE libraries
SPDX-FileCopyrightText: 2020 Ahmad Samir <a.samirh78@gmail.com>
SPDX-License-Identifier: LGPL-2.0-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
*/
#include "widgetsopenorexecutefilehandler.h"
#include "executablefileopendialog_p.h"
#include <KConfigGroup>
#include <KJob>
#include <KJobWidgets>
#include <KSharedConfig>
#include <QApplication>
#include <QMimeDatabase>
KIO::WidgetsOpenOrExecuteFileHandler::WidgetsOpenOrExecuteFileHandler(QObject *parent)
: KIO::OpenOrExecuteFileInterface(parent)
{
}
KIO::WidgetsOpenOrExecuteFileHandler::~WidgetsOpenOrExecuteFileHandler() = default;
static ExecutableFileOpenDialog::Mode promptMode(const QMimeType &mime)
{
// Note that ExecutableFileOpenDialog::OpenAsExecute isn't useful here as
// OpenUrlJob treats .exe (application/x-ms-dos-executable) files as executables
// that are only opened using the default application associated with that MIME type
// e.g. WINE
if (mime.inherits(QStringLiteral("text/plain"))) {
return ExecutableFileOpenDialog::OpenOrExecute;
}
return ExecutableFileOpenDialog::OnlyExecute;
}
void KIO::WidgetsOpenOrExecuteFileHandler::promptUserOpenOrExecute(KJob *job, const QString &mimetype)
{
KConfigGroup cfgGroup(KSharedConfig::openConfig(QStringLiteral("kiorc")), QStringLiteral("Executable scripts"));
const QString value = cfgGroup.readEntry("behaviourOnLaunch", "alwaysAsk");
if (value != QLatin1String("alwaysAsk")) {
Q_EMIT executeFile(value == QLatin1String("execute"));
return;
}
QWidget *parentWidget = nullptr;
if (job) {
parentWidget = KJobWidgets::window(job);
}
if (!parentWidget) {
parentWidget = m_parentWidget;
}
if (!parentWidget) {
parentWidget = qApp->activeWindow();
}
QMimeDatabase db;
QMimeType mime = db.mimeTypeForName(mimetype);
ExecutableFileOpenDialog *dialog = new ExecutableFileOpenDialog(promptMode(mime), parentWidget);
dialog->setAttribute(Qt::WA_DeleteOnClose);
dialog->setModal(true);
connect(dialog, &QDialog::finished, this, [this, dialog, mime](const int result) {
if (result == ExecutableFileOpenDialog::Rejected) {
Q_EMIT canceled();
return;
}
const bool isExecute = result == ExecutableFileOpenDialog::ExecuteFile;
Q_EMIT executeFile(isExecute);
if (dialog->isDontAskAgainChecked()) {
KConfigGroup cfgGroup(KSharedConfig::openConfig(QStringLiteral("kiorc")), QStringLiteral("Executable scripts"));
cfgGroup.writeEntry("behaviourOnLaunch", isExecute ? "execute" : "open");
}
});
dialog->show();
}
void KIO::WidgetsOpenOrExecuteFileHandler::setWindow(QWidget *window)
{
m_parentWidget = window;
}
#include "moc_widgetsopenorexecutefilehandler.cpp"
@@ -0,0 +1,38 @@
/*
This file is part of the KDE libraries
SPDX-FileCopyrightText: 2020 Ahmad Samir <a.samirh78@gmail.com>
SPDX-License-Identifier: LGPL-2.0-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
*/
#ifndef WIDGETSOPENOREXECUTEFILEHANDLER_H
#define WIDGETSOPENOREXECUTEFILEHANDLER_H
#include "openorexecutefileinterface.h"
namespace KIO
{
// TODO KF6: Make KIO::JobUiDelegate inherit from WidgetsOpenOrExecuteFileHandler
// (or even merge the two classes)
// so that setDelegate(new KIO::JobUiDelegate) invokes the dialog boxes on error
// and when showing ExecutableFileOpenDialog.
class WidgetsOpenOrExecuteFileHandler : public OpenOrExecuteFileInterface
{
Q_OBJECT
public:
explicit WidgetsOpenOrExecuteFileHandler(QObject *parent = nullptr);
~WidgetsOpenOrExecuteFileHandler() override;
void promptUserOpenOrExecute(KJob *job, const QString &mimetype) override;
void setWindow(QWidget *window);
private:
// Note: no d pointer because it's not exported at this point
QWidget *m_parentWidget = nullptr;
};
}
#endif // WIDGETSOPENOREXECUTEFILEHANDLER_H
@@ -0,0 +1,79 @@
/*
This file is part of the KDE libraries
SPDX-FileCopyrightText: 2020 David Faure <faure@kde.org>
SPDX-License-Identifier: LGPL-2.0-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
*/
#include "widgetsopenwithhandler.h"
#include "kopenwithdialog.h"
#include "openurljob.h"
#include <KConfigGroup>
#include <KJobWidgets>
#include <KLocalizedString>
#include <KSharedConfig>
#include <QApplication>
#ifdef Q_OS_WIN
#include "widgetsopenwithhandler_win.cpp" // displayNativeOpenWithDialog
#endif
KIO::WidgetsOpenWithHandler::WidgetsOpenWithHandler(QObject *parent)
: KIO::OpenWithHandlerInterface(parent)
{
}
KIO::WidgetsOpenWithHandler::~WidgetsOpenWithHandler() = default;
void KIO::WidgetsOpenWithHandler::setWindow(QWidget *widget)
{
m_parentWidget = widget;
}
void KIO::WidgetsOpenWithHandler::promptUserForApplication(KJob *job, const QList<QUrl> &urls, const QString &mimeType)
{
QWidget *parentWidget = nullptr;
if (job) {
parentWidget = KJobWidgets::window(job);
}
if (!parentWidget) {
parentWidget = m_parentWidget;
}
if (!parentWidget) {
parentWidget = qApp->activeWindow();
}
#ifdef Q_OS_WIN
KConfigGroup cfgGroup(KSharedConfig::openConfig(), QStringLiteral("KOpenWithDialog Settings"));
if (cfgGroup.readEntry("Native", true)) {
// Implemented in applicationlauncherjob_win.cpp
if (displayNativeOpenWithDialog(urls, parentWidget)) {
Q_EMIT handled();
return;
} else {
// Some error happened with the Windows-specific code. Fallback to the KDE one...
}
}
#endif
KOpenWithDialog *dialog = new KOpenWithDialog(urls, mimeType, QString(), QString(), parentWidget);
dialog->setAttribute(Qt::WA_DeleteOnClose);
connect(dialog, &QDialog::accepted, this, [=, this]() {
KService::Ptr service = dialog->service();
if (!service) {
service = KService::Ptr(new KService(QString() /*name*/, dialog->text(), QString() /*icon*/));
}
Q_EMIT serviceSelected(service);
});
connect(dialog, &QDialog::rejected, this, [this]() {
Q_EMIT canceled();
});
dialog->show();
}
#include "moc_widgetsopenwithhandler.cpp"
@@ -0,0 +1,41 @@
/*
This file is part of the KDE libraries
SPDX-FileCopyrightText: 2020 David Faure <faure@kde.org>
SPDX-License-Identifier: LGPL-2.0-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
*/
#ifndef WIDGETSOPENWITHHANDLER_H
#define WIDGETSOPENWITHHANDLER_H
#include "openwithhandlerinterface.h"
class QDialog;
class QWidget;
namespace KIO
{
// TODO KF6: Make KIO::JobUiDelegate inherit from WidgetsOpenWithHandler
// (or even merge the two classes)
// so that setDelegate(new KIO::JobUiDelegate) provides both dialog boxes on error
// and the open-with dialog.
class WidgetsOpenWithHandler : public OpenWithHandlerInterface
{
Q_OBJECT
public:
explicit WidgetsOpenWithHandler(QObject *parent = nullptr);
~WidgetsOpenWithHandler() override;
void promptUserForApplication(KJob *job, const QList<QUrl> &urls, const QString &mimeType) override;
void setWindow(QWidget *widget);
private:
// Note: no d pointer because not exported at this point
QWidget *m_parentWidget = nullptr;
};
}
#endif // WIDGETSOPENWITHHANDLER_H
@@ -0,0 +1,65 @@
/*
This file is part of the KDE libraries
SPDX-FileCopyrightText: 2008 Jarosław Staniek <staniek@kde.org>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#include <QDir>
#include <QWidget>
#include <qt_windows.h>
// TODO move to a shared lib
static int runDll(WId windowId, const QString &libraryName, const QByteArray &functionName, const QString &arguments)
{
HMODULE libHandle = LoadLibraryW((LPCWSTR)libraryName.utf16());
if (!libHandle) {
return 0;
}
typedef int(WINAPI * FunctionType)(HWND, HMODULE, LPCWSTR, int);
FunctionType function = (FunctionType)GetProcAddress(libHandle, functionName.constData());
if (!function) {
return 0;
}
int result = function((HWND)windowId, libHandle, (LPCWSTR)arguments.utf16(), SW_SHOW);
FreeLibrary(libHandle);
return result;
}
static int runDll(WId windowId, const QString &libraryName, const QByteArray &functionName, const QByteArray &arguments)
{
HMODULE libHandle = LoadLibraryW((LPCWSTR)libraryName.utf16());
if (!libHandle) {
return 0;
}
typedef int(WINAPI * FunctionType)(HWND, HMODULE, LPCSTR, int);
FunctionType function = (FunctionType)GetProcAddress(libHandle, functionName.constData());
if (!function) {
return 0;
}
int result = function((HWND)windowId, libHandle, (LPCSTR)arguments.constData(), SW_SHOW);
FreeLibrary(libHandle);
return result;
}
// TODO move to a shared lib
static int runDll(QWidget *parent, const QString &libraryName, const QByteArray &functionName, const QString &arguments)
{
return runDll(parent ? parent->winId() : 0, libraryName, functionName, arguments);
}
// Windows implementation using "OpenAs_RunDLL" entry
static bool displayNativeOpenWithDialog(const QList<QUrl> &lst, QWidget *window)
{
QStringList fnames;
for (const QUrl &url : lst) {
fnames += url.isLocalFile() ? QDir::toNativeSeparators(url.toLocalFile()) : url.toString();
}
int result = runDll(window, QLatin1String("shell32.dll"), "OpenAs_RunDLLW", fnames.join(QLatin1Char(' ')));
return result == 0;
}
@@ -0,0 +1,199 @@
/*
This file is part of the KDE libraries
SPDX-FileCopyrightText: 2020 David Faure <faure@kde.org>
SPDX-License-Identifier: LGPL-2.0-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
*/
#include "widgetsuntrustedprogramhandler.h"
#include <KIconLoader>
#include <KJob>
#include <KJobWidgets>
#include <KLocalizedString>
#include <KStandardGuiItem>
#include <QApplication>
#include <QDialog>
#include <QDialogButtonBox>
#include <QHBoxLayout>
#include <QLabel>
#include <QPlainTextEdit>
#include <QPushButton>
#include <QScreen>
#include <QStyle>
#include <QVBoxLayout>
class KIO::WidgetsUntrustedProgramHandlerPrivate
{
public:
QWidget *m_parentWidget = nullptr;
};
KIO::WidgetsUntrustedProgramHandler::WidgetsUntrustedProgramHandler(QObject *parent)
: KIO::UntrustedProgramHandlerInterface(parent)
, d(std::make_unique<WidgetsUntrustedProgramHandlerPrivate>())
{
}
KIO::WidgetsUntrustedProgramHandler::~WidgetsUntrustedProgramHandler()
{
}
// 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:
explicit SecureMessageDialog(QWidget *parent)
: QDialog(parent)
, m_textEdit(nullptr)
{
}
void setTextEdit(QPlainTextEdit *textEdit)
{
m_textEdit = textEdit;
}
protected:
void showEvent(QShowEvent *e) override
{
if (e->spontaneous()) {
return;
}
// 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);
}
private:
QPlainTextEdit *m_textEdit;
};
QDialog *KIO::WidgetsUntrustedProgramHandler::createDialog(QWidget *parentWidget, const QString &programName)
{
SecureMessageDialog *baseDialog = new SecureMessageDialog(parentWidget);
baseDialog->setWindowTitle(i18nc("Warning about executing unknown program", "Warning"));
QVBoxLayout *topLayout = new QVBoxLayout(baseDialog);
// 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);
const QIcon icon = baseDialog->style()->standardIcon(QStyle::SP_MessageBoxWarning, nullptr, baseDialog);
const QPixmap warningIcon(icon.pixmap(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);
QPlainTextEdit *textEdit = new QPlainTextEdit(baseWidget);
textEdit->setPlainText(programName);
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, &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.
const QSize screenSize = baseDialog->screen()->size();
baseDialog->resize(screenSize.width() / 4, 50);
baseDialog->setMaximumHeight(screenSize.height() / 3);
baseDialog->setMaximumWidth(screenSize.width() / 10 * 8);
baseDialog->setAttribute(Qt::WA_DeleteOnClose);
return baseDialog;
}
void KIO::WidgetsUntrustedProgramHandler::showUntrustedProgramWarning(KJob *job, const QString &programName)
{
QWidget *parentWidget = nullptr;
if (job) {
parentWidget = KJobWidgets::window(job);
}
if (!parentWidget) {
parentWidget = d->m_parentWidget;
}
if (!parentWidget) {
parentWidget = qApp->activeWindow();
}
QDialog *dialog = createDialog(parentWidget, programName);
connect(dialog, &QDialog::accepted, this, [this]() {
Q_EMIT result(true);
});
connect(dialog, &QDialog::rejected, this, [this]() {
Q_EMIT result(false);
});
dialog->show();
}
bool KIO::WidgetsUntrustedProgramHandler::execUntrustedProgramWarning(QWidget *window, const QString &programName)
{
QDialog *dialog = createDialog(window, programName);
return dialog->exec() == QDialog::Accepted;
}
void KIO::WidgetsUntrustedProgramHandler::setWindow(QWidget *window)
{
d->m_parentWidget = window;
}
#include "moc_widgetsuntrustedprogramhandler.cpp"
#include "widgetsuntrustedprogramhandler.moc"
@@ -0,0 +1,50 @@
/*
This file is part of the KDE libraries
SPDX-FileCopyrightText: 2020 David Faure <faure@kde.org>
SPDX-License-Identifier: LGPL-2.0-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
*/
#ifndef WIDGETSUNTRUSTEDPROGRAMHANDLER_H
#define WIDGETSUNTRUSTEDPROGRAMHANDLER_H
#include "untrustedprogramhandlerinterface.h"
#include <memory>
class QDialog;
class QWidget;
namespace KIO
{
// TODO KF6: Make KIO::JobUiDelegate inherit from WidgetsUntrustedProgramHandler
// (or even merge the two classes)
// so that setDelegate(new KIO::JobUiDelegate) provides both dialog boxes on error
// and the messagebox for handling untrusted programs.
// Then port those users of ApplicationLauncherJob which were setting a KDialogJobUiDelegate
// to set a KIO::JobUiDelegate instead.
class WidgetsUntrustedProgramHandlerPrivate;
class WidgetsUntrustedProgramHandler : public UntrustedProgramHandlerInterface
{
Q_OBJECT
public:
explicit WidgetsUntrustedProgramHandler(QObject *parent = nullptr);
~WidgetsUntrustedProgramHandler() override;
void showUntrustedProgramWarning(KJob *job, const QString &programName) override;
// Compat code for KRun::runUrl. Will disappear before KF6
bool execUntrustedProgramWarning(QWidget *window, const QString &programName);
void setWindow(QWidget *window);
private:
QDialog *createDialog(QWidget *parentWidget, const QString &programName);
std::unique_ptr<WidgetsUntrustedProgramHandlerPrivate> d;
};
}
#endif // WIDGETSUNTRUSTEDPROGRAMHANDLER_H