commit 04ab4db09c3cc93a156470e88733fdbf4da6a59e Author: openKylinBot Date: Sat May 14 03:36:26 2022 +0800 Import Upstream version 0.9.3+16.04.20160218 diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..72cbc45 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,145 @@ +project(dbusmenu-qt) +cmake_minimum_required(VERSION 2.8.11) +set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_SOURCE_DIR}/cmake/modules") + +# Build options +option(WITH_DOC "Build documentation (requires Doxygen)" ON) + +# Versions +## Package version +set(dbusmenu_qt_VERSION_MAJOR 0) +set(dbusmenu_qt_VERSION_MINOR 9) +set(dbusmenu_qt_VERSION_PATCH 2) +set(dbusmenu_qt_VERSION ${dbusmenu_qt_VERSION_MAJOR}.${dbusmenu_qt_VERSION_MINOR}.${dbusmenu_qt_VERSION_PATCH}) + +## Lib version +### Bump this one when a binary-incompatible change is introduced +set(dbusmenu_qt_lib_SOVERSION 2) + +### Bump this one when the API is extended in a binary-compatible way +set(dbusmenu_qt_lib_API_VERSION 6) + +### Bump this one when changes do not extend the API +set(dbusmenu_qt_lib_PATCH_VERSION 0) + +set(dbusmenu_qt_lib_VERSION ${dbusmenu_qt_lib_SOVERSION}.${dbusmenu_qt_lib_API_VERSION}.${dbusmenu_qt_lib_PATCH_VERSION}) + +# Check if we want to explicitly select the Qt version to be used or autodetect +if (NOT USE_QT4 AND NOT USE_QT5) + # Autodetect, prefering Qt5 + message(STATUS "Autodetecting Qt version to use") + find_package(Qt5Widgets QUIET) + if (Qt5Widgets_FOUND) + set(USE_QT5 TRUE) + endif() +endif() + +# Detect for which Qt version we're building +if (USE_QT5) + find_package(Qt5Widgets REQUIRED) + find_package(Qt5DBus REQUIRED) + include_directories(${Qt5Widgets_INCLUDE_DIRS} ${Qt5DBus_INCLUDE_DIRS}) + find_package(Qt5Core REQUIRED) + set(CMAKE_AUTOMOC ON) + set(CMAKE_AUTOMOC_RELAXED_MODE ON) + set(CMAKE_POSITION_INDEPENDENT_CODE ON) + + set(QT_SUFFIX "qt5") +else() + find_package(Qt4 REQUIRED) + include_directories( + ${QT_INCLUDE_DIR} + ${QT_QTCORE_INCLUDE_DIR} + ${QT_QTDBUS_INCLUDE_DIR} + ${QT_QTGUI_INCLUDE_DIR} + ) + + set(QT_SUFFIX "qt") +endif() + +include (CheckCXXCompilerFlag) +# Check some compiler flags +check_cxx_compiler_flag(-fvisibility=hidden __DBUSMENU_HAVE_GCC_VISIBILITY) +if (__DBUSMENU_HAVE_GCC_VISIBILITY AND NOT WIN32) + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fvisibility=hidden") +endif (__DBUSMENU_HAVE_GCC_VISIBILITY AND NOT WIN32) + +check_cxx_compiler_flag(-Woverloaded-virtual __DBUSMENU_HAVE_W_OVERLOADED_VIRTUAL) +if (__DBUSMENU_HAVE_W_OVERLOADED_VIRTUAL) + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Woverloaded-virtual") +endif (__DBUSMENU_HAVE_W_OVERLOADED_VIRTUAL) + +check_cxx_compiler_flag(-std=c++11 __DBUSMENU_HAVE_CXX11) +if (__DBUSMENU_HAVE_CXX11) + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11") +endif (__DBUSMENU_HAVE_CXX11) + +include(CMakePackageConfigHelpers) +include(GNUInstallDirs) +set(LIB_DESTINATION "${CMAKE_INSTALL_LIBDIR}") +set(CMAKECONFIG_INSTALL_DIR "${LIB_DESTINATION}/cmake/dbusmenu-${QT_SUFFIX}") +set(INCLUDE_INSTALL_DIR "include/dbusmenu-${QT_SUFFIX}") + +# dist targets +set(ARCHIVE_NAME libdbusmenu-${QT_SUFFIX}-${dbusmenu_qt_VERSION}) +add_custom_target(dist + COMMAND bzr export --root=${ARCHIVE_NAME} ${CMAKE_BINARY_DIR}/${ARCHIVE_NAME}.tar.bz2 + WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} + ) + +add_custom_target(distcheck + COMMAND cd ${CMAKE_BINARY_DIR} + && rm -rf ${ARCHIVE_NAME} + && tar xf ${ARCHIVE_NAME}.tar.bz2 + && mkdir ${ARCHIVE_NAME}/build + && cd ${ARCHIVE_NAME}/build + && cmake -DCMAKE_INSTALL_PREFIX=../install .. + && make + && make install + && make check + ) +add_dependencies(distcheck dist) + +configure_file(dbusmenu-qt.pc.in ${CMAKE_BINARY_DIR}/dbusmenu-${QT_SUFFIX}.pc @ONLY) + +install(FILES ${CMAKE_BINARY_DIR}/dbusmenu-${QT_SUFFIX}.pc + DESTINATION ${LIB_DESTINATION}/pkgconfig + ) + +add_subdirectory(src) +add_subdirectory(tests) +add_subdirectory(tools) + +if(WITH_DOC) + configure_file(Doxyfile.in ${CMAKE_BINARY_DIR}/Doxyfile @ONLY) + + add_custom_target(doc ALL doxygen + WORKING_DIRECTORY ${CMAKE_BINARY_DIR} + ) + + install(DIRECTORY ${CMAKE_BINARY_DIR}/html/ + DESTINATION share/doc/libdbusmenu-${QT_SUFFIX}-doc + ) +endif(WITH_DOC) + +# Generate dbusmenu-qt-config* files +configure_package_config_file( + dbusmenu-qt-config.cmake.in + ${CMAKE_BINARY_DIR}/dbusmenu-${QT_SUFFIX}-config.cmake + INSTALL_DESTINATION ${CMAKECONFIG_INSTALL_DIR} + PATH_VARS INCLUDE_INSTALL_DIR + ) + +write_basic_package_version_file( + ${CMAKE_BINARY_DIR}/dbusmenu-${QT_SUFFIX}-config-version.cmake + VERSION ${dbusmenu_qt_VERSION} + COMPATIBILITY SameMajorVersion + ) + +# Install dbusmenu-qt-config* files +install(FILES + ${CMAKE_BINARY_DIR}/dbusmenu-${QT_SUFFIX}-config.cmake + ${CMAKE_BINARY_DIR}/dbusmenu-${QT_SUFFIX}-config-version.cmake + DESTINATION "${CMAKECONFIG_INSTALL_DIR}" + COMPONENT Devel + ) diff --git a/COPYING b/COPYING new file mode 100644 index 0000000..e38ffa8 --- /dev/null +++ b/COPYING @@ -0,0 +1,481 @@ + GNU LIBRARY GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1991 Free Software Foundation, Inc. + 59 Temple Place - Suite 330 + Boston, MA 02111-1307, 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 library GPL. It is + numbered 2 because it goes with version 2 of the ordinary GPL.] + + 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 Library General Public License, applies to some +specially designated Free Software Foundation software, and to any +other libraries whose authors decide to use it. You can use it for +your libraries, too. + + When we speak of free software, we are referring to freedom, 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 or use pieces of it +in new free programs; and that you know you can do these things. + + To protect your rights, we need to make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the 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 a program 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. + + Our method of protecting your rights has two steps: (1) copyright +the library, and (2) offer you this license which gives you legal +permission to copy, distribute and/or modify the library. + + Also, for each distributor's protection, we want to make certain +that everyone understands that there is no warranty for this free +library. If the library is modified by someone else and passed on, we +want its recipients to know that what they have is not the original +version, so that any problems introduced by others will not reflect on +the original authors' reputations. + + Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that companies distributing free +software will individually obtain patent licenses, thus in effect +transforming the program into proprietary software. To prevent this, +we have made it clear that any patent must be licensed for everyone's +free use or not licensed at all. + + Most GNU software, including some libraries, is covered by the ordinary +GNU General Public License, which was designed for utility programs. This +license, the GNU Library General Public License, applies to certain +designated libraries. This license is quite different from the ordinary +one; be sure to read it in full, and don't assume that anything in it is +the same as in the ordinary license. + + The reason we have a separate public license for some libraries is that +they blur the distinction we usually make between modifying or adding to a +program and simply using it. Linking a program with a library, without +changing the library, is in some sense simply using the library, and is +analogous to running a utility program or application program. However, in +a textual and legal sense, the linked executable is a combined work, a +derivative of the original library, and the ordinary General Public License +treats it as such. + + Because of this blurred distinction, using the ordinary General +Public License for libraries did not effectively promote software +sharing, because most developers did not use the libraries. We +concluded that weaker conditions might promote sharing better. + + However, unrestricted linking of non-free programs would deprive the +users of those programs of all benefit from the free status of the +libraries themselves. This Library General Public License is intended to +permit developers of non-free programs to use free libraries, while +preserving your freedom as a user of such programs to change the free +libraries that are incorporated in them. (We have not seen how to achieve +this as regards changes in header files, but we have achieved it as regards +changes in the actual functions of the Library.) The hope is that this +will lead to faster development of free libraries. + + 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, while the latter only +works together with the library. + + Note that it is possible for a library to be covered by the ordinary +General Public License rather than by this special one. + + GNU LIBRARY GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License Agreement applies to any software library which +contains a notice placed by the copyright holder or other authorized +party saying it may be distributed under the terms of this Library +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 compile 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) 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. + + c) 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. + + d) 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 source code 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 to +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 Library 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 + How to Apply These Terms to Your New Libraries + + If you develop a new library, and you want it to be of the greatest +possible use to the public, we recommend making it free software that +everyone can redistribute and change. You can do so by permitting +redistribution under these terms (or, alternatively, under the terms of the +ordinary General Public License). + + To apply these terms, attach the following notices to the library. It is +safest to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least the +"copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + 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 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + +Also add information on how to contact you by electronic and paper mail. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the library, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the + library `Frob' (a library for tweaking knobs) written by James Random Hacker. + + , 1 April 1990 + Ty Coon, President of Vice + +That's all there is to it! diff --git a/Doxyfile.in b/Doxyfile.in new file mode 100644 index 0000000..cf2d263 --- /dev/null +++ b/Doxyfile.in @@ -0,0 +1,277 @@ +# Doxyfile 1.7.3 + +#--------------------------------------------------------------------------- +# Project related configuration options +#--------------------------------------------------------------------------- +DOXYFILE_ENCODING = UTF-8 +PROJECT_NAME = dbusmenu-qt +PROJECT_NUMBER = @dbusmenu_qt_VERSION@ +PROJECT_BRIEF = +PROJECT_LOGO = +OUTPUT_DIRECTORY = +CREATE_SUBDIRS = NO +OUTPUT_LANGUAGE = English +BRIEF_MEMBER_DESC = YES +REPEAT_BRIEF = YES +ABBREVIATE_BRIEF = +ALWAYS_DETAILED_SEC = NO +INLINE_INHERITED_MEMB = NO +FULL_PATH_NAMES = YES +STRIP_FROM_PATH = +STRIP_FROM_INC_PATH = +SHORT_NAMES = NO +JAVADOC_AUTOBRIEF = NO +QT_AUTOBRIEF = NO +MULTILINE_CPP_IS_BRIEF = NO +INHERIT_DOCS = YES +SEPARATE_MEMBER_PAGES = NO +TAB_SIZE = 4 +ALIASES = +OPTIMIZE_OUTPUT_FOR_C = NO +OPTIMIZE_OUTPUT_JAVA = NO +OPTIMIZE_FOR_FORTRAN = NO +OPTIMIZE_OUTPUT_VHDL = NO +EXTENSION_MAPPING = +BUILTIN_STL_SUPPORT = NO +CPP_CLI_SUPPORT = NO +SIP_SUPPORT = NO +IDL_PROPERTY_SUPPORT = YES +DISTRIBUTE_GROUP_DOC = NO +SUBGROUPING = YES +TYPEDEF_HIDES_STRUCT = NO +SYMBOL_CACHE_SIZE = 0 +#--------------------------------------------------------------------------- +# Build related configuration options +#--------------------------------------------------------------------------- +EXTRACT_ALL = NO +EXTRACT_PRIVATE = NO +EXTRACT_STATIC = NO +EXTRACT_LOCAL_CLASSES = YES +EXTRACT_LOCAL_METHODS = NO +EXTRACT_ANON_NSPACES = NO +HIDE_UNDOC_MEMBERS = NO +HIDE_UNDOC_CLASSES = NO +HIDE_FRIEND_COMPOUNDS = NO +HIDE_IN_BODY_DOCS = NO +INTERNAL_DOCS = NO +CASE_SENSE_NAMES = YES +HIDE_SCOPE_NAMES = NO +SHOW_INCLUDE_FILES = YES +FORCE_LOCAL_INCLUDES = NO +INLINE_INFO = YES +SORT_MEMBER_DOCS = YES +SORT_BRIEF_DOCS = NO +SORT_MEMBERS_CTORS_1ST = NO +SORT_GROUP_NAMES = NO +SORT_BY_SCOPE_NAME = NO +STRICT_PROTO_MATCHING = NO +GENERATE_TODOLIST = YES +GENERATE_TESTLIST = YES +GENERATE_BUGLIST = YES +GENERATE_DEPRECATEDLIST= YES +ENABLED_SECTIONS = +MAX_INITIALIZER_LINES = 30 +SHOW_USED_FILES = YES +SHOW_DIRECTORIES = NO +SHOW_FILES = YES +SHOW_NAMESPACES = YES +FILE_VERSION_FILTER = +LAYOUT_FILE = +#--------------------------------------------------------------------------- +# configuration options related to warning and progress messages +#--------------------------------------------------------------------------- +QUIET = YES +WARNINGS = YES +WARN_IF_UNDOCUMENTED = YES +WARN_IF_DOC_ERROR = YES +WARN_NO_PARAMDOC = NO +WARN_FORMAT = "$file:$line: $text" +WARN_LOGFILE = +#--------------------------------------------------------------------------- +# configuration options related to the input files +#--------------------------------------------------------------------------- +INPUT = @CMAKE_SOURCE_DIR@/src +INPUT_ENCODING = UTF-8 +FILE_PATTERNS = *.h +RECURSIVE = NO +EXCLUDE = +EXCLUDE_SYMLINKS = NO +EXCLUDE_PATTERNS = *_p.* +EXCLUDE_SYMBOLS = +EXAMPLE_PATH = +EXAMPLE_PATTERNS = +EXAMPLE_RECURSIVE = NO +IMAGE_PATH = +INPUT_FILTER = +FILTER_PATTERNS = +FILTER_SOURCE_FILES = NO +FILTER_SOURCE_PATTERNS = +#--------------------------------------------------------------------------- +# configuration options related to source browsing +#--------------------------------------------------------------------------- +SOURCE_BROWSER = NO +INLINE_SOURCES = NO +STRIP_CODE_COMMENTS = YES +REFERENCED_BY_RELATION = NO +REFERENCES_RELATION = NO +REFERENCES_LINK_SOURCE = YES +USE_HTAGS = NO +VERBATIM_HEADERS = YES +#--------------------------------------------------------------------------- +# configuration options related to the alphabetical class index +#--------------------------------------------------------------------------- +ALPHABETICAL_INDEX = YES +COLS_IN_ALPHA_INDEX = 5 +IGNORE_PREFIX = +#--------------------------------------------------------------------------- +# configuration options related to the HTML output +#--------------------------------------------------------------------------- +GENERATE_HTML = YES +HTML_OUTPUT = html +HTML_FILE_EXTENSION = .html +HTML_HEADER = +HTML_FOOTER = +HTML_STYLESHEET = +HTML_COLORSTYLE_HUE = 220 +HTML_COLORSTYLE_SAT = 100 +HTML_COLORSTYLE_GAMMA = 80 +HTML_TIMESTAMP = YES +HTML_ALIGN_MEMBERS = YES +HTML_DYNAMIC_SECTIONS = NO +GENERATE_DOCSET = NO +DOCSET_FEEDNAME = "Doxygen generated docs" +DOCSET_BUNDLE_ID = org.doxygen.Project +DOCSET_PUBLISHER_ID = org.doxygen.Publisher +DOCSET_PUBLISHER_NAME = Publisher +GENERATE_HTMLHELP = NO +CHM_FILE = +HHC_LOCATION = +GENERATE_CHI = NO +CHM_INDEX_ENCODING = +BINARY_TOC = NO +TOC_EXPAND = NO +GENERATE_QHP = NO +QCH_FILE = +QHP_NAMESPACE = org.doxygen.Project +QHP_VIRTUAL_FOLDER = doc +QHP_CUST_FILTER_NAME = +QHP_CUST_FILTER_ATTRS = +QHP_SECT_FILTER_ATTRS = +QHG_LOCATION = +GENERATE_ECLIPSEHELP = NO +ECLIPSE_DOC_ID = org.doxygen.Project +DISABLE_INDEX = NO +ENUM_VALUES_PER_LINE = 4 +GENERATE_TREEVIEW = NO +USE_INLINE_TREES = NO +TREEVIEW_WIDTH = 250 +EXT_LINKS_IN_WINDOW = NO +FORMULA_FONTSIZE = 10 +FORMULA_TRANSPARENT = YES +USE_MATHJAX = NO +MATHJAX_RELPATH = http://www.mathjax.org/mathjax +SEARCHENGINE = YES +SERVER_BASED_SEARCH = NO +#--------------------------------------------------------------------------- +# configuration options related to the LaTeX output +#--------------------------------------------------------------------------- +GENERATE_LATEX = NO +LATEX_OUTPUT = latex +LATEX_CMD_NAME = latex +MAKEINDEX_CMD_NAME = makeindex +COMPACT_LATEX = NO +PAPER_TYPE = a4 +EXTRA_PACKAGES = +LATEX_HEADER = +PDF_HYPERLINKS = YES +USE_PDFLATEX = YES +LATEX_BATCHMODE = NO +LATEX_HIDE_INDICES = NO +LATEX_SOURCE_CODE = NO +#--------------------------------------------------------------------------- +# configuration options related to the RTF output +#--------------------------------------------------------------------------- +GENERATE_RTF = NO +RTF_OUTPUT = rtf +COMPACT_RTF = NO +RTF_HYPERLINKS = NO +RTF_STYLESHEET_FILE = +RTF_EXTENSIONS_FILE = +#--------------------------------------------------------------------------- +# configuration options related to the man page output +#--------------------------------------------------------------------------- +GENERATE_MAN = NO +MAN_OUTPUT = man +MAN_EXTENSION = .3 +MAN_LINKS = NO +#--------------------------------------------------------------------------- +# configuration options related to the XML output +#--------------------------------------------------------------------------- +GENERATE_XML = NO +XML_OUTPUT = xml +XML_SCHEMA = +XML_DTD = +XML_PROGRAMLISTING = YES +#--------------------------------------------------------------------------- +# configuration options for the AutoGen Definitions output +#--------------------------------------------------------------------------- +GENERATE_AUTOGEN_DEF = NO +#--------------------------------------------------------------------------- +# configuration options related to the Perl module output +#--------------------------------------------------------------------------- +GENERATE_PERLMOD = NO +PERLMOD_LATEX = NO +PERLMOD_PRETTY = YES +PERLMOD_MAKEVAR_PREFIX = +#--------------------------------------------------------------------------- +# Configuration options related to the preprocessor +#--------------------------------------------------------------------------- +ENABLE_PREPROCESSING = YES +MACRO_EXPANSION = NO +EXPAND_ONLY_PREDEF = NO +SEARCH_INCLUDES = YES +INCLUDE_PATH = +INCLUDE_FILE_PATTERNS = +PREDEFINED = +EXPAND_AS_DEFINED = +SKIP_FUNCTION_MACROS = YES +#--------------------------------------------------------------------------- +# Configuration::additions related to external references +#--------------------------------------------------------------------------- +TAGFILES = +GENERATE_TAGFILE = +ALLEXTERNALS = NO +EXTERNAL_GROUPS = YES +PERL_PATH = /usr/bin/perl +#--------------------------------------------------------------------------- +# Configuration options related to the dot tool +#--------------------------------------------------------------------------- +CLASS_DIAGRAMS = YES +MSCGEN_PATH = +HIDE_UNDOC_RELATIONS = YES +HAVE_DOT = NO +DOT_NUM_THREADS = 0 +DOT_FONTNAME = Helvetica +DOT_FONTSIZE = 10 +DOT_FONTPATH = +CLASS_GRAPH = YES +COLLABORATION_GRAPH = YES +GROUP_GRAPHS = YES +UML_LOOK = NO +TEMPLATE_RELATIONS = NO +INCLUDE_GRAPH = YES +INCLUDED_BY_GRAPH = YES +CALL_GRAPH = NO +CALLER_GRAPH = NO +GRAPHICAL_HIERARCHY = YES +DIRECTORY_GRAPH = YES +DOT_IMAGE_FORMAT = png +DOT_PATH = +DOTFILE_DIRS = +MSCFILE_DIRS = +DOT_GRAPH_MAX_NODES = 50 +MAX_DOT_GRAPH_DEPTH = 0 +DOT_TRANSPARENT = NO +DOT_MULTI_TARGETS = YES +GENERATE_LEGEND = YES +DOT_CLEANUP = YES diff --git a/NEWS b/NEWS new file mode 100644 index 0000000..7d8650b --- /dev/null +++ b/NEWS @@ -0,0 +1,135 @@ +# 0.9.2 - 2012.03.29 +- Fix disabling and hiding actions (Aurelien Gateau) +- Avoid spamming dbus at startup (Aurelien Gateau) +- Do not print warnings when not necessary (Aurelien Gateau) + +# 0.9.1 - 2012.03.26 +- Add support for "opened" and "closed" events (Aurelien Gateau) +- Add support for icon-data (LP BUG 633339) (Christoph Spielmann) + +# 0.9.0 - 2011.08.30 +- Add support for the "Status" dbusmenu property. Will be used by appmenu-qt for LP BUG 737419 (Aurelien Gateau) +- Collapse multiple separators, get rid of starting and trailing separators (LP BUG 793339) (Aurelien Gateau) + +# 0.8.3 - 2011.06.21 +- If DBusMenuExporter is deleted, delete all DBusMenu instances which were working with it (Aurelien Gateau) +- Only show icons in menu if the platform allows them (Michael Terry) + +# 0.8.2 - 2011.04.12 +- Shortcut handling: Translate "+" into "plus" and "-" into "minus" (LP BUG 712565) (Aurelien Gateau) + +# 0.8.1 - 2011.03.24 +- Added target to build documentation with Doxygen (Aurelien Gateau) + +# 0.8.0 - 2011.02.24 +- Implements version 2 of the dbusmenu protocol (Aurelien Gateau) +- Merged support for KMenu titles (Aurelien Gateau) + +# 0.7.0 - 2011.13.01 +- Switched DBus domain from org.ayatana to com.canonical (Aurelien Gateau) + +# 0.6.6 - 2010.12.08 +- Properly increase version numbers (Aurelien Gateau) + +# 0.6.5 - 2010.12.07 +- Avoid false warnings (Aurelien Gateau) +- Make sure we don't track actions twice (KDE BUG 254066) (Aurelien Gateau) +- CMake-parser-friendly of dbusmenu_version.h (Aurelien Gateau) + +# 0.6.4 - 2010.09.23 +- Trigger action asynchronously when the "clicked" event is received (LP BUG 643393) (Aurelien Gateau) +- Fixed copyright headers (Aurelien Gateau) + +# 0.6.3 - 2010.09.16 +- Moved to LP (Aurelien Gateau) +- Removed all code which did not belong to Canonical. Hopefully we get this + code back in soon (Aurelien Gateau) + +# 0.6.2 - 2010.09.09 +- Fix some memory leaks (Aurelien Gateau) +- Do not keep dangling pointers to deleted actions (LP BUG 624964) (Aurelien Gateau) +- Updated documentation of iconNameForAction() (Aurelien Gateau) + +# 0.6.1 - 2010.09.02 +- Fix some memory leaks (Aurelien Gateau) + +# 0.6.0 - 2010.08.19 +- Added the DBusMenuImporter::actionActivationRequested(QAction*) signal (Aurelien Gateau) +- Fix hardcoded libdir in pkgconfig file (LP BUG 610633) (oneforall) + +# 0.5.2 - 2010.08.05 +- Fix implementation of GetGroupProperties() (Aurelien Gateau) +- Fix detection of QIcon::name() with gold (Aurelien Gateau) + +# 0.5.1 - 2010.07.01 +- Add support for KMenu titles (Christoph Feck) + +# 0.5.0 - 2010.06.28 +- Queue calls to refresh() because we may be spammed with many LayoutUpdated() + signals at once (Aurelien Gateau) +- Turned DBusMenuImporter::updateMenu() into a slot (Aurelien Gateau) + +# 0.4.0 - 2010.06.24 +- Introduce a dbusmenu_version.h file (Aurelien Gateau) +- Introduce updateMenu() and menuUpdated(), deprecate menuReadyToBeShown() (Aurelien Gateau) +- Better build check for QIcon::name() (LP BUG 597975) (Aurelien Gateau) + +# 0.3.5 - 2010.06.17 +- Rework the way menuReadyToBeShown() is emitted (Aurelien Gateau) +- Queue LayoutUpdated() signal to avoid emitting it too often (Aurelien Gateau) +- Increase timeouts: prefer slow but complete menus to fast but incomplete (Aurelien Gateau) +- Use QIcon::name() to return the icon name, when built with Qt 4.7 (Aurelien Gateau) +- Correctly handle non-exclusive action groups (Aurelien Gateau) + +# 0.3.4 - 2010.06.10 +- Fixed KDE bug #237156 (Aurelien Gateau) +- Added support for shortcuts (Aurelien Gateau) +- Make the connection to LayoutUpdated() work :/ (Aurelien Gateau) + +# 0.3.3 - 2010.05.19 +- Introduce a qt minimum version. Qt 4.5 doesn't work. (Michael Jansen) +- Use the FindQjson.cmake file made by pinotree for chokoq because it works. + The old one didn't (for me). (Michael Jansen) +- Refresh after LayoutUpdated signal (Marco Martin) +- Test items added to an existing menu are properly imported (Aurelien Gateau) +- Allow notification of the menu being filled, don't call aboutToShow more than + once per actual menu showing (Aaron Seigo) +- Win32 fixes from Ralf Habacker (Patrick Spendrin) +- Added option to disable tests (Alex Elsayed) + +# 0.3.2 - 2010.04.02 +- Fix some weird positioning of menus and submenus. +- Handle ItemPropertyUpdated() signal. +- Correctly handle properties which are not part of the returned property map + because they are set to their default value. +- Export "visible" property of action. + +# 0.3.1 - 2010.04.01 +- Updated to the latest protocol change: 0 is no longer the default value of + the "toggle-state" property. +- Make it build without QJson again. + +# 0.3.0 - 2010.03.09 +- Moved the DBus side of DBusMenuExporter to a separate class, hiding it from + the outside world. +- Cleaned the API of DBusMenuExporter and DBusMenuImporter. +- Implemented AboutToShow method from the DBusMenu spec. + +# 0.2.2 - 2010.02.17 +- Fixed crash if action is removed from menu after exporter is deleted + (LP BUG 521011). +- Introduced a Qt equivalent of the test application used by dbusmenu-bench in + libdbusmenu-glib. +- Added distcheck target. + +# 0.2.1 - 2010.02.04 +- Export KDE titles as disabled standard items. +- Added temporary workaround to get more dynamic menus to work on GNOME. + +# 0.2.0 - 2010.02.03 +- Make it possible to define the object-path used by DBusMenuExporter. +- Unit tests. +- Follow new DBusMenu spec. + +# 0.1.0 - 2010.01.05 +- First release. diff --git a/README b/README new file mode 100644 index 0000000..ebf157b --- /dev/null +++ b/README @@ -0,0 +1,31 @@ +# Summary + +This library provides a Qt implementation of the DBusMenu protocol. + +The DBusMenu protocol makes it possible for applications to export and import +their menus over DBus. + +# Author + +Canonical DX Team +Maintainer: Renato Filho +Former maintainer: Aurélien Gâteau + +# Documentation + +By default documentation is generated with Doxygen. You can disable +documentation generation by passing -DWITH_DOC=OFF to cmake. + +# Links + +## Source code, bugtracker and tarball hosts + +https://launchpad.net/libdbusmenu-qt + +## KDE developers mirror + +http://gitorious.org/dbusmenu/dbusmenu-qt + +## Spec + +http://people.canonical.com/~agateau/dbusmenu/spec/index.html diff --git a/RELEASE_CHECK_LIST b/RELEASE_CHECK_LIST new file mode 100644 index 0000000..26ff447 --- /dev/null +++ b/RELEASE_CHECK_LIST @@ -0,0 +1,18 @@ +- Verify copy is clean and up to date + bzr st + bzr pull +- Update NEWS + r!bzr log --line -r tag:x.y.z-1.. +- Bump version number in CMakeLists.txt +- Bump library version number in CMakeLists.txt +- Commit +- Create tarball +- Unpack tarball, build and run tests +- Test with KDE trunk +- If ok, create tag + tag=x.y.z + bzr tag $tag +- Push + bzr push +- Upload tarball + lp-project-upload libdbusmenu-qt $tag libdbusmenu-qt-$tag.tar.bz2 diff --git a/cmake/modules/FindQJSON.cmake b/cmake/modules/FindQJSON.cmake new file mode 100644 index 0000000..cd007e1 --- /dev/null +++ b/cmake/modules/FindQJSON.cmake @@ -0,0 +1,44 @@ +# Find QJSON - JSON handling library for Qt +# +# This module defines +# QJSON_FOUND - whether the qsjon library was found +# QJSON_LIBRARIES - the qjson library +# QJSON_INCLUDE_DIR - the include path of the qjson library +# + +if (QJSON_INCLUDE_DIR AND QJSON_LIBRARIES) + + # Already in cache + set (QJSON_FOUND TRUE) + +else (QJSON_INCLUDE_DIR AND QJSON_LIBRARIES) + + if (NOT WIN32) + # use pkg-config to get the values of QJSON_INCLUDE_DIRS + # and QJSON_LIBRARY_DIRS to add as hints to the find commands. + include (FindPkgConfig) + pkg_check_modules (PC_QJSON QJson>=0.5) + endif (NOT WIN32) + + find_library (QJSON_LIBRARIES + NAMES + qjson + PATHS + ${PC_QJSON_LIBRARY_DIRS} + ${LIB_INSTALL_DIR} + ${KDE4_LIB_DIR} + ) + + find_path (QJSON_INCLUDE_DIR + NAMES + qjson/parser.h + PATHS + ${PC_QJSON_INCLUDE_DIRS} + ${INCLUDE_INSTALL_DIR} + ${KDE4_INCLUDE_DIR} + ) + + include(FindPackageHandleStandardArgs) + find_package_handle_standard_args(QJSON DEFAULT_MSG QJSON_LIBRARIES QJSON_INCLUDE_DIR) + +endif (QJSON_INCLUDE_DIR AND QJSON_LIBRARIES) diff --git a/dbusmenu-qt-config.cmake.in b/dbusmenu-qt-config.cmake.in new file mode 100644 index 0000000..7b2b762 --- /dev/null +++ b/dbusmenu-qt-config.cmake.in @@ -0,0 +1,5 @@ +@PACKAGE_INIT@ + +include("${CMAKE_CURRENT_LIST_DIR}/dbusmenu-@QT_SUFFIX@-targets.cmake") + +set_and_check(dbusmenu-@QT_SUFFIX@_INCLUDE_DIRS "@PACKAGE_INCLUDE_INSTALL_DIR@") diff --git a/dbusmenu-qt.pc.in b/dbusmenu-qt.pc.in new file mode 100644 index 0000000..a5e2a25 --- /dev/null +++ b/dbusmenu-qt.pc.in @@ -0,0 +1,10 @@ +prefix=@CMAKE_INSTALL_PREFIX@ +exec_prefix=@CMAKE_INSTALL_PREFIX@ +libdir=@CMAKE_INSTALL_PREFIX@/lib +includedir=@CMAKE_INSTALL_PREFIX@/include/dbusmenu-@QT_SUFFIX@ + +Name: libdbusmenu-@QT_SUFFIX@ +Description: Qt implementation of dbusmenu spec +Version: @dbusmenu_qt_VERSION@ +Libs: -L${libdir} -ldbusmenu-@QT_SUFFIX@ +Cflags: -I${includedir} diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt new file mode 100644 index 0000000..2c4a4e8 --- /dev/null +++ b/src/CMakeLists.txt @@ -0,0 +1,127 @@ +include(CheckCXXSourceCompiles) + +check_cxx_compiler_flag(-Wall __DBUSMENU_HAVE_W_ALL) +if (__DBUSMENU_HAVE_W_ALL) + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall") +endif (__DBUSMENU_HAVE_W_ALL) + +# Check some compiler flags +check_cxx_compiler_flag(-fvisibility=hidden __DBUSMENU_HAVE_GCC_VISIBILITY) +if (__DBUSMENU_HAVE_GCC_VISIBILITY AND NOT WIN32) + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fvisibility=hidden") +endif (__DBUSMENU_HAVE_GCC_VISIBILITY AND NOT WIN32) + +check_cxx_compiler_flag(-Woverloaded-virtual __DBUSMENU_HAVE_W_OVERLOADED_VIRTUAL) +if (__DBUSMENU_HAVE_W_OVERLOADED_VIRTUAL) + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Woverloaded-virtual") +endif (__DBUSMENU_HAVE_W_OVERLOADED_VIRTUAL) + +check_cxx_compiler_flag(-Wall __DBUSMENU_HAVE_W_ALL) +if (__DBUSMENU_HAVE_W_ALL) + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall") +endif (__DBUSMENU_HAVE_W_ALL) + +check_cxx_compiler_flag(-std=c++11 __DBUSMENU_HAVE_CXX11) +if (__DBUSMENU_HAVE_CXX11) + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11") +endif (__DBUSMENU_HAVE_CXX11) + +# Check whether QIcon::name() exists. It was added in late Qt 4.7 cycle, and is +# not present in betas. + +if (NOT USE_QT5) + set(CMAKE_REQUIRED_INCLUDES "${QT_INCLUDE_DIR}") + set(CMAKE_REQUIRED_LIBRARIES "${QT_QTGUI_LIBRARIES};${QT_QTCORE_LIBRARIES}") +else() + set(CMAKE_REQUIRED_INCLUDES "${Qt5Gui_INCLUDE_DIRS};${Qt5Core_INCLUDE_DIRS}") + set(CMAKE_REQUIRED_LIBRARIES "${Qt5Gui_LIBRARIES};${Qt5Core_LIBRARIES}") +endif() +check_cxx_source_compiles(" +#include +int main() { + QIcon icon; + icon.name(); + return 0; +} +" HAVE_QICON_NAME) +if (NOT HAVE_QICON_NAME) + message(STATUS "QIcon::name() does not exist, DBusMenuExporter will not export icon names by itself") +endif() +configure_file(dbusmenu_config.h.in ${CMAKE_CURRENT_BINARY_DIR}/dbusmenu_config.h @ONLY) + +set(dbusmenu_qt_SRCS + dbusmenu_p.cpp + dbusmenuexporter.cpp + dbusmenuexporterdbus_p.cpp + dbusmenuimporter.cpp + dbusmenutypes_p.cpp + dbusmenushortcut_p.cpp + utils.cpp + ) + +include_directories( + ${CMAKE_SOURCE_DIR}/src + ${CMAKE_BINARY_DIR}/src + ) + +if (NOT USE_QT5) + qt4_automoc(${dbusmenu_qt_SRCS}) + qt4_add_dbus_adaptor(dbusmenu_qt_SRCS + ${CMAKE_CURRENT_SOURCE_DIR}/com.canonical.dbusmenu.xml + ${CMAKE_CURRENT_SOURCE_DIR}/dbusmenuexporterdbus_p.h DBusMenuExporterDBus + ) +else() + qt5_add_dbus_adaptor(dbusmenu_qt_SRCS + ${CMAKE_CURRENT_SOURCE_DIR}/com.canonical.dbusmenu.xml + ${CMAKE_CURRENT_SOURCE_DIR}/dbusmenuexporterdbus_p.h DBusMenuExporterDBus + ) +endif() + +configure_file(dbusmenu_version.h.in + ${CMAKE_CURRENT_BINARY_DIR}/dbusmenu_version.h + ) + +add_library(dbusmenu-${QT_SUFFIX} SHARED ${dbusmenu_qt_SRCS}) +set_target_properties(dbusmenu-${QT_SUFFIX} PROPERTIES + VERSION ${dbusmenu_qt_lib_VERSION} + SOVERSION ${dbusmenu_qt_lib_SOVERSION} + ) + + +if (NOT USE_QT5) + target_link_libraries(dbusmenu-${QT_SUFFIX} + ${QT_QTGUI_LIBRARIES} + ${QT_QTDBUS_LIBRARIES} + ${QT_QTCORE_LIBRARIES} + ) +else() + target_link_libraries(dbusmenu-${QT_SUFFIX} + ${Qt5Gui_LIBRARIES} + ${Qt5Core_LIBRARIES} + ${Qt5DBus_LIBRARIES} + ${Qt5Widgets_LIBRARIES} + ) +endif() + +# Make sure linking to the target adds dbusmenu-qt install directory +target_include_directories(dbusmenu-${QT_SUFFIX} + INTERFACE "$") + +install(TARGETS dbusmenu-${QT_SUFFIX} + EXPORT dbusmenu-${QT_SUFFIX}-targets + LIBRARY DESTINATION ${LIB_DESTINATION} + RUNTIME DESTINATION bin + ) + +install(EXPORT dbusmenu-${QT_SUFFIX}-targets + DESTINATION ${CMAKECONFIG_INSTALL_DIR}) + +install(DIRECTORY . + DESTINATION ${INCLUDE_INSTALL_DIR} + FILES_MATCHING PATTERN "*.h" + PATTERN "*_p.h" EXCLUDE + ) + +install(FILES ${CMAKE_CURRENT_BINARY_DIR}/dbusmenu_version.h + DESTINATION ${INCLUDE_INSTALL_DIR} + ) diff --git a/src/com.canonical.dbusmenu.xml b/src/com.canonical.dbusmenu.xml new file mode 100644 index 0000000..b04afa6 --- /dev/null +++ b/src/com.canonical.dbusmenu.xml @@ -0,0 +1,365 @@ + + + + + + + + Name + Type + Description + Default Value + + + type + String + Can be one of: + - "standard": an item which can be clicked to trigger an action or + show another menu + - "separator": a separator + + Vendor specific types can be added by prefixing them with + "x--". + + "standard" + + + label + string + Text of the item, except that: + -# two consecutive underscore characters "__" are displayed as a + single underscore, + -# any remaining underscore characters are not displayed at all, + -# the first of those remaining underscore characters (unless it is + the last character in the string) indicates that the following + character is the access key. + + "" + + + enabled + boolean + Whether the item can be activated or not. + true + + + visible + boolean + True if the item is visible in the menu. + true + + + icon-name + string + Icon name of the item, following the freedesktop.org icon spec. + "" + + + icon-data + binary + PNG data of the icon. + Empty + + + shortcut + array of arrays of strings + The shortcut of the item. Each array represents the key press + in the list of keypresses. Each list of strings contains a list of + modifiers and then the key that is used. The modifier strings + allowed are: "Control", "Alt", "Shift" and "Super". + + - A simple shortcut like Ctrl+S is represented as: + [["Control", "S"]] + - A complex shortcut like Ctrl+Q, Alt+X is represented as: + [["Control", "Q"], ["Alt", "X"]] + Empty + + + toggle-type + string + + If the item can be toggled, this property should be set to: + - "checkmark": Item is an independent togglable item + - "radio": Item is part of a group where only one item can be + toggled at a time + - "": Item cannot be toggled + + "" + + + toggle-state + int + + Describe the current state of a "togglable" item. Can be one of: + - 0 = off + - 1 = on + - anything else = indeterminate + + Note: + The implementation does not itself handle ensuring that only one + item in a radio group is set to "on", or that a group does not have + "on" and "indeterminate" items simultaneously; maintaining this + policy is up to the toolkit wrappers. + + -1 + + + children-display + string + + If the menu item has children this property should be set to + "submenu". + + "" + + + + Vendor specific properties can be added by prefixing them with + "x--". + ]]> + + + + + Provides the version of the DBusmenu API that this API is + implementing. + + + + + + Tells if the menus are in a normal state or they believe that they + could use some attention. Cases for showing them would be if help + were referring to them or they accessors were being highlighted. + This property can have two values: "normal" in almost all cases and + "notice" when they should have a higher priority to be shown. + + + + + + + + + Provides the layout and propertiers that are attached to the entries + that are in the layout. It only gives the items that are children + of the item that is specified in @a parentId. It will return all of the + properties or specific ones depending of the value in @a propertyNames. + + The format is recursive, where the second 'v' is in the same format + as the original 'a(ia{sv}av)'. Its content depends on the value + of @a recursionDepth. + + + The ID of the parent node for the layout. For + grabbing the layout from the root node use zero. + + + + The amount of levels of recursion to use. This affects the + content of the second variant array. + - -1: deliver all the items under the @a parentId. + - 0: no recursion, the array will be empty. + - n: array will contains items up to 'n' level depth. + + + + + The list of item properties we are + interested in. If there are no entries in the list all of + the properties will be sent. + + + + The revision number of the layout. For matching + with layoutUpdated signals. + + + The layout, as a recursive structure. + + + + + + + + Returns the list of items which are children of @a parentId. + + + + A list of ids that we should be finding the properties + on. If the list is empty, all menu items should be sent. + + + + + The list of item properties we are + interested in. If there are no entries in the list all of + the properties will be sent. + + + + + An array of property values. + An item in this area is represented as a struct following + this format: + @li id unsigned the item id + @li properties map(string => variant) the requested item properties + + + + + + + Get a signal property on a single item. This is not useful if you're + going to implement this interface, it should only be used if you're + debugging via a commandline tool. + + + the id of the item which received the event + + + the name of the property to get + + + the value of the property + + + + + -" + ]]> + + the id of the item which received the event + + + the type of event + + + event-specific data + + + The time that the event occured if available or the time the message was sent if not + + + + + + This is called by the applet to notify the application that it is about + to show the menu under the specified item. + + + + Which menu item represents the parent of the item about to be shown. + + + + + Whether this AboutToShow event should result in the menu being updated. + + + + + + + + + + Triggered when there are lots of property updates across many items + so they all get grouped into a single dbus message. The format is + the ID of the item with a hashtable of names and values for those + properties. + + + + + + + + Triggered by the application to notify display of a layout update, up to + revision + + + The revision of the layout that we're currently on + + + + If the layout update is only of a subtree, this is the + parent item for the entries that have changed. It is zero if + the whole layout should be considered invalid. + + + + + + The server is requesting that all clients displaying this + menu open it to the user. This would be for things like + hotkeys that when the user presses them the menu should + open and display itself to the user. + + + ID of the menu that should be activated + + + The time that the event occured + + + + + + + diff --git a/src/dbusmenu_config.h.in b/src/dbusmenu_config.h.in new file mode 100644 index 0000000..c884fb7 --- /dev/null +++ b/src/dbusmenu_config.h.in @@ -0,0 +1,2 @@ +/* Whether QIcon::name() exists */ +#cmakedefine HAVE_QICON_NAME diff --git a/src/dbusmenu_export.h b/src/dbusmenu_export.h new file mode 100644 index 0000000..62dbfdd --- /dev/null +++ b/src/dbusmenu_export.h @@ -0,0 +1,37 @@ +/* This file is part of the dbusmenu-qt library + Copyright 2010 Canonical + Author: Aurelien Gateau + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License (LGPL) as published by the Free Software Foundation; + either version 2 of the License, or (at your option) any later + version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ +#ifndef DBUSMENU_EXPORT_H +#define DBUSMENU_EXPORT_H + +// Include this file from here to make transition from version 0.3.5 and +// earlier easier (no need to conditionally include a file) +#include + +// Qt +#include + +#ifdef dbusmenu_qt_EXPORTS +#define DBUSMENU_EXPORT Q_DECL_EXPORT +#else +#define DBUSMENU_EXPORT Q_DECL_IMPORT +#endif + +#endif /* DBUSMENU_EXPORT_H */ diff --git a/src/dbusmenu_p.cpp b/src/dbusmenu_p.cpp new file mode 100644 index 0000000..bc84092 --- /dev/null +++ b/src/dbusmenu_p.cpp @@ -0,0 +1,94 @@ +/* This file is part of the dbusmenu-qt library + Copyright 2009 Canonical + Author: Aurelien Gateau + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License (LGPL) as published by the Free Software Foundation; + either version 2 of the License, or (at your option) any later + version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ +#include "dbusmenu_p.h" + +// Qt +#include +#include +#include + +// Local +#include "dbusmenuexporter.h" +#include "dbusmenuexporterprivate_p.h" +#include "debug_p.h" + +DBusMenu::DBusMenu(QMenu *menu, DBusMenuExporter *exporter, int parentId) +: QObject(menu) +, m_exporter(exporter) +, m_parentId(parentId) +{ + menu->installEventFilter(this); + connect(m_exporter, SIGNAL(destroyed(QObject*)), SLOT(deleteMe())); +} + +DBusMenu::~DBusMenu() +{ +} + +bool DBusMenu::eventFilter(QObject *, QEvent *event) +{ + QActionEvent *actionEvent = 0; + switch (event->type()) { + case QEvent::ActionAdded: + case QEvent::ActionChanged: + case QEvent::ActionRemoved: + actionEvent = static_cast(event); + break; + default: + return false; + } + switch (event->type()) { + case QEvent::ActionAdded: + addAction(actionEvent->action()); + break; + case QEvent::ActionChanged: + updateAction(actionEvent->action()); + break; + case QEvent::ActionRemoved: + removeAction(actionEvent->action()); + break; + default: + break; + } + return false; +} + +void DBusMenu::addAction(QAction *action) +{ + m_exporter->d->addAction(action, m_parentId); +} + +void DBusMenu::updateAction(QAction *action) +{ + m_exporter->d->updateAction(action); +} + +void DBusMenu::removeAction(QAction *action) +{ + m_exporter->d->removeAction(action, m_parentId); +} + +void DBusMenu::deleteMe() +{ + delete this; +} + +#include "dbusmenu_p.moc" diff --git a/src/dbusmenu_p.h b/src/dbusmenu_p.h new file mode 100644 index 0000000..8e6d315 --- /dev/null +++ b/src/dbusmenu_p.h @@ -0,0 +1,59 @@ +/* This file is part of the dbusmenu-qt library + Copyright 2009 Canonical + Author: Aurelien Gateau + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License (LGPL) as published by the Free Software Foundation; + either version 2 of the License, or (at your option) any later + version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ +#ifndef DBUSMENU_H +#define DBUSMENU_H + +#include +#include + +class QAction; +class QMenu; + +class DBusMenuExporter; + +/** + * Internal class responsible for tracking changes in a menu and reporting them + * through DBusMenuExporter + * @internal + */ +class DBusMenu : public QObject +{ + Q_OBJECT +public: + DBusMenu(QMenu *menu, DBusMenuExporter *exporter, int parentId); + virtual ~DBusMenu(); + +protected: + virtual bool eventFilter(QObject *obj, QEvent *event); + +private Q_SLOTS: + void deleteMe(); + +private: + void addAction(QAction *action); + void updateAction(QAction *action); + void removeAction(QAction *action); + + DBusMenuExporter* m_exporter; + int m_parentId; +}; + +#endif /* DBUSMENU_H */ diff --git a/src/dbusmenu_version.h.in b/src/dbusmenu_version.h.in new file mode 100644 index 0000000..fddc664 --- /dev/null +++ b/src/dbusmenu_version.h.in @@ -0,0 +1,39 @@ +/* This file is part of the KDE libraries + Copyright 2010 Canonical + Author: Aurelien Gateau + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License (LGPL) as published by the Free Software Foundation; + either version 2 of the License, or (at your option) any later + version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ +#ifndef DBUSMENUQT_VERSION_H +#define DBUSMENUQT_VERSION_H + +#define DBUSMENUQT_VERSION_MAJOR @dbusmenu_qt_VERSION_MAJOR@ +#define DBUSMENUQT_VERSION_MINOR @dbusmenu_qt_VERSION_MINOR@ +#define DBUSMENUQT_VERSION_PATCH @dbusmenu_qt_VERSION_PATCH@ + +#define DBUSMENUQT_MAKE_VERSION(a, b, c) (((a) << 16) | ((b) << 8) | (c)) + +#define DBUSMENUQT_VERSION DBUSMENUQT_MAKE_VERSION( \ + DBUSMENUQT_VERSION_MAJOR, \ + DBUSMENUQT_VERSION_MINOR, \ + DBUSMENUQT_VERSION_PATCH) + +// Use this macro to add code which depends on a minimum version of dbusmenu-qt +#define DBUSMENUQT_IS_VERSION(a, b, c) \ + (DBUSMENUQT_VERSION >= DBUSMENUQT_MAKE_VERSION(a, b, c)) + +#endif /*DBUSMENUQT_VERSION_H */ diff --git a/src/dbusmenuexporter.cpp b/src/dbusmenuexporter.cpp new file mode 100644 index 0000000..f25718d --- /dev/null +++ b/src/dbusmenuexporter.cpp @@ -0,0 +1,506 @@ +/* This file is part of the dbusmenu-qt library + Copyright 2009 Canonical + Author: Aurelien Gateau + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License (LGPL) as published by the Free Software Foundation; + either version 2 of the License, or (at your option) any later + version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ +#include "dbusmenuexporter.h" + +// Qt +#include +#include +#include +#include +#include +#include +#include +#include + +// Local +#include "dbusmenu_config.h" +#include "dbusmenu_p.h" +#include "dbusmenuexporterdbus_p.h" +#include "dbusmenuexporterprivate_p.h" +#include "dbusmenutypes_p.h" +#include "dbusmenushortcut_p.h" +#include "debug_p.h" +#include "utils_p.h" + +static const char *KMENU_TITLE = "kmenu_title"; + +//------------------------------------------------- +// +// DBusMenuExporterPrivate +// +//------------------------------------------------- +int DBusMenuExporterPrivate::idForAction(QAction *action) const +{ + DMRETURN_VALUE_IF_FAIL(action, -1); + return m_idForAction.value(action, -2); +} + +void DBusMenuExporterPrivate::addMenu(QMenu *menu, int parentId) +{ + if (menu->findChild()) { + // This can happen if a menu is removed from its parent and added back + // See KDE bug 254066 + return; + } + new DBusMenu(menu, q, parentId); + Q_FOREACH(QAction *action, menu->actions()) { + addAction(action, parentId); + } +} + +QVariantMap DBusMenuExporterPrivate::propertiesForAction(QAction *action) const +{ + DMRETURN_VALUE_IF_FAIL(action, QVariantMap()); + + if (action->objectName() == KMENU_TITLE) { + // Hack: Support for KDE menu titles in a Qt-only library... + return propertiesForKMenuTitleAction(action); + } else if (action->isSeparator()) { + return propertiesForSeparatorAction(action); + } else { + return propertiesForStandardAction(action); + } +} + +QVariantMap DBusMenuExporterPrivate::propertiesForKMenuTitleAction(QAction *action_) const +{ + QVariantMap map; + // In case the other side does not know about x-kde-title, show a disabled item + map.insert("enabled", false); + map.insert("x-kde-title", true); + + const QWidgetAction *widgetAction = qobject_cast(action_); + DMRETURN_VALUE_IF_FAIL(widgetAction, map); + QToolButton *button = qobject_cast(widgetAction->defaultWidget()); + DMRETURN_VALUE_IF_FAIL(button, map); + QAction *action = button->defaultAction(); + DMRETURN_VALUE_IF_FAIL(action, map); + + map.insert("label", swapMnemonicChar(action->text(), '&', '_')); + insertIconProperty(&map, action); + if (!action->isVisible()) { + map.insert("visible", false); + } + return map; +} + +QVariantMap DBusMenuExporterPrivate::propertiesForSeparatorAction(QAction *action) const +{ + QVariantMap map; + map.insert("type", "separator"); + if (!action->isVisible()) { + map.insert("visible", false); + } + return map; +} + +QVariantMap DBusMenuExporterPrivate::propertiesForStandardAction(QAction *action) const +{ + QVariantMap map; + map.insert("label", swapMnemonicChar(action->text(), '&', '_')); + if (!action->isEnabled()) { + map.insert("enabled", false); + } + if (!action->isVisible()) { + map.insert("visible", false); + } + if (action->menu()) { + map.insert("children-display", "submenu"); + } + if (action->isCheckable()) { + bool exclusive = action->actionGroup() && action->actionGroup()->isExclusive(); + map.insert("toggle-type", exclusive ? "radio" : "checkmark"); + map.insert("toggle-state", action->isChecked() ? 1 : 0); + } + insertIconProperty(&map, action); + QKeySequence keySequence = action->shortcut(); + if (!keySequence.isEmpty()) { + DBusMenuShortcut shortcut = DBusMenuShortcut::fromKeySequence(keySequence); + map.insert("shortcut", QVariant::fromValue(shortcut)); + } + return map; +} + +QMenu *DBusMenuExporterPrivate::menuForId(int id) const +{ + if (id == 0) { + return m_rootMenu; + } + QAction *action = m_actionForId.value(id); + // Action may not be in m_actionForId if it has been deleted between the + // time it was announced by the exporter and the time the importer asks for + // it. + return action ? action->menu() : 0; +} + +void DBusMenuExporterPrivate::fillLayoutItem(DBusMenuLayoutItem *item, QMenu *menu, int id, int depth, const QStringList &propertyNames) +{ + item->id = id; + item->properties = m_dbusObject->getProperties(id, propertyNames); + + if (depth != 0 && menu) { + Q_FOREACH(QAction *action, menu->actions()) { + int actionId = m_idForAction.value(action, -1); + if (actionId == -1) { + DMWARNING << "No id for action"; + continue; + } + + DBusMenuLayoutItem child; + fillLayoutItem(&child, action->menu(), actionId, depth - 1, propertyNames); + item->children << child; + } + } +} + +void DBusMenuExporterPrivate::updateAction(QAction *action) +{ + int id = idForAction(action); + if (m_itemUpdatedIds.contains(id)) { + return; + } + m_itemUpdatedIds << id; + m_itemUpdatedTimer->start(); +} + +void DBusMenuExporterPrivate::addAction(QAction *action, int parentId) +{ + int id = m_idForAction.value(action, -1); + if (id != -1) { + DMWARNING << "Already tracking action" << action->text() << "under id" << id; + return; + } + QVariantMap map = propertiesForAction(action); + id = m_nextId++; + QObject::connect(action, SIGNAL(destroyed(QObject*)), q, SLOT(slotActionDestroyed(QObject*))); + m_actionForId.insert(id, action); + m_idForAction.insert(action, id); + m_actionProperties.insert(action, map); + if (action->menu()) { + addMenu(action->menu(), id); + } + ++m_revision; + emitLayoutUpdated(parentId); +} + +/** + * IMPORTANT: action might have already been destroyed when this method is + * called, so don't dereference the pointer (it is a QObject to avoid being + * tempted to dereference) + */ +void DBusMenuExporterPrivate::removeActionInternal(QObject *object) +{ + QAction* action = static_cast(object); + m_actionProperties.remove(action); + int id = m_idForAction.take(action); + m_actionForId.remove(id); +} + +void DBusMenuExporterPrivate::removeAction(QAction *action, int parentId) +{ + removeActionInternal(action); + QObject::disconnect(action, SIGNAL(destroyed(QObject*)), q, SLOT(slotActionDestroyed(QObject*))); + ++m_revision; + emitLayoutUpdated(parentId); +} + +void DBusMenuExporterPrivate::emitLayoutUpdated(int id) +{ + if (m_layoutUpdatedIds.contains(id)) { + return; + } + m_layoutUpdatedIds << id; + m_layoutUpdatedTimer->start(); +} + +void DBusMenuExporterPrivate::insertIconProperty(QVariantMap *map, QAction *action) const +{ + // provide the icon name for per-theme lookups + const QString iconName = q->iconNameForAction(action); + if (!iconName.isEmpty()) { + map->insert("icon-name", iconName); + } + + // provide the serialized icon data in case the icon + // is unnamed or the name isn't supported by the theme + const QIcon icon = action->icon(); + if (!icon.isNull()) { + QBuffer buffer; + icon.pixmap(16).save(&buffer, "PNG"); + map->insert("icon-data", buffer.data()); + } +} + +static void collapseSeparator(QAction* action) +{ + action->setVisible(false); +} + +// Unless the separatorsCollapsible property is set to false, Qt will get rid +// of separators at the beginning and at the end of menus as well as collapse +// multiple separators in the middle. For example, a menu like this: +// +// --- +// Open +// --- +// --- +// Quit +// --- +// +// is displayed like this: +// +// Open +// --- +// Quit +// +// We fake this by setting separators invisible before exporting them. +// +// cf. https://bugs.launchpad.net/libdbusmenu-qt/+bug/793339 +void DBusMenuExporterPrivate::collapseSeparators(QMenu* menu) +{ + QList actions = menu->actions(); + if (actions.isEmpty()) { + return; + } + + QList::Iterator it, begin = actions.begin(), end = actions.end(); + + // Get rid of separators at end + it = end - 1; + for (; it != begin; --it) { + if ((*it)->isSeparator()) { + collapseSeparator(*it); + } else { + break; + } + } + // end now points after the last visible entry + end = it + 1; + it = begin; + + // Get rid of separators at beginnning + for (; it != end; ++it) { + if ((*it)->isSeparator()) { + collapseSeparator(*it); + } else { + break; + } + } + + // Collapse separators in between + bool previousWasSeparator = false; + for (; it != end; ++it) { + QAction* action = *it; + if (action->isSeparator()) { + if (previousWasSeparator) { + collapseSeparator(action); + } else { + previousWasSeparator = true; + } + } else { + previousWasSeparator = false; + } + } +} + +//------------------------------------------------- +// +// DBusMenuExporter +// +//------------------------------------------------- +DBusMenuExporter::DBusMenuExporter(const QString &objectPath, QMenu *menu, const QDBusConnection &_connection) +: QObject(menu) +, d(new DBusMenuExporterPrivate) +{ + d->q = this; + d->m_objectPath = objectPath; + d->m_rootMenu = menu; + d->m_nextId = 1; + d->m_revision = 1; + d->m_emittedLayoutUpdatedOnce = false; + d->m_itemUpdatedTimer = new QTimer(this); + d->m_layoutUpdatedTimer = new QTimer(this); + d->m_dbusObject = new DBusMenuExporterDBus(this); + + d->addMenu(d->m_rootMenu, 0); + + d->m_itemUpdatedTimer->setInterval(0); + d->m_itemUpdatedTimer->setSingleShot(true); + connect(d->m_itemUpdatedTimer, SIGNAL(timeout()), SLOT(doUpdateActions())); + + d->m_layoutUpdatedTimer->setInterval(0); + d->m_layoutUpdatedTimer->setSingleShot(true); + connect(d->m_layoutUpdatedTimer, SIGNAL(timeout()), SLOT(doEmitLayoutUpdated())); + + QDBusConnection connection(_connection); + connection.registerObject(objectPath, d->m_dbusObject, QDBusConnection::ExportAllContents); +} + +DBusMenuExporter::~DBusMenuExporter() +{ + delete d; +} + +void DBusMenuExporter::doUpdateActions() +{ + if (d->m_itemUpdatedIds.isEmpty()) { + return; + } + DBusMenuItemList updatedList; + DBusMenuItemKeysList removedList; + + Q_FOREACH(int id, d->m_itemUpdatedIds) { + QAction *action = d->m_actionForId.value(id); + if (!action) { + // Action does not exist anymore + continue; + } + + QVariantMap& oldProperties = d->m_actionProperties[action]; + QVariantMap newProperties = d->propertiesForAction(action); + QVariantMap updatedProperties; + QStringList removedProperties; + + // Find updated and removed properties + QVariantMap::ConstIterator newEnd = newProperties.constEnd(); + + QVariantMap::ConstIterator + oldIt = oldProperties.constBegin(), + oldEnd = oldProperties.constEnd(); + for(; oldIt != oldEnd; ++oldIt) { + QString key = oldIt.key(); + QVariantMap::ConstIterator newIt = newProperties.constFind(key); + if (newIt != newEnd) { + if (newIt.value() != oldIt.value()) { + updatedProperties.insert(key, newIt.value()); + } + } else { + removedProperties << key; + } + } + + // Find new properties (treat them as updated properties) + QVariantMap::ConstIterator newIt = newProperties.constBegin(); + for (; newIt != newEnd; ++newIt) { + QString key = newIt.key(); + oldIt = oldProperties.constFind(key); + if (oldIt == oldEnd) { + updatedProperties.insert(key, newIt.value()); + } + } + + // Update our data (oldProperties is a reference) + oldProperties = newProperties; + QMenu *menu = action->menu(); + if (menu) { + d->addMenu(menu, id); + } + + if (!updatedProperties.isEmpty()) { + DBusMenuItem item; + item.id = id; + item.properties = updatedProperties; + updatedList << item; + } + if (!removedProperties.isEmpty()) { + DBusMenuItemKeys itemKeys; + itemKeys.id = id; + itemKeys.properties = removedProperties; + removedList << itemKeys; + } + } + d->m_itemUpdatedIds.clear(); + if (!d->m_emittedLayoutUpdatedOnce) { + // No need to tell the world about action changes: nobody knows the + // menu layout so nobody knows about the actions. + // Note: We can't stop in DBusMenuExporterPrivate::addAction(), we + // still need to reach this method because we want our properties to be + // updated, even if we don't announce changes. + return; + } + if (!updatedList.isEmpty() || !removedList.isEmpty()) { + d->m_dbusObject->ItemsPropertiesUpdated(updatedList, removedList); + } +} + +void DBusMenuExporter::doEmitLayoutUpdated() +{ + // Collapse separators for all updated menus + Q_FOREACH(int id, d->m_layoutUpdatedIds) { + QMenu* menu = d->menuForId(id); + if (menu && menu->separatorsCollapsible()) { + d->collapseSeparators(menu); + } + } + + // Tell the world about the update + if (d->m_emittedLayoutUpdatedOnce) { + Q_FOREACH(int id, d->m_layoutUpdatedIds) { + d->m_dbusObject->LayoutUpdated(d->m_revision, id); + } + } else { + // First time we emit LayoutUpdated, no need to emit several layout + // updates, signals the whole layout (id==0) has been updated + d->m_dbusObject->LayoutUpdated(d->m_revision, 0); + d->m_emittedLayoutUpdatedOnce = true; + } + d->m_layoutUpdatedIds.clear(); +} + +QString DBusMenuExporter::iconNameForAction(QAction *action) +{ + DMRETURN_VALUE_IF_FAIL(action, QString()); +#ifdef HAVE_QICON_NAME + QIcon icon = action->icon(); + if (action->isIconVisibleInMenu() && !icon.isNull()) { + return icon.name(); + } else { + return QString(); + } +#else + return QString(); +#endif +} + +void DBusMenuExporter::activateAction(QAction *action) +{ + int id = d->idForAction(action); + DMRETURN_IF_FAIL(id >= 0); + uint timeStamp = QDateTime::currentDateTime().toTime_t(); + d->m_dbusObject->ItemActivationRequested(id, timeStamp); +} + +void DBusMenuExporter::slotActionDestroyed(QObject* object) +{ + d->removeActionInternal(object); +} + +void DBusMenuExporter::setStatus(const QString& status) +{ + d->m_dbusObject->setStatus(status); +} + +QString DBusMenuExporter::status() const +{ + return d->m_dbusObject->status(); +} + +#include "dbusmenuexporter.moc" diff --git a/src/dbusmenuexporter.h b/src/dbusmenuexporter.h new file mode 100644 index 0000000..14950f3 --- /dev/null +++ b/src/dbusmenuexporter.h @@ -0,0 +1,95 @@ +/* This file is part of the dbusmenu-qt library + Copyright 2009 Canonical + Author: Aurelien Gateau + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License (LGPL) as published by the Free Software Foundation; + either version 2 of the License, or (at your option) any later + version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ +#ifndef DBUSMENUEXPORTER_H +#define DBUSMENUEXPORTER_H + +// Qt +#include +#include + +// Local +#include + +class QAction; +class QMenu; + +class DBusMenuExporterPrivate; + +/** + * A DBusMenuExporter instance can serialize a menu over DBus + */ +class DBUSMENU_EXPORT DBusMenuExporter : public QObject +{ + Q_OBJECT +public: + /** + * Creates a DBusMenuExporter exporting menu at the dbus object path + * dbusObjectPath, using the given dbusConnection. + * The instance adds itself to the menu children. + */ + DBusMenuExporter(const QString &dbusObjectPath, QMenu *menu, const QDBusConnection &dbusConnection = QDBusConnection::sessionBus()); + + virtual ~DBusMenuExporter(); + + /** + * Asks the matching DBusMenuImporter to activate @p action. For menus it + * means popup them, for items it means triggering the associated action. + */ + void activateAction(QAction *action); + + /** + * The status of the menu. Can be one of "normal" or "notice". This can be + * used to notify the other side the menu should be made more visible. + * For example, appmenu uses it to tell Unity panel to show/hide the menubar + * when the Alt modifier is pressed/released. + */ + void setStatus(const QString &status); + + /** + * Returns the status of the menu. + * @ref setStatus + */ + QString status() const; + +protected: + /** + * Must extract the icon name for action. This is the name which will + * be used to present the icon over DBus. + * Default implementation returns action->icon().name() when built on Qt + * >= 4.7 and a null string otherwise. + */ + virtual QString iconNameForAction(QAction *action); + +private Q_SLOTS: + void doUpdateActions(); + void doEmitLayoutUpdated(); + void slotActionDestroyed(QObject*); + +private: + Q_DISABLE_COPY(DBusMenuExporter) + DBusMenuExporterPrivate *const d; + + friend class DBusMenuExporterPrivate; + friend class DBusMenuExporterDBus; + friend class DBusMenu; +}; + +#endif /* DBUSMENUEXPORTER_H */ diff --git a/src/dbusmenuexporterdbus_p.cpp b/src/dbusmenuexporterdbus_p.cpp new file mode 100644 index 0000000..006eec6 --- /dev/null +++ b/src/dbusmenuexporterdbus_p.cpp @@ -0,0 +1,186 @@ +/* This file is part of the dbusmenu-qt library + Copyright 2010 Canonical + Author: Aurelien Gateau + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License (LGPL) as published by the Free Software Foundation; + either version 2 of the License, or (at your option) any later + version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ +#include "dbusmenuexporterdbus_p.h" + +// Qt +#include +#include +#include + +// Local +#include "dbusmenuadaptor.h" +#include "dbusmenuexporterprivate_p.h" +#include "dbusmenushortcut_p.h" +#include "debug_p.h" + +static const char *DBUSMENU_INTERFACE = "com.canonical.dbusmenu"; +static const char *FDO_PROPERTIES_INTERFACE = "org.freedesktop.DBus.Properties"; + +DBusMenuExporterDBus::DBusMenuExporterDBus(DBusMenuExporter *exporter) +: QObject(exporter) +, m_exporter(exporter) +, m_status("normal") +{ + DBusMenuTypes_register(); + new DbusmenuAdaptor(this); +} + +uint DBusMenuExporterDBus::GetLayout(int parentId, int recursionDepth, const QStringList &propertyNames, DBusMenuLayoutItem &item) +{ + QMenu *menu = m_exporter->d->menuForId(parentId); + DMRETURN_VALUE_IF_FAIL(menu, 0); + + // Process pending actions, we need them *now* + QMetaObject::invokeMethod(m_exporter, "doUpdateActions"); + m_exporter->d->fillLayoutItem(&item, menu, parentId, recursionDepth, propertyNames); + + return m_exporter->d->m_revision; +} + +void DBusMenuExporterDBus::Event(int id, const QString &eventType, const QDBusVariant &/*data*/, uint /*timestamp*/) +{ + if (eventType == "clicked") { + QAction *action = m_exporter->d->m_actionForId.value(id); + if (!action) { + return; + } + // dbusmenu-glib seems to ignore the Q_NOREPLY and blocks when calling + // Event(), so trigger the action asynchronously + QMetaObject::invokeMethod(action, "trigger", Qt::QueuedConnection); + } else if (eventType == "hovered") { + QMenu *menu = m_exporter->d->menuForId(id); + if (menu) { + QMetaObject::invokeMethod(menu, "aboutToShow"); + } + } +} + +QDBusVariant DBusMenuExporterDBus::GetProperty(int id, const QString &name) +{ + QAction *action = m_exporter->d->m_actionForId.value(id); + DMRETURN_VALUE_IF_FAIL(action, QDBusVariant()); + return QDBusVariant(m_exporter->d->m_actionProperties.value(action).value(name)); +} + +QVariantMap DBusMenuExporterDBus::getProperties(int id, const QStringList &names) const +{ + if (id == 0) { + QVariantMap map; + map.insert("children-display", "submenu"); + return map; + } + QAction *action = m_exporter->d->m_actionForId.value(id); + DMRETURN_VALUE_IF_FAIL(action, QVariantMap()); + QVariantMap all = m_exporter->d->m_actionProperties.value(action); + if (names.isEmpty()) { + return all; + } else { + QVariantMap map; + Q_FOREACH(const QString &name, names) { + QVariant value = all.value(name); + if (value.isValid()) { + map.insert(name, value); + } + } + return map; + } +} + +DBusMenuItemList DBusMenuExporterDBus::GetGroupProperties(const QList &ids, const QStringList &names) +{ + DBusMenuItemList list; + Q_FOREACH(int id, ids) { + DBusMenuItem item; + item.id = id; + item.properties = getProperties(item.id, names); + list << item; + } + return list; +} + +/** + * An helper class for ::AboutToShow, which sets mChanged to true if a menu + * changes after its aboutToShow() signal has been emitted. + */ +class ActionEventFilter: public QObject +{ +public: + ActionEventFilter() + : mChanged(false) + {} + + bool mChanged; +protected: + bool eventFilter(QObject *object, QEvent *event) + { + switch (event->type()) { + case QEvent::ActionAdded: + case QEvent::ActionChanged: + case QEvent::ActionRemoved: + mChanged = true; + // We noticed a change, no need to filter anymore + object->removeEventFilter(this); + break; + default: + break; + } + return false; + } +}; + +bool DBusMenuExporterDBus::AboutToShow(int id) +{ + QMenu *menu = m_exporter->d->menuForId(id); + DMRETURN_VALUE_IF_FAIL(menu, false); + + ActionEventFilter filter; + menu->installEventFilter(&filter); + QMetaObject::invokeMethod(menu, "aboutToShow"); + return filter.mChanged; +} + +void DBusMenuExporterDBus::setStatus(const QString& status) +{ + if (m_status == status) { + return; + } + m_status = status; + + QVariantMap map; + map.insert("Status", QVariant(status)); + + QDBusMessage msg = QDBusMessage::createSignal(m_exporter->d->m_objectPath, FDO_PROPERTIES_INTERFACE, "PropertiesChanged"); + QVariantList args = QVariantList() + << DBUSMENU_INTERFACE + << map + << QStringList() // New properties: none + ; + msg.setArguments(args); + QDBusConnection::sessionBus().send(msg); +} + +QString DBusMenuExporterDBus::status() const +{ + return m_status; +} + + +#include "dbusmenuexporterdbus_p.moc" diff --git a/src/dbusmenuexporterdbus_p.h b/src/dbusmenuexporterdbus_p.h new file mode 100644 index 0000000..95fd00b --- /dev/null +++ b/src/dbusmenuexporterdbus_p.h @@ -0,0 +1,76 @@ +/* This file is part of the dbusmenu-qt library + Copyright 2010 Canonical + Author: Aurelien Gateau + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License (LGPL) as published by the Free Software Foundation; + either version 2 of the License, or (at your option) any later + version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ +#ifndef DBUSMENUEXPORTERDBUS_P_H +#define DBUSMENUEXPORTERDBUS_P_H + +// Local +#include + +// Qt +#include +#include +#include +#include + +class DBusMenuExporter; + +/** + * Internal class implementing the DBus side of DBusMenuExporter + * This avoid exposing the implementation of the DBusMenu spec to the outside + * world. + */ +class DBusMenuExporterDBus : public QObject +{ + Q_OBJECT + Q_CLASSINFO("D-Bus Interface", "com.canonical.dbusmenu") + Q_PROPERTY(uint Version READ Version) + Q_PROPERTY(QString Status READ status) +public: + DBusMenuExporterDBus(DBusMenuExporter *m_exporter); + + uint Version() const { return 2; } + + QString status() const; + void setStatus(const QString &status); + +public Q_SLOTS: + Q_NOREPLY void Event(int id, const QString &eventId, const QDBusVariant &data, uint timestamp); + QDBusVariant GetProperty(int id, const QString &property); + uint GetLayout(int parentId, int recursionDepth, const QStringList &propertyNames, DBusMenuLayoutItem &item); + DBusMenuItemList GetGroupProperties(const QList &ids, const QStringList &propertyNames); + bool AboutToShow(int id); + +Q_SIGNALS: + void ItemsPropertiesUpdated(DBusMenuItemList, DBusMenuItemKeysList); + void LayoutUpdated(uint revision, int parentId); + void ItemActivationRequested(int id, uint timeStamp); + +private: + DBusMenuExporter *m_exporter; + QString m_status; + + friend class DBusMenuExporter; + friend class DBusMenuExporterPrivate; + + QVariantMap getProperties(int id, const QStringList &names) const; +}; + +#endif /* DBUSMENUEXPORTERDBUS_P_H */ diff --git a/src/dbusmenuexporterprivate_p.h b/src/dbusmenuexporterprivate_p.h new file mode 100644 index 0000000..115eed1 --- /dev/null +++ b/src/dbusmenuexporterprivate_p.h @@ -0,0 +1,91 @@ +/* This file is part of the dbusmenu-qt library + Copyright 2010 Canonical + Author: Aurelien Gateau + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License (LGPL) as published by the Free Software Foundation; + either version 2 of the License, or (at your option) any later + version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ +#ifndef DBUSMENUEXPORTERPRIVATE_P_H +#define DBUSMENUEXPORTERPRIVATE_P_H + +// Local +#include "dbusmenuexporter.h" +#include "dbusmenutypes_p.h" + +// Qt +#include +#include +#include +#include + +class QMenu; + +class DBusMenuExporterDBus; + +class DBusMenuExporterPrivate +{ +public: + DBusMenuExporter *q; + + QString m_objectPath; + + DBusMenuExporterDBus *m_dbusObject; + + QMenu *m_rootMenu; + QHash m_actionProperties; + QMap m_actionForId; + QMap m_idForAction; + int m_nextId; + uint m_revision; + bool m_emittedLayoutUpdatedOnce; + + QSet m_itemUpdatedIds; + QTimer *m_itemUpdatedTimer; + + QSet m_layoutUpdatedIds; + QTimer *m_layoutUpdatedTimer; + + int idForAction(QAction *action) const; + void addMenu(QMenu *menu, int parentId); + QVariantMap propertiesForAction(QAction *action) const; + QVariantMap propertiesForKMenuTitleAction(QAction *action_) const; + QVariantMap propertiesForSeparatorAction(QAction *action) const; + QVariantMap propertiesForStandardAction(QAction *action) const; + QMenu *menuForId(int id) const; + void fillLayoutItem(DBusMenuLayoutItem *item, QMenu *menu, int id, int depth, const QStringList &propertyNames); + + void addAction(QAction *action, int parentId); + void updateAction(QAction *action); + void removeAction(QAction *action, int parentId); + /** + * Removes any reference from action in the exporter, but do not notify the + * change outside. This is useful when a submenu is destroyed because we do + * not receive QEvent::ActionRemoved events for its actions. + * IMPORTANT: action might have already been destroyed when this method is + * called, so don't dereference the pointer (it is a QObject to avoid being + * tempted to dereference) + */ + void removeActionInternal(QObject *action); + + void emitLayoutUpdated(int id); + + void insertIconProperty(QVariantMap* map, QAction *action) const; + + void collapseSeparators(QMenu*); +}; + + +#endif /* DBUSMENUEXPORTERPRIVATE_P_H */ diff --git a/src/dbusmenuimporter.cpp b/src/dbusmenuimporter.cpp new file mode 100644 index 0000000..39f5995 --- /dev/null +++ b/src/dbusmenuimporter.cpp @@ -0,0 +1,585 @@ +/* This file is part of the dbusmenu-qt library + Copyright 2009 Canonical + Author: Aurelien Gateau + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License (LGPL) as published by the Free Software Foundation; + either version 2 of the License, or (at your option) any later + version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ +#include "dbusmenuimporter.h" + +// Qt +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +// Local +#include "dbusmenutypes_p.h" +#include "dbusmenushortcut_p.h" +#include "debug_p.h" +#include "utils_p.h" + +//#define BENCHMARK +#ifdef BENCHMARK +#include +static QTime sChrono; +#endif + +static const char *DBUSMENU_INTERFACE = "com.canonical.dbusmenu"; + +static const int ABOUT_TO_SHOW_TIMEOUT = 3000; +static const int REFRESH_TIMEOUT = 4000; + +static const char *DBUSMENU_PROPERTY_ID = "_dbusmenu_id"; +static const char *DBUSMENU_PROPERTY_ICON_NAME = "_dbusmenu_icon_name"; +static const char *DBUSMENU_PROPERTY_ICON_DATA_HASH = "_dbusmenu_icon_data_hash"; + +static QAction *createKdeTitle(QAction *action, QWidget *parent) +{ + QToolButton *titleWidget = new QToolButton(0); + QFont font = titleWidget->font(); + font.setBold(true); + titleWidget->setFont(font); + titleWidget->setIcon(action->icon()); + titleWidget->setText(action->text()); + titleWidget->setDown(true); + titleWidget->setToolButtonStyle(Qt::ToolButtonTextBesideIcon); + + QWidgetAction *titleAction = new QWidgetAction(parent); + titleAction->setDefaultWidget(titleWidget); + return titleAction; +} + +class DBusMenuImporterPrivate +{ +public: + DBusMenuImporter *q; + + QDBusAbstractInterface *m_interface; + QMenu *m_menu; + typedef QMap > ActionForId; + ActionForId m_actionForId; + QSignalMapper m_mapper; + QTimer *m_pendingLayoutUpdateTimer; + + QSet m_idsRefreshedByAboutToShow; + QSet m_pendingLayoutUpdates; + + bool m_mustEmitMenuUpdated; + + DBusMenuImporterType m_type; + + QDBusPendingCallWatcher *refresh(int id) + { + #ifdef BENCHMARK + DMDEBUG << "Starting refresh chrono for id" << id; + sChrono.start(); + #endif + QDBusPendingCall call = m_interface->asyncCall("GetLayout", id, 1, QStringList()); + QDBusPendingCallWatcher *watcher = new QDBusPendingCallWatcher(call, q); + watcher->setProperty(DBUSMENU_PROPERTY_ID, id); + QObject::connect(watcher, SIGNAL(finished(QDBusPendingCallWatcher*)), + q, SLOT(slotGetLayoutFinished(QDBusPendingCallWatcher*))); + + return watcher; + } + + QMenu *createMenu(QWidget *parent) + { + QMenu *menu = q->createMenu(parent); + QObject::connect(menu, SIGNAL(aboutToShow()), + q, SLOT(slotMenuAboutToShow())); + QObject::connect(menu, SIGNAL(aboutToHide()), + q, SLOT(slotMenuAboutToHide())); + return menu; + } + + /** + * Init all the immutable action properties here + * TODO: Document immutable properties? + * + * Note: we remove properties we handle from the map (using QMap::take() + * instead of QMap::value()) to avoid warnings about these properties in + * updateAction() + */ + QAction *createAction(int id, const QVariantMap &_map, QWidget *parent) + { + QVariantMap map = _map; + QAction *action = new QAction(parent); + action->setProperty(DBUSMENU_PROPERTY_ID, id); + + QString type = map.take("type").toString(); + if (type == "separator") { + action->setSeparator(true); + } + + if (map.take("children-display").toString() == "submenu") { + QMenu *menu = createMenu(parent); + action->setMenu(menu); + } + + QString toggleType = map.take("toggle-type").toString(); + if (!toggleType.isEmpty()) { + action->setCheckable(true); + if (toggleType == "radio") { + QActionGroup *group = new QActionGroup(action); + group->addAction(action); + } + } + + bool isKdeTitle = map.take("x-kde-title").toBool(); + updateAction(action, map, map.keys()); + + if (isKdeTitle) { + action = createKdeTitle(action, parent); + } + + return action; + } + + /** + * Update mutable properties of an action. A property may be listed in + * requestedProperties but not in map, this means we should use the default value + * for this property. + * + * @param action the action to update + * @param map holds the property values + * @param requestedProperties which properties has been requested + */ + void updateAction(QAction *action, const QVariantMap &map, const QStringList &requestedProperties) + { + Q_FOREACH(const QString &key, requestedProperties) { + updateActionProperty(action, key, map.value(key)); + } + } + + void updateActionProperty(QAction *action, const QString &key, const QVariant &value) + { + if (key == "label") { + updateActionLabel(action, value); + } else if (key == "enabled") { + updateActionEnabled(action, value); + } else if (key == "toggle-state") { + updateActionChecked(action, value); + } else if (key == "icon-name") { + updateActionIconByName(action, value); + } else if (key == "icon-data") { + updateActionIconByData(action, value); + } else if (key == "visible") { + updateActionVisible(action, value); + } else if (key == "shortcut") { + updateActionShortcut(action, value); + } else if (key == "children-display") { + } else { + DMWARNING << "Unhandled property update" << key; + } + } + + void updateActionLabel(QAction *action, const QVariant &value) + { + QString text = swapMnemonicChar(value.toString(), '_', '&'); + action->setText(text); + } + + void updateActionEnabled(QAction *action, const QVariant &value) + { + action->setEnabled(value.isValid() ? value.toBool(): true); + } + + void updateActionChecked(QAction *action, const QVariant &value) + { + if (action->isCheckable() && value.isValid()) { + action->setChecked(value.toInt() == 1); + } + } + + void updateActionIconByName(QAction *action, const QVariant &value) + { + QString iconName = value.toString(); + QString previous = action->property(DBUSMENU_PROPERTY_ICON_NAME).toString(); + if (previous == iconName) { + return; + } + action->setProperty(DBUSMENU_PROPERTY_ICON_NAME, iconName); + if (iconName.isEmpty()) { + action->setIcon(QIcon()); + return; + } + action->setIcon(q->iconForName(iconName)); + } + + void updateActionIconByData(QAction *action, const QVariant &value) + { + QByteArray data = value.toByteArray(); + uint dataHash = qHash(data); + uint previousDataHash = action->property(DBUSMENU_PROPERTY_ICON_DATA_HASH).toUInt(); + if (previousDataHash == dataHash) { + return; + } + action->setProperty(DBUSMENU_PROPERTY_ICON_DATA_HASH, dataHash); + QPixmap pix; + if (!pix.loadFromData(data)) { + DMWARNING << "Failed to decode icon-data property for action" << action->text(); + action->setIcon(QIcon()); + return; + } + action->setIcon(QIcon(pix)); + } + + void updateActionVisible(QAction *action, const QVariant &value) + { + action->setVisible(value.isValid() ? value.toBool() : true); + } + + void updateActionShortcut(QAction *action, const QVariant &value) + { + QDBusArgument arg = value.value(); + DBusMenuShortcut dmShortcut; + arg >> dmShortcut; + QKeySequence keySequence = dmShortcut.toKeySequence(); + action->setShortcut(keySequence); + } + + QMenu *menuForId(int id) const + { + if (id == 0) { + return q->menu(); + } + QAction *action = m_actionForId.value(id); + if (!action) { + return 0; + } + return action->menu(); + } + + void slotItemsPropertiesUpdated(const DBusMenuItemList &updatedList, const DBusMenuItemKeysList &removedList); + + void sendEvent(int id, const QString &eventId) + { + QVariant empty = QVariant::fromValue(QDBusVariant(QString())); + m_interface->asyncCall("Event", id, eventId, empty, 0u); + } + + bool waitForWatcher(QDBusPendingCallWatcher * _watcher, int maxWait) + { + QPointer watcher(_watcher); + + if(m_type == ASYNCHRONOUS) { + QTimer timer; + timer.setSingleShot(true); + QEventLoop loop; + loop.connect(&timer, SIGNAL(timeout()), SLOT(quit())); + loop.connect(watcher, SIGNAL(finished(QDBusPendingCallWatcher *)), SLOT(quit())); + timer.start(maxWait); + loop.exec(); + timer.stop(); + + if (!watcher) { + // Watcher died. This can happen if importer got deleted while we were + // waiting. See: + // https://bugs.kde.org/show_bug.cgi?id=237156 + return false; + } + + if(!watcher->isFinished()) { + // Timed out + return false; + } + } else { + watcher->waitForFinished(); + } + + if (watcher->isError()) { + DMWARNING << watcher->error().message(); + return false; + } + + return true; + } +}; + +DBusMenuImporter::DBusMenuImporter(const QString &service, const QString &path, QObject *parent) +: DBusMenuImporter(service, path, ASYNCHRONOUS, parent) +{ +} + +DBusMenuImporter::DBusMenuImporter(const QString &service, const QString &path, DBusMenuImporterType type, QObject *parent) +: QObject(parent) +, d(new DBusMenuImporterPrivate) +{ + DBusMenuTypes_register(); + + d->q = this; + d->m_interface = new QDBusInterface(service, path, DBUSMENU_INTERFACE, QDBusConnection::sessionBus(), this); + d->m_menu = 0; + d->m_mustEmitMenuUpdated = false; + + d->m_type = type; + + connect(&d->m_mapper, SIGNAL(mapped(int)), SLOT(sendClickedEvent(int))); + + d->m_pendingLayoutUpdateTimer = new QTimer(this); + d->m_pendingLayoutUpdateTimer->setSingleShot(true); + connect(d->m_pendingLayoutUpdateTimer, SIGNAL(timeout()), SLOT(processPendingLayoutUpdates())); + + // For some reason, using QObject::connect() does not work but + // QDBusConnect::connect() does + QDBusConnection::sessionBus().connect(service, path, DBUSMENU_INTERFACE, "LayoutUpdated", "ui", + this, SLOT(slotLayoutUpdated(uint, int))); + QDBusConnection::sessionBus().connect(service, path, DBUSMENU_INTERFACE, "ItemsPropertiesUpdated", "a(ia{sv})a(ias)", + this, SLOT(slotItemsPropertiesUpdated(DBusMenuItemList, DBusMenuItemKeysList))); + QDBusConnection::sessionBus().connect(service, path, DBUSMENU_INTERFACE, "ItemActivationRequested", "iu", + this, SLOT(slotItemActivationRequested(int, uint))); + + d->refresh(0); +} + +DBusMenuImporter::~DBusMenuImporter() +{ + // Do not use "delete d->m_menu": even if we are being deleted we should + // leave enough time for the menu to finish what it was doing, for example + // if it was being displayed. + d->m_menu->deleteLater(); + delete d; +} + +void DBusMenuImporter::slotLayoutUpdated(uint revision, int parentId) +{ + if (d->m_idsRefreshedByAboutToShow.remove(parentId)) { + return; + } + d->m_pendingLayoutUpdates << parentId; + if (!d->m_pendingLayoutUpdateTimer->isActive()) { + d->m_pendingLayoutUpdateTimer->start(); + } +} + +void DBusMenuImporter::processPendingLayoutUpdates() +{ + QSet ids = d->m_pendingLayoutUpdates; + d->m_pendingLayoutUpdates.clear(); + Q_FOREACH(int id, ids) { + d->refresh(id); + } +} + +QMenu *DBusMenuImporter::menu() const +{ + if (!d->m_menu) { + d->m_menu = d->createMenu(0); + } + return d->m_menu; +} + +void DBusMenuImporterPrivate::slotItemsPropertiesUpdated(const DBusMenuItemList &updatedList, const DBusMenuItemKeysList &removedList) +{ + Q_FOREACH(const DBusMenuItem &item, updatedList) { + QAction *action = m_actionForId.value(item.id); + if (!action) { + // We don't know this action. It probably is in a menu we haven't fetched yet. + continue; + } + + QVariantMap::ConstIterator + it = item.properties.constBegin(), + end = item.properties.constEnd(); + for(; it != end; ++it) { + updateActionProperty(action, it.key(), it.value()); + } + } + + Q_FOREACH(const DBusMenuItemKeys &item, removedList) { + QAction *action = m_actionForId.value(item.id); + if (!action) { + // We don't know this action. It probably is in a menu we haven't fetched yet. + continue; + } + + Q_FOREACH(const QString &key, item.properties) { + updateActionProperty(action, key, QVariant()); + } + } +} + +void DBusMenuImporter::slotItemActivationRequested(int id, uint /*timestamp*/) +{ + QAction *action = d->m_actionForId.value(id); + DMRETURN_IF_FAIL(action); + actionActivationRequested(action); +} + +void DBusMenuImporter::slotGetLayoutFinished(QDBusPendingCallWatcher *watcher) +{ + int parentId = watcher->property(DBUSMENU_PROPERTY_ID).toInt(); + watcher->deleteLater(); + + QDBusPendingReply reply = *watcher; + if (!reply.isValid()) { + DMWARNING << reply.error().message(); + return; + } + + #ifdef BENCHMARK + DMDEBUG << "- items received:" << sChrono.elapsed() << "ms"; + #endif + DBusMenuLayoutItem rootItem = reply.argumentAt<1>(); + + QMenu *menu = d->menuForId(parentId); + if (!menu) { + DMWARNING << "No menu for id" << parentId; + return; + } + + menu->clear(); + + Q_FOREACH(const DBusMenuLayoutItem &dbusMenuItem, rootItem.children) { + QAction *action = d->createAction(dbusMenuItem.id, dbusMenuItem.properties, menu); + DBusMenuImporterPrivate::ActionForId::Iterator it = d->m_actionForId.find(dbusMenuItem.id); + if (it == d->m_actionForId.end()) { + d->m_actionForId.insert(dbusMenuItem.id, action); + } else { + delete *it; + *it = action; + } + menu->addAction(action); + + connect(action, SIGNAL(triggered()), + &d->m_mapper, SLOT(map())); + d->m_mapper.setMapping(action, dbusMenuItem.id); + + if( action->menu() ) + { + d->refresh( dbusMenuItem.id )->waitForFinished(); + } + } + #ifdef BENCHMARK + DMDEBUG << "- Menu filled:" << sChrono.elapsed() << "ms"; + #endif +} + +void DBusMenuImporter::sendClickedEvent(int id) +{ + d->sendEvent(id, QString("clicked")); +} + +void DBusMenuImporter::updateMenu() +{ + d->m_mustEmitMenuUpdated = true; + QMetaObject::invokeMethod(menu(), "aboutToShow"); +} + +void DBusMenuImporter::slotMenuAboutToShow() +{ + QMenu *menu = qobject_cast(sender()); + Q_ASSERT(menu); + + QAction *action = menu->menuAction(); + Q_ASSERT(action); + + int id = action->property(DBUSMENU_PROPERTY_ID).toInt(); + + #ifdef BENCHMARK + QTime time; + time.start(); + #endif + + QDBusPendingCall call = d->m_interface->asyncCall("AboutToShow", id); + QDBusPendingCallWatcher *watcher = new QDBusPendingCallWatcher(call, this); + watcher->setProperty(DBUSMENU_PROPERTY_ID, id); + connect(watcher, SIGNAL(finished(QDBusPendingCallWatcher*)), + SLOT(slotAboutToShowDBusCallFinished(QDBusPendingCallWatcher*))); + + QPointer guard(this); + + if (!d->waitForWatcher(watcher, ABOUT_TO_SHOW_TIMEOUT)) { + DMWARNING << "Application did not answer to AboutToShow() before timeout"; + } + + #ifdef BENCHMARK + DMVAR(time.elapsed()); + #endif + // "this" got deleted during the call to waitForWatcher(), get out + if (!guard) { + return; + } + + if (menu == d->m_menu && d->m_mustEmitMenuUpdated) { + d->m_mustEmitMenuUpdated = false; + menuUpdated(); + } + if (menu == d->m_menu) { + menuReadyToBeShown(); + } + + d->sendEvent(id, QString("opened")); +} + +void DBusMenuImporter::slotAboutToShowDBusCallFinished(QDBusPendingCallWatcher *watcher) +{ + int id = watcher->property(DBUSMENU_PROPERTY_ID).toInt(); + watcher->deleteLater(); + + QDBusPendingReply reply = *watcher; + if (reply.isError()) { + DMWARNING << "Call to AboutToShow() failed:" << reply.error().message(); + return; + } + bool needRefresh = reply.argumentAt<0>(); + + QMenu *menu = d->menuForId(id); + DMRETURN_IF_FAIL(menu); + + if (needRefresh || menu->actions().isEmpty()) { + d->m_idsRefreshedByAboutToShow << id; + QDBusPendingCallWatcher *watcher2 = d->refresh(id); + if (!d->waitForWatcher(watcher2, REFRESH_TIMEOUT)) { + DMWARNING << "Application did not refresh before timeout"; + } + } +} + +void DBusMenuImporter::slotMenuAboutToHide() +{ + QMenu *menu = qobject_cast(sender()); + Q_ASSERT(menu); + + QAction *action = menu->menuAction(); + Q_ASSERT(action); + + int id = action->property(DBUSMENU_PROPERTY_ID).toInt(); + d->sendEvent(id, QString("closed")); +} + +QMenu *DBusMenuImporter::createMenu(QWidget *parent) +{ + return new QMenu(parent); +} + +QIcon DBusMenuImporter::iconForName(const QString &/*name*/) +{ + return QIcon(); +} + +#include "dbusmenuimporter.moc" diff --git a/src/dbusmenuimporter.h b/src/dbusmenuimporter.h new file mode 100644 index 0000000..dc7e773 --- /dev/null +++ b/src/dbusmenuimporter.h @@ -0,0 +1,143 @@ +/* This file is part of the dbusmenu-qt library + Copyright 2009 Canonical + Author: Aurelien Gateau + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License (LGPL) as published by the Free Software Foundation; + either version 2 of the License, or (at your option) any later + version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ +#ifndef DBUSMENUIMPORTER_H +#define DBUSMENUIMPORTER_H + +// Qt +#include + +// Local +#include + +class QAction; +class QDBusAbstractInterface; +class QDBusPendingCallWatcher; +class QDBusVariant; +class QIcon; +class QMenu; + +class DBusMenuImporterPrivate; + +/** + * Determine whether internal method calls should allow the Qt event loop + * to execute or not + */ +enum DBusMenuImporterType { + ASYNCHRONOUS, + SYNCHRONOUS +}; + +/** + * A DBusMenuImporter instance can recreate a menu serialized over DBus by + * DBusMenuExporter + */ +class DBUSMENU_EXPORT DBusMenuImporter : public QObject +{ + Q_OBJECT +public: + /** + * Creates a DBusMenuImporter listening over DBus on service, path + */ + DBusMenuImporter(const QString &service, const QString &path, QObject *parent = 0); + + /** + * Creates a DBusMenuImporter listening over DBus on service, path, with either async + * or sync DBus calls + */ + DBusMenuImporter(const QString &service, const QString &path, DBusMenuImporterType type, QObject *parent = 0); + + virtual ~DBusMenuImporter(); + + /** + * The menu created from listening to the DBusMenuExporter over DBus + */ + QMenu *menu() const; + +public Q_SLOTS: + /** + * Simulates a QMenu::aboutToShow() signal on the menu returned by menu(), + * ensuring it is up to date in case the menu is populated on the fly. It + * is not mandatory to call this method, showing the menu with + * QMenu::popup() or QMenu::exec() will generates an aboutToShow() signal, + * but calling it before ensures the size-hint of the menu is correct when + * it is time to show it, avoiding wrong positioning. + * + * menuUpdated() will be emitted when the menu is ready. + * + * Not that the aboutToShow() signal is only sent to the root menu, not to + * any submenu. + */ + void updateMenu(); + +Q_SIGNALS: + /** + * Emitted after a call to updateMenu(). + * @see updateMenu() + */ + void menuUpdated(); + + /** + * Emitted after every aboutToShow of the root menu. + * This signal is deprecated and only kept to keep compatibility with + * dbusmenu-qt 0.3.x. New code should use updateMenu() and menuUpdated() + * + * @deprecated + */ + void menuReadyToBeShown(); + + /** + * Emitted when the exporter was asked to activate an action + */ + void actionActivationRequested(QAction *); + +protected: + /** + * Must create a menu, may be customized to fit host appearance. + * Default implementation creates a simple QMenu. + */ + virtual QMenu *createMenu(QWidget *parent); + + /** + * Must convert a name into an icon. + * Default implementation returns a null icon. + */ + virtual QIcon iconForName(const QString &); + +private Q_SLOTS: + void sendClickedEvent(int); + void slotMenuAboutToShow(); + void slotMenuAboutToHide(); + void slotAboutToShowDBusCallFinished(QDBusPendingCallWatcher *); + void slotItemActivationRequested(int id, uint timestamp); + void processPendingLayoutUpdates(); + void slotLayoutUpdated(uint revision, int parentId); + void slotGetLayoutFinished(QDBusPendingCallWatcher *); + +private: + Q_DISABLE_COPY(DBusMenuImporter) + DBusMenuImporterPrivate *const d; + friend class DBusMenuImporterPrivate; + + // Use Q_PRIVATE_SLOT to avoid exposing DBusMenuItemList + Q_PRIVATE_SLOT(d, void slotItemsPropertiesUpdated(const DBusMenuItemList &updatedList, const DBusMenuItemKeysList &removedList)) +}; + +#endif /* DBUSMENUIMPORTER_H */ diff --git a/src/dbusmenushortcut_p.cpp b/src/dbusmenushortcut_p.cpp new file mode 100644 index 0000000..29d2e58 --- /dev/null +++ b/src/dbusmenushortcut_p.cpp @@ -0,0 +1,85 @@ +/* This file is part of the dbusmenu-qt library + Copyright 2009 Canonical + Author: Aurelien Gateau + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License (LGPL) as published by the Free Software Foundation; + either version 2 of the License, or (at your option) any later + version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ +#include "dbusmenushortcut_p.h" + +// Qt +#include + +// Local +#include "debug_p.h" + +static const int QT_COLUMN = 0; +static const int DM_COLUMN = 1; + +static void processKeyTokens(QStringList* tokens, int srcCol, int dstCol) +{ + struct Row { + const char* zero; + const char* one; + const char* operator[](int col) const { return col == 0 ? zero : one; } + }; + static const Row table[] = + { {"Meta", "Super"}, + {"Ctrl", "Control"}, + // Special cases for compatibility with libdbusmenu-glib which uses + // "plus" for "+" and "minus" for "-". + // cf https://bugs.launchpad.net/libdbusmenu-qt/+bug/712565 + {"+", "plus"}, + {"-", "minus"}, + {0, 0} + }; + + const Row* ptr = table; + for (; ptr->zero != 0; ++ptr) { + const char* from = (*ptr)[srcCol]; + const char* to = (*ptr)[dstCol]; + tokens->replaceInStrings(from, to); + } +} + +DBusMenuShortcut DBusMenuShortcut::fromKeySequence(const QKeySequence& sequence) +{ + QString string = sequence.toString(); + DBusMenuShortcut shortcut; + QStringList tokens = string.split(", "); + Q_FOREACH(QString token, tokens) { + // Hack: Qt::CTRL | Qt::Key_Plus is turned into the string "Ctrl++", + // but we don't want the call to token.split() to consider the + // second '+' as a separator so we replace it with its final value. + token.replace("++", "+plus"); + QStringList keyTokens = token.split('+'); + processKeyTokens(&keyTokens, QT_COLUMN, DM_COLUMN); + shortcut << keyTokens; + } + return shortcut; +} + +QKeySequence DBusMenuShortcut::toKeySequence() const +{ + QStringList tmp; + Q_FOREACH(const QStringList& keyTokens_, *this) { + QStringList keyTokens = keyTokens_; + processKeyTokens(&keyTokens, DM_COLUMN, QT_COLUMN); + tmp << keyTokens.join(QLatin1String("+")); + } + QString string = tmp.join(QLatin1String(", ")); + return QKeySequence::fromString(string); +} diff --git a/src/dbusmenushortcut_p.h b/src/dbusmenushortcut_p.h new file mode 100644 index 0000000..e26dd8c --- /dev/null +++ b/src/dbusmenushortcut_p.h @@ -0,0 +1,43 @@ +/* This file is part of the dbusmenu-qt library + Copyright 2009 Canonical + Author: Aurelien Gateau + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License (LGPL) as published by the Free Software Foundation; + either version 2 of the License, or (at your option) any later + version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ +#ifndef DBUSMENUSHORTCUT_H +#define DBUSMENUSHORTCUT_H + +// Qt +#include +#include + +// Local +#include + + +class QKeySequence; + +class DBUSMENU_EXPORT DBusMenuShortcut : public QList +{ +public: + QKeySequence toKeySequence() const; + static DBusMenuShortcut fromKeySequence(const QKeySequence&); +}; + +Q_DECLARE_METATYPE(DBusMenuShortcut) + +#endif /* DBUSMENUSHORTCUT_H */ diff --git a/src/dbusmenutypes_p.cpp b/src/dbusmenutypes_p.cpp new file mode 100644 index 0000000..73e68a0 --- /dev/null +++ b/src/dbusmenutypes_p.cpp @@ -0,0 +1,112 @@ +/* This file is part of the dbusmenu-qt library + Copyright 2009 Canonical + Author: Aurelien Gateau + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License (LGPL) as published by the Free Software Foundation; + either version 2 of the License, or (at your option) any later + version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ +#include "dbusmenutypes_p.h" + +// Local +#include +#include + +// Qt +#include +#include + +//// DBusMenuItem +QDBusArgument &operator<<(QDBusArgument &argument, const DBusMenuItem &obj) +{ + argument.beginStructure(); + argument << obj.id << obj.properties; + argument.endStructure(); + return argument; +} + +const QDBusArgument &operator>>(const QDBusArgument &argument, DBusMenuItem &obj) +{ + argument.beginStructure(); + argument >> obj.id >> obj.properties; + argument.endStructure(); + return argument; +} + +//// DBusMenuItemKeys +QDBusArgument &operator<<(QDBusArgument &argument, const DBusMenuItemKeys &obj) +{ + argument.beginStructure(); + argument << obj.id << obj.properties; + argument.endStructure(); + return argument; +} + +const QDBusArgument &operator>>(const QDBusArgument &argument, DBusMenuItemKeys &obj) +{ + argument.beginStructure(); + argument >> obj.id >> obj.properties; + argument.endStructure(); + return argument; +} + +//// DBusMenuLayoutItem +QDBusArgument &operator<<(QDBusArgument &argument, const DBusMenuLayoutItem &obj) +{ + argument.beginStructure(); + argument << obj.id << obj.properties; + argument.beginArray(qMetaTypeId()); + Q_FOREACH(const DBusMenuLayoutItem& child, obj.children) { + argument << QDBusVariant(QVariant::fromValue(child)); + } + argument.endArray(); + argument.endStructure(); + return argument; +} + +const QDBusArgument &operator>>(const QDBusArgument &argument, DBusMenuLayoutItem &obj) +{ + argument.beginStructure(); + argument >> obj.id >> obj.properties; + argument.beginArray(); + while (!argument.atEnd()) { + QDBusVariant dbusVariant; + argument >> dbusVariant; + QDBusArgument childArgument = dbusVariant.variant().value(); + + DBusMenuLayoutItem child; + childArgument >> child; + obj.children.append(child); + } + argument.endArray(); + argument.endStructure(); + return argument; +} + +void DBusMenuTypes_register() +{ + static bool registered = false; + if (registered) { + return; + } + qDBusRegisterMetaType(); + qDBusRegisterMetaType(); + qDBusRegisterMetaType(); + qDBusRegisterMetaType(); + qDBusRegisterMetaType(); + qDBusRegisterMetaType(); + qDBusRegisterMetaType(); + registered = true; +} diff --git a/src/dbusmenutypes_p.h b/src/dbusmenutypes_p.h new file mode 100644 index 0000000..6b4e560 --- /dev/null +++ b/src/dbusmenutypes_p.h @@ -0,0 +1,96 @@ +/* This file is part of the dbusmenu-qt library + Copyright 2009 Canonical + Author: Aurelien Gateau + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License (LGPL) as published by the Free Software Foundation; + either version 2 of the License, or (at your option) any later + version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ +#ifndef DBUSMENUTYPES_P_H +#define DBUSMENUTYPES_P_H + +// Qt +#include +#include +#include + +// Local +#include + +class QDBusArgument; + +//// DBusMenuItem +/** + * Internal struct used to communicate on DBus + */ +struct DBUSMENU_EXPORT DBusMenuItem +{ + int id; + QVariantMap properties; +}; + +Q_DECLARE_METATYPE(DBusMenuItem) + +DBUSMENU_EXPORT QDBusArgument &operator<<(QDBusArgument &argument, const DBusMenuItem &item); +DBUSMENU_EXPORT const QDBusArgument &operator>>(const QDBusArgument &argument, DBusMenuItem &item); + +typedef QList DBusMenuItemList; + +Q_DECLARE_METATYPE(DBusMenuItemList) + + +//// DBusMenuItemKeys +/** + * Represents a list of keys for a menu item + */ +struct DBUSMENU_EXPORT DBusMenuItemKeys +{ + int id; + QStringList properties; +}; + +Q_DECLARE_METATYPE(DBusMenuItemKeys) + +DBUSMENU_EXPORT QDBusArgument &operator<<(QDBusArgument &argument, const DBusMenuItemKeys &); +DBUSMENU_EXPORT const QDBusArgument &operator>>(const QDBusArgument &argument, DBusMenuItemKeys &); + +typedef QList DBusMenuItemKeysList; + +Q_DECLARE_METATYPE(DBusMenuItemKeysList) + +//// DBusMenuLayoutItem +/** + * Represents an item with its children. GetLayout() returns a + * DBusMenuLayoutItemList. + */ +struct DBusMenuLayoutItem; +struct DBUSMENU_EXPORT DBusMenuLayoutItem +{ + int id; + QVariantMap properties; + QList children; +}; + +Q_DECLARE_METATYPE(DBusMenuLayoutItem) + +DBUSMENU_EXPORT QDBusArgument &operator<<(QDBusArgument &argument, const DBusMenuLayoutItem &); +DBUSMENU_EXPORT const QDBusArgument &operator>>(const QDBusArgument &argument, DBusMenuLayoutItem &); + +typedef QList DBusMenuLayoutItemList; + +Q_DECLARE_METATYPE(DBusMenuLayoutItemList) + +void DBusMenuTypes_register(); +#endif /* DBUSMENUTYPES_P_H */ diff --git a/src/debug_p.h b/src/debug_p.h new file mode 100644 index 0000000..bff37bd --- /dev/null +++ b/src/debug_p.h @@ -0,0 +1,48 @@ +/* This file is part of the dbusmenu-qt library + Copyright 2009 Canonical + Author: Aurelien Gateau + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License (LGPL) as published by the Free Software Foundation; + either version 2 of the License, or (at your option) any later + version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ +#ifndef DEBUG_P_H +#define DEBUG_P_H + +#include + +#define _DMBLUE "\033[34m" +#define _DMRED "\033[31m" +#define _DMRESET "\033[0m" +#define _DMTRACE(level, color) (level().nospace() << color << __PRETTY_FUNCTION__ << _DMRESET ":").space() + +// Simple macros to get KDebug like support +#define DMDEBUG _DMTRACE(qDebug, _DMBLUE) +#define DMWARNING _DMTRACE(qWarning, _DMRED) + +// Log a variable name and value +#define DMVAR(var) DMDEBUG << #var ":" << var + +#define DMRETURN_IF_FAIL(cond) if (!(cond)) { \ + DMWARNING << "Condition failed: " #cond; \ + return; \ +} + +#define DMRETURN_VALUE_IF_FAIL(cond, value) if (!(cond)) { \ + DMWARNING << "Condition failed: " #cond; \ + return (value); \ +} + +#endif /* DEBUG_P_H */ diff --git a/src/utils.cpp b/src/utils.cpp new file mode 100644 index 0000000..e0fa004 --- /dev/null +++ b/src/utils.cpp @@ -0,0 +1,64 @@ +/* This file is part of the dbusmenu-qt library + Copyright 2010 Canonical + Author: Aurelien Gateau + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License (LGPL) as published by the Free Software Foundation; + either version 2 of the License, or (at your option) any later + version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ +#include "utils_p.h" + +// Qt +#include + +QString swapMnemonicChar(const QString &in, const char src, const char dst) +{ + QString out; + bool mnemonicFound = false; + + for (int pos = 0; pos < in.length(); ) { + QChar ch = in[pos]; + if (ch == src) { + if (pos == in.length() - 1) { + // 'src' at the end of string, skip it + ++pos; + } else { + if (in[pos + 1] == src) { + // A real 'src' + out += src; + pos += 2; + } else if (!mnemonicFound) { + // We found the mnemonic + mnemonicFound = true; + out += dst; + ++pos; + } else { + // We already have a mnemonic, just skip the char + ++pos; + } + } + } else if (ch == dst) { + // Escape 'dst' + out += dst; + out += dst; + ++pos; + } else { + out += ch; + ++pos; + } + } + + return out; +} diff --git a/src/utils_p.h b/src/utils_p.h new file mode 100644 index 0000000..3f6e888 --- /dev/null +++ b/src/utils_p.h @@ -0,0 +1,31 @@ +/* This file is part of the dbusmenu-qt library + Copyright 2010 Canonical + Author: Aurelien Gateau + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License (LGPL) as published by the Free Software Foundation; + either version 2 of the License, or (at your option) any later + version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ +#ifndef UTILS_P_H +#define UTILS_P_H + +class QString; + +/** + * Swap mnemonic char: Qt uses '&', while dbusmenu uses '_' + */ +QString swapMnemonicChar(const QString &in, const char src, const char dst); + +#endif /* UTILS_P_H */ diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt new file mode 100644 index 0000000..6be27f5 --- /dev/null +++ b/tests/CMakeLists.txt @@ -0,0 +1,124 @@ +if (NOT USE_QT5) + qt4_automoc(slowmenu.cpp) +endif() +add_executable(slowmenu slowmenu.cpp) + +if (NOT USE_QT5) + target_link_libraries(slowmenu + ${QT_QTGUI_LIBRARIES} + ${QT_QTDBUS_LIBRARIES} + ${QT_QTCORE_LIBRARIES} + dbusmenu-qt + ) + + set(test_LIBRARIES + ${QT_QTGUI_LIBRARY} + ${QT_QTCORE_LIBRARY} + ${QT_QTDBUS_LIBRARY} + ${QT_QTTEST_LIBRARY} + dbusmenu-qt + ) + + include_directories( + ${CMAKE_CURRENT_SOURCE_DIR}/../src + ${CMAKE_CURRENT_BINARY_DIR}/../src + ${CMAKE_CURRENT_BINARY_DIR} + ${QT_QTTEST_INCLUDE_DIR} + ${QT_QTDBUS_INCLUDE_DIR} + ) +else() + find_package(Qt5Test REQUIRED) + + target_link_libraries(slowmenu + ${Qt5Gui_LIBRARIES} + ${Qt5Core_LIBRARIES} + ${Qt5DBus_LIBRARIES} + dbusmenu-qt5 + ) + + set(test_LIBRARIES + ${Qt5Gui_LIBRARIES} + ${Qt5Core_LIBRARIES} + ${Qt5DBus_LIBRARIES} + ${Qt5Test_LIBRARIES} + dbusmenu-qt5 + ) + + include_directories( + ${CMAKE_CURRENT_SOURCE_DIR}/../src + ${CMAKE_CURRENT_BINARY_DIR}/../src + ${CMAKE_CURRENT_BINARY_DIR} + ${Qt5Test_INCLUDE_DIRS} + ${Qt5DBus_INCLUDE_DIRS} + ) +endif() + +# Macros to create "check" target +set(_test_executable_list "") + +macro(add_test_executable _executable) + add_test(${_executable} ${_executable}) + set(_test_executable_list "${_test_executable_list};${_executable}") + add_executable(${_executable} ${ARGN}) +endmacro(add_test_executable) + +# Call this at the end +macro(create_check_target) + add_custom_target(check COMMAND ${CMAKE_CTEST_COMMAND} --verbose + DEPENDS ${_test_executable_list}) +endmacro(create_check_target) + +enable_testing() + + +# dbusmenuexportertest +set(dbusmenuexportertest_SRCS + dbusmenuexportertest.cpp + testutils.cpp + ) + +if (NOT USE_QT5) + qt4_automoc(${dbusmenuexportertest_SRCS}) +endif() + +add_test_executable(dbusmenuexportertest ${dbusmenuexportertest_SRCS}) + +target_link_libraries(dbusmenuexportertest + ${test_LIBRARIES} + ) + + +# dbusmenuimportertest +set(dbusmenuimportertest_SRCS + dbusmenuimportertest.cpp + testutils.cpp + ) + +if (NOT USE_QT5) + qt4_automoc(${dbusmenuimportertest_SRCS}) +endif() + +add_test_executable(dbusmenuimportertest ${dbusmenuimportertest_SRCS}) + +target_link_libraries(dbusmenuimportertest + ${test_LIBRARIES} + ) + + +# dbusmenushortcuttest +set(dbusmenushortcuttest_SRCS + dbusmenushortcuttest.cpp + ) + +if (NOT USE_QT5) + qt4_automoc(${dbusmenushortcuttest_SRCS}) +endif() + +add_test_executable(dbusmenushortcuttest ${dbusmenushortcuttest_SRCS}) + +target_link_libraries(dbusmenushortcuttest + ${test_LIBRARIES} + ) + +# Keep this at the end +create_check_target() diff --git a/tests/dbusmenuexportertest.cpp b/tests/dbusmenuexportertest.cpp new file mode 100644 index 0000000..0d844f7 --- /dev/null +++ b/tests/dbusmenuexportertest.cpp @@ -0,0 +1,815 @@ +/* This file is part of the dbusmenu-qt library + Copyright 2009 Canonical + Author: Aurelien Gateau + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License (LGPL) as published by the Free Software Foundation; + either version 2 of the License, or (at your option) any later + version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ +// Self +#include "dbusmenuexportertest.h" + +// Qt +#include +#include +#include +#include +#include +#include + +// DBusMenuQt +#include +#include +#include +#include + +// Local +#include "testutils.h" + +QTEST_MAIN(DBusMenuExporterTest) + +static const char *TEST_SERVICE = "org.kde.dbusmenu-qt-test"; +static const char *TEST_OBJECT_PATH = "/TestMenuBar"; + +Q_DECLARE_METATYPE(QList) + +static DBusMenuLayoutItemList getChildren(QDBusAbstractInterface* iface, int parentId, const QStringList &propertyNames) +{ + QDBusPendingReply reply = iface->call("GetLayout", parentId, /*recursionDepth=*/ 1, propertyNames); + reply.waitForFinished(); + if (!reply.isValid()) { + qFatal("%s", qPrintable(reply.error().message())); + return DBusMenuLayoutItemList(); + } + + DBusMenuLayoutItem rootItem = reply.argumentAt<1>(); + return rootItem.children; +} + +void DBusMenuExporterTest::init() +{ + QVERIFY(QDBusConnection::sessionBus().registerService(TEST_SERVICE)); + QCoreApplication::setAttribute(Qt::AA_DontShowIconsInMenus, false); +} + +void DBusMenuExporterTest::cleanup() +{ + QVERIFY(QDBusConnection::sessionBus().unregisterService(TEST_SERVICE)); +} + +void DBusMenuExporterTest::testGetSomeProperties_data() +{ + QTest::addColumn("label"); + QTest::addColumn("iconName"); + QTest::addColumn("enabled"); + + QTest::newRow("label only") << "label" << QString() << true; + QTest::newRow("disabled, label only") << "label" << QString() << false; + QTest::newRow("icon name") << "label" << "edit-undo" << true; +} + +void DBusMenuExporterTest::testGetSomeProperties() +{ + QFETCH(QString, label); + QFETCH(QString, iconName); + QFETCH(bool, enabled); + + // Create an exporter for a menu with one action, defined by the test data + QMenu inputMenu; + DBusMenuExporter exporter(TEST_OBJECT_PATH, &inputMenu); + + QAction *action = new QAction(label, &inputMenu); + if (!iconName.isEmpty()) { + QIcon icon = QIcon::fromTheme(iconName); + QVERIFY(!icon.isNull()); + action->setIcon(icon); + } + action->setEnabled(enabled); + inputMenu.addAction(action); + + // Check out exporter is on DBus + QDBusInterface iface(TEST_SERVICE, TEST_OBJECT_PATH); + QVERIFY2(iface.isValid(), qPrintable(iface.lastError().message())); + + // Get exported menu info + QStringList propertyNames = QStringList() << "type" << "enabled" << "label" << "icon-name"; + DBusMenuLayoutItemList list = getChildren(&iface, /*parentId=*/0, propertyNames); + DBusMenuLayoutItem item = list.first(); + QVERIFY(item.id != 0); + QVERIFY(item.children.isEmpty()); + QVERIFY(!item.properties.contains("type")); + QCOMPARE(item.properties.value("label").toString(), label); + if (enabled) { + QVERIFY(!item.properties.contains("enabled")); + } else { + QCOMPARE(item.properties.value("enabled").toBool(), false); + } + if (iconName.isEmpty()) { + QVERIFY(!item.properties.contains("icon-name")); + } else { + QCOMPARE(item.properties.value("icon-name").toString(), iconName); + } +} + +void DBusMenuExporterTest::testGetAllProperties() +{ + // set of properties which must be returned because their values are not + // the default values + const QSet a1Properties = QSet() + << "label" + ; + + const QSet separatorProperties = QSet() + << "type"; + + const QSet a2Properties = QSet() + << "label" + << "enabled" + << "icon-name" + << "icon-data" // Icon data is always provided if the icon is valid. + << "visible" + ; + + // Create the menu items + QMenu inputMenu; + DBusMenuExporter exporter(TEST_OBJECT_PATH, &inputMenu); + + inputMenu.addAction("a1"); + + inputMenu.addSeparator(); + + QAction *a2 = new QAction("a2", &inputMenu); + a2->setEnabled(false); + QIcon icon = QIcon::fromTheme("edit-undo"); + QVERIFY(!icon.isNull()); + a2->setIcon(icon); + a2->setVisible(false); + inputMenu.addAction(a2); + + // Export them + QDBusInterface iface(TEST_SERVICE, TEST_OBJECT_PATH); + QVERIFY2(iface.isValid(), qPrintable(iface.lastError().message())); + + // Get children + DBusMenuLayoutItemList list = getChildren(&iface, 0, QStringList()); + QCOMPARE(list.count(), 3); + + // Check we get the right properties + DBusMenuLayoutItem item = list.takeFirst(); + QCOMPARE(QSet::fromList(item.properties.keys()), a1Properties); + + item = list.takeFirst(); + QCOMPARE(QSet::fromList(item.properties.keys()), separatorProperties); + + item = list.takeFirst(); + QCOMPARE(QSet::fromList(item.properties.keys()), a2Properties); +} + +void DBusMenuExporterTest::testGetNonExistentProperty() +{ + const char* NON_EXISTENT_KEY = "i-do-not-exist"; + + QMenu inputMenu; + inputMenu.addAction("a1"); + DBusMenuExporter exporter(TEST_OBJECT_PATH, &inputMenu); + + QDBusInterface iface(TEST_SERVICE, TEST_OBJECT_PATH); + DBusMenuLayoutItemList list = getChildren(&iface, 0, QStringList() << NON_EXISTENT_KEY); + QCOMPARE(list.count(), 1); + + DBusMenuLayoutItem item = list.takeFirst(); + QVERIFY(!item.properties.contains(NON_EXISTENT_KEY)); +} + +void DBusMenuExporterTest::testClickedEvent() +{ + QMenu inputMenu; + QAction *action = inputMenu.addAction("a1"); + QSignalSpy spy(action, SIGNAL(triggered())); + DBusMenuExporter exporter(TEST_OBJECT_PATH, &inputMenu); + + QDBusInterface iface(TEST_SERVICE, TEST_OBJECT_PATH); + DBusMenuLayoutItemList list = getChildren(&iface, 0, QStringList()); + QCOMPARE(list.count(), 1); + int id = list.first().id; + + QVariant empty = QVariant::fromValue(QDBusVariant(QString())); + uint timestamp = QDateTime::currentDateTime().toTime_t(); + iface.call("Event", id, "clicked", empty, timestamp); + QTest::qWait(500); + + QCOMPARE(spy.count(), 1); +} + +void DBusMenuExporterTest::testSubMenu() +{ + QMenu inputMenu; + QMenu *subMenu = inputMenu.addMenu("menu"); + QAction *a1 = subMenu->addAction("a1"); + QAction *a2 = subMenu->addAction("a2"); + DBusMenuExporter exporter(TEST_OBJECT_PATH, &inputMenu); + + QDBusInterface iface(TEST_SERVICE, TEST_OBJECT_PATH); + DBusMenuLayoutItemList list = getChildren(&iface, 0, QStringList()); + QCOMPARE(list.count(), 1); + int id = list.first().id; + + list = getChildren(&iface, id, QStringList()); + QCOMPARE(list.count(), 2); + + DBusMenuLayoutItem item = list.takeFirst(); + QVERIFY(item.id != 0); + QCOMPARE(item.properties.value("label").toString(), a1->text()); + + item = list.takeFirst(); + QCOMPARE(item.properties.value("label").toString(), a2->text()); +} + +void DBusMenuExporterTest::testDynamicSubMenu() +{ + // Track LayoutUpdated() signal: we don't want this signal to be emitted + // too often because it causes refreshes + QDBusInterface iface(TEST_SERVICE, TEST_OBJECT_PATH); + ManualSignalSpy layoutUpdatedSpy; + QDBusConnection::sessionBus().connect(TEST_SERVICE, TEST_OBJECT_PATH, "com.canonical.dbusmenu", "LayoutUpdated", "ui", &layoutUpdatedSpy, SLOT(receiveCall(uint, int))); + + // Create our test menu + QMenu inputMenu; + DBusMenuExporter exporter(TEST_OBJECT_PATH, &inputMenu); + QAction *action = inputMenu.addAction("menu"); + QMenu *subMenu = new QMenu(&inputMenu); + action->setMenu(subMenu); + MenuFiller filler(subMenu); + filler.addAction(new QAction("a1", subMenu)); + filler.addAction(new QAction("a2", subMenu)); + + // Get id of submenu + DBusMenuLayoutItemList list = getChildren(&iface, 0, QStringList()); + QCOMPARE(list.count(), 1); + int id = list.first().id; + + // Nothing for now + QCOMPARE(subMenu->actions().count(), 0); + + // LayoutUpdated should be emitted once because inputMenu is filled + QTest::qWait(500); + QCOMPARE(layoutUpdatedSpy.count(), 1); + QCOMPARE(layoutUpdatedSpy.takeFirst().at(1).toInt(), 0); + + // Pretend we show the menu + QDBusReply aboutToShowReply = iface.call("AboutToShow", id); + QVERIFY2(aboutToShowReply.isValid(), qPrintable(aboutToShowReply.error().message())); + QVERIFY(aboutToShowReply.value()); + QTest::qWait(500); + QCOMPARE(layoutUpdatedSpy.count(), 1); + QCOMPARE(layoutUpdatedSpy.takeFirst().at(1).toInt(), id); + + // Get submenu items + list = getChildren(&iface, id, QStringList()); + QVERIFY(subMenu->actions().count() > 0); + QCOMPARE(list.count(), subMenu->actions().count()); + + for (int pos=0; pos< list.count(); ++pos) { + DBusMenuLayoutItem item = list.at(pos); + QVERIFY(item.id != 0); + QAction *action = subMenu->actions().at(pos); + QVERIFY(action); + QCOMPARE(item.properties.value("label").toString(), action->text()); + } +} + +void DBusMenuExporterTest::testRadioItems() +{ + DBusMenuLayoutItem item; + DBusMenuLayoutItemList list; + QMenu inputMenu; + QVERIFY(QDBusConnection::sessionBus().registerService(TEST_SERVICE)); + DBusMenuExporter exporter(TEST_OBJECT_PATH, &inputMenu); + + // Create 2 radio items, check first one + QAction *a1 = inputMenu.addAction("a1"); + a1->setCheckable(true); + QAction *a2 = inputMenu.addAction("a1"); + a2->setCheckable(true); + + QActionGroup group(0); + group.addAction(a1); + group.addAction(a2); + a1->setChecked(true); + + QVERIFY(!a2->isChecked()); + + // Get item ids + QDBusInterface iface(TEST_SERVICE, TEST_OBJECT_PATH); + list = getChildren(&iface, 0, QStringList()); + QCOMPARE(list.count(), 2); + + // Check items are radios and correctly toggled + item = list.takeFirst(); + QCOMPARE(item.properties.value("toggle-type").toString(), QString("radio")); + QCOMPARE(item.properties.value("toggle-state").toInt(), 1); + int a1Id = item.id; + item = list.takeFirst(); + QCOMPARE(item.properties.value("toggle-type").toString(), QString("radio")); + QCOMPARE(item.properties.value("toggle-state").toInt(), 0); + int a2Id = item.id; + + // Click a2 + ManualSignalSpy spy; + QDBusConnection::sessionBus().connect(TEST_SERVICE, TEST_OBJECT_PATH, "com.canonical.dbusmenu", "ItemsPropertiesUpdated", "a(ia{sv})a(ias)", + &spy, SLOT(receiveCall(DBusMenuItemList, DBusMenuItemKeysList))); + QVariant empty = QVariant::fromValue(QDBusVariant(QString())); + uint timestamp = QDateTime::currentDateTime().toTime_t(); + iface.call("Event", a2Id, "clicked", empty, timestamp); + QTest::qWait(500); + + // Check a1 is not checked, but a2 is + list = getChildren(&iface, 0, QStringList()); + QCOMPARE(list.count(), 2); + + item = list.takeFirst(); + QCOMPARE(item.properties.value("toggle-state").toInt(), 0); + + item = list.takeFirst(); + QCOMPARE(item.properties.value("toggle-state").toInt(), 1); + + // Did we get notified? + QCOMPARE(spy.count(), 1); + QSet updatedIds; + { + QVariantList lst = spy.takeFirst().at(0).toList(); + Q_FOREACH(QVariant variant, lst) { + updatedIds << variant.toInt(); + } + } + + QSet expectedIds; + expectedIds << a1Id << a2Id; + + QCOMPARE(updatedIds, expectedIds); +} + +void DBusMenuExporterTest::testNonExclusiveActionGroup() +{ + DBusMenuLayoutItem item; + DBusMenuLayoutItemList list; + QMenu inputMenu; + QVERIFY(QDBusConnection::sessionBus().registerService(TEST_SERVICE)); + DBusMenuExporter exporter(TEST_OBJECT_PATH, &inputMenu); + + // Create 2 checkable items + QAction *a1 = inputMenu.addAction("a1"); + a1->setCheckable(true); + QAction *a2 = inputMenu.addAction("a1"); + a2->setCheckable(true); + + // Put them into a non exclusive group + QActionGroup group(0); + group.addAction(a1); + group.addAction(a2); + group.setExclusive(false); + + // Get item ids + QDBusInterface iface(TEST_SERVICE, TEST_OBJECT_PATH); + list = getChildren(&iface, 0, QStringList()); + QCOMPARE(list.count(), 2); + + // Check items are checkmark, not radio + item = list.takeFirst(); + QCOMPARE(item.properties.value("toggle-type").toString(), QString("checkmark")); + int a1Id = item.id; + item = list.takeFirst(); + QCOMPARE(item.properties.value("toggle-type").toString(), QString("checkmark")); + int a2Id = item.id; +} + +void DBusMenuExporterTest::testClickDeletedAction() +{ + QMenu inputMenu; + QVERIFY(QDBusConnection::sessionBus().registerService(TEST_SERVICE)); + DBusMenuExporter exporter(TEST_OBJECT_PATH, &inputMenu); + + QAction *a1 = inputMenu.addAction("a1"); + + // Get id + QDBusInterface iface(TEST_SERVICE, TEST_OBJECT_PATH); + DBusMenuLayoutItemList list = getChildren(&iface, 0, QStringList()); + QCOMPARE(list.count(), 1); + int id = list.takeFirst().id; + + // Delete a1, it should not cause a crash when trying to trigger it + delete a1; + + // Send a click to deleted a1 + QVariant empty = QVariant::fromValue(QDBusVariant(QString())); + uint timestamp = QDateTime::currentDateTime().toTime_t(); + iface.call("Event", id, "clicked", empty, timestamp); + QTest::qWait(500); +} + +// Reproduce LP BUG 521011 +// https://bugs.launchpad.net/bugs/521011 +void DBusMenuExporterTest::testDeleteExporterBeforeMenu() +{ + QMenu inputMenu; + QVERIFY(QDBusConnection::sessionBus().registerService(TEST_SERVICE)); + DBusMenuExporter *exporter = new DBusMenuExporter(TEST_OBJECT_PATH, &inputMenu); + + QAction *a1 = inputMenu.addAction("a1"); + delete exporter; + inputMenu.removeAction(a1); +} + +void DBusMenuExporterTest::testUpdateAndDeleteSubMenu() +{ + // Create a menu with a submenu + QMenu inputMenu; + QMenu *subMenu = inputMenu.addMenu("menu"); + QAction *a1 = subMenu->addAction("a1"); + + // Export it + QVERIFY(QDBusConnection::sessionBus().registerService(TEST_SERVICE)); + DBusMenuExporter exporter(TEST_OBJECT_PATH, &inputMenu); + + // Update a1 (which is in subMenu) and delete subMenu right after that. If + // DBusMenuExporter is not careful it will crash in the qWait() because it + // tries to send itemUpdated() for a1. + a1->setText("Not a menu anymore"); + delete subMenu; + QTest::qWait(500); +} + +void DBusMenuExporterTest::testMenuShortcut() +{ + // Create a menu containing an action with a shortcut + QMenu inputMenu; + QVERIFY(QDBusConnection::sessionBus().registerService(TEST_SERVICE)); + DBusMenuExporter *exporter = new DBusMenuExporter(TEST_OBJECT_PATH, &inputMenu); + + QAction *a1 = inputMenu.addAction("a1"); + a1->setShortcut(Qt::CTRL | Qt::Key_A); + + QAction *a2 = inputMenu.addAction("a2"); + a2->setShortcut(QKeySequence(Qt::CTRL | Qt::Key_A, Qt::ALT | Qt::Key_B)); + + // No shortcut, to test the property is not added in this case + QAction *a3 = inputMenu.addAction("a3"); + + QList actionList; + actionList << a1 << a2 << a3; + + // Check out exporter is on DBus + QDBusInterface iface(TEST_SERVICE, TEST_OBJECT_PATH); + QVERIFY2(iface.isValid(), qPrintable(iface.lastError().message())); + + // Get exported menu info + QStringList propertyNames = QStringList() << "label" << "shortcut"; + DBusMenuLayoutItemList list = getChildren(&iface, 0, propertyNames); + QCOMPARE(list.count(), actionList.count()); + + Q_FOREACH(const QAction* action, actionList) { + DBusMenuLayoutItem item = list.takeFirst(); + if (action->shortcut().isEmpty()) { + QVERIFY(!item.properties.contains("shortcut")); + } else { + QVERIFY(item.properties.contains("shortcut")); + QDBusArgument arg = item.properties.value("shortcut").value(); + DBusMenuShortcut shortcut; + arg >> shortcut; + QCOMPARE(shortcut.toKeySequence(), action->shortcut()); + } + } +} + +void DBusMenuExporterTest::testGetGroupProperties() +{ + // Create a menu containing two actions + QMenu inputMenu; + QVERIFY(QDBusConnection::sessionBus().registerService(TEST_SERVICE)); + DBusMenuExporter *exporter = new DBusMenuExporter(TEST_OBJECT_PATH, &inputMenu); + + QAction *a1 = inputMenu.addAction("a1"); + QAction *a2 = inputMenu.addAction("a2"); + + // Check exporter is on DBus + QDBusInterface iface(TEST_SERVICE, TEST_OBJECT_PATH); + QVERIFY2(iface.isValid(), qPrintable(iface.lastError().message())); + + // Get item ids + DBusMenuLayoutItemList list = getChildren(&iface, 0, QStringList()); + QCOMPARE(list.count(), inputMenu.actions().count()); + + int id1 = list.at(0).id; + int id2 = list.at(1).id; + + // Get group properties + QList ids = QList() << id1 << id2; + QDBusReply reply = iface.call("GetGroupProperties", QVariant::fromValue(ids), QStringList()); + QVERIFY2(reply.isValid(), qPrintable(reply.error().message())); + DBusMenuItemList groupPropertiesList = reply.value(); + + // Check the info we received + QCOMPARE(groupPropertiesList.count(), inputMenu.actions().count()); + + Q_FOREACH(const QAction* action, inputMenu.actions()) { + DBusMenuItem item = groupPropertiesList.takeFirst(); + QCOMPARE(item.properties.value("label").toString(), action->text()); + } +} + +void DBusMenuExporterTest::testActivateAction() +{ + // Create a menu containing two actions + QMenu inputMenu; + QVERIFY(QDBusConnection::sessionBus().registerService(TEST_SERVICE)); + DBusMenuExporter *exporter = new DBusMenuExporter(TEST_OBJECT_PATH, &inputMenu); + + QAction *a1 = inputMenu.addAction("a1"); + QAction *a2 = inputMenu.addAction("a2"); + + // Check exporter is on DBus + QDBusInterface iface(TEST_SERVICE, TEST_OBJECT_PATH); + QVERIFY2(iface.isValid(), qPrintable(iface.lastError().message())); + + ManualSignalSpy spy; + QDBusConnection::sessionBus().connect(TEST_SERVICE, TEST_OBJECT_PATH, "com.canonical.dbusmenu", "ItemActivationRequested", "iu", &spy, SLOT(receiveCall(int, uint))); + + // Get item ids + DBusMenuLayoutItemList list = getChildren(&iface, 0, QStringList()); + QCOMPARE(list.count(), inputMenu.actions().count()); + + int id1 = list.at(0).id; + int id2 = list.at(1).id; + + // Trigger actions + exporter->activateAction(a1); + exporter->activateAction(a2); + + // Check we received the signals in the correct order + QTest::qWait(500); + QCOMPARE(spy.count(), 2); + QCOMPARE(spy.takeFirst().at(0).toInt(), id1); + QCOMPARE(spy.takeFirst().at(0).toInt(), id2); +} + +static int trackCount(QMenu* menu) +{ + QList lst = menu->findChildren(); + int count = 0; + Q_FOREACH(QObject* child, lst) { + if (qstrcmp(child->metaObject()->className(), "DBusMenu") == 0) { + ++count; + } + } + return count; +} + +// Check we do not create more than one DBusMenu object for each menu +// See KDE bug 254066 +void DBusMenuExporterTest::testTrackActionsOnlyOnce() +{ + // Create a menu with a submenu, unplug the submenu and plug it back. The + // submenu should not have more than one DBusMenu child object. + QMenu mainMenu; + QVERIFY(QDBusConnection::sessionBus().registerService(TEST_SERVICE)); + DBusMenuExporter *exporter = new DBusMenuExporter(TEST_OBJECT_PATH, &mainMenu); + + QMenu* subMenu = new QMenu("File"); + subMenu->addAction("a1"); + mainMenu.addAction(subMenu->menuAction()); + + QTest::qWait(500); + QCOMPARE(trackCount(subMenu), 1); + + mainMenu.removeAction(subMenu->menuAction()); + + mainMenu.addAction(subMenu->menuAction()); + + QTest::qWait(500); + QCOMPARE(trackCount(subMenu), 1); +} + +// If desktop does not want icon in menus, check we do not export them +void DBusMenuExporterTest::testHonorDontShowIconsInMenusAttribute() +{ + QCoreApplication::setAttribute(Qt::AA_DontShowIconsInMenus, true); + QMenu inputMenu; + DBusMenuExporter exporter(TEST_OBJECT_PATH, &inputMenu); + + QAction *action = new QAction("Undo", &inputMenu); + QIcon icon = QIcon::fromTheme("edit-undo"); + QVERIFY(!icon.isNull()); + action->setIcon(icon); + inputMenu.addAction(action); + + // Check out exporter is on DBus + QDBusInterface iface(TEST_SERVICE, TEST_OBJECT_PATH); + QVERIFY2(iface.isValid(), qPrintable(iface.lastError().message())); + + // Get exported menu info + QStringList propertyNames = QStringList() << "icon-name"; + DBusMenuLayoutItemList list = getChildren(&iface, /*parentId=*/0, propertyNames); + DBusMenuLayoutItem item = list.first(); + QVERIFY(item.id != 0); + QVERIFY(!item.properties.contains("icon-name")); +} + +static bool hasInternalDBusMenuObject(QMenu* menu) +{ + Q_FOREACH(QObject* obj, menu->children()) { + if (obj->inherits("DBusMenu")) { + return true; + } + } + return false; +} + +// DBusMenuExporter adds an instance of an internal class named "DBusMenu" to +// any QMenu it tracks. Check they go away when the exporter is deleted. +void DBusMenuExporterTest::testDBusMenuObjectIsDeletedWhenExporterIsDeleted() +{ + QMenu inputMenu; + QVERIFY(QDBusConnection::sessionBus().registerService(TEST_SERVICE)); + DBusMenuExporter *exporter = new DBusMenuExporter(TEST_OBJECT_PATH, &inputMenu); + + QAction *a1 = inputMenu.addAction("a1"); + QVERIFY2(hasInternalDBusMenuObject(&inputMenu), "Test setup failed"); + delete exporter; + QVERIFY(!hasInternalDBusMenuObject(&inputMenu)); +} + +void DBusMenuExporterTest::testSeparatorCollapsing_data() +{ + QTest::addColumn("input"); + QTest::addColumn("expected"); + + QTest::newRow("one-separator") << "a-b" << "a-b"; + QTest::newRow("two-separators") << "a-b-c" << "a-b-c"; + QTest::newRow("middle-separators") << "a--b" << "a-b"; + QTest::newRow("separators-at-begin") << "--a-b" << "a-b"; + QTest::newRow("separators-at-end") << "a-b--" << "a-b"; + QTest::newRow("separators-everywhere") << "--a---bc--d--" << "a-bc-d"; + QTest::newRow("empty-menu") << "" << ""; + QTest::newRow("separators-only") << "---" << ""; +} + +void DBusMenuExporterTest::testSeparatorCollapsing() +{ + QFETCH(QString, input); + QFETCH(QString, expected); + + // Create menu from menu string + QMenu inputMenu; + + QVERIFY(QDBusConnection::sessionBus().registerService(TEST_SERVICE)); + DBusMenuExporter *exporter = new DBusMenuExporter(TEST_OBJECT_PATH, &inputMenu); + + if (input.isEmpty()) { + // Pretend there was an action so that doEmitLayoutUpdated() is called + // even if the new menu is empty. If we don't do this we don't test + // DBusMenuExporterPrivate::collapseSeparators() for empty menus. + delete inputMenu.addAction("dummy"); + } + + Q_FOREACH(QChar ch, input) { + if (ch == '-') { + inputMenu.addSeparator(); + } else { + inputMenu.addAction(ch); + } + } + + QTest::qWait(500); + + // Check out exporter is on DBus + QDBusInterface iface(TEST_SERVICE, TEST_OBJECT_PATH); + QVERIFY2(iface.isValid(), qPrintable(iface.lastError().message())); + + // Get exported menu info + QStringList propertyNames = QStringList(); + DBusMenuLayoutItemList list = getChildren(&iface, /*parentId=*/0, propertyNames); + + // Recreate a menu string from the item list + QString output; + Q_FOREACH(const DBusMenuLayoutItem& item, list) { + QVariantMap properties = item.properties; + if (properties.contains("visible") && !properties.value("visible").toBool()) { + continue; + } + QString type = properties.value("type").toString(); + if (type == "separator") { + output += '-'; + } else { + output += properties.value("label").toString(); + } + } + + // Check it matches + QCOMPARE(output, expected); +} + +static void checkPropertiesChangedArgs(const QVariantList& args, const QString& name, const QVariant& value) +{ + QCOMPARE(args[0].toString(), QString("com.canonical.dbusmenu")); + QVariantMap map; + map.insert(name, value); + QCOMPARE(args[1].toMap(), map); + QCOMPARE(args[2].toStringList(), QStringList()); +} + +void DBusMenuExporterTest::testSetStatus() +{ + QMenu inputMenu; + QVERIFY(QDBusConnection::sessionBus().registerService(TEST_SERVICE)); + DBusMenuExporter *exporter = new DBusMenuExporter(TEST_OBJECT_PATH, &inputMenu); + ManualSignalSpy spy; + QDBusConnection::sessionBus().connect(TEST_SERVICE, TEST_OBJECT_PATH, "org.freedesktop.DBus.Properties", "PropertiesChanged", "sa{sv}as", &spy, SLOT(receiveCall(QString, QVariantMap, QStringList))); + + QTest::qWait(500); + + // Check our exporter is on DBus + QDBusInterface iface(TEST_SERVICE, TEST_OBJECT_PATH); + QVERIFY2(iface.isValid(), qPrintable(iface.lastError().message())); + + QCOMPARE(exporter->status(), QString("normal")); + + // Change status, a DBus signal should be emitted + exporter->setStatus("notice"); + QCOMPARE(exporter->status(), QString("notice")); + QTest::qWait(500); + QCOMPARE(spy.count(), 1); + checkPropertiesChangedArgs(spy.takeFirst(), "Status", "notice"); + + // Same status => no signal + exporter->setStatus("notice"); + QTest::qWait(500); + QCOMPARE(spy.count(), 0); + + // Change status, a DBus signal should be emitted + exporter->setStatus("normal"); + QTest::qWait(500); + QCOMPARE(spy.count(), 1); + checkPropertiesChangedArgs(spy.takeFirst(), "Status", "normal"); +} + +void DBusMenuExporterTest::testGetIconDataProperty() +{ + // Create an icon + QImage img(16, 16, QImage::Format_ARGB32); + { + QPainter painter(&img); + painter.setCompositionMode(QPainter::CompositionMode_Source); + QRect rect = img.rect(); + painter.fillRect(rect, Qt::transparent); + rect.adjust(2, 2, -2, -2); + painter.fillRect(rect, Qt::red); + rect.adjust(2, 2, -2, -2); + painter.fillRect(rect, Qt::green); + } + + QIcon icon(QPixmap::fromImage(img)); + + // Create a menu with the icon and export it + QMenu inputMenu; + QAction* a1 = inputMenu.addAction("a1"); + a1->setIcon(icon); + DBusMenuExporter exporter(TEST_OBJECT_PATH, &inputMenu); + + // Get properties + QDBusInterface iface(TEST_SERVICE, TEST_OBJECT_PATH); + DBusMenuLayoutItemList layoutItemlist = getChildren(&iface, 0, QStringList()); + QCOMPARE(layoutItemlist.count(), 1); + + QList ids = QList() << layoutItemlist[0].id; + + QDBusReply reply = iface.call("GetGroupProperties", QVariant::fromValue(ids), QStringList()); + + DBusMenuItemList itemlist = reply.value(); + QCOMPARE(itemlist.count(), 1); + + // Check we have the right property + DBusMenuItem item = itemlist.takeFirst(); + QVERIFY(!item.properties.contains("icon-name")); + QVERIFY(item.properties.contains("icon-data")); + + // Check saved image is the same + QByteArray data = item.properties.value("icon-data").toByteArray(); + QVERIFY(!data.isEmpty()); + QImage result; + QVERIFY(result.loadFromData(data, "PNG")); + QCOMPARE(result, img); +} + +#include "dbusmenuexportertest.moc" diff --git a/tests/dbusmenuexportertest.h b/tests/dbusmenuexportertest.h new file mode 100644 index 0000000..3e93f76 --- /dev/null +++ b/tests/dbusmenuexportertest.h @@ -0,0 +1,63 @@ +/* This file is part of the dbusmenu-qt library + Copyright 2009 Canonical + Author: Aurelien Gateau + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License (LGPL) as published by the Free Software Foundation; + either version 2 of the License, or (at your option) any later + version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ +#ifndef DBUSMENUEXPORTERTEST_H +#define DBUSMENUEXPORTERTEST_H + +#define QT_GUI_LIB +#include + +// Qt +#include + +// Local + +class DBusMenuExporterTest : public QObject +{ +Q_OBJECT +private Q_SLOTS: + void testGetSomeProperties(); + void testGetSomeProperties_data(); + void testGetAllProperties(); + void testGetNonExistentProperty(); + void testClickedEvent(); + void testSubMenu(); + void testDynamicSubMenu(); + void testRadioItems(); + void testNonExclusiveActionGroup(); + void testClickDeletedAction(); + void testDeleteExporterBeforeMenu(); + void testUpdateAndDeleteSubMenu(); + void testMenuShortcut(); + void testGetGroupProperties(); + void testActivateAction(); + void testTrackActionsOnlyOnce(); + void testHonorDontShowIconsInMenusAttribute(); + void testDBusMenuObjectIsDeletedWhenExporterIsDeleted(); + void testSeparatorCollapsing_data(); + void testSeparatorCollapsing(); + void testSetStatus(); + void testGetIconDataProperty(); + + void init(); + void cleanup(); +}; + +#endif /* DBUSMENUEXPORTERTEST_H */ diff --git a/tests/dbusmenuimportertest.cpp b/tests/dbusmenuimportertest.cpp new file mode 100644 index 0000000..497f1e7 --- /dev/null +++ b/tests/dbusmenuimportertest.cpp @@ -0,0 +1,343 @@ +/* This file is part of the dbusmenu-qt library + Copyright 2010 Canonical + Author: Aurelien Gateau + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License (LGPL) as published by the Free Software Foundation; + either version 2 of the License, or (at your option) any later + version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ +// Self +#include "dbusmenuimportertest.h" + +// Qt +#include +#include +#include +#include +#include +#include + +// DBusMenuQt +#include +#include +#include + +// Local +#include "testutils.h" + +QTEST_MAIN(DBusMenuImporterTest) + +static const char *TEST_SERVICE = "com.canonical.dbusmenu-qt-test"; +static const char *TEST_OBJECT_PATH = "/TestMenuBar"; + +Q_DECLARE_METATYPE(QAction*) + +void DBusMenuImporterTest::initTestCase() +{ + qRegisterMetaType("QAction*"); + QVERIFY(QDBusConnection::sessionBus().registerService(TEST_SERVICE)); +} + +void DBusMenuImporterTest::cleanup() +{ + waitForDeferredDeletes(); +} + +void DBusMenuImporterTest::testStandardItem() +{ + QMenu inputMenu; + QAction *action = inputMenu.addAction("Test"); + DBusMenuExporter exporter(TEST_OBJECT_PATH, &inputMenu); + + DBusMenuImporter importer(TEST_SERVICE, TEST_OBJECT_PATH); + QTest::qWait(500); + + QMenu *outputMenu = importer.menu(); + QCOMPARE(outputMenu->actions().count(), 1); + QAction *outputAction = outputMenu->actions().first(); + QCOMPARE(outputAction->text(), QString("Test")); +} + +void DBusMenuImporterTest::testAddingNewItem() +{ + QMenu inputMenu; + QAction *action = inputMenu.addAction("Test"); + DBusMenuExporter exporter(TEST_OBJECT_PATH, &inputMenu); + + DBusMenuImporter importer(TEST_SERVICE, TEST_OBJECT_PATH); + QTest::qWait(500); + QMenu *outputMenu = importer.menu(); + QCOMPARE(outputMenu->actions().count(), inputMenu.actions().count()); + + inputMenu.addAction("Test2"); + QTest::qWait(500); + QCOMPARE(outputMenu->actions().count(), inputMenu.actions().count()); +} + +void DBusMenuImporterTest::testShortcut() +{ + QMenu inputMenu; + QAction *action = inputMenu.addAction("Test"); + action->setShortcut(Qt::CTRL | Qt::Key_S); + DBusMenuExporter exporter(TEST_OBJECT_PATH, &inputMenu); + + DBusMenuImporter importer(TEST_SERVICE, TEST_OBJECT_PATH); + QTest::qWait(500); + QMenu *outputMenu = importer.menu(); + + QAction *outputAction = outputMenu->actions().at(0); + QCOMPARE(outputAction->shortcut(), action->shortcut()); +} + +void DBusMenuImporterTest::testDeletingImporterWhileWaitingForAboutToShow() +{ + // Start test program and wait for it to be ready + QProcess slowMenuProcess; + slowMenuProcess.start("./slowmenu"); + QTest::qWait(500); + + // Create importer and wait for the menu + DBusMenuImporter *importer = new DBusMenuImporter(TEST_SERVICE, TEST_OBJECT_PATH); + QTest::qWait(500); + + QMenu *outputMenu = importer->menu(); + QTimer::singleShot(100, importer, SLOT(deleteLater())); + outputMenu->popup(QPoint(0, 0)); + + // If it crashes, it will crash while waiting there + QTest::qWait(500); + + // Done, stop our test program + slowMenuProcess.close(); + slowMenuProcess.waitForFinished(); +} + +void DBusMenuImporterTest::testDynamicMenu() +{ + QMenu rootMenu; + QAction* a1 = new QAction("a1", &rootMenu); + QAction* a2 = new QAction("a2", &rootMenu); + MenuFiller rootMenuFiller(&rootMenu); + rootMenuFiller.addAction(a1); + rootMenuFiller.addAction(a2); + + QMenu subMenu; + MenuFiller subMenuFiller(&subMenu); + subMenuFiller.addAction(new QAction("a3", &subMenu)); + + a1->setMenu(&subMenu); + + DBusMenuExporter exporter(TEST_OBJECT_PATH, &rootMenu); + + // Import this menu + DBusMenuImporter importer(TEST_SERVICE, TEST_OBJECT_PATH); + QTest::qWait(500); + QMenu *outputMenu = importer.menu(); + + // There should be no children for now + QCOMPARE(outputMenu->actions().count(), 0); + + // Update menu, a1 and a2 should get added + QSignalSpy spy(&importer, SIGNAL(menuUpdated())); + QSignalSpy spyOld(&importer, SIGNAL(menuReadyToBeShown())); + importer.updateMenu(); + while (spy.isEmpty()) { + QTest::qWait(500); + } + + QCOMPARE(outputMenu->actions().count(), 2); + QTest::qWait(500); + QAction* a1Output = outputMenu->actions().first(); + + // a1Output should have an empty menu + QMenu* a1OutputMenu = a1Output->menu(); + QVERIFY(a1OutputMenu); + QCOMPARE(a1OutputMenu->actions().count(), 0); + + // Show a1OutputMenu, a3 should get added + QMetaObject::invokeMethod(a1OutputMenu, "aboutToShow"); + QTest::qWait(500); + + QCOMPARE(a1OutputMenu->actions().count(), 1); + + // menuUpdated() and menuReadyToBeShown() should only have been emitted + // once + QCOMPARE(spy.count(), 1); + QCOMPARE(spyOld.count(), 1); +} + +void DBusMenuImporterTest::testActionActivationRequested() +{ + // Export a menu + QMenu inputMenu; + QAction *inputA1 = inputMenu.addAction("a1"); + QAction *inputA2 = inputMenu.addAction("a2"); + DBusMenuExporter exporter(TEST_OBJECT_PATH, &inputMenu); + + // Import the menu + DBusMenuImporter importer(TEST_SERVICE, TEST_OBJECT_PATH); + QSignalSpy spy(&importer, SIGNAL(actionActivationRequested(QAction*))); + + QTest::qWait(500); + QMenu *outputMenu = importer.menu(); + + // Get matching output actions + QCOMPARE(outputMenu->actions().count(), 2); + QAction *outputA1 = outputMenu->actions().at(0); + QAction *outputA2 = outputMenu->actions().at(1); + + // Request activation + exporter.activateAction(inputA1); + exporter.activateAction(inputA2); + + // Check we received the signal in the right order + QTest::qWait(500); + QCOMPARE(spy.count(), 2); + QCOMPARE(spy.takeFirst().at(0).value(), outputA1); + QCOMPARE(spy.takeFirst().at(0).value(), outputA2); +} + +void DBusMenuImporterTest::testActionsAreDeletedWhenImporterIs() +{ + // Export a menu + QMenu inputMenu; + inputMenu.addAction("a1"); + QMenu *inputSubMenu = inputMenu.addMenu("subMenu"); + inputSubMenu->addAction("a2"); + DBusMenuExporter exporter(TEST_OBJECT_PATH, &inputMenu); + + // Import the menu + DBusMenuImporter *importer = new DBusMenuImporter(TEST_SERVICE, TEST_OBJECT_PATH); + QTest::qWait(500); + + // Put all items of the menu in a list of QPointers + QList< QPointer > children; + + QMenu *outputMenu = importer->menu(); + QCOMPARE(outputMenu->actions().count(), 2); + QMenu *outputSubMenu = outputMenu->actions().at(1)->menu(); + QVERIFY(outputSubMenu); + // Fake aboutToShow so that outputSubMenu is populated + QMetaObject::invokeMethod(outputSubMenu, "aboutToShow"); + QCOMPARE(outputSubMenu->actions().count(), 1); + + children << outputMenu->actions().at(0); + children << outputMenu->actions().at(1); + children << outputSubMenu; + children << outputSubMenu->actions().at(0); + + delete importer; + waitForDeferredDeletes(); + + // There should be only invalid pointers in children + Q_FOREACH(QPointer child, children) { + //qDebug() << child; + QVERIFY(child.isNull()); + } +} + +void DBusMenuImporterTest::testIconData() +{ + // Create an icon + QImage img(16, 16, QImage::Format_ARGB32); + { + QPainter painter(&img); + painter.setCompositionMode(QPainter::CompositionMode_Source); + QRect rect = img.rect(); + painter.fillRect(rect, Qt::transparent); + rect.adjust(2, 2, -2, -2); + painter.fillRect(rect, Qt::red); + rect.adjust(2, 2, -2, -2); + painter.fillRect(rect, Qt::green); + } + QIcon inputIcon(QPixmap::fromImage(img)); + + // Export a menu + QMenu inputMenu; + QAction *a1 = inputMenu.addAction("a1"); + a1->setIcon(inputIcon); + DBusMenuExporter exporter(TEST_OBJECT_PATH, &inputMenu); + + // Import the menu + DBusMenuImporter *importer = new DBusMenuImporter(TEST_SERVICE, TEST_OBJECT_PATH); + QTest::qWait(500); + + // Check icon of action + QMenu *outputMenu = importer->menu(); + QCOMPARE(outputMenu->actions().count(), 1); + + QIcon outputIcon = outputMenu->actions().first()->icon(); + QVERIFY(!outputIcon.isNull()); + + QImage result = outputIcon.pixmap(16).toImage(); + QByteArray origBytes, resultBytes; + img.save(origBytes); + result.save(resultBytes); + QCOMPARE(origBytes,resultBytes); +} + +void DBusMenuImporterTest::testInvisibleItem() +{ + QMenu inputMenu; + QAction *action = inputMenu.addAction("Test"); + DBusMenuExporter exporter(TEST_OBJECT_PATH, &inputMenu); + + DBusMenuImporter importer(TEST_SERVICE, TEST_OBJECT_PATH); + QTest::qWait(500); + + QMenu *outputMenu = importer.menu(); + QCOMPARE(outputMenu->actions().count(), 1); + QAction *outputAction = outputMenu->actions().first(); + + QVERIFY(outputAction->isVisible()); + + // Hide the action + action->setVisible(false); + QTest::qWait(500); + QVERIFY(!outputAction->isVisible()); + + // Show the action + action->setVisible(true); + QTest::qWait(500); + QVERIFY(outputAction->isVisible()); +} + +void DBusMenuImporterTest::testDisabledItem() +{ + QMenu inputMenu; + QAction *action = inputMenu.addAction("Test"); + DBusMenuExporter exporter(TEST_OBJECT_PATH, &inputMenu); + + DBusMenuImporter importer(TEST_SERVICE, TEST_OBJECT_PATH); + QTest::qWait(500); + + QMenu *outputMenu = importer.menu(); + QCOMPARE(outputMenu->actions().count(), 1); + QAction *outputAction = outputMenu->actions().first(); + QVERIFY(outputAction->isEnabled()); + + // Disable the action + DMDEBUG << "Disabling"; + action->setEnabled(false); + QTest::qWait(500); + QVERIFY(!outputAction->isEnabled()); + + // Enable the action + action->setEnabled(true); + QTest::qWait(500); + QVERIFY(outputAction->isEnabled()); +} + +#include "dbusmenuimportertest.moc" diff --git a/tests/dbusmenuimportertest.h b/tests/dbusmenuimportertest.h new file mode 100644 index 0000000..64ed647 --- /dev/null +++ b/tests/dbusmenuimportertest.h @@ -0,0 +1,51 @@ +/* This file is part of the dbusmenu-qt library + Copyright 2010 Canonical + Author: Aurelien Gateau + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License (LGPL) as published by the Free Software Foundation; + either version 2 of the License, or (at your option) any later + version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ +#ifndef DBUSMENUIMPORTERTEST_H +#define DBUSMENUIMPORTERTEST_H + +#define QT_GUI_LIB +#include + +// Qt +#include + +// Local + +class DBusMenuImporterTest : public QObject +{ +Q_OBJECT +private Q_SLOTS: + void cleanup(); + void testStandardItem(); + void testAddingNewItem(); + void testShortcut(); + void testDeletingImporterWhileWaitingForAboutToShow(); + void testDynamicMenu(); + void testActionActivationRequested(); + void testActionsAreDeletedWhenImporterIs(); + void testIconData(); + void testInvisibleItem(); + void testDisabledItem(); + + void initTestCase(); +}; + +#endif /* DBUSMENUIMPORTERTEST_H */ diff --git a/tests/dbusmenushortcuttest.cpp b/tests/dbusmenushortcuttest.cpp new file mode 100644 index 0000000..c3036ef --- /dev/null +++ b/tests/dbusmenushortcuttest.cpp @@ -0,0 +1,85 @@ +/* This file is part of the dbusmenu-qt library + Copyright 2010 Canonical + Author: Aurelien Gateau + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License (LGPL) as published by the Free Software Foundation; + either version 2 of the License, or (at your option) any later + version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ +// Self +#include "dbusmenushortcuttest.h" + +// Qt +#include + +// DBusMenuQt +#include +#include + +QTEST_MAIN(DBusMenuShortcutTest) + +namespace QTest +{ +template<> +char *toString(const DBusMenuShortcut &dmShortcut) +{ + QByteArray ba = "DBusMenuShortcut("; + Q_FOREACH(const QStringList& tokens, dmShortcut) { + ba += "("; + ba += tokens.join("+").toUtf8(); + ba += ")"; + } + ba += ")"; + return qstrdup(ba.data()); +} +} + +DBusMenuShortcut createKeyList(const QString& txt) +{ + DBusMenuShortcut lst; + QStringList tokens = txt.split(','); + Q_FOREACH(const QString& token, tokens) { + lst << token.split('+'); + } + return lst; +} + +#define ADD_ROW(ksArgs, klArgs) QTest::newRow(#ksArgs) << QKeySequence ksArgs << createKeyList(klArgs) + +void DBusMenuShortcutTest::testConverter_data() +{ + QTest::addColumn("keySequence"); + QTest::addColumn("keyList"); + + ADD_ROW((Qt::ALT | Qt::Key_F4), "Alt+F4"); + ADD_ROW((Qt::CTRL | Qt::Key_S), "Control+S"); + ADD_ROW((Qt::CTRL | Qt::Key_X, Qt::ALT | Qt::SHIFT | Qt::Key_Q), "Control+X,Alt+Shift+Q"); + ADD_ROW((Qt::META | Qt::Key_E), "Super+E"); + ADD_ROW((Qt::CTRL | Qt::Key_Plus), "Control+plus"); + ADD_ROW((Qt::CTRL | Qt::Key_Minus), "Control+minus"); +} + +void DBusMenuShortcutTest::testConverter() +{ + QFETCH(QKeySequence, keySequence); + QFETCH(DBusMenuShortcut, keyList); + + DBusMenuShortcut list = DBusMenuShortcut::fromKeySequence(keySequence); + QCOMPARE(list, keyList); + QKeySequence sequence = keyList.toKeySequence(); + QCOMPARE(sequence.toString(), keySequence.toString()); +} + +#include "dbusmenushortcuttest.moc" diff --git a/tests/dbusmenushortcuttest.h b/tests/dbusmenushortcuttest.h new file mode 100644 index 0000000..531aac5 --- /dev/null +++ b/tests/dbusmenushortcuttest.h @@ -0,0 +1,40 @@ +/* This file is part of the dbusmenu-qt library + Copyright 2010 Canonical + Author: Aurelien Gateau + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License (LGPL) as published by the Free Software Foundation; + either version 2 of the License, or (at your option) any later + version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ +#ifndef DBUSMENUSHORTCUTTEST_H +#define DBUSMENUSHORTCUTTEST_H + +#define QT_GUI_LIB +#include + +// Qt +#include + +// Local + +class DBusMenuShortcutTest : public QObject +{ +Q_OBJECT +private Q_SLOTS: + void testConverter_data(); + void testConverter(); +}; + +#endif /* DBUSMENUSHORTCUTTEST_H */ diff --git a/tests/slowmenu.cpp b/tests/slowmenu.cpp new file mode 100644 index 0000000..13972c1 --- /dev/null +++ b/tests/slowmenu.cpp @@ -0,0 +1,58 @@ +/* This file is part of the dbusmenu-qt library + Copyright 2010 Canonical + Author: Aurelien Gateau + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License (LGPL) as published by the Free Software Foundation; + either version 2 of the License, or (at your option) any later + version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ +#include + +#include + +#include +#include +#include + +static const char *TEST_SERVICE = "org.kde.dbusmenu-qt-test"; +static const char *TEST_OBJECT_PATH = "/TestMenuBar"; + +SlowMenu::SlowMenu() +: QMenu() +{ + connect(this, SIGNAL(aboutToShow()), SLOT(slotAboutToShow())); +} + +void SlowMenu::slotAboutToShow() +{ + qDebug() << __FUNCTION__ << "Entering"; + QTime time; + time.start(); + while (time.elapsed() < 2000) { + qApp->processEvents(); + } + qDebug() << __FUNCTION__ << "Leaving"; +} + +int main(int argc, char** argv) +{ + QApplication app(argc, argv); + QDBusConnection::sessionBus().registerService(TEST_SERVICE); + SlowMenu* inputMenu = new SlowMenu; + inputMenu->addAction("Test"); + DBusMenuExporter exporter(TEST_OBJECT_PATH, inputMenu); + qDebug() << "Looping"; + return app.exec(); +} diff --git a/tests/slowmenu.h b/tests/slowmenu.h new file mode 100644 index 0000000..cbea0ff --- /dev/null +++ b/tests/slowmenu.h @@ -0,0 +1,37 @@ +/* This file is part of the dbusmenu-qt library + Copyright 2010 Canonical + Author: Aurelien Gateau + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License (LGPL) as published by the Free Software Foundation; + either version 2 of the License, or (at your option) any later + version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ +#ifndef SLOWMENU_H +#define SLOWMENU_H + +#include + +class SlowMenu : public QMenu +{ +Q_OBJECT +public: + SlowMenu(); + +public Q_SLOTS: + void slotAboutToShow(); +}; + + +#endif /* SLOWMENU_H */ diff --git a/tests/testutils.cpp b/tests/testutils.cpp new file mode 100644 index 0000000..8426aa1 --- /dev/null +++ b/tests/testutils.cpp @@ -0,0 +1,34 @@ +/* This file is part of the dbusmenu-qt library + Copyright 2010 Canonical + Author: Aurelien Gateau + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License (LGPL) as published by the Free Software Foundation; + either version 2 of the License, or (at your option) any later + version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ +#include "testutils.h" + +#include + +void waitForDeferredDeletes() +{ + while (QCoreApplication::hasPendingEvents()) { + QCoreApplication::sendPostedEvents(); + QCoreApplication::sendPostedEvents(0, QEvent::DeferredDelete); + QCoreApplication::processEvents(); + } +} + +#include "testutils.moc" diff --git a/tests/testutils.h b/tests/testutils.h new file mode 100644 index 0000000..696d3f9 --- /dev/null +++ b/tests/testutils.h @@ -0,0 +1,109 @@ +/* This file is part of the dbusmenu-qt library + Copyright 2010 Canonical + Author: Aurelien Gateau + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License (LGPL) as published by the Free Software Foundation; + either version 2 of the License, or (at your option) any later + version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ +#ifndef TESTUTILS_H +#define TESTUTILS_H + +// Local +#include +#include + +// Qt +#include +#include +#include + +class ManualSignalSpy : public QObject, public QList +{ + Q_OBJECT +public Q_SLOTS: + void receiveCall(int value) + { + append(QVariantList() << value); + } + + void receiveCall(uint v1, int v2) + { + append(QVariantList() << v1 << v2); + } + + void receiveCall(int v1, uint v2) + { + append(QVariantList() << v1 << v2); + } + + void receiveCall(DBusMenuItemList itemList, DBusMenuItemKeysList removedPropsList) + { + QVariantList propsIds; + Q_FOREACH(DBusMenuItem item, itemList) { + propsIds << item.id; + } + QVariantList removedPropsIds; + Q_FOREACH(DBusMenuItemKeys props, removedPropsList) { + removedPropsIds << props.id; + } + + QVariantList args; + args.push_back(propsIds); + args.push_back(removedPropsIds); + append(args); + } + + void receiveCall(const QString& service, const QVariantMap& modifiedProperties, const QStringList& newProperties) + { + QVariantList args; + args.push_back(service); + args.push_back(modifiedProperties); + args.push_back(newProperties); + append(args); + } +}; + +class MenuFiller : public QObject +{ + Q_OBJECT +public: + MenuFiller(QMenu *menu) + : m_menu(menu) + { + connect(m_menu, SIGNAL(aboutToShow()), SLOT(fillMenu())); + } + + void addAction(QAction *action) + { + m_actions << action; + } + +public Q_SLOTS: + void fillMenu() + { + while (!m_actions.isEmpty()) { + m_menu->addAction(m_actions.takeFirst()); + } + } + +private: + QMenu *m_menu; + QList m_actions; +}; + +void waitForDeferredDeletes(); + +#endif /* TESTUTILS_H */ diff --git a/tools/CMakeLists.txt b/tools/CMakeLists.txt new file mode 100644 index 0000000..c150cc7 --- /dev/null +++ b/tools/CMakeLists.txt @@ -0,0 +1,7 @@ +find_package(QJSON) +if (QJSON_FOUND) + message(STATUS "QJSON found, testapp will be built") + add_subdirectory(testapp) +else (QJSON_FOUND) + message(STATUS "QJSON not found, testapp will not be built") +endif (QJSON_FOUND) diff --git a/tools/testapp/CMakeLists.txt b/tools/testapp/CMakeLists.txt new file mode 100644 index 0000000..110b8b0 --- /dev/null +++ b/tools/testapp/CMakeLists.txt @@ -0,0 +1,46 @@ +set(qtapp_SRCS + main.cpp + ) + +add_executable(dbusmenubench-qtapp ${qtapp_SRCS}) + +if (NOT USE_QT5) + # Qt4 + include_directories( + ${CMAKE_CURRENT_SOURCE_DIR}/../../src + ${CMAKE_CURRENT_BINARY_DIR}/../../src + ${QT_INCLUDE_DIR} + ${QT_QTCORE_INCLUDE_DIR} + ${QT_QTGUI_INCLUDE_DIR} + ${QT_QTDBUS_INCLUDE_DIR} + ${QJSON_INCLUDE_DIR} + ) + + target_link_libraries(dbusmenubench-qtapp + dbusmenu-qt + ${QT_QTGUI_LIBRARY} + ${QT_QTCORE_LIBRARY} + ${QT_QTDBUS_LIBRARY} + ${QJSON_LIBRARIES} + ) +else() + # Qt5 + include_directories( + ${CMAKE_CURRENT_SOURCE_DIR}/../../src + ${CMAKE_CURRENT_BINARY_DIR}/../../src + ${Qt5Widgets_INCLUDE_DIRS} + ${Qt5Core_INCLUDE_DIRS} + ${Qt5Gui_INCLUDE_DIRS} + ${Qt5DBus_INCLUDE_DIRS} + ${QJSON_INCLUDE_DIR} + ) + + target_link_libraries(dbusmenubench-qtapp + dbusmenu-qt5 + ${Qt5Gui_LIBRARIES} + ${Qt5Core_LIBRARIES} + ${Qt5DBus_LIBRARIES} + ${Qt5Widgets_LIBRARIES} + ${QJSON_LIBRARIES} + ) +endif() diff --git a/tools/testapp/main.cpp b/tools/testapp/main.cpp new file mode 100644 index 0000000..d9aa905 --- /dev/null +++ b/tools/testapp/main.cpp @@ -0,0 +1,105 @@ +/* This file is part of the dbusmenu-qt library + Copyright 2010 Canonical + Author: Aurelien Gateau + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License (LGPL) as published by the Free Software Foundation; + either version 2 of the License, or (at your option) any later + version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ +#include +#include +#include +#include +#include + +#include + +#include + +static const char *DBUS_SERVICE = "org.dbusmenu.test"; +static const char *DBUS_PATH = "/MenuBar"; +static const char *USAGE = "dbusmenubench-qtapp "; + +void createMenuItem(QMenu *menu, const QVariant &item) +{ + QVariantMap map = item.toMap(); + + if (map.value("visible").toString() == "false") { + return; + } + + QString type = map.value("type").toString(); + if (type == "separator") { + menu->addSeparator(); + return; + } + + QString label = map.value("label").toString(); + QAction *action = menu->addAction(label); + action->setEnabled(map.value("sensitive").toString() == "true"); + if (map.contains("submenu")) { + QVariantList items = map.value("submenu").toList(); + Q_FOREACH(const QVariant &item, items) { + QMenu *subMenu = new QMenu; + action->setMenu(subMenu); + createMenuItem(subMenu, item); + } + } +} + +void initMenu(QMenu *menu, const QString &fileName) +{ + QJson::Parser parser; + + QFile file(fileName); + if (!file.open(QIODevice::ReadOnly)) { + qCritical() << "Could not open file" << fileName; + return; + } + + bool ok; + QVariant tree = parser.parse(&file, &ok); + if (!ok) { + qCritical() << "Could not parse json data from" << fileName; + return; + } + + QVariantList list = tree.toList(); + Q_FOREACH(const QVariant &item, list) { + createMenuItem(menu, item); + } +} + +int main(int argc, char **argv) +{ + QApplication app(argc, argv); + QMenu menu; + + if (argc != 2) { + qCritical() << USAGE; + return 1; + } + QString jsonFileName = argv[1]; + initMenu(&menu, jsonFileName); + + QDBusConnection connection = QDBusConnection::sessionBus(); + if (!connection.registerService(DBUS_SERVICE)) { + qCritical() << "Could not register" << DBUS_SERVICE; + return 1; + } + + DBusMenuExporter exporter(DBUS_PATH, &menu); + return app.exec(); +}