From 6abff23b831d31a011f4d71d41c1e4bbb32351d4 Mon Sep 17 00:00:00 2001 From: Alf Gaida Date: Sun, 3 Dec 2017 20:08:16 +0100 Subject: [PATCH] Imported upstream release 0.3.1. --- .gitignore | 8 - AUTHORS | 2 +- CHANGELOG | 77 ++- CMakeLists.txt | 37 +- cmake/compiler_settings.cmake | 72 ++- cmake/qt5xdg-config.cmake.in | 18 +- cmake/qt5xdgiconloader-config.cmake.in | 14 +- qtxdg/CMakeLists.txt | 32 +- qtxdg/xdgaction.h | 2 +- qtxdg/xdgautostart.cpp | 4 +- qtxdg/xdgdesktopfile.cpp | 212 +++++--- qtxdg/xdgdesktopfile.h | 22 + qtxdg/xdgdirs.cpp | 12 +- qtxdg/xdgicon.cpp | 34 +- qtxdg/xdgicon.h | 15 +- qtxdg/xdgmenu.cpp | 19 +- qtxdg/xdgmenu.h | 2 +- qtxdg/xdgmenu_p.h | 4 +- qtxdg/xdgmenuapplinkprocessor.cpp | 13 +- qtxdg/xdgmenureader.cpp | 21 +- qtxdg/xdgmenureader.h | 4 +- qtxdg/xdgmenuwidget.cpp | 4 +- qtxdg/xdgmimetype.cpp | 2 +- qtxdg/xmlhelper.cpp | 2 +- release.sh | 28 - test/CMakeLists.txt | 16 +- test/qtxdg_test.cpp | 8 +- test/qtxdg_test.h | 2 +- test/tst_xdgdesktopfile.cpp | 8 +- test/tst_xdgdesktopfile.h | 2 +- test/tst_xdgdirs.cpp | 2 +- util/CMakeLists.txt | 36 +- util/qtxdg-iconfinder.cpp | 74 +++ xdgiconloader/CMakeLists.txt | 32 +- xdgiconloader/plugin/CMakeLists.txt | 49 ++ xdgiconloader/plugin/xdgiconengineplugin.cpp | 35 ++ xdgiconloader/plugin/xdgiconengineplugin.h | 38 ++ xdgiconloader/plugin/xdgiconengineplugin.json | 1 + xdgiconloader/xdgiconloader.cpp | 495 ++++++++++-------- xdgiconloader/xdgiconloader_p.h | 148 ++---- 40 files changed, 1036 insertions(+), 570 deletions(-) delete mode 100644 .gitignore delete mode 100755 release.sh create mode 100644 util/qtxdg-iconfinder.cpp create mode 100644 xdgiconloader/plugin/CMakeLists.txt create mode 100644 xdgiconloader/plugin/xdgiconengineplugin.cpp create mode 100644 xdgiconloader/plugin/xdgiconengineplugin.h create mode 100644 xdgiconloader/plugin/xdgiconengineplugin.json diff --git a/.gitignore b/.gitignore deleted file mode 100644 index 429cda3..0000000 --- a/.gitignore +++ /dev/null @@ -1,8 +0,0 @@ -build* -*.qm -*~ -*.autosave -*-swp -*.swp -CMakeLists.txt.user* -nbproject/ diff --git a/AUTHORS b/AUTHORS index dd2061e..6ec1f48 100644 --- a/AUTHORS +++ b/AUTHORS @@ -4,7 +4,7 @@ Upstream Authors: Copyright: Copyright (c) 2010-2012 Razor team - Copyright (c) 2012-2016 LXQt team + Copyright (c) 2012-2017 LXQt team License: LGPL-2.1+ and LGPL-2.1-or-3-with-Digia-1.1-exception The full text of the LGPL-2.1+ license can be found in the 'COPYING' file. diff --git a/CHANGELOG b/CHANGELOG index ae353a4..a70cca9 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,7 +1,82 @@ -libqtxdg-2.0.0 / 2016-09-17 +libqtxdg-3.1.0 / 2017-10-21 =========================== + * Bump version to 3.1.0 + * xdgdesktopfile: Add API for getting actions info + * xdgdesktopfile: Add support for activating actions + * xdgdesktopfile: Add getting actions + * Check $XDG_CONFIG_HOME and $XDG_CONFIG_DIRS for mimeapps.list first. + * Fix reading and writing mimeapps.list file. + * Don't export github templates + +3.0.0 / 2017-09-22 +================== + + * Release 3.0.0: Update changelog + * Backport support for Scale directory key according to Icon Theme spec + * Bump Major to 3 + * test: Drop Q_FOREACH + * Drop Q_FOREACH + * liblxqt make no sense here + * Copied issue template + * Drops Qt5Core_VERSION_STRING + * Avoid Qt special keywords collision + * XdgDesktopFile: Stops allocating unneeded QMap::keys() + * XdgDesktopFile: Stop allocating unneeded QHash:values() + * XdgDesktopFile: Improve const-ness + * xdgiconloader: Reworks the unthemed/pixmap search + * xdgiconloader: Puts the hicolor at the end of the theme hierarchy + * XdgIcon: Add flag for "FollowsColorScheme" processing + * xdgiconloader: Honor "FolowsColorScheme" theme hint + * xdgiconloader: Support symbolic SVG icons + * More fixes (#131) + * xdgiconloader: Correct hierarchy of fallbacks (#116) + * xdgiconloader: Fix XdgIconLoaderEngine::actualSize() (#130) + * Update CMakeLists.txt + * It adds loadIcon() timing measurements. + * xdgiconloader: Consider all existing files/images + * Check QTXDGX_ICONENGINEPLUGIN_INSTALL_PATH existence + * Mark QTXDGX_ICONENGINEPLUGIN_INSTALL_PATH as advanced + * xdgiconloader: Implement QIconEnginePlugin interface + * Disables uninstall target + * Remove last uses of Java-style (non-mutable) iterators from QtBase + * Adds a development qtxdg-iconfinder utility tool + * Enable strict iterators for debug builds + * Removes extra semi-colons + * Improve build warnings + * Bump year + * QtGui: eradicate Q_FOREACH loops [already const] + * Optimize QIconLoader::findIconHelper() + * Remove unused variable in QIconLoader::findIconHelper() + * Improve use of QHash to minimize double hashing + * QIconLoaderEngine: add missing Q_DECL_OVERRIDEs + * Replace QLatin1Literal with QLatin1String + * QIconCacheGtkReader: use QStringRef more + * Gui: use const (and const APIs) more + * Adds Link Time Optimization + * Replaces CMAKE_SOURCE_DIR by PROJECT_SOURCE_DIR + * Refactors superbuild support + * Remove duplicate use of source header files + * Use AUTOMOC everywhere + * Stop using include_directories() + * Removes test project definition + * Use CMAKE_INCLUDE_CURRENT_DIR + * Adds PROJECT_NAME to the build Qt version message + * Simplify target_compile_definitions() and target_include_directories() + * qiconloader: Reuse Qt implementation + * XdgIconLoader: Fix FTBFS in super-build/in-tree builds + * Allow xdg-user-dirs in the realpath of $HOME. On some systems /home is a symlink and $HOME points to the symlink. This commit allows the xdg-user-dirs to start with the real/canonical path. + * Updates version requirements in pkg-config (.pc) stuff + * Make Qt5Xdg use only the same version Qt5XdgIconLoader + * Adds minimum Qt version requirement (5.4.2) + * test: Fixes false positive in tst_xdgdesktopfile::testReadLocalized() + * Remove cpack (#106) + +2.0.0 / 2016-09-17 +================== + + * Release 2.0.0: Add changelog * Bump version to 2.0.0 * Extend README.md * Updates dependencies diff --git a/CMakeLists.txt b/CMakeLists.txt index 103d5ba..9145c34 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -25,11 +25,13 @@ else() set(CMAKE_CXX_STANDARD 11) endif() -set(QTXDG_MAJOR_VERSION 2) -set(QTXDG_MINOR_VERSION 0) +set(QTXDG_MAJOR_VERSION 3) +set(QTXDG_MINOR_VERSION 1) set(QTXDG_PATCH_VERSION 0) set(QTXDG_VERSION_STRING ${QTXDG_MAJOR_VERSION}.${QTXDG_MINOR_VERSION}.${QTXDG_PATCH_VERSION}) +set(QT_MINIMUM_VERSION "5.6.1") + include(GNUInstallDirs) # Standard directories for installation include(CMakePackageConfigHelpers) include(GenerateExportHeader) @@ -37,13 +39,12 @@ include(create_portable_headers) include(create_pkgconfig_file) include(compiler_settings NO_POLICY_SCOPE) -find_package(Qt5Widgets REQUIRED QUIET) -find_package(Qt5Svg REQUIRED QUIET) -find_package(Qt5Xml REQUIRED QUIET) -find_package(Qt5DBus REQUIRED QUIET) +set(CMAKE_AUTOMOC ON) + +find_package(Qt5 ${QT_MINIMUM_VERSION} CONFIG REQUIRED Widgets Svg Xml DBus) if (BUILD_TESTS) - find_package(Qt5Test REQUIRED QUIET) + find_package(Qt5 ${QT_MINIMUM_VERSION} CONFIG REQUIRED Test) endif() @@ -52,12 +53,13 @@ set(QTXDGX_FILE_NAME "qt5xdg") set(QTXDGX_ICONLOADER_LIBRARY_NAME "Qt5XdgIconLoader") set(QTXDGX_ICONLOADER_FILE_NAME "qt5xdgiconloader") +set(QTXDGX_ICONENGINEPLUGIN_LIBRARY_NAME "Qt5XdgIconPlugin") set(QTXDGX_PKG_CONFIG_DESCRIPTION "Qt5Xdg, a Qt5 implementation of XDG standards") -set(QTXDGX_PKG_CONFIG_REQUIRES "Qt5Core, Qt5Xml, Qt5Widgets, Qt5DBus, Qt5XdgIconLoader") +set(QTXDGX_PKG_CONFIG_REQUIRES "Qt5Core >= ${QT_MINIMUM_VERSION}, Qt5Xml >= ${QT_MINIMUM_VERSION}, Qt5Widgets >= ${QT_MINIMUM_VERSION}, Qt5DBus >= ${QT_MINIMUM_VERSION}, Qt5XdgIconLoader = ${QTXDG_VERSION_STRING}") set(QTXDGX_ICONLOADER_PKG_CONFIG_DESCRIPTION "Qt5XdgIconLader, a Qt5 XDG Icon Loader") -set(QTXDGX_ICONLOADER_PKG_CONFIG_REQUIRES "Qt5Gui, Qt5Svg") +set(QTXDGX_ICONLOADER_PKG_CONFIG_REQUIRES "Qt5Gui >= ${QT_MINIMUM_VERSION}, Qt5Svg >= ${QT_MINIMUM_VERSION}") set(QTXDGX_INTREE_INCLUDEDIR "${CMAKE_CURRENT_BINARY_DIR}/InTreeBuild/include") @@ -65,7 +67,7 @@ if (NOT CMAKE_BUILD_TYPE) set ( CMAKE_BUILD_TYPE Release ) endif (NOT CMAKE_BUILD_TYPE) -message(STATUS "Building with Qt ${Qt5Core_VERSION_STRING}") +message(STATUS "Building ${PROJECT_NAME} with Qt ${Qt5Core_VERSION}") add_subdirectory(xdgiconloader) add_subdirectory(qtxdg) @@ -167,16 +169,5 @@ configure_file( "${CMAKE_CURRENT_BINARY_DIR}/cmake_uninstall.cmake" IMMEDIATE @ONLY) -add_custom_target(uninstall - COMMAND ${CMAKE_COMMAND} -P "${CMAKE_CURRENT_BINARY_DIR}/cmake_uninstall.cmake") - - -# building tarball with CPack ------------------------------------------------- -include (InstallRequiredSystemLibraries) -set (CPACK_PACKAGE_VERSION_MAJOR ${QTXDG_MAJOR_VERSION}) -set (CPACK_PACKAGE_VERSION_MINOR ${QTXDG_MINOR_VERSION}) -set (CPACK_PACKAGE_VERSION_PATCH ${QTXDG_PATCH_VERSION}) -set (CPACK_GENERATOR TBZ2) -set (CPACK_SOURCE_GENERATOR TBZ2) -set (CPACK_SOURCE_IGNORE_FILES /build/;.gitignore;.*~;.git;.kdev4;temp) -include (CPack) +#add_custom_target(uninstall +# COMMAND ${CMAKE_COMMAND} -P "${CMAKE_CURRENT_BINARY_DIR}/cmake_uninstall.cmake") diff --git a/cmake/compiler_settings.cmake b/cmake/compiler_settings.cmake index 044b36a..6e318d9 100644 --- a/cmake/compiler_settings.cmake +++ b/cmake/compiler_settings.cmake @@ -1,5 +1,6 @@ #============================================================================= # Copyright 2015 Luís Pereira +# Copyright 2013 Hong Jen Yee (PCMan) # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions @@ -65,6 +66,14 @@ if ("${CMAKE_CXX_COMPILER_ID}" STREQUAL "Clang") endif() +#----------------------------------------------------------------------------- +# Global definitions +#----------------------------------------------------------------------------- +if (CMAKE_BUILD_TYPE MATCHES "Debug") + add_definitions(-DQT_STRICT_ITERATORS) +endif() + + #----------------------------------------------------------------------------- # Set visibility to hidden to hide symbols, unless they're exported manually # in the code @@ -80,18 +89,25 @@ if (CMAKE_COMPILER_IS_GNUCXX OR QTXDG_COMPILER_IS_CLANGCXX) set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fno-exceptions") endif() - #----------------------------------------------------------------------------- # Common warning flags #----------------------------------------------------------------------------- -set(QTXDG_COMMON_WARNING_FLAGS "-Wall") +set(QTXDG_COMMON_WARNING_FLAGS "-Wall -Wextra -Wchar-subscripts -Wno-long-long -Wpointer-arith -Wundef -Wformat-security") #----------------------------------------------------------------------------- # Warning flags #----------------------------------------------------------------------------- +if (CMAKE_COMPILER_IS_GNUCXX OR QTXDG_COMPILER_IS_CLANGCXX) + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${__QTXDG_COMMON_WARNING_FLAGS} -Wnon-virtual-dtor -Woverloaded-virtual -Wpedantic") +endif() + +if (QTXDG_COMPILER_IS_CLANGCXX) + # qCDebug(), qCWarning, etc trigger a very verbose warning, about.... nothing. Disable it. + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-gnu-zero-variadic-macro-arguments") +endif() + list(APPEND QTXDG_WARNING_FLAGS ${QTXDG_COMMON_WARNING_FLAGS}) -add_definitions(${QTXDG_WARNING_FLAGS}) #----------------------------------------------------------------------------- # String conversion flags @@ -103,3 +119,53 @@ add_definitions( -DQT_NO_URL_CAST_FROM_STRING -DQT_NO_CAST_FROM_BYTEARRAY ) + +#----------------------------------------------------------------------------- +# Linker flags +# Do not allow undefined symbols +#----------------------------------------------------------------------------- +if (CMAKE_COMPILER_IS_GNUCXX OR QTXDG_COMPILER_IS_CLANGCXX) + # -Bsymbolic-functions: replace dynamic symbols used internally in + # shared libs with direct addresses. + set(SYMBOLIC_FLAGS + "-Wl,-Bsymbolic-functions -Wl,-Bsymbolic" + ) + set(CMAKE_SHARED_LINKER_FLAGS + "-Wl,--no-undefined ${SYMBOLIC_FLAGS} ${CMAKE_SHARED_LINKER_FLAGS}" + ) + set(CMAKE_MODULE_LINKER_FLAGS + "-Wl,--no-undefined ${SYMBOLIC_FLAGS} ${CMAKE_MODULE_LINKER_FLAGS}" + ) + set(CMAKE_EXE_LINKER_FLAGS + "${SYMBOLIC_FLAGS} ${CMAKE_EXE_LINKER_FLAGS}" + ) + +endif() + +#----------------------------------------------------------------------------- +# Turn on more aggrassive optimizations not supported by CMake +# References: https://wiki.qt.io/Performance_Tip_Startup_Time +#----------------------------------------------------------------------------- +if (CMAKE_COMPILER_IS_GNUCXX OR QTXDG_COMPILER_IS_CLANGCXX) + # -flto: use link-time optimizations to generate more efficient code + if (CMAKE_COMPILER_IS_GNUCXX) + set(LTO_FLAGS "-flto -fuse-linker-plugin") + # When building static libraries with LTO in gcc >= 4.9, + # "gcc-ar" and "gcc-ranlib" should be used instead of "ar" and "ranlib". + # references: + # https://gcc.gnu.org/gcc-4.9/changes.html + # http://hubicka.blogspot.tw/2014/04/linktime-optimization-in-gcc-2-firefox.html + # https://github.com/monero-project/monero/pull/1065/commits/1855213c8fb8f8727f4107716aab8e7ba826462b + if (NOT CMAKE_CXX_COMPILER_VERSION VERSION_LESS "4.9.0") # gcc >= 4.9 + set(CMAKE_AR "gcc-ar") + set(CMAKE_RANLIB "gcc-ranlib") + endif() + elseif (QTXDG_COMPILER_IS_CLANGCXX) + # The link-time optimization of clang++/llvm seems to be too aggrassive. + # After testing, it breaks the signal/slots of QObject sometimes. + # So disable it for now until there is a solution. + # set(LTO_FLAGS "-flto") + endif() + # apply these options to "Release" build type only + set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} ${LTO_FLAGS}") +endif() diff --git a/cmake/qt5xdg-config.cmake.in b/cmake/qt5xdg-config.cmake.in index 045c920..ebcf150 100644 --- a/cmake/qt5xdg-config.cmake.in +++ b/cmake/qt5xdg-config.cmake.in @@ -1,13 +1,15 @@ @PACKAGE_INIT@ -include(CMakeFindDependencyMacro) +if (NOT TARGET @QTXDGX_LIBRARY_NAME@) + include(CMakeFindDependencyMacro) -find_dependency(Qt5Widgets) -find_dependency(Qt5Xml) -find_dependency(Qt5DBus) -find_dependency(Qt5XdgIconLoader) + find_dependency(Qt5Widgets @QT_MINIMUM_VERSION@) + find_dependency(Qt5Xml @QT_MINIMUM_VERSION@) + find_dependency(Qt5DBus @QT_MINIMUM_VERSION@) + find_dependency(Qt5XdgIconLoader @QTXDG_VERSION_STRING@ EXACT) -if (CMAKE_VERSION VERSION_GREATER 2.8.12) - cmake_policy(SET CMP0024 OLD) + if (CMAKE_VERSION VERSION_GREATER 2.8.12) + cmake_policy(SET CMP0024 NEW) + endif() + include("${CMAKE_CURRENT_LIST_DIR}/qt5xdg-targets.cmake") endif() -include("${CMAKE_CURRENT_LIST_DIR}/qt5xdg-targets.cmake") diff --git a/cmake/qt5xdgiconloader-config.cmake.in b/cmake/qt5xdgiconloader-config.cmake.in index f5ed0ac..765ef45 100644 --- a/cmake/qt5xdgiconloader-config.cmake.in +++ b/cmake/qt5xdgiconloader-config.cmake.in @@ -1,11 +1,13 @@ @PACKAGE_INIT@ -include(CMakeFindDependencyMacro) +if (NOT TARGET @QTXDGX_ICONLOADER_LIBRARY_NAME@) + include(CMakeFindDependencyMacro) -find_dependency(Qt5Gui) -find_dependency(Qt5Svg) + find_dependency(Qt5Gui @QT_MINIMUM_REQUIRED@) + find_dependency(Qt5Svg @QT_MINIMUM_REQUIRED@) -if (CMAKE_VERSION VERSION_GREATER 2.8.12) - cmake_policy(SET CMP0024 OLD) + if (CMAKE_VERSION VERSION_GREATER 2.8.12) + cmake_policy(SET CMP0024 NEW) + endif() + include("${CMAKE_CURRENT_LIST_DIR}/qt5xdgiconloader-targets.cmake") endif() -include("${CMAKE_CURRENT_LIST_DIR}/qt5xdgiconloader-targets.cmake") diff --git a/qtxdg/CMakeLists.txt b/qtxdg/CMakeLists.txt index 6eaea07..261849a 100644 --- a/qtxdg/CMakeLists.txt +++ b/qtxdg/CMakeLists.txt @@ -1,8 +1,5 @@ set(QTX_LIBRARIES Qt5::Widgets Qt5::Xml Qt5::DBus) -include_directories( - "${Qt5Gui_PRIVATE_INCLUDE_DIRS}" -) set(libqtxdg_PUBLIC_H_FILES xdgaction.h xdgdesktopfile.h @@ -63,14 +60,11 @@ set(libqtxdg_MOCS xdgmenuwidget.h ) -QT5_WRAP_CPP(libqtxdg_CXX_FILES ${libqtxdg_MOCS}) - add_library(${QTXDGX_LIBRARY_NAME} SHARED ${libqtxdg_PUBLIC_H_FILES} ${libqtxdg_PRIVATE_H_FILES} - ${libqtxdg_PRIVATE_H_FILES} ${libqtxdg_CPP_FILES} - ${libqtxdg_CXX_FILES} + ${libqtxdg_MOCS} ) target_link_libraries(${QTXDGX_LIBRARY_NAME} @@ -85,24 +79,22 @@ set_target_properties(${QTXDGX_LIBRARY_NAME} PROPERTIES ) target_compile_definitions(${QTXDGX_LIBRARY_NAME} - PRIVATE "QTXDG_COMPILATION=\"1\"" - PRIVATE "QTXDG_VERSION=\"${QTXDG_VERSION_STRING}\"" + PRIVATE + "QTXDG_COMPILATION=\"1\"" + "QTXDG_VERSION=\"${QTXDG_VERSION_STRING}\"" + "QT_NO_KEYWORDS" ) target_include_directories(${QTXDGX_LIBRARY_NAME} - INTERFACE "$" - INTERFACE "$" + INTERFACE + "$" + "$" + "$" + "$" + PRIVATE + ${Qt5Gui_PRIVATE_INCLUDE_DIRS} ) -# include directories and targets for the in tree build -target_include_directories(${QTXDGX_LIBRARY_NAME} - INTERFACE "$" - INTERFACE "$" -) - -export(TARGETS ${QTXDGX_LIBRARY_NAME} APPEND FILE "${CMAKE_BINARY_DIR}/${QTXDGX_FILE_NAME}-targets.cmake") -# end of in tree build stuff - # create the portble headers create_portable_headers(libqtxdg_PORTABLE_HEADERS HEADER_NAMES ${libqtxdg_PUBLIC_CLASSES} diff --git a/qtxdg/xdgaction.h b/qtxdg/xdgaction.h index 154a908..2991a39 100644 --- a/qtxdg/xdgaction.h +++ b/qtxdg/xdgaction.h @@ -71,7 +71,7 @@ public: const XdgDesktopFile& desktopFile() const { return mDesktopFile; } -private slots: +private Q_SLOTS: void runConmmand() const; void updateIcon(); diff --git a/qtxdg/xdgautostart.cpp b/qtxdg/xdgautostart.cpp index 402937e..1bceb40 100644 --- a/qtxdg/xdgautostart.cpp +++ b/qtxdg/xdgautostart.cpp @@ -57,14 +57,14 @@ XdgDesktopFileList XdgAutoStart::desktopFileList(QStringList dirs, bool excludeH QSet processed; XdgDesktopFileList ret; - foreach (const QString &dirName, dirs) + for (const QString &dirName : const_cast(dirs)) { QDir dir(dirName); if (!dir.exists()) continue; const QFileInfoList files = dir.entryInfoList(QStringList(QLatin1String("*.desktop")), QDir::Files | QDir::Readable); - foreach (const QFileInfo &fi, files) + for (const QFileInfo &fi : files) { if (processed.contains(fi.fileName())) continue; diff --git a/qtxdg/xdgdesktopfile.cpp b/qtxdg/xdgdesktopfile.cpp index 4972c37..cde6388 100644 --- a/qtxdg/xdgdesktopfile.cpp +++ b/qtxdg/xdgdesktopfile.cpp @@ -66,6 +66,7 @@ static const QStringList nonDetachExecs = QStringList() static const QLatin1String onlyShowInKey("OnlyShowIn"); static const QLatin1String notShowInKey("NotShowIn"); static const QLatin1String categoriesKey("Categories"); +static const QLatin1String actionsKey("Actions"); static const QLatin1String extendPrefixKey("X-"); static const QLatin1String mimeTypeKey("MimeType"); static const QLatin1String applicationsStr("applications"); @@ -274,14 +275,37 @@ QString &unEscapeExec(QString& str) return doUnEscape(str, repl); } +namespace +{ + /*! + * Helper class for getting the keys for "Additional applications actions" + * ([Desktop Action %s] sections) + */ + class XdgDesktopAction : public XdgDesktopFile + { + public: + XdgDesktopAction(const XdgDesktopFile & parent, const QString & action) + : XdgDesktopFile(parent) + , m_prefix(QString{QLatin1String("Desktop Action %1")}.arg(action)) + {} + + protected: + virtual QString prefix() const { return m_prefix; } + + private: + const QString m_prefix; + }; +} + class XdgDesktopFileData: public QSharedData { public: XdgDesktopFileData(); bool read(const QString &prefix); XdgDesktopFile::Type detectType(XdgDesktopFile *q) const; - bool startApplicationDetached(const XdgDesktopFile *q, const QStringList& urls) const; + bool startApplicationDetached(const XdgDesktopFile *q, const QString & action, const QStringList& urls) const; bool startLinkDetached(const XdgDesktopFile *q) const; - bool startByDBus(const QStringList& urls) const; + bool startByDBus(const QString & action, const QStringList& urls) const; + QStringList getListValue(const XdgDesktopFile * q, const QString & key, bool tryExtendPrefix) const; QString mFileName; bool mIsValid; @@ -365,7 +389,7 @@ XdgDesktopFile::Type XdgDesktopFileData::detectType(XdgDesktopFile *q) const return XdgDesktopFile::UnknownType; } -bool XdgDesktopFileData::startApplicationDetached(const XdgDesktopFile *q, const QStringList& urls) const +bool XdgDesktopFileData::startApplicationDetached(const XdgDesktopFile *q, const QString & action, const QStringList& urls) const { //DBusActivatable handling if (q->value(QLatin1String("DBusActivatable"), false).toBool()) { @@ -390,10 +414,12 @@ bool XdgDesktopFileData::startApplicationDetached(const XdgDesktopFile *q, const * We consider that this violation is more acceptable than an failure * in launching an application. */ - if (startByDBus(urls)) + if (startByDBus(action, urls)) return true; } - QStringList args = q->expandExecString(urls); + QStringList args = action.isEmpty() + ? q->expandExecString(urls) + : XdgDesktopAction{*q, action}.expandExecString(urls); if (args.isEmpty()) return false; @@ -409,9 +435,9 @@ bool XdgDesktopFileData::startApplicationDetached(const XdgDesktopFile *q, const } bool nonDetach = false; - foreach(const QString &s, nonDetachExecs) + for (const QString &s : nonDetachExecs) { - foreach(const QString &a, args) + for (const QString &a : const_cast(args)) { if (a.contains(s)) { @@ -481,8 +507,7 @@ bool XdgDesktopFileData::startLinkDetached(const XdgDesktopFile *q) const return false; } -// TODO: Handle ActivateAction -bool XdgDesktopFileData::startByDBus(const QStringList& urls) const +bool XdgDesktopFileData::startByDBus(const QString & action, const QStringList& urls) const { QFileInfo f(mFileName); QString path(f.completeBaseName()); @@ -509,7 +534,13 @@ bool XdgDesktopFileData::startByDBus(const QStringList& urls) const << ", but trying to continue..."; } QDBusMessage reply; - if (urls.isEmpty()) + if (!action.isEmpty()) + { + QList v_urls; + for (const auto & url : urls) + v_urls.append(url); + reply = app.call(QLatin1String("ActivateAction"), action, v_urls, platformData); + } else if (urls.isEmpty()) reply = app.call(QLatin1String("Activate"), platformData); else reply = app.call(QLatin1String("Open"), urls, platformData); @@ -517,6 +548,19 @@ bool XdgDesktopFileData::startByDBus(const QStringList& urls) const return QDBusMessage::ErrorMessage != reply.type(); } +QStringList XdgDesktopFileData::getListValue(const XdgDesktopFile * q, const QString & key, bool tryExtendPrefix) const +{ + QString used_key = key; + if (!q->contains(used_key) && tryExtendPrefix) + { + used_key = extendPrefixKey + key; + if (!q->contains(used_key)) + return QStringList(); + } + + return q->value(key).toString().split(QLatin1Char(';'), QString::SkipEmptyParts); +} + XdgDesktopFile::XdgDesktopFile(): d(new XdgDesktopFileData) @@ -750,22 +794,13 @@ QVariant XdgDesktopFile::localizedValue(const QString& key, const QVariant& defa QStringList XdgDesktopFile::categories() const { - QString key; - if (contains(categoriesKey)) - { - key = categoriesKey; - } - else - { - key = extendPrefixKey + categoriesKey; - if (!contains(key)) - return QStringList(); - } - - QStringList cats = value(key).toString().split(QLatin1Char(';')); - return cats; + return d->getListValue(this, categoriesKey, true); } +QStringList XdgDesktopFile::actions() const +{ + return d->getListValue(this, actionsKey, false); +} void XdgDesktopFile::removeEntry(const QString& key) { @@ -806,18 +841,41 @@ QIcon const XdgDesktopFile::icon(const QIcon& fallback) const } +QIcon const XdgDesktopFile::actionIcon(const QString & action, const QIcon& fallback) const +{ + return d->mType == ApplicationType + ? XdgDesktopAction{*this, action}.icon(icon(fallback)) + : fallback; +} + + QString const XdgDesktopFile::iconName() const { return value(iconKey).toString(); } +QString const XdgDesktopFile::actionIconName(const QString & action) const +{ + return d->mType == ApplicationType + ? XdgDesktopAction{*this, action}.iconName() + : QString{}; +} + + QStringList XdgDesktopFile::mimeTypes() const { return value(mimeTypeKey).toString().split(QLatin1Char(';'), QString::SkipEmptyParts); } +QString XdgDesktopFile::actionName(const QString & action) const +{ + return d->mType == ApplicationType + ? XdgDesktopAction{*this, action}.name() + : QString{}; +} + XdgDesktopFile::Type XdgDesktopFile::type() const { return d->mType; @@ -839,7 +897,7 @@ bool XdgDesktopFile::startDetached(const QStringList& urls) const switch(d->mType) { case ApplicationType: - return d->startApplicationDetached(this, urls); + return d->startApplicationDetached(this, QString{}, urls); break; case LinkType: @@ -851,6 +909,11 @@ bool XdgDesktopFile::startDetached(const QStringList& urls) const } } +bool XdgDesktopFile::actionActivate(const QString & action, const QStringList& urls) const +{ + return d->mType == ApplicationType ? d->startApplicationDetached(this, action, urls) : false; +} + /************************************************ This is an overloaded function. @@ -953,7 +1016,7 @@ QString expandEnvVariables(const QString str) QStringList expandEnvVariables(const QStringList strs) { QStringList res; - foreach(const QString &s, strs) + for (const QString &s : strs) res << expandEnvVariables(s); return res; @@ -969,9 +1032,9 @@ QStringList XdgDesktopFile::expandExecString(const QStringList& urls) const QString execStr = value(execKey).toString(); unEscapeExec(execStr); - QStringList tokens = parseCombinedArgString(execStr); + const QStringList tokens = parseCombinedArgString(execStr); - foreach (QString token, tokens) + for (QString token : tokens) { // The parseCombinedArgString() splits the string by the space symbols, // we temporarily replaced them on the special characters. @@ -1016,7 +1079,7 @@ QStringList XdgDesktopFile::expandExecString(const QStringList& urls) const // program. Local files may either be passed as file: URLs or as file path. if (token == QLatin1String("%U")) { - foreach (const QString &s, urls) + for (const QString &s : urls) { QUrl url(expandEnvVariables(s)); result << ((!url.toLocalFile().isEmpty()) ? url.toLocalFile() : QString::fromUtf8(url.toEncoded())); @@ -1079,9 +1142,9 @@ bool checkTryExec(const QString& progName) if (progName.startsWith(QDir::separator())) return QFileInfo(progName).isExecutable(); - QStringList dirs = QFile::decodeName(qgetenv("PATH")).split(QLatin1Char(':')); + const QStringList dirs = QFile::decodeName(qgetenv("PATH")).split(QLatin1Char(':')); - foreach (const QString &dir, dirs) + for (const QString &dir : dirs) { if (QFileInfo(QDir(dir), progName).isExecutable()) return true; @@ -1103,7 +1166,7 @@ QString XdgDesktopFile::id(const QString &fileName, bool checkFileExists) QString id = f.absoluteFilePath(); const QStringList dataDirs = XdgDirs::dataDirs(); - foreach(const QString &d, dataDirs) { + for (const QString &d : dataDirs) { if (id.startsWith(d)) { // remove only the first occurence id.replace(id.indexOf(d), d.size(), QString()); @@ -1211,7 +1274,8 @@ bool XdgDesktopFile::isSuitable(bool excludeHidden, const QString &environment) QString expandDynamicUrl(QString url) { - foreach(const QString &line, QProcess::systemEnvironment()) + const QStringList env = QProcess::systemEnvironment(); + for (const QString &line : env) { QString name = line.section(QLatin1Char('='), 0, 0); QString val = line.section(QLatin1Char('='), 1); @@ -1252,8 +1316,8 @@ QString findDesktopFile(const QString& dirName, const QString& desktopName) return fi.canonicalFilePath(); // Working recursively ............ - QFileInfoList dirs = dir.entryInfoList(QStringList(), QDir::Dirs | QDir::NoDotAndDotDot); - foreach (const QFileInfo &d, dirs) + const QFileInfoList dirs = dir.entryInfoList(QStringList(), QDir::Dirs | QDir::NoDotAndDotDot); + for (const QFileInfo &d : dirs) { QString cn = d.canonicalFilePath(); if (dirName != cn) @@ -1273,7 +1337,7 @@ QString findDesktopFile(const QString& desktopName) QStringList dataDirs = XdgDirs::dataDirs(); dataDirs.prepend(XdgDirs::dataHome(false)); - foreach (const QString &dirName, dataDirs) + for (const QString &dirName : const_cast(dataDirs)) { QString f = findDesktopFile(dirName + QLatin1String("/applications"), desktopName); if (!f.isEmpty()) @@ -1395,7 +1459,7 @@ bool readDesktopFile(QIODevice & device, QSettings::SettingsMap & map) if (value.contains(QLatin1Char(';'))) { - map.insert(key, value.split(QLatin1Char(';'))); + map.insert(key, value.split(QLatin1Char(';'), QString::SkipEmptyParts)); } else { @@ -1415,14 +1479,17 @@ bool writeDesktopFile(QIODevice & device, const QSettings::SettingsMap & map) QTextStream stream(&device); QString section; - foreach (const QString &key, map.keys()) + for (auto it = map.constBegin(); it != map.constEnd(); ++it) { - if (! map.value(key).canConvert()) + bool isString = it.value().canConvert(); + bool isStringList = (it.value().type() == QVariant::StringList); + + if ((! isString) && (! isStringList)) { return false; } - QString thisSection = key.section(QLatin1Char('/'), 0, 0); + QString thisSection = it.key().section(QLatin1Char('/'), 0, 0); if (thisSection.isEmpty()) { qWarning() << "No section defined"; @@ -1435,7 +1502,7 @@ bool writeDesktopFile(QIODevice & device, const QSettings::SettingsMap & map) section = thisSection; } - QString remainingKey = key.section(QLatin1Char('/'), 1, -1); + QString remainingKey = it.key().section(QLatin1Char('/'), 1, -1); if (remainingKey.isEmpty()) { @@ -1443,7 +1510,21 @@ bool writeDesktopFile(QIODevice & device, const QSettings::SettingsMap & map) return false; } - stream << remainingKey << QLatin1Char('=') << map.value(key).toString() << QLatin1Char('\n'); + stream << remainingKey << QLatin1Char('='); + + if (isString) + { + stream << it.value().toString() << QLatin1Char(';'); + } + else /* if (isStringList) */ + { + for (const QString &value: it.value().toStringList()) + { + stream << value << QLatin1Char(';'); + } + } + + stream << QLatin1Char('\n'); } @@ -1461,8 +1542,8 @@ void XdgDesktopFileCache::initialize(const QString& dirName) // Working recursively ............ - QFileInfoList files = dir.entryInfoList(QStringList(), QDir::Files | QDir::Dirs | QDir::NoDotAndDotDot); - foreach (const QFileInfo &f, files) + const QFileInfoList files = dir.entryInfoList(QStringList(), QDir::Files | QDir::Dirs | QDir::NoDotAndDotDot); + for (const QFileInfo &f : files) { if (f.isDir()) { @@ -1480,9 +1561,9 @@ void XdgDesktopFileCache::initialize(const QString& dirName) m_fileCache.insert(f.absoluteFilePath(), df); } - QStringList mimes = df->value(mimeTypeKey).toString().split(QLatin1Char(';'), QString::SkipEmptyParts); + const QStringList mimes = df->value(mimeTypeKey).toString().split(QLatin1Char(';'), QString::SkipEmptyParts); - foreach (const QString &mime, mimes) + for (const QString &mime : mimes) { int pref = df->value(initialPreferenceKey, 0).toInt(); // We move the desktopFile forward in the list for this mime, so that @@ -1522,8 +1603,8 @@ void loadMimeCacheDir(const QString& dirName, QHashvalue(mimeTypeKey).toString().split(QLatin1Char(';'), QString::SkipEmptyParts); + const QStringList mimes = df->value(mimeTypeKey).toString().split(QLatin1Char(';'), QString::SkipEmptyParts); - foreach (const QString &mime, mimes) + for (const QString &mime : mimes) { int pref = df->value(initialPreferenceKey, 0).toInt(); // We move the desktopFile forward in the list for this mime, so that @@ -1582,7 +1663,7 @@ void XdgDesktopFileCache::initialize() QStringList dataDirs = XdgDirs::dataDirs(); dataDirs.prepend(XdgDirs::dataHome(false)); - foreach (const QString &dirname, dataDirs) + for (const QString &dirname : const_cast(dataDirs)) { initialize(dirname + QLatin1String("/applications")); // loadMimeCacheDir(dirname + "/applications", m_defaultAppsCache); @@ -1593,7 +1674,8 @@ QList XdgDesktopFileCache::getAppsOfCategory(const QString& cat { QList list; const QString _category = category.toUpper(); - foreach (XdgDesktopFile *desktopFile, instance().m_fileCache.values()) + const QHash fileCache = instance().m_fileCache; + for (XdgDesktopFile *desktopFile : fileCache) { QStringList categories = desktopFile->value(categoriesKey).toString().toUpper().split(QLatin1Char(';')); if (!categories.isEmpty() && (categories.contains(_category) || categories.contains(QLatin1String("X-") + _category))) @@ -1610,14 +1692,23 @@ QList XdgDesktopFileCache::getApps(const QString& mimetype) XdgDesktopFile* XdgDesktopFileCache::getDefaultApp(const QString& mimetype) { - // First, we look in ~/.local/share/applications/mimeapps.list, /usr/local/share/applications/mimeapps.list and - // /usr/share/applications/mimeapps.list (in that order) for a default. - QStringList dataDirs = XdgDirs::dataDirs(); - dataDirs.prepend(XdgDirs::dataHome(false)); - foreach(const QString &dataDir, dataDirs) + // First, we look in following places for a default in specified order: + // ~/.config/mimeapps.list + // /etc/xdg/mimeapps.list + // ~/.local/share/applications/mimeapps.list + // /usr/local/share/applications/mimeapps.list + // /usr/share/applications/mimeapps.list + QStringList mimeDirsList; + + mimeDirsList.append(XdgDirs::configHome(false)); + mimeDirsList.append(XdgDirs::configDirs()); + mimeDirsList.append(XdgDirs::dataHome(false) + QLatin1String("/applications")); + mimeDirsList.append(XdgDirs::dataDirs(QLatin1String("/applications"))); + + for (const QString &mimeDir : const_cast(mimeDirsList)) { - QString defaultsListPath = dataDir + QLatin1String("/applications/mimeapps.list"); - if (QFileInfo(defaultsListPath).exists()) + QString defaultsListPath = mimeDir + QLatin1String("/mimeapps.list"); + if (QFileInfo::exists(defaultsListPath)) { QSettings defaults(defaultsListPath, desktopFileSettingsFormat()); @@ -1628,7 +1719,8 @@ XdgDesktopFile* XdgDesktopFileCache::getDefaultApp(const QString& mimetype) QVariant value = defaults.value(mimetype); if (value.canConvert()) // A single string can also convert to a stringlist { - foreach (const QString &desktopFileName, value.toStringList()) + const QStringList values = value.toStringList(); + for (const QString &desktopFileName : values) { XdgDesktopFile* desktopFile = XdgDesktopFileCache::getFile(desktopFileName); if (desktopFile) diff --git a/qtxdg/xdgdesktopfile.h b/qtxdg/xdgdesktopfile.h index 63c0994..a1d7533 100644 --- a/qtxdg/xdgdesktopfile.h +++ b/qtxdg/xdgdesktopfile.h @@ -130,6 +130,9 @@ public: //! Returns the entry Categories. It supports X-Categories extensions. QStringList categories() const; + //! Returns list of values in entry Actions. + QStringList actions() const; + //! Returns true if there exists a setting called key; returns false otherwise. bool contains(const QString& key) const; @@ -142,9 +145,13 @@ public: //! Returns an icon specified in this file. QIcon const icon(const QIcon& fallback = QIcon()) const; + //! Returns an icon for application action \param action. + QIcon const actionIcon(const QString & action, const QIcon& fallback = QIcon()) const; //! Returns an icon name specified in this file. QString const iconName() const; + //! Returns an icon name for application action \param action. + QString const actionIconName(const QString & action) const; //! Returns an list of mimetypes specified in this file. /*! @return Returns a list of the "MimeType=" entries. @@ -155,6 +162,8 @@ public: //! This function is provided for convenience. It's equivalent to calling localizedValue("Name").toString(). QString name() const { return localizedValue(QLatin1String("Name")).toString(); } + //! Returns an (localized) name for application action \param action. + QString actionName(const QString & action) const; //! This function is provided for convenience. It's equivalent to calling localizedValue("Comment").toString(). QString comment() const { return localizedValue(QLatin1String("Comment")).toString(); } @@ -175,6 +184,19 @@ public: //! This function is provided for convenience. It's equivalent to calling startDetached(QStringList(url)). bool startDetached(const QString& url = QString()) const; + /*! For file with Application type. Activates action defined by the \param action. Action is activated + * either with the [Desktop Action %s]/Exec or by the D-Bus if the [Desktop Entry]/DBusActivatable is set. + * \note Starting is done the same way as \sa startDetached() + * + * \return true on success; otherwise returns false. + * \param urls - A list of files or URLS. Each file is passed as a separate argument to the executable program. + * + * For file with Link type, do nothing. + * + * For file with Directory type, do nothing. + */ + bool actionActivate(const QString & action, const QStringList & urls) const; + /*! A Exec value consists of an executable program optionally followed by one or more arguments. This function expands this arguments and returns command line string parts. Note this method make sense only for Application type. diff --git a/qtxdg/xdgdirs.cpp b/qtxdg/xdgdirs.cpp index 45d5367..bb6400d 100644 --- a/qtxdg/xdgdirs.cpp +++ b/qtxdg/xdgdirs.cpp @@ -108,7 +108,7 @@ QString userDirFallback(XdgDirs::UserDirectory dir) if (home.isEmpty()) return QString::fromLatin1("/tmp"); else if (dir == XdgDirs::Desktop) - fallback = QString::fromLatin1("%1/%2").arg(home).arg(QLatin1String("Desktop")); + fallback = QString::fromLatin1("%1/%2").arg(home, QLatin1String("Desktop")); else fallback = home; @@ -178,10 +178,12 @@ bool XdgDirs::setUserDir(XdgDirs::UserDirectory dir, const QString& value, bool if (dir < XdgDirs::Desktop || dir > XdgDirs::Videos) return false; + const QString home = QFile::decodeName(qgetenv("HOME")); if (!(value.startsWith(QLatin1String("$HOME")) || value.startsWith(QLatin1String("~/")) - || value.startsWith(QFile::decodeName(qgetenv("HOME"))))) - return false; + || value.startsWith(home) + || value.startsWith(QDir(home).canonicalPath()))) + return false; QString folderName = userDirectoryString[dir]; @@ -215,7 +217,7 @@ bool XdgDirs::setUserDir(XdgDirs::UserDirectory dir, const QString& value, bool stream.reset(); configFile.resize(0); if (!foundVar) - stream << QString::fromLatin1("XDG_%1_DIR=\"%2\"\n").arg(folderName.toUpper()).arg(value); + stream << QString::fromLatin1("XDG_%1_DIR=\"%2\"\n").arg(folderName.toUpper(),(value)); for (QVector::iterator i = lines.begin(); i != lines.end(); ++i) stream << *i << QLatin1Char('\n'); @@ -334,7 +336,7 @@ QStringList XdgDirs::autostartDirs(const QString &postfix) { QStringList dirs; const QStringList s = configDirs(); - foreach(const QString &dir, s) + for (const QString &dir : s) dirs << QString::fromLatin1("%1/autostart").arg(dir) + postfix; return dirs; diff --git a/qtxdg/xdgicon.cpp b/qtxdg/xdgicon.cpp index bb1e153..754e0d1 100644 --- a/qtxdg/xdgicon.cpp +++ b/qtxdg/xdgicon.cpp @@ -50,7 +50,7 @@ struct QtIconCache: public IconCache } }; } -Q_GLOBAL_STATIC(IconCache, qtIconCache); +Q_GLOBAL_STATIC(IconCache, qtIconCache) static void qt_cleanup_icon_cache() { @@ -68,25 +68,6 @@ XdgIcon::~XdgIcon() } -/************************************************ - Returns the name of the current icon theme. - ************************************************/ -QString XdgIcon::themeName() -{ - return QIcon::themeName(); -} - - -/************************************************ - Sets the current icon theme to name. - ************************************************/ -void XdgIcon::setThemeName(const QString& themeName) -{ - QIcon::setThemeName(themeName); - XdgIconLoader::instance()->updateSystemTheme(); -} - - /************************************************ Returns the QIcon corresponding to name in the current icon theme. If no such icon is found in the current theme fallback is return instead. @@ -137,7 +118,7 @@ QIcon XdgIcon::fromTheme(const QString& iconName, const QIcon& fallback) ************************************************/ QIcon XdgIcon::fromTheme(const QStringList& iconNames, const QIcon& fallback) { - foreach (const QString &iconName, iconNames) + for (const QString &iconName : iconNames) { QIcon icon = fromTheme(iconName); if (!icon.isNull()) @@ -164,6 +145,17 @@ QIcon XdgIcon::fromTheme(const QString &iconName, return fromTheme(icons); } +bool XdgIcon::followColorScheme() +{ + return XdgIconLoader::instance()->followColorScheme(); +} + + +void XdgIcon::setFollowColorScheme(bool enable) +{ + XdgIconLoader::instance()->setFollowColorScheme(enable); +} + QIcon XdgIcon::defaultApplicationIcon() { diff --git a/qtxdg/xdgicon.h b/qtxdg/xdgicon.h index 594415e..e3157cc 100644 --- a/qtxdg/xdgicon.h +++ b/qtxdg/xdgicon.h @@ -44,8 +44,19 @@ public: const QString &fallbackIcon4 = QString()); static QIcon fromTheme(const QStringList& iconNames, const QIcon& fallback = QIcon()); - static QString themeName(); - static void setThemeName(const QString& themeName); + /*! + * Flag if the "FollowsColorScheme" hint (the KDE extension to XDG + * themes) should be honored. If enabled and the icon theme supports + * this, the icon engine "colorizes" icons based on the application's + * palette. + * + * Default is true (use this extension). + */ + static bool followColorScheme(); + static void setFollowColorScheme(bool enable); + /* TODO: deprecate & remove all QIcon wrappers */ + static QString themeName() { return QIcon::themeName(); } + static void setThemeName(const QString& themeName) { QIcon::setThemeName(themeName); } static QIcon defaultApplicationIcon(); static QString defaultApplicationIconName(); diff --git a/qtxdg/xdgmenu.cpp b/qtxdg/xdgmenu.cpp index b59a001..d5f7efe 100644 --- a/qtxdg/xdgmenu.cpp +++ b/qtxdg/xdgmenu.cpp @@ -205,8 +205,7 @@ void XdgMenu::save(const QString& fileName) if (!file.open(QFile::WriteOnly | QFile::Text)) { qWarning() << QString::fromLatin1("Cannot write file %1:\n%2.") - .arg(fileName) - .arg(file.errorString()); + .arg(fileName, file.errorString()); return; } @@ -225,7 +224,7 @@ void XdgMenuPrivate::load(const QString& fileName) QFile file(fileName); if (!file.open(QFile::ReadOnly | QFile::Text)) { - qWarning() << QString::fromLatin1("%1 not loading: %2").arg(fileName).arg(file.errorString()); + qWarning() << QString::fromLatin1("%1 not loading: %2").arg(fileName, file.errorString()); return; } mXml.setContent(&file, true); @@ -413,7 +412,7 @@ QDomElement XdgMenu::findMenu(QDomElement& baseElement, const QString& path, boo const QStringList names = path.split(QLatin1Char('/'), QString::SkipEmptyParts); QDomElement el = baseElement; - foreach (const QString &name, names) + for (const QString &name : names) { QDomElement p = el; el = d->mXml.createElement(QLatin1String("Menu")); @@ -538,12 +537,12 @@ void XdgMenuPrivate::processDirectoryEntries(QDomElement& element, const QString dirs << parentDirs; bool found = false; - foreach(const QString &file, files){ + for (const QString &file : const_cast(files)){ if (file.startsWith(QLatin1Char('/'))) found = loadDirectoryFile(file, element); else { - foreach (const QString &dir, dirs) + for (const QString &dir : const_cast(dirs)) { found = loadDirectoryFile(dir + QLatin1Char('/') + file, element); if (found) break; @@ -652,7 +651,7 @@ QString XdgMenu::getMenuFileName(const QString& baseName) const QStringList configDirs = XdgDirs::configDirs(); QString menuPrefix = QString::fromLocal8Bit(qgetenv("XDG_MENU_PREFIX")); - foreach(const QString &configDir, configDirs) + for (const QString &configDir : configDirs) { QFileInfo file(QString::fromLatin1("%1/menus/%2%3").arg(configDir, menuPrefix, baseName)); if (file.exists()) @@ -670,9 +669,9 @@ QString XdgMenu::getMenuFileName(const QString& baseName) wellKnownFiles << QLatin1String("gnome-applications.menu"); wellKnownFiles << QLatin1String("lxde-applications.menu"); - foreach(const QString &configDir, configDirs) + for (const QString &configDir : configDirs) { - foreach (const QString &f, wellKnownFiles) + for (const QString &f : const_cast(wellKnownFiles)) { QFileInfo file(QString::fromLatin1("%1/menus/%2").arg(configDir, f)); if (file.exists()) @@ -715,7 +714,7 @@ void XdgMenuPrivate::rebuild() if (prevHash != mHash) { mOutDated = true; - emit changed(); + Q_EMIT changed(); } } diff --git a/qtxdg/xdgmenu.h b/qtxdg/xdgmenu.h index e18e0c3..0a2a3f6 100644 --- a/qtxdg/xdgmenu.h +++ b/qtxdg/xdgmenu.h @@ -112,7 +112,7 @@ public: bool isOutDated() const; -signals: +Q_SIGNALS: void changed(); protected: diff --git a/qtxdg/xdgmenu_p.h b/qtxdg/xdgmenu_p.h index 34443aa..c5d446c 100644 --- a/qtxdg/xdgmenu_p.h +++ b/qtxdg/xdgmenu_p.h @@ -73,10 +73,10 @@ public: QFileSystemWatcher mWatcher; bool mOutDated; -public slots: +public Q_SLOTS: void rebuild(); -signals: +Q_SIGNALS: void changed(); diff --git a/qtxdg/xdgmenuapplinkprocessor.cpp b/qtxdg/xdgmenuapplinkprocessor.cpp index f031940..7643861 100644 --- a/qtxdg/xdgmenuapplinkprocessor.cpp +++ b/qtxdg/xdgmenuapplinkprocessor.cpp @@ -90,7 +90,8 @@ void XdgMenuApplinkProcessor::step1() } // Process childs menus ............................... - foreach (XdgMenuApplinkProcessor* child, mChilds) + + for (XdgMenuApplinkProcessor* child : const_cast&>(mChilds)) child->step1(); } @@ -100,7 +101,7 @@ void XdgMenuApplinkProcessor::step2() // Create AppLinks elements ........................... QDomDocument doc = mElement.ownerDocument(); - foreach (XdgMenuAppFileInfo* fileInfo, mSelected) + for (XdgMenuAppFileInfo* fileInfo : const_cast&>(mSelected)) { if (mOnlyUnallocated && fileInfo->allocated()) continue; @@ -141,7 +142,7 @@ void XdgMenuApplinkProcessor::step2() // Process childs menus ............................... - foreach (XdgMenuApplinkProcessor* child, mChilds) + for (XdgMenuApplinkProcessor* child : const_cast&>(mChilds)) child->step2(); } @@ -189,7 +190,7 @@ void XdgMenuApplinkProcessor::findDesktopFiles(const QString& dirName, const QSt mMenu->addWatchPath(dir.absolutePath()); const QFileInfoList files = dir.entryInfoList(QStringList(QLatin1String("*.desktop")), QDir::Files); - foreach (const QFileInfo &file, files) + for (const QFileInfo &file : files) { XdgDesktopFile* f = XdgDesktopFileCache::getFile(file.canonicalFilePath()); if (f) @@ -199,7 +200,7 @@ void XdgMenuApplinkProcessor::findDesktopFiles(const QString& dirName, const QSt // Working recursively ............ const QFileInfoList dirs = dir.entryInfoList(QStringList(), QDir::Dirs | QDir::NoDotAndDotDot); - foreach (const QFileInfo &d, dirs) + for (const QFileInfo &d : dirs) { QString dn = d.canonicalFilePath(); if (dn != dirName) @@ -242,7 +243,7 @@ bool XdgMenuApplinkProcessor::checkTryExec(const QString& progName) const QStringList dirs = QFile::decodeName(qgetenv("PATH")).split(QLatin1Char(':')); - foreach (const QString &dir, dirs) + for (const QString &dir : dirs) { if (QFileInfo(QDir(dir), progName).isExecutable()) return true; diff --git a/qtxdg/xdgmenureader.cpp b/qtxdg/xdgmenureader.cpp index f4e4393..66f0dec 100644 --- a/qtxdg/xdgmenureader.cpp +++ b/qtxdg/xdgmenureader.cpp @@ -76,7 +76,7 @@ bool XdgMenuReader::load(const QString& fileName, const QString& baseDir) QFile file(mFileName); if (!file.open(QFile::ReadOnly | QFile::Text)) { - mErrorStr = QString::fromLatin1("%1 not loading: %2").arg(fileName).arg(file.errorString()); + mErrorStr = QString::fromLatin1("%1 not loading: %2").arg(fileName, file.errorString()); return false; } //qDebug() << "Load file:" << mFileName; @@ -215,7 +215,7 @@ void XdgMenuReader::processMergeFileTag(QDomElement& element, QStringList* merge QString relativeName; QStringList configDirs = XdgDirs::configDirs(); - foreach (const QString &configDir, configDirs) + for (const QString &configDir : const_cast(configDirs)) { if (mFileName.startsWith(configDir)) { @@ -236,9 +236,9 @@ void XdgMenuReader::processMergeFileTag(QDomElement& element, QStringList* merge if (relativeName.isEmpty()) return; - foreach (const QString &configDir, configDirs) + for (const QString &configDir : configDirs) { - if (QFileInfo(configDir + relativeName).exists()) + if (QFileInfo::exists(configDir + relativeName)) { mergeFile(configDir + relativeName, element, mergedFiles); return; @@ -295,9 +295,9 @@ void XdgMenuReader::processDefaultMergeDirsTag(QDomElement& element, QStringList QStringList dirs = XdgDirs::configDirs(); dirs << XdgDirs::configHome(); - foreach (const QString &dir, dirs) + for (const QString &dir : const_cast(dirs)) { - mergeDir(QString::fromLatin1("%1/menus/%2-merged").arg(dir).arg(menuBaseName), element, mergedFiles); + mergeDir(QString::fromLatin1("%1/menus/%2-merged").arg(dir, menuBaseName), element, mergedFiles); } if (menuBaseName == QLatin1String("applications")) @@ -329,7 +329,7 @@ void XdgMenuReader::processDefaultAppDirsTag(QDomElement& element) QStringList dirs = XdgDirs::dataDirs(); dirs.prepend(XdgDirs::dataHome(false)); - foreach (const QString &dir, dirs) + for (const QString &dir : const_cast (dirs)) { //qDebug() << "Add AppDir: " << dir + "/applications/"; addDirTag(element, QLatin1String("AppDir"), dir + QLatin1String("/applications/")); @@ -360,7 +360,7 @@ void XdgMenuReader::processDefaultDirectoryDirsTag(QDomElement& element) QStringList dirs = XdgDirs::dataDirs(); dirs.prepend(XdgDirs::dataHome(false)); - foreach (const QString &dir, dirs) + for (const QString &dir : const_cast(dirs)) addDirTag(element, QLatin1String("DirectoryDir"), dir + QLatin1String("/desktop-directories/")); } @@ -379,8 +379,7 @@ void XdgMenuReader::addDirTag(QDomElement& previousElement, const QString& tagNa } } - -/************************************************ +/* If fileName is not an absolute path then the file to be merged should be located relative to the location of this menu file. ************************************************/ @@ -431,7 +430,7 @@ void XdgMenuReader::mergeDir(const QString& dirName, QDomElement& element, QStri QDir dir = QDir(dirInfo.canonicalFilePath()); const QFileInfoList files = dir.entryInfoList(QStringList() << QLatin1String("*.menu"), QDir::Files | QDir::Readable); - foreach (const QFileInfo &file, files) + for (const QFileInfo &file : files) mergeFile(file.canonicalFilePath(), element, mergedFiles); } } diff --git a/qtxdg/xdgmenureader.h b/qtxdg/xdgmenureader.h index 27e1f5e..383021d 100644 --- a/qtxdg/xdgmenureader.h +++ b/qtxdg/xdgmenureader.h @@ -47,9 +47,9 @@ public: QString errorString() const { return mErrorStr; } QDomDocument& xml() { return mXml; } -signals: +Q_SIGNALS: -public slots: +public Q_SLOTS: protected: void processMergeTags(QDomElement& element); diff --git a/qtxdg/xdgmenuwidget.cpp b/qtxdg/xdgmenuwidget.cpp index acabb8f..56c8fde 100644 --- a/qtxdg/xdgmenuwidget.cpp +++ b/qtxdg/xdgmenuwidget.cpp @@ -44,7 +44,7 @@ class XdgMenuWidgetPrivate { private: XdgMenuWidget* const q_ptr; - Q_DECLARE_PUBLIC(XdgMenuWidget); + Q_DECLARE_PUBLIC(XdgMenuWidget) public: explicit XdgMenuWidgetPrivate(XdgMenuWidget* parent): @@ -186,7 +186,7 @@ void XdgMenuWidgetPrivate::buildMenu() QAction* first = 0; if (!q->actions().isEmpty()) - first = q->actions().last(); + first = q->actions().constLast(); DomElementIterator it(mXml, QString()); diff --git a/qtxdg/xdgmimetype.cpp b/qtxdg/xdgmimetype.cpp index ed9ab91..9c10597 100644 --- a/qtxdg/xdgmimetype.cpp +++ b/qtxdg/xdgmimetype.cpp @@ -96,7 +96,7 @@ QString XdgMimeType::iconName() const names.append(QMimeType::iconName()); names.append(QMimeType::genericIconName()); - foreach (const QString &s, names) { + for (const QString &s : const_cast(names)) { if (!XdgIcon::fromTheme(s).isNull()) { dx->iconName = s; break; diff --git a/qtxdg/xmlhelper.cpp b/qtxdg/xmlhelper.cpp index 6239ad4..a48baab 100644 --- a/qtxdg/xmlhelper.cpp +++ b/qtxdg/xmlhelper.cpp @@ -41,6 +41,6 @@ QDebug operator<<(QDebug dbg, const QDomElement &el) for (int i=0; i%3").arg(el.tagName()).arg(args).arg(el.text()); + dbg.nospace() << QString::fromLatin1("<%1%2>%3").arg(el.tagName(), args, el.text()); return dbg.space(); } diff --git a/release.sh b/release.sh deleted file mode 100755 index 29ca2c8..0000000 --- a/release.sh +++ /dev/null @@ -1,28 +0,0 @@ -#!/bin/bash - -PROJECT="libqtxdg" -version="$1" -prefix=$PROJECT-$version -shift - -if [[ -z $version ]]; then - >&2 echo "USAGE: $0 " - exit 1 -fi - -mkdir -p "dist/$version" -echo "Creating $prefix.tar.gz" -git archive -9 --format tar.gz $version --prefix="$prefix/" > "dist/$version/$prefix.tar.gz" -gpg --armor --detach-sign "dist/$version/$prefix.tar.gz" -echo "Creating $prefix.tar.xz" -git archive -9 --format tar.xz $version --prefix="$prefix/" > "dist/$version/$prefix.tar.xz" -gpg --armor --detach-sign "dist/$version/$prefix.tar.xz" -cd "dist/$version" - -sha1sum --tag *.tar.gz *.tar.xz >> CHECKSUMS -sha256sum --tag *.tar.gz *.tar.xz >> CHECKSUMS - -cd .. -echo "Uploading to lxqt.org..." - -scp -r "$version" "downloads.lxqt.org:/srv/downloads.lxqt.org/$PROJECT/" diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 8d7b580..780c939 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -1,25 +1,25 @@ -set(PROJECT_NAME "qtxdg_test") - remove_definitions( -DQT_USE_QSTRINGBUILDER -DQT_NO_CAST_FROM_ASCII ) -set(CMAKE_AUTOMOC TRUE) +add_definitions( + -DQT_NO_KEYWORDS +) + +set(CMAKE_INCLUDE_CURRENT_DIR ON) macro(qtxdg_add_test) foreach(_testname ${ARGN}) add_executable(${_testname} ${_testname}.cpp) target_link_libraries(${_testname} Qt5::Test ${QTXDGX_LIBRARY_NAME}) + target_include_directories(${_testname} + PRIVATE "${PROJECT_SOURCE_DIR}/qtxdg" + ) add_test(NAME ${_testname} COMMAND ${_testname}) endforeach() endmacro() -include_directories ( - "${CMAKE_SOURCE_DIR}/qtxdg" - ${CMAKE_CURRENT_BINARY_DIR} -) - set_property(DIRECTORY APPEND PROPERTY COMPILE_DEFINITIONS "QTXDG_BUILDING_TESTS=\"1\"" ) diff --git a/test/qtxdg_test.cpp b/test/qtxdg_test.cpp index 9270605..52dd318 100644 --- a/test/qtxdg_test.cpp +++ b/test/qtxdg_test.cpp @@ -47,15 +47,17 @@ void QtXdgTest::testDefaultApp() { QStringList mimedirs = XdgDirs::dataDirs(); mimedirs.prepend(XdgDirs::dataHome(false)); - foreach (QString mimedir, mimedirs) + for (const QString &mimedir : const_cast(mimedirs)) { QDir dir(mimedir + "/mime"); qDebug() << dir.path(); QStringList filters = (QStringList() << "*.xml"); - foreach(QFileInfo mediaDir, dir.entryInfoList(QDir::Dirs | QDir::NoDotAndDotDot)) + const QFileInfoList &mediaDirs = dir.entryInfoList(QDir::Dirs | QDir::NoDotAndDotDot); + for (const QFileInfo &mediaDir : mediaDirs) { qDebug() << " " << mediaDir.fileName(); - foreach (QString mimeXmlFileName, QDir(mediaDir.absoluteFilePath()).entryList(filters, QDir::Files)) + const QStringList mimeXmlFileNames = QDir(mediaDir.absoluteFilePath()).entryList(filters, QDir::Files); + for (const QString &mimeXmlFileName : mimeXmlFileNames) { QString mimetype = mediaDir.fileName() + "/" + mimeXmlFileName.left(mimeXmlFileName.length() - 4); QString xdg_utils_default = xdgUtilDefaultApp(mimetype); diff --git a/test/qtxdg_test.h b/test/qtxdg_test.h index 3d58c9f..014a406 100644 --- a/test/qtxdg_test.h +++ b/test/qtxdg_test.h @@ -39,7 +39,7 @@ class QtXdgTest : public QObject { Q_OBJECT -private slots: +private Q_SLOTS: void testCustomFormat(); private: diff --git a/test/tst_xdgdesktopfile.cpp b/test/tst_xdgdesktopfile.cpp index 63dd6a3..30db04e 100644 --- a/test/tst_xdgdesktopfile.cpp +++ b/test/tst_xdgdesktopfile.cpp @@ -27,20 +27,20 @@ class Language { public: Language (const QString& lang) - : mPreviousLang(QString::fromLocal8Bit(qgetenv("LANG"))) + : mPreviousLang(QString::fromLocal8Bit(qgetenv("LC_MESSAGES"))) { - qputenv("LANG", lang.toLocal8Bit()); + qputenv("LC_MESSAGES", lang.toLocal8Bit()); } ~Language() { - qputenv("LANG", mPreviousLang.toLocal8Bit()); + qputenv("LC_MESSAGES", mPreviousLang.toLocal8Bit()); } private: QString mPreviousLang; }; -QTEST_MAIN(tst_xdgdesktopfile); +QTEST_MAIN(tst_xdgdesktopfile) void tst_xdgdesktopfile::testRead() { diff --git a/test/tst_xdgdesktopfile.h b/test/tst_xdgdesktopfile.h index 337530c..6a001d2 100644 --- a/test/tst_xdgdesktopfile.h +++ b/test/tst_xdgdesktopfile.h @@ -25,7 +25,7 @@ class tst_xdgdesktopfile : public QObject { Q_OBJECT -private slots: +private Q_SLOTS: void testRead(); void testReadLocalized(); diff --git a/test/tst_xdgdirs.cpp b/test/tst_xdgdirs.cpp index 634fee5..e7ba103 100644 --- a/test/tst_xdgdirs.cpp +++ b/test/tst_xdgdirs.cpp @@ -36,7 +36,7 @@ class tst_xdgdirs : public QObject { Q_OBJECT -private slots: +private Q_SLOTS: void initTestCase(); void cleanupTestCase(); diff --git a/util/CMakeLists.txt b/util/CMakeLists.txt index 7d871ae..05c6231 100644 --- a/util/CMakeLists.txt +++ b/util/CMakeLists.txt @@ -1,26 +1,52 @@ -include_directories ( - "${CMAKE_PROJECT_DIR}/qtxdg" - ${CMAKE_CURRENT_BINARY_DIR} -) +set(CMAKE_INCLUDE_CURRENT_DIR ON) set(QTXDG_DESKTOP_FILE_START_SRCS qtxdg-desktop-file-start.cpp ) +set(QTXDG_ICONFINDER_SRCS + qtxdg-iconfinder.cpp +) + add_executable(qtxdg-desktop-file-start ${QTXDG_DESKTOP_FILE_START_SRCS} ) +add_executable(qtxdg-iconfinder + ${QTXDG_ICONFINDER_SRCS} +) + +target_include_directories(qtxdg-desktop-file-start + PRIVATE "${PROJECT_SOURCE_DIR}/qtxdg" +) + +target_include_directories(qtxdg-iconfinder + PRIVATE "${Qt5Gui_PRIVATE_INCLUDE_DIRS}" +) + target_compile_definitions(qtxdg-desktop-file-start - PRIVATE "-DQTXDG_VERSION=\"${QTXDG_VERSION_STRING}\"" + PRIVATE + "-DQTXDG_VERSION=\"${QTXDG_VERSION_STRING}\"" + "QT_NO_KEYWORDS" +) + +target_compile_definitions(qtxdg-iconfinder + PRIVATE + "-DQTXDG_VERSION=\"${QTXDG_VERSION_STRING}\"" + "QT_NO_KEYWORDS" ) target_link_libraries(qtxdg-desktop-file-start ${QTXDGX_LIBRARY_NAME} ) +target_link_libraries(qtxdg-iconfinder + ${QTXDGX_ICONLOADER_LIBRARY_NAME} +) + install(TARGETS qtxdg-desktop-file-start + qtxdg-iconfinder RUNTIME DESTINATION "${CMAKE_INSTALL_BINDIR}" COMPONENT Runtime ) diff --git a/util/qtxdg-iconfinder.cpp b/util/qtxdg-iconfinder.cpp new file mode 100644 index 0000000..3b60d47 --- /dev/null +++ b/util/qtxdg-iconfinder.cpp @@ -0,0 +1,74 @@ +/* + * libqtxdg - An Qt implementation of freedesktop.org xdg specs + * Copyright (C) 2017 Luís Pereira + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301 USA + */ + +#include // XdgIconLoader needs a QGuiApplication +#include +#include +#include + + +#include +#include + +int main(int argc, char *argv[]) +{ + QGuiApplication app(argc, argv); + app.setApplicationName(QStringLiteral("qtxdg-iconfinder")); + app.setApplicationVersion(QStringLiteral(QTXDG_VERSION)); + + QCommandLineParser parser; + parser.setApplicationDescription(QStringLiteral("QtXdg icon finder")); + parser.addPositionalArgument(QStringLiteral("iconnames"), + QStringLiteral("The icon names to search for"), + QStringLiteral("[iconnames...]")); + parser.addVersionOption(); + parser.addHelpOption(); + parser.process(app); + + if (parser.positionalArguments().isEmpty()) + parser.showHelp(EXIT_FAILURE); + + qint64 totalElapsed = 0; + const auto icons = parser.positionalArguments(); + for (const QString& iconName : icons) { + QElapsedTimer t; + t.start(); + const auto info = XdgIconLoader::instance()->loadIcon(iconName); + qint64 elapsed = t.elapsed(); + const auto icon = info.iconName; + const auto entries = info.entries; + + std::cout << qPrintable(iconName) << + qPrintable(QString::fromLatin1(":")) << qPrintable(icon) << + qPrintable(QString::fromLatin1(":")) << + qPrintable(QString::number(elapsed)) << "\n"; + + for (const auto &i : entries) { + std::cout << "\t" << qPrintable(i->filename) << "\n"; + } + totalElapsed += elapsed; + } + + std::cout << qPrintable(QString::fromLatin1("Total loadIcon() time: ")) << + qPrintable(QString::number(totalElapsed)) << + qPrintable(QString::fromLatin1(" ms")) << "\n"; + + return EXIT_SUCCESS; +} diff --git a/xdgiconloader/CMakeLists.txt b/xdgiconloader/CMakeLists.txt index f7405b3..0e7466f 100644 --- a/xdgiconloader/CMakeLists.txt +++ b/xdgiconloader/CMakeLists.txt @@ -1,7 +1,3 @@ -include_directories( - "${Qt5Gui_PRIVATE_INCLUDE_DIRS}" -) - set(xdgiconloader_PUBLIC_H_FILES ) @@ -38,17 +34,22 @@ configure_file( COPYONLY ) -target_include_directories(${QTXDGX_ICONLOADER_LIBRARY_NAME} - INTERFACE "$" - INTERFACE "$" - INTERFACE "$" +target_compile_definitions(${QTXDGX_ICONLOADER_LIBRARY_NAME} + PRIVATE + "QT_NO_KEYWORDS" ) -# include directories and targets for the in tree build target_include_directories(${QTXDGX_ICONLOADER_LIBRARY_NAME} - PUBLIC "$" - PUBLIC "$" - PUBLIC "$" + INTERFACE + "$" + "$" + "$" + PUBLIC + "$" + "$" + "$" + PRIVATE + ${Qt5Gui_PRIVATE_INCLUDE_DIRS} ) target_link_libraries(${QTXDGX_ICONLOADER_LIBRARY_NAME} @@ -63,7 +64,7 @@ set_target_properties(${QTXDGX_ICONLOADER_LIBRARY_NAME} SOVERSION ${QTXDG_MAJOR_VERSION} ) -export(TARGETS ${QTXDGX_ICONLOADER_LIBRARY_NAME} FILE "${CMAKE_BINARY_DIR}/${QTXDGX_ICONLOADER_FILE_NAME}-targets.cmake") +add_subdirectory(plugin) install(TARGETS ${QTXDGX_ICONLOADER_LIBRARY_NAME} DESTINATION "${CMAKE_INSTALL_LIBDIR}" @@ -77,6 +78,11 @@ install(FILES COMPONENT Devel ) +file(COPY + ${xdgiconloader_PRIVATE_INSTALLABLE_H_FILES} + DESTINATION "${QTXDGX_INTREE_INCLUDEDIR}/${QTXDGX_ICONLOADER_FILE_NAME}/${QTXDG_VERSION_STRING}/private/xdgiconloader" +) + install(FILES "${QTXDGX_INTREE_INCLUDEDIR}/${QTXDGX_ICONLOADER_FILE_NAME}/${XDGICONLOADER_EXPORT_FILE}" DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}/${QTXDGX_ICONLOADER_FILE_NAME}" diff --git a/xdgiconloader/plugin/CMakeLists.txt b/xdgiconloader/plugin/CMakeLists.txt new file mode 100644 index 0000000..e5eb59c --- /dev/null +++ b/xdgiconloader/plugin/CMakeLists.txt @@ -0,0 +1,49 @@ +set(xdgiconengineplugin_CPP_FILES + xdgiconengineplugin.cpp +) + +add_library(${QTXDGX_ICONENGINEPLUGIN_LIBRARY_NAME} MODULE + ${xdgiconengineplugin_CPP_FILES} +) + +target_compile_definitions(${QTXDGX_ICONENGINEPLUGIN_LIBRARY_NAME} + PRIVATE + "QT_NO_KEYWORDS" +) +target_link_libraries(${QTXDGX_ICONENGINEPLUGIN_LIBRARY_NAME} + PUBLIC + Qt5::Gui + "${QTXDGX_ICONLOADER_LIBRARY_NAME}" +) + +target_include_directories("${QTXDGX_ICONENGINEPLUGIN_LIBRARY_NAME}" + PRIVATE + "${Qt5Gui_PRIVATE_INCLUDE_DIRS}" +) + +mark_as_advanced(QTXDGX_ICONENGINEPLUGIN_INSTALL_PATH) + +if (NOT DEFINED QTXDGX_ICONENGINEPLUGIN_INSTALL_PATH) + get_target_property(QT_QMAKE_EXECUTABLE ${Qt5Core_QMAKE_EXECUTABLE} IMPORTED_LOCATION) + if(NOT QT_QMAKE_EXECUTABLE) + message(FATAL_ERROR "qmake is not found.") + endif() + + # execute the command "qmake -query QT_INSTALL_PLUGINS" to get the path of plugins dir. + execute_process(COMMAND "${QT_QMAKE_EXECUTABLE}" -query QT_INSTALL_PLUGINS + OUTPUT_VARIABLE QT_PLUGINS_DIR + OUTPUT_STRIP_TRAILING_WHITESPACE + ) + if (NOT QT_PLUGINS_DIR) + message(FATAL_ERROR "Qt5 plugin directory cannot be detected.") + endif() + set(QTXDGX_ICONENGINEPLUGIN_INSTALL_PATH "${QT_PLUGINS_DIR}/iconengines") +endif() + +message(STATUS "XdgIconEnginePlugin will be installed into: ${QTXDGX_ICONENGINEPLUGIN_INSTALL_PATH}") + +install(TARGETS + "${QTXDGX_ICONENGINEPLUGIN_LIBRARY_NAME}" DESTINATION "${QTXDGX_ICONENGINEPLUGIN_INSTALL_PATH}" + COMPONENT Runtime +) + diff --git a/xdgiconloader/plugin/xdgiconengineplugin.cpp b/xdgiconloader/plugin/xdgiconengineplugin.cpp new file mode 100644 index 0000000..93e5001 --- /dev/null +++ b/xdgiconloader/plugin/xdgiconengineplugin.cpp @@ -0,0 +1,35 @@ +/* BEGIN_COMMON_COPYRIGHT_HEADER + * (c)LGPL2+ + * + * LXQt - a lightweight Qt based desktop + * http://lxqt.org + * + * Copyright: 2017 LXQt team + * Authors: + * Palo Kisa + * + * This program or library is free software; you can redistribute it + * and/or modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 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 + * Lesser General Public License for more details. + + * You should have received a copy of the GNU Lesser General + * Public License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301 USA + * + * END_COMMON_COPYRIGHT_HEADER */ + +#include "xdgiconengineplugin.h" +#include "../xdgiconloader_p.h" + +QIconEngine * XdgIconEnginePlugin::create(const QString & filename/* = QString{}*/) +{ + return new XdgIconLoaderEngine{filename}; +} + diff --git a/xdgiconloader/plugin/xdgiconengineplugin.h b/xdgiconloader/plugin/xdgiconengineplugin.h new file mode 100644 index 0000000..3f9276f --- /dev/null +++ b/xdgiconloader/plugin/xdgiconengineplugin.h @@ -0,0 +1,38 @@ +/* BEGIN_COMMON_COPYRIGHT_HEADER + * (c)LGPL2+ + * + * LXQt - a lightweight Qt based desktop + * http://lxqt.org + * + * Copyright: 2017 LXQt team + * Authors: + * Palo Kisa + * + * This program or library is free software; you can redistribute it + * and/or modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 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 + * Lesser General Public License for more details. + + * You should have received a copy of the GNU Lesser General + * Public License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301 USA + * + * END_COMMON_COPYRIGHT_HEADER */ + +#include + +class XdgIconEnginePlugin : public QIconEnginePlugin +{ + Q_OBJECT + Q_PLUGIN_METADATA(IID "org.qt-project.Qt.QIconEngineFactoryInterface" FILE "xdgiconengineplugin.json") +public: + using QIconEnginePlugin::QIconEnginePlugin; + virtual QIconEngine * create(const QString & filename = QString{}) override; +}; + diff --git a/xdgiconloader/plugin/xdgiconengineplugin.json b/xdgiconloader/plugin/xdgiconengineplugin.json new file mode 100644 index 0000000..6943d22 --- /dev/null +++ b/xdgiconloader/plugin/xdgiconengineplugin.json @@ -0,0 +1 @@ +{"Keys": ["XdgIconLoaderEngine"]} diff --git a/xdgiconloader/xdgiconloader.cpp b/xdgiconloader/xdgiconloader.cpp index cb084fa..1f7dadb 100644 --- a/xdgiconloader/xdgiconloader.cpp +++ b/xdgiconloader/xdgiconloader.cpp @@ -41,10 +41,13 @@ #include #include #include +#include #include #include #include #include +#include +#include #ifdef Q_DEAD_CODE_FROM_QT4_MAC #include @@ -62,104 +65,36 @@ static QString fallbackTheme() { if (const QPlatformTheme *theme = QGuiApplicationPrivate::platformTheme()) { const QVariant themeHint = theme->themeHint(QPlatformTheme::SystemIconFallbackThemeName); - if (themeHint.isValid()) - return themeHint.toString(); - } - return QLatin1String("hicolor"); -} - -XdgIconLoader::XdgIconLoader() : - m_themeKey(1), m_supportsSvg(false), m_initialized(false) -{ -} - -static inline QString systemThemeName() -{ - if (const QPlatformTheme *theme = QGuiApplicationPrivate::platformTheme()) { - const QVariant themeHint = theme->themeHint(QPlatformTheme::SystemIconThemeName); - if (themeHint.isValid()) - return themeHint.toString(); - } - return QIcon::themeName(); -} - -static inline QStringList systemIconSearchPaths() -{ - if (const QPlatformTheme *theme = QGuiApplicationPrivate::platformTheme()) { - const QVariant themeHint = theme->themeHint(QPlatformTheme::IconThemeSearchPaths); - if (themeHint.isValid()) - return themeHint.toStringList(); + if (themeHint.isValid()) { + const QString theme = themeHint.toString(); + if (theme != QLatin1String("hicolor")) + return theme; + } } - return QIcon::themeSearchPaths(); + return QString(); } -#ifndef QT_NO_LIBRARY -//extern QFactoryLoader *qt_iconEngineFactoryLoader(); // qicon.cpp -#endif +#ifdef QT_NO_LIBRARY +static bool gSupportsSvg = false; +#else +static bool gSupportsSvg = true; +#endif //QT_NO_LIBRARY -void XdgIconLoader::ensureInitialized() +void XdgIconLoader::setFollowColorScheme(bool enable) { - if (!m_initialized) { - m_initialized = true; - - Q_ASSERT(qApp); - - m_systemTheme = systemThemeName(); - - if (m_systemTheme.isEmpty()) - m_systemTheme = fallbackTheme(); -#ifndef QT_NO_LIBRARY -// if (qt_iconEngineFactoryLoader()->keyMap().key(QLatin1String("svg"), -1) != -1) - m_supportsSvg = true; -#endif //QT_NO_LIBRARY + if (m_followColorScheme != enable) + { + QIconLoader::instance()->invalidateKey(); + m_followColorScheme = enable; } } XdgIconLoader *XdgIconLoader::instance() { - iconLoaderInstance()->ensureInitialized(); + QIconLoader::instance()->ensureInitialized(); return iconLoaderInstance(); } -// Queries the system theme and invalidates existing -// icons if the theme has changed. -void XdgIconLoader::updateSystemTheme() -{ - // Only change if this is not explicitly set by the user - if (m_userTheme.isEmpty()) { - QString theme = systemThemeName(); - if (theme.isEmpty()) - theme = fallbackTheme(); - if (theme != m_systemTheme) { - m_systemTheme = theme; - invalidateKey(); - } - } -} - -void XdgIconLoader::setThemeName(const QString &themeName) -{ - m_userTheme = themeName; - invalidateKey(); -} - -void XdgIconLoader::setThemeSearchPath(const QStringList &searchPaths) -{ - m_iconDirs = searchPaths; - themeList.clear(); - invalidateKey(); -} - -QStringList XdgIconLoader::themeSearchPaths() const -{ - if (m_iconDirs.isEmpty()) { - m_iconDirs = systemIconSearchPaths(); - // Always add resource directory as search path - m_iconDirs.append(QLatin1String(":/icons")); - } - return m_iconDirs; -} - /*! \class QIconCacheGtkReader \internal @@ -172,7 +107,7 @@ class QIconCacheGtkReader { public: explicit QIconCacheGtkReader(const QString &themeDir); - QVector lookup(const QString &); + QVector lookup(const QStringRef &); bool isValid() const { return m_isValid; } private: QFile m_file; @@ -203,7 +138,7 @@ private: QIconCacheGtkReader::QIconCacheGtkReader(const QString &dirName) : m_isValid(false) { - QFileInfo info(dirName + QLatin1Literal("/icon-theme.cache")); + QFileInfo info(dirName + QLatin1String("/icon-theme.cache")); if (!info.exists() || info.lastModified() < QFileInfo(dirName).lastModified()) return; m_file.setFileName(info.absoluteFilePath()); @@ -245,7 +180,7 @@ static quint32 icon_name_hash(const char *p) with this name is present. The char* are pointers to the mapped data. For example, this would return { "32x32/apps", "24x24/apps" , ... } */ -QVector QIconCacheGtkReader::lookup(const QString &name) +QVector QIconCacheGtkReader::lookup(const QStringRef &name) { QVector ret; if (!isValid()) @@ -295,12 +230,13 @@ QVector QIconCacheGtkReader::lookup(const QString &name) return ret; } -QIconTheme::QIconTheme(const QString &themeName) +XdgIconTheme::XdgIconTheme(const QString &themeName) : m_valid(false) + , m_followsColorScheme(false) { QFile themeIndex; - QStringList iconDirs = QIcon::themeSearchPaths(); + const QStringList iconDirs = QIcon::themeSearchPaths(); for ( int i = 0 ; i < iconDirs.size() ; ++i) { QDir iconDir(iconDirs[i]); QString themeDir = iconDir.path() + QLatin1Char('/') + themeName; @@ -320,27 +256,26 @@ QIconTheme::QIconTheme(const QString &themeName) #ifndef QT_NO_SETTINGS if (themeIndex.exists()) { const QSettings indexReader(themeIndex.fileName(), QSettings::IniFormat); - QStringListIterator keyIterator(indexReader.allKeys()); - while (keyIterator.hasNext()) { - - const QString key = keyIterator.next(); + m_followsColorScheme = indexReader.value(QStringLiteral("Icon Theme/FollowsColorScheme"), false).toBool(); + const QStringList keys = indexReader.allKeys(); + for (auto const &key : keys) { if (key.endsWith(QLatin1String("/Size"))) { // Note the QSettings ini-format does not accept // slashes in key names, hence we have to cheat if (int size = indexReader.value(key).toInt()) { QString directoryKey = key.left(key.size() - 5); - XdgIconDirInfo dirInfo(directoryKey); + QIconDirInfo dirInfo(directoryKey); dirInfo.size = size; QString type = indexReader.value(directoryKey + QLatin1String("/Type") ).toString(); if (type == QLatin1String("Fixed")) - dirInfo.type = XdgIconDirInfo::Fixed; + dirInfo.type = QIconDirInfo::Fixed; else if (type == QLatin1String("Scalable")) - dirInfo.type = XdgIconDirInfo::Scalable; + dirInfo.type = QIconDirInfo::Scalable; else - dirInfo.type = XdgIconDirInfo::Threshold; + dirInfo.type = QIconDirInfo::Threshold; dirInfo.threshold = indexReader.value(directoryKey + QLatin1String("/Threshold"), @@ -353,6 +288,11 @@ QIconTheme::QIconTheme(const QString &themeName) dirInfo.maxSize = indexReader.value(directoryKey + QLatin1String("/MaxSize"), size).toInt(); +#if QT_VERSION >= QT_VERSION_CHECK(5, 9, 0) + dirInfo.scale = indexReader.value(directoryKey + + QLatin1String("/Scale"), + 1).toInt(); +#endif m_keyList.append(dirInfo); } } @@ -362,6 +302,7 @@ QIconTheme::QIconTheme(const QString &themeName) m_parents = indexReader.value( QLatin1String("Icon Theme/Inherits")).toStringList(); m_parents.removeAll(QString()); + m_parents.removeAll(QLatin1String("hicolor")); // Ensure a default platform fallback for all themes if (m_parents.isEmpty()) { @@ -369,33 +310,51 @@ QIconTheme::QIconTheme(const QString &themeName) if (!fallback.isEmpty()) m_parents.append(fallback); } - - // Ensure that all themes fall back to hicolor - if (!m_parents.contains(QLatin1String("hicolor"))) - m_parents.append(QLatin1String("hicolor")); } #endif //QT_NO_SETTINGS } +/* WARNING: + * + * https://standards.freedesktop.org/icon-naming-spec/icon-naming-spec-latest.html + * + * + * The dash “-” character is used to separate levels of specificity in icon + * names, for all contexts other than MimeTypes. For instance, we use + * “input-mouse” as the generic item for all mouse devices, and we use + * “input-mouse-usb” for a USB mouse device. However, if the more specific + * item does not exist in the current theme, and does exist in a parent + * theme, the generic icon from the current theme is preferred, in order + * to keep consistent style. + * + * + * But we believe, that using the more specific icon (even from parents) + * is better for user experience. So we are violating the standard + * intentionally. + * + * Ref. + * https://github.com/lxde/lxqt/issues/1252 + * https://github.com/lxde/libqtxdg/pull/116 + */ QThemeIconInfo XdgIconLoader::findIconHelper(const QString &themeName, const QString &iconName, - QStringList &visited) const + QStringList &visited, + bool dashFallback) const { QThemeIconInfo info; Q_ASSERT(!themeName.isEmpty()); - QPixmap pixmap; - // Used to protect against potential recursions visited << themeName; - QIconTheme theme = themeList.value(themeName); + XdgIconTheme &theme = themeList[themeName]; if (!theme.isValid()) { - theme = QIconTheme(themeName); - if (!theme.isValid()) - theme = QIconTheme(fallbackTheme()); - - themeList.insert(themeName, theme); + theme = XdgIconTheme(themeName); + if (!theme.isValid()) { + const QString fallback = fallbackTheme(); + if (!fallback.isEmpty()) + theme = XdgIconTheme(fallback); + } } const QStringList contentDirs = theme.contentDirs(); @@ -405,7 +364,7 @@ QThemeIconInfo XdgIconLoader::findIconHelper(const QString &themeName, const QString xpmext(QLatin1String(".xpm")); - QString iconNameFallback = iconName; + QStringRef iconNameFallback(&iconName); // Iterate through all icon's fallbacks in current theme while (info.entries.isEmpty()) { @@ -415,21 +374,21 @@ QThemeIconInfo XdgIconLoader::findIconHelper(const QString &themeName, // Add all relevant files for (int i = 0; i < contentDirs.size(); ++i) { - QVector subDirs = theme.keyList(); + QVector subDirs = theme.keyList(); // Try to reduce the amount of subDirs by looking in the GTK+ cache in order to save // a massive amount of file stat (especially if the icon is not there) auto cache = theme.m_gtkCaches.at(i); if (cache->isValid()) { - auto result = cache->lookup(iconNameFallback); + const auto result = cache->lookup(iconNameFallback); if (cache->isValid()) { - const QVector subDirsCopy = subDirs; + const QVector subDirsCopy = subDirs; subDirs.clear(); subDirs.reserve(result.count()); - foreach (const char *s, result) { + for (const char *s : result) { QString path = QString::fromUtf8(s); auto it = std::find_if(subDirsCopy.cbegin(), subDirsCopy.cend(), - [&](const XdgIconDirInfo &info) { + [&](const QIconDirInfo &info) { return info.path == path; } ); if (it != subDirsCopy.cend()) { subDirs.append(*it); @@ -440,45 +399,41 @@ QThemeIconInfo XdgIconLoader::findIconHelper(const QString &themeName, QString contentDir = contentDirs.at(i) + QLatin1Char('/'); for (int j = 0; j < subDirs.size() ; ++j) { - const XdgIconDirInfo &dirInfo = subDirs.at(j); - QString subdir = dirInfo.path; - QDir currentDir(contentDir + subdir); - if (currentDir.exists(pngIconName)) { + const QIconDirInfo &dirInfo = subDirs.at(j); + const QString subDir = contentDir + dirInfo.path + QLatin1Char('/'); + const QString pngPath = subDir + pngIconName; + if (QFile::exists(pngPath)) { PixmapEntry *iconEntry = new PixmapEntry; iconEntry->dir = dirInfo; - iconEntry->filename = currentDir.filePath(pngIconName); + iconEntry->filename = pngPath; // Notice we ensure that pixmap entries always come before // scalable to preserve search order afterwards info.entries.prepend(iconEntry); - } else if (m_supportsSvg && - currentDir.exists(svgIconName)) { - ScalableEntry *iconEntry = new ScalableEntry; - iconEntry->dir = dirInfo; - iconEntry->filename = currentDir.filePath(svgIconName); - info.entries.append(iconEntry); - } else if(currentDir.exists(iconName + xpmext)) { + } else { + const QString svgPath = subDir + svgIconName; + if (gSupportsSvg && QFile::exists(svgPath)) { + ScalableEntry *iconEntry = (followColorScheme() && theme.followsColorScheme()) ? new ScalableFollowsColorEntry : new ScalableEntry; + iconEntry->dir = dirInfo; + iconEntry->filename = svgPath; + info.entries.append(iconEntry); + } + } + const QString xpmPath = subDir + xpmIconName; + if (QFile::exists(xpmPath)) { PixmapEntry *iconEntry = new PixmapEntry; iconEntry->dir = dirInfo; - iconEntry->filename = currentDir.filePath(iconName + xpmext); + iconEntry->filename = xpmPath; // Notice we ensure that pixmap entries always come before // scalable to preserve search order afterwards info.entries.append(iconEntry); - break; } } } - if (!info.entries.isEmpty()) { - info.iconName = iconNameFallback; - break; - } + if (!info.entries.isEmpty()) + info.iconName = iconNameFallback.toString(); - // If it's possible - find next fallback for the icon - const int indexOfDash = iconNameFallback.lastIndexOf(QLatin1Char('-')); - if (indexOfDash == -1) - break; - - iconNameFallback.truncate(indexOfDash); + break; } if (info.entries.isEmpty()) { @@ -496,83 +451,80 @@ QThemeIconInfo XdgIconLoader::findIconHelper(const QString &themeName, } } - if (info.entries.isEmpty()) { - // Search for unthemed icons in main dir of search paths - QStringList themeSearchPaths = QIcon::themeSearchPaths(); - foreach (QString contentDir, themeSearchPaths) { - QDir currentDir(contentDir); - - if (currentDir.exists(iconName + pngext)) { - PixmapEntry *iconEntry = new PixmapEntry; - iconEntry->filename = currentDir.filePath(iconName + pngext); - // Notice we ensure that pixmap entries always come before - // scalable to preserve search order afterwards - info.entries.prepend(iconEntry); - } else if (m_supportsSvg && - currentDir.exists(iconName + svgext)) { - ScalableEntry *iconEntry = new ScalableEntry; - iconEntry->filename = currentDir.filePath(iconName + svgext); - info.entries.append(iconEntry); - break; - } else if (currentDir.exists(iconName + xpmext)) { - PixmapEntry *iconEntry = new PixmapEntry; - iconEntry->filename = currentDir.filePath(iconName + xpmext); - // Notice we ensure that pixmap entries always come before - // scalable to preserve search order afterwards - info.entries.append(iconEntry); - break; - } + if (dashFallback && info.entries.isEmpty()) { + // If it's possible - find next fallback for the icon + const int indexOfDash = iconNameFallback.lastIndexOf(QLatin1Char('-')); + if (indexOfDash != -1) { + iconNameFallback.truncate(indexOfDash); + QStringList _visited; + info = findIconHelper(themeName, iconNameFallback.toString(), _visited, true); } } + return info; +} - /********************************************************************* - Author: Kaitlin Rupert - Date: Aug 12, 2010 - Description: Make it so that the QIcon loader honors /usr/share/pixmaps - directory. This is a valid directory per the Freedesktop.org - icon theme specification. - Bug: https://bugreports.qt.nokia.com/browse/QTBUG-12874 - *********************************************************************/ -#ifdef Q_OS_LINUX - /* Freedesktop standard says to look in /usr/share/pixmaps last */ - if (info.entries.isEmpty()) { - const QString pixmaps(QLatin1String("/usr/share/pixmaps")); +QThemeIconInfo XdgIconLoader::unthemedFallback(const QString &iconName, const QStringList &searchPaths) const +{ + QThemeIconInfo info; + + const QString svgext(QLatin1String(".svg")); + const QString pngext(QLatin1String(".png")); + const QString xpmext(QLatin1String(".xpm")); + + for (const auto &contentDir : searchPaths) { + QDir currentDir(contentDir); - const QDir currentDir(pixmaps); - const XdgIconDirInfo dirInfo(pixmaps); if (currentDir.exists(iconName + pngext)) { PixmapEntry *iconEntry = new PixmapEntry; - iconEntry->dir = dirInfo; iconEntry->filename = currentDir.filePath(iconName + pngext); // Notice we ensure that pixmap entries always come before // scalable to preserve search order afterwards info.entries.prepend(iconEntry); - } else if (m_supportsSvg && - currentDir.exists(iconName + svgext)) { + } else if (gSupportsSvg && + currentDir.exists(iconName + svgext)) { ScalableEntry *iconEntry = new ScalableEntry; - iconEntry->dir = dirInfo; iconEntry->filename = currentDir.filePath(iconName + svgext); info.entries.append(iconEntry); } else if (currentDir.exists(iconName + xpmext)) { PixmapEntry *iconEntry = new PixmapEntry; - iconEntry->dir = dirInfo; iconEntry->filename = currentDir.filePath(iconName + xpmext); // Notice we ensure that pixmap entries always come before // scalable to preserve search order afterwards info.entries.append(iconEntry); } } -#endif - return info; } QThemeIconInfo XdgIconLoader::loadIcon(const QString &name) const { - if (!themeName().isEmpty()) { + const QString theme_name = QIconLoader::instance()->themeName(); + if (!theme_name.isEmpty()) { QStringList visited; - return findIconHelper(themeName(), name, visited); + auto info = findIconHelper(theme_name, name, visited, true); + if (info.entries.isEmpty()) { + const auto hicolorInfo = findIconHelper(QLatin1String("hicolor"), name, visited, true); + if (hicolorInfo.entries.isEmpty()) { + const auto unthemedInfo = unthemedFallback(name, QIcon::themeSearchPaths()); + if (unthemedInfo.entries.isEmpty()) { + /* Freedesktop standard says to look in /usr/share/pixmaps last */ + const QStringList pixmapPath = (QStringList() << QString::fromLatin1("/usr/share/pixmaps")); + const auto pixmapInfo = unthemedFallback(name, pixmapPath); + if (pixmapInfo.entries.isEmpty()) { + return QThemeIconInfo(); + } else { + return pixmapInfo; + } + } else { + return unthemedInfo; + } + } else { + return hicolorInfo; + } + } else { + return info; + } } return QThemeIconInfo(); @@ -623,14 +575,14 @@ bool XdgIconLoaderEngine::hasIcon() const // Lazily load the icon void XdgIconLoaderEngine::ensureLoaded() { - if (!(XdgIconLoader::instance()->themeKey() == m_key)) { + if (!(QIconLoader::instance()->themeKey() == m_key)) { qDeleteAll(m_info.entries); m_info.entries.clear(); m_info.iconName.clear(); Q_ASSERT(m_info.entries.size() == 0); m_info = XdgIconLoader::instance()->loadIcon(m_iconName); - m_key = XdgIconLoader::instance()->themeKey(); + m_key = QIconLoader::instance()->themeKey(); } } @@ -648,16 +600,20 @@ void XdgIconLoaderEngine::paint(QPainter *painter, const QRect &rect, * This algorithm is defined by the freedesktop spec: * http://standards.freedesktop.org/icon-theme-spec/icon-theme-spec-latest.html */ -static bool directoryMatchesSize(const XdgIconDirInfo &dir, int iconsize) +static bool directoryMatchesSize(const QIconDirInfo &dir, int iconsize, int iconscale) { - if (dir.type == XdgIconDirInfo::Fixed) { +#if QT_VERSION >= QT_VERSION_CHECK(5, 9, 0) + if (dir.scale != iconscale) + return false; +#endif + if (dir.type == QIconDirInfo::Fixed) { return dir.size == iconsize; - } else if (dir.type == XdgIconDirInfo::Scalable) { + } else if (dir.type == QIconDirInfo::Scalable) { return iconsize <= dir.maxSize && iconsize >= dir.minSize; - } else if (dir.type == XdgIconDirInfo::Threshold) { + } else if (dir.type == QIconDirInfo::Threshold) { return iconsize >= dir.size - dir.threshold && iconsize <= dir.size + dir.threshold; } @@ -670,12 +626,33 @@ static bool directoryMatchesSize(const XdgIconDirInfo &dir, int iconsize) * This algorithm is defined by the freedesktop spec: * http://standards.freedesktop.org/icon-theme-spec/icon-theme-spec-latest.html */ -static int directorySizeDistance(const XdgIconDirInfo &dir, int iconsize) -{ - if (dir.type == XdgIconDirInfo::Fixed) { +static int directorySizeDistance(const QIconDirInfo &dir, int iconsize, int iconscale) +{ +#if QT_VERSION >= QT_VERSION_CHECK(5, 9, 0) + const int scaledIconSize = iconsize * iconscale; + if (dir.type == QIconDirInfo::Fixed) { + return qAbs(dir.size * dir.scale - scaledIconSize); + + } else if (dir.type == QIconDirInfo::Scalable) { + if (scaledIconSize < dir.minSize * dir.scale) + return dir.minSize * dir.scale - scaledIconSize; + else if (scaledIconSize > dir.maxSize * dir.scale) + return scaledIconSize - dir.maxSize * dir.scale; + else + return 0; + + } else if (dir.type == QIconDirInfo::Threshold) { + if (scaledIconSize < (dir.size - dir.threshold) * dir.scale) + return dir.minSize * dir.scale - scaledIconSize; + else if (scaledIconSize > (dir.size + dir.threshold) * dir.scale) + return scaledIconSize - dir.maxSize * dir.scale; + else return 0; + } +#else + if (dir.type == QIconDirInfo::Fixed) { return qAbs(dir.size - iconsize); - } else if (dir.type == XdgIconDirInfo::Scalable) { + } else if (dir.type == QIconDirInfo::Scalable) { if (iconsize < dir.minSize) return dir.minSize - iconsize; else if (iconsize > dir.maxSize) @@ -683,19 +660,20 @@ static int directorySizeDistance(const XdgIconDirInfo &dir, int iconsize) else return 0; - } else if (dir.type == XdgIconDirInfo::Threshold) { + } else if (dir.type == QIconDirInfo::Threshold) { if (iconsize < dir.size - dir.threshold) return dir.minSize - iconsize; else if (iconsize > dir.size + dir.threshold) return iconsize - dir.maxSize; else return 0; } +#endif Q_ASSERT(1); // Not a valid value return INT_MAX; } -XdgIconLoaderEngineEntry *XdgIconLoaderEngine::entryForSize(const QSize &size) +QIconLoaderEngineEntry *XdgIconLoaderEngine::entryForSize(const QSize &size, int scale) { int iconsize = qMin(size.width(), size.height()); @@ -706,18 +684,18 @@ XdgIconLoaderEngineEntry *XdgIconLoaderEngine::entryForSize(const QSize &size) // Search for exact matches first for (int i = 0; i < numEntries; ++i) { - XdgIconLoaderEngineEntry *entry = m_info.entries.at(i); - if (directoryMatchesSize(entry->dir, iconsize)) { + QIconLoaderEngineEntry *entry = m_info.entries.at(i); + if (directoryMatchesSize(entry->dir, iconsize, scale)) { return entry; } } // Find the minimum distance icon int minimalSize = INT_MAX; - XdgIconLoaderEngineEntry *closestMatch = 0; + QIconLoaderEngineEntry *closestMatch = 0; for (int i = 0; i < numEntries; ++i) { - XdgIconLoaderEngineEntry *entry = m_info.entries.at(i); - int distance = directorySizeDistance(entry->dir, iconsize); + QIconLoaderEngineEntry *entry = m_info.entries.at(i); + int distance = directorySizeDistance(entry->dir, iconsize, scale); if (distance < minimalSize) { minimalSize = distance; closestMatch = entry; @@ -737,10 +715,10 @@ QSize XdgIconLoaderEngine::actualSize(const QSize &size, QIcon::Mode mode, { ensureLoaded(); - XdgIconLoaderEngineEntry *entry = entryForSize(size); + QIconLoaderEngineEntry *entry = entryForSize(size); if (entry) { - const XdgIconDirInfo &dir = entry->dir; - if (dir.type == XdgIconDirInfo::Scalable || dynamic_cast(entry)) + const QIconDirInfo &dir = entry->dir; + if (dir.type == QIconDirInfo::Scalable || dynamic_cast(entry)) return size; else { int dir_size = dir.size; @@ -756,9 +734,10 @@ QSize XdgIconLoaderEngine::actualSize(const QSize &size, QIcon::Mode mode, return QSize(result, result); } } - return QIconEngine::actualSize(size, mode, state); + return {0, 0}; } +// XXX: duplicated from qiconloader.cpp, because this symbol isn't exported :( QPixmap PixmapEntry::pixmap(const QSize &size, QIcon::Mode mode, QIcon::State state) { Q_UNUSED(state); @@ -769,6 +748,8 @@ QPixmap PixmapEntry::pixmap(const QSize &size, QIcon::Mode mode, QIcon::State st basePixmap.load(filename); QSize actualSize = basePixmap.size(); + // If the size of the best match we have (basePixmap) is larger than the + // requested size, we downscale it to match. if (!actualSize.isNull() && (actualSize.width() > size.width() || actualSize.height() > size.height())) actualSize.scale(size, Qt::KeepAspectRatio); @@ -794,6 +775,7 @@ QPixmap PixmapEntry::pixmap(const QSize &size, QIcon::Mode mode, QIcon::State st return cachedPixmap; } +// XXX: duplicated from qiconloader.cpp, because this symbol isn't exported :( QPixmap ScalableEntry::pixmap(const QSize &size, QIcon::Mode mode, QIcon::State state) { if (svgIcon.isNull()) @@ -803,12 +785,90 @@ QPixmap ScalableEntry::pixmap(const QSize &size, QIcon::Mode mode, QIcon::State return svgIcon.pixmap(size, mode, state); } + +// XXX: duplicated from qicon.cpp, because the symbol qt_iconEngineFactoryLoader isn't exported :( +Q_GLOBAL_STATIC_WITH_ARGS(QFactoryLoader, qt_iconEngineFactoryLoader, + (QIconEngineFactoryInterface_iid, QLatin1String("/iconengines"), Qt::CaseInsensitive)) +//extern QFactoryLoader *qt_iconEngineFactoryLoader(); // qicon.cpp + +QPixmap ScalableFollowsColorEntry::pixmap(const QSize &size, QIcon::Mode mode, QIcon::State state) +{ + QIcon & icon = QIcon::Selected == mode ? svgSelectedIcon : svgIcon; + if (icon.isNull()) + { + // The following lines are adapted and updated from KDE's "kiconloader.cpp" -> + // KIconLoaderPrivate::processSvg() and KIconLoaderPrivate::createIconImage(). + // They read the SVG color scheme of SVG icons and give images based on the icon mode. + QByteArray processedContents; + QFile device{filename};; + if (device.open(QIODevice::ReadOnly)) + { + const QPalette pal = qApp->palette(); + QString styleSheet = QStringLiteral(".ColorScheme-Text{color:%1;}") + .arg(mode == QIcon::Selected + ? pal.highlightedText().color().name() + : pal.windowText().color().name()); + QXmlStreamReader xmlReader(&device); + QXmlStreamWriter writer(&processedContents); + while (!xmlReader.atEnd()) + { + if (xmlReader.readNext() == QXmlStreamReader::StartElement + && xmlReader.qualifiedName() == QLatin1String("style") + && xmlReader.attributes().value(QLatin1String("id")) == QLatin1String("current-color-scheme")) + { + writer.writeStartElement(QLatin1String("style")); + writer.writeAttributes(xmlReader.attributes()); + writer.writeCharacters(styleSheet); + writer.writeEndElement(); + while (xmlReader.tokenType() != QXmlStreamReader::EndElement) + xmlReader.readNext(); + } + else if (xmlReader.tokenType() != QXmlStreamReader::Invalid) + writer.writeCurrentToken(xmlReader); + } + } + // use the QSvgIconEngine + // - assemble the content as it is done by the QSvgIconEngine::write() (operator <<) + // - create the QIcon with QSvgIconEngine initialized from the content + const int index = qt_iconEngineFactoryLoader()->indexOf(QStringLiteral("svg")); + if (index != -1) + { + if (QIconEnginePlugin * factory = qobject_cast(qt_iconEngineFactoryLoader()->instance(index))) + { + if (QIconEngine * engine = factory->create()) + { + QByteArray engine_arr; + QDataStream str{&engine_arr, QIODevice::WriteOnly}; + str.setVersion(QDataStream::Qt_4_4); + QHash filenames; + filenames[0] = filename; + QHash svg_buffers; + svg_buffers[0] = processedContents; + str << filenames << static_cast(0)/*isCompressed*/ << svg_buffers << static_cast(0)/*hasAddedPimaps*/; + + QDataStream str_read{&engine_arr, QIODevice::ReadOnly}; + str_read.setVersion(QDataStream::Qt_4_4); + + engine->read(str_read); + icon = QIcon{engine}; + } + } + } + + // load the icon directly from file, if still null + if (icon.isNull()) + icon = QIcon(filename); + } + + return icon.pixmap(size, mode, state); +} + QPixmap XdgIconLoaderEngine::pixmap(const QSize &size, QIcon::Mode mode, QIcon::State state) { ensureLoaded(); - XdgIconLoaderEngineEntry *entry = entryForSize(size); + QIconLoaderEngineEntry *entry = entryForSize(size); if (entry) return entry->pixmap(size, mode, state); @@ -853,6 +913,17 @@ void XdgIconLoaderEngine::virtual_hook(int id, void *data) *reinterpret_cast(data) = m_info.entries.isEmpty(); } break; +#endif +#if QT_VERSION >= QT_VERSION_CHECK(5, 9, 0) + case QIconEngine::ScaledPixmapHook: + { + QIconEngine::ScaledPixmapArgument &arg = *reinterpret_cast(data); + // QIcon::pixmap() multiplies size by the device pixel ratio. + const int integerScale = qCeil(arg.scale); + QIconLoaderEngineEntry *entry = entryForSize(arg.size / integerScale, integerScale); + arg.pixmap = entry ? entry->pixmap(arg.size, arg.mode, arg.state) : QPixmap(); + } + break; #endif default: QIconEngine::virtual_hook(id, data); diff --git a/xdgiconloader/xdgiconloader_p.h b/xdgiconloader/xdgiconloader_p.h index 380a222..713065c 100644 --- a/xdgiconloader/xdgiconloader_p.h +++ b/xdgiconloader/xdgiconloader_p.h @@ -31,8 +31,8 @@ ** ****************************************************************************/ -#ifndef QICONLOADER_P_H -#define QICONLOADER_P_H +#ifndef XDGICONLOADER_P_H +#define XDGICONLOADER_P_H #include @@ -52,65 +52,19 @@ #include #include -#include #include -#include +#include #include #include -#include //QT_BEGIN_NAMESPACE class XdgIconLoader; -struct XdgIconDirInfo -{ - enum Type { Fixed, Scalable, Threshold }; - XdgIconDirInfo(const QString &_path = QString()) : - path(_path), - size(0), - maxSize(0), - minSize(0), - threshold(0), - type(Threshold) {} - QString path; - short size; - short maxSize; - short minSize; - short threshold; - Type type; -}; - -class XdgIconLoaderEngineEntry - { -public: - virtual ~XdgIconLoaderEngineEntry() {} - virtual QPixmap pixmap(const QSize &size, - QIcon::Mode mode, - QIcon::State state) = 0; - QString filename; - XdgIconDirInfo dir; - static int count; -}; - -struct ScalableEntry : public XdgIconLoaderEngineEntry -{ - QPixmap pixmap(const QSize &size, QIcon::Mode mode, QIcon::State state) Q_DECL_OVERRIDE; - QIcon svgIcon; -}; - -struct PixmapEntry : public XdgIconLoaderEngineEntry +struct ScalableFollowsColorEntry : public ScalableEntry { QPixmap pixmap(const QSize &size, QIcon::Mode mode, QIcon::State state) Q_DECL_OVERRIDE; - QPixmap basePixmap; -}; - -typedef QList QThemeIconEntries; - -struct QThemeIconInfo -{ - QThemeIconEntries entries; - QString iconName; + QIcon svgSelectedIcon; }; //class QIconLoaderEngine : public QIconEngine @@ -120,19 +74,19 @@ public: XdgIconLoaderEngine(const QString& iconName = QString()); ~XdgIconLoaderEngine(); - void paint(QPainter *painter, const QRect &rect, QIcon::Mode mode, QIcon::State state); - QPixmap pixmap(const QSize &size, QIcon::Mode mode, QIcon::State state); - QSize actualSize(const QSize &size, QIcon::Mode mode, QIcon::State state); - QIconEngine *clone() const; - bool read(QDataStream &in); - bool write(QDataStream &out) const; + void paint(QPainter *painter, const QRect &rect, QIcon::Mode mode, QIcon::State state) Q_DECL_OVERRIDE; + QPixmap pixmap(const QSize &size, QIcon::Mode mode, QIcon::State state) Q_DECL_OVERRIDE; + QSize actualSize(const QSize &size, QIcon::Mode mode, QIcon::State state) Q_DECL_OVERRIDE; + QIconEngine *clone() const Q_DECL_OVERRIDE; + bool read(QDataStream &in) Q_DECL_OVERRIDE; + bool write(QDataStream &out) const Q_DECL_OVERRIDE; private: - QString key() const; + QString key() const Q_DECL_OVERRIDE; bool hasIcon() const; void ensureLoaded(); - void virtual_hook(int id, void *data); - XdgIconLoaderEngineEntry *entryForSize(const QSize &size); + void virtual_hook(int id, void *data) Q_DECL_OVERRIDE; + QIconLoaderEngineEntry *entryForSize(const QSize &size, int scale = 1); XdgIconLoaderEngine(const XdgIconLoaderEngine &other); QThemeIconInfo m_info; QString m_iconName; @@ -143,20 +97,24 @@ private: class QIconCacheGtkReader; -class QIconTheme +// Note: We can't simply reuse the QIconTheme from Qt > 5.7 because +// the QIconTheme constructor symbol isn't exported. +class XdgIconTheme { public: - QIconTheme(const QString &name); - QIconTheme() : m_valid(false) {} + XdgIconTheme(const QString &name); + XdgIconTheme() = default; QStringList parents() { return m_parents; } - QVector keyList() { return m_keyList; } + QVector keyList() { return m_keyList; } QStringList contentDirs() { return m_contentDirs; } - bool isValid() { return m_valid; } + bool isValid() const { return m_valid; } + bool followsColorScheme() const { return m_followsColorScheme; } private: QStringList m_contentDirs; - QVector m_keyList; + QVector m_keyList; QStringList m_parents; - bool m_valid; + bool m_valid = false; + bool m_followsColorScheme = false; public: QVector> m_gtkCaches; }; @@ -164,43 +122,39 @@ public: class XDGICONLOADER_EXPORT XdgIconLoader { public: - XdgIconLoader(); QThemeIconInfo loadIcon(const QString &iconName) const; - uint themeKey() const { return m_themeKey; } - - QString themeName() const { return m_userTheme.isEmpty() ? m_systemTheme : m_userTheme; } - void setThemeName(const QString &themeName); - QIconTheme theme() { return themeList.value(themeName()); } - void setThemeSearchPath(const QStringList &searchPaths); - QStringList themeSearchPaths() const; - XdgIconDirInfo dirInfo(int dirindex); + + /* TODO: deprecate & remove all QIconLoader wrappers */ + inline uint themeKey() const { return QIconLoader::instance()->themeKey(); } + inline QString themeName() const { return QIconLoader::instance()->themeName(); } + inline void setThemeName(const QString &themeName) { QIconLoader::instance()->setThemeName(themeName); } + inline void setThemeSearchPath(const QStringList &searchPaths) { QIconLoader::instance()->setThemeSearchPath(searchPaths); } + inline QIconDirInfo dirInfo(int dirindex) { return QIconLoader::instance()->dirInfo(dirindex); } + inline QStringList themeSearchPaths() const { return QIconLoader::instance()->themeSearchPaths(); } + inline void updateSystemTheme() { QIconLoader::instance()->updateSystemTheme(); } + inline void invalidateKey() { QIconLoader::instance()->invalidateKey(); } + inline void ensureInitialized() { QIconLoader::instance()->ensureInitialized(); } + inline bool hasUserTheme() const { return QIconLoader::instance()->hasUserTheme(); } + /*! + * Flag if the "FollowsColorScheme" hint (the KDE extension to XDG + * themes) should be honored. + */ + inline bool followColorScheme() const { return m_followColorScheme; } + void setFollowColorScheme(bool enable); + + XdgIconTheme theme() { return themeList.value(QIconLoader::instance()->themeName()); } static XdgIconLoader *instance(); - void updateSystemTheme(); - void invalidateKey() { m_themeKey++; } - void ensureInitialized(); - bool hasUserTheme() const { return !m_userTheme.isEmpty(); } private: QThemeIconInfo findIconHelper(const QString &themeName, const QString &iconName, - QStringList &visited) const; - uint m_themeKey; - bool m_supportsSvg; - bool m_initialized; - - mutable QString m_userTheme; - mutable QString m_systemTheme; - mutable QStringList m_iconDirs; - mutable QHash themeList; + QStringList &visited, + bool dashFallback = false) const; + QThemeIconInfo unthemedFallback(const QString &iconName, const QStringList &searchPaths) const; + mutable QHash themeList; + bool m_followColorScheme = true; }; - -// Note: class template specialization of 'QTypeInfo' must occur at -// global scope -Q_DECLARE_TYPEINFO(XdgIconDirInfo, Q_MOVABLE_TYPE); - -//QT_END_NAMESPACE - #endif // QT_NO_ICON -#endif // QICONLOADER_P_H +#endif // XDGICONLOADER_P_H