OSDN Git Service

initial import
authorIvailo Monev <xakepa10@laimg.moc>
Sun, 26 Jan 2020 13:25:23 +0000 (13:25 +0000)
committerIvailo Monev <xakepa10@laimg.moc>
Sun, 26 Jan 2020 13:25:23 +0000 (13:25 +0000)
56 files changed:
CMakeLists.txt [new file with mode: 0644]
COPYING [new file with mode: 0644]
Doxyfile.in [new file with mode: 0644]
NEWS [new file with mode: 0644]
README [new file with mode: 0644]
RELEASE_CHECK_LIST [new file with mode: 0644]
cmake/modules/FindQJSON.cmake [new file with mode: 0644]
dbusmenu-qt-config.cmake.in [new file with mode: 0644]
dbusmenu-qt.pc.in [new file with mode: 0644]
debian/changelog [new file with mode: 0644]
debian/compat [new file with mode: 0644]
debian/control [new file with mode: 0644]
debian/copyright [new file with mode: 0644]
debian/docs [new file with mode: 0644]
debian/libdbusmenu-qt-dev.install [new file with mode: 0644]
debian/libdbusmenu-qt-doc.install [new file with mode: 0644]
debian/libdbusmenu-qt2.install [new file with mode: 0644]
debian/libdbusmenu-qt5-dev.install [new file with mode: 0644]
debian/libdbusmenu-qt5-doc.install [new file with mode: 0644]
debian/libdbusmenu-qt5.install [new file with mode: 0644]
debian/rules [new file with mode: 0755]
src/CMakeLists.txt [new file with mode: 0644]
src/com.canonical.dbusmenu.xml [new file with mode: 0644]
src/dbusmenu_config.h.in [new file with mode: 0644]
src/dbusmenu_export.h [new file with mode: 0644]
src/dbusmenu_p.cpp [new file with mode: 0644]
src/dbusmenu_p.h [new file with mode: 0644]
src/dbusmenu_version.h.in [new file with mode: 0644]
src/dbusmenuexporter.cpp [new file with mode: 0644]
src/dbusmenuexporter.h [new file with mode: 0644]
src/dbusmenuexporterdbus_p.cpp [new file with mode: 0644]
src/dbusmenuexporterdbus_p.h [new file with mode: 0644]
src/dbusmenuexporterprivate_p.h [new file with mode: 0644]
src/dbusmenuimporter.cpp [new file with mode: 0644]
src/dbusmenuimporter.h [new file with mode: 0644]
src/dbusmenushortcut_p.cpp [new file with mode: 0644]
src/dbusmenushortcut_p.h [new file with mode: 0644]
src/dbusmenutypes_p.cpp [new file with mode: 0644]
src/dbusmenutypes_p.h [new file with mode: 0644]
src/debug_p.h [new file with mode: 0644]
src/utils.cpp [new file with mode: 0644]
src/utils_p.h [new file with mode: 0644]
tests/CMakeLists.txt [new file with mode: 0644]
tests/dbusmenuexportertest.cpp [new file with mode: 0644]
tests/dbusmenuexportertest.h [new file with mode: 0644]
tests/dbusmenuimportertest.cpp [new file with mode: 0644]
tests/dbusmenuimportertest.h [new file with mode: 0644]
tests/dbusmenushortcuttest.cpp [new file with mode: 0644]
tests/dbusmenushortcuttest.h [new file with mode: 0644]
tests/slowmenu.cpp [new file with mode: 0644]
tests/slowmenu.h [new file with mode: 0644]
tests/testutils.cpp [new file with mode: 0644]
tests/testutils.h [new file with mode: 0644]
tools/CMakeLists.txt [new file with mode: 0644]
tools/testapp/CMakeLists.txt [new file with mode: 0644]
tools/testapp/main.cpp [new file with mode: 0644]

diff --git a/CMakeLists.txt b/CMakeLists.txt
new file mode 100644 (file)
index 0000000..72cbc45
--- /dev/null
@@ -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 (file)
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.
+\f
+  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.
+\f
+                 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.
+\f
+  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.
+\f
+  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.
+\f
+  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.
+\f
+  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.
+\f
+  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.
+\f
+  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.
+
+    <one line to give the library's name and a brief idea of what it does.>
+    Copyright (C) <year>  <name of author>
+
+    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.
+
+  <signature of Ty Coon>, 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 (file)
index 0000000..cf2d263
--- /dev/null
@@ -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 (file)
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 (file)
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 <renato.filho@canonical.com>
+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 (file)
index 0000000..26ff447
--- /dev/null
@@ -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 (file)
index 0000000..cd007e1
--- /dev/null
@@ -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 (file)
index 0000000..7b2b762
--- /dev/null
@@ -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 (file)
index 0000000..a5e2a25
--- /dev/null
@@ -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/debian/changelog b/debian/changelog
new file mode 100644 (file)
index 0000000..bc06424
--- /dev/null
@@ -0,0 +1,487 @@
+libdbusmenu-qt (0.9.3+16.04.20160218-0ubuntu1) xenial; urgency=medium
+
+  [ Nick Dedekind ]
+  * Ported tests to Qt5.
+
+ -- Pete Woods <ci-train-bot@canonical.com>  Thu, 18 Feb 2016 10:14:02 +0000
+
+libdbusmenu-qt (0.9.3+15.10.20150604-0ubuntu1) wily; urgency=medium
+
+  [ David Edmundson ]
+  * fix leaks
+  * fix leaks
+
+ -- CI Train Bot <ci-train-bot@canonical.com>  Thu, 04 Jun 2015 23:13:02 +0000
+
+libdbusmenu-qt (0.9.3+14.10.20140619-0ubuntu1) utopic; urgency=low
+
+  [ Aurélien Gâteau ]
+  * Fix build with Clang
+  * With this change, users of dbusmenu-qt no longer need to call
+    include_directories(${dbusmenu-qt5_INCLUDE_DIRS}). Simply adding
+    dbusmenu-qt5 to the target_link_libraries() call takes care of
+    defining the include directory. This requires CMake 2.8.11, so I
+    bumped the minimum version numbers accordingly.
+
+ -- Ubuntu daily release <ps-jenkins@lists.canonical.com>  Thu, 19 Jun 2014 09:07:18 +0000
+
+libdbusmenu-qt (0.9.3+14.04.20140314-0ubuntu1) trusty; urgency=medium
+
+  [ Pete Woods ]
+  * Add importer parameter to allow control over DBus behavior.
+
+ -- Ubuntu daily release <ps-jenkins@lists.canonical.com>  Fri, 14 Mar 2014 16:41:54 +0000
+
+libdbusmenu-qt (0.9.2+14.04.20140305-0ubuntu1) trusty; urgency=low
+
+  * New rebuild forced
+
+ -- Ubuntu daily release <ps-jenkins@lists.canonical.com>  Wed, 05 Mar 2014 09:29:14 +0000
+
+libdbusmenu-qt (0.9.2+14.04.20140218.2-0ubuntu1) trusty; urgency=low
+
+  [ Pete Woods ]
+  * Remove busy watcher (LP: #1280372)
+
+ -- Ubuntu daily release <ps-jenkins@lists.canonical.com>  Tue, 18 Feb 2014 14:33:02 +0000
+
+libdbusmenu-qt (0.9.2+14.04.20140217.1-0ubuntu1) trusty; urgency=low
+
+  [ Pete Woods ]
+  * Fix compile warning when building with -Wpedantic (extra semicolon).
+
+  [ Ted Gould ]
+  * Flushing trunk with a release
+
+  [ Timo Jyrinki ]
+  * Drop reference to libqt5core5 which will get renamed. (LP:
+    #1271058). (LP: #1271058)
+
+ -- Ubuntu daily release <ps-jenkins@lists.canonical.com>  Mon, 17 Feb 2014 18:03:31 +0000
+
+libdbusmenu-qt (0.9.2+14.04.20131209-0ubuntu1) trusty; urgency=low
+
+  [ Marcus Tomlinson ]
+  * When adding a new submenu action, refresh() that action to ensure
+    full menu hierarchy is built.
+
+  [ Ubuntu daily release ]
+  * Automatic snapshot from revision 253
+
+ -- Ubuntu daily release <ps-jenkins@lists.canonical.com>  Mon, 09 Dec 2013 02:29:59 +0000
+
+libdbusmenu-qt (0.9.2+14.04.20131125-0ubuntu1) trusty; urgency=low
+
+  [ Aurélien Gâteau ]
+  * This change install CMake config files for dbusmenu-qt and dbusmenu-
+    qt5. This makes it easy for other projects to use the library with
+    find(dbusmenu-qt) or find(dbusmenu-qt5) without having to ship a
+    FindDBusMenuQt.cmake file. (More about this topic here:
+    http://www.cmake.org/Wiki/CMake/Tutorials/Packaging ). Test programs
+    available here: http://agateau.com/tmp/dmqt-samples.tar.bz2.
+
+  [ Marcus Tomlinson ]
+  * Destructors of classes intended to be base classes updated to
+    virtual.
+
+  [ Ubuntu daily release ]
+  * Automatic snapshot from revision 251
+
+ -- Ubuntu daily release <ps-jenkins@lists.canonical.com>  Mon, 25 Nov 2013 03:56:49 +0000
+
+libdbusmenu-qt (0.9.2+13.10.20130826-0ubuntu1) saucy; urgency=low
+
+  [ Joe Yasi ]
+  * This fixes bug #1035755, [firefox] Extension causes context/drop
+    down menus to disappear. The patch uses the correct X11 event
+    timestamp instead of the system time. (LP: #1035755)
+
+  [ Ubuntu daily release ]
+  * Automatic snapshot from revision 248
+
+ -- Ubuntu daily release <ps-jenkins@lists.canonical.com>  Mon, 26 Aug 2013 10:07:36 +0000
+
+libdbusmenu-qt (0.9.2+13.10.20130628-0ubuntu1) saucy; urgency=low
+
+  [ Łukasz 'sil2100' Zemczak ]
+  * Fix the pkg-config file for the libdbusmenu-qt5 case, as the
+    includedir was missing the correct QT_SUFFIX.
+
+  [ Ubuntu daily release ]
+  * Automatic snapshot from revision 246
+
+ -- Ubuntu daily release <ps-jenkins@lists.canonical.com>  Fri, 28 Jun 2013 02:03:12 +0000
+
+libdbusmenu-qt (0.9.2daily13.05.02-0ubuntu1) saucy; urgency=low
+
+  [ Łukasz 'sil2100' Zemczak ]
+  * debian/control, debian/rules:
+    - Modifications related to compliance with our packaging standards
+
+  [ Ubuntu daily release ]
+  * Automatic snapshot from revision 244
+
+ -- Ubuntu daily release <ps-jenkins@lists.canonical.com>  Thu, 02 May 2013 22:59:30 +0000
+
+libdbusmenu-qt (0.9.2daily13.03.28-0ubuntu1) raring; urgency=low
+
+  [ Łukasz 'sil2100' Zemczak ]
+  * Add inline packaging metadata.
+  * debian/control, 
+    debian/libdbusmenu-qt5.install,
+    debian/libdbusmenu-qt5-dev.install,
+    debian/libdbusmenu-qt5-doc.install:
+    - Add the -qt5 package versions of all libdbusmenu packages
+  * debian/rules:
+    - Enable a double build - first build a Qt4 version of libdbusmenu for
+      libdbusmenu-qt2 and then a Qt5 version for libdbusmenu-qt5
+
+  [ Mathieu Trudel-Lapierre ]
+  * debian/copyright: fix copyright stanza for LGPL 2.
+  * debian/control: bump debhelper Build-Depends to 9.
+  * debian/source.lintian-overrides: drop the override.
+  * debian/watch: dropped; no longer needed with inline packaging.
+  * debian/control:
+    - bump Standards-Version to 3.9.4. 
+    - add Vcs-Bzr/Vcs-Browser.
+    - add comments for developers.
+  * Automatic snapshot from revision 240 (bootstrap). (LP: #1126205)
+
+  [ Ubuntu daily release ]
+  * Automatic snapshot from revision 241
+
+ -- Ubuntu daily release <ps-jenkins@lists.canonical.com>  Thu, 28 Mar 2013 20:27:01 +0000
+
+libdbusmenu-qt (0.9.2-0ubuntu4) raring; urgency=low
+
+  * Mark two library symbols as optional, fixing build failure with GCC 4.8.
+
+ -- Matthias Klose <doko@ubuntu.com>  Tue, 26 Feb 2013 10:02:51 +0100
+
+libdbusmenu-qt (0.9.2-0ubuntu3) quantal; urgency=low
+
+  * Update symbols file again for powerpc and armel to fix FTBFS on those
+    archs
+
+ -- Scott Kitterman <scott@kitterman.com>  Mon, 24 Sep 2012 11:56:51 -0400
+
+libdbusmenu-qt (0.9.2-0ubuntu2) quantal; urgency=low
+
+  * Update symbols file to fix FTBFS
+
+ -- Scott Kitterman <scott@kitterman.com>  Mon, 24 Sep 2012 09:58:44 -0400
+
+libdbusmenu-qt (0.9.2-0ubuntu1) precise; urgency=low
+
+  * New upstream release.
+
+ -- Aurélien Gâteau <aurelien.gateau@ubuntu.com>  Thu, 29 Mar 2012 17:51:21 +0200
+
+libdbusmenu-qt (0.9.1-0ubuntu1) precise; urgency=low
+
+  * New upstream release.
+
+ -- Aurélien Gâteau <aurelien.gateau@ubuntu.com>  Mon, 26 Mar 2012 15:36:30 +0200
+
+libdbusmenu-qt (0.9.0-2ubuntu1) precise; urgency=low
+
+  * Merge from Debian git, remaining changes:
+    - patches/002-use-multiarch-lib-paths: fixed author
+    - copyright: fixed "Source" field
+
+ -- Aurélien Gâteau <aurelien.gateau@ubuntu.com>  Thu, 24 Nov 2011 16:32:30 +0100
+
+libdbusmenu-qt (0.9.0-2) UNRELEASED; urgency=low
+
+  * update description for -dev package too. (Closes: #640251)
+  * Implement multiarch.
+    - bump cmake build dependency to 2.8.5.
+    - bump debhelper build dependency to 8.1.3.
+  * debian/rules: move --parallel after $@ 
+  * New binary package libdbusmenu-qt-doc.
+  * Remove embedded jquery and depend on libjs-jquery.
+
+ -- Praveen Arimbrathodiyil <pravi.a@gmail.com>  Thu, 01 Sep 2011 19:29:20 +0530
+
+libdbusmenu-qt (0.9.0-1) unstable; urgency=low
+
+  * New upstream release.
+  * Add doxygen as build dependency.
+  * Minor fixes to description, thanks to Filipus Klutiero. (Closes: #630193)
+  * Update symbols file.
+
+ -- Praveen Arimbrathodiyil <pravi.a@gmail.com>  Thu, 01 Sep 2011 12:57:01 +0530
+
+libdbusmenu-qt (0.9.0-0ubuntu2) oneiric; urgency=low
+
+  * Remove build-dependency on libqjson-dev.
+  * Convert to multiarch. (LP: #838470)
+  * debian/patches/kubuntu_03_multiarch_support.diff:
+    - add multiarch support to upstream CMake
+
+ -- Aurélien Gâteau <aurelien.gateau@canonical.com>  Wed, 07 Sep 2011 13:05:40 +0200
+
+libdbusmenu-qt (0.9.0-0ubuntu1) oneiric; urgency=low
+
+  * New upstream release.
+  * debian/control:
+    - Update Vcs-Bzr to current kubuntu-packagers location
+  * debian/libdbusmenu-qt2.symbols:
+    - updated
+
+ -- Didier Roche <didrocks@ubuntu.com>  Fri, 02 Sep 2011 13:50:43 +0200
+
+libdbusmenu-qt (0.8.3-0ubuntu1) oneiric; urgency=low
+
+  * New upstream release
+  * debian/control:
+    - Update Vcs-Bzr to current kubuntu-packagers location
+    - Change HomePage to launchpad page
+    - Update Standards-Version
+  * debian/copyright, debian/watch:
+    - point now to launchpad
+  * remove debian/patches/kubuntu_03_dont-show-more-icons-than-desired.diff:
+    upstreamed
+  * debian/libdbusmenu-qt2.symbols:
+    - updated symbols
+
+ -- Didier Roche <didrocks@ubuntu.com>  Wed, 22 Jun 2011 11:13:07 +0200
+
+libdbusmenu-qt (0.8.2-0ubuntu2) natty; urgency=low
+
+  * Add kubuntu_03_dont-show-more-icons-than-desired.diff by Michael Terry
+    https://code.launchpad.net/~mterry/libdbusmenu-qt/dont-show-more-
+    icons-than-desired/+merge/58387 'Respect QAction's setting for
+    whether an icon should be shown in the menu or not.'
+
+ -- Jonathan Riddell <jriddell@ubuntu.com>  Wed, 20 Apr 2011 13:47:45 +0100
+
+libdbusmenu-qt (0.8.2-0ubuntu1) natty; urgency=low
+
+  * New upstream release
+
+ -- Jonathan Riddell <jriddell@ubuntu.com>  Fri, 15 Apr 2011 12:07:57 +0100
+
+libdbusmenu-qt (0.8.1-0ubuntu1) natty; urgency=low
+
+  * New upstream release
+
+ -- Jonathan Riddell <jriddell@ubuntu.com>  Thu, 24 Mar 2011 16:40:25 +0000
+
+libdbusmenu-qt (0.8.0-3) unstable; urgency=low
+
+  * gcc seems to still emit some random symbols on some arches. Mark them as
+    optional and arch specific. (Closes: #628745)
+
+ -- Modestas Vainius <modax@debian.org>  Wed, 01 Jun 2011 13:24:35 +0300
+
+libdbusmenu-qt (0.8.0-2) unstable; urgency=low
+
+  * Fix symbol files to build against gcc 4.6. (Closes: #628745)
+  * Bump Standards-Version to 3.9.2: no changes needed.
+  * Add myself to Uploaders.
+
+ -- Modestas Vainius <modax@debian.org>  Wed, 01 Jun 2011 11:23:55 +0300
+
+libdbusmenu-qt (0.8.0-1) unstable; urgency=low
+
+  * New upstream release (3 private symbols dropped).
+  * Symbols file updated, missing symbols removed.
+  * Thanks to Shravan Aras for helping me with cscope.
+
+ -- Praveen Arimbrathodiyil <pravi.a@gmail.com>  Sat, 02 Apr 2011 23:22:22 +0530
+
+libdbusmenu-qt (0.8.0-0ubuntu1) natty; urgency=low
+
+  * New upstream release
+  * Remove kubuntu_00_external_contributions.diff no longer required
+  * Update kubuntu_01_no_test.diff
+
+ -- Jonathan Riddell <jriddell@ubuntu.com>  Fri, 25 Feb 2011 10:39:05 +0000
+
+libdbusmenu-qt (0.7.0-1) unstable; urgency=low
+
+  * New upstream release. 
+  * DM upload is allowed.
+  * adding libqjson-dev to build dependencies (for testapp). 
+
+ -- Praveen Arimbrathodiyil <pravi.a@gmail.com>  Sat, 19 Mar 2011 11:03:22 +0530
+
+libdbusmenu-qt (0.7.0-0ubuntu1) natty; urgency=low
+
+  * New upstream release
+
+ -- Jonathan Riddell <jriddell@ubuntu.com>  Fri, 14 Jan 2011 15:56:38 +0000
+
+libdbusmenu-qt (0.6.6-1) unstable; urgency=low
+
+  * New upstream release. (Closes: #606199)
+
+  [ Praveen Arimbrathodiyil ]
+  * debian/rules: added support for parallel building.
+  * debian/copyright: cleared copyright info of removed patch.
+  * debian/control: changed homepage to page on launchpad.net.
+  * debian/control: updated vcs from svn to git.
+  * debian/control: updated policy to 3.9.1
+
+  [ José Manuel Santamaría Lema ]
+  * Update symbols file.
+
+ -- Praveen Arimbrathodiyil <pravi.a@gmail.com>  Sun, 27 Feb 2011 07:05:33 +0530
+
+libdbusmenu-qt (0.6.6-0ubuntu1) natty; urgency=low
+
+  * New upstream bugfix release
+
+ -- Jonathan Riddell <jriddell@ubuntu.com>  Thu, 09 Dec 2010 11:59:53 +0000
+
+libdbusmenu-qt (0.6.4-0ubuntu1) maverick; urgency=low
+
+  * New upstream bugfix release
+  * Refresh kubuntu_00_external_contributions.diff
+
+ -- Jonathan Riddell <jriddell@ubuntu.com>  Thu, 23 Sep 2010 13:45:17 +0100
+
+libdbusmenu-qt (0.6.3-0ubuntu1) maverick; urgency=low
+
+  * New upstream release
+  * Add kubuntu_00_external_contributions.diff
+  * Remove kubuntu_02_unbreak_kde_titles.diff
+
+ -- Aurélien Gâteau <aurelien.gateau@canonical.com>  Thu, 16 Sep 2010 17:01:41 +0200
+
+libdbusmenu-qt (0.6.2-0ubuntu1) maverick; urgency=low
+
+  * New upstream release, memory leak and crash fixes
+  * Add kubuntu_02_unbreak_kde_titles.diff from
+    http://gitorious.org/dbusmenu/dbusmenu-
+    qt/commit/71851809ef7e109f02635877ead1dbac48a2e64e
+
+ -- Jonathan Riddell <jriddell@ubuntu.com>  Thu, 09 Sep 2010 16:42:01 +0100
+
+libdbusmenu-qt (0.6.1-0ubuntu1) maverick; urgency=low
+
+  * New upstream release, fixes memory leaks
+
+ -- Jonathan Riddell <jriddell@ubuntu.com>  Fri, 03 Sep 2010 17:14:45 +0100
+
+libdbusmenu-qt (0.6.0-0ubuntu1) maverick; urgency=low
+
+  * New upstream release
+
+ -- Jonathan Riddell <jriddell@ubuntu.com>  Thu, 19 Aug 2010 14:58:03 +0100
+
+libdbusmenu-qt (0.5.2-0ubuntu1) maverick; urgency=low
+
+  * New upstream release
+
+ -- Jonathan Riddell <jriddell@ubuntu.com>  Thu, 05 Aug 2010 16:08:10 +0100
+
+libdbusmenu-qt (0.5.1-1) unstable; urgency=low
+
+  * New upstream release. (Closes: #587546)
+  * debian/watch: changed release url to new location.
+  * debian/copyright: made it machine readable.
+  * debian/copyright: changed debian packaging to LGPL-2+ in sync with
+    upstream license (Thanks to Sune Vuorela)
+  * debian/control: updated policy to 3.9.0
+  * debian/libdbusmenu-qt2.symbols: new symbols added.
+  * debian/control: dev & lib package clarification added to descriptions.
+
+ -- Praveen Arimbrathodiyil <pravi.a@gmail.com>  Fri, 02 Jul 2010 01:30:47 +0530
+
+libdbusmenu-qt (0.5.1-0ubuntu1) maverick; urgency=low
+
+  * New upstream release
+
+ -- Jonathan Riddell <jriddell@ubuntu.com>  Thu, 08 Jul 2010 21:07:52 +0100
+
+libdbusmenu-qt (0.5.0-0ubuntu1) maverick; urgency=low
+
+  * New upstream release
+
+ -- Jonathan Riddell <jriddell@ubuntu.com>  Mon, 28 Jun 2010 16:54:14 +0100
+
+libdbusmenu-qt (0.4.0-0ubuntu1) maverick; urgency=low
+
+  * New upstream release
+
+ -- Jonathan Riddell <jriddell@ubuntu.com>  Fri, 25 Jun 2010 14:20:55 +0100
+
+libdbusmenu-qt (0.3.5-0ubuntu1) maverick; urgency=low
+
+  * New upstream release
+
+ -- Jonathan Riddell <jriddell@ubuntu.com>  Thu, 17 Jun 2010 14:30:00 +0100
+
+libdbusmenu-qt (0.3.4-0ubuntu1) maverick; urgency=low
+
+  * New upstream release
+
+ -- Jonathan Riddell <jriddell@ubuntu.com>  Thu, 10 Jun 2010 23:45:12 +0100
+
+libdbusmenu-qt (0.3.3-1) unstable; urgency=low
+
+  * New upstream release.
+  * added libdbusmenu-qt2.symbols file.
+
+ -- Praveen Arimbrathodiyil <pravi.a@gmail.com>  Fri, 11 Jun 2010 17:13:07 +0530
+
+libdbusmenu-qt (0.3.3-0ubuntu1) maverick; urgency=low
+
+  * New upstream release:
+    - Update kubuntu_01_no_test.diff
+
+ -- Jonathan Thomas <echidnaman@kubuntu.org>  Mon, 07 Jun 2010 22:36:42 -0400
+
+libdbusmenu-qt (0.3.2-1) unstable; urgency=low
+
+  * Initial release. (Closes: #579677)
+
+ -- Praveen Arimbrathodiyil <pravi.a@gmail.com>  Tue, 04 May 2010 13:31:09 +0530
+
+libdbusmenu-qt (0.3.2-0ubuntu1) lucid; urgency=low
+
+  * New upstream release
+
+ -- Jonathan Riddell <jriddell@ubuntu.com>  Thu, 08 Apr 2010 14:23:59 +0100
+
+libdbusmenu-qt (0.3.0-0ubuntu1) lucid; urgency=low
+
+  * New upstream release
+
+ -- Jonathan Riddell <jriddell@ubuntu.com>  Wed, 10 Mar 2010 12:41:06 +0000
+
+libdbusmenu-qt (0.2.2-0ubuntu2) lucid; urgency=low
+
+  * Add build dep on qjson and pkg-config
+
+ -- Jonathan Riddell <jriddell@ubuntu.com>  Wed, 17 Feb 2010 17:02:07 +0000
+
+libdbusmenu-qt (0.2.2-0ubuntu1) lucid; urgency=low
+
+  * New upstream release
+
+ -- Jonathan Riddell <jriddell@ubuntu.com>  Wed, 17 Feb 2010 15:13:29 +0000
+
+libdbusmenu-qt (0.2.1-0ubuntu2) lucid; urgency=low
+
+  * libdbusmenu-qt-dev depends on libdbusmenu-qt1 not 0
+
+ -- Jonathan Riddell <jriddell@ubuntu.com>  Thu, 04 Feb 2010 20:21:41 +0000
+
+libdbusmenu-qt (0.2.1-0ubuntu1) lucid; urgency=low
+
+  * New upstream release
+  * Switch to source format 3.0
+  * New SO version, switch package name to libdbusmenu-qt1
+  * Add kubuntu_01_no_test.diff, tests don't work without X
+
+ -- Jonathan Riddell <jriddell@ubuntu.com>  Thu, 04 Feb 2010 18:13:44 +0000
+
+libdbusmenu-qt (0.1.0-0ubuntu1) lucid; urgency=low
+
+  * Initial Release
+
+ -- Jonathan Riddell <jriddell@ubuntu.com>  Sun, 03 Jan 2010 23:21:24 +0100
+
diff --git a/debian/compat b/debian/compat
new file mode 100644 (file)
index 0000000..ec63514
--- /dev/null
@@ -0,0 +1 @@
+9
diff --git a/debian/control b/debian/control
new file mode 100644 (file)
index 0000000..2732252
--- /dev/null
@@ -0,0 +1,102 @@
+Source: libdbusmenu-qt
+Section: libs
+Priority: optional
+Build-Depends: cmake (>= 2.8.11),
+               debhelper (>= 9),
+               doxygen,
+               libqjson-dev,
+               libqt4-dev,
+               qtbase5-dev,
+Maintainer: Kubuntu Developers <kubuntu-devel@lists.ubuntu.com>
+XSBC-Original-Maintainer: Jonathan Riddell <jriddell@ubuntu.com>
+Standards-Version: 3.9.4
+Homepage: https://launchpad.net/libdbusmenu-qt
+# If you aren't ~dbusmenu-team but need to upload packaging changes,
+# just go ahead.  ~dbusmenu-team will notice and sync up the code again.
+Vcs-Bzr: https://code.launchpad.net/~dbusmenu-team/libdbusmenu-qt/trunk
+Vcs-Browser: https://bazaar.launchpad.net/~dbusmenu-team/libdbusmenu-qt/trunk/files
+
+Package: libdbusmenu-qt2
+Architecture: any
+Pre-Depends: ${misc:Pre-Depends},
+Depends: ${misc:Depends},
+         ${shlibs:Depends},
+Multi-Arch: same
+Description: Qt implementation of the DBusMenu protocol
+ 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.
+ .
+ This package provides shared libraries.
+
+Package: libdbusmenu-qt5
+Architecture: any
+Pre-Depends: ${misc:Pre-Depends},
+Depends: ${misc:Depends},
+         ${shlibs:Depends},
+Suggests: libqt5dbus5,
+          libqt5gui5,
+          libqt5widgets5,
+Multi-Arch: same
+Description: Qt5 implementation of the DBusMenu protocol
+ This library provides a Qt5 implementation of the DBusMenu protocol.
+ .
+ The DBusMenu protocol makes it possible for applications to export
+ and import their menus over DBus.
+ .
+ This package provides shared libraries.
+
+Package: libdbusmenu-qt-dev
+Section: libdevel
+Architecture: any
+Depends: libdbusmenu-qt2 (= ${binary:Version}),
+         libqt4-dev,
+         ${misc:Depends},
+Description: Qt implementation of the DBusMenu protocol (development)
+ 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.
+ .
+ This package provides header files for development.
+
+Package: libdbusmenu-qt5-dev
+Section: libdevel
+Architecture: any
+Depends: libdbusmenu-qt5 (= ${binary:Version}),
+         qtbase5-dev,
+         ${misc:Depends},
+Description: Qt5 implementation of the DBusMenu protocol (development)
+ This library provides a Qt5 implementation of the DBusMenu protocol.
+ .
+ The DBusMenu protocol makes it possible for applications to export
+ and import their menus over DBus.
+ .
+ This package provides header files for development.
+
+Package: libdbusmenu-qt-doc
+Section: doc
+Architecture: all
+Depends: libjs-jquery,
+         ${misc:Depends},
+Description: Qt implementation of the DBusMenu protocol (documentation)
+ 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.
+ .
+ This package provides API documentation in html format.
+
+Package: libdbusmenu-qt5-doc
+Section: doc
+Architecture: all
+Depends: libjs-jquery,
+         ${misc:Depends},
+Description: Qt5 implementation of the DBusMenu protocol (documentation)
+ This library provides a Qt5 implementation of the DBusMenu protocol.
+ .
+ The DBusMenu protocol makes it possible for applications to export
+ and import their menus over DBus.
+ .
+ This package provides API documentation in html format.
diff --git a/debian/copyright b/debian/copyright
new file mode 100644 (file)
index 0000000..d2bc39a
--- /dev/null
@@ -0,0 +1,32 @@
+Format: http://dep.debian.net/deps/dep5/
+Upstream-Name: libdbusmenu-qt
+Upstream-Contact: Aurelien Gateau <aurelien.gateau@canonical.com>
+Source: https://launchpad.net/libdbusmenu-qt
+
+Files: src/*
+Copyright: 2009-2010, Canonical
+Author: Aurelien Gateau <aurelien.gateau@canonical.com>
+License: LGPL-2
+
+Files: debian/*
+Copyright: 2010, Praveen Arimbrathodiyil <pravi.a@gmail.com>
+License: LGPL-2
+
+License: LGPL-2
+ This library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Library General Public
+ License (LGPL), version 2 as published by the Free Software
+ Foundation.
+ .
+ This library is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ Library General Public License for more details.
+ .
+ You should have received a copy of the GNU Library General Public License
+ along with this library; see the file COPYING.LIB.  If not, write to
+ the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ Boston, MA 02110-1301, USA.
+ .
+ On Debian systems, the complete text of the GNU Library General Public
+ License, version 2, can be found in /usr/share/common-licenses/LGPL-2.
diff --git a/debian/docs b/debian/docs
new file mode 100644 (file)
index 0000000..50bd824
--- /dev/null
@@ -0,0 +1,2 @@
+NEWS
+README
diff --git a/debian/libdbusmenu-qt-dev.install b/debian/libdbusmenu-qt-dev.install
new file mode 100644 (file)
index 0000000..11b79c4
--- /dev/null
@@ -0,0 +1,4 @@
+usr/include/dbusmenu-qt/*
+usr/lib/*/lib*-qt.so
+usr/lib/*/pkgconfig/dbusmenu-qt.pc
+usr/lib/*/cmake/dbusmenu-qt/*
diff --git a/debian/libdbusmenu-qt-doc.install b/debian/libdbusmenu-qt-doc.install
new file mode 100644 (file)
index 0000000..6ad6a73
--- /dev/null
@@ -0,0 +1 @@
+usr/share/doc/libdbusmenu-qt-doc/
diff --git a/debian/libdbusmenu-qt2.install b/debian/libdbusmenu-qt2.install
new file mode 100644 (file)
index 0000000..4881f23
--- /dev/null
@@ -0,0 +1 @@
+usr/lib/*/libdbusmenu-qt.so.2*
diff --git a/debian/libdbusmenu-qt5-dev.install b/debian/libdbusmenu-qt5-dev.install
new file mode 100644 (file)
index 0000000..4d6f030
--- /dev/null
@@ -0,0 +1,4 @@
+usr/include/dbusmenu-qt5/*
+usr/lib/*/lib*-qt5.so
+usr/lib/*/pkgconfig/dbusmenu-qt5.pc
+usr/lib/*/cmake/dbusmenu-qt5/*
diff --git a/debian/libdbusmenu-qt5-doc.install b/debian/libdbusmenu-qt5-doc.install
new file mode 100644 (file)
index 0000000..323157f
--- /dev/null
@@ -0,0 +1 @@
+usr/share/doc/libdbusmenu-qt5-doc/
diff --git a/debian/libdbusmenu-qt5.install b/debian/libdbusmenu-qt5.install
new file mode 100644 (file)
index 0000000..00d460a
--- /dev/null
@@ -0,0 +1 @@
+usr/lib/*/libdbusmenu-qt5.so.2*
diff --git a/debian/rules b/debian/rules
new file mode 100755 (executable)
index 0000000..98fe3ca
--- /dev/null
@@ -0,0 +1,45 @@
+#!/usr/bin/make -f
+
+# Uncomment this to turn on verbose mode.
+#export DH_VERBOSE=1
+
+export DPKG_GENSYMBOLS_CHECK_LEVEL=4
+
+%:
+       dh $@ --parallel --buildsystem=cmake
+
+override_dh_auto_configure:
+       mkdir qt4
+       mkdir qt5
+       cd qt4 && QT_SELECT=qt4 cmake -DCMAKE_INSTALL_PREFIX=/usr -DUSE_QT4=true -DCMAKE_BUILD_TYPE=RelWithDebInfo ../
+       cd qt5 && QT_SELECT=qt5 cmake -DCMAKE_INSTALL_PREFIX=/usr -DUSE_QT5=true -DCMAKE_BUILD_TYPE=RelWithDebInfo ../
+
+override_dh_auto_build:
+       cd qt4 && make
+       cd qt5 && make
+
+override_dh_clean:
+       dh_clean
+       rm -rf qt4
+       rm -rf qt5
+
+override_dh_auto_install:
+       cd qt4 && make DESTDIR=../debian/tmp install
+       cd qt5 && make DESTDIR=../debian/tmp install
+       echo "Removing embedded jquery javascript library..."
+       rm debian/tmp/usr/share/doc/libdbusmenu-qt-doc/jquery.js
+       rm debian/tmp/usr/share/doc/libdbusmenu-qt5-doc/jquery.js
+
+override_dh_install:
+       dh_install --fail-missing
+
+override_dh_auto_test:
+       echo "Skipping tests (can't test inside chroot)..."
+
+override_dh_gencontrol:
+       # Ugly hack, since we don't want to have Qt5 as our depends, we prefer
+       # those as Suggests for now
+       sed -i '/^shlibs/s/libqt5[^,]*, //g' debian/libdbusmenu-qt5.substvars
+       sed -i '/^shlibs/s/,[^,]* libqt5.*$$//' debian/libdbusmenu-qt5.substvars
+
+       dh_gencontrol
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
new file mode 100644 (file)
index 0000000..2c4a4e8
--- /dev/null
@@ -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 <QtGui/QIcon>
+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_INTERFACE:${INCLUDE_INSTALL_DIR}>")
+
+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 (file)
index 0000000..b04afa6
--- /dev/null
@@ -0,0 +1,365 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+A library to allow applications to provide simple indications of
+information to be displayed to users of the application through the
+interface shell.
+
+Copyright 2009 Canonical Ltd.
+
+Authors:
+    Ted Gould <ted@canonical.com>
+    Aurélien Gâteau <aurelien.gateau@canonical.com>
+
+This program is free software: you can redistribute it and/or modify it 
+under the terms of either or both of the following licenses:
+
+1) the GNU Lesser General Public License version 3, as published by the 
+Free Software Foundation; and/or
+2) the GNU Lesser General Public License version 2.1, as published by 
+the Free Software Foundation.
+
+This program is distributed in the hope that it will be useful, but 
+WITHOUT ANY WARRANTY; without even the implied warranties of 
+MERCHANTABILITY, SATISFACTORY QUALITY or FITNESS FOR A PARTICULAR 
+PURPOSE.  See the applicable version of the GNU Lesser General Public 
+License for more details.
+
+You should have received a copy of both the GNU Lesser General Public 
+License version 3 and version 2.1 along with this program.  If not, see 
+<http://www.gnu.org/licenses/>
+-->
+<node name="/" xmlns:dox="http://www.canonical.com/dbus/dox.dtd">
+    <dox:d><![CDATA[
+    @mainpage
+
+    The goal of DBusMenu is to expose menus on DBus.
+    
+    Main interface is documented here: @ref com::canonical::dbusmenu
+    ]]></dox:d>
+       <interface name="com.canonical.dbusmenu">
+               <dox:d><![CDATA[
+               A DBus interface to expose menus on DBus.
+
+               Menu items are represented with a unique numeric id and a dictionary of
+               properties.
+
+               To reduce the amount of DBus traffic, a property should only be returned
+               if its value is not the default value.
+
+               Available properties are:
+
+               <table>
+               <tr>
+                       <th>Name</th>
+                       <th>Type</th>
+                       <th>Description</th>
+                       <th>Default Value</th>
+               </tr>
+               <tr>
+                       <td>type</td>
+                       <td>String</td>
+                       <td>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-<vendor>-".
+                       </td>
+                       <td>"standard"</td>
+               </tr>
+               <tr>
+                       <td>label</td>
+                       <td>string</td>
+                       <td>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.
+                       </td>
+                       <td>""</td>
+               </tr>
+               <tr>
+                       <td>enabled</td>
+                       <td>boolean</td>
+                       <td>Whether the item can be activated or not.</td>
+                       <td>true</td>
+               </tr>
+               <tr>
+                       <td>visible</td>
+                       <td>boolean</td>
+                       <td>True if the item is visible in the menu.</td>
+                       <td>true</td>
+               </tr>
+               <tr>
+                       <td>icon-name</td>
+                       <td>string</td>
+                       <td>Icon name of the item, following the freedesktop.org icon spec.</td>
+                       <td>""</td>
+               </tr>
+               <tr>
+                       <td>icon-data</td>
+                       <td>binary</td>
+                       <td>PNG data of the icon.</td>
+                       <td>Empty</td>
+               </tr>
+               <tr>
+                       <td>shortcut</td>
+                       <td>array of arrays of strings</td>
+                       <td>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"]]</td>
+                       <td>Empty</td>
+               </tr>
+               <tr>
+                       <td>toggle-type</td>
+                       <td>string</td>
+                       <td>
+                       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
+                       </td>
+                       <td>""</td>
+               </tr>
+               <tr>
+                       <td>toggle-state</td>
+                       <td>int</td>
+                       <td>
+                       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.
+                       </td>
+                       <td>-1</td>
+               </tr>
+               <tr>
+                       <td>children-display</td>
+                       <td>string</td>
+                       <td>
+                       If the menu item has children this property should be set to
+                       "submenu".
+                       </td>
+                       <td>""</td>
+               </tr>
+               </table>
+
+               Vendor specific properties can be added by prefixing them with
+               "x-<vendor>-".
+               ]]></dox:d>
+
+<!-- Properties -->
+               <property name="Version" type="u" access="read">
+                       <dox:d>
+                       Provides the version of the DBusmenu API that this API is
+                       implementing.
+                       </dox:d>
+               </property>
+
+        <property name="Status" type="s" access="read">
+            <dox:d>
+            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.
+            </dox:d>
+        </property>
+
+<!-- Functions -->
+
+               <method name="GetLayout">
+                       <annotation name="com.trolltech.QtDBus.QtTypeName.Out1" value="DBusMenuLayoutItem"/>
+                       <dox:d>
+                         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.
+                       </dox:d>
+                       <arg type="i" name="parentId" direction="in">
+                               <dox:d>The ID of the parent node for the layout.  For
+                               grabbing the layout from the root node use zero.</dox:d>
+                       </arg>
+                       <arg type="i" name="recursionDepth" direction="in">
+                               <dox:d>
+                                 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.
+                               </dox:d>
+                       </arg>
+                       <arg type="as" name="propertyNames" direction="in" >
+                               <dox:d>
+                                       The list of item properties we are
+                                       interested in.  If there are no entries in the list all of
+                                       the properties will be sent.
+                               </dox:d>
+                       </arg>
+                       <arg type="u" name="revision" direction="out">
+                               <dox:d>The revision number of the layout.  For matching
+                               with layoutUpdated signals.</dox:d>
+                       </arg>
+                       <arg type="(ia{sv}av)" name="layout" direction="out">
+                               <dox:d>The layout, as a recursive structure.</dox:d>
+                       </arg>
+               </method>
+
+               <method name="GetGroupProperties">
+                       <annotation name="com.trolltech.QtDBus.QtTypeName.In0" value="QList&lt;int&gt;"/>
+                       <annotation name="com.trolltech.QtDBus.QtTypeName.Out0" value="DBusMenuItemList"/>
+                       <dox:d>
+                       Returns the list of items which are children of @a parentId.
+                       </dox:d>
+                       <arg type="ai" name="ids" direction="in" >
+                               <dox:d>
+                                       A list of ids that we should be finding the properties
+                                       on.  If the list is empty, all menu items should be sent.
+                               </dox:d>
+                       </arg>
+                       <arg type="as" name="propertyNames" direction="in" >
+                               <dox:d>
+                                       The list of item properties we are
+                                       interested in.  If there are no entries in the list all of
+                                       the properties will be sent.
+                               </dox:d>
+                       </arg>
+                       <arg type="a(ia{sv})" name="properties" direction="out" >
+                               <dox:d>
+                                       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
+                               </dox:d>
+                       </arg>
+               </method>
+
+               <method name="GetProperty">
+                       <dox:d>
+                         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.
+                       </dox:d>
+                       <arg type="i" name="id" direction="in">
+                               <dox:d>the id of the item which received the event</dox:d>
+                       </arg>
+                       <arg type="s" name="name" direction="in">
+                               <dox:d>the name of the property to get</dox:d>
+                       </arg>
+                       <arg type="v" name="value" direction="out">
+                               <dox:d>the value of the property</dox:d>
+                       </arg>
+               </method>
+
+               <method name="Event">
+                       <dox:d><![CDATA[
+                       This is called by the applet to notify the application an event happened on a
+                       menu item.
+
+                       @a type can be one of the following:
+
+                       @li "clicked"
+                       @li "hovered"
+
+                       Vendor specific events can be added by prefixing them with "x-<vendor>-"
+                       ]]></dox:d>
+                       <arg type="i" name="id" direction="in" >
+                               <dox:d>the id of the item which received the event</dox:d>
+                       </arg>
+                       <arg type="s" name="eventId" direction="in" >
+                               <dox:d>the type of event</dox:d>
+                       </arg>
+                       <arg type="v" name="data" direction="in" >
+                               <dox:d>event-specific data</dox:d>
+                       </arg>
+                       <arg type="u" name="timestamp" direction="in" >
+                               <dox:d>The time that the event occured if available or the time the message was sent if not</dox:d>
+                       </arg>
+               </method>
+
+               <method name="AboutToShow">
+                       <dox:d>
+                       This is called by the applet to notify the application that it is about
+                       to show the menu under the specified item.
+                       </dox:d>
+                       <arg type="i" name="id" direction="in">
+                               <dox:d>
+                               Which menu item represents the parent of the item about to be shown.
+                               </dox:d>
+                       </arg>
+                       <arg type="b" name="needUpdate" direction="out">
+                               <dox:d>
+                               Whether this AboutToShow event should result in the menu being updated.
+                               </dox:d>
+                       </arg>
+               </method>
+
+<!-- Signals -->
+               <signal name="ItemsPropertiesUpdated">
+                       <annotation name="com.trolltech.QtDBus.QtTypeName.In0" value="DBusMenuItemList"/>
+                       <annotation name="com.trolltech.QtDBus.QtTypeName.In1" value="DBusMenuItemKeysList"/>
+                       <dox:d>
+                       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.
+                       </dox:d>
+                       <arg type="a(ia{sv})" name="updatedProps" direction="out" />
+                       <arg type="a(ias)" name="removedProps" direction="out" />
+               </signal>
+
+               <signal name="LayoutUpdated">
+                       <dox:d>
+                       Triggered by the application to notify display of a layout update, up to
+                       revision
+                       </dox:d>
+                       <arg type="u" name="revision" direction="out" >
+                               <dox:d>The revision of the layout that we're currently on</dox:d>
+                       </arg>
+                       <arg type="i" name="parent" direction="out" >
+                               <dox:d>
+                               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.
+                               </dox:d>
+                       </arg>
+               </signal>
+               <signal name="ItemActivationRequested">
+                       <dox:d>
+                         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.
+                       </dox:d>
+                       <arg type="i" name="id" direction="out" >
+                               <dox:d>ID of the menu that should be activated</dox:d>
+                       </arg>
+                       <arg type="u" name="timestamp" direction="out" >
+                               <dox:d>The time that the event occured</dox:d>
+                       </arg>
+               </signal>
+
+<!-- End of interesting stuff -->
+
+       </interface>
+</node>
diff --git a/src/dbusmenu_config.h.in b/src/dbusmenu_config.h.in
new file mode 100644 (file)
index 0000000..c884fb7
--- /dev/null
@@ -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 (file)
index 0000000..62dbfdd
--- /dev/null
@@ -0,0 +1,37 @@
+/* This file is part of the dbusmenu-qt library
+   Copyright 2010 Canonical
+   Author: Aurelien Gateau <aurelien.gateau@canonical.com>
+
+   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 <dbusmenu_version.h>
+
+// Qt
+#include <QtCore/QtGlobal>
+
+#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 (file)
index 0000000..bc84092
--- /dev/null
@@ -0,0 +1,94 @@
+/* This file is part of the dbusmenu-qt library
+   Copyright 2009 Canonical
+   Author: Aurelien Gateau <aurelien.gateau@canonical.com>
+
+   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 <QAction>
+#include <QActionEvent>
+#include <QMenu>
+
+// 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<QActionEvent *>(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 (file)
index 0000000..8e6d315
--- /dev/null
@@ -0,0 +1,59 @@
+/* This file is part of the dbusmenu-qt library
+   Copyright 2009 Canonical
+   Author: Aurelien Gateau <aurelien.gateau@canonical.com>
+
+   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 <QEvent>
+#include <QObject>
+
+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 (file)
index 0000000..fddc664
--- /dev/null
@@ -0,0 +1,39 @@
+/* This file is part of the KDE libraries
+   Copyright 2010 Canonical
+   Author: Aurelien Gateau <aurelien.gateau@canonical.com>
+
+   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 (file)
index 0000000..f25718d
--- /dev/null
@@ -0,0 +1,506 @@
+/* This file is part of the dbusmenu-qt library
+   Copyright 2009 Canonical
+   Author: Aurelien Gateau <aurelien.gateau@canonical.com>
+
+   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 <QBuffer>
+#include <QDateTime>
+#include <QMap>
+#include <QMenu>
+#include <QSet>
+#include <QTimer>
+#include <QToolButton>
+#include <QWidgetAction>
+
+// 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<DBusMenu *>()) {
+        // 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<const QWidgetAction *>(action_);
+    DMRETURN_VALUE_IF_FAIL(widgetAction, map);
+    QToolButton *button = qobject_cast<QToolButton *>(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<QAction*>(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<QAction*> actions = menu->actions();
+    if (actions.isEmpty()) {
+        return;
+    }
+
+    QList<QAction*>::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 (file)
index 0000000..14950f3
--- /dev/null
@@ -0,0 +1,95 @@
+/* This file is part of the dbusmenu-qt library
+   Copyright 2009 Canonical
+   Author: Aurelien Gateau <aurelien.gateau@canonical.com>
+
+   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 <QtCore/QObject>
+#include <QtDBus/QDBusConnection>
+
+// Local
+#include <dbusmenu_export.h>
+
+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 (file)
index 0000000..006eec6
--- /dev/null
@@ -0,0 +1,186 @@
+/* This file is part of the dbusmenu-qt library
+   Copyright 2010 Canonical
+   Author: Aurelien Gateau <aurelien.gateau@canonical.com>
+
+   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 <QDBusMessage>
+#include <QMenu>
+#include <QVariant>
+
+// 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<int> &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 (file)
index 0000000..95fd00b
--- /dev/null
@@ -0,0 +1,76 @@
+/* This file is part of the dbusmenu-qt library
+   Copyright 2010 Canonical
+   Author: Aurelien Gateau <aurelien.gateau@canonical.com>
+
+   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 <dbusmenutypes_p.h>
+
+// Qt
+#include <QtCore/QObject>
+#include <QtCore/QVariant>
+#include <QtDBus/QDBusAbstractAdaptor>
+#include <QtDBus/QDBusVariant>
+
+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<int> &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 (file)
index 0000000..115eed1
--- /dev/null
@@ -0,0 +1,91 @@
+/* This file is part of the dbusmenu-qt library
+   Copyright 2010 Canonical
+   Author: Aurelien Gateau <aurelien.gateau@canonical.com>
+
+   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 <QtCore/QHash>
+#include <QtCore/QMap>
+#include <QtCore/QSet>
+#include <QtCore/QVariant>
+
+class QMenu;
+
+class DBusMenuExporterDBus;
+
+class DBusMenuExporterPrivate
+{
+public:
+    DBusMenuExporter *q;
+
+    QString m_objectPath;
+
+    DBusMenuExporterDBus *m_dbusObject;
+
+    QMenu *m_rootMenu;
+    QHash<QAction *, QVariantMap> m_actionProperties;
+    QMap<int, QAction *> m_actionForId;
+    QMap<QAction *, int> m_idForAction;
+    int m_nextId;
+    uint m_revision;
+    bool m_emittedLayoutUpdatedOnce;
+
+    QSet<int> m_itemUpdatedIds;
+    QTimer *m_itemUpdatedTimer;
+
+    QSet<int> 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 (file)
index 0000000..39f5995
--- /dev/null
@@ -0,0 +1,585 @@
+/* This file is part of the dbusmenu-qt library
+   Copyright 2009 Canonical
+   Author: Aurelien Gateau <aurelien.gateau@canonical.com>
+
+   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 <QCoreApplication>
+#include <QDBusConnection>
+#include <QDBusInterface>
+#include <QDBusReply>
+#include <QDBusVariant>
+#include <QFont>
+#include <QMenu>
+#include <QPointer>
+#include <QSignalMapper>
+#include <QTime>
+#include <QTimer>
+#include <QToolButton>
+#include <QWidgetAction>
+
+// Local
+#include "dbusmenutypes_p.h"
+#include "dbusmenushortcut_p.h"
+#include "debug_p.h"
+#include "utils_p.h"
+
+//#define BENCHMARK
+#ifdef BENCHMARK
+#include <QTime>
+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<int, QPointer<QAction> > ActionForId;
+    ActionForId m_actionForId;
+    QSignalMapper m_mapper;
+    QTimer *m_pendingLayoutUpdateTimer;
+
+    QSet<int> m_idsRefreshedByAboutToShow;
+    QSet<int> 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<QDBusArgument>();
+        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<QDBusPendingCallWatcher> 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<int> 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<uint, DBusMenuLayoutItem> 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<QMenu*>(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<QObject> 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<bool> 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<QMenu*>(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 (file)
index 0000000..dc7e773
--- /dev/null
@@ -0,0 +1,143 @@
+/* This file is part of the dbusmenu-qt library
+   Copyright 2009 Canonical
+   Author: Aurelien Gateau <aurelien.gateau@canonical.com>
+
+   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 <QtCore/QObject>
+
+// Local
+#include <dbusmenu_export.h>
+
+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 (file)
index 0000000..29d2e58
--- /dev/null
@@ -0,0 +1,85 @@
+/* This file is part of the dbusmenu-qt library
+   Copyright 2009 Canonical
+   Author: Aurelien Gateau <aurelien.gateau@canonical.com>
+
+   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 <QtGui/QKeySequence>
+
+// 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 (file)
index 0000000..e26dd8c
--- /dev/null
@@ -0,0 +1,43 @@
+/* This file is part of the dbusmenu-qt library
+   Copyright 2009 Canonical
+   Author: Aurelien Gateau <aurelien.gateau@canonical.com>
+
+   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 <QtCore/QMetaType>
+#include <QtCore/QStringList>
+
+// Local
+#include <dbusmenu_export.h>
+
+
+class QKeySequence;
+
+class DBUSMENU_EXPORT DBusMenuShortcut : public QList<QStringList>
+{
+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 (file)
index 0000000..73e68a0
--- /dev/null
@@ -0,0 +1,112 @@
+/* This file is part of the dbusmenu-qt library
+   Copyright 2009 Canonical
+   Author: Aurelien Gateau <aurelien.gateau@canonical.com>
+
+   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 <dbusmenushortcut_p.h>
+#include <debug_p.h>
+
+// Qt
+#include <QDBusArgument>
+#include <QDBusMetaType>
+
+//// 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<QDBusVariant>());
+    Q_FOREACH(const DBusMenuLayoutItem& child, obj.children) {
+        argument << QDBusVariant(QVariant::fromValue<DBusMenuLayoutItem>(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<QDBusArgument>();
+
+        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<DBusMenuItem>();
+    qDBusRegisterMetaType<DBusMenuItemList>();
+    qDBusRegisterMetaType<DBusMenuItemKeys>();
+    qDBusRegisterMetaType<DBusMenuItemKeysList>();
+    qDBusRegisterMetaType<DBusMenuLayoutItem>();
+    qDBusRegisterMetaType<DBusMenuLayoutItemList>();
+    qDBusRegisterMetaType<DBusMenuShortcut>();
+    registered = true;
+}
diff --git a/src/dbusmenutypes_p.h b/src/dbusmenutypes_p.h
new file mode 100644 (file)
index 0000000..6b4e560
--- /dev/null
@@ -0,0 +1,96 @@
+/* This file is part of the dbusmenu-qt library
+   Copyright 2009 Canonical
+   Author: Aurelien Gateau <aurelien.gateau@canonical.com>
+
+   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 <QtCore/QList>
+#include <QtCore/QStringList>
+#include <QtCore/QVariant>
+
+// Local
+#include <dbusmenu_export.h>
+
+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<DBusMenuItem> 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<DBusMenuItemKeys> 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<DBusMenuLayoutItem> children;
+};
+
+Q_DECLARE_METATYPE(DBusMenuLayoutItem)
+
+DBUSMENU_EXPORT QDBusArgument &operator<<(QDBusArgument &argument, const DBusMenuLayoutItem &);
+DBUSMENU_EXPORT const QDBusArgument &operator>>(const QDBusArgument &argument, DBusMenuLayoutItem &);
+
+typedef QList<DBusMenuLayoutItem> 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 (file)
index 0000000..bff37bd
--- /dev/null
@@ -0,0 +1,48 @@
+/* This file is part of the dbusmenu-qt library
+   Copyright 2009 Canonical
+   Author: Aurelien Gateau <aurelien.gateau@canonical.com>
+
+   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 <QDebug>
+
+#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 (file)
index 0000000..e0fa004
--- /dev/null
@@ -0,0 +1,64 @@
+/* This file is part of the dbusmenu-qt library
+   Copyright 2010 Canonical
+   Author: Aurelien Gateau <aurelien.gateau@canonical.com>
+
+   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>
+
+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 (file)
index 0000000..3f6e888
--- /dev/null
@@ -0,0 +1,31 @@
+/* This file is part of the dbusmenu-qt library
+   Copyright 2010 Canonical
+   Author: Aurelien Gateau <aurelien.gateau@canonical.com>
+
+   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 (file)
index 0000000..6be27f5
--- /dev/null
@@ -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 (file)
index 0000000..0d844f7
--- /dev/null
@@ -0,0 +1,815 @@
+/* This file is part of the dbusmenu-qt library
+   Copyright 2009 Canonical
+   Author: Aurelien Gateau <aurelien.gateau@canonical.com>
+
+   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 <QDBusConnection>
+#include <QDBusInterface>
+#include <QDBusReply>
+#include <QIcon>
+#include <QMenu>
+#include <QtTest>
+
+// DBusMenuQt
+#include <dbusmenuexporter.h>
+#include <dbusmenutypes_p.h>
+#include <dbusmenushortcut_p.h>
+#include <debug_p.h>
+
+// 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<int>)
+
+static DBusMenuLayoutItemList getChildren(QDBusAbstractInterface* iface, int parentId, const QStringList &propertyNames)
+{
+    QDBusPendingReply<uint, DBusMenuLayoutItem> 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<QString>("label");
+    QTest::addColumn<QString>("iconName");
+    QTest::addColumn<bool>("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<QString> a1Properties = QSet<QString>()
+        << "label"
+        ;
+
+    const QSet<QString> separatorProperties = QSet<QString>()
+        << "type";
+
+    const QSet<QString> a2Properties = QSet<QString>()
+        << "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<QString>::fromList(item.properties.keys()), a1Properties);
+
+    item = list.takeFirst();
+    QCOMPARE(QSet<QString>::fromList(item.properties.keys()), separatorProperties);
+
+    item = list.takeFirst();
+    QCOMPARE(QSet<QString>::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<bool> 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<int> updatedIds;
+    {
+        QVariantList lst = spy.takeFirst().at(0).toList();
+        Q_FOREACH(QVariant variant, lst) {
+            updatedIds << variant.toInt();
+        }
+    }
+
+    QSet<int> 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<QAction*> 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<QDBusArgument>();
+            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<int> ids = QList<int>() << id1 << id2;
+    QDBusReply<DBusMenuItemList> 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<QObject*> lst = menu->findChildren<QObject*>();
+    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<QString>("input");
+    QTest::addColumn<QString>("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<int> ids = QList<int>() << layoutItemlist[0].id;
+
+    QDBusReply<DBusMenuItemList> 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 (file)
index 0000000..3e93f76
--- /dev/null
@@ -0,0 +1,63 @@
+/* This file is part of the dbusmenu-qt library
+   Copyright 2009 Canonical
+   Author: Aurelien Gateau <aurelien.gateau@canonical.com>
+
+   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 <QtGui>
+
+// Qt
+#include <QObject>
+
+// 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 (file)
index 0000000..497f1e7
--- /dev/null
@@ -0,0 +1,343 @@
+/* This file is part of the dbusmenu-qt library
+   Copyright 2010 Canonical
+   Author: Aurelien Gateau <aurelien.gateau@canonical.com>
+
+   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 <QDBusConnection>
+#include <QDBusInterface>
+#include <QDBusReply>
+#include <QIcon>
+#include <QMenu>
+#include <QtTest>
+
+// DBusMenuQt
+#include <dbusmenuexporter.h>
+#include <dbusmenuimporter.h>
+#include <debug_p.h>
+
+// 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*>("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<QAction*>(), outputA1);
+    QCOMPARE(spy.takeFirst().at(0).value<QAction*>(), 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<QObject> > 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<QObject> 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 (file)
index 0000000..64ed647
--- /dev/null
@@ -0,0 +1,51 @@
+/* This file is part of the dbusmenu-qt library
+   Copyright 2010 Canonical
+   Author: Aurelien Gateau <aurelien.gateau@canonical.com>
+
+   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 <QtGui>
+
+// Qt
+#include <QObject>
+
+// 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 (file)
index 0000000..c3036ef
--- /dev/null
@@ -0,0 +1,85 @@
+/* This file is part of the dbusmenu-qt library
+   Copyright 2010 Canonical
+   Author: Aurelien Gateau <aurelien.gateau@canonical.com>
+
+   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 <QtTest>
+
+// DBusMenuQt
+#include <dbusmenushortcut_p.h>
+#include <debug_p.h>
+
+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<QKeySequence>("keySequence");
+    QTest::addColumn<DBusMenuShortcut>("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 (file)
index 0000000..531aac5
--- /dev/null
@@ -0,0 +1,40 @@
+/* This file is part of the dbusmenu-qt library
+   Copyright 2010 Canonical
+   Author: Aurelien Gateau <aurelien.gateau@canonical.com>
+
+   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 <QtGui>
+
+// Qt
+#include <QObject>
+
+// 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 (file)
index 0000000..13972c1
--- /dev/null
@@ -0,0 +1,58 @@
+/* This file is part of the dbusmenu-qt library
+   Copyright 2010 Canonical
+   Author: Aurelien Gateau <aurelien.gateau@canonical.com>
+
+   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 <slowmenu.moc>
+
+#include <dbusmenuexporter.h>
+
+#include <QtDBus>
+#include <QtGui>
+#include <QApplication>
+
+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 (file)
index 0000000..cbea0ff
--- /dev/null
@@ -0,0 +1,37 @@
+/* This file is part of the dbusmenu-qt library
+   Copyright 2010 Canonical
+   Author: Aurelien Gateau <aurelien.gateau@canonical.com>
+
+   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 <QMenu>
+
+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 (file)
index 0000000..8426aa1
--- /dev/null
@@ -0,0 +1,34 @@
+/* This file is part of the dbusmenu-qt library
+   Copyright 2010 Canonical
+   Author: Aurelien Gateau <aurelien.gateau@canonical.com>
+
+   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 <QCoreApplication>
+
+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 (file)
index 0000000..696d3f9
--- /dev/null
@@ -0,0 +1,109 @@
+/* This file is part of the dbusmenu-qt library
+   Copyright 2010 Canonical
+   Author: Aurelien Gateau <aurelien.gateau@canonical.com>
+
+   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 <debug_p.h>
+#include <dbusmenutypes_p.h>
+
+// Qt
+#include <QObject>
+#include <QMenu>
+#include <QVariant>
+
+class ManualSignalSpy : public QObject, public QList<QVariantList>
+{
+    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<QAction *> m_actions;
+};
+
+void waitForDeferredDeletes();
+
+#endif /* TESTUTILS_H */
diff --git a/tools/CMakeLists.txt b/tools/CMakeLists.txt
new file mode 100644 (file)
index 0000000..c150cc7
--- /dev/null
@@ -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 (file)
index 0000000..110b8b0
--- /dev/null
@@ -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 (file)
index 0000000..d9aa905
--- /dev/null
@@ -0,0 +1,105 @@
+/* This file is part of the dbusmenu-qt library
+   Copyright 2010 Canonical
+   Author: Aurelien Gateau <aurelien.gateau@canonical.com>
+
+   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 <QApplication>
+#include <QDBusConnection>
+#include <QDebug>
+#include <QFile>
+#include <QMenu>
+
+#include <qjson/parser.h>
+
+#include <dbusmenuexporter.h>
+
+static const char *DBUS_SERVICE = "org.dbusmenu.test";
+static const char *DBUS_PATH    = "/MenuBar";
+static const char *USAGE        = "dbusmenubench-qtapp <path/to/menu.json>";
+
+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();
+}