diff --git a/CMakeLists.txt b/CMakeLists.txt index 3a2e4ec6..a4328c6d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,175 +1,173 @@ cmake_minimum_required(VERSION 3.5) set(KF5_VERSION "5.64.0") # handled by release scripts set(KF5_DEP_VERSION "5.63.0") # handled by release scripts project(KIO VERSION ${KF5_VERSION}) include(FeatureSummary) find_package(ECM 5.63.0 NO_MODULE) set_package_properties(ECM PROPERTIES TYPE REQUIRED DESCRIPTION "Extra CMake Modules." URL "https://projects.kde.org/projects/kdesupport/extra-cmake-modules") feature_summary(WHAT REQUIRED_PACKAGES_NOT_FOUND FATAL_ON_MISSING_REQUIRED_PACKAGES) set(CMAKE_MODULE_PATH ${ECM_MODULE_PATH} ${CMAKE_CURRENT_SOURCE_DIR}/cmake ) include(KDEInstallDirs) include(KDEFrameworkCompilerSettings NO_POLICY_SCOPE) include(KDECMakeSettings) - -include(GenerateExportHeader) - +include(ECMGenerateExportHeader) include(ECMMarkAsTest) include(ECMSetupVersion) include(ECMGenerateHeaders) include(ECMAddQch) include(ECMMarkNonGuiExecutable) include(ECMQtDeclareLoggingCategory) ecm_setup_version( PROJECT VARIABLE_PREFIX KIO VERSION_HEADER "${CMAKE_CURRENT_BINARY_DIR}/src/kio_version.h" PACKAGE_VERSION_FILE "${CMAKE_CURRENT_BINARY_DIR}/KF5KIOConfigVersion.cmake" SOVERSION 5) option(KIOCORE_ONLY "Only compile KIOCore, not KIOWidgets or anything that depends on it. This will disable support for cookies and passwordhandling (prompting and storing)." OFF) option(KIO_FORK_SLAVES "If set we start the slaves via QProcess. It's also possible to change this by setting the environment variable KDE_FORK_SLAVES." OFF) # Enable state assertion by default on Jenkins. # This option should eventually be dropped and always be enabled. set(ASSERT_SLAVE_STATES_DEFAULT OFF) if(DEFINED ENV{JENKINS_SERVER_COOKIE}) set(ASSERT_SLAVE_STATES_DEFAULT ON) endif() option(KIO_ASSERT_SLAVE_STATES "Used to control whether slave state assertions are enabled. When not enabled only warnings are generated." ${ASSERT_SLAVE_STATES_DEFAULT}) if(KIO_ASSERT_SLAVE_STATES AND NOT CMAKE_BUILD_TYPE MATCHES "[Dd]ebug$") message(FATAL_ERROR "KIO_ASSERT_SLAVE_STATES option enabled but not a Debug build. This makes no sense!") endif() option(BUILD_QCH "Build API documentation in QCH format (for e.g. Qt Assistant, Qt Creator & KDevelop)" OFF) add_feature_info(QCH ${BUILD_QCH} "API documentation in QCH format (for e.g. Qt Assistant, Qt Creator & KDevelop)") option(BUILD_DESIGNERPLUGIN "Build plugin for Qt Designer" ON) add_feature_info(DESIGNERPLUGIN ${BUILD_DESIGNERPLUGIN} "Build plugin for Qt Designer") find_package(KF5Archive ${KF5_DEP_VERSION} REQUIRED) find_package(KF5Config ${KF5_DEP_VERSION} REQUIRED) find_package(KF5CoreAddons ${KF5_DEP_VERSION} REQUIRED) find_package(KF5Crash ${KF5_DEP_VERSION} REQUIRED) find_package(KF5DBusAddons ${KF5_DEP_VERSION} REQUIRED) find_package(KF5I18n ${KF5_DEP_VERSION} REQUIRED) find_package(KF5Service ${KF5_DEP_VERSION} REQUIRED) find_package(KF5DocTools ${KF5_DEP_VERSION}) find_package(KF5Solid ${KF5_DEP_VERSION} REQUIRED) # for kio_trash if (NOT KIOCORE_ONLY) find_package(KF5Bookmarks ${KF5_DEP_VERSION} REQUIRED) find_package(KF5Completion ${KF5_DEP_VERSION} REQUIRED) find_package(KF5ConfigWidgets ${KF5_DEP_VERSION} REQUIRED) find_package(KF5IconThemes ${KF5_DEP_VERSION} REQUIRED) find_package(KF5ItemViews ${KF5_DEP_VERSION} REQUIRED) find_package(KF5JobWidgets ${KF5_DEP_VERSION} REQUIRED) find_package(KF5WidgetsAddons ${KF5_DEP_VERSION} REQUIRED) find_package(KF5WindowSystem ${KF5_DEP_VERSION} REQUIRED) endif() if (UNIX) find_package(KF5Auth ${KF5_DEP_VERSION} REQUIRED) endif() # tell what is missing without doctools set_package_properties(KF5DocTools PROPERTIES DESCRIPTION "Provides tools to generate documentation in various format from DocBook files" TYPE OPTIONAL PURPOSE "Required to build help ioslave and documentation" ) set(REQUIRED_QT_VERSION 5.11.0) find_package(Qt5 ${REQUIRED_QT_VERSION} CONFIG REQUIRED Widgets DBus Network Concurrent Xml Test) find_package(GSSAPI) set_package_properties(GSSAPI PROPERTIES DESCRIPTION "Allows KIO to make use of certain HTTP authentication services" URL "http://web.mit.edu/kerberos/www" TYPE OPTIONAL PURPOSE "A MIT or HEIMDAL flavor of GSSAPI can be used" ) if (NOT APPLE AND NOT WIN32) find_package(X11) endif() set(HAVE_X11 ${X11_FOUND}) if (HAVE_X11) find_package(Qt5 ${REQUIRED_QT_VERSION} CONFIG REQUIRED X11Extras) endif() # Qt 5.13 deprecated QComboBox::currentIndexChanged(QString) and Qt 5.14 undid that... if (NOT Qt5Widgets_VERSION VERSION_LESS 5.14.0) add_definitions(-DQT_DISABLE_DEPRECATED_BEFORE=0x050d00) endif() add_definitions(-DTRANSLATION_DOMAIN=\"kio5\") if (IS_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/po") ki18n_install(po) if (KF5DocTools_FOUND) kdoctools_install(po) endif() endif() if (KF5DocTools_FOUND) add_subdirectory(docs) endif() include(CheckLibraryExists) add_subdirectory(src) if(BUILD_TESTING) add_subdirectory(autotests) if (NOT KIOCORE_ONLY) add_subdirectory(tests) endif() endif() add_subdirectory(kconf_update) # create a Config.cmake and a ConfigVersion.cmake file and install them set(CMAKECONFIG_INSTALL_DIR "${KDE_INSTALL_CMAKEPACKAGEDIR}/KF5KIO") if (BUILD_QCH) ecm_install_qch_export( TARGETS KF5KIO_QCH FILE KF5KIOQchTargets.cmake DESTINATION "${CMAKECONFIG_INSTALL_DIR}" COMPONENT Devel ) set(PACKAGE_INCLUDE_QCHTARGETS "include(\"\${CMAKE_CURRENT_LIST_DIR}/KF5KIOQchTargets.cmake\")") endif() include(CMakePackageConfigHelpers) configure_package_config_file( "${CMAKE_CURRENT_SOURCE_DIR}/KF5KIOConfig.cmake.in" "${CMAKE_CURRENT_BINARY_DIR}/KF5KIOConfig.cmake" PATH_VARS KDE_INSTALL_DBUSINTERFACEDIR INSTALL_DESTINATION ${CMAKECONFIG_INSTALL_DIR} ) install(FILES "${CMAKE_CURRENT_BINARY_DIR}/KF5KIOConfig.cmake" "${CMAKE_CURRENT_BINARY_DIR}/KF5KIOConfigVersion.cmake" DESTINATION "${CMAKECONFIG_INSTALL_DIR}" COMPONENT Devel ) install(EXPORT KF5KIOTargets DESTINATION "${CMAKECONFIG_INSTALL_DIR}" FILE KF5KIOTargets.cmake NAMESPACE KF5:: ) install(FILES ${CMAKE_CURRENT_BINARY_DIR}/src/kio_version.h DESTINATION ${KDE_INSTALL_INCLUDEDIR_KF5} COMPONENT Devel ) install(FILES kio.categories DESTINATION ${KDE_INSTALL_LOGGINGCATEGORIESDIR}) feature_summary(WHAT ALL FATAL_ON_MISSING_REQUIRED_PACKAGES) diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt index a9108337..4ca0e39e 100644 --- a/src/core/CMakeLists.txt +++ b/src/core/CMakeLists.txt @@ -1,296 +1,304 @@ project(KIOCore) include (ConfigureChecks.cmake) configure_file(config-kiocore.h.cmake ${CMAKE_CURRENT_BINARY_DIR}/config-kiocore.h ) configure_file(config-kmountpoint.h.cmake ${CMAKE_CURRENT_BINARY_DIR}/config-kmountpoint.h) # KSSL_HAVE_SSL only used in kssl/ksslsettings.cpp, but currently ifdefed out #find_package(OpenSSL) #set_package_properties(OpenSSL PROPERTIES DESCRIPTION "Support for secure network communications (SSL and TLS)" # URL "http://openssl.org" # TYPE RECOMMENDED # PURPOSE "KDE uses OpenSSL for the bulk of secure communications, including secure web browsing via HTTPS" # ) #if(OPENSSL_FOUND) # set(KSSL_HAVE_SSL 1) # include_directories(${OPENSSL_INCLUDE_DIR}) #endif() set(kiocore_SRCS idleslave.cpp klocalsocket.cpp connectionbackend.cpp connection.cpp connectionserver.cpp krecentdocument.cpp kfileitemlistproperties.cpp tcpslavebase.cpp directorysizejob.cpp forwardingslavebase.cpp chmodjob.cpp kdiskfreespaceinfo.cpp usernotificationhandler.cpp ksambasharedata.cpp ksambashare.cpp knfsshare.cpp kfileitem.cpp davjob.cpp deletejob.cpp copyjob.cpp filejob.cpp mkdirjob.cpp mkpathjob.cpp kpasswdserverloop.cpp kpasswdserverclient.cpp kremoteencoding.cpp sessiondata.cpp slavebase.cpp dataslave.cpp dataprotocol.cpp desktopexecparser.cpp emptytrashjob.cpp authinfo.cpp slaveinterface.cpp slave.cpp job_error.cpp job.cpp filecopyjob.cpp listjob.cpp mimetypejob.cpp multigetjob.cpp restorejob.cpp simplejob.cpp specialjob.cpp statjob.cpp storedtransferjob.cpp transferjob.cpp filesystemfreespacejob.cpp scheduler.cpp slaveconfig.cpp kprotocolmanager.cpp hostinfo.cpp kdirnotify.cpp kurlauthorized.cpp kacl.cpp udsentry.cpp global.cpp metadata.cpp kprotocolinfo.cpp kprotocolinfofactory.cpp jobtracker.cpp jobuidelegateextension.cpp jobuidelegatefactory.cpp kmountpoint.cpp kcoredirlister.cpp faviconscache.cpp ksslcertificatemanager.cpp ksslerroruidata.cpp ktcpsocket.cpp kssl/ksslsettings.cpp kioglobal_p.cpp batchrenamejob.cpp ) ecm_qt_declare_logging_category(kiocore_SRCS HEADER kiocoredebug.h IDENTIFIER KIO_CORE CATEGORY_NAME kf5.kio.core) if (UNIX) set(kiocore_SRCS ${kiocore_SRCS} klocalsocket_unix.cpp kioglobal_p_unix.cpp ) endif() if (WIN32) set(kiocore_SRCS ${kiocore_SRCS} klocalsocket_win.cpp kioglobal_p_win.cpp ) endif() qt5_add_dbus_interface(kiocore_SRCS org.kde.KSlaveLauncher.xml klauncher_interface) set_source_files_properties(org.kde.KPasswdServer.xml PROPERTIES INCLUDE authinfo.h ) qt5_add_dbus_interface(kiocore_SRCS org.kde.KPasswdServer.xml kpasswdserver_interface) install(FILES org.kde.KDirNotify.xml DESTINATION ${KDE_INSTALL_DBUSINTERFACEDIR} RENAME kf5_org.kde.KDirNotify.xml) install(FILES org.kde.KPasswdServer.xml DESTINATION ${KDE_INSTALL_DBUSINTERFACEDIR} RENAME kf5_org.kde.KPasswdServer.xml) install(FILES org.kde.KSlaveLauncher.xml DESTINATION ${KDE_INSTALL_DBUSINTERFACEDIR} RENAME kf5_org.kde.KSlaveLauncher.xml) add_library(KF5KIOCore ${kiocore_SRCS}) -generate_export_header(KF5KIOCore BASE_NAME KIOCore) add_library(KF5::KIOCore ALIAS KF5KIOCore) +ecm_generate_export_header(KF5KIOCore + BASE_NAME KIOCore + # GROUP_BASE_NAME KF <- enable once all of KF modules use ecm_generate_export_header + VERSION ${KF5_VERSION} + DEPRECATED_BASE_VERSION 0 + DEPRECATION_VERSIONS 3.0 3.1 3.4 4.0 4.3 4.5 4.6 5.0 5.2 5.8 5.24 5.45 5.48 5.63 5.61 5.64 +) +# TODO: add support for EXCLUDE_DEPRECATED_BEFORE_AND_AT to all KIO libs +# needs fixing of undeprecated API being still implemented using own deprecated API target_include_directories(KF5KIOCore PUBLIC "$" # kio_version.h "$" ) target_include_directories(KF5KIOCore INTERFACE "$") target_link_libraries(KF5KIOCore PUBLIC KF5::CoreAddons # KJob KF5::Service Qt5::Network Qt5::Concurrent # QtConcurrentRun in hostinfo.cpp Qt5::DBus PRIVATE Qt5::Xml # davjob.cpp uses QDom KF5::I18n KF5::Crash KF5::DBusAddons # KDEInitInterface ) if (UNIX) target_link_libraries(KF5KIOCore PRIVATE KF5::AuthCore) #SlaveBase uses KAuth::Action endif() if(ACL_FOUND) target_link_libraries(KF5KIOCore PRIVATE ${ACL_LIBS}) endif() set_target_properties(KF5KIOCore PROPERTIES VERSION ${KIO_VERSION_STRING} SOVERSION ${KIO_SOVERSION} EXPORT_NAME KIOCore ) # this should be done by cmake, see bug 371721 if(CMAKE_CXX_COMPILER_ID STREQUAL "Clang" OR CMAKE_CXX_COMPILER_ID STREQUAL "GNU" AND Qt5Core_VERSION VERSION_GREATER 5.8.0) add_custom_command( OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/moc_predefs.h COMMAND "${CMAKE_CXX_COMPILER}" "-dM" "-E" "-c" "${CMAKE_ROOT}/Modules/CMakeCXXCompilerABI.cpp" > ${CMAKE_CURRENT_BINARY_DIR}/moc_predefs.h ) set_property(TARGET KF5KIOCore APPEND PROPERTY AUTOMOC_MOC_OPTIONS --include ${CMAKE_CURRENT_BINARY_DIR}/moc_predefs.h) set_property(TARGET KF5KIOCore APPEND PROPERTY AUTOGEN_TARGET_DEPENDS ${CMAKE_CURRENT_BINARY_DIR}/moc_predefs.h) endif() # Headers prefixed with KIO/ ecm_generate_headers(KIOCore_CamelCase_HEADERS HEADER_NAMES IdleSlave ConnectionServer TCPSlaveBase DirectorySizeJob ForwardingSlaveBase Job # ### should forward to job_base.h, not job.h... JobTracker Global ChmodJob DeleteJob CopyJob EmptyTrashJob FileJob MkdirJob MkpathJob SlaveBase SlaveConfig HostInfo MetaData UDSEntry JobUiDelegateExtension JobUiDelegateFactory SlaveInterface Slave FileCopyJob ListJob MimetypeJob MultiGetJob RestoreJob SimpleJob SpecialJob StatJob StoredTransferJob TransferJob Scheduler AuthInfo DavJob DesktopExecParser FileSystemFreeSpaceJob BatchRenameJob PREFIX KIO REQUIRED_HEADERS KIO_namespaced_HEADERS ) # Create local forwarding header for kio/job_base.h set(REGULAR_HEADER_NAME ${CMAKE_CURRENT_BINARY_DIR}/kio/job_base.h) if (NOT EXISTS ${REGULAR_HEADER_NAME}) file(WRITE ${REGULAR_HEADER_NAME} "#include \"${CMAKE_CURRENT_SOURCE_DIR}/job_base.h\"\n") endif() install(TARGETS KF5KIOCore EXPORT KF5KIOTargets ${KF5_INSTALL_TARGETS_DEFAULT_ARGS}) list(APPEND KIO_namespaced_HEADERS http_slave_defaults.h ioslave_defaults.h job_base.h jobclasses.h ) install(FILES ${KIO_namespaced_HEADERS} DESTINATION ${KDE_INSTALL_INCLUDEDIR_KF5}/KIOCore/kio COMPONENT Devel ) # Headers not prefixed with KIO/ ecm_generate_headers(KIOCore_HEADERS HEADER_NAMES KACL KUrlAuthorized KCoreDirLister KRemoteEncoding KDirNotify KDiskFreeSpaceInfo KFileItem KFileItemListProperties KMountPoint KNFSShare KSambaShare KSambaShareData KPasswdServerClient KProtocolInfo KProtocolManager KRecentDocument KSslCertificateManager KSslErrorUiData KTcpSocket REQUIRED_HEADERS KIOCore_HEADERS ) ecm_generate_headers(KIOCore_HEADERS HEADER_NAMES KSSLSettings RELATIVE kssl REQUIRED_HEADERS KIOCore_HEADERS ) install(FILES ${KIOCore_CamelCase_HEADERS} DESTINATION ${KDE_INSTALL_INCLUDEDIR_KF5}/KIOCore/KIO COMPONENT Devel) install(FILES ksslcertificatemanager_p.h ${KIOCore_HEADERS} ${CMAKE_CURRENT_BINARY_DIR}/kiocore_export.h DESTINATION ${KDE_INSTALL_INCLUDEDIR_KF5}/KIOCore COMPONENT Devel) install(FILES accept-languages.codes DESTINATION ${KDE_INSTALL_CONFDIR}) # make available to ecm_add_qch in parent folder set(KIOCore_QCH_SOURCES ${KIOCore_HEADERS} ${KIO_namespaced_HEADERS} PARENT_SCOPE) include(ECMGeneratePriFile) ecm_generate_pri_file(BASE_NAME KIOCore LIB_NAME KF5KIOCore DEPS "KCoreAddons KService" FILENAME_VAR PRI_FILENAME INCLUDE_INSTALL_DIR ${KDE_INSTALL_INCLUDEDIR_KF5}/KIOCore) install(FILES ${PRI_FILENAME} DESTINATION ${ECM_MKSPECS_INSTALL_DIR}) diff --git a/src/core/copyjob.h b/src/core/copyjob.h index 74c0c816..7998de39 100644 --- a/src/core/copyjob.h +++ b/src/core/copyjob.h @@ -1,430 +1,432 @@ // -*- c++ -*- /* This file is part of the KDE libraries Copyright 2000 Stephan Kulow Copyright 2000-2006 David Faure This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef KIO_COPYJOB_H #define KIO_COPYJOB_H #include #include #include #include #include "kiocore_export.h" #include // filesize_t #include "job_base.h" class QTimer; namespace KIO { /// @internal /// KF6 TODO: move to .cpp and remove aboutToCreate signal struct CopyInfo { QUrl uSource; QUrl uDest; QString linkDest; // for symlinks only int permissions; QDateTime ctime; QDateTime mtime; KIO::filesize_t size; // 0 for dirs }; class CopyJobPrivate; /** * @class KIO::CopyJob copyjob.h * * CopyJob is used to move, copy or symlink files and directories. * Don't create the job directly, but use KIO::copy(), * KIO::move(), KIO::link() and friends. * * @see KIO::copy() * @see KIO::copyAs() * @see KIO::move() * @see KIO::moveAs() * @see KIO::link() * @see KIO::linkAs() */ class KIOCORE_EXPORT CopyJob : public Job { Q_OBJECT public: /** * Defines the mode of the operation */ enum CopyMode { Copy, Move, Link }; ~CopyJob() override; /** * Returns the mode of the operation (copy, move, or link), * depending on whether KIO::copy(), KIO::move() or KIO::link() was called. */ CopyMode operationMode() const; /** * Returns the list of source URLs. * @return the list of source URLs. */ QList srcUrls() const; /** * Returns the destination URL. * @return the destination URL */ QUrl destUrl() const; /** * By default the permissions of the copied files will be those of the source files. * * But when copying "template" files to "new" files, people prefer the umask * to apply, rather than the template's permissions. * For that case, call setDefaultPermissions(true) */ void setDefaultPermissions(bool b); /** * Skip copying or moving any file when the destination already exists, * instead of the default behavior (interactive mode: showing a dialog to the user, * non-interactive mode: aborting with an error). * Initially added for a unit test. * \since 4.2 */ void setAutoSkip(bool autoSkip); /** * Rename files automatically when the destination already exists, * instead of the default behavior (interactive mode: showing a dialog to the user, * non-interactive mode: aborting with an error). * Initially added for a unit test. * \since 4.7 */ void setAutoRename(bool autoRename); /** * Reuse any directory that already exists, instead of the default behavior * (interactive mode: showing a dialog to the user, * non-interactive mode: aborting with an error). * \since 4.2 */ void setWriteIntoExistingDirectories(bool overwriteAllDirs); /** * Reimplemented for internal reasons */ bool doSuspend() override; /** * Reimplemented for internal reasons */ bool doResume() override; Q_SIGNALS: /** * Emitted when the total number of files is known. * @param job the job that emitted this signal * @param files the total number of files */ void totalFiles(KJob *job, unsigned long files); /** * Emitted when the total number of directories is known. * @param job the job that emitted this signal * @param dirs the total number of directories */ void totalDirs(KJob *job, unsigned long dirs); +#if KIOCORE_ENABLE_DEPRECATED_SINCE(5, 2) /** * Emitted when it is known which files / directories are going * to be created. Note that this may still change e.g. when * existing files with the same name are discovered. * @param job the job that emitted this signal * @param files a list of items that are about to be created. * @deprecated since 5.2 -- this signal is unused since kde 3... */ - + KIOCORE_DEPRECATED_VERSION(5, 2, "To be removed due to no known users") QT_MOC_COMPAT void aboutToCreate(KIO::Job *job, const QList &files); +#endif /** * Sends the number of processed files. * @param job the job that emitted this signal * @param files the number of processed files */ void processedFiles(KIO::Job *job, unsigned long files); /** * Sends the number of processed directories. * @param job the job that emitted this signal * @param dirs the number of processed dirs */ void processedDirs(KIO::Job *job, unsigned long dirs); /** * The job is copying a file or directory. * * Note: This signal is used for progress dialogs, it's not emitted for * every file or directory (this would be too slow), but every 200ms. * * @param job the job that emitted this signal * @param src the URL of the file or directory that is currently * being copied * @param dest the destination of the current operation */ void copying(KIO::Job *job, const QUrl &src, const QUrl &dest); /** * The job is creating a symbolic link. * * Note: This signal is used for progress dialogs, it's not emitted for * every file or directory (this would be too slow), but every 200ms. * * @param job the job that emitted this signal * @param target the URL of the file or directory that is currently * being linked * @param to the destination of the current operation */ void linking(KIO::Job *job, const QString &target, const QUrl &to); /** * The job is moving a file or directory. * * Note: This signal is used for progress dialogs, it's not emitted for * every file or directory (this would be too slow), but every 200ms. * * @param job the job that emitted this signal * @param from the URL of the file or directory that is currently * being moved * @param to the destination of the current operation */ void moving(KIO::Job *job, const QUrl &from, const QUrl &to); /** * The job is creating the directory @p dir. * * This signal is emitted for every directory being created. * * @param job the job that emitted this signal * @param dir the directory that is currently being created */ void creatingDir(KIO::Job *job, const QUrl &dir); /** * The user chose to rename @p from to @p to. * * @param job the job that emitted this signal * @param from the original name * @param to the new name */ void renamed(KIO::Job *job, const QUrl &from, const QUrl &to); /** * The job emits this signal when copying or moving a file or directory successfully finished. * This signal is mainly for the Undo feature. * If you simply want to know when a copy job is done, use result(). * * @param job the job that emitted this signal * @param from the source URL * @param to the destination URL * @param mtime the modification time of the source file, hopefully set on the destination file * too (when the kioslave supports it). * @param directory indicates whether a file or directory was successfully copied/moved. * true for a directory, false for file * @param renamed indicates that the destination URL was created using a * rename operation (i.e. fast directory moving). true if is has been renamed */ void copyingDone(KIO::Job *job, const QUrl &from, const QUrl &to, const QDateTime &mtime, bool directory, bool renamed); /** * The job is copying or moving a symbolic link, that points to target. * The new link is created in @p to. The existing one is/was in @p from. * This signal is mainly for the Undo feature. * @param job the job that emitted this signal * @param from the source URL * @param target the target * @param to the destination URL */ void copyingLinkDone(KIO::Job *job, const QUrl &from, const QString &target, const QUrl &to); protected Q_SLOTS: void slotResult(KJob *job) override; protected: CopyJob(CopyJobPrivate &dd); void emitResult(); private: Q_PRIVATE_SLOT(d_func(), void sourceStated(const KIO::UDSEntry &entry, const QUrl &sourceUrl)) Q_DECLARE_PRIVATE(CopyJob) }; /** * Copy a file or directory @p src into the destination @p dest, * which can be a file (including the final filename) or a directory * (into which @p src will be copied). * * This emulates the cp command completely. * * @param src the file or directory to copy * @param dest the destination * @param flags copy() supports HideProgressInfo and Overwrite. * Note: Overwrite has the meaning of both "write into existing directories" and * "overwrite existing files". However if "dest" exists, then src is copied * into a subdir of dest, just like "cp" does. Use copyAs if you don't want that. * * @return the job handling the operation * @see copyAs() */ KIOCORE_EXPORT CopyJob *copy(const QUrl &src, const QUrl &dest, JobFlags flags = DefaultFlags); /** * Copy a file or directory @p src into the destination @p dest, * which is the destination name in any case, even for a directory. * * As opposed to copy(), this doesn't emulate cp, but is the only * way to copy a directory, giving it a new name and getting an error * box if a directory already exists with the same name (or writing the * contents of @p src into @p dest, when using Overwrite). * * @param src the file or directory to copy * @param dest the destination * @param flags copyAs() supports HideProgressInfo and Overwrite. * Note: Overwrite has the meaning of both "write into existing directories" and * "overwrite existing files". * * * @return the job handling the operation */ KIOCORE_EXPORT CopyJob *copyAs(const QUrl &src, const QUrl &dest, JobFlags flags = DefaultFlags); /** * Copy a list of file/dirs @p src into a destination directory @p dest. * * @param src the list of files and/or directories * @param dest the destination * @param flags copy() supports HideProgressInfo and Overwrite. * Note: Overwrite has the meaning of both "write into existing directories" and * "overwrite existing files". However if "dest" exists, then src is copied * into a subdir of dest, just like "cp" does. * @return the job handling the operation */ KIOCORE_EXPORT CopyJob *copy(const QList &src, const QUrl &dest, JobFlags flags = DefaultFlags); /** * Moves a file or directory @p src to the given destination @p dest. * * @param src the file or directory to copy * @param dest the destination * @param flags move() supports HideProgressInfo and Overwrite. * Note: Overwrite has the meaning of both "write into existing directories" and * "overwrite existing files". However if "dest" exists, then src is copied * into a subdir of dest, just like "cp" does. * @return the job handling the operation * @see copy() * @see moveAs() */ KIOCORE_EXPORT CopyJob *move(const QUrl &src, const QUrl &dest, JobFlags flags = DefaultFlags); /** * Moves a file or directory @p src to the given destination @p dest. Unlike move() * this operation will not move @p src into @p dest when @p dest exists: it will * either fail, or move the contents of @p src into it if Overwrite is set. * * @param src the file or directory to copy * @param dest the destination * @param flags moveAs() supports HideProgressInfo and Overwrite. * Note: Overwrite has the meaning of both "write into existing directories" and * "overwrite existing files". * @return the job handling the operation * @see copyAs() */ KIOCORE_EXPORT CopyJob *moveAs(const QUrl &src, const QUrl &dest, JobFlags flags = DefaultFlags); /** * Moves a list of files or directories @p src to the given destination @p dest. * * @param src the list of files or directories to copy * @param dest the destination * @param flags move() supports HideProgressInfo and Overwrite. * Note: Overwrite has the meaning of both "write into existing directories" and * "overwrite existing files". However if "dest" exists, then src is copied * into a subdir of dest, just like "cp" does. * @return the job handling the operation * @see copy() */ KIOCORE_EXPORT CopyJob *move(const QList &src, const QUrl &dest, JobFlags flags = DefaultFlags); /** * Create a link. * If the protocols and hosts are the same, a Unix symlink will be created. * Otherwise, a .desktop file of Type Link and pointing to the src URL will be created. * * @param src The existing file or directory, 'target' of the link. * @param destDir Destination directory where the link will be created. * @param flags link() supports HideProgressInfo only * @return the job handling the operation */ KIOCORE_EXPORT CopyJob *link(const QUrl &src, const QUrl &destDir, JobFlags flags = DefaultFlags); /** * Create several links * If the protocols and hosts are the same, a Unix symlink will be created. * Otherwise, a .desktop file of Type Link and pointing to the src URL will be created. * * @param src The existing files or directories, 'targets' of the link. * @param destDir Destination directory where the links will be created. * @param flags link() supports HideProgressInfo only * @return the job handling the operation * @see link() */ KIOCORE_EXPORT CopyJob *link(const QList &src, const QUrl &destDir, JobFlags flags = DefaultFlags); /** * Create a link. Unlike link() this operation will fail when @p dest is an existing * directory rather than the final name for the link. * If the protocols and hosts are the same, a Unix symlink will be created. * Otherwise, a .desktop file of Type Link and pointing to the src URL will be created. * * @param src The existing file or directory, 'target' of the link. * @param dest Destination (i.e. the final symlink) * @param flags linkAs() supports HideProgressInfo only * @return the job handling the operation * @see link () * @see copyAs() */ KIOCORE_EXPORT CopyJob *linkAs(const QUrl &src, const QUrl &dest, JobFlags flags = DefaultFlags); /** * Trash a file or directory. * This is currently only supported for local files and directories. * Use QUrl::fromLocalFile to create a URL from a local file path. * * @param src file to delete * @param flags trash() supports HideProgressInfo only * @return the job handling the operation */ KIOCORE_EXPORT CopyJob *trash(const QUrl &src, JobFlags flags = DefaultFlags); /** * Trash a list of files or directories. * This is currently only supported for local files and directories. * * @param src the files to delete * @param flags trash() supports HideProgressInfo only * @return the job handling the operation */ KIOCORE_EXPORT CopyJob *trash(const QList &src, JobFlags flags = DefaultFlags); } #endif diff --git a/src/core/global.cpp b/src/core/global.cpp index abaa200c..fb104729 100644 --- a/src/core/global.cpp +++ b/src/core/global.cpp @@ -1,302 +1,298 @@ /* This file is part of the KDE libraries Copyright (C) 2000 David Faure This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License version 2 as published by the Free Software Foundation. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "global.h" #include "kioglobal_p.h" #include "faviconscache_p.h" #include #include #include #include #include #include #include #include #include #include #include "kiocoredebug.h" KFormat::BinaryUnitDialect _k_loadBinaryDialect(); Q_GLOBAL_STATIC_WITH_ARGS(KFormat::BinaryUnitDialect, _k_defaultBinaryDialect, (_k_loadBinaryDialect())) KFormat::BinaryUnitDialect _k_loadBinaryDialect() { KConfigGroup mainGroup(KSharedConfig::openConfig(), "Locale"); KFormat::BinaryUnitDialect dialect(KFormat::BinaryUnitDialect(mainGroup.readEntry("BinaryUnitDialect", int(KFormat::DefaultBinaryDialect)))); dialect = static_cast(mainGroup.readEntry("BinaryUnitDialect", int(dialect))); // Error checking if (dialect <= KFormat::DefaultBinaryDialect || dialect > KFormat::LastBinaryDialect) { dialect = KFormat::IECBinaryDialect; } return dialect; } KIOCORE_EXPORT QString KIO::convertSize(KIO::filesize_t fileSize) { const KFormat::BinaryUnitDialect dialect = *_k_defaultBinaryDialect(); return KFormat().formatByteSize(fileSize, 1, dialect); } KIOCORE_EXPORT QString KIO::convertSizeFromKiB(KIO::filesize_t kibSize) { return convertSize(kibSize * 1024); } KIOCORE_EXPORT QString KIO::number(KIO::filesize_t size) { char charbuf[256]; sprintf(charbuf, "%lld", size); return QLatin1String(charbuf); } KIOCORE_EXPORT unsigned int KIO::calculateRemainingSeconds(KIO::filesize_t totalSize, KIO::filesize_t processedSize, KIO::filesize_t speed) { if ((speed != 0) && (totalSize != 0)) { return (totalSize - processedSize) / speed; } else { return 0; } } KIOCORE_EXPORT QString KIO::convertSeconds(unsigned int seconds) { unsigned int days = seconds / 86400; unsigned int hours = (seconds - (days * 86400)) / 3600; unsigned int mins = (seconds - (days * 86400) - (hours * 3600)) / 60; seconds = (seconds - (days * 86400) - (hours * 3600) - (mins * 60)); const QTime time(hours, mins, seconds); const QString timeStr(time.toString(QStringLiteral("hh:mm:ss"))); if (days > 0) { return i18np("1 day %2", "%1 days %2", days, timeStr); } else { return timeStr; } } -#ifndef KIOCORE_NO_DEPRECATED KIOCORE_EXPORT QTime KIO::calculateRemaining(KIO::filesize_t totalSize, KIO::filesize_t processedSize, KIO::filesize_t speed) { QTime remainingTime; if (speed != 0) { KIO::filesize_t secs; if (totalSize == 0) { secs = 0; } else { secs = (totalSize - processedSize) / speed; } if (secs >= (24 * 60 * 60)) { // Limit to 23:59:59 secs = (24 * 60 * 60) - 1; } int hr = secs / (60 * 60); int mn = (secs - hr * 60 * 60) / 60; int sc = (secs - hr * 60 * 60 - mn * 60); remainingTime.setHMS(hr, mn, sc); } return remainingTime; } -#endif KIOCORE_EXPORT QString KIO::itemsSummaryString(uint items, uint files, uint dirs, KIO::filesize_t size, bool showSize) { if (files == 0 && dirs == 0 && items == 0) { return i18np("%1 Item", "%1 Items", 0); } QString summary; const QString foldersText = i18np("1 Folder", "%1 Folders", dirs); const QString filesText = i18np("1 File", "%1 Files", files); if (files > 0 && dirs > 0) { summary = showSize ? i18nc("folders, files (size)", "%1, %2 (%3)", foldersText, filesText, KIO::convertSize(size)) : i18nc("folders, files", "%1, %2", foldersText, filesText); } else if (files > 0) { summary = showSize ? i18nc("files (size)", "%1 (%2)", filesText, KIO::convertSize(size)) : filesText; } else if (dirs > 0) { summary = foldersText; } if (items > dirs + files) { const QString itemsText = i18np("%1 Item", "%1 Items", items); summary = summary.isEmpty() ? itemsText : i18nc("items: folders, files (size)", "%1: %2", itemsText, summary); } return summary; } KIOCORE_EXPORT QString KIO::encodeFileName(const QString &_str) { QString str(_str); str.replace(QLatin1Char('/'), QChar(0x2044)); // "Fraction slash" return str; } KIOCORE_EXPORT QString KIO::decodeFileName(const QString &_str) { // Nothing to decode. "Fraction slash" is fine in filenames. return _str; } /*************************************************************** * * Utility functions * ***************************************************************/ KIO::CacheControl KIO::parseCacheControl(const QString &cacheControl) { QString tmp = cacheControl.toLower(); if (tmp == QLatin1String("cacheonly")) { return KIO::CC_CacheOnly; } if (tmp == QLatin1String("cache")) { return KIO::CC_Cache; } if (tmp == QLatin1String("verify")) { return KIO::CC_Verify; } if (tmp == QLatin1String("refresh")) { return KIO::CC_Refresh; } if (tmp == QLatin1String("reload")) { return KIO::CC_Reload; } qCDebug(KIO_CORE) << "unrecognized Cache control option:" << cacheControl; return KIO::CC_Verify; } QString KIO::getCacheControlString(KIO::CacheControl cacheControl) { if (cacheControl == KIO::CC_CacheOnly) { return QStringLiteral("CacheOnly"); } if (cacheControl == KIO::CC_Cache) { return QStringLiteral("Cache"); } if (cacheControl == KIO::CC_Verify) { return QStringLiteral("Verify"); } if (cacheControl == KIO::CC_Refresh) { return QStringLiteral("Refresh"); } if (cacheControl == KIO::CC_Reload) { return QStringLiteral("Reload"); } qCDebug(KIO_CORE) << "unrecognized Cache control enum value:" << cacheControl; return QString(); } QString KIO::favIconForUrl(const QUrl &url) { if (url.isLocalFile() || !url.scheme().startsWith(QLatin1String("http"))) { return QString(); } return FavIconsCache::instance()->iconForUrl(url); } QString KIO::iconNameForUrl(const QUrl &url) { const QLatin1String unknown("unknown"); if (url.scheme().isEmpty()) { // empty URL or relative URL (e.g. '~') return unknown; } QMimeDatabase db; const QMimeType mt = db.mimeTypeForUrl(url); const QString mimeTypeIcon = mt.iconName(); QString i = mimeTypeIcon; if (url.isLocalFile()) { // Check to see whether it's an xdg location (e.g. Pictures folder) if (mt.inherits(QStringLiteral("inode/directory"))) { i = KIOPrivate::iconForStandardPath(url.toLocalFile()); } // Let KFileItem::iconName handle things for us if (i == unknown || i.isEmpty() || mt.isDefault()) { const KFileItem item(url, mt.name()); i = item.iconName(); } } else { // It's non-local and maybe on a slow filesystem // Look for a favicon if (url.scheme().startsWith(QLatin1String("http"))) { i = favIconForUrl(url); } // Then handle the trash else if (url.scheme() == QLatin1String("trash") && url.path().length() <= 1) { KConfig trashConfig(QStringLiteral("trashrc"), KConfig::SimpleConfig); if (trashConfig.group("Status").readEntry("Empty", true)) { i = QStringLiteral("user-trash"); } else { i = QStringLiteral("user-trash-full"); } } if (i.isEmpty()) { i = KProtocolInfo::icon(url.scheme()); } // root of protocol: if we found nothing, revert to mimeTypeIcon (which is usually "folder") if (url.path().length() <= 1 && (i == unknown || i.isEmpty())) { i = mimeTypeIcon; } } return !i.isEmpty() ? i : unknown; } QUrl KIO::upUrl(const QUrl &url) { if (!url.isValid() || url.isRelative()) { return QUrl(); } QUrl u(url); if (url.hasQuery()) { u.setQuery(QString()); return u; } if (url.hasFragment()) { u.setFragment(QString()); } u = u.adjusted(QUrl::StripTrailingSlash); /// don't combine with the line below return u.adjusted(QUrl::RemoveFilename); } -#ifndef KIOCORE_NO_DEPRECATED QString KIO::suggestName(const QUrl &baseURL, const QString &oldName) { return KFileUtils::suggestName(baseURL, oldName); } -#endif diff --git a/src/core/global.h b/src/core/global.h index 5a020ef2..cb79d00d 100644 --- a/src/core/global.h +++ b/src/core/global.h @@ -1,348 +1,382 @@ /* This file is part of the KDE libraries Copyright (C) 2000-2005 David Faure This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License version 2 as published by the Free Software Foundation. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef KIO_GLOBAL_H #define KIO_GLOBAL_H #include "kiocore_export.h" #include #include // for QFile::Permissions #include #include "metadata.h" // for source compat #include "jobtracker.h" // for source compat class QUrl; class QTime; #if defined(Q_OS_WIN) && defined(Q_CC_MSVC) // on windows ssize_t is not defined, only SSIZE_T exists #include typedef SSIZE_T ssize_t; #endif /** * @short A namespace for KIO globals * */ namespace KIO { /// 64-bit file offset typedef qlonglong fileoffset_t; /// 64-bit file size typedef qulonglong filesize_t; /** * Converts @p size from bytes to the string representation. * * @param size size in bytes * @return converted size as a string - e.g. 123.4 KiB , 12.0 MiB */ KIOCORE_EXPORT QString convertSize(KIO::filesize_t size); /** * Converts a size to a string representation * Not unlike QString::number(...) * * @param size size in bytes * @return converted size as a string - e.g. 123456789 */ KIOCORE_EXPORT QString number(KIO::filesize_t size); /** * Converts size from kibi-bytes (2^10) to the string representation. * * @param kibSize size in kibi-bytes (2^10) * @return converted size as a string - e.g. 123.4 KiB , 12.0 MiB */ KIOCORE_EXPORT QString convertSizeFromKiB(KIO::filesize_t kibSize); /** * Calculates remaining time in seconds from total size, processed size and speed. * * @param totalSize total size in bytes * @param processedSize processed size in bytes * @param speed speed in bytes per second * @return calculated remaining time in seconds */ KIOCORE_EXPORT unsigned int calculateRemainingSeconds(KIO::filesize_t totalSize, KIO::filesize_t processedSize, KIO::filesize_t speed); /** * Convert @p seconds to a string representing number of days, hours, minutes and seconds * * @param seconds number of seconds to convert * @return string representation in a locale depending format */ KIOCORE_EXPORT QString convertSeconds(unsigned int seconds); +#if KIOCORE_ENABLE_DEPRECATED_SINCE(3, 4) /** * Calculates remaining time from total size, processed size and speed. - * Warning: As QTime is limited to 23:59:59, use calculateRemainingSeconds() instead * * @param totalSize total size in bytes * @param processedSize processed size in bytes * @param speed speed in bytes per second * @return calculated remaining time + * @deprecated Since 3.4, use calculateRemainingSeconds() instead, as QTime is limited to 23:59:59 */ -#ifndef KIOCORE_NO_DEPRECATED -KIOCORE_DEPRECATED_EXPORT QTime calculateRemaining(KIO::filesize_t totalSize, KIO::filesize_t processedSize, KIO::filesize_t speed); +KIOCORE_DEPRECATED_VERSION(3, 4, "Use KIO::calculateRemainingSeconds(KIO::filesize_t, KIO::filesize_t, KIO::filesize_t") +KIOCORE_EXPORT QTime calculateRemaining(KIO::filesize_t totalSize, KIO::filesize_t processedSize, KIO::filesize_t speed); #endif /** * Helper for showing information about a set of files and directories * @param items the number of items (= @p files + @p dirs + number of symlinks :) * @param files the number of files * @param dirs the number of dirs * @param size the sum of the size of the @p files * @param showSize whether to show the size in the result * @return the summary string */ KIOCORE_EXPORT QString itemsSummaryString(uint items, uint files, uint dirs, KIO::filesize_t size, bool showSize); /** * Encodes (from the text displayed to the real filename) * This translates '/' into a "unicode fraction slash", QChar(0x2044). * Used by KIO::link, for instance. * @param str the file name to encode * @return the encoded file name */ KIOCORE_EXPORT QString encodeFileName(const QString &str); /** * Decodes (from the filename to the text displayed) * This doesn't do anything anymore, it used to do the opposite of encodeFileName * when encodeFileName was using %2F for '/'. * @param str the file name to decode * @return the decoded file name */ KIOCORE_EXPORT QString decodeFileName(const QString &str); +#if KIOCORE_ENABLE_DEPRECATED_SINCE(5, 61) /** * Given a directory path and a filename (which usually exists already), * this function returns a suggested name for a file that doesn't exist * in that directory. The existence is only checked for local urls though. * The suggested file name is of the form "foo 1", "foo 2" etc. * @since 5.0 * @deprecated since 5.61, use KFileUtils::suggestName() from KCoreAddons */ -#ifndef KIOCORE_NO_DEPRECATED -KIOCORE_DEPRECATED_EXPORT QString suggestName(const QUrl &baseURL, const QString &oldName); +KIOCORE_DEPRECATED_VERSION(5, 61, "Use KFileUtils::suggestName(const QUrl &, const QString &) from KCoreAddons") +KIOCORE_EXPORT QString suggestName(const QUrl &baseURL, const QString &oldName); #endif /** * Error codes that can be emitted by KIO. */ enum Error { ERR_CANNOT_OPEN_FOR_READING = KJob::UserDefinedError + 1, ERR_CANNOT_OPEN_FOR_WRITING = KJob::UserDefinedError + 2, ERR_CANNOT_LAUNCH_PROCESS = KJob::UserDefinedError + 3, ERR_INTERNAL = KJob::UserDefinedError + 4, ERR_MALFORMED_URL = KJob::UserDefinedError + 5, ERR_UNSUPPORTED_PROTOCOL = KJob::UserDefinedError + 6, ERR_NO_SOURCE_PROTOCOL = KJob::UserDefinedError + 7, ERR_UNSUPPORTED_ACTION = KJob::UserDefinedError + 8, ERR_IS_DIRECTORY = KJob::UserDefinedError + 9, ///< ... where a file was expected ERR_IS_FILE = KJob::UserDefinedError + 10, ///< ... where a directory was expected (e.g. listing) ERR_DOES_NOT_EXIST = KJob::UserDefinedError + 11, ERR_FILE_ALREADY_EXIST = KJob::UserDefinedError + 12, ERR_DIR_ALREADY_EXIST = KJob::UserDefinedError + 13, ERR_UNKNOWN_HOST = KJob::UserDefinedError + 14, ERR_ACCESS_DENIED = KJob::UserDefinedError + 15, ERR_WRITE_ACCESS_DENIED = KJob::UserDefinedError + 16, ERR_CANNOT_ENTER_DIRECTORY = KJob::UserDefinedError + 17, ERR_PROTOCOL_IS_NOT_A_FILESYSTEM = KJob::UserDefinedError + 18, ERR_CYCLIC_LINK = KJob::UserDefinedError + 19, ERR_USER_CANCELED = KJob::KilledJobError, ERR_CYCLIC_COPY = KJob::UserDefinedError + 21, - ERR_COULD_NOT_CREATE_SOCKET = KJob::UserDefinedError + 22, ///< @deprecated +#if KIOCORE_ENABLE_DEPRECATED_SINCE(5, 0) + ERR_COULD_NOT_CREATE_SOCKET = KJob::UserDefinedError + 22, ///< @deprecated Since 5.0, use ERR_CANNOT_CREATE_SOCKET +#endif ERR_CANNOT_CREATE_SOCKET = KJob::UserDefinedError + 22, - ERR_COULD_NOT_CONNECT = KJob::UserDefinedError + 23, ///< @deprecated +#if KIOCORE_ENABLE_DEPRECATED_SINCE(5, 0) + ERR_COULD_NOT_CONNECT = KJob::UserDefinedError + 23, ///< @deprecated Since 5.0, use ERR_CANNOT_CONNECT +#endif ERR_CANNOT_CONNECT = KJob::UserDefinedError + 23, ERR_CONNECTION_BROKEN = KJob::UserDefinedError + 24, ERR_NOT_FILTER_PROTOCOL = KJob::UserDefinedError + 25, - ERR_COULD_NOT_MOUNT = KJob::UserDefinedError + 26, ///< @deprecated +#if KIOCORE_ENABLE_DEPRECATED_SINCE(5, 0) + ERR_COULD_NOT_MOUNT = KJob::UserDefinedError + 26, ///< @deprecated Since 5.0, use ERR_CANNOT_MOUNT +#endif ERR_CANNOT_MOUNT = KJob::UserDefinedError + 26, - ERR_COULD_NOT_UNMOUNT = KJob::UserDefinedError + 27, ///< @deprecated +#if KIOCORE_ENABLE_DEPRECATED_SINCE(5, 0) + ERR_COULD_NOT_UNMOUNT = KJob::UserDefinedError + 27, ///< @deprecated Since 5.0, use ERR_CANNOT_UNMOUNT +#endif ERR_CANNOT_UNMOUNT = KJob::UserDefinedError + 27, - ERR_COULD_NOT_READ = KJob::UserDefinedError + 28, ///< @deprecated +#if KIOCORE_ENABLE_DEPRECATED_SINCE(5, 0) + ERR_COULD_NOT_READ = KJob::UserDefinedError + 28, ///< @deprecated Since 5.0, use ERR_CANNOT_READ +#endif ERR_CANNOT_READ = KJob::UserDefinedError + 28, - ERR_COULD_NOT_WRITE = KJob::UserDefinedError + 29, ///< @deprecated +#if KIOCORE_ENABLE_DEPRECATED_SINCE(5, 0) + ERR_COULD_NOT_WRITE = KJob::UserDefinedError + 29, ///< @deprecated Since 5.0, use ERR_CANNOT_WRITE +#endif ERR_CANNOT_WRITE = KJob::UserDefinedError + 29, - ERR_COULD_NOT_BIND = KJob::UserDefinedError + 30, ///< @deprecated +#if KIOCORE_ENABLE_DEPRECATED_SINCE(5, 0) + ERR_COULD_NOT_BIND = KJob::UserDefinedError + 30, ///< @deprecated Since 5.0, use ERR_CANNOT_BIND +#endif ERR_CANNOT_BIND = KJob::UserDefinedError + 30, - ERR_COULD_NOT_LISTEN = KJob::UserDefinedError + 31, ///< @deprecated +#if KIOCORE_ENABLE_DEPRECATED_SINCE(5, 0) + ERR_COULD_NOT_LISTEN = KJob::UserDefinedError + 31, ///< @deprecated Since 5.0, use ERR_CANNOT_LISTEN +#endif ERR_CANNOT_LISTEN = KJob::UserDefinedError + 31, - ERR_COULD_NOT_ACCEPT = KJob::UserDefinedError + 32, ///< @deprecated +#if KIOCORE_ENABLE_DEPRECATED_SINCE(5, 0) + ERR_COULD_NOT_ACCEPT = KJob::UserDefinedError + 32, ///< @deprecated Since 5.0, use ERR_CANNOT_ACCEPT +#endif ERR_CANNOT_ACCEPT = KJob::UserDefinedError + 32, - ERR_COULD_NOT_LOGIN = KJob::UserDefinedError + 33, ///< @deprecated +#if KIOCORE_ENABLE_DEPRECATED_SINCE(5, 0) + ERR_COULD_NOT_LOGIN = KJob::UserDefinedError + 33, ///< @deprecated Since 5.0, use ERR_CANNOT_LOGIN +#endif ERR_CANNOT_LOGIN = KJob::UserDefinedError + 33, - ERR_COULD_NOT_STAT = KJob::UserDefinedError + 34, ///< @deprecated +#if KIOCORE_ENABLE_DEPRECATED_SINCE(5, 0) + ERR_COULD_NOT_STAT = KJob::UserDefinedError + 34, ///< @deprecated Since 5.0, use ERR_CANNOT_STAT +#endif ERR_CANNOT_STAT = KJob::UserDefinedError + 34, - ERR_COULD_NOT_CLOSEDIR = KJob::UserDefinedError + 35, ///< @deprecated +#if KIOCORE_ENABLE_DEPRECATED_SINCE(5, 0) + ERR_COULD_NOT_CLOSEDIR = KJob::UserDefinedError + 35, ///< @deprecated Since 5.0, use ERR_CANNOT_CLOSEDIR +#endif ERR_CANNOT_CLOSEDIR = KJob::UserDefinedError + 35, - ERR_COULD_NOT_MKDIR = KJob::UserDefinedError + 37, ///< @deprecated +#if KIOCORE_ENABLE_DEPRECATED_SINCE(5, 0) + ERR_COULD_NOT_MKDIR = KJob::UserDefinedError + 37, ///< @deprecated Since 5.0, use ERR_CANNOT_MKDIR +#endif ERR_CANNOT_MKDIR = KJob::UserDefinedError + 37, - ERR_COULD_NOT_RMDIR = KJob::UserDefinedError + 38, ///< @deprecated +#if KIOCORE_ENABLE_DEPRECATED_SINCE(5, 0) + ERR_COULD_NOT_RMDIR = KJob::UserDefinedError + 38, ///< @deprecated Since 5.0, use ERR_CANNOT_RMDIR +#endif ERR_CANNOT_RMDIR = KJob::UserDefinedError + 38, ERR_CANNOT_RESUME = KJob::UserDefinedError + 39, ERR_CANNOT_RENAME = KJob::UserDefinedError + 40, ERR_CANNOT_CHMOD = KJob::UserDefinedError + 41, ERR_CANNOT_DELETE = KJob::UserDefinedError + 42, // The text argument is the protocol that the dead slave supported. // This means for example: file, ftp, http, ... ERR_SLAVE_DIED = KJob::UserDefinedError + 43, ERR_OUT_OF_MEMORY = KJob::UserDefinedError + 44, ERR_UNKNOWN_PROXY_HOST = KJob::UserDefinedError + 45, - ERR_COULD_NOT_AUTHENTICATE = KJob::UserDefinedError + 46, ///< @deprecated +#if KIOCORE_ENABLE_DEPRECATED_SINCE(5, 0) + ERR_COULD_NOT_AUTHENTICATE = KJob::UserDefinedError + 46, ///< @deprecated Since 5.0, use ERR_CANNOT_AUTHENTICATE +#endif ERR_CANNOT_AUTHENTICATE = KJob::UserDefinedError + 46, ERR_ABORTED = KJob::UserDefinedError + 47, ///< Action got aborted from application side ERR_INTERNAL_SERVER = KJob::UserDefinedError + 48, ERR_SERVER_TIMEOUT = KJob::UserDefinedError + 49, ERR_SERVICE_NOT_AVAILABLE = KJob::UserDefinedError + 50, ERR_UNKNOWN = KJob::UserDefinedError + 51, // (was a warning) ERR_CHECKSUM_MISMATCH = 52, ERR_UNKNOWN_INTERRUPT = KJob::UserDefinedError + 53, ERR_CANNOT_DELETE_ORIGINAL = KJob::UserDefinedError + 54, ERR_CANNOT_DELETE_PARTIAL = KJob::UserDefinedError + 55, ERR_CANNOT_RENAME_ORIGINAL = KJob::UserDefinedError + 56, ERR_CANNOT_RENAME_PARTIAL = KJob::UserDefinedError + 57, ERR_NEED_PASSWD = KJob::UserDefinedError + 58, ERR_CANNOT_SYMLINK = KJob::UserDefinedError + 59, ERR_NO_CONTENT = KJob::UserDefinedError + 60, ///< Action succeeded but no content will follow. ERR_DISK_FULL = KJob::UserDefinedError + 61, ERR_IDENTICAL_FILES = KJob::UserDefinedError + 62, ///< src==dest when moving/copying ERR_SLAVE_DEFINED = KJob::UserDefinedError + 63, ///< for slave specified errors that can be ///< rich text. Email links will be handled ///< by the standard email app and all hrefs ///< will be handled by the standard browser. ///< 2000-2009 David Faure This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef KIO_JOB_BASE_H #define KIO_JOB_BASE_H #include #include namespace KIO { class JobUiDelegateExtension; class JobPrivate; /** * @class KIO::Job job_base.h * * The base class for all jobs. * For all jobs created in an application, the code looks like * * \code * KIO::Job* job = KIO::someoperation(some parameters); * connect(job, &KJob::result, this, &MyClass::slotResult); * \endcode * (other connects, specific to the job) * * And slotResult is usually at least: * * \code * void MyClass::slotResult(KJob *job) * { * if (job->error()) { * job->uiDelegate()->showErrorMessage(); * } * } * \endcode * @see KIO::Scheduler */ class KIOCORE_EXPORT Job : public KCompositeJob { Q_OBJECT protected: Job(); Job(JobPrivate &dd); public: virtual ~Job(); void start() override {} // Since KIO autostarts its jobs +#if KIOCORE_ENABLE_DEPRECATED_SINCE(5, 0) /** * Retrieves the UI delegate of this job. * - * @deprecated since 5.0, can now be replaced with uiDelegate() - * * @return the delegate used by the job to communicate with the UI + * + * @deprecated since 5.0, can now be replaced with uiDelegate() */ -#ifndef KIOCORE_NO_DEPRECATED - KIOCORE_DEPRECATED KJobUiDelegate *ui() const; + KIOCORE_DEPRECATED_VERSION(5, 0, "Use KJob::uiDelegate()") + KJobUiDelegate *ui() const; #endif /** * Retrieves the UI delegate extension used by this job. * @since 5.0 */ JobUiDelegateExtension *uiDelegateExtension() const; /** * Sets the UI delegate extension to be used by this job. * The default UI delegate extension is KIO::defaultJobUiDelegateExtension() */ void setUiDelegateExtension(JobUiDelegateExtension *extension); protected: /** * Abort this job. * This kills all subjobs and deletes the job. * */ bool doKill() override; /** * Suspend this job * @see resume */ bool doSuspend() override; /** * Resume this job * @see suspend */ bool doResume() override; public: /** * Converts an error code and a non-i18n error message into an * error message in the current language. The low level (non-i18n) * error message (usually a url) is put into the translated error * message using %1. * * Example for errid == ERR_CANNOT_OPEN_FOR_READING: * \code * i18n( "Could not read\n%1" ).arg( errortext ); * \endcode * Use this to display the error yourself, but for a dialog box * use uiDelegate()->showErrorMessage(). Do not call it if error() * is not 0. * @return the error message and if there is no error, a message * telling the user that the app is broken, so check with * error() whether there is an error */ QString errorString() const override; /** * Converts an error code and a non-i18n error message into i18n * strings suitable for presentation in a detailed error message box. * * @param reqUrl the request URL that generated this error message * @param method the method that generated this error message * (unimplemented) * @return the following strings: caption, error + description, * causes+solutions */ QStringList detailedErrorStrings(const QUrl *reqUrl = nullptr, int method = -1) const; /** * Set the parent Job. * One example use of this is when FileCopyJob calls RenameDialog::open, * it must pass the correct progress ID of the parent CopyJob * (to hide the progress dialog). * You can set the parent job only once. By default a job does not * have a parent job. * @param parentJob the new parent job */ void setParentJob(Job *parentJob); /** * Returns the parent job, if there is one. * @return the parent job, or @c nullptr if there is none * @see setParentJob */ Job *parentJob() const; /** * Set meta data to be sent to the slave, replacing existing * meta data. * @param metaData the meta data to set * @see addMetaData() * @see mergeMetaData() */ void setMetaData(const KIO::MetaData &metaData); /** * Add key/value pair to the meta data that is sent to the slave. * @param key the key of the meta data * @param value the value of the meta data * @see setMetaData() * @see mergeMetaData() */ void addMetaData(const QString &key, const QString &value); /** * Add key/value pairs to the meta data that is sent to the slave. * If a certain key already existed, it will be overridden. * @param values the meta data to add * @see setMetaData() * @see mergeMetaData() */ void addMetaData(const QMap &values); /** * Add key/value pairs to the meta data that is sent to the slave. * If a certain key already existed, it will remain unchanged. * @param values the meta data to merge * @see setMetaData() * @see addMetaData() */ void mergeMetaData(const QMap &values); /** * @internal. For the scheduler. Do not use. */ MetaData outgoingMetaData() const; /** * Get meta data received from the slave. * (Valid when first data is received and/or slave is finished) * @return the job's meta data */ MetaData metaData() const; /** * Query meta data received from the slave. * (Valid when first data is received and/or slave is finished) * @param key the key of the meta data to retrieve * @return the value of the meta data, or QString() if the * @p key does not exist */ QString queryMetaData(const QString &key); protected: Q_SIGNALS: +#if KIOCORE_ENABLE_DEPRECATED_SINCE(5, 0) /** - * @deprecated. Don't use ! * Emitted when the job is canceled. * Signal result() is emitted as well, and error() is, * in this case, ERR_USER_CANCELED. * @param job the job that emitted this signal + * @deprecated Since 5.0. Don't use ! */ -#ifndef KIOCORE_NO_DEPRECATED - KIOCORE_DEPRECATED void canceled(KJob *job); + KIOCORE_DEPRECATED_VERSION(5, 0, "Do not use") + void canceled(KJob *job); #endif /** * Emitted when the slave successfully connected to the host. * There is no guarantee the slave will send this, and this is * currently unused (in the applications). * @param job the job that emitted this signal */ void connected(KIO::Job *job); protected: /** * Add a job that has to be finished before a result * is emitted. This has obviously to be called before * the finish signal is emitted by the slave. * * @param job the subjob to add */ bool addSubjob(KJob *job) override; /** * Mark a sub job as being done. * * Note that this does not terminate the parent job, even if @p job * is the last subjob. emitResult must be called to indicate that * the job is complete. * * @param job the subjob to remove */ bool removeSubjob(KJob *job) override; protected: JobPrivate *const d_ptr; private: Q_DECLARE_PRIVATE(Job) }; /** * Flags for the job properties. * Not all flags are supported in all cases. Please see documentation of * the calling function! */ enum JobFlag { /** * Show the progress info GUI, no Resume and no Overwrite */ DefaultFlags = 0, /** * Hide progress information dialog, i.e. don't show a GUI. */ HideProgressInfo = 1, /** * When set, automatically append to the destination file if it exists already. * WARNING: this is NOT the builtin support for offering the user to resume a previous * partial download. The Resume option is much less used, it allows to append * to an existing file. * This is used by KIO::put(), KIO::file_copy(), KIO::file_move(). */ Resume = 2, /** * When set, automatically overwrite the destination if it exists already. * This is used by KIO::rename(), KIO::put(), KIO::file_copy(), KIO::file_move(), KIO::symlink(). * Otherwise the operation will fail with ERR_FILE_ALREADY_EXIST or ERR_DIR_ALREADY_EXIST. */ Overwrite = 4, /** * When set, notifies the slave that application/job does not want privilege execution. * So in case of failure due to insufficient privileges show an error without attempting * to run the operation as root first. * * @since 5.43 */ NoPrivilegeExecution = 8, }; Q_DECLARE_FLAGS(JobFlags, JobFlag) Q_DECLARE_OPERATORS_FOR_FLAGS(JobFlags) enum LoadType { Reload, NoReload }; } #endif diff --git a/src/core/jobuidelegateextension.h b/src/core/jobuidelegateextension.h index 28c7ee11..47e0f1b6 100644 --- a/src/core/jobuidelegateextension.h +++ b/src/core/jobuidelegateextension.h @@ -1,285 +1,286 @@ /* This file is part of the KDE libraries Copyright (C) 2000 Stephan Kulow Copyright (C) 2000-2013 David Faure Copyright (C) 2006 Kevin Ottens This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef KIO_JOBUIDELEGATEEXTENSION_H #define KIO_JOBUIDELEGATEEXTENSION_H #include "kiocore_export.h" #include #include class KJob; namespace KIO { class Job; class ClipboardUpdater; /** * @since 5.0 */ enum RenameDialog_Option { RenameDialog_Overwrite = 1, ///< We have an existing destination, show details about it and offer to overwrite it. RenameDialog_OverwriteItself = 2, ///< Warn that the current operation would overwrite a file with itself, which is not allowed. RenameDialog_Skip = 4, ///< Offer a "Skip" button, to skip other files too. Requires RenameDialog_MultipleItems. RenameDialog_MultipleItems = 8, ///< Set if the current operation concerns multiple files, so it makes sense to offer buttons that apply the user's choice to all files/folders. RenameDialog_Resume = 16, ///< Offer a "Resume" button (plus "Resume All" if RenameDialog_MultipleItems). RenameDialog_NoRename = 64, ///< Don't offer a "Rename" button. RenameDialog_IsDirectory = 128 ///< The destination is a directory, the dialog updates labels and tooltips accordingly. }; Q_DECLARE_FLAGS(RenameDialog_Options, RenameDialog_Option) // For compat -#ifndef KIOCORE_NO_DEPRECATED +#if KIOCORE_ENABLE_DEPRECATED_SINCE(5, 0) /** * @deprecated since 5.0, use the RenameDialog_Option enum values */ enum { M_OVERWRITE = RenameDialog_Overwrite, M_OVERWRITE_ITSELF = RenameDialog_OverwriteItself, M_SKIP = RenameDialog_Skip, M_MULTI = RenameDialog_MultipleItems, M_RESUME = RenameDialog_Resume, M_NORENAME = RenameDialog_NoRename, M_ISDIR = RenameDialog_IsDirectory }; /** * @deprecated since 5.0, use RenameDialog_Options */ -#ifndef KIOCORE_NO_DEPRECATED -KIOCORE_DEPRECATED typedef RenameDialog_Options RenameDialog_Mode; -#endif +KIOCORE_DEPRECATED_VERSION(5, 0, "Use KIO::RenameDialog_Options") +typedef RenameDialog_Options RenameDialog_Mode; #endif /** * SkipDialog_MultipleItems: Set if the current operation concerns multiple files, so it makes sense * to offer buttons that apply the user's choice to all files/folders. * @since 5.0 */ enum SkipDialog_Option { SkipDialog_MultipleItems = 8 }; Q_DECLARE_FLAGS(SkipDialog_Options, SkipDialog_Option) /** * The result of a rename or skip dialog */ enum RenameDialog_Result { Result_Cancel = 0, Result_Rename = 1, Result_Skip = 2, Result_AutoSkip = 3, Result_Overwrite = 4, Result_OverwriteAll = 5, Result_Resume = 6, Result_ResumeAll = 7, Result_AutoRename = 8, Result_Retry = 9, // @deprecated since 5.0, use the RenameDialog_Option enum values +#if KIOCORE_ENABLE_DEPRECATED_SINCE(5, 0) R_CANCEL = Result_Cancel, R_RENAME = Result_Rename, R_SKIP = Result_Skip, R_AUTO_SKIP = Result_AutoSkip, R_OVERWRITE = Result_Overwrite, R_OVERWRITE_ALL = Result_OverwriteAll, R_RESUME = Result_Resume, R_RESUME_ALL = Result_ResumeAll, R_AUTO_RENAME = Result_AutoRename, R_RETRY = Result_Retry, S_CANCEL = Result_Cancel, S_SKIP = Result_Skip, S_AUTO_SKIP = Result_AutoSkip, S_RETRY = Result_Retry +#endif }; typedef RenameDialog_Result SkipDialog_Result; /** * @class KIO::JobUiDelegateExtension jobuidelegateextension.h * * An abstract class defining interaction with users from KIO jobs: * \li asking what to do in case of a conflict while copying/moving files or directories * \li asking what to do in case of an error while copying/moving files or directories * \li asking for confirmation before deleting files or directories * \li popping up message boxes when the slave requests it * @since 5.0 */ class KIOCORE_EXPORT JobUiDelegateExtension { protected: /** * Constructor */ JobUiDelegateExtension(); /** * Destructor */ virtual ~JobUiDelegateExtension(); public: /** * \relates KIO::RenameDialog * Construct a modal, parent-less "rename" dialog, and return * a result code, as well as the new dest. Much easier to use than the * class RenameDialog directly. * * @param caption the caption for the dialog box * @param src the URL of the file/dir we're trying to copy, as it's part of the text message * @param dest the URL of the destination file/dir, i.e. the one that already exists * @param options parameters for the dialog (which buttons to show...) * @param newDest the new destination path, valid if R_RENAME was returned. * @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 * @return the result */ virtual KIO::RenameDialog_Result askFileRename(KJob *job, const QString &caption, const QUrl &src, const QUrl &dest, KIO::RenameDialog_Options options, QString &newDest, KIO::filesize_t sizeSrc = KIO::filesize_t(-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()) = 0; /** * @internal * See skipdialog.h */ virtual KIO::SkipDialog_Result askSkip(KJob *job, KIO::SkipDialog_Options options, const QString &error_text) = 0; /** * The type of deletion: real deletion, moving the files to the trash * or emptying the trash * Used by askDeleteConfirmation. */ enum DeletionType { Delete, Trash, EmptyTrash }; /** * ForceConfirmation: always ask the user for confirmation * DefaultConfirmation: don't ask the user if he/she said "don't ask again". * * Used by askDeleteConfirmation. */ enum ConfirmationType { DefaultConfirmation, ForceConfirmation }; /** * 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 confirmationType 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 */ virtual bool askDeleteConfirmation(const QList &urls, DeletionType deletionType, ConfirmationType confirmationType) = 0; /** * Message box types. * * Should be kept in sync with SlaveBase::MessageBoxType. * * @since 4.11 */ enum MessageBoxType { QuestionYesNo = 1, WarningYesNo = 2, WarningContinueCancel = 3, WarningYesNoCancel = 4, Information = 5, SSLMessageBox = 6 }; /** * This function allows for the delegation user prompts from the ioslaves. * * @param type the desired type of message box. * @param text the message shown to the user. * @param caption the caption of the message dialog box. * @param buttonYes the text for the YES button. * @param buttonNo the text for the NO button. * @param iconYes the icon shown on the YES button. * @param iconNo the icon shown on the NO button. * @param dontAskAgainName the name used to store result from 'Do not ask again' checkbox. * @param sslMetaData SSL information used by the SSLMessageBox. */ virtual int requestMessageBox(MessageBoxType type, const QString &text, const QString &caption, const QString &buttonYes, const QString &buttonNo, const QString &iconYes = QString(), const QString &iconNo = QString(), const QString &dontAskAgainName = QString(), const KIO::MetaData &sslMetaData = KIO::MetaData()) = 0; enum ClipboardUpdaterMode { UpdateContent, OverwriteContent, RemoveContent }; /** * Creates a clipboard updater as a child of the given job. */ virtual ClipboardUpdater *createClipboardUpdater(Job *job, ClipboardUpdaterMode mode); /** * Update URL in clipboard, if present */ virtual void updateUrlInClipboard(const QUrl &src, const QUrl &dest); private: class Private; Private *const d; }; /** * Returns the default job UI delegate extension to be used by all KIO jobs (in which HideProgressInfo is not set) * Can return nullptr, if no kio GUI library is loaded. * @since 5.0 */ KIOCORE_EXPORT JobUiDelegateExtension *defaultJobUiDelegateExtension(); /** * Internal. Allows the KIO widgets library to register its widget-based job UI delegate extension * automatically. * @since 5.0 */ KIOCORE_EXPORT void setDefaultJobUiDelegateExtension(JobUiDelegateExtension *extension); } // namespace KIO Q_DECLARE_OPERATORS_FOR_FLAGS(KIO::RenameDialog_Options) Q_DECLARE_OPERATORS_FOR_FLAGS(KIO::SkipDialog_Options) #endif diff --git a/src/core/kcoredirlister.h b/src/core/kcoredirlister.h index 63c4fb56..dd34a227 100644 --- a/src/core/kcoredirlister.h +++ b/src/core/kcoredirlister.h @@ -1,626 +1,628 @@ /* This file is part of the KDE project Copyright (C) 1999 David Faure 2001, 2002, 2004-2006 Michael Brade This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef KCOREDIRLISTER_H #define KCOREDIRLISTER_H #include "kfileitem.h" #include "kdirnotify.h" // TODO SIC: remove #include #include #include class KJob; namespace KIO { class Job; class ListJob; } /** * @class KCoreDirLister kcoredirlister.h * * @short Helper class for the kiojob used to list and update a directory. * * The dir lister deals with the kiojob used to list and update a directory * and has signals for the user of this class (e.g. konqueror view or * kdesktop) to create/destroy its items when asked. * * This class is independent from the graphical representation of the dir * (icon container, tree view, ...) and it stores the items (as KFileItems). * * Typical usage : * @li Create an instance. * @li Connect to at least update, clear, itemsAdded, and itemsDeleted. * @li Call openUrl - the signals will be called. * @li Reuse the instance when opening a new url (openUrl). * @li Destroy the instance when not needed anymore (usually destructor). * * Advanced usage : call openUrl with OpenUrlFlag::Keep to list directories * without forgetting the ones previously read (e.g. for a tree view) * * @author Michael Brade */ class KIOCORE_EXPORT KCoreDirLister : public QObject { friend class KCoreDirListerCache; friend struct KCoreDirListerCacheDirectoryData; Q_OBJECT Q_PROPERTY(bool autoUpdate READ autoUpdate WRITE setAutoUpdate) Q_PROPERTY(bool showingDotFiles READ showingDotFiles WRITE setShowingDotFiles) Q_PROPERTY(bool dirOnlyMode READ dirOnlyMode WRITE setDirOnlyMode) Q_PROPERTY(bool delayedMimeTypes READ delayedMimeTypes WRITE setDelayedMimeTypes) Q_PROPERTY(QString nameFilter READ nameFilter WRITE setNameFilter) Q_PROPERTY(QStringList mimeFilter READ mimeFilters WRITE setMimeFilter RESET clearMimeFilter) public: enum OpenUrlFlag { NoFlags = 0x0, ///< No additional flags specified. Keep = 0x1, ///< Previous directories aren't forgotten ///< (they are still watched by kdirwatch and their items ///< are kept for this KCoreDirLister). This is useful for e.g. ///< a treeview. Reload = 0x2 ///< Indicates whether to use the cache or to reread ///< the directory from the disk. ///< Use only when opening a dir not yet listed by this lister ///< without using the cache. Otherwise use updateDirectory. }; Q_DECLARE_FLAGS(OpenUrlFlags, OpenUrlFlag) /** * Create a directory lister. */ KCoreDirLister(QObject *parent = nullptr); /** * Destroy the directory lister. */ virtual ~KCoreDirLister(); /** * Run the directory lister on the given url. * * This method causes KCoreDirLister to emit _all_ the items of @p _url, in any case. * Depending on _flags, either clear() or clear(const QUrl &) will be * emitted first. * * The newItems() signal may be emitted more than once to supply you * with KFileItems, up until the signal completed() is emitted * (and isFinished() returns true). * * @param _url the directory URL. * @param _flags whether to keep previous directories, and whether to reload, see OpenUrlFlags * @return true if successful, * false otherwise (e.g. invalid @p _url) */ virtual bool openUrl(const QUrl &_url, OpenUrlFlags _flags = NoFlags); /** * Stop listing all directories currently being listed. * * Emits canceled() if there was at least one job running. * Emits canceled( const QUrl& ) for each stopped job if * there are at least two directories being watched by KCoreDirLister. */ virtual void stop(); /** * Stop listing the given directory. * * Emits canceled() if the killed job was the last running one. * Emits canceled( const QUrl& ) for the killed job if * there are at least two directories being watched by KCoreDirLister. * No signal is emitted if there was no job running for @p _url. * @param _url the directory URL */ virtual void stop(const QUrl &_url); /** * @return true if the "delayed mimetypes" feature was enabled * @see setDelayedMimeTypes */ bool delayedMimeTypes() const; /** * Delayed mimetypes feature: * If enabled, mime types will be fetched on demand, which leads to a * faster initial directory listing, where icons get progressively replaced * with the correct one while KMimeTypeResolver is going through the items * with unknown or imprecise mimetype (e.g. files with no extension or an * unknown extension). */ void setDelayedMimeTypes(bool delayedMimeTypes); /** * Checks whether KDirWatch will automatically update directories. This is * enabled by default. * @return true if KDirWatch is used to automatically update directories. */ bool autoUpdate() const; /** * Enable/disable automatic directory updating, when a directory changes * (using KDirWatch). * @param enable true to enable, false to disable */ virtual void setAutoUpdate(bool enable); /** * Checks whether hidden files (files beginning with a dot) will be * shown. * By default this option is disabled (hidden files will be not shown). * @return true if dot files are shown, false otherwise * @see setShowingDotFiles() */ bool showingDotFiles() const; /** * Changes the "is viewing dot files" setting. * You need to call emitChanges() afterwards. * By default this option is disabled (hidden files will not be shown). * @param _showDotFiles true to enable showing hidden files, false to * disable * @see showingDotFiles() */ virtual void setShowingDotFiles(bool _showDotFiles); /** * Checks whether the KCoreDirLister only lists directories or all * files. * By default this option is disabled (all files will be shown). * @return true if setDirOnlyMode(true) was called */ bool dirOnlyMode() const; /** * Call this to list only directories. * You need to call emitChanges() afterwards. * By default this option is disabled (all files will be shown). * @param dirsOnly true to list only directories */ virtual void setDirOnlyMode(bool dirsOnly); /** * Returns the top level URL that is listed by this KCoreDirLister. * It might be different from the one given with openUrl() if there was a * redirection. If you called openUrl() with OpenUrlFlag::Keep this is the * first url opened (e.g. in a treeview this is the root). * * @return the url used by this instance to list the files. */ QUrl url() const; /** * Returns all URLs that are listed by this KCoreDirLister. This is only * useful if you called openUrl() with OpenUrlFlag::Keep, as it happens in a * treeview, for example. (Note that the base url is included in the list * as well, of course.) * * @return the list of all listed URLs */ QList directories() const; /** * Actually emit the changes made with setShowingDotFiles, setDirOnlyMode, * setNameFilter and setMimeFilter. */ virtual void emitChanges(); /** * Update the directory @p _dir. This method causes KCoreDirLister to _only_ emit * the items of @p _dir that actually changed compared to the current state in the * cache and updates the cache. * * The current implementation calls updateDirectory automatically for * local files, using KDirWatch (if autoUpdate() is true), but it might be * useful to force an update manually. * * @param _dir the directory URL */ virtual void updateDirectory(const QUrl &_dir); /** * Returns true if no io operation is currently in progress. * @return true if finished, false otherwise */ bool isFinished() const; /** * Returns the file item of the URL. * * Can return an empty KFileItem. * @return the file item for url() itself (".") */ KFileItem rootItem() const; /** * Find an item by its URL. * @param _url the item URL * @return the KFileItem */ virtual KFileItem findByUrl(const QUrl &_url) const; /** * Find an item by its name. * @param name the item name * @return the KFileItem */ virtual KFileItem findByName(const QString &name) const; /** * Set a name filter to only list items matching this name, e.g. "*.cpp". * * You can set more than one filter by separating them with whitespace, e.g * "*.cpp *.h". * Note: the directory is not automatically reloaded. * You need to call emitChanges() afterwards. * * @param filter the new filter, QString() to disable filtering * @see matchesFilter */ virtual void setNameFilter(const QString &filter); /** * Returns the current name filter, as set via setNameFilter() * @return the current name filter, can be QString() if filtering * is turned off */ QString nameFilter() const; /** * Set mime-based filter to only list items matching the given mimetypes. * * NOTE: setting the filter does not automatically reload directory. * Also calling this function will not affect any named filter already set. * * You need to call emitChanges() afterwards. * * @param mimeList a list of mime-types. * * @see clearMimeFilter * @see matchesMimeFilter */ virtual void setMimeFilter(const QStringList &mimeList); /** * Filtering should be done with KFileFilter. This will be implemented in a later * revision of KCoreDirLister. This method may be removed then. * * Set mime-based exclude filter to only list items not matching the given mimetypes * * NOTE: setting the filter does not automatically reload directory. * Also calling this function will not affect any named filter already set. * * @param mimeList a list of mime-types. * @see clearMimeFilter * @see matchesMimeFilter * @internal */ void setMimeExcludeFilter(const QStringList &mimeList); /** * Clears the mime based filter. * * You need to call emitChanges() afterwards. * * @see setMimeFilter */ virtual void clearMimeFilter(); /** * Returns the list of mime based filters, as set via setMimeFilter(). * @return the list of mime based filters. Empty, when no mime filter is set. */ QStringList mimeFilters() const; /** * Checks whether @p name matches a filter in the list of name filters. * @return true if @p name matches a filter in the list, * otherwise false. * @see setNameFilter */ bool matchesFilter(const QString &name) const; /** * Checks whether @p mime matches a filter in the list of mime types * @param mime the mimetype to find in the filter list. * @return true if @p name matches a filter in the list, * otherwise false. * @see setMimeFilter. */ bool matchesMimeFilter(const QString &mime) const; /** * Used by items() and itemsForDir() to specify whether you want * all items for a directory or just the filtered ones. */ enum WhichItems { AllItems = 0, FilteredItems = 1 }; /** * Returns the items listed for the current url(). * This method will NOT start listing a directory, you should only call * this when receiving the finished() signal. * * The items in the KFileItemList are copies of the items used * by KCoreDirLister. * * @param which specifies whether the returned list will contain all entries * or only the ones that passed the nameFilter(), mimeFilter(), * etc. Note that the latter causes iteration over all the * items, filtering them. If this is too slow for you, use the * newItems() signal, sending out filtered items in chunks. * @return the items listed for the current url(). */ KFileItemList items(WhichItems which = FilteredItems) const; /** * Returns the items listed for the given @p dir. * This method will NOT start listing @p dir, you should only call * this when receiving the finished() signal. * * The items in the KFileItemList are copies of the items used * by KCoreDirLister. * * @param dir specifies the url for which the items should be returned. This * is only useful if you use KCoreDirLister with multiple URLs * i.e. using bool OpenUrlFlag::Keep in openUrl(). * @param which specifies whether the returned list will contain all entries * or only the ones that passed the nameFilter, mimeFilter, etc. * Note that the latter causes iteration over all the items, * filtering them. If this is too slow for you, use the * newItems() signal, sending out filtered items in chunks. * @return the items listed for @p dir. */ KFileItemList itemsForDir(const QUrl &dir, WhichItems which = FilteredItems) const; /** * Return the KFileItem for the given URL, if we listed it recently * and it's still in the cache - which is always the case if a directory * view is currently showing this item. If not, then it might be in the * cache, or it might not, in which case you get a null KFileItem. * If you really need a KFileItem for this URL in all cases, then use * KIO::stat() instead. * * @since 4.2 */ static KFileItem cachedItemForUrl(const QUrl &url); Q_SIGNALS: /** * Tell the view that we started to list @p _url. NOTE: this does _not_ imply that there * is really a job running! I.e. KCoreDirLister::jobs() may return an empty list. In this case * the items are taken from the cache. * * The view knows that openUrl should start it, so it might seem useless, * but the view also needs to know when an automatic update happens. * @param _url the URL to list */ void started(const QUrl &_url); /** * Tell the view that listing is finished. There are no jobs running anymore. */ void completed(); /** * Tell the view that the listing of the directory @p _url is finished. * There might be other running jobs left. * @param _url the directory URL */ void completed(const QUrl &_url); /** * Tell the view that the user canceled the listing. No running jobs are left. */ void canceled(); /** * Tell the view that the listing of the directory @p _url was canceled. * There might be other running jobs left. * @param _url the directory URL */ void canceled(const QUrl &_url); /** * Signal a redirection. * Only emitted if there's just one directory to list, i.e. most * probably openUrl() has been called without OpenUrlFlag::Keep. * @param _url the new URL */ void redirection(const QUrl &_url); /** * Signal a redirection. * @param oldUrl the original URL * @param newUrl the new URL */ void redirection(const QUrl &oldUrl, const QUrl &newUrl); /** * Signal to clear all items. * Make sure to connect to this signal to avoid doubled items. */ void clear(); /** * Signal to empty the directory @p _url. * It is only emitted if the lister is holding more than one directory. * @param _url the directory that will be emptied */ void clear(const QUrl &_url); /** * Signal new items. * * @param items a list of new items */ void newItems(const KFileItemList &items); /** * Signal that new items were found during directory listing. * Alternative signal emitted at the same time as newItems(), * but itemsAdded also passes the url of the parent directory. * * @param items a list of new items * @since 4.2 */ void itemsAdded(const QUrl &directoryUrl, const KFileItemList &items); /** * Send a list of items filtered-out by mime-type. * @param items the list of filtered items */ void itemsFilteredByMime(const KFileItemList &items); /** * Signal that items have been deleted * * @since 4.1.2 * @param items the list of deleted items */ void itemsDeleted(const KFileItemList &items); /** * Signal an item to refresh (its mimetype/icon/name has changed). * Note: KFileItem::refresh has already been called on those items. * @param items the items to refresh. This is a list of pairs, where * the first item in the pair is the OLD item, and the second item is the * NEW item. This allows to track which item has changed, especially after * a renaming. */ void refreshItems(const QList > &items); /** * Emitted to display information about running jobs. * Examples of message are "Resolving host", "Connecting to host...", etc. * @param msg the info message */ void infoMessage(const QString &msg); /** * Progress signal showing the overall progress of the KCoreDirLister. * This allows using a progress bar very easily. (see QProgressBar) * @param percent the progress in percent */ void percent(int percent); /** * Emitted when we know the size of the jobs. * @param size the total size in bytes */ void totalSize(KIO::filesize_t size); /** * Regularly emitted to show the progress of this KCoreDirLister. * @param size the processed size in bytes */ void processedSize(KIO::filesize_t size); /** * Emitted to display information about the speed of the jobs. * @param bytes_per_second the speed in bytes/s */ void speed(int bytes_per_second); protected: - /// @deprecated and unused, ignore this +#if KIOCORE_ENABLE_DEPRECATED_SINCE(4, 3) + /// @deprecated Since 4.3, and unused, ignore this enum Changes { NONE = 0, NAME_FILTER = 1, MIME_FILTER = 2, DOT_FILES = 4, DIR_ONLY_MODE = 8 }; +#endif /** * Called for every new item before emitting newItems(). * You may reimplement this method in a subclass to implement your own * filtering. * The default implementation filters out ".." and everything not matching * the name filter(s) * @return true if the item is "ok". * false if the item shall not be shown in a view, e.g. * files not matching a pattern *.cpp ( KFileItem::isHidden()) * @see matchesFilter * @see setNameFilter */ virtual bool matchesFilter(const KFileItem &) const; /** * Called for every new item before emitting newItems(). * You may reimplement this method in a subclass to implement your own * filtering. * The default implementation filters out ".." and everything not matching * the name filter(s) * @return true if the item is "ok". * false if the item shall not be shown in a view, e.g. * files not matching a pattern *.cpp ( KFileItem::isHidden()) * @see matchesMimeFilter * @see setMimeFilter */ virtual bool matchesMimeFilter(const KFileItem &) const; /** * Called by the public matchesFilter() to do the * actual filtering. Those methods may be reimplemented to customize * filtering. * @param name the name to filter * @param filters a list of regular expressions for filtering */ virtual bool doNameFilter(const QString &name, const QList &filters) const; /** * Called by the public matchesMimeFilter() to do the * actual filtering. Those methods may be reimplemented to customize * filtering. * @param mime the mime type to filter * @param filters the list of mime types to filter */ virtual bool doMimeFilter(const QString &mime, const QStringList &filters) const; /** * Reimplement to customize error handling */ virtual void handleError(KIO::Job *); /** * Reimplement to customize error handling * @since 5.0 */ virtual void handleErrorMessage(const QString &message); /** * Reimplemented by KDirLister to associate windows with jobs * @since 5.0 */ virtual void jobStarted(KIO::ListJob *); private: class Private; Private *const d; friend class Private; }; Q_DECLARE_OPERATORS_FOR_FLAGS(KCoreDirLister::OpenUrlFlags) #endif diff --git a/src/core/kfileitem.cpp b/src/core/kfileitem.cpp index f30fec7a..5723d76a 100644 --- a/src/core/kfileitem.cpp +++ b/src/core/kfileitem.cpp @@ -1,1697 +1,1691 @@ /* This file is part of the KDE project Copyright (C) 1999-2011 David Faure 2001 Carsten Pfeiffer This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kfileitem.h" #include "kioglobal_p.h" #include "kiocoredebug.h" #include "../pathhelpers_p.h" #include #include #include #include #include #include #include #include #include #include #include #include #ifndef Q_OS_WIN #include #include #endif #include class KFileItemPrivate : public QSharedData { public: KFileItemPrivate(const KIO::UDSEntry &entry, mode_t mode, mode_t permissions, const QUrl &itemOrDirUrl, bool urlIsDirectory, bool delayedMimeTypes, KFileItem::MimeTypeDetermination mimeTypeDetermination) : m_entry(entry), m_url(itemOrDirUrl), m_strName(), m_strText(), m_iconName(), m_strLowerCaseName(), m_mimeType(), m_fileMode(mode), m_permissions(permissions), m_bLink(false), m_bIsLocalUrl(itemOrDirUrl.isLocalFile()), m_bMimeTypeKnown(false), m_delayedMimeTypes(delayedMimeTypes), m_useIconNameCache(false), m_hidden(Auto), m_slow(SlowUnknown), m_bSkipMimeTypeFromContent(mimeTypeDetermination == KFileItem::SkipMimeTypeFromContent), m_bInitCalled(false) { if (entry.count() != 0) { readUDSEntry(urlIsDirectory); } else { Q_ASSERT(!urlIsDirectory); m_strName = itemOrDirUrl.fileName(); m_strText = KIO::decodeFileName(m_strName); } } /** * Call init() if not yet done. */ void ensureInitialized() const; /** * Computes the text and mode from the UDSEntry. */ void init() const; QString localPath() const; KIO::filesize_t size() const; QDateTime time(KFileItem::FileTimes which) const; void setTime(KFileItem::FileTimes which, uint time_t_val) const; void setTime(KFileItem::FileTimes which, const QDateTime &val) const; bool cmp(const KFileItemPrivate &item) const; bool isSlow() const; /** * Extracts the data from the UDSEntry member and updates the KFileItem * accordingly. */ void readUDSEntry(bool _urlIsDirectory); /** * Parses the given permission set and provides it for access() */ QString parsePermissions(mode_t perm) const; /** * Mime type helper */ void determineMimeTypeHelper(const QUrl &url) const; /** * The UDSEntry that contains the data for this fileitem, if it came from a directory listing. */ mutable KIO::UDSEntry m_entry; /** * The url of the file */ QUrl m_url; /** * The text for this item, i.e. the file name without path, */ QString m_strName; /** * The text for this item, i.e. the file name without path, decoded * ('%%' becomes '%', '%2F' becomes '/') */ QString m_strText; /** * The icon name for this item. */ mutable QString m_iconName; /** * The filename in lower case (to speed up sorting) */ mutable QString m_strLowerCaseName; /** * The mimetype of the file */ mutable QMimeType m_mimeType; /** * The file mode */ mutable mode_t m_fileMode; /** * The permissions */ mutable mode_t m_permissions; /** * Whether the file is a link */ mutable bool m_bLink: 1; /** * True if local file */ bool m_bIsLocalUrl: 1; mutable bool m_bMimeTypeKnown: 1; mutable bool m_delayedMimeTypes: 1; /** True if m_iconName should be used as cache. */ mutable bool m_useIconNameCache: 1; // Auto: check leading dot. enum { Auto, Hidden, Shown } m_hidden: 3; // Slow? (nfs/smb/ssh) mutable enum { SlowUnknown, Fast, Slow } m_slow: 3; /** * True if mime type determination by content should be skipped */ bool m_bSkipMimeTypeFromContent: 1; /** * True if init() was called on demand */ mutable bool m_bInitCalled: 1; // For special case like link to dirs over FTP QString m_guessedMimeType; mutable QString m_access; }; void KFileItemPrivate::ensureInitialized() const { if (!m_bInitCalled) { init(); } } void KFileItemPrivate::init() const { m_access.clear(); // metaInfo = KFileMetaInfo(); // stat() local files if needed if (m_fileMode == KFileItem::Unknown || m_permissions == KFileItem::Unknown || m_entry.count() == 0) { if (m_url.isLocalFile()) { /* directories may not have a slash at the end if * we want to stat() them; it requires that we * change into it .. which may not be allowed * stat("/is/unaccessible") -> rwx------ * stat("/is/unaccessible/") -> EPERM H.Z. * This is the reason for the StripTrailingSlash */ QT_STATBUF buf; const QString path = m_url.adjusted(QUrl::StripTrailingSlash).toLocalFile(); const QByteArray pathBA = QFile::encodeName(path); if (QT_LSTAT(pathBA.constData(), &buf) == 0) { m_entry.reserve(9); m_entry.replace(KIO::UDSEntry::UDS_DEVICE_ID, buf.st_dev); m_entry.replace(KIO::UDSEntry::UDS_INODE, buf.st_ino); mode_t mode = buf.st_mode; if ((buf.st_mode & QT_STAT_MASK) == QT_STAT_LNK) { m_bLink = true; if (QT_STAT(pathBA.constData(), &buf) == 0) { mode = buf.st_mode; } else {// link pointing to nowhere (see FileProtocol::createUDSEntry() in ioslaves/file/file.cpp) mode = (QT_STAT_MASK - 1) | S_IRWXU | S_IRWXG | S_IRWXO; } } m_entry.replace(KIO::UDSEntry::UDS_SIZE, buf.st_size); m_entry.replace(KIO::UDSEntry::UDS_FILE_TYPE, buf.st_mode & QT_STAT_MASK); // extract file type m_entry.replace(KIO::UDSEntry::UDS_ACCESS, buf.st_mode & 07777); // extract permissions m_entry.replace(KIO::UDSEntry::UDS_MODIFICATION_TIME, buf.st_mtime); // TODO: we could use msecs too... m_entry.replace(KIO::UDSEntry::UDS_ACCESS_TIME, buf.st_atime); #ifndef Q_OS_WIN m_entry.replace(KIO::UDSEntry::UDS_USER, KUser(buf.st_uid).loginName()); m_entry.replace(KIO::UDSEntry::UDS_GROUP, KUserGroup(buf.st_gid).name()); #endif // TODO: these can be removed, we can use UDS_FILE_TYPE and UDS_ACCESS everywhere if (m_fileMode == KFileItem::Unknown) { m_fileMode = mode & QT_STAT_MASK; // extract file type } if (m_permissions == KFileItem::Unknown) { m_permissions = mode & 07777; // extract permissions } } } } m_bInitCalled = true; } void KFileItemPrivate::readUDSEntry(bool _urlIsDirectory) { // extract fields from the KIO::UDS Entry m_fileMode = m_entry.numberValue(KIO::UDSEntry::UDS_FILE_TYPE, KFileItem::Unknown); m_permissions = m_entry.numberValue(KIO::UDSEntry::UDS_ACCESS, KFileItem::Unknown); m_strName = m_entry.stringValue(KIO::UDSEntry::UDS_NAME); const QString displayName = m_entry.stringValue(KIO::UDSEntry::UDS_DISPLAY_NAME); if (!displayName.isEmpty()) { m_strText = displayName; } else { m_strText = KIO::decodeFileName(m_strName); } const QString urlStr = m_entry.stringValue(KIO::UDSEntry::UDS_URL); const bool UDS_URL_seen = !urlStr.isEmpty(); if (UDS_URL_seen) { m_url = QUrl(urlStr); if (m_url.isLocalFile()) { m_bIsLocalUrl = true; } } QMimeDatabase db; const QString mimeTypeStr = m_entry.stringValue(KIO::UDSEntry::UDS_MIME_TYPE); m_bMimeTypeKnown = !mimeTypeStr.isEmpty(); if (m_bMimeTypeKnown) { m_mimeType = db.mimeTypeForName(mimeTypeStr); } m_guessedMimeType = m_entry.stringValue(KIO::UDSEntry::UDS_GUESSED_MIME_TYPE); m_bLink = !m_entry.stringValue(KIO::UDSEntry::UDS_LINK_DEST).isEmpty(); // we don't store the link dest const int hiddenVal = m_entry.numberValue(KIO::UDSEntry::UDS_HIDDEN, -1); m_hidden = hiddenVal == 1 ? Hidden : (hiddenVal == 0 ? Shown : Auto); if (_urlIsDirectory && !UDS_URL_seen && !m_strName.isEmpty() && m_strName != QLatin1String(".")) { m_url.setPath(concatPaths(m_url.path(), m_strName)); } m_iconName.clear(); } inline //because it is used only in one place KIO::filesize_t KFileItemPrivate::size() const { ensureInitialized(); // Extract it from the KIO::UDSEntry long long fieldVal = m_entry.numberValue(KIO::UDSEntry::UDS_SIZE, -1); if (fieldVal != -1) { return fieldVal; } // If not in the KIO::UDSEntry, or if UDSEntry empty, use stat() [if local URL] if (m_bIsLocalUrl) { return QFileInfo(m_url.toLocalFile()).size(); } return 0; } static uint udsFieldForTime(KFileItem::FileTimes mappedWhich) { switch (mappedWhich) { case KFileItem::ModificationTime: return KIO::UDSEntry::UDS_MODIFICATION_TIME; case KFileItem::AccessTime: return KIO::UDSEntry::UDS_ACCESS_TIME; case KFileItem::CreationTime: return KIO::UDSEntry::UDS_CREATION_TIME; } return 0; } void KFileItemPrivate::setTime(KFileItem::FileTimes mappedWhich, uint time_t_val) const { m_entry.replace(udsFieldForTime(mappedWhich), time_t_val); } void KFileItemPrivate::setTime(KFileItem::FileTimes mappedWhich, const QDateTime &val) const { const QDateTime dt = val.toLocalTime(); // #160979 setTime(mappedWhich, dt.toSecsSinceEpoch()); } QDateTime KFileItemPrivate::time(KFileItem::FileTimes mappedWhich) const { ensureInitialized(); // Extract it from the KIO::UDSEntry const uint uds = udsFieldForTime(mappedWhich); if (uds > 0) { const long long fieldVal = m_entry.numberValue(uds, -1); if (fieldVal != -1) { return QDateTime::fromMSecsSinceEpoch(1000 * fieldVal); } } return QDateTime(); } inline //because it is used only in one place bool KFileItemPrivate::cmp(const KFileItemPrivate &item) const { if (item.m_bInitCalled) { ensureInitialized(); } if (m_bInitCalled) { item.ensureInitialized(); } #if 0 //qDebug() << "Comparing" << m_url << "and" << item.m_url; //qDebug() << " name" << (m_strName == item.m_strName); //qDebug() << " local" << (m_bIsLocalUrl == item.m_bIsLocalUrl); //qDebug() << " mode" << (m_fileMode == item.m_fileMode); //qDebug() << " perm" << (m_permissions == item.m_permissions); //qDebug() << " UDS_EXTENDED_ACL" << (m_entry.stringValue( KIO::UDSEntry::UDS_EXTENDED_ACL ) == item.m_entry.stringValue( KIO::UDSEntry::UDS_EXTENDED_ACL )); //qDebug() << " UDS_ACL_STRING" << (m_entry.stringValue( KIO::UDSEntry::UDS_ACL_STRING ) == item.m_entry.stringValue( KIO::UDSEntry::UDS_ACL_STRING )); //qDebug() << " UDS_DEFAULT_ACL_STRING" << (m_entry.stringValue( KIO::UDSEntry::UDS_DEFAULT_ACL_STRING ) == item.m_entry.stringValue( KIO::UDSEntry::UDS_DEFAULT_ACL_STRING )); //qDebug() << " m_bLink" << (m_bLink == item.m_bLink); //qDebug() << " m_hidden" << (m_hidden == item.m_hidden); //qDebug() << " size" << (size() == item.size()); //qDebug() << " ModificationTime" << m_entry.numberValue(KIO::UDSEntry::UDS_MODIFICATION_TIME) << item.m_entry.numberValue(KIO::UDSEntry::UDS_MODIFICATION_TIME); //qDebug() << " UDS_ICON_NAME" << (m_entry.stringValue( KIO::UDSEntry::UDS_ICON_NAME ) == item.m_entry.stringValue( KIO::UDSEntry::UDS_ICON_NAME )); #endif return (m_strName == item.m_strName && m_bIsLocalUrl == item.m_bIsLocalUrl && m_fileMode == item.m_fileMode && m_permissions == item.m_permissions && m_entry.stringValue(KIO::UDSEntry::UDS_EXTENDED_ACL) == item.m_entry.stringValue(KIO::UDSEntry::UDS_EXTENDED_ACL) && m_entry.stringValue(KIO::UDSEntry::UDS_ACL_STRING) == item.m_entry.stringValue(KIO::UDSEntry::UDS_ACL_STRING) && m_entry.stringValue(KIO::UDSEntry::UDS_DEFAULT_ACL_STRING) == item.m_entry.stringValue(KIO::UDSEntry::UDS_DEFAULT_ACL_STRING) && m_bLink == item.m_bLink && m_hidden == item.m_hidden && size() == item.size() && m_entry.numberValue(KIO::UDSEntry::UDS_MODIFICATION_TIME) == item.m_entry.numberValue(KIO::UDSEntry::UDS_MODIFICATION_TIME) && m_entry.stringValue(KIO::UDSEntry::UDS_ICON_NAME) == item.m_entry.stringValue(KIO::UDSEntry::UDS_ICON_NAME) && m_entry.stringValue(KIO::UDSEntry::UDS_TARGET_URL) == item.m_entry.stringValue(KIO::UDSEntry::UDS_TARGET_URL) && m_entry.stringValue(KIO::UDSEntry::UDS_LOCAL_PATH) == item.m_entry.stringValue(KIO::UDSEntry::UDS_LOCAL_PATH) ); // Don't compare the mimetypes here. They might not be known, and we don't want to // do the slow operation of determining them here. } inline //because it is used only in one place QString KFileItemPrivate::parsePermissions(mode_t perm) const { ensureInitialized(); static char buffer[ 12 ]; char uxbit, gxbit, oxbit; if ((perm & (S_IXUSR | S_ISUID)) == (S_IXUSR | S_ISUID)) { uxbit = 's'; } else if ((perm & (S_IXUSR | S_ISUID)) == S_ISUID) { uxbit = 'S'; } else if ((perm & (S_IXUSR | S_ISUID)) == S_IXUSR) { uxbit = 'x'; } else { uxbit = '-'; } if ((perm & (S_IXGRP | S_ISGID)) == (S_IXGRP | S_ISGID)) { gxbit = 's'; } else if ((perm & (S_IXGRP | S_ISGID)) == S_ISGID) { gxbit = 'S'; } else if ((perm & (S_IXGRP | S_ISGID)) == S_IXGRP) { gxbit = 'x'; } else { gxbit = '-'; } if ((perm & (S_IXOTH | S_ISVTX)) == (S_IXOTH | S_ISVTX)) { oxbit = 't'; } else if ((perm & (S_IXOTH | S_ISVTX)) == S_ISVTX) { oxbit = 'T'; } else if ((perm & (S_IXOTH | S_ISVTX)) == S_IXOTH) { oxbit = 'x'; } else { oxbit = '-'; } // Include the type in the first char like ls does; people are more used to seeing it, // even though it's not really part of the permissions per se. if (m_bLink) { buffer[0] = 'l'; } else if (m_fileMode != KFileItem::Unknown) { if ((m_fileMode & QT_STAT_MASK) == QT_STAT_DIR) { buffer[0] = 'd'; } #ifdef Q_OS_UNIX else if (S_ISSOCK(m_fileMode)) { buffer[0] = 's'; } else if (S_ISCHR(m_fileMode)) { buffer[0] = 'c'; } else if (S_ISBLK(m_fileMode)) { buffer[0] = 'b'; } else if (S_ISFIFO(m_fileMode)) { buffer[0] = 'p'; } #endif // Q_OS_UNIX else { buffer[0] = '-'; } } else { buffer[0] = '-'; } buffer[1] = (((perm & S_IRUSR) == S_IRUSR) ? 'r' : '-'); buffer[2] = (((perm & S_IWUSR) == S_IWUSR) ? 'w' : '-'); buffer[3] = uxbit; buffer[4] = (((perm & S_IRGRP) == S_IRGRP) ? 'r' : '-'); buffer[5] = (((perm & S_IWGRP) == S_IWGRP) ? 'w' : '-'); buffer[6] = gxbit; buffer[7] = (((perm & S_IROTH) == S_IROTH) ? 'r' : '-'); buffer[8] = (((perm & S_IWOTH) == S_IWOTH) ? 'w' : '-'); buffer[9] = oxbit; // if (hasExtendedACL()) if (m_entry.contains(KIO::UDSEntry::UDS_EXTENDED_ACL)) { buffer[10] = '+'; buffer[11] = 0; } else { buffer[10] = 0; } return QString::fromLatin1(buffer); } void KFileItemPrivate::determineMimeTypeHelper(const QUrl &url) const { QMimeDatabase db; if (m_bSkipMimeTypeFromContent) { const QString scheme = url.scheme(); if (scheme.startsWith(QLatin1String("http")) || scheme == QLatin1String("mailto")) m_mimeType = db.mimeTypeForName(QLatin1String("application/octet-stream")); else m_mimeType = db.mimeTypeForFile(url.path(), QMimeDatabase::MatchMode::MatchExtension); } else { m_mimeType = db.mimeTypeForUrl(url); } } /////// KFileItem::KFileItem() : d(nullptr) { } KFileItem::KFileItem(const KIO::UDSEntry &entry, const QUrl &itemOrDirUrl, bool delayedMimeTypes, bool urlIsDirectory) : d(new KFileItemPrivate(entry, KFileItem::Unknown, KFileItem::Unknown, itemOrDirUrl, urlIsDirectory, delayedMimeTypes, KFileItem::NormalMimeTypeDetermination)) { } KFileItem::KFileItem(mode_t mode, mode_t permissions, const QUrl &url, bool delayedMimeTypes) : d(new KFileItemPrivate(KIO::UDSEntry(), mode, permissions, url, false, delayedMimeTypes, KFileItem::NormalMimeTypeDetermination)) { } KFileItem::KFileItem(const QUrl &url, const QString &mimeType, mode_t mode) : d(new KFileItemPrivate(KIO::UDSEntry(), mode, KFileItem::Unknown, url, false, false, KFileItem::NormalMimeTypeDetermination)) { d->m_bMimeTypeKnown = !mimeType.isEmpty(); if (d->m_bMimeTypeKnown) { QMimeDatabase db; d->m_mimeType = db.mimeTypeForName(mimeType); } } KFileItem::KFileItem(const QUrl &url, KFileItem::MimeTypeDetermination mimeTypeDetermination) : d(new KFileItemPrivate(KIO::UDSEntry(), KFileItem::Unknown, KFileItem::Unknown, url, false, false, mimeTypeDetermination)) { } // Default implementations for: // - Copy constructor // - Move constructor // - Copy assignment // - Move assignment // - Destructor // The compiler will now generate the content of those. KFileItem::KFileItem(const KFileItem&) = default; KFileItem::~KFileItem() = default; KFileItem::KFileItem(KFileItem&&) = default; KFileItem& KFileItem::operator=(const KFileItem&) = default; KFileItem& KFileItem::operator=(KFileItem&&) = default; void KFileItem::refresh() { if (!d) { qCWarning(KIO_CORE) << "null item"; return; } d->m_fileMode = KFileItem::Unknown; d->m_permissions = KFileItem::Unknown; d->m_hidden = KFileItemPrivate::Auto; refreshMimeType(); // Basically, we can't trust any information we got while listing. // Everything could have changed... // Clearing m_entry makes it possible to detect changes in the size of the file, // the time information, etc. d->m_entry.clear(); d->init(); // re-populates d->m_entry } void KFileItem::refreshMimeType() { if (!d) { return; } d->m_mimeType = QMimeType(); d->m_bMimeTypeKnown = false; d->m_iconName.clear(); } void KFileItem::setDelayedMimeTypes(bool b) { if (!d) { return; } d->m_delayedMimeTypes = b; } void KFileItem::setUrl(const QUrl &url) { if (!d) { qCWarning(KIO_CORE) << "null item"; return; } d->m_url = url; setName(url.fileName()); } void KFileItem::setLocalPath(const QString &path) { if (!d) { qCWarning(KIO_CORE) << "null item"; return; } d->m_entry.replace(KIO::UDSEntry::UDS_LOCAL_PATH, path); } void KFileItem::setName(const QString &name) { if (!d) { qCWarning(KIO_CORE) << "null item"; return; } d->ensureInitialized(); d->m_strName = name; if (!d->m_strName.isEmpty()) { d->m_strText = KIO::decodeFileName(d->m_strName); } if (d->m_entry.contains(KIO::UDSEntry::UDS_NAME)) { d->m_entry.replace(KIO::UDSEntry::UDS_NAME, d->m_strName); // #195385 } } QString KFileItem::linkDest() const { if (!d) { return QString(); } d->ensureInitialized(); // Extract it from the KIO::UDSEntry const QString linkStr = d->m_entry.stringValue(KIO::UDSEntry::UDS_LINK_DEST); if (!linkStr.isEmpty()) { return linkStr; } // If not in the KIO::UDSEntry, or if UDSEntry empty, use readlink() [if local URL] if (d->m_bIsLocalUrl) { return QFile::symLinkTarget(d->m_url.adjusted(QUrl::StripTrailingSlash).toLocalFile()); } return QString(); } QString KFileItemPrivate::localPath() const { if (m_bIsLocalUrl) { return m_url.toLocalFile(); } ensureInitialized(); // Extract the local path from the KIO::UDSEntry return m_entry.stringValue(KIO::UDSEntry::UDS_LOCAL_PATH); } QString KFileItem::localPath() const { if (!d) { return QString(); } return d->localPath(); } KIO::filesize_t KFileItem::size() const { if (!d) { return 0; } return d->size(); } bool KFileItem::hasExtendedACL() const { if (!d) { return false; } // Check if the field exists; its value doesn't matter return entry().contains(KIO::UDSEntry::UDS_EXTENDED_ACL); } KACL KFileItem::ACL() const { if (!d) { return KACL(); } if (hasExtendedACL()) { // Extract it from the KIO::UDSEntry const QString fieldVal = d->m_entry.stringValue(KIO::UDSEntry::UDS_ACL_STRING); if (!fieldVal.isEmpty()) { return KACL(fieldVal); } } // create one from the basic permissions return KACL(d->m_permissions); } KACL KFileItem::defaultACL() const { if (!d) { return KACL(); } // Extract it from the KIO::UDSEntry const QString fieldVal = entry().stringValue(KIO::UDSEntry::UDS_DEFAULT_ACL_STRING); if (!fieldVal.isEmpty()) { return KACL(fieldVal); } else { return KACL(); } } QDateTime KFileItem::time(FileTimes which) const { if (!d) { return QDateTime(); } return d->time(which); } QString KFileItem::user() const { if (!d) { return QString(); } return entry().stringValue(KIO::UDSEntry::UDS_USER); } QString KFileItem::group() const { if (!d) { return QString(); } return entry().stringValue(KIO::UDSEntry::UDS_GROUP); } bool KFileItemPrivate::isSlow() const { if (m_slow == SlowUnknown) { const QString path = localPath(); if (!path.isEmpty()) { const KFileSystemType::Type fsType = KFileSystemType::fileSystemType(path); m_slow = (fsType == KFileSystemType::Nfs || fsType == KFileSystemType::Smb) ? Slow : Fast; } else { m_slow = Slow; } } return m_slow == Slow; } bool KFileItem::isSlow() const { if (!d) { return false; } return d->isSlow(); } QString KFileItem::mimetype() const { if (!d) { return QString(); } KFileItem *that = const_cast(this); return that->determineMimeType().name(); } QMimeType KFileItem::determineMimeType() const { if (!d) { return QMimeType(); } if (!d->m_mimeType.isValid() || !d->m_bMimeTypeKnown) { QMimeDatabase db; if (isDir()) { d->m_mimeType = db.mimeTypeForName(QStringLiteral("inode/directory")); } else { bool isLocalUrl; const QUrl url = mostLocalUrl(&isLocalUrl); d->determineMimeTypeHelper(url); // was: d->m_mimeType = KMimeType::findByUrl( url, d->m_fileMode, isLocalUrl ); // => we are no longer using d->m_fileMode for remote URLs. Q_ASSERT(d->m_mimeType.isValid()); //qDebug() << d << "finding final mimetype for" << url << ":" << d->m_mimeType.name(); } d->m_bMimeTypeKnown = true; } if (d->m_delayedMimeTypes) { // if we delayed getting the iconName up till now, this is the right point in time to do so d->m_delayedMimeTypes = false; d->m_useIconNameCache = false; (void)iconName(); } return d->m_mimeType; } bool KFileItem::isMimeTypeKnown() const { if (!d) { return false; } // The mimetype isn't known if determineMimeType was never called (on-demand determination) // or if this fileitem has a guessed mimetype (e.g. ftp symlink) - in which case // it always remains "not fully determined" return d->m_bMimeTypeKnown && d->m_guessedMimeType.isEmpty(); } static bool isDirectoryMounted(const QUrl &url) { // Stating .directory files can cause long freezes when e.g. /home // uses autofs for every user's home directory, i.e. opening /home // in a file dialog will mount every single home directory. // These non-mounted directories can be identified by having 0 size. // There are also other directories with 0 size, such as /proc, that may // be mounted, but those are unlikely to contain .directory (and checking // this would require checking with KMountPoint). // TODO: maybe this could be checked with KFileSystemType instead? QFileInfo info(url.toLocalFile()); if (info.isDir() && info.size() == 0) { return false; } return true; } bool KFileItem::isFinalIconKnown() const { if (!d) { return false; } return d->m_bMimeTypeKnown && (!d->m_delayedMimeTypes); } // KDE5 TODO: merge with comment()? Need to see what lxr says about the usage of both. QString KFileItem::mimeComment() const { if (!d) { return QString(); } const QString displayType = d->m_entry.stringValue(KIO::UDSEntry::UDS_DISPLAY_TYPE); if (!displayType.isEmpty()) { return displayType; } bool isLocalUrl; QUrl url = mostLocalUrl(&isLocalUrl); QMimeType mime = currentMimeType(); // This cannot move to kio_file (with UDS_DISPLAY_TYPE) because it needs // the mimetype to be determined, which is done here, and possibly delayed... if (isLocalUrl && !d->isSlow() && mime.inherits(QStringLiteral("application/x-desktop"))) { KDesktopFile cfg(url.toLocalFile()); QString comment = cfg.desktopGroup().readEntry("Comment"); if (!comment.isEmpty()) { return comment; } } // Support for .directory file in directories if (isLocalUrl && isDir() && !d->isSlow() && isDirectoryMounted(url)) { QUrl u(url); u.setPath(concatPaths(u.path(), QStringLiteral(".directory"))); const KDesktopFile cfg(u.toLocalFile()); const QString comment = cfg.readComment(); if (!comment.isEmpty()) { return comment; } } const QString comment = mime.comment(); //qDebug() << "finding comment for " << url.url() << " : " << d->m_mimeType->name(); if (!comment.isEmpty()) { return comment; } else { return mime.name(); } } static QString iconFromDirectoryFile(const QString &path) { const QString filePath = path + QLatin1String("/.directory"); if (!QFileInfo(filePath).isFile()) { // exists -and- is a file return QString(); } KDesktopFile cfg(filePath); QString icon = cfg.readIcon(); const KConfigGroup group = cfg.desktopGroup(); const QString emptyIcon = group.readEntry("EmptyIcon"); if (!emptyIcon.isEmpty()) { bool isDirEmpty = true; QDirIterator dirIt(path, QDir::Dirs | QDir::Files | QDir::NoDotAndDotDot); while (dirIt.hasNext()) { dirIt.next(); if (dirIt.fileName() != QLatin1String(".directory")) { isDirEmpty = false; break; } } if (isDirEmpty) { icon = emptyIcon; } } if (icon.startsWith(QLatin1String("./"))) { // path is relative with respect to the location // of the .directory file (#73463) return path + icon.midRef(1); } return icon; } static QString iconFromDesktopFile(const QString &path) { KDesktopFile cfg(path); const QString icon = cfg.readIcon(); if (cfg.hasLinkType()) { const KConfigGroup group = cfg.desktopGroup(); const QString emptyIcon = group.readEntry("EmptyIcon"); if (!emptyIcon.isEmpty()) { const QString u = cfg.readUrl(); const QUrl url(u); if (url.scheme() == QLatin1String("trash")) { // We need to find if the trash is empty, preferably without using a KIO job. // So instead kio_trash leaves an entry in its config file for us. KConfig trashConfig(QStringLiteral("trashrc"), KConfig::SimpleConfig); if (trashConfig.group("Status").readEntry("Empty", true)) { return emptyIcon; } } } } return icon; } QString KFileItem::iconName() const { if (!d) { return QString(); } if (d->m_useIconNameCache && !d->m_iconName.isEmpty()) { return d->m_iconName; } d->m_iconName = d->m_entry.stringValue(KIO::UDSEntry::UDS_ICON_NAME); if (!d->m_iconName.isEmpty()) { d->m_useIconNameCache = d->m_bMimeTypeKnown; return d->m_iconName; } bool isLocalUrl; QUrl url = mostLocalUrl(&isLocalUrl); QMimeDatabase db; QMimeType mime; // Use guessed mimetype for the icon if (!d->m_guessedMimeType.isEmpty()) { mime = db.mimeTypeForName(d->m_guessedMimeType); } else { mime = currentMimeType(); } const bool delaySlowOperations = d->m_delayedMimeTypes; if (isLocalUrl && !delaySlowOperations && mime.inherits(QStringLiteral("application/x-desktop"))) { d->m_iconName = iconFromDesktopFile(url.toLocalFile()); if (!d->m_iconName.isEmpty()) { d->m_useIconNameCache = d->m_bMimeTypeKnown; return d->m_iconName; } } if (isLocalUrl && !delaySlowOperations && isDir()) { if (isDirectoryMounted(url)) { d->m_iconName = iconFromDirectoryFile(url.toLocalFile()); if (!d->m_iconName.isEmpty()) { d->m_useIconNameCache = d->m_bMimeTypeKnown; return d->m_iconName; } } d->m_iconName = KIOPrivate::iconForStandardPath(url.toLocalFile()); if (!d->m_iconName.isEmpty()) { d->m_useIconNameCache = d->m_bMimeTypeKnown; return d->m_iconName; } } d->m_iconName = mime.iconName(); d->m_useIconNameCache = d->m_bMimeTypeKnown; return d->m_iconName; } /** * Returns true if this is a desktop file. * Mimetype determination is optional. */ static bool checkDesktopFile(const KFileItem &item, bool _determineMimeType) { // only local files bool isLocalUrl; item.mostLocalUrl(&isLocalUrl); if (!isLocalUrl) { return false; } // only regular files if (!item.isRegularFile()) { return false; } // only if readable if (!item.isReadable()) { return false; } // return true if desktop file QMimeType mime = _determineMimeType ? item.determineMimeType() : item.currentMimeType(); return mime.inherits(QStringLiteral("application/x-desktop")); } QStringList KFileItem::overlays() const { if (!d) { return QStringList(); } d->ensureInitialized(); QStringList names = d->m_entry.stringValue(KIO::UDSEntry::UDS_ICON_OVERLAY_NAMES).split(QLatin1Char(','), QString::SkipEmptyParts); if (d->m_bLink) { names.append(QStringLiteral("emblem-symbolic-link")); } if (!isReadable()) { names.append(QStringLiteral("emblem-locked")); } if (checkDesktopFile(*this, false)) { KDesktopFile cfg(localPath()); const KConfigGroup group = cfg.desktopGroup(); // Add a warning emblem if this is an executable desktop file // which is untrusted. if (group.hasKey("Exec") && !KDesktopFile::isAuthorizedDesktopFile(localPath())) { names.append(QStringLiteral("emblem-important")); } if (cfg.hasDeviceType()) { const QString dev = cfg.readDevice(); if (!dev.isEmpty()) { KMountPoint::Ptr mountPoint = KMountPoint::currentMountPoints().findByDevice(dev); if (mountPoint) { // mounted? names.append(QStringLiteral("emblem-mounted")); } } } } if (isHidden()) { names.append(QStringLiteral("hidden")); } #ifndef Q_OS_WIN if (isDir()) { bool isLocalUrl; const QUrl url = mostLocalUrl(&isLocalUrl); if (isLocalUrl) { const QString path = url.toLocalFile(); if (KSambaShare::instance()->isDirectoryShared(path) || KNFSShare::instance()->isDirectoryShared(path)) { names.append(QStringLiteral("emblem-shared")); } } } #endif // Q_OS_WIN return names; } QString KFileItem::comment() const { if (!d) { return QString(); } return d->m_entry.stringValue(KIO::UDSEntry::UDS_COMMENT); } bool KFileItem::isReadable() const { if (!d) { return false; } d->ensureInitialized(); /* struct passwd * user = getpwuid( geteuid() ); bool isMyFile = (QString::fromLocal8Bit(user->pw_name) == d->m_user); // This gets ugly for the group.... // Maybe we want a static QString for the user and a static QStringList // for the groups... then we need to handle the deletion properly... */ if (d->m_permissions != KFileItem::Unknown) { const mode_t readMask = S_IRUSR | S_IRGRP | S_IROTH; // No read permission at all if ((d->m_permissions & readMask) == 0) { return false; } // Read permissions for all: save a stat call if ((d->m_permissions & readMask) == readMask) { return true; } } // Or if we can't read it - not network transparent if (d->m_bIsLocalUrl && !QFileInfo(d->m_url.toLocalFile()).isReadable()) { return false; } return true; } bool KFileItem::isWritable() const { if (!d) { return false; } d->ensureInitialized(); /* struct passwd * user = getpwuid( geteuid() ); bool isMyFile = (QString::fromLocal8Bit(user->pw_name) == d->m_user); // This gets ugly for the group.... // Maybe we want a static QString for the user and a static QStringList // for the groups... then we need to handle the deletion properly... */ if (d->m_permissions != KFileItem::Unknown) { // No write permission at all if (!(S_IWUSR & d->m_permissions) && !(S_IWGRP & d->m_permissions) && !(S_IWOTH & d->m_permissions)) { return false; } } // Or if we can't write it - not network transparent if (d->m_bIsLocalUrl && !QFileInfo(d->m_url.toLocalFile()).isWritable()) { return false; } return true; } bool KFileItem::isHidden() const { if (!d) { return false; } // The kioslave can specify explicitly that a file is hidden or shown if (d->m_hidden != KFileItemPrivate::Auto) { return d->m_hidden == KFileItemPrivate::Hidden; } // Prefer the filename that is part of the URL, in case the display name is different. QString fileName = d->m_url.fileName(); if (fileName.isEmpty()) { // e.g. "trash:/" fileName = d->m_strName; } return fileName.length() > 1 && fileName[0] == QLatin1Char('.'); // Just "." is current directory, not hidden. } void KFileItem::setHidden() { if (d) { d->m_hidden = KFileItemPrivate::Hidden; } } bool KFileItem::isDir() const { if (!d) { return false; } if (d->m_bSkipMimeTypeFromContent) { return false; } d->ensureInitialized(); if (d->m_fileMode == KFileItem::Unknown) { // Probably the file was deleted already, and KDirLister hasn't told the world yet. //qDebug() << d << url() << "can't say -> false"; return false; // can't say for sure, so no } return (d->m_fileMode & QT_STAT_MASK) == QT_STAT_DIR; } bool KFileItem::isFile() const { if (!d) { return false; } return !isDir(); } -#ifndef KIOCORE_NO_DEPRECATED bool KFileItem::acceptsDrops() const { // A directory ? if (isDir()) { return isWritable(); } // But only local .desktop files and executables if (!d->m_bIsLocalUrl) { return false; } if (mimetype() == QLatin1String("application/x-desktop")) { return true; } // Executable, shell script ... ? if (QFileInfo(d->m_url.toLocalFile()).isExecutable()) { return true; } return false; } -#endif QString KFileItem::getStatusBarInfo() const { if (!d) { return QString(); } QString text = d->m_strText; const QString comment = mimeComment(); if (d->m_bLink) { text += QLatin1Char(' '); if (comment.isEmpty()) { text += i18n("(Symbolic Link to %1)", linkDest()); } else { text += i18n("(%1, Link to %2)", comment, linkDest()); } } else if (targetUrl() != url()) { text += i18n(" (Points to %1)", targetUrl().toDisplayString()); } else if ((d->m_fileMode & QT_STAT_MASK) == QT_STAT_REG) { text += QStringLiteral(" (%1, %2)").arg(comment, KIO::convertSize(size())); } else { text += QStringLiteral(" (%1)").arg(comment); } return text; } bool KFileItem::cmp(const KFileItem &item) const { if (!d && !item.d) { return true; } if (!d || !item.d) { return false; } return d->cmp(*item.d); } bool KFileItem::operator==(const KFileItem &other) const { if (!d && !other.d) { return true; } if (!d || !other.d) { return false; } return d->m_url == other.d->m_url; } bool KFileItem::operator!=(const KFileItem &other) const { return !operator==(other); } bool KFileItem::operator<(const KFileItem &other) const { if (!other.d) { return false; } if (!d) { return other.d->m_url.isValid(); } return d->m_url < other.d->m_url; } bool KFileItem::operator<(const QUrl &other) const { if (!d) { return other.isValid(); } return d->m_url < other; } KFileItem::operator QVariant() const { return QVariant::fromValue(*this); } QString KFileItem::permissionsString() const { if (!d) { return QString(); } d->ensureInitialized(); if (d->m_access.isNull() && d->m_permissions != KFileItem::Unknown) { d->m_access = d->parsePermissions(d->m_permissions); } return d->m_access; } // check if we need to cache this QString KFileItem::timeString(FileTimes which) const { if (!d) { return QString(); } return d->time(which).toString(); } -#ifndef KIOCORE_NO_DEPRECATED QString KFileItem::timeString(unsigned int which) const { if (!d) { return QString(); } switch (which) { case KIO::UDSEntry::UDS_ACCESS_TIME: return timeString(AccessTime); case KIO::UDSEntry::UDS_CREATION_TIME: return timeString(CreationTime); case KIO::UDSEntry::UDS_MODIFICATION_TIME: default: return timeString(ModificationTime); } } -#endif -#ifndef KIOCORE_NO_DEPRECATED void KFileItem::assign(const KFileItem &item) { *this = item; } -#endif QUrl KFileItem::mostLocalUrl(bool *local) const { if (!d) { return QUrl(); } const QString local_path = localPath(); if (!local_path.isEmpty()) { if (local) { *local = true; } return QUrl::fromLocalFile(local_path); } else { if (local) { *local = d->m_bIsLocalUrl; } return d->m_url; } } QDataStream &operator<< (QDataStream &s, const KFileItem &a) { if (a.d) { // We don't need to save/restore anything that refresh() invalidates, // since that means we can re-determine those by ourselves. s << a.d->m_url; s << a.d->m_strName; s << a.d->m_strText; } else { s << QUrl(); s << QString(); s << QString(); } return s; } QDataStream &operator>> (QDataStream &s, KFileItem &a) { QUrl url; QString strName, strText; s >> url; s >> strName; s >> strText; if (!a.d) { qCWarning(KIO_CORE) << "null item"; return s; } if (url.isEmpty()) { a.d = nullptr; return s; } a.d->m_url = url; a.d->m_strName = strName; a.d->m_strText = strText; a.d->m_bIsLocalUrl = a.d->m_url.isLocalFile(); a.d->m_bMimeTypeKnown = false; a.refresh(); return s; } QUrl KFileItem::url() const { if (!d) { return QUrl(); } return d->m_url; } mode_t KFileItem::permissions() const { if (!d) { return 0; } d->ensureInitialized(); return d->m_permissions; } mode_t KFileItem::mode() const { if (!d) { return 0; } d->ensureInitialized(); return d->m_fileMode; } bool KFileItem::isLink() const { if (!d) { return false; } d->ensureInitialized(); return d->m_bLink; } bool KFileItem::isLocalFile() const { if (!d) { return false; } return d->m_bIsLocalUrl; } QString KFileItem::text() const { if (!d) { return QString(); } return d->m_strText; } QString KFileItem::name(bool lowerCase) const { if (!d) { return QString(); } if (!lowerCase) { return d->m_strName; } else if (d->m_strLowerCaseName.isNull()) { d->m_strLowerCaseName = d->m_strName.toLower(); } return d->m_strLowerCaseName; } QUrl KFileItem::targetUrl() const { if (!d) { return QUrl(); } const QString targetUrlStr = d->m_entry.stringValue(KIO::UDSEntry::UDS_TARGET_URL); if (!targetUrlStr.isEmpty()) { return QUrl(targetUrlStr); } else { return url(); } } /* * Mimetype handling. * * Initial state: m_mimeType = QMimeType(). * When currentMimeType() is called first: fast mimetype determination, * might either find an accurate mimetype (-> Final state), otherwise we * set m_mimeType but not m_bMimeTypeKnown (-> Intermediate state) * Intermediate state: determineMimeType() does the real determination -> Final state. * * If delayedMimeTypes isn't set, then we always go to the Final state directly. */ QMimeType KFileItem::currentMimeType() const { if (!d) { return QMimeType(); } if (!d->m_mimeType.isValid()) { // On-demand fast (but not always accurate) mimetype determination Q_ASSERT(!d->m_url.isEmpty()); QMimeDatabase db; if (isDir()) { d->m_mimeType = db.mimeTypeForName(QStringLiteral("inode/directory")); return d->m_mimeType; } const QUrl url = mostLocalUrl(); if (d->m_delayedMimeTypes) { const QList mimeTypes = db.mimeTypesForFileName(url.path()); if (mimeTypes.isEmpty()) { d->m_mimeType = db.mimeTypeForName(QStringLiteral("application/octet-stream")); d->m_bMimeTypeKnown = false; } else { d->m_mimeType = mimeTypes.first(); // If there were conflicting globs. determineMimeType will be able to do better. d->m_bMimeTypeKnown = (mimeTypes.count() == 1); } } else { // ## d->m_fileMode isn't used anymore (for remote urls) d->determineMimeTypeHelper(url); d->m_bMimeTypeKnown = true; } } return d->m_mimeType; } KIO::UDSEntry KFileItem::entry() const { if (!d) { return KIO::UDSEntry(); } d->ensureInitialized(); return d->m_entry; } bool KFileItem::isNull() const { return d == nullptr; } KFileItemList::KFileItemList() { } KFileItemList::KFileItemList(const QList &items) : QList(items) { } KFileItem KFileItemList::findByName(const QString &fileName) const { const_iterator it = begin(); const const_iterator itend = end(); for (; it != itend; ++it) { if ((*it).name() == fileName) { return *it; } } return KFileItem(); } KFileItem KFileItemList::findByUrl(const QUrl &url) const { const_iterator it = begin(); const const_iterator itend = end(); for (; it != itend; ++it) { if ((*it).url() == url) { return *it; } } return KFileItem(); } QList KFileItemList::urlList() const { QList lst; const_iterator it = begin(); const const_iterator itend = end(); for (; it != itend; ++it) { lst.append((*it).url()); } return lst; } QList KFileItemList::targetUrlList() const { QList lst; const_iterator it = begin(); const const_iterator itend = end(); for (; it != itend; ++it) { lst.append((*it).targetUrl()); } return lst; } bool KFileItem::isDesktopFile() const { return checkDesktopFile(*this, true); } bool KFileItem::isRegularFile() const { if (!d) { return false; } d->ensureInitialized(); return (d->m_fileMode & QT_STAT_MASK) == QT_STAT_REG; } QDebug operator<<(QDebug stream, const KFileItem &item) { QDebugStateSaver saver(stream); stream.nospace(); if (item.isNull()) { stream << "[null KFileItem]"; } else { stream << "[KFileItem for " << item.url() << "]"; } return stream; } diff --git a/src/core/kfileitem.h b/src/core/kfileitem.h index e080b6e6..4987de11 100644 --- a/src/core/kfileitem.h +++ b/src/core/kfileitem.h @@ -1,624 +1,630 @@ /* This file is part of the KDE project Copyright (C) 1999-2006 David Faure This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef KFILEITEM_H #define KFILEITEM_H #include "kiocore_export.h" #include #include #include #include #include #include #include #include #include class KFileItemPrivate; /** * @class KFileItem kfileitem.h * * A KFileItem is a generic class to handle a file, local or remote. * In particular, it makes it easier to handle the result of KIO::listDir * (UDSEntry isn't very friendly to use). * It includes many file attributes such as mimetype, icon, text, mode, link... * * KFileItem is implicitly shared, i.e. it can be used as a value and copied around at almost no cost. */ class KIOCORE_EXPORT KFileItem { public: enum { Unknown = static_cast(-1) }; /** * The timestamps associated with a file. * - ModificationTime: the time the file's contents were last modified * - AccessTime: the time the file was last accessed (last read or written to) * - CreationTime: the time the file was created */ enum FileTimes { // warning: don't change without looking at the Private class ModificationTime = 0, AccessTime = 1, CreationTime = 2 //ChangeTime }; enum MimeTypeDetermination { NormalMimeTypeDetermination = 0, SkipMimeTypeFromContent }; /** * Null KFileItem. Doesn't represent any file, only exists for convenience. */ KFileItem(); /** * Creates an item representing a file, from a UDSEntry. * This is the preferred constructor when using KIO::listDir(). * * @param entry the KIO entry used to get the file, contains info about it * @param itemOrDirUrl the URL of the item or of the directory containing this item (see urlIsDirectory). * @param delayedMimeTypes specifies if the mimetype of the given * URL should be determined immediately or on demand. * See the bool delayedMimeTypes in the KDirLister constructor. * @param urlIsDirectory specifies if the url is just the directory of the * fileitem and the filename from the UDSEntry should be used. * * When creating KFileItems out of the UDSEntry emitted by a KIO list job, * use KFileItem(entry, listjob->url(), delayedMimeTypes, true); */ KFileItem(const KIO::UDSEntry &entry, const QUrl &itemOrDirUrl, bool delayedMimeTypes = false, bool urlIsDirectory = false); +#if KIOCORE_ENABLE_DEPRECATED_SINCE(5, 0) /** * Creates an item representing a file, from all the necessary info for it. * @param mode the file mode (according to stat() (e.g. S_IFDIR...) * Set to KFileItem::Unknown if unknown. For local files, KFileItem will use stat(). * @param permissions the access permissions * If you set both the mode and the permissions, you save a ::stat() for * local files. * Set to KFileItem::Unknown if you don't know the mode or the permission. * @param url the file url * * @param delayedMimeTypes specify if the mimetype of the given URL * should be determined immediately or on demand * @deprecated since 5.0. Most callers gave Unknown for mode and permissions, * so just port to KFileItem(url) and setDelayedMimeTypes(true) if necessary. */ -#ifndef KIOCORE_NO_DEPRECATED - KIOCORE_DEPRECATED KFileItem(mode_t mode, mode_t permissions, const QUrl &url, - bool delayedMimeTypes = false); + KIOCORE_DEPRECATED_VERSION(5, 0, "See API docs") + KFileItem(mode_t mode, mode_t permissions, const QUrl &url, + bool delayedMimeTypes = false); #endif /** * Creates an item representing a file, for which the mimetype is already known. * @param url the file url * @param mimeType the name of the file's mimetype * @param mode the mode (S_IFDIR...) */ KFileItem(const QUrl &url, const QString &mimeType = QString(), mode_t mode = KFileItem::Unknown); // KF6 TODO: explicit! /** * Creates an item representing a file, with the option of skipping mime type determination. * @param url the file url * @param mimeTypeDetermination the mode of determining the mime type: * NormalMimeTypeDetermination by content if local file, i.e. access the file, * open and read part of it; * by QMimeDatabase::MatchMode::MatchExtension if not local. * SkipMimeTypeFromContent always by QMimeDatabase::MatchMode::MatchExtension, * i.e. won't access the file by stat() or opening it; * only suitable for files, directories won't be recognized. * @since 5.57 */ KFileItem(const QUrl &url, KFileItem::MimeTypeDetermination mimeTypeDetermination); /** * Copy constructor */ KFileItem(const KFileItem&); /** * Destructor */ ~KFileItem(); /** * Move constructor * @since 5.43 */ KFileItem(KFileItem&&); /** * Copy assignment */ KFileItem& operator=(const KFileItem&); /** * Move assignment * @since 5.43 */ KFileItem& operator=(KFileItem&&); /** * Throw away and re-read (for local files) all information about the file. * This is called when the _file_ changes. */ void refresh(); /** * Re-reads mimetype information. * This is called when the mimetype database changes. */ void refreshMimeType(); /** * Sets mimetype determination to be immediate or on demand. * Call this after the constructor, and before using any mimetype-related method. * @since 5.0 */ void setDelayedMimeTypes(bool b); /** * Returns the url of the file. * @return the url of the file */ QUrl url() const; /** * Sets the item's URL. Do not call unless you know what you are doing! * (used for example when an item got renamed). * @param url the item's URL */ void setUrl(const QUrl &url); /** * Sets the item's local path (UDS_LOCAL_PATH). Do not call unless you know what you are doing! * This won't change the item's name or URL. * (used for example when an item got renamed). * @param path the item's local path * @since 5.20 */ void setLocalPath(const QString &path); /** * Sets the item's name (i.e. the filename). * This is automatically done by setUrl, to set the name from the URL's fileName(). * This method is provided for some special cases like relative paths as names (KFindPart) * @param name the item's name */ void setName(const QString &name); /** * Returns the permissions of the file (stat.st_mode containing only permissions). * @return the permissions of the file */ mode_t permissions() const; /** * Returns the access permissions for the file as a string. * @return the access permission as string */ QString permissionsString() const; /** * Tells if the file has extended access level information ( Posix ACL ) * @return true if the file has extend ACL information or false if it hasn't */ bool hasExtendedACL() const; /** * Returns the access control list for the file. * @return the access control list as a KACL */ KACL ACL() const; /** * Returns the default access control list for the directory. * @return the default access control list as a KACL */ KACL defaultACL() const; /** * Returns the file type (stat.st_mode containing only S_IFDIR, S_IFLNK, ...). * @return the file type */ mode_t mode() const; /** * Returns the owner of the file. * @return the file's owner */ QString user() const; /** * Returns the group of the file. * @return the file's group */ QString group() const; /** * Returns true if this item represents a link in the UNIX sense of * a link. * @return true if the file is a link */ bool isLink() const; /** * Returns true if this item represents a directory. * @return true if the item is a directory */ bool isDir() const; /** * Returns true if this item represents a file (and not a directory) * @return true if the item is a file */ bool isFile() const; /** * Checks whether the file or directory is readable. In some cases * (remote files), we may return true even though it can't be read. * @return true if the file can be read - more precisely, * false if we know for sure it can't */ bool isReadable() const; /** * Checks whether the file or directory is writable. In some cases * (remote files), we may return true even though it can't be written to. * @return true if the file or directory can be written to - more precisely, * false if we know for sure it can't */ bool isWritable() const; /** * Checks whether the file is hidden. * @return true if the file is hidden. */ bool isHidden() const; /** * @return true if the file is a remote URL, or a local file on a network mount. * It will return false only for really-local file systems. * @since 4.7.4 */ bool isSlow() const; /** * Checks whether the file is a readable local .desktop file, * i.e. a file whose path can be given to KDesktopFile * @return true if the file is a desktop file. * @since 4.1 */ bool isDesktopFile() const; /** * Returns the link destination if isLink() == true. * @return the link destination. QString() if the item is not a link */ QString linkDest() const; /** * Returns the target url of the file, which is the same as url() * in cases where the slave doesn't specify UDS_TARGET_URL * @return the target url. * @since 4.1 */ QUrl targetUrl() const; /** * Returns the local path if isLocalFile() == true or the KIO item has * a UDS_LOCAL_PATH atom. * @return the item local path, or QString() if not known */ QString localPath() const; /** * Returns the size of the file, if known. * @return the file size, or 0 if not known */ KIO::filesize_t size() const; /** * Requests the modification, access or creation time, depending on @p which. * @param which the timestamp * @return the time asked for, QDateTime() if not available * @see timeString() */ QDateTime time(FileTimes which) const; /** * Requests the modification, access or creation time as a string, depending * on @p which. * @param which the timestamp * @returns a formatted string of the requested time. * @see time */ QString timeString(FileTimes which = ModificationTime) const; -#ifndef KIOCORE_NO_DEPRECATED - KIOCORE_DEPRECATED QString timeString(unsigned int which) const; + +#if KIOCORE_ENABLE_DEPRECATED_SINCE(4, 0) + KIOCORE_DEPRECATED_VERSION(4, 0, "Use KFileItem::timeString(FileTimes)") + QString timeString(unsigned int which) const; #endif /** * Returns true if the file is a local file. * @return true if the file is local, false otherwise */ bool isLocalFile() const; /** * Returns the text of the file item. * It's not exactly the filename since some decoding happens ('%2F'->'/'). * @return the text of the file item */ QString text() const; /** * Return the name of the file item (without a path). * Similar to text(), but unencoded, i.e. the original name. * @param lowerCase if true, the name will be returned in lower case, * which is useful to speed up sorting by name, case insensitively. * @return the file's name */ QString name(bool lowerCase = false) const; /** * Returns the mimetype of the file item. * If @p delayedMimeTypes was used in the constructor, this will determine * the mimetype first. Equivalent to determineMimeType()->name() * @return the mime type of the file */ QString mimetype() const; /** * Returns the mimetype of the file item. * If delayedMimeTypes was used in the constructor, this will determine * the mimetype first. * @return the mime type */ QMimeType determineMimeType() const; /** * Returns the currently known mimetype of the file item. * This will not try to determine the mimetype if unknown. * @return the known mime type */ QMimeType currentMimeType() const; /** * @return true if we have determined the final icon of this file already. * @since 4.10.2 */ bool isFinalIconKnown() const; /** * @return true if we have determined the mimetype of this file already, * i.e. if determineMimeType() will be fast. Otherwise it will have to * find what the mimetype is, which is a possibly slow operation; usually * this is delayed until necessary. */ bool isMimeTypeKnown() const; /** * Returns the user-readable string representing the type of this file, * like "OpenDocument Text File". * @return the type of this KFileItem */ QString mimeComment() const; /** * Returns the full path name to the icon that represents * this mime type. * @return iconName the name of the file's icon */ QString iconName() const; /** * Returns the overlays (bitfield of KIconLoader::*Overlay flags) that are used * for this item's pixmap. Overlays are used to show for example, whether * a file can be modified. * @return the overlays of the pixmap */ QStringList overlays() const; /** * A comment which can contain anything - even rich text. It will * simply be displayed to the user as is. * * @since 4.6 */ QString comment() const; /** * Returns the string to be displayed in the statusbar, * e.g. when the mouse is over this item * @return the status bar information */ QString getStatusBarInfo() const; +#if KIOCORE_ENABLE_DEPRECATED_SINCE(4, 0) /** * Returns true if files can be dropped over this item. * Contrary to popular belief, not only dirs will return true :) * Executables, .desktop files, will do so as well. * @return true if you can drop files over the item * - * @deprecated This logic is application-dependent, the behavior described above + * @deprecated Since 4.0. This logic is application-dependent, the behavior described above * mostly makes sense for file managers only. * KDirModel has setDropsAllowed for similar (but configurable) logic. */ -#ifndef KIOCORE_NO_DEPRECATED - KIOCORE_DEPRECATED bool acceptsDrops() const; + KIOCORE_DEPRECATED_VERSION(4, 0, "See API docs") + bool acceptsDrops() const; #endif /** * Returns the UDS entry. Used by the tree view to access all details * by position. * @return the UDS entry */ KIO::UDSEntry entry() const; /** * Return true if this item is a regular file, * false otherwise (directory, link, character/block device, fifo, socket) * @since 4.3 */ bool isRegularFile() const; /** * Somewhat like a comparison operator, but more explicit, * and it can detect that two fileitems differ if any property of the file item * has changed (file size, modification date, etc.). Two items are equal if * all properties are equal. In contrast, operator== only compares URLs. * @param item the item to compare * @return true if all values are equal */ bool cmp(const KFileItem &item) const; /** * Returns true if both items share the same URL. */ bool operator==(const KFileItem &other) const; /** * Returns true if both items do not share the same URL. */ bool operator!=(const KFileItem &other) const; /** * Returns true if this item's URL is lexically less than other's URL; otherwise returns false * @since 5.48 */ bool operator<(const KFileItem &other) const; /** * Returns true if this item's URL is lexically less than url other; otherwise returns false * @since 5.48 */ bool operator<(const QUrl &other) const; /** * Converts this KFileItem to a QVariant, this allows to use KFileItem * in QVariant() constructor */ operator QVariant() const; +#if KIOCORE_ENABLE_DEPRECATED_SINCE(4, 0) /** - * @deprecated simply use '=' + * @deprecated Since 4.0, simply use '=' */ -#ifndef KIOCORE_NO_DEPRECATED - KIOCORE_DEPRECATED void assign(const KFileItem &item); + KIOCORE_DEPRECATED_VERSION(4, 0, "Use KFileItem::operator=(const KFileItem&)") + void assign(const KFileItem &item); #endif /** * Tries to give a local URL for this file item if possible. * The given boolean indicates if the returned url is local or not. * \since 4.6 */ QUrl mostLocalUrl(bool *local = nullptr) const; +#if KIOCORE_ENABLE_DEPRECATED_SINCE(5, 0) /** * @deprecated since 5.0 add '&' in front of your boolean argument */ -#ifndef KIOCORE_NO_DEPRECATED + KIOCORE_DEPRECATED_VERSION(5, 0, "Use KFileItem::mostLocalUrl(bool *)") QUrl mostLocalUrl(bool &local) const { return mostLocalUrl(&local); } #endif /** * Return true if default-constructed */ bool isNull() const; private: QSharedDataPointer d; /** * Hides the file. */ void setHidden(); private: KIOCORE_EXPORT friend QDataStream &operator<< (QDataStream &s, const KFileItem &a); KIOCORE_EXPORT friend QDataStream &operator>> (QDataStream &s, KFileItem &a); friend class KFileItemTest; friend class KCoreDirListerCache; }; Q_DECLARE_METATYPE(KFileItem) Q_DECLARE_TYPEINFO(KFileItem, Q_MOVABLE_TYPE); inline uint qHash(const KFileItem &item) { return qHash(item.url()); } /** * @class KFileItemList kfileitem.h * * List of KFileItems, which adds a few helper * methods to QList. */ class KIOCORE_EXPORT KFileItemList : public QList { public: /// Creates an empty list of file items. KFileItemList(); /// Creates a new KFileItemList from a QList of file @p items. KFileItemList(const QList &items); /** * Find a KFileItem by name and return it. * @return the item with the given name, or a null-item if none was found * (see KFileItem::isNull()) */ KFileItem findByName(const QString &fileName) const; /** * Find a KFileItem by URL and return it. * @return the item with the given URL, or a null-item if none was found * (see KFileItem::isNull()) */ KFileItem findByUrl(const QUrl &url) const; /// @return the list of URLs that those items represent QList urlList() const; /// @return the list of target URLs that those items represent /// @since 4.2 QList targetUrlList() const; // TODO KDE-5 add d pointer here so that we can merge KFileItemListProperties into KFileItemList }; KIOCORE_EXPORT QDataStream &operator<< (QDataStream &s, const KFileItem &a); KIOCORE_EXPORT QDataStream &operator>> (QDataStream &s, KFileItem &a); /** * Support for qDebug() << aFileItem * \since 4.4 */ KIOCORE_EXPORT QDebug operator<<(QDebug stream, const KFileItem &item); #endif diff --git a/src/core/krecentdocument.h b/src/core/krecentdocument.h index 680dac95..cc8c400c 100644 --- a/src/core/krecentdocument.h +++ b/src/core/krecentdocument.h @@ -1,120 +1,121 @@ /* -*- c++ -*- * Copyright (C)2000 Daniel M. Duley * * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. * */ #ifndef __KRECENTDOCUMENT_H #define __KRECENTDOCUMENT_H #include "kiocore_export.h" #include #include /** * @class KRecentDocument krecentdocument.h * * Manage the "Recent Document Menu" entries displayed by * applications such as Kicker and Konqueror. * * These entries are automatically generated .desktop files pointing * to the current application and document. You should call the * static add() method whenever the user opens or saves a new * document if you want it to show up in the menu. * * You don't have to worry about this if you are using * QFileDialog to open and save documents, as the KDE implementation * (KFileWidget) already calls this class. User defined limits on the maximum * number of documents to save, etc... are all automatically handled. * * @author Daniel M. Duley */ class KIOCORE_EXPORT KRecentDocument { public: /** * * Return a list of absolute paths to recent document .desktop files, * sorted by date. * */ static QStringList recentDocuments(); /** * Add a new item to the Recent Document menu. * * @param url The url to add. */ static void add(const QUrl &url); /** * Add a new item to the Recent Document menu, specifying the application to open it with. * The above add() method uses QCoreApplication::applicationName() for the app name, * which isn't always flexible enough. * This method is used when an application launches another one to open a document. * * @param url The url to add. * @param desktopEntryName The desktopEntryName of the service to use for opening this document. */ static void add(const QUrl &url, const QString &desktopEntryName); +#if KIOCORE_ENABLE_DEPRECATED_SINCE(5, 0) /** * * Add a new item to the Recent Document menu. Calls add( url ). * * @param documentStr The full path to the document or URL to add. * @param isURL Set to @p true if @p documentStr is an URL and not a local file path. - * @deprecated call add(QUrl(str)) if isURL=true, and add(QUrl::fromLocalFile(str)) if isURL=false. + * @deprecated Since 5.0, call add(QUrl(str)) if isURL=true, and add(QUrl::fromLocalFile(str)) if isURL=false. */ -#ifndef KIOCORE_NO_DEPRECATED - static KIOCORE_DEPRECATED void add(const QString &documentStr, bool isUrl = false) + KIOCORE_DEPRECATED_VERSION(5, 0, "Use KRecentDocument::add(const QUrl &)") + static void add(const QString &documentStr, bool isUrl = false) { if (isUrl) { add(QUrl(documentStr)); } else { add(QUrl::fromLocalFile(documentStr)); } } #endif /** * Clear the recent document menu of all entries. */ static void clear(); /** * Returns the maximum amount of recent document entries allowed. */ static int maximumItems(); /** * Returns the path to the directory where recent document .desktop files * are stored. */ static QString recentDocumentDirectory(); }; #endif diff --git a/src/core/ksambashare.cpp b/src/core/ksambashare.cpp index e09d6cf6..92b7d71f 100644 --- a/src/core/ksambashare.cpp +++ b/src/core/ksambashare.cpp @@ -1,526 +1,524 @@ /* This file is part of the KDE project Copyright (c) 2004 Jan Schaefer Copyright 2010 Rodrigo Belem This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License version 2 as published by the Free Software Foundation. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "ksambashare.h" #include "ksambashare_p.h" #include "ksambasharedata.h" #include "ksambasharedata_p.h" #include "kiocoredebug.h" #include #include #include #include #include #include #include #include #include #include #include Q_DECLARE_LOGGING_CATEGORY(KIO_CORE_SAMBASHARE) Q_LOGGING_CATEGORY(KIO_CORE_SAMBASHARE, "kf5.kio.core.sambashare", QtWarningMsg) // Default smb.conf locations // sorted by priority, most priority first static const char *const DefaultSambaConfigFilePathList[] = { "/etc/samba/smb.conf", "/etc/smb.conf", "/usr/local/etc/smb.conf", "/usr/local/samba/lib/smb.conf", "/usr/samba/lib/smb.conf", "/usr/lib/smb.conf", "/usr/local/lib/smb.conf" }; KSambaSharePrivate::KSambaSharePrivate(KSambaShare *parent) : q_ptr(parent) , data() , smbConf() , userSharePath() , skipUserShare(false) { setUserSharePath(); findSmbConf(); data = parse(getNetUserShareInfo()); } KSambaSharePrivate::~KSambaSharePrivate() { } bool KSambaSharePrivate::isSambaInstalled() { if (QFile::exists(QStringLiteral("/usr/sbin/smbd")) || QFile::exists(QStringLiteral("/usr/local/sbin/smbd"))) { return true; } //qDebug() << "Samba is not installed!"; return false; } // Try to find the samba config file path // in several well-known paths bool KSambaSharePrivate::findSmbConf() { for (const char *str : DefaultSambaConfigFilePathList) { const QString filePath = QString::fromLatin1(str); if (QFile::exists(filePath)) { smbConf = filePath; return true; } } qCDebug(KIO_CORE_SAMBASHARE) << "KSambaShare: Could not find smb.conf!"; return false; } void KSambaSharePrivate::setUserSharePath() { const QString rawString = testparmParamValue(QStringLiteral("usershare path")); const QFileInfo fileInfo(rawString); if (fileInfo.isDir()) { userSharePath = rawString; } } int KSambaSharePrivate::runProcess(const QString &progName, const QStringList &args, QByteArray &stdOut, QByteArray &stdErr) { QProcess process; process.setProcessChannelMode(QProcess::SeparateChannels); process.start(progName, args); //TODO: make it async in future process.waitForFinished(); stdOut = process.readAllStandardOutput(); stdErr = process.readAllStandardError(); return process.exitCode(); } QString KSambaSharePrivate::testparmParamValue(const QString ¶meterName) { if (!isSambaInstalled()) { return QString(); } QByteArray stdErr; QByteArray stdOut; const QStringList args{ QStringLiteral("-d0"), QStringLiteral("-s"), QStringLiteral("--parameter-name"), parameterName, }; runProcess(QStringLiteral("testparm"), args, stdOut, stdErr); //TODO: parse and process error messages. // create a parser for the error output and // send error message somewhere if (!stdErr.isEmpty()) { QList err; err << stdErr.trimmed().split('\n'); if ((err.count() == 2) && err.at(0).startsWith("Load smb config files from") && err.at(1).startsWith("Loaded services file OK.")) { //qDebug() << "Running testparm" << args; } else { qCWarning(KIO_CORE) << "We got some errors while running testparm" << stdErr; } } if (!stdOut.isEmpty()) { return QString::fromLocal8Bit(stdOut.trimmed()); } return QString(); } QByteArray KSambaSharePrivate::getNetUserShareInfo() { if (skipUserShare || !isSambaInstalled()) { return QByteArray(); } QByteArray stdOut; QByteArray stdErr; const QStringList args{ QStringLiteral("usershare"), QStringLiteral("info"), }; runProcess(QStringLiteral("net"), args, stdOut, stdErr); if (!stdErr.isEmpty()) { if (stdErr.contains("You do not have permission to create a usershare")) { skipUserShare = true; } else if (stdErr.contains("usershares are currently disabled")) { skipUserShare = true; } else { //TODO: parse and process other error messages. // create a parser for the error output and // send error message somewhere qCWarning(KIO_CORE) << "We got some errors while running 'net usershare info'"; qCWarning(KIO_CORE) << stdErr; } } return stdOut; } QStringList KSambaSharePrivate::shareNames() const { return data.keys(); } QStringList KSambaSharePrivate::sharedDirs() const { QStringList dirs; QMap::ConstIterator i; for (i = data.constBegin(); i != data.constEnd(); ++i) { if (!dirs.contains(i.value().path())) { dirs << i.value().path(); } } return dirs; } KSambaShareData KSambaSharePrivate::getShareByName(const QString &shareName) const { return data.value(shareName); } QList KSambaSharePrivate::getSharesByPath(const QString &path) const { QList shares; QMap::ConstIterator i; for (i = data.constBegin(); i != data.constEnd(); ++i) { if (i.value().path() == path) { shares << i.value(); } } return shares; } bool KSambaSharePrivate::isShareNameValid(const QString &name) const { // Samba forbidden chars const QRegExp notToMatchRx(QStringLiteral("[%<>*\?|/\\+=;:\",]")); return (notToMatchRx.indexIn(name) == -1); } bool KSambaSharePrivate::isDirectoryShared(const QString &path) const { QMap::ConstIterator i; for (i = data.constBegin(); i != data.constEnd(); ++i) { if (i.value().path() == path) { return true; } } return false; } bool KSambaSharePrivate::isShareNameAvailable(const QString &name) const { // Samba does not allow to name a share with a user name registered in the system return (!KUser::allUserNames().contains(name) || !data.contains(name)); } KSambaShareData::UserShareError KSambaSharePrivate::isPathValid(const QString &path) const { QFileInfo pathInfo = path; if (!pathInfo.exists()) { return KSambaShareData::UserSharePathNotExists; } if (!pathInfo.isDir()) { return KSambaShareData::UserSharePathNotDirectory; } if (pathInfo.isRelative()) { if (pathInfo.makeAbsolute()) { return KSambaShareData::UserSharePathNotAbsolute; } } // TODO: check if the user is root if (KSambaSharePrivate::testparmParamValue(QStringLiteral("usershare owner only")) == QLatin1String("Yes")) { if (!pathInfo.permission(QFile::ReadUser | QFile::WriteUser)) { return KSambaShareData::UserSharePathNotAllowed; } } return KSambaShareData::UserSharePathOk; } KSambaShareData::UserShareError KSambaSharePrivate::isAclValid(const QString &acl) const { const QRegExp aclRx(QStringLiteral("(?:(?:(\\w(\\w|\\s)*)\\\\|)(\\w+\\s*):([fFrRd]{1})(?:,|))*")); // TODO: check if user is a valid smb user return aclRx.exactMatch(acl) ? KSambaShareData::UserShareAclOk : KSambaShareData::UserShareAclInvalid; } KSambaShareData::UserShareError KSambaSharePrivate::guestsAllowed(const KSambaShareData::GuestPermission &guestok) const { if (guestok == KSambaShareData::GuestsAllowed) { if (KSambaSharePrivate::testparmParamValue(QStringLiteral("usershare allow guests")) == QLatin1String("No")) { return KSambaShareData::UserShareGuestsNotAllowed; } } return KSambaShareData::UserShareGuestsOk; } KSambaShareData::UserShareError KSambaSharePrivate::add(const KSambaShareData &shareData) { // TODO: // * check for usershare max shares if (!isSambaInstalled()) { return KSambaShareData::UserShareSystemError; } QByteArray stdOut; QByteArray stdErr; if (data.contains(shareData.name())) { if (data.value(shareData.name()).path() != shareData.path()) { return KSambaShareData::UserShareNameInUse; } } else { // It needs to be added here, otherwise another instance of KSambaShareDataPrivate // will be created and added to data. data.insert(shareData.name(), shareData); } QString guestok = QStringLiteral("guest_ok=%1").arg( (shareData.guestPermission() == KSambaShareData::GuestsNotAllowed) ? QStringLiteral("n") : QStringLiteral("y")); const QStringList args{ QStringLiteral("usershare"), QStringLiteral("add"), shareData.name(), shareData.path(), shareData.comment(), shareData.acl(), guestok, }; int ret = runProcess(QStringLiteral("net"), args, stdOut, stdErr); //TODO: parse and process error messages. if (!stdErr.isEmpty()) { // create a parser for the error output and // send error message somewhere qCWarning(KIO_CORE) << "We got some errors while running 'net usershare add'" << args; qCWarning(KIO_CORE) << stdErr; } return (ret == 0) ? KSambaShareData::UserShareOk : KSambaShareData::UserShareSystemError; } KSambaShareData::UserShareError KSambaSharePrivate::remove(const KSambaShareData &shareData) const { if (!isSambaInstalled()) { return KSambaShareData::UserShareSystemError; } if (!data.contains(shareData.name())) { return KSambaShareData::UserShareNameInvalid; } const QStringList args{ QStringLiteral("usershare"), QStringLiteral("delete"), shareData.name(), }; int result = QProcess::execute(QStringLiteral("net"), args); return (result == 0) ? KSambaShareData::UserShareOk : KSambaShareData::UserShareSystemError; } QMap KSambaSharePrivate::parse(const QByteArray &usershareData) { const QRegExp headerRx(QString::fromLatin1("^\\s*\\[" "([^%<>*\?|/\\+=;:\",]+)" "\\]")); const QRegExp OptValRx(QString::fromLatin1("^\\s*([\\w\\d\\s]+)" "=" "(.*)$")); QTextStream stream(usershareData); QString currentShare; QMap shares; while (!stream.atEnd()) { const QString line = stream.readLine().trimmed(); if (headerRx.exactMatch(line)) { currentShare = headerRx.cap(1).trimmed(); if (!shares.contains(currentShare)) { KSambaShareData shareData; shareData.dd->name = currentShare; shares.insert(currentShare, shareData); } } else if (OptValRx.exactMatch(line)) { const QString key = OptValRx.cap(1).trimmed(); const QString value = OptValRx.cap(2).trimmed(); KSambaShareData shareData = shares[currentShare]; if (key == QLatin1String("path")) { // Samba accepts paths with and w/o trailing slash, we // use and expect path without slash if (value.endsWith(QLatin1Char('/'))) { shareData.dd->path = value.left(value.size() - 1); } else { shareData.dd->path = value; } } else if (key == QLatin1String("comment")) { shareData.dd->comment = value; } else if (key == QLatin1String("usershare_acl")) { shareData.dd->acl = value; } else if (key == QLatin1String("guest_ok")) { shareData.dd->guestPermission = value; } else { qCWarning(KIO_CORE) << "Something nasty happen while parsing 'net usershare info'" << "share:" << currentShare << "key:" << key; } } else if (line.trimmed().isEmpty()) { continue; } else { return shares; } } return shares; } void KSambaSharePrivate::_k_slotFileChange(const QString &path) { if (path != userSharePath) { return; } data = parse(getNetUserShareInfo()); //qDebug() << "path changed:" << path; Q_Q(KSambaShare); emit q->changed(); } KSambaShare::KSambaShare() : QObject(nullptr) , d_ptr(new KSambaSharePrivate(this)) { Q_D(const KSambaShare); if (!d->userSharePath.isEmpty() && QFileInfo::exists(d->userSharePath)) { KDirWatch::self()->addDir(d->userSharePath, KDirWatch::WatchFiles); connect(KDirWatch::self(), SIGNAL(dirty(QString)), this, SLOT(_k_slotFileChange(QString))); } } KSambaShare::~KSambaShare() { Q_D(const KSambaShare); if (KDirWatch::exists() && KDirWatch::self()->contains(d->userSharePath)) { KDirWatch::self()->removeDir(d->userSharePath); } delete d_ptr; } -#ifndef KIOCORE_NO_DEPRECATED QString KSambaShare::smbConfPath() const { Q_D(const KSambaShare); return d->smbConf; } -#endif bool KSambaShare::isDirectoryShared(const QString &path) const { Q_D(const KSambaShare); return d->isDirectoryShared(path); } bool KSambaShare::isShareNameAvailable(const QString &name) const { Q_D(const KSambaShare); return d->isShareNameValid(name) && d->isShareNameAvailable(name); } QStringList KSambaShare::shareNames() const { Q_D(const KSambaShare); return d->shareNames(); } QStringList KSambaShare::sharedDirectories() const { Q_D(const KSambaShare); return d->sharedDirs(); } KSambaShareData KSambaShare::getShareByName(const QString &name) const { Q_D(const KSambaShare); return d->getShareByName(name); } QList KSambaShare::getSharesByPath(const QString &path) const { Q_D(const KSambaShare); return d->getSharesByPath(path); } class KSambaShareSingleton { public: KSambaShare instance; }; Q_GLOBAL_STATIC(KSambaShareSingleton, _instance) KSambaShare *KSambaShare::instance() { return &_instance()->instance; } #include "moc_ksambashare.cpp" diff --git a/src/core/ksambashare.h b/src/core/ksambashare.h index 40d1667c..31d5ec7d 100644 --- a/src/core/ksambashare.h +++ b/src/core/ksambashare.h @@ -1,138 +1,139 @@ /* This file is part of the KDE project Copyright (c) 2004 Jan Schaefer Copyright 2010 Rodrigo Belem This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License version 2 as published by the Free Software Foundation. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef ksambashare_h #define ksambashare_h #include #include "kiocore_export.h" class KSambaShareData; class KSambaSharePrivate; /** * @class KSambaShare ksambashare.h * * This class lists Samba user shares and monitors them for addition, update and removal. * Singleton class, call instance() to get an instance. */ class KIOCORE_EXPORT KSambaShare : public QObject { Q_OBJECT public: /** * @return the one and only instance of KSambaShare. */ static KSambaShare *instance(); /** * Whether or not the given path is shared by Samba. * * @param path the path to check if it is shared by Samba. * * @return whether the given path is shared by Samba. */ bool isDirectoryShared(const QString &path) const; /** * Returns a list of all directories shared by local users in Samba. * The resulting list is not sorted. * * @return a list of all directories shared by Samba. */ QStringList sharedDirectories() const; /** * Tests that a share name is valid and does not conflict with system users names or shares. * * @param name the share name. * * @return whether the given name is already being used or not. * * @since 4.7 */ bool isShareNameAvailable(const QString &name) const; /** * Returns the list of available shares. * * @return @c a QStringList containing the user shares names. * @return @c an empty list if there aren't user shared directories. * * @since 4.7 */ QStringList shareNames() const; /** * Returns the KSambaShareData object of the share name. * * @param name the share name. * * @return @c the KSambaShareData object that matches the name. * @return @c an empty KSambaShareData object if there isn't match for the name. * * @since 4.7 */ KSambaShareData getShareByName(const QString &name) const; /** * Returns a list of KSambaShareData matching the path. * * @param path the path that wants to get KSambaShareData object. * * @return @c the QList of KSambaShareData objects that matches the path. * @return @c an empty QList if there aren't matches for the given path. * * @since 4.7 */ QList getSharesByPath(const QString &path) const; virtual ~KSambaShare(); +#if KIOCORE_ENABLE_DEPRECATED_SINCE(4, 6) /** * Returns the path to the used smb.conf file * or empty string if no file was found * * @return @c the path to the smb.conf file * - * @deprecated + * @deprecated Since 4.6, the conf file is no longer used */ -#ifndef KIOCORE_NO_DEPRECATED - KIOCORE_DEPRECATED QString smbConfPath() const; + KIOCORE_DEPRECATED_VERSION(4, 6, "Conf file no longer used") + QString smbConfPath() const; #endif Q_SIGNALS: /** * Emitted when a share is updated, added or removed */ void changed(); private: KSambaShare(); KSambaSharePrivate *const d_ptr; Q_DECLARE_PRIVATE(KSambaShare) friend class KSambaShareData; friend class KSambaShareSingleton; Q_PRIVATE_SLOT(d_func(), void _k_slotFileChange(const QString &)) }; #endif diff --git a/src/core/ksslcertificatemanager.h b/src/core/ksslcertificatemanager.h index daf38557..838376df 100644 --- a/src/core/ksslcertificatemanager.h +++ b/src/core/ksslcertificatemanager.h @@ -1,123 +1,139 @@ /* This file is part of the KDE project * * Copyright (C) 2007, 2008, 2010 Andreas Hartmetz * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef _INCLUDE_KSSLCERTIFICATEMANAGER_H #define _INCLUDE_KSSLCERTIFICATEMANAGER_H #include "ktcpsocket.h" // TODO KF6 remove include #include #include #include #include #include class QSslCertificate; class KSslCertificateRulePrivate; class KSslCertificateManagerPrivate; //### document this... :/ /** Certificate rule. */ class KIOCORE_EXPORT KSslCertificateRule { public: KSslCertificateRule(const QSslCertificate &cert = QSslCertificate(), const QString &hostName = QString()); KSslCertificateRule(const KSslCertificateRule &other); ~KSslCertificateRule(); KSslCertificateRule &operator=(const KSslCertificateRule &other); QSslCertificate certificate() const; QString hostName() const; void setExpiryDateTime(const QDateTime &dateTime); QDateTime expiryDateTime() const; void setRejected(bool rejected); bool isRejected() const; +#if KIOCORE_ENABLE_DEPRECATED_SINCE(5, 64) /** @deprecated since 5.64, use the QSslError variant. */ - KIOCORE_DEPRECATED bool isErrorIgnored(KSslError::Error error) const; + KIOCORE_DEPRECATED_VERSION(5, 64, "Use KSslCertificateRule::isErrorIgnored(QSslError::SslError)") + bool isErrorIgnored(KSslError::Error error) const; +#endif /** * Returns whether @p error is ignored for this certificate. * @since 5.64 */ bool isErrorIgnored(QSslError::SslError error) const; +#if KIOCORE_ENABLE_DEPRECATED_SINCE(5, 64) /** @deprecated since 5.64, use the QSslError variant. */ - KIOCORE_DEPRECATED void setIgnoredErrors(const QList &errors); + KIOCORE_DEPRECATED_VERSION(5, 64, "Use KSslCertificateRule::setIgnoredErrors(const QList &)") + void setIgnoredErrors(const QList &errors); /** @deprecated since 5.64, use the QSslError variant. */ - KIOCORE_DEPRECATED void setIgnoredErrors(const QList &errors); + KIOCORE_DEPRECATED_VERSION(5, 64, "Use KSslCertificateRule::setIgnoredErrors(const QList &)") + void setIgnoredErrors(const QList &errors); +#endif /** * Set the ignored errors for this certificate. * @since 5.64 */ void setIgnoredErrors(const QList &errors); QList ignoredErrors() const; // TODO KF6 return QSslError::SslError list +#if KIOCORE_ENABLE_DEPRECATED_SINCE(5, 64) /** @deprecated since 5.64, use the QSslError variant. */ - KIOCORE_DEPRECATED QList filterErrors(const QList &errors) const; + KIOCORE_DEPRECATED_VERSION(5, 64, "Use KSslCertificateRule::filterErrors(const QList &)") + QList filterErrors(const QList &errors) const; /** @deprecated since 5.64, use the QSslError variant. */ - KIOCORE_DEPRECATED QList filterErrors(const QList &errors) const; + KIOCORE_DEPRECATED_VERSION(5, 64, "Use KSslCertificateRule::filterErrors(const QList &)") + QList filterErrors(const QList &errors) const; +#endif /** * Filter out errors that are already ignored. * @since 5.64 */ QList filterErrors(const QList &errors) const; private: KSslCertificateRulePrivate *const d; }; //### document this too... :/ /** Certificate manager. */ class KIOCORE_EXPORT KSslCertificateManager { public: static KSslCertificateManager *self(); void setRule(const KSslCertificateRule &rule); void clearRule(const KSslCertificateRule &rule); void clearRule(const QSslCertificate &cert, const QString &hostName); KSslCertificateRule rule(const QSslCertificate &cert, const QString &hostName) const; - // use caCertificates() instead -#ifndef KIOCORE_NO_DEPRECATED - KIOCORE_DEPRECATED QList rootCertificates() const +#if KIOCORE_ENABLE_DEPRECATED_SINCE(4, 6) + /** @deprecated Since 4.6, use caCertificates() instead */ + KIOCORE_DEPRECATED_VERSION(4, 6, "Use KSslCertificateManager::caCertificates()") + QList rootCertificates() const { return caCertificates(); } #endif QList caCertificates() const; +#if KIOCORE_ENABLE_DEPRECATED_SINCE(5, 64) /** @deprecated since 5.64, use the corresponding QSslError variant. */ - static KIOCORE_DEPRECATED QList nonIgnorableErrors(const QList &); + KIOCORE_DEPRECATED_VERSION(5, 64, "Use KSslCertificateManager::nonIgnorableErrors(const QList &)") + static QList nonIgnorableErrors(const QList &); /** @deprecated since 5.64, use the corresponding QSslError variant. */ - static KIOCORE_DEPRECATED QList nonIgnorableErrors(const QList &); + KIOCORE_DEPRECATED_VERSION(5, 64, "Use KSslCertificateManager::nonIgnorableErrors(const QList &)") + static QList nonIgnorableErrors(const QList &); +#endif /** * Returns the subset of @p errors that cannot be ignored, ie. that is considered fatal. * @since 5.64 */ static QList nonIgnorableErrors(const QList &errors); private: friend class KSslCertificateManagerContainer; friend class KSslCertificateManagerPrivate; KSslCertificateManager(); ~KSslCertificateManager(); KSslCertificateManagerPrivate *const d; }; #endif diff --git a/src/core/ktcpsocket.h b/src/core/ktcpsocket.h index 1136d973..a50af2c4 100644 --- a/src/core/ktcpsocket.h +++ b/src/core/ktcpsocket.h @@ -1,399 +1,403 @@ /* This file is part of the KDE libraries Copyright (C) 2007 Thiago Macieira Copyright (C) 2007 Andreas Hartmetz This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef KTCPSOCKET_H #define KTCPSOCKET_H #include #include #include "kiocore_export.h" #include "ksslerroruidata.h" /* Notes on QCA::TLS compatibility In order to check for all validation problems as far as possible we need to use: Validity QCA::TLS::peerCertificateValidity() TLS::IdentityResult QCA::TLS::peerIdentityResult() CertificateChain QCA::TLS::peerCertificateChain().validate() - to find the failing cert! TLS::Error QCA::TLS::errorCode() - for more generic (but stil SSL) errors */ class KSslKeyPrivate; class KIOCORE_EXPORT KSslKey { public: enum Algorithm { Rsa = 0, Dsa, Dh }; enum KeySecrecy { PublicKey, PrivateKey }; KSslKey(); KSslKey(const KSslKey &other); KSslKey(const QSslKey &sslKey); ~KSslKey(); KSslKey &operator=(const KSslKey &other); Algorithm algorithm() const; bool isExportable() const; KeySecrecy secrecy() const; QByteArray toDer() const; private: KSslKeyPrivate *const d; }; class KSslCipherPrivate; class KIOCORE_EXPORT KSslCipher { public: KSslCipher(); KSslCipher(const KSslCipher &other); KSslCipher(const QSslCipher &); ~KSslCipher(); KSslCipher &operator=(const KSslCipher &other); bool isNull() const; QString authenticationMethod() const; QString encryptionMethod() const; QString keyExchangeMethod() const; QString digestMethod() const; /* mainly for internal use */ QString name() const; int supportedBits() const; int usedBits() const; static QList supportedCiphers(); private: KSslCipherPrivate *const d; }; class KSslErrorPrivate; class KTcpSocket; /** To be replaced by QSslError. */ class KIOCORE_EXPORT KSslError { public: enum Error { NoError = 0, UnknownError, InvalidCertificateAuthorityCertificate, InvalidCertificate, CertificateSignatureFailed, SelfSignedCertificate, ExpiredCertificate, RevokedCertificate, InvalidCertificatePurpose, RejectedCertificate, UntrustedCertificate, NoPeerCertificate, HostNameMismatch, PathLengthExceeded }; + +#if KIOCORE_ENABLE_DEPRECATED_SINCE(5, 63) /** @deprecated since 5.63, use the QSslError ctor instead. */ - KIOCORE_DEPRECATED KSslError(KSslError::Error error = NoError, const QSslCertificate &cert = QSslCertificate()); + KIOCORE_DEPRECATED_VERSION(5, 63, "Use KSslError(const QSslError &)") + KSslError(KSslError::Error error = NoError, const QSslCertificate &cert = QSslCertificate()); +#endif KSslError(const QSslError &error); KSslError(const KSslError &other); ~KSslError(); KSslError &operator=(const KSslError &other); Error error() const; QString errorString() const; QSslCertificate certificate() const; /** * Returns the QSslError wrapped by this KSslError. * @since 5.63 */ QSslError sslError() const; private: KSslErrorPrivate *const d; }; //consider killing more convenience functions with huge signatures //### do we need setSession() / session() ? //BIG FAT TODO: do we keep openMode() up to date everywhere it can change? //other TODO: limit possible error strings?, SSL key stuff //TODO protocol (or maybe even application?) dependent automatic proxy choice class KTcpSocketPrivate; class QHostAddress; class KIOCORE_EXPORT KTcpSocket: public QIODevice { Q_OBJECT public: enum State { UnconnectedState = 0, HostLookupState, ConnectingState, ConnectedState, BoundState, ListeningState, ClosingState //hmmm, do we need an SslNegotiatingState? }; enum SslVersion { UnknownSslVersion = 0x01, SslV2 = 0x02, SslV3 = 0x04, TlsV1 = 0x08, SslV3_1 = 0x08, TlsV1SslV3 = 0x10, SecureProtocols = 0x20, TlsV1_0 = TlsV1, TlsV1_1 = 0x40, TlsV1_2 = 0x80, TlsV1_3 = 0x100, AnySslVersion = SslV2 | SslV3 | TlsV1 }; Q_DECLARE_FLAGS(SslVersions, SslVersion) enum Error { UnknownError = 0, ConnectionRefusedError, RemoteHostClosedError, HostNotFoundError, SocketAccessError, SocketResourceError, SocketTimeoutError, NetworkError, UnsupportedSocketOperationError, SslHandshakeFailedError ///< @since 4.10.5 }; /* The following is based on reading the OpenSSL interface code of both QSslSocket and QCA::TLS. Barring oversights it should be accurate. The two cases with the question marks apparently will never be emitted by QSslSocket so there is nothing to compare. QSslError::NoError KTcpSocket::NoError QSslError::UnableToGetIssuerCertificate QCA::ErrorSignatureFailed QSslError::UnableToDecryptCertificateSignature QCA::ErrorSignatureFailed QSslError::UnableToDecodeIssuerPublicKey QCA::ErrorInvalidCA QSslError::CertificateSignatureFailed QCA::ErrorSignatureFailed QSslError::CertificateNotYetValid QCA::ErrorExpired QSslError::CertificateExpired QCA::ErrorExpired QSslError::InvalidNotBeforeField QCA::ErrorExpired QSslError::InvalidNotAfterField QCA::ErrorExpired QSslError::SelfSignedCertificate QCA::ErrorSelfSigned QSslError::SelfSignedCertificateInChain QCA::ErrorSelfSigned QSslError::UnableToGetLocalIssuerCertificate QCA::ErrorInvalidCA QSslError::UnableToVerifyFirstCertificate QCA::ErrorSignatureFailed QSslError::CertificateRevoked QCA::ErrorRevoked QSslError::InvalidCaCertificate QCA::ErrorInvalidCA QSslError::PathLengthExceeded QCA::ErrorPathLengthExceeded QSslError::InvalidPurpose QCA::ErrorInvalidPurpose QSslError::CertificateUntrusted QCA::ErrorUntrusted QSslError::CertificateRejected QCA::ErrorRejected QSslError::SubjectIssuerMismatch QCA::TLS::InvalidCertificate ? QSslError::AuthorityIssuerSerialNumberMismatch QCA::TLS::InvalidCertificate ? QSslError::NoPeerCertificate QCA::TLS::NoCertificate QSslError::HostNameMismatch QCA::TLS::HostMismatch QSslError::UnspecifiedError KTcpSocket::UnknownError QSslError::NoSslSupport Never happens :) */ enum EncryptionMode { UnencryptedMode = 0, SslClientMode, SslServerMode //### not implemented }; enum ProxyPolicy { /// Use the proxy that KProtocolManager suggests for the connection parameters given. AutoProxy = 0, /// Use the proxy set by setProxy(), if any; otherwise use no proxy. ManualProxy }; KTcpSocket(QObject *parent = nullptr); ~KTcpSocket(); //from QIODevice //reimplemented virtuals - the ones not reimplemented are OK for us bool atEnd() const override; qint64 bytesAvailable() const override; qint64 bytesToWrite() const override; bool canReadLine() const override; void close() override; bool isSequential() const override; bool open(QIODevice::OpenMode open) override; bool waitForBytesWritten(int msecs) override; //### Document that this actually tries to read *more* data bool waitForReadyRead(int msecs = 30000) override; protected: qint64 readData(char *data, qint64 maxSize) override; qint64 writeData(const char *data, qint64 maxSize) override; Q_SIGNALS: /// @since 4.8.1 /// Forwarded from QSslSocket void encryptedBytesWritten(qint64 written); public: //from QAbstractSocket void abort(); void connectToHost(const QString &hostName, quint16 port, ProxyPolicy policy = AutoProxy); void connectToHost(const QHostAddress &hostAddress, quint16 port, ProxyPolicy policy = AutoProxy); /** * Take the hostname and port from @p url and connect to them. The information from a * full URL enables the most accurate choice of proxy in case of proxy rules that * depend on high-level information like protocol or username. * @see KProtocolManager::proxyForUrl() */ void connectToHost(const QUrl &url, ProxyPolicy policy = AutoProxy); void disconnectFromHost(); Error error() const; //### QAbstractSocket's model is strange. error() should be related to the //current state and *NOT* just report the last error if there was one. QList sslErrors() const; //### the errors returned can only have a subset of all //possible QSslError::SslError enum values depending on backend bool flush(); bool isValid() const; QHostAddress localAddress() const; QHostAddress peerAddress() const; QString peerName() const; quint16 peerPort() const; void setVerificationPeerName(const QString &hostName); #ifndef QT_NO_NETWORKPROXY /** * @see: connectToHost() */ QNetworkProxy proxy() const; #endif qint64 readBufferSize() const; //probably hard to implement correctly #ifndef QT_NO_NETWORKPROXY /** * @see: connectToHost() */ void setProxy(const QNetworkProxy &proxy); //people actually seem to need it #endif void setReadBufferSize(qint64 size); State state() const; bool waitForConnected(int msecs = 30000); bool waitForDisconnected(int msecs = 30000); //from QSslSocket void addCaCertificate(const QSslCertificate &certificate); // bool addCaCertificates(const QString &path, QSsl::EncodingFormat format = QSsl::Pem, // QRegExp::PatternSyntax syntax = QRegExp::FixedString); void addCaCertificates(const QList &certificates); QList caCertificates() const; QList ciphers() const; void connectToHostEncrypted(const QString &hostName, quint16 port, OpenMode openMode = ReadWrite); // bool isEncrypted() const { return encryptionMode() != UnencryptedMode } QSslCertificate localCertificate() const; QList peerCertificateChain() const; KSslKey privateKey() const; KSslCipher sessionCipher() const; void setCaCertificates(const QList &certificates); void setCiphers(const QList &ciphers); //### void setCiphers(const QString &ciphers); //what about i18n? void setLocalCertificate(const QSslCertificate &certificate); void setLocalCertificate(const QString &fileName, QSsl::EncodingFormat format = QSsl::Pem); void setPrivateKey(const KSslKey &key); void setPrivateKey(const QString &fileName, KSslKey::Algorithm algorithm = KSslKey::Rsa, QSsl::EncodingFormat format = QSsl::Pem, const QByteArray &passPhrase = QByteArray()); void setAdvertisedSslVersion(SslVersion version); SslVersion advertisedSslVersion() const; //always equal to last setSslAdvertisedVersion SslVersion negotiatedSslVersion() const; //negotiated version; downgrades are possible. QString negotiatedSslVersionName() const; bool waitForEncrypted(int msecs = 30000); EncryptionMode encryptionMode() const; /** * Returns the state of the socket @p option. * * @see QAbstractSocket::socketOption * * @since 4.5.0 */ QVariant socketOption(QAbstractSocket::SocketOption options) const; /** * Sets the socket @p option to @p value. * * @see QAbstractSocket::setSocketOption * * @since 4.5.0 */ void setSocketOption(QAbstractSocket::SocketOption options, const QVariant &value); /** * Returns the socket's SSL configuration. * * @since 4.8.4 */ QSslConfiguration sslConfiguration() const; /** * Sets the socket's SSL configuration. * * @since 4.8.4 */ void setSslConfiguration(const QSslConfiguration &configuration); Q_SIGNALS: //from QAbstractSocket void connected(); void disconnected(); void error(KTcpSocket::Error); void hostFound(); #ifndef QT_NO_NETWORKPROXY void proxyAuthenticationRequired(const QNetworkProxy &proxy, QAuthenticator *authenticator); #endif // only for raw socket state, SSL is separate void stateChanged(KTcpSocket::State); //from QSslSocket void encrypted(); void encryptionModeChanged(EncryptionMode); void sslErrors(const QList &errors); public Q_SLOTS: void ignoreSslErrors(); void startClientEncryption(); // void startServerEncryption(); //not implemented private: Q_PRIVATE_SLOT(d, void reemitReadyRead()) Q_PRIVATE_SLOT(d, void reemitSocketError(QAbstractSocket::SocketError)) Q_PRIVATE_SLOT(d, void reemitSslErrors(const QList &)) Q_PRIVATE_SLOT(d, void reemitStateChanged(QAbstractSocket::SocketState)) Q_PRIVATE_SLOT(d, void reemitModeChanged(QSslSocket::SslMode)) //debugging H4X void showSslErrors(); friend class KTcpSocketPrivate; KTcpSocketPrivate *const d; }; #endif // KTCPSOCKET_H diff --git a/src/core/scheduler.cpp b/src/core/scheduler.cpp index eaa7b7f1..49d14a57 100644 --- a/src/core/scheduler.cpp +++ b/src/core/scheduler.cpp @@ -1,1328 +1,1322 @@ /* This file is part of the KDE libraries Copyright (C) 2000 Stephan Kulow Waldo Bastian Copyright (C) 2009, 2010 Andreas Hartmetz This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License version 2 as published by the Free Software Foundation. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "scheduler.h" #include "scheduler_p.h" #include "sessiondata_p.h" #include "slaveconfig.h" #include "slave.h" #include "connection_p.h" #include "job_p.h" #include #include //#include #include #include #include #include #include // Slaves may be idle for a certain time (3 minutes) before they are killed. static const int s_idleSlaveLifetime = 3 * 60; using namespace KIO; static inline Slave *jobSlave(SimpleJob *job) { return SimpleJobPrivate::get(job)->m_slave; } static inline int jobCommand(SimpleJob *job) { return SimpleJobPrivate::get(job)->m_command; } static inline void startJob(SimpleJob *job, Slave *slave) { SimpleJobPrivate::get(job)->start(slave); } // here be uglies // forward declaration to break cross-dependency of SlaveKeeper and SchedulerPrivate static void setupSlave(KIO::Slave *slave, const QUrl &url, const QString &protocol, const QStringList &proxyList, bool newSlave, const KIO::MetaData *config = nullptr); // same reason as above static Scheduler *scheduler(); static Slave *heldSlaveForJob(SimpleJob *job); int SerialPicker::changedPrioritySerial(int oldSerial, int newPriority) const { Q_ASSERT(newPriority >= -10 && newPriority <= 10); newPriority = qBound(-10, newPriority, 10); int unbiasedSerial = oldSerial % m_jobsPerPriority; return unbiasedSerial + newPriority * m_jobsPerPriority; } SlaveKeeper::SlaveKeeper() { m_grimTimer.setSingleShot(true); connect(&m_grimTimer, &QTimer::timeout, this, &SlaveKeeper::grimReaper); } SlaveKeeper::~SlaveKeeper() { grimReaper(); } void SlaveKeeper::returnSlave(Slave *slave) { Q_ASSERT(slave); slave->setIdle(); m_idleSlaves.insert(slave->host(), slave); scheduleGrimReaper(); } Slave *SlaveKeeper::takeSlaveForJob(SimpleJob *job) { Slave *slave = heldSlaveForJob(job); if (slave) { return slave; } QUrl url = SimpleJobPrivate::get(job)->m_url; // TODO take port, username and password into account QMultiHash::Iterator it = m_idleSlaves.find(url.host()); if (it == m_idleSlaves.end()) { it = m_idleSlaves.begin(); } if (it == m_idleSlaves.end()) { return nullptr; } slave = it.value(); m_idleSlaves.erase(it); return slave; } bool SlaveKeeper::removeSlave(Slave *slave) { // ### performance not so great QMultiHash::Iterator it = m_idleSlaves.begin(); for (; it != m_idleSlaves.end(); ++it) { if (it.value() == slave) { m_idleSlaves.erase(it); return true; } } return false; } void SlaveKeeper::clear() { m_idleSlaves.clear(); } QList SlaveKeeper::allSlaves() const { return m_idleSlaves.values(); } void SlaveKeeper::scheduleGrimReaper() { if (!m_grimTimer.isActive()) { m_grimTimer.start((s_idleSlaveLifetime / 2) * 1000); } } //private slot void SlaveKeeper::grimReaper() { QMultiHash::Iterator it = m_idleSlaves.begin(); while (it != m_idleSlaves.end()) { Slave *slave = it.value(); if (slave->idleTime() >= s_idleSlaveLifetime) { it = m_idleSlaves.erase(it); if (slave->job()) { //qDebug() << "Idle slave" << slave << "still has job" << slave->job(); } slave->kill(); // avoid invoking slotSlaveDied() because its cleanup services are not needed slave->deref(); } else { ++it; } } if (!m_idleSlaves.isEmpty()) { scheduleGrimReaper(); } } int HostQueue::lowestSerial() const { QMap::ConstIterator first = m_queuedJobs.constBegin(); if (first != m_queuedJobs.constEnd()) { return first.key(); } return SerialPicker::maxSerial; } void HostQueue::queueJob(SimpleJob *job) { const int serial = SimpleJobPrivate::get(job)->m_schedSerial; Q_ASSERT(serial != 0); Q_ASSERT(!m_queuedJobs.contains(serial)); Q_ASSERT(!m_runningJobs.contains(job)); m_queuedJobs.insert(serial, job); } SimpleJob *HostQueue::takeFirstInQueue() { Q_ASSERT(!m_queuedJobs.isEmpty()); QMap::iterator first = m_queuedJobs.begin(); SimpleJob *job = first.value(); m_queuedJobs.erase(first); m_runningJobs.insert(job); return job; } bool HostQueue::removeJob(SimpleJob *job) { const int serial = SimpleJobPrivate::get(job)->m_schedSerial; if (m_runningJobs.remove(job)) { Q_ASSERT(!m_queuedJobs.contains(serial)); return true; } if (m_queuedJobs.remove(serial)) { return true; } return false; } QList HostQueue::allSlaves() const { QList ret; ret.reserve(m_runningJobs.size()); for (SimpleJob *job : m_runningJobs) { Slave *slave = jobSlave(job); Q_ASSERT(slave); ret.append(slave); } return ret; } ConnectedSlaveQueue::ConnectedSlaveQueue() { m_startJobsTimer.setSingleShot(true); connect(&m_startJobsTimer, &QTimer::timeout, this, &ConnectedSlaveQueue::startRunnableJobs); } bool ConnectedSlaveQueue::queueJob(SimpleJob *job, Slave *slave) { QHash::Iterator it = m_connectedSlaves.find(slave); if (it == m_connectedSlaves.end()) { return false; } SimpleJobPrivate::get(job)->m_slave = slave; PerSlaveQueue &jobs = it.value(); jobs.waitingList.append(job); if (!jobs.runningJob) { // idle slave now has a job to run m_runnableSlaves.insert(slave); m_startJobsTimer.start(); } return true; } bool ConnectedSlaveQueue::removeJob(SimpleJob *job) { Slave *slave = jobSlave(job); Q_ASSERT(slave); QHash::Iterator it = m_connectedSlaves.find(slave); if (it == m_connectedSlaves.end()) { return false; } PerSlaveQueue &jobs = it.value(); if (jobs.runningJob || jobs.waitingList.isEmpty()) { // a slave that was busy running a job was not runnable. // a slave that has no waiting job(s) was not runnable either. Q_ASSERT(!m_runnableSlaves.contains(slave)); } const bool removedRunning = jobs.runningJob == job; const bool removedWaiting = jobs.waitingList.removeAll(job) != 0; if (removedRunning) { jobs.runningJob = nullptr; Q_ASSERT(!removedWaiting); } const bool removedTheJob = removedRunning || removedWaiting; if (!slave->isAlive()) { removeSlave(slave); return removedTheJob; } if (removedRunning && jobs.waitingList.count()) { m_runnableSlaves.insert(slave); m_startJobsTimer.start(); } if (removedWaiting && jobs.waitingList.isEmpty()) { m_runnableSlaves.remove(slave); } return removedTheJob; } void ConnectedSlaveQueue::addSlave(Slave *slave) { Q_ASSERT(slave); if (!m_connectedSlaves.contains(slave)) { m_connectedSlaves.insert(slave, PerSlaveQueue()); } } bool ConnectedSlaveQueue::removeSlave(Slave *slave) { QHash::Iterator it = m_connectedSlaves.find(slave); if (it == m_connectedSlaves.end()) { return false; } PerSlaveQueue &jobs = it.value(); // we need a copy as kill() ends up removing the job from waitingList const QList waitingJobs = jobs.waitingList; for (SimpleJob *job : waitingJobs) { // ### for compatibility with the old scheduler we don't touch the running job, if any. // make sure that the job doesn't call back into Scheduler::cancelJob(); this would // a) crash and b) be unnecessary because we clean up just fine. SimpleJobPrivate::get(job)->m_schedSerial = 0; job->kill(); } m_connectedSlaves.erase(it); m_runnableSlaves.remove(slave); slave->kill(); return true; } // KDE5: only one caller, for doubtful reasons. remove this if possible. bool ConnectedSlaveQueue::isIdle(Slave *slave) { QHash::Iterator it = m_connectedSlaves.find(slave); if (it == m_connectedSlaves.end()) { return false; } return it.value().runningJob == nullptr; } //private slot void ConnectedSlaveQueue::startRunnableJobs() { QSet::Iterator it = m_runnableSlaves.begin(); while (it != m_runnableSlaves.end()) { Slave *slave = *it; if (!slave->isConnected()) { // this polling is somewhat inefficient... m_startJobsTimer.start(); ++it; continue; } it = m_runnableSlaves.erase(it); PerSlaveQueue &jobs = m_connectedSlaves[slave]; SimpleJob *job = jobs.waitingList.takeFirst(); Q_ASSERT(!jobs.runningJob); jobs.runningJob = job; const QUrl url = job->url(); // no port is -1 in QUrl, but in kde3 we used 0 and the kioslaves assume that. const int port = url.port() == -1 ? 0 : url.port(); if (slave->host() == QLatin1String("")) { MetaData configData = SlaveConfig::self()->configData(url.scheme(), url.host()); slave->setConfig(configData); slave->setProtocol(url.scheme()); slave->setHost(url.host(), port, url.userName(), url.password()); } Q_ASSERT(slave->protocol() == url.scheme()); Q_ASSERT(slave->host() == url.host()); Q_ASSERT(slave->port() == port); startJob(job, slave); } } static void ensureNoDuplicates(QMap *queuesBySerial) { Q_UNUSED(queuesBySerial); #ifdef SCHEDULER_DEBUG // a host queue may *never* be in queuesBySerial twice. QSet seen; auto it = queuesBySerial->cbegin(); for (; it != queuesBySerial->cend(); ++it) { Q_ASSERT(!seen.contains(it.value())); seen.insert(it.value()); } #endif } static void verifyRunningJobsCount(QHash *queues, int runningJobsCount) { Q_UNUSED(queues); Q_UNUSED(runningJobsCount); #ifdef SCHEDULER_DEBUG int realRunningJobsCount = 0; auto it = queues->cbegin(); for (; it != queues->cend(); ++it) { realRunningJobsCount += it.value().runningJobsCount(); } Q_ASSERT(realRunningJobsCount == runningJobsCount); // ...and of course we may never run the same job twice! QSet seenJobs; auto it2 = queues->cbegin(); for (; it2 != queues->cend(); ++it2) { for (SimpleJob *job : it2.value().runningJobs()) { Q_ASSERT(!seenJobs.contains(job)); seenJobs.insert(job); } } #endif } ProtoQueue::ProtoQueue(int maxSlaves, int maxSlavesPerHost) : m_maxConnectionsPerHost(maxSlavesPerHost ? maxSlavesPerHost : maxSlaves), m_maxConnectionsTotal(qMax(maxSlaves, maxSlavesPerHost)), m_runningJobsCount(0) { /*qDebug() << "m_maxConnectionsTotal:" << m_maxConnectionsTotal << "m_maxConnectionsPerHost:" << m_maxConnectionsPerHost;*/ Q_ASSERT(m_maxConnectionsPerHost >= 1); Q_ASSERT(maxSlaves >= maxSlavesPerHost); m_startJobTimer.setSingleShot(true); connect(&m_startJobTimer, &QTimer::timeout, this, &ProtoQueue::startAJob); } ProtoQueue::~ProtoQueue() { // Gather list of all slaves first const QList slaves = allSlaves(); // Clear the idle slaves in the keeper to avoid dangling pointers m_slaveKeeper.clear(); for (Slave *slave : slaves) { // kill the slave process, then remove the interface in our process slave->kill(); slave->deref(); } } void ProtoQueue::queueJob(SimpleJob *job) { QString hostname = SimpleJobPrivate::get(job)->m_url.host(); HostQueue &hq = m_queuesByHostname[hostname]; const int prevLowestSerial = hq.lowestSerial(); Q_ASSERT(hq.runningJobsCount() <= m_maxConnectionsPerHost); // nevert insert a job twice Q_ASSERT(SimpleJobPrivate::get(job)->m_schedSerial == 0); SimpleJobPrivate::get(job)->m_schedSerial = m_serialPicker.next(); const bool wasQueueEmpty = hq.isQueueEmpty(); hq.queueJob(job); // note that HostQueue::queueJob() into an empty queue changes its lowestSerial() too... // the queue's lowest serial job may have changed, so update the ordered list of queues. // however, we ignore all jobs that would cause more connections to a host than allowed. if (prevLowestSerial != hq.lowestSerial()) { if (hq.runningJobsCount() < m_maxConnectionsPerHost) { // if the connection limit didn't keep the HQ unscheduled it must have been lack of jobs if (m_queuesBySerial.remove(prevLowestSerial) == 0) { Q_UNUSED(wasQueueEmpty); Q_ASSERT(wasQueueEmpty); } m_queuesBySerial.insert(hq.lowestSerial(), &hq); } else { #ifdef SCHEDULER_DEBUG // ### this assertion may fail if the limits were modified at runtime! // if the per-host connection limit is already reached the host queue's lowest serial // should not be queued. Q_ASSERT(!m_queuesBySerial.contains(prevLowestSerial)); #endif } } // just in case; startAJob() will refuse to start a job if it shouldn't. m_startJobTimer.start(); ensureNoDuplicates(&m_queuesBySerial); } void ProtoQueue::changeJobPriority(SimpleJob *job, int newPrio) { SimpleJobPrivate *jobPriv = SimpleJobPrivate::get(job); QHash::Iterator it = m_queuesByHostname.find(jobPriv->m_url.host()); if (it == m_queuesByHostname.end()) { return; } HostQueue &hq = it.value(); const int prevLowestSerial = hq.lowestSerial(); if (hq.isJobRunning(job) || !hq.removeJob(job)) { return; } jobPriv->m_schedSerial = m_serialPicker.changedPrioritySerial(jobPriv->m_schedSerial, newPrio); hq.queueJob(job); const bool needReinsert = hq.lowestSerial() != prevLowestSerial; // the host queue might be absent from m_queuesBySerial because the connections per host limit // for that host has been reached. if (needReinsert && m_queuesBySerial.remove(prevLowestSerial)) { m_queuesBySerial.insert(hq.lowestSerial(), &hq); } ensureNoDuplicates(&m_queuesBySerial); } void ProtoQueue::removeJob(SimpleJob *job) { SimpleJobPrivate *jobPriv = SimpleJobPrivate::get(job); HostQueue &hq = m_queuesByHostname[jobPriv->m_url.host()]; const int prevLowestSerial = hq.lowestSerial(); const int prevRunningJobs = hq.runningJobsCount(); Q_ASSERT(hq.runningJobsCount() <= m_maxConnectionsPerHost); if (hq.removeJob(job)) { if (hq.lowestSerial() != prevLowestSerial) { // we have dequeued the not yet running job with the lowest serial Q_ASSERT(!jobPriv->m_slave); Q_ASSERT(prevRunningJobs == hq.runningJobsCount()); if (m_queuesBySerial.remove(prevLowestSerial) == 0) { // make sure that the queue was not scheduled for a good reason Q_ASSERT(hq.runningJobsCount() == m_maxConnectionsPerHost); } } else { if (prevRunningJobs != hq.runningJobsCount()) { // we have dequeued a previously running job Q_ASSERT(prevRunningJobs - 1 == hq.runningJobsCount()); m_runningJobsCount--; Q_ASSERT(m_runningJobsCount >= 0); } } if (!hq.isQueueEmpty() && hq.runningJobsCount() < m_maxConnectionsPerHost) { // this may be a no-op, but it's faster than first checking if it's already in. m_queuesBySerial.insert(hq.lowestSerial(), &hq); } if (hq.isEmpty()) { // no queued jobs, no running jobs. this destroys hq from above. m_queuesByHostname.remove(jobPriv->m_url.host()); } if (jobPriv->m_slave && jobPriv->m_slave->isAlive()) { m_slaveKeeper.returnSlave(jobPriv->m_slave); } // just in case; startAJob() will refuse to start a job if it shouldn't. m_startJobTimer.start(); } else { // should be a connected slave // if the assertion fails the job has probably changed the host part of its URL while // running, so we can't find it by hostname. don't do this. const bool removed = m_connectedSlaveQueue.removeJob(job); Q_UNUSED(removed); Q_ASSERT(removed); } ensureNoDuplicates(&m_queuesBySerial); } Slave *ProtoQueue::createSlave(const QString &protocol, SimpleJob *job, const QUrl &url) { int error; QString errortext; Slave *slave = Slave::createSlave(protocol, url, error, errortext); if (slave) { scheduler()->connect(slave, SIGNAL(slaveDied(KIO::Slave*)), SLOT(slotSlaveDied(KIO::Slave*))); scheduler()->connect(slave, SIGNAL(slaveStatus(qint64,QByteArray,QString,bool)), SLOT(slotSlaveStatus(qint64,QByteArray,QString,bool))); } else { qCWarning(KIO_CORE) << "couldn't create slave:" << errortext; if (job) { job->slotError(error, errortext); } } return slave; } bool ProtoQueue::removeSlave(KIO::Slave *slave) { const bool removedConnected = m_connectedSlaveQueue.removeSlave(slave); const bool removedUnconnected = m_slaveKeeper.removeSlave(slave); Q_ASSERT(!(removedConnected && removedUnconnected)); return removedConnected || removedUnconnected; } QList ProtoQueue::allSlaves() const { QList ret(m_slaveKeeper.allSlaves()); auto it = m_queuesByHostname.cbegin(); for (; it != m_queuesByHostname.cend(); ++it) { ret.append(it.value().allSlaves()); } ret.append(m_connectedSlaveQueue.allSlaves()); return ret; } //private slot void ProtoQueue::startAJob() { ensureNoDuplicates(&m_queuesBySerial); verifyRunningJobsCount(&m_queuesByHostname, m_runningJobsCount); #ifdef SCHEDULER_DEBUG //qDebug() << "m_runningJobsCount:" << m_runningJobsCount; auto it = m_queuesByHostname.cbegin(); for (; it != m_queuesByHostname.cend(); ++it) { const QList list = it.value().runningJobs(); for (SimpleJob *job : list) { //qDebug() << SimpleJobPrivate::get(job)->m_url; } } #endif if (m_runningJobsCount >= m_maxConnectionsTotal) { #ifdef SCHEDULER_DEBUG //qDebug() << "not starting any jobs because maxConnectionsTotal has been reached."; #endif return; } QMap::iterator first = m_queuesBySerial.begin(); if (first != m_queuesBySerial.end()) { // pick a job and maintain the queue invariant: lower serials first HostQueue *hq = first.value(); const int prevLowestSerial = first.key(); Q_UNUSED(prevLowestSerial); Q_ASSERT(hq->lowestSerial() == prevLowestSerial); // the following assertions should hold due to queueJob(), takeFirstInQueue() and // removeJob() being correct Q_ASSERT(hq->runningJobsCount() < m_maxConnectionsPerHost); SimpleJob *startingJob = hq->takeFirstInQueue(); Q_ASSERT(hq->runningJobsCount() <= m_maxConnectionsPerHost); Q_ASSERT(hq->lowestSerial() != prevLowestSerial); m_queuesBySerial.erase(first); // we've increased hq's runningJobsCount() by calling nexStartingJob() // so we need to check again. if (!hq->isQueueEmpty() && hq->runningJobsCount() < m_maxConnectionsPerHost) { m_queuesBySerial.insert(hq->lowestSerial(), hq); } // always increase m_runningJobsCount because it's correct if there is a slave and if there // is no slave, removeJob() will balance the number again. removeJob() would decrease the // number too much otherwise. // Note that createSlave() can call slotError() on a job which in turn calls removeJob(), // so increase the count here already. m_runningJobsCount++; bool isNewSlave = false; Slave *slave = m_slaveKeeper.takeSlaveForJob(startingJob); SimpleJobPrivate *jobPriv = SimpleJobPrivate::get(startingJob); if (!slave) { isNewSlave = true; slave = createSlave(jobPriv->m_protocol, startingJob, jobPriv->m_url); } if (slave) { jobPriv->m_slave = slave; setupSlave(slave, jobPriv->m_url, jobPriv->m_protocol, jobPriv->m_proxyList, isNewSlave); startJob(startingJob, slave); } else { // dispose of our records about the job and mark the job as unknown // (to prevent crashes later) // note that the job's slotError() can have called removeJob() first, so check that // it's not a ghost job with null serial already. if (jobPriv->m_schedSerial) { removeJob(startingJob); jobPriv->m_schedSerial = 0; } } } else { #ifdef SCHEDULER_DEBUG //qDebug() << "not starting any jobs because there is no queued job."; #endif } if (!m_queuesBySerial.isEmpty()) { m_startJobTimer.start(); } } class KIO::SchedulerPrivate { public: SchedulerPrivate() : q(new Scheduler()), m_slaveOnHold(nullptr), m_checkOnHold(true), // !! Always check with KLauncher for the first request m_ignoreConfigReparse(false) { } ~SchedulerPrivate() { delete q; q = nullptr; for (ProtoQueue *p : qAsConst(m_protocols)) { const QList list = p->allSlaves(); for (Slave *slave : list) { slave->kill(); } } qDeleteAll(m_protocols); } Scheduler *q; Slave *m_slaveOnHold; QUrl m_urlOnHold; bool m_checkOnHold; bool m_ignoreConfigReparse; SessionData sessionData; void doJob(SimpleJob *job); -#ifndef KIOCORE_NO_DEPRECATED void scheduleJob(SimpleJob *job); -#endif void setJobPriority(SimpleJob *job, int priority); void cancelJob(SimpleJob *job); void jobFinished(KIO::SimpleJob *job, KIO::Slave *slave); void putSlaveOnHold(KIO::SimpleJob *job, const QUrl &url); void removeSlaveOnHold(); Slave *getConnectedSlave(const QUrl &url, const KIO::MetaData &metaData); bool assignJobToSlave(KIO::Slave *slave, KIO::SimpleJob *job); bool disconnectSlave(KIO::Slave *slave); void checkSlaveOnHold(bool b); void publishSlaveOnHold(); Slave *heldSlaveForJob(KIO::SimpleJob *job); bool isSlaveOnHoldFor(const QUrl &url); void updateInternalMetaData(SimpleJob *job); MetaData metaDataFor(const QString &protocol, const QStringList &proxyList, const QUrl &url); void setupSlave(KIO::Slave *slave, const QUrl &url, const QString &protocol, const QStringList &proxyList, bool newSlave, const KIO::MetaData *config = nullptr); void slotSlaveDied(KIO::Slave *slave); void slotSlaveStatus(qint64 pid, const QByteArray &protocol, const QString &host, bool connected); void slotReparseSlaveConfiguration(const QString &, const QDBusMessage &); void slotSlaveOnHoldListChanged(); void slotSlaveConnected(); void slotSlaveError(int error, const QString &errorMsg); ProtoQueue *protoQ(const QString &protocol, const QString &host) { ProtoQueue *pq = m_protocols.value(protocol, nullptr); if (!pq) { //qDebug() << "creating ProtoQueue instance for" << protocol; const int maxSlaves = KProtocolInfo::maxSlaves(protocol); int maxSlavesPerHost = -1; if (!host.isEmpty()) { bool ok = false; const int value = SlaveConfig::self()->configData(protocol, host, QStringLiteral("MaxConnections")).toInt(&ok); if (ok) { maxSlavesPerHost = value; } } if (maxSlavesPerHost == -1) { maxSlavesPerHost = KProtocolInfo::maxSlavesPerHost(protocol); } // Never allow maxSlavesPerHost to exceed maxSlaves. pq = new ProtoQueue(maxSlaves, qMin(maxSlaves, maxSlavesPerHost)); m_protocols.insert(protocol, pq); } return pq; } private: QHash m_protocols; }; static QThreadStorage s_storage; static SchedulerPrivate *schedulerPrivate() { if (!s_storage.hasLocalData()) { s_storage.setLocalData(new SchedulerPrivate); } return s_storage.localData(); } Scheduler *Scheduler::self() { return schedulerPrivate()->q; } SchedulerPrivate *Scheduler::d_func() { return schedulerPrivate(); } //static Scheduler *scheduler() { return schedulerPrivate()->q; } //static Slave *heldSlaveForJob(SimpleJob *job) { return schedulerPrivate()->heldSlaveForJob(job); } Scheduler::Scheduler() { setObjectName(QStringLiteral("scheduler")); const QString dbusPath = QStringLiteral("/KIO/Scheduler"); const QString dbusInterface = QStringLiteral("org.kde.KIO.Scheduler"); QDBusConnection dbus = QDBusConnection::sessionBus(); // Not needed, right? We just want to emit two signals. //dbus.registerObject("/KIO/Scheduler", this, QDBusConnection::ExportScriptableSlots | // QDBusConnection::ExportScriptableSignals); dbus.connect(QString(), dbusPath, dbusInterface, QStringLiteral("reparseSlaveConfiguration"), this, SLOT(slotReparseSlaveConfiguration(QString,QDBusMessage))); dbus.connect(QString(), dbusPath, dbusInterface, QStringLiteral("slaveOnHoldListChanged"), this, SLOT(slotSlaveOnHoldListChanged())); } Scheduler::~Scheduler() { } void Scheduler::doJob(SimpleJob *job) { schedulerPrivate()->doJob(job); } -#ifndef KIOCORE_NO_DEPRECATED void Scheduler::scheduleJob(SimpleJob *job) { schedulerPrivate()->scheduleJob(job); } -#endif void Scheduler::setJobPriority(SimpleJob *job, int priority) { schedulerPrivate()->setJobPriority(job, priority); } void Scheduler::cancelJob(SimpleJob *job) { schedulerPrivate()->cancelJob(job); } void Scheduler::jobFinished(KIO::SimpleJob *job, KIO::Slave *slave) { schedulerPrivate()->jobFinished(job, slave); } void Scheduler::putSlaveOnHold(KIO::SimpleJob *job, const QUrl &url) { schedulerPrivate()->putSlaveOnHold(job, url); } void Scheduler::removeSlaveOnHold() { schedulerPrivate()->removeSlaveOnHold(); } void Scheduler::publishSlaveOnHold() { schedulerPrivate()->publishSlaveOnHold(); } bool Scheduler::isSlaveOnHoldFor(const QUrl &url) { return schedulerPrivate()->isSlaveOnHoldFor(url); } void Scheduler::updateInternalMetaData(SimpleJob *job) { schedulerPrivate()->updateInternalMetaData(job); } KIO::Slave *Scheduler::getConnectedSlave(const QUrl &url, const KIO::MetaData &config) { return schedulerPrivate()->getConnectedSlave(url, config); } bool Scheduler::assignJobToSlave(KIO::Slave *slave, KIO::SimpleJob *job) { return schedulerPrivate()->assignJobToSlave(slave, job); } bool Scheduler::disconnectSlave(KIO::Slave *slave) { return schedulerPrivate()->disconnectSlave(slave); } bool Scheduler::connect(const char *signal, const QObject *receiver, const char *member) { return QObject::connect(self(), signal, receiver, member); } bool Scheduler::connect(const QObject *sender, const char *signal, const QObject *receiver, const char *member) { return QObject::connect(sender, signal, receiver, member); } bool Scheduler::disconnect(const QObject *sender, const char *signal, const QObject *receiver, const char *member) { return QObject::disconnect(sender, signal, receiver, member); } bool Scheduler::connect(const QObject *sender, const char *signal, const char *member) { return QObject::connect(sender, signal, member); } void Scheduler::checkSlaveOnHold(bool b) { schedulerPrivate()->checkSlaveOnHold(b); } void Scheduler::emitReparseSlaveConfiguration() { // Do it immediately in this process, otherwise we might send a request before reparsing // (e.g. when changing useragent in the plugin) schedulerPrivate()->slotReparseSlaveConfiguration(QString(), QDBusMessage()); schedulerPrivate()->m_ignoreConfigReparse = true; emit self()->reparseSlaveConfiguration(QString()); } void SchedulerPrivate::slotReparseSlaveConfiguration(const QString &proto, const QDBusMessage &) { if (m_ignoreConfigReparse) { //qDebug() << "Ignoring signal sent by myself"; m_ignoreConfigReparse = false; return; } //qDebug() << "proto=" << proto; KProtocolManager::reparseConfiguration(); SlaveConfig::self()->reset(); sessionData.reset(); NetRC::self()->reload(); QHash::ConstIterator it = proto.isEmpty() ? m_protocols.constBegin() : m_protocols.constFind(proto); // not found? if (it == m_protocols.constEnd()) { return; } QHash::ConstIterator endIt = proto.isEmpty() ? m_protocols.constEnd() : it + 1; for (; it != endIt; ++it) { const QList list = it.value()->allSlaves(); for (Slave *slave : list) { slave->send(CMD_REPARSECONFIGURATION); slave->resetHost(); } } } void SchedulerPrivate::slotSlaveOnHoldListChanged() { m_checkOnHold = true; } static bool mayReturnContent(int cmd, const QString &protocol) { if (cmd == CMD_GET) { return true; } if (cmd == CMD_MULTI_GET) { return true; } if (cmd == CMD_SPECIAL && protocol.startsWith(QLatin1String("http"), Qt::CaseInsensitive)) { return true; } return false; } void SchedulerPrivate::doJob(SimpleJob *job) { //qDebug() << job; KIO::SimpleJobPrivate *const jobPriv = SimpleJobPrivate::get(job); jobPriv->m_proxyList.clear(); jobPriv->m_protocol = KProtocolManager::slaveProtocol(job->url(), jobPriv->m_proxyList); if (mayReturnContent(jobCommand(job), jobPriv->m_protocol)) { jobPriv->m_checkOnHold = m_checkOnHold; m_checkOnHold = false; } ProtoQueue *proto = protoQ(jobPriv->m_protocol, job->url().host()); proto->queueJob(job); } -#ifndef KIOCORE_NO_DEPRECATED void SchedulerPrivate::scheduleJob(SimpleJob *job) { //qDebug() << job; setJobPriority(job, 1); } -#endif void SchedulerPrivate::setJobPriority(SimpleJob *job, int priority) { //qDebug() << job << priority; const QString protocol = SimpleJobPrivate::get(job)->m_protocol; if (!protocol.isEmpty()) { ProtoQueue *proto = protoQ(SimpleJobPrivate::get(job)->m_protocol, job->url().host()); proto->changeJobPriority(job, priority); } } void SchedulerPrivate::cancelJob(SimpleJob *job) { // this method is called all over the place in job.cpp, so just do this check here to avoid // much boilerplate in job code. if (SimpleJobPrivate::get(job)->m_schedSerial == 0) { //qDebug() << "Doing nothing because I don't know job" << job; return; } Slave *slave = jobSlave(job); //qDebug() << job << slave; if (slave) { //qDebug() << "Scheduler: killing slave " << slave->slave_pid(); slave->kill(); } jobFinished(job, slave); } void SchedulerPrivate::jobFinished(SimpleJob *job, Slave *slave) { //qDebug() << job << slave; KIO::SimpleJobPrivate *const jobPriv = SimpleJobPrivate::get(job); // make sure that we knew about the job! Q_ASSERT(jobPriv->m_schedSerial); ProtoQueue *pq = m_protocols.value(jobPriv->m_protocol); if (pq) { pq->removeJob(job); } if (slave) { // If we have internal meta-data, tell existing ioslaves to reload // their configuration. if (jobPriv->m_internalMetaData.count()) { //qDebug() << "Updating ioslaves with new internal metadata information"; ProtoQueue *queue = m_protocols.value(slave->protocol()); if (queue) { QListIterator it(queue->allSlaves()); while (it.hasNext()) { Slave *runningSlave = it.next(); if (slave->host() == runningSlave->host()) { slave->setConfig(metaDataFor(slave->protocol(), jobPriv->m_proxyList, job->url())); /*qDebug() << "Updated configuration of" << slave->protocol() << "ioslave, pid=" << slave->slave_pid();*/ } } } } slave->setJob(nullptr); slave->disconnect(job); } jobPriv->m_schedSerial = 0; // this marks the job as unscheduled again jobPriv->m_slave = nullptr; // Clear the values in the internal metadata container since they have // already been taken care of above... jobPriv->m_internalMetaData.clear(); } // static void setupSlave(KIO::Slave *slave, const QUrl &url, const QString &protocol, const QStringList &proxyList, bool newSlave, const KIO::MetaData *config) { schedulerPrivate()->setupSlave(slave, url, protocol, proxyList, newSlave, config); } MetaData SchedulerPrivate::metaDataFor(const QString &protocol, const QStringList &proxyList, const QUrl &url) { const QString host = url.host(); MetaData configData = SlaveConfig::self()->configData(protocol, host); sessionData.configDataFor(configData, protocol, host); if (proxyList.isEmpty()) { configData.remove(QStringLiteral("UseProxy")); configData.remove(QStringLiteral("ProxyUrls")); } else { configData[QStringLiteral("UseProxy")] = proxyList.first(); configData[QStringLiteral("ProxyUrls")] = proxyList.join(QLatin1Char(',')); } if (configData.contains(QLatin1String("EnableAutoLogin")) && configData.value(QStringLiteral("EnableAutoLogin")).compare(QLatin1String("true"), Qt::CaseInsensitive) == 0) { NetRC::AutoLogin l; l.login = url.userName(); bool usern = (protocol == QLatin1String("ftp")); if (NetRC::self()->lookup(url, l, usern)) { configData[QStringLiteral("autoLoginUser")] = l.login; configData[QStringLiteral("autoLoginPass")] = l.password; if (usern) { QString macdef; QMap::ConstIterator it = l.macdef.constBegin(); for (; it != l.macdef.constEnd(); ++it) { macdef += it.key() + QLatin1Char('\\') + it.value().join(QLatin1Char('\\')) + QLatin1Char('\n'); } configData[QStringLiteral("autoLoginMacro")] = macdef; } } } return configData; } void SchedulerPrivate::setupSlave(KIO::Slave *slave, const QUrl &url, const QString &protocol, const QStringList &proxyList, bool newSlave, const KIO::MetaData *config) { int port = url.port(); if (port == -1) { // no port is -1 in QUrl, but in kde3 we used 0 and the kioslaves assume that. port = 0; } const QString host = url.host(); const QString user = url.userName(); const QString passwd = url.password(); if (newSlave || slave->host() != host || slave->port() != port || slave->user() != user || slave->passwd() != passwd) { MetaData configData = metaDataFor(protocol, proxyList, url); if (config) { configData += *config; } slave->setConfig(configData); slave->setProtocol(url.scheme()); slave->setHost(host, port, user, passwd); } } void SchedulerPrivate::slotSlaveStatus(qint64, const QByteArray &, const QString &, bool) { } void SchedulerPrivate::slotSlaveDied(KIO::Slave *slave) { //qDebug() << slave; Q_ASSERT(slave); Q_ASSERT(!slave->isAlive()); ProtoQueue *pq = m_protocols.value(slave->protocol()); if (pq) { if (slave->job()) { pq->removeJob(slave->job()); } // in case this was a connected slave... pq->removeSlave(slave); } if (slave == m_slaveOnHold) { m_slaveOnHold = nullptr; m_urlOnHold.clear(); } // can't use slave->deref() here because we need to use deleteLater slave->aboutToDelete(); slave->deleteLater(); } void SchedulerPrivate::putSlaveOnHold(KIO::SimpleJob *job, const QUrl &url) { Slave *slave = jobSlave(job); //qDebug() << job << url << slave; slave->disconnect(job); // prevent the fake death of the slave from trying to kill the job again; // cf. Slave::hold(const QUrl &url) called in SchedulerPrivate::publishSlaveOnHold(). slave->setJob(nullptr); SimpleJobPrivate::get(job)->m_slave = nullptr; if (m_slaveOnHold) { m_slaveOnHold->kill(); } m_slaveOnHold = slave; m_urlOnHold = url; m_slaveOnHold->suspend(); } void SchedulerPrivate::publishSlaveOnHold() { //qDebug() << m_slaveOnHold; if (!m_slaveOnHold) { return; } m_slaveOnHold->hold(m_urlOnHold); emit q->slaveOnHoldListChanged(); } bool SchedulerPrivate::isSlaveOnHoldFor(const QUrl &url) { if (url.isValid() && m_urlOnHold.isValid() && url == m_urlOnHold) { return true; } return Slave::checkForHeldSlave(url); } Slave *SchedulerPrivate::heldSlaveForJob(SimpleJob *job) { Slave *slave = nullptr; KIO::SimpleJobPrivate *const jobPriv = SimpleJobPrivate::get(job); if (jobPriv->m_checkOnHold) { slave = Slave::holdSlave(jobPriv->m_protocol, job->url()); } if (!slave && m_slaveOnHold) { // Make sure that the job wants to do a GET or a POST, and with no offset const int cmd = jobPriv->m_command; bool canJobReuse = (cmd == CMD_GET || cmd == CMD_MULTI_GET); if (KIO::TransferJob *tJob = qobject_cast(job)) { canJobReuse = (canJobReuse || cmd == CMD_SPECIAL); if (canJobReuse) { KIO::MetaData outgoing = tJob->outgoingMetaData(); const QString resume = outgoing.value(QStringLiteral("resume")); const QString rangeStart = outgoing.value(QStringLiteral("range-start")); //qDebug() << "Resume metadata is" << resume; canJobReuse = (resume.isEmpty() || resume == QLatin1Char('0')) && (rangeStart.isEmpty() || rangeStart == QLatin1Char('0')); } } if (job->url() == m_urlOnHold) { if (canJobReuse) { //qDebug() << "HOLD: Reusing held slave (" << m_slaveOnHold << ")"; slave = m_slaveOnHold; } else { //qDebug() << "HOLD: Discarding held slave (" << m_slaveOnHold << ")"; m_slaveOnHold->kill(); } m_slaveOnHold = nullptr; m_urlOnHold.clear(); } } else if (slave) { //qDebug() << "HOLD: Reusing klauncher held slave (" << slave << ")"; } return slave; } void SchedulerPrivate::removeSlaveOnHold() { //qDebug() << m_slaveOnHold; if (m_slaveOnHold) { m_slaveOnHold->kill(); } m_slaveOnHold = nullptr; m_urlOnHold.clear(); } Slave *SchedulerPrivate::getConnectedSlave(const QUrl &url, const KIO::MetaData &config) { QStringList proxyList; const QString protocol = KProtocolManager::slaveProtocol(url, proxyList); ProtoQueue *pq = protoQ(protocol, url.host()); Slave *slave = pq->createSlave(protocol, /* job */nullptr, url); if (slave) { setupSlave(slave, url, protocol, proxyList, true, &config); pq->m_connectedSlaveQueue.addSlave(slave); slave->send(CMD_CONNECT); q->connect(slave, SIGNAL(connected()), SLOT(slotSlaveConnected())); q->connect(slave, SIGNAL(error(int,QString)), SLOT(slotSlaveError(int,QString))); } //qDebug() << url << slave; return slave; } void SchedulerPrivate::slotSlaveConnected() { //qDebug(); Slave *slave = static_cast(q->sender()); slave->setConnected(true); q->disconnect(slave, SIGNAL(connected()), q, SLOT(slotSlaveConnected())); emit q->slaveConnected(slave); } void SchedulerPrivate::slotSlaveError(int errorNr, const QString &errorMsg) { Slave *slave = static_cast(q->sender()); //qDebug() << slave << errorNr << errorMsg; ProtoQueue *pq = protoQ(slave->protocol(), slave->host()); if (!slave->isConnected() || pq->m_connectedSlaveQueue.isIdle(slave)) { // Only forward to application if slave is idle or still connecting. // ### KDE5: can we remove this apparently arbitrary behavior and just always emit SlaveError? emit q->slaveError(slave, errorNr, errorMsg); } } bool SchedulerPrivate::assignJobToSlave(KIO::Slave *slave, SimpleJob *job) { //qDebug() << slave << job; // KDE5: queueing of jobs can probably be removed, it provides very little benefit ProtoQueue *pq = m_protocols.value(slave->protocol()); if (pq) { pq->removeJob(job); return pq->m_connectedSlaveQueue.queueJob(job, slave); } return false; } bool SchedulerPrivate::disconnectSlave(KIO::Slave *slave) { //qDebug() << slave; ProtoQueue *pq = m_protocols.value(slave->protocol()); return (pq ? pq->m_connectedSlaveQueue.removeSlave(slave) : false); } void SchedulerPrivate::checkSlaveOnHold(bool b) { //qDebug() << b; m_checkOnHold = b; } void SchedulerPrivate::updateInternalMetaData(SimpleJob *job) { KIO::SimpleJobPrivate *const jobPriv = SimpleJobPrivate::get(job); // Preserve all internal meta-data so they can be sent back to the // ioslaves as needed... const QUrl jobUrl = job->url(); //qDebug() << job << jobPriv->m_internalMetaData; QMapIterator it(jobPriv->m_internalMetaData); while (it.hasNext()) { it.next(); if (it.key().startsWith(QLatin1String("{internal~currenthost}"), Qt::CaseInsensitive)) { SlaveConfig::self()->setConfigData(jobUrl.scheme(), jobUrl.host(), it.key().mid(22), it.value()); } else if (it.key().startsWith(QLatin1String("{internal~allhosts}"), Qt::CaseInsensitive)) { SlaveConfig::self()->setConfigData(jobUrl.scheme(), QString(), it.key().mid(19), it.value()); } } } #include "moc_scheduler.cpp" #include "moc_scheduler_p.cpp" diff --git a/src/core/scheduler.h b/src/core/scheduler.h index 01f63b2a..9ab4f15c 100644 --- a/src/core/scheduler.h +++ b/src/core/scheduler.h @@ -1,300 +1,302 @@ // -*- c++ -*- /* This file is part of the KDE libraries Copyright (C) 2000 Stephan Kulow Waldo Bastian This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef _kio_scheduler_h #define _kio_scheduler_h #include "simplejob.h" #include #include namespace KIO { class Slave; class SlaveConfig; class SchedulerPrivate; /** * @class KIO::Scheduler scheduler.h * * The KIO::Scheduler manages io-slaves for the application. * It also queues jobs and assigns the job to a slave when one * becomes available. * * There are 3 possible ways for a job to get a slave: * *

1. Direct

* This is the default. When you create a job the * KIO::Scheduler will be notified and will find either an existing * slave that is idle or it will create a new slave for the job. * * Example: * \code * TransferJob *job = KIO::get(QUrl("http://www.kde.org")); * \endcode * * *

2. Scheduled

* If you create a lot of jobs, you might want not want to have a * slave for each job. If you schedule a job, a maximum number * of slaves will be created. When more jobs arrive, they will be * queued. When a slave is finished with a job, it will be assigned * a job from the queue. * * Example: * \code * TransferJob *job = KIO::get(QUrl("http://www.kde.org")); * KIO::Scheduler::setJobPriority(job, 1); * \endcode * *

3. Connection Oriented

* For some operations it is important that multiple jobs use * the same connection. This can only be ensured if all these jobs * use the same slave. * * You can ask the scheduler to open a slave for connection oriented * operations. You can then use the scheduler to assign jobs to this * slave. The jobs will be queued and the slave will handle these jobs * one after the other. * * Example: * \code * Slave *slave = KIO::Scheduler::getConnectedSlave( * QUrl("pop3://bastian:password@mail.kde.org")); * TransferJob *job1 = KIO::get( * QUrl("pop3://bastian:password@mail.kde.org/msg1")); * KIO::Scheduler::assignJobToSlave(slave, job1); * TransferJob *job2 = KIO::get( * QUrl("pop3://bastian:password@mail.kde.org/msg2")); * KIO::Scheduler::assignJobToSlave(slave, job2); * TransferJob *job3 = KIO::get( * QUrl("pop3://bastian:password@mail.kde.org/msg3")); * KIO::Scheduler::assignJobToSlave(slave, job3); * * // ... Wait for jobs to finish... * * KIO::Scheduler::disconnectSlave(slave); * \endcode * * Note that you need to explicitly disconnect the slave when the * connection goes down, so your error handler should contain: * \code * if (error == KIO::ERR_CONNECTION_BROKEN) * KIO::Scheduler::disconnectSlave(slave); * \endcode * * @see KIO::Slave * @see KIO::Job **/ class KIOCORE_EXPORT Scheduler : public QObject { Q_OBJECT Q_CLASSINFO("D-Bus Interface", "org.kde.KIO.Scheduler") public: /** * Register @p job with the scheduler. * The default is to create a new slave for the job if no slave * is available. This can be changed by calling setJobPriority. * @param job the job to register */ static void doJob(SimpleJob *job); +#if KIOCORE_ENABLE_DEPRECATED_SINCE(4, 5) /** * Schedules @p job scheduled for later execution. * This method is deprecated and just sets the job's priority to 1. It is * recommended to replace calls to scheduleJob(job) with setJobPriority(job, 1). * @param job the job to schedule + * @deprecated Since 4.5, use setJobPriority(SimpleJob *job, int priority) */ -#ifndef KIOCORE_NO_DEPRECATED - KIOCORE_DEPRECATED static void scheduleJob(SimpleJob *job); + KIOCORE_DEPRECATED_VERSION(4, 5, "Use Scheduler::setJobPriority(SimpleJob *, int )") + static void scheduleJob(SimpleJob *job); #endif /** * Changes the priority of @p job; jobs of the same priority run in the order in which * they were created. Jobs of lower numeric priority always run before any * waiting jobs of higher numeric priority. The range of priority is -10 to 10, * the default priority of jobs is 0. * @param job the job to change * @param priority new priority of @p job, lower runs earlier */ static void setJobPriority(SimpleJob *job, int priority); /** * Stop the execution of a job. * @param job the job to cancel */ static void cancelJob(SimpleJob *job); /** * Called when a job is done. * @param job the finished job * @param slave the slave that executed the @p job */ static void jobFinished(KIO::SimpleJob *job, KIO::Slave *slave); /** * Puts a slave on notice. A next job may reuse this slave if it * requests the same URL. * * A job can be put on hold after it has emit'ed its mimetype. * Based on the mimetype, the program can give control to another * component in the same process which can then resume the job * by simply asking for the same URL again. * @param job the job that should be stopped * @param url the URL that is handled by the @p url */ static void putSlaveOnHold(KIO::SimpleJob *job, const QUrl &url); /** * Removes any slave that might have been put on hold. If a slave * was put on hold it will be killed. */ static void removeSlaveOnHold(); /** * Send the slave that was put on hold back to KLauncher. This * allows another process to take over the slave and resume the job * that was started. */ static void publishSlaveOnHold(); /** * Requests a slave for use in connection-oriented mode. * * @param url This defines the username,password,host & port to * connect with. * @param config Configuration data for the slave. * * @return A pointer to a connected slave or @c nullptr if an error occurred. * @see assignJobToSlave() * @see disconnectSlave() */ static KIO::Slave *getConnectedSlave(const QUrl &url, const KIO::MetaData &config = MetaData()); /** * Uses @p slave to do @p job. * This function should be called immediately after creating a Job. * * @param slave The slave to use. The slave must have been obtained * with a call to getConnectedSlave and must not * be currently assigned to any other job. * @param job The job to do. * * @return true is successful, false otherwise. * * @see getConnectedSlave() * @see disconnectSlave() * @see slaveConnected() * @see slaveError() */ static bool assignJobToSlave(KIO::Slave *slave, KIO::SimpleJob *job); /** * Disconnects @p slave. * * @param slave The slave to disconnect. The slave must have been * obtained with a call to getConnectedSlave * and must not be assigned to any job. * * @return true is successful, false otherwise. * * @see getConnectedSlave * @see assignJobToSlave */ static bool disconnectSlave(KIO::Slave *slave); /** * Function to connect signals emitted by the scheduler. * * @see slaveConnected() * @see slaveError() */ // KDE5: those methods should probably be removed, ugly and only marginally useful static bool connect(const char *signal, const QObject *receiver, const char *member); static bool connect(const QObject *sender, const char *signal, const QObject *receiver, const char *member); static bool disconnect(const QObject *sender, const char *signal, const QObject *receiver, const char *member); bool connect(const QObject *sender, const char *signal, const char *member); /** * When true, the next job will check whether KLauncher has a slave * on hold that is suitable for the job. * @param b true when KLauncher has a job on hold */ static void checkSlaveOnHold(bool b); static void emitReparseSlaveConfiguration(); /** * Returns true if there is a slave on hold for @p url. * * @since 4.7 */ static bool isSlaveOnHoldFor(const QUrl &url); /** * Updates the internal metadata from job. * * @since 4.6.5 */ static void updateInternalMetaData(SimpleJob *job); Q_SIGNALS: void slaveConnected(KIO::Slave *slave); void slaveError(KIO::Slave *slave, int error, const QString &errorMsg); // DBUS Q_SCRIPTABLE void reparseSlaveConfiguration(const QString &); Q_SCRIPTABLE void slaveOnHoldListChanged(); private: Q_DISABLE_COPY(Scheduler) Scheduler(); ~Scheduler(); static Scheduler *self(); Q_PRIVATE_SLOT(d_func(), void slotSlaveDied(KIO::Slave *slave)) Q_PRIVATE_SLOT(d_func(), void slotSlaveStatus(qint64 pid, const QByteArray &protocol, const QString &host, bool connected)) // connected to D-Bus signal: Q_PRIVATE_SLOT(d_func(), void slotReparseSlaveConfiguration(const QString &, const QDBusMessage &)) Q_PRIVATE_SLOT(d_func(), void slotSlaveOnHoldListChanged()) Q_PRIVATE_SLOT(d_func(), void slotSlaveConnected()) Q_PRIVATE_SLOT(d_func(), void slotSlaveError(int error, const QString &errorMsg)) private: friend class SchedulerPrivate; SchedulerPrivate *d_func(); }; } #endif diff --git a/src/core/slavebase.cpp b/src/core/slavebase.cpp index 944f8129..49af3bfb 100644 --- a/src/core/slavebase.cpp +++ b/src/core/slavebase.cpp @@ -1,1568 +1,1564 @@ /* * This file is part of the KDE libraries * Copyright (c) 2000 Waldo Bastian * Copyright (c) 2000 David Faure * Copyright (c) 2000 Stephan Kulow * Copyright (c) 2007 Thiago Macieira * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License version 2 as published by the Free Software Foundation. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. * **/ // TODO: remove me #undef QT_NO_CAST_FROM_ASCII #include "slavebase.h" #include #include #include #include #ifdef Q_OS_WIN #include #endif #include #include #include #include #include #include #include #include #include #include #include #include #include "kremoteencoding.h" #include "kioglobal_p.h" #include "connection_p.h" #include "commands_p.h" #include "ioslave_defaults.h" #include "slaveinterface.h" #include "kpasswdserverclient.h" #include "kiocoredebug.h" #ifdef Q_OS_UNIX #include #endif #if KIO_ASSERT_SLAVE_STATES #define KIO_STATE_ASSERT(cond, where, what) Q_ASSERT_X(cond, where, what) #else #define KIO_STATE_ASSERT(cond, where, what) do { if (!(cond)) qCWarning(KIO_CORE) << what; } while (false) #endif extern "C" { static void sigpipe_handler(int sig); } using namespace KIO; typedef QList AuthKeysList; typedef QMap AuthKeysMap; #define KIO_DATA QByteArray data; QDataStream stream( &data, QIODevice::WriteOnly ); stream #define KIO_FILESIZE_T(x) quint64(x) static const int KIO_MAX_ENTRIES_PER_BATCH = 200; static const int KIO_MAX_SEND_BATCH_TIME = 300; namespace KIO { class SlaveBasePrivate { public: SlaveBase * const q; explicit SlaveBasePrivate(SlaveBase *owner) : q(owner) , nextTimeoutMsecs(0) , m_passwdServerClient(nullptr) , m_confirmationAsked(false) , m_privilegeOperationStatus(OperationNotAllowed) { if (!qEnvironmentVariableIsEmpty("KIOSLAVE_ENABLE_TESTMODE")) { QStandardPaths::setTestModeEnabled(true); } pendingListEntries.reserve(KIO_MAX_ENTRIES_PER_BATCH); appConnection.setReadMode(Connection::ReadMode::Polled); } ~SlaveBasePrivate() { delete m_passwdServerClient; } UDSEntryList pendingListEntries; QElapsedTimer m_timeSinceLastBatch; Connection appConnection; QString poolSocket; bool isConnectedToApp; QString slaveid; bool resume: 1; bool needSendCanResume: 1; bool onHold: 1; bool wasKilled: 1; bool inOpenLoop: 1; bool exit_loop: 1; MetaData configData; KConfig *config = nullptr; KConfigGroup *configGroup = nullptr; QMap mapConfig; QUrl onHoldUrl; QElapsedTimer lastTimeout; QElapsedTimer nextTimeout; qint64 nextTimeoutMsecs; KIO::filesize_t totalSize; KRemoteEncoding *remotefile = nullptr; enum { Idle, InsideMethod, FinishedCalled, ErrorCalled } m_state; bool m_finalityCommand = true; // whether finished() or error() may/must be called QByteArray timeoutData; KPasswdServerClient *m_passwdServerClient = nullptr; bool m_rootEntryListed = false; bool m_confirmationAsked; QSet m_tempAuths; QString m_warningCaption; QString m_warningMessage; int m_privilegeOperationStatus; PrivilegeOperationStatus askConfirmation() { int status = q->messageBox(SlaveBase::WarningContinueCancel, m_warningMessage, m_warningCaption, QStringLiteral("Continue"), QStringLiteral("Cancel")); switch (status) { case SlaveBase::Continue: return OperationAllowed; case SlaveBase::Cancel: return OperationCanceled; default: return OperationNotAllowed; } } void updateTempAuthStatus() { #ifdef Q_OS_UNIX QSet::iterator it = m_tempAuths.begin(); while (it != m_tempAuths.end()) { KAuth::Action action(*it); if (action.status() != KAuth::Action::AuthorizedStatus) { it = m_tempAuths.erase(it); } else { ++it; } } #endif } bool hasTempAuth() const { return !m_tempAuths.isEmpty(); } // Reconstructs configGroup from configData and mIncomingMetaData void rebuildConfig() { mapConfig.clear(); // mIncomingMetaData cascades over config, so we write config first, // to let it be overwritten MetaData::ConstIterator end = configData.constEnd(); for (MetaData::ConstIterator it = configData.constBegin(); it != end; ++it) { mapConfig.insert(it.key(), it->toUtf8()); } end = q->mIncomingMetaData.constEnd(); for (MetaData::ConstIterator it = q->mIncomingMetaData.constBegin(); it != end; ++it) { mapConfig.insert(it.key(), it->toUtf8()); } delete configGroup; configGroup = nullptr; delete config; config = nullptr; } bool finalState() const { return ((m_state == FinishedCalled) || (m_state == ErrorCalled)); } void verifyState(const char *cmdName) { KIO_STATE_ASSERT(finalState(), Q_FUNC_INFO, qUtf8Printable(QStringLiteral("%1 did not call finished() or error()! Please fix the %2 KIO slave") .arg(cmdName) .arg(QCoreApplication::applicationName()))); // Force the command into finished state. We'll not reach this for Debug builds // that fail the assertion. For Release builds we'll have made sure that the // command is actually finished after the verification regardless of what // the slave did. if (!finalState()) { q->finished(); } } void verifyErrorFinishedNotCalled(const char *cmdName) { KIO_STATE_ASSERT(!finalState(), Q_FUNC_INFO, qUtf8Printable(QStringLiteral("%1 called finished() or error(), but it's not supposed to! Please fix the %2 KIO slave") .arg(cmdName) .arg(QCoreApplication::applicationName()))); } KPasswdServerClient *passwdServerClient() { if (!m_passwdServerClient) { m_passwdServerClient = new KPasswdServerClient; } return m_passwdServerClient; } }; } static SlaveBase *globalSlave; static volatile bool slaveWriteError = false; static const char *s_protocol; #ifdef Q_OS_UNIX extern "C" { static void genericsig_handler(int sigNumber) { ::signal(sigNumber, SIG_IGN); //WABA: Don't do anything that requires malloc, we can deadlock on it since //a SIGTERM signal can come in while we are in malloc/free. //qDebug()<<"kioslave : exiting due to signal "<setKillFlag(); } ::signal(SIGALRM, SIG_DFL); alarm(5); //generate an alarm signal in 5 seconds, in this time the slave has to exit } } #endif ////////////// SlaveBase::SlaveBase(const QByteArray &protocol, const QByteArray &pool_socket, const QByteArray &app_socket) : mProtocol(protocol), d(new SlaveBasePrivate(this)) { Q_ASSERT(!app_socket.isEmpty()); d->poolSocket = QFile::decodeName(pool_socket); s_protocol = protocol.data(); KCrash::initialize(); #ifdef Q_OS_UNIX struct sigaction act; act.sa_handler = sigpipe_handler; sigemptyset(&act.sa_mask); act.sa_flags = 0; sigaction(SIGPIPE, &act, nullptr); ::signal(SIGINT, &genericsig_handler); ::signal(SIGQUIT, &genericsig_handler); ::signal(SIGTERM, &genericsig_handler); #endif globalSlave = this; d->isConnectedToApp = true; // by kahl for netmgr (need a way to identify slaves) d->slaveid = QString::fromUtf8(protocol) + QString::number(getpid()); d->resume = false; d->needSendCanResume = false; d->mapConfig = QMap(); d->onHold = false; d->wasKilled = false; // d->processed_size = 0; d->totalSize = 0; connectSlave(QFile::decodeName(app_socket)); d->remotefile = nullptr; d->inOpenLoop = false; d->exit_loop = false; } SlaveBase::~SlaveBase() { delete d->configGroup; delete d->config; delete d->remotefile; delete d; s_protocol = ""; } void SlaveBase::dispatchLoop() { while (!d->exit_loop) { if (d->nextTimeout.isValid() && (d->nextTimeout.hasExpired(d->nextTimeoutMsecs))) { QByteArray data = d->timeoutData; d->nextTimeout.invalidate(); d->timeoutData = QByteArray(); special(data); } Q_ASSERT(d->appConnection.inited()); int ms = -1; if (d->nextTimeout.isValid()) { ms = qMax(d->nextTimeout.elapsed() - d->nextTimeoutMsecs, 1); } int ret = -1; if (d->appConnection.hasTaskAvailable() || d->appConnection.waitForIncomingTask(ms)) { // dispatch application messages int cmd; QByteArray data; ret = d->appConnection.read(&cmd, data); if (ret != -1) { if (d->inOpenLoop) { dispatchOpenCommand(cmd, data); } else { dispatch(cmd, data); } } } else { ret = d->appConnection.isConnected() ? 0 : -1; } if (ret == -1) { // some error occurred, perhaps no more application // When the app exits, should the slave be put back in the pool ? if (!d->exit_loop && d->isConnectedToApp && !d->poolSocket.isEmpty()) { disconnectSlave(); d->isConnectedToApp = false; closeConnection(); d->updateTempAuthStatus(); connectSlave(d->poolSocket); } else { break; } } //I think we get here when we were killed in dispatch() and not in select() if (wasKilled()) { //qDebug() << "slave was killed, returning"; break; } // execute deferred deletes QCoreApplication::sendPostedEvents(nullptr, QEvent::DeferredDelete); } // execute deferred deletes QCoreApplication::sendPostedEvents(nullptr, QEvent::DeferredDelete); } void SlaveBase::connectSlave(const QString &address) { d->appConnection.connectToRemote(QUrl(address)); if (!d->appConnection.inited()) { /*qDebug() << "failed to connect to" << address << endl << "Reason:" << d->appConnection.errorString();*/ exit(); } d->inOpenLoop = false; } void SlaveBase::disconnectSlave() { d->appConnection.close(); } void SlaveBase::setMetaData(const QString &key, const QString &value) { mOutgoingMetaData.insert(key, value); // replaces existing key if already there } QString SlaveBase::metaData(const QString &key) const { auto it = mIncomingMetaData.find(key); if (it != mIncomingMetaData.end()) { return *it; } return d->configData.value(key); } MetaData SlaveBase::allMetaData() const { return mIncomingMetaData; } bool SlaveBase::hasMetaData(const QString &key) const { if (mIncomingMetaData.contains(key)) { return true; } if (d->configData.contains(key)) { return true; } return false; } QMap SlaveBase::mapConfig() const { return d->mapConfig; } bool SlaveBase::configValue(QString key, bool defaultValue) const { return d->mapConfig.value(key, defaultValue).toBool(); } int SlaveBase::configValue(QString key, int defaultValue) const { return d->mapConfig.value(key, defaultValue).toInt(); } QString SlaveBase::configValue(QString key, const QString &defaultValue) const { return d->mapConfig.value(key, defaultValue).toString(); } KConfigGroup *SlaveBase::config() { if (!d->config) { d->config = new KConfig(QString(), KConfig::SimpleConfig); d->configGroup = new KConfigGroup(d->config, QString()); auto end = d->mapConfig.cend(); for (auto it = d->mapConfig.cbegin(); it != end; ++it) { d->configGroup->writeEntry(it.key(), it->toString().toUtf8(), KConfigGroup::WriteConfigFlags()); } } return d->configGroup; } void SlaveBase::sendMetaData() { sendAndKeepMetaData(); mOutgoingMetaData.clear(); } void SlaveBase::sendAndKeepMetaData() { if (!mOutgoingMetaData.isEmpty()) { KIO_DATA << mOutgoingMetaData; send(INF_META_DATA, data); } } KRemoteEncoding *SlaveBase::remoteEncoding() { if (d->remotefile) { return d->remotefile; } const QByteArray charset(metaData(QStringLiteral("Charset")).toLatin1()); return (d->remotefile = new KRemoteEncoding(charset.constData())); } void SlaveBase::data(const QByteArray &data) { sendMetaData(); send(MSG_DATA, data); } void SlaveBase::dataReq() { //sendMetaData(); if (d->needSendCanResume) { canResume(0); } send(MSG_DATA_REQ); } void SlaveBase::opened() { sendMetaData(); send(MSG_OPENED); d->inOpenLoop = true; } void SlaveBase::error(int _errid, const QString &_text) { KIO_STATE_ASSERT(d->m_finalityCommand, Q_FUNC_INFO, qUtf8Printable(QStringLiteral("error() was called, but it's not supposed to! Please fix the %1 KIO slave") .arg(QCoreApplication::applicationName()))); if (d->m_state == d->ErrorCalled) { KIO_STATE_ASSERT(false, Q_FUNC_INFO, qUtf8Printable(QStringLiteral("error() called twice! Please fix the %1 KIO slave") .arg(QCoreApplication::applicationName()))); return; } else if (d->m_state == d->FinishedCalled) { KIO_STATE_ASSERT(false, Q_FUNC_INFO, qUtf8Printable(QStringLiteral("error() called after finished()! Please fix the %1 KIO slave") .arg(QCoreApplication::applicationName()))); return; } d->m_state = d->ErrorCalled; mIncomingMetaData.clear(); // Clear meta data d->rebuildConfig(); mOutgoingMetaData.clear(); KIO_DATA << static_cast(_errid) << _text; send(MSG_ERROR, data); //reset d->totalSize = 0; d->inOpenLoop = false; d->m_confirmationAsked = false; d->m_privilegeOperationStatus = OperationNotAllowed; } void SlaveBase::connected() { send(MSG_CONNECTED); } void SlaveBase::finished() { if (!d->pendingListEntries.isEmpty()) { if (!d->m_rootEntryListed) { qCWarning(KIO_CORE) << "UDSEntry for '.' not found, creating a default one. Please fix the" << QCoreApplication::applicationName() << "KIO slave"; KIO::UDSEntry entry; entry.fastInsert(KIO::UDSEntry::UDS_NAME, QStringLiteral(".")); entry.fastInsert(KIO::UDSEntry::UDS_FILE_TYPE, S_IFDIR); entry.fastInsert(KIO::UDSEntry::UDS_SIZE, 0); entry.fastInsert(KIO::UDSEntry::UDS_ACCESS, S_IRUSR | S_IWUSR | S_IXUSR | S_IRGRP | S_IWGRP | S_IXGRP | S_IROTH | S_IXOTH); d->pendingListEntries.append(entry); } listEntries(d->pendingListEntries); d->pendingListEntries.clear(); } KIO_STATE_ASSERT(d->m_finalityCommand, Q_FUNC_INFO, qUtf8Printable(QStringLiteral("finished() was called, but it's not supposed to! Please fix the %2 KIO slave") .arg(QCoreApplication::applicationName()))); if (d->m_state == d->FinishedCalled) { KIO_STATE_ASSERT(false, Q_FUNC_INFO, qUtf8Printable(QStringLiteral("finished() called twice! Please fix the %1 KIO slave") .arg(QCoreApplication::applicationName()))); return; } else if (d->m_state == d->ErrorCalled) { KIO_STATE_ASSERT(false, Q_FUNC_INFO, qUtf8Printable(QStringLiteral("finished() called after error()! Please fix the %1 KIO slave") .arg(QCoreApplication::applicationName()))); return; } d->m_state = d->FinishedCalled; mIncomingMetaData.clear(); // Clear meta data d->rebuildConfig(); sendMetaData(); send(MSG_FINISHED); // reset d->totalSize = 0; d->inOpenLoop = false; d->m_rootEntryListed = false; d->m_confirmationAsked = false; d->m_privilegeOperationStatus = OperationNotAllowed; } void SlaveBase::needSubUrlData() { send(MSG_NEED_SUBURL_DATA); } void SlaveBase::slaveStatus(const QString &host, bool connected) { qint64 pid = getpid(); qint8 b = connected ? 1 : 0; KIO_DATA << pid << mProtocol << host << b << d->onHold << d->onHoldUrl << d->hasTempAuth(); send(MSG_SLAVE_STATUS_V2, data); } void SlaveBase::canResume() { send(MSG_CANRESUME); } void SlaveBase::totalSize(KIO::filesize_t _bytes) { KIO_DATA << KIO_FILESIZE_T(_bytes); send(INF_TOTAL_SIZE, data); //this one is usually called before the first item is listed in listDir() d->totalSize = _bytes; } void SlaveBase::processedSize(KIO::filesize_t _bytes) { bool emitSignal = false; if (_bytes == d->totalSize) { emitSignal = true; } else { if (d->lastTimeout.isValid()) { emitSignal = d->lastTimeout.hasExpired(100); // emit size 10 times a second } else { emitSignal = true; } } if (emitSignal) { KIO_DATA << KIO_FILESIZE_T(_bytes); send(INF_PROCESSED_SIZE, data); d->lastTimeout.start(); } // d->processed_size = _bytes; } void SlaveBase::written(KIO::filesize_t _bytes) { KIO_DATA << KIO_FILESIZE_T(_bytes); send(MSG_WRITTEN, data); } void SlaveBase::position(KIO::filesize_t _pos) { KIO_DATA << KIO_FILESIZE_T(_pos); send(INF_POSITION, data); } void SlaveBase::processedPercent(float /* percent */) { //qDebug() << "STUB"; } void SlaveBase::speed(unsigned long _bytes_per_second) { KIO_DATA << static_cast(_bytes_per_second); send(INF_SPEED, data); } void SlaveBase::redirection(const QUrl &_url) { KIO_DATA << _url; send(INF_REDIRECTION, data); } void SlaveBase::errorPage() { send(INF_ERROR_PAGE); } static bool isSubCommand(int cmd) { return ((cmd == CMD_REPARSECONFIGURATION) || (cmd == CMD_META_DATA) || (cmd == CMD_CONFIG) || (cmd == CMD_SUBURL) || (cmd == CMD_SLAVE_STATUS) || (cmd == CMD_SLAVE_CONNECT) || (cmd == CMD_SLAVE_HOLD) || (cmd == CMD_MULTI_GET)); } void SlaveBase::mimeType(const QString &_type) { //qDebug() << _type; int cmd; do { // Send the meta-data each time we send the mime-type. if (!mOutgoingMetaData.isEmpty()) { //qDebug() << "emitting meta data"; KIO_DATA << mOutgoingMetaData; send(INF_META_DATA, data); } KIO_DATA << _type; send(INF_MIME_TYPE, data); while (true) { cmd = 0; int ret = -1; if (d->appConnection.hasTaskAvailable() || d->appConnection.waitForIncomingTask(-1)) { ret = d->appConnection.read(&cmd, data); } if (ret == -1) { //qDebug() << "read error"; exit(); } //qDebug() << "got" << cmd; if (cmd == CMD_HOST) { // Ignore. continue; } if (!isSubCommand(cmd)) { break; } dispatch(cmd, data); } } while (cmd != CMD_NONE); mOutgoingMetaData.clear(); } void SlaveBase::exit() { d->exit_loop = true; // Using ::exit() here is too much (crashes in qdbus's qglobalstatic object), // so let's cleanly exit dispatchLoop() instead. // Update: we do need to call exit(), otherwise a long download (get()) would // keep going until it ends, even though the application exited. ::exit(255); } void SlaveBase::warning(const QString &_msg) { KIO_DATA << _msg; send(INF_WARNING, data); } void SlaveBase::infoMessage(const QString &_msg) { KIO_DATA << _msg; send(INF_INFOMESSAGE, data); } -#ifndef KIOCORE_NO_DEPRECATED bool SlaveBase::requestNetwork(const QString &host) { KIO_DATA << host << d->slaveid; send(MSG_NET_REQUEST, data); if (waitForAnswer(INF_NETWORK_STATUS, 0, data) != -1) { bool status; QDataStream stream(data); stream >> status; return status; } else { return false; } } void SlaveBase::dropNetwork(const QString &host) { KIO_DATA << host << d->slaveid; send(MSG_NET_DROP, data); } -#endif void SlaveBase::statEntry(const UDSEntry &entry) { KIO_DATA << entry; send(MSG_STAT_ENTRY, data); } -#ifndef KIOCORE_NO_DEPRECATED void SlaveBase::listEntry(const UDSEntry &entry, bool _ready) { if (_ready) { // #366795: many slaves don't create an entry for ".", so we keep track if they do // and we provide a fallback in finished() otherwise. if (entry.stringValue(KIO::UDSEntry::UDS_NAME) == QLatin1Char('.')) { d->m_rootEntryListed = true; } listEntries(d->pendingListEntries); d->pendingListEntries.clear(); } else { listEntry(entry); } } -#endif void SlaveBase::listEntry(const UDSEntry &entry) { // #366795: many slaves don't create an entry for ".", so we keep track if they do // and we provide a fallback in finished() otherwise. if (entry.stringValue(KIO::UDSEntry::UDS_NAME) == QLatin1Char('.')) { d->m_rootEntryListed = true; } // We start measuring the time from the point we start filling the list if (d->pendingListEntries.isEmpty()) { d->m_timeSinceLastBatch.restart(); } d->pendingListEntries.append(entry); // If more then KIO_MAX_SEND_BATCH_TIME time is passed, emit the current batch // Also emit if we have piled up a large number of entries already, to save memory (and time) if (d->m_timeSinceLastBatch.elapsed() > KIO_MAX_SEND_BATCH_TIME || d->pendingListEntries.size() > KIO_MAX_ENTRIES_PER_BATCH) { listEntries(d->pendingListEntries); d->pendingListEntries.clear(); // Restart time d->m_timeSinceLastBatch.restart(); } } void SlaveBase::listEntries(const UDSEntryList &list) { QByteArray data; QDataStream stream(&data, QIODevice::WriteOnly); for (const UDSEntry &entry : list) { stream << entry; } send(MSG_LIST_ENTRIES, data); } static void sigpipe_handler(int) { // We ignore a SIGPIPE in slaves. // A SIGPIPE can happen in two cases: // 1) Communication error with application. // 2) Communication error with network. slaveWriteError = true; // Don't add anything else here, especially no debug output } void SlaveBase::setHost(QString const &, quint16, QString const &, QString const &) { } KIOCORE_EXPORT QString KIO::unsupportedActionErrorString(const QString &protocol, int cmd) { switch (cmd) { case CMD_CONNECT: return i18n("Opening connections is not supported with the protocol %1.", protocol); case CMD_DISCONNECT: return i18n("Closing connections is not supported with the protocol %1.", protocol); case CMD_STAT: return i18n("Accessing files is not supported with the protocol %1.", protocol); case CMD_PUT: return i18n("Writing to %1 is not supported.", protocol); case CMD_SPECIAL: return i18n("There are no special actions available for protocol %1.", protocol); case CMD_LISTDIR: return i18n("Listing folders is not supported for protocol %1.", protocol); case CMD_GET: return i18n("Retrieving data from %1 is not supported.", protocol); case CMD_MIMETYPE: return i18n("Retrieving mime type information from %1 is not supported.", protocol); case CMD_RENAME: return i18n("Renaming or moving files within %1 is not supported.", protocol); case CMD_SYMLINK: return i18n("Creating symlinks is not supported with protocol %1.", protocol); case CMD_COPY: return i18n("Copying files within %1 is not supported.", protocol); case CMD_DEL: return i18n("Deleting files from %1 is not supported.", protocol); case CMD_MKDIR: return i18n("Creating folders is not supported with protocol %1.", protocol); case CMD_CHMOD: return i18n("Changing the attributes of files is not supported with protocol %1.", protocol); case CMD_CHOWN: return i18n("Changing the ownership of files is not supported with protocol %1.", protocol); case CMD_SUBURL: return i18n("Using sub-URLs with %1 is not supported.", protocol); case CMD_MULTI_GET: return i18n("Multiple get is not supported with protocol %1.", protocol); case CMD_OPEN: return i18n("Opening files is not supported with protocol %1.", protocol); default: return i18n("Protocol %1 does not support action %2.", protocol, cmd); }/*end switch*/ } void SlaveBase::openConnection() { error(ERR_UNSUPPORTED_ACTION, unsupportedActionErrorString(mProtocol, CMD_CONNECT)); } void SlaveBase::closeConnection() { } // No response! void SlaveBase::stat(QUrl const &) { error(ERR_UNSUPPORTED_ACTION, unsupportedActionErrorString(mProtocol, CMD_STAT)); } void SlaveBase::put(QUrl const &, int, JobFlags) { error(ERR_UNSUPPORTED_ACTION, unsupportedActionErrorString(mProtocol, CMD_PUT)); } void SlaveBase::special(const QByteArray &) { error(ERR_UNSUPPORTED_ACTION, unsupportedActionErrorString(mProtocol, CMD_SPECIAL)); } void SlaveBase::listDir(QUrl const &) { error(ERR_UNSUPPORTED_ACTION, unsupportedActionErrorString(mProtocol, CMD_LISTDIR)); } void SlaveBase::get(QUrl const &) { error(ERR_UNSUPPORTED_ACTION, unsupportedActionErrorString(mProtocol, CMD_GET)); } void SlaveBase::open(QUrl const &, QIODevice::OpenMode) { error(ERR_UNSUPPORTED_ACTION, unsupportedActionErrorString(mProtocol, CMD_OPEN)); } void SlaveBase::read(KIO::filesize_t) { error(ERR_UNSUPPORTED_ACTION, unsupportedActionErrorString(mProtocol, CMD_READ)); } void SlaveBase::write(const QByteArray &) { error(ERR_UNSUPPORTED_ACTION, unsupportedActionErrorString(mProtocol, CMD_WRITE)); } void SlaveBase::seek(KIO::filesize_t) { error(ERR_UNSUPPORTED_ACTION, unsupportedActionErrorString(mProtocol, CMD_SEEK)); } void SlaveBase::close() { error(ERR_UNSUPPORTED_ACTION, unsupportedActionErrorString(mProtocol, CMD_CLOSE)); } void SlaveBase::mimetype(QUrl const &url) { get(url); } void SlaveBase::rename(QUrl const &, QUrl const &, JobFlags) { error(ERR_UNSUPPORTED_ACTION, unsupportedActionErrorString(mProtocol, CMD_RENAME)); } void SlaveBase::symlink(QString const &, QUrl const &, JobFlags) { error(ERR_UNSUPPORTED_ACTION, unsupportedActionErrorString(mProtocol, CMD_SYMLINK)); } void SlaveBase::copy(QUrl const &, QUrl const &, int, JobFlags) { error(ERR_UNSUPPORTED_ACTION, unsupportedActionErrorString(mProtocol, CMD_COPY)); } void SlaveBase::del(QUrl const &, bool) { error(ERR_UNSUPPORTED_ACTION, unsupportedActionErrorString(mProtocol, CMD_DEL)); } void SlaveBase::setLinkDest(const QUrl &, const QString &) { error(ERR_UNSUPPORTED_ACTION, unsupportedActionErrorString(mProtocol, CMD_SETLINKDEST)); } void SlaveBase::mkdir(QUrl const &, int) { error(ERR_UNSUPPORTED_ACTION, unsupportedActionErrorString(mProtocol, CMD_MKDIR)); } void SlaveBase::chmod(QUrl const &, int) { error(ERR_UNSUPPORTED_ACTION, unsupportedActionErrorString(mProtocol, CMD_CHMOD)); } void SlaveBase::setModificationTime(QUrl const &, const QDateTime &) { error(ERR_UNSUPPORTED_ACTION, unsupportedActionErrorString(mProtocol, CMD_SETMODIFICATIONTIME)); } void SlaveBase::chown(QUrl const &, const QString &, const QString &) { error(ERR_UNSUPPORTED_ACTION, unsupportedActionErrorString(mProtocol, CMD_CHOWN)); } void SlaveBase::setSubUrl(QUrl const &) { error(ERR_UNSUPPORTED_ACTION, unsupportedActionErrorString(mProtocol, CMD_SUBURL)); } void SlaveBase::multiGet(const QByteArray &) { error(ERR_UNSUPPORTED_ACTION, unsupportedActionErrorString(mProtocol, CMD_MULTI_GET)); } void SlaveBase::slave_status() { slaveStatus(QString(), false); } void SlaveBase::reparseConfiguration() { delete d->remotefile; d->remotefile = nullptr; } bool SlaveBase::openPasswordDialog(AuthInfo &info, const QString &errorMsg) { const int errorCode = openPasswordDialogV2(info, errorMsg); return errorCode == KJob::NoError; } int SlaveBase::openPasswordDialogV2(AuthInfo &info, const QString &errorMsg) { const long windowId = metaData(QStringLiteral("window-id")).toLong(); const unsigned long userTimestamp = metaData(QStringLiteral("user-timestamp")).toULong(); QString errorMessage; if (metaData(QStringLiteral("no-auth-prompt")).compare(QLatin1String("true"), Qt::CaseInsensitive) == 0) { errorMessage = QStringLiteral(""); } else { errorMessage = errorMsg; } AuthInfo dlgInfo(info); // Make sure the modified flag is not set. dlgInfo.setModified(false); // Prevent queryAuthInfo from caching the user supplied password since // we need the ioslaves to first authenticate against the server with // it to ensure it is valid. dlgInfo.setExtraField(QStringLiteral("skip-caching-on-query"), true); KPasswdServerClient *passwdServerClient = d->passwdServerClient(); const int errCode = passwdServerClient->queryAuthInfo(&dlgInfo, errorMessage, windowId, userTimestamp); if (errCode == KJob::NoError) { info = dlgInfo; } return errCode; } int SlaveBase::messageBox(MessageBoxType type, const QString &text, const QString &caption, const QString &buttonYes, const QString &buttonNo) { return messageBox(text, type, caption, buttonYes, buttonNo, QString()); } int SlaveBase::messageBox(const QString &text, MessageBoxType type, const QString &caption, const QString &_buttonYes, const QString &_buttonNo, const QString &dontAskAgainName) { QString buttonYes = _buttonYes.isNull() ? i18n("&Yes") : _buttonYes; QString buttonNo = _buttonNo.isNull() ? i18n("&No") : _buttonNo; //qDebug() << "messageBox " << type << " " << text << " - " << caption << buttonYes << buttonNo; KIO_DATA << static_cast(type) << text << caption << buttonYes << buttonNo << dontAskAgainName; send(INF_MESSAGEBOX, data); if (waitForAnswer(CMD_MESSAGEBOXANSWER, 0, data) != -1) { QDataStream stream(data); int answer; stream >> answer; //qDebug() << "got messagebox answer" << answer; return answer; } else { return 0; // communication failure } } bool SlaveBase::canResume(KIO::filesize_t offset) { //qDebug() << "offset=" << KIO::number(offset); d->needSendCanResume = false; KIO_DATA << KIO_FILESIZE_T(offset); send(MSG_RESUME, data); if (offset) { int cmd; if (waitForAnswer(CMD_RESUMEANSWER, CMD_NONE, data, &cmd) != -1) { //qDebug() << "returning" << (cmd == CMD_RESUMEANSWER); return cmd == CMD_RESUMEANSWER; } else { return false; } } else { // No resuming possible -> no answer to wait for return true; } } int SlaveBase::waitForAnswer(int expected1, int expected2, QByteArray &data, int *pCmd) { int cmd = 0; int result = -1; for (;;) { if (d->appConnection.hasTaskAvailable() || d->appConnection.waitForIncomingTask(-1)) { result = d->appConnection.read(&cmd, data); } if (result == -1) { //qDebug() << "read error."; return -1; } if (cmd == expected1 || cmd == expected2) { if (pCmd) { *pCmd = cmd; } return result; } if (isSubCommand(cmd)) { dispatch(cmd, data); } else { qFatal("Fatal Error: Got cmd %d, while waiting for an answer!", cmd); } } } int SlaveBase::readData(QByteArray &buffer) { int result = waitForAnswer(MSG_DATA, 0, buffer); //qDebug() << "readData: length = " << result << " "; return result; } void SlaveBase::setTimeoutSpecialCommand(int timeout, const QByteArray &data) { if (timeout > 0) { d->nextTimeoutMsecs = timeout*1000; // from seconds to milliseconds d->nextTimeout.start(); } else if (timeout == 0) { d->nextTimeoutMsecs = 1000; // Immediate timeout d->nextTimeout.start(); } else { d->nextTimeout.invalidate(); // Canceled } d->timeoutData = data; } void SlaveBase::dispatch(int command, const QByteArray &data) { QDataStream stream(data); QUrl url; int i; d->m_finalityCommand = true; // default switch (command) { case CMD_HOST: { QString passwd; QString host, user; quint16 port; stream >> host >> port >> user >> passwd; d->m_state = d->InsideMethod; d->m_finalityCommand = false; setHost(host, port, user, passwd); d->m_state = d->Idle; } break; case CMD_CONNECT: { openConnection(); } break; case CMD_DISCONNECT: { closeConnection(); } break; case CMD_SLAVE_STATUS: { d->m_state = d->InsideMethod; d->m_finalityCommand = false; slave_status(); // TODO verify that the slave has called slaveStatus()? d->m_state = d->Idle; } break; case CMD_SLAVE_CONNECT: { d->onHold = false; QString app_socket; QDataStream stream(data); stream >> app_socket; d->appConnection.send(MSG_SLAVE_ACK); disconnectSlave(); d->isConnectedToApp = true; connectSlave(app_socket); virtual_hook(AppConnectionMade, nullptr); } break; case CMD_SLAVE_HOLD: { QUrl url; QDataStream stream(data); stream >> url; d->onHoldUrl = url; d->onHold = true; disconnectSlave(); d->isConnectedToApp = false; // Do not close connection! connectSlave(d->poolSocket); } break; case CMD_REPARSECONFIGURATION: { d->m_state = d->InsideMethod; d->m_finalityCommand = false; reparseConfiguration(); d->m_state = d->Idle; } break; case CMD_CONFIG: { stream >> d->configData; d->rebuildConfig(); delete d->remotefile; d->remotefile = nullptr; } break; case CMD_GET: { stream >> url; d->m_state = d->InsideMethod; get(url); d->verifyState("get()"); d->m_state = d->Idle; } break; case CMD_OPEN: { stream >> url >> i; QIODevice::OpenMode mode = QFlag(i); d->m_state = d->InsideMethod; open(url, mode); //krazy:exclude=syscalls d->m_state = d->Idle; } break; case CMD_PUT: { int permissions; qint8 iOverwrite, iResume; stream >> url >> iOverwrite >> iResume >> permissions; JobFlags flags; if (iOverwrite != 0) { flags |= Overwrite; } if (iResume != 0) { flags |= Resume; } // Remember that we need to send canResume(), TransferJob is expecting // it. Well, in theory this shouldn't be done if resume is true. // (the resume bool is currently unused) d->needSendCanResume = true /* !resume */; d->m_state = d->InsideMethod; put(url, permissions, flags); d->verifyState("put()"); d->m_state = d->Idle; } break; case CMD_STAT: { stream >> url; d->m_state = d->InsideMethod; stat(url); //krazy:exclude=syscalls d->verifyState("stat()"); d->m_state = d->Idle; } break; case CMD_MIMETYPE: { stream >> url; d->m_state = d->InsideMethod; mimetype(url); d->verifyState("mimetype()"); d->m_state = d->Idle; } break; case CMD_LISTDIR: { stream >> url; d->m_state = d->InsideMethod; listDir(url); d->verifyState("listDir()"); d->m_state = d->Idle; } break; case CMD_MKDIR: { stream >> url >> i; d->m_state = d->InsideMethod; mkdir(url, i); //krazy:exclude=syscalls d->verifyState("mkdir()"); d->m_state = d->Idle; } break; case CMD_RENAME: { qint8 iOverwrite; QUrl url2; stream >> url >> url2 >> iOverwrite; JobFlags flags; if (iOverwrite != 0) { flags |= Overwrite; } d->m_state = d->InsideMethod; rename(url, url2, flags); //krazy:exclude=syscalls d->verifyState("rename()"); d->m_state = d->Idle; } break; case CMD_SYMLINK: { qint8 iOverwrite; QString target; stream >> target >> url >> iOverwrite; JobFlags flags; if (iOverwrite != 0) { flags |= Overwrite; } d->m_state = d->InsideMethod; symlink(target, url, flags); d->verifyState("symlink()"); d->m_state = d->Idle; } break; case CMD_COPY: { int permissions; qint8 iOverwrite; QUrl url2; stream >> url >> url2 >> permissions >> iOverwrite; JobFlags flags; if (iOverwrite != 0) { flags |= Overwrite; } d->m_state = d->InsideMethod; copy(url, url2, permissions, flags); d->verifyState("copy()"); d->m_state = d->Idle; } break; case CMD_DEL: { qint8 isFile; stream >> url >> isFile; d->m_state = d->InsideMethod; del(url, isFile != 0); d->verifyState("del()"); d->m_state = d->Idle; } break; case CMD_CHMOD: { stream >> url >> i; d->m_state = d->InsideMethod; chmod(url, i); d->verifyState("chmod()"); d->m_state = d->Idle; } break; case CMD_CHOWN: { QString owner, group; stream >> url >> owner >> group; d->m_state = d->InsideMethod; chown(url, owner, group); d->verifyState("chown()"); d->m_state = d->Idle; } break; case CMD_SETMODIFICATIONTIME: { QDateTime dt; stream >> url >> dt; d->m_state = d->InsideMethod; setModificationTime(url, dt); d->verifyState("setModificationTime()"); d->m_state = d->Idle; } break; case CMD_SPECIAL: { d->m_state = d->InsideMethod; special(data); d->verifyState("special()"); d->m_state = d->Idle; } break; case CMD_META_DATA: { //qDebug() << "(" << getpid() << ") Incoming meta-data..."; stream >> mIncomingMetaData; d->rebuildConfig(); } break; case CMD_SUBURL: { stream >> url; d->m_state = d->InsideMethod; setSubUrl(url); d->verifyErrorFinishedNotCalled("setSubUrl()"); d->m_state = d->Idle; } break; case CMD_NONE: { qCWarning(KIO_CORE) << "Got unexpected CMD_NONE!"; } break; case CMD_MULTI_GET: { d->m_state = d->InsideMethod; multiGet(data); d->verifyState("multiGet()"); d->m_state = d->Idle; } break; case CMD_FILESYSTEMFREESPACE: { stream >> url; void *data = static_cast(&url); d->m_state = d->InsideMethod; virtual_hook(GetFileSystemFreeSpace, data); d->verifyState("fileSystemFreeSpace()"); d->m_state = d->Idle; } break; default: { // Some command we don't understand. // Just ignore it, it may come from some future version of KIO. } break; } } bool SlaveBase::checkCachedAuthentication(AuthInfo &info) { KPasswdServerClient *passwdServerClient = d->passwdServerClient(); return (passwdServerClient->checkAuthInfo(&info, metaData(QStringLiteral("window-id")).toLong(), metaData(QStringLiteral("user-timestamp")).toULong())); } void SlaveBase::dispatchOpenCommand(int command, const QByteArray &data) { QDataStream stream(data); switch (command) { case CMD_READ: { KIO::filesize_t bytes; stream >> bytes; read(bytes); break; } case CMD_WRITE: { write(data); break; } case CMD_SEEK: { KIO::filesize_t offset; stream >> offset; seek(offset); break; } case CMD_NONE: break; case CMD_CLOSE: close(); // must call finish(), which will set d->inOpenLoop=false break; default: // Some command we don't understand. // Just ignore it, it may come from some future version of KIO. break; } } bool SlaveBase::cacheAuthentication(const AuthInfo &info) { KPasswdServerClient *passwdServerClient = d->passwdServerClient(); passwdServerClient->addAuthInfo(info, metaData(QStringLiteral("window-id")).toLongLong()); return true; } int SlaveBase::connectTimeout() { bool ok; QString tmp = metaData(QStringLiteral("ConnectTimeout")); int result = tmp.toInt(&ok); if (ok) { return result; } return DEFAULT_CONNECT_TIMEOUT; } int SlaveBase::proxyConnectTimeout() { bool ok; QString tmp = metaData(QStringLiteral("ProxyConnectTimeout")); int result = tmp.toInt(&ok); if (ok) { return result; } return DEFAULT_PROXY_CONNECT_TIMEOUT; } int SlaveBase::responseTimeout() { bool ok; QString tmp = metaData(QStringLiteral("ResponseTimeout")); int result = tmp.toInt(&ok); if (ok) { return result; } return DEFAULT_RESPONSE_TIMEOUT; } int SlaveBase::readTimeout() { bool ok; QString tmp = metaData(QStringLiteral("ReadTimeout")); int result = tmp.toInt(&ok); if (ok) { return result; } return DEFAULT_READ_TIMEOUT; } bool SlaveBase::wasKilled() const { return d->wasKilled; } void SlaveBase::setKillFlag() { d->wasKilled = true; } void SlaveBase::send(int cmd, const QByteArray &arr) { slaveWriteError = false; if (!d->appConnection.send(cmd, arr)) // Note that slaveWriteError can also be set by sigpipe_handler { slaveWriteError = true; } if (slaveWriteError) { exit(); } } void SlaveBase::virtual_hook(int id, void *data) { Q_UNUSED(data); switch(id) { case GetFileSystemFreeSpace: { error(ERR_UNSUPPORTED_ACTION, unsupportedActionErrorString(mProtocol, CMD_FILESYSTEMFREESPACE)); } break; } } void SlaveBase::lookupHost(const QString &host) { KIO_DATA << host; send(MSG_HOST_INFO_REQ, data); } int SlaveBase::waitForHostInfo(QHostInfo &info) { QByteArray data; int result = waitForAnswer(CMD_HOST_INFO, 0, data); if (result == -1) { info.setError(QHostInfo::UnknownError); info.setErrorString(i18n("Unknown Error")); return result; } QDataStream stream(data); QString hostName; QList addresses; int error; QString errorString; stream >> hostName >> addresses >> error >> errorString; info.setHostName(hostName); info.setAddresses(addresses); info.setError(QHostInfo::HostInfoError(error)); info.setErrorString(errorString); return result; } PrivilegeOperationStatus SlaveBase::requestPrivilegeOperation() { if (d->m_privilegeOperationStatus == OperationNotAllowed) { QByteArray buffer; send(MSG_PRIVILEGE_EXEC); waitForAnswer(MSG_PRIVILEGE_EXEC, 0, buffer); QDataStream ds(buffer); ds >> d->m_privilegeOperationStatus >> d->m_warningCaption >> d-> m_warningMessage; } if (metaData(QStringLiteral("UnitTesting")) != QLatin1String("true") && d->m_privilegeOperationStatus == OperationAllowed && !d->m_confirmationAsked) { d->m_privilegeOperationStatus = d->askConfirmation(); d->m_confirmationAsked = true; } return KIO::PrivilegeOperationStatus(d->m_privilegeOperationStatus); } void SlaveBase::addTemporaryAuthorization(const QString &action) { d->m_tempAuths.insert(action); } diff --git a/src/core/slavebase.h b/src/core/slavebase.h index 6bcd04d7..12017a82 100644 --- a/src/core/slavebase.h +++ b/src/core/slavebase.h @@ -1,1039 +1,1049 @@ /* Copyright (C) 2000 David Faure This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef SLAVEBASE_H #define SLAVEBASE_H #include #include #include #include "job_base.h" // for KIO::JobFlags #include #include class KConfigGroup; class KRemoteEncoding; class QUrl; namespace KIO { class Connection; class SlaveBasePrivate; /** * @class KIO::SlaveBase slavebase.h * * There are two classes that specifies the protocol between application (job) * and kioslave. SlaveInterface is the class to use on the application end, * SlaveBase is the one to use on the slave end. * * Slave implementations should simply inherit SlaveBase * * A call to foo() results in a call to slotFoo() on the other end. * * Note that a kioslave doesn't have a Qt event loop. When idle, it's waiting for a command * on the socket that connects it to the application. So don't expect a kioslave to react * to D-Bus signals for instance. KIOSlaves are short-lived anyway, so any kind of watching * or listening for notifications should be done elsewhere, for instance in a kded module * (see kio_desktop's desktopnotifier.cpp for an example). * * If a kioslave needs a Qt event loop within the implementation of one method, e.g. to * wait for an asynchronous operation to finish, that is possible, using QEventLoop. */ class KIOCORE_EXPORT SlaveBase { public: SlaveBase(const QByteArray &protocol, const QByteArray &pool_socket, const QByteArray &app_socket); virtual ~SlaveBase(); /** * @internal * Terminate the slave by calling the destructor and then ::exit() */ Q_NORETURN void exit(); /** * @internal */ void dispatchLoop(); /////////// // Message Signals to send to the job /////////// /** * Sends data in the slave to the job (i.e. in get). * * To signal end of data, simply send an empty * QByteArray(). * * @param data the data read by the slave */ void data(const QByteArray &data); /** * Asks for data from the job. * @see readData */ void dataReq(); /** * open succeeds * @see open() */ void opened(); /** * Call to signal an error. * This also finishes the job, so you must not call * finished() after calling this. * * If the error code is KIO::ERR_SLAVE_DEFINED then the * _text should contain the complete translated text of * of the error message. * * For all other error codes, _text should match the corresponding * error code. Usually, _text is a file or host name, or the error which * was passed from the server.
* For example, for KIO::ERR_DOES_NOT_EXIST, _text may only * be the file or folder which does not exist, nothing else. Otherwise, * this would break error strings generated by KIO::buildErrorString().
* If you have to add more details than what the standard error codes * provide, you'll need to use KIO::ERR_SLAVE_DEFINED. * For a complete list of what _text should contain for each error code, * look at the source of KIO::buildErrorString(). * * You can add rich text markup to the message, the places where the * error message will be displayed are rich text aware. * * @see KIO::Error * @see KIO::buildErrorString * @param _errid the error code from KIO::Error * @param _text the rich text error message */ void error(int _errid, const QString &_text); /** * Call in openConnection, if you reimplement it, when you're done. */ void connected(); /** * Call to signal successful completion of any command * besides openConnection and closeConnection. Do not * call this after calling error(). */ void finished(); /** * Call to signal that data from the sub-URL is needed */ void needSubUrlData(); /** * Used to report the status of the slave. * @param host the slave is currently connected to. (Should be * empty if not connected) * @param connected Whether an actual network connection exists. **/ void slaveStatus(const QString &host, bool connected); /** * Call this from stat() to express details about an object, the * UDSEntry customarily contains the atoms describing file name, size, * mimetype, etc. * @param _entry The UDSEntry containing all of the object attributes. */ void statEntry(const UDSEntry &_entry); /** * Call this in listDir, each time you have a bunch of entries * to report. * @param _entry The UDSEntry containing all of the object attributes. */ void listEntries(const UDSEntryList &_entry); /** * Call this at the beginning of put(), to give the size of the existing * partial file, if there is one. The @p offset argument notifies the * other job (the one that gets the data) about the offset to use. * In this case, the boolean returns whether we can indeed resume or not * (we can't if the protocol doing the get() doesn't support setting an offset) */ bool canResume(KIO::filesize_t offset); /** * Call this at the beginning of get(), if the "range-start" metadata was set * and returning byte ranges is implemented by this protocol. */ void canResume(); /////////// // Info Signals to send to the job /////////// /** * Call this in get and copy, to give the total size * of the file. */ void totalSize(KIO::filesize_t _bytes); /** * Call this during get and copy, once in a while, * to give some info about the current state. * Don't emit it in listDir, listEntries speaks for itself. */ void processedSize(KIO::filesize_t _bytes); void position(KIO::filesize_t _pos); void written(KIO::filesize_t _bytes); /** * Only use this if you can't know in advance the size of the * copied data. For example, if you're doing variable bitrate * compression of the source. * * STUB ! Currently unimplemented. Here now for binary compatibility. * * Call this during get and copy, once in a while, * to give some info about the current state. * Don't emit it in listDir, listEntries speaks for itself. */ void processedPercent(float percent); /** * Call this in get and copy, to give the current transfer * speed, but only if it can't be calculated out of the size you * passed to processedSize (in most cases you don't want to call it) */ void speed(unsigned long _bytes_per_second); /** * Call this to signal a redirection * The job will take care of going to that url. */ void redirection(const QUrl &_url); /** * Tell that we will only get an error page here. * This means: the data you'll get isn't the data you requested, * but an error page (usually HTML) that describes an error. */ void errorPage(); /** * Call this in mimetype() and in get(), when you know the mimetype. * See mimetype about other ways to implement it. */ void mimeType(const QString &_type); /** * Call to signal a warning, to be displayed in a dialog box. */ void warning(const QString &msg); /** * Call to signal a message, to be displayed if the application wants to, * for instance in a status bar. Usual examples are "connecting to host xyz", etc. */ void infoMessage(const QString &msg); /** * Type of message box. Should be kept in sync with KMessageBox::ButtonCode. */ enum MessageBoxType { QuestionYesNo = 1, WarningYesNo = 2, WarningContinueCancel = 3, WarningYesNoCancel = 4, Information = 5, SSLMessageBox = 6 }; /** * Button codes. Should be kept in sync with KMessageBox::ButtonCode */ enum ButtonCode { Ok = 1, Cancel = 2, Yes = 3, No = 4, Continue = 5 }; /** * Call this to show a message box from the slave * @param type type of message box: QuestionYesNo, WarningYesNo, WarningContinueCancel... * @param text Message string. May contain newlines. * @param caption Message box title. * @param buttonYes The text for the first button. * The default is i18n("&Yes"). * @param buttonNo The text for the second button. * The default is i18n("&No"). * Note: for ContinueCancel, buttonYes is the continue button and buttonNo is unused. * and for Information, none is used. * @return a button code, as defined in ButtonCode, or 0 on communication error. */ int messageBox(MessageBoxType type, const QString &text, const QString &caption = QString(), const QString &buttonYes = QString(), const QString &buttonNo = QString()); /** * Call this to show a message box from the slave * @param text Message string. May contain newlines. * @param type type of message box: QuestionYesNo, WarningYesNo, WarningContinueCancel... * @param caption Message box title. * @param buttonYes The text for the first button. * The default is i18n("&Yes"). * @param buttonNo The text for the second button. * The default is i18n("&No"). * Note: for ContinueCancel, buttonYes is the continue button and buttonNo is unused. * and for Information, none is used. * @param dontAskAgainName the name used to store result from 'Do not ask again' checkbox. * @return a button code, as defined in ButtonCode, or 0 on communication error. */ int messageBox(const QString &text, MessageBoxType type, const QString &caption = QString(), const QString &buttonYes = QString(), const QString &buttonNo = QString(), const QString &dontAskAgainName = QString()); /** * Sets meta-data to be send to the application before the first * data() or finished() signal. */ void setMetaData(const QString &key, const QString &value); /** * Queries for the existence of a certain config/meta-data entry * send by the application to the slave. */ bool hasMetaData(const QString &key) const; /** * Queries for config/meta-data send by the application to the slave. */ QString metaData(const QString &key) const; /** * @internal for ForwardingSlaveBase * Contains all metadata (but no config) sent by the application to the slave. */ MetaData allMetaData() const; /** * Returns a map to query config/meta-data information from. * * The application provides the slave with all configuration information * relevant for the current protocol and host. * * Use configValue() as shorcut. * @since 5.64 */ QMap mapConfig() const; /** * Returns a bool from the config/meta-data information. * @since 5.64 */ bool configValue(QString key, bool defaultValue) const; /** * Returns an int from the config/meta-data information. * @since 5.64 */ int configValue(QString key, int defaultValue) const; /** * Returns a QString from the config/meta-data information. * @since 5.64 */ QString configValue(QString key, const QString &defaultValue=QString()) const; +#if KIOCORE_ENABLE_DEPRECATED_SINCE(5, 64) /** * Returns a configuration object to query config/meta-data information * from. * * The application provides the slave with all configuration information * relevant for the current protocol and host. * - * TODO KF6: remove, perhaps rename mapConfig() to config() * @deprecated since 5.64 use mapConfig instead */ - KIOCORE_DEPRECATED KConfigGroup *config(); + KIOCORE_DEPRECATED_VERSION(5, 64, "Use SlaveBase::mapConfig()") + KConfigGroup *config(); + // KF6: perhaps rename mapConfig() to config() when removing this +#endif /** * Returns an object that can translate remote filenames into proper * Unicode forms. This encoding can be set by the user. */ KRemoteEncoding *remoteEncoding(); /////////// // Commands sent by the job, the slave has to // override what it wants to implement /////////// /** * Set the host * * Called directly by createSlave, this is why there is no equivalent in * SlaveInterface, unlike the other methods. * * This method is called whenever a change in host, port or user occurs. */ virtual void setHost(const QString &host, quint16 port, const QString &user, const QString &pass); /** * Prepare slave for streaming operation */ virtual void setSubUrl(const QUrl &url); /** * Opens the connection (forced) * When this function gets called the slave is operating in * connection-oriented mode. * When a connection gets lost while the slave operates in * connection oriented mode, the slave should report * ERR_CONNECTION_BROKEN instead of reconnecting. The user is * expected to disconnect the slave in the error handler. */ virtual void openConnection(); /** * Closes the connection (forced) * Called when the application disconnects the slave to close * any open network connections. * * When the slave was operating in connection-oriented mode, * it should reset itself to connectionless (default) mode. */ virtual void closeConnection(); /** * get, aka read. * @param url the full url for this request. Host, port and user of the URL * can be assumed to be the same as in the last setHost() call. * * The slave should first "emit" the mimetype by calling mimeType(), * and then "emit" the data using the data() method. * * The reason why we need get() to emit the mimetype is: * when pasting a URL in krunner, or konqueror's location bar, * we have to find out what is the mimetype of that URL. * Rather than doing it with a call to mimetype(), then the app or part * would have to do a second request to the same server, this is done * like this: get() is called, and when it emits the mimetype, the job * is put on hold and the right app or part is launched. When that app * or part calls get(), the slave is magically reused, and the download * can now happen. All with a single call to get() in the slave. * This mechanism is also described in KIO::get(). */ virtual void get(const QUrl &url); /** * open. * @param url the full url for this request. Host, port and user of the URL * can be assumed to be the same as in the last setHost() call. * @param mode see \ref QIODevice::OpenMode */ virtual void open(const QUrl &url, QIODevice::OpenMode mode); /** * read. * @param size the requested amount of data to read * @see KIO::FileJob::read() */ virtual void read(KIO::filesize_t size); /** * write. * @param data the data to write * @see KIO::FileJob::write() */ virtual void write(const QByteArray &data); /** * seek. * @param offset the requested amount of data to read * @see KIO::FileJob::read() */ virtual void seek(KIO::filesize_t offset); /** * close. * @see KIO::FileJob::close() */ virtual void close(); /** * put, i.e. write data into a file. * * @param url where to write the file * @param permissions may be -1. In this case no special permission mode is set. * @param flags We support Overwrite here. Hopefully, we're going to * support Resume in the future, too. * If the file indeed already exists, the slave should NOT apply the * permissions change to it. * The support for resuming using .part files is done by calling canResume(). * * IMPORTANT: Use the "modified" metadata in order to set the modification time of the file. * * @see canResume() */ virtual void put(const QUrl &url, int permissions, JobFlags flags); /** * Finds all details for one file or directory. * The information returned is the same as what listDir returns, * but only for one file or directory. * Call statEntry() after creating the appropriate UDSEntry for this * url. * * You can use the "details" metadata to optimize this method to only * do as much work as needed by the application. * By default details is 2 (all details wanted, including modification time, size, etc.), * details==1 is used when deleting: we don't need all the information if it takes * too much time, no need to follow symlinks etc. * details==0 is used for very simple probing: we'll only get the answer * "it's a file or a directory (or a symlink), or it doesn't exist". */ virtual void stat(const QUrl &url); /** * Finds mimetype for one file or directory. * * This method should either emit 'mimeType' or it * should send a block of data big enough to be able * to determine the mimetype. * * If the slave doesn't reimplement it, a get will * be issued, i.e. the whole file will be downloaded before * determining the mimetype on it - this is obviously not a * good thing in most cases. */ virtual void mimetype(const QUrl &url); /** * Lists the contents of @p url. * The slave should emit ERR_CANNOT_ENTER_DIRECTORY if it doesn't exist, * if we don't have enough permissions. * It should also emit totalFiles as soon as it knows how many * files it will list. * You should not list files if the path in @p url is empty, but redirect * to a non-empty path instead. */ virtual void listDir(const QUrl &url); /** * Create a directory * @param url path to the directory to create * @param permissions the permissions to set after creating the directory * (-1 if no permissions to be set) * The slave emits ERR_CANNOT_MKDIR if failure. */ virtual void mkdir(const QUrl &url, int permissions); /** * Rename @p oldname into @p newname. * If the slave returns an error ERR_UNSUPPORTED_ACTION, the job will * ask for copy + del instead. * * Important: the slave must implement the logic "if the destination already * exists, error ERR_DIR_ALREADY_EXIST or ERR_FILE_ALREADY_EXIST". * For performance reasons no stat is done in the destination before hand, * the slave must do it. * * By default, rename() is only called when renaming (moving) from * yourproto://host/path to yourproto://host/otherpath. * * If you set renameFromFile=true then rename() will also be called when * moving a file from file:///path to yourproto://host/otherpath. * Otherwise such a move would have to be done the slow way (copy+delete). * See KProtocolManager::canRenameFromFile() for more details. * * If you set renameToFile=true then rename() will also be called when * moving a file from yourproto: to file:. * See KProtocolManager::canRenameToFile() for more details. * * @param src where to move the file from * @param dest where to move the file to * @param flags We support Overwrite here */ virtual void rename(const QUrl &src, const QUrl &dest, JobFlags flags); /** * Creates a symbolic link named @p dest, pointing to @p target, which * may be a relative or an absolute path. * @param target The string that will become the "target" of the link (can be relative) * @param dest The symlink to create. * @param flags We support Overwrite here */ virtual void symlink(const QString &target, const QUrl &dest, JobFlags flags); /** * Change permissions on @p url * The slave emits ERR_DOES_NOT_EXIST or ERR_CANNOT_CHMOD */ virtual void chmod(const QUrl &url, int permissions); /** * Change ownership of @p url * The slave emits ERR_DOES_NOT_EXIST or ERR_CANNOT_CHOWN */ virtual void chown(const QUrl &url, const QString &owner, const QString &group); /** * Sets the modification time for @url * For instance this is what CopyJob uses to set mtime on dirs at the end of a copy. * It could also be used to set the mtime on any file, in theory. * The usual implementation on unix is to call utime(path, &myutimbuf). * The slave emits ERR_DOES_NOT_EXIST or ERR_CANNOT_SETTIME */ virtual void setModificationTime(const QUrl &url, const QDateTime &mtime); /** * Copy @p src into @p dest. * * By default, copy() is only called when copying a file from * yourproto://host/path to yourproto://host/otherpath. * * If you set copyFromFile=true then copy() will also be called when * moving a file from file:///path to yourproto://host/otherpath. * Otherwise such a copy would have to be done the slow way (get+put). * See also KProtocolManager::canCopyFromFile(). * * If you set copyToFile=true then copy() will also be called when * moving a file from yourproto: to file:. * See also KProtocolManager::canCopyToFile(). * * If the slave returns an error ERR_UNSUPPORTED_ACTION, the job will * ask for get + put instead. * @param src where to copy the file from (decoded) * @param dest where to copy the file to (decoded) * @param permissions may be -1. In this case no special permission mode is set. * @param flags We support Overwrite here * * Don't forget to set the modification time of @p dest to be the modification time of @p src. */ virtual void copy(const QUrl &src, const QUrl &dest, int permissions, JobFlags flags); /** * Delete a file or directory. * @param url file/directory to delete * @param isfile if true, a file should be deleted. * if false, a directory should be deleted. * * By default, del() on a directory should FAIL if the directory is not empty. * However, if metadata("recurse") == "true", then the slave can do a recursive deletion. * This behavior is only invoked if the slave specifies deleteRecursive=true in its protocol file. */ virtual void del(const QUrl &url, bool isfile); /** * Change the destination of a symlink * @param url the url of the symlink to modify * @param target the new destination (target) of the symlink */ virtual void setLinkDest(const QUrl &url, const QString &target); /** * Used for any command that is specific to this slave (protocol) * Examples are : HTTP POST, mount and unmount (kio_file) * * @param data packed data; the meaning is completely dependent on the * slave, but usually starts with an int for the command number. * Document your slave's commands, at least in its header file. */ virtual void special(const QByteArray &data); /** * Used for multiple get. Currently only used foir HTTP pielining * support. * * @param data packed data; Contains number of URLs to fetch, and for * each URL the URL itself and its associated MetaData. */ virtual void multiGet(const QByteArray &data); /** * Called to get the status of the slave. Slave should respond * by calling slaveStatus(...) */ virtual void slave_status(); /** * Called by the scheduler to tell the slave that the configuration * changed (i.e. proxy settings) . */ virtual void reparseConfiguration(); /** * @return timeout value for connecting to remote host. */ int connectTimeout(); /** * @return timeout value for connecting to proxy in secs. */ int proxyConnectTimeout(); /** * @return timeout value for read from first data from * remote host in seconds. */ int responseTimeout(); /** * @return timeout value for read from subsequent data from * remote host in secs. */ int readTimeout(); /** * This function sets a timeout of @p timeout seconds and calls * special(data) when the timeout occurs as if it was called by the * application. * * A timeout can only occur when the slave is waiting for a command * from the application. * * Specifying a negative timeout cancels a pending timeout. * * Only one timeout at a time is supported, setting a timeout * cancels any pending timeout. */ void setTimeoutSpecialCommand(int timeout, const QByteArray &data = QByteArray()); ///////////////// // Dispatching (internal) //////////////// /** * @internal */ virtual void dispatch(int command, const QByteArray &data); /** * @internal */ virtual void dispatchOpenCommand(int command, const QByteArray &data); /** * Read data sent by the job, after a dataReq * * @param buffer buffer where data is stored * @return 0 on end of data, * > 0 bytes read * < 0 error **/ int readData(QByteArray &buffer); +#if KIOCORE_ENABLE_DEPRECATED_SINCE(5, 0) /** * It collects entries and emits them via listEntries * when enough of them are there or a certain time * frame exceeded (to make sure the app gets some * items in time but not too many items one by one * as this will cause a drastic performance penalty). * @param _entry The UDSEntry containing all of the object attributes. * @param ready set to true after emitting all items. @p _entry is not * used in this case * @deprecated since 5.0. the listEntry(entry, true) indicated * that the entry listing was completed. However, each slave should * already call finished() to also tell us that we're done listing. * You should make sure that finished() is called when the entry * listing is completed and simply remove the call to listEntry(entry, true). */ -#ifndef KIOCORE_NO_DEPRECATED - KIOCORE_DEPRECATED void listEntry(const UDSEntry &_entry, bool ready); + KIOCORE_DEPRECATED_VERSION(5, 0, "See API docs") + void listEntry(const UDSEntry &_entry, bool ready); #endif /** * It collects entries and emits them via listEntries * when enough of them are there or a certain time * frame exceeded (to make sure the app gets some * items in time but not too many items one by one * as this will cause a drastic performance penalty). * @param entry The UDSEntry containing all of the object attributes. * @since 5.0 */ void listEntry(const UDSEntry &entry); /** * internal function to connect a slave to/ disconnect from * either the slave pool or the application */ void connectSlave(const QString &path); void disconnectSlave(); /** * Prompt the user for Authorization info (login & password). * * Use this function to request authorization information from * the end user. You can also pass an error message which explains * why a previous authorization attempt failed. Here is a very * simple example: * * \code * KIO::AuthInfo authInfo; * int errorCode = openPasswordDialogV2(authInfo); * if (!errorCode) { * qDebug() << QLatin1String("User: ") << authInfo.username; * qDebug() << QLatin1String("Password: not displayed here!"); * } else { * error(errorCode, QString()); * } * \endcode * * You can also preset some values like the username, caption or * comment as follows: * * \code * KIO::AuthInfo authInfo; * authInfo.caption = i18n("Acme Password Dialog"); * authInfo.username = "Wile E. Coyote"; * QString errorMsg = i18n("You entered an incorrect password."); * int errorCode = openPasswordDialogV2(authInfo, errorMsg); * [...] * \endcode * * \note You should consider using checkCachedAuthentication() to * see if the password is available in kpasswdserver before calling * this function. * * \note A call to this function can fail and return @p false, * if the password server could not be started for whatever reason. * * \note This function does not store the password information * automatically (and has not since kdelibs 4.7). If you want to * store the password information in a persistent storage like * KWallet, then you MUST call @ref cacheAuthentication. * * @see checkCachedAuthentication * @param info See AuthInfo. * @param errorMsg Error message to show * @return a KIO error code: NoError (0), KIO::USER_CANCELED, or other error codes. */ int openPasswordDialogV2(KIO::AuthInfo &info, const QString &errorMsg = QString()); +#if KIOCORE_ENABLE_DEPRECATED_SINCE(5, 24) /** * @deprecated since KF 5.24, use openPasswordDialogV2. * The return value works differently: * instead of * if (!openPasswordDialog()) { error(USER_CANCELED); } * store and pass the return value to error(), when NOT zero, * as shown documentation for openPasswordDialogV2(). */ - KIOCORE_DEPRECATED bool openPasswordDialog(KIO::AuthInfo &info, const QString &errorMsg = QString()); + KIOCORE_DEPRECATED_VERSION(5, 24, "Use SlaveBase::openPasswordDialogV2(...)") + bool openPasswordDialog(KIO::AuthInfo &info, const QString &errorMsg = QString()); +#endif /** * Checks for cached authentication based on parameters * given by @p info. * * Use this function to check if any cached password exists * for the URL given by @p info. If @p AuthInfo::realmValue * and/or @p AuthInfo::verifyPath flag is specified, then * they will also be factored in determining the presence * of a cached password. Note that @p Auth::url is a required * parameter when attempting to check for cached authorization * info. Here is a simple example: * * \code * AuthInfo info; * info.url = QUrl("http://www.foobar.org/foo/bar"); * info.username = "somename"; * info.verifyPath = true; * if ( !checkCachedAuthentication( info ) ) * { * int errorCode = openPasswordDialogV2(info); * .... * } * \endcode * * @param info See AuthInfo. * @return @p true if cached Authorization is found, false otherwise. */ bool checkCachedAuthentication(AuthInfo &info); /** * Caches @p info in a persistent storage like KWallet. * * Note that calling openPasswordDialogV2 does not store passwords * automatically for you (and has not since kdelibs 4.7). * * Here is a simple example of how to use cacheAuthentication: * * \code * AuthInfo info; * info.url = QUrl("http://www.foobar.org/foo/bar"); * info.username = "somename"; * info.verifyPath = true; * if ( !checkCachedAuthentication( info ) ) { * int errorCode = openPasswordDialogV2(info); * if (!errorCode) { * if (info.keepPassword) { // user asked password be save/remembered * cacheAuthentication(info); * } * } * } * \endcode * * @param info See AuthInfo. * @return @p true if @p info was successfully cached. */ bool cacheAuthentication(const AuthInfo &info); +#if KIOCORE_ENABLE_DEPRECATED_SINCE(5, 0) /** - * @deprecated for a very very long time, not implemented anymore - * Probably dates back to model dialup times. - * * Used by the slave to check if it can connect * to a given host. This should be called where the slave is ready * to do a ::connect() on a socket. For each call to * requestNetwork must exist a matching call to * dropNetwork, or the system will stay online until * KNetMgr gets closed (or the SlaveBase gets destructed)! * * If KNetMgr is not running, then this is a no-op and returns true * * @param host tells the netmgr the host the slave wants to connect * to. As this could also be a proxy, we can't just take * the host currently connected to (but that's the default * value) * * @return true in theory, the host is reachable * false the system is offline and the host is in a remote network. + * + * @deprecated Since 5.0, for a very very long time, not implemented anymore + * Probably dates back to model dialup times. */ -#ifndef KIOCORE_NO_DEPRECATED + KIOCORE_DEPRECATED_VERSION(5, 0, "Not implemented & used") bool requestNetwork(const QString &host = QString()); #endif +#if KIOCORE_ENABLE_DEPRECATED_SINCE(5, 0) /** - * @deprecated for a very very long time, not implemented anymore - * Probably dates back to model dialup times. * * Used by the slave to withdraw a connection requested by * requestNetwork. This function cancels the last call to * requestNetwork. If a client uses more than one internet * connection, it must use dropNetwork(host) to * stop each request. * * If KNetMgr is not running, then this is a no-op. * * @param host the host passed to requestNetwork * * A slave should call this function every time it disconnect from a host. + * * + * @deprecated Since 5.0, for a very very long time, not implemented anymore + * Probably dates back to model dialup times. * */ -#ifndef KIOCORE_NO_DEPRECATED + KIOCORE_DEPRECATED_VERSION(5, 0, "Not implemented & used") void dropNetwork(const QString &host = QString()); #endif /** * Wait for an answer to our request, until we get @p expected1 or @p expected2 * @return the result from readData, as well as the cmd in *pCmd if set, and the data in @p data */ int waitForAnswer(int expected1, int expected2, QByteArray &data, int *pCmd = nullptr); /** * Internal function to transmit meta data to the application. * m_outgoingMetaData will be cleared; this means that if the slave is for * example put on hold and picked up by a different KIO::Job later the new * job will not see the metadata sent before. * See kio/DESIGN.krun for an overview of the state * progression of a job/slave. * @warning calling this method may seriously interfere with the operation * of KIO which relies on the presence of some metadata at some points in time. * You should not use it if you are not familiar with KIO and not before * the slave is connected to the last job before returning to idle state. */ void sendMetaData(); /** * Internal function to transmit meta data to the application. * Like sendMetaData() but m_outgoingMetaData will not be cleared. * This method is mainly useful in code that runs before the slave is connected * to its final job. */ void sendAndKeepMetaData(); /** If your ioslave was killed by a signal, wasKilled() returns true. Check it regularly in lengthy functions (e.g. in get();) and return as fast as possible from this function if wasKilled() returns true. This will ensure that your slave destructor will be called correctly. */ bool wasKilled() const; /** Internally used. * @internal */ void setKillFlag(); /** Internally used * @internal */ void lookupHost(const QString &host); /** Internally used * @internal */ int waitForHostInfo(QHostInfo &info); /** * Checks with job if privilege operation is allowed. * @return privilege operation status. * @see PrivilegeOperationStatus * @since 5.43 */ PrivilegeOperationStatus requestPrivilegeOperation(); /** * Adds @p action to the list of PolicyKit actions which the * slave is authorized to perform. * * @param action the PolicyKit action * @since 5.45 */ void addTemporaryAuthorization(const QString &action); protected: /** * Name of the protocol supported by this slave */ QByteArray mProtocol; //Often used by TcpSlaveBase and unlikely to change MetaData mOutgoingMetaData; MetaData mIncomingMetaData; enum VirtualFunctionId { AppConnectionMade = 0, GetFileSystemFreeSpace = 1 // KF6 TODO: Turn into a virtual method }; virtual void virtual_hook(int id, void *data); private: // This helps catching missing tr()/i18n() calls in error(). void error(int _errid, const QByteArray &_text); void send(int cmd, const QByteArray &arr = QByteArray()); SlaveBasePrivate *const d; friend class SlaveBasePrivate; }; /** * Returns an appropriate error message if the given command @p cmd * is an unsupported action (ERR_UNSUPPORTED_ACTION). * @param protocol name of the protocol * @param cmd given command * @see enum Command */ KIOCORE_EXPORT QString unsupportedActionErrorString(const QString &protocol, int cmd); } #endif diff --git a/src/core/slaveinterface.h b/src/core/slaveinterface.h index ae8cd1d5..327f44b3 100644 --- a/src/core/slaveinterface.h +++ b/src/core/slaveinterface.h @@ -1,199 +1,216 @@ /* This file is part of the KDE project Copyright (C) 2000 David Faure This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef __kio_slaveinterface_h #define __kio_slaveinterface_h #include #include #include #include #include #include class QUrl; namespace KIO { class Connection; // better there is one ... class SlaveInterfacePrivate; // Definition of enum Command has been moved to global.h /** * Identifiers for KIO informational messages. */ enum Info { INF_TOTAL_SIZE = 10, INF_PROCESSED_SIZE = 11, INF_SPEED, INF_REDIRECTION = 20, INF_MIME_TYPE = 21, INF_ERROR_PAGE = 22, INF_WARNING = 23, - INF_GETTING_FILE, ///< @deprecated +#if KIOCORE_ENABLE_DEPRECATED_SINCE(3, 0) + INF_GETTING_FILE, ///< @deprecated Since 3.0 +#else + INF_GETTING_FILE_DEPRECATED_DO_NOT_USE, +#endif INF_UNUSED = 25, ///< now unused INF_INFOMESSAGE, INF_META_DATA, INF_NETWORK_STATUS, INF_MESSAGEBOX, INF_POSITION // add new ones here once a release is done, to avoid breaking binary compatibility }; /** * Identifiers for KIO data messages. */ enum Message { MSG_DATA = 100, MSG_DATA_REQ, MSG_ERROR, MSG_CONNECTED, MSG_FINISHED, MSG_STAT_ENTRY, // 105 MSG_LIST_ENTRIES, MSG_RENAMED, ///< unused MSG_RESUME, - MSG_SLAVE_STATUS, ///< only for compatibility, use V2 for KF >= 5.45. TODO KF6: remove +#if KIOCORE_ENABLE_DEPRECATED_SINCE(5, 45) + MSG_SLAVE_STATUS, ///< @deprecated Since 5.45, use MSG_SLAVE_STATUS_V2 +#else + MSG_SLAVE_STATUS_DEPRECATED_DO_NOT_USE, +#endif MSG_SLAVE_ACK, // 110 MSG_NET_REQUEST, MSG_NET_DROP, MSG_NEED_SUBURL_DATA, MSG_CANRESUME, - MSG_AUTH_KEY, ///< @deprecated - MSG_DEL_AUTH_KEY, ///< @deprecated +#if KIOCORE_ENABLE_DEPRECATED_SINCE(3, 1) + MSG_AUTH_KEY, ///< @deprecated Since 3.1 + MSG_DEL_AUTH_KEY, ///< @deprecated Since 3.1 +#else + MSG_AUTH_KEY_DEPRECATED_DO_NOT_USE, + MSG_DEL_AUTH_KEY_DEPRECATED_DO_NOT_USE, +#endif MSG_OPENED, MSG_WRITTEN, MSG_HOST_INFO_REQ, MSG_PRIVILEGE_EXEC, MSG_SLAVE_STATUS_V2 // add new ones here once a release is done, to avoid breaking binary compatibility }; /** * @class KIO::SlaveInterface slaveinterface.h * * There are two classes that specifies the protocol between application * ( KIO::Job) and kioslave. SlaveInterface is the class to use on the application * end, SlaveBase is the one to use on the slave end. * * A call to foo() results in a call to slotFoo() on the other end. */ class KIOCORE_EXPORT SlaveInterface : public QObject { Q_OBJECT protected: SlaveInterface(SlaveInterfacePrivate &dd, QObject *parent = nullptr); public: virtual ~SlaveInterface(); +#if KIOCORE_ENABLE_DEPRECATED_SINCE(5, 0) // TODO KF6: remove these methods, Connection isn't an exported class - KIOCORE_DEPRECATED void setConnection(Connection *connection); - KIOCORE_DEPRECATED Connection *connection() const; + KIOCORE_DEPRECATED_VERSION(5, 0, "Do not use") + void setConnection(Connection *connection); + KIOCORE_DEPRECATED_VERSION(5, 0, "Do not use") + Connection *connection() const; +#endif // Send our answer to the MSG_RESUME (canResume) request // (to tell the "put" job whether to resume or not) void sendResumeAnswer(bool resume); /** * Sends our answer for the INF_MESSAGEBOX request. * * @since 4.11 */ void sendMessageBoxAnswer(int result); void setOffset(KIO::filesize_t offset); KIO::filesize_t offset() const; Q_SIGNALS: /////////// // Messages sent by the slave /////////// void data(const QByteArray &); void dataReq(); void error(int, const QString &); void connected(); void finished(); void slaveStatus(qint64, const QByteArray &, const QString &, bool); void listEntries(const KIO::UDSEntryList &); void statEntry(const KIO::UDSEntry &); void needSubUrlData(); void canResume(KIO::filesize_t); void open(); void written(KIO::filesize_t); void close(); void privilegeOperationRequested(); /////////// // Info sent by the slave ////////// void metaData(const KIO::MetaData &); void totalSize(KIO::filesize_t); void processedSize(KIO::filesize_t); void redirection(const QUrl &); void position(KIO::filesize_t); void speed(unsigned long); void errorPage(); void mimeType(const QString &); void warning(const QString &); void infoMessage(const QString &); //void connectFinished(); //it does not get emitted anywhere protected: ///////////////// // Dispatching //////////////// virtual bool dispatch(); virtual bool dispatch(int _cmd, const QByteArray &data); void messageBox(int type, const QString &text, const QString &caption, const QString &buttonYes, const QString &buttonNo); void messageBox(int type, const QString &text, const QString &caption, const QString &buttonYes, const QString &buttonNo, const QString &dontAskAgainName); // I need to identify the slaves void requestNetwork(const QString &, const QString &); void dropNetwork(const QString &, const QString &); protected Q_SLOTS: void calcSpeed(); protected: SlaveInterfacePrivate *const d_ptr; Q_DECLARE_PRIVATE(SlaveInterface) private: Q_PRIVATE_SLOT(d_func(), void slotHostInfo(QHostInfo)) }; } #endif diff --git a/src/core/statjob.cpp b/src/core/statjob.cpp index df982f65..ca552266 100644 --- a/src/core/statjob.cpp +++ b/src/core/statjob.cpp @@ -1,216 +1,212 @@ /* This file is part of the KDE libraries Copyright (C) 2000 Stephan Kulow 2000-2009 David Faure This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "statjob.h" #include "job_p.h" #include "slave.h" #include "scheduler.h" #include #include using namespace KIO; class KIO::StatJobPrivate: public SimpleJobPrivate { public: inline StatJobPrivate(const QUrl &url, int command, const QByteArray &packedArgs) : SimpleJobPrivate(url, command, packedArgs), m_bSource(true), m_details(2) {} UDSEntry m_statResult; QUrl m_redirectionURL; bool m_bSource; short int m_details; void slotStatEntry(const KIO::UDSEntry &entry); void slotRedirection(const QUrl &url); /** * @internal * Called by the scheduler when a @p slave gets to * work on this job. * @param slave the slave that starts working on this job */ void start(Slave *slave) override; Q_DECLARE_PUBLIC(StatJob) static inline StatJob *newJob(const QUrl &url, int command, const QByteArray &packedArgs, JobFlags flags) { StatJob *job = new StatJob(*new StatJobPrivate(url, command, packedArgs)); job->setUiDelegate(KIO::createDefaultJobUiDelegate()); if (!(flags & HideProgressInfo)) { KIO::getJobTracker()->registerJob(job); emitStating(job, url); } return job; } }; StatJob::StatJob(StatJobPrivate &dd) : SimpleJob(dd) { } StatJob::~StatJob() { } -#ifndef KIOCORE_NO_DEPRECATED void StatJob::setSide(bool source) { d_func()->m_bSource = source; } -#endif void StatJob::setSide(StatSide side) { d_func()->m_bSource = side == SourceSide; } void StatJob::setDetails(short int details) { d_func()->m_details = details; } const UDSEntry &StatJob::statResult() const { return d_func()->m_statResult; } QUrl StatJob::mostLocalUrl() const { if (!url().isLocalFile()) { const UDSEntry &udsEntry = d_func()->m_statResult; const QString path = udsEntry.stringValue(KIO::UDSEntry::UDS_LOCAL_PATH); if (!path.isEmpty()) { return QUrl::fromLocalFile(path); } } return url(); } void StatJobPrivate::start(Slave *slave) { Q_Q(StatJob); m_outgoingMetaData.insert(QStringLiteral("statSide"), m_bSource ? QStringLiteral("source") : QStringLiteral("dest")); m_outgoingMetaData.insert(QStringLiteral("details"), QString::number(m_details)); q->connect(slave, SIGNAL(statEntry(KIO::UDSEntry)), SLOT(slotStatEntry(KIO::UDSEntry))); q->connect(slave, SIGNAL(redirection(QUrl)), SLOT(slotRedirection(QUrl))); SimpleJobPrivate::start(slave); } void StatJobPrivate::slotStatEntry(const KIO::UDSEntry &entry) { //qCDebug(KIO_CORE); m_statResult = entry; } // Slave got a redirection request void StatJobPrivate::slotRedirection(const QUrl &url) { Q_Q(StatJob); //qCDebug(KIO_CORE) << m_url << "->" << url; if (!KUrlAuthorized::authorizeUrlAction(QStringLiteral("redirect"), m_url, url)) { qCWarning(KIO_CORE) << "Redirection from" << m_url << "to" << url << "REJECTED!"; q->setError(ERR_ACCESS_DENIED); q->setErrorText(url.toDisplayString()); return; } m_redirectionURL = url; // We'll remember that when the job finishes // Tell the user that we haven't finished yet emit q->redirection(q, m_redirectionURL); } void StatJob::slotFinished() { Q_D(StatJob); if (!d->m_redirectionURL.isEmpty() && d->m_redirectionURL.isValid()) { //qCDebug(KIO_CORE) << "StatJob: Redirection to " << m_redirectionURL; if (queryMetaData(QStringLiteral("permanent-redirect")) == QLatin1String("true")) { emit permanentRedirection(this, d->m_url, d->m_redirectionURL); } if (d->m_redirectionHandlingEnabled) { d->m_packedArgs.truncate(0); QDataStream stream(&d->m_packedArgs, QIODevice::WriteOnly); stream << d->m_redirectionURL; d->restartAfterRedirection(&d->m_redirectionURL); return; } } // Return slave to the scheduler SimpleJob::slotFinished(); } void StatJob::slotMetaData(const KIO::MetaData &_metaData) { Q_D(StatJob); SimpleJob::slotMetaData(_metaData); storeSSLSessionFromJob(d->m_redirectionURL); } StatJob *KIO::stat(const QUrl &url, JobFlags flags) { // Assume sideIsSource. Gets are more common than puts. return stat(url, StatJob::SourceSide, 2, flags); } StatJob *KIO::mostLocalUrl(const QUrl &url, JobFlags flags) { StatJob *job = stat(url, StatJob::SourceSide, 2, flags); if (url.isLocalFile()) { QTimer::singleShot(0, job, &StatJob::slotFinished); Scheduler::cancelJob(job); // deletes the slave if not 0 } return job; } -#ifndef KIOCORE_NO_DEPRECATED StatJob *KIO::stat(const QUrl &url, bool sideIsSource, short int details, JobFlags flags) { //qCDebug(KIO_CORE) << "stat" << url; KIO_ARGS << url; StatJob *job = StatJobPrivate::newJob(url, CMD_STAT, packedArgs, flags); job->setSide(sideIsSource ? StatJob::SourceSide : StatJob::DestinationSide); job->setDetails(details); return job; } -#endif StatJob *KIO::stat(const QUrl &url, KIO::StatJob::StatSide side, short int details, JobFlags flags) { //qCDebug(KIO_CORE) << "stat" << url; KIO_ARGS << url; StatJob *job = StatJobPrivate::newJob(url, CMD_STAT, packedArgs, flags); job->setSide(side); job->setDetails(details); return job; } #include "moc_statjob.cpp" diff --git a/src/core/statjob.h b/src/core/statjob.h index 812600dd..025f3f4f 100644 --- a/src/core/statjob.h +++ b/src/core/statjob.h @@ -1,231 +1,235 @@ /* This file is part of the KDE libraries Copyright (C) 2000 Stephan Kulow 2000-2013 David Faure This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef KIO_STATJOB_H #define KIO_STATJOB_H #include "simplejob.h" #include namespace KIO { class StatJobPrivate; /** * @class KIO::StatJob statjob.h * * A KIO job that retrieves information about a file or directory. * @see KIO::stat() */ class KIOCORE_EXPORT StatJob : public SimpleJob { Q_OBJECT public: enum StatSide { SourceSide, DestinationSide }; ~StatJob() override; /** * A stat() can have two meanings. Either we want to read from this URL, * or to check if we can write to it. First case is "source", second is "dest". * It is necessary to know what the StatJob is for, to tune the kioslave's behavior * (e.g. with FTP). * By default it is SourceSide. * @param side SourceSide or DestinationSide */ void setSide(StatSide side); +#if KIOCORE_ENABLE_DEPRECATED_SINCE(4, 0) /** * A stat() can have two meanings. Either we want to read from this URL, * or to check if we can write to it. First case is "source", second is "dest". * It is necessary to know what the StatJob is for, to tune the kioslave's behavior * (e.g. with FTP). * @param source true for "source" mode, false for "dest" mode - * @deprecated use setSide(StatSide side). + * @deprecated Since 4.0, use setSide(StatSide side). */ -#ifndef KIOCORE_NO_DEPRECATED - KIOCORE_DEPRECATED void setSide(bool source); + KIOCORE_DEPRECATED_VERSION(4, 0, "Use StatJob::setSide(StatSide)") + void setSide(bool source); #endif /** * Selects the level of @p details we want. * By default this is 2 (all details wanted, including modification time, size, etc.), * setDetails(1) is used when deleting: we don't need all the information if it takes * too much time, no need to follow symlinks etc. * setDetails(0) is used for very simple probing: we'll only get the answer * "it's a file or a directory, or it doesn't exist". This is used by KRun. * @param details 2 for all details, 1 for simple, 0 for very simple */ void setDetails(short int details); /** * @brief Result of the stat operation. * Call this in the slot connected to result, * and only after making sure no error happened. * @return the result of the stat */ const UDSEntry &statResult() const; /** * @brief most local URL * Call this in the slot connected to result, * and only after making sure no error happened. * @return the most local URL for the URL we were stat'ing. * * Sample usage: * * @code * KIO::StatJob* job = KIO::mostLocalUrl("desktop:/foo"); * job->uiDelegate()->setWindow(this); * connect(job, &KJob::result, this, &MyClass::slotMostLocalUrlResult); * [...] * // and in the slot slotMostLocalUrlResult(KJob *job) * if (job->error()) { * [...] // doesn't exist * } else { * const QUrl localUrl = job->mostLocalUrl(); * // localUrl = file:///$HOME/Desktop/foo * [...] * } * @endcode * * \since 4.4 */ QUrl mostLocalUrl() const; Q_SIGNALS: /** * Signals a redirection. * Use to update the URL shown to the user. * The redirection itself is handled internally. * @param job the job that is redirected * @param url the new url */ void redirection(KIO::Job *job, const QUrl &url); /** * Signals a permanent redirection. * The redirection itself is handled internally. * @param job the job that is redirected * @param fromUrl the original URL * @param toUrl the new URL */ void permanentRedirection(KIO::Job *job, const QUrl &fromUrl, const QUrl &toUrl); protected Q_SLOTS: void slotFinished() override; void slotMetaData(const KIO::MetaData &_metaData) override; protected: StatJob(StatJobPrivate &dd); private: Q_PRIVATE_SLOT(d_func(), void slotStatEntry(const KIO::UDSEntry &entry)) Q_PRIVATE_SLOT(d_func(), void slotRedirection(const QUrl &url)) Q_DECLARE_PRIVATE(StatJob) friend KIOCORE_EXPORT StatJob *mostLocalUrl(const QUrl &url, JobFlags flags); }; /** * Find all details for one file or directory. * * @param url the URL of the file * @param flags Can be HideProgressInfo here * @return the job handling the operation. */ KIOCORE_EXPORT StatJob *stat(const QUrl &url, JobFlags flags = DefaultFlags); /** * Find all details for one file or directory. * This version of the call includes two additional booleans, @p sideIsSource and @p details. * * @param url the URL of the file * @param side is SourceSide when stating a source file (we will do a get on it if * the stat works) and DestinationSide when stating a destination file (target of a copy). * The reason for this parameter is that in some cases the kioslave might not * be able to determine a file's existence (e.g. HTTP doesn't allow it, FTP * has issues with case-sensitivity on some systems). * When the slave can't reliably determine the existence of a file, it will: * @li be optimistic if SourceSide, i.e. it will assume the file exists, * and if it doesn't this will appear when actually trying to download it * @li be pessimistic if DestinationSide, i.e. it will assume the file * doesn't exist, to prevent showing "about to overwrite" errors to the user. * If you simply want to check for existence without downloading/uploading afterwards, * then you should use DestinationSide. * * @param details selects the level of details we want. * By default this is 2 (all details wanted, including modification time, size, etc.), * setDetails(1) is used when deleting: we don't need all the information if it takes * too much time, no need to follow symlinks etc. * setDetails(0) is used for very simple probing: we'll only get the answer * "it's a file or a directory or a symlink, or it doesn't exist". This is used by KRun and DeleteJob. * @param flags Can be HideProgressInfo here * @return the job handling the operation. */ KIOCORE_EXPORT StatJob *stat(const QUrl &url, KIO::StatJob::StatSide side, short int details, JobFlags flags = DefaultFlags); + +#if KIOCORE_ENABLE_DEPRECATED_SINCE(4, 0) /** * Find all details for one file or directory. * This version of the call includes two additional booleans, @p sideIsSource and @p details. * * @param url the URL of the file * @param sideIsSource is true when stating a source file (we will do a get on it if * the stat works) and false when stating a destination file (target of a copy). * The reason for this parameter is that in some cases the kioslave might not * be able to determine a file's existence (e.g. HTTP doesn't allow it, FTP * has issues with case-sensitivity on some systems). * When the slave can't reliably determine the existence of a file, it will: * @li be optimistic if sideIsSource=true, i.e. it will assume the file exists, * and if it doesn't this will appear when actually trying to download it * @li be pessimistic if sideIsSource=false, i.e. it will assume the file * doesn't exist, to prevent showing "about to overwrite" errors to the user. * If you simply want to check for existence without downloading/uploading afterwards, * then you should use sideIsSource=false. * * @param details selects the level of details we want. * By default this is 2 (all details wanted, including modification time, size, etc.), * setDetails(1) is used when deleting: we don't need all the information if it takes * too much time, no need to follow symlinks etc. * setDetails(0) is used for very simple probing: we'll only get the answer * "it's a file or a directory, or it doesn't exist". This is used by KRun. * @param flags Can be HideProgressInfo here * @return the job handling the operation. + * @deprecated Since 4.0, use stat(const QUrl &, KIO::StatJob::StatSide, short int, JobFlags) */ -#ifndef KIOCORE_NO_DEPRECATED -KIOCORE_DEPRECATED_EXPORT StatJob *stat(const QUrl &url, bool sideIsSource, - short int details, JobFlags flags = DefaultFlags); +KIOCORE_DEPRECATED_VERSION(4, 0, "Use KIO::stat(const QUrl &, KIO::StatJob::StatSide, short int, JobFlags)") +KIOCORE_EXPORT StatJob *stat(const QUrl &url, bool sideIsSource, + short int details, JobFlags flags = DefaultFlags); #endif /** * Tries to map a local URL for the given URL, using a KIO job. * * Starts a (stat) job for determining the "most local URL" for a given URL. * Retrieve the result with StatJob::mostLocalUrl in the result slot. * @param url The URL we are testing. * \since 4.4 */ KIOCORE_EXPORT StatJob *mostLocalUrl(const QUrl &url, JobFlags flags = DefaultFlags); } #endif diff --git a/src/core/transferjob.cpp b/src/core/transferjob.cpp index 10c728f9..0bb93697 100644 --- a/src/core/transferjob.cpp +++ b/src/core/transferjob.cpp @@ -1,493 +1,489 @@ /* This file is part of the KDE libraries Copyright (C) 2000 Stephan Kulow 2000-2013 David Faure This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "transferjob.h" #include "job_p.h" #include "slave.h" #include #include using namespace KIO; static const int MAX_READ_BUF_SIZE = (64 * 1024); // 64 KB at a time seems reasonable... TransferJob::TransferJob(TransferJobPrivate &dd) : SimpleJob(dd) { Q_D(TransferJob); if (d->m_command == CMD_PUT) { d->m_extraFlags |= JobPrivate::EF_TransferJobDataSent; } if (d->m_outgoingDataSource) { connect(d->m_outgoingDataSource, SIGNAL(readChannelFinished()), SLOT(slotIODeviceClosedBeforeStart())); } } TransferJob::~TransferJob() { } // Slave sends data void TransferJob::slotData(const QByteArray &_data) { Q_D(TransferJob); if (d->m_command == CMD_GET && !d->m_isMimetypeEmitted) { qCWarning(KIO_CORE) << "mimeType() not emitted when sending first data!; job URL =" << d->m_url << "data size =" << _data.size(); } // shut up the warning, HACK: downside is that it changes the meaning of the variable d->m_isMimetypeEmitted = true; if (d->m_redirectionURL.isEmpty() || !d->m_redirectionURL.isValid() || error()) { emit data(this, _data); } } void KIO::TransferJob::setTotalSize(KIO::filesize_t bytes) { setTotalAmount(KJob::Bytes, bytes); } // Slave got a redirection request void TransferJob::slotRedirection(const QUrl &url) { Q_D(TransferJob); //qDebug() << url; if (!KUrlAuthorized::authorizeUrlAction(QStringLiteral("redirect"), d->m_url, url)) { qCWarning(KIO_CORE) << "Redirection from" << d->m_url << "to" << url << "REJECTED!"; return; } // Some websites keep redirecting to themselves where each redirection // acts as the stage in a state-machine. We define "endless redirections" // as 5 redirections to the same URL. if (d->m_redirectionList.count(url) > 5) { //qDebug() << "CYCLIC REDIRECTION!"; setError(ERR_CYCLIC_LINK); setErrorText(d->m_url.toDisplayString()); } else { d->m_redirectionURL = url; // We'll remember that when the job finishes d->m_redirectionList.append(url); QString sslInUse = queryMetaData(QStringLiteral("ssl_in_use")); if (!sslInUse.isNull()) { // the key is present addMetaData(QStringLiteral("ssl_was_in_use"), sslInUse); } else { addMetaData(QStringLiteral("ssl_was_in_use"), QStringLiteral("FALSE")); } // Tell the user that we haven't finished yet emit redirection(this, d->m_redirectionURL); } } void TransferJob::slotFinished() { Q_D(TransferJob); //qDebug() << d->m_url; if (!d->m_redirectionURL.isEmpty() && d->m_redirectionURL.isValid()) { //qDebug() << "Redirection to" << m_redirectionURL; if (queryMetaData(QStringLiteral("permanent-redirect")) == QLatin1String("true")) { emit permanentRedirection(this, d->m_url, d->m_redirectionURL); } if (queryMetaData(QStringLiteral("redirect-to-get")) == QLatin1String("true")) { d->m_command = CMD_GET; d->m_outgoingMetaData.remove(QStringLiteral("CustomHTTPMethod")); d->m_outgoingMetaData.remove(QStringLiteral("content-type")); } if (d->m_redirectionHandlingEnabled) { // Honour the redirection // We take the approach of "redirecting this same job" // Another solution would be to create a subjob, but the same problem // happens (unpacking+repacking) d->staticData.truncate(0); d->m_incomingMetaData.clear(); if (queryMetaData(QStringLiteral("cache")) != QLatin1String("reload")) { addMetaData(QStringLiteral("cache"), QStringLiteral("refresh")); } d->m_internalSuspended = false; // The very tricky part is the packed arguments business QUrl dummyUrl; QDataStream istream(d->m_packedArgs); switch (d->m_command) { case CMD_GET: case CMD_STAT: case CMD_DEL: { d->m_packedArgs.truncate(0); QDataStream stream(&d->m_packedArgs, QIODevice::WriteOnly); stream << d->m_redirectionURL; break; } case CMD_PUT: { int permissions; qint8 iOverwrite, iResume; istream >> dummyUrl >> iOverwrite >> iResume >> permissions; d->m_packedArgs.truncate(0); QDataStream stream(&d->m_packedArgs, QIODevice::WriteOnly); stream << d->m_redirectionURL << iOverwrite << iResume << permissions; break; } case CMD_SPECIAL: { int specialcmd; istream >> specialcmd; if (specialcmd == 1) { // HTTP POST d->m_outgoingMetaData.remove(QStringLiteral("content-type")); addMetaData(QStringLiteral("cache"), QStringLiteral("reload")); d->m_packedArgs.truncate(0); QDataStream stream(&d->m_packedArgs, QIODevice::WriteOnly); stream << d->m_redirectionURL; d->m_command = CMD_GET; } break; } } d->restartAfterRedirection(&d->m_redirectionURL); return; } } SimpleJob::slotFinished(); } void TransferJob::setAsyncDataEnabled(bool enabled) { Q_D(TransferJob); if (enabled) { d->m_extraFlags |= JobPrivate::EF_TransferJobAsync; } else { d->m_extraFlags &= ~JobPrivate::EF_TransferJobAsync; } } void TransferJob::sendAsyncData(const QByteArray &dataForSlave) { Q_D(TransferJob); if (d->m_extraFlags & JobPrivate::EF_TransferJobNeedData) { if (d->m_slave) { d->m_slave->send(MSG_DATA, dataForSlave); } if (d->m_extraFlags & JobPrivate::EF_TransferJobDataSent) { // put job -> emit progress KIO::filesize_t size = processedAmount(KJob::Bytes) + dataForSlave.size(); setProcessedAmount(KJob::Bytes, size); } } d->m_extraFlags &= ~JobPrivate::EF_TransferJobNeedData; } -#ifndef KIOCORE_NO_DEPRECATED void TransferJob::setReportDataSent(bool enabled) { Q_D(TransferJob); if (enabled) { d->m_extraFlags |= JobPrivate::EF_TransferJobDataSent; } else { d->m_extraFlags &= ~JobPrivate::EF_TransferJobDataSent; } } -#endif -#ifndef KIOCORE_NO_DEPRECATED bool TransferJob::reportDataSent() const { return (d_func()->m_extraFlags & JobPrivate::EF_TransferJobDataSent); } -#endif QString TransferJob::mimetype() const { return d_func()->m_mimetype; } QUrl TransferJob::redirectUrl() const { return d_func()->m_redirectionURL; } // Slave requests data void TransferJob::slotDataReq() { Q_D(TransferJob); QByteArray dataForSlave; d->m_extraFlags |= JobPrivate::EF_TransferJobNeedData; if (!d->staticData.isEmpty()) { dataForSlave = d->staticData; d->staticData.clear(); } else { emit dataReq(this, dataForSlave); if (d->m_extraFlags & JobPrivate::EF_TransferJobAsync) { return; } } static const int max_size = 14 * 1024 * 1024; if (dataForSlave.size() > max_size) { //qDebug() << "send" << dataForSlave.size() / 1024 / 1024 << "MB of data in TransferJob::dataReq. This needs to be split, which requires a copy. Fix the application."; d->staticData = QByteArray(dataForSlave.data() + max_size, dataForSlave.size() - max_size); dataForSlave.truncate(max_size); } sendAsyncData(dataForSlave); if (d->m_subJob) { // Bitburger protocol in action d->internalSuspend(); // Wait for more data from subJob. d->m_subJob->d_func()->internalResume(); // Ask for more! } } void TransferJob::slotMimetype(const QString &type) { Q_D(TransferJob); d->m_mimetype = type; if (d->m_command == CMD_GET && d->m_isMimetypeEmitted) { qCWarning(KIO_CORE) << "mimetype() emitted again, or after sending first data!; job URL =" << d->m_url; } d->m_isMimetypeEmitted = true; emit mimetype(this, type); } void TransferJobPrivate::internalSuspend() { m_internalSuspended = true; if (m_slave) { m_slave->suspend(); } } void TransferJobPrivate::internalResume() { m_internalSuspended = false; if (m_slave && !q_func()->isSuspended()) { m_slave->resume(); } } bool TransferJob::doResume() { Q_D(TransferJob); if (!SimpleJob::doResume()) { return false; } if (d->m_internalSuspended) { d->internalSuspend(); } return true; } bool TransferJob::isErrorPage() const { return d_func()->m_errorPage; } void TransferJobPrivate::start(Slave *slave) { Q_Q(TransferJob); Q_ASSERT(slave); JobPrivate::emitTransferring(q, m_url); q->connect(slave, &SlaveInterface::data, q, &TransferJob::slotData); if (m_outgoingDataSource) { if (m_extraFlags & JobPrivate::EF_TransferJobAsync) { q->connect(m_outgoingDataSource, &QIODevice::readyRead, q, [this]() { slotDataReqFromDevice(); }); q->connect(m_outgoingDataSource, SIGNAL(readChannelFinished()), SLOT(slotIODeviceClosed())); // We don't really need to disconnect since we're never checking // m_closedBeforeStart again but it's the proper thing to do logically QObject::disconnect(m_outgoingDataSource, SIGNAL(readChannelFinished()), q, SLOT(slotIODeviceClosedBeforeStart())); if (m_closedBeforeStart) { QMetaObject::invokeMethod(q, "slotIODeviceClosed", Qt::QueuedConnection); } else if (m_outgoingDataSource->bytesAvailable() > 0) { QMetaObject::invokeMethod(q, "slotDataReqFromDevice", Qt::QueuedConnection); } } else { q->connect(slave, &SlaveInterface::dataReq, q, [this]() { slotDataReqFromDevice(); }); } } else q->connect(slave, &SlaveInterface::dataReq, q, &TransferJob::slotDataReq); q->connect(slave, &SlaveInterface::redirection, q, &TransferJob::slotRedirection); q->connect(slave, &SlaveInterface::mimeType, q, &TransferJob::slotMimetype); q->connect(slave, &SlaveInterface::errorPage, q, [this]() { m_errorPage = true; }); q->connect(slave, &SlaveInterface::needSubUrlData, q, [this]() { slotNeedSubUrlData(); }); q->connect(slave, &SlaveInterface::canResume, q, [q](KIO::filesize_t offset) { emit q->canResume(q, offset); }); if (slave->suspended()) { m_mimetype = QStringLiteral("unknown"); // WABA: The slave was put on hold. Resume operation. slave->resume(); } SimpleJobPrivate::start(slave); if (m_internalSuspended) { slave->suspend(); } } void TransferJobPrivate::slotNeedSubUrlData() { Q_Q(TransferJob); // Job needs data from subURL. m_subJob = KIO::get(m_subUrl, NoReload, HideProgressInfo); internalSuspend(); // Put job on hold until we have some data. q->connect(m_subJob, &TransferJob::data, q, [this](KIO::Job *job, const QByteArray &data) { slotSubUrlData(job, data); }); q->addSubjob(m_subJob); } void TransferJobPrivate::slotSubUrlData(KIO::Job *, const QByteArray &data) { // The Alternating Bitburg protocol in action again. staticData = data; m_subJob->d_func()->internalSuspend(); // Put job on hold until we have delivered the data. internalResume(); // Activate ourselves again. } void TransferJob::slotMetaData(const KIO::MetaData &_metaData) { Q_D(TransferJob); SimpleJob::slotMetaData(_metaData); storeSSLSessionFromJob(d->m_redirectionURL); } void TransferJobPrivate::slotDataReqFromDevice() { Q_Q(TransferJob); bool done = false; QByteArray dataForSlave; m_extraFlags |= JobPrivate::EF_TransferJobNeedData; if (m_outgoingDataSource) { dataForSlave.resize(MAX_READ_BUF_SIZE); //Code inspired in QNonContiguousByteDevice qint64 bytesRead = m_outgoingDataSource->read(dataForSlave.data(), MAX_READ_BUF_SIZE); if (bytesRead >= 0) { dataForSlave.resize(bytesRead); } else { dataForSlave.clear(); } done = ((bytesRead == -1) || (bytesRead == 0 && m_outgoingDataSource->atEnd() && !m_outgoingDataSource->isSequential())); } if (dataForSlave.isEmpty()) { emit q->dataReq(q, dataForSlave); if (!done && (m_extraFlags & JobPrivate::EF_TransferJobAsync)) { return; } } q->sendAsyncData(dataForSlave); if (m_subJob) { // Bitburger protocol in action internalSuspend(); // Wait for more data from subJob. m_subJob->d_func()->internalResume(); // Ask for more! } } void TransferJobPrivate::slotIODeviceClosedBeforeStart() { m_closedBeforeStart = true; } void TransferJobPrivate::slotIODeviceClosed() { Q_Q(TransferJob); const QByteArray remainder = m_outgoingDataSource->readAll(); if (!remainder.isEmpty()) { m_extraFlags |= JobPrivate::EF_TransferJobNeedData; q->sendAsyncData(remainder); } m_extraFlags |= JobPrivate::EF_TransferJobNeedData; //We send an empty data array to indicate the stream is over q->sendAsyncData(QByteArray()); if (m_subJob) { // Bitburger protocol in action internalSuspend(); // Wait for more data from subJob. m_subJob->d_func()->internalResume(); // Ask for more! } } void TransferJob::slotResult(KJob *job) { Q_D(TransferJob); // This can only be our suburl. Q_ASSERT(job == d->m_subJob); SimpleJob::slotResult(job); if (!error() && job == d->m_subJob) { d->m_subJob = nullptr; // No action required d->internalResume(); // Make sure we get the remaining data. } } void TransferJob::setModificationTime(const QDateTime &mtime) { addMetaData(QStringLiteral("modified"), mtime.toString(Qt::ISODate)); } TransferJob *KIO::get(const QUrl &url, LoadType reload, JobFlags flags) { // Send decoded path and encoded query KIO_ARGS << url; TransferJob *job = TransferJobPrivate::newJob(url, CMD_GET, packedArgs, QByteArray(), flags); if (reload == Reload) { job->addMetaData(QStringLiteral("cache"), QStringLiteral("reload")); } return job; } #include "moc_transferjob.cpp" diff --git a/src/core/transferjob.h b/src/core/transferjob.h index fd4543c9..0cacf70b 100644 --- a/src/core/transferjob.h +++ b/src/core/transferjob.h @@ -1,317 +1,319 @@ /* This file is part of the KDE libraries Copyright (C) 2000 Stephan Kulow 2000-2013 David Faure This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef KIO_TRANSFERJOB_H #define KIO_TRANSFERJOB_H #include "simplejob.h" namespace KIO { class TransferJobPrivate; /** * @class KIO::TransferJob transferjob.h * * The transfer job pumps data into and/or out of a Slave. * Data is sent to the slave on request of the slave ( dataReq). * If data coming from the slave can not be handled, the * reading of data from the slave should be suspended. */ class KIOCORE_EXPORT TransferJob : public SimpleJob { Q_OBJECT public: ~TransferJob() override; /** * Sets the modification time of the file to be created (by KIO::put) * Note that some kioslaves might ignore this. */ void setModificationTime(const QDateTime &mtime); /** * Checks whether we got an error page. This currently only happens * with HTTP urls. Call this from your slot connected to result(). * * @return true if we got an (HTML) error page from the server * instead of what we asked for. */ bool isErrorPage() const; /** * Enable the async data mode. * When async data is enabled, data should be provided to the job by * calling sendAsyncData() instead of returning data in the * dataReq() signal. */ void setAsyncDataEnabled(bool enabled); /** * Provide data to the job when async data is enabled. * Should be called exactly once after receiving a dataReq signal * Sending an empty block indicates end of data. */ void sendAsyncData(const QByteArray &data); +#if KIOCORE_ENABLE_DEPRECATED_SINCE(4, 3) /** * When enabled, the job reports the amount of data that has been sent, * instead of the amount of data that has been received. * @see slotProcessedSize * @see slotSpeed * @deprecated since 4.2.1, this is unnecessary (it is always false for * KIO::get and true for KIO::put) */ -#ifndef KIOCORE_NO_DEPRECATED - KIOCORE_DEPRECATED void setReportDataSent(bool enabled); + KIOCORE_DEPRECATED_VERSION(4, 3, "No longer needed") + void setReportDataSent(bool enabled); #endif +#if KIOCORE_ENABLE_DEPRECATED_SINCE(4, 3) /** * Returns whether the job reports the amount of data that has been * sent (true), or whether the job reports the amount of data that * has been received (false) * @deprecated since 4.2.1, this is unnecessary (it is always false for * KIO::get and true for KIO::put) */ -#ifndef KIOCORE_NO_DEPRECATED - KIOCORE_DEPRECATED bool reportDataSent() const; + KIOCORE_DEPRECATED_VERSION(4, 3, "No longer needed") + bool reportDataSent() const; #endif /** * Call this in the slot connected to result, * and only after making sure no error happened. * @return the mimetype of the URL */ QString mimetype() const; /** * After the job has finished, it will return the final url in case a redirection * has happened. * @return the final url that can be empty in case no redirection has happened. * @since 5.0 */ QUrl redirectUrl() const; /** * Set the total size of data that we are going to send * in a put job. Helps getting proper progress information. * @since 4.2.1 */ void setTotalSize(KIO::filesize_t bytes); protected: /** * Called when m_subJob finishes. * @param job the job that finished */ void slotResult(KJob *job) override; /** * Reimplemented for internal reasons */ bool doResume() override; Q_SIGNALS: /** * Data from the slave has arrived. * @param job the job that emitted this signal * @param data data received from the slave. * * End of data (EOD) has been reached if data.size() == 0, however, you * should not be certain of data.size() == 0 ever happening (e.g. in case * of an error), so you should rely on result() instead. */ void data(KIO::Job *job, const QByteArray &data); /** * Request for data. * Please note, that you shouldn't put too large chunks * of data in it as this requires copies within the frame * work, so you should rather split the data you want * to pass here in reasonable chunks (about 1MB maximum) * * @param job the job that emitted this signal * @param data buffer to fill with data to send to the * slave. An empty buffer indicates end of data. (EOD) */ void dataReq(KIO::Job *job, QByteArray &data); /** * Signals a redirection. * Use to update the URL shown to the user. * The redirection itself is handled internally. * @param job the job that emitted this signal * @param url the new URL */ void redirection(KIO::Job *job, const QUrl &url); /** * Signals a permanent redirection. * The redirection itself is handled internally. * @param job the job that emitted this signal * @param fromUrl the original URL * @param toUrl the new URL */ void permanentRedirection(KIO::Job *job, const QUrl &fromUrl, const QUrl &toUrl); /** * Mimetype determined. * @param job the job that emitted this signal * @param type the mime type */ void mimetype(KIO::Job *job, const QString &type); /** * @internal * Emitted if the "put" job found an existing partial file * (in which case offset is the size of that file) * and emitted by the "get" job if it supports resuming to * the given offset - in this case @p offset is unused) */ void canResume(KIO::Job *job, KIO::filesize_t offset); protected Q_SLOTS: virtual void slotRedirection(const QUrl &url); void slotFinished() override; virtual void slotData(const QByteArray &data); virtual void slotDataReq(); virtual void slotMimetype(const QString &mimetype); void slotMetaData(const KIO::MetaData &_metaData) override; protected: TransferJob(TransferJobPrivate &dd); private: Q_PRIVATE_SLOT(d_func(), void slotPostRedirection()) Q_PRIVATE_SLOT(d_func(), void slotIODeviceClosed()) Q_PRIVATE_SLOT(d_func(), void slotIODeviceClosedBeforeStart()) Q_DECLARE_PRIVATE(TransferJob) // A FileCopyJob may control one or more TransferJobs friend class FileCopyJob; friend class FileCopyJobPrivate; }; /** * Get (means: read). * This is the job to use in order to "download" a file into memory. * The slave emits the data through the data() signal. * * Special case: if you want to determine the mimetype of the file first, * and then read it with the appropriate component, you can still use * a KIO::get() directly. When that job emits the mimeType signal, (which is * guaranteed to happen before it emits any data), put the job on hold: * * @code * job->putOnHold(); * KIO::Scheduler::publishSlaveOnHold(); * @endcode * * and forget about the job. The next time someone does a KIO::get() on the * same URL (even in another process) this job will be resumed. This saves KIO * from doing two requests to the server. * * @param url the URL of the file * @param reload Reload to reload the file, NoReload if it can be taken from the cache * @param flags Can be HideProgressInfo here * @return the job handling the operation. */ KIOCORE_EXPORT TransferJob *get(const QUrl &url, LoadType reload = NoReload, JobFlags flags = DefaultFlags); /** * Put (means: write) * * @param url Where to write data. * @param permissions May be -1. In this case no special permission mode is set. * @param flags Can be HideProgressInfo, Overwrite and Resume here. WARNING: * Setting Resume means that the data will be appended to @p dest if @p dest exists. * @return the job handling the operation. * @see multi_get() */ KIOCORE_EXPORT TransferJob *put(const QUrl &url, int permissions, JobFlags flags = DefaultFlags); /** * HTTP POST (for form data). * * Example: * \code * job = KIO::http_post( url, postData, KIO::HideProgressInfo ); * job->addMetaData("content-type", contentType ); * job->addMetaData("referrer", referrerURL); * \endcode * * @p postData is the data that you want to send and * @p contentType is the complete HTTP header line that * specifies the content's MIME type, for example * "Content-Type: text/xml". * * You MUST specify content-type! * * Often @p contentType is * "Content-Type: application/x-www-form-urlencoded" and * the @p postData is then an ASCII string (without null-termination!) * with characters like space, linefeed and percent escaped like %20, * %0A and %25. * * @param url Where to write the data. * @param postData Encoded data to post. * @param flags Can be HideProgressInfo here * @return the job handling the operation. */ KIOCORE_EXPORT TransferJob *http_post(const QUrl &url, const QByteArray &postData, JobFlags flags = DefaultFlags); /** * HTTP POST. * * This function, unlike the one that accepts a QByteArray, accepts an IO device * from which to read the encoded data to be posted to the server in order to * to avoid holding the content of very large post requests, e.g. multimedia file * uploads, in memory. * * @param url Where to write the data. * @param device the device to read from * @param size Size of the encoded post data. * @param flags Can be HideProgressInfo here * @return the job handling the operation. * * @since 4.7 */ KIOCORE_EXPORT TransferJob *http_post(const QUrl &url, QIODevice *device, qint64 size = -1, JobFlags flags = DefaultFlags); /** * HTTP DELETE. * * Though this function servers the same purpose as KIO::file_delete, unlike * file_delete it accommodates HTTP specific actions such as redirections. * * @param url url resource to delete. * @param flags Can be HideProgressInfo here * @return the job handling the operation. * * @since 4.7.3 */ KIOCORE_EXPORT TransferJob *http_delete(const QUrl &url, JobFlags flags = DefaultFlags); } #endif diff --git a/src/core/udsentry.cpp b/src/core/udsentry.cpp index db594b9d..7a467733 100644 --- a/src/core/udsentry.cpp +++ b/src/core/udsentry.cpp @@ -1,495 +1,489 @@ /* This file is part of the KDE project Copyright (C) 2000-2005 David Faure Copyright (C) 2007 Norbert Frese Copyright (C) 2007 Thiago Macieira Copyright (C) 2013-2014 Frank Reininghaus This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "udsentry.h" #include #include #include #include #include #include using namespace KIO; //BEGIN UDSEntryPrivate /* ---------- UDSEntryPrivate ------------ */ class KIO::UDSEntryPrivate : public QSharedData { public: void reserve(int size); void insert(uint udsField, const QString &value); void replace(uint udsField, const QString &value); void insert(uint udsField, long long value); void replace(uint udsField, long long value); int count() const; QString stringValue(uint udsField) const; long long numberValue(uint udsField, long long defaultValue = -1) const; -#ifndef KIOCORE_NO_DEPRECATED QList listFields() const; -#endif QVector fields() const; bool contains(uint udsField) const; void clear(); void save(QDataStream &s) const; void load(QDataStream &s); void debugUDSEntry(QDebug &stream) const; /** * @param field numeric UDS field id * @return the name of the field */ static QString nameOfUdsField(uint field); private: struct Field { inline Field() {} inline Field(const uint index, const QString &value) : m_str(value), m_index(index) {} inline Field(const uint index, long long value = 0) : m_long(value), m_index(index) {} QString m_str; long long m_long = LLONG_MIN; uint m_index = 0; }; std::vector storage; }; void UDSEntryPrivate::reserve(int size) { storage.reserve(size); } void UDSEntryPrivate::insert(uint udsField, const QString &value) { Q_ASSERT(udsField & KIO::UDSEntry::UDS_STRING); Q_ASSERT(std::find_if(storage.cbegin(), storage.cend(), [udsField](const Field &entry) {return entry.m_index == udsField;}) == storage.cend()); storage.emplace_back(udsField, value); } void UDSEntryPrivate::replace(uint udsField, const QString &value) { Q_ASSERT(udsField & KIO::UDSEntry::UDS_STRING); auto it = std::find_if(storage.begin(), storage.end(), [udsField](const Field &entry) {return entry.m_index == udsField;}); if (it != storage.end()) { it->m_str = value; return; } storage.emplace_back(udsField, value); } void UDSEntryPrivate::insert(uint udsField, long long value) { Q_ASSERT(udsField & KIO::UDSEntry::UDS_NUMBER); Q_ASSERT(std::find_if(storage.cbegin(), storage.cend(), [udsField](const Field &entry) {return entry.m_index == udsField;}) == storage.cend()); storage.emplace_back(udsField, value); } void UDSEntryPrivate::replace(uint udsField, long long value) { Q_ASSERT(udsField & KIO::UDSEntry::UDS_NUMBER); auto it = std::find_if(storage.begin(), storage.end(), [udsField](const Field &entry) {return entry.m_index == udsField;}); if (it != storage.end()) { it->m_long = value; return; } storage.emplace_back(udsField, value); } int UDSEntryPrivate::count() const { return storage.size(); } QString UDSEntryPrivate::stringValue(uint udsField) const { auto it = std::find_if(storage.cbegin(), storage.cend(), [udsField](const Field &entry) {return entry.m_index == udsField;}); if (it != storage.cend()) { return it->m_str; } return QString(); } long long UDSEntryPrivate::numberValue(uint udsField, long long defaultValue) const { auto it = std::find_if(storage.cbegin(), storage.cend(), [udsField](const Field &entry) {return entry.m_index == udsField;}); if (it != storage.cend()) { return it->m_long; } return defaultValue; } -#ifndef KIOCORE_NO_DEPRECATED QList UDSEntryPrivate::listFields() const { QList res; res.reserve(storage.size()); for (const Field &field : storage) { res.append(field.m_index); } return res; } -#endif QVector UDSEntryPrivate::fields() const { QVector res; res.reserve(storage.size()); for (const Field &field : storage) { res.append(field.m_index); } return res; } bool UDSEntryPrivate::contains(uint udsField) const { auto it = std::find_if(storage.cbegin(), storage.cend(), [udsField](const Field &entry) {return entry.m_index == udsField;}); return (it != storage.cend()); } void UDSEntryPrivate::clear() { storage.clear(); } void UDSEntryPrivate::save(QDataStream &s) const { s << static_cast(storage.size()); for (const Field &field : storage) { uint uds = field.m_index; s << uds; if (uds & KIO::UDSEntry::UDS_STRING) { s << field.m_str; } else if (uds & KIO::UDSEntry::UDS_NUMBER) { s << field.m_long; } else { Q_ASSERT_X(false, "KIO::UDSEntry", "Found a field with an invalid type"); } } } void UDSEntryPrivate::load(QDataStream &s) { clear(); quint32 size; s >> size; reserve(size); // We cache the loaded strings. Some of them, like, e.g., the user, // will often be the same for many entries in a row. Caching them // permits to use implicit sharing to save memory. static QVector cachedStrings; if (quint32(cachedStrings.size()) < size) { cachedStrings.resize(size); } for (quint32 i = 0; i < size; ++i) { quint32 uds; s >> uds; if (uds & KIO::UDSEntry::UDS_STRING) { // If the QString is the same like the one we read for the // previous UDSEntry at the i-th position, use an implicitly // shared copy of the same QString to save memory. QString buffer; s >> buffer; if (buffer != cachedStrings.at(i)) { cachedStrings[i] = buffer; } insert(uds, cachedStrings.at(i)); } else if (uds & KIO::UDSEntry::UDS_NUMBER) { long long value; s >> value; insert(uds, value); } else { Q_ASSERT_X(false, "KIO::UDSEntry", "Found a field with an invalid type"); } } } QString UDSEntryPrivate::nameOfUdsField(uint field) { switch (field) { case UDSEntry::UDS_SIZE: return QStringLiteral("UDS_SIZE"); case UDSEntry::UDS_SIZE_LARGE: return QStringLiteral("UDS_SIZE_LARGE"); case UDSEntry::UDS_USER: return QStringLiteral("UDS_USER"); case UDSEntry::UDS_ICON_NAME: return QStringLiteral("UDS_ICON_NAME"); case UDSEntry::UDS_GROUP: return QStringLiteral("UDS_GROUP"); case UDSEntry::UDS_NAME: return QStringLiteral("UDS_NAME"); case UDSEntry::UDS_LOCAL_PATH: return QStringLiteral("UDS_LOCAL_PATH"); case UDSEntry::UDS_HIDDEN: return QStringLiteral("UDS_HIDDEN"); case UDSEntry::UDS_ACCESS: return QStringLiteral("UDS_ACCESS"); case UDSEntry::UDS_MODIFICATION_TIME: return QStringLiteral("UDS_MODIFICATION_TIME"); case UDSEntry::UDS_ACCESS_TIME: return QStringLiteral("UDS_ACCESS_TIME"); case UDSEntry::UDS_CREATION_TIME: return QStringLiteral("UDS_CREATION_TIME"); case UDSEntry::UDS_FILE_TYPE: return QStringLiteral("UDS_FILE_TYPE"); case UDSEntry::UDS_LINK_DEST: return QStringLiteral("UDS_LINK_DEST"); case UDSEntry::UDS_URL: return QStringLiteral("UDS_URL"); case UDSEntry::UDS_MIME_TYPE: return QStringLiteral("UDS_MIME_TYPE"); case UDSEntry::UDS_GUESSED_MIME_TYPE: return QStringLiteral("UDS_GUESSED_MIME_TYPE"); case UDSEntry::UDS_XML_PROPERTIES: return QStringLiteral("UDS_XML_PROPERTIES"); case UDSEntry::UDS_EXTENDED_ACL: return QStringLiteral("UDS_EXTENDED_ACL"); case UDSEntry::UDS_ACL_STRING: return QStringLiteral("UDS_ACL_STRING"); case UDSEntry::UDS_DEFAULT_ACL_STRING: return QStringLiteral("UDS_DEFAULT_ACL_STRING"); case UDSEntry::UDS_DISPLAY_NAME: return QStringLiteral("UDS_DISPLAY_NAME"); case UDSEntry::UDS_TARGET_URL: return QStringLiteral("UDS_TARGET_URL"); case UDSEntry::UDS_DISPLAY_TYPE: return QStringLiteral("UDS_DISPLAY_TYPE"); case UDSEntry::UDS_ICON_OVERLAY_NAMES: return QStringLiteral("UDS_ICON_OVERLAY_NAMES"); case UDSEntry::UDS_COMMENT: return QStringLiteral("UDS_COMMENT"); case UDSEntry::UDS_DEVICE_ID: return QStringLiteral("UDS_DEVICE_ID"); case UDSEntry::UDS_INODE: return QStringLiteral("UDS_INODE"); case UDSEntry::UDS_EXTRA: return QStringLiteral("UDS_EXTRA"); case UDSEntry::UDS_EXTRA_END: return QStringLiteral("UDS_EXTRA_END"); default: return QStringLiteral("Unknown uds field %1").arg(field); } } void UDSEntryPrivate::debugUDSEntry(QDebug &stream) const { QDebugStateSaver saver(stream); stream.nospace() << "["; for (const Field &field : storage) { stream << " " << nameOfUdsField(field.m_index) << "="; if (field.m_index & KIO::UDSEntry::UDS_STRING) { stream << field.m_str; } else if (field.m_index & KIO::UDSEntry::UDS_NUMBER) { stream << field.m_long; } else { Q_ASSERT_X(false, "KIO::UDSEntry", "Found a field with an invalid type"); } } stream << " ]"; } //END UDSEntryPrivate //BEGIN UDSEntry /* ---------- UDSEntry ------------ */ UDSEntry::UDSEntry() : d(new UDSEntryPrivate()) { } // BUG: this API doesn't allow to handle symlinks correctly (we need buff from QT_LSTAT for most things, but buff from QT_STAT for st_mode and st_size) UDSEntry::UDSEntry(const QT_STATBUF &buff, const QString &name) : d(new UDSEntryPrivate()) { #ifndef Q_OS_WIN d->reserve(10); #else d->reserve(8); #endif d->insert(UDS_NAME, name); d->insert(UDS_SIZE, buff.st_size); d->insert(UDS_DEVICE_ID, buff.st_dev); d->insert(UDS_INODE, buff.st_ino); d->insert(UDS_FILE_TYPE, buff.st_mode & QT_STAT_MASK); // extract file type d->insert(UDS_ACCESS, buff.st_mode & 07777); // extract permissions d->insert(UDS_MODIFICATION_TIME, buff.st_mtime); d->insert(UDS_ACCESS_TIME, buff.st_atime); #ifndef Q_OS_WIN d->insert(UDS_USER, KUser(buff.st_uid).loginName()); d->insert(UDS_GROUP, KUserGroup(buff.st_gid).name()); #endif } UDSEntry::UDSEntry(const UDSEntry&) = default; UDSEntry::~UDSEntry() = default; UDSEntry::UDSEntry(UDSEntry&&) = default; UDSEntry& UDSEntry::operator=(const UDSEntry&) = default; UDSEntry& UDSEntry::operator=(UDSEntry&&) = default; QString UDSEntry::stringValue(uint field) const { return d->stringValue(field); } long long UDSEntry::numberValue(uint field, long long defaultValue) const { return d->numberValue(field, defaultValue); } bool UDSEntry::isDir() const { return (numberValue(UDS_FILE_TYPE) & QT_STAT_MASK) == QT_STAT_DIR; } bool UDSEntry::isLink() const { return !stringValue(UDS_LINK_DEST).isEmpty(); } void UDSEntry::reserve(int size) { d->reserve(size); } void UDSEntry::fastInsert(uint field, const QString &value) { d->insert(field, value); } void UDSEntry::fastInsert(uint field, long long value) { d->insert(field, value); } void UDSEntry::insert(uint field, const QString &value) { d->replace(field, value); } void UDSEntry::insert(uint field, long long value) { d->replace(field, value); } void UDSEntry::replace(uint field, const QString &value) { d->replace(field, value); } void UDSEntry::replace(uint field, long long value) { d->replace(field, value); } -#ifndef KIOCORE_NO_DEPRECATED QList UDSEntry::listFields() const { return d->listFields(); } -#endif QVector UDSEntry::fields() const { return d->fields(); } int UDSEntry::count() const { return d->count(); } bool UDSEntry::contains(uint field) const { return d->contains(field); } void UDSEntry::clear() { d->clear(); } //END UDSEntry KIOCORE_EXPORT QDebug operator<<(QDebug stream, const KIO::UDSEntry &entry) { entry.d->debugUDSEntry(stream); return stream; } KIOCORE_EXPORT QDataStream &operator<<(QDataStream &s, const KIO::UDSEntry &a) { a.d->save(s); return s; } KIOCORE_EXPORT QDataStream &operator>>(QDataStream &s, KIO::UDSEntry &a) { a.d->load(s); return s; } KIOCORE_EXPORT bool operator==(const KIO::UDSEntry &entry, const KIO::UDSEntry &other) { if (entry.count() != other.count()) { return false; } const QVector fields = entry.fields(); for (uint field : fields) { if (!other.contains(field)) { return false; } if (field & UDSEntry::UDS_STRING) { if (entry.stringValue(field) != other.stringValue(field)) { return false; } } else { if (entry.numberValue(field) != other.numberValue(field)) { return false; } } } return true; } KIOCORE_EXPORT bool operator!=(const KIO::UDSEntry &entry, const KIO::UDSEntry &other) { return !(entry == other); } diff --git a/src/core/udsentry.h b/src/core/udsentry.h index 50f34e03..30b4cb3a 100644 --- a/src/core/udsentry.h +++ b/src/core/udsentry.h @@ -1,399 +1,406 @@ /* This file is part of the KDE libraries Copyright (C) 2000-2005 David Faure Copyright (C) 2007 Norbert Frese Copyright (C) 2007 Thiago Macieira This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License version 2 as published by the Free Software Foundation. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef UDSENTRY_H #define UDSENTRY_H #include #include #include #include #include #include #include "kiocore_export.h" namespace KIO { class UDSEntry; } KIOCORE_EXPORT QDataStream &operator<< (QDataStream &s, const KIO::UDSEntry &a); KIOCORE_EXPORT QDataStream &operator>> (QDataStream &s, KIO::UDSEntry &a); /** * Support for qDebug() << aUDSEntry * \since 5.22 */ KIOCORE_EXPORT QDebug operator<<(QDebug stream, const KIO::UDSEntry &entry); /** * Returns true if the entry contains the same data as the other * @since 5.63 */ KIOCORE_EXPORT bool operator== (const KIO::UDSEntry &entry, const KIO::UDSEntry &other); /** * Returns true if the entry does not contain the same data as the other * @since 5.63 */ KIOCORE_EXPORT bool operator!= (const KIO::UDSEntry &entry, const KIO::UDSEntry &other); namespace KIO { class UDSEntryPrivate; /** * @class KIO::UDSEntry udsentry.h * * Universal Directory Service * * UDS entry is the data structure representing all the fields about a given URL * (file or directory). * * The KIO::listDir() and KIO:stat() operations use this data structure. * * KIO defines a number of standard fields, see the UDS_XXX enums (see StandardFieldTypes). * at the moment UDSEntry only provides fields with numeric indexes, * but there might be named fields with string indexes in the future. * * For instance, to retrieve the name of the entry, use: * \code * QString displayName = entry.stringValue( KIO::UDSEntry::UDS_NAME ); * \endcode * * To know the modification time of the file/url: * \code * QDateTime mtime = QDateTime::fromSecsSinceEpoch(entry.numberValue(KIO::UDSEntry::UDS_MODIFICATION_TIME, 0)); * if (mtime.isValid()) * ... * \endcode */ class KIOCORE_EXPORT UDSEntry { public: UDSEntry(); /** * Create a UDSEntry by QT_STATBUF * @param buff QT_STATBUFF object * @param name filename * @since 5.0 */ UDSEntry(const QT_STATBUF &buff, const QString &name = QString()); /** * Copy constructor */ UDSEntry(const UDSEntry&); /** * Destructor */ ~UDSEntry(); /** * Move constructor * @since 5.44 */ UDSEntry(UDSEntry&&); /** * Copy assignment */ UDSEntry& operator=(const UDSEntry&); /** * Move assignment * @since 5.44 */ UDSEntry& operator=(UDSEntry&&); /** * @return value of a textual field */ QString stringValue(uint field) const; /** * @return value of a numeric field */ long long numberValue(uint field, long long defaultValue = 0) const; // Convenience methods. // Let's not add one method per field, only methods that have some more logic // than just calling stringValue(field) or numberValue(field). /// @return true if this entry is a directory (or a link to a directory) bool isDir() const; /// @return true if this entry is a link bool isLink() const; /** * Calling this function before inserting items into an empty UDSEntry may save time and memory. * @param size number of items for which memory will be pre-allocated */ void reserve(int size); +#if KIOCORE_ENABLE_DEPRECATED_SINCE(5, 48) /** * insert field with string value * @param field numeric field id * @param value to set * @deprecated since 5.48 in favor of fastInsert or replace */ - KIOCORE_DEPRECATED void insert(uint field, const QString &value); + KIOCORE_DEPRECATED_VERSION(5, 48, "Use UDSEntry::fastInsert(uint, const QString &) or UDSEntry::replace(uint, const QString &)") + void insert(uint field, const QString &value); +#endif +#if KIOCORE_ENABLE_DEPRECATED_SINCE(5, 48) /** * insert field with numeric value * @param field numeric field id * @param l value to set * @deprecated since 5.48 in favor of fastInsert or replace */ - KIOCORE_DEPRECATED void insert(uint field, long long l); + KIOCORE_DEPRECATED_VERSION(5, 48, "Use UDSEntry::fastInsert(uint, long long) or UDSEntry::replace(uint, long long)") + void insert(uint field, long long l); +#endif /** * insert field with string value, it will assert if the field is already inserted. In that case, use replace() instead. * @param field numeric field id * @param value to set * @since 5.48 */ void fastInsert(uint field, const QString &value); /** * insert field with numeric value, it will assert if the field is already inserted. In that case, use replace() instead. * @param field numeric field id * @param l value to set * @since 5.48 */ void fastInsert(uint field, long long l); /** * count fields * @return the number of fields */ int count() const; /** * check existence of a field * @param field numeric field id */ bool contains(uint field) const; +#if KIOCORE_ENABLE_DEPRECATED_SINCE(5, 8) /** * List all fields. * @return all fields. * @deprecated since 5.8. Use fields() instead. */ -#ifndef KIOCORE_NO_DEPRECATED - KIOCORE_DEPRECATED QList listFields() const; + KIOCORE_DEPRECATED_VERSION(5, 8, "Use UDSEntry::fields()") + QList listFields() const; #endif /** * A vector of fields being present for the current entry. * @return all fields for the current entry. * @since 5.8 */ QVector fields() const; /** * remove all fields */ void clear(); /** * Constants used to specify the type of a UDSField. */ enum StandardFieldTypes { // First let's define the item types: bit field /// Indicates that the field is a QString UDS_STRING = 0x01000000, /// Indicates that the field is a number (long long) UDS_NUMBER = 0x02000000, /// Indicates that the field represents a time, which is modelled by a long long UDS_TIME = 0x04000000 | UDS_NUMBER, // The rest isn't a bit field /// Size of the file UDS_SIZE = 1 | UDS_NUMBER, /// @internal UDS_SIZE_LARGE = 2 | UDS_NUMBER, /// User ID of the file owner UDS_USER = 3 | UDS_STRING, /// Name of the icon, that should be used for displaying. /// It overrides all other detection mechanisms UDS_ICON_NAME = 4 | UDS_STRING, /// Group ID of the file owner UDS_GROUP = 5 | UDS_STRING, /// Filename - as displayed in directory listings etc. /// "." has the usual special meaning of "current directory" /// UDS_NAME must always be set and never be empty, neither contain '/'. /// /// Note that KIO will append the UDS_NAME to the url of their /// parent directory, so all kioslaves must use that naming scheme /// ("url_of_parent/filename" will be the full url of that file). /// To customize the appearance of files without changing the url /// of the items, use UDS_DISPLAY_NAME. UDS_NAME = 6 | UDS_STRING, /// A local file path if the ioslave display files sitting /// on the local filesystem (but in another hierarchy, e.g. settings:/ or remote:/) UDS_LOCAL_PATH = 7 | UDS_STRING, /// Treat the file as a hidden file (if set to 1) or as a normal file (if set to 0). /// This field overrides the default behavior (the check for a leading dot in the filename). UDS_HIDDEN = 8 | UDS_NUMBER, /// Access permissions (part of the mode returned by stat) UDS_ACCESS = 9 | UDS_NUMBER, /// The last time the file was modified UDS_MODIFICATION_TIME = 10 | UDS_TIME, /// The last time the file was opened UDS_ACCESS_TIME = 11 | UDS_TIME, /// The time the file was created UDS_CREATION_TIME = 12 | UDS_TIME, /// File type, part of the mode returned by stat /// (for a link, this returns the file type of the pointed item) /// check UDS_LINK_DEST to know if this is a link UDS_FILE_TYPE = 13 | UDS_NUMBER, /// Name of the file where the link points to /// Allows to check for a symlink (don't use S_ISLNK !) UDS_LINK_DEST = 14 | UDS_STRING, /// An alternative URL (If different from the caption). /// Can be used to mix different hierarchies. /// /// Use UDS_DISPLAY_NAME if you simply want to customize the user-visible filenames, or use /// UDS_TARGET_URL if you want "links" to unrelated urls. UDS_URL = 15 | UDS_STRING, /// A mime type; the slave should set it if it's known. UDS_MIME_TYPE = 16 | UDS_STRING, /// A mime type to be used for displaying only. /// But when 'running' the file, the mimetype is re-determined /// This is for special cases like symlinks in FTP; you probably don't want to use this one. UDS_GUESSED_MIME_TYPE = 17 | UDS_STRING, /// XML properties, e.g. for WebDAV UDS_XML_PROPERTIES = 18 | UDS_STRING, /// Indicates that the entry has extended ACL entries UDS_EXTENDED_ACL = 19 | UDS_NUMBER, /// The access control list serialized into a single string. UDS_ACL_STRING = 20 | UDS_STRING, /// The default access control list serialized into a single string. /// Only available for directories. UDS_DEFAULT_ACL_STRING = 21 | UDS_STRING, /// If set, contains the label to display instead of /// the 'real name' in UDS_NAME /// @since 4.1 UDS_DISPLAY_NAME = 22 | UDS_STRING, /// This file is a shortcut or mount, pointing to an /// URL in a different hierarchy /// @since 4.1 UDS_TARGET_URL = 23 | UDS_STRING, /// User-readable type of file (if not specified, /// the mimetype's description is used) /// @since 4.4 UDS_DISPLAY_TYPE = 24 | UDS_STRING, /// 25 was used by the now removed UDS_NEPOMUK_URI /// A comma-separated list of supplementary icon overlays /// which will be added to the list of overlays created /// by KFileItem. /// /// @since 4.5 UDS_ICON_OVERLAY_NAMES = 26 | UDS_STRING, /// 27 was used by the now removed UDS_NEPOMUK_QUERY /// A comment which will be displayed as is to the user. The string /// value may contain plain text or Qt-style rich-text extensions. /// /// @since 4.6 UDS_COMMENT = 28 | UDS_STRING, /// Device number for this file, used to detect hardlinks /// @since 4.7.3 UDS_DEVICE_ID = 29 | UDS_NUMBER, /// Inode number for this file, used to detect hardlinks /// @since 4.7.3 UDS_INODE = 30 | UDS_NUMBER, /// Extra data (used only if you specified Columns/ColumnsTypes) /// NB: you cannot repeat this entry; use UDS_EXTRA + i /// until UDS_EXTRA_END. UDS_EXTRA = 100 | UDS_STRING, /// Extra data (used only if you specified Columns/ColumnsTypes) /// NB: you cannot repeat this entry; use UDS_EXTRA + i /// until UDS_EXTRA_END. UDS_EXTRA_END = 140 | UDS_STRING }; private: QSharedDataPointer d; friend KIOCORE_EXPORT QDataStream& ::operator<< (QDataStream &s, const KIO::UDSEntry &a); friend KIOCORE_EXPORT QDataStream& ::operator>> (QDataStream &s, KIO::UDSEntry &a); friend KIOCORE_EXPORT QDebug (::operator<<) (QDebug stream, const KIO::UDSEntry &entry); public: /** * Replace or insert field with string value * @param field numeric field id * @param value to set * @since 5.47 */ void replace(uint field, const QString &value); /** * Replace or insert field with numeric value * @param field numeric field id * @param l value to set * @since 5.47 */ void replace(uint field, long long l); }; } Q_DECLARE_TYPEINFO(KIO::UDSEntry, Q_MOVABLE_TYPE); namespace KIO { /** * A directory listing is a list of UDSEntry instances. * * To list the name and size of all the files in a directory listing you would do: * \code * KIO::UDSEntryList::ConstIterator it = entries.begin(); * const KIO::UDSEntryList::ConstIterator end = entries.end(); * for (; it != end; ++it) { * const KIO::UDSEntry& entry = *it; * QString name = entry.stringValue( KIO::UDSEntry::UDS_NAME ); * bool isDir = entry.isDir(); * KIO::filesize_t size = entry.numberValue( KIO::UDSEntry::UDS_SIZE, -1 ); * ... * } * \endcode */ typedef QList UDSEntryList; } // end namespace Q_DECLARE_METATYPE(KIO::UDSEntry) #endif /*UDSENTRY_H*/ diff --git a/src/filewidgets/CMakeLists.txt b/src/filewidgets/CMakeLists.txt index 883e53c7..04909682 100644 --- a/src/filewidgets/CMakeLists.txt +++ b/src/filewidgets/CMakeLists.txt @@ -1,109 +1,117 @@ project(KIOFileWidgets) find_package(KF5Bookmarks ${KF5_DEP_VERSION} REQUIRED) find_package(KF5XmlGui ${KF5_DEP_VERSION} REQUIRED) configure_file(config-kiofilewidgets.h.cmake ${CMAKE_CURRENT_BINARY_DIR}/config-kiofilewidgets.h) set(kiofilewidgets_SRCS kstatusbarofflineindicator.cpp kfilemetapreview.cpp kimagefilepreview.cpp kpreviewwidgetbase.cpp krecentdirs.cpp defaultviewadapter.cpp kdiroperator.cpp kdiroperatordetailview.cpp kdiroperatoriconview.cpp kdirsortfilterproxymodel.cpp #used in combination with kdirmodel.cpp kencodingfiledialog.cpp kfilebookmarkhandler.cpp kfilecopytomenu.cpp kfilecustomdialog.cpp kfilefiltercombo.cpp kfilewidget.cpp kfilewidgetdocktitlebar.cpp kfileplacesitem.cpp kfileplacesmodel.cpp kfileplacesview.cpp kfileplaceeditdialog.cpp kfilepreviewgenerator.cpp knameandurlinputdialog.cpp knewfilemenu.cpp kurlnavigatordropdownbutton.cpp kurlnavigatorbuttonbase.cpp kurlnavigatorbutton.cpp kurlnavigatorplacesselector.cpp kurlnavigatorprotocolcombo.cpp kurlnavigatortogglebutton.cpp kurlnavigator.cpp kurlnavigatormenu.cpp kurlnavigatorpathselectoreventfilter.cpp ) qt5_add_resources(kiofilewidgets_SRCS ../new_file_templates/templates.qrc) add_library(KF5KIOFileWidgets ${kiofilewidgets_SRCS}) -generate_export_header(KF5KIOFileWidgets BASE_NAME KIOFileWidgets) add_library(KF5::KIOFileWidgets ALIAS KF5KIOFileWidgets) +ecm_generate_export_header(KF5KIOFileWidgets + BASE_NAME KIOFileWidgets + # GROUP_BASE_NAME KF <- enable once all of KF modules use ecm_generate_export_header + VERSION ${KF5_VERSION} + DEPRECATED_BASE_VERSION 0 + DEPRECATION_VERSIONS 4.3 4.5 5.0 5.33 +) +# TODO: add support for EXCLUDE_DEPRECATED_BEFORE_AND_AT to all KIO libs +# needs fixing of undeprecated API being still implemented using own deprecated API target_include_directories(KF5KIOFileWidgets INTERFACE "$") target_link_libraries(KF5KIOFileWidgets PUBLIC KF5::KIOWidgets KF5::Bookmarks # in KFilePlacesModel's API KF5::ItemViews # kdirsortfilterproxymodel KF5::XmlGui # for KActionCollection, used by KFileWidget/KDirOperator KF5::Solid # KFilePlacesModel/KFilePlacesView PRIVATE KF5::IconThemes # KIconLoader KF5::I18n ) set_target_properties(KF5KIOFileWidgets PROPERTIES VERSION ${KIO_VERSION_STRING} SOVERSION ${KIO_SOVERSION} EXPORT_NAME KIOFileWidgets ) ecm_generate_headers(KIOFileWidgets_HEADERS HEADER_NAMES KAbstractViewAdapter KImageFilePreview KPreviewWidgetBase KRecentDirs KStatusBarOfflineIndicator KDirOperator KDirSortFilterProxyModel KFileCopyToMenu KFileCustomDialog KFileFilterCombo KFilePlaceEditDialog KFilePlacesModel KFilePlacesView KFilePreviewGenerator KFileWidget KUrlNavigator KNewFileMenu KNameAndUrlInputDialog KEncodingFileDialog REQUIRED_HEADERS KIOFileWidgets_HEADERS ) install(TARGETS KF5KIOFileWidgets EXPORT KF5KIOTargets ${KF5_INSTALL_TARGETS_DEFAULT_ARGS}) install(FILES ${KIOFileWidgets_HEADERS} ${CMAKE_CURRENT_BINARY_DIR}/kiofilewidgets_export.h DESTINATION ${KDE_INSTALL_INCLUDEDIR_KF5}/KIOFileWidgets COMPONENT Devel) # make available to ecm_add_qch in parent folder set(KIOFileWidgets_QCH_SOURCES ${KIOFileWidgets_HEADERS} PARENT_SCOPE) include(ECMGeneratePriFile) ecm_generate_pri_file(BASE_NAME KIOFileWidgets LIB_NAME KF5KIOFileWidgets DEPS "KIOWidgets KBookmarks KXmlGui Solid" FILENAME_VAR PRI_FILENAME INCLUDE_INSTALL_DIR ${KDE_INSTALL_INCLUDEDIR_KF5}/KIOFileWidgets) install(FILES ${PRI_FILENAME} DESTINATION ${ECM_MKSPECS_INSTALL_DIR}) diff --git a/src/filewidgets/kfilepreviewgenerator.h b/src/filewidgets/kfilepreviewgenerator.h index b4d55110..b3faf3f0 100644 --- a/src/filewidgets/kfilepreviewgenerator.h +++ b/src/filewidgets/kfilepreviewgenerator.h @@ -1,142 +1,145 @@ /******************************************************************************* * Copyright (C) 2008-2009 by Peter Penz * * * * This library is free software; you can redistribute it and/or * * modify it under the terms of the GNU Library General Public * * License as published by the Free Software Foundation; either * * version 2 of the License, or (at your option) any later version. * * * * This library is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * * Library General Public License for more details. * * * * You should have received a copy of the GNU Library General Public License * * along with this library; see the file COPYING.LIB. If not, write to * * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * * Boston, MA 02110-1301, USA. * *******************************************************************************/ #ifndef KFILEPREVIEWGENERATOR_H #define KFILEPREVIEWGENERATOR_H #include "kiofilewidgets_export.h" #include class KAbstractViewAdapter; class KDirModel; class QAbstractItemView; class QAbstractProxyModel; /** * @class KFilePreviewGenerator kfilepreviewgenerator.h * * @brief Generates previews for files of an item view. * * Per default a preview is generated for each item. * Additionally the clipboard is checked for cut items. * The icon state for cut items gets dimmed automatically. * * The following strategy is used when creating previews: * - The previews for currently visible items are created before * the previews for invisible items. * - If the user changes the visible area by using the scrollbars, * all pending previews get paused. As soon as the user stays * on the same position for a short delay, the previews are * resumed. Also in this case the previews for the visible items * are generated first. * * @since 4.2 */ class KIOFILEWIDGETS_EXPORT KFilePreviewGenerator : public QObject { Q_OBJECT public: /** * @param parent Item view containing the file items where previews should * be generated. It is mandatory that the item view specifies * an icon size by QAbstractItemView::setIconSize() and that * the model of the view (or the source model of the proxy model) * is an instance of KDirModel. Otherwise no previews will be generated. */ KFilePreviewGenerator(QAbstractItemView *parent); /** @internal */ KFilePreviewGenerator(KAbstractViewAdapter *parent, QAbstractProxyModel *model); virtual ~KFilePreviewGenerator(); /** * If \a show is set to true, a preview is generated for each item. If \a show * is false, the MIME type icon of the item is shown instead. Per default showing * the preview is turned on. Note that it is mandatory that the item view * specifies an icon size by QAbstractItemView::setIconSize(), otherwise * KFilePreviewGenerator::isPreviewShown() will always return false. */ void setPreviewShown(bool show); bool isPreviewShown() const; +#if KIOFILEWIDGETS_ENABLE_DEPRECATED_SINCE(4, 3) /** - * @deprecated Use KFilePreviewGenerator::updateIcons() instead. + * @deprecated Since 4.3, use KFilePreviewGenerator::updateIcons() instead. */ + KIOFILEWIDGETS_DEPRECATED_VERSION(4, 3, "Use KFilePreviewGenerator::updateIcons()") void updatePreviews(); +#endif /** * Sets the list of enabled thumbnail plugins. * Per default all plugins enabled in the KConfigGroup "PreviewSettings" * are used. * * Note that this method doesn't cause already generated previews * to be regenerated. * * For a list of available plugins, call KServiceTypeTrader::self()->query("ThumbCreator"). * * @see enabledPlugins */ void setEnabledPlugins(const QStringList &list); /** * Returns the list of enabled thumbnail plugins. * @see setEnabledPlugins */ QStringList enabledPlugins() const; public Q_SLOTS: /** * Updates the icons for all items. Usually it is only * necessary to invoke this method when the icon size of the abstract item view * has been changed by QAbstractItemView::setIconSize(). Note that this method * should also be invoked if previews have been turned off, as the icons for * cut items must be updated when the icon size has changed. * @since 4.3 */ void updateIcons(); /** Cancels all pending previews. */ void cancelPreviews(); private: class Private; Private *const d; /// @internal class LayoutBlocker; class TileSet; Q_DISABLE_COPY(KFilePreviewGenerator) Q_PRIVATE_SLOT(d, void updateIcons(const KFileItemList &)) Q_PRIVATE_SLOT(d, void updateIcons(const QModelIndex &, const QModelIndex &)) Q_PRIVATE_SLOT(d, void addToPreviewQueue(const KFileItem &, const QPixmap &)) Q_PRIVATE_SLOT(d, void slotPreviewJobFinished(KJob *)) Q_PRIVATE_SLOT(d, void updateCutItems()) Q_PRIVATE_SLOT(d, void dispatchIconUpdateQueue()) Q_PRIVATE_SLOT(d, void pauseIconUpdates()) Q_PRIVATE_SLOT(d, void resumeIconUpdates()) Q_PRIVATE_SLOT(d, void resolveMimeType()) Q_PRIVATE_SLOT(d, void requestSequenceIcon(const QModelIndex &, int)) Q_PRIVATE_SLOT(d, void delayedIconUpdate()) Q_PRIVATE_SLOT(d, void rowsAboutToBeRemoved(const QModelIndex &, int, int)) }; #endif diff --git a/src/filewidgets/kfilewidget.cpp b/src/filewidgets/kfilewidget.cpp index b96b47d7..58edb3fb 100644 --- a/src/filewidgets/kfilewidget.cpp +++ b/src/filewidgets/kfilewidget.cpp @@ -1,2967 +1,2965 @@ // -*- c++ -*- /* This file is part of the KDE libraries Copyright (C) 1997, 1998 Richard Moore 1998 Stephan Kulow 1998 Daniel Grana 1999,2000,2001,2002,2003 Carsten Pfeiffer 2003 Clarence Dang 2007 David Faure 2008 Rafael Fernández López This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kfilewidget.h" #include "../pathhelpers_p.h" #include "kfileplacesview.h" #include "kfileplacesmodel.h" #include "kfilebookmarkhandler_p.h" #include "kurlcombobox.h" #include "kurlnavigator.h" #include "kfilepreviewgenerator.h" #include "kfilewidgetdocktitlebar_p.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include class KFileWidgetPrivate { public: explicit KFileWidgetPrivate(KFileWidget *widget) : q(widget), boxLayout(nullptr), placesDock(nullptr), placesView(nullptr), placesViewSplitter(nullptr), placesViewWidth(-1), labeledCustomWidget(nullptr), bottomCustomWidget(nullptr), autoSelectExtCheckBox(nullptr), operationMode(KFileWidget::Opening), bookmarkHandler(nullptr), toolbar(nullptr), locationEdit(nullptr), ops(nullptr), filterWidget(nullptr), autoSelectExtChecked(false), keepLocation(false), hasView(false), hasDefaultFilter(false), inAccept(false), dummyAdded(false), confirmOverwrite(false), differentHierarchyLevelItemsEntered(false), iconSizeSlider(nullptr), zoomOutAction(nullptr), zoomInAction(nullptr) { } ~KFileWidgetPrivate() { delete bookmarkHandler; // Should be deleted before ops! delete ops; } void updateLocationWhatsThis(); void updateAutoSelectExtension(); void initSpeedbar(); void setPlacesViewSplitterSizes(); void setLafBoxColumnWidth(); void initGUI(); void readViewConfig(); void writeViewConfig(); void setNonExtSelection(); void setLocationText(const QUrl &); void setLocationText(const QList &); void appendExtension(QUrl &url); void updateLocationEditExtension(const QString &); QString findMatchingFilter(const QString &filter, const QString &filename) const; void updateFilter(); void updateFilterText(); QList &parseSelectedUrls(); /** * Parses the string "line" for files. If line doesn't contain any ", the * whole line will be interpreted as one file. If the number of " is odd, * an empty list will be returned. Otherwise, all items enclosed in " " * will be returned as correct urls. */ QList tokenize(const QString &line) const; /** * Reads the recent used files and inserts them into the location combobox */ void readRecentFiles(); /** * Saves the entries from the location combobox. */ void saveRecentFiles(); /** * called when an item is highlighted/selected in multiselection mode. * handles setting the locationEdit. */ void multiSelectionChanged(); /** * Returns the absolute version of the URL specified in locationEdit. */ QUrl getCompleteUrl(const QString &) const; /** * Sets the dummy entry on the history combo box. If the dummy entry * already exists, it is overwritten with this information. */ void setDummyHistoryEntry(const QString &text, const QPixmap &icon = QPixmap(), bool usePreviousPixmapIfNull = true); /** * Removes the dummy entry of the history combo box. */ void removeDummyHistoryEntry(); /** * Asks for overwrite confirmation using a KMessageBox and returns * true if the user accepts. * * @since 4.2 */ bool toOverwrite(const QUrl &); // private slots void _k_slotLocationChanged(const QString &); void _k_urlEntered(const QUrl &); void _k_enterUrl(const QUrl &); void _k_enterUrl(const QString &); void _k_locationAccepted(const QString &); void _k_slotFilterChanged(); void _k_fileHighlighted(const KFileItem &); void _k_fileSelected(const KFileItem &); void _k_slotLoadingFinished(); void _k_fileCompletion(const QString &); void _k_toggleSpeedbar(bool); void _k_toggleBookmarks(bool); void _k_slotAutoSelectExtClicked(); void _k_placesViewSplitterMoved(int, int); void _k_activateUrlNavigator(); void _k_zoomOutIconsSize(); void _k_zoomInIconsSize(); void _k_slotIconSizeSliderMoved(int); void _k_slotIconSizeChanged(int); void _k_slotViewDoubleClicked(const QModelIndex&); void _k_slotViewKeyEnterReturnPressed(); void addToRecentDocuments(); QString locationEditCurrentText() const; /** * KIO::NetAccess::mostLocalUrl local replacement. * This method won't show any progress dialogs for stating, since * they are very annoying when stating. */ QUrl mostLocalUrl(const QUrl &url); void setInlinePreviewShown(bool show); KFileWidget * const q; // the last selected url QUrl url; // the selected filenames in multiselection mode -- FIXME QString filenames; // now following all kind of widgets, that I need to rebuild // the geometry management QBoxLayout *boxLayout; QGridLayout *lafBox; QVBoxLayout *vbox; QLabel *locationLabel; QWidget *opsWidget; QWidget *pathSpacer; QLabel *filterLabel; KUrlNavigator *urlNavigator; QPushButton *okButton, *cancelButton; QDockWidget *placesDock; KFilePlacesView *placesView; QSplitter *placesViewSplitter; // caches the places view width. This value will be updated when the splitter // is moved. This allows us to properly set a value when the dialog itself // is resized int placesViewWidth; QWidget *labeledCustomWidget; QWidget *bottomCustomWidget; // Automatically Select Extension stuff QCheckBox *autoSelectExtCheckBox; QString extension; // current extension for this filter QList statJobs; QList urlList; //the list of selected urls KFileWidget::OperationMode operationMode; // The file class used for KRecentDirs QString fileClass; KFileBookmarkHandler *bookmarkHandler; KActionMenu *bookmarkButton; KToolBar *toolbar; KUrlComboBox *locationEdit; KDirOperator *ops; KFileFilterCombo *filterWidget; QTimer filterDelayTimer; KFilePlacesModel *model; // whether or not the _user_ has checked the above box bool autoSelectExtChecked : 1; // indicates if the location edit should be kept or cleared when changing // directories bool keepLocation : 1; // the KDirOperators view is set in KFileWidget::show(), so to avoid // setting it again and again, we have this nice little boolean :) bool hasView : 1; bool hasDefaultFilter : 1; // necessary for the operationMode bool autoDirectoryFollowing : 1; bool inAccept : 1; // true between beginning and end of accept() bool dummyAdded : 1; // if the dummy item has been added. This prevents the combo from having a // blank item added when loaded bool confirmOverwrite : 1; bool differentHierarchyLevelItemsEntered; QSlider *iconSizeSlider; QAction *zoomOutAction; QAction *zoomInAction; // The group which stores app-specific settings. These settings are recent // files and urls. Visual settings (view mode, sorting criteria...) are not // app-specific and are stored in kdeglobals KConfigGroup configGroup; }; Q_GLOBAL_STATIC(QUrl, lastDirectory) // to set the start path static const char autocompletionWhatsThisText[] = I18N_NOOP("While typing in the text area, you may be presented " "with possible matches. " "This feature can be controlled by clicking with the right mouse button " "and selecting a preferred mode from the Text Completion menu."); // returns true if the string contains "
:/" sequence, where is at least 2 alpha chars static bool containsProtocolSection(const QString &string) { int len = string.length(); static const char prot[] = ":/"; for (int i = 0; i < len;) { i = string.indexOf(QLatin1String(prot), i); if (i == -1) { return false; } int j = i - 1; for (; j >= 0; j--) { const QChar &ch(string[j]); if (ch.toLatin1() == 0 || !ch.isLetter()) { break; } if (ch.isSpace() && (i - j - 1) >= 2) { return true; } } if (j < 0 && i >= 2) { return true; // at least two letters before ":/" } i += 3; // skip : and / and one char } return false; } // this string-to-url conversion function handles relative paths, full paths and URLs // without the http-prepending that QUrl::fromUserInput does. static QUrl urlFromString(const QString& str) { if (QDir::isAbsolutePath(str)) { return QUrl::fromLocalFile(str); } QUrl url(str); if (url.isRelative()) { url.clear(); url.setPath(str); } return url; } KFileWidget::KFileWidget(const QUrl &_startDir, QWidget *parent) : QWidget(parent), d(new KFileWidgetPrivate(this)) { QUrl startDir(_startDir); // qDebug() << "startDir" << startDir; QString filename; d->okButton = new QPushButton(this); KGuiItem::assign(d->okButton, KStandardGuiItem::ok()); d->okButton->setDefault(true); d->cancelButton = new QPushButton(this); KGuiItem::assign(d->cancelButton, KStandardGuiItem::cancel()); // The dialog shows them d->okButton->hide(); d->cancelButton->hide(); d->opsWidget = new QWidget(this); QVBoxLayout *opsWidgetLayout = new QVBoxLayout(d->opsWidget); opsWidgetLayout->setContentsMargins(0, 0, 0, 0); opsWidgetLayout->setSpacing(0); //d->toolbar = new KToolBar(this, true); d->toolbar = new KToolBar(d->opsWidget, true); d->toolbar->setObjectName(QStringLiteral("KFileWidget::toolbar")); d->toolbar->setMovable(false); opsWidgetLayout->addWidget(d->toolbar); d->model = new KFilePlacesModel(this); // Resolve this now so that a 'kfiledialog:' URL, if specified, // does not get inserted into the urlNavigator history. d->url = getStartUrl(startDir, d->fileClass, filename); startDir = d->url; // Don't pass startDir to the KUrlNavigator at this stage: as well as // the above, it may also contain a file name which should not get // inserted in that form into the old-style navigation bar history. // Wait until the KIO::stat has been done later. // // The stat cannot be done before this point, bug 172678. d->urlNavigator = new KUrlNavigator(d->model, QUrl(), d->opsWidget); //d->toolbar); d->urlNavigator->setPlacesSelectorVisible(false); opsWidgetLayout->addWidget(d->urlNavigator); QUrl u; KUrlComboBox *pathCombo = d->urlNavigator->editor(); #ifdef Q_OS_WIN #if 0 foreach (const QFileInfo &drive, QFSFileEngine::drives()) { u = QUrl::fromLocalFile(drive.filePath()); pathCombo->addDefaultUrl(u, KIO::pixmapForUrl(u, 0, KIconLoader::Small), i18n("Drive: %1", u.toLocalFile())); } #else #pragma message("QT5 PORT") #endif #else u = QUrl::fromLocalFile(QDir::rootPath()); pathCombo->addDefaultUrl(u, KIO::pixmapForUrl(u, 0, KIconLoader::Small), u.toLocalFile()); #endif u = QUrl::fromLocalFile(QDir::homePath()); pathCombo->addDefaultUrl(u, KIO::pixmapForUrl(u, 0, KIconLoader::Small), u.toLocalFile()); QUrl docPath = QUrl::fromLocalFile(QStandardPaths::writableLocation(QStandardPaths::DocumentsLocation)); if (u.adjusted(QUrl::StripTrailingSlash) != docPath.adjusted(QUrl::StripTrailingSlash) && QDir(docPath.toLocalFile()).exists()) { pathCombo->addDefaultUrl(docPath, KIO::pixmapForUrl(docPath, 0, KIconLoader::Small), docPath.toLocalFile()); } u = QUrl::fromLocalFile(QStandardPaths::writableLocation(QStandardPaths::DesktopLocation)); pathCombo->addDefaultUrl(u, KIO::pixmapForUrl(u, 0, KIconLoader::Small), u.toLocalFile()); d->ops = new KDirOperator(QUrl(), d->opsWidget); d->ops->setObjectName(QStringLiteral("KFileWidget::ops")); d->ops->setIsSaving(d->operationMode == Saving); opsWidgetLayout->addWidget(d->ops); connect(d->ops, SIGNAL(urlEntered(QUrl)), SLOT(_k_urlEntered(QUrl))); connect(d->ops, SIGNAL(fileHighlighted(KFileItem)), SLOT(_k_fileHighlighted(KFileItem))); connect(d->ops, SIGNAL(fileSelected(KFileItem)), SLOT(_k_fileSelected(KFileItem))); connect(d->ops, SIGNAL(finishedLoading()), SLOT(_k_slotLoadingFinished())); connect(d->ops, SIGNAL(keyEnterReturnPressed()), SLOT(_k_slotViewKeyEnterReturnPressed())); d->ops->setupMenu(KDirOperator::SortActions | KDirOperator::FileActions | KDirOperator::ViewActions); KActionCollection *coll = d->ops->actionCollection(); coll->addAssociatedWidget(this); // add nav items to the toolbar // // NOTE: The order of the button icons here differs from that // found in the file manager and web browser, but has been discussed // and agreed upon on the kde-core-devel mailing list: // // http://lists.kde.org/?l=kde-core-devel&m=116888382514090&w=2 coll->action(QStringLiteral("up"))->setWhatsThis(i18n("Click this button to enter the parent folder.

" "For instance, if the current location is file:/home/konqi clicking this " "button will take you to file:/home.
")); coll->action(QStringLiteral("back"))->setWhatsThis(i18n("Click this button to move backwards one step in the browsing history.")); coll->action(QStringLiteral("forward"))->setWhatsThis(i18n("Click this button to move forward one step in the browsing history.")); coll->action(QStringLiteral("reload"))->setWhatsThis(i18n("Click this button to reload the contents of the current location.")); coll->action(QStringLiteral("mkdir"))->setShortcut(QKeySequence(Qt::Key_F10)); coll->action(QStringLiteral("mkdir"))->setWhatsThis(i18n("Click this button to create a new folder.")); QAction *goToNavigatorAction = coll->addAction(QStringLiteral("gotonavigator"), this, SLOT(_k_activateUrlNavigator())); goToNavigatorAction->setShortcut(QKeySequence(Qt::CTRL + Qt::Key_L)); KToggleAction *showSidebarAction = new KToggleAction(i18n("Show Places Panel"), this); coll->addAction(QStringLiteral("toggleSpeedbar"), showSidebarAction); showSidebarAction->setShortcut(QKeySequence(Qt::Key_F9)); connect(showSidebarAction, SIGNAL(toggled(bool)), SLOT(_k_toggleSpeedbar(bool))); KToggleAction *showBookmarksAction = new KToggleAction(i18n("Show Bookmarks Button"), this); coll->addAction(QStringLiteral("toggleBookmarks"), showBookmarksAction); connect(showBookmarksAction, SIGNAL(toggled(bool)), SLOT(_k_toggleBookmarks(bool))); // Build the settings menu KActionMenu *menu = new KActionMenu(QIcon::fromTheme(QStringLiteral("configure")), i18n("Options"), this); coll->addAction(QStringLiteral("extra menu"), menu); menu->setWhatsThis(i18n("This is the preferences menu for the file dialog. " "Various options can be accessed from this menu including:
    " "
  • how files are sorted in the list
  • " "
  • types of view, including icon and list
  • " "
  • showing of hidden files
  • " "
  • the Places panel
  • " "
  • file previews
  • " "
  • separating folders from files
")); menu->addAction(coll->action(QStringLiteral("allow expansion"))); menu->addSeparator(); menu->addAction(coll->action(QStringLiteral("show hidden"))); menu->addAction(showSidebarAction); menu->addAction(showBookmarksAction); menu->addAction(coll->action(QStringLiteral("preview"))); menu->setDelayed(false); connect(menu->menu(), &QMenu::aboutToShow, d->ops, &KDirOperator::updateSelectionDependentActions); d->iconSizeSlider = new QSlider(this); d->iconSizeSlider->setSizePolicy(QSizePolicy::Maximum, QSizePolicy::Fixed); d->iconSizeSlider->setMinimumWidth(40); d->iconSizeSlider->setOrientation(Qt::Horizontal); d->iconSizeSlider->setMinimum(0); d->iconSizeSlider->setMaximum(100); d->iconSizeSlider->installEventFilter(this); connect(d->iconSizeSlider, &QAbstractSlider::valueChanged, d->ops, &KDirOperator::setIconsZoom); connect(d->iconSizeSlider, SIGNAL(valueChanged(int)), this, SLOT(_k_slotIconSizeChanged(int))); connect(d->iconSizeSlider, SIGNAL(sliderMoved(int)), this, SLOT(_k_slotIconSizeSliderMoved(int))); connect(d->ops, &KDirOperator::currentIconSizeChanged, [this](int value) { d->iconSizeSlider->setValue(value); d->zoomOutAction->setDisabled(value <= d->iconSizeSlider->minimum()); d->zoomInAction->setDisabled(value >= d->iconSizeSlider->maximum()); }); d->zoomOutAction = new QAction(QIcon::fromTheme(QStringLiteral("file-zoom-out")), i18n("Zoom out"), this); connect(d->zoomOutAction, SIGNAL(triggered()), SLOT(_k_zoomOutIconsSize())); d->zoomInAction = new QAction(QIcon::fromTheme(QStringLiteral("file-zoom-in")), i18n("Zoom in"), this); connect(d->zoomInAction, SIGNAL(triggered()), SLOT(_k_zoomInIconsSize())); QWidget *midSpacer = new QWidget(this); midSpacer->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); d->toolbar->addAction(coll->action(QStringLiteral("back"))); d->toolbar->addAction(coll->action(QStringLiteral("forward"))); d->toolbar->addAction(coll->action(QStringLiteral("up"))); d->toolbar->addAction(coll->action(QStringLiteral("reload"))); d->toolbar->addSeparator(); d->toolbar->addAction(coll->action(QStringLiteral("icons view"))); d->toolbar->addAction(coll->action(QStringLiteral("compact view"))); d->toolbar->addAction(coll->action(QStringLiteral("details view"))); d->toolbar->addSeparator(); d->toolbar->addAction(coll->action(QStringLiteral("inline preview"))); d->toolbar->addAction(coll->action(QStringLiteral("sorting menu"))); d->toolbar->addWidget(midSpacer); d->toolbar->addAction(d->zoomOutAction); d->toolbar->addWidget(d->iconSizeSlider); d->toolbar->addAction(d->zoomInAction); d->toolbar->addSeparator(); d->toolbar->addAction(coll->action(QStringLiteral("mkdir"))); d->toolbar->addAction(menu); d->toolbar->setToolButtonStyle(Qt::ToolButtonIconOnly); d->toolbar->setMovable(false); KUrlCompletion *pathCompletionObj = new KUrlCompletion(KUrlCompletion::DirCompletion); pathCombo->setCompletionObject(pathCompletionObj); pathCombo->setAutoDeleteCompletionObject(true); connect(d->urlNavigator, SIGNAL(urlChanged(QUrl)), this, SLOT(_k_enterUrl(QUrl))); connect(d->urlNavigator, &KUrlNavigator::returnPressed, d->ops, QOverload<>::of(&QWidget::setFocus)); QString whatsThisText; // the Location label/edit d->locationLabel = new QLabel(i18n("&Name:"), this); d->locationEdit = new KUrlComboBox(KUrlComboBox::Files, true, this); d->locationEdit->installEventFilter(this); // Properly let the dialog be resized (to smaller). Otherwise we could have // huge dialogs that can't be resized to smaller (it would be as big as the longest // item in this combo box). (ereslibre) d->locationEdit->setSizeAdjustPolicy(QComboBox::AdjustToMinimumContentsLength); connect(d->locationEdit, SIGNAL(editTextChanged(QString)), SLOT(_k_slotLocationChanged(QString))); d->updateLocationWhatsThis(); d->locationLabel->setBuddy(d->locationEdit); KUrlCompletion *fileCompletionObj = new KUrlCompletion(KUrlCompletion::FileCompletion); d->locationEdit->setCompletionObject(fileCompletionObj); d->locationEdit->setAutoDeleteCompletionObject(true); connect(fileCompletionObj, SIGNAL(match(QString)), SLOT(_k_fileCompletion(QString))); connect(d->locationEdit, SIGNAL(returnPressed(QString)), this, SLOT(_k_locationAccepted(QString))); // the Filter label/edit d->filterLabel = new QLabel(this); d->filterWidget = new KFileFilterCombo(this); d->updateFilterText(); // Properly let the dialog be resized (to smaller). Otherwise we could have // huge dialogs that can't be resized to smaller (it would be as big as the longest // item in this combo box). (ereslibre) d->filterWidget->setSizeAdjustPolicy(QComboBox::AdjustToMinimumContentsLength); d->filterLabel->setBuddy(d->filterWidget); connect(d->filterWidget, SIGNAL(filterChanged()), SLOT(_k_slotFilterChanged())); d->filterDelayTimer.setSingleShot(true); d->filterDelayTimer.setInterval(300); connect(d->filterWidget, &QComboBox::editTextChanged, &d->filterDelayTimer, QOverload<>::of(&QTimer::start)); connect(&d->filterDelayTimer, SIGNAL(timeout()), SLOT(_k_slotFilterChanged())); // the Automatically Select Extension checkbox // (the text, visibility etc. is set in updateAutoSelectExtension(), which is called by readConfig()) d->autoSelectExtCheckBox = new QCheckBox(this); const int spacingHint = style()->pixelMetric(QStyle::PM_DefaultLayoutSpacing); d->autoSelectExtCheckBox->setStyleSheet(QStringLiteral("QCheckBox { padding-top: %1px; }").arg(spacingHint)); connect(d->autoSelectExtCheckBox, SIGNAL(clicked()), SLOT(_k_slotAutoSelectExtClicked())); d->initGUI(); // activate GM // read our configuration KSharedConfig::Ptr config = KSharedConfig::openConfig(); KConfigGroup group(config, ConfigGroup); readConfig(group); coll->action(QStringLiteral("inline preview"))->setChecked(d->ops->isInlinePreviewShown()); d->iconSizeSlider->setValue(d->ops->iconsZoom()); KFilePreviewGenerator *pg = d->ops->previewGenerator(); if (pg) { coll->action(QStringLiteral("inline preview"))->setChecked(pg->isPreviewShown()); } // getStartUrl() above will have resolved the startDir parameter into // a directory and file name in the two cases: (a) where it is a // special "kfiledialog:" URL, or (b) where it is a plain file name // only without directory or protocol. For any other startDir // specified, it is not possible to resolve whether there is a file name // present just by looking at the URL; the only way to be sure is // to stat it. bool statRes = false; if (filename.isEmpty()) { KIO::StatJob *statJob = KIO::stat(startDir, KIO::HideProgressInfo); KJobWidgets::setWindow(statJob, this); statRes = statJob->exec(); // qDebug() << "stat of" << startDir << "-> statRes" << statRes << "isDir" << statJob->statResult().isDir(); if (!statRes || !statJob->statResult().isDir()) { filename = startDir.fileName(); startDir = startDir.adjusted(QUrl::RemoveFilename | QUrl::StripTrailingSlash); // qDebug() << "statJob -> startDir" << startDir << "filename" << filename; } } d->ops->setUrl(startDir, true); d->urlNavigator->setLocationUrl(startDir); if (d->placesView) { d->placesView->setUrl(startDir); } // We have a file name either explicitly specified, or have checked that // we could stat it and it is not a directory. Set it. if (!filename.isEmpty()) { QLineEdit *lineEdit = d->locationEdit->lineEdit(); // qDebug() << "selecting filename" << filename; if (statRes) { d->setLocationText(QUrl(filename)); } else { lineEdit->setText(filename); // Preserve this filename when clicking on the view (cf _k_fileHighlighted) lineEdit->setModified(true); } lineEdit->selectAll(); } d->locationEdit->setFocus(); } KFileWidget::~KFileWidget() { KSharedConfig::Ptr config = KSharedConfig::openConfig(); config->sync(); delete d; } void KFileWidget::setLocationLabel(const QString &text) { d->locationLabel->setText(text); } void KFileWidget::setFilter(const QString &filter) { int pos = filter.indexOf(QLatin1Char('/')); // Check for an un-escaped '/', if found // interpret as a MIME filter. if (pos > 0 && filter[pos - 1] != QLatin1Char('\\')) { QStringList filters = filter.split(QLatin1Char(' '), QString::SkipEmptyParts); setMimeFilter(filters); return; } // Strip the escape characters from // escaped '/' characters. QString copy(filter); for (pos = 0; (pos = copy.indexOf(QLatin1String("\\/"), pos)) != -1; ++pos) { copy.remove(pos, 1); } d->ops->clearFilter(); d->filterWidget->setFilter(copy); d->ops->setNameFilter(d->filterWidget->currentFilter()); d->ops->updateDir(); d->hasDefaultFilter = false; d->filterWidget->setEditable(true); d->updateAutoSelectExtension(); } QString KFileWidget::currentFilter() const { return d->filterWidget->currentFilter(); } void KFileWidget::setMimeFilter(const QStringList &mimeTypes, const QString &defaultType) { d->filterWidget->setMimeFilter(mimeTypes, defaultType); QStringList types = d->filterWidget->currentFilter().split(QLatin1Char(' '), QString::SkipEmptyParts); //QStringList::split(" ", d->filterWidget->currentFilter()); types.append(QStringLiteral("inode/directory")); d->ops->clearFilter(); d->ops->setMimeFilter(types); d->hasDefaultFilter = !defaultType.isEmpty(); d->filterWidget->setEditable(!d->hasDefaultFilter || d->operationMode != Saving); d->updateAutoSelectExtension(); d->updateFilterText(); } void KFileWidget::clearFilter() { d->filterWidget->setFilter(QString()); d->ops->clearFilter(); d->hasDefaultFilter = false; d->filterWidget->setEditable(true); d->updateAutoSelectExtension(); } QString KFileWidget::currentMimeFilter() const { int i = d->filterWidget->currentIndex(); if (d->filterWidget->showsAllTypes() && i == 0) { return QString(); // The "all types" item has no mimetype } return d->filterWidget->filters().at(i); } QMimeType KFileWidget::currentFilterMimeType() { QMimeDatabase db; return db.mimeTypeForName(currentMimeFilter()); } void KFileWidget::setPreviewWidget(KPreviewWidgetBase *w) { d->ops->setPreviewWidget(w); d->ops->clearHistory(); d->hasView = true; } QUrl KFileWidgetPrivate::getCompleteUrl(const QString &_url) const { // qDebug() << "got url " << _url; const QString url = KShell::tildeExpand(_url); QUrl u; if (QDir::isAbsolutePath(url)) { u = QUrl::fromLocalFile(url); } else { QUrl relativeUrlTest(ops->url()); relativeUrlTest.setPath(concatPaths(relativeUrlTest.path(), url)); if (!ops->dirLister()->findByUrl(relativeUrlTest).isNull() || !KProtocolInfo::isKnownProtocol(relativeUrlTest)) { u = relativeUrlTest; } else { // Try to preserve URLs if they have a scheme (for example, // "https://example.com/foo.txt") and otherwise resolve relative // paths to absolute ones (e.g. "foo.txt" -> "file:///tmp/foo.txt"). u = QUrl(url); if (u.isRelative()) { u = relativeUrlTest; } } } return u; } QSize KFileWidget::sizeHint() const { int fontSize = fontMetrics().height(); const QSize goodSize(48 * fontSize, 30 * fontSize); const QSize screenSize = QApplication::desktop()->availableGeometry(this).size(); const QSize minSize(screenSize / 2); const QSize maxSize(screenSize * qreal(0.9)); return (goodSize.expandedTo(minSize).boundedTo(maxSize)); } static QString relativePathOrUrl(const QUrl &baseUrl, const QUrl &url); // Called by KFileDialog void KFileWidget::slotOk() { // qDebug() << "slotOk\n"; const QString locationEditCurrentText(KShell::tildeExpand(d->locationEditCurrentText())); QList locationEditCurrentTextList(d->tokenize(locationEditCurrentText)); KFile::Modes mode = d->ops->mode(); // if there is nothing to do, just return from here if (locationEditCurrentTextList.isEmpty()) { return; } // Make sure that one of the modes was provided if (!((mode & KFile::File) || (mode & KFile::Directory) || (mode & KFile::Files))) { mode |= KFile::File; // qDebug() << "No mode() provided"; } // if we are on file mode, and the list of provided files/folder is greater than one, inform // the user about it if (locationEditCurrentTextList.count() > 1) { if (mode & KFile::File) { KMessageBox::sorry(this, i18n("You can only select one file"), i18n("More than one file provided")); return; } /** * Logic of the next part of code (ends at "end multi relative urls"). * * We allow for instance to be at "/" and insert '"home/foo/bar.txt" "boot/grub/menu.lst"'. * Why we need to support this ? Because we provide tree views, which aren't plain. * * Now, how does this logic work. It will get the first element on the list (with no filename), * following the previous example say "/home/foo" and set it as the top most url. * * After this, it will iterate over the rest of items and check if this URL (topmost url) * contains the url being iterated. * * As you might have guessed it will do "/home/foo" against "/boot/grub" (again stripping * filename), and a false will be returned. Then we upUrl the top most url, resulting in * "/home" against "/boot/grub", what will again return false, so we upUrl again. Now we * have "/" against "/boot/grub", what returns true for us, so we can say that the closest * common ancestor of both is "/". * * This example has been written for 2 urls, but this works for any number of urls. */ if (!d->differentHierarchyLevelItemsEntered) { // avoid infinite recursion. running this int start = 0; QUrl topMostUrl; KIO::StatJob *statJob = nullptr; bool res = false; // we need to check for a valid first url, so in theory we only iterate one time over // this loop. However it can happen that the user did // "home/foo/nonexistantfile" "boot/grub/menu.lst", so we look for a good first // candidate. while (!res && start < locationEditCurrentTextList.count()) { topMostUrl = locationEditCurrentTextList.at(start); statJob = KIO::stat(topMostUrl, KIO::HideProgressInfo); KJobWidgets::setWindow(statJob, this); res = statJob->exec(); start++; } Q_ASSERT(statJob); // if this is not a dir, strip the filename. after this we have an existent and valid // dir (we stated correctly the file). if (!statJob->statResult().isDir()) { topMostUrl = topMostUrl.adjusted(QUrl::RemoveFilename | QUrl::StripTrailingSlash); } // now the funny part. for the rest of filenames, go and look for the closest ancestor // of all them. for (int i = start; i < locationEditCurrentTextList.count(); ++i) { QUrl currUrl = locationEditCurrentTextList.at(i); KIO::StatJob *statJob = KIO::stat(currUrl, KIO::HideProgressInfo); KJobWidgets::setWindow(statJob, this); int res = statJob->exec(); if (res) { // again, we don't care about filenames if (!statJob->statResult().isDir()) { currUrl = currUrl.adjusted(QUrl::RemoveFilename | QUrl::StripTrailingSlash); } // iterate while this item is contained on the top most url while (!topMostUrl.matches(currUrl, QUrl::StripTrailingSlash) && !topMostUrl.isParentOf(currUrl)) { topMostUrl = KIO::upUrl(topMostUrl); } } } // now recalculate all paths for them being relative in base of the top most url QStringList stringList; stringList.reserve(locationEditCurrentTextList.count()); for (int i = 0; i < locationEditCurrentTextList.count(); ++i) { Q_ASSERT(topMostUrl.isParentOf(locationEditCurrentTextList[i])); stringList << relativePathOrUrl(topMostUrl, locationEditCurrentTextList[i]); } d->ops->setUrl(topMostUrl, true); const bool signalsBlocked = d->locationEdit->lineEdit()->blockSignals(true); d->locationEdit->lineEdit()->setText(QStringLiteral("\"%1\"").arg(stringList.join(QStringLiteral("\" \"")))); d->locationEdit->lineEdit()->blockSignals(signalsBlocked); d->differentHierarchyLevelItemsEntered = true; slotOk(); return; } /** * end multi relative urls */ } else if (!locationEditCurrentTextList.isEmpty()) { // if we are on file or files mode, and we have an absolute url written by // the user, convert it to relative if (!locationEditCurrentText.isEmpty() && !(mode & KFile::Directory) && (QDir::isAbsolutePath(locationEditCurrentText) || containsProtocolSection(locationEditCurrentText))) { QString fileName; QUrl url = urlFromString(locationEditCurrentText); if (d->operationMode == Opening) { KIO::StatJob *statJob = KIO::stat(url, KIO::HideProgressInfo); KJobWidgets::setWindow(statJob, this); int res = statJob->exec(); if (res) { if (!statJob->statResult().isDir()) { fileName = url.fileName(); url = url.adjusted(QUrl::RemoveFilename); // keeps trailing slash } else { if (!url.path().endsWith(QLatin1Char('/'))) { url.setPath(url.path() + QLatin1Char('/')); } } } } else { const QUrl directory = url.adjusted(QUrl::RemoveFilename); //Check if the folder exists KIO::StatJob *statJob = KIO::stat(directory, KIO::HideProgressInfo); KJobWidgets::setWindow(statJob, this); int res = statJob->exec(); if (res) { if (statJob->statResult().isDir()) { url = url.adjusted(QUrl::StripTrailingSlash); fileName = url.fileName(); url = url.adjusted(QUrl::RemoveFilename); } } } d->ops->setUrl(url, true); const bool signalsBlocked = d->locationEdit->lineEdit()->blockSignals(true); d->locationEdit->lineEdit()->setText(fileName); d->locationEdit->lineEdit()->blockSignals(signalsBlocked); slotOk(); return; } } // restore it d->differentHierarchyLevelItemsEntered = false; // locationEditCurrentTextList contains absolute paths // this is the general loop for the File and Files mode. Obviously we know // that the File mode will iterate only one time here bool directoryMode = (mode & KFile::Directory); bool onlyDirectoryMode = directoryMode && !(mode & KFile::File) && !(mode & KFile::Files); QList::ConstIterator it = locationEditCurrentTextList.constBegin(); bool filesInList = false; while (it != locationEditCurrentTextList.constEnd()) { QUrl url(*it); if (d->operationMode == Saving && !directoryMode) { d->appendExtension(url); } d->url = url; KIO::StatJob *statJob = KIO::stat(url, KIO::HideProgressInfo); KJobWidgets::setWindow(statJob, this); int res = statJob->exec(); if (!KUrlAuthorized::authorizeUrlAction(QStringLiteral("open"), QUrl(), url)) { QString msg = KIO::buildErrorString(KIO::ERR_ACCESS_DENIED, d->url.toDisplayString()); KMessageBox::error(this, msg); return; } // if we are on local mode, make sure we haven't got a remote base url if ((mode & KFile::LocalOnly) && !d->mostLocalUrl(d->url).isLocalFile()) { KMessageBox::sorry(this, i18n("You can only select local files"), i18n("Remote files not accepted")); return; } const auto &supportedSchemes = d->model->supportedSchemes(); if (!supportedSchemes.isEmpty() && !supportedSchemes.contains(d->url.scheme())) { KMessageBox::sorry(this, i18np("The selected URL uses an unsupported scheme. " "Please use the following scheme: %2", "The selected URL uses an unsupported scheme. " "Please use one of the following schemes: %2", supportedSchemes.size(), supportedSchemes.join(QLatin1String(", "))), i18n("Unsupported URL scheme")); return; } // if we are given a folder when not on directory mode, let's get into it if (res && !directoryMode && statJob->statResult().isDir()) { // check if we were given more than one folder, in that case we don't know to which one // cd ++it; while (it != locationEditCurrentTextList.constEnd()) { QUrl checkUrl(*it); KIO::StatJob *checkStatJob = KIO::stat(checkUrl, KIO::HideProgressInfo); KJobWidgets::setWindow(checkStatJob, this); bool res = checkStatJob->exec(); if (res && checkStatJob->statResult().isDir()) { KMessageBox::sorry(this, i18n("More than one folder has been selected and this dialog does not accept folders, so it is not possible to decide which one to enter. Please select only one folder to list it."), i18n("More than one folder provided")); return; } else if (res) { filesInList = true; } ++it; } if (filesInList) { KMessageBox::information(this, i18n("At least one folder and one file has been selected. Selected files will be ignored and the selected folder will be listed"), i18n("Files and folders selected")); } d->ops->setUrl(url, true); const bool signalsBlocked = d->locationEdit->lineEdit()->blockSignals(true); d->locationEdit->lineEdit()->setText(QString()); d->locationEdit->lineEdit()->blockSignals(signalsBlocked); return; } else if (res && onlyDirectoryMode && !statJob->statResult().isDir()) { // if we are given a file when on directory only mode, reject it return; } else if (!(mode & KFile::ExistingOnly) || res) { // if we don't care about ExistingOnly flag, add the file even if // it doesn't exist. If we care about it, don't add it to the list if (!onlyDirectoryMode || (res && statJob->statResult().isDir())) { d->urlList << url; } filesInList = true; } else { KMessageBox::sorry(this, i18n("The file \"%1\" could not be found", url.toDisplayString(QUrl::PreferLocalFile)), i18n("Cannot open file")); return; // do not emit accepted() if we had ExistingOnly flag and stat failed } if ((d->operationMode == Saving) && d->confirmOverwrite && !d->toOverwrite(url)) { return; } ++it; } // if we have reached this point and we didn't return before, that is because // we want this dialog to be accepted emit accepted(); } void KFileWidget::accept() { d->inAccept = true; // parseSelectedUrls() checks that *lastDirectory() = d->ops->url(); if (!d->fileClass.isEmpty()) { KRecentDirs::add(d->fileClass, d->ops->url().toString()); } // clear the topmost item, we insert it as full path later on as item 1 d->locationEdit->setItemText(0, QString()); const QList list = selectedUrls(); QList::const_iterator it = list.begin(); int atmost = d->locationEdit->maxItems(); //don't add more items than necessary for (; it != list.end() && atmost > 0; ++it) { const QUrl &url = *it; // we strip the last slash (-1) because KUrlComboBox does that as well // when operating in file-mode. If we wouldn't , dupe-finding wouldn't // work. QString file = url.isLocalFile() ? url.toLocalFile() : url.toDisplayString(); // remove dupes for (int i = 1; i < d->locationEdit->count(); i++) { if (d->locationEdit->itemText(i) == file) { d->locationEdit->removeItem(i--); break; } } //FIXME I don't think this works correctly when the KUrlComboBox has some default urls. //KUrlComboBox should provide a function to add an url and rotate the existing ones, keeping //track of maxItems, and we shouldn't be able to insert items as we please. d->locationEdit->insertItem(1, file); atmost--; } d->writeViewConfig(); d->saveRecentFiles(); d->addToRecentDocuments(); if (!(mode() & KFile::Files)) { // single selection emit fileSelected(d->url); } d->ops->close(); } void KFileWidgetPrivate::_k_fileHighlighted(const KFileItem &i) { if ((!i.isNull() && i.isDir()) || (locationEdit->hasFocus() && !locationEdit->currentText().isEmpty())) { // don't disturb return; } const bool modified = locationEdit->lineEdit()->isModified(); if (!(ops->mode() & KFile::Files)) { if (i.isNull()) { if (!modified) { setLocationText(QUrl()); } return; } url = i.url(); if (!locationEdit->hasFocus()) { // don't disturb while editing setLocationText(url); } emit q->fileHighlighted(url); } else { multiSelectionChanged(); emit q->selectionChanged(); } locationEdit->lineEdit()->setModified(false); // When saving, and when double-click mode is being used, highlight the // filename after a file is single-clicked so the user has a chance to quickly // rename it if desired // Note that double-clicking will override this and overwrite regardless of // single/double click mouse setting (see _k_slotViewDoubleClicked() ) if (operationMode == KFileWidget::Saving) { locationEdit->setFocus(); } } void KFileWidgetPrivate::_k_fileSelected(const KFileItem &i) { if (!i.isNull() && i.isDir()) { return; } if (!(ops->mode() & KFile::Files)) { if (i.isNull()) { setLocationText(QUrl()); return; } setLocationText(i.url()); } else { multiSelectionChanged(); emit q->selectionChanged(); } // Same as above in _k_fileHighlighted(), but for single-click mode if (operationMode == KFileWidget::Saving) { locationEdit->setFocus(); } else { q->slotOk(); } } // I know it's slow to always iterate thru the whole filelist // (d->ops->selectedItems()), but what can we do? void KFileWidgetPrivate::multiSelectionChanged() { if (locationEdit->hasFocus() && !locationEdit->currentText().isEmpty()) { // don't disturb return; } const KFileItemList list = ops->selectedItems(); if (list.isEmpty()) { setLocationText(QUrl()); return; } setLocationText(list.urlList()); } void KFileWidgetPrivate::setDummyHistoryEntry(const QString &text, const QPixmap &icon, bool usePreviousPixmapIfNull) { // setCurrentItem() will cause textChanged() being emitted, // so slotLocationChanged() will be called. Make sure we don't clear // the KDirOperator's view-selection in there QObject::disconnect(locationEdit, SIGNAL(editTextChanged(QString)), q, SLOT(_k_slotLocationChanged(QString))); bool dummyExists = dummyAdded; int cursorPosition = locationEdit->lineEdit()->cursorPosition(); if (dummyAdded) { if (!icon.isNull()) { locationEdit->setItemIcon(0, icon); locationEdit->setItemText(0, text); } else { if (!usePreviousPixmapIfNull) { locationEdit->setItemIcon(0, QPixmap()); } locationEdit->setItemText(0, text); } } else { if (!text.isEmpty()) { if (!icon.isNull()) { locationEdit->insertItem(0, icon, text); } else { if (!usePreviousPixmapIfNull) { locationEdit->insertItem(0, QPixmap(), text); } else { locationEdit->insertItem(0, text); } } dummyAdded = true; dummyExists = true; } } if (dummyExists && !text.isEmpty()) { locationEdit->setCurrentIndex(0); } locationEdit->lineEdit()->setCursorPosition(cursorPosition); QObject::connect(locationEdit, SIGNAL(editTextChanged(QString)), q, SLOT(_k_slotLocationChanged(QString))); } void KFileWidgetPrivate::removeDummyHistoryEntry() { if (!dummyAdded) { return; } // setCurrentItem() will cause textChanged() being emitted, // so slotLocationChanged() will be called. Make sure we don't clear // the KDirOperator's view-selection in there QObject::disconnect(locationEdit, SIGNAL(editTextChanged(QString)), q, SLOT(_k_slotLocationChanged(QString))); if (locationEdit->count()) { locationEdit->removeItem(0); } locationEdit->setCurrentIndex(-1); dummyAdded = false; QObject::connect(locationEdit, SIGNAL(editTextChanged(QString)), q, SLOT(_k_slotLocationChanged(QString))); } void KFileWidgetPrivate::setLocationText(const QUrl &url) { if (!url.isEmpty()) { QPixmap mimeTypeIcon = KIconLoader::global()->loadMimeTypeIcon(KIO::iconNameForUrl(url), KIconLoader::Small); if (!url.isRelative()) { const QUrl directory = url.adjusted(QUrl::RemoveFilename); if (!directory.path().isEmpty()) { q->setUrl(directory, false); } else { q->setUrl(url, false); } } setDummyHistoryEntry(url.fileName(), mimeTypeIcon); } else { removeDummyHistoryEntry(); } if (operationMode == KFileWidget::Saving) { setNonExtSelection(); } } static QString relativePathOrUrl(const QUrl &baseUrl, const QUrl &url) { if (baseUrl.isParentOf(url)) { const QString basePath(QDir::cleanPath(baseUrl.path())); QString relPath(QDir::cleanPath(url.path())); relPath.remove(0, basePath.length()); if (relPath.startsWith(QLatin1Char('/'))) { relPath.remove(0, 1); } return relPath; } else { return url.toDisplayString(); } } void KFileWidgetPrivate::setLocationText(const QList &urlList) { const QUrl currUrl = ops->url(); if (urlList.count() > 1) { QString urls; for (const QUrl &url : urlList) { urls += QStringLiteral("\"%1\"").arg(relativePathOrUrl(currUrl, url)) + QLatin1Char(' '); } urls.chop(1); setDummyHistoryEntry(urls, QPixmap(), false); } else if (urlList.count() == 1) { const QPixmap mimeTypeIcon = KIconLoader::global()->loadMimeTypeIcon(KIO::iconNameForUrl(urlList[0]), KIconLoader::Small); setDummyHistoryEntry(relativePathOrUrl(currUrl, urlList[0]), mimeTypeIcon); } else { removeDummyHistoryEntry(); } if (operationMode == KFileWidget::Saving) { setNonExtSelection(); } } void KFileWidgetPrivate::updateLocationWhatsThis() { QString whatsThisText; if (operationMode == KFileWidget::Saving) { whatsThisText = QLatin1String("") + i18n("This is the name to save the file as.") + i18n(autocompletionWhatsThisText); } else if (ops->mode() & KFile::Files) { whatsThisText = QLatin1String("") + i18n("This is the list of files to open. More than " "one file can be specified by listing several " "files, separated by spaces.") + i18n(autocompletionWhatsThisText); } else { whatsThisText = QLatin1String("") + i18n("This is the name of the file to open.") + i18n(autocompletionWhatsThisText); } locationLabel->setWhatsThis(whatsThisText); locationEdit->setWhatsThis(whatsThisText); } void KFileWidgetPrivate::initSpeedbar() { if (placesDock) { return; } placesDock = new QDockWidget(i18nc("@title:window", "Places"), q); placesDock->setFeatures(QDockWidget::NoDockWidgetFeatures); placesDock->setTitleBarWidget(new KDEPrivate::KFileWidgetDockTitleBar(placesDock)); placesView = new KFilePlacesView(placesDock); placesView->setModel(model); placesView->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); placesView->setObjectName(QStringLiteral("url bar")); QObject::connect(placesView, SIGNAL(urlChanged(QUrl)), q, SLOT(_k_enterUrl(QUrl))); // need to set the current url of the urlbar manually (not via urlEntered() // here, because the initial url of KDirOperator might be the same as the // one that will be set later (and then urlEntered() won't be emitted). // TODO: KDE5 ### REMOVE THIS when KDirOperator's initial URL (in the c'tor) is gone. placesView->setUrl(url); placesDock->setWidget(placesView); placesViewSplitter->insertWidget(0, placesDock); // initialize the size of the splitter placesViewWidth = configGroup.readEntry(SpeedbarWidth, placesView->sizeHint().width()); // Needed for when the dialog is shown with the places panel initially hidden setPlacesViewSplitterSizes(); QObject::connect(placesDock, SIGNAL(visibilityChanged(bool)), q, SLOT(_k_toggleSpeedbar(bool))); } void KFileWidgetPrivate::setPlacesViewSplitterSizes() { if (placesViewWidth > 0) { QList sizes = placesViewSplitter->sizes(); sizes[0] = placesViewWidth; sizes[1] = q->width() - placesViewWidth - placesViewSplitter->handleWidth(); placesViewSplitter->setSizes(sizes); } } void KFileWidgetPrivate::setLafBoxColumnWidth() { // In order to perfectly align the filename widget with KDirOperator's icon view // - placesViewWidth needs to account for the size of the splitter handle // - the lafBox grid layout spacing should only affect the label, but not the line edit const int adjustment = placesViewSplitter->handleWidth() - lafBox->horizontalSpacing(); lafBox->setColumnMinimumWidth(0, placesViewWidth + adjustment); } void KFileWidgetPrivate::initGUI() { delete boxLayout; // deletes all sub layouts boxLayout = new QVBoxLayout(q); boxLayout->setContentsMargins(0, 0, 0, 0); // no additional margin to the already existing placesViewSplitter = new QSplitter(q); placesViewSplitter->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); placesViewSplitter->setChildrenCollapsible(false); boxLayout->addWidget(placesViewSplitter); QObject::connect(placesViewSplitter, SIGNAL(splitterMoved(int,int)), q, SLOT(_k_placesViewSplitterMoved(int,int))); placesViewSplitter->insertWidget(0, opsWidget); vbox = new QVBoxLayout(); vbox->setContentsMargins(0, 0, 0, 0); boxLayout->addLayout(vbox); lafBox = new QGridLayout(); lafBox->addWidget(locationLabel, 0, 0, Qt::AlignVCenter | Qt::AlignRight); lafBox->addWidget(locationEdit, 0, 1, Qt::AlignVCenter); lafBox->addWidget(okButton, 0, 2, Qt::AlignVCenter); lafBox->addWidget(filterLabel, 1, 0, Qt::AlignVCenter | Qt::AlignRight); lafBox->addWidget(filterWidget, 1, 1, Qt::AlignVCenter); lafBox->addWidget(cancelButton, 1, 2, Qt::AlignVCenter); lafBox->setColumnStretch(1, 4); vbox->addLayout(lafBox); // add the Automatically Select Extension checkbox vbox->addWidget(autoSelectExtCheckBox); q->setTabOrder(ops, autoSelectExtCheckBox); q->setTabOrder(autoSelectExtCheckBox, locationEdit); q->setTabOrder(locationEdit, filterWidget); q->setTabOrder(filterWidget, okButton); q->setTabOrder(okButton, cancelButton); q->setTabOrder(cancelButton, urlNavigator); q->setTabOrder(urlNavigator, ops); } void KFileWidgetPrivate::_k_slotFilterChanged() { // qDebug(); filterDelayTimer.stop(); QString filter = filterWidget->currentFilter(); ops->clearFilter(); if (filter.contains(QLatin1Char('/'))) { QStringList types = filter.split(QLatin1Char(' '), QString::SkipEmptyParts); types.prepend(QStringLiteral("inode/directory")); ops->setMimeFilter(types); } else if (filter.contains(QLatin1Char('*')) || filter.contains(QLatin1Char('?')) || filter.contains(QLatin1Char('['))) { ops->setNameFilter(filter); } else { ops->setNameFilter(QLatin1Char('*') + filter.replace(QLatin1Char(' '), QLatin1Char('*')) + QLatin1Char('*')); } updateAutoSelectExtension(); ops->updateDir(); emit q->filterChanged(filter); } void KFileWidget::setUrl(const QUrl &url, bool clearforward) { // qDebug(); d->ops->setUrl(url, clearforward); } // Protected void KFileWidgetPrivate::_k_urlEntered(const QUrl &url) { // qDebug(); QString filename = locationEditCurrentText(); KUrlComboBox *pathCombo = urlNavigator->editor(); if (pathCombo->count() != 0) { // little hack pathCombo->setUrl(url); } bool blocked = locationEdit->blockSignals(true); if (keepLocation) { QUrl currentUrl = urlFromString(filename); locationEdit->changeUrl(0, QIcon::fromTheme(KIO::iconNameForUrl(currentUrl)), currentUrl); locationEdit->lineEdit()->setModified(true); } locationEdit->blockSignals(blocked); urlNavigator->setLocationUrl(url); // is trigged in ctor before completion object is set KUrlCompletion *completion = dynamic_cast(locationEdit->completionObject()); if (completion) { completion->setDir(url); } if (placesView) { placesView->setUrl(url); } } void KFileWidgetPrivate::_k_locationAccepted(const QString &url) { Q_UNUSED(url); // qDebug(); q->slotOk(); } void KFileWidgetPrivate::_k_enterUrl(const QUrl &url) { // qDebug(); // append '/' if needed: url combo does not add it // tokenize() expects it because it uses QUrl::adjusted(QUrl::RemoveFilename) QUrl u(url); if (!u.path().isEmpty() && !u.path().endsWith(QLatin1Char('/'))) { u.setPath(u.path() + QLatin1Char('/')); } q->setUrl(u); // We need to check window()->focusWidget() instead of locationEdit->hasFocus // because when the window is showing up locationEdit // may still not have focus but it'll be the one that will have focus when the window // gets it and we don't want to steal its focus either if (q->window()->focusWidget() != locationEdit) { ops->setFocus(); } } void KFileWidgetPrivate::_k_enterUrl(const QString &url) { // qDebug(); _k_enterUrl(urlFromString(KUrlCompletion::replacedPath(url, true, true))); } bool KFileWidgetPrivate::toOverwrite(const QUrl &url) { // qDebug(); KIO::StatJob *statJob = KIO::stat(url, KIO::HideProgressInfo); KJobWidgets::setWindow(statJob, q); bool res = statJob->exec(); if (res) { int ret = KMessageBox::warningContinueCancel(q, i18n("The file \"%1\" already exists. Do you wish to overwrite it?", url.fileName()), i18n("Overwrite File?"), KStandardGuiItem::overwrite(), KStandardGuiItem::cancel(), QString(), KMessageBox::Notify | KMessageBox::Dangerous); if (ret != KMessageBox::Continue) { return false; } return true; } return true; } -#ifndef KIOFILEWIDGETS_NO_DEPRECATED void KFileWidget::setSelection(const QString &url) { // qDebug() << "setSelection " << url; if (url.isEmpty()) { return; } QUrl u = d->getCompleteUrl(url); if (!u.isValid()) { // Relative path was treated as URL, but it was found to be invalid. qWarning() << url << " is not a correct argument for setSelection!"; return; } setSelectedUrl(urlFromString(url)); } -#endif void KFileWidget::setSelectedUrl(const QUrl &url) { // Honor protocols that do not support directory listing if (!url.isRelative() && !KProtocolManager::supportsListing(url)) { return; } d->setLocationText(url); } void KFileWidgetPrivate::_k_slotLoadingFinished() { const QString currentText = locationEdit->currentText(); if (currentText.isEmpty()) { return; } ops->blockSignals(true); QUrl u(ops->url()); if (currentText.startsWith(QLatin1Char('/'))) u.setPath(currentText); else u.setPath(concatPaths(ops->url().path(), currentText)); ops->setCurrentItem(u); ops->blockSignals(false); } void KFileWidgetPrivate::_k_fileCompletion(const QString &match) { // qDebug(); if (match.isEmpty() || locationEdit->currentText().contains(QLatin1Char('"'))) { return; } const QUrl url = urlFromString(match); const QPixmap pix = KIconLoader::global()->loadMimeTypeIcon(KIO::iconNameForUrl(url), KIconLoader::Small); setDummyHistoryEntry(locationEdit->currentText(), pix, !locationEdit->currentText().isEmpty()); } void KFileWidgetPrivate::_k_slotLocationChanged(const QString &text) { // qDebug(); locationEdit->lineEdit()->setModified(true); if (text.isEmpty() && ops->view()) { ops->view()->clearSelection(); } if (text.isEmpty()) { removeDummyHistoryEntry(); } else { setDummyHistoryEntry(text); } if (!locationEdit->lineEdit()->text().isEmpty()) { const QList urlList(tokenize(text)); ops->setCurrentItems(urlList); } updateFilter(); } QUrl KFileWidget::selectedUrl() const { // qDebug(); if (d->inAccept) { return d->url; } else { return QUrl(); } } QList KFileWidget::selectedUrls() const { // qDebug(); QList list; if (d->inAccept) { if (d->ops->mode() & KFile::Files) { list = d->parseSelectedUrls(); } else { list.append(d->url); } } return list; } QList &KFileWidgetPrivate::parseSelectedUrls() { // qDebug(); if (filenames.isEmpty()) { return urlList; } urlList.clear(); if (filenames.contains(QLatin1Char('/'))) { // assume _one_ absolute filename QUrl u; if (containsProtocolSection(filenames)) { u = QUrl(filenames); } else { u.setPath(filenames); } if (u.isValid()) { urlList.append(u); } else KMessageBox::error(q, i18n("The chosen filenames do not\n" "appear to be valid."), i18n("Invalid Filenames")); } else { urlList = tokenize(filenames); } filenames.clear(); // indicate that we parsed that one return urlList; } // FIXME: current implementation drawback: a filename can't contain quotes QList KFileWidgetPrivate::tokenize(const QString &line) const { // qDebug(); QList urls; QUrl u(ops->url()); if (!u.path().endsWith(QLatin1Char('/'))) { u.setPath(u.path() + QLatin1Char('/')); } QString name; const int count = line.count(QLatin1Char('"')); if (count == 0) { // no " " -> assume one single file if (!QDir::isAbsolutePath(line)) { u = u.adjusted(QUrl::RemoveFilename); u.setPath(u.path() + line); if (u.isValid()) { urls.append(u); } } else { urls << QUrl::fromLocalFile(line); } return urls; } int start = 0; int index1 = -1, index2 = -1; while (true) { index1 = line.indexOf(QLatin1Char('"'), start); index2 = line.indexOf(QLatin1Char('"'), index1 + 1); if (index1 < 0 || index2 < 0) { break; } // get everything between the " " name = line.mid(index1 + 1, index2 - index1 - 1); // since we use setPath we need to do this under a temporary url QUrl _u(u); QUrl currUrl(name); if (!QDir::isAbsolutePath(currUrl.url())) { _u = _u.adjusted(QUrl::RemoveFilename); _u.setPath(_u.path() + name); } else { // we allow to insert various absolute paths like: // "/home/foo/bar.txt" "/boot/grub/menu.lst" _u = currUrl; } if (_u.isValid()) { urls.append(_u); } start = index2 + 1; } return urls; } QString KFileWidget::selectedFile() const { // qDebug(); if (d->inAccept) { const QUrl url = d->mostLocalUrl(d->url); if (url.isLocalFile()) { return url.toLocalFile(); } else { KMessageBox::sorry(const_cast(this), i18n("You can only select local files."), i18n("Remote Files Not Accepted")); } } return QString(); } QStringList KFileWidget::selectedFiles() const { // qDebug(); QStringList list; if (d->inAccept) { if (d->ops->mode() & KFile::Files) { const QList urls = d->parseSelectedUrls(); QList::const_iterator it = urls.begin(); while (it != urls.end()) { QUrl url = d->mostLocalUrl(*it); if (url.isLocalFile()) { list.append(url.toLocalFile()); } ++it; } } else { // single-selection mode if (d->url.isLocalFile()) { list.append(d->url.toLocalFile()); } } } return list; } QUrl KFileWidget::baseUrl() const { return d->ops->url(); } void KFileWidget::resizeEvent(QResizeEvent *event) { QWidget::resizeEvent(event); if (d->placesDock) { // we don't want our places dock actually changing size when we resize // and qt doesn't make it easy to enforce such a thing with QSplitter d->setPlacesViewSplitterSizes(); } } void KFileWidget::showEvent(QShowEvent *event) { if (!d->hasView) { // delayed view-creation Q_ASSERT(d); Q_ASSERT(d->ops); d->ops->setView(KFile::Default); d->ops->view()->setSizePolicy(QSizePolicy(QSizePolicy::Maximum, QSizePolicy::Maximum)); d->hasView = true; connect(d->ops->view(), SIGNAL(doubleClicked(QModelIndex)), this, SLOT(_k_slotViewDoubleClicked(QModelIndex))); } d->ops->clearHistory(); QWidget::showEvent(event); } bool KFileWidget::eventFilter(QObject *watched, QEvent *event) { const bool res = QWidget::eventFilter(watched, event); QKeyEvent *keyEvent = dynamic_cast(event); if (watched == d->iconSizeSlider && keyEvent) { if (keyEvent->key() == Qt::Key_Left || keyEvent->key() == Qt::Key_Up || keyEvent->key() == Qt::Key_Right || keyEvent->key() == Qt::Key_Down) { d->_k_slotIconSizeSliderMoved(d->iconSizeSlider->value()); } } else if (watched == d->locationEdit && event->type() == QEvent::KeyPress) { if (keyEvent->modifiers() & Qt::AltModifier) { switch (keyEvent->key()) { case Qt::Key_Up: d->ops->actionCollection()->action(QStringLiteral("up"))->trigger(); break; case Qt::Key_Left: d->ops->actionCollection()->action(QStringLiteral("back"))->trigger(); break; case Qt::Key_Right: d->ops->actionCollection()->action(QStringLiteral("forward"))->trigger(); break; default: break; } } } return res; } void KFileWidget::setMode(KFile::Modes m) { // qDebug(); d->ops->setMode(m); if (d->ops->dirOnlyMode()) { d->filterWidget->setDefaultFilter(i18n("*|All Folders")); } else { d->filterWidget->setDefaultFilter(i18n("*|All Files")); } d->updateAutoSelectExtension(); } KFile::Modes KFileWidget::mode() const { return d->ops->mode(); } void KFileWidgetPrivate::readViewConfig() { ops->setViewConfig(configGroup); ops->readConfig(configGroup); KUrlComboBox *combo = urlNavigator->editor(); autoDirectoryFollowing = configGroup.readEntry(AutoDirectoryFollowing, DefaultDirectoryFollowing); KCompletion::CompletionMode cm = (KCompletion::CompletionMode) configGroup.readEntry(PathComboCompletionMode, static_cast(KCompletion::CompletionPopup)); if (cm != KCompletion::CompletionPopup) { combo->setCompletionMode(cm); } cm = (KCompletion::CompletionMode) configGroup.readEntry(LocationComboCompletionMode, static_cast(KCompletion::CompletionPopup)); if (cm != KCompletion::CompletionPopup) { locationEdit->setCompletionMode(cm); } // show or don't show the speedbar _k_toggleSpeedbar(configGroup.readEntry(ShowSpeedbar, true)); // show or don't show the bookmarks _k_toggleBookmarks(configGroup.readEntry(ShowBookmarks, false)); // does the user want Automatically Select Extension? autoSelectExtChecked = configGroup.readEntry(AutoSelectExtChecked, DefaultAutoSelectExtChecked); updateAutoSelectExtension(); // should the URL navigator use the breadcrumb navigation? urlNavigator->setUrlEditable(!configGroup.readEntry(BreadcrumbNavigation, true)); // should the URL navigator show the full path? urlNavigator->setShowFullPath(configGroup.readEntry(ShowFullPath, false)); int w1 = q->minimumSize().width(); int w2 = toolbar->sizeHint().width(); if (w1 < w2) { q->setMinimumWidth(w2); } } void KFileWidgetPrivate::writeViewConfig() { // these settings are global settings; ALL instances of the file dialog // should reflect them. // There is no way to tell KFileOperator::writeConfig() to write to // kdeglobals so we write settings to a temporary config group then copy // them all to kdeglobals KConfig tmp(QString(), KConfig::SimpleConfig); KConfigGroup tmpGroup(&tmp, ConfigGroup); KUrlComboBox *pathCombo = urlNavigator->editor(); //saveDialogSize( tmpGroup, KConfigGroup::Persistent | KConfigGroup::Global ); tmpGroup.writeEntry(PathComboCompletionMode, static_cast(pathCombo->completionMode())); tmpGroup.writeEntry(LocationComboCompletionMode, static_cast(locationEdit->completionMode())); const bool showSpeedbar = placesDock && !placesDock->isHidden(); tmpGroup.writeEntry(ShowSpeedbar, showSpeedbar); if (placesViewWidth > 0) { tmpGroup.writeEntry(SpeedbarWidth, placesViewWidth); } tmpGroup.writeEntry(ShowBookmarks, bookmarkHandler != nullptr); tmpGroup.writeEntry(AutoSelectExtChecked, autoSelectExtChecked); tmpGroup.writeEntry(BreadcrumbNavigation, !urlNavigator->isUrlEditable()); tmpGroup.writeEntry(ShowFullPath, urlNavigator->showFullPath()); ops->writeConfig(tmpGroup); // Copy saved settings to kdeglobals tmpGroup.copyTo(&configGroup, KConfigGroup::Persistent | KConfigGroup::Global); } void KFileWidgetPrivate::readRecentFiles() { // qDebug(); QObject::disconnect(locationEdit, SIGNAL(editTextChanged(QString)), q, SLOT(_k_slotLocationChanged(QString))); locationEdit->setMaxItems(configGroup.readEntry(RecentFilesNumber, DefaultRecentURLsNumber)); locationEdit->setUrls(configGroup.readPathEntry(RecentFiles, QStringList()), KUrlComboBox::RemoveBottom); locationEdit->setCurrentIndex(-1); QObject::connect(locationEdit, SIGNAL(editTextChanged(QString)), q, SLOT(_k_slotLocationChanged(QString))); KUrlComboBox *combo = urlNavigator->editor(); combo->setUrls(configGroup.readPathEntry(RecentURLs, QStringList()), KUrlComboBox::RemoveTop); combo->setMaxItems(configGroup.readEntry(RecentURLsNumber, DefaultRecentURLsNumber)); combo->setUrl(ops->url()); // since we delayed this moment, initialize the directory of the completion object to // our current directory (that was very probably set on the constructor) KUrlCompletion *completion = dynamic_cast(locationEdit->completionObject()); if (completion) { completion->setDir(ops->url()); } } void KFileWidgetPrivate::saveRecentFiles() { // qDebug(); configGroup.writePathEntry(RecentFiles, locationEdit->urls()); KUrlComboBox *pathCombo = urlNavigator->editor(); configGroup.writePathEntry(RecentURLs, pathCombo->urls()); } QPushButton *KFileWidget::okButton() const { return d->okButton; } QPushButton *KFileWidget::cancelButton() const { return d->cancelButton; } // Called by KFileDialog void KFileWidget::slotCancel() { d->writeViewConfig(); d->ops->close(); } void KFileWidget::setKeepLocation(bool keep) { d->keepLocation = keep; } bool KFileWidget::keepsLocation() const { return d->keepLocation; } void KFileWidget::setOperationMode(OperationMode mode) { // qDebug(); d->operationMode = mode; d->keepLocation = (mode == Saving); d->filterWidget->setEditable(!d->hasDefaultFilter || mode != Saving); if (mode == Opening) { // don't use KStandardGuiItem::open() here which has trailing ellipsis! d->okButton->setText(i18n("&Open")); d->okButton->setIcon(QIcon::fromTheme(QStringLiteral("document-open"))); // hide the new folder actions...usability team says they shouldn't be in open file dialog actionCollection()->removeAction(actionCollection()->action(QStringLiteral("mkdir"))); } else if (mode == Saving) { KGuiItem::assign(d->okButton, KStandardGuiItem::save()); d->setNonExtSelection(); } else { KGuiItem::assign(d->okButton, KStandardGuiItem::ok()); } d->updateLocationWhatsThis(); d->updateAutoSelectExtension(); if (d->ops) { d->ops->setIsSaving(mode == Saving); } d->updateFilterText(); } KFileWidget::OperationMode KFileWidget::operationMode() const { return d->operationMode; } void KFileWidgetPrivate::_k_slotAutoSelectExtClicked() { // qDebug() << "slotAutoSelectExtClicked(): " // << autoSelectExtCheckBox->isChecked() << endl; // whether the _user_ wants it on/off autoSelectExtChecked = autoSelectExtCheckBox->isChecked(); // update the current filename's extension updateLocationEditExtension(extension /* extension hasn't changed */); } void KFileWidgetPrivate::_k_placesViewSplitterMoved(int pos, int index) { // qDebug(); // we need to record the size of the splitter when the splitter changes size // so we can keep the places box the right size! if (placesDock && index == 1) { placesViewWidth = pos; // qDebug() << "setting lafBox minwidth to" << placesViewWidth; setLafBoxColumnWidth(); } } void KFileWidgetPrivate::_k_activateUrlNavigator() { // qDebug(); QLineEdit* lineEdit = urlNavigator->editor()->lineEdit(); // If the text field currently has focus and everything is selected, // pressing the keyboard shortcut returns the whole thing to breadcrumb mode if (urlNavigator->isUrlEditable() && lineEdit->hasFocus() && lineEdit->selectedText() == lineEdit->text() ) { urlNavigator->setUrlEditable(false); } else { urlNavigator->setUrlEditable(true); urlNavigator->setFocus(); lineEdit->selectAll(); } } void KFileWidgetPrivate::_k_zoomOutIconsSize() { const int currValue = ops->iconsZoom(); const int futValue = qMax(0, currValue - 10); iconSizeSlider->setValue(futValue); _k_slotIconSizeSliderMoved(futValue); } void KFileWidgetPrivate::_k_zoomInIconsSize() { const int currValue = ops->iconsZoom(); const int futValue = qMin(100, currValue + 10); iconSizeSlider->setValue(futValue); _k_slotIconSizeSliderMoved(futValue); } void KFileWidgetPrivate::_k_slotIconSizeChanged(int _value) { int maxSize = KIconLoader::SizeEnormous - KIconLoader::SizeSmall; int value = (maxSize * _value / 100) + KIconLoader::SizeSmall; switch (value) { case KIconLoader::SizeSmall: case KIconLoader::SizeSmallMedium: case KIconLoader::SizeMedium: case KIconLoader::SizeLarge: case KIconLoader::SizeHuge: case KIconLoader::SizeEnormous: iconSizeSlider->setToolTip(i18n("Icon size: %1 pixels (standard size)", value)); break; default: iconSizeSlider->setToolTip(i18n("Icon size: %1 pixels", value)); break; } } void KFileWidgetPrivate::_k_slotIconSizeSliderMoved(int _value) { // Force this to be called in case this slot is called first on the // slider move. _k_slotIconSizeChanged(_value); QPoint global(iconSizeSlider->rect().topLeft()); global.ry() += iconSizeSlider->height() / 2; QHelpEvent toolTipEvent(QEvent::ToolTip, QPoint(0, 0), iconSizeSlider->mapToGlobal(global)); QApplication::sendEvent(iconSizeSlider, &toolTipEvent); } void KFileWidgetPrivate::_k_slotViewDoubleClicked(const QModelIndex &index) { // double clicking to save should only work on files if (operationMode == KFileWidget::Saving && index.isValid() && ops->selectedItems().constFirst().isFile()) { q->slotOk(); } } void KFileWidgetPrivate::_k_slotViewKeyEnterReturnPressed() { // an enter/return event occured in the view // when we are saving one file and there is no selection in the view (otherwise we get an activated event) if (operationMode == KFileWidget::Saving && (ops->mode() & KFile::File) && ops->selectedItems().isEmpty()) { q->slotOk(); } } static QString getExtensionFromPatternList(const QStringList &patternList) { // qDebug(); QString ret; // qDebug() << "\tgetExtension " << patternList; QStringList::ConstIterator patternListEnd = patternList.end(); for (QStringList::ConstIterator it = patternList.begin(); it != patternListEnd; ++it) { // qDebug() << "\t\ttry: \'" << (*it) << "\'"; // is this pattern like "*.BMP" rather than useless things like: // // README // *. // *.* // *.JP*G // *.JP? // *.[Jj][Pp][Gg] if ((*it).startsWith(QLatin1String("*.")) && (*it).length() > 2 && (*it).indexOf(QLatin1Char('*'), 2) < 0 && (*it).indexOf(QLatin1Char('?'), 2) < 0 && (*it).indexOf(QLatin1Char('['), 2) < 0 && (*it).indexOf(QLatin1Char(']'), 2) < 0) { ret = (*it).mid(1); break; } } return ret; } static QString stripUndisplayable(const QString &string) { QString ret = string; ret.remove(QLatin1Char(':')); ret = KLocalizedString::removeAcceleratorMarker(ret); return ret; } //QString KFileWidget::currentFilterExtension() //{ // return d->extension; //} void KFileWidgetPrivate::updateAutoSelectExtension() { if (!autoSelectExtCheckBox) { return; } QMimeDatabase db; // // Figure out an extension for the Automatically Select Extension thing // (some Windows users apparently don't know what to do when confronted // with a text file called "COPYING" but do know what to do with // COPYING.txt ...) // // qDebug() << "Figure out an extension: "; QString lastExtension = extension; extension.clear(); // Automatically Select Extension is only valid if the user is _saving_ a _file_ if ((operationMode == KFileWidget::Saving) && (ops->mode() & KFile::File)) { // // Get an extension from the filter // QString filter = filterWidget->currentFilter(); if (!filter.isEmpty()) { // if the currently selected filename already has an extension which // is also included in the currently allowed extensions, keep it // otherwise use the default extension QString currentExtension = db.suffixForFileName(locationEditCurrentText()); if (currentExtension.isEmpty()) { currentExtension = locationEditCurrentText().section(QLatin1Char('.'), -1, -1); } // qDebug() << "filter:" << filter << "locationEdit:" << locationEditCurrentText() << "currentExtension:" << currentExtension; QString defaultExtension; QStringList extensionList; // e.g. "*.cpp" if (filter.indexOf(QLatin1Char('/')) < 0) { extensionList = filter.split(QLatin1Char(' '), QString::SkipEmptyParts); defaultExtension = getExtensionFromPatternList(extensionList); } // e.g. "text/html" else { QMimeType mime = db.mimeTypeForName(filter); if (mime.isValid()) { extensionList = mime.globPatterns(); defaultExtension = mime.preferredSuffix(); if (!defaultExtension.isEmpty()) { defaultExtension.prepend(QLatin1Char('.')); } } } if ((!currentExtension.isEmpty() && extensionList.contains(QLatin1String("*.") + currentExtension)) || filter == QLatin1String("application/octet-stream")) { extension = QLatin1Char('.') + currentExtension; } else { extension = defaultExtension; } // qDebug() << "List:" << extensionList << "auto-selected extension:" << extension; } // // GUI: checkbox // QString whatsThisExtension; if (!extension.isEmpty()) { // remember: sync any changes to the string with below autoSelectExtCheckBox->setText(i18n("Automatically select filename e&xtension (%1)", extension)); whatsThisExtension = i18n("the extension %1", extension); autoSelectExtCheckBox->setEnabled(true); autoSelectExtCheckBox->setChecked(autoSelectExtChecked); } else { // remember: sync any changes to the string with above autoSelectExtCheckBox->setText(i18n("Automatically select filename e&xtension")); whatsThisExtension = i18n("a suitable extension"); autoSelectExtCheckBox->setChecked(false); autoSelectExtCheckBox->setEnabled(false); } const QString locationLabelText = stripUndisplayable(locationLabel->text()); autoSelectExtCheckBox->setWhatsThis(QLatin1String("") + i18n( "This option enables some convenient features for " "saving files with extensions:
" "
    " "
  1. Any extension specified in the %1 text " "area will be updated if you change the file type " "to save in.
    " "
  2. " "
  3. If no extension is specified in the %2 " "text area when you click " "Save, %3 will be added to the end of the " "filename (if the filename does not already exist). " "This extension is based on the file type that you " "have chosen to save in.
    " "
    " "If you do not want KDE to supply an extension for the " "filename, you can either turn this option off or you " "can suppress it by adding a period (.) to the end of " "the filename (the period will be automatically " "removed)." "
  4. " "
" "If unsure, keep this option enabled as it makes your " "files more manageable." , locationLabelText, locationLabelText, whatsThisExtension) + QLatin1String("
") ); autoSelectExtCheckBox->show(); // update the current filename's extension updateLocationEditExtension(lastExtension); } // Automatically Select Extension not valid else { autoSelectExtCheckBox->setChecked(false); autoSelectExtCheckBox->hide(); } } // Updates the extension of the filename specified in d->locationEdit if the // Automatically Select Extension feature is enabled. // (this prevents you from accidentally saving "file.kwd" as RTF, for example) void KFileWidgetPrivate::updateLocationEditExtension(const QString &lastExtension) { if (!autoSelectExtCheckBox->isChecked() || extension.isEmpty()) { return; } QString urlStr = locationEditCurrentText(); if (urlStr.isEmpty()) { return; } QUrl url = getCompleteUrl(urlStr); // qDebug() << "updateLocationEditExtension (" << url << ")"; const int fileNameOffset = urlStr.lastIndexOf(QLatin1Char('/')) + 1; QString fileName = urlStr.mid(fileNameOffset); const int dot = fileName.lastIndexOf(QLatin1Char('.')); const int len = fileName.length(); if (dot > 0 && // has an extension already and it's not a hidden file // like ".hidden" (but we do accept ".hidden.ext") dot != len - 1 // and not deliberately suppressing extension ) { // exists? KIO::StatJob *statJob = KIO::stat(url, KIO::HideProgressInfo); KJobWidgets::setWindow(statJob, q); bool result = statJob->exec(); if (result) { // qDebug() << "\tfile exists"; if (statJob->statResult().isDir()) { // qDebug() << "\tisDir - won't alter extension"; return; } // --- fall through --- } // // try to get rid of the current extension // // catch "double extensions" like ".tar.gz" if (lastExtension.length() && fileName.endsWith(lastExtension)) { fileName.chop(lastExtension.length()); } else if (extension.length() && fileName.endsWith(extension)) { fileName.chop(extension.length()); } // can only handle "single extensions" else { fileName.truncate(dot); } // add extension const QString newText = urlStr.leftRef(fileNameOffset) + fileName + extension; if (newText != locationEditCurrentText()) { locationEdit->setItemText(locationEdit->currentIndex(), newText); locationEdit->lineEdit()->setModified(true); } } } QString KFileWidgetPrivate::findMatchingFilter(const QString &filter, const QString &filename) const { const QStringList patterns = filter.left(filter.indexOf(QLatin1Char('|'))).split(QLatin1Char(' '), QString::SkipEmptyParts); // '*.foo *.bar|Foo type' -> '*.foo', '*.bar' for (const QString &p : patterns) { QRegExp rx(p); rx.setPatternSyntax(QRegExp::Wildcard); if (rx.exactMatch(filename)) { return p; } } return QString(); } // Updates the filter if the extension of the filename specified in d->locationEdit is changed // (this prevents you from accidently saving "file.kwd" as RTF, for example) void KFileWidgetPrivate::updateFilter() { // qDebug(); if ((operationMode == KFileWidget::Saving) && (ops->mode() & KFile::File)) { QString urlStr = locationEditCurrentText(); if (urlStr.isEmpty()) { return; } if (filterWidget->isMimeFilter()) { QMimeDatabase db; QMimeType mime = db.mimeTypeForFile(urlStr, QMimeDatabase::MatchExtension); if (mime.isValid() && !mime.isDefault()) { if (filterWidget->currentFilter() != mime.name() && filterWidget->filters().indexOf(mime.name()) != -1) { filterWidget->setCurrentFilter(mime.name()); } } } else { QString filename = urlStr.mid(urlStr.lastIndexOf(QLatin1Char('/')) + 1); // only filename // accept any match to honor the user's selection; see later code handling the "*" match if (!findMatchingFilter(filterWidget->currentFilter(), filename).isEmpty()) { return; } const QStringList list = filterWidget->filters(); for (const QString &filter : list) { QString match = findMatchingFilter(filter, filename); if (!match.isEmpty()) { if (match != QLatin1String("*")) { // never match the catch-all filter filterWidget->setCurrentFilter(filter); } return; // do not repeat, could match a later filter } } } } } // applies only to a file that doesn't already exist void KFileWidgetPrivate::appendExtension(QUrl &url) { // qDebug(); if (!autoSelectExtCheckBox->isChecked() || extension.isEmpty()) { return; } QString fileName = url.fileName(); if (fileName.isEmpty()) { return; } // qDebug() << "appendExtension(" << url << ")"; const int len = fileName.length(); const int dot = fileName.lastIndexOf(QLatin1Char('.')); const bool suppressExtension = (dot == len - 1); const bool unspecifiedExtension = (dot <= 0); // don't KIO::Stat if unnecessary if (!(suppressExtension || unspecifiedExtension)) { return; } // exists? KIO::StatJob *statJob = KIO::stat(url, KIO::HideProgressInfo); KJobWidgets::setWindow(statJob, q); bool res = statJob->exec(); if (res) { // qDebug() << "\tfile exists - won't append extension"; return; } // suppress automatically append extension? if (suppressExtension) { // // Strip trailing dot // This allows lazy people to have autoSelectExtCheckBox->isChecked // but don't want a file extension to be appended // e.g. "README." will make a file called "README" // // If you really want a name like "README.", then type "README.." // and the trailing dot will be removed (or just stop being lazy and // turn off this feature so that you can type "README.") // // qDebug() << "\tstrip trailing dot"; QString path = url.path(); path.chop(1); url.setPath(path); } // evilmatically append extension :) if the user hasn't specified one else if (unspecifiedExtension) { // qDebug() << "\tappending extension \'" << extension << "\'..."; url = url.adjusted(QUrl::RemoveFilename); // keeps trailing slash url.setPath(url.path() + fileName + extension); // qDebug() << "\tsaving as \'" << url << "\'"; } } // adds the selected files/urls to 'recent documents' void KFileWidgetPrivate::addToRecentDocuments() { int m = ops->mode(); int atmost = KRecentDocument::maximumItems(); //don't add more than we need. KRecentDocument::add() is pretty slow if (m & KFile::LocalOnly) { const QStringList files = q->selectedFiles(); QStringList::ConstIterator it = files.begin(); for (; it != files.end() && atmost > 0; ++it) { KRecentDocument::add(QUrl::fromLocalFile(*it)); atmost--; } } else { // urls const QList urls = q->selectedUrls(); QList::ConstIterator it = urls.begin(); for (; it != urls.end() && atmost > 0; ++it) { if ((*it).isValid()) { KRecentDocument::add(*it); atmost--; } } } } KUrlComboBox *KFileWidget::locationEdit() const { return d->locationEdit; } KFileFilterCombo *KFileWidget::filterWidget() const { return d->filterWidget; } KActionCollection *KFileWidget::actionCollection() const { return d->ops->actionCollection(); } void KFileWidgetPrivate::_k_toggleSpeedbar(bool show) { if (show) { initSpeedbar(); placesDock->show(); setLafBoxColumnWidth(); // check to see if they have a home item defined, if not show the home button QUrl homeURL; homeURL.setPath(QDir::homePath()); KFilePlacesModel *model = static_cast(placesView->model()); for (int rowIndex = 0; rowIndex < model->rowCount(); rowIndex++) { QModelIndex index = model->index(rowIndex, 0); QUrl url = model->url(index); if (homeURL.matches(url, QUrl::StripTrailingSlash)) { toolbar->removeAction(ops->actionCollection()->action(QStringLiteral("home"))); break; } } } else { if (q->sender() == placesDock && placesDock && placesDock->isVisibleTo(q)) { // we didn't *really* go away! the dialog was simply hidden or // we changed virtual desktops or ... return; } if (placesDock) { placesDock->hide(); } QAction *homeAction = ops->actionCollection()->action(QStringLiteral("home")); QAction *reloadAction = ops->actionCollection()->action(QStringLiteral("reload")); if (!toolbar->actions().contains(homeAction)) { toolbar->insertAction(reloadAction, homeAction); } // reset the lafbox to not follow the width of the splitter lafBox->setColumnMinimumWidth(0, 0); } static_cast(q->actionCollection()->action(QStringLiteral("toggleSpeedbar")))->setChecked(show); // if we don't show the places panel, at least show the places menu urlNavigator->setPlacesSelectorVisible(!show); } void KFileWidgetPrivate::_k_toggleBookmarks(bool show) { if (show) { if (bookmarkHandler) { return; } bookmarkHandler = new KFileBookmarkHandler(q); q->connect(bookmarkHandler, SIGNAL(openUrl(QString)), SLOT(_k_enterUrl(QString))); bookmarkButton = new KActionMenu(QIcon::fromTheme(QStringLiteral("bookmarks")), i18n("Bookmarks"), q); bookmarkButton->setDelayed(false); q->actionCollection()->addAction(QStringLiteral("bookmark"), bookmarkButton); bookmarkButton->setMenu(bookmarkHandler->menu()); bookmarkButton->setWhatsThis(i18n("This button allows you to bookmark specific locations. " "Click on this button to open the bookmark menu where you may add, " "edit or select a bookmark.

" "These bookmarks are specific to the file dialog, but otherwise operate " "like bookmarks elsewhere in KDE.
")); toolbar->addAction(bookmarkButton); } else if (bookmarkHandler) { delete bookmarkHandler; bookmarkHandler = nullptr; delete bookmarkButton; bookmarkButton = nullptr; } static_cast(q->actionCollection()->action(QStringLiteral("toggleBookmarks")))->setChecked(show); } // static, overloaded QUrl KFileWidget::getStartUrl(const QUrl &startDir, QString &recentDirClass) { QString fileName; // result discarded return getStartUrl(startDir, recentDirClass, fileName); } // static, overloaded QUrl KFileWidget::getStartUrl(const QUrl &startDir, QString &recentDirClass, QString &fileName) { recentDirClass.clear(); fileName.clear(); QUrl ret; bool useDefaultStartDir = startDir.isEmpty(); if (!useDefaultStartDir) { if (startDir.scheme() == QLatin1String("kfiledialog")) { // The startDir URL with this protocol may be in the format: // directory() fileName() // 1. kfiledialog:///keyword "/" keyword // 2. kfiledialog:///keyword?global "/" keyword // 3. kfiledialog:///keyword/ "/" keyword // 4. kfiledialog:///keyword/?global "/" keyword // 5. kfiledialog:///keyword/filename /keyword filename // 6. kfiledialog:///keyword/filename?global /keyword filename QString keyword; QString urlDir = startDir.adjusted(QUrl::RemoveFilename | QUrl::StripTrailingSlash).path(); QString urlFile = startDir.fileName(); if (urlDir == QLatin1String("/")) { // '1'..'4' above keyword = urlFile; fileName.clear(); } else { // '5' or '6' above keyword = urlDir.mid(1); fileName = urlFile; } if (startDir.query() == QLatin1String("global")) { recentDirClass = QStringLiteral("::%1").arg(keyword); } else { recentDirClass = QStringLiteral(":%1").arg(keyword); } ret = QUrl::fromLocalFile(KRecentDirs::dir(recentDirClass)); } else { // not special "kfiledialog" URL // "foo.png" only gives us a file name, the default start dir will be used. // "file:foo.png" (from KHTML/webkit, due to fromPath()) means the same // (and is the reason why we don't just use QUrl::isRelative()). // In all other cases (startDir contains a directory path, or has no // fileName for us anyway, such as smb://), startDir is indeed a dir url. if (!startDir.adjusted(QUrl::RemoveFilename | QUrl::StripTrailingSlash).path().isEmpty() || startDir.fileName().isEmpty()) { // can use start directory ret = startDir; // will be checked by stat later // If we won't be able to list it (e.g. http), then use default if (!KProtocolManager::supportsListing(ret)) { useDefaultStartDir = true; fileName = startDir.fileName(); } } else { // file name only fileName = startDir.fileName(); useDefaultStartDir = true; } } } if (useDefaultStartDir) { if (lastDirectory()->isEmpty()) { *lastDirectory() = QUrl::fromLocalFile(QStandardPaths::writableLocation(QStandardPaths::DocumentsLocation)); const QUrl home(QUrl::fromLocalFile(QDir::homePath())); // if there is no docpath set (== home dir), we prefer the current // directory over it. We also prefer the homedir when our CWD is // different from our homedirectory or when the document dir // does not exist if (lastDirectory()->adjusted(QUrl::StripTrailingSlash) == home.adjusted(QUrl::StripTrailingSlash) || QDir::currentPath() != QDir::homePath() || !QDir(lastDirectory()->toLocalFile()).exists()) { *lastDirectory() = QUrl::fromLocalFile(QDir::currentPath()); } } ret = *lastDirectory(); } // qDebug() << "for" << startDir << "->" << ret << "recentDirClass" << recentDirClass << "fileName" << fileName; return ret; } void KFileWidget::setStartDir(const QUrl &directory) { if (directory.isValid()) { *lastDirectory() = directory; } } void KFileWidgetPrivate::setNonExtSelection() { // Enhanced rename: Don't highlight the file extension. QString filename = locationEditCurrentText(); QMimeDatabase db; QString extension = db.suffixForFileName(filename); if (!extension.isEmpty()) { locationEdit->lineEdit()->setSelection(0, filename.length() - extension.length() - 1); } else { int lastDot = filename.lastIndexOf(QLatin1Char('.')); if (lastDot > 0) { locationEdit->lineEdit()->setSelection(0, lastDot); } else { locationEdit->lineEdit()->selectAll(); } } } // Sets the filter text to "File type" if the dialog is saving and a mimetype // filter has been set; otherwise, the text is "Filter:" void KFileWidgetPrivate::updateFilterText() { QString label; QString whatsThisText; if (operationMode == KFileWidget::Saving && filterWidget->isMimeFilter()) { label = i18n("&File type:"); whatsThisText = i18n("This is the file type selector. It is used to select the format that the file will be saved as."); } else { label = i18n("&Filter:"); whatsThisText = i18n("This is the filter to apply to the file list. " "File names that do not match the filter will not be shown.

" "You may select from one of the preset filters in the " "drop down menu, or you may enter a custom filter " "directly into the text area.

" "Wildcards such as * and ? are allowed.

"); } if (filterLabel) { filterLabel->setText(label); filterLabel->setWhatsThis(whatsThisText); } if (filterWidget) { filterWidget->setWhatsThis(whatsThisText); } } KToolBar *KFileWidget::toolBar() const { return d->toolbar; } void KFileWidget::setCustomWidget(QWidget *widget) { delete d->bottomCustomWidget; d->bottomCustomWidget = widget; // add it to the dialog, below the filter list box. // Change the parent so that this widget is a child of the main widget d->bottomCustomWidget->setParent(this); d->vbox->addWidget(d->bottomCustomWidget); //d->vbox->addSpacing(3); // can't do this every time... // FIXME: This should adjust the tab orders so that the custom widget // comes after the Cancel button. The code appears to do this, but the result // somehow screws up the tab order of the file path combo box. Not a major // problem, but ideally the tab order with a custom widget should be // the same as the order without one. setTabOrder(d->cancelButton, d->bottomCustomWidget); setTabOrder(d->bottomCustomWidget, d->urlNavigator); } void KFileWidget::setCustomWidget(const QString &text, QWidget *widget) { delete d->labeledCustomWidget; d->labeledCustomWidget = widget; QLabel *label = new QLabel(text, this); label->setAlignment(Qt::AlignRight); d->lafBox->addWidget(label, 2, 0, Qt::AlignVCenter); d->lafBox->addWidget(widget, 2, 1, Qt::AlignVCenter); } KDirOperator *KFileWidget::dirOperator() { return d->ops; } void KFileWidget::readConfig(KConfigGroup &group) { d->configGroup = group; d->readViewConfig(); d->readRecentFiles(); } QString KFileWidgetPrivate::locationEditCurrentText() const { return QDir::fromNativeSeparators(locationEdit->currentText()); } QUrl KFileWidgetPrivate::mostLocalUrl(const QUrl &url) { if (url.isLocalFile()) { return url; } KIO::StatJob *statJob = KIO::stat(url, KIO::HideProgressInfo); KJobWidgets::setWindow(statJob, q); bool res = statJob->exec(); if (!res) { return url; } const QString path = statJob->statResult().stringValue(KIO::UDSEntry::UDS_LOCAL_PATH); if (!path.isEmpty()) { QUrl newUrl; newUrl.setPath(path); return newUrl; } return url; } void KFileWidgetPrivate::setInlinePreviewShown(bool show) { ops->setInlinePreviewShown(show); } void KFileWidget::setConfirmOverwrite(bool enable) { d->confirmOverwrite = enable; } void KFileWidget::setInlinePreviewShown(bool show) { d->setInlinePreviewShown(show); } QSize KFileWidget::dialogSizeHint() const { int fontSize = fontMetrics().height(); QSize goodSize(48 * fontSize, 30 * fontSize); QSize screenSize = QApplication::desktop()->availableGeometry(this).size(); QSize minSize(screenSize / 2); QSize maxSize(screenSize * qreal(0.9)); return (goodSize.expandedTo(minSize).boundedTo(maxSize)); } void KFileWidget::setViewMode(KFile::FileView mode) { d->ops->setView(mode); d->hasView = true; } void KFileWidget::setSupportedSchemes(const QStringList &schemes) { d->model->setSupportedSchemes(schemes); d->ops->setSupportedSchemes(schemes); d->urlNavigator->setCustomProtocols(schemes); } QStringList KFileWidget::supportedSchemes() const { return d->model->supportedSchemes(); } #include "moc_kfilewidget.cpp" diff --git a/src/filewidgets/kfilewidget.h b/src/filewidgets/kfilewidget.h index eb17417d..e82a1c9b 100644 --- a/src/filewidgets/kfilewidget.h +++ b/src/filewidgets/kfilewidget.h @@ -1,619 +1,620 @@ // -*- c++ -*- /* This file is part of the KDE libraries Copyright (C) 1997, 1998 Richard Moore 1998 Stephan Kulow 1998 Daniel Grana 2000,2001 Carsten Pfeiffer 2001 Frerich Raabe 2007 David Faure 2008 Rafael Fernández López This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef KFILEWIDGET_H #define KFILEWIDGET_H #include "kiofilewidgets_export.h" #include "kfile.h" #include class QUrl; class QPushButton; class KActionCollection; class KToolBar; class KFileWidgetPrivate; class KUrlComboBox; class KFileFilterCombo; class KPreviewWidgetBase; class QMimeType; class KConfigGroup; class KJob; class KFileItem; class KDirOperator; /** * @class KFileWidget kfilewidget.h * * File selector widget. * * This is the contents of the KDE file dialog, without the actual QDialog around it. * It can be embedded directly into applications. */ class KIOFILEWIDGETS_EXPORT KFileWidget : public QWidget { Q_OBJECT public: /** * Constructs a file selector widget. * * @param startDir This can either be: * @li An empty URL (QUrl()) to start in the current working directory, * or the last directory where a file has been selected. * @li The path or URL of a starting directory. * @li An initial file name to select, with the starting directory being * the current working directory or the last directory where a file * has been selected. * @li The path or URL of a file, specifying both the starting directory and * an initially selected file name. * @li A URL of the form @c kfiledialog:///<keyword> to start in the * directory last used by a filedialog in the same application that * specified the same keyword. * @li A URL of the form @c kfiledialog:///<keyword>/<filename> * to start in the directory last used by a filedialog in the same * application that specified the same keyword, and to initially * select the specified filename. * @li A URL of the form @c kfiledialog:///<keyword>?global to start * in the directory last used by a filedialog in any application that * specified the same keyword. * @li A URL of the form @c kfiledialog:///<keyword>/<filename>?global * to start in the directory last used by a filedialog in any * application that specified the same keyword, and to initially * select the specified filename. * * @param parent The parent widget of this widget * */ explicit KFileWidget(const QUrl &startDir, QWidget *parent = nullptr); /** * Destructor */ ~KFileWidget() override; /** * Defines some default behavior of the filedialog. * E.g. in mode @p Opening and @p Saving, the selected files/urls will * be added to the "recent documents" list. The Saving mode also implies * setKeepLocation() being set. * * @p Other means that no default actions are performed. * * @see setOperationMode * @see operationMode */ enum OperationMode { Other = 0, Opening, Saving }; /** * @returns The selected fully qualified filename. */ QUrl selectedUrl() const; /** * @returns The list of selected URLs. */ QList selectedUrls() const; /** * @returns the currently shown directory. */ QUrl baseUrl() const; /** * Returns the full path of the selected file in the local filesystem. * (Local files only) */ QString selectedFile() const; /** * Returns a list of all selected local files. */ QStringList selectedFiles() const; /** * Sets the directory to view. * * @param url URL to show. * @param clearforward Indicates whether the forward queue * should be cleared. */ void setUrl(const QUrl &url, bool clearforward = true); -#if !defined(KIOFILEWIDGETS_NO_DEPRECATED) && !defined(DOXYGEN_SHOULD_SKIP_THIS) +#if KIOFILEWIDGETS_ENABLE_DEPRECATED_SINCE(5, 33) /** * Sets the file to preselect to @p pathOrUrl * * This method handles absolute paths (on Unix, but probably not correctly on Windows) * and absolute URLs as strings (but for those you should use setSelectedUrl instead). * * This method does not work with relative paths (filenames) * (it would misinterpret a ':' or a '#' in the filename). * * @deprecated since 5.33, use setSelectedUrl instead, after ensuring that * construct the QUrl correctly (e.g. use fromLocalFile for local paths). */ - KIOFILEWIDGETS_DEPRECATED void setSelection(const QString &pathOrUrl); + KIOFILEWIDGETS_DEPRECATED_VERSION(5, 33, "Use KFileWidget::setSelectedUrl(const QUrl &)") + void setSelection(const QString &pathOrUrl); #endif /** * Sets the URL to preselect to @p url * * This method handles absolute URLs (remember to use fromLocalFile for local paths). * It also handles relative URLs, which you should construct like this: * QUrl relativeUrl; relativeUrl.setPath(fileName); * * @since 5.33 */ void setSelectedUrl(const QUrl &url); /** * Sets the operational mode of the filedialog to @p Saving, @p Opening * or @p Other. This will set some flags that are specific to loading * or saving files. E.g. setKeepLocation() makes mostly sense for * a save-as dialog. So setOperationMode( KFileWidget::Saving ); sets * setKeepLocation for example. * * The mode @p Saving, together with a default filter set via * setMimeFilter() will make the filter combobox read-only. * * The default mode is @p Opening. * * Call this method right after instantiating KFileWidget. * * @see operationMode * @see KFileWidget::OperationMode */ void setOperationMode(OperationMode); /** * @returns the current operation mode, Opening, Saving or Other. Default * is Other. * * @see operationMode * @see KFileWidget::OperationMode */ OperationMode operationMode() const; /** * Sets whether the filename/url should be kept when changing directories. * This is for example useful when having a predefined filename where * the full path for that file is searched. * * This is implicitly set when operationMode() is KFileWidget::Saving * * getSaveFileName() and getSaveUrl() set this to true by default, so that * you can type in the filename and change the directory without having * to type the name again. */ void setKeepLocation(bool keep); /** * @returns whether the contents of the location edit are kept when * changing directories. */ bool keepsLocation() const; /** * Sets the filter to be used to @p filter. * * You can set more * filters for the user to select separated by '\n'. Every * filter entry is defined through namefilter|text to display. * If no | is found in the expression, just the namefilter is * shown. Examples: * * \code * kfile->setFilter("*.cpp|C++ Source Files\n*.h|Header files"); * kfile->setFilter("*.cpp"); * kfile->setFilter("*.cpp|Sources (*.cpp)"); * kfile->setFilter("*.cpp|" + i18n("Sources (*.cpp)")); * kfile->setFilter("*.cpp *.cc *.C|C++ Source Files\n*.h *.H|Header files"); * \endcode * * Note: The text to display is not parsed in any way. So, if you * want to show the suffix to select by a specific filter, you must * repeat it. * * If the filter contains an unescaped '/', a mimetype-filter is assumed. * If you would like a '/' visible in your filter it can be escaped with * a '\'. You can specify multiple mimetypes like this (separated with * space): * * \code * kfile->setFilter( "image/png text/html text/plain" ); * kfile->setFilter( "*.cue|CUE\\/BIN Files (*.cue)" ); * \endcode * * @see filterChanged * @see setMimeFilter */ void setFilter(const QString &filter); /** * Returns the current filter as entered by the user or one of the * predefined set via setFilter(). * * @see setFilter() * @see filterChanged() */ QString currentFilter() const; /** * Returns the mimetype for the desired output format. * * This is only valid if setFilterMimeType() has been called * previously. * * @see setFilterMimeType() */ QMimeType currentFilterMimeType(); /** * Sets the filter up to specify the output type. * * @param types a list of mimetypes that can be used as output format * @param defaultType the default mimetype to use as output format, if any. * If @p defaultType is set, it will be set as the current item. * Otherwise, a first item showing all the mimetypes will be created. * Typically, @p defaultType should be empty for loading and set for saving. * * Do not use in conjunction with setFilter() */ void setMimeFilter(const QStringList &types, const QString &defaultType = QString()); /** * The mimetype for the desired output format. * * This is only valid if setMimeFilter() has been called * previously. * * @see setMimeFilter() */ QString currentMimeFilter() const; /** * Clears any mime- or namefilter. Does not reload the directory. */ void clearFilter(); /** * Adds a preview widget and enters the preview mode. * * In this mode the dialog is split and the right part contains your * preview widget. * * Ownership is transferred to KFileWidget. You need to create the * preview-widget with "new", i.e. on the heap. * * @param w The widget to be used for the preview. */ void setPreviewWidget(KPreviewWidgetBase *w); /** * Sets the mode of the dialog. * * The mode is defined as (in kfile.h): * \code * enum Mode { * File = 1, * Directory = 2, * Files = 4, * ExistingOnly = 8, * LocalOnly = 16 * }; * \endcode * You can OR the values, e.g. * \code * KFile::Modes mode = KFile::Files | * KFile::ExistingOnly | * KFile::LocalOnly ); * setMode( mode ); * \endcode */ void setMode(KFile::Modes m); /** * Returns the mode of the filedialog. * @see setMode() */ KFile::Modes mode() const; /** * Sets the text to be displayed in front of the selection. * * The default is "Location". * Most useful if you want to make clear what * the location is used for. */ void setLocationLabel(const QString &text); /** * Returns a pointer to the toolbar. * */ KToolBar *toolBar() const; /** * @returns a pointer to the OK-Button in the filedialog. * Note that the button is hidden and unconnected when using KFileWidget alone; * KFileDialog shows it and connects to it. */ QPushButton *okButton() const; /** * @returns a pointer to the Cancel-Button in the filedialog. * Note that the button is hidden and unconnected when using KFileWidget alone; * KFileDialog shows it and connects to it. */ QPushButton *cancelButton() const; /** * @returns the combobox used to type the filename or full location of the file. */ KUrlComboBox *locationEdit() const; /** * @returns the combobox that contains the filters */ KFileFilterCombo *filterWidget() const; /** * @returns a pointer to the action collection, holding all the used * KActions. */ KActionCollection *actionCollection() const; /** * This method implements the logic to determine the user's default directory * to be listed. E.g. the documents directory, home directory or a recently * used directory. * @param startDir A URL specifying the initial directory, or using the * @c kfiledialog:/// syntax to specify a last used * directory. If this URL specifies a file name, it is * ignored. Refer to the KFileWidget::KFileWidget() * documentation for the @c kfiledialog:/// URL syntax. * @param recentDirClass If the @c kfiledialog:/// syntax is used, this * will return the string to be passed to KRecentDirs::dir() and * KRecentDirs::add(). * @return The URL that should be listed by default (e.g. by KFileDialog or * KDirSelectDialog). * @see KFileWidget::KFileWidget() */ static QUrl getStartUrl(const QUrl &startDir, QString &recentDirClass); /** * Similar to getStartUrl(const QUrl& startDir,QString& recentDirClass), * but allows both the recent start directory keyword and a suggested file name * to be returned. * @param startDir A URL specifying the initial directory and/or filename, * or using the @c kfiledialog:/// syntax to specify a * last used location. * Refer to the KFileWidget::KFileWidget() * documentation for the @c kfiledialog:/// URL syntax. * @param recentDirClass If the @c kfiledialog:/// syntax is used, this * will return the string to be passed to KRecentDirs::dir() and * KRecentDirs::add(). * @param fileName The suggested file name, if specified as part of the * @p StartDir URL. * @return The URL that should be listed by default (e.g. by KFileDialog or * KDirSelectDialog). * * @see KFileWidget::KFileWidget() * @since 4.3 */ static QUrl getStartUrl(const QUrl &startDir, QString &recentDirClass, QString &fileName); /** * @internal * Used by KDirSelectDialog to share the dialog's start directory. */ static void setStartDir(const QUrl &directory); /** * Set a custom widget that should be added to the file dialog. * @param widget A widget, or a widget of widgets, for displaying custom * data in the file widget. This can be used, for example, to * display a check box with the caption "Open as read-only". * When creating this widget, you don't need to specify a parent, * since the widget's parent will be set automatically by KFileWidget. */ void setCustomWidget(QWidget *widget); /** * Sets a custom widget that should be added below the location and the filter * editors. * @param text Label of the custom widget, which is displayed below the labels * "Location:" and "Filter:". * @param widget Any kind of widget, but preferable a combo box or a line editor * to be compliant with the location and filter layout. * When creating this widget, you don't need to specify a parent, * since the widget's parent will be set automatically by KFileWidget. */ void setCustomWidget(const QString &text, QWidget *widget); /** * Sets whether the user should be asked for confirmation * when an overwrite might occur. * * @param enable Set this to true to enable checking. * @since 4.2 */ void setConfirmOverwrite(bool enable); /** * Forces the inline previews to be shown or hidden, depending on @p show. * * @param show Whether to show inline previews or not. * @since 4.2 */ void setInlinePreviewShown(bool show); /** * Provides a size hint, useful for dialogs that embed the widget. * * @return a QSize, calculated to be optimal for a dialog. * @since 5.0 */ QSize dialogSizeHint() const; /** * Sets how the view should be displayed. * * @see KFile::FileView * @since 5.0 */ void setViewMode(KFile::FileView mode); /** * Reimplemented */ QSize sizeHint() const override; /** * Set the URL schemes that the file widget should allow navigating to. * * If the returned list is empty, all schemes are supported. * * @sa QFileDialog::setSupportedSchemes * @since 5.43 */ void setSupportedSchemes(const QStringList &schemes); /** * Returns the URL schemes that the file widget should allow navigating to. * * If the returned list is empty, all schemes are supported. Examples for * schemes are @c "file" or @c "ftp". * * @sa QFileDialog::supportedSchemes * @since 5.43 */ QStringList supportedSchemes() const; public Q_SLOTS: /** * Called when clicking ok (when this widget is used in KFileDialog) * Might or might not call accept(). */ void slotOk(); void accept(); void slotCancel(); protected: void resizeEvent(QResizeEvent *event) override; void showEvent(QShowEvent *event) override; bool eventFilter(QObject *watched, QEvent *event) override; Q_SIGNALS: /** * Emitted when the user selects a file. It is only emitted in single- * selection mode. The best way to get notified about selected file(s) * is to connect to the okClicked() signal inherited from KDialog * and call selectedFile(), selectedFiles(), * selectedUrl() or selectedUrls(). * * \since 4.4 */ void fileSelected(const QUrl &); /** * Emitted when the user highlights a file. * \since 4.4 */ void fileHighlighted(const QUrl &); /** * Emitted when the user highlights one or more files in multiselection mode. * * Note: fileHighlighted() or fileSelected() are @em not * emitted in multiselection mode. You may use selectedItems() to * ask for the current highlighted items. * @see fileSelected */ void selectionChanged(); /** * Emitted when the filter changed, i.e. the user entered an own filter * or chose one of the predefined set via setFilter(). * * @param filter contains the new filter (only the extension part, * not the explanation), i.e. "*.cpp" or "*.cpp *.cc". * * @see setFilter() * @see currentFilter() */ void filterChanged(const QString &filter); /** * Emitted by slotOk() (directly or asynchronously) once everything has * been done. Should be used by the caller to call accept(). */ void accepted(); public: /** * @returns the KDirOperator used to navigate the filesystem * @since 4.3 */ KDirOperator *dirOperator(); /** * reads the configuration for this widget from the given config group * @param group the KConfigGroup to read from * @since 4.4 */ void readConfig(KConfigGroup &group); private: friend class KFileWidgetPrivate; KFileWidgetPrivate *const d; Q_PRIVATE_SLOT(d, void _k_slotLocationChanged(const QString &)) Q_PRIVATE_SLOT(d, void _k_urlEntered(const QUrl &)) Q_PRIVATE_SLOT(d, void _k_enterUrl(const QUrl &)) Q_PRIVATE_SLOT(d, void _k_enterUrl(const QString &)) Q_PRIVATE_SLOT(d, void _k_locationAccepted(const QString &)) Q_PRIVATE_SLOT(d, void _k_slotFilterChanged()) Q_PRIVATE_SLOT(d, void _k_fileHighlighted(const KFileItem &)) Q_PRIVATE_SLOT(d, void _k_fileSelected(const KFileItem &)) Q_PRIVATE_SLOT(d, void _k_slotLoadingFinished()) Q_PRIVATE_SLOT(d, void _k_fileCompletion(const QString &)) Q_PRIVATE_SLOT(d, void _k_toggleSpeedbar(bool)) Q_PRIVATE_SLOT(d, void _k_toggleBookmarks(bool)) Q_PRIVATE_SLOT(d, void _k_slotAutoSelectExtClicked()) Q_PRIVATE_SLOT(d, void _k_placesViewSplitterMoved(int, int)) Q_PRIVATE_SLOT(d, void _k_activateUrlNavigator()) Q_PRIVATE_SLOT(d, void _k_zoomOutIconsSize()) Q_PRIVATE_SLOT(d, void _k_zoomInIconsSize()) Q_PRIVATE_SLOT(d, void _k_slotIconSizeSliderMoved(int)) Q_PRIVATE_SLOT(d, void _k_slotIconSizeChanged(int)) Q_PRIVATE_SLOT(d, void _k_slotViewDoubleClicked(const QModelIndex&)) Q_PRIVATE_SLOT(d, void _k_slotViewKeyEnterReturnPressed()) }; #endif diff --git a/src/filewidgets/knewfilemenu.h b/src/filewidgets/knewfilemenu.h index 3462a278..5fc9ed81 100644 --- a/src/filewidgets/knewfilemenu.h +++ b/src/filewidgets/knewfilemenu.h @@ -1,203 +1,204 @@ /* This file is part of the KDE project Copyright (C) 1998-2009 David Faure 2003 Sven Leiber This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 or at your option version 3. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef KNEWFILEMENU_H #define KNEWFILEMENU_H #include #include #include "kiofilewidgets_export.h" class KJob; class KActionCollection; class KNewFileMenuPrivate; /** * @class KNewFileMenu knewfilemenu.h * * The 'Create New' submenu, for creating files using templates * (e.g. "new HTML file") and directories. * * The same instance can be used by both for the File menu and the RMB popup menu, * in a file manager. This is also used in the file dialog's RMB menu. * * To use this class, you need to connect aboutToShow() of the File menu * with slotCheckUpToDate() and to call slotCheckUpToDate() before showing * the RMB popupmenu. * * KNewFileMenu automatically updates the list of templates shown if installed templates * are added/updated/deleted. * * @author Björn Ruberg * Made dialogs working asynchronously * @author David Faure * Ideas and code for the new template handling mechanism ('link' desktop files) * from Christoph Pickart * @since 4.5 */ class KIOFILEWIDGETS_EXPORT KNewFileMenu : public KActionMenu { Q_OBJECT public: /** * Constructor. * @param collection the KActionCollection the QAction with name @p name should be added to. * @param name action name, when adding the action to @p collection * @param parent the parent object, for ownership. * If the parent object is a widget, it will also used as parent widget * for any dialogs that this class might show. Otherwise, call setParentWidget. * @note If you want the "Create directory..." action shortcut to show up next to its text, * make sure to have an action with name "create_dir" (and shortcut set) in @p collection. * This will only work with KIO >= 5.27. * From KIO >= 5.53, an action named "create_file" (and shortcut set) in @p collection * will be linked to the creation of the first file template (either from XDG_TEMPLATES_DIR * or from :/kio5/newfile-templates) */ KNewFileMenu(KActionCollection *collection, const QString &name, QObject *parent); /** * Destructor. * KNewMenu uses internally a globally shared cache, so that multiple instances * of it don't need to parse the installed templates multiple times. Therefore * you can safely create and delete KNewMenu instances without a performance issue. */ virtual ~KNewFileMenu(); /** * Returns the modality of dialogs */ bool isModal() const; /** * Returns the files that the popup is shown for */ QList popupFiles() const; /** * Sets the modality of dialogs created by KNewFile. Set to false if you do not want to block * your application window when entering a new directory name i.e. */ void setModal(bool modality); /** * Sets a parent widget for the dialogs shown by KNewFileMenu. * This is strongly recommended, for apps with a main window. */ void setParentWidget(QWidget *parentWidget); /** * Set the files the popup is shown for * Call this before showing up the menu */ void setPopupFiles(const QList &files); -#ifndef KIOFILEWIDGETS_NO_DEPRECATED +#if KIOFILEWIDGETS_ENABLE_DEPRECATED_SINCE(5, 0) + KIOFILEWIDGETS_DEPRECATED_VERSION(5, 0, "Use KNewFileMenu::setPopupFiles(const QList &)") void setPopupFiles(const QUrl &file) { setPopupFiles(QList() << file); } #endif /** * Only show the files in a given set of mimetypes. * This is useful in specialized applications (while file managers, on * the other hand, want to show all mimetypes). */ void setSupportedMimeTypes(const QStringList &mime); /** * Set if the directory view currently shows dot files. */ void setViewShowsHiddenFiles(bool b); /** * Returns the mimetypes set in supportedMimeTypes() */ QStringList supportedMimeTypes() const; public Q_SLOTS: /** * Checks if updating the list is necessary * IMPORTANT : Call this in the slot for aboutToShow. * And while you're there, you probably want to call setViewShowsHiddenFiles ;) */ void checkUpToDate(); /** * Call this to create a new directory as if the user had done it using * a popupmenu. This is useful to make sure that creating a directory with * a key shortcut (e.g. F10) triggers the exact same code as when using * the New menu. * Requirements: call setPopupFiles first, and keep this KNewFileMenu instance * alive (the mkdir is async). */ void createDirectory(); /** * Call this to create a new file as if the user had done it using * a popupmenu. This is useful to make sure that creating a directory with * a key shortcut (e.g. Shift-F10) triggers the exact same code as when using * the New menu. * Requirements: call setPopupFiles first, and keep this KNewFileMenu instance * alive (the copy is async). * @since 5.53 */ void createFile(); Q_SIGNALS: /** * Emitted once the file (or symlink) @p url has been successfully created */ void fileCreated(const QUrl &url); /** * Emitted once the directory @p url has been successfully created */ void directoryCreated(const QUrl &url); protected Q_SLOTS: /** * Called when the job that copied the template has finished. * This method is virtual so that error handling can be reimplemented. * Make sure to call the base class slotResult when !job->error() though. */ virtual void slotResult(KJob *job); private: Q_PRIVATE_SLOT(d, void _k_slotAbortDialog()) Q_PRIVATE_SLOT(d, void _k_slotActionTriggered(QAction *)) Q_PRIVATE_SLOT(d, void _k_slotCreateDirectory(bool writeHiddenDir = false)) Q_PRIVATE_SLOT(d, void _k_slotCreateHiddenDirectory()) Q_PRIVATE_SLOT(d, void _k_slotFillTemplates()) Q_PRIVATE_SLOT(d, void _k_slotOtherDesktopFile()) Q_PRIVATE_SLOT(d, void _k_slotOtherDesktopFileClosed()) Q_PRIVATE_SLOT(d, void _k_slotRealFileOrDir()) Q_PRIVATE_SLOT(d, void _k_slotTextChanged(const QString)) Q_PRIVATE_SLOT(d, void _k_slotSymLink()) Q_PRIVATE_SLOT(d, void _k_slotUrlDesktopFile()) friend class KNewFileMenuPrivate; KNewFileMenuPrivate *const d; }; #endif diff --git a/src/filewidgets/kurlnavigator.cpp b/src/filewidgets/kurlnavigator.cpp index 72df48b9..00a2f0db 100644 --- a/src/filewidgets/kurlnavigator.cpp +++ b/src/filewidgets/kurlnavigator.cpp @@ -1,1333 +1,1313 @@ /***************************************************************************** * Copyright (C) 2006-2010 by Peter Penz * * Copyright (C) 2006 by Aaron J. Seigo * * Copyright (C) 2007 by Kevin Ottens * * Copyright (C) 2007 by Urs Wolfer * * * * This library is free software; you can redistribute it and/or * * modify it under the terms of the GNU Library General Public * * License as published by the Free Software Foundation; either * * version 2 of the License, or (at your option) any later version. * * * * This library is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * * Library General Public License for more details. * * * * You should have received a copy of the GNU Library General Public License * * along with this library; see the file COPYING.LIB. If not, write to * * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * * Boston, MA 02110-1301, USA. * *****************************************************************************/ #include "kurlnavigator.h" #include "kurlnavigatorplacesselector_p.h" #include "kurlnavigatorprotocolcombo_p.h" #include "kurlnavigatordropdownbutton_p.h" #include "kurlnavigatorbutton_p.h" #include "kurlnavigatortogglebutton_p.h" #include "kurlnavigatorpathselectoreventfilter_p.h" #include "urlutil_p.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include using namespace KDEPrivate; struct LocationData { QUrl url; -#ifndef KIOFILEWIDGETS_NO_DEPRECATED QUrl rootUrl; // KDE5: remove after the deprecated methods have been removed QPoint pos; // KDE5: remove after the deprecated methods have been removed -#endif QByteArray state; }; class Q_DECL_HIDDEN KUrlNavigator::Private { public: Private(KUrlNavigator *q, KFilePlacesModel *placesModel); void initialize(const QUrl &url); /** Applies the edited URL in m_pathBox to the URL navigator */ void applyUncommittedUrl(); void slotReturnPressed(); void slotProtocolChanged(const QString &); void openPathSelectorMenu(); /** * Appends the widget at the end of the URL navigator. It is assured * that the filler widget remains as last widget to fill the remaining * width. */ void appendWidget(QWidget *widget, int stretch = 0); /** * This slot is connected to the clicked signal of the navigation bar button. It calls switchView(). * Moreover, if switching from "editable" mode to the breadcrumb view, it calls applyUncommittedUrl(). */ void slotToggleEditableButtonPressed(); /** * Switches the navigation bar between the breadcrumb view and the * traditional view (see setUrlEditable()). */ void switchView(); /** Emits the signal urlsDropped(). */ void dropUrls(const QUrl &destination, QDropEvent *event); /** * Is invoked when a navigator button has been clicked. Changes the URL * of the navigator if the left mouse button has been used. If the middle * mouse button has been used, the signal tabRequested() will be emitted. */ void slotNavigatorButtonClicked(const QUrl &url, Qt::MouseButton button, Qt::KeyboardModifiers modifiers); void openContextMenu(const QPoint &p); void slotPathBoxChanged(const QString &text); void updateContent(); /** * Updates all buttons to have one button for each part of the * current URL. Existing buttons, which are available by m_navButtons, * are reused if possible. If the URL is longer, new buttons will be * created, if the URL is shorter, the remaining buttons will be deleted. * @param startIndex Start index of URL part (/), where the buttons * should be created for each following part. */ void updateButtons(int startIndex); /** * Updates the visibility state of all buttons describing the URL. If the * width of the URL navigator is too small, the buttons representing the upper * paths of the URL will be hidden and moved to a drop down menu. */ void updateButtonVisibility(); /** * @return Text for the first button of the URL navigator. */ QString firstButtonText() const; /** * Returns the URL that should be applied for the button with the index \a index. */ QUrl buttonUrl(int index) const; void switchToBreadcrumbMode(); /** * Deletes all URL navigator buttons. m_navButtons is * empty after this operation. */ void deleteButtons(); /** * Retrieves the place url for the current url. * E. g. for the path "fish://root@192.168.0.2/var/lib" the string * "fish://root@192.168.0.2" will be returned, which leads to the * navigation indication 'Custom Path > var > lib". For e. g. * "settings:///System/" the path "settings://" will be returned. */ QUrl retrievePlaceUrl() const; /** * Returns true, if the MIME type of the path represents a * compressed file like TAR or ZIP. */ bool isCompressedPath(const QUrl &path) const; void removeTrailingSlash(QString &url) const; /** * Returns the current history index, if \a historyIndex is * smaller than 0. If \a historyIndex is greater or equal than * the number of available history items, the largest possible * history index is returned. For the other cases just \a historyIndex * is returned. */ int adjustedHistoryIndex(int historyIndex) const; bool m_editable : 1; bool m_active : 1; bool m_showPlacesSelector : 1; bool m_showFullPath : 1; int m_historyIndex; QHBoxLayout *m_layout; QList m_history; KUrlNavigatorPlacesSelector *m_placesSelector; KUrlComboBox *m_pathBox; KUrlNavigatorProtocolCombo *m_protocols; KUrlNavigatorDropDownButton *m_dropDownButton; QList m_navButtons; KUrlNavigatorButtonBase *m_toggleEditableMode; QUrl m_homeUrl; QStringList m_customProtocols; QWidget *m_dropWidget; KUrlNavigator * const q; }; KUrlNavigator::Private::Private(KUrlNavigator *q, KFilePlacesModel *placesModel) : m_editable(false), m_active(true), m_showPlacesSelector(placesModel != nullptr), m_showFullPath(false), m_historyIndex(0), m_layout(new QHBoxLayout), m_placesSelector(nullptr), m_pathBox(nullptr), m_protocols(nullptr), m_dropDownButton(nullptr), m_navButtons(), m_toggleEditableMode(nullptr), m_homeUrl(), m_customProtocols(QStringList()), m_dropWidget(nullptr), q(q) { m_layout->setSpacing(0); m_layout->setContentsMargins(0, 0, 0, 0); // initialize the places selector q->setAutoFillBackground(false); if (placesModel != nullptr) { m_placesSelector = new KUrlNavigatorPlacesSelector(q, placesModel); connect(m_placesSelector, &KUrlNavigatorPlacesSelector::placeActivated, q, &KUrlNavigator::setLocationUrl); connect(m_placesSelector, &KUrlNavigatorPlacesSelector::tabRequested, q, &KUrlNavigator::tabRequested); connect(placesModel, SIGNAL(rowsInserted(QModelIndex,int,int)), q, SLOT(updateContent())); connect(placesModel, SIGNAL(rowsRemoved(QModelIndex,int,int)), q, SLOT(updateContent())); connect(placesModel, SIGNAL(dataChanged(QModelIndex,QModelIndex)), q, SLOT(updateContent())); } // create protocol combo m_protocols = new KUrlNavigatorProtocolCombo(QString(), q); connect(m_protocols, SIGNAL(activated(QString)), q, SLOT(slotProtocolChanged(QString))); // create drop down button for accessing all paths of the URL m_dropDownButton = new KUrlNavigatorDropDownButton(q); m_dropDownButton->setForegroundRole(QPalette::WindowText); m_dropDownButton->installEventFilter(q); connect(m_dropDownButton, SIGNAL(clicked()), q, SLOT(openPathSelectorMenu())); // initialize the path box of the traditional view m_pathBox = new KUrlComboBox(KUrlComboBox::Directories, true, q); m_pathBox->setSizeAdjustPolicy(QComboBox::AdjustToMinimumContentsLength); m_pathBox->installEventFilter(q); KUrlCompletion *kurlCompletion = new KUrlCompletion(KUrlCompletion::DirCompletion); m_pathBox->setCompletionObject(kurlCompletion); m_pathBox->setAutoDeleteCompletionObject(true); connect(m_pathBox, SIGNAL(returnPressed()), q, SLOT(slotReturnPressed())); connect(m_pathBox, &KUrlComboBox::urlActivated, q, &KUrlNavigator::setLocationUrl); connect(m_pathBox, SIGNAL(editTextChanged(QString)), q, SLOT(slotPathBoxChanged(QString))); // create toggle button which allows to switch between // the breadcrumb and traditional view m_toggleEditableMode = new KUrlNavigatorToggleButton(q); m_toggleEditableMode->installEventFilter(q); m_toggleEditableMode->setMinimumWidth(20); connect(m_toggleEditableMode, SIGNAL(clicked()), q, SLOT(slotToggleEditableButtonPressed())); if (m_placesSelector != nullptr) { m_layout->addWidget(m_placesSelector); } m_layout->addWidget(m_protocols); m_layout->addWidget(m_dropDownButton); m_layout->addWidget(m_pathBox, 1); m_layout->addWidget(m_toggleEditableMode); q->setContextMenuPolicy(Qt::CustomContextMenu); connect(q, SIGNAL(customContextMenuRequested(QPoint)), q, SLOT(openContextMenu(QPoint))); } void KUrlNavigator::Private::initialize(const QUrl &url) { LocationData data; data.url = url.adjusted(QUrl::NormalizePathSegments); m_history.prepend(data); q->setLayoutDirection(Qt::LeftToRight); const int minHeight = m_pathBox->sizeHint().height(); q->setMinimumHeight(minHeight); q->setLayout(m_layout); q->setMinimumWidth(100); updateContent(); } void KUrlNavigator::Private::appendWidget(QWidget *widget, int stretch) { m_layout->insertWidget(m_layout->count() - 1, widget, stretch); } void KUrlNavigator::Private::applyUncommittedUrl() { // Parts of the following code have been taken // from the class KateFileSelector located in // kate/app/katefileselector.hpp of Kate. // Copyright (C) 2001 Christoph Cullmann // Copyright (C) 2001 Joseph Wenninger // Copyright (C) 2001 Anders Lund const QUrl typedUrl = q->uncommittedUrl(); QStringList urls = m_pathBox->urls(); urls.removeAll(typedUrl.toString()); urls.prepend(typedUrl.toString()); m_pathBox->setUrls(urls, KUrlComboBox::RemoveBottom); q->setLocationUrl(typedUrl); // The URL might have been adjusted by KUrlNavigator::setUrl(), hence // synchronize the result in the path box. const QUrl currentUrl = q->locationUrl(); m_pathBox->setUrl(currentUrl); } void KUrlNavigator::Private::slotReturnPressed() { applyUncommittedUrl(); emit q->returnPressed(); if (QApplication::keyboardModifiers() & Qt::ControlModifier) { // Pressing Ctrl+Return automatically switches back to the breadcrumb mode. // The switch must be done asynchronously, as we are in the context of the // editor. QMetaObject::invokeMethod(q, "switchToBreadcrumbMode", Qt::QueuedConnection); } } void KUrlNavigator::Private::slotProtocolChanged(const QString &protocol) { Q_ASSERT(m_editable); QUrl url; url.setScheme(protocol); if (protocol == QLatin1String("file")) { url.setPath(QStringLiteral("/")); } else { // With no authority set we'll get e.g. "ftp:" instead of "ftp://". // We want the latter, so let's set an empty authority. url.setAuthority(QString()); } m_pathBox->setEditUrl(url); } void KUrlNavigator::Private::openPathSelectorMenu() { if (m_navButtons.count() <= 0) { return; } const QUrl firstVisibleUrl = m_navButtons.first()->url(); QString spacer; QPointer popup = new QMenu(q); auto *popupFilter = new KUrlNavigatorPathSelectorEventFilter(popup.data()); connect(popupFilter, &KUrlNavigatorPathSelectorEventFilter::tabRequested, q, &KUrlNavigator::tabRequested); popup->installEventFilter(popupFilter); popup->setLayoutDirection(Qt::LeftToRight); const QUrl placeUrl = retrievePlaceUrl(); int idx = placeUrl.path().count(QLatin1Char('/')); // idx points to the first directory // after the place path const QString path = m_history[m_historyIndex].url.path(); QString dirName = path.section(QLatin1Char('/'), idx, idx); if (dirName.isEmpty()) { if (placeUrl.isLocalFile()) { dirName = QStringLiteral("/"); } else { dirName = placeUrl.toDisplayString(); } } do { const QString text = spacer + dirName; QAction *action = new QAction(text, popup); const QUrl currentUrl = buttonUrl(idx); if (currentUrl == firstVisibleUrl) { popup->addSeparator(); } action->setData(QVariant(currentUrl.toString())); popup->addAction(action); ++idx; spacer.append(QLatin1String(" ")); dirName = path.section(QLatin1Char('/'), idx, idx); } while (!dirName.isEmpty()); const QPoint pos = q->mapToGlobal(m_dropDownButton->geometry().bottomRight()); const QAction *activatedAction = popup->exec(pos); if (activatedAction != nullptr) { const QUrl url(activatedAction->data().toString()); q->setLocationUrl(url); } // Delete the menu, unless it has been deleted in its own nested event loop already. if (popup) { popup->deleteLater(); } } void KUrlNavigator::Private::slotToggleEditableButtonPressed() { if (m_editable) { applyUncommittedUrl(); } switchView(); } void KUrlNavigator::Private::switchView() { m_toggleEditableMode->setFocus(); m_editable = !m_editable; m_toggleEditableMode->setChecked(m_editable); updateContent(); if (q->isUrlEditable()) { m_pathBox->setFocus(); } q->requestActivation(); emit q->editableStateChanged(m_editable); } void KUrlNavigator::Private::dropUrls(const QUrl &destination, QDropEvent *event) { if (event->mimeData()->hasUrls()) { m_dropWidget = qobject_cast(q->sender()); emit q->urlsDropped(destination, event); } } void KUrlNavigator::Private::slotNavigatorButtonClicked(const QUrl &url, Qt::MouseButton button, Qt::KeyboardModifiers modifiers) { if (button & Qt::MidButton || (button & Qt::LeftButton && modifiers & Qt::ControlModifier)) { emit q->tabRequested(url); } else if (button & Qt::LeftButton) { q->setLocationUrl(url); } } void KUrlNavigator::Private::openContextMenu(const QPoint &p) { q->setActive(true); QPointer popup = new QMenu(q); // provide 'Copy' action, which copies the current URL of // the URL navigator into the clipboard QAction *copyAction = popup->addAction(QIcon::fromTheme(QStringLiteral("edit-copy")), i18n("Copy")); // provide 'Paste' action, which copies the current clipboard text // into the URL navigator QAction *pasteAction = popup->addAction(QIcon::fromTheme(QStringLiteral("edit-paste")), i18n("Paste")); QClipboard *clipboard = QApplication::clipboard(); pasteAction->setEnabled(!clipboard->text().isEmpty()); popup->addSeparator(); //We are checking for receivers because it's odd to have a tab entry even if it's not supported, like in the case of the open dialog if (q->receivers(SIGNAL(tabRequested(QUrl))) > 0) { for (auto button : qAsConst(m_navButtons)) { if (button->geometry().contains(p)) { const auto url = button->url(); QAction* openInTab = popup->addAction(QIcon::fromTheme(QStringLiteral("tab-new")), i18n("Open %1 in tab", button->text())); q->connect(openInTab, &QAction::triggered, q, [this, url](){ Q_EMIT q->tabRequested(url); }); break; } } } // provide radiobuttons for toggling between the edit and the navigation mode QAction *editAction = popup->addAction(i18n("Edit")); editAction->setCheckable(true); QAction *navigateAction = popup->addAction(i18n("Navigate")); navigateAction->setCheckable(true); QActionGroup *modeGroup = new QActionGroup(popup); modeGroup->addAction(editAction); modeGroup->addAction(navigateAction); if (q->isUrlEditable()) { editAction->setChecked(true); } else { navigateAction->setChecked(true); } popup->addSeparator(); // allow showing of the full path QAction *showFullPathAction = popup->addAction(i18n("Show Full Path")); showFullPathAction->setCheckable(true); showFullPathAction->setChecked(q->showFullPath()); QAction *activatedAction = popup->exec(QCursor::pos()); if (activatedAction == copyAction) { QMimeData *mimeData = new QMimeData(); mimeData->setText(q->locationUrl().toDisplayString(QUrl::PreferLocalFile)); clipboard->setMimeData(mimeData); } else if (activatedAction == pasteAction) { q->setLocationUrl(QUrl::fromUserInput(clipboard->text())); } else if (activatedAction == editAction) { q->setUrlEditable(true); } else if (activatedAction == navigateAction) { q->setUrlEditable(false); } else if (activatedAction == showFullPathAction) { q->setShowFullPath(showFullPathAction->isChecked()); } // Delete the menu, unless it has been deleted in its own nested event loop already. if (popup) { popup->deleteLater(); } } void KUrlNavigator::Private::slotPathBoxChanged(const QString &text) { if (text.isEmpty()) { const QString protocol = q->locationUrl().scheme(); m_protocols->setProtocol(protocol); if (m_customProtocols.count() != 1) { m_protocols->show(); } } else { m_protocols->hide(); } } void KUrlNavigator::Private::updateContent() { const QUrl currentUrl = q->locationUrl(); if (m_placesSelector != nullptr) { m_placesSelector->updateSelection(currentUrl); } if (m_editable) { m_protocols->hide(); m_dropDownButton->hide(); deleteButtons(); m_toggleEditableMode->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Preferred); q->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Fixed); m_pathBox->show(); m_pathBox->setUrl(currentUrl); } else { m_pathBox->hide(); m_protocols->hide(); m_toggleEditableMode->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred); q->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed); // Calculate the start index for the directories that should be shown as buttons // and create the buttons QUrl placeUrl; if ((m_placesSelector != nullptr) && !m_showFullPath) { placeUrl = m_placesSelector->selectedPlaceUrl(); } if (!placeUrl.isValid()) { placeUrl = retrievePlaceUrl(); } QString placePath = placeUrl.path(); removeTrailingSlash(placePath); const int startIndex = placePath.count(QLatin1Char('/')); updateButtons(startIndex); } } void KUrlNavigator::Private::updateButtons(int startIndex) { QUrl currentUrl = q->locationUrl(); if (!currentUrl.isValid()) { // QFileDialog::setDirectory not called yet return; } const QString path = currentUrl.path(); bool createButton = false; const int oldButtonCount = m_navButtons.count(); int idx = startIndex; bool hasNext = true; do { createButton = (idx - startIndex >= oldButtonCount); const bool isFirstButton = (idx == startIndex); const QString dirName = path.section(QLatin1Char('/'), idx, idx); hasNext = isFirstButton || !dirName.isEmpty(); if (hasNext) { KUrlNavigatorButton *button = nullptr; if (createButton) { button = new KUrlNavigatorButton(buttonUrl(idx), q); button->installEventFilter(q); button->setForegroundRole(QPalette::WindowText); connect(button, SIGNAL(urlsDropped(QUrl,QDropEvent*)), q, SLOT(dropUrls(QUrl,QDropEvent*))); connect(button, SIGNAL(clicked(QUrl,Qt::MouseButton,Qt::KeyboardModifiers)), q, SLOT(slotNavigatorButtonClicked(QUrl,Qt::MouseButton,Qt::KeyboardModifiers))); connect(button, SIGNAL(finishedTextResolving()), q, SLOT(updateButtonVisibility())); appendWidget(button); } else { button = m_navButtons[idx - startIndex]; button->setUrl(buttonUrl(idx)); } if (isFirstButton) { button->setText(firstButtonText()); } button->setActive(q->isActive()); if (createButton) { if (!isFirstButton) { setTabOrder(m_navButtons.last(), button); } m_navButtons.append(button); } ++idx; button->setActiveSubDirectory(path.section(QLatin1Char('/'), idx, idx)); } } while (hasNext); // delete buttons which are not used anymore const int newButtonCount = idx - startIndex; if (newButtonCount < oldButtonCount) { const QList::iterator itBegin = m_navButtons.begin() + newButtonCount; const QList::iterator itEnd = m_navButtons.end(); QList::iterator it = itBegin; while (it != itEnd) { (*it)->hide(); (*it)->deleteLater(); ++it; } m_navButtons.erase(itBegin, itEnd); } setTabOrder(m_dropDownButton, m_navButtons.first()); setTabOrder(m_navButtons.last(), m_toggleEditableMode); updateButtonVisibility(); } void KUrlNavigator::Private::updateButtonVisibility() { if (m_editable) { return; } const int buttonsCount = m_navButtons.count(); if (buttonsCount == 0) { m_dropDownButton->hide(); return; } // Subtract all widgets from the available width, that must be shown anyway int availableWidth = q->width() - m_toggleEditableMode->minimumWidth(); if ((m_placesSelector != nullptr) && m_placesSelector->isVisible()) { availableWidth -= m_placesSelector->width(); } if ((m_protocols != nullptr) && m_protocols->isVisible()) { availableWidth -= m_protocols->width(); } // Check whether buttons must be hidden at all... int requiredButtonWidth = 0; for (const KUrlNavigatorButton *button : qAsConst(m_navButtons)) { requiredButtonWidth += button->minimumWidth(); } if (requiredButtonWidth > availableWidth) { // At least one button must be hidden. This implies that the // drop-down button must get visible, which again decreases the // available width. availableWidth -= m_dropDownButton->width(); } // Hide buttons... QList::const_iterator it = m_navButtons.constEnd(); const QList::const_iterator itBegin = m_navButtons.constBegin(); bool isLastButton = true; bool hasHiddenButtons = false; QLinkedList buttonsToShow; while (it != itBegin) { --it; KUrlNavigatorButton *button = (*it); availableWidth -= button->minimumWidth(); if ((availableWidth <= 0) && !isLastButton) { button->hide(); hasHiddenButtons = true; } else { // Don't show the button immediately, as setActive() // might change the size and a relayout gets triggered // after showing the button. So the showing of all buttons // is postponed until all buttons have the correct // activation state. buttonsToShow.append(button); } isLastButton = false; } // All buttons have the correct activation state and // can be shown now for (KUrlNavigatorButton *button : qAsConst(buttonsToShow)) { button->show(); } if (hasHiddenButtons) { m_dropDownButton->show(); } else { // Check whether going upwards is possible. If this is the case, show the drop-down button. QUrl url(m_navButtons.front()->url()); const bool visible = !url.matches(KIO::upUrl(url), QUrl::StripTrailingSlash) && (url.scheme() != QLatin1String("nepomuksearch")); m_dropDownButton->setVisible(visible); } } QString KUrlNavigator::Private::firstButtonText() const { QString text; // The first URL navigator button should get the name of the // place instead of the directory name if ((m_placesSelector != nullptr) && !m_showFullPath) { text = m_placesSelector->selectedPlaceText(); } if (text.isEmpty()) { const QUrl currentUrl = q->locationUrl(); if (currentUrl.isLocalFile()) { #ifdef Q_OS_WIN text = currentUrl.path().length() > 1 ? currentUrl.path().left(2) : QDir::rootPath(); #else text = m_showFullPath ? QStringLiteral("/") : i18n("Custom Path"); #endif } else { text = currentUrl.scheme() + QLatin1Char(':'); if (!currentUrl.host().isEmpty()) { text += QLatin1Char(' ') + currentUrl.host(); } } } return text; } QUrl KUrlNavigator::Private::buttonUrl(int index) const { if (index < 0) { index = 0; } // Keep scheme, hostname etc. as this is needed for e. g. browsing // FTP directories QUrl url = q->locationUrl(); QString path = url.path(); if (!path.isEmpty()) { if (index == 0) { // prevent the last "/" from being stripped // or we end up with an empty path #ifdef Q_OS_WIN path = path.length() > 1 ? path.left(2) : QDir::rootPath(); #else path = QStringLiteral("/"); #endif } else { path = path.section(QLatin1Char('/'), 0, index); } } url.setPath(path); return url; } void KUrlNavigator::Private::switchToBreadcrumbMode() { q->setUrlEditable(false); } void KUrlNavigator::Private::deleteButtons() { for (KUrlNavigatorButton *button : qAsConst(m_navButtons)) { button->hide(); button->deleteLater(); } m_navButtons.clear(); } QUrl KUrlNavigator::Private::retrievePlaceUrl() const { QUrl currentUrl = q->locationUrl(); currentUrl.setPath(QString()); return currentUrl; } bool KUrlNavigator::Private::isCompressedPath(const QUrl &url) const { QMimeDatabase db; const QMimeType mime = db.mimeTypeForUrl(QUrl(url.toString(QUrl::StripTrailingSlash))); // Note: this list of MIME types depends on the protocols implemented by kio_archive and krarc return mime.inherits(QStringLiteral("application/x-compressed-tar")) || mime.inherits(QStringLiteral("application/x-bzip-compressed-tar")) || mime.inherits(QStringLiteral("application/x-lzma-compressed-tar")) || mime.inherits(QStringLiteral("application/x-xz-compressed-tar")) || mime.inherits(QStringLiteral("application/x-tar")) || mime.inherits(QStringLiteral("application/x-tarz")) || mime.inherits(QStringLiteral("application/x-tzo")) || // (not sure KTar supports those?) mime.inherits(QStringLiteral("application/zip")) || mime.inherits(QStringLiteral("application/x-archive")) || mime.inherits(QStringLiteral("application/vnd.rar")) || // the following depends on krarc mime.inherits(QStringLiteral("application/x-7z-compressed")) || mime.inherits(QStringLiteral("application/x-ace")) || mime.inherits(QStringLiteral("application/x-arj")) || mime.inherits(QStringLiteral("application/x-cpio")) || mime.inherits(QStringLiteral("application/x-lha")); } void KUrlNavigator::Private::removeTrailingSlash(QString &url) const { const int length = url.length(); if ((length > 0) && (url.at(length - 1) == QLatin1Char('/'))) { url.remove(length - 1, 1); } } int KUrlNavigator::Private::adjustedHistoryIndex(int historyIndex) const { if (historyIndex < 0) { historyIndex = m_historyIndex; } else if (historyIndex >= m_history.size()) { historyIndex = m_history.size() - 1; Q_ASSERT(historyIndex >= 0); // m_history.size() must always be > 0 } return historyIndex; } // ------------------------------------------------------------------------------------------------ KUrlNavigator::KUrlNavigator(QWidget *parent) : QWidget(parent), d(new Private(this, nullptr)) { d->initialize(QUrl()); } KUrlNavigator::KUrlNavigator(KFilePlacesModel *placesModel, const QUrl &url, QWidget *parent) : QWidget(parent), d(new Private(this, placesModel)) { d->initialize(url); } KUrlNavigator::~KUrlNavigator() { delete d; } QUrl KUrlNavigator::locationUrl(int historyIndex) const { historyIndex = d->adjustedHistoryIndex(historyIndex); return d->m_history[historyIndex].url; } void KUrlNavigator::saveLocationState(const QByteArray &state) { d->m_history[d->m_historyIndex].state = state; } QByteArray KUrlNavigator::locationState(int historyIndex) const { historyIndex = d->adjustedHistoryIndex(historyIndex); return d->m_history[historyIndex].state; } bool KUrlNavigator::goBack() { const int count = d->m_history.count(); if (d->m_historyIndex < count - 1) { const QUrl newUrl = locationUrl(d->m_historyIndex + 1); emit urlAboutToBeChanged(newUrl); ++d->m_historyIndex; d->updateContent(); emit historyChanged(); emit urlChanged(locationUrl()); return true; } return false; } bool KUrlNavigator::goForward() { if (d->m_historyIndex > 0) { const QUrl newUrl = locationUrl(d->m_historyIndex - 1); emit urlAboutToBeChanged(newUrl); --d->m_historyIndex; d->updateContent(); emit historyChanged(); emit urlChanged(locationUrl()); return true; } return false; } bool KUrlNavigator::goUp() { const QUrl currentUrl = locationUrl(); const QUrl upUrl = KIO::upUrl(currentUrl); if (upUrl != currentUrl) { // TODO use url.matches(KIO::upUrl(url), QUrl::StripTrailingSlash) setLocationUrl(upUrl); return true; } return false; } void KUrlNavigator::goHome() { if (d->m_homeUrl.isEmpty() || !d->m_homeUrl.isValid()) { setLocationUrl(QUrl::fromLocalFile(QDir::homePath())); } else { setLocationUrl(d->m_homeUrl); } } void KUrlNavigator::setHomeUrl(const QUrl &url) { d->m_homeUrl = url; } QUrl KUrlNavigator::homeUrl() const { return d->m_homeUrl; } void KUrlNavigator::setUrlEditable(bool editable) { if (d->m_editable != editable) { d->switchView(); } } bool KUrlNavigator::isUrlEditable() const { return d->m_editable; } void KUrlNavigator::setShowFullPath(bool show) { if (d->m_showFullPath != show) { d->m_showFullPath = show; d->updateContent(); } } bool KUrlNavigator::showFullPath() const { return d->m_showFullPath; } void KUrlNavigator::setActive(bool active) { if (active != d->m_active) { d->m_active = active; d->m_dropDownButton->setActive(active); for (KUrlNavigatorButton *button : qAsConst(d->m_navButtons)) { button->setActive(active); } update(); if (active) { emit activated(); } } } bool KUrlNavigator::isActive() const { return d->m_active; } void KUrlNavigator::setPlacesSelectorVisible(bool visible) { if (visible == d->m_showPlacesSelector) { return; } if (visible && (d->m_placesSelector == nullptr)) { // the places selector cannot get visible as no // places model is available return; } d->m_showPlacesSelector = visible; d->m_placesSelector->setVisible(visible); } bool KUrlNavigator::isPlacesSelectorVisible() const { return d->m_showPlacesSelector; } QUrl KUrlNavigator::uncommittedUrl() const { KUriFilterData filteredData(d->m_pathBox->currentText().trimmed()); filteredData.setCheckForExecutables(false); if (KUriFilter::self()->filterUri(filteredData, QStringList{ QStringLiteral("kshorturifilter"), QStringLiteral("kurisearchfilter")})) { return filteredData.uri(); } else { return QUrl::fromUserInput(filteredData.typedString()); } } void KUrlNavigator::setLocationUrl(const QUrl &newUrl) { if (newUrl == locationUrl()) { return; } QUrl url = newUrl.adjusted(QUrl::NormalizePathSegments); // This will be used below; we define it here because in the lower part of the // code locationUrl() and url become the same URLs QUrl firstChildUrl = KIO::UrlUtil::firstChildUrl(locationUrl(), url); if ((url.scheme() == QLatin1String("tar")) || (url.scheme() == QLatin1String("zip")) || (url.scheme() == QLatin1String("krarc"))) { // The URL represents a tar- or zip-file, or an archive file supported by krarc. // Check whether the URL is really part of the archive file, otherwise // replace it by the local path again. bool insideCompressedPath = d->isCompressedPath(url); if (!insideCompressedPath) { QUrl prevUrl = url; QUrl parentUrl = KIO::upUrl(url); while (parentUrl != prevUrl) { if (d->isCompressedPath(parentUrl)) { insideCompressedPath = true; break; } prevUrl = parentUrl; parentUrl = KIO::upUrl(parentUrl); } } if (!insideCompressedPath) { // drop the tar: or zip: or krarc: protocol since we are not // inside the compressed path url.setScheme(QStringLiteral("file")); firstChildUrl.setScheme(QStringLiteral("file")); } } // Check whether current history element has the same URL. // If this is the case, just ignore setting the URL. const LocationData &data = d->m_history[d->m_historyIndex]; const bool isUrlEqual = url.matches(locationUrl(), QUrl::StripTrailingSlash) || (!url.isValid() && url.matches(data.url, QUrl::StripTrailingSlash)); if (isUrlEqual) { return; } emit urlAboutToBeChanged(url); if (d->m_historyIndex > 0) { // If an URL is set when the history index is not at the end (= 0), // then clear all previous history elements so that a new history // tree is started from the current position. QList::iterator begin = d->m_history.begin(); QList::iterator end = begin + d->m_historyIndex; d->m_history.erase(begin, end); d->m_historyIndex = 0; } Q_ASSERT(d->m_historyIndex == 0); LocationData newData; newData.url = url; d->m_history.insert(0, newData); // Prevent an endless growing of the history: remembering // the last 100 Urls should be enough... const int historyMax = 100; if (d->m_history.size() > historyMax) { QList::iterator begin = d->m_history.begin() + historyMax; QList::iterator end = d->m_history.end(); d->m_history.erase(begin, end); } emit historyChanged(); emit urlChanged(url); if (firstChildUrl.isValid()) { emit urlSelectionRequested(firstChildUrl); } d->updateContent(); requestActivation(); } void KUrlNavigator::requestActivation() { setActive(true); } void KUrlNavigator::setFocus() { if (isUrlEditable()) { d->m_pathBox->setFocus(); } else { QWidget::setFocus(); } } -#ifndef KIOFILEWIDGETS_NO_DEPRECATED void KUrlNavigator::setUrl(const QUrl &url) { // deprecated setLocationUrl(url); } -#endif -#ifndef KIOFILEWIDGETS_NO_DEPRECATED void KUrlNavigator::saveRootUrl(const QUrl &url) { // deprecated d->m_history[d->m_historyIndex].rootUrl = url; } -#endif -#ifndef KIOFILEWIDGETS_NO_DEPRECATED void KUrlNavigator::savePosition(int x, int y) { // deprecated d->m_history[d->m_historyIndex].pos = QPoint(x, y); } -#endif void KUrlNavigator::keyPressEvent(QKeyEvent *event) { if (isUrlEditable() && (event->key() == Qt::Key_Escape)) { setUrlEditable(false); } else { QWidget::keyPressEvent(event); } } void KUrlNavigator::keyReleaseEvent(QKeyEvent *event) { QWidget::keyReleaseEvent(event); } void KUrlNavigator::mousePressEvent(QMouseEvent *event) { if (event->button() == Qt::MiddleButton) { requestActivation(); } QWidget::mousePressEvent(event); } void KUrlNavigator::mouseReleaseEvent(QMouseEvent *event) { if (event->button() == Qt::MidButton) { const QRect bounds = d->m_toggleEditableMode->geometry(); if (bounds.contains(event->pos())) { // The middle mouse button has been clicked above the // toggle-editable-mode-button. Paste the clipboard content // as location URL. QClipboard *clipboard = QApplication::clipboard(); const QMimeData *mimeData = clipboard->mimeData(); if (mimeData->hasText()) { const QString text = mimeData->text(); setLocationUrl(QUrl::fromUserInput(text)); } } } QWidget::mouseReleaseEvent(event); } void KUrlNavigator::resizeEvent(QResizeEvent *event) { QTimer::singleShot(0, this, SLOT(updateButtonVisibility())); QWidget::resizeEvent(event); } void KUrlNavigator::wheelEvent(QWheelEvent *event) { setActive(true); QWidget::wheelEvent(event); } bool KUrlNavigator::eventFilter(QObject *watched, QEvent *event) { switch (event->type()) { case QEvent::FocusIn: if (watched == d->m_pathBox) { requestActivation(); setFocus(); } for (KUrlNavigatorButton *button : qAsConst(d->m_navButtons)) { button->setShowMnemonic(true); } break; case QEvent::FocusOut: for (KUrlNavigatorButton *button : qAsConst(d->m_navButtons)) { button->setShowMnemonic(false); } break; default: break; } return QWidget::eventFilter(watched, event); } int KUrlNavigator::historySize() const { return d->m_history.count(); } int KUrlNavigator::historyIndex() const { return d->m_historyIndex; } KUrlComboBox *KUrlNavigator::editor() const { return d->m_pathBox; } void KUrlNavigator::setCustomProtocols(const QStringList &protocols) { d->m_customProtocols = protocols; d->m_protocols->setCustomProtocols(d->m_customProtocols); } QStringList KUrlNavigator::customProtocols() const { return d->m_customProtocols; } QWidget *KUrlNavigator::dropWidget() const { return d->m_dropWidget; } -#ifndef KIOFILEWIDGETS_NO_DEPRECATED const QUrl &KUrlNavigator::url() const { // deprecated // Workaround required because of flawed interface ('const QUrl&' is returned // instead of 'QUrl'): remember the URL to prevent a dangling pointer static QUrl url; url = locationUrl(); return url; } -#endif -#ifndef KIOFILEWIDGETS_NO_DEPRECATED QUrl KUrlNavigator::url(int index) const { // deprecated return d->buttonUrl(index); } -#endif -#ifndef KIOFILEWIDGETS_NO_DEPRECATED QUrl KUrlNavigator::historyUrl(int historyIndex) const { // deprecated return locationUrl(historyIndex); } -#endif -#ifndef KIOFILEWIDGETS_NO_DEPRECATED const QUrl &KUrlNavigator::savedRootUrl() const { // deprecated // Workaround required because of flawed interface ('const QUrl&' is returned // instead of 'QUrl'): remember the root URL to prevent a dangling pointer static QUrl rootUrl; rootUrl = d->m_history[d->m_historyIndex].rootUrl; return rootUrl; } -#endif -#ifndef KIOFILEWIDGETS_NO_DEPRECATED QPoint KUrlNavigator::savedPosition() const { // deprecated return d->m_history[d->m_historyIndex].pos; } -#endif -#ifndef KIOFILEWIDGETS_NO_DEPRECATED void KUrlNavigator::setHomeUrl(const QString &homeUrl) { // deprecated setLocationUrl(QUrl::fromUserInput(homeUrl)); } -#endif #include "moc_kurlnavigator.cpp" diff --git a/src/filewidgets/kurlnavigator.h b/src/filewidgets/kurlnavigator.h index 14d294dd..118ec06a 100644 --- a/src/filewidgets/kurlnavigator.h +++ b/src/filewidgets/kurlnavigator.h @@ -1,499 +1,522 @@ /***************************************************************************** * Copyright (C) 2006-2010 by Peter Penz * * Copyright (C) 2006 by Aaron J. Seigo * * Copyright (C) 2007 by Kevin Ottens * * Copyright (C) 2007 by Urs Wolfer * * * * This library is free software; you can redistribute it and/or * * modify it under the terms of the GNU Library General Public * * License as published by the Free Software Foundation; either * * version 2 of the License, or (at your option) any later version. * * * * This library is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * * Library General Public License for more details. * * * * You should have received a copy of the GNU Library General Public License * * along with this library; see the file COPYING.LIB. If not, write to * * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * * Boston, MA 02110-1301, USA. * *****************************************************************************/ #ifndef KURLNAVIGATOR_H #define KURLNAVIGATOR_H #include "kiofilewidgets_export.h" #include #include #include class KFilePlacesModel; class KUrlComboBox; class QMouseEvent; /** * @class KUrlNavigator kurlnavigator.h * * @brief Widget that allows to navigate through the paths of an URL. * * The URL navigator offers two modes: * - Editable: The URL of the location is editable inside an editor. * By pressing RETURN the URL will get activated. * - Non editable ("breadcrumb view"): The URL of the location is represented by * a number of buttons, where each button represents a path * of the URL. By clicking on a button the path will get * activated. This mode also supports drag and drop of items. * * The mode can be changed by clicking on the empty area of the URL navigator. * It is recommended that the application remembers the setting * or allows to configure the default mode (see KUrlNavigator::setUrlEditable()). * * The URL navigator remembers the URL history during navigation and allows to go * back and forward within this history. * * In the non editable mode ("breadcrumb view") it can be configured whether * the full path should be shown. It is recommended that the application * remembers the setting or allows to configure the default mode (see * KUrlNavigator::setShowFullPath()). * * The typical usage of the KUrlNavigator is: * - Create an instance providing a places model and an URL. * - Create an instance of QAbstractItemView which shows the content of the URL * given by the URL navigator. * - Connect to the signal KUrlNavigator::urlChanged() and synchronize the content of * QAbstractItemView with the URL given by the URL navigator. * * It is recommended, that the application remembers the state of the QAbstractItemView * when the URL has been changed. This allows to restore the view state when going back in history. * KUrlNavigator offers support for remembering the view state: * - The signal urlAboutToBeChanged() will be emitted before the URL change takes places. * This allows the application to store the view state by KUrlNavigator::saveLocationState(). * - The signal urlChanged() will be emitted after the URL change took place. This allows * the application to restore the view state by getting the values from * KUrlNavigator::locationState(). */ class KIOFILEWIDGETS_EXPORT KUrlNavigator : public QWidget { Q_OBJECT public: /** @since 4.5 */ KUrlNavigator(QWidget *parent = nullptr); /** * @param placesModel Model for the places which are selectable inside a * menu. A place can be a bookmark or a device. If it is 0, * no places selector is displayed. * @param url URL which is used for the navigation or editing. * @param parent Parent widget. */ KUrlNavigator(KFilePlacesModel *placesModel, const QUrl &url, QWidget *parent); virtual ~KUrlNavigator(); /** * @return URL of the location given by the \a historyIndex. If \a historyIndex * is smaller than 0, the URL of the current location is returned. * @since 4.5 */ QUrl locationUrl(int historyIndex = -1) const; /** * Saves the location state described by \a state for the current location. It is recommended * that at least the scroll position of a view is remembered and restored when traversing * through the history. Saving the location state should be done when the signal * KUrlNavigator::urlAboutToBeChanged() has been emitted. Restoring the location state (see * KUrlNavigator::locationState()) should be done when the signal KUrlNavigator::urlChanged() * has been emitted. * * Example: * \code * QByteArray state; * QDataStream data(&state, QIODevice::WriteOnly); * data << QPoint(x, y); * data << ...; * ... * urlNavigator->saveLocationState(state); * \endcode * * @since 4.5 */ void saveLocationState(const QByteArray &state); /** * @return Location state given by \a historyIndex. If \a historyIndex * is smaller than 0, the state of the current location is returned. * @see KUrlNavigator::saveLocationState() * @since 4.5 */ QByteArray locationState(int historyIndex = -1) const; /** * Goes back one step in the URL history. The signals * KUrlNavigator::urlAboutToBeChanged(), KUrlNavigator::urlChanged() and * KUrlNavigator::historyChanged() are emitted if true is returned. False is returned * if the beginning of the history has already been reached and hence going back was * not possible. The history index (see KUrlNavigator::historyIndex()) is * increased by one if the operation was successful. */ bool goBack(); /** * Goes forward one step in the URL history. The signals * KUrlNavigator::urlAboutToBeChanged(), KUrlNavigator::urlChanged() and * KUrlNavigator::historyChanged() are emitted if true is returned. False is returned * if the end of the history has already been reached and hence going forward * was not possible. The history index (see KUrlNavigator::historyIndex()) is * decreased by one if the operation was successful. */ bool goForward(); /** * Goes up one step of the URL path and remembers the old path * in the history. The signals KUrlNavigator::urlAboutToBeChanged(), * KUrlNavigator::urlChanged() and KUrlNavigator::historyChanged() are * emitted if true is returned. False is returned if going up was not * possible as the root has been reached. */ bool goUp(); /** * Goes to the home URL and remembers the old URL in the history. * The signals KUrlNavigator::urlAboutToBeChanged(), KUrlNavigator::urlChanged() * and KUrlNavigator::historyChanged() are emitted. * * @see KUrlNavigator::setHomeUrl() */ // KDE5: Remove the home-property. It is sufficient to invoke // KUrlNavigator::setLocationUrl(homeUrl) on application-side. void goHome(); /** * Sets the home URL used by KUrlNavigator::goHome(). If no * home URL is set, the default home path of the user is used. * @since 4.5 */ // KDE5: Remove the home-property. It is sufficient to invoke // KUrlNavigator::setLocationUrl(homeUrl) on application-side. void setHomeUrl(const QUrl &url); QUrl homeUrl() const; /** * Allows to edit the URL of the navigation bar if \a editable * is true, and sets the focus accordingly. * If \a editable is false, each part of * the URL is presented by a button for a fast navigation ("breadcrumb view"). */ void setUrlEditable(bool editable); /** * @return True, if the URL is editable within a line editor. * If false is returned, each part of the URL is presented by a button * for fast navigation ("breadcrumb view"). */ bool isUrlEditable() const; /** * Shows the full path of the URL even if a place represents a part of the URL. * Assuming that a place called "Pictures" uses the URL /home/user/Pictures. * An URL like /home/user/Pictures/2008 is shown as [Pictures] > [2008] * in the breadcrumb view, if showing the full path is turned off. If * showing the full path is turned on, the URL is shown * as [/] > [home] > [Pictures] > [2008]. * @since 4.2 */ void setShowFullPath(bool show); /** * @return True, if the full path of the URL should be shown in the breadcrumb view. * @since 4.2 */ bool showFullPath() const; /** * Set the URL navigator to the active mode, if \a active * is true. The active mode is default. The inactive mode only differs * visually from the active mode, no change of the behavior is given. * * Using the URL navigator in the inactive mode is useful when having split views, * where the inactive view is indicated by an inactive URL * navigator visually. */ void setActive(bool active); /** * @return True, if the URL navigator is in the active mode. * @see KUrlNavigator::setActive() */ bool isActive() const; /** * Sets the places selector visible, if \a visible is true. * The places selector allows to select the places provided * by the places model passed in the constructor. Per default * the places selector is visible. */ void setPlacesSelectorVisible(bool visible); /** @return True, if the places selector is visible. */ bool isPlacesSelectorVisible() const; /** * @return The currently entered, but not accepted URL. * It is possible that the returned URL is not valid. */ QUrl uncommittedUrl() const; /** * @return The amount of locations in the history. The data for each * location can be retrieved by KUrlNavigator::locationUrl() and * KUrlNavigator::locationState(). */ int historySize() const; /** * @return The history index of the current location, where * 0 <= history index < KUrlNavigator::historySize(). 0 is the most * recent history entry. */ int historyIndex() const; /** * @return The used editor when the navigator is in the edit mode * @see KUrlNavigator::setUrlEditable() */ KUrlComboBox *editor() const; /** * If an application supports only some special protocols, they can be set * with \a protocols . */ // TODO KF6 rename to setSupportedSchemes to match KDirOperator and KFileWidget void setCustomProtocols(const QStringList &protocols); /** * @return The custom protocols if they are set, QStringList() otherwise. */ QStringList customProtocols() const; /** * The child widget that received the QDropEvent when dropping on the URL * navigator. You can pass this widget to KJobWidgets::setWindow() * if you need to show a drop menu with KIO::drop(). * @return Child widget that has received the last drop event, or nullptr if * nothing has been dropped yet on the URL navigator. * @since 5.37 * @see KIO::drop() */ QWidget *dropWidget() const; -#if !defined(KIOFILEWIDGETS_NO_DEPRECATED) && !defined(DOXYGEN_SHOULD_SKIP_THIS) +#if KIOFILEWIDGETS_ENABLE_DEPRECATED_SINCE(4, 5) /** * @return The current URL of the location. - * @deprecated Use KUrlNavigator::locationUrl() instead. + * @deprecated Since 4.5, use KUrlNavigator::locationUrl() instead. */ - KIOFILEWIDGETS_DEPRECATED const QUrl &url() const; + KIOFILEWIDGETS_DEPRECATED_VERSION(4, 5, "Use KUrlNavigator::locationUrl(int)") + const QUrl &url() const; +#endif +#if KIOFILEWIDGETS_ENABLE_DEPRECATED_SINCE(4, 5) /** * @return The portion of the current URL up to the path part given * by \a index. Assuming that the current URL is /home/peter/Documents/Music, * then the following URLs are returned for an index: * - index <= 0: /home * - index is 1: /home/peter * - index is 2: /home/peter/Documents * - index >= 3: /home/peter/Documents/Music - * @deprecated It should not be necessary for a client of KUrlNavigator to query this information. + * @deprecated Since 4.5. It should not be necessary for a client of KUrlNavigator to query this information. */ - KIOFILEWIDGETS_DEPRECATED QUrl url(int index) const; + KIOFILEWIDGETS_DEPRECATED_VERSION(4, 5, "Do not use") + QUrl url(int index) const; +#endif +#if KIOFILEWIDGETS_ENABLE_DEPRECATED_SINCE(4, 5) /** * @return URL for the history element with the index \a historyIndex. * The history index 0 represents the most recent URL. * @since 4.3 - * @deprecated Use KUrlNavigator::locationUrl(historyIndex) instead. + * @deprecated Since 4.5, use KUrlNavigator::locationUrl(historyIndex) instead. */ - KIOFILEWIDGETS_DEPRECATED QUrl historyUrl(int historyIndex) const; + KIOFILEWIDGETS_DEPRECATED_VERSION(4, 5, "Use KUrlNavigator::locationUrl(int)") + QUrl historyUrl(int historyIndex) const; +#endif +#if KIOFILEWIDGETS_ENABLE_DEPRECATED_SINCE(4, 5) /** * @return The saved root URL for the current URL (see KUrlNavigator::saveRootUrl()). - * @deprecated Use KUrlNavigator::locationState() instead. + * @deprecated Since 4.5, use KUrlNavigator::locationState() instead. */ - KIOFILEWIDGETS_DEPRECATED const QUrl &savedRootUrl() const; + KIOFILEWIDGETS_DEPRECATED_VERSION(4, 5, "Use KUrlNavigator::locationState(int)") + const QUrl &savedRootUrl() const; +#endif +#if KIOFILEWIDGETS_ENABLE_DEPRECATED_SINCE(4, 5) /** * @return The saved contents position of the upper left corner * for the current URL. - * @deprecated Use KUrlNavigator::locationState() instead. + * @deprecated Since 4.5, use KUrlNavigator::locationState() instead. */ - KIOFILEWIDGETS_DEPRECATED QPoint savedPosition() const; + KIOFILEWIDGETS_DEPRECATED_VERSION(4, 5, "Use KUrlNavigator::locationState(int)") + QPoint savedPosition() const; +#endif - /** @deprecated Use setHomeUrl(const QUrl& url) instead. */ - KIOFILEWIDGETS_DEPRECATED void setHomeUrl(const QString &homeUrl); +#if KIOFILEWIDGETS_ENABLE_DEPRECATED_SINCE(4, 5) + /** @deprecated Since 4.5, use setHomeUrl(const QUrl& url) instead. */ + KIOFILEWIDGETS_DEPRECATED_VERSION(4, 5, "Use KUrlNavigator::setHomeUrl(const QUrl&)") + void setHomeUrl(const QString &homeUrl); #endif public Q_SLOTS: /** * Sets the location to \a url. The old URL is added to the history. * The signals KUrlNavigator::urlAboutToBeChanged(), KUrlNavigator::urlChanged() * and KUrlNavigator::historyChanged() are emitted. Use * KUrlNavigator::locationUrl() to read the location. * @since 4.5 */ void setLocationUrl(const QUrl &url); /** * Activates the URL navigator (KUrlNavigator::isActive() will return true) * and emits the signal KUrlNavigator::activated(). * @see KUrlNavigator::setActive() */ void requestActivation(); #if !defined(DOXYGEN_SHOULD_SKIP_THIS) // KDE5: Remove and listen for focus-signal instead void setFocus(); #endif -#if !defined(KIOFILEWIDGETS_NO_DEPRECATED) && !defined(DOXYGEN_SHOULD_SKIP_THIS) +#if KIOFILEWIDGETS_ENABLE_DEPRECATED_SINCE(4, 5) /** * Sets the location to \a url. - * @deprecated Use KUrlNavigator::setLocationUrl(url). + * @deprecated Since 4.5, use KUrlNavigator::setLocationUrl(url). */ - KIOFILEWIDGETS_DEPRECATED void setUrl(const QUrl &url); + KIOFILEWIDGETS_DEPRECATED_VERSION(4, 5, "Use KUrlNavigator::setLocationUrl(const QUrl&))") + void setUrl(const QUrl &url); +#endif +#if KIOFILEWIDGETS_ENABLE_DEPRECATED_SINCE(4, 5) /** * Saves the used root URL of the content for the current history element. - * @deprecated Use KUrlNavigator::saveLocationState() instead. + * @deprecated Since 4.5, use KUrlNavigator::saveLocationState() instead. */ - KIOFILEWIDGETS_DEPRECATED void saveRootUrl(const QUrl &url); + KIOFILEWIDGETS_DEPRECATED_VERSION(4, 5, "Use KUrlNavigator::saveLocationState(const QByteArray &)") + void saveRootUrl(const QUrl &url); +#endif +#if KIOFILEWIDGETS_ENABLE_DEPRECATED_SINCE(4, 5) /** * Saves the coordinates of the contents for the current history element. - * @deprecated Use KUrlNavigator::saveLocationState() instead. + * @deprecated Since 4.5, use KUrlNavigator::saveLocationState() instead. */ - KIOFILEWIDGETS_DEPRECATED void savePosition(int x, int y); + KIOFILEWIDGETS_DEPRECATED_VERSION(4, 5, "Use KUrlNavigator::saveLocationState(const QByteArray &)") + void savePosition(int x, int y); #endif Q_SIGNALS: /** * Is emitted, if the URL navigator has been activated by * an user interaction * @see KUrlNavigator::setActive() */ void activated(); /** * Is emitted, if the location URL has been changed e. g. by * the user. * @see KUrlNavigator::setUrl() */ void urlChanged(const QUrl &url); /** * Is emitted, before the location URL is going to be changed to \a newUrl. * The signal KUrlNavigator::urlChanged() will be emitted after the change * has been done. Connecting to this signal is useful to save the state * of a view with KUrlNavigator::saveLocationState(). * @since 4.5 */ void urlAboutToBeChanged(const QUrl &newUrl); /** * Is emitted, if the editable state for the URL has been changed * (see KUrlNavigator::setUrlEditable()). */ void editableStateChanged(bool editable); /** * Is emitted, if the history has been changed. Usually * the history is changed if a new URL has been selected. */ void historyChanged(); /** * Is emitted if a dropping has been done above the destination * \a destination. The receiver must accept the drop event if * the dropped data can be handled. * @since 4.2 */ void urlsDropped(const QUrl &destination, QDropEvent *event); /** * This signal is emitted when the Return or Enter key is pressed. */ void returnPressed(); /** * Is emitted if the URL \a url should be opened in a new tab because * the user clicked on a breadcrumb with the middle mouse button. * @since 4.5 */ void tabRequested(const QUrl &url); /** * When the URL is changed and the new URL (e.g. /home/user1/) * is a parent of the previous URL (e.g. /home/user1/data/stuff), * then this signal is emitted and \a url is set to the child * directory of the new URL which is an ancestor of the old URL * (in the example paths this would be /home/user1/data/). * This signal allows file managers to pre-select the directory * that the user is navigating up from. * @since 5.37.0 */ void urlSelectionRequested(const QUrl &url); protected: #if !defined(DOXYGEN_SHOULD_SKIP_THIS) /** * If the Escape key is pressed, the navigation bar should switch * to the breadcrumb view. * @see QWidget::keyPressEvent() */ void keyPressEvent(QKeyEvent *event) override; /** * Reimplemented for internal purposes. */ void keyReleaseEvent(QKeyEvent *event) override; /** * Paste the clipboard content as URL, if the middle mouse * button has been clicked. * @see QWidget::mouseReleaseEvent() */ void mouseReleaseEvent(QMouseEvent *event) override; /** * Reimplemented to activate on middle mousse button click */ void mousePressEvent(QMouseEvent *event) override; void resizeEvent(QResizeEvent *event) override; void wheelEvent(QWheelEvent *event) override; bool eventFilter(QObject *watched, QEvent *event) override; #endif private: Q_PRIVATE_SLOT(d, void slotReturnPressed()) Q_PRIVATE_SLOT(d, void slotProtocolChanged(const QString &protocol)) Q_PRIVATE_SLOT(d, void slotToggleEditableButtonPressed()) Q_PRIVATE_SLOT(d, void dropUrls(const QUrl &destination, QDropEvent *)) Q_PRIVATE_SLOT(d, void slotNavigatorButtonClicked(const QUrl &url, Qt::MouseButton button, Qt::KeyboardModifiers modifiers)) Q_PRIVATE_SLOT(d, void openContextMenu(QPoint)) Q_PRIVATE_SLOT(d, void openPathSelectorMenu()) Q_PRIVATE_SLOT(d, void updateButtonVisibility()) Q_PRIVATE_SLOT(d, void switchToBreadcrumbMode()) Q_PRIVATE_SLOT(d, void slotPathBoxChanged(const QString &text)) Q_PRIVATE_SLOT(d, void updateContent()) private: class Private; Private *const d; Q_DISABLE_COPY(KUrlNavigator) }; #endif diff --git a/src/widgets/CMakeLists.txt b/src/widgets/CMakeLists.txt index 4b8eece0..cbc0a625 100644 --- a/src/widgets/CMakeLists.txt +++ b/src/widgets/CMakeLists.txt @@ -1,204 +1,212 @@ project(KIOWidgets) #include (ConfigureChecks.cmake) # Device desktop files aren't really used anymore set(KIO_NO_SOLID TRUE) find_package(ACL) set(HAVE_LIBACL ${ACL_FOUND}) set(HAVE_POSIX_ACL ${ACL_FOUND}) 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) set(kiowidgets_SRCS accessmanager.cpp accessmanagerreply_p.cpp fileundomanager.cpp kacleditwidget.cpp kpropertiesdialog.cpp kurlrequesterdialog.cpp kurlcombobox.cpp kfileitemactions.cpp delegateanimationhandler.cpp imagefilter.cpp kfileitemdelegate.cpp kdesktopfileactions.cpp kopenwithdialog.cpp kfile.cpp pastedialog.cpp paste.cpp clipboardupdater.cpp kabstractfileitemactionplugin.cpp koverlayiconplugin.cpp kbuildsycocaprogressdialog.cpp kurlrequester.cpp krun.cpp sslui.cpp kurlpixmapprovider.cpp pixmaploader.cpp thumbsequencecreator.cpp thumbcreator.cpp kshellcompletion.cpp kurlcompletion.cpp kurifilter.cpp dropjob.cpp openfilemanagerwindowjob.cpp pastejob.cpp previewjob.cpp renamedialog.cpp ksslcertificatebox.cpp kdynamicjobtracker.cpp ksslinfodialog.cpp joburlcache.cpp skipdialog.cpp jobuidelegate.cpp kdirlister.cpp kdirmodel.cpp executablefileopendialog.cpp dndpopupmenuplugin.cpp kurifiltersearchprovideractions.cpp ) if (WIN32) list(APPEND kiowidgets_SRCS krun_win.cpp ) else() list(APPEND kiowidgets_SRCS kautomount.cpp ) endif() ecm_qt_declare_logging_category(kiowidgets_SRCS HEADER kio_widgets_debug.h IDENTIFIER KIO_WIDGETS CATEGORY_NAME kf5.kio.widgets) qt5_add_dbus_adaptor(kiowidgets_SRCS org.kde.kio.FileUndoManager.xml fileundomanager_p.h KIO::FileUndoManagerPrivate fileundomanager_adaptor KIOFileUndoManagerAdaptor) qt5_add_dbus_interface(kiowidgets_SRCS org.kde.kuiserver.xml kuiserver_interface) ki18n_wrap_ui(kiowidgets_SRCS checksumswidget.ui certificateparty.ui sslinfo.ui kpropertiesdesktopadvbase.ui kpropertiesdesktopbase.ui ) add_library(KF5KIOWidgets ${kiowidgets_SRCS}) -generate_export_header(KF5KIOWidgets BASE_NAME KIOWidgets) add_library(KF5::KIOWidgets ALIAS KF5KIOWidgets) +ecm_generate_export_header(KF5KIOWidgets + BASE_NAME KIOWidgets + # GROUP_BASE_NAME KF <- enable once all of KF modules use ecm_generate_export_header + VERSION ${KF5_VERSION} + DEPRECATED_BASE_VERSION 0 + DEPRECATION_VERSIONS 4.0 4.1 4.3 4.4 4.5 4.6 4.7 5.0 5.4 5.6 5.25 5.31 5.32 5.64 +) +# TODO: add support for EXCLUDE_DEPRECATED_BEFORE_AND_AT to all KIO libs +# needs fixing of undeprecated API being still implemented using own deprecated API target_include_directories(KF5KIOWidgets INTERFACE "$") target_link_libraries(KF5KIOWidgets PUBLIC KF5::KIOCore KF5::JobWidgets KF5::Service Qt5::Network # SSL KF5::Completion # KUrlCompletion uses KCompletion KF5::WidgetsAddons # keditlistwidget PRIVATE Qt5::Concurrent Qt5::DBus KF5::I18n KF5::IconThemes # KIconLoader KF5::WindowSystem # KStartupInfo KF5::ConfigWidgets # KColorScheme ) if(ACL_FOUND) target_link_libraries(KF5KIOWidgets PRIVATE ${ACL_LIBS}) endif() set_target_properties(KF5KIOWidgets PROPERTIES VERSION ${KIO_VERSION_STRING} SOVERSION ${KIO_SOVERSION} EXPORT_NAME KIOWidgets ) # Headers not prefixed with KIO/ ecm_generate_headers(KIOWidgets_HEADERS HEADER_NAMES KPropertiesDialog KUrlRequesterDialog KUrlComboBox KFileItemActions KFileItemDelegate KAutoMount KDesktopFileActions KOpenWithDialog KAbstractFileItemActionPlugin KOverlayIconPlugin KBuildSycocaProgressDialog KFile KUrlRequester KRun KUrlPixmapProvider KSslCertificateBox KSslInfoDialog KDirLister KDirModel KShellCompletion KUrlCompletion KUriFilter REQUIRED_HEADERS KIOWidgets_HEADERS ) # Headers prefixed with KIO/ ecm_generate_headers(KIOWidgets_CamelCase_HEADERS HEADER_NAMES AccessManager SslUi ThumbSequenceCreator ThumbCreator DropJob DndPopupMenuPlugin OpenFileManagerWindowJob PasteJob PreviewJob RenameDialog SkipDialog JobUiDelegate FileUndoManager Paste PixmapLoader KUriFilterSearchProviderActions # KF6: fix and move to non-KIO prefixed install folder PREFIX KIO REQUIRED_HEADERS KIO_namespaced_widgets_HEADERS ) install(FILES ${KIOWidgets_CamelCase_HEADERS} DESTINATION ${KDE_INSTALL_INCLUDEDIR_KF5}/KIOWidgets/KIO COMPONENT Devel) install(TARGETS KF5KIOWidgets EXPORT KF5KIOTargets ${KF5_INSTALL_TARGETS_DEFAULT_ARGS}) install(FILES org.kde.kio.FileUndoManager.xml DESTINATION ${KDE_INSTALL_DBUSINTERFACEDIR} RENAME kf5_org.kde.kio.FileUndoManager.xml) install(FILES ${KIO_namespaced_widgets_HEADERS} DESTINATION ${KDE_INSTALL_INCLUDEDIR_KF5}/KIOWidgets/kio COMPONENT Devel) install(FILES ${KIOWidgets_HEADERS} ${CMAKE_CURRENT_BINARY_DIR}/kiowidgets_export.h DESTINATION ${KDE_INSTALL_INCLUDEDIR_KF5}/KIOWidgets COMPONENT Devel) install(FILES kfileitemactionplugin.desktop kpropertiesdialogplugin.desktop kurifilterplugin.desktop konqpopupmenuplugin.desktop kiodndpopupmenuplugin.desktop DESTINATION ${KDE_INSTALL_KSERVICETYPES5DIR} ) # make available to ecm_add_qch in parent folder set(KIOWidgets_QCH_SOURCES ${KIOWidgets_HEADERS} ${KIO_namespaced_widgets_HEADERS} PARENT_SCOPE) include(ECMGeneratePriFile) ecm_generate_pri_file(BASE_NAME KIOWidgets LIB_NAME KF5KIOWidgets DEPS "KIOCore KBookmarks KXmlGui Solid" FILENAME_VAR PRI_FILENAME INCLUDE_INSTALL_DIR ${KDE_INSTALL_INCLUDEDIR_KF5}/KIOWidgets) install(FILES ${PRI_FILENAME} DESTINATION ${ECM_MKSPECS_INSTALL_DIR}) diff --git a/src/widgets/accessmanager.cpp b/src/widgets/accessmanager.cpp index 6b1346fc..5ce094e8 100644 --- a/src/widgets/accessmanager.cpp +++ b/src/widgets/accessmanager.cpp @@ -1,571 +1,567 @@ /* * This file is part of the KDE project. * * Copyright (C) 2009 - 2012 Dawit Alemayehu * Copyright (C) 2008 - 2009 Urs Wolfer * Copyright (C) 2007 Trolltech ASA * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. * */ #include "accessmanager.h" #include "accessmanagerreply_p.h" #include "job.h" #include "kjobwidgets.h" #include "scheduler.h" #include "kio_widgets_debug.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define QL1S(x) QLatin1String(x) #define QL1C(x) QLatin1Char(x) static const QNetworkRequest::Attribute gSynchronousNetworkRequestAttribute = QNetworkRequest::SynchronousRequestAttribute; static qint64 sizeFromRequest(const QNetworkRequest &req) { const QVariant size = req.header(QNetworkRequest::ContentLengthHeader); if (!size.isValid()) { return -1; } bool ok = false; const qlonglong value = size.toLongLong(&ok); return (ok ? value : -1); } namespace KIO { class Q_DECL_HIDDEN AccessManager::AccessManagerPrivate { public: AccessManagerPrivate() : externalContentAllowed(true), emitReadyReadOnMetaDataChange(false), window(nullptr) {} void setMetaDataForRequest(QNetworkRequest request, KIO::MetaData &metaData); bool externalContentAllowed; bool emitReadyReadOnMetaDataChange; KIO::MetaData requestMetaData; KIO::MetaData sessionMetaData; QPointer window; }; namespace Integration { class Q_DECL_HIDDEN CookieJar::CookieJarPrivate { public: CookieJarPrivate() : windowId((WId) - 1), isEnabled(true), isStorageDisabled(false) {} WId windowId; bool isEnabled; bool isStorageDisabled; }; } } using namespace KIO; AccessManager::AccessManager(QObject *parent) : QNetworkAccessManager(parent), d(new AccessManager::AccessManagerPrivate()) { // KDE Cookiejar (KCookieJar) integration... setCookieJar(new KIO::Integration::CookieJar); } AccessManager::~AccessManager() { delete d; } void AccessManager::setExternalContentAllowed(bool allowed) { d->externalContentAllowed = allowed; } bool AccessManager::isExternalContentAllowed() const { return d->externalContentAllowed; } -#ifndef KIOWIDGETS_NO_DEPRECATED void AccessManager::setCookieJarWindowId(WId id) { QWidget *window = QWidget::find(id); if (!window) { return; } KIO::Integration::CookieJar *jar = qobject_cast (cookieJar()); if (jar) { jar->setWindowId(id); } d->window = window->isWindow() ? window : window->window(); } -#endif void AccessManager::setWindow(QWidget *widget) { if (!widget) { return; } d->window = widget->isWindow() ? widget : widget->window(); if (!d->window) { return; } KIO::Integration::CookieJar *jar = qobject_cast (cookieJar()); if (jar) { jar->setWindowId(d->window->winId()); } } -#ifndef KIOWIDGETS_NO_DEPRECATED WId AccessManager::cookieJarWindowid() const { KIO::Integration::CookieJar *jar = qobject_cast (cookieJar()); if (jar) { return jar->windowId(); } return 0; } -#endif QWidget *AccessManager::window() const { return d->window; } KIO::MetaData &AccessManager::requestMetaData() { return d->requestMetaData; } KIO::MetaData &AccessManager::sessionMetaData() { return d->sessionMetaData; } void AccessManager::putReplyOnHold(QNetworkReply *reply) { KDEPrivate::AccessManagerReply *r = qobject_cast(reply); if (!r) { return; } r->putOnHold(); } void AccessManager::setEmitReadyReadOnMetaDataChange(bool enable) { d->emitReadyReadOnMetaDataChange = enable; } QNetworkReply *AccessManager::createRequest(Operation op, const QNetworkRequest &req, QIODevice *outgoingData) { const QUrl reqUrl(req.url()); if (!d->externalContentAllowed && !KDEPrivate::AccessManagerReply::isLocalRequest(reqUrl) && reqUrl.scheme() != QL1S("data")) { //qDebug() << "Blocked: " << reqUrl; return new KDEPrivate::AccessManagerReply(op, req, QNetworkReply::ContentAccessDenied, i18n("Blocked request."), this); } // Check if the internal ignore content disposition header is set. const bool ignoreContentDisposition = req.hasRawHeader("x-kdewebkit-ignore-disposition"); // Retrieve the KIO meta data... KIO::MetaData metaData; d->setMetaDataForRequest(req, metaData); KIO::SimpleJob *kioJob = nullptr; switch (op) { case HeadOperation: { //qDebug() << "HeadOperation:" << reqUrl; kioJob = KIO::mimetype(reqUrl, KIO::HideProgressInfo); break; } case GetOperation: { //qDebug() << "GetOperation:" << reqUrl; if (!reqUrl.path().isEmpty() || reqUrl.host().isEmpty()) { kioJob = KIO::storedGet(reqUrl, KIO::NoReload, KIO::HideProgressInfo); } else { kioJob = KIO::stat(reqUrl, KIO::HideProgressInfo); } // WORKAROUND: Avoid the brain damaged stuff QtWebKit does when a POST // operation is redirected! See BR# 268694. metaData.remove(QStringLiteral("content-type")); // Remove the content-type from a GET/HEAD request! break; } case PutOperation: { //qDebug() << "PutOperation:" << reqUrl; if (outgoingData) { Q_ASSERT(outgoingData->isReadable()); StoredTransferJob* storedJob = KIO::storedPut(outgoingData, reqUrl, -1, KIO::HideProgressInfo); storedJob->setAsyncDataEnabled(outgoingData->isSequential()); QVariant len = req.header(QNetworkRequest::ContentLengthHeader); if (len.isValid()) { storedJob->setTotalSize(len.toInt()); } kioJob = storedJob; } else { kioJob = KIO::put(reqUrl, -1, KIO::HideProgressInfo); } break; } case PostOperation: { kioJob = KIO::storedHttpPost(outgoingData, reqUrl, sizeFromRequest(req), KIO::HideProgressInfo); if (!metaData.contains(QLatin1String("content-type"))) { const QVariant header = req.header(QNetworkRequest::ContentTypeHeader); if (header.isValid()) { metaData.insert(QStringLiteral("content-type"), (QStringLiteral("Content-Type: ") + header.toString())); } else { metaData.insert(QStringLiteral("content-type"), QStringLiteral("Content-Type: application/x-www-form-urlencoded")); } } break; } case DeleteOperation: { //qDebug() << "DeleteOperation:" << reqUrl; kioJob = KIO::http_delete(reqUrl, KIO::HideProgressInfo); break; } case CustomOperation: { const QByteArray &method = req.attribute(QNetworkRequest::CustomVerbAttribute).toByteArray(); //qDebug() << "CustomOperation:" << reqUrl << "method:" << method << "outgoing data:" << outgoingData; if (method.isEmpty()) { return new KDEPrivate::AccessManagerReply(op, req, QNetworkReply::ProtocolUnknownError, i18n("Unknown HTTP verb."), this); } const qint64 size = sizeFromRequest(req); if (size > 0) { kioJob = KIO::http_post(reqUrl, outgoingData, size, KIO::HideProgressInfo); } else { kioJob = KIO::get(reqUrl, KIO::NoReload, KIO::HideProgressInfo); } metaData.insert(QStringLiteral("CustomHTTPMethod"), QString::fromUtf8(method)); break; } default: { qCWarning(KIO_WIDGETS) << "Unsupported KIO operation requested! Deferring to QNetworkAccessManager..."; return QNetworkAccessManager::createRequest(op, req, outgoingData); } } // Set the job priority switch (req.priority()) { case QNetworkRequest::HighPriority: KIO::Scheduler::setJobPriority(kioJob, -5); break; case QNetworkRequest::LowPriority: KIO::Scheduler::setJobPriority(kioJob, 5); break; default: break; } KDEPrivate::AccessManagerReply *reply; /* NOTE: Here we attempt to handle synchronous XHR requests. Unfortunately, due to the fact that QNAM is both synchronous and multi-thread while KIO is completely the opposite (asynchronous and not thread safe), the code below might cause crashes like the one reported in bug# 287778 (nested event loops are inherently dangerous). Unfortunately, all attempts to address the crash has so far failed due to the many regressions they caused, e.g. bug# 231932 and 297954. Hence, until a solution is found, we have to live with the side effects of creating nested event loops. */ if (req.attribute(gSynchronousNetworkRequestAttribute).toBool()) { KJobWidgets::setWindow(kioJob, d->window); kioJob->setRedirectionHandlingEnabled(true); if (kioJob->exec()) { QByteArray data; if (StoredTransferJob *storedJob = qobject_cast< KIO::StoredTransferJob * >(kioJob)) { data = storedJob->data(); } reply = new KDEPrivate::AccessManagerReply(op, req, data, kioJob->url(), kioJob->metaData(), this); //qDebug() << "Synchronous XHR:" << reply << reqUrl; } else { qCWarning(KIO_WIDGETS) << "Failed to create a synchronous XHR for" << reqUrl; qCWarning(KIO_WIDGETS) << "REASON:" << kioJob->errorString(); reply = new KDEPrivate::AccessManagerReply(op, req, QNetworkReply::UnknownNetworkError, kioJob->errorText(), this); } } else { // Set the window on the KIO ui delegate if (d->window) { KJobWidgets::setWindow(kioJob, d->window); } // Disable internal automatic redirection handling kioJob->setRedirectionHandlingEnabled(false); // Set the job priority switch (req.priority()) { case QNetworkRequest::HighPriority: KIO::Scheduler::setJobPriority(kioJob, -5); break; case QNetworkRequest::LowPriority: KIO::Scheduler::setJobPriority(kioJob, 5); break; default: break; } // Set the meta data for this job... kioJob->setMetaData(metaData); // Create the reply... reply = new KDEPrivate::AccessManagerReply(op, req, kioJob, d->emitReadyReadOnMetaDataChange, this); //qDebug() << reply << reqUrl; } if (ignoreContentDisposition && reply) { //qDebug() << "Content-Disposition WILL BE IGNORED!"; reply->setIgnoreContentDisposition(ignoreContentDisposition); } return reply; } static inline void moveMetaData(KIO::MetaData &metaData, const QString &metaDataKey, QNetworkRequest &request, const QByteArray &requestKey) { if (request.hasRawHeader(requestKey)) { metaData.insert(metaDataKey, QString::fromUtf8(request.rawHeader(requestKey))); request.setRawHeader(requestKey, QByteArray()); } } void AccessManager::AccessManagerPrivate::setMetaDataForRequest(QNetworkRequest request, KIO::MetaData &metaData) { // Add any meta data specified within request... QVariant userMetaData = request.attribute(static_cast(MetaData)); if (userMetaData.isValid() && userMetaData.type() == QVariant::Map) { metaData += userMetaData.toMap(); } metaData.insert(QStringLiteral("PropagateHttpHeader"), QStringLiteral("true")); moveMetaData(metaData, QStringLiteral("UserAgent"), request, QByteArrayLiteral("User-Agent")); moveMetaData(metaData, QStringLiteral("accept"), request, QByteArrayLiteral("Accept")); moveMetaData(metaData, QStringLiteral("Charsets"), request, QByteArrayLiteral("Accept-Charset")); moveMetaData(metaData, QStringLiteral("Languages"), request, QByteArrayLiteral("Accept-Language")); moveMetaData(metaData, QStringLiteral("referrer"), request, QByteArrayLiteral("Referer")); //Don't try to correct spelling! moveMetaData(metaData, QStringLiteral("content-type"), request, QByteArrayLiteral("Content-Type")); if (request.attribute(QNetworkRequest::AuthenticationReuseAttribute) == QNetworkRequest::Manual) { metaData.insert(QStringLiteral("no-preemptive-auth-reuse"), QStringLiteral("true")); } request.setRawHeader("Content-Length", QByteArray()); request.setRawHeader("Connection", QByteArray()); request.setRawHeader("If-None-Match", QByteArray()); request.setRawHeader("If-Modified-Since", QByteArray()); request.setRawHeader("x-kdewebkit-ignore-disposition", QByteArray()); QStringList customHeaders; const QList list = request.rawHeaderList(); for (const QByteArray &key : list) { const QByteArray value = request.rawHeader(key); if (value.length()) { customHeaders << (QString::fromUtf8(key) + QLatin1String(": ") + QString::fromUtf8(value)); } } if (!customHeaders.isEmpty()) { metaData.insert(QStringLiteral("customHTTPHeader"), customHeaders.join(QLatin1String("\r\n"))); } // Append per request meta data, if any... if (!requestMetaData.isEmpty()) { metaData += requestMetaData; // Clear per request meta data... requestMetaData.clear(); } // Append per session meta data, if any... if (!sessionMetaData.isEmpty()) { metaData += sessionMetaData; } } using namespace KIO::Integration; static QSsl::SslProtocol qSslProtocolFromString(const QString &str) { if (str.compare(QStringLiteral("SSLv3"), Qt::CaseInsensitive) == 0) { return QSsl::SslV3; } if (str.compare(QStringLiteral("SSLv2"), Qt::CaseInsensitive) == 0) { return QSsl::SslV2; } if (str.compare(QStringLiteral("TLSv1"), Qt::CaseInsensitive) == 0) { return QSsl::TlsV1_0; } return QSsl::AnyProtocol; } bool KIO::Integration::sslConfigFromMetaData(const KIO::MetaData &metadata, QSslConfiguration &sslconfig) { bool success = false; if (metadata.value(QStringLiteral("ssl_in_use")) == QLatin1String("TRUE")) { const QSsl::SslProtocol sslProto = qSslProtocolFromString(metadata.value(QStringLiteral("ssl_protocol_version"))); QList cipherList; cipherList << QSslCipher(metadata.value(QStringLiteral("ssl_cipher_name")), sslProto); sslconfig.setCaCertificates(QSslCertificate::fromData(metadata.value(QStringLiteral("ssl_peer_chain")).toUtf8())); sslconfig.setCiphers(cipherList); sslconfig.setProtocol(sslProto); success = sslconfig.isNull(); } return success; } CookieJar::CookieJar(QObject *parent) : QNetworkCookieJar(parent), d(new CookieJar::CookieJarPrivate) { reparseConfiguration(); } CookieJar::~CookieJar() { delete d; } WId CookieJar::windowId() const { return d->windowId; } bool CookieJar::isCookieStorageDisabled() const { return d->isStorageDisabled; } QList CookieJar::cookiesForUrl(const QUrl &url) const { QList cookieList; if (!d->isEnabled) { return cookieList; } QDBusInterface kcookiejar(QStringLiteral("org.kde.kcookiejar5"), QStringLiteral("/modules/kcookiejar"), QStringLiteral("org.kde.KCookieServer")); QDBusReply reply = kcookiejar.call(QStringLiteral("findDOMCookies"), url.toString(QUrl::RemoveUserInfo), (qlonglong)d->windowId); if (!reply.isValid()) { qCWarning(KIO_WIDGETS) << "Unable to communicate with the cookiejar!"; return cookieList; } const QString cookieStr = reply.value(); const QStringList cookies = cookieStr.split(QStringLiteral("; "), QString::SkipEmptyParts); for (const QString &cookie : cookies) { const int index = cookie.indexOf(QL1C('=')); const QStringRef name = cookie.leftRef(index); const QStringRef value = cookie.rightRef((cookie.length() - index - 1)); cookieList << QNetworkCookie(name.toUtf8(), value.toUtf8()); //qDebug() << "cookie: name=" << name << ", value=" << value; } return cookieList; } bool CookieJar::setCookiesFromUrl(const QList &cookieList, const QUrl &url) { if (!d->isEnabled) { return false; } QDBusInterface kcookiejar(QStringLiteral("org.kde.kcookiejar5"), QStringLiteral("/modules/kcookiejar"), QStringLiteral("org.kde.KCookieServer")); for (const QNetworkCookie &cookie : cookieList) { QByteArray cookieHeader("Set-Cookie: "); if (d->isStorageDisabled && !cookie.isSessionCookie()) { QNetworkCookie sessionCookie(cookie); sessionCookie.setExpirationDate(QDateTime()); cookieHeader += sessionCookie.toRawForm(); } else { cookieHeader += cookie.toRawForm(); } kcookiejar.call(QStringLiteral("addCookies"), url.toString(QUrl::RemoveUserInfo), cookieHeader, (qlonglong)d->windowId); //qDebug() << "[" << d->windowId << "]" << cookieHeader << " from " << url; } return !kcookiejar.lastError().isValid(); } void CookieJar::setDisableCookieStorage(bool disable) { d->isStorageDisabled = disable; } void CookieJar::setWindowId(WId id) { d->windowId = id; } void CookieJar::reparseConfiguration() { KConfigGroup cfg = KSharedConfig::openConfig(QStringLiteral("kcookiejarrc"), KConfig::NoGlobals)->group("Cookie Policy"); d->isEnabled = cfg.readEntry("Cookies", true); } diff --git a/src/widgets/accessmanager.h b/src/widgets/accessmanager.h index acfa912c..2b00a156 100644 --- a/src/widgets/accessmanager.h +++ b/src/widgets/accessmanager.h @@ -1,368 +1,370 @@ /* * This file is part of the KDE project. * * Copyright (C) 2008 - 2009 Urs Wolfer * Copyright (C) 2009 - 2012 Dawit Alemayehu * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. * */ #ifndef KIO_ACCESSMANAGER_H #define KIO_ACCESSMANAGER_H #include #include "kiowidgets_export.h" #include // WId #include #include #include class QWidget; namespace KIO { /** * @class KIO::AccessManager accessmanager.h * * @short A KDE implementation of QNetworkAccessManager. * * Use this class instead of QNetworkAccessManager if you want to integrate * with KDE's KIO and KCookieJar modules for network operations and cookie * handling respectively. * * Here is a simple example that shows how to set the QtWebKit module to use KDE's * KIO for its network operations: * @code * QWebView *view = new QWebView(this); * KIO::Integration::AccessManager *manager = new KIO::Integration::AccessManager(view); * view->page()->setNetworkAccessManager(manager); * @endcode * * To access member functions in the cookiejar class at a later point in your * code simply downcast the pointer returned by QWebPage::networkAccessManager * as follows: * @code * KIO::Integration::AccessManager *manager = qobject_cast(view->page()->accessManager()); * @endcode * * Please note that this class is in the KIO namespace for backward compatibility. * You should use KIO::Integration::AccessManager to access this class in your * code. * * IMPORTANTThis class is not a replacement for the standard KDE API. * It should ONLY be used to provide KDE integration in applications that * cannot use the standard KDE API directly. * * @author Urs Wolfer \ * @author Dawit Alemayehu \ * * @deprecated Use the KIO::Integration::AccessManager typedef to access this class instead. * @since 4.3 */ class KIOWIDGETS_EXPORT AccessManager : public QNetworkAccessManager { Q_OBJECT public: /*! * Extensions to QNetworkRequest::Attribute enums. * @since 4.3.2 */ enum Attribute { MetaData = QNetworkRequest::User, /** < Used to send KIO MetaData back and forth. type: QVariant::Map. */ KioError /**< Used to send KIO error codes that cannot be mapped into QNetworkReply::NetworkError. type: QVariant::Int */ }; /** * Constructor */ AccessManager(QObject *parent); /** * Destructor */ virtual ~AccessManager(); /** * Set @p allowed to false if you don't want any external content to be fetched. * By default external content is fetched. */ void setExternalContentAllowed(bool allowed); /** * Returns true if external content is going to be fetched. * * @see setExternalContentAllowed */ bool isExternalContentAllowed() const; +#if KIOWIDGETS_ENABLE_DEPRECATED_SINCE(5, 0) /** * Sets the cookiejar's window id to @p id. * * This is a convenience function that allows you to set the cookiejar's * window id. Note that this function does nothing unless the cookiejar in * use is of type KIO::Integration::CookieJar. * * By default the cookiejar's window id is set to false. Make sure you call * this function and set the window id to its proper value when create an * instance of this object. Otherwise, the KDE cookiejar will not be able * to properly manage session based cookies. * * @see KIO::Integration::CookieJar::setWindowId. * @since 4.4 - * @deprecated Use setWindow + * @deprecated Since 5.0, use KIO::Integration::CookieJar::setWindowId */ -#ifndef KIOWIDGETS_NO_DEPRECATED - KIOWIDGETS_DEPRECATED void setCookieJarWindowId(WId id); + KIOWIDGETS_DEPRECATED_VERSION(5, 0, "Use KIO::Integration::CookieJar::setWindowId(...)") + void setCookieJarWindowId(WId id); #endif /** * Sets the window associated with this network access manager. * * Note that @p widget will be used as a parent for dialogs in KIO as well * as the cookie jar. If @p widget is not a window, this function will * invoke @ref QWidget::window() to obtain the window for the given widget. * * @see KIO::Integration::CookieJar::setWindow. * @since 4.7 */ void setWindow(QWidget *widget); +#if KIOWIDGETS_ENABLE_DEPRECATED_SINCE(5, 0) /** * Returns the cookiejar's window id. * * This is a convenience function that returns the window id associated * with the cookiejar. Note that this function will return a 0 if the * cookiejar is not of type KIO::Integration::CookieJar or a window id * has not yet been set. * * @see KIO::Integration::CookieJar::windowId. * @since 4.4 - * @deprecated Use KIO::Integration::CookieJar::windowId + * @deprecated Since 5.0, use KIO::Integration::CookieJar::windowId */ -#ifndef KIOWIDGETS_NO_DEPRECATED - KIOWIDGETS_DEPRECATED WId cookieJarWindowid() const; + KIOWIDGETS_DEPRECATED_VERSION(5, 0, "Use KIO::Integration::CookieJar::windowId()") + WId cookieJarWindowid() const; #endif /** * Returns the window associated with this network access manager. * * @see setWindow * @since 4.7 */ QWidget *window() const; /** * Returns a reference to the temporary meta data container. * * See kdelibs/kio/DESIGN.metadata for list of supported KIO meta data. * * Use this function when you want to set per request KIO meta data that * will be removed after it has been sent once. * * @since 4.4 */ KIO::MetaData &requestMetaData(); /** * Returns a reference to the persistent meta data container. * * See kdelibs/kio/DESIGN.metadata for list of supported KIO meta data. * * Use this function when you want to set per session KIO meta data that * will be sent with every request. * * Unlike @p requestMetaData, the meta data values set using the reference * returned by this function will not be deleted and will be sent with every * request. * * @since 4.4 */ KIO::MetaData &sessionMetaData(); /** * Puts the ioslave associated with the given @p reply on hold. * * This function is intended to make possible the implementation of * the special case mentioned in KIO::get's documentation within the * KIO-QNAM integration. * * @see KIO::get. * @since 4.6 */ static void putReplyOnHold(QNetworkReply *reply); /** * Sets the network reply object to emit readyRead when it receives meta data. * * Meta data is any information that is not the actual content itself, e.g. * HTTP response headers of the HTTP protocol. * * Calling this function will force the code connecting to QNetworkReply's * readyRead signal to prematurely start dealing with the content that might * not yet have arrived. However, it is essential to make the put ioslave on * hold functionality of KIO work in libraries like QtWebKit. * * @see QNetworkReply::metaDataChanged * @since 4.7 */ void setEmitReadyReadOnMetaDataChange(bool); protected: /** * Reimplemented for internal reasons, the API is not affected. * * @see QNetworkAccessManager::createRequest * @internal */ QNetworkReply *createRequest(Operation op, const QNetworkRequest &req, QIODevice *outgoingData = nullptr) override; private: class AccessManagerPrivate; AccessManagerPrivate *const d; }; namespace Integration { // KDE5: Move AccessManager into the KIO::Integration namespace. typedef KIO::AccessManager AccessManager; /** * Maps KIO SSL meta data into the given QSslConfiguration object. * * @since 4.5 * @return true if @p metadata contains ssl information and the mapping succeeded. */ KIOWIDGETS_EXPORT bool sslConfigFromMetaData(const KIO::MetaData &metadata, QSslConfiguration &sslconfig); /** * @class KIO::CookieJar accessmanager.h * * @short A KDE implementation of QNetworkCookieJar. * * Use this class in place of QNetworkCookieJar if you want to integrate with * KDE's cookiejar instead of the one that comes with Qt. * * Here is a simple example that shows how to set the QtWebKit module to use KDE's * cookiejar: * @code * QWebView *view = new QWebView(this); * KIO::Integration::CookieJar *cookieJar = new KIO::Integration::CookieJar; * cookieJar->setWindowId(view->window()->winId()); * view->page()->networkAccessManager()->setCookieJar(cookieJar); * @endcode * * To access member functions in the cookiejar class at a later point in your * code simply downcast the pointer returned by QNetworkAccessManager::cookieJar * as follows: * @code * KIO::Integration::CookieJar *cookieJar = qobject_cast(view->page()->accessManager()->cookieJar()); * @endcode * * IMPORTANTThis class is not a replacement for the standard KDE API. * It should ONLY be used to provide KDE integration in applications that * cannot use the standard KDE API directly. * * @see QNetworkAccessManager::setCookieJar for details. * * @author Dawit Alemayehu \ * @since 4.4 */ class KIOWIDGETS_EXPORT CookieJar : public QNetworkCookieJar { Q_OBJECT public: /** * Constructs a KNetworkCookieJar with parent @p parent. */ explicit CookieJar(QObject *parent = nullptr); /** * Destroys the KNetworkCookieJar. */ ~CookieJar(); /** * Returns the currently set window id. The default value is -1. */ WId windowId() const; /** * Sets the window id of the application. * * This value is used by KDE's cookiejar to manage session cookies, namely * to delete them when the last application referring to such cookies is * closed by the end user. * * @see QWidget::window() * @see QWidget::winId() * * @param id the value of @ref QWidget::winId() from the window that contains your widget. */ void setWindowId(WId id); /** * Reparse the KDE cookiejar configuration file. */ void reparseConfiguration(); /** * Reimplemented for internal reasons, the API is not affected. * * @see QNetworkCookieJar::cookiesForUrl * @internal */ QList cookiesForUrl(const QUrl &url) const override; /** * Reimplemented for internal reasons, the API is not affected. * * @see QNetworkCookieJar::setCookiesFromUrl * @internal */ bool setCookiesFromUrl(const QList &cookieList, const QUrl &url) override; /** * Returns true if persistent caching of cookies is disabled. * * @see setDisableCookieStorage * @since 4.6 */ bool isCookieStorageDisabled() const; /** * Prevent persistent storage of cookies. * * Call this function if you do not want cookies to be stored locally for * later access without disabling the cookiejar. All cookies will be discarded * once the sessions that are using the cookie are done. * * @since 4.6 */ void setDisableCookieStorage(bool disable); private: class CookieJarPrivate; CookieJarPrivate *const d; }; } } #endif // KIO_ACCESSMANAGER_H diff --git a/src/widgets/kdirmodel.cpp b/src/widgets/kdirmodel.cpp index fe1907af..ea26c87d 100644 --- a/src/widgets/kdirmodel.cpp +++ b/src/widgets/kdirmodel.cpp @@ -1,1310 +1,1308 @@ /* This file is part of the KDE project Copyright (C) 2006 David Faure This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kdirmodel.h" #include "kdirlister.h" #include "kfileitem.h" #include "kio_widgets_debug.h" #include #include #include #include #include "joburlcache_p.h" #include #include #include #include #include #include #include #include #include #include #include #ifdef Q_OS_WIN #include #endif class KDirModelNode; class KDirModelDirNode; static QUrl cleanupUrl(const QUrl &url) { QUrl u = url; u.setPath(QDir::cleanPath(u.path())); // remove double slashes in the path, simplify "foo/." to "foo/", etc. u = u.adjusted(QUrl::StripTrailingSlash); // KDirLister does this too, so we remove the slash before comparing with the root node url. u.setQuery(QString()); u.setFragment(QString()); return u; } // We create our own tree behind the scenes to have fast lookup from an item to its parent, // and also to get the children of an item fast. class KDirModelNode { public: KDirModelNode(KDirModelDirNode *parent, const KFileItem &item) : m_item(item), m_parent(parent), m_preview() { } virtual ~KDirModelNode() { // Required, code will delete ptrs to this or a subclass. } // m_item is KFileItem() for the root item const KFileItem &item() const { return m_item; } void setItem(const KFileItem &item) { m_item = item; } KDirModelDirNode *parent() const { return m_parent; } // linear search int rowNumber() const; // O(n) QIcon preview() const { return m_preview; } void setPreview(const QPixmap &pix) { m_preview = QIcon(); m_preview.addPixmap(pix); } void setPreview(const QIcon &icn) { m_preview = icn; } private: KFileItem m_item; KDirModelDirNode *const m_parent; QIcon m_preview; }; // Specialization for directory nodes class KDirModelDirNode : public KDirModelNode { public: KDirModelDirNode(KDirModelDirNode *parent, const KFileItem &item) : KDirModelNode(parent, item), m_childNodes(), m_childCount(KDirModel::ChildCountUnknown), m_populated(false) {} ~KDirModelDirNode() override { qDeleteAll(m_childNodes); } QList m_childNodes; // owns the nodes // If we listed the directory, the child count is known. Otherwise it can be set via setChildCount. int childCount() const { return m_childNodes.isEmpty() ? m_childCount : m_childNodes.count(); } void setChildCount(int count) { m_childCount = count; } bool isPopulated() const { return m_populated; } void setPopulated(bool populated) { m_populated = populated; } bool isSlow() const { return item().isSlow(); } // For removing all child urls from the global hash. void collectAllChildUrls(QList &urls) const { urls.reserve(urls.size() + m_childNodes.size()); for (KDirModelNode *node : m_childNodes) { const KFileItem &item = node->item(); urls.append(cleanupUrl(item.url())); if (item.isDir()) { static_cast(node)->collectAllChildUrls(urls); } } } private: int m_childCount: 31; bool m_populated: 1; }; int KDirModelNode::rowNumber() const { if (!m_parent) { return 0; } return m_parent->m_childNodes.indexOf(const_cast(this)); } //// class KDirModelPrivate { public: explicit KDirModelPrivate(KDirModel *model) : q(model), m_dirLister(nullptr), m_rootNode(new KDirModelDirNode(nullptr, KFileItem())), m_dropsAllowed(KDirModel::NoDrops), m_jobTransfersVisible(false) { } ~KDirModelPrivate() { delete m_rootNode; } void _k_slotNewItems(const QUrl &directoryUrl, const KFileItemList &); void _k_slotCompleted(const QUrl &directoryUrl); void _k_slotDeleteItems(const KFileItemList &); void _k_slotRefreshItems(const QList > &); void _k_slotClear(); void _k_slotRedirection(const QUrl &oldUrl, const QUrl &newUrl); void _k_slotJobUrlsChanged(const QStringList &urlList); void clear() { delete m_rootNode; m_rootNode = new KDirModelDirNode(nullptr, KFileItem()); } // Emit expand for each parent and then return the // last known parent if there is no node for this url KDirModelNode *expandAllParentsUntil(const QUrl &url) const; // Return the node for a given url, using the hash. KDirModelNode *nodeForUrl(const QUrl &url) const; KDirModelNode *nodeForIndex(const QModelIndex &index) const; QModelIndex indexForNode(KDirModelNode *node, int rowNumber = -1 /*unknown*/) const; bool isDir(KDirModelNode *node) const { return (node == m_rootNode) || node->item().isDir(); } QUrl urlForNode(KDirModelNode *node) const { /** * Queries and fragments are removed from the URL, so that the URL of * child items really starts with the URL of the parent. * * For instance ksvn+http://url?rev=100 is the parent for ksvn+http://url/file?rev=100 * so we have to remove the query in both to be able to compare the URLs */ QUrl url(node == m_rootNode ? m_dirLister->url() : node->item().url()); if (url.hasQuery() || url.hasFragment()) { // avoid detach if not necessary. url.setQuery(QString()); url.setFragment(QString()); // kill ref (#171117) } return url; } void removeFromNodeHash(KDirModelNode *node, const QUrl &url); void clearAllPreviews(KDirModelDirNode *node); #ifndef NDEBUG void dump(); #endif KDirModel * const q; KDirLister *m_dirLister; KDirModelDirNode *m_rootNode; KDirModel::DropsAllowed m_dropsAllowed; bool m_jobTransfersVisible; // key = current known parent node (always a KDirModelDirNode but KDirModelNode is more convenient), // value = final url[s] being fetched QMap > m_urlsBeingFetched; QHash m_nodeHash; // global node hash: url -> node QStringList m_allCurrentDestUrls; //list of all dest urls that have jobs on them (e.g. copy, download) }; KDirModelNode *KDirModelPrivate::nodeForUrl(const QUrl &_url) const // O(1), well, O(length of url as a string) { QUrl url = cleanupUrl(_url); if (url == urlForNode(m_rootNode)) { return m_rootNode; } return m_nodeHash.value(url); } void KDirModelPrivate::removeFromNodeHash(KDirModelNode *node, const QUrl &url) { if (node->item().isDir()) { QList urls; static_cast(node)->collectAllChildUrls(urls); for (const QUrl &u : qAsConst(urls)) { m_nodeHash.remove(u); } } m_nodeHash.remove(cleanupUrl(url)); } KDirModelNode *KDirModelPrivate::expandAllParentsUntil(const QUrl &_url) const // O(depth) { QUrl url = cleanupUrl(_url); //qDebug() << url; QUrl nodeUrl = urlForNode(m_rootNode); if (url == nodeUrl) { return m_rootNode; } // Protocol mismatch? Don't even start comparing paths then. #171721 if (url.scheme() != nodeUrl.scheme()) { return nullptr; } const QString pathStr = url.path(); // no trailing slash KDirModelDirNode *dirNode = m_rootNode; if (!pathStr.startsWith(nodeUrl.path())) { return nullptr; } for (;;) { QString nodePath = nodeUrl.path(); if (!nodePath.endsWith(QLatin1Char('/'))) { nodePath += QLatin1Char('/'); } if (!pathStr.startsWith(nodePath)) { qCWarning(KIO_WIDGETS) << "The kioslave for" << url.scheme() << "violates the hierarchy structure:" << "I arrived at node" << nodePath << ", but" << pathStr << "does not start with that path."; return nullptr; } // E.g. pathStr is /a/b/c and nodePath is /a/. We want to find the node with url /a/b const int nextSlash = pathStr.indexOf(QLatin1Char('/'), nodePath.length()); const QString newPath = pathStr.left(nextSlash); // works even if nextSlash==-1 nodeUrl.setPath(newPath); nodeUrl = nodeUrl.adjusted(QUrl::StripTrailingSlash); // #172508 KDirModelNode *node = nodeForUrl(nodeUrl); if (!node) { //qDebug() << "child equal or starting with" << url << "not found"; // return last parent found: return dirNode; } emit q->expand(indexForNode(node)); //qDebug() << " nodeUrl=" << nodeUrl; if (nodeUrl == url) { //qDebug() << "Found node" << node << "for" << url; return node; } //qDebug() << "going into" << node->item().url(); Q_ASSERT(isDir(node)); dirNode = static_cast(node); } // NOTREACHED //return 0; } #ifndef NDEBUG void KDirModelPrivate::dump() { qCDebug(KIO_WIDGETS) << "Dumping contents of KDirModel" << q << "dirLister url:" << m_dirLister->url(); QHashIterator it(m_nodeHash); while (it.hasNext()) { it.next(); qDebug(KIO_WIDGETS) << it.key() << it.value(); } } #endif // node -> index. If rowNumber is set (or node is root): O(1). Otherwise: O(n). QModelIndex KDirModelPrivate::indexForNode(KDirModelNode *node, int rowNumber) const { if (node == m_rootNode) { return QModelIndex(); } Q_ASSERT(node->parent()); return q->createIndex(rowNumber == -1 ? node->rowNumber() : rowNumber, 0, node); } // index -> node. O(1) KDirModelNode *KDirModelPrivate::nodeForIndex(const QModelIndex &index) const { return index.isValid() ? static_cast(index.internalPointer()) : m_rootNode; } /* * This model wraps the data held by KDirLister. * * The internal pointer of the QModelIndex for a given file is the node for that file in our own tree. * E.g. index(2,0) returns a QModelIndex with row=2 internalPointer= * * Invalid parent index means root of the tree, m_rootNode */ #ifndef NDEBUG static QString debugIndex(const QModelIndex &index) { QString str; if (!index.isValid()) { str = QStringLiteral("[invalid index, i.e. root]"); } else { KDirModelNode *node = static_cast(index.internalPointer()); str = QLatin1String("[index for ") + node->item().url().toString(); if (index.column() > 0) { str += QLatin1String(", column ") + QString::number(index.column()); } str += QLatin1Char(']'); } return str; } #endif KDirModel::KDirModel(QObject *parent) : QAbstractItemModel(parent), d(new KDirModelPrivate(this)) { setDirLister(new KDirLister(this)); } KDirModel::~KDirModel() { delete d; } void KDirModel::setDirLister(KDirLister *dirLister) { if (d->m_dirLister) { d->clear(); delete d->m_dirLister; } d->m_dirLister = dirLister; d->m_dirLister->setParent(this); connect(d->m_dirLister, &KCoreDirLister::itemsAdded, this, [this](const QUrl &dirUrl, const KFileItemList &items){d->_k_slotNewItems(dirUrl, items);} ); connect(d->m_dirLister, static_cast(&KCoreDirLister::completed), this, [this](const QUrl &dirUrl){d->_k_slotCompleted(dirUrl);} ); connect(d->m_dirLister, &KCoreDirLister::itemsDeleted, this, [this](const KFileItemList &items){d->_k_slotDeleteItems(items);} ); connect(d->m_dirLister, &KCoreDirLister::refreshItems, this, [this](const QList > &items){d->_k_slotRefreshItems(items);} ); connect(d->m_dirLister, QOverload<>::of(&KCoreDirLister::clear), this, [this](){d->_k_slotClear();} ); connect(d->m_dirLister, QOverload::of(&KCoreDirLister::redirection), this, [this](const QUrl &oldUrl, const QUrl &newUrl){d->_k_slotRedirection(oldUrl, newUrl);} ); } Qt::DropActions KDirModel::supportedDropActions() const { return Qt::CopyAction | Qt::MoveAction | Qt::LinkAction | Qt::IgnoreAction; } KDirLister *KDirModel::dirLister() const { return d->m_dirLister; } void KDirModelPrivate::_k_slotNewItems(const QUrl &directoryUrl, const KFileItemList &items) { //qDebug() << "directoryUrl=" << directoryUrl; KDirModelNode *result = nodeForUrl(directoryUrl); // O(depth) // If the directory containing the items wasn't found, then we have a big problem. // Are you calling KDirLister::openUrl(url,true,false)? Please use expandToUrl() instead. if (!result) { qCWarning(KIO_WIDGETS) << "Items emitted in directory" << directoryUrl << "but that directory isn't in KDirModel!" << "Root directory:" << urlForNode(m_rootNode); for (const KFileItem &item : items) { qDebug() << "Item:" << item.url(); } #ifndef NDEBUG dump(); #endif Q_ASSERT(result); } Q_ASSERT(isDir(result)); KDirModelDirNode *dirNode = static_cast(result); const QModelIndex index = indexForNode(dirNode); // O(n) const int newItemsCount = items.count(); const int newRowCount = dirNode->m_childNodes.count() + newItemsCount; #if 0 #ifndef NDEBUG // debugIndex only defined in debug mode //qDebug() << items.count() << "in" << directoryUrl << "index=" << debugIndex(index) << "newRowCount=" << newRowCount; #endif #endif q->beginInsertRows(index, newRowCount - newItemsCount, newRowCount - 1); // parent, first, last const QList urlsBeingFetched = m_urlsBeingFetched.value(dirNode); //qDebug() << "urlsBeingFetched for dir" << dirNode << directoryUrl << ":" << urlsBeingFetched; QList emitExpandFor; dirNode->m_childNodes.reserve(newRowCount); KFileItemList::const_iterator it = items.begin(); KFileItemList::const_iterator end = items.end(); for (; it != end; ++it) { const bool isDir = it->isDir(); KDirModelNode *node = isDir ? new KDirModelDirNode(dirNode, *it) : new KDirModelNode(dirNode, *it); #ifndef NDEBUG // Test code for possible duplication of items in the childnodes list, // not sure if/how it ever happened. //if (dirNode->m_childNodes.count() && // dirNode->m_childNodes.last()->item().name() == (*it).name()) { // qCWarning(KIO_WIDGETS) << "Already having" << (*it).name() << "in" << directoryUrl // << "url=" << dirNode->m_childNodes.last()->item().url(); // abort(); //} #endif dirNode->m_childNodes.append(node); const QUrl url = it->url(); m_nodeHash.insert(cleanupUrl(url), node); //qDebug() << url; if (!urlsBeingFetched.isEmpty()) { const QUrl dirUrl(url); for (const QUrl &urlFetched : qAsConst(urlsBeingFetched)) { if (dirUrl.matches(urlFetched, QUrl::StripTrailingSlash) || dirUrl.isParentOf(urlFetched)) { //qDebug() << "Listing found" << dirUrl.url() << "which is a parent of fetched url" << urlFetched; const QModelIndex parentIndex = indexForNode(node, dirNode->m_childNodes.count() - 1); Q_ASSERT(parentIndex.isValid()); emitExpandFor.append(parentIndex); if (isDir && dirUrl != urlFetched) { q->fetchMore(parentIndex); m_urlsBeingFetched[node].append(urlFetched); } } } } } q->endInsertRows(); // Emit expand signal after rowsInserted signal has been emitted, // so that any proxy model will have updated its mapping already for (const QModelIndex &idx : qAsConst(emitExpandFor)) { emit q->expand(idx); } } void KDirModelPrivate::_k_slotCompleted(const QUrl &directoryUrl) { KDirModelNode *result = nodeForUrl(directoryUrl); // O(depth) Q_ASSERT(isDir(result)); KDirModelDirNode *dirNode = static_cast(result); m_urlsBeingFetched.remove(dirNode); } void KDirModelPrivate::_k_slotDeleteItems(const KFileItemList &items) { //qDebug() << items.count(); // I assume all items are from the same directory. // From KDirLister's code, this should be the case, except maybe emitChanges? const KFileItem item = items.first(); Q_ASSERT(!item.isNull()); QUrl url = item.url(); KDirModelNode *node = nodeForUrl(url); // O(depth) if (!node) { qCWarning(KIO_WIDGETS) << "No node found for item that was just removed:" << url; return; } KDirModelDirNode *dirNode = node->parent(); if (!dirNode) { return; } QModelIndex parentIndex = indexForNode(dirNode); // O(n) // Short path for deleting a single item if (items.count() == 1) { const int r = node->rowNumber(); q->beginRemoveRows(parentIndex, r, r); removeFromNodeHash(node, url); delete dirNode->m_childNodes.takeAt(r); q->endRemoveRows(); return; } // We need to make lists of consecutive row numbers, for the beginRemoveRows call. // Let's use a bit array where each bit represents a given child node. const int childCount = dirNode->m_childNodes.count(); QBitArray rowNumbers(childCount, false); for (const KFileItem &item : items) { if (!node) { // don't lookup the first item twice url = item.url(); node = nodeForUrl(url); if (!node) { qCWarning(KIO_WIDGETS) << "No node found for item that was just removed:" << url; continue; } if (!node->parent()) { // The root node has been deleted, but it was not first in the list 'items'. // see https://bugs.kde.org/show_bug.cgi?id=196695 return; } } rowNumbers.setBit(node->rowNumber(), 1); // O(n) removeFromNodeHash(node, url); node = nullptr; } int start = -1; int end = -1; bool lastVal = false; // Start from the end, otherwise all the row numbers are offset while we go for (int i = childCount - 1; i >= 0; --i) { const bool val = rowNumbers.testBit(i); if (!lastVal && val) { end = i; //qDebug() << "end=" << end; } if ((lastVal && !val) || (i == 0 && val)) { start = val ? i : i + 1; //qDebug() << "beginRemoveRows" << start << end; q->beginRemoveRows(parentIndex, start, end); for (int r = end; r >= start; --r) { // reverse because takeAt changes indexes ;) //qDebug() << "Removing from m_childNodes at" << r; delete dirNode->m_childNodes.takeAt(r); } q->endRemoveRows(); } lastVal = val; } } void KDirModelPrivate::_k_slotRefreshItems(const QList > &items) { QModelIndex topLeft, bottomRight; // Solution 1: we could emit dataChanged for one row (if items.size()==1) or all rows // Solution 2: more fine-grained, actually figure out the beginning and end rows. for (QList >::const_iterator fit = items.begin(), fend = items.end(); fit != fend; ++fit) { Q_ASSERT(!fit->first.isNull()); Q_ASSERT(!fit->second.isNull()); const QUrl oldUrl = fit->first.url(); const QUrl newUrl = fit->second.url(); KDirModelNode *node = nodeForUrl(oldUrl); // O(n); maybe we could look up to the parent only once //qDebug() << "in model for" << m_dirLister->url() << ":" << oldUrl << "->" << newUrl << "node=" << node; if (!node) { // not found [can happen when renaming a dir, redirection was emitted already] continue; } if (node != m_rootNode) { // we never set an item in the rootnode, we use m_dirLister->rootItem instead. bool hasNewNode = false; // A file became directory (well, it was overwritten) if (fit->first.isDir() != fit->second.isDir()) { //qDebug() << "DIR/FILE STATUS CHANGE"; const int r = node->rowNumber(); removeFromNodeHash(node, oldUrl); KDirModelDirNode *dirNode = node->parent(); delete dirNode->m_childNodes.takeAt(r); // i.e. "delete node" node = fit->second.isDir() ? new KDirModelDirNode(dirNode, fit->second) : new KDirModelNode(dirNode, fit->second); dirNode->m_childNodes.insert(r, node); // same position! hasNewNode = true; } else { node->setItem(fit->second); } if (oldUrl != newUrl || hasNewNode) { // What if a renamed dir had children? -> kdirlister takes care of emitting for each item //qDebug() << "Renaming" << oldUrl << "to" << newUrl << "in node hash"; m_nodeHash.remove(cleanupUrl(oldUrl)); m_nodeHash.insert(cleanupUrl(newUrl), node); } // Mimetype changed -> forget cached icon (e.g. from "cut", #164185 comment #13) if (fit->first.determineMimeType().name() != fit->second.determineMimeType().name()) { node->setPreview(QIcon()); } const QModelIndex index = indexForNode(node); if (!topLeft.isValid() || index.row() < topLeft.row()) { topLeft = index; } if (!bottomRight.isValid() || index.row() > bottomRight.row()) { bottomRight = index; } } } #ifndef NDEBUG // debugIndex only defined in debug mode //qDebug() << "dataChanged(" << debugIndex(topLeft) << " - " << debugIndex(bottomRight); Q_UNUSED(debugIndex(QModelIndex())); // fix compiler warning #endif bottomRight = bottomRight.sibling(bottomRight.row(), q->columnCount(QModelIndex()) - 1); emit q->dataChanged(topLeft, bottomRight); } // Called when a kioslave redirects (e.g. smb:/Workgroup -> smb://workgroup) // and when renaming a directory. void KDirModelPrivate::_k_slotRedirection(const QUrl &oldUrl, const QUrl &newUrl) { KDirModelNode *node = nodeForUrl(oldUrl); if (!node) { return; } m_nodeHash.remove(cleanupUrl(oldUrl)); m_nodeHash.insert(cleanupUrl(newUrl), node); // Ensure the node's URL is updated. In case of a listjob redirection // we won't get a refreshItem, and in case of renaming a directory // we'll get it too late (so the hash won't find the old url anymore). KFileItem item = node->item(); if (!item.isNull()) { // null if root item, #180156 item.setUrl(newUrl); node->setItem(item); } // The items inside the renamed directory have been handled before, // KDirLister took care of emitting refreshItem for each of them. } void KDirModelPrivate::_k_slotClear() { const int numRows = m_rootNode->m_childNodes.count(); if (numRows > 0) { q->beginRemoveRows(QModelIndex(), 0, numRows - 1); q->endRemoveRows(); } m_nodeHash.clear(); //emit layoutAboutToBeChanged(); clear(); //emit layoutChanged(); } void KDirModelPrivate::_k_slotJobUrlsChanged(const QStringList &urlList) { QStringList dirtyUrls; std::set_symmetric_difference(urlList.begin(), urlList.end(), m_allCurrentDestUrls.constBegin(), m_allCurrentDestUrls.constEnd(), std::back_inserter(dirtyUrls)); m_allCurrentDestUrls = urlList; for (const QString &dirtyUrl : qAsConst(dirtyUrls)) { if (KDirModelNode *node = nodeForUrl(QUrl(dirtyUrl))) { const QModelIndex idx = indexForNode(node); emit q->dataChanged(idx, idx, {KDirModel::HasJobRole}); } } } void KDirModelPrivate::clearAllPreviews(KDirModelDirNode *dirNode) { const int numRows = dirNode->m_childNodes.count(); if (numRows > 0) { KDirModelNode *lastNode = nullptr; for (KDirModelNode *node : qAsConst(dirNode->m_childNodes)) { node->setPreview(QIcon()); //node->setPreview(QIcon::fromTheme(node->item().iconName())); if (isDir(node)) { // recurse into child dirs clearAllPreviews(static_cast(node)); } lastNode = node; } emit q->dataChanged(indexForNode(dirNode->m_childNodes.at(0), 0), // O(1) indexForNode(lastNode, numRows - 1)); // O(1) } } void KDirModel::clearAllPreviews() { d->clearAllPreviews(d->m_rootNode); } void KDirModel::itemChanged(const QModelIndex &index) { // This method is really a itemMimeTypeChanged(), it's mostly called by KFilePreviewGenerator. // When the mimetype is determined, clear the old "preview" (could be // mimetype dependent like when cutting files, #164185) KDirModelNode *node = d->nodeForIndex(index); if (node) { node->setPreview(QIcon()); } #ifndef NDEBUG // debugIndex only defined in debug mode //qDebug() << "dataChanged(" << debugIndex(index); #endif emit dataChanged(index, index); } int KDirModel::columnCount(const QModelIndex &) const { return ColumnCount; } QVariant KDirModel::data(const QModelIndex &index, int role) const { if (index.isValid()) { KDirModelNode *node = static_cast(index.internalPointer()); const KFileItem &item(node->item()); switch (role) { case Qt::DisplayRole: switch (index.column()) { case Name: return item.text(); case Size: return KIO::convertSize(item.size()); // size formatted as QString case ModifiedTime: { QDateTime dt = item.time(KFileItem::ModificationTime); return dt.toString(Qt::SystemLocaleShortDate); } case Permissions: return item.permissionsString(); case Owner: return item.user(); case Group: return item.group(); case Type: return item.mimeComment(); } break; case Qt::EditRole: switch (index.column()) { case Name: return item.text(); } break; case Qt::DecorationRole: if (index.column() == Name) { if (!node->preview().isNull()) { //qDebug() << item->url() << " preview found"; return node->preview(); } Q_ASSERT(!item.isNull()); //qDebug() << item->url() << " overlays=" << item->overlays(); return KDE::icon(item.iconName(), item.overlays()); } break; case Qt::TextAlignmentRole: if (index.column() == Size) { // use a right alignment for L2R and R2L languages const Qt::Alignment alignment = Qt::AlignRight | Qt::AlignVCenter; return int(alignment); } break; case Qt::ToolTipRole: return item.text(); case FileItemRole: return QVariant::fromValue(item); case ChildCountRole: if (!item.isDir()) { return ChildCountUnknown; } else { KDirModelDirNode *dirNode = static_cast(node); int count = dirNode->childCount(); if (count == ChildCountUnknown && item.isReadable() && !dirNode->isSlow()) { const QString path = item.localPath(); if (!path.isEmpty()) { // slow // QDir dir(path); // count = dir.entryList(QDir::AllEntries|QDir::NoDotAndDotDot|QDir::System).count(); #ifdef Q_OS_WIN QString s = path + QLatin1String("\\*.*"); s.replace(QLatin1Char('/'), QLatin1Char('\\')); count = 0; WIN32_FIND_DATA findData; HANDLE hFile = FindFirstFile((LPWSTR)s.utf16(), &findData); if (hFile != INVALID_HANDLE_VALUE) { do { if (!(findData.cFileName[0] == '.' && findData.cFileName[1] == '\0') && !(findData.cFileName[0] == '.' && findData.cFileName[1] == '.' && findData.cFileName[2] == '\0')) { ++count; } } while (FindNextFile(hFile, &findData) != 0); FindClose(hFile); } #else DIR *dir = QT_OPENDIR(QFile::encodeName(path).constData()); if (dir) { count = 0; QT_DIRENT *dirEntry = nullptr; while ((dirEntry = QT_READDIR(dir))) { if (dirEntry->d_name[0] == '.') { if (dirEntry->d_name[1] == '\0') { // skip "." continue; } if (dirEntry->d_name[1] == '.' && dirEntry->d_name[2] == '\0') { // skip ".." continue; } } ++count; } QT_CLOSEDIR(dir); } #endif //qDebug() << "child count for " << path << ":" << count; dirNode->setChildCount(count); } } return count; } case HasJobRole: if (d->m_jobTransfersVisible && d->m_allCurrentDestUrls.isEmpty() == false) { KDirModelNode *node = d->nodeForIndex(index); const QString url = node->item().url().toString(); //return whether or not there are job dest urls visible in the view, so the delegate knows which ones to paint. return QVariant(d->m_allCurrentDestUrls.contains(url)); } } } return QVariant(); } void KDirModel::sort(int column, Qt::SortOrder order) { // Not implemented - we should probably use QSortFilterProxyModel instead. QAbstractItemModel::sort(column, order); } bool KDirModel::setData(const QModelIndex &index, const QVariant &value, int role) { switch (role) { case Qt::EditRole: if (index.column() == Name && value.type() == QVariant::String) { Q_ASSERT(index.isValid()); KDirModelNode *node = static_cast(index.internalPointer()); const KFileItem &item = node->item(); const QString newName = value.toString(); if (newName.isEmpty() || newName == item.text() || (newName == QLatin1Char('.')) || (newName == QLatin1String(".."))) { return true; } QUrl newUrl = item.url().adjusted(QUrl::RemoveFilename); newUrl.setPath(newUrl.path() + KIO::encodeFileName(newName)); KIO::Job *job = KIO::rename(item.url(), newUrl, item.url().isLocalFile() ? KIO::HideProgressInfo : KIO::DefaultFlags); job->uiDelegate()->setAutoErrorHandlingEnabled(true); // undo handling KIO::FileUndoManager::self()->recordJob(KIO::FileUndoManager::Rename, QList() << item.url(), newUrl, job); return true; } break; case Qt::DecorationRole: if (index.column() == Name) { Q_ASSERT(index.isValid()); // Set new pixmap - e.g. preview KDirModelNode *node = static_cast(index.internalPointer()); //qDebug() << "setting icon for " << node->item()->url(); Q_ASSERT(node); if (value.type() == QVariant::Icon) { const QIcon icon(qvariant_cast(value)); node->setPreview(icon); } else if (value.type() == QVariant::Pixmap) { node->setPreview(qvariant_cast(value)); } emit dataChanged(index, index); return true; } break; default: break; } return false; } int KDirModel::rowCount(const QModelIndex &parent) const { KDirModelNode *node = d->nodeForIndex(parent); if (!node || !d->isDir(node)) { // #176555 return 0; } KDirModelDirNode *parentNode = static_cast(node); Q_ASSERT(parentNode); const int count = parentNode->m_childNodes.count(); #if 0 QStringList filenames; for (int i = 0; i < count; ++i) { filenames << d->urlForNode(parentNode->m_childNodes.at(i)).fileName(); } //qDebug() << "rowCount for " << d->urlForNode(parentNode) << ": " << count << filenames; #endif return count; } QModelIndex KDirModel::parent(const QModelIndex &index) const { if (!index.isValid()) { return QModelIndex(); } KDirModelNode *childNode = static_cast(index.internalPointer()); Q_ASSERT(childNode); KDirModelNode *parentNode = childNode->parent(); Q_ASSERT(parentNode); return d->indexForNode(parentNode); // O(n) } // Reimplemented to avoid the default implementation which calls parent // (O(n) for finding the parent's row number for nothing). This implementation is O(1). QModelIndex KDirModel::sibling(int row, int column, const QModelIndex &index) const { if (!index.isValid()) { return QModelIndex(); } KDirModelNode *oldChildNode = static_cast(index.internalPointer()); Q_ASSERT(oldChildNode); KDirModelNode *parentNode = oldChildNode->parent(); Q_ASSERT(parentNode); Q_ASSERT(d->isDir(parentNode)); KDirModelNode *childNode = static_cast(parentNode)->m_childNodes.value(row); // O(1) if (childNode) { return createIndex(row, column, childNode); } return QModelIndex(); } void KDirModel::requestSequenceIcon(const QModelIndex &index, int sequenceIndex) { emit needSequenceIcon(index, sequenceIndex); } void KDirModel::setJobTransfersVisible(bool value) { if (value) { d->m_jobTransfersVisible = true; connect(&JobUrlCache::instance(), SIGNAL(jobUrlsChanged(QStringList)), this, SLOT(_k_slotJobUrlsChanged(QStringList)), Qt::UniqueConnection); JobUrlCache::instance().requestJobUrlsChanged(); } else { disconnect(this, SLOT(_k_slotJobUrlsChanged(QStringList))); } } bool KDirModel::jobTransfersVisible() const { return d->m_jobTransfersVisible; } QList KDirModel::simplifiedUrlList(const QList &urls) { if (urls.isEmpty()) { return urls; } QList ret(urls); std::sort(ret.begin(), ret.end()); QList::iterator it = ret.begin(); QUrl url = *it; ++it; while (it != ret.end()) { if (url.isParentOf(*it) || url == *it) { it = ret.erase(it); } else { url = *it; ++it; } } return ret; } QStringList KDirModel::mimeTypes() const { return KUrlMimeData::mimeDataTypes(); } QMimeData *KDirModel::mimeData(const QModelIndexList &indexes) const { QList urls, mostLocalUrls; urls.reserve(indexes.size()); mostLocalUrls.reserve(indexes.size()); bool canUseMostLocalUrls = true; for (const QModelIndex &index : indexes) { const KFileItem &item = d->nodeForIndex(index)->item(); urls << item.url(); bool isLocal; mostLocalUrls << item.mostLocalUrl(isLocal); if (!isLocal) { canUseMostLocalUrls = false; } } QMimeData *data = new QMimeData(); const bool different = canUseMostLocalUrls && (mostLocalUrls != urls); urls = simplifiedUrlList(urls); if (different) { mostLocalUrls = simplifiedUrlList(mostLocalUrls); KUrlMimeData::setUrls(urls, mostLocalUrls, data); } else { data->setUrls(urls); } return data; } // Public API; not much point in calling it internally KFileItem KDirModel::itemForIndex(const QModelIndex &index) const { if (!index.isValid()) { return d->m_dirLister->rootItem(); } else { return static_cast(index.internalPointer())->item(); } } -#ifndef KIOWIDGETS_NO_DEPRECATED QModelIndex KDirModel::indexForItem(const KFileItem *item) const { // Note that we can only use the URL here, not the pointer. // KFileItems can be copied. return indexForUrl(item->url()); // O(n) } -#endif QModelIndex KDirModel::indexForItem(const KFileItem &item) const { // Note that we can only use the URL here, not the pointer. // KFileItems can be copied. return indexForUrl(item.url()); // O(n) } // url -> index. O(n) QModelIndex KDirModel::indexForUrl(const QUrl &url) const { KDirModelNode *node = d->nodeForUrl(url); // O(depth) if (!node) { //qDebug() << url << "not found"; return QModelIndex(); } return d->indexForNode(node); // O(n) } QModelIndex KDirModel::index(int row, int column, const QModelIndex &parent) const { KDirModelNode *parentNode = d->nodeForIndex(parent); // O(1) Q_ASSERT(parentNode); if (d->isDir(parentNode)) { KDirModelNode *childNode = static_cast(parentNode)->m_childNodes.value(row); // O(1) if (childNode) { return createIndex(row, column, childNode); } } return QModelIndex(); } QVariant KDirModel::headerData(int section, Qt::Orientation orientation, int role) const { if (orientation == Qt::Horizontal) { switch (role) { case Qt::DisplayRole: switch (section) { case Name: return i18nc("@title:column", "Name"); case Size: return i18nc("@title:column", "Size"); case ModifiedTime: return i18nc("@title:column", "Date"); case Permissions: return i18nc("@title:column", "Permissions"); case Owner: return i18nc("@title:column", "Owner"); case Group: return i18nc("@title:column", "Group"); case Type: return i18nc("@title:column", "Type"); } } } return QVariant(); } bool KDirModel::hasChildren(const QModelIndex &parent) const { if (!parent.isValid()) { return true; } const KFileItem &parentItem = static_cast(parent.internalPointer())->item(); Q_ASSERT(!parentItem.isNull()); return parentItem.isDir(); } Qt::ItemFlags KDirModel::flags(const QModelIndex &index) const { Qt::ItemFlags f = Qt::ItemIsEnabled; if (index.column() == Name) { f |= Qt::ItemIsSelectable | Qt::ItemIsEditable | Qt::ItemIsDragEnabled; } // Allow dropping onto this item? if (d->m_dropsAllowed != NoDrops) { if (!index.isValid()) { if (d->m_dropsAllowed & DropOnDirectory) { f |= Qt::ItemIsDropEnabled; } } else { KFileItem item = itemForIndex(index); if (item.isNull()) { qCWarning(KIO_WIDGETS) << "Invalid item returned for index"; } else if (item.isDir()) { if (d->m_dropsAllowed & DropOnDirectory) { f |= Qt::ItemIsDropEnabled; } } else { // regular file item if (d->m_dropsAllowed & DropOnAnyFile) { f |= Qt::ItemIsDropEnabled; } else if (d->m_dropsAllowed & DropOnLocalExecutable) { if (!item.localPath().isEmpty()) { // Desktop file? if (item.determineMimeType().inherits(QStringLiteral("application/x-desktop"))) { f |= Qt::ItemIsDropEnabled; } // Executable, shell script ... ? else if (QFileInfo(item.localPath()).isExecutable()) { f |= Qt::ItemIsDropEnabled; } } } } } } return f; } bool KDirModel::canFetchMore(const QModelIndex &parent) const { if (!parent.isValid()) { return false; } // We now have a bool KDirModelNode::m_populated, // to avoid calling fetchMore more than once on empty dirs. // But this wastes memory, and how often does someone open and re-open an empty dir in a treeview? // Maybe we can ask KDirLister "have you listed already"? (to discuss with M. Brade) KDirModelNode *node = static_cast(parent.internalPointer()); const KFileItem &item = node->item(); return item.isDir() && !static_cast(node)->isPopulated() && static_cast(node)->m_childNodes.isEmpty(); } void KDirModel::fetchMore(const QModelIndex &parent) { if (!parent.isValid()) { return; } KDirModelNode *parentNode = static_cast(parent.internalPointer()); KFileItem parentItem = parentNode->item(); Q_ASSERT(!parentItem.isNull()); if (!parentItem.isDir()) { return; } KDirModelDirNode *dirNode = static_cast(parentNode); if (dirNode->isPopulated()) { return; } dirNode->setPopulated(true); const QUrl parentUrl = parentItem.url(); d->m_dirLister->openUrl(parentUrl, KDirLister::Keep); } bool KDirModel::dropMimeData(const QMimeData *data, Qt::DropAction action, int row, int column, const QModelIndex &parent) { // Not sure we want to implement any drop handling at this level, // but for sure the default QAbstractItemModel implementation makes no sense for a dir model. Q_UNUSED(data); Q_UNUSED(action); Q_UNUSED(row); Q_UNUSED(column); Q_UNUSED(parent); return false; } void KDirModel::setDropsAllowed(DropsAllowed dropsAllowed) { d->m_dropsAllowed = dropsAllowed; } void KDirModel::expandToUrl(const QUrl &url) { // emit expand for each parent and return last parent KDirModelNode *result = d->expandAllParentsUntil(url); // O(depth) //qDebug() << url << result; if (!result) { // doesn't seem related to our base url? return; } if (!(result->item().isNull()) && result->item().url() == url) { // We have it already, nothing to do //qDebug() << "have it already item=" <item()*/; return; } d->m_urlsBeingFetched[result].append(url); if (result == d->m_rootNode) { //qDebug() << "Remembering to emit expand after listing the root url"; // the root is fetched by default, so it must be currently being fetched return; } //qDebug() << "Remembering to emit expand after listing" << result->item().url(); // start a new fetch to look for the next level down the URL const QModelIndex parentIndex = d->indexForNode(result); // O(n) Q_ASSERT(parentIndex.isValid()); fetchMore(parentIndex); } bool KDirModel::insertRows(int, int, const QModelIndex &) { return false; } bool KDirModel::insertColumns(int, int, const QModelIndex &) { return false; } bool KDirModel::removeRows(int, int, const QModelIndex &) { return false; } bool KDirModel::removeColumns(int, int, const QModelIndex &) { return false; } #include "moc_kdirmodel.cpp" diff --git a/src/widgets/kdirmodel.h b/src/widgets/kdirmodel.h index 876a5af2..72849f15 100644 --- a/src/widgets/kdirmodel.h +++ b/src/widgets/kdirmodel.h @@ -1,281 +1,282 @@ /* This file is part of the KDE project Copyright (C) 2006 David Faure This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef KDIRMODEL_H #define KDIRMODEL_H #include #include "kiowidgets_export.h" #include class KDirLister; class KDirModelPrivate; class JobUrlCache; /** * @class KDirModel kdirmodel.h * * @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(); /** * 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; +#if KIOWIDGETS_ENABLE_DEPRECATED_SINCE(4, 0) /** * Return the index for a given kfileitem. This can be slow. - * @deprecated use the method that takes a KFileItem by value + * @deprecated Since 4.0, use the method that takes a KFileItem by value */ -#ifndef KIOWIDGETS_NO_DEPRECATED - KIOWIDGETS_DEPRECATED QModelIndex indexForItem(const KFileItem *) const; + KIOWIDGETS_DEPRECATED_VERSION(4, 0, "Use KDirModel::indexForItem(const KFileItem &)") + QModelIndex indexForItem(const KFileItem *) const; #endif /** * Return the index for a given kfileitem. This can be slow. */ QModelIndex indexForItem(const KFileItem &) const; /** * Return the index for a given url. This can be slow. */ 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) */ 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. */ 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 */ 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 ChildCountRole = 0x2C4D0A40, ///< returns the number of items in a directory, or ChildCountUnknown HasJobRole = 0x01E555A5 ///< returns whether or not there is a job on an item (file/directory) }; 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. }; Q_DECLARE_FLAGS(DropsAllowed, DropsAllowedFlag) /// Set whether dropping onto items should be allowed, and for which kind of item /// Drops are disabled by default. 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; /** * 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. * @since 4.2 */ static QList simplifiedUrlList(const QList &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. * @since 4.3 */ 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. * * @since 4.5 */ void setJobTransfersVisible(bool value); /** * Returns whether or not displaying job transfers has been enabled. * @since 4.5 */ 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. * @since 4.3 */ 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; KDirModelPrivate *const d; }; Q_DECLARE_OPERATORS_FOR_FLAGS(KDirModel::DropsAllowed) #endif /* KDIRMODEL_H */ diff --git a/src/widgets/kpropertiesdialog.cpp b/src/widgets/kpropertiesdialog.cpp index bf4cca0c..99ea4814 100644 --- a/src/widgets/kpropertiesdialog.cpp +++ b/src/widgets/kpropertiesdialog.cpp @@ -1,3928 +1,3926 @@ /* This file is part of the KDE project Copyright (C) 1998, 1999 Torben Weis Copyright (c) 1999, 2000 Preston Brown Copyright (c) 2000 Simon Hausmann Copyright (c) 2000 David Faure Copyright (c) 2003 Waldo Bastian This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ /* * kpropertiesdialog.cpp * View/Edit Properties of files, locally or remotely * * some FilePermissionsPropsPlugin-changes by * Henner Zeller * some layout management by * Bertrand Leconte * the rest of the layout management, bug fixes, adaptation to libkio, * template feature by * David Faure * More layout, cleanups, and fixes by * Preston Brown * Plugin capability, cleanups and port to KDialog by * Simon Hausmann * KDesktopPropsPlugin by * Waldo Bastian */ #include "kpropertiesdialog.h" #include "kpropertiesdialog_p.h" #include "kio_widgets_debug.h" #include "../pathhelpers_p.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #if HAVE_POSIX_ACL extern "C" { # include # include } #endif #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "ui_checksumswidget.h" #include "ui_kpropertiesdesktopbase.h" #include "ui_kpropertiesdesktopadvbase.h" #if HAVE_POSIX_ACL #include "kacleditwidget.h" #endif #include #include #ifdef Q_OS_WIN #include #include #include #ifdef __GNUC__ # warning TODO: port completely to win32 #endif #endif using namespace KDEPrivate; static QString nameFromFileName(QString nameStr) { if (nameStr.endsWith(QLatin1String(".desktop"))) { nameStr.chop(8); } if (nameStr.endsWith(QLatin1String(".kdelnk"))) { nameStr.chop(7); } // Make it human-readable (%2F => '/', ...) nameStr = KIO::decodeFileName(nameStr); return nameStr; } const mode_t KFilePermissionsPropsPlugin::fperm[3][4] = { {S_IRUSR, S_IWUSR, S_IXUSR, S_ISUID}, {S_IRGRP, S_IWGRP, S_IXGRP, S_ISGID}, {S_IROTH, S_IWOTH, S_IXOTH, S_ISVTX} }; class Q_DECL_HIDDEN KPropertiesDialog::KPropertiesDialogPrivate { public: explicit KPropertiesDialogPrivate(KPropertiesDialog *qq) : q(qq) , m_aborted(false) , fileSharePage(nullptr) { } ~KPropertiesDialogPrivate() { } /** * Common initialization for all constructors */ void init(); /** * Inserts all pages in the dialog. */ void insertPages(); KPropertiesDialog * const q; bool m_aborted; QWidget *fileSharePage; /** * The URL of the props dialog (when shown for only one file) */ QUrl m_singleUrl; /** * List of items this props dialog is shown for */ KFileItemList m_items; /** * For templates */ QString m_defaultName; QUrl m_currentDir; /** * List of all plugins inserted ( first one first ) */ QList m_pageList; }; KPropertiesDialog::KPropertiesDialog(const KFileItem &item, QWidget *parent) : KPageDialog(parent), d(new KPropertiesDialogPrivate(this)) { setWindowTitle(i18n("Properties for %1", KIO::decodeFileName(item.name()))); Q_ASSERT(!item.isNull()); d->m_items.append(item); d->m_singleUrl = item.url(); Q_ASSERT(!d->m_singleUrl.isEmpty()); d->init(); } KPropertiesDialog::KPropertiesDialog(const QString &title, QWidget *parent) : KPageDialog(parent), d(new KPropertiesDialogPrivate(this)) { setWindowTitle(i18n("Properties for %1", title)); d->init(); } KPropertiesDialog::KPropertiesDialog(const KFileItemList &_items, QWidget *parent) : KPageDialog(parent), d(new KPropertiesDialogPrivate(this)) { if (_items.count() > 1) { setWindowTitle(i18np("Properties for 1 item", "Properties for %1 Selected Items", _items.count())); } else { setWindowTitle(i18n("Properties for %1", KIO::decodeFileName(_items.first().name()))); } Q_ASSERT(!_items.isEmpty()); d->m_singleUrl = _items.first().url(); Q_ASSERT(!d->m_singleUrl.isEmpty()); d->m_items = _items; d->init(); } KPropertiesDialog::KPropertiesDialog(const QUrl &_url, QWidget *parent) : KPageDialog(parent), d(new KPropertiesDialogPrivate(this)) { setWindowTitle(i18n("Properties for %1", KIO::decodeFileName(_url.fileName()))); d->m_singleUrl = _url; KIO::StatJob *job = KIO::stat(_url); KJobWidgets::setWindow(job, parent); job->exec(); KIO::UDSEntry entry = job->statResult(); d->m_items.append(KFileItem(entry, _url)); d->init(); } KPropertiesDialog::KPropertiesDialog(const QList& urls, QWidget* parent) : KPageDialog(parent), d(new KPropertiesDialogPrivate(this)) { if (urls.count() > 1) { setWindowTitle(i18np("Properties for 1 item", "Properties for %1 Selected Items", urls.count())); } else { setWindowTitle(i18n("Properties for %1", KIO::decodeFileName(urls.first().fileName()))); } Q_ASSERT(!urls.isEmpty()); d->m_singleUrl = urls.first(); Q_ASSERT(!d->m_singleUrl.isEmpty()); 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()); #ifndef _WIN32_WCE SHELLEXECUTEINFOW execInfo; #else SHELLEXECUTEINFO execInfo; #endif memset(&execInfo, 0, sizeof(execInfo)); execInfo.cbSize = sizeof(execInfo); #ifndef _WIN32_WCE execInfo.fMask = SEE_MASK_INVOKEIDLIST | SEE_MASK_NOCLOSEPROCESS | SEE_MASK_FLAG_NO_UI; #else execInfo.fMask = SEE_MASK_NOCLOSEPROCESS | SEE_MASK_FLAG_NO_UI; #endif const QString verb(QLatin1String("properties")); execInfo.lpVerb = (LPCWSTR)verb.utf16(); execInfo.lpFile = (LPCWSTR)path_.utf16(); #ifndef _WIN32_WCE return ShellExecuteExW(&execInfo); #else return ShellExecuteEx(&execInfo); //There is no native file property dialog in wince // return false; #endif } #endif bool KPropertiesDialog::showDialog(const KFileItem &item, QWidget *parent, bool modal) { // TODO: do we really want to show the win32 property dialog? // This means we lose metainfo, support for .desktop files, etc. (DF) #ifdef Q_OS_WIN QString localPath = item.localPath(); if (!localPath.isEmpty()) { return showWin32FilePropertyDialog(localPath); } #endif KPropertiesDialog *dlg = new KPropertiesDialog(item, parent); if (modal) { dlg->exec(); } else { dlg->show(); } return true; } bool KPropertiesDialog::showDialog(const QUrl &_url, QWidget *parent, bool modal) { #ifdef Q_OS_WIN if (_url.isLocalFile()) { return showWin32FilePropertyDialog(_url.toLocalFile()); } #endif KPropertiesDialog *dlg = new KPropertiesDialog(_url, parent); if (modal) { dlg->exec(); } else { dlg->show(); } return true; } bool KPropertiesDialog::showDialog(const KFileItemList &_items, QWidget *parent, bool modal) { if (_items.count() == 1) { const KFileItem item = _items.first(); if (item.entry().count() == 0 && item.localPath().isEmpty()) // this remote item wasn't listed by a slave // Let's stat to get more info on the file { return KPropertiesDialog::showDialog(item.url(), parent, modal); } else { return KPropertiesDialog::showDialog(_items.first(), parent, modal); } } KPropertiesDialog *dlg = new KPropertiesDialog(_items, parent); if (modal) { dlg->exec(); } else { dlg->show(); } return true; } bool KPropertiesDialog::showDialog(const QList &urls, QWidget* parent, bool modal) { KPropertiesDialog *dlg = new KPropertiesDialog(urls, parent); if (modal) { dlg->exec(); } else { dlg->show(); } return true; } void KPropertiesDialog::KPropertiesDialogPrivate::init() { q->setFaceType(KPageDialog::Tabbed); insertPages(); KConfigGroup group(KSharedConfig::openConfig(), "KPropertiesDialog"); KWindowConfig::restoreWindowSize(q->windowHandle(), group); } void KPropertiesDialog::showFileSharingPage() { if (d->fileSharePage) { // FIXME: this showFileSharingPage thingy looks broken! (tokoe) // showPage( pageIndex( d->fileSharePage)); } } void KPropertiesDialog::setFileSharingPage(QWidget *page) { d->fileSharePage = page; } void KPropertiesDialog::setFileNameReadOnly(bool ro) { for (KPropertiesDialogPlugin *it : qAsConst(d->m_pageList)) { if (auto *filePropsPlugin = qobject_cast(it)) { filePropsPlugin->setFileNameReadOnly(ro); } else if (auto *urlPropsPlugin = qobject_cast(it)) { urlPropsPlugin->setFileNameReadOnly(ro); } } } KPropertiesDialog::~KPropertiesDialog() { qDeleteAll(d->m_pageList); delete d; KConfigGroup group(KSharedConfig::openConfig(), "KPropertiesDialog"); KWindowConfig::saveWindowSize(windowHandle(), group, KConfigBase::Persistent); } void KPropertiesDialog::insertPlugin(KPropertiesDialogPlugin *plugin) { connect(plugin, &KPropertiesDialogPlugin::changed, plugin, QOverload<>::of(&KPropertiesDialogPlugin::setDirty)); d->m_pageList.append(plugin); } QUrl KPropertiesDialog::url() const { return d->m_singleUrl; } KFileItem &KPropertiesDialog::item() { return d->m_items.first(); } KFileItemList KPropertiesDialog::items() const { return d->m_items; } QUrl KPropertiesDialog::currentDir() const { return d->m_currentDir; } QString KPropertiesDialog::defaultName() const { return d->m_defaultName; } bool KPropertiesDialog::canDisplay(const KFileItemList &_items) { // TODO: cache the result of those calls. Currently we parse .desktop files far too many times return KFilePropsPlugin::supports(_items) || KFilePermissionsPropsPlugin::supports(_items) || KDesktopPropsPlugin::supports(_items) || KUrlPropsPlugin::supports(_items) || KDevicePropsPlugin::supports(_items) /* || KPreviewPropsPlugin::supports( _items )*/; } void KPropertiesDialog::slotOk() { accept(); } void KPropertiesDialog::accept() { QList::const_iterator pageListIt; d->m_aborted = false; KFilePropsPlugin *filePropsPlugin = qobject_cast(d->m_pageList.first()); // If any page is dirty, then set the main one (KFilePropsPlugin) as // dirty too. This is what makes it possible to save changes to a global // desktop file into a local one. In other cases, it doesn't hurt. for (pageListIt = d->m_pageList.constBegin(); pageListIt != d->m_pageList.constEnd(); ++pageListIt) { if ((*pageListIt)->isDirty() && filePropsPlugin) { filePropsPlugin->setDirty(); break; } } // Apply the changes in the _normal_ order of the tabs now // This is because in case of renaming a file, KFilePropsPlugin will call // KPropertiesDialog::rename, so other tab will be ok with whatever order // BUT for file copied from templates, we need to do the renaming first ! for (pageListIt = d->m_pageList.constBegin(); pageListIt != d->m_pageList.constEnd() && !d->m_aborted; ++pageListIt) { if ((*pageListIt)->isDirty()) { // qDebug() << "applying changes for " << (*pageListIt)->metaObject()->className(); (*pageListIt)->applyChanges(); // applyChanges may change d->m_aborted. } else { // qDebug() << "skipping page " << (*pageListIt)->metaObject()->className(); } } if (!d->m_aborted && filePropsPlugin) { filePropsPlugin->postApplyChanges(); } if (!d->m_aborted) { emit applied(); emit propertiesClosed(); deleteLater(); // somewhat like Qt::WA_DeleteOnClose would do. KPageDialog::accept(); } // else, keep dialog open for user to fix the problem. } void KPropertiesDialog::slotCancel() { reject(); } void KPropertiesDialog::reject() { emit canceled(); emit propertiesClosed(); deleteLater(); KPageDialog::reject(); } void KPropertiesDialog::KPropertiesDialogPrivate::insertPages() { if (m_items.isEmpty()) { return; } if (KFilePropsPlugin::supports(m_items)) { KPropertiesDialogPlugin *p = new KFilePropsPlugin(q); q->insertPlugin(p); } if (KFilePermissionsPropsPlugin::supports(m_items)) { KPropertiesDialogPlugin *p = new KFilePermissionsPropsPlugin(q); q->insertPlugin(p); } if (KChecksumsPlugin::supports(m_items)) { KPropertiesDialogPlugin *p = new KChecksumsPlugin(q); q->insertPlugin(p); } if (KDesktopPropsPlugin::supports(m_items)) { KPropertiesDialogPlugin *p = new KDesktopPropsPlugin(q); q->insertPlugin(p); } if (KUrlPropsPlugin::supports(m_items)) { KPropertiesDialogPlugin *p = new KUrlPropsPlugin(q); q->insertPlugin(p); } if (KDevicePropsPlugin::supports(m_items)) { KPropertiesDialogPlugin *p = new KDevicePropsPlugin(q); q->insertPlugin(p); } // if ( KPreviewPropsPlugin::supports( m_items ) ) { // KPropertiesDialogPlugin *p = new KPreviewPropsPlugin(q); // q->insertPlugin(p); // } //plugins if (m_items.count() != 1) { return; } const KFileItem item = m_items.first(); const QString mimetype = item.mimetype(); if (mimetype.isEmpty()) { return; } QString query = QStringLiteral( "(((not exist [X-KDE-Protocol]) and (not exist [X-KDE-Protocols])) or ([X-KDE-Protocol] == '%1') or ('%1' in [X-KDE-Protocols]))" ).arg(item.url().scheme()); // qDebug() << "trader query: " << query; const KService::List offers = KMimeTypeTrader::self()->query(mimetype, QStringLiteral("KPropertiesDialog/Plugin"), query); for (const KService::Ptr &ptr : offers) { KPropertiesDialogPlugin *plugin = ptr->createInstance(q); if (!plugin) { continue; } plugin->setObjectName(ptr->name()); q->insertPlugin(plugin); } } void KPropertiesDialog::updateUrl(const QUrl &_newUrl) { Q_ASSERT(d->m_items.count() == 1); // qDebug() << "KPropertiesDialog::updateUrl (pre)" << _newUrl; QUrl newUrl = _newUrl; emit saveAs(d->m_singleUrl, newUrl); // qDebug() << "KPropertiesDialog::updateUrl (post)" << newUrl; d->m_singleUrl = newUrl; d->m_items.first().setUrl(newUrl); Q_ASSERT(!d->m_singleUrl.isEmpty()); // If we have an Desktop page, set it dirty, so that a full file is saved locally // Same for a URL page (because of the Name= hack) for (KPropertiesDialogPlugin *it : qAsConst(d->m_pageList)) { if (qobject_cast(it) || qobject_cast(it)) { //qDebug() << "Setting page dirty"; it->setDirty(); break; } } } void KPropertiesDialog::rename(const QString &_name) { Q_ASSERT(d->m_items.count() == 1); // qDebug() << "KPropertiesDialog::rename " << _name; QUrl newUrl; // if we're creating from a template : use currentdir if (!d->m_currentDir.isEmpty()) { newUrl = d->m_currentDir; newUrl.setPath(concatPaths(newUrl.path(), _name)); } else { // It's a directory, so strip the trailing slash first newUrl = d->m_singleUrl.adjusted(QUrl::StripTrailingSlash); // Now change the filename newUrl = newUrl.adjusted(QUrl::RemoveFilename); // keep trailing slash newUrl.setPath(concatPaths(newUrl.path(), _name)); } updateUrl(newUrl); } void KPropertiesDialog::abortApplying() { d->m_aborted = true; } class Q_DECL_HIDDEN KPropertiesDialogPlugin::KPropertiesDialogPluginPrivate { public: KPropertiesDialogPluginPrivate() { } ~KPropertiesDialogPluginPrivate() { } bool m_bDirty; int fontHeight; }; KPropertiesDialogPlugin::KPropertiesDialogPlugin(KPropertiesDialog *_props) : QObject(_props), d(new KPropertiesDialogPluginPrivate) { properties = _props; d->fontHeight = 2 * properties->fontMetrics().height(); d->m_bDirty = false; } KPropertiesDialogPlugin::~KPropertiesDialogPlugin() { delete d; } -#ifndef KIOWIDGETS_NO_DEPRECATED bool KPropertiesDialogPlugin::isDesktopFile(const KFileItem &_item) { return _item.isDesktopFile(); } -#endif void KPropertiesDialogPlugin::setDirty(bool b) { d->m_bDirty = b; } void KPropertiesDialogPlugin::setDirty() { d->m_bDirty = true; } bool KPropertiesDialogPlugin::isDirty() const { return d->m_bDirty; } void KPropertiesDialogPlugin::applyChanges() { qCWarning(KIO_WIDGETS) << "applyChanges() not implemented in page !"; } int KPropertiesDialogPlugin::fontHeight() const { return d->fontHeight; } /////////////////////////////////////////////////////////////////////////////// class KFilePropsPlugin::KFilePropsPluginPrivate { public: KFilePropsPluginPrivate() { dirSizeJob = nullptr; dirSizeUpdateTimer = nullptr; m_lined = nullptr; m_capacityBar = nullptr; m_linkTargetLineEdit = nullptr; } ~KFilePropsPluginPrivate() { if (dirSizeJob) { dirSizeJob->kill(); } } KIO::DirectorySizeJob *dirSizeJob; QTimer *dirSizeUpdateTimer; QFrame *m_frame; bool bMultiple; bool bIconChanged; bool bKDesktopMode; bool bDesktopFile; KCapacityBar *m_capacityBar; QString mimeType; QString oldFileName; KLineEdit *m_lined; QLabel *m_fileNameLabel = nullptr; QGridLayout *m_grid = nullptr; QWidget *iconArea; QLabel *m_sizeLabel; QPushButton *m_sizeDetermineButton; QPushButton *m_sizeStopButton; KLineEdit *m_linkTargetLineEdit; QString m_sRelativePath; bool m_bFromTemplate; /** * The initial filename */ QString oldName; }; KFilePropsPlugin::KFilePropsPlugin(KPropertiesDialog *_props) : KPropertiesDialogPlugin(_props), d(new KFilePropsPluginPrivate) { d->bMultiple = (properties->items().count() > 1); d->bIconChanged = false; d->bDesktopFile = KDesktopPropsPlugin::supports(properties->items()); // qDebug() << "KFilePropsPlugin::KFilePropsPlugin bMultiple=" << d->bMultiple; // We set this data from the first item, and we'll // check that the other items match against it, resetting when not. bool isLocal; const KFileItem item = properties->item(); QUrl url = item.mostLocalUrl(isLocal); bool isReallyLocal = item.url().isLocalFile(); bool bDesktopFile = item.isDesktopFile(); mode_t mode = item.mode(); bool hasDirs = item.isDir() && !item.isLink(); bool hasRoot = url.path() == QLatin1String("/"); QString iconStr = item.iconName(); QString directory = properties->url().adjusted(QUrl::RemoveFilename | QUrl::StripTrailingSlash).path(); QString protocol = properties->url().scheme(); d->bKDesktopMode = protocol == QLatin1String("desktop") || properties->currentDir().scheme() == QLatin1String("desktop"); QString mimeComment = item.mimeComment(); d->mimeType = item.mimetype(); KIO::filesize_t totalSize = item.size(); QString magicMimeComment; QMimeDatabase db; if (isLocal) { QMimeType magicMimeType = db.mimeTypeForFile(url.toLocalFile(), QMimeDatabase::MatchContent); if (magicMimeType.isValid() && !magicMimeType.isDefault()) { magicMimeComment = magicMimeType.comment(); } } #ifdef Q_OS_WIN if (isReallyLocal) { directory = QDir::toNativeSeparators(directory.mid(1)); } #endif // Those things only apply to 'single file' mode QString filename; bool isTrash = false; d->m_bFromTemplate = false; // And those only to 'multiple' mode uint iDirCount = hasDirs ? 1 : 0; uint iFileCount = 1 - iDirCount; d->m_frame = new QFrame(); properties->addPage(d->m_frame, i18nc("@title:tab File properties", "&General")); QVBoxLayout *vbl = new QVBoxLayout(d->m_frame); vbl->setContentsMargins(0, 0, 0, 0); vbl->setObjectName(QStringLiteral("vbl")); QGridLayout *grid = new QGridLayout(); // unknown rows d->m_grid = grid; grid->setColumnStretch(0, 0); grid->setColumnStretch(1, 0); grid->setColumnStretch(2, 1); const int spacingHint = d->m_frame->style()->pixelMetric(QStyle::PM_DefaultLayoutSpacing); grid->addItem(new QSpacerItem(spacingHint, 0), 0, 1); vbl->addLayout(grid); int curRow = 0; if (!d->bMultiple) { QString path; if (!d->m_bFromTemplate) { isTrash = (properties->url().scheme() == QLatin1String("trash")); // Extract the full name, but without file: for local files path = properties->url().toDisplayString(QUrl::PreferLocalFile); } else { path = concatPaths(properties->currentDir().path(), properties->defaultName()); directory = properties->currentDir().toDisplayString(QUrl::PreferLocalFile); } if (d->bDesktopFile) { determineRelativePath(path); } // Extract the file name only filename = properties->defaultName(); if (filename.isEmpty()) { // no template const QFileInfo finfo(item.name()); // this gives support for UDS_NAME, e.g. for kio_trash or kio_system filename = finfo.fileName(); // Make sure only the file's name is displayed (#160964). } else { d->m_bFromTemplate = true; setDirty(); // to enforce that the copy happens } d->oldFileName = filename; // Make it human-readable filename = nameFromFileName(filename); if (d->bKDesktopMode && d->bDesktopFile) { KDesktopFile config(url.toLocalFile()); if (config.desktopGroup().hasKey("Name")) { filename = config.readName(); } } d->oldName = filename; } else { // Multiple items: see what they have in common const KFileItemList items = properties->items(); KFileItemList::const_iterator kit = items.begin(); const KFileItemList::const_iterator kend = items.end(); for (++kit /*no need to check the first one again*/; kit != kend; ++kit) { const QUrl url = (*kit).url(); // qDebug() << "KFilePropsPlugin::KFilePropsPlugin " << url.toDisplayString(); // The list of things we check here should match the variables defined // at the beginning of this method. if (url.isLocalFile() != isLocal) { isLocal = false; // not all local } if (bDesktopFile && (*kit).isDesktopFile() != bDesktopFile) { bDesktopFile = false; // not all desktop files } if ((*kit).mode() != mode) { mode = (mode_t)0; } if (KIO::iconNameForUrl(url) != iconStr) { iconStr = QStringLiteral("document-multiple"); } if (url.adjusted(QUrl::RemoveFilename | QUrl::StripTrailingSlash).path() != directory) { directory.clear(); } if (url.scheme() != protocol) { protocol.clear(); } if (!mimeComment.isNull() && (*kit).mimeComment() != mimeComment) { mimeComment.clear(); } if (isLocal && !magicMimeComment.isNull()) { QMimeType magicMimeType = db.mimeTypeForFile(url.toLocalFile(), QMimeDatabase::MatchContent); if (magicMimeType.isValid() && magicMimeType.comment() != magicMimeComment) { magicMimeComment.clear(); } } if (isLocal && url.path() == QLatin1String("/")) { hasRoot = true; } if ((*kit).isDir() && !(*kit).isLink()) { iDirCount++; hasDirs = true; } else { iFileCount++; totalSize += (*kit).size(); } } } if (!isReallyLocal && !protocol.isEmpty()) { directory += QLatin1String(" (") + protocol + QLatin1Char(')'); } if (!isTrash && (bDesktopFile || ((mode & QT_STAT_MASK) == QT_STAT_DIR)) && !d->bMultiple // not implemented for multiple && enableIconButton()) { // #56857 KIconButton *iconButton = new KIconButton(d->m_frame); int bsize = 66 + 2 * iconButton->style()->pixelMetric(QStyle::PM_ButtonMargin); iconButton->setFixedSize(bsize, bsize); iconButton->setIconSize(48); iconButton->setStrictIconSize(false); if (bDesktopFile && isLocal) { const KDesktopFile config(url.toLocalFile()); if (config.hasDeviceType()) { iconButton->setIconType(KIconLoader::Desktop, KIconLoader::Device); } else { iconButton->setIconType(KIconLoader::Desktop, KIconLoader::Application); } } else { iconButton->setIconType(KIconLoader::Desktop, KIconLoader::Place); } iconButton->setIcon(iconStr); d->iconArea = iconButton; connect(iconButton, &KIconButton::iconChanged, this, &KFilePropsPlugin::slotIconChanged); } else { QLabel *iconLabel = new QLabel(d->m_frame); iconLabel->setAlignment(Qt::AlignCenter); int bsize = 66 + 2 * iconLabel->style()->pixelMetric(QStyle::PM_ButtonMargin); iconLabel->setFixedSize(bsize, bsize); iconLabel->setPixmap(QIcon::fromTheme(iconStr).pixmap(48)); d->iconArea = iconLabel; } grid->addWidget(d->iconArea, curRow, 0, Qt::AlignCenter); KFileItemListProperties itemList(KFileItemList() << item); if (d->bMultiple || isTrash || hasRoot || !(d->m_bFromTemplate || itemList.supportsMoving())) { setFileNameReadOnly(true); if (d->bMultiple) { d->m_fileNameLabel->setText(KIO::itemsSummaryString(iFileCount + iDirCount, iFileCount, iDirCount, 0, false)); } } else { d->m_lined = new KLineEdit(d->m_frame); d->m_lined->setObjectName(QStringLiteral("KFilePropsPlugin::nameLineEdit")); d->m_lined->setText(filename); d->m_lined->setFocus(); // Enhanced rename: Don't highlight the file extension. QString extension = db.suffixForFileName(filename); if (!extension.isEmpty()) { d->m_lined->setSelection(0, filename.length() - extension.length() - 1); } else { int lastDot = filename.lastIndexOf(QLatin1Char('.')); if (lastDot > 0) { d->m_lined->setSelection(0, lastDot); } } connect(d->m_lined, &QLineEdit::textChanged, this, &KFilePropsPlugin::nameFileChanged); grid->addWidget(d->m_lined, curRow, 2); } ++curRow; KSeparator *sep = new KSeparator(Qt::Horizontal, d->m_frame); grid->addWidget(sep, curRow, 0, 1, 3); ++curRow; QLabel *l; if (!mimeComment.isEmpty() && !isTrash) { l = new QLabel(i18n("Type:"), d->m_frame); grid->addWidget(l, curRow, 0, Qt::AlignRight | Qt::AlignTop); QFrame *box = new QFrame(d->m_frame); QVBoxLayout *boxLayout = new QVBoxLayout(box); boxLayout->setSpacing(2); // without that spacing the button literally “sticks” to the label ;) boxLayout->setContentsMargins(0, 0, 0, 0); l = new QLabel(mimeComment, box); l->setTextInteractionFlags(Qt::TextSelectableByMouse | Qt::TextSelectableByKeyboard); grid->addWidget(box, curRow++, 2); QPushButton *button = new QPushButton(box); button->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed); // Minimum still makes the button grow to the entire layout width button->setIcon(QIcon::fromTheme(QStringLiteral("configure"))); boxLayout->addWidget(l); boxLayout->addWidget(button); if (d->mimeType == QLatin1String("application/octet-stream")) { button->setText(i18n("Create New File Type")); } else { button->setText(i18n("File Type Options")); } connect(button, &QAbstractButton::clicked, this, &KFilePropsPlugin::slotEditFileType); if (!KAuthorized::authorizeAction(QStringLiteral("editfiletype"))) { button->hide(); } } if (!magicMimeComment.isEmpty() && magicMimeComment != mimeComment) { l = new QLabel(i18n("Contents:"), d->m_frame); grid->addWidget(l, curRow, 0, Qt::AlignRight); l = new QLabel(magicMimeComment, d->m_frame); grid->addWidget(l, curRow++, 2); } if (!directory.isEmpty()) { l = new QLabel(i18n("Location:"), d->m_frame); grid->addWidget(l, curRow, 0, Qt::AlignRight); l = new KSqueezedTextLabel(directory, d->m_frame); // force the layout direction to be always LTR l->setLayoutDirection(Qt::LeftToRight); // but if we are in RTL mode, align the text to the right // otherwise the text is on the wrong side of the dialog if (properties->layoutDirection() == Qt::RightToLeft) { l->setAlignment(Qt::AlignRight); } l->setTextInteractionFlags(Qt::TextSelectableByMouse | Qt::TextSelectableByKeyboard); grid->addWidget(l, curRow++, 2); } l = new QLabel(i18n("Size:"), d->m_frame); grid->addWidget(l, curRow, 0, Qt::AlignRight | Qt::AlignTop); d->m_sizeLabel = new QLabel(d->m_frame); d->m_sizeLabel->setTextInteractionFlags(Qt::TextSelectableByMouse | Qt::TextSelectableByKeyboard); grid->addWidget(d->m_sizeLabel, curRow++, 2); if (!hasDirs) { // Only files [and symlinks] d->m_sizeLabel->setText(QStringLiteral("%1 (%2)").arg(KIO::convertSize(totalSize), QLocale().toString(totalSize))); d->m_sizeDetermineButton = nullptr; d->m_sizeStopButton = nullptr; } else { // Directory QHBoxLayout *sizelay = new QHBoxLayout(); grid->addLayout(sizelay, curRow++, 2); // buttons d->m_sizeDetermineButton = new QPushButton(i18n("Calculate"), d->m_frame); d->m_sizeStopButton = new QPushButton(i18n("Stop"), d->m_frame); d->m_sizeDetermineButton->setIcon(QIcon::fromTheme(QStringLiteral("view-refresh"))); d->m_sizeStopButton->setIcon(QIcon::fromTheme(QStringLiteral("dialog-cancel"))); connect(d->m_sizeDetermineButton, &QAbstractButton::clicked, this, &KFilePropsPlugin::slotSizeDetermine); connect(d->m_sizeStopButton, &QAbstractButton::clicked, this, &KFilePropsPlugin::slotSizeStop); sizelay->addWidget(d->m_sizeDetermineButton, 0); sizelay->addWidget(d->m_sizeStopButton, 0); sizelay->addStretch(10); // so that the buttons don't grow horizontally // auto-launch for local dirs only, and not for '/' if (isLocal && !hasRoot) { d->m_sizeDetermineButton->setText(i18n("Refresh")); slotSizeDetermine(); } else { d->m_sizeStopButton->setEnabled(false); } } if (!d->bMultiple && item.isLink()) { l = new QLabel(i18n("Points to:"), d->m_frame); grid->addWidget(l, curRow, 0, Qt::AlignRight); d->m_linkTargetLineEdit = new KLineEdit(item.linkDest(), d->m_frame); grid->addWidget(d->m_linkTargetLineEdit, curRow++, 2); connect(d->m_linkTargetLineEdit, &QLineEdit::textChanged, this, QOverload<>::of(&KFilePropsPlugin::setDirty)); } if (!d->bMultiple) { // Dates for multiple don't make much sense... QDateTime dt = item.time(KFileItem::CreationTime); if (!dt.isNull()) { l = new QLabel(i18n("Created:"), d->m_frame); grid->addWidget(l, curRow, 0, Qt::AlignRight); l = new QLabel(dt.toString(Qt::DefaultLocaleLongDate), d->m_frame); grid->addWidget(l, curRow++, 2); } dt = item.time(KFileItem::ModificationTime); if (!dt.isNull()) { l = new QLabel(i18n("Modified:"), d->m_frame); grid->addWidget(l, curRow, 0, Qt::AlignRight); l = new QLabel(dt.toString(Qt::DefaultLocaleLongDate), d->m_frame); l->setTextInteractionFlags(Qt::TextSelectableByMouse | Qt::TextSelectableByKeyboard); grid->addWidget(l, curRow++, 2); } dt = item.time(KFileItem::AccessTime); if (!dt.isNull()) { l = new QLabel(i18n("Accessed:"), d->m_frame); grid->addWidget(l, curRow, 0, Qt::AlignRight); l = new QLabel(dt.toString(Qt::DefaultLocaleLongDate), d->m_frame); l->setTextInteractionFlags(Qt::TextSelectableByMouse | Qt::TextSelectableByKeyboard); grid->addWidget(l, curRow++, 2); } } if (hasDirs) { // only for directories sep = new KSeparator(Qt::Horizontal, d->m_frame); grid->addWidget(sep, curRow, 0, 1, 3); ++curRow; if (isLocal) { KMountPoint::Ptr mp = KMountPoint::currentMountPoints().findByPath(url.toLocalFile()); if (mp) { l = new QLabel(i18n("File System:"), d->m_frame); grid->addWidget(l, curRow, 0, Qt::AlignRight); l = new QLabel(d->m_frame); grid->addWidget(l, curRow++, 2); l->setTextInteractionFlags(Qt::TextSelectableByMouse | Qt::TextSelectableByKeyboard); l->setText(mp->mountType()); l = new QLabel(i18n("Mounted on:"), d->m_frame); grid->addWidget(l, curRow, 0, Qt::AlignRight); l = new KSqueezedTextLabel(mp->mountPoint(), d->m_frame); l->setTextInteractionFlags(Qt::TextSelectableByMouse | Qt::TextSelectableByKeyboard); grid->addWidget(l, curRow++, 2); l = new QLabel(i18n("Mounted from:"), d->m_frame); grid->addWidget(l, curRow, 0, Qt::AlignRight); l = new QLabel(mp->mountedFrom(), d->m_frame); grid->addWidget(l, curRow++, 2); l->setTextInteractionFlags(Qt::TextSelectableByMouse | Qt::TextSelectableByKeyboard); } } l = new QLabel(i18nc("Amount of used and available space on this device or partition", "Free space:"), d->m_frame); grid->addWidget(l, curRow, 0, Qt::AlignRight); d->m_capacityBar = new KCapacityBar(KCapacityBar::DrawTextOutline, d->m_frame); d->m_capacityBar->setText(i18nc("@info:status", "Unknown size")); grid->addWidget(d->m_capacityBar, curRow++, 2); KIO::FileSystemFreeSpaceJob *job = KIO::fileSystemFreeSpace(url); connect(job, &KIO::FileSystemFreeSpaceJob::result, this, &KFilePropsPlugin::slotFreeSpaceResult); } vbl->addStretch(1); } bool KFilePropsPlugin::enableIconButton() const { bool iconEnabled = false; const KFileItem item = properties->item(); // If the current item is a directory, check if it's writable, // so we can create/update a .directory // Current item is a file, same thing: check if it is writable if (item.isWritable()) { iconEnabled = true; } return iconEnabled; } // QString KFilePropsPlugin::tabName () const // { // return i18n ("&General"); // } void KFilePropsPlugin::setFileNameReadOnly(bool ro) { Q_ASSERT(ro); // false isn't supported if (ro && !d->m_fileNameLabel) { Q_ASSERT(!d->m_bFromTemplate); delete d->m_lined; d->m_lined = nullptr; d->m_fileNameLabel = new QLabel(d->m_frame); d->m_fileNameLabel->setTextInteractionFlags(Qt::TextSelectableByMouse | Qt::TextSelectableByKeyboard); d->m_fileNameLabel->setText(d->oldName); // will get overwritten if d->bMultiple d->m_grid->addWidget(d->m_fileNameLabel, 0, 2); } } void KFilePropsPlugin::slotEditFileType() { QString mime; if (d->mimeType == QLatin1String("application/octet-stream")) { const int pos = d->oldFileName.lastIndexOf(QLatin1Char('.')); if (pos != -1) { mime = QLatin1Char('*') + d->oldFileName.midRef(pos); } else { mime = QStringLiteral("*"); } } else { mime = d->mimeType; } KMimeTypeEditor::editMimeType(mime, properties->window()); } void KFilePropsPlugin::slotIconChanged() { d->bIconChanged = true; emit changed(); } void KFilePropsPlugin::nameFileChanged(const QString &text) { properties->buttonBox()->button(QDialogButtonBox::Ok)->setEnabled(!text.isEmpty()); emit changed(); } static QString relativeAppsLocation(const QString &file) { const QString canonical = QFileInfo(file).canonicalFilePath(); const QStringList dirs = QStandardPaths::standardLocations(QStandardPaths::ApplicationsLocation); for (const QString &base : dirs) { QDir base_dir = QDir(base); if (base_dir.exists() && canonical.startsWith(base_dir.canonicalPath())) { return canonical.mid(base.length() + 1); } } return QString(); // return empty if the file is not in apps } void KFilePropsPlugin::determineRelativePath(const QString &path) { // now let's make it relative d->m_sRelativePath = relativeAppsLocation(path); } void KFilePropsPlugin::slotFreeSpaceResult(KIO::Job *job, KIO::filesize_t size, KIO::filesize_t available) { if (!job->error()) { const quint64 used = size - available; const int percentUsed = qRound(100.0 * qreal(used) / qreal(size)); d->m_capacityBar->setText( i18nc("Available space out of total partition size (percent used)", "%1 free of %2 (%3% used)", KIO::convertSize(available), KIO::convertSize(size), percentUsed)); d->m_capacityBar->setValue(percentUsed); } else { d->m_capacityBar->setText(i18nc("@info:status", "Unknown size")); d->m_capacityBar->setValue(0); } } void KFilePropsPlugin::slotDirSizeUpdate() { KIO::filesize_t totalSize = d->dirSizeJob->totalSize(); KIO::filesize_t totalFiles = d->dirSizeJob->totalFiles(); KIO::filesize_t totalSubdirs = d->dirSizeJob->totalSubdirs(); d->m_sizeLabel->setText( i18n("Calculating... %1 (%2)\n%3, %4", KIO::convertSize(totalSize), QLocale().toString(totalSize), i18np("1 file", "%1 files", totalFiles), i18np("1 sub-folder", "%1 sub-folders", totalSubdirs))); } void KFilePropsPlugin::slotDirSizeFinished(KJob *job) { if (job->error()) { d->m_sizeLabel->setText(job->errorString()); } else { KIO::filesize_t totalSize = d->dirSizeJob->totalSize(); KIO::filesize_t totalFiles = d->dirSizeJob->totalFiles(); KIO::filesize_t totalSubdirs = d->dirSizeJob->totalSubdirs(); d->m_sizeLabel->setText(QStringLiteral("%1 (%2)\n%3, %4") .arg(KIO::convertSize(totalSize), QLocale().toString(totalSize), i18np("1 file", "%1 files", totalFiles), i18np("1 sub-folder", "%1 sub-folders", totalSubdirs))); } d->m_sizeStopButton->setEnabled(false); // just in case you change something and try again :) d->m_sizeDetermineButton->setText(i18n("Refresh")); d->m_sizeDetermineButton->setEnabled(true); d->dirSizeJob = nullptr; delete d->dirSizeUpdateTimer; d->dirSizeUpdateTimer = nullptr; } void KFilePropsPlugin::slotSizeDetermine() { d->m_sizeLabel->setText(i18n("Calculating...")); // qDebug() << "properties->item()=" << properties->item() << "URL=" << properties->item().url(); d->dirSizeJob = KIO::directorySize(properties->items()); d->dirSizeUpdateTimer = new QTimer(this); connect(d->dirSizeUpdateTimer, &QTimer::timeout, this, &KFilePropsPlugin::slotDirSizeUpdate); d->dirSizeUpdateTimer->start(500); connect(d->dirSizeJob, &KJob::result, this, &KFilePropsPlugin::slotDirSizeFinished); d->m_sizeStopButton->setEnabled(true); d->m_sizeDetermineButton->setEnabled(false); // also update the "Free disk space" display if (d->m_capacityBar) { const KFileItem item = properties->item(); KIO::FileSystemFreeSpaceJob *job = KIO::fileSystemFreeSpace(item.url()); connect(job, &KIO::FileSystemFreeSpaceJob::result, this, &KFilePropsPlugin::slotFreeSpaceResult); } } void KFilePropsPlugin::slotSizeStop() { if (d->dirSizeJob) { KIO::filesize_t totalSize = d->dirSizeJob->totalSize(); d->m_sizeLabel->setText(i18n("At least %1", KIO::convertSize(totalSize))); d->dirSizeJob->kill(); d->dirSizeJob = nullptr; } if (d->dirSizeUpdateTimer) { d->dirSizeUpdateTimer->stop(); } d->m_sizeStopButton->setEnabled(false); d->m_sizeDetermineButton->setEnabled(true); } KFilePropsPlugin::~KFilePropsPlugin() { delete d; } bool KFilePropsPlugin::supports(const KFileItemList & /*_items*/) { return true; } void KFilePropsPlugin::applyChanges() { if (d->dirSizeJob) { slotSizeStop(); } // qDebug() << "KFilePropsPlugin::applyChanges"; if (d->m_lined) { QString n = d->m_lined->text(); // Remove trailing spaces (#4345) while (! n.isEmpty() && n[n.length() - 1].isSpace()) { n.chop(1); } if (n.isEmpty()) { KMessageBox::sorry(properties, i18n("The new file name is empty.")); properties->abortApplying(); return; } // Do we need to rename the file ? // qDebug() << "oldname = " << d->oldName; // qDebug() << "newname = " << n; if (d->oldName != n || d->m_bFromTemplate) { // true for any from-template file KIO::CopyJob *job = nullptr; QUrl oldurl = properties->url(); QString newFileName = KIO::encodeFileName(n); if (d->bDesktopFile && !newFileName.endsWith(QLatin1String(".desktop")) && !newFileName.endsWith(QLatin1String(".kdelnk"))) { newFileName += QLatin1String(".desktop"); } // Tell properties. Warning, this changes the result of properties->url() ! properties->rename(newFileName); // Update also relative path (for apps) if (!d->m_sRelativePath.isEmpty()) { determineRelativePath(properties->url().toLocalFile()); } // qDebug() << "New URL = " << properties->url(); // qDebug() << "old = " << oldurl.url(); // Don't remove the template !! if (!d->m_bFromTemplate) { // (normal renaming) job = KIO::moveAs(oldurl, properties->url()); } else { // Copying a template job = KIO::copyAs(oldurl, properties->url()); } connect(job, &KJob::result, this, &KFilePropsPlugin::slotCopyFinished); connect(job, &KIO::CopyJob::renamed, this, &KFilePropsPlugin::slotFileRenamed); // wait for job QEventLoop eventLoop; connect(this, &KFilePropsPlugin::leaveModality, &eventLoop, &QEventLoop::quit); eventLoop.exec(QEventLoop::ExcludeUserInputEvents); return; } properties->updateUrl(properties->url()); // Update also relative path (for apps) if (!d->m_sRelativePath.isEmpty()) { determineRelativePath(properties->url().toLocalFile()); } } // No job, keep going slotCopyFinished(nullptr); } void KFilePropsPlugin::slotCopyFinished(KJob *job) { // qDebug() << "KFilePropsPlugin::slotCopyFinished"; if (job) { // allow apply() to return emit leaveModality(); if (job->error()) { job->uiDelegate()->showErrorMessage(); // Didn't work. Revert the URL to the old one properties->updateUrl(static_cast(job)->srcUrls().constFirst()); properties->abortApplying(); // Don't apply the changes to the wrong file ! return; } } Q_ASSERT(!properties->item().isNull()); Q_ASSERT(!properties->item().url().isEmpty()); // Save the file locally if (d->bDesktopFile && !d->m_sRelativePath.isEmpty()) { // qDebug() << "KFilePropsPlugin::slotCopyFinished " << d->m_sRelativePath; const QString newPath = QStandardPaths::writableLocation(QStandardPaths::ApplicationsLocation) + QLatin1Char('/') + d->m_sRelativePath; const QUrl newURL = QUrl::fromLocalFile(newPath); // qDebug() << "KFilePropsPlugin::slotCopyFinished path=" << newURL; properties->updateUrl(newURL); } if (d->bKDesktopMode && d->bDesktopFile) { // Renamed? Update Name field // Note: The desktop ioslave does this as well, but not when // the file is copied from a template. if (d->m_bFromTemplate) { KIO::StatJob *job = KIO::stat(properties->url()); job->exec(); KIO::UDSEntry entry = job->statResult(); KFileItem item(entry, properties->url()); KDesktopFile config(item.localPath()); KConfigGroup cg = config.desktopGroup(); QString nameStr = nameFromFileName(properties->url().fileName()); cg.writeEntry("Name", nameStr); cg.writeEntry("Name", nameStr, KConfigGroup::Persistent | KConfigGroup::Localized); } } if (d->m_linkTargetLineEdit && !d->bMultiple) { const KFileItem item = properties->item(); const QString newTarget = d->m_linkTargetLineEdit->text(); if (newTarget != item.linkDest()) { // qDebug() << "Updating target of symlink to" << newTarget; KIO::Job *job = KIO::symlink(newTarget, item.url(), KIO::Overwrite); job->uiDelegate()->setAutoErrorHandlingEnabled(true); job->exec(); } } // "Link to Application" templates need to be made executable // Instead of matching against a filename we check if the destination // is an Application now. if (d->m_bFromTemplate) { // destination is not necessarily local, use the src template KDesktopFile templateResult(static_cast(job)->srcUrls().constFirst().toLocalFile()); if (templateResult.hasApplicationType()) { // We can either stat the file and add the +x bit or use the larger chmod() job // with a umask designed to only touch u+x. This is only one KIO job, so let's // do that. KFileItem appLink(properties->item()); KFileItemList fileItemList; fileItemList << appLink; // first 0100 adds u+x, second 0100 only allows chmod to change u+x KIO::Job *chmodJob = KIO::chmod(fileItemList, 0100, 0100, QString(), QString(), KIO::HideProgressInfo); chmodJob->exec(); } } } void KFilePropsPlugin::applyIconChanges() { KIconButton *iconButton = qobject_cast(d->iconArea); if (!iconButton || !d->bIconChanged) { return; } // handle icon changes - only local files (or pseudo-local) for now // TODO: Use KTempFile and KIO::file_copy with overwrite = true QUrl url = properties->url(); KIO::StatJob *job = KIO::mostLocalUrl(url); KJobWidgets::setWindow(job, properties); job->exec(); url = job->mostLocalUrl(); if (url.isLocalFile()) { QString path; if ((properties->item().mode() & QT_STAT_MASK) == QT_STAT_DIR) { path = url.toLocalFile() + QLatin1String("/.directory"); // don't call updateUrl because the other tabs (i.e. permissions) // apply to the directory, not the .directory file. } else { path = url.toLocalFile(); } // Get the default image QMimeDatabase db; QString str = db.mimeTypeForFile(url.toLocalFile(), QMimeDatabase::MatchExtension).iconName(); // Is it another one than the default ? QString sIcon; if (str != iconButton->icon()) { sIcon = iconButton->icon(); } // (otherwise write empty value) // qDebug() << "**" << path << "**"; // If default icon and no .directory file -> don't create one if (!sIcon.isEmpty() || QFile::exists(path)) { KDesktopFile cfg(path); // qDebug() << "sIcon = " << (sIcon); // qDebug() << "str = " << (str); cfg.desktopGroup().writeEntry("Icon", sIcon); cfg.sync(); cfg.reparseConfiguration(); if (cfg.desktopGroup().readEntry("Icon") != sIcon) { KMessageBox::sorry(nullptr, i18n("Could not save properties. You do not " "have sufficient access to write to %1.", path)); } } } } void KFilePropsPlugin::slotFileRenamed(KIO::Job *, const QUrl &, const QUrl &newUrl) { // This is called in case of an existing local file during the copy/move operation, // if the user chooses Rename. properties->updateUrl(newUrl); } void KFilePropsPlugin::postApplyChanges() { // Save the icon only after applying the permissions changes (#46192) applyIconChanges(); const KFileItemList items = properties->items(); const QList lst = items.urlList(); org::kde::KDirNotify::emitFilesChanged(QList(lst)); } class KFilePermissionsPropsPlugin::KFilePermissionsPropsPluginPrivate { public: KFilePermissionsPropsPluginPrivate() { } ~KFilePermissionsPropsPluginPrivate() { } QFrame *m_frame; QCheckBox *cbRecursive; QLabel *explanationLabel; KComboBox *ownerPermCombo, *groupPermCombo, *othersPermCombo; QCheckBox *extraCheckbox; mode_t partialPermissions; KFilePermissionsPropsPlugin::PermissionsMode pmode; bool canChangePermissions; bool isIrregular; bool hasExtendedACL; KACL extendedACL; KACL defaultACL; bool fileSystemSupportsACLs; KComboBox *grpCombo; KLineEdit *usrEdit; KLineEdit *grpEdit; // Old permissions mode_t permissions; // Old group QString strGroup; // Old owner QString strOwner; }; #define UniOwner (S_IRUSR|S_IWUSR|S_IXUSR) #define UniGroup (S_IRGRP|S_IWGRP|S_IXGRP) #define UniOthers (S_IROTH|S_IWOTH|S_IXOTH) #define UniRead (S_IRUSR|S_IRGRP|S_IROTH) #define UniWrite (S_IWUSR|S_IWGRP|S_IWOTH) #define UniExec (S_IXUSR|S_IXGRP|S_IXOTH) #define UniSpecial (S_ISUID|S_ISGID|S_ISVTX) // synced with PermissionsTarget const mode_t KFilePermissionsPropsPlugin::permissionsMasks[3] = {UniOwner, UniGroup, UniOthers}; const mode_t KFilePermissionsPropsPlugin::standardPermissions[4] = { 0, UniRead, UniRead | UniWrite, (mode_t) - 1 }; // synced with PermissionsMode and standardPermissions const char *const KFilePermissionsPropsPlugin::permissionsTexts[4][4] = { { I18N_NOOP("No Access"), I18N_NOOP("Can Only View"), I18N_NOOP("Can View & Modify"), nullptr }, { I18N_NOOP("No Access"), I18N_NOOP("Can Only View Content"), I18N_NOOP("Can View & Modify Content"), nullptr }, { nullptr, nullptr, nullptr, nullptr}, // no texts for links { I18N_NOOP("No Access"), I18N_NOOP("Can Only View/Read Content"), I18N_NOOP("Can View/Read & Modify/Write"), nullptr } }; KFilePermissionsPropsPlugin::KFilePermissionsPropsPlugin(KPropertiesDialog *_props) : KPropertiesDialogPlugin(_props), d(new KFilePermissionsPropsPluginPrivate) { d->cbRecursive = nullptr; d->grpCombo = nullptr; d->grpEdit = nullptr; d->usrEdit = nullptr; bool isLocal = properties->url().isLocalFile(); bool isTrash = (properties->url().scheme() == QLatin1String("trash")); KUser myself(KUser::UseEffectiveUID); const bool IamRoot = myself.isSuperUser(); const KFileItem item = properties->item(); bool isLink = item.isLink(); bool isDir = item.isDir(); // all dirs bool hasDir = item.isDir(); // at least one dir d->permissions = item.permissions(); // common permissions to all files d->partialPermissions = d->permissions; // permissions that only some files have (at first we take everything) d->isIrregular = isIrregular(d->permissions, isDir, isLink); d->strOwner = item.user(); d->strGroup = item.group(); d->hasExtendedACL = item.ACL().isExtended() || item.defaultACL().isValid(); d->extendedACL = item.ACL(); d->defaultACL = item.defaultACL(); d->fileSystemSupportsACLs = false; if (properties->items().count() > 1) { // Multiple items: see what they have in common const KFileItemList items = properties->items(); KFileItemList::const_iterator it = items.begin(); const KFileItemList::const_iterator kend = items.end(); for (++it /*no need to check the first one again*/; it != kend; ++it) { if (!d->isIrregular) d->isIrregular |= isIrregular((*it).permissions(), (*it).isDir() == isDir, (*it).isLink() == isLink); d->hasExtendedACL = d->hasExtendedACL || (*it).hasExtendedACL(); if ((*it).isLink() != isLink) { isLink = false; } if ((*it).isDir() != isDir) { isDir = false; } hasDir |= (*it).isDir(); if ((*it).permissions() != d->permissions) { d->permissions &= (*it).permissions(); d->partialPermissions |= (*it).permissions(); } if ((*it).user() != d->strOwner) { d->strOwner.clear(); } if ((*it).group() != d->strGroup) { d->strGroup.clear(); } } } if (isLink) { d->pmode = PermissionsOnlyLinks; } else if (isDir) { d->pmode = PermissionsOnlyDirs; } else if (hasDir) { d->pmode = PermissionsMixed; } else { d->pmode = PermissionsOnlyFiles; } // keep only what's not in the common permissions d->partialPermissions = d->partialPermissions & ~d->permissions; bool isMyFile = false; if (isLocal && !d->strOwner.isEmpty()) { // local files, and all owned by the same person if (myself.isValid()) { isMyFile = (d->strOwner == myself.loginName()); } else { qCWarning(KIO_WIDGETS) << "I don't exist ?! geteuid=" << KUserId::currentEffectiveUserId().toString(); } } else { //We don't know, for remote files, if they are ours or not. //So we let the user change permissions, and //KIO::chmod will tell, if he had no right to do it. isMyFile = true; } d->canChangePermissions = (isMyFile || IamRoot) && (!isLink); // create GUI d->m_frame = new QFrame(); properties->addPage(d->m_frame, i18n("&Permissions")); QBoxLayout *box = new QVBoxLayout(d->m_frame); box->setContentsMargins(0, 0, 0, 0); QWidget *l; QLabel *lbl; QGroupBox *gb; QGridLayout *gl; QPushButton *pbAdvancedPerm = nullptr; /* Group: Access Permissions */ gb = new QGroupBox(i18n("Access Permissions"), d->m_frame); box->addWidget(gb); gl = new QGridLayout(gb); gl->setColumnStretch(1, 1); l = d->explanationLabel = new QLabel(gb); if (isLink) d->explanationLabel->setText(i18np("This file is a link and does not have permissions.", "All files are links and do not have permissions.", properties->items().count())); else if (!d->canChangePermissions) { d->explanationLabel->setText(i18n("Only the owner can change permissions.")); } gl->addWidget(l, 0, 0, 1, 2); lbl = new QLabel(i18n("O&wner:"), gb); gl->addWidget(lbl, 1, 0, Qt::AlignRight); l = d->ownerPermCombo = new KComboBox(gb); lbl->setBuddy(l); gl->addWidget(l, 1, 1); connect(d->ownerPermCombo, QOverload::of(&QComboBox::activated), this, &KPropertiesDialogPlugin::changed); l->setWhatsThis(i18n("Specifies the actions that the owner is allowed to do.")); lbl = new QLabel(i18n("Gro&up:"), gb); gl->addWidget(lbl, 2, 0, Qt::AlignRight); l = d->groupPermCombo = new KComboBox(gb); lbl->setBuddy(l); gl->addWidget(l, 2, 1); connect(d->groupPermCombo, QOverload::of(&QComboBox::activated), this, &KPropertiesDialogPlugin::changed); l->setWhatsThis(i18n("Specifies the actions that the members of the group are allowed to do.")); lbl = new QLabel(i18n("O&thers:"), gb); gl->addWidget(lbl, 3, 0, Qt::AlignRight); l = d->othersPermCombo = new KComboBox(gb); lbl->setBuddy(l); gl->addWidget(l, 3, 1); connect(d->othersPermCombo, QOverload::of(&QComboBox::activated), this, &KPropertiesDialogPlugin::changed); l->setWhatsThis(i18n("Specifies the actions that all users, who are neither " "owner nor in the group, are allowed to do.")); if (!isLink) { l = d->extraCheckbox = new QCheckBox(hasDir ? i18n("Only own&er can rename and delete folder content") : i18n("Is &executable"), gb); connect(d->extraCheckbox, &QAbstractButton::clicked, this, &KPropertiesDialogPlugin::changed); gl->addWidget(l, 4, 1); l->setWhatsThis(hasDir ? i18n("Enable this option to allow only the folder's owner to " "delete or rename the contained files and folders. Other " "users can only add new files, which requires the 'Modify " "Content' permission.") : i18n("Enable this option to mark the file as executable. This only makes " "sense for programs and scripts. It is required when you want to " "execute them.")); QLayoutItem *spacer = new QSpacerItem(0, 20, QSizePolicy::Minimum, QSizePolicy::Expanding); gl->addItem(spacer, 5, 0, 1, 3); pbAdvancedPerm = new QPushButton(i18n("A&dvanced Permissions"), gb); gl->addWidget(pbAdvancedPerm, 6, 0, 1, 2, Qt::AlignRight); connect(pbAdvancedPerm, &QAbstractButton::clicked, this, &KFilePermissionsPropsPlugin::slotShowAdvancedPermissions); } else { d->extraCheckbox = nullptr; } /**** Group: Ownership ****/ gb = new QGroupBox(i18n("Ownership"), d->m_frame); box->addWidget(gb); gl = new QGridLayout(gb); gl->addItem(new QSpacerItem(0, 10), 0, 0); /*** Set Owner ***/ l = new QLabel(i18n("User:"), gb); gl->addWidget(l, 1, 0, Qt::AlignRight); /* GJ: Don't autocomplete more than 1000 users. This is a kind of random * value. Huge sites having 10.000+ user have a fair chance of using NIS, * (possibly) making this unacceptably slow. * OTOH, it is nice to offer this functionality for the standard user. */ int maxEntries = 1000; /* File owner: For root, offer a KLineEdit with autocompletion. * For a user, who can never chown() a file, offer a QLabel. */ if (IamRoot && isLocal) { d->usrEdit = new KLineEdit(gb); KCompletion *kcom = d->usrEdit->completionObject(); kcom->setOrder(KCompletion::Sorted); QStringList userNames = KUser::allUserNames(maxEntries); kcom->setItems(userNames); d->usrEdit->setCompletionMode((userNames.size() < maxEntries) ? KCompletion::CompletionAuto : KCompletion::CompletionNone); d->usrEdit->setText(d->strOwner); gl->addWidget(d->usrEdit, 1, 1); connect(d->usrEdit, &QLineEdit::textChanged, this, &KPropertiesDialogPlugin::changed); } else { l = new QLabel(d->strOwner, gb); gl->addWidget(l, 1, 1); } /*** Set Group ***/ KUser user(KUser::UseEffectiveUID); QStringList groupList = user.groupNames(); const bool isMyGroup = groupList.contains(d->strGroup); /* add the group the file currently belongs to .. * .. if it is not there already */ if (!isMyGroup) { groupList += d->strGroup; } l = new QLabel(i18n("Group:"), gb); gl->addWidget(l, 2, 0, Qt::AlignRight); /* Set group: if possible to change: * - Offer a KLineEdit for root, since he can change to any group. * - Offer a KComboBox for a normal user, since he can change to a fixed * (small) set of groups only. * If not changeable: offer a QLabel. */ if (IamRoot && isLocal) { d->grpEdit = new KLineEdit(gb); KCompletion *kcom = new KCompletion; kcom->setItems(groupList); d->grpEdit->setCompletionObject(kcom, true); d->grpEdit->setAutoDeleteCompletionObject(true); d->grpEdit->setCompletionMode(KCompletion::CompletionAuto); d->grpEdit->setText(d->strGroup); gl->addWidget(d->grpEdit, 2, 1); connect(d->grpEdit, &QLineEdit::textChanged, this, &KPropertiesDialogPlugin::changed); } else if ((groupList.count() > 1) && isMyFile && isLocal) { d->grpCombo = new KComboBox(gb); d->grpCombo->setObjectName(QStringLiteral("combogrouplist")); d->grpCombo->addItems(groupList); d->grpCombo->setCurrentIndex(groupList.indexOf(d->strGroup)); gl->addWidget(d->grpCombo, 2, 1); connect(d->grpCombo, QOverload::of(&QComboBox::activated), this, &KPropertiesDialogPlugin::changed); } else { l = new QLabel(d->strGroup, gb); gl->addWidget(l, 2, 1); } gl->setColumnStretch(2, 10); // "Apply recursive" checkbox if (hasDir && !isLink && !isTrash) { d->cbRecursive = new QCheckBox(i18n("Apply changes to all subfolders and their contents"), d->m_frame); connect(d->cbRecursive, &QAbstractButton::clicked, this, &KPropertiesDialogPlugin::changed); box->addWidget(d->cbRecursive); } updateAccessControls(); if (isTrash) { //don't allow to change properties for file into trash enableAccessControls(false); if (pbAdvancedPerm) { pbAdvancedPerm->setEnabled(false); } } box->addStretch(10); } #if HAVE_POSIX_ACL static bool fileSystemSupportsACL(const QByteArray &path) { bool fileSystemSupportsACLs = false; #ifdef Q_OS_FREEBSD struct statfs buf; fileSystemSupportsACLs = (statfs(path.data(), &buf) == 0) && (buf.f_flags & MNT_ACLS); #elif defined Q_OS_MACOS fileSystemSupportsACLs = getxattr(path.data(), "system.posix_acl_access", nullptr, 0, 0, XATTR_NOFOLLOW) >= 0 || errno == ENODATA; #else fileSystemSupportsACLs = getxattr(path.data(), "system.posix_acl_access", nullptr, 0) >= 0 || errno == ENODATA; #endif return fileSystemSupportsACLs; } #endif void KFilePermissionsPropsPlugin::slotShowAdvancedPermissions() { bool isDir = (d->pmode == PermissionsOnlyDirs) || (d->pmode == PermissionsMixed); QDialog dlg(properties); dlg.setModal(true); dlg.setWindowTitle(i18n("Advanced Permissions")); QLabel *l, *cl[3]; QGroupBox *gb; QGridLayout *gl; QVBoxLayout *vbox = new QVBoxLayout; dlg.setLayout(vbox); // Group: Access Permissions gb = new QGroupBox(i18n("Access Permissions"), &dlg); vbox->addWidget(gb); gl = new QGridLayout(gb); gl->addItem(new QSpacerItem(0, 10), 0, 0); QVector theNotSpecials; l = new QLabel(i18n("Class"), gb); gl->addWidget(l, 1, 0); theNotSpecials.append(l); QString readWhatsThis; QString readLabel; if (isDir) { readLabel = i18n("Show\nEntries"); readWhatsThis = i18n("This flag allows viewing the content of the folder."); } else { readLabel = i18n("Read"); readWhatsThis = i18n("The Read flag allows viewing the content of the file."); } QString writeWhatsThis; QString writeLabel; if (isDir) { writeLabel = i18n("Write\nEntries"); writeWhatsThis = i18n("This flag allows adding, renaming and deleting of files. " "Note that deleting and renaming can be limited using the Sticky flag."); } else { writeLabel = i18n("Write"); writeWhatsThis = i18n("The Write flag allows modifying the content of the file."); } QString execLabel; QString execWhatsThis; if (isDir) { execLabel = i18nc("Enter folder", "Enter"); execWhatsThis = i18n("Enable this flag to allow entering the folder."); } else { execLabel = i18n("Exec"); execWhatsThis = i18n("Enable this flag to allow executing the file as a program."); } // GJ: Add space between normal and special modes QSize size = l->sizeHint(); size.setWidth(size.width() + 15); l->setFixedSize(size); gl->addWidget(l, 1, 3); l = new QLabel(i18n("Special"), gb); gl->addWidget(l, 1, 4, 1, 1); QString specialWhatsThis; if (isDir) specialWhatsThis = i18n("Special flag. Valid for the whole folder, the exact " "meaning of the flag can be seen in the right hand column."); else specialWhatsThis = i18n("Special flag. The exact meaning of the flag can be seen " "in the right hand column."); l->setWhatsThis(specialWhatsThis); cl[0] = new QLabel(i18n("User"), gb); gl->addWidget(cl[0], 2, 0); theNotSpecials.append(cl[0]); cl[1] = new QLabel(i18n("Group"), gb); gl->addWidget(cl[1], 3, 0); theNotSpecials.append(cl[1]); cl[2] = new QLabel(i18n("Others"), gb); gl->addWidget(cl[2], 4, 0); theNotSpecials.append(cl[2]); QString setUidWhatsThis; if (isDir) setUidWhatsThis = i18n("If this flag is set, the owner of this folder will be " "the owner of all new files."); else setUidWhatsThis = i18n("If this file is an executable and the flag is set, it will " "be executed with the permissions of the owner."); QString setGidWhatsThis; if (isDir) setGidWhatsThis = i18n("If this flag is set, the group of this folder will be " "set for all new files."); else setGidWhatsThis = i18n("If this file is an executable and the flag is set, it will " "be executed with the permissions of the group."); QString stickyWhatsThis; if (isDir) stickyWhatsThis = i18n("If the Sticky flag is set on a folder, only the owner " "and root can delete or rename files. Otherwise everybody " "with write permissions can do this."); else stickyWhatsThis = i18n("The Sticky flag on a file is ignored on Linux, but may " "be used on some systems"); mode_t aPermissions, aPartialPermissions; mode_t dummy1, dummy2; if (!d->isIrregular) { switch (d->pmode) { case PermissionsOnlyFiles: getPermissionMasks(aPartialPermissions, dummy1, aPermissions, dummy2); break; case PermissionsOnlyDirs: case PermissionsMixed: getPermissionMasks(dummy1, aPartialPermissions, dummy2, aPermissions); break; case PermissionsOnlyLinks: aPermissions = UniRead | UniWrite | UniExec | UniSpecial; aPartialPermissions = 0; break; } } else { aPermissions = d->permissions; aPartialPermissions = d->partialPermissions; } // Draw Checkboxes QCheckBox *cba[3][4]; for (int row = 0; row < 3; ++row) { for (int col = 0; col < 4; ++col) { QCheckBox *cb = new QCheckBox(gb); if (col != 3) { theNotSpecials.append(cb); } cba[row][col] = cb; cb->setChecked(aPermissions & fperm[row][col]); if (aPartialPermissions & fperm[row][col]) { cb->setTristate(); cb->setCheckState(Qt::PartiallyChecked); } else if (d->cbRecursive && d->cbRecursive->isChecked()) { cb->setTristate(); } cb->setEnabled(d->canChangePermissions); gl->addWidget(cb, row + 2, col + 1); switch (col) { case 0: cb->setText(readLabel); cb->setWhatsThis(readWhatsThis); break; case 1: cb->setText(writeLabel); cb->setWhatsThis(writeWhatsThis); break; case 2: cb->setText(execLabel); cb->setWhatsThis(execWhatsThis); break; case 3: switch (row) { case 0: cb->setText(i18n("Set UID")); cb->setWhatsThis(setUidWhatsThis); break; case 1: cb->setText(i18n("Set GID")); cb->setWhatsThis(setGidWhatsThis); break; case 2: cb->setText(i18nc("File permission", "Sticky")); cb->setWhatsThis(stickyWhatsThis); break; } break; } } } gl->setColumnStretch(6, 10); #if HAVE_POSIX_ACL KACLEditWidget *extendedACLs = nullptr; // FIXME make it work with partial entries if (properties->items().count() == 1) { QByteArray path = QFile::encodeName(properties->item().url().toLocalFile()); d->fileSystemSupportsACLs = fileSystemSupportsACL(path); } if (d->fileSystemSupportsACLs) { std::for_each(theNotSpecials.begin(), theNotSpecials.end(), std::mem_fun(&QWidget::hide)); extendedACLs = new KACLEditWidget(&dlg); extendedACLs->setEnabled(d->canChangePermissions); vbox->addWidget(extendedACLs); if (d->extendedACL.isValid() && d->extendedACL.isExtended()) { extendedACLs->setACL(d->extendedACL); } else { extendedACLs->setACL(KACL(aPermissions)); } if (d->defaultACL.isValid()) { extendedACLs->setDefaultACL(d->defaultACL); } if (properties->items().constFirst().isDir()) { extendedACLs->setAllowDefaults(true); } } #endif QDialogButtonBox *buttonBox = new QDialogButtonBox(&dlg); buttonBox->setStandardButtons(QDialogButtonBox::Ok | QDialogButtonBox::Cancel); connect(buttonBox, &QDialogButtonBox::accepted, &dlg, &QDialog::accept); connect(buttonBox, &QDialogButtonBox::rejected, &dlg, &QDialog::reject); vbox->addWidget(buttonBox); if (dlg.exec() != QDialog::Accepted) { return; } mode_t andPermissions = mode_t(~0); mode_t orPermissions = 0; for (int row = 0; row < 3; ++row) for (int col = 0; col < 4; ++col) { switch (cba[row][col]->checkState()) { case Qt::Checked: orPermissions |= fperm[row][col]; //fall through case Qt::Unchecked: andPermissions &= ~fperm[row][col]; break; case Qt::PartiallyChecked: break; } } d->isIrregular = false; const KFileItemList items = properties->items(); KFileItemList::const_iterator it = items.begin(); const KFileItemList::const_iterator kend = items.end(); for (; it != kend; ++it) { if (isIrregular(((*it).permissions() & andPermissions) | orPermissions, (*it).isDir(), (*it).isLink())) { d->isIrregular = true; break; } } d->permissions = orPermissions; d->partialPermissions = andPermissions; #if HAVE_POSIX_ACL // override with the acls, if present if (extendedACLs) { d->extendedACL = extendedACLs->getACL(); d->defaultACL = extendedACLs->getDefaultACL(); d->hasExtendedACL = d->extendedACL.isExtended() || d->defaultACL.isValid(); d->permissions = d->extendedACL.basePermissions(); d->permissions |= (andPermissions | orPermissions) & (S_ISUID | S_ISGID | S_ISVTX); } #endif updateAccessControls(); emit changed(); } // QString KFilePermissionsPropsPlugin::tabName () const // { // return i18n ("&Permissions"); // } KFilePermissionsPropsPlugin::~KFilePermissionsPropsPlugin() { delete d; } bool KFilePermissionsPropsPlugin::supports(const KFileItemList & /*_items*/) { return true; } // sets a combo box in the Access Control frame void KFilePermissionsPropsPlugin::setComboContent(QComboBox *combo, PermissionsTarget target, mode_t permissions, mode_t partial) { combo->clear(); if (d->isIrregular) { //#176876 return; } if (d->pmode == PermissionsOnlyLinks) { combo->addItem(i18n("Link")); combo->setCurrentIndex(0); return; } mode_t tMask = permissionsMasks[target]; int textIndex; for (textIndex = 0; standardPermissions[textIndex] != (mode_t) - 1; textIndex++) { if ((standardPermissions[textIndex]&tMask) == (permissions & tMask & (UniRead | UniWrite))) { break; } } Q_ASSERT(standardPermissions[textIndex] != (mode_t) - 1); // must not happen, would be irreglar for (int i = 0; permissionsTexts[(int)d->pmode][i]; i++) { combo->addItem(i18n(permissionsTexts[(int)d->pmode][i])); } if (partial & tMask & ~UniExec) { combo->addItem(i18n("Varying (No Change)")); combo->setCurrentIndex(3); } else { combo->setCurrentIndex(textIndex); } } // permissions are irregular if they cant be displayed in a combo box. bool KFilePermissionsPropsPlugin::isIrregular(mode_t permissions, bool isDir, bool isLink) { if (isLink) { // links are always ok return false; } mode_t p = permissions; if (p & (S_ISUID | S_ISGID)) { // setuid/setgid -> irregular return true; } if (isDir) { p &= ~S_ISVTX; // ignore sticky on dirs // check supported flag combinations mode_t p0 = p & UniOwner; if ((p0 != 0) && (p0 != (S_IRUSR | S_IXUSR)) && (p0 != UniOwner)) { return true; } p0 = p & UniGroup; if ((p0 != 0) && (p0 != (S_IRGRP | S_IXGRP)) && (p0 != UniGroup)) { return true; } p0 = p & UniOthers; if ((p0 != 0) && (p0 != (S_IROTH | S_IXOTH)) && (p0 != UniOthers)) { return true; } return false; } if (p & S_ISVTX) { // sticky on file -> irregular return true; } // check supported flag combinations mode_t p0 = p & UniOwner; bool usrXPossible = !p0; // true if this file could be an executable if (p0 & S_IXUSR) { if ((p0 == S_IXUSR) || (p0 == (S_IWUSR | S_IXUSR))) { return true; } usrXPossible = true; } else if (p0 == S_IWUSR) { return true; } p0 = p & UniGroup; bool grpXPossible = !p0; // true if this file could be an executable if (p0 & S_IXGRP) { if ((p0 == S_IXGRP) || (p0 == (S_IWGRP | S_IXGRP))) { return true; } grpXPossible = true; } else if (p0 == S_IWGRP) { return true; } if (p0 == 0) { grpXPossible = true; } p0 = p & UniOthers; bool othXPossible = !p0; // true if this file could be an executable if (p0 & S_IXOTH) { if ((p0 == S_IXOTH) || (p0 == (S_IWOTH | S_IXOTH))) { return true; } othXPossible = true; } else if (p0 == S_IWOTH) { return true; } // check that there either all targets are executable-compatible, or none return (p & UniExec) && !(usrXPossible && grpXPossible && othXPossible); } // enables/disabled the widgets in the Access Control frame void KFilePermissionsPropsPlugin::enableAccessControls(bool enable) { d->ownerPermCombo->setEnabled(enable); d->groupPermCombo->setEnabled(enable); d->othersPermCombo->setEnabled(enable); if (d->extraCheckbox) { d->extraCheckbox->setEnabled(enable); } if (d->cbRecursive) { d->cbRecursive->setEnabled(enable); } } // updates all widgets in the Access Control frame void KFilePermissionsPropsPlugin::updateAccessControls() { setComboContent(d->ownerPermCombo, PermissionsOwner, d->permissions, d->partialPermissions); setComboContent(d->groupPermCombo, PermissionsGroup, d->permissions, d->partialPermissions); setComboContent(d->othersPermCombo, PermissionsOthers, d->permissions, d->partialPermissions); switch (d->pmode) { case PermissionsOnlyLinks: enableAccessControls(false); break; case PermissionsOnlyFiles: enableAccessControls(d->canChangePermissions && !d->isIrregular && !d->hasExtendedACL); if (d->canChangePermissions) d->explanationLabel->setText(d->isIrregular || d->hasExtendedACL ? i18np("This file uses advanced permissions", "These files use advanced permissions.", properties->items().count()) : QString()); if (d->partialPermissions & UniExec) { d->extraCheckbox->setTristate(); d->extraCheckbox->setCheckState(Qt::PartiallyChecked); } else { d->extraCheckbox->setTristate(false); d->extraCheckbox->setChecked(d->permissions & UniExec); } break; case PermissionsOnlyDirs: enableAccessControls(d->canChangePermissions && !d->isIrregular && !d->hasExtendedACL); // if this is a dir, and we can change permissions, don't dis-allow // recursive, we can do that for ACL setting. if (d->cbRecursive) { d->cbRecursive->setEnabled(d->canChangePermissions && !d->isIrregular); } if (d->canChangePermissions) d->explanationLabel->setText(d->isIrregular || d->hasExtendedACL ? i18np("This folder uses advanced permissions.", "These folders use advanced permissions.", properties->items().count()) : QString()); if (d->partialPermissions & S_ISVTX) { d->extraCheckbox->setTristate(); d->extraCheckbox->setCheckState(Qt::PartiallyChecked); } else { d->extraCheckbox->setTristate(false); d->extraCheckbox->setChecked(d->permissions & S_ISVTX); } break; case PermissionsMixed: enableAccessControls(d->canChangePermissions && !d->isIrregular && !d->hasExtendedACL); if (d->canChangePermissions) d->explanationLabel->setText(d->isIrregular || d->hasExtendedACL ? i18n("These files use advanced permissions.") : QString()); if (d->partialPermissions & S_ISVTX) { d->extraCheckbox->setTristate(); d->extraCheckbox->setCheckState(Qt::PartiallyChecked); } else { d->extraCheckbox->setTristate(false); d->extraCheckbox->setChecked(d->permissions & S_ISVTX); } break; } } // gets masks for files and dirs from the Access Control frame widgets void KFilePermissionsPropsPlugin::getPermissionMasks(mode_t &andFilePermissions, mode_t &andDirPermissions, mode_t &orFilePermissions, mode_t &orDirPermissions) { andFilePermissions = mode_t(~UniSpecial); andDirPermissions = mode_t(~(S_ISUID | S_ISGID)); orFilePermissions = 0; orDirPermissions = 0; if (d->isIrregular) { return; } mode_t m = standardPermissions[d->ownerPermCombo->currentIndex()]; if (m != (mode_t) - 1) { orFilePermissions |= m & UniOwner; if ((m & UniOwner) && ((d->pmode == PermissionsMixed) || ((d->pmode == PermissionsOnlyFiles) && (d->extraCheckbox->checkState() == Qt::PartiallyChecked)))) { andFilePermissions &= ~(S_IRUSR | S_IWUSR); } else { andFilePermissions &= ~(S_IRUSR | S_IWUSR | S_IXUSR); if ((m & S_IRUSR) && (d->extraCheckbox->checkState() == Qt::Checked)) { orFilePermissions |= S_IXUSR; } } orDirPermissions |= m & UniOwner; if (m & S_IRUSR) { orDirPermissions |= S_IXUSR; } andDirPermissions &= ~(S_IRUSR | S_IWUSR | S_IXUSR); } m = standardPermissions[d->groupPermCombo->currentIndex()]; if (m != (mode_t) - 1) { orFilePermissions |= m & UniGroup; if ((m & UniGroup) && ((d->pmode == PermissionsMixed) || ((d->pmode == PermissionsOnlyFiles) && (d->extraCheckbox->checkState() == Qt::PartiallyChecked)))) { andFilePermissions &= ~(S_IRGRP | S_IWGRP); } else { andFilePermissions &= ~(S_IRGRP | S_IWGRP | S_IXGRP); if ((m & S_IRGRP) && (d->extraCheckbox->checkState() == Qt::Checked)) { orFilePermissions |= S_IXGRP; } } orDirPermissions |= m & UniGroup; if (m & S_IRGRP) { orDirPermissions |= S_IXGRP; } andDirPermissions &= ~(S_IRGRP | S_IWGRP | S_IXGRP); } m = d->othersPermCombo->currentIndex() >= 0 ? standardPermissions[d->othersPermCombo->currentIndex()] : (mode_t) - 1; if (m != (mode_t) - 1) { orFilePermissions |= m & UniOthers; if ((m & UniOthers) && ((d->pmode == PermissionsMixed) || ((d->pmode == PermissionsOnlyFiles) && (d->extraCheckbox->checkState() == Qt::PartiallyChecked)))) { andFilePermissions &= ~(S_IROTH | S_IWOTH); } else { andFilePermissions &= ~(S_IROTH | S_IWOTH | S_IXOTH); if ((m & S_IROTH) && (d->extraCheckbox->checkState() == Qt::Checked)) { orFilePermissions |= S_IXOTH; } } orDirPermissions |= m & UniOthers; if (m & S_IROTH) { orDirPermissions |= S_IXOTH; } andDirPermissions &= ~(S_IROTH | S_IWOTH | S_IXOTH); } if (((d->pmode == PermissionsMixed) || (d->pmode == PermissionsOnlyDirs)) && (d->extraCheckbox->checkState() != Qt::PartiallyChecked)) { andDirPermissions &= ~S_ISVTX; if (d->extraCheckbox->checkState() == Qt::Checked) { orDirPermissions |= S_ISVTX; } } } void KFilePermissionsPropsPlugin::applyChanges() { mode_t orFilePermissions; mode_t orDirPermissions; mode_t andFilePermissions; mode_t andDirPermissions; if (!d->canChangePermissions) { return; } if (!d->isIrregular) getPermissionMasks(andFilePermissions, andDirPermissions, orFilePermissions, orDirPermissions); else { orFilePermissions = d->permissions; andFilePermissions = d->partialPermissions; orDirPermissions = d->permissions; andDirPermissions = d->partialPermissions; } QString owner, group; if (d->usrEdit) { owner = d->usrEdit->text(); } if (d->grpEdit) { group = d->grpEdit->text(); } else if (d->grpCombo) { group = d->grpCombo->currentText(); } if (owner == d->strOwner) { owner.clear(); // no change } if (group == d->strGroup) { group.clear(); } bool recursive = d->cbRecursive && d->cbRecursive->isChecked(); bool permissionChange = false; KFileItemList files, dirs; const KFileItemList items = properties->items(); KFileItemList::const_iterator it = items.begin(); const KFileItemList::const_iterator kend = items.end(); for (; it != kend; ++it) { if ((*it).isDir()) { dirs.append(*it); if ((*it).permissions() != (((*it).permissions() & andDirPermissions) | orDirPermissions)) { permissionChange = true; } } else if ((*it).isFile()) { files.append(*it); if ((*it).permissions() != (((*it).permissions() & andFilePermissions) | orFilePermissions)) { permissionChange = true; } } } const bool ACLChange = (d->extendedACL != properties->item().ACL()); const bool defaultACLChange = (d->defaultACL != properties->item().defaultACL()); if (owner.isEmpty() && group.isEmpty() && !recursive && !permissionChange && !ACLChange && !defaultACLChange) { return; } KIO::Job *job; if (!files.isEmpty()) { job = KIO::chmod(files, orFilePermissions, ~andFilePermissions, owner, group, false); if (ACLChange && d->fileSystemSupportsACLs) { job->addMetaData(QStringLiteral("ACL_STRING"), d->extendedACL.isValid() ? d->extendedACL.asString() : QStringLiteral("ACL_DELETE")); } if (defaultACLChange && d->fileSystemSupportsACLs) { job->addMetaData(QStringLiteral("DEFAULT_ACL_STRING"), d->defaultACL.isValid() ? d->defaultACL.asString() : QStringLiteral("ACL_DELETE")); } connect(job, &KJob::result, this, &KFilePermissionsPropsPlugin::slotChmodResult); QEventLoop eventLoop; connect(this, &KFilePermissionsPropsPlugin::leaveModality, &eventLoop, &QEventLoop::quit); eventLoop.exec(QEventLoop::ExcludeUserInputEvents); } if (!dirs.isEmpty()) { job = KIO::chmod(dirs, orDirPermissions, ~andDirPermissions, owner, group, recursive); if (ACLChange && d->fileSystemSupportsACLs) { job->addMetaData(QStringLiteral("ACL_STRING"), d->extendedACL.isValid() ? d->extendedACL.asString() : QStringLiteral("ACL_DELETE")); } if (defaultACLChange && d->fileSystemSupportsACLs) { job->addMetaData(QStringLiteral("DEFAULT_ACL_STRING"), d->defaultACL.isValid() ? d->defaultACL.asString() : QStringLiteral("ACL_DELETE")); } connect(job, &KJob::result, this, &KFilePermissionsPropsPlugin::slotChmodResult); QEventLoop eventLoop; connect(this, &KFilePermissionsPropsPlugin::leaveModality, &eventLoop, &QEventLoop::quit); eventLoop.exec(QEventLoop::ExcludeUserInputEvents); } } void KFilePermissionsPropsPlugin::slotChmodResult(KJob *job) { // qDebug() << "KFilePermissionsPropsPlugin::slotChmodResult"; if (job->error()) { job->uiDelegate()->showErrorMessage(); } // allow apply() to return emit leaveModality(); } class KChecksumsPlugin::KChecksumsPluginPrivate { public: KChecksumsPluginPrivate() { } ~KChecksumsPluginPrivate() { } QWidget m_widget; Ui::ChecksumsWidget m_ui; QFileSystemWatcher fileWatcher; QString m_md5; QString m_sha1; QString m_sha256; }; KChecksumsPlugin::KChecksumsPlugin(KPropertiesDialog *dialog) : KPropertiesDialogPlugin(dialog), d(new KChecksumsPluginPrivate) { d->m_ui.setupUi(&d->m_widget); properties->addPage(&d->m_widget, i18nc("@title:tab", "C&hecksums")); d->m_ui.md5CopyButton->hide(); d->m_ui.sha1CopyButton->hide(); d->m_ui.sha256CopyButton->hide(); connect(d->m_ui.lineEdit, &QLineEdit::textChanged, this, [=](const QString &text) { slotVerifyChecksum(text.toLower()); }); connect(d->m_ui.md5Button, &QPushButton::clicked, this, &KChecksumsPlugin::slotShowMd5); connect(d->m_ui.sha1Button, &QPushButton::clicked, this, &KChecksumsPlugin::slotShowSha1); connect(d->m_ui.sha256Button, &QPushButton::clicked, this, &KChecksumsPlugin::slotShowSha256); d->fileWatcher.addPath(properties->item().localPath()); connect(&d->fileWatcher, &QFileSystemWatcher::fileChanged, this, &KChecksumsPlugin::slotInvalidateCache); auto clipboard = QApplication::clipboard(); connect(d->m_ui.md5CopyButton, &QPushButton::clicked, this, [=]() { clipboard->setText(d->m_md5); }); connect(d->m_ui.sha1CopyButton, &QPushButton::clicked, this, [=]() { clipboard->setText(d->m_sha1); }); connect(d->m_ui.sha256CopyButton, &QPushButton::clicked, this, [=]() { clipboard->setText(d->m_sha256); }); connect(d->m_ui.pasteButton, &QPushButton::clicked, this, [=]() { d->m_ui.lineEdit->setText(clipboard->text()); }); setDefaultState(); } KChecksumsPlugin::~KChecksumsPlugin() { delete d; } bool KChecksumsPlugin::supports(const KFileItemList &items) { if (items.count() != 1) { return false; } const KFileItem item = items.first(); return item.isFile() && !item.localPath().isEmpty() && item.isReadable() && !item.isDesktopFile() && !item.isLink(); } void KChecksumsPlugin::slotInvalidateCache() { d->m_md5 = QString(); d->m_sha1 = QString(); d->m_sha256 = QString(); } void KChecksumsPlugin::slotShowMd5() { auto label = new QLabel(i18nc("@action:button", "Calculating..."), &d->m_widget); label->setTextInteractionFlags(Qt::TextSelectableByMouse | Qt::TextSelectableByKeyboard); d->m_ui.calculateWidget->layout()->replaceWidget(d->m_ui.md5Button, label); d->m_ui.md5Button->hide(); showChecksum(QCryptographicHash::Md5, label, d->m_ui.md5CopyButton); } void KChecksumsPlugin::slotShowSha1() { auto label = new QLabel(i18nc("@action:button", "Calculating..."), &d->m_widget); label->setTextInteractionFlags(Qt::TextSelectableByMouse | Qt::TextSelectableByKeyboard); d->m_ui.calculateWidget->layout()->replaceWidget(d->m_ui.sha1Button, label); d->m_ui.sha1Button->hide(); showChecksum(QCryptographicHash::Sha1, label, d->m_ui.sha1CopyButton); } void KChecksumsPlugin::slotShowSha256() { auto label = new QLabel(i18nc("@action:button", "Calculating..."), &d->m_widget); label->setTextInteractionFlags(Qt::TextSelectableByMouse | Qt::TextSelectableByKeyboard); d->m_ui.calculateWidget->layout()->replaceWidget(d->m_ui.sha256Button, label); d->m_ui.sha256Button->hide(); showChecksum(QCryptographicHash::Sha256, label, d->m_ui.sha256CopyButton); } void KChecksumsPlugin::slotVerifyChecksum(const QString &input) { auto algorithm = detectAlgorithm(input); // Input is not a supported hash algorithm. if (algorithm == QCryptographicHash::Md4) { if (input.isEmpty()) { setDefaultState(); } else { setInvalidChecksumState(); } return; } const QString checksum = cachedChecksum(algorithm); // Checksum already in cache. if (!checksum.isEmpty()) { const bool isMatch = (checksum == input); if (isMatch) { setMatchState(); } else { setMismatchState(); } return; } // Calculate checksum in another thread. auto futureWatcher = new QFutureWatcher(this); connect(futureWatcher, &QFutureWatcher::finished, this, [=]() { const QString checksum = futureWatcher->result(); futureWatcher->deleteLater(); cacheChecksum(checksum, algorithm); switch (algorithm) { case QCryptographicHash::Md5: slotShowMd5(); break; case QCryptographicHash::Sha1: slotShowSha1(); break; case QCryptographicHash::Sha256: slotShowSha256(); break; default: break; } const bool isMatch = (checksum == input); if (isMatch) { setMatchState(); } else { setMismatchState(); } }); // Notify the user about the background computation. setVerifyState(); auto future = QtConcurrent::run(&KChecksumsPlugin::computeChecksum, algorithm, properties->item().localPath()); futureWatcher->setFuture(future); } bool KChecksumsPlugin::isMd5(const QString &input) { QRegularExpression regex(QStringLiteral("^[a-f0-9]{32}$")); return regex.match(input).hasMatch(); } bool KChecksumsPlugin::isSha1(const QString &input) { QRegularExpression regex(QStringLiteral("^[a-f0-9]{40}$")); return regex.match(input).hasMatch(); } bool KChecksumsPlugin::isSha256(const QString &input) { QRegularExpression regex(QStringLiteral("^[a-f0-9]{64}$")); return regex.match(input).hasMatch(); } QString KChecksumsPlugin::computeChecksum(QCryptographicHash::Algorithm algorithm, const QString &path) { QFile file(path); if (!file.open(QIODevice::ReadOnly)) { return QString(); } QCryptographicHash hash(algorithm); hash.addData(&file); return QString::fromLatin1(hash.result().toHex()); } QCryptographicHash::Algorithm KChecksumsPlugin::detectAlgorithm(const QString &input) { if (isMd5(input)) { return QCryptographicHash::Md5; } if (isSha1(input)) { return QCryptographicHash::Sha1; } if (isSha256(input)) { return QCryptographicHash::Sha256; } // Md4 used as negative error code. return QCryptographicHash::Md4; } void KChecksumsPlugin::setDefaultState() { QColor defaultColor = d->m_widget.palette().color(QPalette::Base); QPalette palette = d->m_widget.palette(); palette.setColor(QPalette::Base, defaultColor); d->m_ui.feedbackLabel->hide(); d->m_ui.lineEdit->setPalette(palette); d->m_ui.lineEdit->setToolTip(QString()); } void KChecksumsPlugin::setInvalidChecksumState() { KColorScheme colorScheme(QPalette::Active, KColorScheme::View); QColor warningColor = colorScheme.background(KColorScheme::NegativeBackground).color(); QPalette palette = d->m_widget.palette(); palette.setColor(QPalette::Base, warningColor); d->m_ui.feedbackLabel->setText(i18n("Invalid checksum.")); d->m_ui.feedbackLabel->show(); d->m_ui.lineEdit->setPalette(palette); d->m_ui.lineEdit->setToolTip(i18nc("@info:tooltip", "The given input is not a valid MD5, SHA1 or SHA256 checksum.")); } void KChecksumsPlugin::setMatchState() { KColorScheme colorScheme(QPalette::Active, KColorScheme::View); QColor positiveColor = colorScheme.background(KColorScheme::PositiveBackground).color(); QPalette palette = d->m_widget.palette(); palette.setColor(QPalette::Base, positiveColor); d->m_ui.feedbackLabel->setText(i18n("Checksums match.")); d->m_ui.feedbackLabel->show(); d->m_ui.lineEdit->setPalette(palette); d->m_ui.lineEdit->setToolTip(i18nc("@info:tooltip", "The computed checksum and the expected checksum match.")); } void KChecksumsPlugin::setMismatchState() { KColorScheme colorScheme(QPalette::Active, KColorScheme::View); QColor warningColor = colorScheme.background(KColorScheme::NegativeBackground).color(); QPalette palette = d->m_widget.palette(); palette.setColor(QPalette::Base, warningColor); d->m_ui.feedbackLabel->setText(i18n("

Checksums do not match.

" "This may be due to a faulty download. Try re-downloading the file.
" "If the verification still fails, contact the source of the file.")); d->m_ui.feedbackLabel->show(); d->m_ui.lineEdit->setPalette(palette); d->m_ui.lineEdit->setToolTip(i18nc("@info:tooltip", "The computed checksum and the expected checksum differ.")); } void KChecksumsPlugin::setVerifyState() { // Users can paste a checksum at any time, so reset to default. setDefaultState(); d->m_ui.feedbackLabel->setText(i18nc("notify the user about a computation in the background", "Verifying checksum...")); d->m_ui.feedbackLabel->show(); } void KChecksumsPlugin::showChecksum(QCryptographicHash::Algorithm algorithm, QLabel *label, QPushButton *copyButton) { const QString checksum = cachedChecksum(algorithm); // Checksum in cache, nothing else to do. if (!checksum.isEmpty()) { label->setText(checksum); return; } // Calculate checksum in another thread. auto futureWatcher = new QFutureWatcher(this); connect(futureWatcher, &QFutureWatcher::finished, this, [=]() { const QString checksum = futureWatcher->result(); futureWatcher->deleteLater(); label->setText(checksum); cacheChecksum(checksum, algorithm); copyButton->show(); }); auto future = QtConcurrent::run(&KChecksumsPlugin::computeChecksum, algorithm, properties->item().localPath()); futureWatcher->setFuture(future); } QString KChecksumsPlugin::cachedChecksum(QCryptographicHash::Algorithm algorithm) const { switch (algorithm) { case QCryptographicHash::Md5: return d->m_md5; case QCryptographicHash::Sha1: return d->m_sha1; case QCryptographicHash::Sha256: return d->m_sha256; default: break; } return QString(); } void KChecksumsPlugin::cacheChecksum(const QString &checksum, QCryptographicHash::Algorithm algorithm) { switch (algorithm) { case QCryptographicHash::Md5: d->m_md5 = checksum; break; case QCryptographicHash::Sha1: d->m_sha1 = checksum; break; case QCryptographicHash::Sha256: d->m_sha256 = checksum; break; default: return; } } class KUrlPropsPlugin::KUrlPropsPluginPrivate { public: KUrlPropsPluginPrivate() { } ~KUrlPropsPluginPrivate() { } QFrame *m_frame; KUrlRequester *URLEdit; QString URLStr; bool fileNameReadOnly = false; }; KUrlPropsPlugin::KUrlPropsPlugin(KPropertiesDialog *_props) : KPropertiesDialogPlugin(_props), d(new KUrlPropsPluginPrivate) { d->m_frame = new QFrame(); properties->addPage(d->m_frame, i18n("U&RL")); QVBoxLayout *layout = new QVBoxLayout(d->m_frame); layout->setContentsMargins(0, 0, 0, 0); QLabel *l; l = new QLabel(d->m_frame); l->setObjectName(QStringLiteral("Label_1")); l->setText(i18n("URL:")); layout->addWidget(l, Qt::AlignRight); d->URLEdit = new KUrlRequester(d->m_frame); layout->addWidget(d->URLEdit); KIO::StatJob *job = KIO::mostLocalUrl(properties->url()); KJobWidgets::setWindow(job, properties); job->exec(); QUrl url = job->mostLocalUrl(); if (url.isLocalFile()) { QString path = url.toLocalFile(); QFile f(path); if (!f.open(QIODevice::ReadOnly)) { return; } f.close(); KDesktopFile config(path); const KConfigGroup dg = config.desktopGroup(); d->URLStr = dg.readPathEntry("URL", QString()); if (!d->URLStr.isEmpty()) { d->URLEdit->setUrl(QUrl(d->URLStr)); } } connect(d->URLEdit, &KUrlRequester::textChanged, this, &KPropertiesDialogPlugin::changed); layout->addStretch(1); } KUrlPropsPlugin::~KUrlPropsPlugin() { delete d; } void KUrlPropsPlugin::setFileNameReadOnly(bool ro) { d->fileNameReadOnly = ro; } // QString KUrlPropsPlugin::tabName () const // { // return i18n ("U&RL"); // } bool KUrlPropsPlugin::supports(const KFileItemList &_items) { if (_items.count() != 1) { return false; } const KFileItem item = _items.first(); // check if desktop file if (!item.isDesktopFile()) { return false; } // open file and check type bool isLocal; QUrl url = item.mostLocalUrl(isLocal); if (!isLocal) { return false; } KDesktopFile config(url.toLocalFile()); return config.hasLinkType(); } void KUrlPropsPlugin::applyChanges() { KIO::StatJob *job = KIO::mostLocalUrl(properties->url()); KJobWidgets::setWindow(job, properties); job->exec(); const QUrl url = job->mostLocalUrl(); if (!url.isLocalFile()) { KMessageBox::sorry(nullptr, i18n("Could not save properties. Only entries on local file systems are supported.")); return; } QString path = url.toLocalFile(); QFile f(path); if (!f.open(QIODevice::ReadWrite)) { KMessageBox::sorry(nullptr, i18n("Could not save properties. You do not have " "sufficient access to write to %1.", path)); return; } f.close(); KDesktopFile config(path); KConfigGroup dg = config.desktopGroup(); dg.writeEntry("Type", QStringLiteral("Link")); dg.writePathEntry("URL", d->URLEdit->url().toString()); // Users can't create a Link .desktop file with a Name field, // but distributions can. Update the Name field in that case, // if the file name could have been changed. if (!d->fileNameReadOnly && dg.hasKey("Name")) { const QString nameStr = nameFromFileName(properties->url().fileName()); dg.writeEntry("Name", nameStr); dg.writeEntry("Name", nameStr, KConfigBase::Persistent | KConfigBase::Localized); } } /* ---------------------------------------------------- * * KDevicePropsPlugin * * -------------------------------------------------- */ class KDevicePropsPlugin::KDevicePropsPluginPrivate { public: KDevicePropsPluginPrivate() { } ~KDevicePropsPluginPrivate() { } bool isMounted() const { const QString dev = device->currentText(); return !dev.isEmpty() && KMountPoint::currentMountPoints().findByDevice(dev); } QFrame *m_frame; QStringList mountpointlist; QLabel *m_freeSpaceText; QLabel *m_freeSpaceLabel; QProgressBar *m_freeSpaceBar; KComboBox *device; QLabel *mountpoint; QCheckBox *readonly; QStringList m_devicelist; }; KDevicePropsPlugin::KDevicePropsPlugin(KPropertiesDialog *_props) : KPropertiesDialogPlugin(_props), d(new KDevicePropsPluginPrivate) { d->m_frame = new QFrame(); properties->addPage(d->m_frame, i18n("De&vice")); QStringList devices; const KMountPoint::List mountPoints = KMountPoint::possibleMountPoints(); for (const KMountPoint::Ptr &mp : mountPoints) { QString mountPoint = mp->mountPoint(); QString device = mp->mountedFrom(); // qDebug()<<"mountPoint :"<mountType() :"<mountType(); if ((mountPoint != QLatin1String("-")) && (mountPoint != QLatin1String("none")) && !mountPoint.isEmpty() && device != QLatin1String("none")) { devices.append(device + QLatin1String(" (") + mountPoint + QLatin1Char(')')); d->m_devicelist.append(device); d->mountpointlist.append(mountPoint); } } QGridLayout *layout = new QGridLayout(d->m_frame); layout->setContentsMargins(0, 0, 0, 0); layout->setColumnStretch(1, 1); QLabel *label; label = new QLabel(d->m_frame); label->setText(devices.isEmpty() ? i18n("Device (/dev/fd0):") : // old style i18n("Device:")); // new style (combobox) layout->addWidget(label, 0, 0, Qt::AlignRight); d->device = new KComboBox(d->m_frame); d->device->setObjectName(QStringLiteral("ComboBox_device")); d->device->setEditable(true); d->device->addItems(devices); layout->addWidget(d->device, 0, 1); connect(d->device, QOverload::of(&QComboBox::activated), this, &KDevicePropsPlugin::slotActivated); d->readonly = new QCheckBox(d->m_frame); d->readonly->setObjectName(QStringLiteral("CheckBox_readonly")); d->readonly->setText(i18n("Read only")); layout->addWidget(d->readonly, 1, 1); label = new QLabel(d->m_frame); label->setText(i18n("File system:")); layout->addWidget(label, 2, 0, Qt::AlignRight); QLabel *fileSystem = new QLabel(d->m_frame); layout->addWidget(fileSystem, 2, 1); label = new QLabel(d->m_frame); label->setText(devices.isEmpty() ? i18n("Mount point (/mnt/floppy):") : // old style i18n("Mount point:")); // new style (combobox) layout->addWidget(label, 3, 0, Qt::AlignRight); d->mountpoint = new QLabel(d->m_frame); d->mountpoint->setObjectName(QStringLiteral("LineEdit_mountpoint")); layout->addWidget(d->mountpoint, 3, 1); // show disk free d->m_freeSpaceText = new QLabel(i18nc("Amount of used and available space on this device or partition", "Free space:"), d->m_frame); layout->addWidget(d->m_freeSpaceText, 4, 0, Qt::AlignRight); d->m_freeSpaceLabel = new QLabel(d->m_frame); layout->addWidget(d->m_freeSpaceLabel, 4, 1); d->m_freeSpaceBar = new QProgressBar(d->m_frame); d->m_freeSpaceBar->setObjectName(QStringLiteral("freeSpaceBar")); layout->addWidget(d->m_freeSpaceBar, 5, 0, 1, 2); // we show it in the slot when we know the values d->m_freeSpaceText->hide(); d->m_freeSpaceLabel->hide(); d->m_freeSpaceBar->hide(); KSeparator *sep = new KSeparator(Qt::Horizontal, d->m_frame); layout->addWidget(sep, 6, 0, 1, 2); layout->setRowStretch(7, 1); KIO::StatJob *job = KIO::mostLocalUrl(_props->url()); KJobWidgets::setWindow(job, _props); job->exec(); QUrl url = job->mostLocalUrl(); if (!url.isLocalFile()) { return; } QString path = url.toLocalFile(); QFile f(path); if (!f.open(QIODevice::ReadOnly)) { return; } f.close(); const KDesktopFile _config(path); const KConfigGroup config = _config.desktopGroup(); QString deviceStr = config.readEntry("Dev"); QString mountPointStr = config.readEntry("MountPoint"); bool ro = config.readEntry("ReadOnly", false); fileSystem->setText(config.readEntry("FSType")); d->device->setEditText(deviceStr); if (!deviceStr.isEmpty()) { // Set default options for this device (first matching entry) int index = d->m_devicelist.indexOf(deviceStr); if (index != -1) { //qDebug() << "found it" << index; slotActivated(index); } } if (!mountPointStr.isEmpty()) { d->mountpoint->setText(mountPointStr); updateInfo(); } d->readonly->setChecked(ro); connect(d->device, QOverload::of(&QComboBox::activated), this, &KPropertiesDialogPlugin::changed); connect(d->device, &QComboBox::currentTextChanged, this, &KPropertiesDialogPlugin::changed); connect(d->readonly, &QAbstractButton::toggled, this, &KPropertiesDialogPlugin::changed); connect(d->device, &QComboBox::currentTextChanged, this, &KDevicePropsPlugin::slotDeviceChanged); } KDevicePropsPlugin::~KDevicePropsPlugin() { delete d; } // QString KDevicePropsPlugin::tabName () const // { // return i18n ("De&vice"); // } void KDevicePropsPlugin::updateInfo() { // we show it in the slot when we know the values d->m_freeSpaceText->hide(); d->m_freeSpaceLabel->hide(); d->m_freeSpaceBar->hide(); if (!d->mountpoint->text().isEmpty() && d->isMounted()) { KDiskFreeSpaceInfo info = KDiskFreeSpaceInfo::freeSpaceInfo(d->mountpoint->text()); slotFoundMountPoint(info.mountPoint(), info.size() / 1024, info.used() / 1024, info.available() / 1024); } } void KDevicePropsPlugin::slotActivated(int index) { // index can be more than the number of known devices, when the user types // a "custom" device. if (index < d->m_devicelist.count()) { // Update mountpoint so that it matches the device that was selected in the combo d->device->setEditText(d->m_devicelist[index]); d->mountpoint->setText(d->mountpointlist[index]); } updateInfo(); } void KDevicePropsPlugin::slotDeviceChanged() { // Update mountpoint so that it matches the typed device int index = d->m_devicelist.indexOf(d->device->currentText()); if (index != -1) { d->mountpoint->setText(d->mountpointlist[index]); } else { d->mountpoint->setText(QString()); } updateInfo(); } void KDevicePropsPlugin::slotFoundMountPoint(const QString &, quint64 kibSize, quint64 /*kibUsed*/, quint64 kibAvail) { d->m_freeSpaceText->show(); d->m_freeSpaceLabel->show(); const int percUsed = kibSize != 0 ? (100 - (int)(100.0 * kibAvail / kibSize)) : 100; d->m_freeSpaceLabel->setText( i18nc("Available space out of total partition size (percent used)", "%1 free of %2 (%3% used)", KIO::convertSizeFromKiB(kibAvail), KIO::convertSizeFromKiB(kibSize), percUsed)); d->m_freeSpaceBar->setRange(0, 100); d->m_freeSpaceBar->setValue(percUsed); d->m_freeSpaceBar->show(); } bool KDevicePropsPlugin::supports(const KFileItemList &_items) { if (_items.count() != 1) { return false; } const KFileItem item = _items.first(); // check if desktop file if (!item.isDesktopFile()) { return false; } // open file and check type bool isLocal; QUrl url = item.mostLocalUrl(isLocal); if (!isLocal) { return false; } KDesktopFile config(url.toLocalFile()); return config.hasDeviceType(); } void KDevicePropsPlugin::applyChanges() { KIO::StatJob *job = KIO::mostLocalUrl(properties->url()); KJobWidgets::setWindow(job, properties); job->exec(); const QUrl url = job->mostLocalUrl(); if (!url.isLocalFile()) { return; } const QString path = url.toLocalFile(); QFile f(path); if (!f.open(QIODevice::ReadWrite)) { KMessageBox::sorry(nullptr, i18n("Could not save properties. You do not have sufficient " "access to write to %1.", path)); return; } f.close(); KDesktopFile _config(path); KConfigGroup config = _config.desktopGroup(); config.writeEntry("Type", QStringLiteral("FSDevice")); config.writeEntry("Dev", d->device->currentText()); config.writeEntry("MountPoint", d->mountpoint->text()); config.writeEntry("ReadOnly", d->readonly->isChecked()); config.sync(); } /* ---------------------------------------------------- * * KDesktopPropsPlugin * * -------------------------------------------------- */ class KDesktopPropsPlugin::KDesktopPropsPluginPrivate { public: KDesktopPropsPluginPrivate() : w(new Ui_KPropertiesDesktopBase) , m_frame(new QFrame()) { } ~KDesktopPropsPluginPrivate() { delete w; } Ui_KPropertiesDesktopBase *w; QWidget *m_frame; QString m_origCommandStr; QString m_terminalOptionStr; QString m_suidUserStr; QString m_dbusStartupType; QString m_dbusServiceName; QString m_origDesktopFile; bool m_terminalBool; bool m_suidBool; bool m_hasDiscreteGpuBool; bool m_runOnDiscreteGpuBool; bool m_startupBool; }; KDesktopPropsPlugin::KDesktopPropsPlugin(KPropertiesDialog *_props) : KPropertiesDialogPlugin(_props), d(new KDesktopPropsPluginPrivate) { QMimeDatabase db; d->w->setupUi(d->m_frame); properties->addPage(d->m_frame, i18n("&Application")); bool bKDesktopMode = properties->url().scheme() == QLatin1String("desktop") || properties->currentDir().scheme() == QLatin1String("desktop"); if (bKDesktopMode) { // Hide Name entry d->w->nameEdit->hide(); d->w->nameLabel->hide(); } d->w->pathEdit->setMode(KFile::Directory | KFile::LocalOnly); d->w->pathEdit->lineEdit()->setAcceptDrops(false); connect(d->w->nameEdit, &QLineEdit::textChanged, this, &KPropertiesDialogPlugin::changed); connect(d->w->genNameEdit, &QLineEdit::textChanged, this, &KPropertiesDialogPlugin::changed); connect(d->w->commentEdit, &QLineEdit::textChanged, this, &KPropertiesDialogPlugin::changed); connect(d->w->commandEdit, &QLineEdit::textChanged, this, &KPropertiesDialogPlugin::changed); connect(d->w->pathEdit, &KUrlRequester::textChanged, this, &KPropertiesDialogPlugin::changed); connect(d->w->browseButton, &QAbstractButton::clicked, this, &KDesktopPropsPlugin::slotBrowseExec); connect(d->w->addFiletypeButton, &QAbstractButton::clicked, this, &KDesktopPropsPlugin::slotAddFiletype); connect(d->w->delFiletypeButton, &QAbstractButton::clicked, this, &KDesktopPropsPlugin::slotDelFiletype); connect(d->w->advancedButton, &QAbstractButton::clicked, this, &KDesktopPropsPlugin::slotAdvanced); enum DiscreteGpuCheck { NotChecked, Present, Absent }; static DiscreteGpuCheck s_gpuCheck = NotChecked; if (s_gpuCheck == NotChecked) { // Check whether we have a discrete gpu bool hasDiscreteGpu = false; QDBusInterface iface(QStringLiteral("org.kde.Solid.PowerManagement"), QStringLiteral("/org/kde/Solid/PowerManagement"), QStringLiteral("org.kde.Solid.PowerManagement"), QDBusConnection::sessionBus()); if (iface.isValid()) { QDBusReply reply = iface.call(QStringLiteral("hasDualGpu")); if (reply.isValid()) { hasDiscreteGpu = reply.value(); } } s_gpuCheck = hasDiscreteGpu ? Present : Absent; } d->m_hasDiscreteGpuBool = s_gpuCheck == Present; // now populate the page KIO::StatJob *job = KIO::mostLocalUrl(_props->url()); KJobWidgets::setWindow(job, _props); job->exec(); QUrl url = job->mostLocalUrl(); if (!url.isLocalFile()) { return; } d->m_origDesktopFile = url.toLocalFile(); QFile f(d->m_origDesktopFile); if (!f.open(QIODevice::ReadOnly)) { return; } f.close(); KDesktopFile _config(d->m_origDesktopFile); KConfigGroup config = _config.desktopGroup(); QString nameStr = _config.readName(); QString genNameStr = _config.readGenericName(); QString commentStr = _config.readComment(); QString commandStr = config.readEntry("Exec", QString()); d->m_origCommandStr = commandStr; QString pathStr = config.readEntry("Path", QString()); // not readPathEntry, see kservice.cpp d->m_terminalBool = config.readEntry("Terminal", false); d->m_terminalOptionStr = config.readEntry("TerminalOptions"); d->m_suidBool = config.readEntry("X-KDE-SubstituteUID", false); d->m_suidUserStr = config.readEntry("X-KDE-Username"); if (d->m_hasDiscreteGpuBool) { d->m_runOnDiscreteGpuBool = config.readEntry("X-KDE-RunOnDiscreteGpu", false); } if (config.hasKey("StartupNotify")) { d->m_startupBool = config.readEntry("StartupNotify", true); } else { d->m_startupBool = config.readEntry("X-KDE-StartupNotify", true); } d->m_dbusStartupType = config.readEntry("X-DBUS-StartupType").toLower(); // ### should there be a GUI for this setting? // At least we're copying it over to the local file, to avoid side effects (#157853) d->m_dbusServiceName = config.readEntry("X-DBUS-ServiceName"); const QStringList mimeTypes = config.readXdgListEntry("MimeType"); if (nameStr.isEmpty() || bKDesktopMode) { // We'll use the file name if no name is specified // because we _need_ a Name for a valid file. // But let's do it in apply, not here, so that we pick up the right name. setDirty(); } if (!bKDesktopMode) { d->w->nameEdit->setText(nameStr); } d->w->genNameEdit->setText(genNameStr); d->w->commentEdit->setText(commentStr); d->w->commandEdit->setText(commandStr); d->w->pathEdit->lineEdit()->setText(pathStr); // was: d->w->filetypeList->setFullWidth(true); // d->w->filetypeList->header()->setStretchEnabled(true, d->w->filetypeList->columns()-1); for (QStringList::ConstIterator it = mimeTypes.begin(); it != mimeTypes.end();) { QMimeType p = db.mimeTypeForName(*it); ++it; QString preference; if (it != mimeTypes.end()) { bool numeric; (*it).toInt(&numeric); if (numeric) { preference = *it; ++it; } } if (p.isValid()) { QTreeWidgetItem *item = new QTreeWidgetItem(); item->setText(0, p.name()); item->setText(1, p.comment()); item->setText(2, preference); d->w->filetypeList->addTopLevelItem(item); } } d->w->filetypeList->resizeColumnToContents(0); } KDesktopPropsPlugin::~KDesktopPropsPlugin() { delete d; } void KDesktopPropsPlugin::slotAddFiletype() { QMimeDatabase db; KMimeTypeChooserDialog dlg(i18n("Add File Type for %1", properties->url().fileName()), i18n("Select one or more file types to add:"), QStringList(), // no preselected mimetypes QString(), QStringList(), KMimeTypeChooser::Comments | KMimeTypeChooser::Patterns, d->m_frame); if (dlg.exec() == QDialog::Accepted) { const QStringList list = dlg.chooser()->mimeTypes(); for (const QString &mimetype : list) { QMimeType p = db.mimeTypeForName(mimetype); if (!p.isValid()) { continue; } bool found = false; int count = d->w->filetypeList->topLevelItemCount(); for (int i = 0; !found && i < count; ++i) { if (d->w->filetypeList->topLevelItem(i)->text(0) == mimetype) { found = true; } } if (!found) { QTreeWidgetItem *item = new QTreeWidgetItem(); item->setText(0, p.name()); item->setText(1, p.comment()); d->w->filetypeList->addTopLevelItem(item); } d->w->filetypeList->resizeColumnToContents(0); } } emit changed(); } void KDesktopPropsPlugin::slotDelFiletype() { QTreeWidgetItem *cur = d->w->filetypeList->currentItem(); if (cur) { delete cur; emit changed(); } } void KDesktopPropsPlugin::checkCommandChanged() { if (KIO::DesktopExecParser::executableName(d->w->commandEdit->text()) != KIO::DesktopExecParser::executableName(d->m_origCommandStr)) { d->m_origCommandStr = d->w->commandEdit->text(); d->m_dbusStartupType.clear(); // Reset d->m_dbusServiceName.clear(); } } void KDesktopPropsPlugin::applyChanges() { // qDebug(); KIO::StatJob *job = KIO::mostLocalUrl(properties->url()); KJobWidgets::setWindow(job, properties); job->exec(); const QUrl url = job->mostLocalUrl(); if (!url.isLocalFile()) { KMessageBox::sorry(nullptr, i18n("Could not save properties. Only entries on local file systems are supported.")); return; } const QString path(url.toLocalFile()); // make sure the directory exists QDir().mkpath(QFileInfo(path).absolutePath()); QFile f(path); if (!f.open(QIODevice::ReadWrite)) { KMessageBox::sorry(nullptr, i18n("Could not save properties. You do not have " "sufficient access to write to %1.", path)); return; } f.close(); // If the command is changed we reset certain settings that are strongly // coupled to the command. checkCommandChanged(); KDesktopFile origConfig(d->m_origDesktopFile); QScopedPointer _config(origConfig.copyTo(path)); KConfigGroup config = _config->desktopGroup(); config.writeEntry("Type", QStringLiteral("Application")); config.writeEntry("Comment", d->w->commentEdit->text()); config.writeEntry("Comment", d->w->commentEdit->text(), KConfigGroup::Persistent | KConfigGroup::Localized); // for compat config.writeEntry("GenericName", d->w->genNameEdit->text()); config.writeEntry("GenericName", d->w->genNameEdit->text(), KConfigGroup::Persistent | KConfigGroup::Localized); // for compat config.writeEntry("Exec", d->w->commandEdit->text()); config.writeEntry("Path", d->w->pathEdit->lineEdit()->text()); // not writePathEntry, see kservice.cpp // Write mimeTypes QStringList mimeTypes; int count = d->w->filetypeList->topLevelItemCount(); for (int i = 0; i < count; ++i) { QTreeWidgetItem *item = d->w->filetypeList->topLevelItem(i); QString preference = item->text(2); mimeTypes.append(item->text(0)); if (!preference.isEmpty()) { mimeTypes.append(preference); } } // qDebug() << mimeTypes; config.writeXdgListEntry("MimeType", mimeTypes); if (!d->w->nameEdit->isHidden()) { QString nameStr = d->w->nameEdit->text(); config.writeEntry("Name", nameStr); config.writeEntry("Name", nameStr, KConfigGroup::Persistent | KConfigGroup::Localized); } config.writeEntry("Terminal", d->m_terminalBool); config.writeEntry("TerminalOptions", d->m_terminalOptionStr); config.writeEntry("X-KDE-SubstituteUID", d->m_suidBool); config.writeEntry("X-KDE-Username", d->m_suidUserStr); if (d->m_hasDiscreteGpuBool) { config.writeEntry("X-KDE-RunOnDiscreteGpu", d->m_runOnDiscreteGpuBool); } config.writeEntry("StartupNotify", d->m_startupBool); config.writeEntry("X-DBUS-StartupType", d->m_dbusStartupType); config.writeEntry("X-DBUS-ServiceName", d->m_dbusServiceName); config.sync(); // KSycoca update needed? bool updateNeeded = !relativeAppsLocation(path).isEmpty(); if (updateNeeded) { KBuildSycocaProgressDialog::rebuildKSycoca(d->m_frame); } } void KDesktopPropsPlugin::slotBrowseExec() { QUrl f = QFileDialog::getOpenFileUrl(d->m_frame); if (f.isEmpty()) { return; } if (!f.isLocalFile()) { KMessageBox::sorry(d->m_frame, i18n("Only executables on local file systems are supported.")); return; } QString path = f.toLocalFile(); path = KShell::quoteArg(path); d->w->commandEdit->setText(path); } void KDesktopPropsPlugin::slotAdvanced() { QDialog dlg(d->m_frame); dlg.setObjectName(QStringLiteral("KPropertiesDesktopAdv")); dlg.setModal(true); dlg.setWindowTitle(i18n("Advanced Options for %1", properties->url().fileName())); Ui_KPropertiesDesktopAdvBase w; QWidget *mainWidget = new QWidget(&dlg); w.setupUi(mainWidget); QDialogButtonBox *buttonBox = new QDialogButtonBox(&dlg); buttonBox->setStandardButtons(QDialogButtonBox::Ok | QDialogButtonBox::Cancel); connect(buttonBox, &QDialogButtonBox::accepted, &dlg, &QDialog::accept); connect(buttonBox, &QDialogButtonBox::rejected, &dlg, &QDialog::reject); QVBoxLayout *layout = new QVBoxLayout; layout->addWidget(mainWidget); layout->addWidget(buttonBox); dlg.setLayout(layout); // If the command is changed we reset certain settings that are strongly // coupled to the command. checkCommandChanged(); // check to see if we use konsole if not do not add the nocloseonexit // because we don't know how to do this on other terminal applications KConfigGroup confGroup(KSharedConfig::openConfig(), QStringLiteral("General")); QString preferredTerminal = confGroup.readPathEntry("TerminalApplication", QStringLiteral("konsole")); bool terminalCloseBool = false; if (preferredTerminal == QLatin1String("konsole")) { terminalCloseBool = d->m_terminalOptionStr.contains(QLatin1String("--noclose")); w.terminalCloseCheck->setChecked(terminalCloseBool); d->m_terminalOptionStr.remove(QStringLiteral("--noclose")); } else { w.terminalCloseCheck->hide(); } w.terminalCheck->setChecked(d->m_terminalBool); w.terminalEdit->setText(d->m_terminalOptionStr); w.terminalCloseCheck->setEnabled(d->m_terminalBool); w.terminalEdit->setEnabled(d->m_terminalBool); w.terminalEditLabel->setEnabled(d->m_terminalBool); w.suidCheck->setChecked(d->m_suidBool); w.suidEdit->setText(d->m_suidUserStr); w.suidEdit->setEnabled(d->m_suidBool); w.suidEditLabel->setEnabled(d->m_suidBool); if (d->m_hasDiscreteGpuBool) { w.discreteGpuCheck->setChecked(d->m_runOnDiscreteGpuBool); } else { w.discreteGpuGroupBox->hide(); } w.startupInfoCheck->setChecked(d->m_startupBool); if (d->m_dbusStartupType == QLatin1String("unique")) { w.dbusCombo->setCurrentIndex(2); } else if (d->m_dbusStartupType == QLatin1String("multi")) { w.dbusCombo->setCurrentIndex(1); } else if (d->m_dbusStartupType == QLatin1String("wait")) { w.dbusCombo->setCurrentIndex(3); } else { w.dbusCombo->setCurrentIndex(0); } // Provide username completion up to 1000 users. const int maxEntries = 1000; QStringList userNames = KUser::allUserNames(maxEntries); if (userNames.size() < maxEntries) { KCompletion *kcom = new KCompletion; kcom->setOrder(KCompletion::Sorted); w.suidEdit->setCompletionObject(kcom, true); w.suidEdit->setAutoDeleteCompletionObject(true); w.suidEdit->setCompletionMode(KCompletion::CompletionAuto); kcom->setItems(userNames); } connect(w.terminalEdit, &QLineEdit::textChanged, this, &KPropertiesDialogPlugin::changed); connect(w.terminalCloseCheck, &QAbstractButton::toggled, this, &KPropertiesDialogPlugin::changed); connect(w.terminalCheck, &QAbstractButton::toggled, this, &KPropertiesDialogPlugin::changed); connect(w.suidCheck, &QAbstractButton::toggled, this, &KPropertiesDialogPlugin::changed); connect(w.suidEdit, &QLineEdit::textChanged, this, &KPropertiesDialogPlugin::changed); connect(w.discreteGpuCheck, &QAbstractButton::toggled, this, &KPropertiesDialogPlugin::changed); connect(w.startupInfoCheck, &QAbstractButton::toggled, this, &KPropertiesDialogPlugin::changed); connect(w.dbusCombo, QOverload::of(&QComboBox::activated), this, &KPropertiesDialogPlugin::changed); if (dlg.exec() == QDialog::Accepted) { d->m_terminalOptionStr = w.terminalEdit->text().trimmed(); d->m_terminalBool = w.terminalCheck->isChecked(); d->m_suidBool = w.suidCheck->isChecked(); d->m_suidUserStr = w.suidEdit->text().trimmed(); if (d->m_hasDiscreteGpuBool) { d->m_runOnDiscreteGpuBool = w.discreteGpuCheck->isChecked(); } d->m_startupBool = w.startupInfoCheck->isChecked(); if (w.terminalCloseCheck->isChecked()) { d->m_terminalOptionStr.append(QLatin1String(" --noclose")); } switch (w.dbusCombo->currentIndex()) { case 1: d->m_dbusStartupType = QStringLiteral("multi"); break; case 2: d->m_dbusStartupType = QStringLiteral("unique"); break; case 3: d->m_dbusStartupType = QStringLiteral("wait"); break; default: d->m_dbusStartupType = QStringLiteral("none"); break; } } } bool KDesktopPropsPlugin::supports(const KFileItemList &_items) { if (_items.count() != 1) { return false; } const KFileItem item = _items.first(); // check if desktop file if (!item.isDesktopFile()) { return false; } // open file and check type bool isLocal; QUrl url = item.mostLocalUrl(isLocal); if (!isLocal) { return false; } KDesktopFile config(url.toLocalFile()); return config.hasApplicationType() && KAuthorized::authorize(QStringLiteral("run_desktop_files")) && KAuthorized::authorize(QStringLiteral("shell_access")); } #include "moc_kpropertiesdialog.cpp" #include "moc_kpropertiesdialog_p.cpp" diff --git a/src/widgets/kpropertiesdialog.h b/src/widgets/kpropertiesdialog.h index d60a39da..2b3c64ba 100644 --- a/src/widgets/kpropertiesdialog.h +++ b/src/widgets/kpropertiesdialog.h @@ -1,465 +1,469 @@ /* This file is part of the KDE project Copyright (C) 1998, 1999 Torben Weis Copyright (c) 1999, 2000 Preston Brown Copyright (c) 2000 Simon Hausmann Copyright (c) 2000 David Faure This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef KPROPERTIESDIALOG_H #define KPROPERTIESDIALOG_H #include #include #include "kiowidgets_export.h" #include #include class KPropertiesDialogPlugin; class KJob; namespace KIO { class Job; } /** * @class KPropertiesDialog kpropertiesdialog.h * * 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 mimetype, 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 mimetype, simply create a KFileItemList * and pass it to the other constructor. * * @since 5.10 */ explicit KPropertiesDialog(const QList &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 caption 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. */ virtual ~KPropertiesDialog(); /** * 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 &urls, QWidget *parent = nullptr, bool modal = true); /** * Adds a "3rd party" properties plugin to the dialog. Useful * for extending the properties mechanism. * * To create a new plugin type, inherit from the base class KPropertiesDialogPlugin * and implement all the methods. If you define a service .desktop file * for your plugin, you do not need to call insertPlugin(). * * @param plugin is a pointer to the KPropertiesDialogPlugin. The Properties * dialog will do destruction for you. The KPropertiesDialogPlugin \b must * have been created with the KPropertiesDialog as its parent. * @see KPropertiesDialogPlugin */ void insertPlugin(KPropertiesDialogPlugin *plugin); -#ifndef KIOWIDGETS_NO_DEPRECATED +#if KIOWIDGETS_ENABLE_DEPRECATED_SINCE(5, 0) /** * @deprecated since 5.0, use url() */ - KIOWIDGETS_DEPRECATED QUrl kurl() const + KIOWIDGETS_DEPRECATED_VERSION(5, 0, "Use KPropertiesDialog::url()") + QUrl kurl() const { return url(); } #endif /** * 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 * \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'. * @deprecated since 5.25, use accept() */ - KIOWIDGETS_DEPRECATED virtual void slotOk(); + KIOWIDGETS_DEPRECATED_VERSION(5, 25, "Use KPropertiesDialog::accept()") + virtual void slotOk(); /** * Called when the user presses 'Cancel'. * @deprecated since 5.25, use reject() */ - KIOWIDGETS_DEPRECATED virtual void slotCancel(); + KIOWIDGETS_DEPRECATED_VERSION(5, 25, "Use KPropertiesDialog::reject()") + virtual void slotCancel(); /** * 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); Q_SIGNALS: void leaveModality(); private: class KPropertiesDialogPrivate; KPropertiesDialogPrivate *const d; Q_DISABLE_COPY(KPropertiesDialog) }; /** * 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, define a service that implements the KPropertiesDialog/Plugin * servicetype, as well as the mimetypes for which the plugin should be created. * For instance, X-KDE-ServiceTypes=KPropertiesDialog/Plugin,text/html,application/x-mymimetype. * * You can also include X-KDE-Protocol=file if you want that plugin * to be loaded only for local files, for instance. */ class KIOWIDGETS_EXPORT KPropertiesDialogPlugin : public QObject { Q_OBJECT public: /** * Constructor * To insert tabs into the properties dialog, use the add methods provided by * KPageDialog (the properties dialog is a KPageDialog). */ KPropertiesDialogPlugin(KPropertiesDialog *_props); virtual ~KPropertiesDialogPlugin(); /** * 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(); +#if KIOWIDGETS_ENABLE_DEPRECATED_SINCE(4, 1) /** * Convenience method for most ::supports methods * @return true if the file is a local, regular, readable, desktop file - * @deprecated use KFileItem::isDesktopFile + * @deprecated Since 4.1, use KFileItem::isDesktopFile */ -#ifndef KIOWIDGETS_NO_DEPRECATED - static KIOWIDGETS_DEPRECATED bool isDesktopFile(const KFileItem &_item); + KIOWIDGETS_DEPRECATED_VERSION(4, 1, "Use KFileItem::isDesktopFile()") + static bool isDesktopFile(const KFileItem &_item); #endif void setDirty(bool b); bool isDirty() const; public Q_SLOTS: void setDirty(); // same as setDirty( true ). TODO KDE5: void setDirty(bool dirty=true); 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 *properties; /** * Returns the font height. */ int fontHeight() const; private: class KPropertiesDialogPluginPrivate; KPropertiesDialogPluginPrivate *const d; }; #endif diff --git a/src/widgets/krun.cpp b/src/widgets/krun.cpp index 363a2ae9..1b18cc14 100644 --- a/src/widgets/krun.cpp +++ b/src/widgets/krun.cpp @@ -1,1800 +1,1780 @@ /* This file is part of the KDE libraries Copyright (C) 2000 Torben Weis Copyright (C) 2006 David Faure Copyright (C) 2009 Michael Pyne This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "krun.h" #include "krun_p.h" #include // HAVE_X11 #include "kio_widgets_debug.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "kio/job.h" #include "kio/global.h" #include "kio/scheduler.h" #include "kopenwithdialog.h" #include "krecentdocument.h" #include "kdesktopfileactions.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #if HAVE_X11 #include #endif #include KRun::KRunPrivate::KRunPrivate(KRun *parent) : q(parent), m_showingDialog(false) { } void KRun::KRunPrivate::startTimer() { m_timer->start(0); } // --------------------------------------------------------------------------- static QString schemeHandler(const QString &protocol) { // We have up to two sources of data, for protocols not handled by kioslaves (so called "helper") : // 1) the exec line of the .protocol file, if there's one // 2) the application associated with x-scheme-handler/ if there's one // If both exist, then: // A) if the .protocol file says "launch an application", then the new-style handler-app has priority // B) but if the .protocol file is for a kioslave (e.g. kio_http) then this has priority over // firefox or chromium saying x-scheme-handler/http. Gnome people want to send all HTTP urls // to a webbrowser, but we want mimetype-determination-in-calling-application by default // (the user can configure a BrowserApplication though) const KService::Ptr service = KMimeTypeTrader::self()->preferredService(QLatin1String("x-scheme-handler/") + protocol); if (service) { return service->exec(); // for helper protocols, the handler app has priority over the hardcoded one (see A above) } Q_ASSERT(KProtocolInfo::isHelperProtocol(protocol)); return KProtocolInfo::exec(protocol); } static bool checkNeedPortalSupport() { return !QStandardPaths::locate(QStandardPaths::RuntimeLocation, QLatin1String("flatpak-info")).isEmpty() || qEnvironmentVariableIsSet("SNAP"); } static qint64 runProcessRunner(KProcessRunner *processRunner, QWidget *widget) { QObject *receiver = widget ? static_cast(widget) : static_cast(qApp); QObject::connect(processRunner, &KProcessRunner::error, receiver, [widget](const QString &errorString) { QEventLoopLocker locker; KMessageBox::sorry(widget, errorString); }); return processRunner->pid(); } // --------------------------------------------------------------------------- // Helper function that returns whether a file has the execute bit set or not. static bool hasExecuteBit(const QString &fileName) { QFileInfo file(fileName); return file.isExecutable(); } bool KRun::isExecutableFile(const QUrl &url, const QString &mimetype) { if (!url.isLocalFile()) { return false; } // While isExecutable performs similar check to this one, some users depend on // this method not returning true for application/x-desktop QMimeDatabase db; QMimeType mimeType = db.mimeTypeForName(mimetype); if (!mimeType.inherits(QStringLiteral("application/x-executable")) && !mimeType.inherits(QStringLiteral("application/x-ms-dos-executable")) && !mimeType.inherits(QStringLiteral("application/x-executable-script")) && !mimeType.inherits(QStringLiteral("application/x-sharedlib"))) { return false; } if (!hasExecuteBit(url.toLocalFile()) && !mimeType.inherits(QStringLiteral("application/x-ms-dos-executable"))) { return false; } return true; } void KRun::handleInitError(int kioErrorCode, const QString &errorMsg) { Q_UNUSED(kioErrorCode); d->m_showingDialog = true; KMessageBox::error(d->m_window, errorMsg); d->m_showingDialog = false; } void KRun::handleError(KJob *job) { Q_ASSERT(job); if (job) { d->m_showingDialog = true; job->uiDelegate()->showErrorMessage(); d->m_showingDialog = false; } } // Simple QDialog that resizes the given text edit after being shown to more // or less fit the enclosed text. class SecureMessageDialog : public QDialog { Q_OBJECT public: SecureMessageDialog(QWidget *parent) : QDialog(parent), m_textEdit(nullptr) { } void setTextEdit(QPlainTextEdit *textEdit) { m_textEdit = textEdit; } protected: void showEvent(QShowEvent *e) override { 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; }; // Shows confirmation dialog whether an untrusted program should be run // or not, since it may be potentially dangerous. static int showUntrustedProgramWarning(const QString &programName, QWidget *window) { SecureMessageDialog *baseDialog = new SecureMessageDialog(window); baseDialog->setWindowTitle(i18nc("Warning about executing unknown program", "Warning")); QVBoxLayout *topLayout = new QVBoxLayout; baseDialog->setLayout(topLayout); // Dialog will have explanatory text with a disabled lineedit with the // Exec= to make it visually distinct. QWidget *baseWidget = new QWidget(baseDialog); QHBoxLayout *mainLayout = new QHBoxLayout(baseWidget); QLabel *iconLabel = new QLabel(baseWidget); QPixmap warningIcon(KIconLoader::global()->loadIcon(QStringLiteral("dialog-warning"), KIconLoader::NoGroup, KIconLoader::SizeHuge)); mainLayout->addWidget(iconLabel); iconLabel->setPixmap(warningIcon); QVBoxLayout *contentLayout = new QVBoxLayout; QString warningMessage = i18nc("program name follows in a line edit below", "This will start the program:"); QLabel *message = new QLabel(warningMessage, baseWidget); contentLayout->addWidget(message); 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. #if QT_VERSION < QT_VERSION_CHECK(5, 14, 0) const QSize screenSize = QApplication::screens().at(0)->size(); #else const QSize screenSize = baseDialog->screen()->size(); #endif baseDialog->resize(screenSize.width() / 4, 50); baseDialog->setMaximumHeight(screenSize.height() / 3); baseDialog->setMaximumWidth(screenSize.width() / 10 * 8); return baseDialog->exec(); } // Helper function that attempts to set execute bit for given file. static bool setExecuteBit(const QString &fileName, QString &errorString) { QFile file(fileName); // corresponds to owner on unix, which will have to do since if the user // isn't the owner we can't change perms anyways. if (!file.setPermissions(QFile::ExeUser | file.permissions())) { errorString = file.errorString(); qCWarning(KIO_WIDGETS) << "Unable to change permissions for" << fileName << errorString; return false; } return true; } -#ifndef KIOWIDGETS_NO_DEPRECATED bool KRun::runUrl(const QUrl &url, const QString &mimetype, QWidget *window, bool tempFile, bool runExecutables, const QString &suggestedFileName, const QByteArray &asn) { RunFlags flags = tempFile ? KRun::DeleteTemporaryFiles : RunFlags(); if (runExecutables) { flags |= KRun::RunExecutables; } return runUrl(url, mimetype, window, flags, suggestedFileName, asn); } -#endif // This is called by foundMimeType, since it knows the mimetype of the URL bool KRun::runUrl(const QUrl &u, const QString &_mimetype, QWidget *window, RunFlags flags, const QString &suggestedFileName, const QByteArray &asn) { const QMimeDatabase db; const bool runExecutables = flags.testFlag(KRun::RunExecutables); const bool tempFile = flags.testFlag(KRun::DeleteTemporaryFiles); bool noRun = false; bool noAuth = false; if (_mimetype == QLatin1String("inode/directory-locked")) { KMessageBox::error(window, i18n("Unable to enter %1.\nYou do not have access rights to this location.", u.toDisplayString().toHtmlEscaped())); return false; } else if (_mimetype == QLatin1String("application/x-desktop")) { if (u.isLocalFile() && runExecutables) { return KDesktopFileActions::runWithStartup(u, true, asn); } } else if (isExecutable(_mimetype)) { // Check whether file is executable script const QMimeType mime = db.mimeTypeForName(_mimetype); #ifdef Q_OS_WIN bool isNativeBinary = !mime.inherits(QStringLiteral("text/plain")); #else bool isNativeBinary = !mime.inherits(QStringLiteral("text/plain")) && !mime.inherits(QStringLiteral("application/x-ms-dos-executable")); #endif // Only run local files if (u.isLocalFile() && runExecutables) { if (KAuthorized::authorize(QStringLiteral("shell_access"))) { bool canRun = true; bool isFileExecutable = hasExecuteBit(u.toLocalFile()); // For executables that aren't scripts and without execute bit, // show prompt asking user if he wants to run the program. if (!isFileExecutable && isNativeBinary) { canRun = false; int result = showUntrustedProgramWarning(u.fileName(), window); if (result == QDialog::Accepted) { QString errorString; if (!setExecuteBit(u.toLocalFile(), errorString)) { KMessageBox::sorry( window, i18n("Unable to make file %1 executable.\n%2.", u.toLocalFile(), errorString) ); } else { canRun = true; } } } else if (!isFileExecutable && !isNativeBinary) { // Don't try to run scripts/exes without execute bit, instead // open them with default application canRun = false; } if (canRun) { return (KRun::runCommand(KShell::quoteArg(u.toLocalFile()), QString(), QString(), window, asn, u.adjusted(QUrl::RemoveFilename).toLocalFile())); // just execute the url as a command // ## TODO implement deleting the file if tempFile==true } } else { // Show no permission warning noAuth = true; } } else if (isNativeBinary) { // Show warning for executables that aren't scripts noRun = true; } } if (noRun) { KMessageBox::sorry(window, i18n("The file %1 is an executable program. " "For safety it will not be started.", u.toDisplayString().toHtmlEscaped())); return false; } if (noAuth) { KMessageBox::error(window, i18n("You do not have permission to run %1.", u.toDisplayString().toHtmlEscaped())); return false; } QList lst; lst.append(u); KService::Ptr offer = KMimeTypeTrader::self()->preferredService(_mimetype); if (!offer) { #ifdef Q_OS_WIN // As KDE on windows doesn't know about the windows default applications offers will be empty in nearly all cases. // So we use QDesktopServices::openUrl to let windows decide how to open the file return QDesktopServices::openUrl(u); #else // Open-with dialog // TODO : pass the mimetype as a parameter, to show it (comment field) in the dialog ! // Hmm, in fact KOpenWithDialog::setServiceType already guesses the mimetype from the first URL of the list... return displayOpenWithDialog(lst, window, tempFile, suggestedFileName, asn); #endif } return KRun::runApplication(*offer, lst, window, flags, suggestedFileName, asn); } bool KRun::displayOpenWithDialog(const QList &lst, QWidget *window, bool tempFiles, const QString &suggestedFileName, const QByteArray &asn) { if (!KAuthorized::authorizeAction(QStringLiteral("openwith"))) { KMessageBox::sorry(window, i18n("You are not authorized to select an application to open this file.")); return false; } #ifdef Q_OS_WIN KConfigGroup cfgGroup(KSharedConfig::openConfig(), QStringLiteral("KOpenWithDialog Settings")); if (cfgGroup.readEntry("Native", true)) { return KRun::KRunPrivate::displayNativeOpenWithDialog(lst, window, tempFiles, suggestedFileName, asn); } #endif KOpenWithDialog dialog(lst, QString(), QString(), window); dialog.setWindowModality(Qt::WindowModal); if (dialog.exec()) { KService::Ptr service = dialog.service(); if (!service) { //qDebug() << "No service set, running " << dialog.text(); service = KService::Ptr(new KService(QString() /*name*/, dialog.text(), QString() /*icon*/)); } const RunFlags flags = tempFiles ? KRun::DeleteTemporaryFiles : RunFlags(); return KRun::runApplication(*service, lst, window, flags, suggestedFileName, asn); } return false; } -#ifndef KIOWIDGETS_NO_DEPRECATED void KRun::shellQuote(QString &_str) { // Credits to Walter, says Bernd G. :) if (_str.isEmpty()) { // Don't create an explicit empty parameter return; } const QChar q = QLatin1Char('\''); _str.replace(q, QLatin1String("'\\''")).prepend(q).append(q); } -#endif QStringList KRun::processDesktopExec(const KService &_service, const QList &_urls, bool tempFiles, const QString &suggestedFileName) { KIO::DesktopExecParser parser(_service, _urls); parser.setUrlsAreTempFiles(tempFiles); parser.setSuggestedFileName(suggestedFileName); return parser.resultingArguments(); } -#ifndef KIOWIDGETS_NO_DEPRECATED QString KRun::binaryName(const QString &execLine, bool removePath) { return removePath ? KIO::DesktopExecParser::executableName(execLine) : KIO::DesktopExecParser::executablePath(execLine); } -#endif // This code is also used in klauncher. // TODO: move this to KProcessRunner bool KRun::checkStartupNotify(const QString & /*binName*/, const KService *service, bool *silent_arg, QByteArray *wmclass_arg) { bool silent = false; QByteArray wmclass; if (service && service->property(QStringLiteral("StartupNotify")).isValid()) { silent = !service->property(QStringLiteral("StartupNotify")).toBool(); wmclass = service->property(QStringLiteral("StartupWMClass")).toString().toLatin1(); } else if (service && service->property(QStringLiteral("X-KDE-StartupNotify")).isValid()) { silent = !service->property(QStringLiteral("X-KDE-StartupNotify")).toBool(); wmclass = service->property(QStringLiteral("X-KDE-WMClass")).toString().toLatin1(); } else { // non-compliant app if (service) { if (service->isApplication()) { // doesn't have .desktop entries needed, start as non-compliant wmclass = "0"; // krazy:exclude=doublequote_chars } else { return false; // no startup notification at all } } else { #if 0 // Create startup notification even for apps for which there shouldn't be any, // just without any visual feedback. This will ensure they'll be positioned on the proper // virtual desktop, and will get user timestamp from the ASN ID. wmclass = '0'; silent = true; #else // That unfortunately doesn't work, when the launched non-compliant application // launches another one that is compliant and there is any delay inbetween (bnc:#343359) return false; #endif } } if (silent_arg) { *silent_arg = silent; } if (wmclass_arg) { *wmclass_arg = wmclass; } return true; } static qint64 runApplicationImpl(const KService::Ptr &service, const QList &_urls, QWidget *window, KRun::RunFlags flags, const QString &suggestedFileName, const QByteArray &asn) { QList urlsToRun = _urls; if ((_urls.count() > 1) && !service->allowMultipleFiles()) { // We need to launch the application N times. That sucks. // We ignore the result for application 2 to N. // For the first file we launch the application in the // usual way. The reported result is based on this // application. QList::ConstIterator it = _urls.begin(); while (++it != _urls.end()) { QList singleUrl; singleUrl.append(*it); runApplicationImpl(service, singleUrl, window, flags, suggestedFileName, QByteArray()); } urlsToRun.clear(); urlsToRun.append(_urls.first()); } // QTBUG-59017 Calling winId() on an embedded widget will break interaction // with it on high-dpi multi-screen setups (cf. also Bug 363548), hence using // its parent window instead auto windowId = WId{}; if (window) { window = window->window(); windowId = window ? window->winId() : WId{}; } auto *processRunner = new KProcessRunner(service, urlsToRun, windowId, flags, suggestedFileName, asn); return runProcessRunner(processRunner, window); } // WARNING: don't call this from DesktopExecParser, since klauncher uses that too... // TODO: make this async, see the job->exec() in there... static QList resolveURLs(const QList &_urls, const KService &_service) { // Check which protocols the application supports. // This can be a list of actual protocol names, or just KIO for KDE apps. const QStringList appSupportedProtocols = KIO::DesktopExecParser::supportedProtocols(_service); QList urls(_urls); if (!appSupportedProtocols.contains(QLatin1String("KIO"))) { for (QUrl &url : urls) { bool supported = KIO::DesktopExecParser::isProtocolInSupportedList(url, appSupportedProtocols); //qDebug() << "Looking at url=" << url << " supported=" << supported; if (!supported && KProtocolInfo::protocolClass(url.scheme()) == QLatin1String(":local")) { // Maybe we can resolve to a local URL? KIO::StatJob *job = KIO::mostLocalUrl(url); if (job->exec()) { // ## nasty nested event loop! const QUrl localURL = job->mostLocalUrl(); if (localURL != url) { url = localURL; //qDebug() << "Changed to" << localURL; } } } } } return urls; } // Helper function to make the given .desktop file executable by ensuring // that a #!/usr/bin/env xdg-open line is added if necessary and the file has // the +x bit set for the user. Returns false if either fails. static bool makeServiceFileExecutable(const QString &fileName, QString &errorString) { // Open the file and read the first two characters, check if it's // #!. If not, create a new file, prepend appropriate lines, and copy // over. QFile desktopFile(fileName); if (!desktopFile.open(QFile::ReadOnly)) { errorString = desktopFile.errorString(); qCWarning(KIO_WIDGETS) << "Error opening service" << fileName << errorString; return false; } QByteArray header = desktopFile.peek(2); // First two chars of file if (header.size() == 0) { errorString = desktopFile.errorString(); qCWarning(KIO_WIDGETS) << "Error inspecting service" << fileName << errorString; return false; // Some kind of error } if (header != "#!") { // Add header QSaveFile saveFile; saveFile.setFileName(fileName); if (!saveFile.open(QIODevice::WriteOnly)) { errorString = saveFile.errorString(); qCWarning(KIO_WIDGETS) << "Unable to open replacement file for" << fileName << errorString; return false; } QByteArray shebang("#!/usr/bin/env xdg-open\n"); if (saveFile.write(shebang) != shebang.size()) { errorString = saveFile.errorString(); qCWarning(KIO_WIDGETS) << "Error occurred adding header for" << fileName << errorString; saveFile.cancelWriting(); return false; } // Now copy the one into the other and then close and reopen desktopFile QByteArray desktopData(desktopFile.readAll()); if (desktopData.isEmpty()) { errorString = desktopFile.errorString(); qCWarning(KIO_WIDGETS) << "Unable to read service" << fileName << errorString; saveFile.cancelWriting(); return false; } if (saveFile.write(desktopData) != desktopData.size()) { errorString = saveFile.errorString(); qCWarning(KIO_WIDGETS) << "Error copying service" << fileName << errorString; saveFile.cancelWriting(); return false; } desktopFile.close(); if (!saveFile.commit()) { // Figures.... errorString = saveFile.errorString(); qCWarning(KIO_WIDGETS) << "Error committing changes to service" << fileName << errorString; return false; } if (!desktopFile.open(QFile::ReadOnly)) { errorString = desktopFile.errorString(); qCWarning(KIO_WIDGETS) << "Error re-opening service" << fileName << errorString; return false; } } // Add header return setExecuteBit(fileName, errorString); } // Helper function to make a .desktop file executable if prompted by the user. // returns true if KRun::run() should continue with execution, false if user declined // to make the file executable or we failed to make it executable. static bool makeServiceExecutable(const KService &service, QWidget *window) { if (!KAuthorized::authorize(QStringLiteral("run_desktop_files"))) { qCWarning(KIO_WIDGETS) << "No authorization to execute " << service.entryPath(); KMessageBox::sorry(window, i18n("You are not authorized to execute this service.")); return false; // Don't circumvent the Kiosk } // We can use KStandardDirs::findExe to resolve relative pathnames // but that gets rid of the command line arguments. QString program = QFileInfo(service.exec()).canonicalFilePath(); if (program.isEmpty()) { // e.g. due to command line arguments program = service.exec(); } int result = showUntrustedProgramWarning(program, window); if (result != QDialog::Accepted) { return false; } // Assume that service is an absolute path since we're being called (relative paths // would have been allowed unless Kiosk said no, therefore we already know where the // .desktop file is. Now add a header to it if it doesn't already have one // and add the +x bit. QString errorString; if (!::makeServiceFileExecutable(service.entryPath(), errorString)) { QString serviceName = service.name(); if (serviceName.isEmpty()) { serviceName = service.genericName(); } KMessageBox::sorry( window, i18n("Unable to make the service %1 executable, aborting execution.\n%2.", serviceName, errorString) ); return false; } return true; } -#ifndef KIOWIDGETS_NO_DEPRECATED bool KRun::run(const KService &_service, const QList &_urls, QWidget *window, bool tempFiles, const QString &suggestedFileName, const QByteArray &asn) { const RunFlags flags = tempFiles ? KRun::DeleteTemporaryFiles : RunFlags(); return runApplication(_service, _urls, window, flags, suggestedFileName, asn) != 0; } -#endif qint64 KRun::runApplication(const KService &service, const QList &urls, QWidget *window, RunFlags flags, const QString &suggestedFileName, const QByteArray &asn) { if (!service.entryPath().isEmpty() && !KDesktopFile::isAuthorizedDesktopFile(service.entryPath()) && !::makeServiceExecutable(service, window)) { return 0; } KService::Ptr servicePtr(new KService(service)); // clone return runApplicationImpl(servicePtr, urls, window, flags, suggestedFileName, asn); } qint64 KRun::runService(const KService &_service, const QList &_urls, QWidget *window, bool tempFiles, const QString &suggestedFileName, const QByteArray &asn) { if (!_service.entryPath().isEmpty() && !KDesktopFile::isAuthorizedDesktopFile(_service.entryPath()) && !::makeServiceExecutable(_service, window)) { return 0; } if (!tempFiles) { // Remember we opened those urls, for the "recent documents" menu in kicker for (const QUrl &url : _urls) { KRecentDocument::add(url, _service.desktopEntryName()); } } bool useKToolInvocation = !(tempFiles || _service.entryPath().isEmpty() || !suggestedFileName.isEmpty()); if (useKToolInvocation) { // Is klauncher installed? Let's try to start it, if it fails, then we won't use it. static int klauncherAvailable = -1; if (klauncherAvailable == -1) { KToolInvocation::ensureKdeinitRunning(); QDBusConnectionInterface *dbusDaemon = QDBusConnection::sessionBus().interface(); klauncherAvailable = dbusDaemon->isServiceRegistered(QStringLiteral("org.kde.klauncher5")); } if (klauncherAvailable == 0) { useKToolInvocation = false; } } if (!useKToolInvocation) { KService::Ptr servicePtr(new KService(_service)); // clone return runApplicationImpl(servicePtr, _urls, window, tempFiles ? RunFlags(DeleteTemporaryFiles) : RunFlags(), suggestedFileName, asn); } // Resolve urls if needed, depending on what the app supports const QList urls = resolveURLs(_urls, _service); //qDebug() << "Running" << _service.entryPath() << _urls << "using klauncher"; QString error; int pid = 0; //TODO KF6: change KToolInvokation to take a qint64* QByteArray myasn = asn; // startServiceByDesktopPath() doesn't take QWidget*, add it to the startup info now if (window) { if (myasn.isEmpty()) { myasn = KStartupInfo::createNewStartupId(); } if (myasn != "0") { KStartupInfoId id; id.initId(myasn); KStartupInfoData data; // QTBUG-59017 Calling winId() on an embedded widget will break interaction // with it on high-dpi multi-screen setups (cf. also Bug 363548), hence using // its parent window instead if (window->window()) { data.setLaunchedBy(window->window()->winId()); } KStartupInfo::sendChange(id, data); } } int i = KToolInvocation::startServiceByDesktopPath( _service.entryPath(), QUrl::toStringList(urls), &error, nullptr, &pid, myasn ); if (i != 0) { //qDebug() << error; KMessageBox::sorry(window, error); return 0; } //qDebug() << "startServiceByDesktopPath worked fine"; return pid; } bool KRun::run(const QString &_exec, const QList &_urls, QWidget *window, const QString &_name, const QString &_icon, const QByteArray &asn) { KService::Ptr service(new KService(_name, _exec, _icon)); return runApplication(*service, _urls, window, RunFlags{}, QString(), asn); } bool KRun::runCommand(const QString &cmd, QWidget *window, const QString &workingDirectory) { if (cmd.isEmpty()) { qCWarning(KIO_WIDGETS) << "Command was empty, nothing to run"; return false; } const QStringList args = KShell::splitArgs(cmd); if (args.isEmpty()) { qCWarning(KIO_WIDGETS) << "Command could not be parsed."; return false; } const QString bin = args.first(); return KRun::runCommand(cmd, bin, bin /*iconName*/, window, QByteArray(), workingDirectory); } bool KRun::runCommand(const QString &cmd, const QString &execName, const QString &iconName, QWidget *window, const QByteArray &asn) { return runCommand(cmd, execName, iconName, window, asn, QString()); } bool KRun::runCommand(const QString &cmd, const QString &execName, const QString &iconName, QWidget *window, const QByteArray &asn, const QString &workingDirectory) { //qDebug() << "runCommand " << cmd << "," << execName; // QTBUG-59017 Calling winId() on an embedded widget will break interaction // with it on high-dpi multi-screen setups (cf. also Bug 363548), hence using // its parent window instead auto windowId = WId{}; if (window) { window = window->window(); windowId = window ? window->winId() : WId{}; } auto *processRunner = new KProcessRunner(cmd, execName, iconName, windowId, asn, workingDirectory); return runProcessRunner(processRunner, window); } KRun::KRun(const QUrl &url, QWidget *window, bool showProgressInfo, const QByteArray &asn) : d(new KRunPrivate(this)) { d->m_timer = new QTimer(this); d->m_timer->setObjectName(QStringLiteral("KRun::timer")); d->m_timer->setSingleShot(true); d->init(url, window, showProgressInfo, asn); } void KRun::KRunPrivate::init(const QUrl &url, QWidget *window, bool showProgressInfo, const QByteArray &asn) { m_bFault = false; m_bAutoDelete = true; m_bProgressInfo = showProgressInfo; m_bFinished = false; m_job = nullptr; m_strURL = url; m_bScanFile = false; m_bIsDirectory = false; m_runExecutables = true; m_followRedirections = true; m_window = window; m_asn = asn; q->setEnableExternalBrowser(true); // Start the timer. This means we will return to the event // loop and do initialization afterwards. // Reason: We must complete the constructor before we do anything else. m_bCheckPrompt = false; m_bInit = true; q->connect(m_timer, &QTimer::timeout, q, &KRun::slotTimeout); startTimer(); //qDebug() << "new KRun" << q << url << "timer=" << m_timer; } void KRun::init() { //qDebug() << "INIT called"; if (!d->m_strURL.isValid() || d->m_strURL.scheme().isEmpty()) { const QString error = !d->m_strURL.isValid() ? d->m_strURL.errorString() : d->m_strURL.toString(); handleInitError(KIO::ERR_MALFORMED_URL, i18n("Malformed URL\n%1", error)); qCWarning(KIO_WIDGETS) << "Malformed URL:" << error; d->m_bFault = true; d->m_bFinished = true; d->startTimer(); return; } if (!KUrlAuthorized::authorizeUrlAction(QStringLiteral("open"), QUrl(), d->m_strURL)) { QString msg = KIO::buildErrorString(KIO::ERR_ACCESS_DENIED, d->m_strURL.toDisplayString()); handleInitError(KIO::ERR_ACCESS_DENIED, msg); d->m_bFault = true; d->m_bFinished = true; d->startTimer(); return; } if (d->m_externalBrowserEnabled && checkNeedPortalSupport()) { // use the function from QDesktopServices as it handles portals correctly d->m_bFault = !QDesktopServices::openUrl(d->m_strURL); d->m_bFinished = true; d->startTimer(); return; } if (!d->m_externalBrowser.isEmpty() && d->m_strURL.scheme().startsWith(QLatin1String("http"))) { if (d->runExecutable(d->m_externalBrowser)) { return; } } else if (d->m_strURL.isLocalFile() && (d->m_strURL.host().isEmpty() || (d->m_strURL.host() == QLatin1String("localhost")) || (d->m_strURL.host().compare(QHostInfo::localHostName(), Qt::CaseInsensitive) == 0))) { const QString localPath = d->m_strURL.toLocalFile(); if (!QFile::exists(localPath)) { handleInitError(KIO::ERR_DOES_NOT_EXIST, i18n("Unable to run the command specified. " "The file or folder %1 does not exist.", localPath.toHtmlEscaped())); d->m_bFault = true; d->m_bFinished = true; d->startTimer(); return; } QMimeDatabase db; QMimeType mime = db.mimeTypeForUrl(d->m_strURL); //qDebug() << "MIME TYPE is " << mime.name(); if (!d->m_externalBrowser.isEmpty() && ( mime.inherits(QStringLiteral("text/html")) || mime.inherits(QStringLiteral("application/xhtml+xml")))) { if (d->runExecutable(d->m_externalBrowser)) { return; } } else if (mime.isDefault() && !QFileInfo(localPath).isReadable()) { // Unknown mimetype because the file is unreadable, no point in showing an open-with dialog (#261002) const QString msg = KIO::buildErrorString(KIO::ERR_ACCESS_DENIED, localPath); handleInitError(KIO::ERR_ACCESS_DENIED, msg); d->m_bFault = true; d->m_bFinished = true; d->startTimer(); return; } else { mimeTypeDetermined(mime.name()); return; } } else if (KIO::DesktopExecParser::hasSchemeHandler(d->m_strURL)) { //qDebug() << "Using scheme handler"; const QString exec = schemeHandler(d->m_strURL.scheme()); if (exec.isEmpty()) { mimeTypeDetermined(KProtocolManager::defaultMimetype(d->m_strURL)); return; } else { if (run(exec, QList() << d->m_strURL, d->m_window, QString(), QString(), d->m_asn)) { d->m_bFinished = true; d->startTimer(); return; } } } #if 0 // removed for KF5 (for portability). Reintroduce a bool or flag if useful. // Did we already get the information that it is a directory ? if ((d->m_mode & QT_STAT_MASK) == QT_STAT_DIR) { mimeTypeDetermined("inode/directory"); return; } #endif // Let's see whether it is a directory if (!KProtocolManager::supportsListing(d->m_strURL)) { // No support for listing => it can't be a directory (example: http) if (!KProtocolManager::supportsReading(d->m_strURL)) { // No support for reading files either => we can't do anything (example: mailto URL, with no associated app) handleInitError(KIO::ERR_UNSUPPORTED_ACTION, i18n("Could not find any application or handler for %1", d->m_strURL.toDisplayString())); d->m_bFault = true; d->m_bFinished = true; d->startTimer(); return; } scanFile(); return; } //qDebug() << "Testing directory (stating)"; // It may be a directory or a file, let's stat KIO::JobFlags flags = d->m_bProgressInfo ? KIO::DefaultFlags : KIO::HideProgressInfo; KIO::StatJob *job = KIO::stat(d->m_strURL, KIO::StatJob::SourceSide, 0 /* no details */, flags); KJobWidgets::setWindow(job, d->m_window); connect(job, &KJob::result, this, &KRun::slotStatResult); d->m_job = job; //qDebug() << "Job" << job << "is about stating" << d->m_strURL; } KRun::~KRun() { //qDebug() << this; d->m_timer->stop(); killJob(); //qDebug() << this << "done"; delete d; } bool KRun::KRunPrivate::runExecutable(const QString &_exec) { QList urls; urls.append(m_strURL); if (_exec.startsWith(QLatin1Char('!'))) { // Literal command const QString exec = _exec.midRef(1) + QLatin1String(" %u"); if (q->run(exec, urls, m_window, QString(), QString(), m_asn)) { m_bFinished = true; startTimer(); return true; } } else { KService::Ptr service = KService::serviceByStorageId(_exec); if (service && q->runApplication(*service, urls, m_window, RunFlags{}, QString(), m_asn)) { m_bFinished = true; startTimer(); return true; } } return false; } void KRun::KRunPrivate::showPrompt() { ExecutableFileOpenDialog *dialog = new ExecutableFileOpenDialog(promptMode(), q->window()); dialog->setAttribute(Qt::WA_DeleteOnClose); connect(dialog, &ExecutableFileOpenDialog::finished, q, [this, dialog](int result){ onDialogFinished(result, dialog->isDontAskAgainChecked()); }); dialog->show(); } bool KRun::KRunPrivate::isPromptNeeded() { if (m_strURL == QUrl(QStringLiteral("remote:/x-wizard_service.desktop"))) { return false; } const QMimeDatabase db; const QMimeType mime = db.mimeTypeForUrl(m_strURL); const bool isFileExecutable = (isExecutableFile(m_strURL, mime.name()) || mime.inherits(QStringLiteral("application/x-desktop"))); if (isFileExecutable) { KConfigGroup cfgGroup(KSharedConfig::openConfig(QStringLiteral("kiorc")), "Executable scripts"); const QString value = cfgGroup.readEntry("behaviourOnLaunch", "alwaysAsk"); if (value == QLatin1String("alwaysAsk")) { return true; } else { q->setRunExecutables(value == QLatin1String("execute")); } } return false; } ExecutableFileOpenDialog::Mode KRun::KRunPrivate::promptMode() { const QMimeDatabase db; const QMimeType mime = db.mimeTypeForUrl(m_strURL); if (mime.inherits(QStringLiteral("text/plain"))) { return ExecutableFileOpenDialog::OpenOrExecute; } #ifndef Q_OS_WIN if (mime.inherits(QStringLiteral("application/x-ms-dos-executable"))) { return ExecutableFileOpenDialog::OpenAsExecute; } #endif return ExecutableFileOpenDialog::OnlyExecute; } void KRun::KRunPrivate::onDialogFinished(int result, bool isDontAskAgainSet) { if (result == ExecutableFileOpenDialog::Rejected) { m_bFinished = true; m_bInit = false; startTimer(); return; } q->setRunExecutables(result == ExecutableFileOpenDialog::ExecuteFile); if (isDontAskAgainSet) { QString output = result == ExecutableFileOpenDialog::OpenFile ? QStringLiteral("open") : QStringLiteral("execute"); KConfigGroup cfgGroup(KSharedConfig::openConfig(QStringLiteral("kiorc")), "Executable scripts"); cfgGroup.writeEntry("behaviourOnLaunch", output); } startTimer(); } void KRun::scanFile() { //qDebug() << d->m_strURL; // First, let's check for well-known extensions // Not when there is a query in the URL, in any case. if (!d->m_strURL.hasQuery()) { QMimeDatabase db; QMimeType mime = db.mimeTypeForUrl(d->m_strURL); if (!mime.isDefault() || d->m_strURL.isLocalFile()) { //qDebug() << "Scanfile: MIME TYPE is " << mime.name(); mimeTypeDetermined(mime.name()); return; } } // No mimetype found, and the URL is not local (or fast mode not allowed). // We need to apply the 'KIO' method, i.e. either asking the server or // getting some data out of the file, to know what mimetype it is. if (!KProtocolManager::supportsReading(d->m_strURL)) { qCWarning(KIO_WIDGETS) << "#### NO SUPPORT FOR READING!"; d->m_bFault = true; d->m_bFinished = true; d->startTimer(); return; } //qDebug() << this << "Scanning file" << d->m_strURL; KIO::JobFlags flags = d->m_bProgressInfo ? KIO::DefaultFlags : KIO::HideProgressInfo; KIO::TransferJob *job = KIO::get(d->m_strURL, KIO::NoReload /*reload*/, flags); KJobWidgets::setWindow(job, d->m_window); connect(job, &KJob::result, this, &KRun::slotScanFinished); connect(job, QOverload::of(&KIO::TransferJob::mimetype), this, &KRun::slotScanMimeType); d->m_job = job; //qDebug() << "Job" << job << "is about getting from" << d->m_strURL; } // When arriving in that method there are 6 possible states: // must_show_prompt, must_init, must_scan_file, found_dir, done+error or done+success. void KRun::slotTimeout() { if (d->m_bCheckPrompt) { d->m_bCheckPrompt = false; if (d->isPromptNeeded()) { d->showPrompt(); return; } } if (d->m_bInit) { d->m_bInit = false; init(); return; } if (d->m_bFault) { emit error(); } if (d->m_bFinished) { emit finished(); } else { if (d->m_bScanFile) { d->m_bScanFile = false; scanFile(); return; } else if (d->m_bIsDirectory) { d->m_bIsDirectory = false; mimeTypeDetermined(QStringLiteral("inode/directory")); return; } } if (d->m_bAutoDelete) { deleteLater(); return; } } void KRun::slotStatResult(KJob *job) { d->m_job = nullptr; const int errCode = job->error(); if (errCode) { // ERR_NO_CONTENT is not an error, but an indication no further // actions needs to be taken. if (errCode != KIO::ERR_NO_CONTENT) { qCWarning(KIO_WIDGETS) << this << "ERROR" << job->error() << job->errorString(); handleError(job); //qDebug() << this << " KRun returning from showErrorDialog, starting timer to delete us"; d->m_bFault = true; } d->m_bFinished = true; // will emit the error and autodelete this d->startTimer(); } else { //qDebug() << "Finished"; KIO::StatJob *statJob = qobject_cast(job); if (!statJob) { qFatal("Fatal Error: job is a %s, should be a StatJob", typeid(*job).name()); } // Update our URL in case of a redirection setUrl(statJob->url()); const KIO::UDSEntry entry = statJob->statResult(); const mode_t mode = entry.numberValue(KIO::UDSEntry::UDS_FILE_TYPE); if ((mode & QT_STAT_MASK) == QT_STAT_DIR) { d->m_bIsDirectory = true; // it's a dir } else { d->m_bScanFile = true; // it's a file } d->m_localPath = entry.stringValue(KIO::UDSEntry::UDS_LOCAL_PATH); // mimetype already known? (e.g. print:/manager) const QString knownMimeType = entry.stringValue(KIO::UDSEntry::UDS_MIME_TYPE); if (!knownMimeType.isEmpty()) { mimeTypeDetermined(knownMimeType); d->m_bFinished = true; } // We should have found something assert(d->m_bScanFile || d->m_bIsDirectory); // Start the timer. Once we get the timer event this // protocol server is back in the pool and we can reuse it. // This gives better performance than starting a new slave d->startTimer(); } } void KRun::slotScanMimeType(KIO::Job *, const QString &mimetype) { if (mimetype.isEmpty()) { qCWarning(KIO_WIDGETS) << "get() didn't emit a mimetype! Probably a kioslave bug, please check the implementation of" << url().scheme(); } mimeTypeDetermined(mimetype); d->m_job = nullptr; } void KRun::slotScanFinished(KJob *job) { d->m_job = nullptr; const int errCode = job->error(); if (errCode) { // ERR_NO_CONTENT is not an error, but an indication no further // actions needs to be taken. if (errCode != KIO::ERR_NO_CONTENT) { qCWarning(KIO_WIDGETS) << this << "ERROR (stat):" << job->error() << ' ' << job->errorString(); handleError(job); d->m_bFault = true; } d->m_bFinished = true; // will emit the error and autodelete this d->startTimer(); } } void KRun::mimeTypeDetermined(const QString &mimeType) { // foundMimeType reimplementations might show a dialog box; // make sure some timer doesn't kill us meanwhile (#137678, #156447) Q_ASSERT(!d->m_showingDialog); d->m_showingDialog = true; foundMimeType(mimeType); d->m_showingDialog = false; // We cannot assume that we're finished here. Some reimplementations // start a KIO job and call setFinished only later. } void KRun::foundMimeType(const QString &type) { //qDebug() << "Resulting mime type is " << type; QMimeDatabase db; KIO::TransferJob *job = qobject_cast(d->m_job); if (job) { // Update our URL in case of a redirection if (d->m_followRedirections) { setUrl(job->url()); } job->putOnHold(); KIO::Scheduler::publishSlaveOnHold(); d->m_job = nullptr; } Q_ASSERT(!d->m_bFinished); // Support for preferred service setting, see setPreferredService if (!d->m_preferredService.isEmpty()) { //qDebug() << "Attempting to open with preferred service: " << d->m_preferredService; KService::Ptr serv = KService::serviceByDesktopName(d->m_preferredService); if (serv && serv->hasMimeType(type)) { QList lst; lst.append(d->m_strURL); if (KRun::runApplication(*serv, lst, d->m_window, RunFlags{}, QString(), d->m_asn)) { setFinished(true); return; } /// Note: if that service failed, we'll go to runUrl below to /// maybe find another service, even though an error dialog box was /// already displayed. That's good if runUrl tries another service, /// but it's not good if it tries the same one :} } } // Resolve .desktop files from media:/, remote:/, applications:/ etc. QMimeType mime = db.mimeTypeForName(type); if (!mime.isValid()) { qCWarning(KIO_WIDGETS) << "Unknown mimetype " << type; } else if (mime.inherits(QStringLiteral("application/x-desktop")) && !d->m_localPath.isEmpty()) { d->m_strURL = QUrl::fromLocalFile(d->m_localPath); } KRun::RunFlags runFlags; if (d->m_runExecutables) { runFlags |= KRun::RunExecutables; } if (!KRun::runUrl(d->m_strURL, type, d->m_window, runFlags, d->m_suggestedFileName, d->m_asn)) { d->m_bFault = true; } setFinished(true); } void KRun::killJob() { if (d->m_job) { //qDebug() << this << "m_job=" << d->m_job; d->m_job->kill(); d->m_job = nullptr; } } void KRun::abort() { if (d->m_bFinished) { return; } //qDebug() << this << "m_showingDialog=" << d->m_showingDialog; killJob(); // If we're showing an error message box, the rest will be done // after closing the msgbox -> don't autodelete nor emit signals now. if (d->m_showingDialog) { return; } d->m_bFault = true; d->m_bFinished = true; d->m_bInit = false; d->m_bScanFile = false; // will emit the error and autodelete this d->startTimer(); } QWidget *KRun::window() const { return d->m_window; } bool KRun::hasError() const { return d->m_bFault; } bool KRun::hasFinished() const { return d->m_bFinished; } bool KRun::autoDelete() const { return d->m_bAutoDelete; } void KRun::setAutoDelete(bool b) { d->m_bAutoDelete = b; } void KRun::setEnableExternalBrowser(bool b) { d->m_externalBrowserEnabled = b; if (d->m_externalBrowserEnabled) { d->m_externalBrowser = KConfigGroup(KSharedConfig::openConfig(), "General").readEntry("BrowserApplication"); // If a default browser isn't set in kdeglobals, fall back to mimeapps.list if (!d->m_externalBrowser.isEmpty()) { return; } KSharedConfig::Ptr profile = KSharedConfig::openConfig(QStringLiteral("mimeapps.list"), KConfig::NoGlobals, QStandardPaths::GenericConfigLocation); KConfigGroup defaultApps(profile, "Default Applications"); d->m_externalBrowser = defaultApps.readEntry("x-scheme-handler/https"); if (d->m_externalBrowser.isEmpty()) { d->m_externalBrowser = defaultApps.readEntry("x-scheme-handler/http"); } } else { d->m_externalBrowser.clear(); } } void KRun::setPreferredService(const QString &desktopEntryName) { d->m_preferredService = desktopEntryName; } void KRun::setRunExecutables(bool b) { d->m_runExecutables = b; } void KRun::setSuggestedFileName(const QString &fileName) { d->m_suggestedFileName = fileName; } void KRun::setShowScriptExecutionPrompt(bool showPrompt) { d->m_bCheckPrompt = showPrompt; } void KRun::setFollowRedirections(bool followRedirections) { d->m_followRedirections = followRedirections; } QString KRun::suggestedFileName() const { return d->m_suggestedFileName; } bool KRun::isExecutable(const QString &mimeTypeName) { QMimeDatabase db; QMimeType mimeType = db.mimeTypeForName(mimeTypeName); return (mimeType.inherits(QLatin1String("application/x-desktop")) || mimeType.inherits(QLatin1String("application/x-executable")) || /* See https://bugs.freedesktop.org/show_bug.cgi?id=97226 */ mimeType.inherits(QLatin1String("application/x-sharedlib")) || mimeType.inherits(QLatin1String("application/x-ms-dos-executable")) || mimeType.inherits(QLatin1String("application/x-shellscript"))); } void KRun::setUrl(const QUrl &url) { d->m_strURL = url; } QUrl KRun::url() const { return d->m_strURL; } void KRun::setError(bool error) { d->m_bFault = error; } void KRun::setProgressInfo(bool progressInfo) { d->m_bProgressInfo = progressInfo; } bool KRun::progressInfo() const { return d->m_bProgressInfo; } void KRun::setFinished(bool finished) { d->m_bFinished = finished; if (finished) { d->startTimer(); } } void KRun::setJob(KIO::Job *job) { d->m_job = job; } KIO::Job *KRun::job() { return d->m_job; } -#ifndef KIOWIDGETS_NO_DEPRECATED QTimer &KRun::timer() { return *d->m_timer; } -#endif -#ifndef KIOWIDGETS_NO_DEPRECATED void KRun::setDoScanFile(bool scanFile) { d->m_bScanFile = scanFile; } -#endif -#ifndef KIOWIDGETS_NO_DEPRECATED bool KRun::doScanFile() const { return d->m_bScanFile; } -#endif -#ifndef KIOWIDGETS_NO_DEPRECATED void KRun::setIsDirecory(bool isDirectory) { d->m_bIsDirectory = isDirectory; } -#endif bool KRun::isDirectory() const { return d->m_bIsDirectory; } -#ifndef KIOWIDGETS_NO_DEPRECATED void KRun::setInitializeNextAction(bool initialize) { d->m_bInit = initialize; } -#endif -#ifndef KIOWIDGETS_NO_DEPRECATED bool KRun::initializeNextAction() const { return d->m_bInit; } -#endif bool KRun::isLocalFile() const { return d->m_strURL.isLocalFile(); } /****************/ KProcessRunner::KProcessRunner(const KService::Ptr &service, const QList &urls, WId windowId, KRun::RunFlags flags, const QString &suggestedFileName, const QByteArray &asn) : m_process{new KProcess}, m_executable(KIO::DesktopExecParser::executablePath(service->exec())) { KIO::DesktopExecParser execParser(*service, urls); execParser.setUrlsAreTempFiles(flags & KRun::DeleteTemporaryFiles); execParser.setSuggestedFileName(suggestedFileName); const QStringList args = execParser.resultingArguments(); if (args.isEmpty()) { emitDelayedError(i18n("Error processing Exec field in %1", service->entryPath())); return; } //qDebug() << "runTempService: KProcess args=" << args; *m_process << args; enum DiscreteGpuCheck { NotChecked, Present, Absent }; static DiscreteGpuCheck s_gpuCheck = NotChecked; if (service->runOnDiscreteGpu() && s_gpuCheck == NotChecked) { // Check whether we have a discrete gpu bool hasDiscreteGpu = false; QDBusInterface iface(QStringLiteral("org.kde.Solid.PowerManagement"), QStringLiteral("/org/kde/Solid/PowerManagement"), QStringLiteral("org.kde.Solid.PowerManagement"), QDBusConnection::sessionBus()); if (iface.isValid()) { QDBusReply reply = iface.call(QStringLiteral("hasDualGpu")); if (reply.isValid()) { hasDiscreteGpu = reply.value(); } } s_gpuCheck = hasDiscreteGpu ? Present : Absent; } if (service->runOnDiscreteGpu() && s_gpuCheck == Present) { m_process->setEnv(QStringLiteral("DRI_PRIME"), QStringLiteral("1")); } QString workingDir(service->workingDirectory()); if (workingDir.isEmpty() && !urls.isEmpty() && urls.first().isLocalFile()) { workingDir = urls.first().adjusted(QUrl::RemoveFilename).toLocalFile(); } m_process->setWorkingDirectory(workingDir); if ((flags & KRun::DeleteTemporaryFiles) == 0) { // Remember we opened those urls, for the "recent documents" menu in kicker for (const QUrl &url : urls) { KRecentDocument::add(url, service->desktopEntryName()); } } const QString bin = KIO::DesktopExecParser::executableName(m_executable); init(service, bin, service->name(), service->icon(), windowId, asn); } KProcessRunner::KProcessRunner(const QString &cmd, const QString &execName, const QString &iconName, WId windowId, const QByteArray &asn, const QString &workingDirectory) : m_process{new KProcess}, m_executable(execName) { m_process->setShellCommand(cmd); if (!workingDirectory.isEmpty()) { m_process->setWorkingDirectory(workingDirectory); } QString bin = KIO::DesktopExecParser::executableName(m_executable); KService::Ptr service = KService::serviceByDesktopName(bin); init(service, bin, execName /*user-visible name*/, iconName, windowId, asn); } void KProcessRunner::init(const KService::Ptr &service, const QString &bin, const QString &userVisibleName, const QString &iconName, WId windowId, const QByteArray &asn) { if (service && !service->entryPath().isEmpty() && !KDesktopFile::isAuthorizedDesktopFile(service->entryPath())) { qCWarning(KIO_WIDGETS) << "No authorization to execute " << service->entryPath(); emitDelayedError(i18n("You are not authorized to execute this file.")); return; } #if HAVE_X11 static bool isX11 = QGuiApplication::platformName() == QLatin1String("xcb"); if (isX11) { bool silent; QByteArray wmclass; const bool startup_notify = (asn != "0" && KRun::checkStartupNotify(QString() /*unused*/, service.data(), &silent, &wmclass)); if (startup_notify) { m_startupId.initId(asn); m_startupId.setupStartupEnv(); KStartupInfoData data; data.setHostname(); data.setBin(bin); if (!userVisibleName.isEmpty()) { data.setName(userVisibleName); } else if (service && !service->name().isEmpty()) { data.setName(service->name()); } data.setDescription(i18n("Launching %1", data.name())); if (!iconName.isEmpty()) { data.setIcon(iconName); } else if (service && !service->icon().isEmpty()) { data.setIcon(service->icon()); } if (!wmclass.isEmpty()) { data.setWMClass(wmclass); } if (silent) { data.setSilent(KStartupInfoData::Yes); } data.setDesktop(KWindowSystem::currentDesktop()); if (windowId) { data.setLaunchedBy(windowId); } if (service && !service->entryPath().isEmpty()) { data.setApplicationId(service->entryPath()); } KStartupInfo::sendStartup(m_startupId, data); } } #else Q_UNUSED(bin); Q_UNUSED(userVisibleName); Q_UNUSED(iconName); #endif startProcess(); } void KProcessRunner::startProcess() { connect(m_process.get(), QOverload::of(&QProcess::finished), this, &KProcessRunner::slotProcessExited); m_process->start(); if (!m_process->waitForStarted()) { //qDebug() << "wait for started failed, exitCode=" << process->exitCode() // << "exitStatus=" << process->exitStatus(); // Note that exitCode is 255 here (the first time), and 0 later on (bug?). // Use delayed invocation so the caller has time to connect to the signal QMetaObject::invokeMethod(this, [this]() { slotProcessExited(255, m_process->exitStatus()); }, Qt::QueuedConnection); } else { m_pid = m_process->processId(); #if HAVE_X11 if (!m_startupId.isNull() && m_pid) { KStartupInfoData data; data.addPid(m_pid); KStartupInfo::sendChange(m_startupId, data); KStartupInfo::resetStartupEnv(); } #endif } } KProcessRunner::~KProcessRunner() { // This destructor deletes m_process, since it's a unique_ptr. } qint64 KProcessRunner::pid() const { return m_pid; } void KProcessRunner::terminateStartupNotification() { #if HAVE_X11 if (!m_startupId.isNull()) { KStartupInfoData data; data.addPid(m_pid); // announce this pid for the startup notification has finished data.setHostname(); KStartupInfo::sendFinish(m_startupId, data); } #endif } void KProcessRunner::emitDelayedError(const QString &errorMsg) { // Use delayed invocation so the caller has time to connect to the signal QMetaObject::invokeMethod(this, [this, errorMsg]() { emit error(errorMsg); deleteLater(); }, Qt::QueuedConnection); } void KProcessRunner::slotProcessExited(int exitCode, QProcess::ExitStatus exitStatus) { //qDebug() << m_executable << "exitCode=" << exitCode << "exitStatus=" << exitStatus; Q_UNUSED(exitStatus) terminateStartupNotification(); // do this before the messagebox if (exitCode != 0 && !m_executable.isEmpty()) { // Let's see if the error is because the exe doesn't exist. // When this happens, waitForStarted returns false, but not if kioexec // was involved, then we come here, that's why the code is here. // // We'll try to find the executable relatively to current directory, // (or with a full path, if m_executable is absolute), and then in the PATH. if (!QFile(m_executable).exists() && QStandardPaths::findExecutable(m_executable).isEmpty()) { const QString &errorString = i18n("Could not find the program '%1'", m_executable); qWarning() << errorString; emit error(errorString); } else { //qDebug() << process->readAllStandardError(); } } deleteLater(); } #include "moc_krun.cpp" #include "moc_krun_p.cpp" #include "krun.moc" diff --git a/src/widgets/krun.h b/src/widgets/krun.h index fc9a4d4f..adb77e15 100644 --- a/src/widgets/krun.h +++ b/src/widgets/krun.h @@ -1,661 +1,672 @@ /* This file is part of the KDE project Copyright (C) 1998, 1999 Torben Weis Copyright (C) 2006 David Faure This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef KRUN_H #define KRUN_H #include "kiowidgets_export.h" #include #include #include class KService; class KJob; class QTimer; namespace KIO { class Job; } /** * @class KRun krun.h * * To open files with their associated applications in KDE, use KRun. * * It can execute any desktop entry, as well as any file, using * the default application or another application "bound" to the file type * (or URL protocol). * * In that example, the mimetype of the file is not known by the application, * so a KRun instance must be created. It will determine the mimetype by itself. * If the mimetype is known, or if you even know the service (application) to * use for this file, use one of the static methods. * * By default KRun uses auto deletion. It causes the KRun instance to delete * itself when the it finished its task. If you allocate the KRun * object on the stack you must disable auto deletion, otherwise it will crash. * * This respects the "shell_access", "openwith" and "run_desktop_files" Kiosk * action restrictions (see KAuthorized::authorize()). * * @short Opens files with their associated applications in KDE */ class KIOWIDGETS_EXPORT KRun : public QObject { Q_OBJECT public: /** * @param url the URL of the file or directory to 'run' * * @param window * The top-level widget of the app that invoked this object. * It is used to make sure private information like passwords * are properly handled per application. * * @param showProgressInfo * Whether to show progress information when determining the * type of the file (i.e. when using KIO::stat and KIO::mimetype) * Before you set this to false to avoid a dialog box, think about * a very slow FTP server... * It is always better to provide progress info in such cases. * * @param asn * Application startup notification id, if available (otherwise ""). * * Porting note: kdelibs 4 had mode_t mode and bool isLocalFile arguments after * window and before showProgressInfo. Removed in KF5. */ KRun(const QUrl &url, QWidget *window, bool showProgressInfo = true, const QByteArray &asn = QByteArray()); /** * Destructor. Don't call it yourself, since a KRun object auto-deletes * itself. */ virtual ~KRun(); /** * Abort this KRun. This kills any jobs launched by it, * and leads to deletion if auto-deletion is on. * This is much safer than deleting the KRun (in case it's * currently showing an error dialog box, for instance) */ void abort(); /** * Returns true if the KRun instance has an error. * @return true when an error occurred * @see error() */ bool hasError() const; /** * Returns true if the KRun instance has finished. * @return true if the KRun instance has finished * @see finished() */ bool hasFinished() const; /** * Checks whether auto delete is activated. * Auto-deletion causes the KRun instance to delete itself * when it finished its task. * By default auto deletion is on. * @return true if auto deletion is on, false otherwise */ bool autoDelete() const; /** * Enables or disabled auto deletion. * Auto deletion causes the KRun instance to delete itself * when it finished its task. If you allocate the KRun * object on the stack you must disable auto deletion. * By default auto deletion is on. * @param b true to enable auto deletion, false to disable */ void setAutoDelete(bool b); /** * Set the preferred service for opening this URL, after * its mimetype will have been found by KRun. IMPORTANT: the service is * only used if its configuration says it can handle this mimetype. * This is used for instance for the X-KDE-LastOpenedWith key in * the recent documents list, or for the app selection in * KParts::BrowserOpenOrSaveQuestion. * @param desktopEntryName the desktopEntryName of the service, e.g. "kate". */ void setPreferredService(const QString &desktopEntryName); /** * Sets whether executables, .desktop files or shell scripts should * be run by KRun. This is enabled by default. * @param b whether to run executable files or not. * @see isExecutable() */ void setRunExecutables(bool b); /** * Sets whether KRun should follow URLs redirections. * This is enabled by default * @param b whether to follow redirections or not. * @since 5.55 */ void setFollowRedirections(bool b); /** * Sets whether the external webbrowser setting should be honoured. * This is enabled by default. * This should only be disabled in webbrowser applications. * @param b whether to enable the external browser or not. */ void setEnableExternalBrowser(bool b); /** * Sets the file name to use in the case of downloading the file to a tempfile * in order to give to a non-url-aware application. Some apps rely on the extension * to determine the mimetype of the file. Usually the file name comes from the URL, * but in the case of the HTTP Content-Disposition header, we need to override the * file name. */ void setSuggestedFileName(const QString &fileName); /** * Sets whether a prompt should be shown before executing scripts or desktop files. * If enabled, KRun uses the "kiorc" configuration file to decide whether to open the * file, execute it or show a prompt. * @since 5.4 */ void setShowScriptExecutionPrompt(bool showPrompt); /** * Suggested file name given by the server (e.g. HTTP content-disposition) */ QString suggestedFileName() const; /** * Associated window, as passed to the constructor * @since 4.9.3 */ QWidget *window() const; +#if KIOWIDGETS_ENABLE_DEPRECATED_SINCE(5, 6) /** * Open a list of URLs with a certain service (application). * * @param service the service to run * @param urls the list of URLs, can be empty (app launched * without argument) * @param window The top-level widget of the app that invoked this object. * @param tempFiles if true and urls are local files, they will be deleted * when the application exits. * @param suggestedFileName see setSuggestedFileName * @param asn Application startup notification id, if any (otherwise ""). * @return @c true on success, @c false on error * * @deprecated since 5.6, use runApplication instead. No change needed on the application side, * the only difference is the return value (qint64 instead of bool). */ -#ifndef KIOWIDGETS_NO_DEPRECATED - static KIOWIDGETS_DEPRECATED bool run(const KService &service, const QList &urls, QWidget *window, + KIOWIDGETS_DEPRECATED_VERSION(5, 6, "Use KRun::runApplication(const KService &, const QList &, QWidget *, RunFlags, const QString &, const QByteArray &") + static bool run(const KService &service, const QList &urls, QWidget *window, bool tempFiles = false, const QString &suggestedFileName = QString(), const QByteArray &asn = QByteArray()); #endif /** * Open a list of URLs with a certain service (application). * * Prefer runApplication(), unless you need to wait for the application * to register to D-Bus before this method returns (but that should rather * be done with D-Bus activation). * * @param service the service to run * @param urls the list of URLs, can be empty (app launched * without argument) * @param window The top-level widget of the app that invoked this object. * @param tempFiles if true and urls are local files, they will be deleted * when the application exits. * @param suggestedFileName see setSuggestedFileName * @param asn Application startup notification id, if any (otherwise ""). * @return 0 on error, the process ID on success * @since 5.6 */ static qint64 runService(const KService &service, const QList &urls, QWidget *window, bool tempFiles = false, const QString &suggestedFileName = QString(), const QByteArray &asn = QByteArray()); // TODO KF6: deprecate/remove enum RunFlag { DeleteTemporaryFiles = 0x1, ///< the URLs passed to the service will be deleted when it exits (if the URLs are local files) RunExecutables = 0x2, ///< Whether to run URLs that are executable scripts or binaries @see isExecutableFile() @since 5.31 }; Q_DECLARE_FLAGS(RunFlags, RunFlag) /** * Run an application (known from its .desktop file, i.e. as a KService) * * Unlike runService, this does not wait for the application to register to D-Bus * before returning. Such behavior is better done with D-Bus activation anyway. * * @param service the service to run * @param urls the list of URLs, can be empty (app launched * without argument) * @param window The top-level widget of the app that invoked this object. * @param flags various flags * @param suggestedFileName see setSuggestedFileName * @param asn Application startup notification id, if any (otherwise ""). * @return 0 on error, the process ID on success * @since 5.24 */ static qint64 runApplication(const KService &service, const QList &urls, QWidget *window, RunFlags flags = RunFlags(), const QString &suggestedFileName = QString(), const QByteArray &asn = QByteArray()); /** * Open a list of URLs with an executable. * * @param exec the name of the executable, for example * "/usr/bin/netscape %u". * Don't forget to include the %u if you know that the applications * supports URLs. Otherwise, non-local urls will first be downloaded * to a temp file (using kioexec). * @param urls the list of URLs to open, can be empty (app launched without argument) * @param window The top-level widget of the app that invoked this object. * @param name the logical name of the application, for example * "Netscape 4.06". * @param icon the icon which should be used by the application. * @param asn Application startup notification id, if any (otherwise ""). * @return @c true on success, @c false on error */ static bool run(const QString &exec, const QList &urls, QWidget *window, const QString &name = QString(), const QString &icon = QString(), const QByteArray &asn = QByteArray()); +#if KIOWIDGETS_ENABLE_DEPRECATED_SINCE(5, 31) /** * Open the given URL. * * This function is used after the mime type * is found out. It will search for all services which can handle * the mime type and call run() afterwards. * @param url the URL to open * @param mimetype the mime type of the resource * @param window The top-level widget of the app that invoked this object. * @param tempFile if true and url is a local file, it will be deleted * when the launched application exits. * @param runExecutables if false then local .desktop files, * executables and shell scripts will not be run. * See also isExecutable(). * @param suggestedFileName see setSuggestedFileName * @param asn Application startup notification id, if any (otherwise ""). * @return @c true on success, @c false on error * @deprecated since 5.31, use runUrl() with RunFlags instead. */ -#ifndef KIOWIDGETS_NO_DEPRECATED - static bool KIOWIDGETS_DEPRECATED runUrl(const QUrl &url, const QString &mimetype, QWidget *window, - bool tempFile = false, bool runExecutables = true, - const QString &suggestedFileName = QString(), const QByteArray &asn = QByteArray()); + KIOWIDGETS_DEPRECATED_VERSION(5, 31, "Use KRun::const QUrl &, const QString &, QWidget *, RunFlags, const QString &, const QByteArray &") + static bool runUrl(const QUrl &url, const QString &mimetype, QWidget *window, + bool tempFile = false, bool runExecutables = true, + const QString &suggestedFileName = QString(), const QByteArray &asn = QByteArray()); #endif /** * Open the given URL. * * This function can be used after the mime type has been found out. * It will search for all services which can handle the mime type and call run() afterwards. * * @param url The URL to open. * @param mimetype The mime type of the resource. * @param window The top-level widget of the app that invoked this object. * @param flags Various run flags. * @param suggestedFileName See setSuggestedFileName() * @param asn Application startup notification id, if any (otherwise ""). * @return @c true on success, @c false on error * @since 5.31 */ static bool runUrl(const QUrl &url, const QString &mimetype, QWidget *window, RunFlags flags, const QString &suggestedFileName = QString(), const QByteArray &asn = QByteArray()); /** * Run the given shell command and notifies KDE of the starting * of the application. If the program to be called doesn't exist, * an error box will be displayed. * * Use only when you know the full command line. Otherwise use the other * static methods, or KRun's constructor. * * @param cmd must be a shell command. You must not append "&" * to it, since the function will do that for you. * @param window The top-level widget of the app that invoked this object. * @param workingDirectory directory to use for relative paths, so that * a command like "kwrite file.txt" finds file.txt from the right place * * @return @c true on success, @c false on error */ static bool runCommand(const QString &cmd, QWidget *window, const QString &workingDirectory = QString()); /** * Same as the other runCommand(), but it also takes the name of the * binary, to display an error message in case it couldn't find it. * * @param cmd must be a shell command. You must not append "&" * to it, since the function will do that for you. * @param execName the name of the executable * @param icon icon for app starting notification * @param window The top-level widget of the app that invoked this object. * @param asn Application startup notification id, if any (otherwise ""). * @return @c true on success, @c false on error */ static bool runCommand(const QString &cmd, const QString &execName, const QString &icon, QWidget *window, const QByteArray &asn = QByteArray()); /** * Overload that also takes a working directory, so that a command like * "kwrite file.txt" finds file.txt from the right place. * @param workingDirectory the working directory for the started process. The default * (if passing an empty string) is the user's document path. * @since 4.4 */ static bool runCommand(const QString &cmd, const QString &execName, const QString &icon, QWidget *window, const QByteArray &asn, const QString &workingDirectory); // TODO KDE5: merge the above with 5-args runCommand, using QString() /** * Display the Open-With dialog for those URLs, and run the chosen application. * @param lst the list of applications to run * @param window The top-level widget of the app that invoked this object. * @param tempFiles if true and lst are local files, they will be deleted * when the application exits. * @param suggestedFileName see setSuggestedFileName * @param asn Application startup notification id, if any (otherwise ""). * @return false if the dialog was canceled */ static bool displayOpenWithDialog(const QList &lst, QWidget *window, bool tempFiles = false, const QString &suggestedFileName = QString(), const QByteArray &asn = QByteArray()); // TODO deprecate and provide RunFlags() overload +#if KIOWIDGETS_ENABLE_DEPRECATED_SINCE(4, 0) /** * Quotes a string for the shell. * An empty string will @em not be quoted. * - * @deprecated Use KShell::quoteArg() instead. @em Note that this function + * @param str the string to quote. The quoted string will be written here + * + * @deprecated Since 4.0, use KShell::quoteArg() instead. @em Note that this function * behaves differently for empty arguments and returns the result * differently. - * - * @param str the string to quote. The quoted string will be written here */ -#ifndef KIOWIDGETS_NO_DEPRECATED - static KIOWIDGETS_DEPRECATED void shellQuote(QString &str); + KIOWIDGETS_DEPRECATED_VERSION(4, 0, "Use KShell::quoteArg(...)") + static void shellQuote(QString &str); #endif +#if KIOWIDGETS_ENABLE_DEPRECATED_SINCE(5, 0) /** * Processes a Exec= line as found in .desktop files. * @param _service the service to extract information from. * @param _urls The urls the service should open. * @param tempFiles if true and urls are local files, they will be deleted * when the application exits. * @param suggestedFileName see setSuggestedFileName * * @return a list of arguments suitable for QProcess. * @deprecated since 5.0, use KIO::DesktopExecParser */ -#ifndef KIOWIDGETS_NO_DEPRECATED - static KIOWIDGETS_DEPRECATED QStringList processDesktopExec(const KService &_service, const QList &_urls, + KIOWIDGETS_DEPRECATED_VERSION(5, 0, "Use KIO::DesktopExecParser") + static QStringList processDesktopExec(const KService &_service, const QList &_urls, bool tempFiles = false, const QString &suggestedFileName = QString()); #endif +#if KIOWIDGETS_ENABLE_DEPRECATED_SINCE(5, 0) /** * Given a full command line (e.g. the Exec= line from a .desktop file), * extract the name of the binary being run. * @param execLine the full command line * @param removePath if true, remove a (relative or absolute) path. E.g. /usr/bin/ls becomes ls. * @return the name of the executable to run * @deprecated since 5.0, use KIO::DesktopExecParser::executableName if removePath was true, * or KIO::DesktopExecParser::executablePath if removePath was false. */ -#ifndef KIOWIDGETS_NO_DEPRECATED + KIOWIDGETS_DEPRECATED_VERSION(5, 0, "See API docs") static QString binaryName(const QString &execLine, bool removePath); #endif /** * Returns whether @p mimeType refers to an executable program instead * of a data file. */ static bool isExecutable(const QString &mimeType); /** * Returns whether the @p url of @p mimetype is executable. * To be executable the file must pass the following rules: * -# Must reside on the local filesystem. * -# Must be marked as executable for the user by the filesystem. * -# The mime type must inherit application/x-executable, application/x-executable-script or application/x-sharedlib. * To allow a script to run when the above rules are satisfied add the entry * @code * X-KDE-IsAlso=application/x-executable-script * @endcode * to the mimetype's desktop file. */ static bool isExecutableFile(const QUrl &url, const QString &mimetype); /** * @internal */ static bool checkStartupNotify(const QString &binName, const KService *service, bool *silent_arg, QByteArray *wmclass_arg); Q_SIGNALS: /** * Emitted when the operation finished. * This signal is emitted in all cases of completion, whether successful or with error. * @see hasFinished() */ void finished(); /** * Emitted when the operation had an error. * @see hasError() */ void error(); protected Q_SLOTS: /** * All following protected slots are used by subclasses of KRun! */ /** * This slot is called whenever the internal timer fired, * in order to move on to the next step. */ void slotTimeout(); // KDE5: rename to slotNextStep() or something like that /** * This slot is called when the scan job is finished. */ void slotScanFinished(KJob *); /** * This slot is called when the scan job has found out * the mime type. */ void slotScanMimeType(KIO::Job *, const QString &type); /** * Call this from subclasses when you have determined the mimetype. * It will call foundMimeType, but also sets up protection against deletion during message boxes. * @since 4.0.2 */ void mimeTypeDetermined(const QString &mimeType); /** * This slot is called when the 'stat' job has finished. */ virtual void slotStatResult(KJob *); protected: /** * All following protected methods are used by subclasses of KRun! */ /** * Initializes the krun object. */ virtual void init(); /** * Start scanning a file. */ virtual void scanFile(); /** * Called if the mimetype has been detected. The function runs * the application associated with this mimetype. * Reimplement this method to implement a different behavior, * like opening the component for displaying the URL embedded. * * Important: call setFinished(true) once you are done! * Usually at the end of the foundMimeType reimplementation, but if the * reimplementation is asynchronous (e.g. uses KIO jobs) then * it can be called later instead. */ virtual void foundMimeType(const QString &type); /** * Kills the file scanning job. */ virtual void killJob(); /** * Called when KRun detects an error during the init phase. * * The default implementation shows a message box. * @since 5.0 */ virtual void handleInitError(int kioErrorCode, const QString &errorMsg); /** * Called when a KIO job started by KRun gives an error. * * The default implementation shows a message box. */ virtual void handleError(KJob *job); /** * Sets the url. */ void setUrl(const QUrl &url); /** * Returns the url. */ QUrl url() const; /** * Sets whether an error has occurred. */ void setError(bool error); /** * Sets whether progress information shall be shown. */ void setProgressInfo(bool progressInfo); /** * Returns whether progress information are shown. */ bool progressInfo() const; /** * Marks this 'KRun' instance as finished. */ void setFinished(bool finished); /** * Sets the job. */ void setJob(KIO::Job *job); /** * Returns the job. */ KIO::Job *job(); +#if KIOWIDGETS_ENABLE_DEPRECATED_SINCE(4, 4) /** * Returns the timer object. - * @deprecated setFinished(true) now takes care of the timer().start(0), + * @deprecated Since 4.4. setFinished(true) now takes care of the timer().start(0), * so this can be removed. */ -#ifndef KIOWIDGETS_NO_DEPRECATED - KIOWIDGETS_DEPRECATED QTimer &timer(); + KIOWIDGETS_DEPRECATED_VERSION(4, 4, "See API docs") + QTimer &timer(); #endif +#if KIOWIDGETS_ENABLE_DEPRECATED_SINCE(4, 1) /** * Indicate that the next action is to scan the file. - * @deprecated not useful in public API + * @deprecated Since 4.1. Not useful in public API */ -#ifndef KIOWIDGETS_NO_DEPRECATED - KIOWIDGETS_DEPRECATED void setDoScanFile(bool scanFile); + KIOWIDGETS_DEPRECATED_VERSION(4, 1, "Do not use") + void setDoScanFile(bool scanFile); #endif +#if KIOWIDGETS_ENABLE_DEPRECATED_SINCE(4, 1) /** * Returns whether the file shall be scanned. - * @deprecated not useful in public API + * @deprecated Since 4.1. Not useful in public API */ -#ifndef KIOWIDGETS_NO_DEPRECATED - KIOWIDGETS_DEPRECATED bool doScanFile() const; + KIOWIDGETS_DEPRECATED_VERSION(4, 1, "Do not use") + bool doScanFile() const; #endif +#if KIOWIDGETS_ENABLE_DEPRECATED_SINCE(4, 1) /** * Sets whether it is a directory. - * @deprecated typo in the name, and not useful as a public method + * @deprecated Since 4.1. Typo in the name, and not useful as a public method */ -#ifndef KIOWIDGETS_NO_DEPRECATED - KIOWIDGETS_DEPRECATED void setIsDirecory(bool isDirectory); + KIOWIDGETS_DEPRECATED_VERSION(4, 1, "Do not use") + void setIsDirecory(bool isDirectory); #endif /** * Returns whether it is a directory. */ bool isDirectory() const; +#if KIOWIDGETS_ENABLE_DEPRECATED_SINCE(4, 1) /** - * @deprecated not useful in public API + * @deprecated Since 4.1. Not useful in public API */ -#ifndef KIOWIDGETS_NO_DEPRECATED - KIOWIDGETS_DEPRECATED void setInitializeNextAction(bool initialize); + KIOWIDGETS_DEPRECATED_VERSION(4, 1, "Do not use") + void setInitializeNextAction(bool initialize); #endif +#if KIOWIDGETS_ENABLE_DEPRECATED_SINCE(4, 1) /** - * @deprecated not useful in public API + * @deprecated Since 4.1. Not useful in public API */ -#ifndef KIOWIDGETS_NO_DEPRECATED - KIOWIDGETS_DEPRECATED bool initializeNextAction() const; + KIOWIDGETS_DEPRECATED_VERSION(4, 1, "Do not use") + bool initializeNextAction() const; #endif /** * Returns whether it is a local file. */ bool isLocalFile() const; private: class KRunPrivate; KRunPrivate *const d; }; #endif diff --git a/src/widgets/kurifilter.cpp b/src/widgets/kurifilter.cpp index 2cf76b8e..5bb8391f 100644 --- a/src/widgets/kurifilter.cpp +++ b/src/widgets/kurifilter.cpp @@ -1,695 +1,693 @@ /* This file is part of the KDE libraries * Copyright (C) 2000 Yves Arrouye * Copyright (C) 2000,2010 Dawit Alemayehu * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. **/ #include "kurifilter.h" #include "hostinfo.h" #include #include #include #include #include #include #include typedef QList KUriFilterPluginList; typedef QMap SearchProviderMap; static QString lookupIconNameFor(const QUrl &url, KUriFilterData::UriTypes type) { QString iconName; switch (type) { case KUriFilterData::NetProtocol: iconName = KIO::iconNameForUrl(url); break; case KUriFilterData::Executable: { QString exeName = url.path(); exeName.remove(0, exeName.lastIndexOf(QLatin1Char('/')) + 1); // strip path if given KService::Ptr service = KService::serviceByDesktopName(exeName); if (service && service->icon() != QLatin1String("unknown")) { iconName = service->icon(); } // Try to find an icon with the same name as the binary (useful for non-kde apps) // Use iconPath rather than loadIcon() as the latter uses QPixmap (not threadsafe) else if (!KIconLoader::global()->iconPath(exeName, KIconLoader::NoGroup, true).isNull()) { iconName = exeName; } else // not found, use default { iconName = QStringLiteral("system-run"); } break; } case KUriFilterData::Help: { iconName = QStringLiteral("khelpcenter"); break; } case KUriFilterData::Shell: { iconName = QStringLiteral("konsole"); break; } case KUriFilterData::Error: case KUriFilterData::Blocked: { iconName = QStringLiteral("error"); break; } default: break; } return iconName; } class Q_DECL_HIDDEN KUriFilterSearchProvider::KUriFilterSearchProviderPrivate { public: KUriFilterSearchProviderPrivate() {} KUriFilterSearchProviderPrivate(const KUriFilterSearchProviderPrivate &other) : desktopEntryName(other.desktopEntryName), iconName(other.iconName), name(other.name), keys(other.keys) {} QString desktopEntryName; QString iconName; QString name; QStringList keys; }; KUriFilterSearchProvider::KUriFilterSearchProvider() : d(new KUriFilterSearchProvider::KUriFilterSearchProviderPrivate) { } KUriFilterSearchProvider::KUriFilterSearchProvider(const KUriFilterSearchProvider &other) : d(new KUriFilterSearchProvider::KUriFilterSearchProviderPrivate(*(other.d))) { } KUriFilterSearchProvider::~KUriFilterSearchProvider() { delete d; } QString KUriFilterSearchProvider::desktopEntryName() const { return d->desktopEntryName; } QString KUriFilterSearchProvider::iconName() const { return d->iconName; } QString KUriFilterSearchProvider::name() const { return d->name; } QStringList KUriFilterSearchProvider::keys() const { return d->keys; } QString KUriFilterSearchProvider::defaultKey() const { if (d->keys.isEmpty()) { return QString(); } return d->keys.first(); } KUriFilterSearchProvider &KUriFilterSearchProvider::operator=(const KUriFilterSearchProvider &other) { d->desktopEntryName = other.d->desktopEntryName; d->iconName = other.d->iconName; d->keys = other.d->keys; d->name = other.d->name; return *this; } void KUriFilterSearchProvider::setDesktopEntryName(const QString &desktopEntryName) { d->desktopEntryName = desktopEntryName; } void KUriFilterSearchProvider::setIconName(const QString &iconName) { d->iconName = iconName; } void KUriFilterSearchProvider::setName(const QString &name) { d->name = name; } void KUriFilterSearchProvider::setKeys(const QStringList &keys) { d->keys = keys; } class KUriFilterDataPrivate { public: explicit KUriFilterDataPrivate(const QUrl &u, const QString &typedUrl) : checkForExecs(true), wasModified(true), uriType(KUriFilterData::Unknown), searchFilterOptions(KUriFilterData::SearchFilterOptionNone), url(u.adjusted(QUrl::NormalizePathSegments)), typedString(typedUrl) { } ~KUriFilterDataPrivate() { } void setData(const QUrl &u, const QString &typedUrl) { checkForExecs = true; wasModified = true; uriType = KUriFilterData::Unknown; searchFilterOptions = KUriFilterData::SearchFilterOptionNone; url = u.adjusted(QUrl::NormalizePathSegments); typedString = typedUrl; errMsg.clear(); iconName.clear(); absPath.clear(); args.clear(); searchTerm.clear(); searchProvider.clear(); searchTermSeparator = QChar(); alternateDefaultSearchProvider.clear(); alternateSearchProviders.clear(); searchProviderMap.clear(); defaultUrlScheme.clear(); } KUriFilterDataPrivate(KUriFilterDataPrivate *data) { wasModified = data->wasModified; checkForExecs = data->checkForExecs; uriType = data->uriType; searchFilterOptions = data->searchFilterOptions; url = data->url; typedString = data->typedString; errMsg = data->errMsg; iconName = data->iconName; absPath = data->absPath; args = data->args; searchTerm = data->searchTerm; searchTermSeparator = data->searchTermSeparator; searchProvider = data->searchProvider; alternateDefaultSearchProvider = data->alternateDefaultSearchProvider; alternateSearchProviders = data->alternateSearchProviders; searchProviderMap = data->searchProviderMap; defaultUrlScheme = data->defaultUrlScheme; } bool checkForExecs; bool wasModified; KUriFilterData::UriTypes uriType; KUriFilterData::SearchFilterOptions searchFilterOptions; QUrl url; QString typedString; QString errMsg; QString iconName; QString absPath; QString args; QString searchTerm; QString searchProvider; QString alternateDefaultSearchProvider; QString defaultUrlScheme; QChar searchTermSeparator; QStringList alternateSearchProviders; QStringList searchProviderList; SearchProviderMap searchProviderMap; }; KUriFilterData::KUriFilterData() : d(new KUriFilterDataPrivate(QUrl(), QString())) { } KUriFilterData::KUriFilterData(const QUrl &url) : d(new KUriFilterDataPrivate(url, url.toString())) { } KUriFilterData::KUriFilterData(const QString &url) : d(new KUriFilterDataPrivate(QUrl::fromUserInput(url), url)) { } KUriFilterData::KUriFilterData(const KUriFilterData &other) : d(new KUriFilterDataPrivate(other.d)) { } KUriFilterData::~KUriFilterData() { delete d; } QUrl KUriFilterData::uri() const { return d->url; } QString KUriFilterData::errorMsg() const { return d->errMsg; } KUriFilterData::UriTypes KUriFilterData::uriType() const { return d->uriType; } QString KUriFilterData::absolutePath() const { return d->absPath; } bool KUriFilterData::hasAbsolutePath() const { return !d->absPath.isEmpty(); } QString KUriFilterData::argsAndOptions() const { return d->args; } bool KUriFilterData::hasArgsAndOptions() const { return !d->args.isEmpty(); } bool KUriFilterData::checkForExecutables() const { return d->checkForExecs; } QString KUriFilterData::typedString() const { return d->typedString; } QString KUriFilterData::searchTerm() const { return d->searchTerm; } QChar KUriFilterData::searchTermSeparator() const { return d->searchTermSeparator; } QString KUriFilterData::searchProvider() const { return d->searchProvider; } QStringList KUriFilterData::preferredSearchProviders() const { return d->searchProviderList; } KUriFilterSearchProvider KUriFilterData::queryForSearchProvider(const QString &provider) const { const KUriFilterSearchProvider *searchProvider = d->searchProviderMap.value(provider); if (searchProvider) { return *(searchProvider); } return KUriFilterSearchProvider(); } QString KUriFilterData::queryForPreferredSearchProvider(const QString &provider) const { const KUriFilterSearchProvider *searchProvider = d->searchProviderMap.value(provider); if (searchProvider) { return (searchProvider->defaultKey() % searchTermSeparator() % searchTerm()); } return QString(); } QStringList KUriFilterData::allQueriesForSearchProvider(const QString &provider) const { const KUriFilterSearchProvider *searchProvider = d->searchProviderMap.value(provider); if (searchProvider) { return searchProvider->keys(); } return QStringList(); } QString KUriFilterData::iconNameForPreferredSearchProvider(const QString &provider) const { const KUriFilterSearchProvider *searchProvider = d->searchProviderMap.value(provider); if (searchProvider) { return searchProvider->iconName(); } return QString(); } QStringList KUriFilterData::alternateSearchProviders() const { return d->alternateSearchProviders; } QString KUriFilterData::alternateDefaultSearchProvider() const { return d->alternateDefaultSearchProvider; } QString KUriFilterData::defaultUrlScheme() const { return d->defaultUrlScheme; } KUriFilterData::SearchFilterOptions KUriFilterData::searchFilteringOptions() const { return d->searchFilterOptions; } QString KUriFilterData::iconName() { if (d->wasModified) { d->iconName = lookupIconNameFor(d->url, d->uriType); d->wasModified = false; } return d->iconName; } void KUriFilterData::setData(const QUrl &url) { d->setData(url, url.toString()); } void KUriFilterData::setData(const QString &url) { d->setData(QUrl(url), url); } bool KUriFilterData::setAbsolutePath(const QString &absPath) { // Since a malformed URL could possibly be a relative // URL we tag it as a possible local resource... if ((d->url.scheme().isEmpty() || d->url.isLocalFile())) { d->absPath = absPath; return true; } return false; } void KUriFilterData::setCheckForExecutables(bool check) { d->checkForExecs = check; } void KUriFilterData::setAlternateSearchProviders(const QStringList &providers) { d->alternateSearchProviders = providers; } void KUriFilterData::setAlternateDefaultSearchProvider(const QString &provider) { d->alternateDefaultSearchProvider = provider; } void KUriFilterData::setDefaultUrlScheme(const QString &scheme) { d->defaultUrlScheme = scheme; } void KUriFilterData::setSearchFilteringOptions(SearchFilterOptions options) { d->searchFilterOptions = options; } KUriFilterData &KUriFilterData::operator=(const QUrl &url) { d->setData(url, url.toString()); return *this; } KUriFilterData &KUriFilterData::operator=(const QString &url) { d->setData(QUrl(url), url); return *this; } /************************* KUriFilterPlugin ******************************/ KUriFilterPlugin::KUriFilterPlugin(const QString &name, QObject *parent) : QObject(parent), d(nullptr) { setObjectName(name); } // KF6 TODO //KUriFilterPlugin::~KUriFilterPlugin() //{ //} KCModule *KUriFilterPlugin::configModule(QWidget *, const char *) const { return nullptr; } QString KUriFilterPlugin::configName() const { return objectName(); } void KUriFilterPlugin::setFilteredUri(KUriFilterData &data, const QUrl &uri) const { data.d->url = uri.adjusted(QUrl::NormalizePathSegments); data.d->wasModified = true; //qDebug() << "Got filtered to:" << uri; } void KUriFilterPlugin::setErrorMsg(KUriFilterData &data, const QString &errmsg) const { data.d->errMsg = errmsg; } void KUriFilterPlugin::setUriType(KUriFilterData &data, KUriFilterData::UriTypes type) const { data.d->uriType = type; data.d->wasModified = true; } void KUriFilterPlugin::setArguments(KUriFilterData &data, const QString &args) const { data.d->args = args; } void KUriFilterPlugin::setSearchProvider(KUriFilterData &data, const QString &provider, const QString &term, const QChar &separator) const { data.d->searchProvider = provider; data.d->searchTerm = term; data.d->searchTermSeparator = separator; } void KUriFilterPlugin::setSearchProviders(KUriFilterData &data, const QList &providers) const { data.d->searchProviderList.reserve(data.d->searchProviderList.size() + providers.size()); for (KUriFilterSearchProvider *searchProvider : providers) { data.d->searchProviderList << searchProvider->name(); data.d->searchProviderMap.insert(searchProvider->name(), searchProvider); } } QString KUriFilterPlugin::iconNameFor(const QUrl &url, KUriFilterData::UriTypes type) const { return lookupIconNameFor(url, type); } QHostInfo KUriFilterPlugin::resolveName(const QString &hostname, unsigned long timeout) const { return KIO::HostInfo::lookupHost(hostname, timeout); } /******************************* KUriFilter ******************************/ class KUriFilterPrivate { public: KUriFilterPrivate() {} ~KUriFilterPrivate() { qDeleteAll(pluginList); pluginList.clear(); } QVector pluginList; }; class KUriFilterSingleton { public: KUriFilter instance; }; Q_GLOBAL_STATIC(KUriFilterSingleton, m_self) KUriFilter *KUriFilter::self() { return &m_self()->instance; } KUriFilter::KUriFilter() : d(new KUriFilterPrivate()) { loadPlugins(); } KUriFilter::~KUriFilter() { delete d; } bool KUriFilter::filterUri(KUriFilterData &data, const QStringList &filters) { bool filtered = false; for (KUriFilterPlugin *plugin : qAsConst(d->pluginList)) { // If no specific filters were requested, iterate through all the plugins. // Otherwise, only use available filters. if (filters.isEmpty() || filters.contains(plugin->objectName())) { if (plugin->filterUri(data)) { filtered = true; } } } return filtered; } bool KUriFilter::filterUri(QUrl &uri, const QStringList &filters) { KUriFilterData data(uri); bool filtered = filterUri(data, filters); if (filtered) { uri = data.uri(); } return filtered; } bool KUriFilter::filterUri(QString &uri, const QStringList &filters) { KUriFilterData data(uri); bool filtered = filterUri(data, filters); if (filtered) { uri = data.uri().toString(); } return filtered; } QUrl KUriFilter::filteredUri(const QUrl &uri, const QStringList &filters) { KUriFilterData data(uri); filterUri(data, filters); return data.uri(); } QString KUriFilter::filteredUri(const QString &uri, const QStringList &filters) { KUriFilterData data(uri); filterUri(data, filters); return data.uri().toString(); } -#ifndef KIOWIDGETS_NO_DEPRECATED bool KUriFilter::filterSearchUri(KUriFilterData &data) { return filterSearchUri(data, (NormalTextFilter | WebShortcutFilter)); } -#endif bool KUriFilter::filterSearchUri(KUriFilterData &data, SearchFilterTypes types) { QStringList filters; if (types & WebShortcutFilter) { filters << QStringLiteral("kurisearchfilter"); } if (types & NormalTextFilter) { filters << QStringLiteral("kuriikwsfilter"); } return filterUri(data, filters); } QStringList KUriFilter::pluginNames() const { QStringList res; res.reserve(d->pluginList.size()); std::transform(d->pluginList.constBegin(), d->pluginList.constEnd(), std::back_inserter(res), [](const KUriFilterPlugin *plugin) { return plugin->objectName(); }); return res; } void KUriFilter::loadPlugins() { QVector plugins = KPluginLoader::findPlugins(QStringLiteral("kf5/urifilters")); const QString prefKey = QStringLiteral("X-KDE-InitialPreference"); // Sort the plugins by order of priority std::sort(plugins.begin(), plugins.end(), [prefKey](const KPluginMetaData &a, const KPluginMetaData &b) { return a.rawData().value(prefKey).toInt() > b.rawData().value(prefKey).toInt(); }); QStringList pluginNames; pluginNames.reserve(plugins.count()); for (const KPluginMetaData &pluginMetaData : qAsConst(plugins)) { const QString fileName = pluginMetaData.fileName().section(QLatin1Char('/'), -1); if (!pluginNames.contains(fileName)) { pluginNames << fileName; KPluginFactory *factory = qobject_cast(pluginMetaData.instantiate()); if (factory) { KUriFilterPlugin *plugin = factory->create(nullptr); if (plugin) { d->pluginList << plugin; } } } } } diff --git a/src/widgets/kurifilter.h b/src/widgets/kurifilter.h index 88c6469f..a2f4962b 100644 --- a/src/widgets/kurifilter.h +++ b/src/widgets/kurifilter.h @@ -1,1012 +1,1014 @@ /* * This file is part of the KDE libraries * Copyright (C) 2000-2001,2003,2010 Dawit Alemayehu * * Original author * Copyright (C) 2000 Yves Arrouye * * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. **/ #ifndef KURIFILTER_H #define KURIFILTER_H #include "kiowidgets_export.h" #include #include #include #include #include #ifdef Q_OS_WIN #undef ERROR #endif class KUriFilterPrivate; class KUriFilterDataPrivate; class KCModule; class QHostInfo; /** * @class KUriFilterSearchProvider kurifilter.h * * Class that holds information about a search provider. * * @since 4.6 */ class KIOWIDGETS_EXPORT KUriFilterSearchProvider { public: /** * Default constructor. */ KUriFilterSearchProvider(); /** * Copy constructor. */ KUriFilterSearchProvider(const KUriFilterSearchProvider &); /** * Destructor. */ virtual ~KUriFilterSearchProvider(); /** * Returns the desktop filename of the search provider without any extension. * * For example, if the desktop filename of the search provider was * "foobar.desktop", this function will return "foobar". */ QString desktopEntryName() const; /** * Returns the descriptive name of the search provider, e.g. "Google News". * * This name comes from the "Name=" property entry in the desktop file that * contains the search provider's information. */ QString name() const; /** * Returns the icon name associated with the search provider when available. */ virtual QString iconName() const; /** * Returns all the web shortcut keys associated with this search provider. * * @see defaultKey */ QStringList keys() const; /** * Returns the default web shortcut key for this search provider. * * Right now this is the same as doing keys().first(), it might however * change based on what the backend plugins do. * * @see keys */ QString defaultKey() const; /** * Assignment operator. */ KUriFilterSearchProvider &operator=(const KUriFilterSearchProvider &); protected: void setDesktopEntryName(const QString &); void setIconName(const QString &); void setKeys(const QStringList &); void setName(const QString &); private: friend class KUriFilterPlugin; class KUriFilterSearchProviderPrivate; KUriFilterSearchProviderPrivate *const d; }; /** * @class KUriFilterData kurifilter.h * * This class is a basic messaging class used to exchange filtering information * between the filter plugins and the application requesting the filtering * service. * * Use this object if you require a more detailed information about the URI you * want to filter. Any application can create an instance of this class and send * it to KUriFilter to have the plugins fill out all possible information about * the URI. * * On successful filtering you can use @ref uriType() to determine what type * of resource the request was filtered into. See @ref KUriFilter::UriTypes for * details. If an error is encountered, then @ref KUriFilter::Error is returned. * You can use @ref errorMsg to obtain the error information. * * The functions in this class are not reentrant. * * \b Example * * Here is a basic example of how this class is used with @ref KUriFilter: * \code * KUriFilterData filterData (QLatin1String("kde.org")); * bool filtered = KUriFilter::self()->filterUri(filterData); * \endcode * * If you are only interested in getting the list of preferred search providers, * then you can do the following: * * \code * KUriFilterData data; * data.setData(""); * data.setSearchFilteringOption(KUriFilterData::RetrievePreferredSearchProvidersOnly); * bool filtered = KUriFilter::self()->filterSearchUri(data, KUriFilter::NormalTextFilter); * \endcode * * @short A class for exchanging filtering information. * @author Dawit Alemayehu */ class KIOWIDGETS_EXPORT KUriFilterData { public: /** * Describes the type of the URI that was filtered. * Here is a brief description of the types: * * @li NetProtocol - Any network protocol: http, ftp, nttp, pop3, etc... * @li LocalFile - A local file whose executable flag is not set * @li LocalDir - A local directory * @li Executable - A local file whose executable flag is set * @li Help - A man or info page * @li Shell - A shell executable (ex: echo "Test..." >> ~/testfile) * @li Blocked - A URI that should be blocked/filtered (ex: ad filtering) * @li Error - An incorrect URI (ex: "~johndoe" when user johndoe * does not exist in that system ) * @li Unknown - A URI that is not identified. Default value when * a KUriFilterData is first created. */ enum UriTypes { NetProtocol = 0, LocalFile, LocalDir, Executable, Help, Shell, Blocked, Error, Unknown }; /** * This enum describes the search filtering options to be used. * * @li SearchFilterOptionNone * No search filter options are set and normal filtering is performed * on the input data. * @li RetrieveSearchProvidersOnly * If set, the list of all available search providers are returned without * any input filtering. This flag only applies when used in conjunction * with the @ref KUriFilter::NormalTextFilter flag. * @li RetrievePreferredSearchProvidersOnly * If set, the list of preferred search providers are returned without * any input filtering. This flag only applies when used in conjunction * with the @ref KUriFilter::NormalTextFilter flag. * @li RetrieveAvailableSearchProvidersOnly * Same as doing RetrievePreferredSearchProvidersOnly | RetrieveSearchProvidersOnly, * where all available search providers are returned if no preferred ones * ones are available. No input filtering will be performed. * * @see setSearchFilteringOptions * @see KUriFilter::filterSearchUri * @since 4.6 */ enum SearchFilterOption { SearchFilterOptionNone = 0x0, RetrieveSearchProvidersOnly = 0x01, RetrievePreferredSearchProvidersOnly = 0x02, RetrieveAvailableSearchProvidersOnly = (RetrievePreferredSearchProvidersOnly | RetrieveSearchProvidersOnly) }; Q_DECLARE_FLAGS(SearchFilterOptions, SearchFilterOption) /** * Default constructor. * * Creates a UriFilterData object. */ KUriFilterData(); /** * Creates a KUriFilterData object from the given URL. * * @param url is the URL to be filtered. */ explicit KUriFilterData(const QUrl &url); /** * Creates a KUriFilterData object from the given string. * * @param url is the string to be filtered. */ explicit KUriFilterData(const QString &url); /** * Copy constructor. * * Creates a KUriFilterData object from another KURIFilterData object. * * @param other the uri filter data to be copied. */ KUriFilterData(const KUriFilterData &other); /** * Destructor. */ ~KUriFilterData(); /** * Returns the filtered or the original URL. * * If one of the plugins successfully filtered the original input, this * function returns it. Otherwise, it will return the input itself. * * @return the filtered or original url. */ QUrl uri() const; /** * Returns an error message. * * This functions returns the error message set by the plugin whenever the * uri type is set to KUriFilterData::ERROR. Otherwise, it returns a nullptr * string. * * @return the error message or a nullptr when there is none. */ QString errorMsg() const; /** * Returns the URI type. * * This method always returns KUriFilterData::UNKNOWN if the given URL was * not filtered. * * @return the type of the URI */ UriTypes uriType() const; /** * Returns the absolute path if one has already been set. * * @return the absolute path, or QString() * * @see hasAbsolutePath() */ QString absolutePath() const; /** * Checks whether the supplied data had an absolute path. * * @return true if the supplied data has an absolute path * * @see absolutePath() */ bool hasAbsolutePath() const; /** * Returns the command line options and arguments for a local resource * when present. * * @return options and arguments when present, otherwise QString() */ QString argsAndOptions() const; /** * Checks whether the current data is a local resource with command line * options and arguments. * * @return true if the current data has command line options and arguments */ bool hasArgsAndOptions() const; /** * @return true if the filters should attempt to check whether the * supplied uri is an executable. False otherwise. */ bool checkForExecutables() const; /** * The string as typed by the user, before any URL processing is done. */ QString typedString() const; /** * Returns the search term portion of the typed string. * * If the @ref typedString was not filtered by a search filter plugin, this * function returns an empty string. * * @see typedString * @since 4.5 */ QString searchTerm() const; /** * Returns the character that is used to separate the search term from the * keyword. * * If @ref typedString was not filtered by a search filter plugin, this * function returns a null character. * * @see typedString * @since 4.5 */ QChar searchTermSeparator() const; /** * Returns the name of the search service provider, e.g. Google. * * If @ref typedString was not filtered by a search filter plugin, this * function returns an empty string. * * @see typedString * @since 4.5 */ QString searchProvider() const; /** * Returns a list of the names of preferred or available search providers. * * This function returns the list of providers marked as preferred whenever * the input data, i.e. @ref typedString, is successfully filtered. * * If no default search provider has been selected prior to a filter request, * this function will return an empty list. To avoid this problem you must * either set an alternate default search provider using @ref setAlternateDefaultSearchProvider * or set one of the @ref SearchFilterOption flags if you are only interested * in getting the list of providers and not filtering the input. * * Additionally, you can also provide alternate search providers in case * there are no preferred ones already selected. * * You can use @ref queryForPreferredServiceProvider to obtain the query * associated with the list of search providers returned by this function. * * @see setAlternateSearchProviders * @see setAlternateDefaultSearchProvider * @see setSearchFilteringOption * @see queryForPreferredServiceProvider * @since 4.5 */ QStringList preferredSearchProviders() const; /** * Returns information about @p provider. * * You can use this function to obtain the more information about the search * providers returned by @ref preferredSearchProviders. * * @see preferredSearchProviders * @see KUriFilterSearchProvider * @since 4.6 */ KUriFilterSearchProvider queryForSearchProvider(const QString &provider) const; /** * Returns the web shortcut url for the given preferred search provider. * * You can use this function to obtain the query for the preferred search * providers returned by @ref preferredSearchProviders. * * The query returned by this function is in web shortcut format, i.e. * "gg:foo bar", and must be re-filtered through KUriFilter to obtain a * valid url. * * @see preferredSearchProviders * @since 4.5 */ QString queryForPreferredSearchProvider(const QString &provider) const; /** * Returns all the query urls for the given search provider. * * Use this function to obtain all the different queries that can be used * for the given provider. For example, if a search engine provider named * "foobar" has web shortcuts named "foobar", "foo" and "bar", then this * function, unlike @ref queryForPreferredSearchProvider, will return a * a query for each and every web shortcut. * * @see queryForPreferredSearchProvider * @since 4.6 */ QStringList allQueriesForSearchProvider(const QString &provider) const; /** * Returns the icon associated with the given preferred search provider. * * You can use this function to obtain the icon names associated with the * preferred search providers returned by @ref preferredSearchProviders. * * @see preferredSearchProviders * @since 4.5 */ QString iconNameForPreferredSearchProvider(const QString &provider) const; /** * Returns the list of alternate search providers. * * This function returns an empty list if @ref setAlternateSearchProviders * was not called to set the alternate search providers to be when no * preferred providers have been chosen by the user through the search * configuration module. * * @see setAlternatteSearchProviders * @see preferredSearchProviders * @since 4.5 */ QStringList alternateSearchProviders() const; /** * Returns the search provider to use when a default provider is not available. * * This function returns an empty string if @ref setAlternateDefaultSearchProvider * was not called to set the default search provider to be used when none has been * chosen by the user through the search configuration module. * * @see setAlternateDefaultSearchProvider * @since 4.5 */ QString alternateDefaultSearchProvider() const; /** * Returns the default protocol to use when filtering potentially valid url inputs. * * By default this function will return an empty string. * * @see setDefaultUrlScheme * @since 4.6 */ QString defaultUrlScheme() const; /** * Returns the specified search filter options. * * By default this function returns @ref SearchFilterOptionNone. * * @see setSearchFilteringOptions * @since 4.6 */ SearchFilterOptions searchFilteringOptions() const; /** * The name of the icon that matches the current filtered URL. * * This function returns a null string by default and when no icon is found * for the filtered URL. */ QString iconName(); /** * Check whether the provided uri is executable or not. * * Setting this to false ensures that typing the name of an executable does * not start that application. This is useful in the location bar of a * browser. The default value is true. */ void setCheckForExecutables(bool check); /** * Same as above except the argument is a URL. * * Use this function to set the string to be filtered when you construct an * empty filter object. * * @param url the URL to be filtered. */ void setData(const QUrl &url); /** * Sets the URL to be filtered. * * Use this function to set the string to be * filtered when you construct an empty filter * object. * * @param url the string to be filtered. */ void setData(const QString &url); /** * Sets the absolute path to be used whenever the supplied data is a * relative local URL. * * NOTE: This function should only be used for local resources, i.e. the * "file:/" protocol. It is useful for specifying the absolute path in * cases where the actual URL might be relative. If deriving the path from * a QUrl, make sure you set the argument for this function to the result * of calling path () instead of url (). * * @param abs_path the absolute path to the local resource. * * @return true if absolute path is successfully set. Otherwise, false. */ bool setAbsolutePath(const QString &abs_path); /** * Sets a list of search providers to use in case no preferred search * providers are available. * * The list of preferred search providers set using this function will only * be used if the default and favorite search providers have not yet been * selected by the user. Otherwise, the providers specified through this * function will be ignored. * * @see alternateSearchProviders * @see preferredSearchProviders * @since 4.5 */ void setAlternateSearchProviders(const QStringList &providers); /** * Sets the search provider to use in case no default provider is available. * * The default search provider set using this function will only be used if * the default and favorite search providers have not yet been selected by * the user. Otherwise, the default provider specified by through function * will be ignored. * * @see alternateDefaultSearchProvider * @see preferredSearchProviders * @since 4.5 */ void setAlternateDefaultSearchProvider(const QString &provider); /** * Sets the default scheme used when filtering potentially valid url inputs. * * Use this function to change the default protocol used when filtering * potentially valid url inputs. The default protocol is http. * * If the scheme is specified without a separator, then "://" will be used * as the separator by default. For example, if the default url scheme was * simply set to "ftp", then a potentially valid url input such as "kde.org" * will be filtered to "ftp://kde.org". * * @see defaultUrlScheme * @since 4.6 */ void setDefaultUrlScheme(const QString &); /** * Sets the options used by search filter plugins to filter requests. * * The default search filter option is @ref SearchFilterOptionNone. See * @ref SearchFilterOption for the description of the other flags. * * It is important to note that the options set through this function can * prevent any filtering from being performed by search filter plugins. * As such, @ref uriTypes can return KUriFilterData::Unknown and @ref uri * can return an invalid url even though the filtering request returned * a successful response. * * @see searchFilteringOptions * @since 4.6 */ void setSearchFilteringOptions(SearchFilterOptions options); /** * Overloaded assignment operator. * * This function allows you to easily assign a QUrl * to a KUriFilterData object. * * @return an instance of a KUriFilterData object. */ KUriFilterData &operator=(const QUrl &url); /** * Overloaded assignment operator. * * This function allows you to easily assign a QString to a KUriFilterData * object. * * @return an instance of a KUriFilterData object. */ KUriFilterData &operator=(const QString &url); private: friend class KUriFilterPlugin; KUriFilterDataPrivate *const d; }; /** * @class KUriFilterPlugin kurifilter.h * * Base class for URI filter plugins. * * This class applies a single filter to a URI. All plugins designed to provide * URI filtering service should inherit from this abstract class and provide a * concrete implementation. * * All inheriting classes need to implement the pure virtual function * @ref filterUri. * * @short Abstract class for URI filter plugins. */ class KIOWIDGETS_EXPORT KUriFilterPlugin : public QObject { Q_OBJECT public: +#if KIOWIDGETS_ENABLE_DEPRECATED_SINCE(4, 6) /** * List for holding the following search provider information: * ([search provider name], [search query, search query icon name]) * * @since 4.5 - * @deprecated Use @ref KUriFilterSearchProvider instead. See @ref setSearchProviders; + * @deprecated Since 4.6, use @ref KUriFilterSearchProvider instead. See @ref setSearchProviders; */ -#ifndef KIOWIDGETS_NO_DEPRECATED - KIOWIDGETS_DEPRECATED typedef QHash > ProviderInfoList; + KIOWIDGETS_DEPRECATED_VERSION(4, 6, "Use KUriFilterSearchProvider") + typedef QHash > ProviderInfoList; #endif /** * Constructs a filter plugin with a given name * * @param parent the parent object, or @c nullptr for no parent * @param name the name of the plugin, mandatory */ explicit KUriFilterPlugin(const QString &name, QObject *parent = nullptr); // KF6 TODO ~KUriFilterPlugin(); /** * Filters a URI. * * @param data the URI data to be filtered. * @return A boolean indicating whether the URI has been changed. */ virtual bool filterUri(KUriFilterData &data) const = 0; /** * Creates a configuration module for the filter. * * It is the responsibility of the caller to delete the module once it is * not needed anymore. * * @return A configuration module, or @c nullptr if the filter isn't configurable. */ virtual KCModule *configModule(QWidget *, const char *) const; /** * Returns the name of the configuration module for the filter. * * @return the name of a configuration module or QString() if none. */ virtual QString configName() const; protected: /** * Sets the URL in @p data to @p uri. */ void setFilteredUri(KUriFilterData &data, const QUrl &uri) const; /** * Sets the error message in @p data to @p errormsg. */ void setErrorMsg(KUriFilterData &data, const QString &errmsg) const; /** * Sets the URI type in @p data to @p type. */ void setUriType(KUriFilterData &data, KUriFilterData::UriTypes type) const; /** * Sets the arguments and options string in @p data to @p args if any were * found during filtering. */ void setArguments(KUriFilterData &data, const QString &args) const; /** * Sets the name of the search provider, the search term and keyword/term * separator in @p data. * * @since 4.5 */ void setSearchProvider(KUriFilterData &data, const QString &provider, const QString &term, const QChar &separator) const; /** * Sets the information about the search @p providers in @p data. * * @since 4.6 */ void setSearchProviders(KUriFilterData &data, const QList &providers) const; /** * Returns the icon name for the given @p url and URI @p type. * * @since 4.5 */ QString iconNameFor(const QUrl &url, KUriFilterData::UriTypes type) const; /** * Performs a DNS lookup for @p hostname and returns the result. * * This function uses the KIO/KHTML DNS cache to speed up the * lookup. It also avoids doing a reverse lookup if the given * host name is already an ip address. * * \note All uri filter plugins that need to perform a hostname * lookup should use this function. * * @param hostname the hostname to lookup. * @param timeout the amount of time in msecs to wait for the lookup. * @return the result of the host name lookup. * * @since 4.7 */ QHostInfo resolveName(const QString &hostname, unsigned long timeout) const; private: class KUriFilterPluginPrivate *const d; }; /** * @class KUriFilter kurifilter.h * * KUriFilter applies a number of filters to a URI and returns a filtered version if any * filter matches. * A simple example is "kde.org" to "http://www.kde.org", which is commonplace in web browsers. * * The filters are implemented as plugins in @ref KUriFilterPlugin subclasses. * * KUriFilter is a singleton object: obtain the instance by calling * @p KUriFilter::self() and use the public member functions to * perform the filtering. * * \b Example * * To simply filter a given string: * * \code * QString url("kde.org"); * bool filtered = KUriFilter::self()->filteredUri( url ); * \endcode * * You can alternatively use a QUrl: * * \code * QUrl url("kde.org"); * bool filtered = KUriFilter::self()->filterUri( url ); * \endcode * * If you have a constant string or a constant URL, simply invoke the * corresponding function to obtain the filtered string or URL instead * of a boolean flag: * * \code * QString filteredText = KUriFilter::self()->filteredUri( "kde.org" ); * \endcode * * All of the above examples should result in "kde.org" being filtered into * "http://kde.org". * * You can also restrict the filters to be used by supplying the name of the * filters you want to use. By default all available filters are used. * * To use specific filters, add the names of the filters you want to use to a * QStringList and invoke the appropriate filtering function. * * The examples below show the use of specific filters. KDE ships with the * following filter plugins by default: * * kshorturifilter: * This is used for filtering potentially valid url inputs such as "kde.org" * Additionally it filters shell variables and shortcuts such as $HOME and * ~ as well as man and info page shortcuts, # and ## respectively. * * kuriikwsfilter: * This is used for filtering normal input text into a web search url using the * configured fallback search engine selected by the user. * * kurisearchfilter: * This is used for filtering KDE webshortcuts. For example "gg:KDE" will be * converted to a url for searching the work "KDE" using the Google search * engine. * * localdomainfilter: * This is used for doing a DNS lookup to determine whether the input is a valid * local address. * * fixuphosturifilter: * This is used to append "www." to the host name of a pre filtered http url * if the original url cannot be resolved. * * \code * QString text ("kde.org"); * bool filtered = KUriFilter::self()->filterUri(text, QLatin1String("kshorturifilter")); * \endcode * * The above code should result in "kde.org" being filtered into "http://kde.org". * * \code * QStringList list; * list << QLatin1String("kshorturifilter") << QLatin1String("localdomainfilter"); * bool filtered = KUriFilter::self()->filterUri( text, list ); * \endcode * * Additionally if you only want to do search related filtering, you can use the * search specific function, @ref filterSearchUri, that is available in KDE * 4.5 and higher. For example, to search for a given input on the web you * can do the following: * * KUriFilterData filterData ("foo"); * bool filtered = KUriFilter::self()->filterSearchUri(filterData, KUriFilterData::NormalTextFilter); * * KUriFilter converts all filtering requests to use @ref KUriFilterData * internally. The use of this bi-directional class allows you to send specific * instructions to the filter plugins as well as receive detailed information * about the filtered request from them. See the documentation of KUriFilterData * class for more examples and details. * * All functions in this class are thread safe and reentrant. * * @short Filters the given input into a valid url whenever possible. */ class KIOWIDGETS_EXPORT KUriFilter { public: /** * This enum describes the types of search plugin filters available. * * @li NormalTextFilter The plugin used to filter normal text, e.g. "some term to search". * @li WebShortcutFilter The plugin used to filter web shortcuts, e.g. gg:KDE. */ enum SearchFilterType { NormalTextFilter = 0x01, WebShortcutFilter = 0x02 }; Q_DECLARE_FLAGS(SearchFilterTypes, SearchFilterType) /** * Destructor */ ~KUriFilter(); /** * Returns an instance of KUriFilter. */ static KUriFilter *self(); /** * Filters @p data using the specified @p filters. * * If no named filters are specified, the default, then all the * URI filter plugins found will be used. * * @param data object that contains the URI to be filtered. * @param filters specify the list of filters to be used. * * @return a boolean indicating whether the URI has been changed */ bool filterUri(KUriFilterData &data, const QStringList &filters = QStringList()); /** * Filters the URI given by the URL. * * The given URL is filtered based on the specified list of filters. * If the list is empty all available filters would be used. * * @param uri the URI to filter. * @param filters specify the list of filters to be used. * * @return a boolean indicating whether the URI has been changed */ bool filterUri(QUrl &uri, const QStringList &filters = QStringList()); /** * Filters a string representing a URI. * * The given URL is filtered based on the specified list of filters. * If the list is empty all available filters would be used. * * @param uri The URI to filter. * @param filters specify the list of filters to be used. * * @return a boolean indicating whether the URI has been changed */ bool filterUri(QString &uri, const QStringList &filters = QStringList()); /** * Returns the filtered URI. * * The given URL is filtered based on the specified list of filters. * If the list is empty all available filters would be used. * * @param uri The URI to filter. * @param filters specify the list of filters to be used. * * @return the filtered URI or null if it cannot be filtered */ QUrl filteredUri(const QUrl &uri, const QStringList &filters = QStringList()); /** * Return a filtered string representation of a URI. * * The given URL is filtered based on the specified list of filters. * If the list is empty all available filters would be used. * * @param uri the URI to filter. * @param filters specify the list of filters to be used. * * @return the filtered URI or null if it cannot be filtered */ QString filteredUri(const QString &uri, const QStringList &filters = QStringList()); +#if KIOWIDGETS_ENABLE_DEPRECATED_SINCE(4, 6) /** * See @ref filterSearchUri(KUriFilterData&, SearchFilterTypes) * * @since 4.5 - * @deprecated Use filterSearchUri(KUriFilterData&, SearchFilterTypes) instead. + * @deprecated Since 4.6, use filterSearchUri(KUriFilterData&, SearchFilterTypes) instead. */ -#ifndef KIOWIDGETS_NO_DEPRECATED - KIOWIDGETS_DEPRECATED bool filterSearchUri(KUriFilterData &data); + KIOWIDGETS_DEPRECATED_VERSION(4, 6, "Use KUriFilter::filterSearchUri(KUriFilterData &, SearchFilterTypes)") + bool filterSearchUri(KUriFilterData &data); #endif /** * Filter @p data using the criteria specified by @p types. * * The search filter type can be individual value of @ref SearchFilterTypes * or a combination of those types using the bitwise OR operator. * * You can also use the flags from @ref KUriFilterData::SearchFilterOption * to alter the filtering mechanisms of the search filter providers. * * @param data object that contains the URI to be filtered. * @param types the search filters used to filter the request. * @return true if the specified @p data was successfully filtered. * * @see KUriFilterData::setSearchFilteringOptions * @since 4.6 */ bool filterSearchUri(KUriFilterData &data, SearchFilterTypes types); /** * Return a list of the names of all loaded plugins. * * @return a QStringList of plugin names */ QStringList pluginNames() const; protected: /** * Constructor. * * Creates a KUriFilter object and calls @ref loadPlugins to load all * available URI filter plugins. */ KUriFilter(); /** * Loads all allowed plugins. * * This function only loads URI filter plugins that have not been disabled. */ void loadPlugins(); private: KUriFilterPrivate *const d; friend class KUriFilterSingleton; }; Q_DECLARE_OPERATORS_FOR_FLAGS(KUriFilterData::SearchFilterOptions) Q_DECLARE_OPERATORS_FOR_FLAGS(KUriFilter::SearchFilterTypes) #endif diff --git a/src/widgets/kurlrequester.cpp b/src/widgets/kurlrequester.cpp index bea162e1..84538d8b 100644 --- a/src/widgets/kurlrequester.cpp +++ b/src/widgets/kurlrequester.cpp @@ -1,716 +1,708 @@ /* This file is part of the KDE libraries Copyright (C) 1999,2000,2001 Carsten Pfeiffer Copyright (C) 2013 Teo Mrnjavac This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License version 2, as published by the Free Software Foundation. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kurlrequester.h" #include "kio_widgets_debug.h" #include "../pathhelpers_p.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include 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 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, QOverload<>::of(&KComboBox::returnPressed), receiver, QOverload<>::of(&KUrlRequester::returnPressed)); connect(combo, QOverload::of(&KComboBox::returnPressed), receiver, QOverload::of(&KUrlRequester::returnPressed)); } else if (edit) { connect(edit, &QLineEdit::textChanged, receiver, &KUrlRequester::textChanged); connect(edit, &QLineEdit::textEdited, receiver, &KUrlRequester::textEdited); connect(edit, QOverload<>::of(&QLineEdit::returnPressed), receiver, QOverload<>::of(&KUrlRequester::returnPressed)); if (auto kline = qobject_cast(edit)) { connect(kline, QOverload::of(&KLineEdit::returnPressed), receiver, QOverload::of(&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(combo->completionObject()); } else { comp = qobject_cast(edit->completionObject()); } QString enteredPath; if (comp) enteredPath = comp->replacedPath(txt); else enteredPath = txt; if (QDir::isAbsolutePath(enteredPath)) { return QUrl::fromLocalFile(enteredPath); } const QUrl enteredUrl = QUrl(enteredPath); // absolute or relative if (enteredUrl.isRelative() && !txt.isEmpty()) { QUrl finalUrl(m_startDir); finalUrl.setPath(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); } // Converts from "*.foo *.bar|Comment" to "Comment (*.foo *.bar)" QStringList kToQFilters(const QString &filters) const { QStringList qFilters = filters.split(QLatin1Char('\n'), QString::SkipEmptyParts); for (QString &qFilter : qFilters) { int sep = qFilter.indexOf(QLatin1Char('|')); const QStringRef globs = qFilter.leftRef(sep); const QStringRef desc = qFilter.midRef(sep + 1); qFilter = desc + QLatin1String(" (") + globs + QLatin1Char(')'); } return qFilters; } 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 _k_slotUpdateUrl(); void _k_slotOpenDialog(); void _k_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; QString fileDialogFilter; 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(editWidget); d->edit = qobject_cast(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() { delete d; } 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(combo) : static_cast(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, SIGNAL(pressed()), m_parent, SLOT(_k_slotUpdateUrl())); widget->installEventFilter(m_parent); m_parent->setFocusProxy(widget); m_parent->setFocusPolicy(Qt::StrongFocus); topLayout->addWidget(myButton); connectSignals(m_parent); connect(myButton, SIGNAL(clicked()), m_parent, SLOT(_k_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, SIGNAL(triggered(bool)), SLOT(_k_slotOpenDialog())); } void KUrlRequester::setUrl(const QUrl &url) { d->setText(url.toDisplayString(QUrl::PreferLocalFile)); } -#ifndef KIOWIDGETS_NO_DEPRECATED void KUrlRequester::setPath(const QString &path) { d->setText(path); } -#endif 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::_k_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); emit m_parent->urlSelected(url()); } } else { 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::_k_slotFileDialogAccepted() { if (!myFileDialog) { return; } const QUrl newUrl = myFileDialog->selectedUrls().constFirst(); if (newUrl.isValid()) { m_parent->setUrl(newUrl); 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; } void KUrlRequester::setFilter(const QString &filter) { d->fileDialogFilter = filter; if (d->myFileDialog) { d->myFileDialog->setNameFilters(d->kToQFilters(d->fileDialogFilter)); } } QString KUrlRequester::filter() const { return d->fileDialogFilter; } 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; } -#ifndef KIOWIDGETS_NO_DEPRECATED 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->kToQFilters(d->fileDialogFilter)); } d->applyFileMode(d->myFileDialog, d->fileDialogMode, d->fileDialogAcceptMode); d->myFileDialog->setWindowModality(d->fileDialogModality); connect(d->myFileDialog, SIGNAL(accepted()), SLOT(_k_slotFileDialogAccepted())); } return d->myFileDialog; } -#endif void KUrlRequester::clear() { d->setText(QString()); } KLineEdit *KUrlRequester::lineEdit() const { return d->edit; } KComboBox *KUrlRequester::comboBox() const { return d->combo; } void KUrlRequester::KUrlRequesterPrivate::_k_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; } -#ifndef KIOWIDGETS_NO_DEPRECATED void KUrlRequester::setClickMessage(const QString &msg) { setPlaceholderText(msg); } -#endif void KUrlRequester::setPlaceholderText(const QString &msg) { if (d->edit) { d->edit->setPlaceholderText(msg); } } -#ifndef KIOWIDGETS_NO_DEPRECATED QString KUrlRequester::clickMessage() const { return placeholderText(); } -#endif 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(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 "moc_kurlrequester.cpp" #include "kurlrequester.moc" diff --git a/src/widgets/kurlrequester.h b/src/widgets/kurlrequester.h index 28057fad..133e153d 100644 --- a/src/widgets/kurlrequester.h +++ b/src/widgets/kurlrequester.h @@ -1,386 +1,387 @@ /* This file is part of the KDE libraries Copyright (C) 1999,2000,2001 Carsten Pfeiffer Copyright (C) 2013 Teo Mrnjavac This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License version 2, as published by the Free Software Foundation. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef KURLREQUESTER_H #define KURLREQUESTER_H #include "kiowidgets_export.h" #include #include #include #include #include class KComboBox; class KLineEdit; class KUrlCompletion; class QEvent; class QPushButton; class QString; /** * @class KUrlRequester kurlrequester.h * * This class is a widget showing a lineedit and a button, which invokes a * filedialog. File name completion is available in the lineedit. * * The defaults for the filedialog are to ask for one existing local file. * The default filter is "*", i.e. show all files, and the start directory is * the current working directory, or the last directory where a file has been * selected. * * You can change this behavior by using setMode(), setFilter() and 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 */ class KIOWIDGETS_EXPORT KUrlRequester : public QWidget { Q_OBJECT Q_PROPERTY(QUrl url READ url WRITE setUrl NOTIFY textChanged USER true) Q_PROPERTY(QString filter READ filter WRITE setFilter) Q_PROPERTY(KFile::Modes mode READ mode WRITE setMode) Q_PROPERTY(QFileDialog::AcceptMode acceptMode READ acceptMode WRITE setAcceptMode) -#ifndef KIOWIDGETS_NO_DEPRECATED + /// @deprecated Since 5.0, use placeholderText Q_PROPERTY(QString clickMessage READ clickMessage WRITE setClickMessage) -#endif 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(); /** * @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 * @since 4.3 */ 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. * * @since 4.2 */ QString text() const; /** * Sets the mode of the file dialog. * Note: you can only select one file with the filedialog, * so KFile::Files doesn't make much sense. * @see QFileDialog::setFileMode() */ void setMode(KFile::Modes m); /** * Returns the current mode * @see QFileDialog::fileMode() */ KFile::Modes mode() const; /** * Sets the open / save mode of the file dialog. * @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, separated by \n. * @see QFileDialog::setNameFilters() */ void setFilter(const QString &filter); /** * Returns the filters for the file dialog, separated by \n. * @see QFileDialog::nameFilters() */ QString filter() const; /** * Sets the mimetype filters for the file dialog. * @see QFileDialog::setMimeTypeFilters() * @since 5.31 */ void setMimeTypeFilters(const QStringList &mimeTypes); /** * Returns the mimetype 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. */ -#ifndef KIOWIDGETS_NO_DEPRECATED - virtual KIOWIDGETS_DEPRECATED QFileDialog *fileDialog() const; -#endif + 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(); +#if KIOWIDGETS_ENABLE_DEPRECATED_SINCE(5, 0) /** * @returns the message set with setClickMessage * @since 4.2 - * @deprecated use KUrlRequester::placeholderText instead. + * @deprecated Since 5.0, use KUrlRequester::placeholderText instead. */ -#ifndef KIOWIDGETS_NO_DEPRECATED - KIOWIDGETS_DEPRECATED QString clickMessage() const; + KIOWIDGETS_DEPRECATED_VERSION(5, 0, "Use KUrlRequester::placeholderText()") + QString clickMessage() const; #endif +#if KIOWIDGETS_ENABLE_DEPRECATED_SINCE(5, 0) /** * Set a click message @p msg * @since 4.2 - * @deprecated use KUrlRequester::setPlaceholderText instead. + * @deprecated Since 5.0, use KUrlRequester::setPlaceholderText instead. */ -#ifndef KIOWIDGETS_NO_DEPRECATED - KIOWIDGETS_DEPRECATED void setClickMessage(const QString &msg); + KIOWIDGETS_DEPRECATED_VERSION(5, 0, "Use KUrlRequester::setPlaceholderText(const QString&)") + void setClickMessage(const QString &msg); #endif /** * @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 * @since 4.4 */ Qt::WindowModality fileDialogModality() const; /** * Set the window modality for the file dialog to @p modality * Directory selection dialogs are always modal * @since 4.4 */ 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. * @since 4.3 */ void setStartDir(const QUrl &startDir); +#if KIOWIDGETS_ENABLE_DEPRECATED_SINCE(4, 3) /** * Sets the url in the lineedit to @p QUrl::fromLocalFile(path). * This is only for local paths; do not pass a url here. * This method is mostly for "local paths only" url requesters, * for instance those set up with setMode(KFile::File|KFile::ExistingOnly|KFile::LocalOnly) * - * @deprecated Use setUrl(QUrl::fromLocalFile(path)) instead. + * @deprecated Since 4.3. Use setUrl(QUrl::fromLocalFile(path)) instead. */ -#ifndef KIOWIDGETS_NO_DEPRECATED - KIOWIDGETS_DEPRECATED void setPath(const QString &path); + KIOWIDGETS_DEPRECATED_VERSION(4, 3, "Use KUrlRequester::setUrlQUrl::fromLocalFile(path))") + void setPath(const QString &path); #endif /** * 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 * @since 4.3 */ 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. */ void returnPressed(); /** * Emitted when return or enter was pressed in the lineedit. * The parameter contains the contents of the lineedit. */ void returnPressed(const QString &); /** * 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; KUrlRequesterPrivate *const d; Q_DISABLE_COPY(KUrlRequester) Q_PRIVATE_SLOT(d, void _k_slotUpdateUrl()) Q_PRIVATE_SLOT(d, void _k_slotOpenDialog()) Q_PRIVATE_SLOT(d, void _k_slotFileDialogAccepted()) }; 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 diff --git a/src/widgets/kurlrequesterdialog.cpp b/src/widgets/kurlrequesterdialog.cpp index b7ec26ae..da1cf0e5 100644 --- a/src/widgets/kurlrequesterdialog.cpp +++ b/src/widgets/kurlrequesterdialog.cpp @@ -1,143 +1,141 @@ /* This file is part of the KDE libraries Copyright (C) 2000 Wilco Greven library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kurlrequesterdialog.h" #include #include #include #include #include #include #include #include #include class KUrlRequesterDialogPrivate { public: explicit KUrlRequesterDialogPrivate(KUrlRequesterDialog *qq) : q(qq) { } KUrlRequesterDialog * const q; void initDialog(const QString &text, const QUrl &url); // slots void _k_slotTextChanged(const QString &); KUrlRequester *urlRequester; QDialogButtonBox *buttonBox; }; KUrlRequesterDialog::KUrlRequesterDialog(const QUrl &urlName, QWidget *parent) : QDialog(parent), d(new KUrlRequesterDialogPrivate(this)) { d->initDialog(i18n("Location:"), urlName); } KUrlRequesterDialog::KUrlRequesterDialog(const QUrl &urlName, const QString &_text, QWidget *parent) : QDialog(parent), d(new KUrlRequesterDialogPrivate(this)) { d->initDialog(_text, urlName); } KUrlRequesterDialog::~KUrlRequesterDialog() { delete d; } void KUrlRequesterDialogPrivate::initDialog(const QString &text, const QUrl &urlName) { QVBoxLayout *topLayout = new QVBoxLayout; q->setLayout(topLayout); QLabel *label = new QLabel(text, q); topLayout->addWidget(label); urlRequester = new KUrlRequester(urlName, q); urlRequester->setMinimumWidth(urlRequester->sizeHint().width() * 3); topLayout->addWidget(urlRequester); urlRequester->setFocus(); QObject::connect(urlRequester->lineEdit(), SIGNAL(textChanged(QString)), q, SLOT(_k_slotTextChanged(QString))); /* KFile::Mode mode = static_cast( KFile::File | KFile::ExistingOnly ); urlRequester_->setMode( mode ); */ buttonBox = new QDialogButtonBox(q); buttonBox->setStandardButtons(QDialogButtonBox::Ok | QDialogButtonBox::Cancel); QObject::connect(buttonBox, &QDialogButtonBox::accepted, q, &QDialog::accept); QObject::connect(buttonBox, &QDialogButtonBox::rejected, q, &QDialog::reject); topLayout->addWidget(buttonBox); _k_slotTextChanged(urlName.toString()); } void KUrlRequesterDialogPrivate::_k_slotTextChanged(const QString &text) { bool state = !text.trimmed().isEmpty(); buttonBox->button(QDialogButtonBox::Ok)->setEnabled(state); } QUrl KUrlRequesterDialog::selectedUrl() const { if (result() == QDialog::Accepted) { return d->urlRequester->url(); } else { return QUrl(); } } QUrl KUrlRequesterDialog::getUrl(const QUrl &dir, QWidget *parent, const QString &caption) { KUrlRequesterDialog dlg(dir, parent); dlg.setWindowTitle(caption.isEmpty() ? i18n("Open") : caption); dlg.exec(); const QUrl &url = dlg.selectedUrl(); if (url.isValid()) { KRecentDocument::add(url); } return url; } -#ifndef KIOWIDGETS_NO_DEPRECATED QFileDialog *KUrlRequesterDialog::fileDialog() { return d->urlRequester->fileDialog(); } -#endif KUrlRequester *KUrlRequesterDialog::urlRequester() { return d->urlRequester; } #include "moc_kurlrequesterdialog.cpp" diff --git a/src/widgets/kurlrequesterdialog.h b/src/widgets/kurlrequesterdialog.h index 20b8249e..32cffa2a 100644 --- a/src/widgets/kurlrequesterdialog.h +++ b/src/widgets/kurlrequesterdialog.h @@ -1,109 +1,110 @@ /* This file is part of the KDE libraries Copyright (C) 2000 Wilco Greven library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef KURLREQUESTERDIALOG_H #define KURLREQUESTERDIALOG_H #include "kiowidgets_export.h" #include #include class KUrlRequester; class QFileDialog; class KUrlRequesterDialogPrivate; /** * @class KUrlRequesterDialog kurlrequesterdialog.h * * 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 */ 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(); /** * 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 caption The caption to use for the dialog. */ static QUrl getUrl(const QUrl &url = QUrl(), QWidget *parent = nullptr, const QString &caption = QString()); +#if KIOWIDGETS_ENABLE_DEPRECATED_SINCE(5, 0) /** * Returns a pointer to the file dialog used by the KUrlRequester. * @deprecated since 5.0, use urlRequester() methods instead. */ -#ifndef KIOWIDGETS_NO_DEPRECATED - KIOWIDGETS_DEPRECATED QFileDialog *fileDialog(); + KIOWIDGETS_DEPRECATED_VERSION(5, 0, "Use KUrlRequesterDialog::urlRequester() and methods on it") + QFileDialog *fileDialog(); #endif /** * Returns a pointer to the KUrlRequester. */ KUrlRequester *urlRequester(); private: friend class KUrlRequesterDialogPrivate; KUrlRequesterDialogPrivate *const d; Q_DISABLE_COPY(KUrlRequesterDialog) Q_PRIVATE_SLOT(d, void _k_slotTextChanged(const QString &)) }; #endif // KURLREQUESTERDIALOG_H diff --git a/src/widgets/paste.cpp b/src/widgets/paste.cpp index b2e2bde0..d2ed9bbb 100644 --- a/src/widgets/paste.cpp +++ b/src/widgets/paste.cpp @@ -1,374 +1,373 @@ /* This file is part of the KDE libraries Copyright (C) 2000 David Faure This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License version 2 as published by the Free Software Foundation. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "paste.h" #include "pastedialog_p.h" #include "kio_widgets_debug.h" #include "kio/job.h" #include "kio/copyjob.h" #include "kio/deletejob.h" #include "kio/global.h" #include "kio/renamedialog.h" #include "kprotocolmanager.h" #include "../pathhelpers_p.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include // This could be made a public method, if there's a need for pasting only urls // and not random data. /** * Pastes URLs from the clipboard. This results in a copy or move job, * depending on whether the user has copied or cut the items. * * @param mimeData the mimeData to paste, usually QApplication::clipboard()->mimeData() * @param destDir Destination directory where the items will be copied/moved. * @param flags the flags are passed to KIO::copy or KIO::move. * @return the copy or move job handling the operation, or @c nullptr if there is nothing to do * @since ... */ //KIOWIDGETS_EXPORT Job *pasteClipboardUrls(const QUrl& destDir, JobFlags flags = DefaultFlags); static KIO::Job *pasteClipboardUrls(const QMimeData *mimeData, const QUrl &destDir, KIO::JobFlags flags = KIO::DefaultFlags) { const QList urls = KUrlMimeData::urlsFromMimeData(mimeData, KUrlMimeData::PreferLocalUrls); if (!urls.isEmpty()) { const bool move = KIO::isClipboardDataCut(mimeData); KIO::Job *job = nullptr; if (move) { job = KIO::move(urls, destDir, flags); } else { job = KIO::copy(urls, destDir, flags); } return job; } return nullptr; } 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(0); 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(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(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) { org::kde::KDirNotify::emitFilesAdded(url.adjusted(QUrl::RemoveFilename | QUrl::StripTrailingSlash)); } }); 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, clipboard); if (dlg.exec() != QDialog::Accepted) { return QByteArray(); } if (clipboard && dlg.clipboardChanged()) { KMessageBox::sorry(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(); const QString chosenFormat = formats[ dlg.comboItem() ]; //qDebug() << " result=" << result << " chosenFormat=" << chosenFormat; *newUrl = u; newUrl->setPath(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.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 mimetype choice for a QTextDrag, those mimetypes look ugly. if (mimeData->hasText()) { ba = mimeData->text().toLocal8Bit(); // encoding OK? } else { const QStringList formats = extractFormats(mimeData); if (formats.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(formats.first()); } 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); } // The main method for pasting KIOWIDGETS_EXPORT KIO::Job *KIO::pasteClipboard(const QUrl &destUrl, QWidget *widget, bool move) { Q_UNUSED(move); if (!destUrl.isValid()) { KMessageBox::error(widget, i18n("Malformed URL\n%1", destUrl.errorString())); qCWarning(KIO_WIDGETS) << destUrl.errorString(); return nullptr; } // TODO: if we passed mimeData as argument, we could write unittests that don't // mess up the clipboard and that don't need QtGui. const QMimeData *mimeData = QApplication::clipboard()->mimeData(); if (mimeData->hasUrls()) { // We can ignore the bool move, KIO::paste decodes it KIO::Job *job = pasteClipboardUrls(mimeData, destUrl); if (job) { KJobWidgets::setWindow(job, widget); return job; } } return pasteMimeDataImpl(mimeData, destUrl, QString(), widget, true /*clipboard*/); } -// deprecated. KF6: remove -KIOWIDGETS_DEPRECATED_EXPORT QString KIO::pasteActionText() +QString KIO::pasteActionText() { const QMimeData *mimeData = QApplication::clipboard()->mimeData(); const QList urls = KUrlMimeData::urlsFromMimeData(mimeData); if (!urls.isEmpty()) { if (urls.first().isLocalFile()) { return i18np("&Paste File", "&Paste %1 Files", urls.count()); } else { return i18np("&Paste URL", "&Paste %1 URLs", urls.count()); } } else if (!mimeData->formats().isEmpty()) { return i18n("&Paste Clipboard Contents"); } else { return QString(); } } KIOWIDGETS_EXPORT QString KIO::pasteActionText(const QMimeData *mimeData, bool *enable, const KFileItem &destItem) { bool canPasteData = false; QList 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; } // The [new] main method for dropping KIOWIDGETS_EXPORT KIO::Job *KIO::pasteMimeData(const QMimeData *mimeData, const QUrl &destUrl, const QString &dialogText, QWidget *widget) { return pasteMimeDataImpl(mimeData, destUrl, dialogText, widget, false /*not clipboard*/); } 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'); } diff --git a/src/widgets/paste.h b/src/widgets/paste.h index 2f2cef69..59faacee 100644 --- a/src/widgets/paste.h +++ b/src/widgets/paste.h @@ -1,112 +1,121 @@ /* This file is part of the KDE libraries Copyright (C) 2000-2005 David Faure This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License version 2 as published by the Free Software Foundation. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef KIO_PASTE_H #define KIO_PASTE_H #include "kiowidgets_export.h" #include class QWidget; class QUrl; class QMimeData; class KFileItem; namespace KIO { class Job; class CopyJob; +#if KIOWIDGETS_ENABLE_DEPRECATED_SINCE(5, 4) /** * Pastes the content of the clipboard to the given destination URL. * URLs are treated separately (performing a file copy) * from other data (which is saved into a file after asking the user * to choose a filename and the preferred data format) * * @param destURL the URL to receive the data * @param widget parent widget to use for dialogs * @param move true to move the data, false to copy -- now ignored and handled automatically * @return the job that handles the operation * @deprecated since 5.4, use KIO::paste() from (which takes care of undo/redo too) */ -KIOWIDGETS_DEPRECATED_EXPORT Job *pasteClipboard(const QUrl &destURL, QWidget *widget, bool move = false); +KIOWIDGETS_DEPRECATED_VERSION(5, 4, "Use KIO::paste(...) from ") +KIOWIDGETS_EXPORT Job *pasteClipboard(const QUrl &destURL, QWidget *widget, bool move = false); +#endif +#if KIOWIDGETS_ENABLE_DEPRECATED_SINCE(5, 4) /** * Save the given mime @p data to the given destination URL * after offering the user to choose a data format. * This is the method used when handling drops (of anything else than URLs) * onto dolphin and konqueror. * * @param data the QMimeData, usually from a QDropEvent * @param destUrl the URL of the directory where the data will be pasted. * The filename to use in that directory is prompted by this method. * @param dialogText the text to show in the dialog * @param widget parent widget to use for dialogs * * @see pasteClipboard() * @deprecated since 5.4, use KIO::paste() from (which takes care of undo/redo too) */ -KIOWIDGETS_DEPRECATED_EXPORT Job *pasteMimeData(const QMimeData *data, const QUrl &destUrl, +KIOWIDGETS_DEPRECATED_VERSION(5, 4, "Use KIO::paste(...) from ") +KIOWIDGETS_EXPORT Job *pasteMimeData(const QMimeData *data, const QUrl &destUrl, const QString &dialogText, QWidget *widget); +#endif /** * 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); +#if KIOWIDGETS_ENABLE_DEPRECATED_SINCE(5, 4) /** * Returns the text to use for the Paste action, when the application supports * pasting files, urls, and clipboard data, using pasteClipboard(). * @return a string suitable for QAction::setText, or an empty string if pasting * isn't possible right now. * @deprecated since 5.4, use pasteActionText(const QMimeData *, bool*, const KFileItem &) */ -KIOWIDGETS_DEPRECATED_EXPORT QString pasteActionText(); +KIOWIDGETS_DEPRECATED_VERSION(5, 4, "Use KIO::pasteActionText(const QMimeData *, bool*, const KFileItem &)") +KIOWIDGETS_EXPORT QString pasteActionText(); +#endif /** * 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 diff --git a/src/widgets/previewjob.cpp b/src/widgets/previewjob.cpp index 4b50d38d..5c421fb3 100644 --- a/src/widgets/previewjob.cpp +++ b/src/widgets/previewjob.cpp @@ -1,828 +1,822 @@ // -*- c++ -*- /* This file is part of the KDE libraries Copyright (C) 2000 David Faure 2000 Carsten Pfeiffer 2001 Malte Starostik This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "previewjob.h" #if defined(Q_OS_UNIX) && !defined(Q_OS_ANDROID) #define WITH_SHM 1 #else #define WITH_SHM 0 #endif #if WITH_SHM #include #include #endif #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "job_p.h" namespace KIO { struct PreviewItem; } using namespace KIO; struct KIO::PreviewItem { KFileItem item; KService::Ptr plugin; }; class KIO::PreviewJobPrivate: public KIO::JobPrivate { public: PreviewJobPrivate(const KFileItemList &items, const QSize &size) : initialItems(items), width(size.width()), height(size.height()), cacheWidth(width), cacheHeight(height), bScale(true), bSave(true), ignoreMaximumSize(false), sequenceIndex(0), succeeded(false), maximumLocalSize(0), maximumRemoteSize(0), iconSize(0), iconAlpha(70), shmid(-1), shmaddr(nullptr) { // http://specifications.freedesktop.org/thumbnail-spec/thumbnail-spec-latest.html#DIRECTORY thumbRoot = QStandardPaths::writableLocation(QStandardPaths::GenericCacheLocation) + QLatin1String("/thumbnails/"); } enum { STATE_STATORIG, // if the thumbnail exists STATE_GETORIG, // if we create it STATE_CREATETHUMB // thumbnail:/ slave } state; KFileItemList initialItems; QStringList enabledPlugins; // Some plugins support remote URLs, QHash m_remoteProtocolPlugins; // Our todo list :) // We remove the first item at every step, so use QLinkedList QLinkedList items; // The current item PreviewItem currentItem; // The modification time of that URL QDateTime tOrig; // Path to thumbnail cache for the current size QString thumbPath; // Original URL of current item in RFC2396 format // (file:///path/to/a%20file instead of file:/path/to/a file) QByteArray origName; // Thumbnail file name for current item QString thumbName; // Size of thumbnail int width; int height; // Unscaled size of thumbnail (128 or 256 if cache is enabled) int cacheWidth; int cacheHeight; // Whether the thumbnail should be scaled bool bScale; // Whether we should save the thumbnail bool bSave; bool ignoreMaximumSize; int sequenceIndex; bool succeeded; // If the file to create a thumb for was a temp file, this is its name QString tempName; KIO::filesize_t maximumLocalSize; KIO::filesize_t maximumRemoteSize; // the size for the icon overlay int iconSize; // the transparency of the blended mimetype icon int iconAlpha; // Shared memory segment Id. The segment is allocated to a size // of extent x extent x 4 (32 bit image) on first need. int shmid; // And the data area uchar *shmaddr; // Root of thumbnail cache QString thumbRoot; void getOrCreateThumbnail(); bool statResultThumbnail(); void createThumbnail(const QString &); void cleanupTempFile(); void determineNextFile(); void emitPreview(const QImage &thumb); void startPreview(); void slotThumbData(KIO::Job *, const QByteArray &); Q_DECLARE_PUBLIC(PreviewJob) }; -#ifndef KIOWIDGETS_NO_DEPRECATED PreviewJob::PreviewJob(const KFileItemList &items, int width, int height, int iconSize, int iconAlpha, bool scale, bool save, const QStringList *enabledPlugins) : KIO::Job(*new PreviewJobPrivate(items, QSize(width, height ? height : width))) { Q_D(PreviewJob); d->enabledPlugins = enabledPlugins ? *enabledPlugins : availablePlugins(); d->iconSize = iconSize; d->iconAlpha = iconAlpha; d->bScale = scale; d->bSave = save && scale; // Return to event loop first, determineNextFile() might delete this; QTimer::singleShot(0, this, SLOT(startPreview())); } -#endif PreviewJob::PreviewJob(const KFileItemList &items, const QSize &size, const QStringList *enabledPlugins) : KIO::Job(*new PreviewJobPrivate(items, size)) { Q_D(PreviewJob); if (enabledPlugins) { d->enabledPlugins = *enabledPlugins; } else { const KConfigGroup globalConfig(KSharedConfig::openConfig(), "PreviewSettings"); d->enabledPlugins = globalConfig.readEntry("Plugins", QStringList { QStringLiteral("directorythumbnail"), QStringLiteral("imagethumbnail"), QStringLiteral("jpegthumbnail")}); } // Return to event loop first, determineNextFile() might delete this; QTimer::singleShot(0, this, SLOT(startPreview())); } PreviewJob::~PreviewJob() { #if WITH_SHM Q_D(PreviewJob); if (d->shmaddr) { shmdt((char *)d->shmaddr); shmctl(d->shmid, IPC_RMID, nullptr); } #endif } void PreviewJob::setOverlayIconSize(int size) { Q_D(PreviewJob); d->iconSize = size; } int PreviewJob::overlayIconSize() const { Q_D(const PreviewJob); return d->iconSize; } void PreviewJob::setOverlayIconAlpha(int alpha) { Q_D(PreviewJob); d->iconAlpha = qBound(0, alpha, 255); } int PreviewJob::overlayIconAlpha() const { Q_D(const PreviewJob); return d->iconAlpha; } void PreviewJob::setScaleType(ScaleType type) { Q_D(PreviewJob); switch (type) { case Unscaled: d->bScale = false; d->bSave = false; break; case Scaled: d->bScale = true; d->bSave = false; break; case ScaledAndCached: d->bScale = true; d->bSave = true; break; default: break; } } PreviewJob::ScaleType PreviewJob::scaleType() const { Q_D(const PreviewJob); if (d->bScale) { return d->bSave ? ScaledAndCached : Scaled; } return Unscaled; } void PreviewJobPrivate::startPreview() { Q_Q(PreviewJob); // Load the list of plugins to determine which mimetypes are supported const KService::List plugins = KServiceTypeTrader::self()->query(QStringLiteral("ThumbCreator")); QMap mimeMap; QHash > protocolMap; for (KService::List::ConstIterator it = plugins.constBegin(); it != plugins.constEnd(); ++it) { QStringList protocols = (*it)->property(QStringLiteral("X-KDE-Protocols")).toStringList(); const QString p = (*it)->property(QStringLiteral("X-KDE-Protocol")).toString(); if (!p.isEmpty()) { protocols.append(p); } for (const QString &protocol : qAsConst(protocols)) { // We cannot use mimeTypes() here, it doesn't support groups such as: text/* const QStringList mtypes = (*it)->serviceTypes(); // Add supported mimetype for this protocol QStringList &_ms = m_remoteProtocolPlugins[protocol]; for (const QString &_m : mtypes) { if (_m != QLatin1String("ThumbCreator")) { protocolMap[protocol].insert(_m, *it); if (!_ms.contains(_m)) { _ms.append(_m); } } } } if (enabledPlugins.contains((*it)->desktopEntryName())) { const QStringList mimeTypes = (*it)->serviceTypes(); for (QStringList::ConstIterator mt = mimeTypes.constBegin(); mt != mimeTypes.constEnd(); ++mt) { if (*mt != QLatin1String("ThumbCreator")) { mimeMap.insert(*mt, *it); } } } } const auto mountsList = KMountPoint::currentMountPoints(); KMountPoint::List encryptedMountsList; const auto thumbRootMount = mountsList.findByPath(thumbRoot); std::copy_if(mountsList.begin(), mountsList.end(), std::back_inserter(encryptedMountsList), [&thumbRootMount] (KMountPoint::Ptr mount) { return (thumbRootMount != mount) && (mount->mountType() == QLatin1String("fuse.cryfs") || mount->mountType() == QLatin1String("fuse.encfs")); }); // Look for images and store the items in our todo list :) bool bNeedCache = false; KFileItemList::const_iterator kit = initialItems.constBegin(); const KFileItemList::const_iterator kend = initialItems.constEnd(); for (; kit != kend; ++kit) { PreviewItem item; item.item = *kit; if (encryptedMountsList.findByPath(item.item.localPath())) { continue; } const QString mimeType = item.item.mimetype(); KService::Ptr plugin(nullptr); // look for protocol-specific thumbnail plugins first QHash >::const_iterator it = protocolMap.constFind(item.item.url().scheme()); if (it != protocolMap.constEnd()) { plugin = it.value().value(mimeType); } if (!plugin) { QMap::ConstIterator pluginIt = mimeMap.constFind(mimeType); if (pluginIt == mimeMap.constEnd()) { QString groupMimeType = mimeType; groupMimeType.replace(QRegExp(QStringLiteral("/.*")), QStringLiteral("/*")); pluginIt = mimeMap.constFind(groupMimeType); if (pluginIt == mimeMap.constEnd()) { QMimeDatabase db; // check mime type inheritance, resolve aliases const QMimeType mimeInfo = db.mimeTypeForName(mimeType); if (mimeInfo.isValid()) { const QStringList parentMimeTypes = mimeInfo.allAncestors(); for (const QString &parentMimeType : parentMimeTypes) { pluginIt = mimeMap.constFind(parentMimeType); if (pluginIt != mimeMap.constEnd()) { break; } } } } } if (pluginIt != mimeMap.constEnd()) { plugin = *pluginIt; } } if (plugin) { item.plugin = plugin; items.append(item); if (!bNeedCache && bSave && plugin->property(QStringLiteral("CacheThumbnail")).toBool()) { const QUrl url = (*kit).url(); if (!url.isLocalFile() || !url.adjusted(QUrl::RemoveFilename).toLocalFile().startsWith(thumbRoot)) { bNeedCache = true; } } } else { emit q->failed(*kit); } } KConfigGroup cg(KSharedConfig::openConfig(), "PreviewSettings"); maximumLocalSize = cg.readEntry("MaximumSize", std::numeric_limits::max()); maximumRemoteSize = cg.readEntry("MaximumRemoteSize", 0); if (bNeedCache) { if (width <= 128 && height <= 128) { cacheWidth = cacheHeight = 128; } else { cacheWidth = cacheHeight = 256; } thumbPath = thumbRoot + QLatin1String(cacheWidth == 128 ? "normal/" : "large/"); if (!QDir(thumbPath).exists()) { if (QDir().mkpath(thumbPath)) { // Qt5 TODO: mkpath(dirPath, permissions) QFile f(thumbPath); f.setPermissions(QFile::ReadUser | QFile::WriteUser | QFile::ExeUser); // 0700 } } } else { bSave = false; } initialItems.clear(); determineNextFile(); } void PreviewJob::removeItem(const QUrl &url) { Q_D(PreviewJob); for (QLinkedList::Iterator it = d->items.begin(); it != d->items.end(); ++it) if ((*it).item.url() == url) { d->items.erase(it); break; } if (d->currentItem.item.url() == url) { KJob *job = subjobs().first(); job->kill(); removeSubjob(job); d->determineNextFile(); } } void KIO::PreviewJob::setSequenceIndex(int index) { d_func()->sequenceIndex = index; } int KIO::PreviewJob::sequenceIndex() const { return d_func()->sequenceIndex; } void PreviewJob::setIgnoreMaximumSize(bool ignoreSize) { d_func()->ignoreMaximumSize = ignoreSize; } void PreviewJobPrivate::cleanupTempFile() { if (!tempName.isEmpty()) { Q_ASSERT((!QFileInfo(tempName).isDir() && QFileInfo(tempName).isFile()) || QFileInfo(tempName).isSymLink()); QFile::remove(tempName); tempName.clear(); } } void PreviewJobPrivate::determineNextFile() { Q_Q(PreviewJob); if (!currentItem.item.isNull()) { if (!succeeded) { emit q->failed(currentItem.item); } } // No more items ? if (items.isEmpty()) { q->emitResult(); return; } else { // First, stat the orig file state = PreviewJobPrivate::STATE_STATORIG; currentItem = items.first(); succeeded = false; items.removeFirst(); KIO::Job *job = KIO::stat(currentItem.item.url(), KIO::HideProgressInfo); job->addMetaData(QStringLiteral("thumbnail"), QStringLiteral("1")); job->addMetaData(QStringLiteral("no-auth-prompt"), QStringLiteral("true")); q->addSubjob(job); } } void PreviewJob::slotResult(KJob *job) { Q_D(PreviewJob); removeSubjob(job); Q_ASSERT(!hasSubjobs()); // We should have only one job at a time ... switch (d->state) { case PreviewJobPrivate::STATE_STATORIG: { if (job->error()) { // that's no good news... // Drop this one and move on to the next one d->determineNextFile(); return; } const KIO::UDSEntry entry = static_cast(job)->statResult(); d->tOrig = QDateTime::fromSecsSinceEpoch(entry.numberValue(KIO::UDSEntry::UDS_MODIFICATION_TIME, 0)); bool skipCurrentItem = false; const KIO::filesize_t size = (KIO::filesize_t)entry.numberValue(KIO::UDSEntry::UDS_SIZE, 0); const QUrl itemUrl = d->currentItem.item.mostLocalUrl(); if (itemUrl.isLocalFile() || KProtocolInfo::protocolClass(itemUrl.scheme()) == QLatin1String(":local")) { skipCurrentItem = !d->ignoreMaximumSize && size > d->maximumLocalSize && !d->currentItem.plugin->property(QStringLiteral("IgnoreMaximumSize")).toBool(); } else { // For remote items the "IgnoreMaximumSize" plugin property is not respected skipCurrentItem = !d->ignoreMaximumSize && size > d->maximumRemoteSize; // Remote directories are not supported, don't try to do a file_copy on them if (!skipCurrentItem) { // TODO update item.mimeType from the UDS entry, in case it wasn't set initially // But we don't use the mimetype anymore, we just use isDir(). if (d->currentItem.item.isDir()) { skipCurrentItem = true; } } } if (skipCurrentItem) { d->determineNextFile(); return; } bool pluginHandlesSequences = d->currentItem.plugin->property(QStringLiteral("HandleSequences"), QVariant::Bool).toBool(); if (!d->currentItem.plugin->property(QStringLiteral("CacheThumbnail")).toBool() || (d->sequenceIndex && pluginHandlesSequences)) { // This preview will not be cached, no need to look for a saved thumbnail // Just create it, and be done d->getOrCreateThumbnail(); return; } if (d->statResultThumbnail()) { return; } d->getOrCreateThumbnail(); return; } case PreviewJobPrivate::STATE_GETORIG: { if (job->error()) { d->cleanupTempFile(); d->determineNextFile(); return; } d->createThumbnail(static_cast(job)->destUrl().toLocalFile()); return; } case PreviewJobPrivate::STATE_CREATETHUMB: { d->cleanupTempFile(); d->determineNextFile(); return; } } } bool PreviewJobPrivate::statResultThumbnail() { if (thumbPath.isEmpty()) { return false; } QUrl url = currentItem.item.mostLocalUrl(); // Don't include the password if any url.setPassword(QString()); origName = url.toEncoded(); QCryptographicHash md5(QCryptographicHash::Md5); md5.addData(QFile::encodeName(QString::fromUtf8(origName))); thumbName = QString::fromUtf8(QFile::encodeName(QString::fromLatin1(md5.result().toHex()))) + QLatin1String(".png"); QImage thumb; if (!thumb.load(thumbPath + thumbName)) { return false; } if (thumb.text(QStringLiteral("Thumb::URI")) != QString::fromUtf8(origName) || thumb.text(QStringLiteral("Thumb::MTime")).toLongLong() != tOrig.toSecsSinceEpoch()) { return false; } QString thumbnailerVersion = currentItem.plugin->property(QStringLiteral("ThumbnailerVersion"), QVariant::String).toString(); if (!thumbnailerVersion.isEmpty() && thumb.text(QStringLiteral("Software")).startsWith(QLatin1String("KDE Thumbnail Generator"))) { //Check if the version matches //The software string should read "KDE Thumbnail Generator pluginName (vX)" QString softwareString = thumb.text(QStringLiteral("Software")).remove(QStringLiteral("KDE Thumbnail Generator")).trimmed(); if (softwareString.isEmpty()) { // The thumbnail has been created with an older version, recreating return false; } int versionIndex = softwareString.lastIndexOf(QLatin1String("(v")); if (versionIndex < 0) { return false; } QString cachedVersion = softwareString.remove(0, versionIndex + 2); cachedVersion.chop(1); uint thumbnailerMajor = thumbnailerVersion.toInt(); uint cachedMajor = cachedVersion.toInt(); if (thumbnailerMajor > cachedMajor) { return false; } } // Found it, use it emitPreview(thumb); succeeded = true; determineNextFile(); return true; } void PreviewJobPrivate::getOrCreateThumbnail() { Q_Q(PreviewJob); // We still need to load the orig file ! (This is getting tedious) :) const KFileItem &item = currentItem.item; const QString localPath = item.localPath(); if (!localPath.isEmpty()) { createThumbnail(localPath); } else { const QUrl fileUrl = item.url(); // heuristics for remote URL support bool supportsProtocol = false; if (m_remoteProtocolPlugins.value(fileUrl.scheme()).contains(item.mimetype())) { // There's a plugin supporting this protocol and mimetype supportsProtocol = true; } else if (m_remoteProtocolPlugins.value(QStringLiteral("KIO")).contains(item.mimetype())) { // Assume KIO understands any URL, ThumbCreator slaves who have // X-KDE-Protocols=KIO will get fed the remote URL directly. supportsProtocol = true; } if (supportsProtocol) { createThumbnail(fileUrl.toString()); return; } if (item.isDir()) { // Skip remote dirs (bug 208625) cleanupTempFile(); determineNextFile(); return; } // No plugin support access to this remote content, copy the file // to the local machine, then create the thumbnail state = PreviewJobPrivate::STATE_GETORIG; QTemporaryFile localFile; localFile.setAutoRemove(false); localFile.open(); tempName = localFile.fileName(); const QUrl currentURL = item.mostLocalUrl(); KIO::Job *job = KIO::file_copy(currentURL, QUrl::fromLocalFile(tempName), -1, KIO::Overwrite | KIO::HideProgressInfo /* No GUI */); job->addMetaData(QStringLiteral("thumbnail"), QStringLiteral("1")); q->addSubjob(job); } } void PreviewJobPrivate::createThumbnail(const QString &pixPath) { Q_Q(PreviewJob); state = PreviewJobPrivate::STATE_CREATETHUMB; QUrl thumbURL; thumbURL.setScheme(QStringLiteral("thumbnail")); thumbURL.setPath(pixPath); KIO::TransferJob *job = KIO::get(thumbURL, NoReload, HideProgressInfo); q->addSubjob(job); q->connect(job, SIGNAL(data(KIO::Job*,QByteArray)), SLOT(slotThumbData(KIO::Job*,QByteArray))); bool save = bSave && currentItem.plugin->property(QStringLiteral("CacheThumbnail")).toBool() && !sequenceIndex; job->addMetaData(QStringLiteral("mimeType"), currentItem.item.mimetype()); job->addMetaData(QStringLiteral("width"), QString().setNum(save ? cacheWidth : width)); job->addMetaData(QStringLiteral("height"), QString().setNum(save ? cacheHeight : height)); job->addMetaData(QStringLiteral("iconSize"), QString().setNum(save ? 64 : iconSize)); job->addMetaData(QStringLiteral("iconAlpha"), QString().setNum(iconAlpha)); job->addMetaData(QStringLiteral("plugin"), currentItem.plugin->library()); job->addMetaData(QStringLiteral("enabledPlugins"), enabledPlugins.join(QLatin1Char(','))); if (sequenceIndex) { job->addMetaData(QStringLiteral("sequence-index"), QString().setNum(sequenceIndex)); } #if WITH_SHM if (shmid == -1) { if (shmaddr) { shmdt((char *)shmaddr); shmctl(shmid, IPC_RMID, nullptr); } shmid = shmget(IPC_PRIVATE, cacheWidth * cacheHeight * 4, IPC_CREAT | 0600); if (shmid != -1) { shmaddr = (uchar *)(shmat(shmid, nullptr, SHM_RDONLY)); if (shmaddr == (uchar *) - 1) { shmctl(shmid, IPC_RMID, nullptr); shmaddr = nullptr; shmid = -1; } } else { shmaddr = nullptr; } } if (shmid != -1) { job->addMetaData(QStringLiteral("shmid"), QString().setNum(shmid)); } #endif } void PreviewJobPrivate::slotThumbData(KIO::Job *, const QByteArray &data) { bool save = bSave && currentItem.plugin->property(QStringLiteral("CacheThumbnail")).toBool() && (!currentItem.item.url().isLocalFile() || !currentItem.item.url().adjusted(QUrl::RemoveFilename).toLocalFile().startsWith(thumbRoot)) && !sequenceIndex; QImage thumb; #if WITH_SHM if (shmaddr) { // Keep this in sync with kdebase/kioslave/thumbnail.cpp QDataStream str(data); int width, height; quint8 iFormat; str >> width >> height >> iFormat; QImage::Format format = static_cast(iFormat); thumb = QImage(shmaddr, width, height, format).copy(); } else #endif thumb.loadFromData(data); if (thumb.isNull()) { QDataStream s(data); s >> thumb; } if (save) { thumb.setText(QStringLiteral("Thumb::URI"), QString::fromUtf8(origName)); thumb.setText(QStringLiteral("Thumb::MTime"), QString::number(tOrig.toSecsSinceEpoch())); thumb.setText(QStringLiteral("Thumb::Size"), number(currentItem.item.size())); thumb.setText(QStringLiteral("Thumb::Mimetype"), currentItem.item.mimetype()); QString thumbnailerVersion = currentItem.plugin->property(QStringLiteral("ThumbnailerVersion"), QVariant::String).toString(); QString signature = QLatin1String("KDE Thumbnail Generator ") + currentItem.plugin->name(); if (!thumbnailerVersion.isEmpty()) { signature.append(QLatin1String(" (v") + thumbnailerVersion + QLatin1Char(')')); } thumb.setText(QStringLiteral("Software"), signature); QSaveFile saveFile(thumbPath + thumbName); if (saveFile.open(QIODevice::WriteOnly)) { if (thumb.save(&saveFile, "PNG")) { saveFile.commit(); } } } emitPreview(thumb); succeeded = true; } void PreviewJobPrivate::emitPreview(const QImage &thumb) { Q_Q(PreviewJob); QPixmap pix; if (thumb.width() > width || thumb.height() > height) { pix = QPixmap::fromImage(thumb.scaled(QSize(width, height), Qt::KeepAspectRatio, Qt::SmoothTransformation)); } else { pix = QPixmap::fromImage(thumb); } emit q->gotPreview(currentItem.item, pix); } QStringList PreviewJob::availablePlugins() { QStringList result; const KService::List plugins = KServiceTypeTrader::self()->query(QStringLiteral("ThumbCreator")); for (const KService::Ptr &plugin : plugins) { const QString desktopEntryName = plugin->desktopEntryName(); if (!result.contains(desktopEntryName)) { result.append(desktopEntryName); } } return result; } QStringList PreviewJob::defaultPlugins() { const QStringList blacklist = QStringList() << QStringLiteral("textthumbnail"); QStringList defaultPlugins = availablePlugins(); for (const QString &plugin : blacklist) { defaultPlugins.removeAll(plugin); } return defaultPlugins; } QStringList PreviewJob::supportedMimeTypes() { QStringList result; const KService::List plugins = KServiceTypeTrader::self()->query(QStringLiteral("ThumbCreator")); for (const KService::Ptr &plugin : plugins) { result += plugin->mimeTypes(); } return result; } -#ifndef KIOWIDGETS_NO_DEPRECATED PreviewJob *KIO::filePreview(const KFileItemList &items, int width, int height, int iconSize, int iconAlpha, bool scale, bool save, const QStringList *enabledPlugins) { return new PreviewJob(items, width, height, iconSize, iconAlpha, scale, save, enabledPlugins); } PreviewJob *KIO::filePreview(const QList &items, int width, int height, int iconSize, int iconAlpha, bool scale, bool save, const QStringList *enabledPlugins) { KFileItemList fileItems; fileItems.reserve(items.size()); for (const QUrl &url : items) { Q_ASSERT(url.isValid()); // please call us with valid urls only fileItems.append(KFileItem(url)); } return new PreviewJob(fileItems, width, height, iconSize, iconAlpha, scale, save, enabledPlugins); } -#endif PreviewJob *KIO::filePreview(const KFileItemList &items, const QSize &size, const QStringList *enabledPlugins) { return new PreviewJob(items, size, enabledPlugins); } -#ifndef KIOWIDGETS_NO_DEPRECATED KIO::filesize_t PreviewJob::maximumFileSize() { KConfigGroup cg(KSharedConfig::openConfig(), "PreviewSettings"); return cg.readEntry("MaximumSize", 5 * 1024 * 1024LL /* 5MB */); } -#endif #include "moc_previewjob.cpp" diff --git a/src/widgets/previewjob.h b/src/widgets/previewjob.h index a9668ba5..f5c67ea9 100644 --- a/src/widgets/previewjob.h +++ b/src/widgets/previewjob.h @@ -1,312 +1,318 @@ // -*- c++ -*- /* This file is part of the KDE libraries Copyright (C) 2000 David Faure 2000 Carsten Pfeiffer 2001 Malte Starostik This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef KIO_PREVIEWJOB_H #define KIO_PREVIEWJOB_H #include "kiowidgets_export.h" #include #include class QPixmap; namespace KIO { class PreviewJobPrivate; /*! * @class KIO::PreviewJob previewjob.h * * This class catches a preview (thumbnail) for files. * @short KIO Job to get a thumbnail picture */ class KIOWIDGETS_EXPORT PreviewJob : public KIO::Job { Q_OBJECT public: /** * Specifies the type of scaling that is applied to the generated preview. * @since 4.7 */ enum ScaleType { /** * The original size of the preview will be returned. Most previews * will return a size of 256 x 256 pixels. */ Unscaled, /** * The preview will be scaled to the size specified when constructing * the PreviewJob. The aspect ratio will be kept. */ Scaled, /** * The preview will be scaled to the size specified when constructing * the PreviewJob. The result will be cached for later use. Per default * ScaledAndCached is set. */ ScaledAndCached }; -#ifndef KIOWIDGETS_NO_DEPRECATED +#if KIOWIDGETS_ENABLE_DEPRECATED_SINCE(4, 7) /** * Creates a new PreviewJob. * @param items a list of files to create previews for * @param width the desired width * @param height the desired height, 0 to use the @p width * @param iconSize the size of the mimetype icon to overlay over the * preview or zero to not overlay an icon. This has no effect if the * preview plugin that will be used doesn't use icon overlays. * @param iconAlpha transparency to use for the icon overlay * @param scale if the image is to be scaled to the requested size or * returned in its original size * @param save if the image should be cached for later use * @param enabledPlugins If non-zero, this points to a list containing * the names of the plugins that may be used. If enabledPlugins is zero * all available plugins are used. * - * @deprecated Use PreviewJob(const KFileItemList&, const QSize&, const QStringList*) in combination + * @deprecated Since 4.7, use PreviewJob(const KFileItemList&, const QSize&, const QStringList*) in combination * with the setter-methods instead. Note that the semantics of * \p enabledPlugins has been slightly changed. */ - KIOWIDGETS_DEPRECATED PreviewJob(const KFileItemList &items, int width, int height, - int iconSize, int iconAlpha, bool scale, bool save, - const QStringList *enabledPlugins); + KIOWIDGETS_DEPRECATED_VERSION(4, 7, "Use PreviewJob(const KFileItemList&, const QSize&, const QStringList*)") + PreviewJob(const KFileItemList &items, int width, int height, + int iconSize, int iconAlpha, bool scale, bool save, + const QStringList *enabledPlugins); #endif /** * @param items List of files to create previews for. * @param size Desired size of the preview. * @param enabledPlugins If non-zero it defines the list of plugins that * are considered for generating the preview. If * enabledPlugins is zero the plugins specified in the * KConfigGroup "PreviewSettings" are used. * @since 4.7 */ PreviewJob(const KFileItemList &items, const QSize &size, const QStringList *enabledPlugins = nullptr); virtual ~PreviewJob(); /** * Sets the size of the MIME-type icon which overlays the preview. If zero * is passed no overlay will be shown at all. The setting has no effect if * the preview plugin that will be used does not use icon overlays. Per * default the size is set to 0. * @since 4.7 */ void setOverlayIconSize(int size); /** * @return The size of the MIME-type icon which overlays the preview. * @see PreviewJob::setOverlayIconSize() * @since 4.7 */ int overlayIconSize() const; /** * Sets the alpha-value for the MIME-type icon which overlays the preview. * The alpha-value may range from 0 (= fully transparent) to 255 (= opaque). * Per default the value is set to 70. * @see PreviewJob::setOverlayIconSize() * @since 4.7 */ void setOverlayIconAlpha(int alpha); /** * @return The alpha-value for the MIME-type icon which overlays the preview. * Per default 70 is returned. * @see PreviewJob::setOverlayIconAlpha() * @since 4.7 */ int overlayIconAlpha() const; /** * Sets the scale type for the generated preview. Per default * PreviewJob::ScaledAndCached is set. * @see PreviewJob::ScaleType * @since 4.7 */ void setScaleType(ScaleType type); /** * @return The scale type for the generated preview. * @see PreviewJob::ScaleType * @since 4.7 */ ScaleType scaleType() const; /** * Removes an item from preview processing. Use this if you passed * an item to filePreview and want to delete it now. * * @param url the url of the item that should be removed from the preview queue */ void removeItem(const QUrl &url); /** * If @p ignoreSize is true, then the preview is always * generated regardless of the settings **/ void setIgnoreMaximumSize(bool ignoreSize = true); /** * Sets the sequence index given to the thumb creators. * Use the sequence index, it is possible to create alternative * icons for the same item. For example it may allow iterating through * the items of a directory, or the frames of a video. * * @since 4.3 **/ void setSequenceIndex(int index); /** * Returns the currently set sequence index * * @since 4.3 **/ int sequenceIndex() const; /** * Returns a list of all available preview plugins. The list * contains the basenames of the plugins' .desktop files (no path, * no .desktop). * @return the list of all available plugins */ static QStringList availablePlugins(); /** * Returns a list of plugins that should be enabled by default, which is all plugins * Minus the plugins specified in an internal blacklist * @return the list of plugins that should be enabled by default * @since 5.40 */ static QStringList defaultPlugins(); /** * Returns a list of all supported MIME types. The list can * contain entries like text/ * (without the space). * @return the list of mime types */ static QStringList supportedMimeTypes(); +#if KIOWIDGETS_ENABLE_DEPRECATED_SINCE(4, 5) /** * Returns the default "maximum file size", in bytes, used by PreviewJob. * This is useful for applications providing a GUI for letting the user change the size. * @since 4.1 - * @deprecated PreviewJob uses different maximum file sizes dependent on the URL since 4.5. + * @deprecated Since 4.5, PreviewJob uses different maximum file sizes dependent on the URL. * The returned file size is only valid for local URLs. */ -#ifndef KIOWIDGETS_NO_DEPRECATED - KIOWIDGETS_DEPRECATED static KIO::filesize_t maximumFileSize(); + KIOWIDGETS_DEPRECATED_VERSION(4, 5, "See API dox") + static KIO::filesize_t maximumFileSize(); #endif Q_SIGNALS: /** * Emitted when a thumbnail picture for @p item has been successfully * retrieved. * @param item the file of the preview * @param preview the preview image */ void gotPreview(const KFileItem &item, const QPixmap &preview); /** * Emitted when a thumbnail for @p item could not be created, * either because a ThumbCreator for its MIME type does not * exist, or because something went wrong. * @param item the file that failed */ void failed(const KFileItem &item); protected Q_SLOTS: void slotResult(KJob *job) override; private: Q_PRIVATE_SLOT(d_func(), void startPreview()) Q_PRIVATE_SLOT(d_func(), void slotThumbData(KIO::Job *, const QByteArray &)) Q_DECLARE_PRIVATE(PreviewJob) }; -#ifndef KIOWIDGETS_NO_DEPRECATED +#if KIOWIDGETS_ENABLE_DEPRECATED_SINCE(4, 7) /** * Creates a PreviewJob to generate or retrieve a preview image * for the given URL. * * @param items files to get previews for * @param width the maximum width to use * @param height the maximum height to use, if this is 0, the same * value as width is used. * @param iconSize the size of the mimetype icon to overlay over the * preview or zero to not overlay an icon. This has no effect if the * preview plugin that will be used doesn't use icon overlays. * @param iconAlpha transparency to use for the icon overlay * @param scale if the image is to be scaled to the requested size or * returned in its original size * @param save if the image should be cached for later use * @param enabledPlugins if non-zero, this points to a list containing * the names of the plugins that may be used. * @return the new PreviewJob * @see PreviewJob::availablePlugins() - * @deprecated Use KIO::filePreview(const KFileItemList&, const QSize&, const QStringList*) in combination + * @deprecated Since 4.7, use KIO::filePreview(const KFileItemList&, const QSize&, const QStringList*) in combination * with the setter-methods instead. Note that the semantics of * \p enabledPlugins has been slightly changed. */ -KIOWIDGETS_DEPRECATED_EXPORT PreviewJob *filePreview(const KFileItemList &items, int width, int height = 0, int iconSize = 0, int iconAlpha = 70, bool scale = true, bool save = true, const QStringList *enabledPlugins = nullptr); // KDE5: use enums instead of bool scale + bool save +KIOWIDGETS_DEPRECATED_VERSION(4, 7, "Use KIO::filePreview(const KFileItemList &, const QSize &, const QStringList *") +KIOWIDGETS_EXPORT PreviewJob *filePreview(const KFileItemList &items, int width, int height = 0, int iconSize = 0, int iconAlpha = 70, bool scale = true, bool save = true, const QStringList *enabledPlugins = nullptr); // KDE5: use enums instead of bool scale + bool save +#endif +#if KIOWIDGETS_ENABLE_DEPRECATED_SINCE(4, 7) /** * Creates a PreviewJob to generate or retrieve a preview image * for the given URL. * * @param items files to get previews for * @param width the maximum width to use * @param height the maximum height to use, if this is 0, the same * value as width is used. * @param iconSize the size of the mimetype icon to overlay over the * preview or zero to not overlay an icon. This has no effect if the * preview plugin that will be used doesn't use icon overlays. * @param iconAlpha transparency to use for the icon overlay * @param scale if the image is to be scaled to the requested size or * returned in its original size * @param save if the image should be cached for later use * @param enabledPlugins if non-zero, this points to a list containing * the names of the plugins that may be used. * @return the new PreviewJob * @see PreviewJob::availablePlugins() - * @deprecated Use KIO::filePreview(const KFileItemList&, const QSize&, const QStringList*) in combination + * @deprecated Since 4.7, use KIO::filePreview(const KFileItemList&, const QSize&, const QStringList*) in combination * with the setter-methods instead. Note that the semantics of * \p enabledPlugins has been slightly changed. */ -KIOWIDGETS_DEPRECATED_EXPORT PreviewJob *filePreview(const QList &items, int width, int height = 0, int iconSize = 0, int iconAlpha = 70, bool scale = true, bool save = true, const QStringList *enabledPlugins = nullptr); +KIOWIDGETS_DEPRECATED_VERSION(4, 7, "Use KIO::filePreview(const KFileItemList &, const QSize &, const QStringList *") +KIOWIDGETS_EXPORT PreviewJob *filePreview(const QList &items, int width, int height = 0, int iconSize = 0, int iconAlpha = 70, bool scale = true, bool save = true, const QStringList *enabledPlugins = nullptr); #endif /** * Creates a PreviewJob to generate a preview image for the given items. * @param items List of files to create previews for. * @param size Desired size of the preview. * @param enabledPlugins If non-zero it defines the list of plugins that * are considered for generating the preview. If * enabledPlugins is zero the plugins specified in the * KConfigGroup "PreviewSettings" are used. * @since 4.7 */ KIOWIDGETS_EXPORT PreviewJob *filePreview(const KFileItemList &items, const QSize &size, const QStringList *enabledPlugins = nullptr); } #endif diff --git a/src/widgets/renamedialog.cpp b/src/widgets/renamedialog.cpp index d6d01a83..d2c4c2a1 100644 --- a/src/widgets/renamedialog.cpp +++ b/src/widgets/renamedialog.cpp @@ -1,674 +1,672 @@ /* This file is part of the KDE libraries Copyright (C) 2000 Stephan Kulow 1999 - 2008 David Faure 2001, 2006 Holger Freyther This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kio/renamedialog.h" #include "kio_widgets_debug.h" #include "../pathhelpers_p.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #if 0 #include #endif #include #include using namespace KIO; static QLabel *createLabel(QWidget *parent, const QString &text, bool containerTitle = false) { QLabel *label = new QLabel(parent); if (containerTitle) { QFont font = label->font(); font.setBold(true); label->setFont(font); } label->setAlignment(Qt::AlignHCenter); label->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::Fixed); label->setText(text); return label; } static QLabel *createDateLabel(QWidget *parent, const KFileItem &item) { const QString text = i18n("Date: %1", item.timeString(KFileItem::ModificationTime)); return createLabel(parent, text); } static QLabel *createSizeLabel(QWidget *parent, const KFileItem &item) { const QString text = i18n("Size: %1", KIO::convertSize(item.size())); return createLabel(parent, text); } static KSqueezedTextLabel *createSqueezedLabel(QWidget *parent, const QString &text) { KSqueezedTextLabel *label = new KSqueezedTextLabel(text, parent); label->setAlignment(Qt::AlignHCenter); label->setSizePolicy(QSizePolicy::Ignored, QSizePolicy::Fixed); return label; } /** @internal */ class Q_DECL_HIDDEN RenameDialog::RenameDialogPrivate { public: RenameDialogPrivate() { bCancel = nullptr; bRename = bSkip = bOverwrite = nullptr; bResume = bSuggestNewName = nullptr; bApplyAll = nullptr; m_pLineEdit = nullptr; m_srcPendingPreview = false; m_destPendingPreview = false; m_srcPreview = nullptr; m_destPreview = nullptr; m_srcArea = nullptr; m_destArea = nullptr; } void setRenameBoxText(const QString &fileName) { // sets the text in file name line edit box, selecting the filename (but not the extension if there is one). QMimeDatabase db; const QString extension = db.suffixForFileName(fileName); m_pLineEdit->setText(fileName); if (!extension.isEmpty()) { const int selectionLength = fileName.length() - extension.length() - 1; m_pLineEdit->setSelection(0, selectionLength); } else { m_pLineEdit->selectAll(); } } QPushButton *bCancel; QPushButton *bRename; QPushButton *bSkip; QPushButton *bOverwrite; QPushButton *bResume; QPushButton *bSuggestNewName; QCheckBox *bApplyAll; QLineEdit *m_pLineEdit; QUrl src; QUrl dest; bool m_srcPendingPreview; bool m_destPendingPreview; QLabel *m_srcPreview; QLabel *m_destPreview; QScrollArea *m_srcArea; QScrollArea *m_destArea; KFileItem srcItem; KFileItem destItem; }; RenameDialog::RenameDialog(QWidget *parent, const QString &_caption, const QUrl &_src, const QUrl &_dest, RenameDialog_Options _options, KIO::filesize_t sizeSrc, KIO::filesize_t sizeDest, const QDateTime &ctimeSrc, const QDateTime &ctimeDest, const QDateTime &mtimeSrc, const QDateTime &mtimeDest) : QDialog(parent), d(new RenameDialogPrivate) { setObjectName(QStringLiteral("KIO::RenameDialog")); d->src = _src; d->dest = _dest; setWindowTitle(_caption); d->bCancel = new QPushButton(this); KGuiItem::assign(d->bCancel, KStandardGuiItem::cancel()); connect(d->bCancel, &QAbstractButton::clicked, this, &RenameDialog::cancelPressed); if (_options & RenameDialog_MultipleItems) { d->bApplyAll = new QCheckBox(i18n("Appl&y to All"), this); d->bApplyAll->setToolTip((_options & RenameDialog_IsDirectory) ? i18n("When this is checked the button pressed will be applied to all subsequent folder conflicts for the remainder of the current job.\nUnless you press Skip you will still be prompted in case of a conflict with an existing file in the directory.") : i18n("When this is checked the button pressed will be applied to all subsequent conflicts for the remainder of the current job.")); connect(d->bApplyAll, &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_IsDirectory) ? i18n("Do not copy or move this folder, skip to the next item instead") : i18n("Do not copy or move this file, skip to the next item instead")); connect(d->bSkip, &QAbstractButton::clicked, this, &RenameDialog::skipPressed); } if (_options & RenameDialog_Overwrite) { const QString text = (_options & RenameDialog_IsDirectory) ? i18nc("Write files into an existing folder", "&Write Into") : i18n("&Overwrite"); d->bOverwrite = new QPushButton(text, this); d->bOverwrite->setToolTip(i18n("Files and folders will be copied into the existing directory, alongside its existing contents.\nYou will be prompted again in case of a conflict with an existing file in the directory.")); connect(d->bOverwrite, &QAbstractButton::clicked, this, &RenameDialog::overwritePressed); } if (_options & RenameDialog_Resume) { d->bResume = new QPushButton(i18n("&Resume"), this); connect(d->bResume, &QAbstractButton::clicked, this, &RenameDialog::resumePressed); } QVBoxLayout *pLayout = new QVBoxLayout(this); pLayout->addStrut(400); // makes dlg at least that wide // User tries to overwrite a file with itself ? if (_options & RenameDialog_OverwriteItself) { QLabel *lb = new QLabel(i18n("This action would overwrite '%1' with itself.\n" "Please enter a new file name:", KStringHandler::csqueeze(d->src.toDisplayString(QUrl::PreferLocalFile), 100)), this); lb->setTextFormat(Qt::PlainText); d->bRename->setText(i18n("C&ontinue")); pLayout->addWidget(lb); } else if (_options & RenameDialog_Overwrite) { if (d->src.isLocalFile()) { d->srcItem = KFileItem(d->src); } else { UDSEntry srcUds; srcUds.fastInsert(UDSEntry::UDS_NAME, d->src.fileName()); srcUds.fastInsert(UDSEntry::UDS_MODIFICATION_TIME, mtimeSrc.toMSecsSinceEpoch() / 1000); srcUds.fastInsert(UDSEntry::UDS_CREATION_TIME, ctimeSrc.toMSecsSinceEpoch() / 1000); srcUds.fastInsert(UDSEntry::UDS_SIZE, sizeSrc); d->srcItem = KFileItem(srcUds, d->src); } if (d->dest.isLocalFile()) { d->destItem = KFileItem(d->dest); } else { UDSEntry destUds; destUds.fastInsert(UDSEntry::UDS_NAME, d->dest.fileName()); destUds.fastInsert(UDSEntry::UDS_MODIFICATION_TIME, mtimeDest.toMSecsSinceEpoch() / 1000); destUds.fastInsert(UDSEntry::UDS_CREATION_TIME, ctimeDest.toMSecsSinceEpoch() / 1000); destUds.fastInsert(UDSEntry::UDS_SIZE, sizeDest); d->destItem = KFileItem(destUds, d->dest); } d->m_srcPreview = createLabel(parent, QString()); d->m_destPreview = createLabel(parent, QString()); d->m_srcPreview->setMinimumHeight(KIconLoader::SizeEnormous); d->m_destPreview->setMinimumHeight(KIconLoader::SizeEnormous); d->m_srcPreview->setAlignment(Qt::AlignCenter); d->m_destPreview->setAlignment(Qt::AlignCenter); d->m_srcPendingPreview = true; d->m_destPendingPreview = true; // widget d->m_srcArea = createContainerLayout(parent, d->srcItem, d->m_srcPreview); d->m_destArea = createContainerLayout(parent, d->destItem, d->m_destPreview); connect(d->m_srcArea->verticalScrollBar(), &QAbstractSlider::valueChanged, d->m_destArea->verticalScrollBar(), &QAbstractSlider::setValue); connect(d->m_destArea->verticalScrollBar(), &QAbstractSlider::valueChanged, d->m_srcArea->verticalScrollBar(), &QAbstractSlider::setValue); connect(d->m_srcArea->horizontalScrollBar(), &QAbstractSlider::valueChanged, d->m_destArea->horizontalScrollBar(), &QAbstractSlider::setValue); connect(d->m_destArea->horizontalScrollBar(), &QAbstractSlider::valueChanged, d->m_srcArea->horizontalScrollBar(), &QAbstractSlider::setValue); // create layout QGridLayout *gridLayout = new QGridLayout(); pLayout->addLayout(gridLayout); int gridRow = 0; QLabel *titleLabel = new QLabel(i18n("This action will overwrite the destination."), this); gridLayout->addWidget(titleLabel, gridRow, 0, 1, 2); // takes the complete first line if (mtimeDest > mtimeSrc) { QLabel *warningLabel = new QLabel(i18n("Warning, the destination is more recent."), this); gridLayout->addWidget(warningLabel, ++gridRow, 0, 1, 2); } gridLayout->setRowMinimumHeight(++gridRow, 15); // spacer QLabel *srcTitle = createLabel(parent, i18n("Source"), true); gridLayout->addWidget(srcTitle, ++gridRow, 0); QLabel *destTitle = createLabel(parent, i18n("Destination"), true); gridLayout->addWidget(destTitle, gridRow, 1); QLabel *srcUrlLabel = createSqueezedLabel(parent, d->src.toDisplayString(QUrl::PreferLocalFile)); srcUrlLabel->setTextFormat(Qt::PlainText); gridLayout->addWidget(srcUrlLabel, ++gridRow, 0); QLabel *destUrlLabel = createSqueezedLabel(parent, d->dest.toDisplayString(QUrl::PreferLocalFile)); destUrlLabel->setTextFormat(Qt::PlainText); gridLayout->addWidget(destUrlLabel, gridRow, 1); // The labels containing previews or icons gridLayout->addWidget(d->m_srcArea, ++gridRow, 0); gridLayout->addWidget(d->m_destArea, gridRow, 1); QLabel *srcDateLabel = createDateLabel(parent, d->srcItem); gridLayout->addWidget(srcDateLabel, ++gridRow, 0); QLabel *destDateLabel = createDateLabel(parent, d->destItem); gridLayout->addWidget(destDateLabel, gridRow, 1); QLabel *srcSizeLabel = createSizeLabel(parent, d->srcItem); gridLayout->addWidget(srcSizeLabel, ++gridRow, 0); QLabel *destSizeLabel = createSizeLabel(parent, d->destItem); gridLayout->addWidget(destSizeLabel, gridRow, 1); } else { // This is the case where we don't want to allow overwriting, the existing // file must be preserved (e.g. when renaming). QString sentence1; if (mtimeDest < mtimeSrc) { sentence1 = i18n("An older item named '%1' already exists.", d->dest.toDisplayString(QUrl::PreferLocalFile)); } else if (mtimeDest == mtimeSrc) { sentence1 = i18n("A similar file named '%1' already exists.", d->dest.toDisplayString(QUrl::PreferLocalFile)); } else { sentence1 = i18n("A more recent item named '%1' already exists.", d->dest.toDisplayString(QUrl::PreferLocalFile)); } QLabel *lb = new KSqueezedTextLabel(sentence1, this); lb->setTextFormat(Qt::PlainText); pLayout->addWidget(lb); } if (!(_options & RenameDialog_OverwriteItself) && !(_options & RenameDialog_NoRename)) { if (_options & RenameDialog_Overwrite) { pLayout->addSpacing(15); // spacer } QLabel *lb2 = new QLabel(i18n("Rename:"), this); pLayout->addWidget(lb2); } QHBoxLayout *layout2 = new QHBoxLayout(); pLayout->addLayout(layout2); d->m_pLineEdit = new QLineEdit(this); layout2->addWidget(d->m_pLineEdit); if (d->bRename) { const QString fileName = d->dest.fileName(); d->setRenameBoxText(KIO::decodeFileName(fileName)); connect(d->m_pLineEdit, &QLineEdit::textChanged, this, &RenameDialog::enableRenameButton); d->m_pLineEdit->setFocus(); } else { d->m_pLineEdit->hide(); } if (d->bSuggestNewName) { layout2->addWidget(d->bSuggestNewName); setTabOrder(d->m_pLineEdit, d->bSuggestNewName); } KSeparator *separator = new KSeparator(this); pLayout->addWidget(separator); QHBoxLayout *layout = new QHBoxLayout(); pLayout->addLayout(layout); layout->addStretch(1); if (d->bApplyAll) { layout->addWidget(d->bApplyAll); setTabOrder(d->bApplyAll, d->bCancel); } if (d->bRename) { layout->addWidget(d->bRename); setTabOrder(d->bRename, d->bCancel); } if (d->bSkip) { layout->addWidget(d->bSkip); setTabOrder(d->bSkip, d->bCancel); } if (d->bOverwrite) { layout->addWidget(d->bOverwrite); setTabOrder(d->bOverwrite, d->bCancel); } if (d->bResume) { layout->addWidget(d->bResume); setTabOrder(d->bResume, d->bCancel); } d->bCancel->setDefault(true); layout->addWidget(d->bCancel); resize(sizeHint()); #if 1 // without kfilemetadata // don't wait for kfilemetadata, but wait until the layouting is done if (_options & RenameDialog_Overwrite) { QMetaObject::invokeMethod(this, "resizePanels", Qt::QueuedConnection); } #endif } RenameDialog::~RenameDialog() { delete d; } void RenameDialog::enableRenameButton(const QString &newDest) { if (newDest != KIO::decodeFileName(d->dest.fileName()) && !newDest.isEmpty()) { d->bRename->setEnabled(true); d->bRename->setDefault(true); if (d->bOverwrite) { d->bOverwrite->setEnabled(false); // prevent confusion (#83114) } } else { d->bRename->setEnabled(false); if (d->bOverwrite) { d->bOverwrite->setEnabled(true); } } } QUrl RenameDialog::newDestUrl() { const QString fileName = d->m_pLineEdit->text(); QUrl newDest = d->dest.adjusted(QUrl::RemoveFilename); // keeps trailing slash newDest.setPath(newDest.path() + KIO::encodeFileName(fileName)); return newDest; } QUrl RenameDialog::autoDestUrl() const { const QUrl destDirectory = d->dest.adjusted(QUrl::RemoveFilename | QUrl::StripTrailingSlash); const QString newName = KFileUtils::suggestName(destDirectory, d->dest.fileName()); QUrl newDest(destDirectory); newDest.setPath(concatPaths(newDest.path(), newName)); return newDest; } void RenameDialog::cancelPressed() { done(Result_Cancel); } // Rename void RenameDialog::renamePressed() { if (d->m_pLineEdit->text().isEmpty()) { return; } if (d->bApplyAll && d->bApplyAll->isChecked()) { done(Result_AutoRename); } else { const QUrl u = newDestUrl(); if (!u.isValid()) { KMessageBox::error(this, i18n("Malformed URL\n%1", u.errorString())); qCWarning(KIO_WIDGETS) << u.errorString(); return; } done(Result_Rename); } } -#ifndef KIOWIDGETS_NO_DEPRECATED QString RenameDialog::suggestName(const QUrl &baseURL, const QString &oldName) { return KFileUtils::suggestName(baseURL, oldName); } -#endif // Propose button clicked void RenameDialog::suggestNewNamePressed() { /* no name to play with */ if (d->m_pLineEdit->text().isEmpty()) { return; } QUrl destDirectory = d->dest.adjusted(QUrl::RemoveFilename | QUrl::StripTrailingSlash); d->setRenameBoxText(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::autoSkipPressed() { done(Result_AutoSkip); } void RenameDialog::overwritePressed() { if (d->bApplyAll && d->bApplyAll->isChecked()) { done(Result_OverwriteAll); } else { done(Result_Overwrite); } } void RenameDialog::overwriteAllPressed() { done(Result_OverwriteAll); } void RenameDialog::resumePressed() { if (d->bApplyAll && d->bApplyAll->isChecked()) { done(Result_ResumeAll); } else { done(Result_Resume); } } void RenameDialog::resumeAllPressed() { done(Result_ResumeAll); } void RenameDialog::applyAllPressed() { if (d->bApplyAll && d->bApplyAll->isChecked()) { d->m_pLineEdit->setText(KIO::decodeFileName(d->dest.fileName())); d->m_pLineEdit->setEnabled(false); if (d->bRename) { d->bRename->setEnabled(true); } if (d->bSuggestNewName) { d->bSuggestNewName->setEnabled(false); } } else { d->m_pLineEdit->setEnabled(true); if (d->bRename) { d->bRename->setEnabled(false); } if (d->bSuggestNewName) { d->bSuggestNewName->setEnabled(true); } } } void RenameDialog::showSrcIcon(const KFileItem &fileitem) { // The preview job failed, show a standard file icon. d->m_srcPendingPreview = false; const int size = d->m_srcPreview->height(); const QPixmap pix = KIconLoader::global()->loadMimeTypeIcon(fileitem.iconName(), KIconLoader::Desktop, size); d->m_srcPreview->setPixmap(pix); } void RenameDialog::showDestIcon(const KFileItem &fileitem) { // The preview job failed, show a standard file icon. d->m_destPendingPreview = false; const int size = d->m_destPreview->height(); const QPixmap pix = KIconLoader::global()->loadMimeTypeIcon(fileitem.iconName(), KIconLoader::Desktop, size); d->m_destPreview->setPixmap(pix); } void RenameDialog::showSrcPreview(const KFileItem &fileitem, const QPixmap &pixmap) { Q_UNUSED(fileitem); if (d->m_srcPendingPreview) { d->m_srcPreview->setPixmap(pixmap); d->m_srcPendingPreview = false; } } void RenameDialog::showDestPreview(const KFileItem &fileitem, const QPixmap &pixmap) { Q_UNUSED(fileitem); if (d->m_destPendingPreview) { d->m_destPreview->setPixmap(pixmap); d->m_destPendingPreview = false; } } void RenameDialog::resizePanels() { Q_ASSERT(d->m_srcArea != nullptr); Q_ASSERT(d->m_destArea != nullptr); Q_ASSERT(d->m_srcPreview != nullptr); Q_ASSERT(d->m_destPreview != nullptr); // using QDesktopWidget geometry as Kephal isn't accessible here in kdelibs const QSize screenSize = QApplication::desktop()->availableGeometry(this).size(); QSize halfSize = d->m_srcArea->widget()->sizeHint().expandedTo(d->m_destArea->widget()->sizeHint()); const QSize currentSize = d->m_srcArea->size().expandedTo(d->m_destArea->size()); const int maxHeightPossible = screenSize.height() - (size().height() - currentSize.height()); QSize maxHalfSize = QSize(screenSize.width() / qreal(2.1), maxHeightPossible * qreal(0.9)); if (halfSize.height() > maxHalfSize.height() && halfSize.width() <= maxHalfSize.width() + d->m_srcArea->verticalScrollBar()->width()) { halfSize.rwidth() += d->m_srcArea->verticalScrollBar()->width(); maxHalfSize.rwidth() += d->m_srcArea->verticalScrollBar()->width(); } d->m_srcArea->setMinimumSize(halfSize.boundedTo(maxHalfSize)); d->m_destArea->setMinimumSize(halfSize.boundedTo(maxHalfSize)); KIO::PreviewJob *srcJob = KIO::filePreview(KFileItemList() << d->srcItem, QSize(d->m_srcPreview->width() * qreal(0.9), d->m_srcPreview->height())); srcJob->setScaleType(KIO::PreviewJob::Unscaled); KIO::PreviewJob *destJob = KIO::filePreview(KFileItemList() << d->destItem, QSize(d->m_destPreview->width() * qreal(0.9), d->m_destPreview->height())); destJob->setScaleType(KIO::PreviewJob::Unscaled); connect(srcJob, &PreviewJob::gotPreview, this, &RenameDialog::showSrcPreview); connect(destJob, &PreviewJob::gotPreview, this, &RenameDialog::showDestPreview); connect(srcJob, &PreviewJob::failed, this, &RenameDialog::showSrcIcon); connect(destJob, &PreviewJob::failed, this, &RenameDialog::showDestIcon); } QScrollArea *RenameDialog::createContainerLayout(QWidget *parent, const KFileItem &item, QLabel *preview) { KFileItemList itemList; itemList << item; #if 0 // PENDING // KFileMetaDataWidget was deprecated for a Nepomuk widget, which is itself deprecated... // If we still want metadata shown, we need a plugin that fetches data from KFileMetaData::ExtractorCollection KFileMetaDataWidget *metaWidget = new KFileMetaDataWidget(this); metaWidget->setReadOnly(true); metaWidget->setItems(itemList); // ### This is going to call resizePanels twice! Need to split it up to do preview job only once on each side connect(metaWidget, SIGNAL(metaDataRequestFinished(KFileItemList)), this, SLOT(resizePanels())); #endif // Encapsulate the MetaDataWidgets inside a container with stretch at the bottom. // This prevents that the meta data widgets get vertically stretched // in the case where the height of m_metaDataArea > m_metaDataWidget. QWidget *widgetContainer = new QWidget(parent); QVBoxLayout *containerLayout = new QVBoxLayout(widgetContainer); containerLayout->setContentsMargins(0, 0, 0, 0); containerLayout->setSpacing(0); containerLayout->addWidget(preview); #if 0 // PENDING containerLayout->addWidget(metaWidget); #endif containerLayout->addStretch(1); QScrollArea *metaDataArea = new QScrollArea(parent); metaDataArea->setWidget(widgetContainer); metaDataArea->setWidgetResizable(true); metaDataArea->setFrameShape(QFrame::NoFrame); return metaDataArea; } diff --git a/src/widgets/renamedialog.h b/src/widgets/renamedialog.h index 75773d6f..31196647 100644 --- a/src/widgets/renamedialog.h +++ b/src/widgets/renamedialog.h @@ -1,133 +1,134 @@ /* This file is part of the KDE libraries Copyright (C) 2000 Stephan Kulow 1999 - 2008 David Faure 2001 Holger Freyther This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef KIO_RENAMEDIALOG_H #define KIO_RENAMEDIALOG_H #include #include #include #include #include #include "kiowidgets_export.h" class QScrollArea; class QLabel; class QPixmap; class KFileItem; class KSqueezedTextLabel; namespace KIO { /** * @class KIO::RenameDialog renamedialog.h * * 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 caption the caption 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 &caption, 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(); /** * @return the new destination * valid only if RENAME was chosen */ QUrl newDestUrl(); /** * @return an automatically renamed destination * @since 4.5 * valid always */ QUrl autoDestUrl() const; +#if KIOWIDGETS_ENABLE_DEPRECATED_SINCE(5, 0) /** * Given a directory path and a filename (which usually exists already), * this function returns a suggested name for a file that doesn't exist * in that directory. The existence is only checked for local urls though. * The suggested file name is of the form "foo 1", "foo 2" etc. - * @deprecated use KFileUtils::suggestName from KCoreAddons + * @deprecated Since 5.0. Use KIO::suggestName, since 5.61, use KFileUtils::suggestName from KCoreAddons */ -#ifndef KIOWIDGETS_NO_DEPRECATED - static KIOWIDGETS_DEPRECATED QString suggestName(const QUrl &baseURL, const QString &oldName); + KIOWIDGETS_DEPRECATED_VERSION(5, 0, "Use KFileUtils::suggestName(const QUrl &, const QString &) from KCoreAddons >= 5.61, or KIO::suggestName(const QUrl &, const QString &)") + static QString suggestName(const QUrl &baseURL, const QString &oldName); #endif public Q_SLOTS: void cancelPressed(); void renamePressed(); void skipPressed(); void autoSkipPressed(); void overwritePressed(); void overwriteAllPressed(); void resumePressed(); void resumeAllPressed(); void suggestNewNamePressed(); protected Q_SLOTS: void enableRenameButton(const QString &); private Q_SLOTS: void applyAllPressed(); void showSrcIcon(const KFileItem &); void showDestIcon(const KFileItem &); void showSrcPreview(const KFileItem &, const QPixmap &); void showDestPreview(const KFileItem &, const QPixmap &); void resizePanels(); private: QScrollArea *createContainerLayout(QWidget *parent, const KFileItem &item, QLabel *preview); class RenameDialogPrivate; RenameDialogPrivate *const d; }; } #endif diff --git a/src/widgets/sslui.h b/src/widgets/sslui.h index 8a323c2d..122cea56 100644 --- a/src/widgets/sslui.h +++ b/src/widgets/sslui.h @@ -1,65 +1,70 @@ /* This file is part of the KDE project * * Copyright (C) 2009 Andreas Hartmetz * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef _KSSLUI_H #define _KSSLUI_H #include "kiowidgets_export.h" #include -#include // TODO KF6 remove this include +#if KIOWIDGETS_ENABLE_DEPRECATED_SINCE(5, 64) +#include +#endif 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 KIOWIDGETS_ENABLE_DEPRECATED_SINCE(5, 64) /** * @deprecated since 5.64 use the KSslErrorUiData variant instead. */ -bool KIOWIDGETS_DEPRECATED_EXPORT askIgnoreSslErrors(const KTcpSocket *socket, - RulesStorage storedRules = RecallAndStoreRules); // TODO KF6 remove +KIOWIDGETS_DEPRECATED_VERSION(5, 64, "Use KIO::SslUi::askIgnoreSslErrors(const KSslErrorUiData &, RulesStorage)") +bool KIOWIDGETS_EXPORT askIgnoreSslErrors(const KTcpSocket *socket, + RulesStorage storedRules = RecallAndStoreRules); +#endif /** * 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 diff --git a/src/widgets/thumbcreator.h b/src/widgets/thumbcreator.h index 2ae5e6f9..65aefa1a 100644 --- a/src/widgets/thumbcreator.h +++ b/src/widgets/thumbcreator.h @@ -1,185 +1,190 @@ /* This file is part of the KDE libraries Copyright (C) 2000 Malte Starostik This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef _THUMBCREATOR_H_ #define _THUMBCREATOR_H_ #include "kiowidgets_export.h" class QString; class QImage; class QWidget; /** * @class ThumbCreator thumbcreator.h * * Base class for thumbnail generator plugins. * * KIO::PreviewJob, via the "thumbnail" kioslave, uses instances of this class * to generate the thumbnail previews. * * To add support for a new document type, subclass ThumbCreator and implement * create() to generate a thumbnail for a given path. Then create a factory * method called "new_creator" to return instances of your subclass: * \code * extern "C" * { * Q_DECL_EXPORT ThumbCreator *new_creator() * { * return new FooThumbCreator(); * } * }; * \endcode * * Compile your ThumbCreator as a module; for example, the relevant CMake code * for a thumbnailer for the "foo" filetype might look like * \code * set(foothumbnail_SRCS foothumbnail.cpp) * add_library(foothumbnail MODULE ${foothumbnail_SRCS}) * target_link_libraries(foothumbnail PRIVATE KF5::KIOWidgets) * * install(TARGETS foothumbnail DESTINATION ${PLUGIN_INSTALL_DIR}) * \endcode * * You also need to create a desktop file describing the thumbnailer. For * example: * \code * [Desktop Entry] * Type=Service * Name=Foo Documents * X-KDE-ServiceTypes=ThumbCreator * MimeType=application/x-foo; * CacheThumbnail=true * X-KDE-Library=foothumbcreator * \endcode * * Of course, you will need to install it: * \code * install(FILES foothumbcreator.desktop DESTINATION ${SERVICES_INSTALL_DIR}) * \endcode * * Note that you can supply a comma-separated list of mimetypes to the MimeTypes * entry, naming all mimetypes your ThumbCreator supports. You can also use * simple wildcards, like * \htmlonly "text/*".\endhtmlonly\latexonly text/$\ast$.\endlatexonly * * If the thumbnail creation is cheap (such as text previews), you can set * \code * CacheThumbnail=false * \endcode * in the desktop file to prevent your thumbnails from being cached on disk. * * You can also use the "ThumbnailerVersion" optional property in the .desktop * file, like * \code * ThumbnailerVersion=5 * \endcode * When this is incremented (or defined when it previously was not), all the * previously-cached thumbnails for this creator will be discarded. You should * increase the version if and only if old thumbnails need to be regenerated. */ // KF6 TODO: put this in the KIO namespace class KIOWIDGETS_EXPORT ThumbCreator { public: /** * Flags to provide hints to the user of this plugin. * @see flags() */ enum Flags { None = 0, /**< No hints. */ +#if KIOWIDGETS_ENABLE_DEPRECATED_SINCE(5, 32) DrawFrame = 1, /**< \deprecated since 5.32. Used to paint a frame around the preview, but applications take care of that nowadays. */ +#endif BlendIcon = 2 /**< The mimetype icon should be blended over the preview. */ }; /** * Destructor. */ virtual ~ThumbCreator(); /** * Creates a thumbnail. * * Note that this method should not do any scaling. The @p width and @p * height parameters are provided as hints for images that are generated * from non-image data (like text). * * @param path The path of the file to create a preview for. This is * always a local path. * @param width The requested preview width (see the note on scaling * above). * @param height The requested preview height (see the note on scaling * above). * @param img The QImage to store the preview in. * * @return @c true if a preview was successfully generated and store in @p * img, @c false otherwise. */ virtual bool create(const QString &path, int width, int height, QImage &img) = 0; // KF6 TODO: turn first arg into QUrl (see thumbnail/htmlcreator.cpp) /** * Returns the flags for this plugin. * * @return XOR'd flags values. * @see Flags */ virtual Flags flags() const; /** * Create a widget for configuring the thumb creator. * * The caller will take ownership of the returned instance and must ensure * its deletion. * * The default implementation returns @c nullptr. * * The following key in the thumbcreator .desktop file must be set to * mark the plugin as configurable: * \code * Configurable=true * \endcode * * @return A QWidget instance, which the caller takes ownership of, or @c nullptr. */ virtual QWidget *createConfigurationWidget(); /** * Write the updated configuration. * * @param configurationWidget An object returned by * createConfigurationWidget(). */ virtual void writeConfiguration(const QWidget *configurationWidget); }; +#if KIOWIDGETS_ENABLE_DEPRECATED_SINCE(5, 0) /** * @class ThumbCreatorV2 thumbcreator.h * @since 4.7 * @deprecated since 5.0, use ThumbCreator */ -class KIOWIDGETS_DEPRECATED_EXPORT ThumbCreatorV2 : public ThumbCreator +class KIOWIDGETS_DEPRECATED_VERSION(5, 0, "Use ThumbCreator") +KIOWIDGETS_EXPORT ThumbCreatorV2 : public ThumbCreator { public: virtual ~ThumbCreatorV2(); }; +#endif // KF6 TODO: rename this to something less generic typedef ThumbCreator *(*newCreator)(); #endif