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:
@@ -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.<br/>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 K‑osvajačev iskačući meni
|
||||
Comment[sr@latin]=Priključak za K‑osvajač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 ¤t, 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>&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 "Dial up tool".</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>&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 "Dial up tool".</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&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>&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>&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>&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><qt><p>This list should show the types of file that your application can handle. This list is organized by <u>mimetypes</u>.</p>
|
||||
<p>MIME, Multipurpose Internet (e)Mail Extension, is a standard protocol for identifying the type of data based on filename extensions and correspondent <u>mimetypes</u>. Example: the "bmp" part that comes after the dot in flower.bmp indicates that it is a specific kind of image, <u>image/x-bmp</u>. 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.</p>
|
||||
<p>If you want to associate this application with one or more mimetypes that are not in this list, click on the button <b>Add</b> 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 <b>Remove</b> below.</p></qt></string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>&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><qt><p>This list should show the types of file that your application can handle. This list is organized by <u>mimetypes</u>.</p>
|
||||
<p>MIME, Multipurpose Internet (e)Mail Extension, is a standard protocol for identifying the type of data based on filename extensions and correspondent <u>mimetypes</u>. Example: the "bmp" part that comes after the dot in flower.bmp indicates that it is a specific kind of image, <u>image/x-bmp</u>. 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.</p>
|
||||
<p>If you want to associate this application with one or more mimetypes that are not in this list, click on the button <b>Add</b> 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 <b>Remove</b> below.</p></qt></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&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<QString>"/>
|
||||
</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
|
||||
Reference in New Issue
Block a user