commit 8762635176142550e4736dae90f8d6629cf30421 Author: Andrew Lee (李健秋) Date: Thu Aug 13 03:54:32 2015 +0800 Adding upstream version 1.2.0. Signed-off-by: Andrew Lee (李健秋) 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