From 8762635176142550e4736dae90f8d6629cf30421 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andrew=20Lee=20=28=E6=9D=8E=E5=81=A5=E7=A7=8B=29?= Date: Thu, 13 Aug 2015 03:54:32 +0800 Subject: [PATCH] Adding upstream version 1.2.0. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Andrew Lee (李健秋) --- .gitignore | 8 + AUTHORS | 10 + CMakeLists.txt | 354 ++++++ COPYING | 461 +++++++ README | 43 + cmake/FindLibMagic.cmake | 23 + cmake/FindLibSuffix.cmake | 26 + cmake/cmake_uninstall.cmake.in | 21 + cmake/create_pkgconfig_file.cmake | 36 + cmake/create_portable_headers.cmake | 30 + cmake/qt5xdg-config.cmake.in | 54 + cmake/qt5xdg_use.cmake | 23 + cmake/qtxdg-config.cmake.in | 53 + cmake/qtxdg_use.cmake | 42 + desktopenvironment_p.cpp | 11 + examples/use-qtxdg/CMakeLists.txt | 18 + examples/use-qtxdg/README | 9 + examples/use-qtxdg/main.cpp | 8 + qiconfix/qiconloader.cpp | 677 ++++++++++ qiconfix/qiconloader_p.h | 199 +++ qiconfix/qiconloader_p_qt4.h | 216 ++++ qiconfix/qiconloader_qt4.cpp | 737 +++++++++++ release.sh | 26 + test/CMakeLists.txt | 52 + test/qtxdg_test.cpp | 141 +++ test/qtxdg_test.h | 28 + xdgaction.cpp | 156 +++ xdgaction.h | 86 ++ xdgautostart.cpp | 91 ++ xdgautostart.h | 60 + xdgdesktopfile.cpp | 1763 +++++++++++++++++++++++++++ xdgdesktopfile.h | 262 ++++ xdgdesktopfile_p.h | 7 + xdgdirs.cpp | 430 +++++++ xdgdirs.h | 149 +++ xdgicon.cpp | 200 +++ xdgicon.h | 62 + xdgmacros.h | 45 + xdgmenu.cpp | 825 +++++++++++++ xdgmenu.h | 130 ++ xdgmenu_p.h | 88 ++ xdgmenuapplinkprocessor.cpp | 275 +++++ xdgmenuapplinkprocessor.h | 102 ++ xdgmenulayoutprocessor.cpp | 430 +++++++ xdgmenulayoutprocessor.h | 115 ++ xdgmenureader.cpp | 456 +++++++ xdgmenureader.h | 80 ++ xdgmenurules.cpp | 289 +++++ xdgmenurules.h | 135 ++ xdgmenuwidget.cpp | 275 +++++ xdgmenuwidget.h | 89 ++ xdgmime.cpp | 366 ++++++ xdgmime.h | 99 ++ xdgmimetype.cpp | 104 ++ xdgmimetype.h | 129 ++ xmlhelper.cpp | 51 + xmlhelper.h | 162 +++ 57 files changed, 10817 insertions(+) create mode 100644 .gitignore create mode 100644 AUTHORS create mode 100644 CMakeLists.txt create mode 100644 COPYING create mode 100644 README create mode 100644 cmake/FindLibMagic.cmake create mode 100644 cmake/FindLibSuffix.cmake create mode 100644 cmake/cmake_uninstall.cmake.in create mode 100644 cmake/create_pkgconfig_file.cmake create mode 100644 cmake/create_portable_headers.cmake create mode 100644 cmake/qt5xdg-config.cmake.in create mode 100644 cmake/qt5xdg_use.cmake create mode 100644 cmake/qtxdg-config.cmake.in create mode 100644 cmake/qtxdg_use.cmake create mode 100644 desktopenvironment_p.cpp create mode 100644 examples/use-qtxdg/CMakeLists.txt create mode 100644 examples/use-qtxdg/README create mode 100644 examples/use-qtxdg/main.cpp create mode 100644 qiconfix/qiconloader.cpp create mode 100644 qiconfix/qiconloader_p.h create mode 100644 qiconfix/qiconloader_p_qt4.h create mode 100644 qiconfix/qiconloader_qt4.cpp create mode 100755 release.sh create mode 100644 test/CMakeLists.txt create mode 100644 test/qtxdg_test.cpp create mode 100644 test/qtxdg_test.h create mode 100644 xdgaction.cpp create mode 100644 xdgaction.h create mode 100644 xdgautostart.cpp create mode 100644 xdgautostart.h create mode 100644 xdgdesktopfile.cpp create mode 100644 xdgdesktopfile.h create mode 100644 xdgdesktopfile_p.h create mode 100644 xdgdirs.cpp create mode 100644 xdgdirs.h create mode 100644 xdgicon.cpp create mode 100644 xdgicon.h create mode 100644 xdgmacros.h create mode 100644 xdgmenu.cpp create mode 100644 xdgmenu.h create mode 100644 xdgmenu_p.h create mode 100644 xdgmenuapplinkprocessor.cpp create mode 100644 xdgmenuapplinkprocessor.h create mode 100644 xdgmenulayoutprocessor.cpp create mode 100644 xdgmenulayoutprocessor.h create mode 100644 xdgmenureader.cpp create mode 100644 xdgmenureader.h create mode 100644 xdgmenurules.cpp create mode 100644 xdgmenurules.h create mode 100644 xdgmenuwidget.cpp create mode 100644 xdgmenuwidget.h create mode 100644 xdgmime.cpp create mode 100644 xdgmime.h create mode 100644 xdgmimetype.cpp create mode 100644 xdgmimetype.h create mode 100644 xmlhelper.cpp create mode 100644 xmlhelper.h diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..429cda3 --- /dev/null +++ b/.gitignore @@ -0,0 +1,8 @@ +build* +*.qm +*~ +*.autosave +*-swp +*.swp +CMakeLists.txt.user* +nbproject/ diff --git a/AUTHORS b/AUTHORS new file mode 100644 index 0000000..190d298 --- /dev/null +++ b/AUTHORS @@ -0,0 +1,10 @@ +Upstream Authors: + LXQt team: http://lxqt.org + Razor team: http://razor-qt.org + +Copyright: + Copyright (c) 2010-2012 Razor team + Copyright (c) 2012-2014 LXQt team + +License: GPL-2 and LGPL-2.1+ +The full text of the licenses can be found in the 'COPYING' file. diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..95266aa --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,354 @@ +cmake_minimum_required( VERSION 2.8.5 ) + +project(libqtxdg) + +# Support different versions of Qt +option(USE_QT4 "Build with Qt4." $ENV{USE_QT4}) +option(BUILD_TESTS "Builds tests" OFF) + +if (USE_QT4) + set(USE_QT5 FALSE) +else() + set(USE_QT5 TRUE) +endif() + +# The Qt4 version can be compiled with libmagic or with QtMimeTypes +# QtMimeTypes is the preferred way and also the default. libmagic will be +# dropped in future releases. +if (NOT USE_QT5) + option(USE_QTMIMETYPES "Use QtMimeTypes library" ON) +endif() + +# Standard directories for installation +include(GNUInstallDirs) + +# additional cmake files +set(CMAKE_MODULE_PATH "${CMAKE_MODULE_PATH}" "${CMAKE_CURRENT_SOURCE_DIR}/cmake") + +set (MAJOR_VERSION 1) +set (MINOR_VERSION 2) +set (PATCH_VERSION 0) +set(QTXDG_VERSION_STRING ${MAJOR_VERSION}.${MINOR_VERSION}.${PATCH_VERSION}) + +add_definitions(-Wall -DQTXDG_COMPILATION=1) + +if ("${CMAKE_CXX_COMPILER_ID}" STREQUAL "Clang") + set(QTXDG_COMPILER_IS_CLANGCXX 1) +endif() + +if (CMAKE_COMPILER_IS_GNUCXX OR QTXDG_COMPILER_IS_CLANGCXX) + # set visibility to hidden to hide symbols, unless they're exported manually in the code + set(CMAKE_CXX_FLAGS "-fvisibility=hidden -fvisibility-inlines-hidden -fno-exceptions ${CMAKE_CXX_FLAGS}") +endif() + +find_package(PkgConfig) + +if (USE_QT5) + cmake_minimum_required(VERSION 2.8.11) + find_package(Qt5Widgets REQUIRED QUIET) + find_package(Qt5Xml REQUIRED QUIET) + find_package(Qt5DBus REQUIRED QUIET) + if (BUILD_TESTS) + find_package(Qt5Test REQUIRED QUIET) + endif() + + # if both Qt4 and Qt5 are installed we must check what version was found + if (NOT ${Qt5Core_VERSION_MAJOR} EQUAL 5) + message(FATAL_ERROR "Qt was found, but NOT Qt5.") + endif() + + set(QTXDGX_LIBRARY_NAME "Qt5Xdg") + set(QTXDGX_FILE_NAME "qt5xdg") + + set(QTXDGX_PKG_CONFIG_DESCRIPTION "Qt5Xdg, a Qt5 implementation of XDG standards") + set(QTXDGX_PKG_CONFIG_REQUIRES "Qt5Core, Qt5Xml, Qt5Widgets, Qt5DBus") + + include_directories( + "${Qt5Widgets_INCLUDE_DIRS}" + "${Qt5Gui_PRIVATE_INCLUDE_DIRS}" + "${Qt5Xml_INCLUDE_DIRS}" + ) + add_definitions(${Qt5Core_DEFINITIONS}) +# set(CMAKE_CXX_FLAGS +# "${CMAKE_CXX_FLAGS} ${Qt5Widgets_EXECUTABLE_COMPILE_FLAGS}" +# ) + + # QMimeDatabase and QMimeType are part of Qt5Core + # We just use that as an mimetype provider. + # An empty MIMETYPES_PROVIDER_LIBRARY means we are using Qt internal + # mimetypes support + set(MIMETYPES_PROVIDER_LIBRARY "") + add_definitions("-DHAVE_QTMIMETYPES") + + set(QTX_LIBRARIES ${Qt5Widgets_LIBRARIES} ${Qt5Xml_LIBRARIES} ${Qt5DBus_LIBRARIES}) + message(STATUS "Building with Qt ${Qt5Core_VERSION_STRING}") +else() + find_package(Qt4 REQUIRED QtCore QtGui QtXml QtDBus QUIET) + if (BUILD_TESTS) + find_package(Qt4 REQUIRED QtTest QUIET) + endif() + + # if both Qt4 and Qt5 are installed we must check what version was found + if (NOT ${QT_VERSION_MAJOR} EQUAL 4) + message(FATAL_ERROR "Qt was found, but NOT Qt4") + endif() + + set(QTXDGX_LIBRARY_NAME "qtxdg") + set(QTXDGX_FILE_NAME "qtxdg") + + set(QTXDGX_PKG_CONFIG_DESCRIPTION "QtXdg, a Qt4 implementation of XDG standards") + + include(${QT_USE_FILE}) + set(QTX_LIBRARIES + ${QT_QTCORE_LIBRARY} + ${QT_QTGUI_LIBRARY} + ${QT_QTXML_LIBRARY} + ${QT_QTDBUS_LIBRARY} + ) + message(STATUS "Building with Qt ${QTVERSION}") +endif() + + +set(libqtxdg_PUBLIC_H_FILES + xdgaction.h + xdgdesktopfile.h + xdgdirs.h + xdgicon.h + xdgmenu.h + xdgmenuwidget.h + xmlhelper.h + xdgautostart.h + xdgmacros.h +) + +set(libqtxdg_PUBLIC_CLASSES + XdgAction + XdgDesktopFile + XdgDirs + XdgIcon + XdgMenu + XdgMenuWidget + XmlHelper + XdgAutoStart +) + +set(libqtxdg_PRIVATE_H_FILES + xdgmenuapplinkprocessor.h + xdgmenulayoutprocessor.h + xdgmenu_p.h + xdgmenureader.h + xdgmenurules.h + xdgdesktopfile_p.h +) + +set(libqtxdg_CPP_FILES + xdgaction.cpp + xdgdesktopfile.cpp + xdgdirs.cpp + xdgicon.cpp + xdgmenuapplinkprocessor.cpp + xdgmenu.cpp + xdgmenulayoutprocessor.cpp + xdgmenureader.cpp + xdgmenurules.cpp + xdgmenuwidget.cpp + xmlhelper.cpp + xdgautostart.cpp +) + +set(libqtxdg_MOCS + xdgaction.h + xdgmenuapplinkprocessor.h + xdgmenu.h + xdgmenu_p.h + xdgmenureader.h + xdgmenurules.h + xdgmenuwidget.h +) + +if (USE_QT5) + list(APPEND libqtxdg_PRIVATE_INSTALLABLE_H_FILES qiconfix/qiconloader_p.h) + list(APPEND libqtxdg_CPP_FILES qiconfix/qiconloader.cpp) +else() + list(APPEND libqtxdg_PRIVATE_H_FILES qiconfix/qiconloader_p_qt4.h) + list(APPEND libqtxdg_CPP_FILES qiconfix/qiconloader_qt4.cpp) +endif() + +if (NOT USE_QT5) + if (USE_QTMIMETYPES) + # Using QtMimeTypes to provide a better mimetype support on Qt4 + # Project repo: https://qt.gitorious.org/qtplayground/mimetypes + pkg_check_modules(QTMIMETYPES REQUIRED + QtMimeTypes + ) + include_directories("${QTMIMETYPES_INCLUDE_DIRS}") + set(QTXDGX_PKG_CONFIG_REQUIRES "QtCore, QtXml, QtDbus, QtMimeTypes") + set(MIMETYPES_PROVIDER_LIBRARY ${QTMIMETYPES_LIBRARIES}) + link_directories("${QTMIMETYPES_LIBRARY_DIRS}") + add_definitions("-DHAVE_QTMIMETYPES") + else() + # Use libmagic + find_package(LibMagic REQUIRED QUIET) + set(MIMETYPES_PROVIDER_LIBRARY ${LIBMAGIC_LIBRARY}) + set(QTXDGX_PKG_CONFIG_REQUIRES "QtCore, QtXml, QtDBus") + + list(APPEND libqtxdg_PUBLIC_H_FILES xdgmime.h) + list(APPEND libqtxdg_PUBLIC_CLASSES XdgMime) + list(APPEND libqtxdg_CPP_FILES xdgmime.cpp) + endif() +endif() + +if (USE_QTMIMETYPES OR USE_QT5) + list(APPEND libqtxdg_PUBLIC_H_FILES xdgmimetype.h) + list(APPEND libqtxdg_PUBLIC_CLASSES XdgMimeType) + list(APPEND libqtxdg_CPP_FILES xdgmimetype.cpp) +endif() + + +set(APP_SHARE_DIR "${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_DATADIR}/lib${QTXDGX_FILE_NAME}") +add_definitions(-DTRANSLATIONS_DIR=\"${APP_SHARE_DIR}\") + + +#************************************************ +# Build 2 config.cmake files +# One for in-tree build and second for normal one. +#************************************************ +set(QTXDG_MAJOR_VERSION ${MAJOR_VERSION}) +set(QTXDG_MINOR_VERSION ${MINOR_VERSION}) +set(QTXDG_PATCH_VERSION ${PATCH_VERSION}) + +# In tree compilation ...................... +set(QTXDG_INCLUDE_DIR "${CMAKE_CURRENT_SOURCE_DIR};${CMAKE_CURRENT_BINARY_DIR}") + +if (USE_QT5) + set(QTXDG_PRIVATE_INCLUDE_DIR "${CMAKE_CURRENT_SOURCE_DIR}/qiconfix") + configure_file( + "${CMAKE_CURRENT_SOURCE_DIR}/cmake/qt5xdg-config.cmake.in" + "${CMAKE_CURRENT_BINARY_DIR}/cmake/${QTXDGX_FILE_NAME}-config.cmake" + @ONLY + ) +else() + configure_file( + "${CMAKE_CURRENT_SOURCE_DIR}/cmake/qtxdg-config.cmake.in" + "${CMAKE_CURRENT_BINARY_DIR}/cmake/${QTXDGX_FILE_NAME}-config.cmake" + @ONLY + ) +endif() + +configure_file( + "${CMAKE_CURRENT_SOURCE_DIR}/cmake/${QTXDGX_FILE_NAME}_use.cmake" + "${CMAKE_CURRENT_BINARY_DIR}/cmake/${QTXDGX_FILE_NAME}_use.cmake" + @ONLY +) + +# Instalable ............................... +set(QTXDG_INCLUDE_DIR "${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_INCLUDEDIR}/${QTXDGX_FILE_NAME}") + +if (USE_QT5) + set(QTXDG_PRIVATE_INCLUDE_DIR "${QTXDG_INCLUDE_DIR}/${QTXDG_VERSION_STRING}") + configure_file( + "${CMAKE_CURRENT_SOURCE_DIR}/cmake/qt5xdg-config.cmake.in" + "${CMAKE_CURRENT_BINARY_DIR}/${QTXDGX_FILE_NAME}-config.cmake" + @ONLY + ) +else() + configure_file( + "${CMAKE_CURRENT_SOURCE_DIR}/cmake/qtxdg-config.cmake.in" + "${CMAKE_CURRENT_BINARY_DIR}/${QTXDGX_FILE_NAME}-config.cmake" + @ONLY + ) +endif() + +#********************************************************** + +include(FindLibSuffix) + +if(USE_QT5) + QT5_WRAP_CPP(libqtxdg_CXX_FILES ${libqtxdg_MOCS}) +else() + QT4_WRAP_CPP(libqtxdg_CXX_FILES ${libqtxdg_MOCS}) +endif() + + +if (NOT CMAKE_BUILD_TYPE) + set ( CMAKE_BUILD_TYPE Release ) +endif (NOT CMAKE_BUILD_TYPE) + + +add_library(${QTXDGX_LIBRARY_NAME} SHARED + ${libqtxdg_PUBLIC_H_FILES} + ${libqtxdg_PRIVATE_H_FILES} + ${libqtxdg_PRIVATE_INSTALLABLE_H_FILES} + ${libqtxdg_PRIVATE_H_FILES} + ${libqtxdg_CPP_FILES} + ${libqtxdg_CXX_FILES} +) + + +target_link_libraries(${QTXDGX_LIBRARY_NAME} + ${QTX_LIBRARIES} + ${MIMETYPES_PROVIDER_LIBRARY} +) + +set_target_properties(${QTXDGX_LIBRARY_NAME} PROPERTIES + VERSION ${MAJOR_VERSION}.${MINOR_VERSION}.${PATCH_VERSION} + SOVERSION ${MAJOR_VERSION} +) + +# create the portable headers +include(create_portable_headers) +create_portable_headers(libqtxdg_PORTABLE_HEADERS ${libqtxdg_PUBLIC_CLASSES}) + +install(TARGETS ${QTXDGX_LIBRARY_NAME} DESTINATION "${CMAKE_INSTALL_LIBDIR}") +install(FILES ${libqtxdg_PUBLIC_H_FILES} DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}/${QTXDGX_FILE_NAME}") + +if (USE_QT5) + install(FILES + ${libqtxdg_PRIVATE_INSTALLABLE_H_FILES} + DESTINATION + "${QTXDG_PRIVATE_INCLUDE_DIR}/private/qtxdg" + ) +endif() + +install(FILES "${CMAKE_CURRENT_BINARY_DIR}/${QTXDGX_FILE_NAME}-config.cmake" DESTINATION "${CMAKE_INSTALL_DATADIR}/cmake/${QTXDGX_FILE_NAME}") +install(FILES "${CMAKE_CURRENT_BINARY_DIR}/cmake/${QTXDGX_FILE_NAME}_use.cmake" DESTINATION "${CMAKE_INSTALL_DATADIR}/cmake/${QTXDGX_FILE_NAME}") +install(FILES ${libqtxdg_PORTABLE_HEADERS} DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}/${QTXDGX_FILE_NAME}") + +include(create_pkgconfig_file) +create_pkgconfig_file(${QTXDGX_LIBRARY_NAME} + ${QTXDGX_PKG_CONFIG_DESCRIPTION} + ${QTXDGX_PKG_CONFIG_REQUIRES} + ${QTXDGX_FILE_NAME} + ${QTXDG_VERSION_STRING} +) + + +if(BUILD_TESTS) + enable_testing() + add_definitions(-DQTXDG_TESTS=1) + add_subdirectory(test) +else() + message(STATUS "") + message(STATUS "For building tests use -DBUILD_TESTS=Yes option.") + message(STATUS "") +endif() + +# uninstall target +configure_file( + "${CMAKE_CURRENT_SOURCE_DIR}/cmake/cmake_uninstall.cmake.in" + "${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 ${MAJOR_VERSION}) +set (CPACK_PACKAGE_VERSION_MINOR ${MINOR_VERSION}) +set (CPACK_PACKAGE_VERSION_PATCH ${PATCH_VERSION}) +set (CPACK_GENERATOR TBZ2) +set (CPACK_SOURCE_GENERATOR TBZ2) +set (CPACK_SOURCE_IGNORE_FILES /build/;.gitignore;.*~;.git;.kdev4;temp) +include (CPack) diff --git a/COPYING b/COPYING new file mode 100644 index 0000000..a8dd823 --- /dev/null +++ b/COPYING @@ -0,0 +1,461 @@ + + GNU LESSER GENERAL PUBLIC LICENSE + Version 2.1, February 1999 + + Copyright (C) 1991, 1999 Free Software Foundation, Inc. + 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + +[This is the first released version of the Lesser GPL. It also counts + as the successor of the GNU Library Public License, version 2, hence + the version number 2.1.] + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +Licenses are intended to guarantee your freedom to share and change +free software--to make sure the software is free for all its users. + + This license, the Lesser General Public License, applies to some +specially designated software packages--typically libraries--of the +Free Software Foundation and other authors who decide to use it. You +can use it too, but we suggest you first think carefully about whether +this license or the ordinary General Public License is the better +strategy to use in any particular case, based on the explanations +below. + + When we speak of free software, we are referring to freedom of use, +not price. Our General Public Licenses are designed to make sure that +you have the freedom to distribute copies of free software (and charge +for this service if you wish); that you receive source code or can get +it if you want it; that you can change the software and use pieces of +it in new free programs; and that you are informed that you can do +these things. + + To protect your rights, we need to make restrictions that forbid +distributors to deny you these rights or to ask you to surrender these +rights. These restrictions translate to certain responsibilities for +you if you distribute copies of the library or if you modify it. + + For example, if you distribute copies of the library, whether gratis +or for a fee, you must give the recipients all the rights that we gave +you. You must make sure that they, too, receive or can get the source +code. If you link other code with the library, you must provide +complete object files to the recipients, so that they can relink them +with the library after making changes to the library and recompiling +it. And you must show them these terms so they know their rights. + + We protect your rights with a two-step method: (1) we copyright the +library, and (2) we offer you this license, which gives you legal +permission to copy, distribute and/or modify the library. + + To protect each distributor, we want to make it very clear that +there is no warranty for the free library. Also, if the library is +modified by someone else and passed on, the recipients should know +that what they have is not the original version, so that the original +author's reputation will not be affected by problems that might be +introduced by others. + + Finally, software patents pose a constant threat to the existence of +any free program. We wish to make sure that a company cannot +effectively restrict the users of a free program by obtaining a +restrictive license from a patent holder. Therefore, we insist that +any patent license obtained for a version of the library must be +consistent with the full freedom of use specified in this license. + + Most GNU software, including some libraries, is covered by the +ordinary GNU General Public License. This license, the GNU Lesser +General Public License, applies to certain designated libraries, and +is quite different from the ordinary General Public License. We use +this license for certain libraries in order to permit linking those +libraries into non-free programs. + + When a program is linked with a library, whether statically or using +a shared library, the combination of the two is legally speaking a +combined work, a derivative of the original library. The ordinary +General Public License therefore permits such linking only if the +entire combination fits its criteria of freedom. The Lesser General +Public License permits more lax criteria for linking other code with +the library. + + We call this license the "Lesser" General Public License because it +does Less to protect the user's freedom than the ordinary General +Public License. It also provides other free software developers Less +of an advantage over competing non-free programs. These disadvantages +are the reason we use the ordinary General Public License for many +libraries. However, the Lesser license provides advantages in certain +special circumstances. + + For example, on rare occasions, there may be a special need to +encourage the widest possible use of a certain library, so that it +becomes a de-facto standard. To achieve this, non-free programs must +be allowed to use the library. A more frequent case is that a free +library does the same job as widely used non-free libraries. In this +case, there is little to gain by limiting the free library to free +software only, so we use the Lesser General Public License. + + In other cases, permission to use a particular library in non-free +programs enables a greater number of people to use a large body of +free software. For example, permission to use the GNU C Library in +non-free programs enables many more people to use the whole GNU +operating system, as well as its variant, the GNU/Linux operating +system. + + Although the Lesser General Public License is Less protective of the +users' freedom, it does ensure that the user of a program that is +linked with the Library has the freedom and the wherewithal to run +that program using a modified version of the Library. + + The precise terms and conditions for copying, distribution and +modification follow. Pay close attention to the difference between a +"work based on the library" and a "work that uses the library". The +former contains code derived from the library, whereas the latter must +be combined with the library in order to run. + + GNU LESSER GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License Agreement applies to any software library or other +program which contains a notice placed by the copyright holder or +other authorized party saying it may be distributed under the terms of +this Lesser General Public License (also called "this License"). +Each licensee is addressed as "you". + + A "library" means a collection of software functions and/or data +prepared so as to be conveniently linked with application programs +(which use some of those functions and data) to form executables. + + The "Library", below, refers to any such software library or work +which has been distributed under these terms. A "work based on the +Library" means either the Library or any derivative work under +copyright law: that is to say, a work containing the Library or a +portion of it, either verbatim or with modifications and/or translated +straightforwardly into another language. (Hereinafter, translation is +included without limitation in the term "modification".) + + "Source code" for a work means the preferred form of the work for +making modifications to it. For a library, complete source code means +all the source code for all modules it contains, plus any associated +interface definition files, plus the scripts used to control +compilation and installation of the library. + + Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running a program using the Library is not restricted, and output from +such a program is covered only if its contents constitute a work based +on the Library (independent of the use of the Library in a tool for +writing it). Whether that is true depends on what the Library does +and what the program that uses the Library does. + + 1. You may copy and distribute verbatim copies of the Library's +complete source code as you receive it, in any medium, provided that +you conspicuously and appropriately publish on each copy an +appropriate copyright notice and disclaimer of warranty; keep intact +all the notices that refer to this License and to the absence of any +warranty; and distribute a copy of this License along with the +Library. + + You may charge a fee for the physical act of transferring a copy, +and you may at your option offer warranty protection in exchange for a +fee. + + 2. You may modify your copy or copies of the Library or any portion +of it, thus forming a work based on the Library, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) The modified work must itself be a software library. + + b) You must cause the files modified to carry prominent notices + stating that you changed the files and the date of any change. + + c) You must cause the whole of the work to be licensed at no + charge to all third parties under the terms of this License. + + d) If a facility in the modified Library refers to a function or a + table of data to be supplied by an application program that uses + the facility, other than as an argument passed when the facility + is invoked, then you must make a good faith effort to ensure that, + in the event an application does not supply such function or + table, the facility still operates, and performs whatever part of + its purpose remains meaningful. + + (For example, a function in a library to compute square roots has + a purpose that is entirely well-defined independent of the + application. Therefore, Subsection 2d requires that any + application-supplied function or table used by this function must + be optional: if the application does not supply it, the square + root function must still compute square roots.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Library, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Library, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote +it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Library. + +In addition, mere aggregation of another work not based on the Library +with the Library (or with a work based on the Library) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may opt to apply the terms of the ordinary GNU General Public +License instead of this License to a given copy of the Library. To do +this, you must alter all the notices that refer to this License, so +that they refer to the ordinary GNU General Public License, version 2, +instead of to this License. (If a newer version than version 2 of the +ordinary GNU General Public License has appeared, then you can specify +that version instead if you wish.) Do not make any other change in +these notices. + + Once this change is made in a given copy, it is irreversible for +that copy, so the ordinary GNU General Public License applies to all +subsequent copies and derivative works made from that copy. + + This option is useful when you wish to copy part of the code of +the Library into a program that is not a library. + + 4. You may copy and distribute the Library (or a portion or +derivative of it, under Section 2) in object code or executable form +under the terms of Sections 1 and 2 above provided that you accompany +it with the complete corresponding machine-readable source code, which +must be distributed under the terms of Sections 1 and 2 above on a +medium customarily used for software interchange. + + If distribution of object code is made by offering access to copy +from a designated place, then offering equivalent access to copy the +source code from the same place satisfies the requirement to +distribute the source code, even though third parties are not +compelled to copy the source along with the object code. + + 5. A program that contains no derivative of any portion of the +Library, but is designed to work with the Library by being compiled or +linked with it, is called a "work that uses the Library". Such a +work, in isolation, is not a derivative work of the Library, and +therefore falls outside the scope of this License. + + However, linking a "work that uses the Library" with the Library +creates an executable that is a derivative of the Library (because it +contains portions of the Library), rather than a "work that uses the +library". The executable is therefore covered by this License. +Section 6 states terms for distribution of such executables. + + When a "work that uses the Library" uses material from a header file +that is part of the Library, the object code for the work may be a +derivative work of the Library even though the source code is not. +Whether this is true is especially significant if the work can be +linked without the Library, or if the work is itself a library. The +threshold for this to be true is not precisely defined by law. + + If such an object file uses only numerical parameters, data +structure layouts and accessors, and small macros and small inline +functions (ten lines or less in length), then the use of the object +file is unrestricted, regardless of whether it is legally a derivative +work. (Executables containing this object code plus portions of the +Library will still fall under Section 6.) + + Otherwise, if the work is a derivative of the Library, you may +distribute the object code for the work under the terms of Section 6. +Any executables containing that work also fall under Section 6, +whether or not they are linked directly with the Library itself. + + 6. As an exception to the Sections above, you may also combine or +link a "work that uses the Library" with the Library to produce a +work containing portions of the Library, and distribute that work +under terms of your choice, provided that the terms permit +modification of the work for the customer's own use and reverse +engineering for debugging such modifications. + + You must give prominent notice with each copy of the work that the +Library is used in it and that the Library and its use are covered by +this License. You must supply a copy of this License. If the work +during execution displays copyright notices, you must include the +copyright notice for the Library among them, as well as a reference +directing the user to the copy of this License. Also, you must do one +of these things: + + a) Accompany the work with the complete corresponding + machine-readable source code for the Library including whatever + changes were used in the work (which must be distributed under + Sections 1 and 2 above); and, if the work is an executable linked + with the Library, with the complete machine-readable "work that + uses the Library", as object code and/or source code, so that the + user can modify the Library and then relink to produce a modified + executable containing the modified Library. (It is understood + that the user who changes the contents of definitions files in the + Library will not necessarily be able to recompile the application + to use the modified definitions.) + + b) Use a suitable shared library mechanism for linking with the + Library. A suitable mechanism is one that (1) uses at run time a + copy of the library already present on the user's computer system, + rather than copying library functions into the executable, and (2) + will operate properly with a modified version of the library, if + the user installs one, as long as the modified version is + interface-compatible with the version that the work was made with. + + c) Accompany the work with a written offer, valid for at least + three years, to give the same user the materials specified in + Subsection 6a, above, for a charge no more than the cost of + performing this distribution. + + d) If distribution of the work is made by offering access to copy + from a designated place, offer equivalent access to copy the above + specified materials from the same place. + + e) Verify that the user has already received a copy of these + materials or that you have already sent this user a copy. + + For an executable, the required form of the "work that uses the +Library" must include any data and utility programs needed for +reproducing the executable from it. However, as a special exception, +the materials to be distributed need not include anything that is +normally distributed (in either source or binary form) with the major +components (compiler, kernel, and so on) of the operating system on +which the executable runs, unless that component itself accompanies +the executable. + + It may happen that this requirement contradicts the license +restrictions of other proprietary libraries that do not normally +accompany the operating system. Such a contradiction means you cannot +use both them and the Library together in an executable that you +distribute. + + 7. You may place library facilities that are a work based on the +Library side-by-side in a single library together with other library +facilities not covered by this License, and distribute such a combined +library, provided that the separate distribution of the work based on +the Library and of the other library facilities is otherwise +permitted, and provided that you do these two things: + + a) Accompany the combined library with a copy of the same work + based on the Library, uncombined with any other library + facilities. This must be distributed under the terms of the + Sections above. + + b) Give prominent notice with the combined library of the fact + that part of it is a work based on the Library, and explaining + where to find the accompanying uncombined form of the same work. + + 8. You may not copy, modify, sublicense, link with, or distribute +the Library except as expressly provided under this License. Any +attempt otherwise to copy, modify, sublicense, link with, or +distribute the Library is void, and will automatically terminate your +rights under this License. However, parties who have received copies, +or rights, from you under this License will not have their licenses +terminated so long as such parties remain in full compliance. + + 9. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Library or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Library (or any work based on the +Library), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Library or works based on it. + + 10. Each time you redistribute the Library (or any work based on the +Library), the recipient automatically receives a license from the +original licensor to copy, distribute, link with or modify the Library +subject to these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties with +this License. + + 11. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Library at all. For example, if a patent +license would not permit royalty-free redistribution of the Library by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Library. + +If any portion of this section is held invalid or unenforceable under +any particular circumstance, the balance of the section is intended to +apply, and the section as a whole is intended to apply in other +circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 12. If the distribution and/or use of the Library is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Library under this License +may add an explicit geographical distribution limitation excluding those +countries, so that distribution is permitted only in or among +countries not thus excluded. In such case, this License incorporates +the limitation as if written in the body of this License. + + 13. The Free Software Foundation may publish revised and/or new +versions of the Lesser General Public License from time to time. +Such new versions will be similar in spirit to the present version, +but may differ in detail to address new problems or concerns. + +Each version is given a distinguishing version number. If the Library +specifies a version number of this License which applies to it and +"any later version", you have the option of following the terms and +conditions either of that version or of any later version published by +the Free Software Foundation. If the Library does not specify a +license version number, you may choose any version ever published by +the Free Software Foundation. + + 14. If you wish to incorporate parts of the Library into other free +programs whose distribution conditions are incompatible with these, +write to the author to ask for permission. For software which is +copyrighted by the Free Software Foundation, write to the Free +Software Foundation; we sometimes make exceptions for this. Our +decision will be guided by the two goals of preserving the free status +of all derivatives of our free software and of promoting the sharing +and reuse of software generally. + + NO WARRANTY + + 15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO +WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW. +EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR +OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY +KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE +LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME +THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN +WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY +AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU +FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR +CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE +LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING +RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A +FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF +SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH +DAMAGES. + + END OF TERMS AND CONDITIONS diff --git a/README b/README new file mode 100644 index 0000000..9302686 --- /dev/null +++ b/README @@ -0,0 +1,43 @@ +Overview +======== +libqtxdg is An Qt implementation of freedesktop.org xdg specifications. +It can be built with Qt4 and Qt5 + + +Dependencies +============ +Qt4 build: + Qt4 + libmagic OR QtMimeTypes + +QtMimeTypes the preffered. libmagic is deprecated and may be dropped in future +releases. +QtMimeTypes can be found at: https://qt.gitorious.org/qtplayground/mimetypes + +Qt5 build: + Qt5 + + +Configuration +============ +libqtxdg uses the CMake build system. Everything that applies to CMake also +applies here. + +Configuration options: + USE_QT5 Builds with Qt5, defaults to then environment variable + with the same name. + + USE_QTMIMETYPES It only affects the Qt4 build. Builds using QtMimeTypes. + Defaults to On. If set to OFF libmagic will be used. + + BUILD_TESTS Builds tests, defaults to OFF + +Configuration Examples: + Build with Qt5 and build self tests: + cmake -DUSE_QT5=ON -DBUILD_TESTS=ON .. + + Build with Qt4 and no tests using QtMimeTypes + cmake -DUSE_QT5=OFF .. + + Build with Qt4, no tests and using libmagic + cmake -DUSE_QT5=OFF -DUSE_QTMIMETYPES=OFF .. diff --git a/cmake/FindLibMagic.cmake b/cmake/FindLibMagic.cmake new file mode 100644 index 0000000..929226f --- /dev/null +++ b/cmake/FindLibMagic.cmake @@ -0,0 +1,23 @@ +FIND_PATH(LIBMAGIC_INCLUDE_DIR magic.h) + +FIND_LIBRARY(LIBMAGIC_LIBRARY NAMES magic) + +IF (LIBMAGIC_INCLUDE_DIR AND LIBMAGIC_LIBRARY) + SET(LIBMAGIC_FOUND TRUE) +ENDIF (LIBMAGIC_INCLUDE_DIR AND LIBMAGIC_LIBRARY) + +IF (LIBMAGIC_FOUND) + IF (NOT LibMagic_FIND_QUIETLY) + MESSAGE(STATUS "Found libmagic: ${LIBMAGIC_LIBRARY}") + MESSAGE(STATUS " includes: ${LIBMAGIC_INCLUDE_DIR}") + ENDIF (NOT LibMagic_FIND_QUIETLY) +ELSE (LIBMAGIC_FOUND) + IF (LibMagic_FIND_REQUIRED) + MESSAGE(STATUS "") + MESSAGE(STATUS "libmagic development package cannot be found. Install it, please") + MESSAGE(STATUS "For example in (open)SUSE it's file-devel package") + MESSAGE(STATUS "") + MESSAGE(FATAL_ERROR "Could not find libmagic") + ENDIF (LibMagic_FIND_REQUIRED) +ENDIF (LIBMAGIC_FOUND) + diff --git a/cmake/FindLibSuffix.cmake b/cmake/FindLibSuffix.cmake new file mode 100644 index 0000000..f7a8c3b --- /dev/null +++ b/cmake/FindLibSuffix.cmake @@ -0,0 +1,26 @@ +# some system (rpm builds) setup LIB_SUFFIX for cmake. If there is no set, try to get it from system +IF(NOT DEFINED LIB_SUFFIX AND LIB_SUFFIX_ALREADY_SET) + MESSAGE(STATUS "*********************************************************************") + MESSAGE(STATUS "LIB_SUFFIX variable is not defined. It will be autodetected now") + MESSAGE(STATUS "You can set it manually with -DLIB_SUFFIX= (64 for example)") + + # All 32bit system have empty lib suffix + if(CMAKE_SIZEOF_VOID_P EQUAL 8) + # If there is lib64 dir, set suffix to 64 + if(IS_DIRECTORY "${CMAKE_INSTALL_PREFIX}/lib64") + set(LIB_SUFFIX 64) + elseif(IS_DIRECTORY "${CMAKE_INSTALL_PREFIX}/lib") + set(LIB_SUFFIX "") + else() + message(WARNING "LIB_SUFFIX cannot be autodetected. No \"${CMAKE_INSTALL_PREFIX}/lib\" neither \"${CMAKE_INSTALL_PREFIX}/lib64\" found.") + set(LIB_SUFFIX "") + endif() + else() + set(LIB_SUFFIX "") + endif() + + set(LIB_SUFFIX_ALREADY_SET 1) + + message(STATUS "LIB_SUFFIX autodetected as '${LIB_SUFFIX}', libraries will be installed into \"${CMAKE_INSTALL_PREFIX}/lib${LIB_SUFFIX}\"") + MESSAGE(STATUS "*********************************************************************") +ENDIF(NOT DEFINED LIB_SUFFIX AND LIB_SUFFIX_ALREADY_SET) diff --git a/cmake/cmake_uninstall.cmake.in b/cmake/cmake_uninstall.cmake.in new file mode 100644 index 0000000..2037e36 --- /dev/null +++ b/cmake/cmake_uninstall.cmake.in @@ -0,0 +1,21 @@ +if(NOT EXISTS "@CMAKE_CURRENT_BINARY_DIR@/install_manifest.txt") + message(FATAL_ERROR "Cannot find install manifest: @CMAKE_CURRENT_BINARY_DIR@/install_manifest.txt") +endif(NOT EXISTS "@CMAKE_CURRENT_BINARY_DIR@/install_manifest.txt") + +file(READ "@CMAKE_CURRENT_BINARY_DIR@/install_manifest.txt" files) +string(REGEX REPLACE "\n" ";" files "${files}") +foreach(file ${files}) + message(STATUS "Uninstalling $ENV{DESTDIR}${file}") + if(IS_SYMLINK "$ENV{DESTDIR}${file}" OR EXISTS "$ENV{DESTDIR}${file}") + exec_program( + "@CMAKE_COMMAND@" ARGS "-E remove \"$ENV{DESTDIR}${file}\"" + OUTPUT_VARIABLE rm_out + RETURN_VALUE rm_retval + ) + if(NOT "${rm_retval}" STREQUAL 0) + message(FATAL_ERROR "Problem when removing $ENV{DESTDIR}${file}") + endif(NOT "${rm_retval}" STREQUAL 0) + else(IS_SYMLINK "$ENV{DESTDIR}${file}" OR EXISTS "$ENV{DESTDIR}${file}") + message(STATUS "File $ENV{DESTDIR}${file} does not exist.") + endif(IS_SYMLINK "$ENV{DESTDIR}${file}" OR EXISTS "$ENV{DESTDIR}${file}") +endforeach(file) diff --git a/cmake/create_pkgconfig_file.cmake b/cmake/create_pkgconfig_file.cmake new file mode 100644 index 0000000..92592af --- /dev/null +++ b/cmake/create_pkgconfig_file.cmake @@ -0,0 +1,36 @@ +# +# Write a pkg-config pc file for given "name" with "decription" +# Arguments: +# name: a library name (withoud "lib" prefix and "so" suffixes +# desc: a desription string +# requires: required libraries +# include_rel_dir: include directory, relative to includedir +# version: package version +# +macro (create_pkgconfig_file name desc requires include_rel_dir version) + set(_pkgfname "${CMAKE_CURRENT_BINARY_DIR}/${name}.pc") + message(STATUS "${name}: writing pkgconfig file ${_pkgfname}") + + file(WRITE "${_pkgfname}" + "# file generated by razor-qt cmake build\n" + "prefix=${CMAKE_INSTALL_PREFIX}\n" + "libdir=\${prefix}/${CMAKE_INSTALL_LIBDIR}\n" + "includedir=\${prefix}/${CMAKE_INSTALL_INCLUDEDIR}\n" + "\n" + "Name: ${name}\n" + "Description: ${desc}\n" + "Version: ${version}\n" + "Requires: ${requires}\n" + "Libs: -L\${libdir} -l${name}\n" + "Cflags: -I\${includedir} -I\${includedir}/${include_rel_dir}\n" + "\n" + ) + + # FreeBSD loves to install files to different locations + # http://www.freebsd.org/doc/handbook/dirstructure.html + if(${CMAKE_SYSTEM_NAME} STREQUAL "FreeBSD") + install(FILES ${_pkgfname} DESTINATION libdata/pkgconfig) + else() + install(FILES ${_pkgfname} DESTINATION "${CMAKE_INSTALL_LIBDIR}/pkgconfig") + endif() +endmacro() diff --git a/cmake/create_portable_headers.cmake b/cmake/create_portable_headers.cmake new file mode 100644 index 0000000..ac29c84 --- /dev/null +++ b/cmake/create_portable_headers.cmake @@ -0,0 +1,30 @@ +# Creates portable headers; e.g.: +# Creates XdgAction from xdgaction.h +# XdgAction contents: +# #include "xdgaction.h" +# +# Use: +# set(PUBLIC_CLASSES MyClass YourClass) +# create_portable_headers(PORTABLE_HEADERS ${PUBLIC_CLASSES}) +# PORTABLE_HEADER is an return value that contains the full name of the +# generated headers. + +function(create_portable_headers outfiles) + set(options) + set(oneValueArgs) + set(multiValueArgs) + + cmake_parse_arguments(_CREATE_PORTABLE_HEADERS "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN}) + set(class_list ${_CREATE_PORTABLE_HEADERS_UNPARSED_ARGUMENTS}) + foreach(f ${class_list}) + string(TOLOWER "${f}.h" _filename) + + file(WRITE "${CMAKE_CURRENT_BINARY_DIR}/${f}" + "#include \"${_filename}\"") + + list(APPEND ${outfiles} "${CMAKE_CURRENT_BINARY_DIR}/${f}") + endforeach() + + set(${outfiles} ${${outfiles}} PARENT_SCOPE) +endfunction() + diff --git a/cmake/qt5xdg-config.cmake.in b/cmake/qt5xdg-config.cmake.in new file mode 100644 index 0000000..1a338f5 --- /dev/null +++ b/cmake/qt5xdg-config.cmake.in @@ -0,0 +1,54 @@ +# - Find the QtXdg include and library dirs and define a some macros +# +# The module defines the following variables +# QTXDG_FOUND - Set to TRUE if all of the above has been found +# +# QTXDG_INCLUDE_DIR - The QtXdg include directory +# +# QTXDG_INCLUDE_DIRS - The QtXdg lib and it's dependencies include directories +# +# QTXDG_LIBRARY_DIRS - The QtXdg lib and it's dependencies linker search paths +# +# QTXDG_LIBRARY - The QtXdg library itself +# QTXDG_LIBRARIES - The QtXdg library and all it's dependencies +# +# QTXDG_USE_FILE - The variable QTXDG_USE_FILE is set which is the path +# to a CMake file that can be included to compile qtxdg +# applications and libraries. It sets up the compilation +# environment for include directories and populates a +# QTXDG_LIBRARIES variable. +# +# QTXDG_QT_LIBRARIES - The QtXdg Qt dependencies libraries +# +# Typical usage: +# option(USE_QT5 "Build using Qt5. Default off" OFF) +# if (USE_QT5) +# find_package(QT5XDG) +# else() +# find_package(QTXDG) +# endif() +# +# include(${QTXDG_USE_FILE}) +# add_executable(use-qtxdg main.cpp) +# target_link_libraries(use-qtxdg ${QTXDG_LIBRARIES}) + +set(QTXDG_INCLUDE_DIR "@QTXDG_INCLUDE_DIR@") +set(QTXDG_LIBRARY @QTXDGX_LIBRARY_NAME@) +set(QTXDG_PRIVATE_INCLUDE_DIR "@QTXDG_PRIVATE_INCLUDE_DIR@" CACHE PATH "Qt5Xdg private include dir") + +set(QTXDG_LIBRARIES ${QTXDG_LIBRARY}) +set(QTXDG_INCLUDE_DIRS "${QTXDG_INCLUDE_DIR}") +set(QTXDG_PRIVATE_INCLUDE_DIRS "${QTXDG_PRIVATE_INCLUDE_DIR}") + +set(QTXDG_LIBRARY_DIRS "@CMAKE_INSTALL_FULL_LIBDIR@") + +set(QTXDG_USE_FILE "${CMAKE_CURRENT_LIST_DIR}/@QTXDGX_FILE_NAME@_use.cmake") +set(QTXDG_FOUND 1) +set(QTXDG_QTMIMETYPES @USE_QTMIMETYPES@) + +set(QTXDG_MAJOR_VERSION @QTXDG_MAJOR_VERSION@) +set(QTXDG_MINOR_VERSION @QTXDG_MINOR_VERSION@) +set(QTXDG_PATCH_VERSION @QTXDG_PATCH_VERSION@) +set(QTXDG_VERSION @QTXDG_MAJOR_VERSION@.@QTXDG_MINOR_VERSION@.@QTXDG_PATCH_VERSION@) + +mark_as_advanced(QTXDG_LIBRARY QTXDG_INCLUDE_DIR QTXDG_PRIVATE_INCLUDE_DIR) diff --git a/cmake/qt5xdg_use.cmake b/cmake/qt5xdg_use.cmake new file mode 100644 index 0000000..1cdb58e --- /dev/null +++ b/cmake/qt5xdg_use.cmake @@ -0,0 +1,23 @@ +find_package(Qt5Widgets REQUIRED) +find_package(Qt5Xml REQUIRED) +find_package(Qt5DBus REQUIRED) + +if (QTXDG_QTMIMETYPES) + add_definitions("-DQT_MIMETYPES") +endif() + +add_definitions(${Qt5Core_DEFINITIONS}) +set(CMAKE_CXX_FLAGS + "${CMAKE_CXX_FLAGS} ${Qt5Widgets_EXECUTABLE_COMPILE_FLAGS}" +) +set(QTXDG_QT_LIBRARIES ${Qt5Widgets_LIBRARIES} ${Qt5Xml_LIBRARIES} ${Qt5DBus_LIBRARIES}) +set(QTXDG_LIBRARIES ${QTXDG_LIBRARIES} ${QTXDG_QT_LIBRARIES}) +set(QTXDG_INCLUDE_DIRS + "${QTXDG_INCLUDE_DIRS}" + "${Qt5Widgets_INCLUDE_DIRS}" + "${Qt5Xml_INCLUDE_DIRS}" + "${Qt5DBus_INCLUDE_DIRS}" +) + +link_directories("${QTXDG_LIBRARY_DIRS}") +include_directories("${QTXDG_INCLUDE_DIRS}") diff --git a/cmake/qtxdg-config.cmake.in b/cmake/qtxdg-config.cmake.in new file mode 100644 index 0000000..33fa866 --- /dev/null +++ b/cmake/qtxdg-config.cmake.in @@ -0,0 +1,53 @@ +# - Find the QtXdg include and library dirs and define a some macros +# +# The module defines the following variables +# QTXDG_FOUND - Set to TRUE if all of the above has been found +# +# QTXDG_INCLUDE_DIR - The QtXdg include directory +# +# QTXDG_INCLUDE_DIRS - The QtXdg lib and it's dependencies include directories +# +# QTXDG_LIBRARY_DIRS - The QtXdg lib and it's dependencies linker search paths +# +# QTXDG_LIBRARY - The QtXdg library itself +# QTXDG_LIBRARIES - The QtXdg library and all it's dependencies +# +# QTXDG_USE_FILE - The variable QTXDG_USE_FILE is set which is the path +# to a CMake file that can be included to compile qtxdg +# applications and libraries. It sets up the compilation +# environment for include directories and populates a +# QTXDG_LIBRARIES variable. +# +# QTXDG_QT_LIBRARIES - The QtXdg Qt dependencies libraries +# +# Typical usage: +# option(USE_QT5 "Build using Qt5. Default off" OFF) +# if (USE_QT5) +# find_package(QT5XDG) +# else() +# find_package(QTXDG) +# endif() +# +# include(${QTXDG_USE_FILE}) +# add_executable(use-qtxdg main.cpp) +# target_link_libraries(use-qtxdg ${QTXDG_LIBRARIES}) + +set(QTXDG_INCLUDE_DIR "@QTXDG_INCLUDE_DIR@") +set(QTXDG_PRIVATE_INCLUDE_DIR "@QTXDG_PRIVATE_INCLUDE_DIR@") +set(QTXDG_LIBRARY @QTXDGX_LIBRARY_NAME@) + +set(QTXDG_LIBRARIES ${QTXDG_LIBRARY}) +set(QTXDG_INCLUDE_DIRS "${QTXDG_INCLUDE_DIR}") + +set(QTXDG_LIBRARY_DIRS "@CMAKE_INSTALL_FULL_LIBDIR@") + +set(QTXDG_USE_FILE "${CMAKE_CURRENT_LIST_DIR}/@QTXDGX_FILE_NAME@_use.cmake") +set(QTXDG_FOUND 1) +set(QTXDG_QTMIMETYPES @USE_QTMIMETYPES@) + +set(QTXDG_MAJOR_VERSION @QTXDG_MAJOR_VERSION@) +set(QTXDG_MINOR_VERSION @QTXDG_MINOR_VERSION@) +set(QTXDG_PATCH_VERSION @QTXDG_PATCH_VERSION@) +set(QTXDG_VERSION @QTXDG_MAJOR_VERSION@.@QTXDG_MINOR_VERSION@.@QTXDG_PATCH_VERSION@) + +mark_as_advanced(QTXDG_LIBRARY QTXDG_INCLUDE_DIR) diff --git a/cmake/qtxdg_use.cmake b/cmake/qtxdg_use.cmake new file mode 100644 index 0000000..16d4a6a --- /dev/null +++ b/cmake/qtxdg_use.cmake @@ -0,0 +1,42 @@ +# - Find the Razor-qt include and library dirs and define a some macros +# + + +find_package(Qt4 REQUIRED QtCore QtGui QtXml QtDBus QUIET) +include(${QT_USE_FILE}) + +set(QTXDG_QT_LIBRARIES + ${QT_QTCORE_LIBRARY} + ${QT_QTGUI_LIBRARY} + ${QT_QTXML_LIBRARY} + ${QT_DBUS_LIBRARY} +) + +set(QTXDG_LIBRARIES ${QTXDG_LIBRARY} ${QTXDG_QT_LIBRARIES}) + +set(QTXDG_INCLUDE_DIRS + ${QTXDG_INCLUDE_DIRS} + ${QT_QTCORE_INCLUDE_DIR} + ${QT_QTGUI_INCLUDE_DIR} + ${QT_QTXML_INCLUDE_DIR} + ${QT_QTDBUS_INCLUDE_DIR} +) + +set(QTXDG_DEFINITIONS ${QT_DEFINITIONS}) + +if (QTXDG_QTMIMETYPES) + find_package(PkgConfig) + pkg_check_modules(QTMIMETYPES REQUIRED + QtMimeTypes + ) + + set(QTXDG_LIBRARIES ${QTXDG_LIBRARY} ${QTMIMETYPES_LIBRARIES}) + set(QTXDG_LIBRARY_DIRS ${QTXDG_LIBRARY_DIRS} ${QTMIMETYPES_LIBRARY_DIRS}) + set(QTXDG_DEFINITIONS ${QTXDG_DEFINITIONS} "-DQT_MIMETYPES") + include_directories(${QTXDG_INCLUDE_DIR} ${QTMIMETYPES_INCLUDE_DIRS}) + link_directories(${QTXDG_LIBRARY_DIRS}) + add_definitions("-DQT_MIMETYPES") +else() + include_directories(${QTXDG_INCLUDE_DIR} ${QTMIMETYPES_INCLUDE_DIRS}) + link_directories(${QTXDG_LIBRARY_DIRS}) +endif() diff --git a/desktopenvironment_p.cpp b/desktopenvironment_p.cpp new file mode 100644 index 0000000..215560f --- /dev/null +++ b/desktopenvironment_p.cpp @@ -0,0 +1,11 @@ + +#include + +static inline QByteArray detectDesktopEnvironment() +{ + const QByteArray _desktop = qgetenv("XDG_CURRENT_DESKTOP"); + if (!_desktop.isEmpty()) { + return _desktop.toUpper(); + } + return QByteArray("UNKNOWN"); +} diff --git a/examples/use-qtxdg/CMakeLists.txt b/examples/use-qtxdg/CMakeLists.txt new file mode 100644 index 0000000..0674766 --- /dev/null +++ b/examples/use-qtxdg/CMakeLists.txt @@ -0,0 +1,18 @@ +project(use-qtxdg) +cmake_minimum_required(VERSION 2.6) + +option(USE_QT5 "Build using Qt5. Default off" OFF) + +if (USE_QT5) + find_package(QT5XDG) +else() + find_package(QTXDG) +endif() + +include(${QTXDG_USE_FILE}) + +add_executable(use-qtxdg main.cpp) + +# The QTXDG_QT_LIBRARIES variable contains the needed Qt libraries. They are +# set taking in account the choosed Qt version. +target_link_libraries(use-qtxdg ${QTXDG_QT_LIBRARIES} ${QTXDG_LIBRARIES}) diff --git a/examples/use-qtxdg/README b/examples/use-qtxdg/README new file mode 100644 index 0000000..9f9d687 --- /dev/null +++ b/examples/use-qtxdg/README @@ -0,0 +1,9 @@ +An example of how to write an CMakeLists.txt to use libqtxdg in a portable +way. + +Building with Qt4 is the default. Ex: +cmake.. + +To build with Qt5 just pass to set the USE_QT5 to Yes. Ex: + +cmake -DUSE_QT5=On .. diff --git a/examples/use-qtxdg/main.cpp b/examples/use-qtxdg/main.cpp new file mode 100644 index 0000000..51dedb0 --- /dev/null +++ b/examples/use-qtxdg/main.cpp @@ -0,0 +1,8 @@ +#include +#include + +int main(int argc, char **argv) +{ + qDebug() << XdgDirs::dataDirs(); + return 0; +} diff --git a/qiconfix/qiconloader.cpp b/qiconfix/qiconloader.cpp new file mode 100644 index 0000000..5a868c9 --- /dev/null +++ b/qiconfix/qiconloader.cpp @@ -0,0 +1,677 @@ +/**************************************************************************** +** +** Copyright (C) 2014 Digia Plc and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/legal +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL21$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Digia. For licensing terms and +** conditions see http://qt.digia.com/licensing. For further information +** use the contact form at http://qt.digia.com/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 or version 3 as published by the Free +** Software Foundation and appearing in the file LICENSE.LGPLv21 and +** LICENSE.LGPLv3 included in the packaging of this file. Please review the +** following information to ensure the GNU Lesser General Public License +** requirements will be met: https://www.gnu.org/licenses/lgpl.html and +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Digia gives you certain additional +** rights. These rights are described in the Digia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ +#ifndef QT_NO_ICON +#include "qiconloader_p.h" + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef Q_WS_MAC +#include +#endif + +#include + +//QT_BEGIN_NAMESPACE + +namespace QtXdg { + +Q_GLOBAL_STATIC(QIconLoader, iconLoaderInstance) + +/* Theme to use in last resort, if the theme does not have the icon, neither the parents */ +static QString fallbackTheme() +{ + if (const QPlatformTheme *theme = QGuiApplicationPrivate::platformTheme()) { + const QVariant themeHint = theme->themeHint(QPlatformTheme::SystemIconFallbackThemeName); + if (themeHint.isValid()) + return themeHint.toString(); + } + return QString("hicolor"); +} + +QIconLoader::QIconLoader() : + m_themeKey(1), m_supportsSvg(false), m_initialized(false) +{ +} + +// We lazily initialize the loader to make static icons +// work. Though we do not officially support this. + +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(); + } + return QIcon::themeSearchPaths(); +} + +#ifndef QT_NO_LIBRARY +//extern QFactoryLoader *qt_iconEngineFactoryLoader(); // qicon.cpp +#endif + +void QIconLoader::ensureInitialized() +{ + 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 + } +} + +QIconLoader *QIconLoader::instance() +{ + iconLoaderInstance()->ensureInitialized(); + return iconLoaderInstance(); +} + +// Queries the system theme and invalidates existing +// icons if the theme has changed. +void QIconLoader::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 QIconLoader::setThemeName(const QString &themeName) +{ + m_userTheme = themeName; + invalidateKey(); +} + +void QIconLoader::setThemeSearchPath(const QStringList &searchPaths) +{ + m_iconDirs = searchPaths; + themeList.clear(); + invalidateKey(); +} + +QStringList QIconLoader::themeSearchPaths() const +{ + if (m_iconDirs.isEmpty()) { + m_iconDirs = systemIconSearchPaths(); + // Always add resource directory as search path + m_iconDirs.append(QLatin1String(":/icons")); + } + return m_iconDirs; +} + +QIconTheme::QIconTheme(const QString &themeName) + : m_valid(false) +{ + QFile themeIndex; + + QStringList iconDirs = QIcon::themeSearchPaths(); + for ( int i = 0 ; i < iconDirs.size() ; ++i) { + QDir iconDir(iconDirs[i]); + QString themeDir = iconDir.path() + QLatin1Char('/') + themeName; + themeIndex.setFileName(themeDir + QLatin1String("/index.theme")); + if (themeIndex.exists()) { + m_contentDir = themeDir; + m_valid = true; + + QStringList themeSearchPaths = QIcon::themeSearchPaths(); + foreach (QString path, themeSearchPaths) + { + if (!path.startsWith(':') && QFileInfo(path).isDir()) + m_contentDirs.append(path + QLatin1Char('/') + themeName); + } + + break; + } + } +#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(); + 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); + QIconDirInfo dirInfo(directoryKey); + dirInfo.size = size; + QString type = indexReader.value(directoryKey + + QLatin1String("/Type") + ).toString(); + + if (type == QLatin1String("Fixed")) + dirInfo.type = QIconDirInfo::Fixed; + else if (type == QLatin1String("Scalable")) + dirInfo.type = QIconDirInfo::Scalable; + else + dirInfo.type = QIconDirInfo::Threshold; + + dirInfo.threshold = indexReader.value(directoryKey + + QLatin1String("/Threshold"), + 2).toInt(); + + dirInfo.minSize = indexReader.value(directoryKey + + QLatin1String("/MinSize"), + size).toInt(); + + dirInfo.maxSize = indexReader.value(directoryKey + + QLatin1String("/MaxSize"), + size).toInt(); + m_keyList.append(dirInfo); + } + } + } + + // Parent themes provide fallbacks for missing icons + m_parents = indexReader.value( + QLatin1String("Icon Theme/Inherits")).toStringList(); + m_parents.removeAll(QString()); + + // Ensure a default platform fallback for all themes + if (m_parents.isEmpty()) { + const QString fallback = fallbackTheme(); + 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 +} + +QThemeIconEntries QIconLoader::findIconHelper(const QString &themeName, + const QString &iconName, + QStringList &visited) const +{ + QThemeIconEntries entries; + Q_ASSERT(!themeName.isEmpty()); + + QPixmap pixmap; + + // Used to protect against potential recursions + visited << themeName; + + QIconTheme theme = themeList.value(themeName); + if (!theme.isValid()) { + theme = QIconTheme(themeName); + if (!theme.isValid()) + theme = QIconTheme(fallbackTheme()); + + themeList.insert(themeName, theme); + } + + QStringList contentDirs = theme.contentDirs(); + const QVector subDirs = theme.keyList(); + + const QString svgext(QLatin1String(".svg")); + const QString pngext(QLatin1String(".png")); + const QString xpmext(QLatin1String(".xpm")); + + // Add all relevant files + for (int i = 0; i < subDirs.size() ; ++i) { + const QIconDirInfo &dirInfo = subDirs.at(i); + QString subdir = dirInfo.path; + + foreach (QString contentDir, contentDirs) { + QDir currentDir(contentDir + '/' + subdir); + + 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 + entries.prepend(iconEntry); + } else if (m_supportsSvg && + currentDir.exists(iconName + svgext)) { + ScalableEntry *iconEntry = new ScalableEntry; + iconEntry->dir = dirInfo; + iconEntry->filename = currentDir.filePath(iconName + svgext); + entries.append(iconEntry); + break; + } 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 + entries.append(iconEntry); + break; + } + } + } + + if (entries.isEmpty()) { + const QStringList parents = theme.parents(); + // Search recursively through inherited themes + for (int i = 0 ; i < parents.size() ; ++i) { + + const QString parentTheme = parents.at(i).trimmed(); + + if (!visited.contains(parentTheme)) // guard against recursion + entries = findIconHelper(parentTheme, iconName, visited); + + if (!entries.isEmpty()) // success + break; + } + } + + /********************************************************************* + 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 (entries.isEmpty()) { + const QString pixmaps(QLatin1String("/usr/share/pixmaps")); + + QDir currentDir(pixmaps); + QIconDirInfo 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 + entries.prepend(iconEntry); + } else if (m_supportsSvg && + currentDir.exists(iconName + svgext)) { + ScalableEntry *iconEntry = new ScalableEntry; + iconEntry->dir = dirInfo; + iconEntry->filename = currentDir.filePath(iconName + svgext); + 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 + entries.append(iconEntry); + } + + } +#endif + + if (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 + entries.prepend(iconEntry); + } else if (m_supportsSvg && + currentDir.exists(iconName + svgext)) { + ScalableEntry *iconEntry = new ScalableEntry; + iconEntry->filename = currentDir.filePath(iconName + svgext); + 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 + entries.append(iconEntry); + break; + } + } + } + return entries; +} + +QThemeIconEntries QIconLoader::loadIcon(const QString &name) const +{ + if (!themeName().isEmpty()) { + QStringList visited; + return findIconHelper(themeName(), name, visited); + } + + return QThemeIconEntries(); +} + + +// -------- Icon Loader Engine -------- // + + +QIconLoaderEngineFixed::QIconLoaderEngineFixed(const QString& iconName) + : m_iconName(iconName), m_key(0) +{ +} + +QIconLoaderEngineFixed::~QIconLoaderEngineFixed() +{ + qDeleteAll(m_entries); +} + +QIconLoaderEngineFixed::QIconLoaderEngineFixed(const QIconLoaderEngineFixed &other) + : QIconEngine(other), + m_iconName(other.m_iconName), + m_key(0) +{ +} + +QIconEngine *QIconLoaderEngineFixed::clone() const +{ + return new QIconLoaderEngineFixed(*this); +} + +bool QIconLoaderEngineFixed::read(QDataStream &in) { + in >> m_iconName; + return true; +} + +bool QIconLoaderEngineFixed::write(QDataStream &out) const +{ + out << m_iconName; + return true; +} + +bool QIconLoaderEngineFixed::hasIcon() const +{ + return !(m_entries.isEmpty()); +} + +// Lazily load the icon +void QIconLoaderEngineFixed::ensureLoaded() +{ + if (!(QIconLoader::instance()->themeKey() == m_key)) { + + qDeleteAll(m_entries); + + m_entries = QIconLoader::instance()->loadIcon(m_iconName); + m_key = QIconLoader::instance()->themeKey(); + } +} + +void QIconLoaderEngineFixed::paint(QPainter *painter, const QRect &rect, + QIcon::Mode mode, QIcon::State state) +{ + QSize pixmapSize = rect.size(); +#if defined(Q_WS_MAC) + pixmapSize *= qt_mac_get_scalefactor(); +#endif + painter->drawPixmap(rect, pixmap(pixmapSize, mode, state)); +} + +/* + * This algorithm is defined by the freedesktop spec: + * http://standards.freedesktop.org/icon-theme-spec/icon-theme-spec-latest.html + */ +static bool directoryMatchesSize(const QIconDirInfo &dir, int iconsize) +{ + if (dir.type == QIconDirInfo::Fixed) { + return dir.size == iconsize; + + } else if (dir.type == QIconDirInfo::Scalable) { + return dir.size <= dir.maxSize && + iconsize >= dir.minSize; + + } else if (dir.type == QIconDirInfo::Threshold) { + return iconsize >= dir.size - dir.threshold && + iconsize <= dir.size + dir.threshold; + } + + Q_ASSERT(1); // Not a valid value + return false; +} + +/* + * This algorithm is defined by the freedesktop spec: + * http://standards.freedesktop.org/icon-theme-spec/icon-theme-spec-latest.html + */ +static int directorySizeDistance(const QIconDirInfo &dir, int iconsize) +{ + if (dir.type == QIconDirInfo::Fixed) { + return qAbs(dir.size - iconsize); + + } else if (dir.type == QIconDirInfo::Scalable) { + if (iconsize < dir.minSize) + return dir.minSize - iconsize; + else if (iconsize > dir.maxSize) + return iconsize - dir.maxSize; + else + return 0; + + } 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; + } + + Q_ASSERT(1); // Not a valid value + return INT_MAX; +} + +QIconLoaderEngineEntry *QIconLoaderEngineFixed::entryForSize(const QSize &size) +{ + int iconsize = qMin(size.width(), size.height()); + + // Note that m_entries are sorted so that png-files + // come first + + const int numEntries = m_entries.size(); + + // Search for exact matches first + for (int i = 0; i < numEntries; ++i) { + QIconLoaderEngineEntry *entry = m_entries.at(i); + if (directoryMatchesSize(entry->dir, iconsize)) { + return entry; + } + } + + // Find the minimum distance icon + int minimalSize = INT_MAX; + QIconLoaderEngineEntry *closestMatch = 0; + for (int i = 0; i < numEntries; ++i) { + QIconLoaderEngineEntry *entry = m_entries.at(i); + int distance = directorySizeDistance(entry->dir, iconsize); + if (distance < minimalSize) { + minimalSize = distance; + closestMatch = entry; + } + } + return closestMatch; +} + +/* + * Returns the actual icon size. For scalable svg's this is equivalent + * to the requested size. Otherwise the closest match is returned but + * we can never return a bigger size than the requested size. + * + */ +QSize QIconLoaderEngineFixed::actualSize(const QSize &size, QIcon::Mode mode, + QIcon::State state) +{ + ensureLoaded(); + + QIconLoaderEngineEntry *entry = entryForSize(size); + if (entry) { + const QIconDirInfo &dir = entry->dir; + if (dir.type == QIconDirInfo::Scalable) + return size; + else { + int result = qMin(dir.size, qMin(size.width(), size.height())); + return QSize(result, result); + } + } + return QIconEngine::actualSize(size, mode, state); +} + +QPixmap PixmapEntry::pixmap(const QSize &size, QIcon::Mode mode, QIcon::State state) +{ + Q_UNUSED(state); + + // Ensure that basePixmap is lazily initialized before generating the + // key, otherwise the cache key is not unique + if (basePixmap.isNull()) + basePixmap.load(filename); + + QSize actualSize = basePixmap.size(); + if (!actualSize.isNull() && (actualSize.width() > size.width() || actualSize.height() > size.height())) + actualSize.scale(size, Qt::KeepAspectRatio); + + QString key = QLatin1String("$qt_theme_") + % HexString(basePixmap.cacheKey()) + % HexString(mode) + % HexString(QGuiApplication::palette().cacheKey()) + % HexString(actualSize.width()) + % HexString(actualSize.height()); + + QPixmap cachedPixmap; + if (QPixmapCache::find(key, &cachedPixmap)) { + return cachedPixmap; + } else { + if (basePixmap.size() != actualSize) + cachedPixmap = basePixmap.scaled(actualSize, Qt::IgnoreAspectRatio, Qt::SmoothTransformation); + else + cachedPixmap = basePixmap; + if (QGuiApplication *guiApp = qobject_cast(qApp)) + cachedPixmap = static_cast(QObjectPrivate::get(guiApp))->applyQIconStyleHelper(mode, cachedPixmap); + QPixmapCache::insert(key, cachedPixmap); + } + return cachedPixmap; +} + +QPixmap ScalableEntry::pixmap(const QSize &size, QIcon::Mode mode, QIcon::State state) +{ + if (svgIcon.isNull()) + svgIcon = QIcon(filename); + + // Simply reuse svg icon engine + return svgIcon.pixmap(size, mode, state); +} + +QPixmap QIconLoaderEngineFixed::pixmap(const QSize &size, QIcon::Mode mode, + QIcon::State state) +{ + ensureLoaded(); + + QIconLoaderEngineEntry *entry = entryForSize(size); + if (entry) + return entry->pixmap(size, mode, state); + + return QPixmap(); +} + +QString QIconLoaderEngineFixed::key() const +{ + return QLatin1String("QIconLoaderEngineFixed"); +} + +void QIconLoaderEngineFixed::virtual_hook(int id, void *data) +{ + ensureLoaded(); + + switch (id) { + case QIconEngine::AvailableSizesHook: + { + QIconEngine::AvailableSizesArgument &arg + = *reinterpret_cast(data); + const int N = m_entries.size(); + QList sizes; + sizes.reserve(N); + + // Gets all sizes from the DirectoryInfo entries + for (int i = 0; i < N; ++i) { + int size = m_entries.at(i)->dir.size; + sizes.append(QSize(size, size)); + } + arg.sizes.swap(sizes); // commit + } + break; + case QIconEngine::IconNameHook: + { + QString &name = *reinterpret_cast(data); + name = m_iconName; + } + break; + default: + QIconEngine::virtual_hook(id, data); + } +} + +} // QtXdg + +//QT_END_NAMESPACE + +#endif //QT_NO_ICON diff --git a/qiconfix/qiconloader_p.h b/qiconfix/qiconloader_p.h new file mode 100644 index 0000000..6610ed4 --- /dev/null +++ b/qiconfix/qiconloader_p.h @@ -0,0 +1,199 @@ +/**************************************************************************** +** +** Copyright (C) 2014 Digia Plc and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/legal +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL21$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Digia. For licensing terms and +** conditions see http://qt.digia.com/licensing. For further information +** use the contact form at http://qt.digia.com/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 or version 3 as published by the Free +** Software Foundation and appearing in the file LICENSE.LGPLv21 and +** LICENSE.LGPLv3 included in the packaging of this file. Please review the +** following information to ensure the GNU Lesser General Public License +** requirements will be met: https://www.gnu.org/licenses/lgpl.html and +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Digia gives you certain additional +** rights. These rights are described in the Digia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QICONLOADER_P_H +#define QICONLOADER_P_H + +#include + +#ifndef QT_NO_ICON +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include +#include +#include +#include +#include +#include +#include +#include + +//QT_BEGIN_NAMESPACE + +namespace QtXdg { + +class QIconLoader; + +struct QIconDirInfo +{ + enum Type { Fixed, Scalable, Threshold }; + QIconDirInfo(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 : 4; +}; + +class QIconLoaderEngineEntry + { +public: + virtual ~QIconLoaderEngineEntry() {} + virtual QPixmap pixmap(const QSize &size, + QIcon::Mode mode, + QIcon::State state) = 0; + QString filename; + QIconDirInfo dir; + static int count; +}; + +struct ScalableEntry : public QIconLoaderEngineEntry +{ + QPixmap pixmap(const QSize &size, QIcon::Mode mode, QIcon::State state) Q_DECL_OVERRIDE; + QIcon svgIcon; +}; + +struct PixmapEntry : public QIconLoaderEngineEntry +{ + QPixmap pixmap(const QSize &size, QIcon::Mode mode, QIcon::State state) Q_DECL_OVERRIDE; + QPixmap basePixmap; +}; + +typedef QList QThemeIconEntries; + +//class QIconLoaderEngine : public QIconEngine +class QIconLoaderEngineFixed : public QIconEngine +{ +public: + QIconLoaderEngineFixed(const QString& iconName = QString()); + ~QIconLoaderEngineFixed(); + + 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; + +private: + QString key() const; + bool hasIcon() const; + void ensureLoaded(); + void virtual_hook(int id, void *data); + QIconLoaderEngineEntry *entryForSize(const QSize &size); + QIconLoaderEngineFixed(const QIconLoaderEngineFixed &other); + QThemeIconEntries m_entries; + QString m_iconName; + uint m_key; + + friend class QIconLoader; +}; + +class QIconTheme +{ +public: + QIconTheme(const QString &name); + QIconTheme() : m_valid(false) {} + QStringList parents() { return m_parents; } + QVector keyList() { return m_keyList; } + QString contentDir() { return m_contentDir; } + QStringList contentDirs() { return m_contentDirs; } + bool isValid() { return m_valid; } + +private: + QString m_contentDir; + QStringList m_contentDirs; + QVector m_keyList; + QStringList m_parents; + bool m_valid; +}; + +class Q_GUI_EXPORT QIconLoader +{ +public: + QIconLoader(); + QThemeIconEntries 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; + QIconDirInfo dirInfo(int dirindex); + static QIconLoader *instance(); + void updateSystemTheme(); + void invalidateKey() { m_themeKey++; } + void ensureInitialized(); + +private: + QThemeIconEntries 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; +}; + +} // QtXdg + +// Note: class template specialization of 'QTypeInfo' must occur at +// global scope +Q_DECLARE_TYPEINFO(QtXdg::QIconDirInfo, Q_MOVABLE_TYPE); + +//QT_END_NAMESPACE + +#endif // QT_NO_ICON + +#endif // QICONLOADER_P_H diff --git a/qiconfix/qiconloader_p_qt4.h b/qiconfix/qiconloader_p_qt4.h new file mode 100644 index 0000000..6147af3 --- /dev/null +++ b/qiconfix/qiconloader_p_qt4.h @@ -0,0 +1,216 @@ +/* BEGIN_COMMON_COPYRIGHT_HEADER + * (c)LGPL2 + */ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** GNU Lesser General Public License Usage +** This file may be used under the terms of the GNU Lesser General Public +** License version 2.1 as published by the Free Software Foundation and +** appearing in the file LICENSE.LGPL included in the packaging of this +** file. Please review the following information to ensure the GNU Lesser +** General Public License version 2.1 requirements will be met: +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU General +** Public License version 3.0 as published by the Free Software Foundation +** and appearing in the file LICENSE.GPL included in the packaging of this +** file. Please review the following information to ensure the GNU General +** Public License version 3.0 requirements will be met: +** http://www.gnu.org/copyleft/gpl.html. +** +** Other Usage +** Alternatively, this file may be used in accordance with the terms and +** conditions contained in a signed written agreement between you and Nokia. +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ +//END_COMMON_COPYRIGHT_HEADER + +/************************************************************************* + It's fixes the following bugs: + * QIcon::fromTheme returns pixmaps that are bigger than requested + https://bugreports.qt.nokia.com/browse/QTBUG-17953 + + * Qt should honor /usr/share/pixmaps as a valid icon directory on Linux + https://bugreports.qt.nokia.com/browse/QTBUG-12874 + + *************************************************************************/ + +#ifndef QDESKTOPICON_P_H +#define QDESKTOPICON_P_H + +#ifndef QT_NO_ICON +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include +#include +#include +//#include "qt/qicon_p.h" +//#include "qt/qfactoryloader_p.h" +#include + +namespace QtXdg { + +class QIconLoader; + +struct QIconDirInfo +{ + enum Type { Fixed, Scalable, Threshold }; + QIconDirInfo(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 : 4; +}; + +class QIconLoaderEngineEntry + { +public: + virtual ~QIconLoaderEngineEntry() {} + virtual QPixmap pixmap(const QSize &size, + QIcon::Mode mode, + QIcon::State state) = 0; + QString filename; + QIconDirInfo dir; + static int count; +}; + +struct ScalableEntry : public QIconLoaderEngineEntry +{ + QPixmap pixmap(const QSize &size, QIcon::Mode mode, QIcon::State state); + QIcon svgIcon; +}; + +struct PixmapEntry : public QIconLoaderEngineEntry +{ + QPixmap pixmap(const QSize &size, QIcon::Mode mode, QIcon::State state); + QPixmap basePixmap; +}; + +typedef QList QThemeIconEntries; + +#if QT_VERSION < QT_VERSION_CHECK(5,0,0) +class QIconLoaderEngineFixed : public QIconEngineV2 +#else +class QIconLoaderEngineFixed : public QIconEngine +#endif +{ +public: + QIconLoaderEngineFixed(const QString& iconName = QString()); + ~QIconLoaderEngineFixed(); + + 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); +#if QT_VERSION < QT_VERSION_CHECK(5,0,0) + QIconEngineV2 *clone() const; +#else + QIconEngine *clone() const; +#endif + bool read(QDataStream &in); + bool write(QDataStream &out) const; + +private: + QString key() const; + bool hasIcon() const; + void ensureLoaded(); + void virtual_hook(int id, void *data); + QIconLoaderEngineEntry *entryForSize(const QSize &size); + QIconLoaderEngineFixed(const QIconLoaderEngineFixed &other); + QThemeIconEntries m_entries; + QString m_iconName; + uint m_key; + + friend class QIconLoader; +}; + +class QIconTheme +{ +public: + QIconTheme(const QString &name); + QIconTheme() : m_valid(false) {} + QStringList parents() { return m_parents; } + QList keyList() { return m_keyList; } + QString contentDir() { return m_contentDir; } + QStringList contentDirs() { return m_contentDirs; } + bool isValid() { return m_valid; } + +private: + QString m_contentDir; + QStringList m_contentDirs; + QList m_keyList; + QStringList m_parents; + bool m_valid; +}; + +class QIconLoader : public QObject +{ +public: + QIconLoader(); + QThemeIconEntries 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; + QIconDirInfo dirInfo(int dirindex); + static QIconLoader *instance(); + void updateSystemTheme(); + void invalidateKey() { m_themeKey++; } + void ensureInitialized(); + +private: + QThemeIconEntries 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; +}; + +} // QtXdg + +#endif // QDESKTOPICON_P_H + +#endif //QT_NO_ICON diff --git a/qiconfix/qiconloader_qt4.cpp b/qiconfix/qiconloader_qt4.cpp new file mode 100644 index 0000000..b508e5f --- /dev/null +++ b/qiconfix/qiconloader_qt4.cpp @@ -0,0 +1,737 @@ +/* BEGIN_COMMON_COPYRIGHT_HEADER + * (c)LGPL2 + */ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** GNU Lesser General Public License Usage +** This file may be used under the terms of the GNU Lesser General Public +** License version 2.1 as published by the Free Software Foundation and +** appearing in the file LICENSE.LGPL included in the packaging of this +** file. Please review the following information to ensure the GNU Lesser +** General Public License version 2.1 requirements will be met: +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU General +** Public License version 3.0 as published by the Free Software Foundation +** and appearing in the file LICENSE.GPL included in the packaging of this +** file. Please review the following information to ensure the GNU General +** Public License version 3.0 requirements will be met: +** http://www.gnu.org/copyleft/gpl.html. +** +** Other Usage +** Alternatively, this file may be used in accordance with the terms and +** conditions contained in a signed written agreement between you and Nokia. +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ +//END_COMMON_COPYRIGHT_HEADER + +#ifndef QT_NO_ICON +#include "qiconloader_p_qt4.h" + +//#include "qt/qapplication_p.h" +//#include +//#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +//#ifdef Q_WS_MAC +//#include +//#endif + +//#ifdef Q_WS_X11 +//#include "qt/qt_x11_p.h" +//#endif +#include + +#if QT_VERSION < 0x040700 +#include +#endif + +namespace QtXdg { + +Q_GLOBAL_STATIC(QIconLoader, iconLoaderInstance) + +/* Theme to use in last resort, if the theme does not have the icon, neither the parents */ +/*static QString fallbackTheme() +{ +#ifdef Q_WS_X11 + if (X11->desktopEnvironment == DE_GNOME) { + return QLatin1String("gnome"); + } else if (X11->desktopEnvironment == DE_KDE) { + return X11->desktopVersion >= 4 + ? QString::fromLatin1("oxygen") + : QString::fromLatin1("crystalsvg"); + } else { + return QLatin1String("hicolor"); + } +#endif + return QString(); +} +*/ +QIconLoader::QIconLoader() : + m_themeKey(1), m_supportsSvg(false), m_initialized(false) +{ +} + +// We lazily initialize the loader to make static icons +// work. Though we do not officially support this. +void QIconLoader::ensureInitialized() +{ + if (!m_initialized) { + m_initialized = true; + + Q_ASSERT(qApp); + + m_systemTheme = QIcon::themeName(); + +#ifndef QT_NO_LIBRARY +// QFactoryLoader iconFactoryLoader(QIconEngineFactoryInterfaceV2_iid, +// QLatin1String("/iconengines"), +// Qt::CaseInsensitive); +// if (iconFactoryLoader.keys().contains(QLatin1String("svg"))) + m_supportsSvg = true; +#endif //QT_NO_LIBRARY + } +} + +QIconLoader *QIconLoader::instance() +{ + return iconLoaderInstance(); +} + +// Queries the system theme and invalidates existing +// icons if the theme has changed. +void QIconLoader::updateSystemTheme() +{ + // Only change if this is not explicitly set by the user + if (m_userTheme.isEmpty()) { + QString theme = QIcon::themeName();//qt_guiPlatformPlugin()->systemIconThemeName(); + //if (theme.isEmpty()) + // theme = fallbackTheme(); + if (theme != m_systemTheme) { + m_systemTheme = theme; + invalidateKey(); + } + } +} + +void QIconLoader::setThemeName(const QString &themeName) +{ + m_userTheme = themeName; + invalidateKey(); +} + +void QIconLoader::setThemeSearchPath(const QStringList &searchPaths) +{ + m_iconDirs = searchPaths; + themeList.clear(); + invalidateKey(); +} + +QStringList QIconLoader::themeSearchPaths() const +{ + if (m_iconDirs.isEmpty()) + { + m_iconDirs = QIcon::themeSearchPaths();//qt_guiPlatformPlugin()->iconThemeSearchPaths(); + // Always add resource directory as search path + m_iconDirs.append(QLatin1String(":/icons")); + } + return m_iconDirs; +} + + +QIconTheme::QIconTheme(const QString &themeName) + : m_valid(false) +{ + + QFile themeIndex; + + QList keyList; + QStringList iconDirs = QIcon::themeSearchPaths(); + for ( int i = 0 ; i < iconDirs.size() ; ++i) { + QDir iconDir(iconDirs[i]); + QString themeDir = iconDir.path() + QLatin1Char('/') + themeName; + themeIndex.setFileName(themeDir + QLatin1String("/index.theme")); + if (themeIndex.exists()) { + m_contentDir = themeDir; + m_valid = true; + + QStringList themeSearchPaths = QIcon::themeSearchPaths(); + foreach (QString path, themeSearchPaths) + { + if (!path.startsWith(':') && QFileInfo(path).isDir()) + m_contentDirs.append(path + QLatin1Char('/') + themeName); + } + + break; + } + } + + +#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(); + 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); + QIconDirInfo dirInfo(directoryKey); + dirInfo.size = size; + QString type = indexReader.value(directoryKey + + QLatin1String("/Type") + ).toString(); + + if (type == QLatin1String("Fixed")) + dirInfo.type = QIconDirInfo::Fixed; + else if (type == QLatin1String("Scalable")) + dirInfo.type = QIconDirInfo::Scalable; + else + dirInfo.type = QIconDirInfo::Threshold; + + dirInfo.threshold = indexReader.value(directoryKey + + QLatin1String("/Threshold"), + 2).toInt(); + + dirInfo.minSize = indexReader.value(directoryKey + + QLatin1String("/MinSize"), + size).toInt(); + + dirInfo.maxSize = indexReader.value(directoryKey + + QLatin1String("/MaxSize"), + size).toInt(); + m_keyList.append(dirInfo); + } + } + } + + // Parent themes provide fallbacks for missing icons + m_parents = indexReader.value( + QLatin1String("Icon Theme/Inherits")).toStringList(); + + // Ensure a default platform fallback for all themes + if (m_parents.isEmpty()) + m_parents.append(QIcon::themeName());//fallbackTheme()); + + // Ensure that all themes fall back to hicolor + if (!m_parents.contains(QLatin1String("hicolor"))) + m_parents.append(QLatin1String("hicolor")); + } +#endif //QT_NO_SETTINGS +} + + +QThemeIconEntries QIconLoader::findIconHelper(const QString &themeName, + const QString &iconName, + QStringList &visited) const +{ + QThemeIconEntries entries; + Q_ASSERT(!themeName.isEmpty()); + + QPixmap pixmap; + + // Used to protect against potential recursions + visited << themeName; + + QIconTheme theme = themeList.value(themeName); + if (!theme.isValid()) { + theme = QIconTheme(themeName); + if (!theme.isValid()) + theme = QIconTheme(QIcon::themeName());//fallbackTheme()); + + themeList.insert(themeName, theme); + } + + QStringList contentDirs = theme.contentDirs(); + QList subDirs = theme.keyList(); + + const QString svgext(QLatin1String(".svg")); + const QString pngext(QLatin1String(".png")); + const QString xpmext(QLatin1String(".xpm")); + + // Add all relevant files + for (int i = 0; i < subDirs.size() ; ++i) + { + const QIconDirInfo &dirInfo = subDirs.at(i); + QString subdir = dirInfo.path; + + foreach (QString contentDir, contentDirs) + { + QDir currentDir(contentDir + '/' + subdir); + + 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 + entries.prepend(iconEntry); + break; + } + else if (m_supportsSvg && + currentDir.exists(iconName + svgext)) + { + ScalableEntry *iconEntry = new ScalableEntry; + iconEntry->dir = dirInfo; + iconEntry->filename = currentDir.filePath(iconName + svgext); + entries.append(iconEntry); + break; + } + 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 + entries.append(iconEntry); + break; + } + } + } + + if (entries.isEmpty()) { + const QStringList parents = theme.parents(); + // Search recursively through inherited themes + for (int i = 0 ; i < parents.size() ; ++i) { + + const QString parentTheme = parents.at(i).trimmed(); + + if (!visited.contains(parentTheme)) // guard against recursion + entries = findIconHelper(parentTheme, iconName, visited); + + if (!entries.isEmpty()) // success + break; + } + } + + /********************************************************************* + 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 (entries.isEmpty()) { + const QString pixmaps(QLatin1String("/usr/share/pixmaps")); + + QDir currentDir(pixmaps); + QIconDirInfo 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 + entries.prepend(iconEntry); + } else if (m_supportsSvg && + currentDir.exists(iconName + svgext)) { + ScalableEntry *iconEntry = new ScalableEntry; + iconEntry->dir = dirInfo; + iconEntry->filename = currentDir.filePath(iconName + svgext); + 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 + entries.append(iconEntry); + } + + } +#endif + + if (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 + entries.prepend(iconEntry); + } else if (m_supportsSvg && + currentDir.exists(iconName + svgext)) { + ScalableEntry *iconEntry = new ScalableEntry; + iconEntry->filename = currentDir.filePath(iconName + svgext); + 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 + entries.append(iconEntry); + break; + } + } + } + return entries; +} + +QThemeIconEntries QIconLoader::loadIcon(const QString &name) const +{ + if (!themeName().isEmpty()) { + QStringList visited; + return findIconHelper(themeName(), name, visited); + } + + return QThemeIconEntries(); +} + + +// -------- Icon Loader Engine -------- // + + +QIconLoaderEngineFixed::QIconLoaderEngineFixed(const QString& iconName) + : m_iconName(iconName), m_key(0) +{ +} + +QIconLoaderEngineFixed::~QIconLoaderEngineFixed() +{ + while (!m_entries.isEmpty()) + delete m_entries.takeLast(); + Q_ASSERT(m_entries.size() == 0); +} + +QIconLoaderEngineFixed::QIconLoaderEngineFixed(const QIconLoaderEngineFixed &other) +#if QT_VERSION < QT_VERSION_CHECK(5,0,0) + : QIconEngineV2(other), +#else + : QIconEngine(other), +#endif + m_iconName(other.m_iconName), + m_key(0) +{ +} + + +#if QT_VERSION < QT_VERSION_CHECK(5,0,0) +QIconEngineV2 *QIconLoaderEngineFixed::clone() const +#else +QIconEngine *QIconLoaderEngineFixed::clone() const +#endif +{ + return new QIconLoaderEngineFixed(*this); +} + +bool QIconLoaderEngineFixed::read(QDataStream &in) { + in >> m_iconName; + return true; +} + +bool QIconLoaderEngineFixed::write(QDataStream &out) const +{ + out << m_iconName; + return true; +} + +bool QIconLoaderEngineFixed::hasIcon() const +{ + return !(m_entries.isEmpty()); +} + +// Lazily load the icon +void QIconLoaderEngineFixed::ensureLoaded() +{ + + iconLoaderInstance()->ensureInitialized(); + + if (!(iconLoaderInstance()->themeKey() == m_key)) { + + while (!m_entries.isEmpty()) + delete m_entries.takeLast(); + + Q_ASSERT(m_entries.size() == 0); + m_entries = iconLoaderInstance()->loadIcon(m_iconName); + m_key = iconLoaderInstance()->themeKey(); + } +} + +void QIconLoaderEngineFixed::paint(QPainter *painter, const QRect &rect, + QIcon::Mode mode, QIcon::State state) +{ + QSize pixmapSize = rect.size(); +#if defined(Q_WS_MAC) + pixmapSize *= qt_mac_get_scalefactor(); +#endif + painter->drawPixmap(rect, pixmap(pixmapSize, mode, state)); +} + +/* + * This algorithm is defined by the freedesktop spec: + * http://standards.freedesktop.org/icon-theme-spec/icon-theme-spec-latest.html + */ +static bool directoryMatchesSize(const QIconDirInfo &dir, int iconsize) +{ + if (dir.type == QIconDirInfo::Fixed) { + return dir.size == iconsize; + + } else if (dir.type == QIconDirInfo::Scalable) { + return dir.size <= dir.maxSize && + iconsize >= dir.minSize; + + } else if (dir.type == QIconDirInfo::Threshold) { + return iconsize >= dir.size - dir.threshold && + iconsize <= dir.size + dir.threshold; + } + + Q_ASSERT(1); // Not a valid value + return false; +} + +/* + * This algorithm is defined by the freedesktop spec: + * http://standards.freedesktop.org/icon-theme-spec/icon-theme-spec-latest.html + */ +static int directorySizeDistance(const QIconDirInfo &dir, int iconsize) +{ + if (dir.type == QIconDirInfo::Fixed) { + return qAbs(dir.size - iconsize); + + } else if (dir.type == QIconDirInfo::Scalable) { + if (iconsize < dir.minSize) + return dir.minSize - iconsize; + else if (iconsize > dir.maxSize) + return iconsize - dir.maxSize; + else + return 0; + + } 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; + } + + Q_ASSERT(1); // Not a valid value + return INT_MAX; +} + +QIconLoaderEngineEntry *QIconLoaderEngineFixed::entryForSize(const QSize &size) +{ + int iconsize = qMin(size.width(), size.height()); + + // Note that m_entries are sorted so that png-files + // come first + + // Search for exact matches first + for (int i = 0; i < m_entries.count(); ++i) { + QIconLoaderEngineEntry *entry = m_entries.at(i); + if (directoryMatchesSize(entry->dir, iconsize)) { + return entry; + } + } + + // Find the minimum distance icon + int minimalSize = INT_MAX; + QIconLoaderEngineEntry *closestMatch = 0; + for (int i = 0; i < m_entries.count(); ++i) { + QIconLoaderEngineEntry *entry = m_entries.at(i); + int distance = directorySizeDistance(entry->dir, iconsize); + if (distance < minimalSize) { + minimalSize = distance; + closestMatch = entry; + } + } + return closestMatch; +} + +/* + * Returns the actual icon size. For scalable svg's this is equivalent + * to the requested size. Otherwise the closest match is returned but + * we can never return a bigger size than the requested size. + * + */ +QSize QIconLoaderEngineFixed::actualSize(const QSize &size, QIcon::Mode mode, + QIcon::State state) +{ + ensureLoaded(); + QIconLoaderEngineEntry *entry = entryForSize(size); + if (entry) { + const QIconDirInfo &dir = entry->dir; + if (dir.type == QIconDirInfo::Scalable) + { + return size; + } + else { + if (dir.size == 0) + { + entry->dir.size = QPixmap(entry->filename).size().width(); + entry->dir.minSize = dir.size; + entry->dir.maxSize = dir.size; + } + int result = qMin(dir.size, qMin(size.width(), size.height())); + + return QSize(result, result); + } + } +#if QT_VERSION < QT_VERSION_CHECK(5,0,0) + return QIconEngineV2::actualSize(size, mode, state); +#else + return QIconEngine::actualSize(size, mode, state); +#endif +} + +QPixmap PixmapEntry::pixmap(const QSize &size, QIcon::Mode mode, QIcon::State state) +{ + Q_UNUSED(state); + + // Ensure that basePixmap is lazily initialized before generating the + // key, otherwise the cache key is not unique + if (basePixmap.isNull()) + basePixmap.load(filename); + + QSize actualSize = basePixmap.size(); + if (!actualSize.isNull() && (actualSize.width() > size.width() || actualSize.height() > size.height())) + actualSize.scale(size, Qt::KeepAspectRatio); + + + QString key = QString("$qt_theme_%1%2%3%4%5") + .arg(basePixmap.cacheKey(), 16, 16, QChar('0')) + .arg(mode, 8, 16, QChar('0')) + .arg(qApp->palette().cacheKey(),16, 16, QChar('0')) + .arg(actualSize.width(), 8, 16, QChar('0')) + .arg(actualSize.height(), 8, 16, QChar('0')); + + QPixmap cachedPixmap; + if (QPixmapCache::find(key, &cachedPixmap)) { + return cachedPixmap; + } else { + if (basePixmap.size() != actualSize) + basePixmap = basePixmap.scaled(actualSize, Qt::IgnoreAspectRatio, Qt::SmoothTransformation); + + QStyleOption opt(0); + opt.palette = qApp->palette(); + cachedPixmap = qApp->style()->generatedIconPixmap(mode, basePixmap, &opt); + QPixmapCache::insert(key, cachedPixmap); + } + return cachedPixmap; +} + +QPixmap ScalableEntry::pixmap(const QSize &size, QIcon::Mode mode, QIcon::State state) +{ + if (svgIcon.isNull()) + svgIcon = QIcon(filename); + + // Simply reuse svg icon engine + return svgIcon.pixmap(size, mode, state); +} + +QPixmap QIconLoaderEngineFixed::pixmap(const QSize &size, QIcon::Mode mode, + QIcon::State state) +{ + + ensureLoaded(); + + QIconLoaderEngineEntry *entry = entryForSize(size); + + if (entry) + return entry->pixmap(size, mode, state); + + return QPixmap(); +} + +QString QIconLoaderEngineFixed::key() const +{ + return QLatin1String("QIconLoaderEngineFixed"); +} + +void QIconLoaderEngineFixed::virtual_hook(int id, void *data) +{ + ensureLoaded(); + + switch (id) { +#if QT_VERSION < QT_VERSION_CHECK(5,0,0) + case QIconEngineV2::AvailableSizesHook: +#else + case QIconEngine::AvailableSizesHook: +#endif + { +#if QT_VERSION < QT_VERSION_CHECK(5,0,0) + QIconEngineV2::AvailableSizesArgument &arg + = *reinterpret_cast(data); +#else + QIconEngine::AvailableSizesArgument &arg + = *reinterpret_cast(data); +#endif + const QList directoryKey = iconLoaderInstance()->theme().keyList(); + arg.sizes.clear(); + + // Gets all sizes from the DirectoryInfo entries + for (int i = 0 ; i < m_entries.size() ; ++i) { + int size = m_entries.at(i)->dir.size; + arg.sizes.append(QSize(size, size)); + } + } + break; +#if (QT_VERSION >= 0x040700) && (QT_VERSION < 0x050000) + case QIconEngineV2::IconNameHook: + { + QString &name = *reinterpret_cast(data); + name = m_iconName; + } + break; +#elif QT_VERSION > QT_VERSION_CHECK(5,0,0) + case QIconEngine::IconNameHook: + { + QString &name = *reinterpret_cast(data); + name = m_iconName; + } + break; +#else// QT_VERSION > QT_VERSION_CHECK(5,0,0) +#warning QIconEngineV2::IconNameHook is ignored due Qt version. Upgrade to 4.7.x +#endif + default: +#if QT_VERSION < QT_VERSION_CHECK(5,0,0) + QIconEngineV2::virtual_hook(id, data); +#else + QIconEngine::virtual_hook(id, data); +#endif + } +} + +} // QtXdg + +#endif //QT_NO_ICON diff --git a/release.sh b/release.sh new file mode 100755 index 0000000..98b997f --- /dev/null +++ b/release.sh @@ -0,0 +1,26 @@ +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" "lxde.org:/var/www/lxqt/downloads/$PROJECT/" diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt new file mode 100644 index 0000000..b5792b5 --- /dev/null +++ b/test/CMakeLists.txt @@ -0,0 +1,52 @@ +set(PROJECT_NAME "qtxdg_test") + +set(${PROJECT_NAME}_SRCS + qtxdg_test.cpp +) + +set(${PROJECT_NAME}_MOCS + qtxdg_test.h +) + +set(LIBRARIES + ${QTXDGX_LIBRARY_NAME} +) + + +if (BUILD_TESTS) + add_definitions(-DQTXDG_BUILDING_TESTS=1) +endif() + +if (USE_QT5) + qt5_wrap_cpp(MOCS ${${PROJECT_NAME}_MOCS}) +else() + qt4_wrap_cpp(MOCS ${${PROJECT_NAME}_MOCS}) +endif() + +include_directories ( + ${CMAKE_SOURCE_DIR} +) + +if (USE_QT5) + add_definitions(${Qt5Test_DEFINITINS}) + include_directories ( + ${Qt5Test_INCLUDE_DIRS} + ) + set(CMAKE_CXX_FLAGS + "${CMAKE_CXX_FLAGS} ${Qt5Test_EXECUTABLE_COMPILE_FLAGS}" + ) +else() + include_directories ( + ${QT_QTCORE_INCLUDE_DIR} + ) +endif() + +add_executable(${PROJECT_NAME} ${${PROJECT_NAME}_SRCS} ${UIS} ${RSCS} ${TRS} ${MOCS} ) + +if (USE_QT5) + target_link_libraries ( ${PROJECT_NAME} ${Qt5Test_LIBRARIES} ${LIBRARIES} ) +else() + target_link_libraries ( ${PROJECT_NAME} ${QT_LIBRARIES} ${LIBRARIES} ) +endif() + +add_test(NAME ${PROJECT_NAME} COMMAND ${PROJECT_NAME}) diff --git a/test/qtxdg_test.cpp b/test/qtxdg_test.cpp new file mode 100644 index 0000000..8b20d44 --- /dev/null +++ b/test/qtxdg_test.cpp @@ -0,0 +1,141 @@ +#include "qtxdg_test.h" + +#include "xdgdesktopfile.h" +#include "xdgdesktopfile_p.h" +#include "xdgdirs.h" + +#include + +#include +#include +#include + +#include +#include + +void QtXdgTest::testDefaultApp() +{ + QStringList mimedirs = XdgDirs::dataDirs(); + mimedirs.prepend(XdgDirs::dataHome(false)); + foreach (QString mimedir, mimedirs) + { + QDir dir(mimedir + "/mime"); + qDebug() << dir.path(); + QStringList filters = (QStringList() << "*.xml"); + foreach(QFileInfo mediaDir, dir.entryInfoList(QDir::Dirs | QDir::NoDotAndDotDot)) + { + qDebug() << " " << mediaDir.fileName(); + foreach (QString mimeXmlFileName, QDir(mediaDir.absoluteFilePath()).entryList(filters, QDir::Files)) + { + QString mimetype = mediaDir.fileName() + "/" + mimeXmlFileName.left(mimeXmlFileName.length() - 4); + QString xdg_utils_default = xdgUtilDefaultApp(mimetype); + QString desktop_file_default = xdgDesktopFileDefaultApp(mimetype); + + if (xdg_utils_default != desktop_file_default) + { + qDebug() << mimetype; + qDebug() << "xdg-mime query default:" << xdg_utils_default; + qDebug() << "xdgdesktopfile default:" << desktop_file_default; + } + } + } + + } +} + +void QtXdgTest::compare(QString mimetype) +{ + QString xdgUtilDefault = xdgUtilDefaultApp(mimetype); + QString xdgDesktopDefault = xdgDesktopFileDefaultApp(mimetype); + if (xdgUtilDefault != xdgDesktopDefault) + { + qDebug() << mimetype; + qDebug() << "xdg-mime default:" << xdgUtilDefault; + qDebug() << "xdgDesktopfile default:" << xdgDesktopDefault; + } +} + + +void QtXdgTest::testTextHtml() +{ + compare("text/html"); +} + +void QtXdgTest::testMeldComparison() +{ + compare("application/x-meld-comparison"); +} + +void QtXdgTest::testCustomFormat() +{ + QSettings::Format desktopFormat = QSettings::registerFormat("list", readDesktopFile, writeDesktopFile); + QFile::remove("/tmp/test.list"); + QFile::remove("/tmp/test2.list"); + QSettings test("/tmp/test.list", desktopFormat); + test.beginGroup("Default Applications"); + test.setValue("text/plain", QString("gvim.desktop")); + test.setValue("text/html", QString("firefox.desktop")); + test.endGroup(); + test.beginGroup("Other Applications"); + test.setValue("application/pdf", QString("qpdfview.desktop")); + test.setValue("image/svg+xml", QString("inkscape.desktop")); + test.sync(); + + QFile::copy("/tmp/test.list", "/tmp/test2.list"); + + QSettings test2("/tmp/test2.list", desktopFormat); + QVERIFY(test2.allKeys().size() == 4); + + test2.beginGroup("Default Applications"); +// qDebug() << test2.value("text/plain"); + QVERIFY(test2.value("text/plain") == QString("gvim.desktop")); + +// qDebug() << test2.value("text/html"); + QVERIFY(test2.value("text/html") == QString("firefox.desktop")); + test2.endGroup(); + + test2.beginGroup("Other Applications"); +// qDebug() << test2.value("application/pdf"); + QVERIFY(test2.value("application/pdf") == QString("qpdfview.desktop")); + +// qDebug() << test2.value("image/svg+xml"); + QVERIFY(test2.value("image/svg+xml") == QString("inkscape.desktop")); + test2.endGroup(); +} + + +QString QtXdgTest::xdgDesktopFileDefaultApp(QString mimetype) +{ + XdgDesktopFile *defaultApp = XdgDesktopFileCache::getDefaultApp(mimetype); + QString defaultAppS; + if (defaultApp) + { + defaultAppS = QFileInfo(defaultApp->fileName()).fileName(); + } + return defaultAppS; +} + + + +QString QtXdgTest::xdgUtilDefaultApp(QString mimetype) +{ + QProcess xdg_mime; + QString program = "xdg-mime"; + QStringList args = (QStringList() << "query" << "default" << mimetype); + qDebug() << "running" << program << args.join(" "); + xdg_mime.start(program, args); + xdg_mime.waitForFinished(1000); + return QString(xdg_mime.readAll()).trimmed(); +} + +#if 0 +int main(int argc, char** args) +{ +// QtXdgTest().testDefaultApp(); +// qDebug() << "Default for text/html:" << QtXdgTest().xdgDesktopFileDefaultApp("text/html"); +// QtXdgTest().testMeldComparison(); + qDebug() << QtXdgTest().testCustomFormat(); +}; +#endif // 0 + +QTEST_MAIN(QtXdgTest) diff --git a/test/qtxdg_test.h b/test/qtxdg_test.h new file mode 100644 index 0000000..480b6e6 --- /dev/null +++ b/test/qtxdg_test.h @@ -0,0 +1,28 @@ +#ifndef QTXDG_TEST_H +#define QTXDG_TEST_H + +#include +#include +#include + +class QtXdgTest : public QObject +{ + Q_OBJECT + +private slots: + void testCustomFormat(); + +private: + // Test that XdgDesktopFile and xdg-mime script agree on + // default application for each mime-type. + void testDefaultApp(); + + void testTextHtml(); + void testMeldComparison(); + void compare(QString mimetype); + QString xdgDesktopFileDefaultApp(QString mimetype); + QString xdgUtilDefaultApp(QString mimetype); + +}; + +#endif /* QTXDG_TEST_H */ diff --git a/xdgaction.cpp b/xdgaction.cpp new file mode 100644 index 0000000..29c1712 --- /dev/null +++ b/xdgaction.cpp @@ -0,0 +1,156 @@ +/* BEGIN_COMMON_COPYRIGHT_HEADER + * (c)LGPL2+ + * + * Razor - a lightweight, Qt based, desktop toolset + * http://razor-qt.org + * + * Copyright: 2010-2011 Razor team + * Authors: + * Alexander Sokoloff + * + * 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 "xdgaction.h" +#include "xdgicon.h" +#include +#include + + +/************************************************ + + ************************************************/ +XdgAction::XdgAction(QObject *parent): + QAction(parent) +{ +} + + +/************************************************ + + ************************************************/ +XdgAction::XdgAction(const XdgDesktopFile& desktopFile, QObject *parent): + QAction(parent) +{ + load(desktopFile); +} + + +/************************************************ + + ************************************************/ +XdgAction::XdgAction(const XdgDesktopFile* desktopFile, QObject *parent): + QAction(parent) +{ + load(*desktopFile); +} + + +/************************************************ + + ************************************************/ +XdgAction::XdgAction(const QString& desktopFileName, QObject *parent): + QAction(parent) +{ + XdgDesktopFile df; + df.load(desktopFileName); + load(df); +} + + +/************************************************ + + ************************************************/ +XdgAction::XdgAction(const XdgAction& other, QObject *parent): + QAction(parent) +{ + load(other.mDesktopFile); +} + + +/************************************************ + + ************************************************/ +XdgAction::~XdgAction() +{ +} + + +/************************************************ + + ************************************************/ +XdgAction& XdgAction::operator=(const XdgAction& other) +{ + load(other.mDesktopFile); + return *this; +} + + +/************************************************ + + ************************************************/ +bool XdgAction::isValid() const +{ + return mDesktopFile.isValid(); +} + + +/************************************************ + + ************************************************/ +void XdgAction::load(const XdgDesktopFile& desktopFile) +{ + mDesktopFile = desktopFile; + if (mDesktopFile.isValid()) + { + // & is reserved for mnemonics + setText(mDesktopFile.name().replace('&', QLatin1String("&&"))); + setToolTip(mDesktopFile.comment()); + + connect(this, SIGNAL(triggered()), this, SLOT(runConmmand())); + QMetaObject::invokeMethod(this, "updateIcon", Qt::QueuedConnection); + } + else + { + setText(QString()); + setToolTip(QString()); + setIcon(QIcon()); + } +} + + +/************************************************ + + ************************************************/ +void XdgAction::runConmmand() const +{ + if (mDesktopFile.isValid()) + mDesktopFile.startDetached(); +} + + +/************************************************ + + ************************************************/ +void XdgAction::updateIcon() +{ + setIcon(mDesktopFile.icon()); + if (icon().isNull()) + setIcon(XdgIcon::fromTheme("application-x-executable")); + QCoreApplication::processEvents(); +} diff --git a/xdgaction.h b/xdgaction.h new file mode 100644 index 0000000..d642d10 --- /dev/null +++ b/xdgaction.h @@ -0,0 +1,86 @@ +/* BEGIN_COMMON_COPYRIGHT_HEADER + * (c)LGPL2+ + * + * Razor - a lightweight, Qt based, desktop toolset + * http://razor-qt.org + * + * Copyright: 2010-2011 Razor team + * Authors: + * Alexander Sokoloff + * + * 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 */ + + + +#ifndef QTXDG_XDGACTION_H +#define QTXDG_XDGACTION_H + +#include "xdgmacros.h" +#include "xdgdesktopfile.h" + +#include +#include + + +/*******************************************************************/ /*! + @brief The XdgAction class provides an QAction object based on XdgDesktopFile. + + The following properties of the action are set based on the desktopFile. + Text - XdgDesktopFile localizeValue("Name") + Icon - XdgDesktopFile icon() + ToolTip - XdgDesktopFile localizeValue("Comment") + + Internally this function will create a copy of the desktopFile, so you + can delete original XdgDesktopFile object. + + When an action is activated by the user; for example, when the user clicks + a menu option, toolbar button or when trigger() was called, XdgAction start + the application defined in XdgDesktopFile. @sa XdgDesktopFile::startDetached. +****************************************/ +//******************************************************************* +class QTXDG_API XdgAction : public QAction +{ + Q_OBJECT +public: + explicit XdgAction(QObject *parent=0); + explicit XdgAction(const XdgDesktopFile& desktopFile, QObject *parent=0); + explicit XdgAction(const XdgDesktopFile* desktopFile, QObject *parent=0); + explicit XdgAction(const QString& desktopFileName, QObject *parent=0); + // Constructs a XdgAction that is a copy of the given XdgAction. + explicit XdgAction(const XdgAction& other, QObject *parent=0); + + /// Destroys the object and frees allocated resources. + virtual ~XdgAction(); + XdgAction& operator=(const XdgAction& other); + + //! Returns true if the XdgAction is valid; otherwise returns false. + bool isValid() const; + + const XdgDesktopFile& desktopFile() const { return mDesktopFile; } + +private slots: + void runConmmand() const; + void updateIcon(); + +private: + void load(const XdgDesktopFile& desktopFile); + + XdgDesktopFile mDesktopFile; +}; + +#endif // QTXDG_XDGACTION_H diff --git a/xdgautostart.cpp b/xdgautostart.cpp new file mode 100644 index 0000000..4328950 --- /dev/null +++ b/xdgautostart.cpp @@ -0,0 +1,91 @@ +/* BEGIN_COMMON_COPYRIGHT_HEADER + * (c)LGPL2+ + * + * Razor - a lightweight, Qt based, desktop toolset + * http://razor-qt.org + * + * Copyright: 2012 Razor team + * Authors: + * Alexander Sokoloff + * + * 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 "xdgautostart.h" +#include "xdgdirs.h" +#include +#include +#include + +/************************************************ +The Autostart Directories are $XDG_CONFIG_DIRS/autostart. If the same filename is +located under multiple Autostart Directories only the file under the most +important directory should be used. + +When multiple .desktop files with the same name exists in multiple directories +then only the Hidden key in the most important .desktop file must be considered: +If it is set to true all .desktop files with the same name in the other +directories MUST be ignored as well. + ************************************************/ +XdgDesktopFileList XdgAutoStart::desktopFileList(bool excludeHidden) +{ + QStringList dirs; + dirs << XdgDirs::autostartHome(false) << XdgDirs::autostartDirs(); + + return desktopFileList(dirs, excludeHidden); +} + +XdgDesktopFileList XdgAutoStart::desktopFileList(QStringList dirs, bool excludeHidden) +{ + dirs.removeDuplicates(); + + QSet processed; + XdgDesktopFileList ret; + foreach (QString dirName, dirs) + { + QDir dir(dirName); + if (!dir.exists()) + continue; + + QFileInfoList files = dir.entryInfoList(QStringList("*.desktop"), QDir::Files | QDir::Readable); + foreach (QFileInfo fi, files) + { + if (processed.contains(fi.fileName())) + continue; + + processed << fi.fileName(); + + XdgDesktopFile desktop; + if (!desktop.load(fi.absoluteFilePath())) + continue; + + if (!desktop.isSuitable(excludeHidden)) + continue; + + ret << desktop; + } + } + return ret; +} + + +QString XdgAutoStart::localPath(const XdgDesktopFile& file) +{ + QFileInfo fi(file.fileName()); + return QString("%1/%2").arg(XdgDirs::autostartHome(), fi.fileName()); +} diff --git a/xdgautostart.h b/xdgautostart.h new file mode 100644 index 0000000..ec268a2 --- /dev/null +++ b/xdgautostart.h @@ -0,0 +1,60 @@ +/* BEGIN_COMMON_COPYRIGHT_HEADER + * (c)LGPL2+ + * + * Razor - a lightweight, Qt based, desktop toolset + * http://razor-qt.org + * + * Copyright: 2012 Razor team + * Authors: + * Alexander Sokoloff + * + * 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 */ + + +#ifndef QTXDG_XDGAUTOSTART_H +#define QTXDG_XDGAUTOSTART_H + +#include "xdgmacros.h" +#include "xdgdesktopfile.h" + +/*! @brief The XdgAutoStart class implements the "Desktop Application Autostart Specification" + * from freedesktop.org. + * This specification defines a method for automatically starting applications during the startup + * of a desktop environment and after mounting a removable medium. + * Now we impliment only startup. + * + * @sa http://standards.freedesktop.org/autostart-spec/autostart-spec-latest.html + */ +class QTXDG_API XdgAutoStart +{ +public: + /*! Returns a list of XdgDesktopFile objects for all the .desktop files in the Autostart directories + When the .desktop file has the Hidden key set to true, the .desktop file must be ignored. But you + can change this behavior by setting excludeHidden to false. */ + static XdgDesktopFileList desktopFileList(bool excludeHidden=true); + + /*! Returns a list of XdgDesktopFile objects for .desktop files in the specified Autostart directories + When the .desktop file has the Hidden key set to true, the .desktop file must be ignored. But you + can change this behavior by setting excludeHidden to false. */ + static XdgDesktopFileList desktopFileList(QStringList dirs, bool excludeHidden=true); + + /// For XdgDesktopFile returns the file path of the same name in users personal autostart directory. + static QString localPath(const XdgDesktopFile& file); +}; + +#endif // XDGAUTOSTART_H diff --git a/xdgdesktopfile.cpp b/xdgdesktopfile.cpp new file mode 100644 index 0000000..d92a872 --- /dev/null +++ b/xdgdesktopfile.cpp @@ -0,0 +1,1763 @@ +/* BEGIN_COMMON_COPYRIGHT_HEADER + * (c)LGPL2+ + * + * Razor - a lightweight, Qt based, desktop toolset + * http://razor-qt.org + * + * Copyright: 2010-2011 Razor team + * Authors: + * Alexander Sokoloff + * + * 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 */ + + + +/********************************************************************* + See: http://standards.freedesktop.org/desktop-entry-spec + +*********************************************************************/ +#include + +#include "xdgdesktopfile.h" +#include "xdgdesktopfile_p.h" + +#ifdef HAVE_QTMIMETYPES +#include +#include +#else +#include "xdgmime.h" +#endif + +#include "xdgicon.h" +#include "xdgdirs.h" +#include "desktopenvironment_p.cpp" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include // for the % operator +#if QT_VERSION < QT_VERSION_CHECK(5,0,0) +#else +#include +#endif + +#include +#include +#include +#include +#include +#include +#include + +// A list of executables that can't be run with QProcess::startDetached(). They +// will be run with QProcess::start() +static const QStringList nonDetachExecs = QStringList() + << QLatin1String("pkexec"); + +static const char onlyShowInKey[] = "OnlyShowIn"; +static const char notShowInKey[] = "NotShowIn"; +static const char categoriesKey[] = "Categories"; +static const char extendPrefixKey[] = "X-"; + +// Helper functions prototypes +bool checkTryExec(const QString& progName); +QString &doEscape(QString& str, const QHash &repl); +QString &doUnEscape(QString& str, const QHash &repl); +QString &escape(QString& str); +QString &escapeExec(QString& str); +QString expandDynamicUrl(QString url); +QString expandEnvVariables(const QString str); +QStringList expandEnvVariables(const QStringList strs); +QString findDesktopFile(const QString& dirName, const QString& desktopName); +QString findDesktopFile(const QString& desktopName); +static QStringList parseCombinedArgString(const QString &program); +bool read(const QString &prefix); +void replaceVar(QString &str, const QString &varName, const QString &after); +QString &unEscape(QString& str); +QString &unEscapeExec(QString& str); +void loadMimeCacheDir(const QString& dirName, QHash > & cache); +/************************************************ + + ************************************************/ +QString &doEscape(QString& str, const QHash &repl) +{ + // First we replace slash. + str.replace('\\', "\\\\"); + + QHashIterator i(repl); + while (i.hasNext()) { + i.next(); + if (i.key() != '\\') + str.replace(i.key(), QString("\\\\%1").arg(i.value())); + } + + return str; +} + +/************************************************ + The escape sequences \s, \n, \t, \r, and \\ are supported for values + of type string and localestring, meaning ASCII space, newline, tab, + carriage return, and backslash, respectively. + ************************************************/ +QString &escape(QString& str) +{ + QHash repl; + repl.insert('\n', 'n'); + repl.insert('\t', 't'); + repl.insert('\r', 'r'); + + return doEscape(str, repl); +} + + +/************************************************ + Quoting must be done by enclosing the argument between double quotes and + escaping the + double quote character, + backtick character ("`"), + dollar sign ("$") and + backslash character ("\") +by preceding it with an additional backslash character. +Implementations must undo quoting before expanding field codes and before +passing the argument to the executable program. + +Note that the general escape rule for values of type string states that the +backslash character can be escaped as ("\\") as well and that this escape +rule is applied before the quoting rule. As such, to unambiguously represent a +literal backslash character in a quoted argument in a desktop entry file +requires the use of four successive backslash characters ("\\\\"). +Likewise, a literal dollar sign in a quoted argument in a desktop entry file +is unambiguously represented with ("\\$"). + ************************************************/ +QString &escapeExec(QString& str) +{ + QHash repl; + // The parseCombinedArgString() splits the string by the space symbols, + // we temporarily replace them on the special characters. + // Replacement will reverse after the splitting. + repl.insert('"', '"'); // double quote, + repl.insert('\'', '\''); // single quote ("'"), + repl.insert('\\', '\\'); // backslash character ("\"), + repl.insert('$', '$'); // dollar sign ("$"), + + return doEscape(str, repl); +} + + + +/************************************************ + + ************************************************/ +QString &doUnEscape(QString& str, const QHash &repl) +{ + int n = 0; + while (1) + { + n=str.indexOf("\\", n); + if (n < 0 || n > str.length() - 2) + break; + + if (repl.contains(str.at(n+1))) + { + str.replace(n, 2, repl.value(str.at(n+1))); + } + + n++; + } + + return str; +} + + +/************************************************ + The escape sequences \s, \n, \t, \r, and \\ are supported for values + of type string and localestring, meaning ASCII space, newline, tab, + carriage return, and backslash, respectively. + ************************************************/ +QString &unEscape(QString& str) +{ + QHash repl; + repl.insert('\\', '\\'); + repl.insert('s', ' '); + repl.insert('n', '\n'); + repl.insert('t', '\t'); + repl.insert('r', '\r'); + + return doUnEscape(str, repl); +} + + + +/************************************************ + Quoting must be done by enclosing the argument between double quotes and + escaping the + double quote character, + backtick character ("`"), + dollar sign ("$") and + backslash character ("\") +by preceding it with an additional backslash character. +Implementations must undo quoting before expanding field codes and before +passing the argument to the executable program. + +Reserved characters are + space (" "), + tab, + newline, + double quote, + single quote ("'"), + backslash character ("\"), + greater-than sign (">"), + less-than sign ("<"), + tilde ("~"), + vertical bar ("|"), + ampersand ("&"), + semicolon (";"), + dollar sign ("$"), + asterisk ("*"), + question mark ("?"), + hash mark ("#"), + parenthesis ("(") and (")") + backtick character ("`"). + +Note that the general escape rule for values of type string states that the +backslash character can be escaped as ("\\") as well and that this escape +rule is applied before the quoting rule. As such, to unambiguously represent a +literal backslash character in a quoted argument in a desktop entry file +requires the use of four successive backslash characters ("\\\\"). +Likewise, a literal dollar sign in a quoted argument in a desktop entry file +is unambiguously represented with ("\\$"). + ************************************************/ +QString &unEscapeExec(QString& str) +{ + unEscape(str); + QHash repl; + // The parseCombinedArgString() splits the string by the space symbols, + // we temporarily replace them on the special characters. + // Replacement will reverse after the splitting. + repl.insert(' ', 01); // space + repl.insert('\t', 02); // tab + repl.insert('\n', 03); // newline, + + repl.insert('"', '"'); // double quote, + repl.insert('\'', '\''); // single quote ("'"), + repl.insert('\\', '\\'); // backslash character ("\"), + repl.insert('>', '>'); // greater-than sign (">"), + repl.insert('<', '<'); // less-than sign ("<"), + repl.insert('~', '~'); // tilde ("~"), + repl.insert('|', '|'); // vertical bar ("|"), + repl.insert('&', '&'); // ampersand ("&"), + repl.insert(';', ';'); // semicolon (";"), + repl.insert('$', '$'); // dollar sign ("$"), + repl.insert('*', '*'); // asterisk ("*"), + repl.insert('?', '?'); // question mark ("?"), + repl.insert('#', '#'); // hash mark ("#"), + repl.insert('(', '('); // parenthesis ("(") + repl.insert(')', ')'); // parenthesis (")") + repl.insert('`', '`'); // backtick character ("`"). + + return doUnEscape(str, repl); +} + +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 startLinkDetached(const XdgDesktopFile *q) const; + bool startByDBus(const QStringList& urls) const; + + QString mFileName; + bool mIsValid; + mutable bool mValidIsChecked; + mutable QHash mIsShow; + QMap mItems; + + XdgDesktopFile::Type mType; +}; + + +/************************************************ + + ************************************************/ +XdgDesktopFileData::XdgDesktopFileData(): + mIsValid(false), + mValidIsChecked(false) +{ +} + + +/************************************************ + + ************************************************/ +bool XdgDesktopFileData::read(const QString &prefix) +{ + QFile file(mFileName); + + if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) + return false; + + QString section; + QTextStream stream(&file); + bool prefixExists = false; + while (!stream.atEnd()) { + QString line = stream.readLine().trimmed(); + + // Skip comments ...................... + if (line.startsWith('#')) + continue; + + + // Section .............................. + if (line.startsWith('[') && line.endsWith(']')) + { + section = line.mid(1, line.length()-2); + if (section == prefix) + prefixExists = true; + + continue; + } + + QString key = line.section('=', 0, 0).trimmed(); + QString value = line.section('=', 1).trimmed(); + + if (key.isEmpty()) + continue; + + mItems[section + "/" + key] = QVariant(value); + } + + + // Not check for empty prefix + mIsValid = (prefix.isEmpty()) || prefixExists; + return mIsValid; +} + +/************************************************ + + ************************************************/ +XdgDesktopFile::Type XdgDesktopFileData::detectType(XdgDesktopFile *q) const +{ + QString typeStr = q->value("Type").toString(); + if (typeStr == "Application") + return XdgDesktopFile::ApplicationType; + + if (typeStr == "Link") + return XdgDesktopFile::LinkType; + + if (typeStr == "Directory") + return XdgDesktopFile::DirectoryType; + + if (!q->value("Exec").toString().isEmpty()) + return XdgDesktopFile::ApplicationType; + + return XdgDesktopFile::UnknownType; +} + + +/************************************************ + + ************************************************/ +bool XdgDesktopFileData::startApplicationDetached(const XdgDesktopFile *q, const QStringList& urls) const +{ + //DBusActivatable handling + if (q->value(QLatin1String("DBusActivatable"), false).toBool()) + return startByDBus(urls); + + QStringList args = q->expandExecString(urls); + + if (args.isEmpty()) + return false; + + if (q->value("Terminal").toBool()) + { + QString term = getenv("TERM"); + if (term.isEmpty()) + term = "xterm"; + + args.prepend("-e"); + args.prepend(term); + } + + bool nonDetach = false; + foreach(const QString &s, nonDetachExecs) + { + foreach(const QString &a, args) + { + if (a.contains(s)) + { + nonDetach = true; + } + } + } + + QString cmd = args.takeFirst(); + + if (nonDetach) + { + QScopedPointer p(new QProcess); +#if QT_VERSION >= QT_VERSION_CHECK(5,0,0) + p->setStandardInputFile(QProcess::nullDevice()); +#else + p->setStandardInputFile(QLatin1String("/dev/null")); +#endif + p->setProcessChannelMode(QProcess::ForwardedChannels); + p->start(cmd, args); + bool started = p->waitForStarted(); + if (started) + { + QProcess* proc = p.take(); //release the pointer(will be selfdestroyed upon finish) + QObject::connect(proc, SIGNAL(finished(int, QProcess::ExitStatus)), proc, SLOT(deleteLater())); + } + return started; + } + else + { + return QProcess::startDetached(cmd, args); + } +} + + +/************************************************ + + ************************************************/ +bool XdgDesktopFileData::startLinkDetached(const XdgDesktopFile *q) const +{ + QString url = q->url(); + + if (url.isEmpty()) + { + qWarning() << "XdgDesktopFileData::startLinkDetached: url is empty."; + return false; + } + + QString scheme = QUrl(url).scheme(); + + if (scheme.isEmpty() || scheme.toUpper() == "FILE") + { + // Local file + QFileInfo fi(url); + +#ifdef HAVE_QTMIMETYPES + QMimeDatabase db; + QMimeType mimeInfo = db.mimeTypeForFile(fi); + XdgDesktopFile* desktopFile = XdgDesktopFileCache::getDefaultApp(mimeInfo.name()); +#else + XdgMimeInfo mimeInfo(fi); + XdgDesktopFile* desktopFile = XdgDesktopFileCache::getDefaultApp(mimeInfo.mimeType()); +#endif + + if (desktopFile) + return desktopFile->startDetached(url); + } + else + { + // Internet URL + return QDesktopServices::openUrl(QUrl::fromEncoded(url.toLocal8Bit())); + } + + return false; +} + +// TODO: Handle ActivateAction +bool XdgDesktopFileData::startByDBus(const QStringList& urls) const +{ + QFileInfo f(mFileName); + QString path(f.completeBaseName()); + + QVariantMap platformData; + platformData.insert(QLatin1String("desktop-startup-id"), QString::fromUtf8(qgetenv("DESKTOP_STARTUP_ID"))); + + path = path.replace(QLatin1Char('.'), QLatin1Char('/')).prepend(QLatin1Char('/')); + QDBusInterface app(f.completeBaseName(), path, QLatin1String("org.freedesktop.Application")); + QDBusMessage reply; + if (urls.isEmpty()) + reply = app.call(QLatin1String("Activate"), platformData); + else + reply = app.call(QLatin1String("Open"), urls, platformData); + + return QDBusMessage::ErrorMessage != reply.type(); +} + + + +/************************************************ + + ************************************************/ +XdgDesktopFile::XdgDesktopFile(): + d(new XdgDesktopFileData) +{ +} + + +/************************************************ + + ************************************************/ +XdgDesktopFile::XdgDesktopFile(const XdgDesktopFile& other): + d(other.d) +{ +} + +/************************************************ + + ************************************************/ +XdgDesktopFile::XdgDesktopFile(Type type, const QString& name, const QString &value): + d(new XdgDesktopFileData) +{ + d->mFileName = name + ".desktop"; + d->mType = type; + setValue("Version", "1.0"); + setValue("Name", name); + if (type == XdgDesktopFile::ApplicationType) + { + setValue("Type", "Application"); + setValue("Exec", value); + } + else if (type == XdgDesktopFile::LinkType) + { + setValue("Type", "Link"); + setValue("URL", value); + } + else if (type == XdgDesktopFile::DirectoryType) + { + setValue("Type", "Directory"); + } + d->mIsValid = check(); +} + + +/************************************************ + + ************************************************/ +XdgDesktopFile::~XdgDesktopFile() +{ +} + + +/************************************************ + + ************************************************/ +XdgDesktopFile& XdgDesktopFile::operator=(const XdgDesktopFile& other) +{ + d = other.d; + return *this; +} + + +/************************************************ + + ************************************************/ +bool XdgDesktopFile::operator==(const XdgDesktopFile &other) const +{ + return d->mItems == other.d->mItems; +} + + +/************************************************ + + ************************************************/ +bool XdgDesktopFile::load(const QString& fileName) +{ + if (fileName.startsWith(QDir::separator())) { // absolute path + QFileInfo f(fileName); + if (f.exists()) + d->mFileName = f.canonicalFilePath(); + else + d->mFileName = QString(); + } else { // relative path + d->mFileName = findDesktopFile(fileName); + } + d->read(prefix()); + d->mIsValid = d->mIsValid && check(); + d->mType = d->detectType(this); + return isValid(); +} + + +/************************************************ + + ************************************************/ +bool XdgDesktopFile::save(QIODevice *device) const +{ + QTextStream stream(device); + QMap::const_iterator i = d->mItems.constBegin(); + + QString section; + while (i != d->mItems.constEnd()) + { + QString path = i.key(); + QString sect = path.section('/',0,0); + if (sect != section) + { + section = sect; + stream << "[" << section << "]" << endl; + } + QString key = path.section('/', 1); + stream << key << "=" << i.value().toString() << endl; + ++i; + } + return true; +} + + +/************************************************ + + ************************************************/ +bool XdgDesktopFile::save(const QString &fileName) const +{ + QFile file(fileName); + if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) + return false; + + return save(&file); +} + + +/************************************************ + + ************************************************/ +QVariant XdgDesktopFile::value(const QString& key, const QVariant& defaultValue) const +{ + QString path = (!prefix().isEmpty()) ? prefix() + "/" + key : key; + QVariant res = d->mItems.value(path, defaultValue); + if (res.type() == QVariant::String) + { + QString s = res.toString(); + return unEscape(s); + } + + return res; +} + + +/************************************************ + + ************************************************/ +void XdgDesktopFile::setValue(const QString &key, const QVariant &value) +{ + QString path = (!prefix().isEmpty()) ? prefix() + "/" + key : key; + if (value.type() == QVariant::String) + { + + QString s=value.toString(); + if (key.toUpper() == "EXEC") + escapeExec(s); + else + escape(s); + + d->mItems[path] = QVariant(s); + + if (key.toUpper() == "TYPE") + d->mType = d->detectType(this); + } + else + { + d->mItems[path] = value; + } +} + + +/************************************************ + + ************************************************/ +void XdgDesktopFile::setLocalizedValue(const QString &key, const QVariant &value) +{ + setValue(localizedKey(key), value); +} + + +/************************************************ + LC_MESSAGES value Possible keys in order of matching + lang_COUNTRY@MODIFIER lang_COUNTRY@MODIFIER, lang_COUNTRY, lang@MODIFIER, lang, + default value + lang_COUNTRY lang_COUNTRY, lang, default value + lang@MODIFIER lang@MODIFIER, lang, default value + lang lang, default value + ************************************************/ +QString XdgDesktopFile::localizedKey(const QString& key) const +{ + QString lang = getenv("LC_MESSAGES"); + + if (lang.isEmpty()) + lang = getenv("LC_ALL"); + + if (lang.isEmpty()) + lang = getenv("LANG"); + + + QString modifier = lang.section('@', 1); + if (!modifier.isEmpty()) + lang.truncate(lang.length() - modifier.length() - 1); + + QString encoding = lang.section('.', 1); + if (!encoding.isEmpty()) + lang.truncate(lang.length() - encoding.length() - 1); + + + QString country = lang.section('_', 1); + if (!country.isEmpty()) + lang.truncate(lang.length() - country.length() - 1); + + + + //qDebug() << "LC_MESSAGES: " << getenv("LC_MESSAGES"); + //qDebug() << "Lang:" << lang; + //qDebug() << "Country:" << country; + //qDebug() << "Encoding:" << encoding; + //qDebug() << "Modifier:" << modifier; + + + if (!modifier.isEmpty() && !country.isEmpty()) + { + QString k = QString("%1[%2_%3@%4]").arg(key, lang, country, modifier); + //qDebug() << "\t try " << k << contains(k); + if (contains(k)) + return k; + } + + if (!country.isEmpty()) + { + QString k = QString("%1[%2_%3]").arg(key, lang, country); + //qDebug() << "\t try " << k << contains(k); + if (contains(k)) + return k; + } + + if (!modifier.isEmpty()) + { + QString k = QString("%1[%2@%3]").arg(key, lang, modifier); + //qDebug() << "\t try " << k << contains(k); + if (contains(k)) + return k; + } + + QString k = QString("%1[%2]").arg(key, lang); + //qDebug() << "\t try " << k << contains(k); + if (contains(k)) + return k; + + + //qDebug() << "\t try " << key << contains(key); + return key; +} + +/************************************************ + + ************************************************/ +QVariant XdgDesktopFile::localizedValue(const QString& key, const QVariant& defaultValue) const +{ + return value(localizedKey(key), defaultValue); +} + + +QStringList XdgDesktopFile::categories() const +{ + QString key; + if (contains(QLatin1String(categoriesKey))) + { + key = QLatin1String(categoriesKey); + } + else + { + key = QLatin1String(extendPrefixKey) % QLatin1String(categoriesKey); + if (!contains(key)) + return QStringList(); + } + + QStringList cats = value(key).toString().split(QLatin1Char(';')); + return cats; +} + +/************************************************ + + ************************************************/ +void XdgDesktopFile::removeEntry(const QString& key) +{ + QString path = (!prefix().isEmpty()) ? prefix() + "/" + key : key; + d->mItems.remove(path); +} + + +/************************************************ + + ************************************************/ +bool XdgDesktopFile::contains(const QString& key) const +{ + QString path = (!prefix().isEmpty()) ? prefix() + "/" + key : key; + return d->mItems.contains(path); +} + + +/************************************************ + + ************************************************/ +bool XdgDesktopFile::isValid() const +{ + return d->mIsValid; +} + + +/************************************************ + + ************************************************/ +QString XdgDesktopFile::fileName() const +{ + return d->mFileName; +} + + +/************************************************ + + ************************************************/ +QIcon const XdgDesktopFile::icon(const QIcon& fallback) const +{ + QIcon result = XdgIcon::fromTheme(value("Icon").toString(), fallback); + + if (result.isNull() && type() == ApplicationType) { + result = XdgIcon::fromTheme("application-x-executable.png"); + // TODO Maybe defaults for other desktopfile types as well.. + } + + return result; +} + + +/************************************************ + + ************************************************/ +QString const XdgDesktopFile::iconName() const +{ + return value("Icon").toString(); +} + + +/************************************************ + + ************************************************/ +XdgDesktopFile::Type XdgDesktopFile::type() const +{ + return d->mType; +} + + +/************************************************ + Starts the program defined in this desktop file in a new process, and detaches + from it. Returns true on success; otherwise returns false. If the calling process + exits, the detached process will continue to live. + + Urls - the list of URLs or files to open, can be empty (app launched without + argument) + If the function is successful then *pid is set to the process identifier of the + started process. + ************************************************/ +bool XdgDesktopFile::startDetached(const QStringList& urls) const +{ + switch(d->mType) + { + case ApplicationType: + return d->startApplicationDetached(this, urls); + + case LinkType: + return d->startLinkDetached(this); + + default: + return false; + } +} + + +/************************************************ + This is an overloaded function. + ************************************************/ +bool XdgDesktopFile::startDetached(const QString& url) const +{ + if (url.isEmpty()) + return startDetached(QStringList()); + else + return startDetached(QStringList(url)); +} + + +/************************************************ + + ************************************************/ +static QStringList parseCombinedArgString(const QString &program) +{ + QStringList args; + QString tmp; + int quoteCount = 0; + bool inQuote = false; + + // handle quoting. tokens can be surrounded by double quotes + // "hello world". three consecutive double quotes represent + // the quote character itself. + for (int i = 0; i < program.size(); ++i) { + if (program.at(i) == QLatin1Char('"')) { + ++quoteCount; + if (quoteCount == 3) { + // third consecutive quote + quoteCount = 0; + tmp += program.at(i); + } + continue; + } + if (quoteCount) { + if (quoteCount == 1) + inQuote = !inQuote; + quoteCount = 0; + } + if (!inQuote && program.at(i).isSpace()) { + if (!tmp.isEmpty()) { + args += tmp; + tmp.clear(); + } + } else { + tmp += program.at(i); + } + } + if (!tmp.isEmpty()) + args += tmp; + + return args; +} + + +/************************************************ + + ************************************************/ +void replaceVar(QString &str, const QString &varName, const QString &after) +{ + str.replace(QRegExp(QString("\\$%1(?!\\w)").arg(varName)), after); + str.replace(QRegExp(QString("\\$\\{%1\\}").arg(varName)), after); +} + + +/************************************************ + + ************************************************/ +QString expandEnvVariables(const QString str) +{ + QString scheme = QUrl(str).scheme(); + + if (scheme == "http" || scheme == "https" || scheme == "shttp" || + scheme == "ftp" || scheme == "ftps" || + scheme == "pop" || scheme == "pops" || + scheme == "imap" || scheme == "imaps" || + scheme == "mailto" || + scheme == "nntp" || + scheme == "irc" || + scheme == "telnet" || + scheme == "xmpp" || + scheme == "irc" || + scheme == "nfs" + ) + return str; + + QString res = str; + res.replace(QRegExp("~(?=$|/)"), getenv("HOME")); + + replaceVar(res, "HOME", getenv("HOME")); + replaceVar(res, "USER", getenv("USER")); + +#if QT_VERSION < QT_VERSION_CHECK(5,0,0) + replaceVar(res, "XDG_DESKTOP_DIR", QDesktopServices::storageLocation(QDesktopServices::DesktopLocation)); + replaceVar(res, "XDG_TEMPLATES_DIR", QDesktopServices::storageLocation(QDesktopServices::TempLocation)); + replaceVar(res, "XDG_DOCUMENTS_DIR", QDesktopServices::storageLocation(QDesktopServices::DocumentsLocation)); + replaceVar(res, "XDG_MUSIC_DIR", QDesktopServices::storageLocation(QDesktopServices::MusicLocation)); + replaceVar(res, "XDG_PICTURES_DIR", QDesktopServices::storageLocation(QDesktopServices::PicturesLocation)); + replaceVar(res, "XDG_VIDEOS_DIR", QDesktopServices::storageLocation(QDesktopServices::MoviesLocation)); + replaceVar(res, "XDG_PHOTOS_DIR", QDesktopServices::storageLocation(QDesktopServices::PicturesLocation)); + replaceVar(res, "XDG_MOVIES_DIR", QDesktopServices::storageLocation(QDesktopServices::MoviesLocation)); +#else + replaceVar(res, "XDG_DESKTOP_DIR", QStandardPaths::writableLocation(QStandardPaths::DesktopLocation)); + replaceVar(res, "XDG_TEMPLATES_DIR", QStandardPaths::writableLocation(QStandardPaths::TempLocation)); + replaceVar(res, "XDG_DOCUMENTS_DIR", QStandardPaths::writableLocation(QStandardPaths::DocumentsLocation)); + replaceVar(res, "XDG_MUSIC_DIR", QStandardPaths::writableLocation(QStandardPaths::MusicLocation)); + replaceVar(res, "XDG_PICTURES_DIR", QStandardPaths::writableLocation(QStandardPaths::PicturesLocation)); + replaceVar(res, "XDG_VIDEOS_DIR", QStandardPaths::writableLocation(QStandardPaths::MoviesLocation)); + replaceVar(res, "XDG_PHOTOS_DIR", QStandardPaths::writableLocation(QStandardPaths::PicturesLocation)); + replaceVar(res, "XDG_MOVIES_DIR", QStandardPaths::writableLocation(QStandardPaths::MoviesLocation)); +#endif // QT_VERSION < QT_VERSION_CHECK(5,0,0) + + return res; +} + + +/************************************************ + + ************************************************/ +QStringList expandEnvVariables(const QStringList strs) +{ + QStringList res; + foreach(QString s, strs) + res << expandEnvVariables(s); + + return res; +} + + +/************************************************ + + ************************************************/ +QStringList XdgDesktopFile::expandExecString(const QStringList& urls) const +{ + if (d->mType != ApplicationType) + return QStringList(); + + QStringList result; + + QString execStr = value("Exec").toString(); + unEscapeExec(execStr); + QStringList tokens = parseCombinedArgString(execStr); + + foreach (QString token, tokens) + { + // The parseCombinedArgString() splits the string by the space symbols, + // we temporarily replaced them on the special characters. + // Now we reverse it. + token.replace(01, ' '); + token.replace(02, '\t'); + token.replace(03, '\n'); + + // ---------------------------------------------------------- + // A single file name, even if multiple files are selected. + if (token == "%f") + { + if (!urls.isEmpty()) + result << expandEnvVariables(urls.at(0)); + continue; + } + + // ---------------------------------------------------------- + // A list of files. Use for apps that can open several local files at once. + // Each file is passed as a separate argument to the executable program. + if (token == "%F") + { + result << expandEnvVariables(urls); + continue; + } + + // ---------------------------------------------------------- + // A single URL. Local files may either be passed as file: URLs or as file path. + if (token == "%u") + { + if (!urls.isEmpty()) + { + QUrl url; + url.setUrl(expandEnvVariables(urls.at(0))); + result << ((!url.toLocalFile().isEmpty()) ? url.toLocalFile() : url.toEncoded()); + } + continue; + } + + // ---------------------------------------------------------- + // A list of URLs. Each URL is passed as a separate argument to the executable + // program. Local files may either be passed as file: URLs or as file path. + if (token == "%U") + { + foreach (QString s, urls) + { + QUrl url(expandEnvVariables(s)); + result << ((!url.toLocalFile().isEmpty()) ? url.toLocalFile() : url.toEncoded()); + } + continue; + } + + // ---------------------------------------------------------- + // The Icon key of the desktop entry expanded as two arguments, first --icon + // and then the value of the Icon key. Should not expand to any arguments if + // the Icon key is empty or missing. + if (token == "%i") + { + QString icon = value("Icon").toString(); + if (!icon.isEmpty()) + result << "-icon" << icon.replace('%', "%%"); + continue; + } + + + // ---------------------------------------------------------- + // The translated name of the application as listed in the appropriate Name key + // in the desktop entry. + if (token == "%c") + { + result << localizedValue("Name").toString().replace('%', "%%"); + continue; + } + + // ---------------------------------------------------------- + // The location of the desktop file as either a URI (if for example gotten from + // the vfolder system) or a local filename or empty if no location is known. + if (token == "%k") + { + result << fileName().replace('%', "%%"); + break; + } + + // ---------------------------------------------------------- + // Deprecated. + // Deprecated field codes should be removed from the command line and ignored. + if (token == "%d" || token == "%D" || + token == "%n" || token == "%N" || + token == "%v" || token == "%m" + ) + { + continue; + } + + // ---------------------------------------------------------- + result << expandEnvVariables(token); + } + + return result; +} + + + +/************************************************ + Check if the program is actually installed. + ************************************************/ +bool checkTryExec(const QString& progName) +{ + if (progName.startsWith(QDir::separator())) + return QFileInfo(progName).isExecutable(); + + QStringList dirs = QString(getenv("PATH")).split(":"); + + foreach (QString dir, dirs) + { + if (QFileInfo(QDir(dir), progName).isExecutable()) + return true; + } + + return false; +} + + +/************************************************ + + ************************************************/ +bool XdgDesktopFile::isShow(const QString& environment) const +{ + const QString env = environment.toUpper(); + + if (d->mIsShow.contains(env)) + return d->mIsShow.value(env); + + d->mIsShow.insert(env, false); + // Means "this application exists, but don't display it in the menus". + if (value("NoDisplay").toBool()) + return false; + + // The file is inapplicable to the current environment + if (!isSuitable(true, env)) + return false; + + + d->mIsShow.insert(env, true); + return true; +} + +bool XdgDesktopFile::isShown(const QString &environment) const +{ + const QString env = environment.toUpper(); + + if (d->mIsShow.contains(env)) + return d->mIsShow.value(env); + + d->mIsShow.insert(env, false); + + // Means "this application exists, but don't display it in the menus". + if (value("NoDisplay").toBool()) + return false; + + // The file is not suitable to the current environment + if (!isSuitable(true, env)) + return false; + + d->mIsShow.insert(env, true); + return true; +} + + +/************************************************ + + ************************************************/ +bool XdgDesktopFile::isApplicable(bool excludeHidden, const QString& environment) const +{ + // Hidden should have been called Deleted. It means the user deleted + // (at his level) something that was present + if (excludeHidden && value("Hidden").toBool()) + return false; + + // A list of strings identifying the environments that should display/not + // display a given desktop entry. + // OnlyShowIn ........ + if (contains("OnlyShowIn")) + { + QStringList s = value("OnlyShowIn").toString().split(';'); + if (!s.contains(environment)) + return false; + } + + // NotShowIn ......... + if (contains("NotShowIn")) + { + QStringList s = value("NotShowIn").toString().split(';'); + if (s.contains(environment)) + return false; + } + + // actually installed. If not, entry may not show in menus, etc. + QString s = value("TryExec").toString(); + if (!s.isEmpty() && ! checkTryExec(s)) + return false; + + return true; +} + +bool XdgDesktopFile::isSuitable(bool excludeHidden, const QString &environment) const +{ + // Hidden should have been called Deleted. It means the user deleted + // (at his level) something that was present + if (excludeHidden && value("Hidden").toBool()) + return false; + + // A list of strings identifying the environments that should display/not + // display a given desktop entry. + // OnlyShowIn ........ + QString env; + if (environment.isEmpty()) + env = QString(detectDesktopEnvironment()); + else { + env = environment.toUpper(); + } + + QString key; + bool keyFound = false; + if (contains(QLatin1String(onlyShowInKey))) + { + key = QLatin1String(onlyShowInKey); + keyFound = true; + } + else + { + key = QLatin1String(extendPrefixKey) % QLatin1String(onlyShowInKey); + keyFound = contains(key) ? true : false; + } + + if (keyFound) + { + QStringList s = value(key).toString().toUpper().split(QLatin1Char(';')); + if (!s.contains(env)) + return false; + } + + // NotShowIn ......... + keyFound = false; + if (contains(QLatin1String(notShowInKey))) + { + key = QLatin1String(notShowInKey); + keyFound = true; + } + else + { + key = QLatin1String(extendPrefixKey) % QLatin1String(notShowInKey); + keyFound = contains(key) ? true : false; + } + + if (keyFound) + { + QStringList s = value(key).toString().toUpper().split(QLatin1Char(';')); + if (!s.contains(env)) + return false; + } + + // actually installed. If not, entry may not show in menus, etc. + QString s = value("TryExec").toString(); + if (!s.isEmpty() && ! checkTryExec(s)) + return false; + + return true; +} + +/************************************************ + + ************************************************/ +QString expandDynamicUrl(QString url) +{ + foreach(QString line, QProcess::systemEnvironment()) + { + QString name = line.section("=", 0, 0); + QString val = line.section("=", 1); + url.replace(QString("$%1").arg(name), val); + url.replace(QString("${%1}").arg(name), val); + } + + return url; +} + + +/************************************************ + + ************************************************/ +QString XdgDesktopFile::url() const +{ + if (type() != LinkType) + return QString(); + + QString url; + + url = value("URL").toString(); + if (!url.isEmpty()) + return url; + + // WTF? What standard describes it? + url = expandDynamicUrl(value("URL[$e]").toString()); + if (!url.isEmpty()) + return url; + + return QString(); +} + + + +/************************************************ + + ************************************************/ +QString findDesktopFile(const QString& dirName, const QString& desktopName) +{ + QDir dir(dirName); + QFileInfo fi(dir, desktopName); + + if (fi.exists()) + return fi.canonicalFilePath(); + + // Working recursively ............ + QFileInfoList dirs = dir.entryInfoList(QStringList(), QDir::Dirs | QDir::NoDotAndDotDot); + foreach (QFileInfo d, dirs) + { + QString cn = d.canonicalFilePath(); + if (dirName != cn) + { + QString f = findDesktopFile(cn, desktopName); + if (!f.isEmpty()) + return f; + } + } + + return QString(); +} + + +/************************************************ + + ************************************************/ +QString findDesktopFile(const QString& desktopName) +{ + QStringList dataDirs = XdgDirs::dataDirs(); + dataDirs.prepend(XdgDirs::dataHome(false)); + + foreach (QString dirName, dataDirs) + { + QString f = findDesktopFile(dirName + "/applications", desktopName); + if (!f.isEmpty()) + return f; + } + + return QString(); +} + + + + +/************************************************ + + ************************************************/ +XdgDesktopFile* XdgDesktopFileCache::getFile(const QString& fileName) +{ + if (instance().m_fileCache.contains(fileName)) + { + return instance().m_fileCache.value(fileName); + } + + if (fileName.startsWith(QDir::separator())) + { + // Absolute path ........................ + //qDebug() << "XdgDesktopFileCache: add new file" << fileName; + XdgDesktopFile* desktopFile = load(fileName); + if (desktopFile->isValid()) + instance().m_fileCache.insert(fileName, desktopFile); + return desktopFile; + } + else + { + // Search desktop file .................. + QString filePath = findDesktopFile(fileName); + XdgDesktopFile* desktopFile; + //qDebug() << "Sokoloff XdgDesktopFileCache::getFile found fileName" << fileName << filePath; + if (!filePath.isEmpty()) + { + // The file was found + if (!instance().m_fileCache.contains(filePath)) + { + desktopFile = load(filePath); + instance().m_fileCache.insert(filePath, desktopFile); + } + else + desktopFile = instance().m_fileCache.value(filePath); + + return desktopFile; + + } + else + { + return new XdgDesktopFile; + } + } +} + +QList XdgDesktopFileCache::getAllFiles() +{ + return instance().m_fileCache.values(); +} + + + +XdgDesktopFileCache & XdgDesktopFileCache::instance() +{ + static XdgDesktopFileCache cache; + if (!cache.m_IsInitialized) + { + cache.initialize(); + cache.m_IsInitialized = true; + } + + return cache; +} + + + +/*! + * Handles files with a syntax similar to desktopfiles as QSettings files. + * The differences between ini-files and desktopfiles are: + * desktopfiles uses '#' as comment marker, and ';' as list-separator. + * Every key/value must be inside a section (i.e. there is no 'General' pseudo-section) + */ +bool readDesktopFile(QIODevice & device, QSettings::SettingsMap & map) +{ + QString section; + QTextStream stream(&device); + + while (!stream.atEnd()) { + QString line = stream.readLine().trimmed(); + + // Skip comments and empty lines + if (line.startsWith('#') || line.isEmpty()) + continue; + + // Section .............................. + if (line.startsWith('[') && line.endsWith(']')) + { + section = line.mid(1, line.length()-2); + continue; + } + + QString key = line.section('=', 0, 0).trimmed(); + QString value = line.section('=', 1).trimmed(); + + if (key.isEmpty()) + continue; + + if (section.isEmpty()) + { + qWarning() << "key=value outside section"; + return false; + } + + key.prepend("/"); + key.prepend(section); + + if (value.contains(";")) + { + map.insert(key, value.split(";")); + } + else + { + map.insert(key, value); + } + + } + + return true; +} + +/*! See readDesktopFile + */ +bool writeDesktopFile(QIODevice & device, const QSettings::SettingsMap & map) +{ + QTextStream stream(&device); + QString section; + + foreach (QString key, map.keys()) + { + if (! map.value(key).canConvert()) + { + return false; + } + + QString thisSection = key.section("/", 0, 0); + if (thisSection.isEmpty()) + { + qWarning() << "No section defined"; + return false; + } + + if (thisSection != section) + { + stream << "[" << thisSection << "]" << "\n"; + section = thisSection; + } + + QString remainingKey = key.section("/", 1, -1); + + if (remainingKey.isEmpty()) + { + qWarning() << "Only one level in key..." ; + return false; + } + + stream << remainingKey << "=" << map.value(key).toString() << "\n"; + + } + + return true; +} + + +/************************************************ + + ************************************************/ +void XdgDesktopFileCache::initialize(const QString& dirName) +{ + QDir dir(dirName); + // Directories have the type "application/x-directory", but in the desktop file + // are shown as "inode/directory". To handle these cases, we use this hash. + QHash specials; + specials.insert("inode/directory", "application/x-directory"); + + + // Working recursively ............ + QFileInfoList files = dir.entryInfoList(QStringList(), QDir::Files | QDir::Dirs | QDir::NoDotAndDotDot); + foreach (QFileInfo f, files) + { + if (f.isDir()) + { + initialize(f.absoluteFilePath()); + continue; + } + + + XdgDesktopFile* df = load(f.absoluteFilePath()); + if (!df) + continue; + + if (! m_fileCache.contains(f.absoluteFilePath())) + { + m_fileCache.insert(f.absoluteFilePath(), df); + } + + QStringList mimes = df->value("MimeType").toString().split(';', QString::SkipEmptyParts); + + foreach (QString mime, mimes) + { + int pref = df->value("InitialPreference", 0).toInt(); + // We move the desktopFile forward in the list for this mime, so that + // no desktopfile in front of it have a lower initialPreference. + int position = m_defaultAppsCache[mime].length(); + while (position > 0 && m_defaultAppsCache[mime][position - 1]->value("InitialPreference, 0").toInt() < pref) + { + position--; + } + m_defaultAppsCache[mime].insert(position, df); + } + } + +} + +XdgDesktopFile* XdgDesktopFileCache::load(const QString& fileName) +{ + XdgDesktopFile* desktopFile = new XdgDesktopFile(); + desktopFile->load(fileName); + return desktopFile; +} + +void loadMimeCacheDir(const QString& dirName, QHash > & cache) +{ + QDir dir(dirName); + // Directories have the type "application/x-directory", but in the desktop file + // are shown as "inode/directory". To handle these cases, we use this hash. + QHash specials; + specials.insert("inode/directory", "application/x-directory"); + + + // Working recursively ............ + QFileInfoList files = dir.entryInfoList(QStringList(), QDir::Files | QDir::Dirs | QDir::NoDotAndDotDot); + foreach (QFileInfo f, files) + { + if (f.isDir()) + { + loadMimeCacheDir(f.absoluteFilePath(), cache); + continue; + } + + + XdgDesktopFile* df = XdgDesktopFileCache::getFile(f.absoluteFilePath()); + if (!df) + continue; + + QStringList mimes = df->value("MimeType").toString().split(';', QString::SkipEmptyParts); + + foreach (QString mime, mimes) + { + int pref = df->value("InitialPreference", 0).toInt(); + // We move the desktopFile forward in the list for this mime, so that + // no desktopfile in front of it have a lower initialPreference. + int position = cache[mime].length(); + while (position > 0 && cache[mime][position - 1]->value("InitialPreference, 0").toInt() < pref) + { + position--; + } + cache[mime].insert(position, df); + } + } +} + +QSettings::Format XdgDesktopFileCache::desktopFileSettingsFormat() +{ + static QSettings::Format format = QSettings::InvalidFormat; + + if (format == QSettings::InvalidFormat) + { + format = QSettings::registerFormat("*.list", readDesktopFile, writeDesktopFile); + qDebug() << "registerFormat returned:" << format; + } + + return format; +} + + +XdgDesktopFileCache::XdgDesktopFileCache() : + m_IsInitialized(false), + m_defaultAppsCache(), + m_fileCache() +{ +} + +XdgDesktopFileCache::~XdgDesktopFileCache() +{ +} + + + +void XdgDesktopFileCache::initialize() +{ + QStringList dataDirs = XdgDirs::dataDirs(); + dataDirs.prepend(XdgDirs::dataHome(false)); + + foreach (const QString dirname, dataDirs) + { + initialize(dirname + "/applications"); +// loadMimeCacheDir(dirname + "/applications", m_defaultAppsCache); + } +} + +QList XdgDesktopFileCache::getAppsOfCategory(const QString& category) +{ + QList list; + const QString _category = category.toUpper(); + foreach (XdgDesktopFile *desktopFile, instance().m_fileCache.values()) + { + QStringList categories = desktopFile->value("Categories").toString().toUpper().split(QLatin1Char(';')); + if (!categories.isEmpty() && (categories.contains(_category) || categories.contains(QLatin1String("X-") % _category))) + list.append(desktopFile); + } + return list; +} + +QList XdgDesktopFileCache::getApps(const QString& mimetype) +{ + return instance().m_defaultAppsCache.value(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) + { + QString defaultsListPath = dataDir + "/applications/mimeapps.list"; + if (QFileInfo(defaultsListPath).exists()) + { + QSettings defaults(defaultsListPath, desktopFileSettingsFormat()); + + + defaults.beginGroup("Default Applications"); + if (defaults.contains(mimetype)) + { + QVariant value = defaults.value(mimetype); + if (value.canConvert()) // A single string can also convert to a stringlist + { + foreach (const QString desktopFileName, value.toStringList()) + { + XdgDesktopFile* desktopFile = XdgDesktopFileCache::getFile(desktopFileName); + if (desktopFile->isValid()) + { + return desktopFile; + } + else + { + qWarning() << desktopFileName << "not a valid desktopfile"; + } + } + } + } + defaults.endGroup(); + } + } + + // If we havent found anything up to here, we look for a desktopfile that declares + // the ability to handle the given mimetype. See getApps. + QList apps = getApps(mimetype); + XdgDesktopFile* desktopFile = apps.isEmpty() ? 0 : apps[0]; + return desktopFile; +} + diff --git a/xdgdesktopfile.h b/xdgdesktopfile.h new file mode 100644 index 0000000..0712ca6 --- /dev/null +++ b/xdgdesktopfile.h @@ -0,0 +1,262 @@ +/* BEGIN_COMMON_COPYRIGHT_HEADER + * (c)LGPL2+ + * + * Razor - a lightweight, Qt based, desktop toolset + * http://razor-qt.org + * + * Copyright: 2010-2011 Razor team + * Authors: + * Alexander Sokoloff + * + * 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 */ + + + +#ifndef QTXDG_XDGDESKTOPFILE_H +#define QTXDG_XDGDESKTOPFILE_H + +#include "xdgmacros.h" + +#include +//#include +#include +#include +#include +#include +#include + +class XdgDesktopFileData; + +/** + \brief Desktop files handling. + XdgDesktopFile class gives the interface for reading the values from the XDG .desktop file. + The interface of this class is similar on QSettings. XdgDesktopFile objects can be passed + around by value since the XdgDesktopFile class uses implicit data sharing. + + The Desktop Entry Specification defines 3 types of desktop entries: Application, Link and + Directory. The format of .desktop file is described on + http://standards.freedesktop.org/desktop-entry-spec/desktop-entry-spec-latest.html + + Note that not all methods in this class make sense for all types of desktop files. + \author Alexander Sokoloff + */ + +class QTXDG_API XdgDesktopFile +{ +public: + /*! The Desktop Entry Specification defines 3 types of desktop entries: Application, Link and + Directory. File type is determined by the "Type" tag. */ + enum Type + { + UnknownType, //! Unknown desktop file type. Maybe is invalid. + ApplicationType, //! The file describes application. + LinkType, //! The file describes URL. + DirectoryType //! The file describes directory settings. + }; + + //! Constructs an empty XdgDesktopFile + XdgDesktopFile(); + + /*! Constructs a copy of other. + This operation takes constant time, because XdgDesktopFile is implicitly shared. This makes + returning a XdgDesktopFile from a function very fast. If a shared instance is modified, + it will be copied (copy-on-write), and that takes linear time. */ + XdgDesktopFile(const XdgDesktopFile& other); + + /*! Constructs a new basic DesktopFile. If type is: + - ApplicationType, "value" should be the Exec value; + - LinkType, "value" should be the URL; + - DirectoryType, "value" should be omitted */ + XdgDesktopFile(XdgDesktopFile::Type type, const QString& name, const QString& value = 0); + + //! Destroys the object. + virtual ~XdgDesktopFile(); + + //! Assigns other to this DesktopFile and returns a reference to this DesktopFile. + XdgDesktopFile& operator=(const XdgDesktopFile& other); + + //! Returns true if both files contain the identical key-value pairs + bool operator==(const XdgDesktopFile &other) const; + + //! Loads an DesktopFile from the file with the given fileName. + virtual bool load(const QString& fileName); + + //! Saves the DesktopFile to the file with the given fileName. Returns true if successful; otherwise returns false. + virtual bool save(const QString &fileName) const; + + /*! This is an overloaded function. + This function writes a DesktopFile to the given device. */ + virtual bool save(QIODevice *device) const; + + /*! Returns the value for key. If the key doesn't exist, returns defaultValue. + If no default value is specified, a default QVariant is returned. */ + QVariant value(const QString& key, const QVariant& defaultValue = QVariant()) const; + + /*! Returns the localized value for key. In the desktop file keys may be postfixed by [LOCALE]. If the + localized value doesn't exist, returns non lokalized value. If non localized value doesn't exist, returns defaultValue. + LOCALE must be of the form lang_COUNTRY.ENCODING@MODIFIER, where _COUNTRY, .ENCODING, and @MODIFIER may be omitted. + + If no default value is specified, a default QVariant is returned. */ + QVariant localizedValue(const QString& key, const QVariant& defaultValue = QVariant()) const; + + //! Sets the value of setting key to value. If the key already exists, the previous value is overwritten. + void setValue(const QString &key, const QVariant &value); + + /*! Sets the value of setting key to value. If a localized version of the key already exists, the previous value is + overwritten. Otherwise, it overwrites the the un-localized version. */ + void setLocalizedValue(const QString &key, const QVariant &value); + + //! Removes the entry with the specified key, if it exists. + void removeEntry(const QString& key); + + //! Returns the entry Categories. It supports X-Categories extensions. + QStringList categories() const; + + //! Returns true if there exists a setting called key; returns false otherwise. + bool contains(const QString& key) const; + + //! Returns true if the XdgDesktopFile is valid; otherwise returns false. + bool isValid() const; + + /*! Returns the file name of the desktop file. + * Returns QString() if the file wasn't found when load was called. */ + QString fileName() const; + + //! Returns an icon specified in this file. + QIcon const icon(const QIcon& fallback = QIcon()) const; + + //! Returns an icon name specified in this file. + QString const iconName() const; + + //! This function is provided for convenience. It's equivalent to calling localizedValue("Name").toString(). + QString name() const { return localizedValue("Name").toString(); } + + //! This function is provided for convenience. It's equivalent to calling localizedValue("Comment").toString(). + QString comment() const { return localizedValue("Comment").toString(); } + + /*! Returns the desktop file type. + @see XdgDesktopFile::Type */ + Type type() const; + + /*! For file with Application type. Starts the program with the optional urls in a new process, and detaches from it. + Returns true on success; otherwise returns false. + @par urls - A list of files or URLS. Each file is passed as a separate argument to the executable program. + + For file with Link type. Opens URL in the associated application. Parametr urls is not used. + + For file with Directory type, do nothing. */ + bool startDetached(const QStringList& urls) const; + + //! This function is provided for convenience. It's equivalent to calling startDetached(QStringList(url)). + bool startDetached(const QString& url = QString()) 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. + @par urls - A list of files or URLS. Each file is passed as a separate argument to the result string program.*/ + QStringList expandExecString(const QStringList& urls = QStringList()) const; + + /*! Returns the URL for the Link desktop file; otherwise an empty string is returned. */ + QString url() const; + + /*! The desktop entry specification defines a number of fields to control the visibility of the application menu. This function + checks whether to display a this application or not. */ + QTXDG_DEPRECATED bool isShow(const QString& environment = "Razor") const; + + /*! The desktop entry specification defines a number of fields to control + the visibility of the application menu. Thisfunction checks whether + to display a this application or not. + @par environment - User supplied desktop environment name. If not + supplied the desktop will be detected reading the + XDG_CURRENT_DESKTOP environment variable. If not set, "UNKNOWN" + will be used as the desktop name. All operations envolving the + desktop environment name are case insensitive. + */ + bool isShown(const QString &environment = QString()) const; + + /*! This fuction returns true if the desktop file is applicable to the current environment. + @par excludeHidden - if set to true (default), files with "Hidden=true" will be considered "not applicable". + Setting this to false is be useful when the user wants to enable/disable items and wants to see those + that are Hidden */ + QTXDG_DEPRECATED bool isApplicable(bool excludeHidden = true, const QString& environment = "Razor") const; + + /*! This fuction returns true if the desktop file is applicable to the + current environment. + @par excludeHidden - if set to true (default), files with + "Hidden=true" will be considered "not applicable". Setting this + to false is be useful when the user wants to enable/disable items + and wants to see those that are Hidden + @par environment - User supplied desktop environment name. If not + supplied the desktop will be detected reading the + XDG_CURRENT_DESKTOP environment variable. If not set, "UNKNOWN" + will be used as the desktop name. All operations envolving the + desktop environment name are case insensitive. + */ + bool isSuitable(bool excludeHidden = true, const QString &environment = QString()) const; + +protected: + virtual QString prefix() const { return "Desktop Entry"; } + virtual bool check() const { return true; } +private: + /*! Returns the localized version of the key if the Desktop File already contains a localized version of it. + If not, returns the same key back */ + QString localizedKey(const QString& key) const; + + QSharedDataPointer d; +}; + + +/// Synonym for QList +typedef QList XdgDesktopFileList; + + +class QTXDG_API XdgDesktopFileCache +{ +public: + static XdgDesktopFile* getFile(const QString& fileName); + static QList getAllFiles(); + static QList getApps(const QString & mimeType); + static XdgDesktopFile* getDefaultApp(const QString& mimeType); + static QSettings::Format desktopFileSettingsFormat(); + + /*! Return all desktop apps that have category for their Categories key + * Note that, according to xdg's spec, for non-standard categories "X-" + * is added to the beginning of the category's name. This method takes care + * of both cases. + * See http://standards.freedesktop.org/menu-spec/menu-spec-latest.html#desktop-entry-extensions + */ + static QList getAppsOfCategory(const QString &category); + +private: + static XdgDesktopFileCache & instance(); + static XdgDesktopFile* load(const QString & fileName); + + XdgDesktopFileCache(); + ~XdgDesktopFileCache(); + + void initialize(); + void initialize(const QString & dirName); + bool m_IsInitialized; + QHash > m_defaultAppsCache; + QHash m_fileCache; + }; + + +#endif // QTXDG_XDGDESKTOPFILE_H + + diff --git a/xdgdesktopfile_p.h b/xdgdesktopfile_p.h new file mode 100644 index 0000000..b28ec33 --- /dev/null +++ b/xdgdesktopfile_p.h @@ -0,0 +1,7 @@ +#ifndef XDGDESKTOPFILE_P_H +#define XDGDESKTOPFILE_P_H + +QTXDG_AUTOTEST bool readDesktopFile(QIODevice & device, QSettings::SettingsMap & map); +QTXDG_AUTOTEST bool writeDesktopFile(QIODevice & device, const QSettings::SettingsMap & map); + +#endif // XDGDESKTOPFILE_P_H diff --git a/xdgdirs.cpp b/xdgdirs.cpp new file mode 100644 index 0000000..2671ae2 --- /dev/null +++ b/xdgdirs.cpp @@ -0,0 +1,430 @@ +/* BEGIN_COMMON_COPYRIGHT_HEADER + * (c)LGPL2+ + * + * Razor - a lightweight, Qt based, desktop toolset + * http://razor-qt.org + * + * Copyright: 2010-2011 Razor team + * Authors: + * Alexander Sokoloff + * + * 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 "xdgdirs.h" +#include +#include +#include // for the % operator +#include +#if QT_VERSION >= QT_VERSION_CHECK(5,0,0) +#include +#endif + +static const QString userDirectoryString[8] = +{ + "Desktop", + "Download", + "Templates", + "Publicshare", + "Documents", + "Music", + "Pictures", + "Videos" +}; + +// Helper functions prototypes +void fixBashShortcuts(QString &s); +void removeEndingSlash(QString &s); +QString createDirectory(const QString &dir); + +#if QT_VERSION >= QT_VERSION_CHECK(5,0,0) +void cleanAndAddPostfix(QStringList &dirs, const QString& postfix); +#endif + +#if QT_VERSION < QT_VERSION_CHECK(5,0,0) +QString xdgSingleDir(const QString &envVar, const QString &def, bool createDir); +QStringList xdgDirList(const QString &envVar, const QString &postfix); +#endif + +/************************************************ + Helper func. + ************************************************/ +void fixBashShortcuts(QString &s) +{ + if (s.startsWith(QLatin1Char('~'))) + s = QString(getenv("HOME")) + (s).mid(1); +} + +void removeEndingSlash(QString &s) +{ + // We don't check for empty strings. Caller must check it. + + // Remove the ending slash, except for root dirs. + if (s.length() > 1 && s.endsWith(QLatin1Char('/'))) + s.chop(1); +} + +QString createDirectory(const QString &dir) +{ + QDir d(dir); + if (!d.exists()) + { + if (!d.mkpath(".")) + { + qWarning() << QString("Can't create %1 directory.").arg(d.absolutePath()); + } + } + QString r = d.absolutePath(); + removeEndingSlash(r); + return r; +} + +void cleanAndAddPostfix(QStringList &dirs, const QString& postfix) +{ + const int N = dirs.count(); + for(int i = 0; i < N; ++i) + { + fixBashShortcuts(dirs[i]); + removeEndingSlash(dirs[i]); + dirs[i].append(postfix); + } +} + +/************************************************ + Helper func. + ************************************************/ +#if QT_VERSION < QT_VERSION_CHECK(5,0,0) +QString xdgSingleDir(const QString &envVar, const QString &def, bool createDir) +{ + QString s(getenv(envVar.toAscii())); + + if (!s.isEmpty()) + fixBashShortcuts(s); + else + s = QString("%1/%2").arg(getenv("HOME"), def); + + if (createDir) + return createDirectory(s); + + removeEndingSlash(s); + return s; +} +#endif + +/************************************************ + Helper func. + ************************************************/ +#if QT_VERSION < QT_VERSION_CHECK(5,0,0) +QStringList xdgDirList(const QString &envVar, const QString &postfix) +{ + QStringList dirs = QString(getenv(envVar.toAscii())).split(':', QString::SkipEmptyParts); + + QMutableStringListIterator i(dirs); + while(i.hasNext()) { + i.next(); + QString s = i.value(); + if (s.isEmpty()) { + i.remove(); + } else { + fixBashShortcuts(s); + removeEndingSlash(s); + i.setValue(s % postfix); + } + } + return dirs; +} +#endif +/************************************************ + + ************************************************/ +QString XdgDirs::userDir(XdgDirs::UserDirectory dir) +{ + // possible values for UserDirectory + if (dir < 0 || dir > 7) + return QString(); + + QString folderName = userDirectoryString[dir]; + + QString fallback; + if (getenv("HOME") == NULL) + return QString("/tmp"); + else if (dir == XdgDirs::Desktop) + fallback = QString("%1/%2").arg(getenv("HOME")).arg("Desktop"); + else + fallback = QString(getenv("HOME")); + + QString configDir(configHome()); + QFile configFile(configDir + "/user-dirs.dirs"); + if (!configFile.exists()) + return fallback; + + if (!configFile.open(QIODevice::ReadOnly | QIODevice::Text)) + return fallback; + + QString userDirVar("XDG_" + folderName.toUpper() + "_DIR"); + QTextStream in(&configFile); + QString line; + while (!in.atEnd()) + { + line = in.readLine(); + if (line.contains(userDirVar)) + { + configFile.close(); + + // get path between quotes + line = line.section(QLatin1Char('"'), 1, 1); + line.replace(QLatin1String("$HOME"), QLatin1String("~")); + fixBashShortcuts(line); + return line; + } + } + + configFile.close(); + return fallback; +} + + +/************************************************ + + ************************************************/ +bool XdgDirs::setUserDir(XdgDirs::UserDirectory dir, const QString& value, bool createDir) +{ + // possible values for UserDirectory + if (dir < 0 || dir > 7) + return false; + + if (!(value.startsWith(QLatin1String("$HOME")) + || value.startsWith(QLatin1String("~/")) + || value.startsWith(QString(getenv("HOME"))))) + return false; + + QString folderName = userDirectoryString[dir]; + + QString configDir(configHome()); + QFile configFile(configDir % QLatin1String("/user-dirs.dirs")); + + // create the file if doesn't exist and opens it + if (!configFile.open(QIODevice::ReadWrite | QIODevice::Text)) + return false; + + QTextStream stream(&configFile); + QVector lines; + QString line; + bool foundVar = false; + while (!stream.atEnd()) + { + line = stream.readLine(); + if (line.indexOf(QLatin1String("XDG_") + folderName.toUpper() + QLatin1String("_DIR")) == 0) + { + foundVar = true; + QString path = line.section(QLatin1Char('"'), 1, 1); + line.replace(path, value); + lines.append(line); + } + else if (line.indexOf(QLatin1String("XDG_")) == 0) + { + lines.append(line); + } + } + + stream.reset(); + configFile.resize(0); + if (!foundVar) + stream << QString("XDG_%1_DIR=\"%2\"\n").arg(folderName.toUpper()).arg(value); + + for (QVector::iterator i = lines.begin(); i != lines.end(); ++i) + stream << *i << "\n"; + + configFile.close(); + + if (createDir) { + QString path = QString(value).replace(QLatin1String("$HOME"), QLatin1String("~")); + fixBashShortcuts(path); + QDir().mkpath(path); + } + + return true; +} + + +/************************************************ + + ************************************************/ +QString XdgDirs::dataHome(bool createDir) +{ +#if QT_VERSION >= QT_VERSION_CHECK(5,0,0) + QString s = QStandardPaths::writableLocation(QStandardPaths::DataLocation); + fixBashShortcuts(s); + if (createDir) + return createDirectory(s); + + removeEndingSlash(s); + return s; +#else + return xdgSingleDir("XDG_DATA_HOME", QLatin1String(".local/share"), createDir); +#endif +} + + +/************************************************ + + ************************************************/ +QString XdgDirs::configHome(bool createDir) +{ +#if QT_VERSION >= QT_VERSION_CHECK(5,0,0) + QString s = QStandardPaths::writableLocation(QStandardPaths::GenericConfigLocation); + fixBashShortcuts(s); + if (createDir) + return createDirectory(s); + + removeEndingSlash(s); + return s; +#else + return xdgSingleDir("XDG_CONFIG_HOME", QLatin1String(".config"), createDir); +#endif +} + + +/************************************************ + + ************************************************/ +QStringList XdgDirs::dataDirs(const QString &postfix) +{ +#if QT_VERSION >= QT_VERSION_CHECK(5,0,0) + QString d = QFile::decodeName(qgetenv("XDG_DATA_DIRS")); + QStringList dirs = d.split(QLatin1Char(':'), QString::SkipEmptyParts); + + QMutableListIterator it(dirs); + while (it.hasNext()) { + const QString dir = it.next(); + if (!dir.startsWith(QLatin1Char('/'))) + it.remove(); + } + + dirs.removeDuplicates(); + cleanAndAddPostfix(dirs, postfix); + return dirs; +#else + QStringList dirs = xdgDirList("XDG_DATA_DIRS", postfix); + if (dirs.isEmpty()) + { + dirs << QLatin1String("/usr/local/share") % postfix; + dirs << QLatin1String("/usr/share") % postfix; + } + + return dirs; +#endif + +} + + +/************************************************ + + ************************************************/ +QStringList XdgDirs::configDirs(const QString &postfix) +{ +#if QT_VERSION >= QT_VERSION_CHECK(5,0,0) + QStringList dirs; + const QString env = QFile::decodeName(qgetenv("XDG_CONFIG_DIRS")); + if (env.isEmpty()) + dirs.append(QString::fromLatin1("/etc/xdg")); + else + dirs = env.split(QLatin1Char(':'), QString::SkipEmptyParts); + + cleanAndAddPostfix(dirs, postfix); + return dirs; +#else + QStringList dirs = xdgDirList("XDG_CONFIG_DIRS", postfix); + if (dirs.isEmpty()) + { + dirs << QLatin1String("/etc/xdg") % postfix; + } + + return dirs; +#endif +} + + +/************************************************ + + ************************************************/ +QString XdgDirs::cacheHome(bool createDir) +{ +#if QT_VERSION >= QT_VERSION_CHECK(5,0,0) + QString s = QStandardPaths::writableLocation(QStandardPaths::GenericCacheLocation); + fixBashShortcuts(s); + if (createDir) + return createDirectory(s); + + removeEndingSlash(s); + return s; +#else + return xdgSingleDir("XDG_CACHE_HOME", QLatin1String(".cache"), createDir); +#endif + +} + + +/************************************************ + + ************************************************/ +QString XdgDirs::runtimeDir() +{ +#if QT_VERSION >= QT_VERSION_CHECK(5,0,0) + QString result = QStandardPaths::writableLocation(QStandardPaths::RuntimeLocation); + fixBashShortcuts(result); + removeEndingSlash(result); + return result; +#else + QString result(getenv("XDG_RUNTIME_DIR")); + fixBashShortcuts(result); + return result; +#endif +} + + +/************************************************ + + ************************************************/ +QString XdgDirs::autostartHome(bool createDir) +{ + QString s = QString("%1/autostart").arg(configHome(createDir)); + fixBashShortcuts(s); + + if (createDir) + return createDirectory(s); + + QDir d(s); + QString r = d.absolutePath(); + removeEndingSlash(r); + return r; +} + + +/************************************************ + + ************************************************/ +QStringList XdgDirs::autostartDirs(const QString &postfix) +{ + QStringList dirs; + QStringList s = configDirs(); + foreach(QString dir, s) + dirs << QString("%1/autostart").arg(dir) + postfix; + + return dirs; +} diff --git a/xdgdirs.h b/xdgdirs.h new file mode 100644 index 0000000..0ac1298 --- /dev/null +++ b/xdgdirs.h @@ -0,0 +1,149 @@ +/* BEGIN_COMMON_COPYRIGHT_HEADER + * (c)LGPL2+ + * + * Razor - a lightweight, Qt based, desktop toolset + * http://razor-qt.org + * + * Copyright: 2010-2011 Razor team + * Authors: + * Alexander Sokoloff + * + * 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 */ + + + +#ifndef QTXDG_XDGDIRS_H +#define QTXDG_XDGDIRS_H + +#include "xdgmacros.h" +#include +#include + +/*! @brief The XdgMenu class implements the "XDG Base Directory Specification" from freedesktop.org. + * This specification defines where these files should be looked for by defining one or more base + * directories relative to which files should be located. + * + * All postfix parameters should start with an '/' slash. + * + * @sa http://standards.freedesktop.org/basedir-spec/basedir-spec-latest.html + */ + +class QTXDG_API XdgDirs +{ +public: + enum UserDirectory + { + Desktop, + Download, + Templates, + PublicShare, + Documents, + Music, + Pictures, + Videos + }; + + /*! @brief Returns the path to the user folder passed as parameter dir defined in + * $XDG_CONFIG_HOME/user-dirs.dirs. Returns /tmp if no $HOME defined, $HOME/Desktop if + * dir equals XdgDirs::Desktop or $HOME othewise. + */ + static QString userDir(UserDirectory dir); + + /*! @brief Returns true if writting into configuration file $XDG_CONFIG_HOME/user-dirs.dirs + * the path in value for the directory in dir is succesfull. Returns false otherwise. If + * createDir is true, dir will be created if it doesn't exist. + */ + static bool setUserDir(UserDirectory dir, const QString &value, bool createDir); + + /*! @brief Returns the path to the directory that corresponds to the $XDG_DATA_HOME. + * If @i createDir is true, the function will create the directory. + * + * $XDG_DATA_HOME defines the base directory relative to which user specific data files + * should be stored. If $XDG_DATA_HOME is either not set or empty, a default equal to + * $HOME/.local/share should be used. + */ + static QString dataHome(bool createDir=true); + + + /*! @brief Returns the path to the directory that corresponds to the $XDG_CONFIG_HOME. + * If @i createDir is true, the function will create the directory. + * + * $XDG_CONFIG_HOME defines the base directory relative to which user specific configuration + * files should be stored. If $XDG_CONFIG_HOME is either not set or empty, a default equal + * to $HOME/.config should be used. + */ + static QString configHome(bool createDir=true); + + + /*! @brief Returns a list of all directories that corresponds to the $XDG_DATA_DIRS. + * $XDG_DATA_DIRS defines the preference-ordered set of base directories to search for data + * files in addition to the $XDG_DATA_HOME base directory. If $XDG_DATA_DIRS is either not set + * or empty, a value equal to /usr/local/share:/usr/share is used. + * + * If the postfix is not empty it will append to end of each returned directory. + */ + static QStringList dataDirs(const QString &postfix = QString()); + + + /*! @brief Returns a list of all directories that corresponds to the $XDG_CONFIG_DIRS. + * $XDG_CONFIG_DIRS defines the preference-ordered set of base directories to search for + * configuration files in addition to the $XDG_CONFIG_HOME base directory. If $XDG_CONFIG_DIRS + * is either not set or empty, a value equal to /etc/xdg should be used. + * + * If the postfix is not empty it will append to end of each returned directory. + */ + static QStringList configDirs(const QString &postfix = QString()); + + + /*! @brief Returns the path to the directory that corresponds to the $XDG_CACHE_HOME. + * If @i createDir is true, the function will create the directory. + * + * $XDG_CACHE_HOME defines the base directory relative to which user specific non-essential + * data files should be stored. If $XDG_CACHE_HOME is either not set or empty, + * a default equal to $HOME/.cache should be used. + */ + static QString cacheHome(bool createDir=true); + + + /*! @brief Returns the path to the directory that corresponds to the $XDG_RUNTIME_DIR. + * $XDG_RUNTIME_DIR defines the base directory relative to which user-specific non-essential + * runtime files and other file objects (such as sockets, named pipes, ...) should be stored. + * The directory MUST be owned by the user, and he MUST be the only one having read and write + * access to it. Its Unix access mode MUST be 0700. + */ + static QString runtimeDir(); + + /*! @brief Returns the path to the directory that corresponds to the $XDG_CONFIG_HOME/autostart + * + * If $XDG_CONFIG_HOME is not set, the Autostart Directory in the user's home directory is + * ~/.config/autostart/ + */ + static QString autostartHome(bool createDir=true); + + /*! @brief Returns a list of all directories that correspond to $XDG_CONFIG_DIRS/autostart + * If $XDG_CONFIG_DIRS is not set, the system wide Autostart Directory is /etc/xdg/autostart + * + * If the postfix is not empty it will append to end of each returned directory. + * + * Note: this does not include the user's autostart directory + * @sa autostartHome() + */ + static QStringList autostartDirs(const QString &postfix = QString()); +}; + +#endif // QTXDG_XDGDIRS_H diff --git a/xdgicon.cpp b/xdgicon.cpp new file mode 100644 index 0000000..d32e5e9 --- /dev/null +++ b/xdgicon.cpp @@ -0,0 +1,200 @@ +/* BEGIN_COMMON_COPYRIGHT_HEADER + * (c)LGPL2+ + * + * Razor - a lightweight, Qt based, desktop toolset + * http://razor-qt.org + * + * Copyright: 2010-2011 Razor team + * Authors: + * Alexander Sokoloff + * + * 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 "xdgicon.h" + +#include +#include +#include +#include +#include +#include +#if QT_VERSION < QT_VERSION_CHECK(5,0,0) +#include "qiconfix/qiconloader_p_qt4.h" +#else +#include "qiconfix/qiconloader_p.h" +#endif +#include + +#define DEFAULT_APP_ICON "application-x-executable" + +/************************************************ + + ************************************************/ +static void qt_cleanup_icon_cache(); +typedef QCache IconCache; + +namespace { +struct QtIconCache: public IconCache +{ + QtIconCache() + { + qAddPostRoutine(qt_cleanup_icon_cache); + } +}; +} +Q_GLOBAL_STATIC(IconCache, qtIconCache); + +static void qt_cleanup_icon_cache() +{ + qtIconCache()->clear(); +} + + + +/************************************************ + + ************************************************/ +XdgIcon::XdgIcon() +{ +} + + +/************************************************ + + ************************************************/ +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); + QtXdg::QIconLoader::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. + ************************************************/ +QIcon XdgIcon::fromTheme(const QString& iconName, const QIcon& fallback) +{ + if (iconName.isEmpty()) + return fallback; + + bool isAbsolute = (iconName[0] == '/'); + + QString name = QFileInfo(iconName).fileName(); + if (name.endsWith(".png", Qt::CaseInsensitive) || + name.endsWith(".svg", Qt::CaseInsensitive) || + name.endsWith(".xpm", Qt::CaseInsensitive)) + { + name.truncate(name.length() - 4); + } + + QIcon icon; + + if (qtIconCache()->contains(name)) { + icon = *qtIconCache()->object(name); + } else { + QIcon *cachedIcon; + if (!isAbsolute) + cachedIcon = new QIcon(new QtXdg::QIconLoaderEngineFixed(name)); + else + cachedIcon = new QIcon(iconName); + qtIconCache()->insert(name, cachedIcon); + icon = *cachedIcon; + } + + // Note the qapp check is to allow lazy loading of static icons + // Supporting fallbacks will not work for this case. + if (qApp && !isAbsolute && icon.availableSizes().isEmpty()) + { + return fallback; + } + return icon; +} + + +/************************************************ + Returns the QIcon corresponding to names in the current icon theme. If no such icon + is found in the current theme fallback is return instead. + ************************************************/ +QIcon XdgIcon::fromTheme(const QStringList& iconNames, const QIcon& fallback) +{ + foreach (QString iconName, iconNames) + { + QIcon icon = fromTheme(iconName); + if (!icon.isNull()) + return icon; + } + + return fallback; +} + + +/************************************************ + + ************************************************/ +QIcon XdgIcon::fromTheme(const QString &iconName, + const QString &fallbackIcon1, + const QString &fallbackIcon2, + const QString &fallbackIcon3, + const QString &fallbackIcon4) +{ + QStringList icons; + icons << iconName; + if (!fallbackIcon1.isEmpty()) icons << fallbackIcon1; + if (!fallbackIcon2.isEmpty()) icons << fallbackIcon2; + if (!fallbackIcon3.isEmpty()) icons << fallbackIcon3; + if (!fallbackIcon4.isEmpty()) icons << fallbackIcon4; + + return fromTheme(icons); +} + +/************************************************ + + ************************************************/ +QIcon XdgIcon::defaultApplicationIcon() +{ + return fromTheme(DEFAULT_APP_ICON); +} + + +/************************************************ + + ************************************************/ +QString XdgIcon::defaultApplicationIconName() +{ + return DEFAULT_APP_ICON; +} diff --git a/xdgicon.h b/xdgicon.h new file mode 100644 index 0000000..63fe6ea --- /dev/null +++ b/xdgicon.h @@ -0,0 +1,62 @@ +/* BEGIN_COMMON_COPYRIGHT_HEADER + * (c)LGPL2+ + * + * Razor - a lightweight, Qt based, desktop toolset + * http://razor-qt.org + * + * Copyright: 2010-2011 Razor team + * Authors: + * Alexander Sokoloff + * + * 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 */ + + + +#ifndef QTXDG_XDGICON_H +#define QTXDG_XDGICON_H + +#include "xdgmacros.h" +#include +#include +#include + +class QTXDG_API XdgIcon +{ +public: + static QIcon fromTheme(const QString& iconName, const QIcon& fallback = QIcon()); + static QIcon fromTheme(const QString& iconName, + const QString &fallbackIcon1, + const QString &fallbackIcon2 = QString(), + const QString &fallbackIcon3 = QString(), + const QString &fallbackIcon4 = QString()); + static QIcon fromTheme(const QStringList& iconNames, const QIcon& fallback = QIcon()); + + static QString themeName(); + static void setThemeName(const QString& themeName); + + static QIcon defaultApplicationIcon(); + static QString defaultApplicationIconName(); + +protected: + explicit XdgIcon(); + virtual ~XdgIcon(); +private: + +}; + +#endif // QTXDG_XDGICON_H diff --git a/xdgmacros.h b/xdgmacros.h new file mode 100644 index 0000000..4c7fe1c --- /dev/null +++ b/xdgmacros.h @@ -0,0 +1,45 @@ +/* + * libqtxdg - An Qt implementation of freedesktop.org xdg specs + * Copyright (C) 2014 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 + */ + +#ifndef QTXDG_MACROS_H +#define QTXDG_MACROS_H + +#ifdef __cplusplus +# include +# ifndef QTXDG_DEPRECATED +# define QTXDG_DEPRECATED Q_DECL_DEPRECATED +# endif +#endif + +#ifdef QTXDG_COMPILATION + #define QTXDG_API Q_DECL_EXPORT +#else + #define QTXDG_API Q_DECL_IMPORT +#endif + +#if defined(QTXDG_COMPILATION) && defined(QTXDG_BUILDING_TESTS) +# define QTXDG_AUTOTEST Q_DECL_IMPORT +#elif defined(QTXDG_COMPILATION) && defined(QTXDG_TESTS) +# define QTXDG_AUTOTEST Q_DECL_EXPORT +#else +# define QTXDG_AUTOTEST +#endif + +#endif // QTXDG_MACROS_H diff --git a/xdgmenu.cpp b/xdgmenu.cpp new file mode 100644 index 0000000..9e538e8 --- /dev/null +++ b/xdgmenu.cpp @@ -0,0 +1,825 @@ +/* BEGIN_COMMON_COPYRIGHT_HEADER + * (c)LGPL2+ + * + * Razor - a lightweight, Qt based, desktop toolset + * http://razor-qt.org + * + * Copyright: 2010-2011 Razor team + * Authors: + * Alexander Sokoloff + * + * 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 "xdgmenu.h" +#include "xdgmenu_p.h" +#include "xdgmenureader.h" +#include "xmlhelper.h" +#include "xdgmenurules.h" +#include "xdgmenuapplinkprocessor.h" +#include "xdgdirs.h" +#include "xdgmenulayoutprocessor.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +// Helper functions prototypes +void installTranslation(const QString &name); +bool isParent(const QDomElement& parent, const QDomElement& child); + +void installTranslation(const QString &name) +{ + static bool alreadyLoaded = false; + + if (alreadyLoaded) + return; + + QString locale = QLocale::system().name(); + QTranslator *translator = new QTranslator(qApp); + translator->load(QString("%1/%2_%3.qm").arg(TRANSLATIONS_DIR, name, locale)); + + QCoreApplication::installTranslator(translator); + alreadyLoaded = true; +} + + +/************************************************ + + ************************************************/ +XdgMenu::XdgMenu(QObject *parent) : + QObject(parent), + d_ptr(new XdgMenuPrivate(this)) +{ + installTranslation("libqtxdg"); +} + + +/************************************************ + + ************************************************/ +XdgMenu::~XdgMenu() +{ + Q_D(XdgMenu); + delete d; +} + + +/************************************************ + + ************************************************/ +XdgMenuPrivate::XdgMenuPrivate(XdgMenu *parent): + mOutDated(true), + q_ptr(parent) +{ + mRebuildDelayTimer.setSingleShot(true); + mRebuildDelayTimer.setInterval(REBUILD_DELAY); + + connect(&mRebuildDelayTimer, SIGNAL(timeout()), this, SLOT(rebuild())); + connect(&mWatcher, SIGNAL(fileChanged(QString)), &mRebuildDelayTimer, SLOT(start())); + connect(&mWatcher, SIGNAL(directoryChanged(QString)), &mRebuildDelayTimer, SLOT(start())); + + + connect(this, SIGNAL(changed()), q_ptr, SIGNAL(changed())); +} + + +/************************************************ + + ************************************************/ +const QString XdgMenu::logDir() const +{ + Q_D(const XdgMenu); + return d->mLogDir; +} + + +/************************************************ + + ************************************************/ +void XdgMenu::setLogDir(const QString& directory) +{ + Q_D(XdgMenu); + d->mLogDir = directory; +} + + +/************************************************ + + ************************************************/ +const QDomDocument XdgMenu::xml() const +{ + Q_D(const XdgMenu); + return d->mXml; +} + + +/************************************************ + + ************************************************/ +QString XdgMenu::menuFileName() const +{ + Q_D(const XdgMenu); + return d->mMenuFileName; +} + + +/************************************************ + + ************************************************/ +QStringList XdgMenu::environments() +{ + Q_D(XdgMenu); + return d->mEnvironments; +} + +void XdgMenu::setEnvironments(const QStringList &envs) +{ + Q_D(XdgMenu); + d->mEnvironments = envs; +} +void XdgMenu::setEnvironments(const QString &env) +{ + setEnvironments(QStringList() << env); +} + +/************************************************ + + ************************************************/ +const QString XdgMenu::errorString() const +{ + Q_D(const XdgMenu); + return d->mErrorString; +} + + +/************************************************ + + ************************************************/ +bool XdgMenu::read(const QString& menuFileName) +{ + Q_D(XdgMenu); + + d->mMenuFileName = menuFileName; + + d->clearWatcher(); + + XdgMenuReader reader(this); + if (!reader.load(d->mMenuFileName)) + { + qWarning() << reader.errorString(); + d->mErrorString = reader.errorString(); + return false; + } + + d->mXml = reader.xml(); + QDomElement root = d->mXml.documentElement(); + d->saveLog("00-reader.xml"); + + d->simplify(root); + d->saveLog("01-simplify.xml"); + + d->mergeMenus(root); + d->saveLog("02-mergeMenus.xml"); + + d->moveMenus(root); + d->saveLog("03-moveMenus.xml"); + + d->mergeMenus(root); + d->saveLog("04-mergeMenus.xml"); + + d->deleteDeletedMenus(root); + d->saveLog("05-deleteDeletedMenus.xml"); + + d->processDirectoryEntries(root, QStringList()); + d->saveLog("06-processDirectoryEntries.xml"); + + d->processApps(root); + d->saveLog("07-processApps.xml"); + + d->processLayouts(root); + d->saveLog("08-processLayouts.xml"); + + d->deleteEmpty(root); + d->saveLog("09-deleteEmpty.xml"); + + d->fixSeparators(root); + d->saveLog("10-fixSeparators.xml"); + + + d->mOutDated = false; + d->mHash = QCryptographicHash::hash(d->mXml.toByteArray(), QCryptographicHash::Md5); + + return true; +} + + +/************************************************ + + ************************************************/ +void XdgMenu::save(const QString& fileName) +{ + Q_D(const XdgMenu); + + QFile file(fileName); + if (!file.open(QFile::WriteOnly | QFile::Text)) + { + qWarning() << QString("Cannot write file %1:\n%2.") + .arg(fileName) + .arg(file.errorString()); + return; + } + + QTextStream ts(&file); + d->mXml.save(ts, 2); + + file.close(); +} + + +/************************************************ + For debug only + ************************************************/ +void XdgMenuPrivate::load(const QString& fileName) +{ + QFile file(fileName); + if (!file.open(QFile::ReadOnly | QFile::Text)) + { + qWarning() << QString("%1 not loading: %2").arg(fileName).arg(file.errorString()); + return; + } + mXml.setContent(&file, true); +} + + +/************************************************ + + ************************************************/ +void XdgMenuPrivate::saveLog(const QString& logFileName) +{ + Q_Q(XdgMenu); + if (!mLogDir.isEmpty()) + q->save(mLogDir + "/" + logFileName); +} + + +/************************************************ + + ************************************************/ +void XdgMenuPrivate::mergeMenus(QDomElement& element) +{ + QHash menus; + + MutableDomElementIterator it(element, "Menu"); + + it.toFront(); + while(it.hasNext()) + { + it.next(); + menus[it.current().attribute("name")] = it.current(); + } + + + it.toBack(); + while (it.hasPrevious()) + { + QDomElement src = it.previous(); + QDomElement dest = menus[src.attribute("name")]; + if (dest != src) + { + prependChilds(src, dest); + element.removeChild(src); + } + } + + + QDomElement n = element.firstChildElement("Menu"); + while (!n.isNull()) + { + mergeMenus(n); + n = n.nextSiblingElement("Menu"); + } + + it.toFront(); + while(it.hasNext()) + mergeMenus(it.next()); +} + + +/************************************************ + + ************************************************/ +void XdgMenuPrivate::simplify(QDomElement& element) +{ + MutableDomElementIterator it(element); + //it.toFront(); + while(it.hasNext()) + { + QDomElement n = it.next(); + + if (n.tagName() == "Name") + { + // The field must not contain the slash character ("/"); + // implementations should discard any name containing a slash. + element.setAttribute("name", n.text().remove('/')); + n.parentNode().removeChild(n); + } + + // ...................................... + else if(n.tagName() == "Deleted") + { + element.setAttribute("deleted", true); + n.parentNode().removeChild(n); + } + else if(n.tagName() == "NotDeleted") + { + element.setAttribute("deleted", false); + n.parentNode().removeChild(n); + } + + // ...................................... + else if(n.tagName() == "OnlyUnallocated") + { + element.setAttribute("onlyUnallocated", true); + n.parentNode().removeChild(n); + } + else if(n.tagName() == "NotOnlyUnallocated") + { + element.setAttribute("onlyUnallocated", false); + n.parentNode().removeChild(n); + } + + // ...................................... + else if(n.tagName() == "FileInfo") + { + n.parentNode().removeChild(n); + } + + // ...................................... + else if(n.tagName() == "Menu") + { + simplify(n); + } + + } + +} + + +/************************************************ + + ************************************************/ +void XdgMenuPrivate::prependChilds(QDomElement& srcElement, QDomElement& destElement) +{ + MutableDomElementIterator it(srcElement); + + it.toBack(); + while(it.hasPrevious()) + { + QDomElement n = it.previous(); + destElement.insertBefore(n, destElement.firstChild()); + } + + if (srcElement.attributes().contains("deleted") && + !destElement.attributes().contains("deleted") + ) + destElement.setAttribute("deleted", srcElement.attribute("deleted")); + + if (srcElement.attributes().contains("onlyUnallocated") && + !destElement.attributes().contains("onlyUnallocated") + ) + destElement.setAttribute("onlyUnallocated", srcElement.attribute("onlyUnallocated")); +} + + +/************************************************ + + ************************************************/ +void XdgMenuPrivate::appendChilds(QDomElement& srcElement, QDomElement& destElement) +{ + MutableDomElementIterator it(srcElement); + + while(it.hasNext()) + destElement.appendChild(it.next()); + + if (srcElement.attributes().contains("deleted")) + destElement.setAttribute("deleted", srcElement.attribute("deleted")); + + if (srcElement.attributes().contains("onlyUnallocated")) + destElement.setAttribute("onlyUnallocated", srcElement.attribute("onlyUnallocated")); +} + + +/************************************************ + Search item by path. The path can be absolute or relative. If the element not + found, the behavior depends on a parameter "createNonExisting." If it's true, then + the missing items will be created, otherwise the function returns 0. + ************************************************/ +QDomElement XdgMenu::findMenu(QDomElement& baseElement, const QString& path, bool createNonExisting) +{ + Q_D(XdgMenu); + // Absolute path .................. + if (path.startsWith('/')) + { + QDomElement root = d->mXml.documentElement(); + return findMenu(root, path.section('/', 2), createNonExisting); + } + + // Relative path .................. + if (path.isEmpty()) + return baseElement; + + + QString name = path.section('/', 0, 0); + MutableDomElementIterator it(baseElement); + while(it.hasNext()) + { + QDomElement n = it.next(); + if (n.attribute("name") == name) + return findMenu(n, path.section('/', 1), createNonExisting); + } + + + + // Not found ...................... + if (!createNonExisting) + return QDomElement(); + + + QStringList names = path.split('/', QString::SkipEmptyParts); + QDomElement el = baseElement; + foreach (QString name, names) + { + QDomElement p = el; + el = d->mXml.createElement("Menu"); + p.appendChild(el); + el.setAttribute("name", name); + } + return el; + +} + + +/************************************************ + + ************************************************/ +bool isParent(const QDomElement& parent, const QDomElement& child) +{ + QDomNode n = child; + while (!n.isNull()) + { + if (n == parent) + return true; + n = n.parentNode(); + } + return false; +} + + +/************************************************ + Recurse to resolve elements for each menu starting with any child menu before + handling the more top level menus. So the deepest menus have their operations + performed first. Within each , execute operations in the order that + they appear. + If the destination path does not exist, simply relocate the origin element, + and change its field to match the destination path. + If the origin path does not exist, do nothing. + If both paths exist, take the origin element, delete its element, and + prepend its remaining child elements to the destination element. + ************************************************/ +void XdgMenuPrivate::moveMenus(QDomElement& element) +{ + Q_Q(XdgMenu); + + { + MutableDomElementIterator i(element, "Menu"); + while(i.hasNext()) + moveMenus(i.next()); + } + + MutableDomElementIterator i(element, "Move"); + while(i.hasNext()) + { + i.next(); + QString oldPath = i.current().lastChildElement("Old").text(); + QString newPath = i.current().lastChildElement("New").text(); + + element.removeChild(i.current()); + + if (oldPath.isEmpty() || newPath.isEmpty()) + continue; + + QDomElement oldMenu = q->findMenu(element, oldPath, false); + if (oldMenu.isNull()) + continue; + + QDomElement newMenu = q->findMenu(element, newPath, true); + + if (isParent(oldMenu, newMenu)) + continue; + + appendChilds(oldMenu, newMenu); + oldMenu.parentNode().removeChild(oldMenu); + } +} + + +/************************************************ + For each containing a element which is not followed by a + element, remove that menu and all its child menus. + + Kmenuedit create .hidden menu entry, delete it too. + ************************************************/ +void XdgMenuPrivate::deleteDeletedMenus(QDomElement& element) +{ + MutableDomElementIterator i(element, "Menu"); + while(i.hasNext()) + { + QDomElement e = i.next(); + if (e.attribute("deleted") == "1" || + e.attribute("name") == ".hidden" + ) + element.removeChild(e); + else + deleteDeletedMenus(e); + } + +} + + +/************************************************ + + ************************************************/ +void XdgMenuPrivate::processDirectoryEntries(QDomElement& element, const QStringList& parentDirs) +{ + QStringList dirs; + QStringList files; + + element.setAttribute("title", element.attribute("name")); + + MutableDomElementIterator i(element, QString()); + i.toBack(); + while(i.hasPrevious()) + { + QDomElement e = i.previous(); + + if (e.tagName() == "Directory") + { + files << e.text(); + element.removeChild(e); + } + + else if (e.tagName() == "DirectoryDir") + { + dirs << e.text(); + element.removeChild(e); + } + } + + dirs << parentDirs; + + bool found = false; + foreach(QString file, files){ + if (file.startsWith('/')) + found = loadDirectoryFile(file, element); + else + { + foreach (QString dir, dirs) + { + found = loadDirectoryFile(dir + "/" + file, element); + if (found) break; + } + } + if (found) break; + } + + + MutableDomElementIterator it(element, "Menu"); + while(it.hasNext()) + { + QDomElement e = it.next(); + processDirectoryEntries(e, dirs); + } + +} + + +/************************************************ + + ************************************************/ +bool XdgMenuPrivate::loadDirectoryFile(const QString& fileName, QDomElement& element) +{ + XdgDesktopFile file; + file.load(fileName); + + if (!file.isValid()) + return false; + + + element.setAttribute("title", file.localizedValue("Name").toString()); + element.setAttribute("comment", file.localizedValue("Comment").toString()); + element.setAttribute("icon", file.value("Icon").toString()); + + Q_Q(XdgMenu); + q->addWatchPath(QFileInfo(file.fileName()).absolutePath()); + return true; +} + + +/************************************************ + + ************************************************/ +void XdgMenuPrivate::processApps(QDomElement& element) +{ + Q_Q(XdgMenu); + XdgMenuApplinkProcessor processor(element, q); + processor.run(); +} + + +/************************************************ + + ************************************************/ +void XdgMenuPrivate::deleteEmpty(QDomElement& element) +{ + MutableDomElementIterator it(element, "Menu"); + while(it.hasNext()) + deleteEmpty(it.next()); + + if (element.attribute("keep") == "true") + return; + + QDomElement childMenu = element.firstChildElement("Menu"); + QDomElement childApps = element.firstChildElement("AppLink"); + + if (childMenu.isNull() && childApps.isNull()) + { + element.parentNode().removeChild(element); + } +} + + +/************************************************ + + ************************************************/ +void XdgMenuPrivate::processLayouts(QDomElement& element) +{ + XdgMenuLayoutProcessor proc(element); + proc.run(); +} + + +/************************************************ + + ************************************************/ +void XdgMenuPrivate::fixSeparators(QDomElement& element) +{ + + MutableDomElementIterator it(element, "Separator"); + while(it.hasNext()) + { + QDomElement s = it.next(); + if (s.previousSiblingElement().tagName() == "Separator") + element.removeChild(s); + } + + + QDomElement first = element.firstChild().toElement(); + if (first.tagName() == "Separator") + element.removeChild(first); + + QDomElement last = element.lastChild().toElement(); + if (last.tagName() == "Separator") + element.removeChild(last); + + + MutableDomElementIterator mi(element, "Menu"); + while(mi.hasNext()) + fixSeparators(mi.next()); +} + + +/************************************************ + $XDG_CONFIG_DIRS/menus/${XDG_MENU_PREFIX}applications.menu + The first file found in the search path should be used; other files are ignored. + ************************************************/ +QString XdgMenu::getMenuFileName(const QString& baseName) +{ + QStringList configDirs = XdgDirs::configDirs(); + QString menuPrefix = getenv("XDG_MENU_PREFIX"); + + foreach(QString configDir, configDirs) + { + QFileInfo file(QString("%1/menus/%2%3").arg(configDir, menuPrefix, baseName)); + if (file.exists()) + return file.filePath(); + } + + QStringList wellKnownFiles; + // razor- is a priority for us + wellKnownFiles << "razor-applications.menu"; + // the "global" menu file name on suse and fedora + wellKnownFiles << "applications.menu"; + // rest files ordered by priority (descending) + wellKnownFiles << "kde4-applications.menu"; + wellKnownFiles << "kde-applications.menu"; + wellKnownFiles << "gnome-applications.menu"; + wellKnownFiles << "lxde-applications.menu"; + + foreach(QString configDir, configDirs) + { + foreach (QString f, wellKnownFiles) + { + QFileInfo file(QString("%1/menus/%2").arg(configDir, f)); + if (file.exists()) + return file.filePath(); + } + } + + + return QString(); +} + + +/************************************************ + + ************************************************/ +void XdgMenu::addWatchPath(const QString &path) +{ + Q_D(XdgMenu); + + if (d->mWatcher.files().contains(path)) + return; + + if (d->mWatcher.directories().contains(path)) + return; + + d->mWatcher.addPath(path); +} + + +/************************************************ + + ************************************************/ +bool XdgMenu::isOutDated() const +{ + Q_D(const XdgMenu); + return d->mOutDated; +} + + +/************************************************ + + ************************************************/ +void XdgMenuPrivate::rebuild() +{ + Q_Q(XdgMenu); + QByteArray prevHash = mHash; + q->read(mMenuFileName); + + if (prevHash != mHash) + { + mOutDated = true; + emit changed(); + } +} + + +/************************************************ + + ************************************************/ +void XdgMenuPrivate::clearWatcher() +{ + QStringList sl; + sl << mWatcher.files(); + sl << mWatcher.directories(); + if (sl.length()) + mWatcher.removePaths(sl); +} diff --git a/xdgmenu.h b/xdgmenu.h new file mode 100644 index 0000000..54357ea --- /dev/null +++ b/xdgmenu.h @@ -0,0 +1,130 @@ +/* BEGIN_COMMON_COPYRIGHT_HEADER + * (c)LGPL2+ + * + * Razor - a lightweight, Qt based, desktop toolset + * http://razor-qt.org + * + * Copyright: 2010-2011 Razor team + * Authors: + * Alexander Sokoloff + * + * 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 */ + + + +#ifndef QTXDG_XDGMENU_H +#define QTXDG_XDGMENU_H + +#include "xdgmacros.h" +#include +#include +#include +#include + + +class QDomDocument; +class QDomElement; +class XdgMenuPrivate; + + +/*! @brief The XdgMenu class implements the "Desktop Menu Specification" from freedesktop.org. + + Freedesktop menu is a user-visible hierarchy of applications, typically displayed as a menu. + + Example usage: +@code + QString menuFile = XdgMenu::getMenuFileName(); + XdgMenu xdgMenu(); + + bool res = xdgMenu.read(menuFile); + if (!res) + { + QMessageBox::warning(this, "Parse error", xdgMenu.errorString()); + } + + QDomElement rootElement = xdgMenu.xml().documentElement() + @endcode + + @sa http://specifications.freedesktop.org/menu-spec/menu-spec-latest.html + */ + +class QTXDG_API XdgMenu : public QObject +{ +Q_OBJECT + friend class XdgMenuReader; + friend class XdgMenuApplinkProcessor; + +public: + explicit XdgMenu(QObject *parent = 0); + virtual ~XdgMenu(); + + bool read(const QString& menuFileName); + void save(const QString& fileName); + + const QDomDocument xml() const; + QString menuFileName() const; + + QDomElement findMenu(QDomElement& baseElement, const QString& path, bool createNonExisting); + + /*! Returns a list of strings identifying the environments that should + * display a desktop entry. Internally all comparisions involving the + * desktop enviroment names are made case insensitive. + */ + QStringList environments(); + + /*! + * Set currently running environments. Example: RAZOR, KDE, or GNOME... + * Internally all comparisions involving the desktop enviroment names + * are made case insensitive. + */ + void setEnvironments(const QStringList &envs); + void setEnvironments(const QString &env); + + /*! + * Returns a string description of the last error that occurred if read() returns false. + */ + const QString errorString() const; + + /*! + * @brief The name of the directory for the debug XML-files. + */ + const QString logDir() const; + + /*! + * @brief The name of the directory for the debug XML-files. If a directory is specified, + * then after you run the XdgMenu::read, you can see and check the results of the each step. + */ + void setLogDir(const QString& directory); + + static QString getMenuFileName(const QString& baseName = "applications.menu"); + + bool isOutDated() const; + +signals: + void changed(); + +protected: + void addWatchPath(const QString& path); + +private: + XdgMenuPrivate* const d_ptr; + Q_DECLARE_PRIVATE(XdgMenu) +}; + + +#endif // QTXDG_XDGMENU_H diff --git a/xdgmenu_p.h b/xdgmenu_p.h new file mode 100644 index 0000000..fb2c936 --- /dev/null +++ b/xdgmenu_p.h @@ -0,0 +1,88 @@ +/* BEGIN_COMMON_COPYRIGHT_HEADER + * (c)LGPL2+ + * + * Razor - a lightweight, Qt based, desktop toolset + * http://razor-qt.org + * + * Copyright: 2010-2011 Razor team + * Authors: + * Alexander Sokoloff + * + * 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 "xdgmenu.h" +#include +#include +#include + +#define REBUILD_DELAY 3000 + +class QDomElement; +class QStringList; +class QString; +class QDomDocument; + +class XdgMenuPrivate: QObject +{ +Q_OBJECT +public: + XdgMenuPrivate(XdgMenu* parent); + + void simplify(QDomElement& element); + void mergeMenus(QDomElement& element); + void moveMenus(QDomElement& element); + void deleteDeletedMenus(QDomElement& element); + void processDirectoryEntries(QDomElement& element, const QStringList& parentDirs); + void processApps(QDomElement& element); + void deleteEmpty(QDomElement& element); + void processLayouts(QDomElement& element); + void fixSeparators(QDomElement& element); + + bool loadDirectoryFile(const QString& fileName, QDomElement& element); + void prependChilds(QDomElement& srcElement, QDomElement& destElement); + void appendChilds(QDomElement& srcElement, QDomElement& destElement); + + void saveLog(const QString& logFileName); + void load(const QString& fileName); + + void clearWatcher(); + + QString mErrorString; + QStringList mEnvironments; + QString mMenuFileName; + QString mLogDir; + QDomDocument mXml; + QByteArray mHash; + QTimer mRebuildDelayTimer; + + QFileSystemWatcher mWatcher; + bool mOutDated; + +public slots: + void rebuild(); + +signals: + void changed(); + + +private: + XdgMenu* const q_ptr; + Q_DECLARE_PUBLIC(XdgMenu) +}; + diff --git a/xdgmenuapplinkprocessor.cpp b/xdgmenuapplinkprocessor.cpp new file mode 100644 index 0000000..c7ceedc --- /dev/null +++ b/xdgmenuapplinkprocessor.cpp @@ -0,0 +1,275 @@ +/* BEGIN_COMMON_COPYRIGHT_HEADER + * (c)LGPL2+ + * + * Razor - a lightweight, Qt based, desktop toolset + * http://razor-qt.org + * + * Copyright: 2010-2011 Razor team + * Authors: + * Alexander Sokoloff + * + * 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 "xdgmenu.h" +#include "xdgmenuapplinkprocessor.h" +#include "xmlhelper.h" +#include "xdgdesktopfile.h" + +#include + + +/************************************************ + + ************************************************/ +XdgMenuApplinkProcessor::XdgMenuApplinkProcessor(QDomElement& element, XdgMenu* menu, XdgMenuApplinkProcessor *parent) : + QObject(parent) +{ + mElement = element; + mParent = parent; + mMenu = menu; + + mOnlyUnallocated = element.attribute("onlyUnallocated") == "1"; + + MutableDomElementIterator i(element, "Menu"); + while(i.hasNext()) + { + QDomElement e = i.next(); + mChilds.append(new XdgMenuApplinkProcessor(e, mMenu, this)); + } + +} + + +/************************************************ + + ************************************************/ +XdgMenuApplinkProcessor::~XdgMenuApplinkProcessor() +{ +} + + +/************************************************ + + ************************************************/ +void XdgMenuApplinkProcessor::run() +{ + step1(); + step2(); +} + + +/************************************************ + + ************************************************/ +void XdgMenuApplinkProcessor::step1() +{ + fillAppFileInfoList(); + createRules(); + + // Check Include rules & mark as allocated ............ + XdgMenuAppFileInfoHashIterator i(mAppFileInfoHash); + while(i.hasNext()) + { + i.next(); + XdgDesktopFile* file = i.value()->desktopFile(); + + if (mRules.checkInclude(i.key(), *file)) + { + if (!mOnlyUnallocated) + i.value()->setAllocated(true); + + if (!mRules.checkExclude(i.key(), *file)) + { + mSelected.append(i.value()); + } + + } + } + + // Process childs menus ............................... + foreach (XdgMenuApplinkProcessor* child, mChilds) + child->step1(); +} + + +/************************************************ + + ************************************************/ +void XdgMenuApplinkProcessor::step2() +{ + // Create AppLinks elements ........................... + QDomDocument doc = mElement.ownerDocument(); + + foreach (XdgMenuAppFileInfo* fileInfo, mSelected) + { + if (mOnlyUnallocated && fileInfo->allocated()) + continue; + + XdgDesktopFile* file = fileInfo->desktopFile(); + + bool show = false; + QStringList envs = mMenu->environments(); + const int N = envs.size(); + for (int i = 0; i < N; ++i) + { + if (file->isShown(envs.at(i))) + { + show = true; + break; + } + } + + if (!show) + continue; + + QDomElement appLink = doc.createElement("AppLink"); + + appLink.setAttribute("id", fileInfo->id()); + appLink.setAttribute("title", file->localizedValue("Name").toString()); + appLink.setAttribute("comment", file->localizedValue("Comment").toString()); + appLink.setAttribute("genericName", file->localizedValue("GenericName").toString()); + appLink.setAttribute("exec", file->value("Exec").toString()); + appLink.setAttribute("terminal", file->value("Terminal").toBool()); + appLink.setAttribute("startupNotify", file->value("StartupNotify").toBool()); + appLink.setAttribute("path", file->value("Path").toString()); + appLink.setAttribute("icon", file->value("Icon").toString()); + appLink.setAttribute("desktopFile", file->fileName()); + + mElement.appendChild(appLink); + + } + + + // Process childs menus ............................... + foreach (XdgMenuApplinkProcessor* child, mChilds) + child->step2(); +} + + +/************************************************ + For each element, build a pool of desktop entries by collecting entries found + in each for the menu element. If two entries have the same desktop-file id, + the entry for the earlier (closer to the top of the file) must be discarded. + + Next, add to the pool the entries for any s specified by ancestor + elements. If a parent menu has a duplicate entry (same desktop-file id), the entry + for the child menu has priority. + ************************************************/ +void XdgMenuApplinkProcessor::fillAppFileInfoList() +{ + // Build a pool by collecting entries found in + { + MutableDomElementIterator i(mElement, "AppDir"); + i.toBack(); + while(i.hasPrevious()) + { + QDomElement e = i.previous(); + findDesktopFiles(e.text(), QString()); + mElement.removeChild(e); + } + } + + // Add the entries for ancestor ................ + if (mParent) + { + XdgMenuAppFileInfoHashIterator i(mParent->mAppFileInfoHash); + while(i.hasNext()) + { + i.next(); + //if (!mAppFileInfoHash.contains(i.key())) + mAppFileInfoHash.insert(i.key(), i.value()); + } + } +} + + +/************************************************ + + ************************************************/ +void XdgMenuApplinkProcessor::findDesktopFiles(const QString& dirName, const QString& prefix) +{ + QDir dir(dirName); + mMenu->addWatchPath(dir.absolutePath()); + QFileInfoList files = dir.entryInfoList(QStringList("*.desktop"), QDir::Files); + + foreach (QFileInfo file, files) + { + XdgDesktopFile* f = XdgDesktopFileCache::getFile(file.canonicalFilePath()); + if (f) + mAppFileInfoHash.insert(prefix + file.fileName(), new XdgMenuAppFileInfo(f, prefix + file.fileName(), this)); + } + + + // Working recursively ............ + QFileInfoList dirs = dir.entryInfoList(QStringList(), QDir::Dirs | QDir::NoDotAndDotDot); + foreach (QFileInfo d, dirs) + { + QString dn = d.canonicalFilePath(); + if (dn != dirName) + { + findDesktopFiles(dn, QString("%1%2-").arg(prefix, d.fileName())); + } + } +} + + +/************************************************ + Create rules + ************************************************/ +void XdgMenuApplinkProcessor::createRules() +{ + MutableDomElementIterator i(mElement, QString()); + while(i.hasNext()) + { + QDomElement e = i.next(); + if (e.tagName()=="Include") + { + mRules.addInclude(e); + mElement.removeChild(e); + } + + else if (e.tagName()=="Exclude") + { + mRules.addExclude(e); + mElement.removeChild(e); + } + } + +} + + +/************************************************ + Check if the program is actually installed. + ************************************************/ +bool XdgMenuApplinkProcessor::checkTryExec(const QString& progName) +{ + if (progName.startsWith(QDir::separator())) + return QFileInfo(progName).isExecutable(); + + QStringList dirs = QString(getenv("PATH")).split(":"); + + foreach (QString dir, dirs) + { + if (QFileInfo(QDir(dir), progName).isExecutable()) + return true; + } + + return false; +} + diff --git a/xdgmenuapplinkprocessor.h b/xdgmenuapplinkprocessor.h new file mode 100644 index 0000000..6b85331 --- /dev/null +++ b/xdgmenuapplinkprocessor.h @@ -0,0 +1,102 @@ +/* BEGIN_COMMON_COPYRIGHT_HEADER + * (c)LGPL2+ + * + * Razor - a lightweight, Qt based, desktop toolset + * http://razor-qt.org + * + * Copyright: 2010-2011 Razor team + * Authors: + * Alexander Sokoloff + * + * 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 */ + + +#ifndef QTXDG_XDGMENUAPPLINKPROCESSOR_H +#define QTXDG_XDGMENUAPPLINKPROCESSOR_H + +#include "xdgmenurules.h" +#include +#include +#include +#include +#include + +class XdgMenu; +class XdgMenuAppFileInfo; +class XdgDesktopFile; + +typedef QLinkedList XdgMenuAppFileInfoList; + +typedef QHash XdgMenuAppFileInfoHash; +typedef QHashIterator XdgMenuAppFileInfoHashIterator; + +class XdgMenuApplinkProcessor : public QObject +{ + Q_OBJECT +public: + explicit XdgMenuApplinkProcessor(QDomElement& element, XdgMenu* menu, XdgMenuApplinkProcessor *parent = 0); + virtual ~XdgMenuApplinkProcessor(); + void run(); + +protected: + void step1(); + void step2(); + void fillAppFileInfoList(); + void findDesktopFiles(const QString& dirName, const QString& prefix); + + //bool loadDirectoryFile(const QString& fileName, QDomElement& element); + void createRules(); + bool checkTryExec(const QString& progName); + +private: + XdgMenuApplinkProcessor* mParent; + QLinkedList mChilds; + XdgMenuAppFileInfoHash mAppFileInfoHash; + XdgMenuAppFileInfoList mSelected; + QDomElement mElement; + bool mOnlyUnallocated; + + XdgMenu* mMenu; + XdgMenuRules mRules; +}; + + +class XdgMenuAppFileInfo: public QObject +{ + Q_OBJECT +public: + explicit XdgMenuAppFileInfo(XdgDesktopFile* desktopFile, const QString& id, QObject *parent) + : QObject(parent) + { + mDesktopFile = desktopFile; + mAllocated = false; + mId = id; + } + + XdgDesktopFile* desktopFile() const { return mDesktopFile; } + bool allocated() const { return mAllocated; } + void setAllocated(bool value) { mAllocated = value; } + QString id() const { return mId; } +private: + XdgDesktopFile* mDesktopFile; + bool mAllocated; + QString mId; +}; + + +#endif // QTXDG_XDGMENUAPPLINKPROCESSOR_H diff --git a/xdgmenulayoutprocessor.cpp b/xdgmenulayoutprocessor.cpp new file mode 100644 index 0000000..f4aa414 --- /dev/null +++ b/xdgmenulayoutprocessor.cpp @@ -0,0 +1,430 @@ +/* BEGIN_COMMON_COPYRIGHT_HEADER + * (c)LGPL2+ + * + * Razor - a lightweight, Qt based, desktop toolset + * http://razor-qt.org + * + * Copyright: 2010-2011 Razor team + * Authors: + * Alexander Sokoloff + * + * 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 "xdgmenulayoutprocessor.h" +#include "xmlhelper.h" +#include +#include + + +// Helper functions prototypes +QDomElement findLastElementByTag(const QDomElement element, const QString tagName); +int childsCount(const QDomElement& element); + +/************************************************ + + ************************************************/ +QDomElement findLastElementByTag(const QDomElement element, const QString tagName) +{ + QDomNodeList l = element.elementsByTagName(tagName); + if (l.isEmpty()) + return QDomElement(); + + return l.at(l.length()-1).toElement(); +} + + +/************************************************ + If no default-layout has been specified then the layout as specified by + the following elements should be assumed: + + + + + ************************************************/ +XdgMenuLayoutProcessor::XdgMenuLayoutProcessor(QDomElement& element): + mElement(element) +{ + mDefaultParams.mShowEmpty = false; + mDefaultParams.mInline = false; + mDefaultParams.mInlineLimit = 4; + mDefaultParams.mInlineHeader = true; + mDefaultParams.mInlineAlias = false; + + mDefaultLayout = findLastElementByTag(element, "DefaultLayout"); + + if (mDefaultLayout.isNull()) + { + // Create DefaultLayout node + QDomDocument doc = element.ownerDocument(); + mDefaultLayout = doc.createElement("DefaultLayout"); + + QDomElement menus = doc.createElement("Merge"); + menus.setAttribute("type", "menus"); + mDefaultLayout.appendChild(menus); + + QDomElement files = doc.createElement("Merge"); + files.setAttribute("type", "files"); + mDefaultLayout.appendChild(files); + + mElement.appendChild(mDefaultLayout); + } + + setParams(mDefaultLayout, &mDefaultParams); + + // If a menu does not contain a element or if it contains an empty element + // then the default layout should be used. + mLayout = findLastElementByTag(element, "Layout"); + if (mLayout.isNull() || !mLayout.hasChildNodes()) + mLayout = mDefaultLayout; +} + + +/************************************************ + + ************************************************/ +XdgMenuLayoutProcessor::XdgMenuLayoutProcessor(QDomElement& element, XdgMenuLayoutProcessor *parent): + mElement(element) +{ + mDefaultParams = parent->mDefaultParams; + + // DefaultLayout ............................ + QDomElement defaultLayout = findLastElementByTag(element, "DefaultLayout"); + + if (defaultLayout.isNull()) + mDefaultLayout = parent->mDefaultLayout; + else + mDefaultLayout = defaultLayout; + + setParams(mDefaultLayout, &mDefaultParams); + + // If a menu does not contain a element or if it contains an empty element + // then the default layout should be used. + mLayout = findLastElementByTag(element, "Layout"); + if (mLayout.isNull() || !mLayout.hasChildNodes()) + mLayout = mDefaultLayout; + +} + + +/************************************************ + + ************************************************/ +void XdgMenuLayoutProcessor::setParams(QDomElement defaultLayout, LayoutParams *result) +{ + if (defaultLayout.hasAttribute("show_empty")) + result->mShowEmpty = defaultLayout.attribute("show_empty") == "true"; + + if (defaultLayout.hasAttribute("inline")) + result->mInline = defaultLayout.attribute("inline") == "true"; + + if (defaultLayout.hasAttribute("inline_limit")) + result->mInlineLimit = defaultLayout.attribute("inline_limit").toInt(); + + if (defaultLayout.hasAttribute("inline_header")) + result->mInlineHeader = defaultLayout.attribute("inline_header") == "true"; + + if (defaultLayout.hasAttribute("inline_alias")) + result->mInlineAlias = defaultLayout.attribute("inline_alias") == "true"; +} + + +/************************************************ + + ************************************************/ +QDomElement XdgMenuLayoutProcessor::searchElement(const QString &tagName, const QString &attributeName, const QString &attributeValue) const +{ + DomElementIterator it(mElement, tagName); + while (it.hasNext()) + { + QDomElement e = it.next(); + if (e.attribute(attributeName) == attributeValue) + { + return e; + } + } + + return QDomElement(); +} + + +/************************************************ + + ************************************************/ +int childsCount(const QDomElement& element) +{ + int count = 0; + DomElementIterator it(element); + while (it.hasNext()) + { + QString tag = it.next().tagName(); + if (tag == "AppLink" || tag == "Menu" || tag == "Separator") + count ++; + } + + return count; +} + + +/************************************************ + + ************************************************/ +void XdgMenuLayoutProcessor::run() +{ + QDomDocument doc = mLayout.ownerDocument(); + mResult = doc.createElement("Result"); + mElement.appendChild(mResult); + + // Process childs menus ............................... + { + DomElementIterator it(mElement, "Menu"); + while (it.hasNext()) + { + QDomElement e = it.next(); + XdgMenuLayoutProcessor p(e, this); + p.run(); + } + } + + + // Step 1 ................................... + DomElementIterator it(mLayout); + it.toFront(); + while (it.hasNext()) + { + QDomElement e = it.next(); + + if (e.tagName() == "Filename") + processFilenameTag(e); + + else if (e.tagName() == "Menuname") + processMenunameTag(e); + + else if (e.tagName() == "Separator") + processSeparatorTag(e); + + else if (e.tagName() == "Merge") + { + QDomElement merge = mResult.ownerDocument().createElement("Merge"); + merge.setAttribute("type", e.attribute("type")); + mResult.appendChild(merge); + } + } + + // Step 2 ................................... + { + MutableDomElementIterator ri(mResult, "Merge"); + while (ri.hasNext()) + { + processMergeTag(ri.next()); + } + } + + // Move result cilds to element ............. + MutableDomElementIterator ri(mResult); + while (ri.hasNext()) + { + mElement.appendChild(ri.next()); + } + + // Final .................................... + mElement.removeChild(mResult); + + if (mLayout.parentNode() == mElement) + mElement.removeChild(mLayout); + + if (mDefaultLayout.parentNode() == mElement) + mElement.removeChild(mDefaultLayout); + +} + + +/************************************************ + The element is the most basic matching rule. + It matches a desktop entry if the desktop entry has the given desktop-file id + ************************************************/ +void XdgMenuLayoutProcessor::processFilenameTag(const QDomElement &element) +{ + QString id = element.text(); + + QDomElement appLink = searchElement("AppLink", "id", id); + if (!appLink.isNull()) + mResult.appendChild(appLink); +} + + +/************************************************ + Its contents references an immediate sub-menu of the current menu, as such it should never contain + a slash. If no such sub-menu exists the element should be ignored. + This element may have various attributes, the default values are taken from the DefaultLayout key. + + show_empty [ bool ] + defines whether a menu that contains no desktop entries and no sub-menus + should be shown at all. + + inline [ bool ] + If the inline attribute is "true" the menu that is referenced may be copied into the current + menu at the current point instead of being inserted as sub-menu of the current menu. + + inline_limit [ int ] + defines the maximum number of entries that can be inlined. If the sub-menu has more entries + than inline_limit, the sub-menu will not be inlined. If the inline_limit is 0 (zero) there + is no limit. + + inline_header [ bool ] + defines whether an inlined menu should be preceded with a header entry listing the caption of + the sub-menu. + + inline_alias [ bool ] + defines whether a single inlined entry should adopt the caption of the inlined menu. In such + case no additional header entry will be added regardless of the value of the inline_header + attribute. + + Example: if a menu has a sub-menu titled "WordProcessor" with a single entry "OpenOffice 4.2", and + both inline="true" and inline_alias="true" are specified then this would result in the + "OpenOffice 4.2" entry being inlined in the current menu but the "OpenOffice 4.2" caption of the + entry would be replaced with "WordProcessor". + ************************************************/ +void XdgMenuLayoutProcessor::processMenunameTag(const QDomElement &element) +{ + QString id = element.text(); + QDomElement menu = searchElement("Menu", "name", id); + if (menu.isNull()) + return; + + LayoutParams params = mDefaultParams; + setParams(element, ¶ms); + + int count = childsCount(menu); + + if (count == 0) + { + if (params.mShowEmpty) + { + menu.setAttribute("keep", "true"); + mResult.appendChild(menu); + } + return; + } + + + bool doInline = params.mInline && + (!params.mInlineLimit || params.mInlineLimit > count); + + bool doAlias = params.mInlineAlias && + doInline && (count == 1); + + bool doHeader = params.mInlineHeader && + doInline && !doAlias; + + + if (!doInline) + { + mResult.appendChild(menu); + return; + } + + + // Header .................................... + if (doHeader) + { + QDomElement header = mLayout.ownerDocument().createElement("Header"); + + QDomNamedNodeMap attrs = menu.attributes(); + for (int i=0; i < attrs.count(); ++i) + { + header.setAttributeNode(attrs.item(i).toAttr()); + } + + mResult.appendChild(header); + } + + // Alias ..................................... + if (doAlias) + { + menu.firstChild().toElement().setAttribute("title", menu.attribute("title")); + } + + // Inline .................................... + MutableDomElementIterator it(menu); + while (it.hasNext()) + { + mResult.appendChild(it.next()); + } + +} + + +/************************************************ + It indicates a suggestion to draw a visual separator at this point in the menu. + elements at the start of a menu, at the end of a menu or that directly + follow other elements may be ignored. + ************************************************/ +void XdgMenuLayoutProcessor::processSeparatorTag(const QDomElement &element) +{ + QDomElement separator = element.ownerDocument().createElement("Separator"); + mResult.appendChild(separator); +} + + +/************************************************ + It indicates the point where desktop entries and sub-menus that are not explicitly mentioned + within the or element are to be inserted. + It has a type attribute that indicates which elements should be inserted: + + type="menus" + means that all sub-menus that are not explicitly mentioned should be inserted in alphabetical + order of their visual caption at this point. + + type="files" means that all desktop entries contained in this menu that are not explicitly + mentioned should be inserted in alphabetical order of their visual caption at this point. + + type="all" means that a mix of all sub-menus and all desktop entries that are not explicitly + mentioned should be inserted in alphabetical order of their visual caption at this point. + + ************************************************/ +void XdgMenuLayoutProcessor::processMergeTag(const QDomElement &element) +{ + QString type = element.attribute("type"); + QMap map; + MutableDomElementIterator it(mElement); + + while (it.hasNext()) + { + QDomElement e = it.next(); + if ( + ((type == "menus" || type == "all") && e.tagName() == "Menu" ) || + ((type == "files" || type == "all") && e.tagName() == "AppLink") + ) + map.insert(e.attribute("title"), e); + } + + QMapIterator mi(map); + while (mi.hasNext()) { + mi.next(); + mResult.insertBefore(mi.value(), element); + } + + mResult.removeChild(element); +} + diff --git a/xdgmenulayoutprocessor.h b/xdgmenulayoutprocessor.h new file mode 100644 index 0000000..d67c61b --- /dev/null +++ b/xdgmenulayoutprocessor.h @@ -0,0 +1,115 @@ +/* BEGIN_COMMON_COPYRIGHT_HEADER + * (c)LGPL2+ + * + * Razor - a lightweight, Qt based, desktop toolset + * http://razor-qt.org + * + * Copyright: 2010-2011 Razor team + * Authors: + * Alexander Sokoloff + * + * 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 */ + + +#ifndef QTXDG_XDGMENULAYOUTPROCESSOR_H +#define QTXDG_XDGMENULAYOUTPROCESSOR_H + +#include +#include + +struct LayoutItem +{ + enum Type{ + Filename, + Menuname, + Separator, + MergeMenus, + MergeFiles, + MergeAll, + }; + + Type type; + bool showEmpty; + bool isInline; + bool inlineLimit; + bool inlineHeader; + bool inlineAlias; + QString fileId; +}; + +//class Layout: public QList +//{ +//public: +/* Layout() {} + + + bool showEmpty() { return mShowEmpty; } + void setShowEmpty(bool value) { mShowEmpty = value; } + + bool isInline() { return mInline; } + void setInline(bool value) { mInline = value; } + + int inlineLimit() { return mInlineLimit; } + void setInlineLimit(int value) { mInlineLimit = value; } + + bool inlineHeader() {return mInlineHeader; } + void setInlineHeader(bool value) { mInlineHeader = value; } + + bool inlineAlias() { return mInlineAlias; } + void setInlineAlias(bool value) { mInlineAlias = value; } + + + +private: +*/ + +struct LayoutParams +{ + bool mShowEmpty; + bool mInline; + int mInlineLimit; + bool mInlineHeader; + bool mInlineAlias; +}; + + +class XdgMenuLayoutProcessor +{ +public: + XdgMenuLayoutProcessor(QDomElement& element); + void run(); + +protected: + XdgMenuLayoutProcessor(QDomElement& element, XdgMenuLayoutProcessor *parent); + +private: + void setParams(QDomElement defaultLayout, LayoutParams *result); + QDomElement searchElement(const QString &tagName, const QString &attributeName, const QString &attributeValue) const; + void processFilenameTag(const QDomElement &element); + void processMenunameTag(const QDomElement &element); + void processSeparatorTag(const QDomElement &element); + void processMergeTag(const QDomElement &element); + + LayoutParams mDefaultParams; + QDomElement& mElement; + QDomElement mDefaultLayout; + QDomElement mLayout; + QDomElement mResult; +}; + +#endif // QTXDG_XDGMENULAYOUTPROCESSOR_H diff --git a/xdgmenureader.cpp b/xdgmenureader.cpp new file mode 100644 index 0000000..9e6b14b --- /dev/null +++ b/xdgmenureader.cpp @@ -0,0 +1,456 @@ +/* BEGIN_COMMON_COPYRIGHT_HEADER + * (c)LGPL2+ + * + * Razor - a lightweight, Qt based, desktop toolset + * http://razor-qt.org + * + * Copyright: 2010-2011 Razor team + * Authors: + * Alexander Sokoloff + * + * 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 "xdgmenureader.h" +#include "xdgmenu.h" +#include "xdgdirs.h" +#include "xmlhelper.h" + +#include +#include +#include +#include +#include +#include +#include +#include + + + +/************************************************ + + ************************************************/ +XdgMenuReader::XdgMenuReader(XdgMenu* menu, XdgMenuReader* parentReader, QObject *parent) : + QObject(parent), + mMenu(menu) +{ + mParentReader = parentReader; + if (mParentReader) + mBranchFiles << mParentReader->mBranchFiles; +} + + +/************************************************ + + ************************************************/ +XdgMenuReader::~XdgMenuReader() +{ + +} + + +/************************************************ + + ************************************************/ +bool XdgMenuReader::load(const QString& fileName, const QString& baseDir) +{ + if (fileName.isEmpty()) + { + mErrorStr = QLatin1String("Menu file not defined."); + return false; + } + + QFileInfo fileInfo(QDir(baseDir), fileName); + + mFileName = fileInfo.canonicalFilePath(); + mDirName = fileInfo.canonicalPath(); + + if (mBranchFiles.contains(mFileName)) + return false; // Recursive loop detected + + mBranchFiles << mFileName; + + QFile file(mFileName); + if (!file.open(QFile::ReadOnly | QFile::Text)) + { + mErrorStr = QString("%1 not loading: %2").arg(fileName).arg(file.errorString()); + return false; + } + //qDebug() << "Load file:" << mFileName; + mMenu->addWatchPath(mFileName); + + QString errorStr; + int errorLine; + int errorColumn; + + if (!mXml.setContent(&file, true, &errorStr, &errorLine, &errorColumn)) + { + mErrorStr = QString("Parse error at line %1, column %2:\n%3") + .arg(errorLine) + .arg(errorColumn) + .arg(errorStr); + return false; + } + + QDomElement root = mXml.documentElement(); + + QDomElement debugElement = mXml.createElement("FileInfo"); + debugElement.setAttribute("file", mFileName); + if (mParentReader) + debugElement.setAttribute("parent", mParentReader->fileName()); + + QDomNode null; + root.insertBefore(debugElement, null); + + processMergeTags(root); + return true; +} + + +/************************************************ + Duplicate elements (that specify the same file) are handled as with + duplicate elements (the last duplicate is used). + ************************************************/ +void XdgMenuReader::processMergeTags(QDomElement& element) +{ + QDomElement n = element.lastChildElement(); + QStringList mergedFiles; + + + while (!n.isNull()) + { + QDomElement next = n.previousSiblingElement(); + // MergeFile .................. + if (n.tagName() == "MergeFile") + { + processMergeFileTag(n, &mergedFiles); + n.parentNode().removeChild(n); + } + + // MergeDir ................... + else if(n.tagName() == "MergeDir") + { + processMergeDirTag(n, &mergedFiles); + n.parentNode().removeChild(n); + } + + // DefaultMergeDirs ........... + else if (n.tagName() == "DefaultMergeDirs") + { + processDefaultMergeDirsTag(n, &mergedFiles); + n.parentNode().removeChild(n); + } + + // AppDir ................... + else if(n.tagName() == "AppDir") + { + processAppDirTag(n); + n.parentNode().removeChild(n); + } + + // DefaultAppDirs ............. + else if(n.tagName() == "DefaultAppDirs") + { + processDefaultAppDirsTag(n); + n.parentNode().removeChild(n); + } + + // DirectoryDir ................... + else if(n.tagName() == "DirectoryDir") + { + processDirectoryDirTag(n); + n.parentNode().removeChild(n); + } + + // DefaultDirectoryDirs ........... + else if(n.tagName() == "DefaultDirectoryDirs") + { + processDefaultDirectoryDirsTag(n); + n.parentNode().removeChild(n); + } + + + // Menu ....................... + else if(n.tagName() == "Menu") + { + processMergeTags(n); + } + + n = next; + } + +} + + +/************************************************ + Any number of elements may be listed below a element, giving + the name of another menu file to be merged into this one. + If fileName is not an absolute path then the file to be merged should be located + relative to the location of this menu. + + If the type attribute is missing or set to "path" then the contents of the + element indicates the file to be merged. + + If the type attribute is set to "parent" and the file that contains this + element is located under one of the paths specified by + $XDG_CONFIG_DIRS, the contents of the element should be ignored and the remaining + paths specified by $XDG_CONFIG_DIRS are searched for a file with the same relative + filename. The first file encountered should be merged. There should be no merging + at all if no matching file is found. ( Libmenu additional scans ~/.config/menus.) + ************************************************/ +void XdgMenuReader::processMergeFileTag(QDomElement& element, QStringList* mergedFiles) +{ + //qDebug() << "Process " << element;// << "in" << mFileName; + + if (element.attribute("type") != "parent") + { + mergeFile(element.text(), element, mergedFiles); + } + + else + { + QString relativeName; + QStringList configDirs = XdgDirs::configDirs(); + + foreach (QString configDir, configDirs) + { + if (mFileName.startsWith(configDir)) + { + relativeName = mFileName.mid(configDir.length()); + configDirs.removeAll(configDir); + break; + } + } + + + if (relativeName.isEmpty()) + { + QString configHome = XdgDirs::configHome(); + if (mFileName.startsWith(configHome)) + relativeName = mFileName.mid(configHome.length()); + } + + if (relativeName.isEmpty()) + return; + + foreach (QString configDir, configDirs) + { + if (QFileInfo(configDir + relativeName).exists()) + { + mergeFile(configDir + relativeName, element, mergedFiles); + return; + } + } + } +} + + +/************************************************ + A contains the name of a directory. Each file in the given directory + which ends in the ".menu" extension should be merged in the same way that a + would be. If the filename given as a is not an absolute + path, it should be located relative to the location of the menu file being parsed. + The files inside the merged directory are not merged in any specified order. + + Duplicate elements (that specify the same directory) are handled as with + duplicate elements (the last duplicate is used). + + KDE additional scans ~/.config/menus. + ************************************************/ +void XdgMenuReader::processMergeDirTag(QDomElement& element, QStringList* mergedFiles) +{ + //qDebug() << "Process " << element;// << "in" << mFileName; + + mergeDir(element.text(), element, mergedFiles); + element.parentNode().removeChild(element); +} + + +/************************************************ + The element has no content. The element should be treated as if it were a list of + elements containing the default merge directory locations. When expanding + to a list of , the default locations that are earlier + in the search path go later in the so that they have priority. + + Note that a system that uses either gnome-applications.menu or kde-applications.menu + depending on the desktop environment in use must still use applications-merged as the + default merge directory in both cases. + + Implementations may chose to use .menu files with names other than application.menu + for tasks or menus other than the main application menu. In that case the first part + of the name of the default merge directory is derived from the name of the .menu file. + ************************************************/ +void XdgMenuReader::processDefaultMergeDirsTag(QDomElement& element, QStringList* mergedFiles) +{ + //qDebug() << "Process " << element;// << "in" << mFileName; + + QString menuBaseName = QFileInfo(mMenu->menuFileName()).baseName(); + int n = menuBaseName.lastIndexOf('-'); + if (n>-1) + menuBaseName = menuBaseName.mid(n+1); + + QStringList dirs = XdgDirs::configDirs(); + dirs << XdgDirs::configHome(); + + foreach (QString dir, dirs) + { + mergeDir(QString("%1/menus/%2-merged").arg(dir).arg(menuBaseName), element, mergedFiles); + } + + if (menuBaseName == "applications") + mergeFile(QString("%1/menus/applications-kmenuedit.menu").arg(XdgDirs::configHome()), element, mergedFiles); +} + + +/************************************************ + If the filename given as an is not an absolute path, it should be located + relative to the location of the menu file being parsed. + ************************************************/ +void XdgMenuReader::processAppDirTag(QDomElement& element) +{ + //qDebug() << "Process " << element; + addDirTag(element, "AppDir", element.text()); +} + + +/************************************************ + The element has no content. The element should be treated as if it were a list of + elements containing the default app dir locations + ($XDG_DATA_DIRS/applications/). + + menu-cache additional prepends $XDG_DATA_HOME/applications. + ************************************************/ +void XdgMenuReader::processDefaultAppDirsTag(QDomElement& element) +{ + //qDebug() << "Process " << element; + QStringList dirs = XdgDirs::dataDirs(); + dirs.prepend(XdgDirs::dataHome(false)); + + foreach (QString dir, dirs) + { + //qDebug() << "Add AppDir: " << dir + "/applications/"; + addDirTag(element, "AppDir", dir + "/applications/"); + } +} + +/************************************************ + If the filename given as a is not an absolute path, it should be + located relative to the location of the menu file being parsed. + ************************************************/ +void XdgMenuReader::processDirectoryDirTag(QDomElement& element) +{ + //qDebug() << "Process " << element; + addDirTag(element, "DirectoryDir", element.text()); +} + + +/************************************************ + The element has no content. The element should be treated as if it were a list of + elements containing the default desktop dir locations + ($XDG_DATA_DIRS/desktop-directories/). + + menu-cache additional prepends $XDG_DATA_HOME/applications. + ************************************************/ +void XdgMenuReader::processDefaultDirectoryDirsTag(QDomElement& element) +{ + //qDebug() << "Process " << element; + QStringList dirs = XdgDirs::dataDirs(); + dirs.prepend(XdgDirs::dataHome(false)); + + foreach (QString dir, dirs) + addDirTag(element, "DirectoryDir", dir + "/desktop-directories/"); +} + +/************************************************ + + ************************************************/ +void XdgMenuReader::addDirTag(QDomElement& previousElement, const QString& tagName, const QString& dir) +{ + QFileInfo dirInfo(mDirName, dir); + if (dirInfo.isDir()) + { +// qDebug() << "\tAdding " + dirInfo.canonicalFilePath(); + QDomElement element = mXml.createElement(tagName); + element.appendChild(mXml.createTextNode(dirInfo.canonicalFilePath())); + previousElement.parentNode().insertBefore(element, previousElement); + } +} + + +/************************************************ + If fileName is not an absolute path then the file to be merged should be located + relative to the location of this menu file. + ************************************************/ +void XdgMenuReader::mergeFile(const QString& fileName, QDomElement& element, QStringList* mergedFiles) +{ + XdgMenuReader reader(mMenu, this); + QFileInfo fileInfo(QDir(mDirName), fileName); + + if (!fileInfo.exists()) + return; + + if (mergedFiles->contains(fileInfo.canonicalFilePath())) + { + //qDebug() << "\tSkip: allredy merged"; + return; + } + + //qDebug() << "Merge file: " << fileName; + mergedFiles->append(fileInfo.canonicalFilePath()); + + if (reader.load(fileName, mDirName)) + { + //qDebug() << "\tOK"; + QDomElement n = reader.xml().firstChildElement().firstChildElement(); + while (!n.isNull()) + { + // As a special exception, remove the element from the root + // element of each file being merged. + if (n.tagName() != "Name") + { + QDomNode imp = mXml.importNode(n, true); + element.parentNode().insertBefore(imp, element); + } + + n = n.nextSiblingElement(); + } + } +} + + +/************************************************ + + ************************************************/ +void XdgMenuReader::mergeDir(const QString& dirName, QDomElement& element, QStringList* mergedFiles) +{ + QFileInfo dirInfo(mDirName, dirName); + + if (dirInfo.isDir()) + { + //qDebug() << "Merge dir: " << dirInfo.canonicalFilePath(); + QDir dir = QDir(dirInfo.canonicalFilePath()); + const QFileInfoList files = dir.entryInfoList(QStringList() << "*.menu", QDir::Files | QDir::Readable); + + foreach (QFileInfo file, files) + mergeFile(file.canonicalFilePath(), element, mergedFiles); + } +} + + + + diff --git a/xdgmenureader.h b/xdgmenureader.h new file mode 100644 index 0000000..1962cfb --- /dev/null +++ b/xdgmenureader.h @@ -0,0 +1,80 @@ +/* BEGIN_COMMON_COPYRIGHT_HEADER + * (c)LGPL2+ + * + * Razor - a lightweight, Qt based, desktop toolset + * http://razor-qt.org + * + * Copyright: 2010-2011 Razor team + * Authors: + * Alexander Sokoloff + * + * 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 */ + + +#ifndef QTXDG_XDGMENUREADER_H +#define QTXDG_XDGMENUREADER_H + +#include +#include +#include +#include +#include +class XdgMenu; +class XdgMenuReader : public QObject +{ + Q_OBJECT +public: + explicit XdgMenuReader(XdgMenu* menu, XdgMenuReader* parentReader = 0, QObject *parent = 0); + virtual ~XdgMenuReader(); + + bool load(const QString& fileName, const QString& baseDir = QString()); + QString fileName() const { return mFileName; } + QString errorString() const { return mErrorStr; } + QDomDocument& xml() { return mXml; } + +signals: + +public slots: + +protected: + void processMergeTags(QDomElement& element); + void processMergeFileTag(QDomElement& element, QStringList* mergedFiles); + void processMergeDirTag(QDomElement& element, QStringList* mergedFiles); + void processDefaultMergeDirsTag(QDomElement& element, QStringList* mergedFiles); + + void processAppDirTag(QDomElement& element); + void processDefaultAppDirsTag(QDomElement& element); + + void processDirectoryDirTag(QDomElement& element); + void processDefaultDirectoryDirsTag(QDomElement& element); + void addDirTag(QDomElement& previousElement, const QString& tagName, const QString& dir); + + void mergeFile(const QString& fileName, QDomElement& element, QStringList* mergedFiles); + void mergeDir(const QString& dirName, QDomElement& element, QStringList* mergedFiles); + +private: + QString mFileName; + QString mDirName; + QString mErrorStr; + QDomDocument mXml; + XdgMenuReader* mParentReader; + QStringList mBranchFiles; + XdgMenu* mMenu; +}; + +#endif // QTXDG_XDGMENUREADER_H diff --git a/xdgmenurules.cpp b/xdgmenurules.cpp new file mode 100644 index 0000000..eca7dd1 --- /dev/null +++ b/xdgmenurules.cpp @@ -0,0 +1,289 @@ +/* BEGIN_COMMON_COPYRIGHT_HEADER + * (c)LGPL2+ + * + * Razor - a lightweight, Qt based, desktop toolset + * http://razor-qt.org + * + * Copyright: 2010-2011 Razor team + * Authors: + * Alexander Sokoloff + * + * 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 */ + + +/********************************************************************* + See: http://standards.freedesktop.org/desktop-entry-spec + +*********************************************************************/ + +#include "xdgmenurules.h" +#include "xmlhelper.h" + +#include +#include + + + +/************************************************ + + ************************************************/ +XdgMenuRule::XdgMenuRule(const QDomElement& element, QObject* parent) : + QObject(parent) +{ + Q_UNUSED(element) +} + + +/************************************************ + + ************************************************/ +XdgMenuRule::~XdgMenuRule() +{ +} + + + + + +/************************************************ + The element contains a list of matching rules. If any of the matching rules + inside the element match a desktop entry, then the entire rule matches + the desktop entry. + ************************************************/ +XdgMenuRuleOr::XdgMenuRuleOr(const QDomElement& element, QObject* parent) : + XdgMenuRule(element, parent) +{ + //qDebug() << "Create OR rule"; + DomElementIterator iter(element, QString()); + + while(iter.hasNext()) + { + QDomElement e = iter.next(); + + if (e.tagName() == "Or") + mChilds.append(new XdgMenuRuleOr(e, this)); + + else if (e.tagName() == "And") + mChilds.append(new XdgMenuRuleAnd(e, this)); + + else if (e.tagName() == "Not") + mChilds.append(new XdgMenuRuleNot(e, this)); + + else if (e.tagName() == "Filename") + mChilds.append(new XdgMenuRuleFileName(e, this)); + + else if (e.tagName() == "Category") + mChilds.append(new XdgMenuRuleCategory(e, this)); + + else if (e.tagName() == "All") + mChilds.append(new XdgMenuRuleAll(e, this)); + + else + qWarning() << "Unknown rule" << e.tagName(); + } + +} + + +/************************************************ + + ************************************************/ +bool XdgMenuRuleOr::check(const QString& desktopFileId, const XdgDesktopFile& desktopFile) +{ + for (QLinkedList::Iterator i=mChilds.begin(); i!=mChilds.end(); ++i) + if ((*i)->check(desktopFileId, desktopFile)) return true; + + return false; +} + + +/************************************************ + The element contains a list of matching rules. If each of the matching rules + inside the element match a desktop entry, then the entire rule matches + the desktop entry. + ************************************************/ +XdgMenuRuleAnd::XdgMenuRuleAnd(const QDomElement& element, QObject *parent) : + XdgMenuRuleOr(element, parent) +{ +// qDebug() << "Create AND rule"; +} + + +/************************************************ + + ************************************************/ +bool XdgMenuRuleAnd::check(const QString& desktopFileId, const XdgDesktopFile& desktopFile) +{ + for (QLinkedList::Iterator i=mChilds.begin(); i!=mChilds.end(); ++i) + if (!(*i)->check(desktopFileId, desktopFile)) return false; + + return mChilds.count(); +} + + + +/************************************************ + The element contains a list of matching rules. If any of the matching rules + inside the element matches a desktop entry, then the entire rule does + not match the desktop entry. That is, matching rules below have a logical OR + relationship. + ************************************************/ +XdgMenuRuleNot::XdgMenuRuleNot(const QDomElement& element, QObject *parent) : + XdgMenuRuleOr(element, parent) +{ +// qDebug() << "Create NOT rule"; +} + + +/************************************************ + + ************************************************/ +bool XdgMenuRuleNot::check(const QString& desktopFileId, const XdgDesktopFile& desktopFile) +{ + return ! XdgMenuRuleOr::check(desktopFileId, desktopFile); +} + + + +/************************************************ + The element is the most basic matching rule. It matches a desktop entry + if the desktop entry has the given desktop-file id. See Desktop-File Id. + ************************************************/ +XdgMenuRuleFileName::XdgMenuRuleFileName(const QDomElement& element, QObject *parent) : + XdgMenuRule(element, parent) +{ + //qDebug() << "Create FILENAME rule"; + mId = element.text(); +} + + +/************************************************ + + ************************************************/ +bool XdgMenuRuleFileName::check(const QString& desktopFileId, const XdgDesktopFile& desktopFile) +{ + Q_UNUSED(desktopFile) + //qDebug() << "XdgMenuRuleFileName:" << desktopFileId << mId; + return desktopFileId == mId; +} + + + +/************************************************ + The element is another basic matching predicate. It matches a desktop entry + if the desktop entry has the given category in its Categories field. + ************************************************/ +XdgMenuRuleCategory::XdgMenuRuleCategory(const QDomElement& element, QObject *parent) : + XdgMenuRule(element, parent) +{ + mCategory = element.text(); +} + + +/************************************************ + + ************************************************/ +bool XdgMenuRuleCategory::check(const QString& desktopFileId, const XdgDesktopFile& desktopFile) +{ + Q_UNUSED(desktopFileId) + QStringList cats = desktopFile.categories(); + return cats.contains(mCategory); +} + + +/************************************************ + The element is a matching rule that matches all desktop entries. + ************************************************/ +XdgMenuRuleAll::XdgMenuRuleAll(const QDomElement& element, QObject *parent) : + XdgMenuRule(element, parent) +{ +} + + +/************************************************ + + ************************************************/ +bool XdgMenuRuleAll::check(const QString& desktopFileId, const XdgDesktopFile& desktopFile) +{ + Q_UNUSED(desktopFileId) + Q_UNUSED(desktopFile) + return true; +} + + + + +/************************************************ + + ************************************************/ +XdgMenuRules::XdgMenuRules(QObject* parent) : + QObject(parent) +{ +} + + +/************************************************ + + ************************************************/ +XdgMenuRules::~XdgMenuRules() +{ +} + + +/************************************************ + + ************************************************/ +void XdgMenuRules::addInclude(const QDomElement& element) +{ + mIncludeRules.append(new XdgMenuRuleOr(element, this)); +} + + +/************************************************ + + ************************************************/ +void XdgMenuRules::addExclude(const QDomElement& element) +{ + mExcludeRules.append(new XdgMenuRuleOr(element, this)); +} + + +/************************************************ + + ************************************************/ +bool XdgMenuRules::checkInclude(const QString& desktopFileId, const XdgDesktopFile& desktopFile) +{ + for (QLinkedList::Iterator i=mIncludeRules.begin(); i!=mIncludeRules.end(); ++i) + if ((*i)->check(desktopFileId, desktopFile)) return true; + + return false; +} + + +/************************************************ + + ************************************************/ +bool XdgMenuRules::checkExclude(const QString& desktopFileId, const XdgDesktopFile& desktopFile) +{ + for (QLinkedList::Iterator i=mExcludeRules.begin(); i!=mExcludeRules.end(); ++i) + if ((*i)->check(desktopFileId, desktopFile)) return true; + + return false; +} + + diff --git a/xdgmenurules.h b/xdgmenurules.h new file mode 100644 index 0000000..9ce558f --- /dev/null +++ b/xdgmenurules.h @@ -0,0 +1,135 @@ +/* BEGIN_COMMON_COPYRIGHT_HEADER + * (c)LGPL2+ + * + * Razor - a lightweight, Qt based, desktop toolset + * http://razor-qt.org + * + * Copyright: 2010-2011 Razor team + * Authors: + * Alexander Sokoloff + * + * 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 */ + + +/********************************************************************* + See: http://standards.freedesktop.org/desktop-entry-spec + +*********************************************************************/ + +#ifndef QTXDG_XDGMENURULES_H +#define QTXDG_XDGMENURULES_H + +#include +#include +#include + +#include "xdgdesktopfile.h" + +class XdgMenuRule : public QObject +{ + Q_OBJECT +public: + explicit XdgMenuRule(const QDomElement& element, QObject* parent = 0); + virtual ~XdgMenuRule(); + + virtual bool check(const QString& desktopFileId, const XdgDesktopFile& desktopFile) = 0; +}; + + +class XdgMenuRuleOr : public XdgMenuRule +{ + Q_OBJECT +public: + explicit XdgMenuRuleOr(const QDomElement& element, QObject* parent = 0); + + bool check(const QString& desktopFileId, const XdgDesktopFile& desktopFile); + +protected: + QLinkedList mChilds; +}; + + +class XdgMenuRuleAnd : public XdgMenuRuleOr +{ + Q_OBJECT +public: + explicit XdgMenuRuleAnd(const QDomElement& element, QObject* parent = 0); + bool check(const QString& desktopFileId, const XdgDesktopFile& desktopFile); +}; + + +class XdgMenuRuleNot : public XdgMenuRuleOr +{ + Q_OBJECT +public: + explicit XdgMenuRuleNot(const QDomElement& element, QObject* parent = 0); + bool check(const QString& desktopFileId, const XdgDesktopFile& desktopFile); +}; + + +class XdgMenuRuleFileName : public XdgMenuRule +{ + Q_OBJECT +public: + explicit XdgMenuRuleFileName(const QDomElement& element, QObject* parent = 0); + bool check(const QString& desktopFileId, const XdgDesktopFile& desktopFile); +private: + QString mId; +}; + + +class XdgMenuRuleCategory : public XdgMenuRule +{ + Q_OBJECT +public: + explicit XdgMenuRuleCategory(const QDomElement& element, QObject* parent = 0); + bool check(const QString& desktopFileId, const XdgDesktopFile& desktopFile); +private: + QString mCategory; +}; + + +class XdgMenuRuleAll : public XdgMenuRule +{ + Q_OBJECT +public: + explicit XdgMenuRuleAll(const QDomElement& element, QObject* parent = 0); + bool check(const QString& desktopFileId, const XdgDesktopFile& desktopFile); +}; + + + +class XdgMenuRules : public QObject +{ + Q_OBJECT +public: + explicit XdgMenuRules(QObject* parent = 0); + virtual ~XdgMenuRules(); + + void addInclude(const QDomElement& element); + void addExclude(const QDomElement& element); + + bool checkInclude(const QString& desktopFileId, const XdgDesktopFile& desktopFile); + bool checkExclude(const QString& desktopFileId, const XdgDesktopFile& desktopFile); + +protected: + QLinkedList mIncludeRules; + QLinkedList mExcludeRules; +}; + +#endif // QTXDG_XDGMENURULES_H diff --git a/xdgmenuwidget.cpp b/xdgmenuwidget.cpp new file mode 100644 index 0000000..1f12605 --- /dev/null +++ b/xdgmenuwidget.cpp @@ -0,0 +1,275 @@ +/* BEGIN_COMMON_COPYRIGHT_HEADER + * (c)LGPL2+ + * + * Razor - a lightweight, Qt based, desktop toolset + * http://razor-qt.org + * + * Copyright: 2010-2011 Razor team + * Authors: + * Alexander Sokoloff + * + * 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 "xdgmenuwidget.h" +#include "xdgicon.h" +#include "xmlhelper.h" +#include "xdgaction.h" +#include "xdgmenu.h" + +#include +#include +#include +#if QT_VERSION < QT_VERSION_CHECK(5,0,0) +#else +#include +#endif +#include +#include +#include + +class XdgMenuWidgetPrivate +{ +private: + XdgMenuWidget* const q_ptr; + Q_DECLARE_PUBLIC(XdgMenuWidget); + +public: + explicit XdgMenuWidgetPrivate(XdgMenuWidget* parent): + q_ptr(parent) + {} + + void init(const QDomElement& xml); + void buildMenu(); + + QDomElement mXml; + + void mouseMoveEvent(QMouseEvent *event); + + QPoint mDragStartPosition; + +private: + XdgAction* createAction(const QDomElement& xml); + static QString escape(QString string); +}; + + + +/************************************************ + + ************************************************/ +XdgMenuWidget::XdgMenuWidget(const XdgMenu& xdgMenu, const QString& title, QWidget* parent): + QMenu(parent), + d_ptr(new XdgMenuWidgetPrivate(this)) +{ + d_ptr->init(xdgMenu.xml().documentElement()); + setTitle(XdgMenuWidgetPrivate::escape(title)); +} + +/************************************************ + + ************************************************/ +XdgMenuWidget::XdgMenuWidget(const QDomElement& menuElement, QWidget* parent): + QMenu(parent), + d_ptr(new XdgMenuWidgetPrivate(this)) +{ + d_ptr->init(menuElement); +} + + +/************************************************ + + ************************************************/ +XdgMenuWidget::XdgMenuWidget(const XdgMenuWidget& other, QWidget* parent): + QMenu(parent), + d_ptr(new XdgMenuWidgetPrivate(this)) +{ + d_ptr->init(other.d_ptr->mXml); +} + + +/************************************************ + + ************************************************/ +void XdgMenuWidgetPrivate::init(const QDomElement& xml) +{ + Q_Q(XdgMenuWidget); + mXml = xml; + + q->clear(); + + QString title; + if (! xml.attribute("title").isEmpty()) + title = xml.attribute("title"); + else + title = xml.attribute("name"); + q->setTitle(escape(title)); + + q->setToolTip(xml.attribute("comment")); + + + QIcon parentIcon; + QMenu* parentMenu = qobject_cast(q->parent()); + if (parentMenu) + parentIcon = parentMenu->icon(); + + q->setIcon(XdgIcon::fromTheme(mXml.attribute("icon"), parentIcon)); + + buildMenu(); +} + + +/************************************************ + + ************************************************/ +XdgMenuWidget::~XdgMenuWidget() +{ + delete d_ptr; +} + + +/************************************************ + + ************************************************/ +XdgMenuWidget& XdgMenuWidget::operator=(const XdgMenuWidget& other) +{ + Q_D(XdgMenuWidget); + d->init(other.d_ptr->mXml); + + return *this; +} + + +/************************************************ + + ************************************************/ +bool XdgMenuWidget::event(QEvent* event) +{ + Q_D(XdgMenuWidget); + + if (event->type() == QEvent::MouseButtonPress) + { + QMouseEvent *e = static_cast(event); + if (e->button() == Qt::LeftButton) + d->mDragStartPosition = e->pos(); + } + + else if (event->type() == QEvent::MouseMove) + { + QMouseEvent *e = static_cast(event); + d->mouseMoveEvent(e); + } + + return QMenu::event(event); +} + + +/************************************************ + + ************************************************/ +void XdgMenuWidgetPrivate::mouseMoveEvent(QMouseEvent *event) +{ + if (!(event->buttons() & Qt::LeftButton)) + return; + + if ((event->pos() - mDragStartPosition).manhattanLength() < QApplication::startDragDistance()) + return; + + Q_Q(XdgMenuWidget); + XdgAction *a = qobject_cast(q->actionAt(event->pos())); + if (!a) + return; + + QList urls; + urls << QUrl(a->desktopFile().fileName()); + + QMimeData *mimeData = new QMimeData(); + mimeData->setUrls(urls); + + QDrag *drag = new QDrag(q); + drag->setMimeData(mimeData); + drag->exec(Qt::CopyAction | Qt::LinkAction); +} + + +/************************************************ + + ************************************************/ +void XdgMenuWidgetPrivate::buildMenu() +{ + Q_Q(XdgMenuWidget); + + QAction* first = 0; + if (!q->actions().isEmpty()) + first = q->actions().last(); + + + DomElementIterator it(mXml, QString()); + while(it.hasNext()) + { + QDomElement xml = it.next(); + + // Build submenu ........................ + if (xml.tagName() == "Menu") + q->insertMenu(first, new XdgMenuWidget(xml, q)); + + //Build application link ................ + else if (xml.tagName() == "AppLink") + q->insertAction(first, createAction(xml)); + + //Build separator ....................... + else if (xml.tagName() == "Separator") + q->insertSeparator(first); + + } +} + + +/************************************************ + + ************************************************/ +XdgAction* XdgMenuWidgetPrivate::createAction(const QDomElement& xml) +{ + Q_Q(XdgMenuWidget); + XdgAction* action = new XdgAction(xml.attribute("desktopFile"), q); + + QString title; + if (!xml.attribute("title").isEmpty()) + title = xml.attribute("title"); + else + title = xml.attribute("name"); + + + if (!xml.attribute("genericName").isEmpty() && + xml.attribute("genericName") != title) + title += QString(" (%1)").arg(xml.attribute("genericName")); + + action->setText(escape(title)); + return action; +} + + +/************************************************ + This should be used when a menu item text is set + otherwise Qt uses the &'s for creating mnemonics + ************************************************/ +QString XdgMenuWidgetPrivate::escape(QString string) +{ + return string.replace("&", "&&"); +} + diff --git a/xdgmenuwidget.h b/xdgmenuwidget.h new file mode 100644 index 0000000..53e24e6 --- /dev/null +++ b/xdgmenuwidget.h @@ -0,0 +1,89 @@ +/* BEGIN_COMMON_COPYRIGHT_HEADER + * (c)LGPL2+ + * + * Razor - a lightweight, Qt based, desktop toolset + * http://razor-qt.org + * + * Copyright: 2010-2011 Razor team + * Authors: + * Alexander Sokoloff + * + * 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 */ + + +#ifndef QTXDG_XDGMENUWIDGET_H +#define QTXDG_XDGMENUWIDGET_H + +#include "xdgmacros.h" +#include +#include + +class XdgMenu; +class QEvent; +class XdgMenuWidgetPrivate; + + +/*! + @brief The XdgMenuWidget class provides an QMenu widget for application menu or its part. + + Example usage: + @code + QString menuFile = XdgMenu::getMenuFileName(); + XdgMenu xdgMenu(menuFile); + + bool res = xdgMenu.read(); + if (res) + { + XdgMenuWidget menu(xdgMenu, QString(), this); + menu.exec(QCursor::pos()); + } + else + { + QMessageBox::warning(this, "Parse error", xdgMenu.errorString()); + } + @endcode + */ + +class QTXDG_API XdgMenuWidget : public QMenu +{ + Q_OBJECT +public: + /// Constructs a menu for root documentElement in xdgMenu with some text and parent. + XdgMenuWidget(const XdgMenu& xdgMenu, const QString& title = QString(), QWidget* parent=0); + + /// Constructs a menu for menuElement with parent. + explicit XdgMenuWidget(const QDomElement& menuElement, QWidget* parent=0); + + /// Constructs a copy of other. + XdgMenuWidget(const XdgMenuWidget& other, QWidget* parent=0); + + /// Assigns other to this menu. + XdgMenuWidget& operator=(const XdgMenuWidget& other); + + /// Destroys the menu. + virtual ~XdgMenuWidget(); + +protected: + bool event(QEvent* event); + +private: + XdgMenuWidgetPrivate* const d_ptr; + Q_DECLARE_PRIVATE(XdgMenuWidget) +}; + +#endif // QTXDG_XDGMENUWIDGET_H diff --git a/xdgmime.cpp b/xdgmime.cpp new file mode 100644 index 0000000..520a20c --- /dev/null +++ b/xdgmime.cpp @@ -0,0 +1,366 @@ +/* BEGIN_COMMON_COPYRIGHT_HEADER + * (c)LGPL2+ + * + * Razor - a lightweight, Qt based, desktop toolset + * http://razor-qt.org + * + * Copyright: 2010-2011 Razor team + * Authors: + * Alexander Sokoloff + * + * 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 "xdgmime.h" +#include "xdgicon.h" +#include "xdgdirs.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +struct XdgMimeData +{ +public: + XdgMimeData(QString media, QString subtype); + bool readXml(QIODevice* xml); + + QString mMedia; + QString mSubtype; + + bool mDbLoaded; + QString mComment; + QMap mLocalizedComments; + QStringList mPatterns; + QString mSubClassOf; +}; + + + +/************************************************ + + ************************************************/ +XdgMimeInfo::XdgMimeInfo(const QString& mimeType) +{ + QString media = mimeType.section('/', 0, 0); + QString subtype = mimeType.section('/', 1); + mData = new XdgMimeData(media, subtype); +} + +XdgMimeInfo::~XdgMimeInfo() +{ + delete mData; + mData = 0; +} + +/************************************************ + + ************************************************/ +QString getFileMimeType(const QFileInfo& fileInfo, bool followSymLinks) +{ + + QString result("application/octet-stream"); + + magic_t magicMimePredictor; + magicMimePredictor = magic_open(MAGIC_MIME_TYPE); // Open predictor + if (!magicMimePredictor) { + qWarning() << "libmagic: Unable to initialize magic library"; + return result; + } + + + if (magic_load(magicMimePredictor, 0)) { // if not 0 - error + qWarning() << QString("libmagic: Can't load magic database - %1").arg(magic_error(magicMimePredictor)); + magic_close(magicMimePredictor); // Close predictor + return result; + } + + QByteArray ar = fileInfo.absoluteFilePath().toLocal8Bit(); + if (followSymLinks && fileInfo.isSymLink()) + { + ar = fileInfo.symLinkTarget().toLocal8Bit(); + } + char *file = ar.data(); + + // getting mime-type ........................ + const char *mime; + mime = magic_file(magicMimePredictor, file); + result = QString(mime); + + // Close predictor .......................... + magic_close(magicMimePredictor); + + return result; +} + + +/************************************************ + + ************************************************/ +XdgMimeInfo::XdgMimeInfo(const QFileInfo& file, bool followSymLinks) +{ + QString mimeType = getFileMimeType(file, followSymLinks); + QString media = mimeType.section('/', 0, 0); + QString subtype = mimeType.section('/', 1); + mData = new XdgMimeData(media, subtype); +} + + +/************************************************ + + ************************************************/ +QString XdgMimeInfo::mimeType() const +{ + return mData->mMedia + "/" + mData->mSubtype; +} + + +QString XdgMimeInfo::mediaType() const +{ + return mData->mMedia; +} + + +QString XdgMimeInfo::subType() const +{ + return mData->mSubtype; +} + +QString XdgMimeInfo::comment() const +{ + return mData->mComment; +} + +QString XdgMimeInfo::localizedComment() const +{ + // FIXME + return mData->mComment; +} + +QStringList XdgMimeInfo::patterns() const +{ + return mData->mPatterns; +} + +/************************************************ + + ************************************************/ +QString XdgMimeInfo::iconName() const +{ + QStringList names; + names << QString("%1-x-%2").arg(mData->mMedia, mData->mSubtype); + names << QString("%1-%2").arg(mData->mMedia, mData->mSubtype); + names << QString("%1-x-generic").arg(mData->mMedia); + names << QString("%1-generic").arg(mData->mMedia); + + foreach (QString s, names) + { + if (!XdgIcon::fromTheme(s).isNull()) + return s; + } + + return "unknown"; +} + +/************************************************ + + ************************************************/ +QIcon XdgMimeInfo::icon() const +{ + return XdgIcon::fromTheme(iconName()); +} + + +QString XdgMimeInfo::subClassOf() const +{ + return mData->mSubClassOf; +} + + +bool XdgMimeInfo::loadFromDb(QIODevice* xml) +{ + return mData->readXml(xml); +} + + + +XdgMimeData::XdgMimeData(QString media, QString subtype): + mMedia(media), + mSubtype(subtype), + mDbLoaded(false) +{ +} + +bool XdgMimeData::readXml(QIODevice* xml) +{ + QDomDocument domDocument; + if (! domDocument.setContent(xml, false)) + { + return false; + } + + QDomElement rootElement = domDocument.documentElement(); + if (rootElement.nodeName() != "mime-type") + { + return false; + } + + if (rootElement.attribute("type") != mMedia + "/" + mSubtype) + { + return false; + } + + QDomNodeList commentNodes = rootElement.elementsByTagName("comment"); + for(int i = 0; i < commentNodes.size(); i++) + { + if (! commentNodes.item(i).isElement()) + { + continue; + } + + QDomElement commentElement = commentNodes.item(i).toElement(); + + if (commentElement.hasAttribute("xml:lang")) + { + mLocalizedComments[commentElement.attribute("xml:lang")] = commentElement.text(); + } + else + { + mComment = commentElement.text(); + } + } + + QSet collectedPatterns; + QDomNodeList globNodes = rootElement.elementsByTagName("glob"); + for(int i = 0; i < globNodes.size(); i++) + { + if (globNodes.item(i).isElement() && globNodes.item(i).toElement().hasAttribute("pattern")) + { + collectedPatterns << globNodes.item(i).toElement().attribute("pattern"); + } + } + + mPatterns = collectedPatterns.toList(); + mPatterns.sort(); + + QDomNodeList subClassOfElements = rootElement.elementsByTagName("sub-class-of"); + if (subClassOfElements.size() > 0) + { + mSubClassOf = subClassOfElements.at(0).toElement().attribute("type"); + } + + return true; +} + + +QStringList XdgMimeInfoCache::mediatypes() +{ + return cache().keys(); +} + +QStringList XdgMimeInfoCache::subtypes(const QString& media) +{ + return cache().value(media).keys(); +} + +XdgMimeInfo* XdgMimeInfoCache::xdgMimeInfo(const QString & media, const QString & subtype) +{ + return cache().value(media).value(subtype); +} + +XdgMimeInfo* XdgMimeInfoCache::xdgMimeInfo(const QString& mimetype) +{ + QString media = mimetype.section("/", 0, 0); + QString subtype = mimetype.section("/", 1, 1); + return xdgMimeInfo(media, subtype); +} + + +void loadMimeInfoCache(QMap > & cache) +{ + qDebug() << "loadMimeInfoCache"; + QStringList datadirs = XdgDirs::dataDirs(); + datadirs.prepend(XdgDirs::dataHome(false)); + const QStringList filters = (QStringList() << "*.xml"); + + foreach (const QString datadir, datadirs) + { + QDir mimedir(datadir + "/mime"); + + if (! mimedir.exists()) + { + continue; + } + + + foreach (QFileInfo mediadirInfo, mimedir.entryInfoList(QDir::Dirs | QDir::NoDotAndDotDot)) + { + QString media = mediadirInfo.fileName(); + + QDir mediadir(mediadirInfo.absoluteFilePath()); + foreach (QFileInfo subtypefileInfo, mediadir.entryInfoList(filters, QDir::Files)) + { + QString subtype = subtypefileInfo.fileName().left(subtypefileInfo.fileName().length() - 4); + //qDebug() << "subtype:" << subtype; + QFile subtypefile(subtypefileInfo.absoluteFilePath()); + XdgMimeInfo* mimeInfo = new XdgMimeInfo(media + "/" + subtype); + if (subtypefile.open(QIODevice::ReadOnly) && mimeInfo->loadFromDb(&subtypefile)) + { + cache[media][subtype] = mimeInfo; + } + else + { + delete mimeInfo; + } + + } + } + } + + // TESTING + XdgMimeData data("application", "msword"); + QFile mswordxml("/usr/share/mime/application/msword.xml"); + mswordxml.open(QIODevice::ReadOnly); + data.readXml(&mswordxml); + qDebug() << "================================================================================="; + qDebug() << "data:" << data.mMedia << data.mSubtype << data.mComment << data.mLocalizedComments << data.mPatterns; + qDebug() << "================================================================================="; +} + +QMap > & XdgMimeInfoCache::cache() +{ + static QMap > _cache; + static bool cache_loaded = false; + + if (! cache_loaded) + { + loadMimeInfoCache(_cache); + cache_loaded = true; + } + + return _cache; +} diff --git a/xdgmime.h b/xdgmime.h new file mode 100644 index 0000000..7c4a201 --- /dev/null +++ b/xdgmime.h @@ -0,0 +1,99 @@ +/* BEGIN_COMMON_COPYRIGHT_HEADER + * (c)LGPL2+ + * + * Razor - a lightweight, Qt based, desktop toolset + * http://razor-qt.org + * + * Copyright: 2010-2011 Razor team + * Authors: + * Alexander Sokoloff + * + * 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 */ + + +#ifndef QTXDG_XDGMIME_H +#define QTXDG_XDGMIME_H + +#include +#include +#include + +#include "xdgmacros.h" + +struct XdgMimeData; + +/*! @brief The XdgMimeInfo class provides mime information about file. + */ +class QTXDG_DEPRECATED QTXDG_API XdgMimeInfo +{ +public: + /// Constructs a XdgMimeInfo with the mimeType type. + explicit XdgMimeInfo(const QString& mimeType); + + /** + Constructs a new XdgMimeInfo that gives mime information about the given file. + If file is symlink and followSymLinks is true function gives information for the + file the link references rather than for the link itself. + **/ + explicit XdgMimeInfo(const QFileInfo& file, bool followSymLinks=true); + + ~XdgMimeInfo(); + + /// Returns the name of the mime type. + QString mimeType() const; + + /// Returns the media type, eg. 'application' for mimetype 'application/pdf' + QString mediaType() const; + + /// Returns the subtype, e.g. 'pdf' for 'application/pdf' + QString subType() const; + + QString comment() const; + + QString localizedComment() const; + + QStringList patterns() const; + + /// Returns an icon associated with the mime type. + QIcon icon() const; + + /// Returns an icon associated with the mime type. + QString iconName() const; + + QString subClassOf() const; + + bool loadFromDb(QIODevice* xml); + +private: + XdgMimeData *mData; +}; + + +class QTXDG_DEPRECATED QTXDG_API XdgMimeInfoCache +{ +public: + static QStringList mediatypes(); + static QStringList subtypes(const QString & media); + static XdgMimeInfo* xdgMimeInfo(const QString & media, const QString & subtype); + static XdgMimeInfo* xdgMimeInfo(const QString & mimetype); + +private: + static QMap > & cache(); +}; + +#endif // QTXDG_XDGMIME_H diff --git a/xdgmimetype.cpp b/xdgmimetype.cpp new file mode 100644 index 0000000..7059e5c --- /dev/null +++ b/xdgmimetype.cpp @@ -0,0 +1,104 @@ +/* + * libqtxdg - An Qt implementation of freedesktop.org xdg specs + * Copyright (C) 2014 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 "xdgmimetype.h" + +#include "xdgicon.h" + +class XdgMimeTypePrivate : public QSharedData { +public: + XdgMimeTypePrivate(); + XdgMimeTypePrivate(const XdgMimeType& other); + + void computeIconName(); + + QString iconName; + bool computed; +}; + +XdgMimeTypePrivate::XdgMimeTypePrivate() + : computed(false) +{ +} + +XdgMimeTypePrivate::XdgMimeTypePrivate(const XdgMimeType& other) + : iconName(other.dx->iconName), + computed(other.dx->computed) +{ +} + +XdgMimeType::XdgMimeType() + : QMimeType(), + dx(new XdgMimeTypePrivate()) +{ +} + +XdgMimeType::XdgMimeType(const QMimeType& mime) + : QMimeType(mime), + dx(new XdgMimeTypePrivate()) +{ +} + +XdgMimeType::XdgMimeType(const XdgMimeType& mime) + : QMimeType(mime), + dx(mime.dx) +{ +} + +XdgMimeType &XdgMimeType::operator=(const XdgMimeType &other) +{ + QMimeType::operator =(other); + + if (dx != other.dx) + dx = other.dx; + + return *this; +} + +XdgMimeType::~XdgMimeType() +{ +} + +QString XdgMimeType::iconName() const +{ + if (dx->computed) { + return dx->iconName; + } else { + dx->iconName.clear(); + QStringList names; + + names.append(QMimeType::iconName()); + names.append(QMimeType::genericIconName()); + + foreach (QString s, names) { + if (!XdgIcon::fromTheme(s).isNull()) { + dx->iconName = s; + break; + } + } + dx->computed = true; + return dx->iconName; + } +} + +QIcon XdgMimeType::icon() const +{ + return XdgIcon::fromTheme((iconName())); +} diff --git a/xdgmimetype.h b/xdgmimetype.h new file mode 100644 index 0000000..a654834 --- /dev/null +++ b/xdgmimetype.h @@ -0,0 +1,129 @@ +/* + * libqtxdg - An Qt implementation of freedesktop.org xdg specs + * Copyright (C) 2014 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 + */ + +#ifndef QTXDG_MIMETYPE_H +#define QTXDG_MIMETYPE_H + +#include "xdgmacros.h" +#include +#include +#include +#include + +#include + +class XdgMimeTypePrivate; + +//! Describes types of file or data, represented by a MIME type string. +/*! This class is an QMimeType descendent. The differences are the icon() and + * iconName() methods. @see icon() @see iconName() + * + * Some parts of the documentation are based on QMimeType documentation. + * @see http://qt-project.org/doc/qt-5/qmimetype.html + * + * @author Luís Pereira (luis.artur.pereira@gmail.com) + */ + +class QTXDG_API XdgMimeType : public QMimeType { +public: + + /*! Constructs an XdgMimeType object initialized with default property + values that indicate an invalid MIME type. + @see QMimeType::QMimeType() + */ + XdgMimeType(); + + /*! Constructs an XdgMimeType object from an QMimeType object + * @see QMimeType + */ + XdgMimeType(const QMimeType& mime); + + //! Constructs an XdgMimeType object from another XdgMimeType object + XdgMimeType(const XdgMimeType& mime); + + /*! Assigns the data of other to this XdgMimeType object. + * @return a reference to this object. + */ + XdgMimeType &operator=(const XdgMimeType &other); + + + /*! Compares the other XdgMimeType object to this XdgMimeType object. + * @return true if other equals this XdgMimeType object, otherwise returns + * false. The name is the unique identifier for a mimetype, so two mimetypes + * with the same name, are equal. + * @see QMimeType::operator==() + */ + bool operator==(const XdgMimeType &other) const + { + return QMimeType::operator==(other); + } + + inline bool operator!=(const XdgMimeType &other) const + { + return QMimeType::operator==(other); + } + + void swap(XdgMimeType &other) + { + QMimeType::swap(other); + qSwap(dx, other.dx); + } + + //! Destructs the mimetype + ~XdgMimeType(); + + //! Returns the name of the MIME type. + /*! The same as QMimeType::name(). Provided for compatibilty with deprecated + * XdgMimeInfo::mimeType(). + * @see QMimeType::name() + */ + inline QString mimeType() const { return QMimeType::name(); } + + //! Returns an icon associated with the mimetype. + /*! @return an icon from the current icon theme associated with the + * mimetype. If the icon theme doesn't provide one it returns QIcon(). + * It gets the icon name from iconName() and then gives it to + * XdgIcon::fromTheme(). + * @see iconName() @see XdgIcon::fromTheme() + */ + QIcon icon() const; + + //! Returns an icon name associated with the mimetype. + /*! @return an icon name from the current icon theme associated with the + * mimetype. If the current icon theme doesn't provide one, it returns an + * empty QString. + * The returned icon name is suitable to be given to XdgIcon::fromTheme() + * to load the icon. + * @see XdgIcon::fromTheme() + */ + QString iconName() const; + +protected: + friend class XdgMimeTypePrivate; + QExplicitlySharedDataPointer dx; + + +}; +#if QT_VERSION < QT_VERSION_CHECK(5,0,0) +#else +Q_DECLARE_SHARED(XdgMimeType) +#endif + +#endif // QTXDG_MIMETYPE_H diff --git a/xmlhelper.cpp b/xmlhelper.cpp new file mode 100644 index 0000000..13781b8 --- /dev/null +++ b/xmlhelper.cpp @@ -0,0 +1,51 @@ +/* BEGIN_COMMON_COPYRIGHT_HEADER + * (c)LGPL2+ + * + * Razor - a lightweight, Qt based, desktop toolset + * http://razor-qt.org + * + * Copyright: 2010-2011 Razor team + * Authors: + * Alexander Sokoloff + * + * 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 "xmlhelper.h" + +#include +#include +#include + + +/************************************************ + + ************************************************/ +QDebug operator<<(QDebug dbg, const QDomElement &el) +{ + QDomNamedNodeMap map = el.attributes(); + + QString args; + for (int i=0; i%3").arg(el.tagName()).arg(args).arg(el.text()); + return dbg.space(); +} + + diff --git a/xmlhelper.h b/xmlhelper.h new file mode 100644 index 0000000..571dbb9 --- /dev/null +++ b/xmlhelper.h @@ -0,0 +1,162 @@ +/* BEGIN_COMMON_COPYRIGHT_HEADER + * (c)LGPL2+ + * + * Razor - a lightweight, Qt based, desktop toolset + * http://razor-qt.org + * + * Copyright: 2010-2011 Razor team + * Authors: + * Alexander Sokoloff + * + * 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 */ + + +#ifndef QTXDG_XMLHELPER_H +#define QTXDG_XMLHELPER_H + +#include "xdgmacros.h" +#include +#include +#include + +class QTXDG_API DomElementIterator +{ +public: + explicit DomElementIterator(const QDomNode& parentNode, const QString& tagName = QString()) + { + mTagName = tagName; + mParent = parentNode; + toFront(); + } + + void toFront() + { + mNext = mParent.firstChildElement(mTagName); + } + + bool hasNext() + { + return (!mNext.isNull()); + } + + const QDomElement& next() + { + mCur = mNext; + mNext = mNext.nextSiblingElement(mTagName); + return mCur; + } + + + void toBack() + { + mNext = mParent.lastChildElement(mTagName); + } + + + bool hasPrevious() + { + return (!mNext.isNull()); + } + + const QDomElement& previous() + { + mCur = mNext; + mNext = mNext.previousSiblingElement(mTagName); + return mCur; + } + + const QDomElement& current() const + { + return mCur; + } + + +private: + QString mTagName; + QDomNode mParent; + QDomElement mCur; + QDomElement mNext; +}; + +class MutableDomElementIterator +{ +public: + explicit MutableDomElementIterator(QDomNode& parentNode, const QString& tagName = QString()) + { + mTagName = tagName; + mParent = parentNode; + toFront(); + } + + void toFront() + { + mNext = mParent.firstChildElement(mTagName); + } + + bool hasNext() + { + return (!mNext.isNull()); + } + + QDomElement& next() + { + mCur = mNext; + mNext = mNext.nextSiblingElement(mTagName); + return mCur; + } + + + void toBack() + { + mNext = mParent.lastChildElement(mTagName); + } + + + bool hasPrevious() + { + return (!mNext.isNull()); + } + + QDomElement& previous() + { + mCur = mNext; + mNext = mNext.previousSiblingElement(mTagName); + return mCur; + } + + QDomElement& current() + { + return mCur; + } + + +private: + QString mTagName; + QDomNode mParent; + QDomElement mCur; + QDomElement mNext; +}; + + + + + +QDebug operator<<(QDebug dbg, const QDomElement &el); + + +#endif // QTXDG_XMLHELPER_H