aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--CMakeLists.txt14
-rw-r--r--cmake/lcov.cmake68
-rw-r--r--examples/simple.py8
-rw-r--r--src/CMakeLists.txt1
-rw-r--r--src/QMenuModel/CMakeLists.txt13
-rw-r--r--src/common/CMakeLists.txt42
-rw-r--r--src/common/qdbusactiongroup.cpp (renamed from src/QMenuModel/qdbusactiongroup.cpp)4
-rw-r--r--src/common/qdbusactiongroup.h (renamed from src/QMenuModel/qdbusactiongroup.h)2
-rw-r--r--src/common/qdbusmenumodel.cpp (renamed from src/QMenuModel/qdbusmenumodel.cpp)6
-rw-r--r--src/common/qdbusmenumodel.h (renamed from src/QMenuModel/qdbusmenumodel.h)12
-rw-r--r--src/common/qdbusobject.cpp (renamed from src/QMenuModel/qdbusobject.cpp)62
-rw-r--r--src/common/qdbusobject.h (renamed from src/QMenuModel/qdbusobject.h)6
-rw-r--r--src/common/qmenumodel.cpp (renamed from src/QMenuModel/qmenumodel.cpp)97
-rw-r--r--src/common/qmenumodel.h (renamed from src/QMenuModel/qmenumodel.h)7
-rw-r--r--tests/CMakeLists.txt2
-rw-r--r--tests/client/CMakeLists.txt46
-rw-r--r--tests/client/menuchangestest.cpp65
-rw-r--r--tests/client/modeltest.cpp185
-rwxr-xr-xtests/client/script_menuchangestest.py14
-rwxr-xr-xtests/client/script_modeltest.py31
-rwxr-xr-xtests/client/script_servicetest.py12
-rw-r--r--tests/client/servicetest.cpp101
-rw-r--r--tests/script/CMakeLists.txt34
-rw-r--r--tests/script/dbusmenuscript.cpp75
-rw-r--r--tests/script/dbusmenuscript.h35
-rw-r--r--tests/script/menuscript.py172
26 files changed, 1054 insertions, 60 deletions
diff --git a/CMakeLists.txt b/CMakeLists.txt
index d8dad33..65e6fa8 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -6,6 +6,7 @@ find_package(Qt4 REQUIRED)
include(FindPkgConfig)
pkg_check_modules(GLIB REQUIRED glib-2.0>=2.32)
+pkg_check_modules(GIO REQUIRED gio-2.0>=2.32)
add_definitions(-DQT_NO_KEYWORDS)
find_program(DBUS_RUNNER dbus-test-runner)
@@ -14,7 +15,10 @@ find_program(DBUS_RUNNER dbus-test-runner)
OPTION(BUILD_WITH_COVERAGE "Build with coverage analysis support" OFF)
if(BUILD_WITH_COVERAGE)
message(STATUS "Using coverage flags")
- SET(COVERAGE_COMMAND "/usr/bin/gcov")
+ find_program(COVERAGE_COMMAND gcov)
+ if(NOT COVERAGE_COMMAND)
+ message(FATAL_ERROR "gcov command not found")
+ endif()
SET(CMAKE_C_FLAGS "-g -O0 -Wall -fprofile-arcs -ftest-coverage")
SET(CMAKE_CXX_FLAGS "-g -O0 -Wall -fprofile-arcs -ftest-coverage")
SET(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -fprofile-arcs -ftest-coverage -lgcov")
@@ -24,6 +28,14 @@ endif()
add_subdirectory(src)
add_subdirectory(examples)
+# Tests Tools
+if(NOT DBUS_RUNNER)
+ message(STATUS "dbus-test-runner not found tests disabled.")
+else()
+ enable_testing()
+ add_subdirectory(tests)
+endif()
+
# Tests Tools
#if(NOT DBUS_RUNNER)
diff --git a/cmake/lcov.cmake b/cmake/lcov.cmake
new file mode 100644
index 0000000..5758859
--- /dev/null
+++ b/cmake/lcov.cmake
@@ -0,0 +1,68 @@
+# - This module creates a new 'lcov' target which generates
+# a coverage analysis html output.
+# LCOV is a graphical front-end for GCC's coverage testing tool gcov. Please see
+# http://ltp.sourceforge.net/coverage/lcov.php
+#
+# Usage: you must add an option to your CMakeLists.txt to build your application
+# with coverage support. Then you need to include this file to the lcov target.
+#
+# Example:
+# IF(BUILD_WITH_COVERAGE)
+# SET(CMAKE_C_FLAGS "-g -O0 -Wall -fprofile-arcs -ftest-coverage")
+# SET(CMAKE_CXX_FLAGS "-g -O0 -Wall -fprofile-arcs -ftest-coverage")
+# SET(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -fprofile-arcs -ftest-coverage -lgcov")
+# include(${CMAKE_SOURCE_DIR}/cmake/lcov.cmake)
+# ENDIF(BUILD_WITH_COVERAGE)
+#=============================================================================
+# Copyright 2010 ascolab GmbH
+#
+# Distributed under the OSI-approved BSD License (the "License");
+# see accompanying file Copyright.txt for details.
+#
+# This software is distributed WITHOUT ANY WARRANTY; without even the
+# implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+# See the License for more information.
+#=============================================================================
+# (To distributed this file outside of CMake, substitute the full
+# License text for the above reference.)
+
+set(REMOVE_PATTERN
+ q*.h
+ *.moc
+ moc_*.cxx
+ locale_facets.h
+ new)
+
+## lcov target
+ADD_CUSTOM_TARGET(lcov)
+ADD_CUSTOM_COMMAND(TARGET lcov
+ COMMAND mkdir -p coverage
+ WORKING_DIRECTORY ${CMAKE_BINARY_DIR}
+ )
+ADD_CUSTOM_COMMAND(TARGET lcov
+ COMMAND lcov --directory . --zerocounters
+ WORKING_DIRECTORY ${CMAKE_BINARY_DIR}
+ )
+ADD_CUSTOM_COMMAND(TARGET lcov
+ COMMAND make test
+ WORKING_DIRECTORY ${CMAKE_BINARY_DIR}
+ )
+ADD_CUSTOM_COMMAND(TARGET lcov
+ COMMAND lcov --directory . --capture --output-file ./coverage/stap_all.info --no-checksum --compat-libtool
+ WORKING_DIRECTORY ${CMAKE_BINARY_DIR}
+ )
+ADD_CUSTOM_COMMAND(TARGET lcov
+ COMMAND lcov --directory . -r ./coverage/stap_all.info ${REMOVE_PATTERN} --output-file ./coverage/stap.info
+ WORKING_DIRECTORY ${CMAKE_BINARY_DIR}
+ )
+ADD_CUSTOM_COMMAND(TARGET lcov
+ COMMAND genhtml -o ./coverage --title "Code Coverage" --legend --show-details --demangle-cpp ./coverage/stap.info
+ WORKING_DIRECTORY ${CMAKE_BINARY_DIR}
+ )
+ADD_CUSTOM_COMMAND(TARGET lcov
+ COMMAND echo "Open ${CMAKE_BINARY_DIR}/coverage/index.html to view the coverage analysis results."
+ WORKING_DIRECTORY ${CMAKE_BINARY_DIR}
+ )
+ADD_DEPENDENCIES(lcov helloworld)
+
+
diff --git a/examples/simple.py b/examples/simple.py
index bc2d37c..4efbb8a 100644
--- a/examples/simple.py
+++ b/examples/simple.py
@@ -26,10 +26,16 @@ numbers.append ('One', 'one')
numbers.append ('Two', 'two')
numbers.append ('Three', 'three')
+numbers2 = Gio.Menu ()
+numbers2.append ('One2', 'one2')
+numbers2.append ('Two2', 'two2')
+numbers2.append ('Three2', 'three2')
+
+
menu = Gio.Menu ()
menu.append ('Menu item', 'one')
menu.append_section ('Numbers', numbers)
-menu.append_submenu ('Submenu', numbers)
+menu.append_submenu ('Submenu', numbers2)
# export the menu and action group on d-bus
bus = Gio.bus_get_sync (Gio.BusType.SESSION, None)
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index e2c06cb..7215efc 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -1,3 +1,4 @@
project(src)
+add_subdirectory(common)
add_subdirectory(QMenuModel)
diff --git a/src/QMenuModel/CMakeLists.txt b/src/QMenuModel/CMakeLists.txt
index ee3f28c..bf3bb5a 100644
--- a/src/QMenuModel/CMakeLists.txt
+++ b/src/QMenuModel/CMakeLists.txt
@@ -1,16 +1,11 @@
+
+project(qmenumodelqmlplugin)
+
set(QMENUMODEL_SRC
- qmenumodel.cpp
- qdbusobject.cpp
- qdbusmenumodel.cpp
- qdbusactiongroup.cpp
plugin.cpp
)
set(QMENUMODEL_HEADERS
- qmenumodel.h
- qdbusobject.h
- qdbusmenumodel.h
- qdbusactiongroup.h
plugin.h
)
@@ -25,6 +20,7 @@ add_library(qmenumodel MODULE
include_directories(
${CMAKE_CURRENT_SOURCE_DIR}
+ ${qmenumodelcommon_SOURCE_DIR}
${QT_INCLUDE_DIR}
${QT_QTCORE_INCLUDE_DIR}
${QT_QTGUI_INCLUDE_DIR}
@@ -34,6 +30,7 @@ include_directories(
)
target_link_libraries(qmenumodel
+ qmenumodelcommon
${QT_QTCORE_LIBRARY}
${QT_QTGUI_LIBRARY}
${QT_QTDCLARATIVE_LIBRARY}
diff --git a/src/common/CMakeLists.txt b/src/common/CMakeLists.txt
new file mode 100644
index 0000000..1428000
--- /dev/null
+++ b/src/common/CMakeLists.txt
@@ -0,0 +1,42 @@
+project(qmenumodelcommon)
+
+set(QMENUMODELCOMMON_SRC
+ qmenumodel.cpp
+ qdbusobject.cpp
+ qdbusmenumodel.cpp
+ qdbusactiongroup.cpp
+)
+
+set(QMENUMODELCOMMON_HEADERS
+ qmenumodel.h
+ qdbusobject.h
+ qdbusmenumodel.h
+ qdbusactiongroup.h
+)
+
+qt4_wrap_cpp(QMENUMODELCOMMON_MOC
+ ${QMENUMODELCOMMON_HEADERS}
+)
+
+add_library(qmenumodelcommon STATIC
+ ${QMENUMODELCOMMON_SRC}
+ ${QMENUMODELCOMMON_MOC}
+)
+
+set_target_properties(qmenumodelcommon PROPERTIES COMPILE_FLAGS -fPIC)
+
+include_directories(
+ ${CMAKE_CURRENT_SOURCE_DIR}
+ ${QT_INCLUDE_DIR}
+ ${QT_QTCORE_INCLUDE_DIR}
+ ${QT_QTGUI_INCLUDE_DIR}
+ ${GLIB_INCLUDE_DIRS}
+ ${GIO_INCLUDE_DIRS}
+)
+
+target_link_libraries(qmenumodelcommon
+ ${QT_QTCORE_LIBRARY}
+ ${QT_QTGUI_LIBRARY}
+ ${GLIB_LDFLAGS}
+ ${GIO_LDFLAGS}
+)
diff --git a/src/QMenuModel/qdbusactiongroup.cpp b/src/common/qdbusactiongroup.cpp
index ca430fb..8475c29 100644
--- a/src/QMenuModel/qdbusactiongroup.cpp
+++ b/src/common/qdbusactiongroup.cpp
@@ -44,12 +44,12 @@ void QDBusActionGroup::serviceAppear(GDBusConnection *connection)
void QDBusActionGroup::start()
{
- QDbusObject::connect();
+ QDBusObject::connect();
}
void QDBusActionGroup::stop()
{
- QDbusObject::disconnect();
+ QDBusObject::disconnect();
}
void QDBusActionGroup::busTypeChanged(BusType)
diff --git a/src/QMenuModel/qdbusactiongroup.h b/src/common/qdbusactiongroup.h
index a32b03e..fe743b6 100644
--- a/src/QMenuModel/qdbusactiongroup.h
+++ b/src/common/qdbusactiongroup.h
@@ -7,7 +7,7 @@
#include <QAction>
#include <QSet>
-class QDBusActionGroup : public QObject, public QDbusObject
+class QDBusActionGroup : public QObject, public QDBusObject
{
Q_OBJECT
Q_PROPERTY(int busType READ busType WRITE setIntBusType NOTIFY busTypeChanged)
diff --git a/src/QMenuModel/qdbusmenumodel.cpp b/src/common/qdbusmenumodel.cpp
index c551464..6e35c85 100644
--- a/src/QMenuModel/qdbusmenumodel.cpp
+++ b/src/common/qdbusmenumodel.cpp
@@ -12,12 +12,12 @@ QDBusMenuModel::~QDBusMenuModel()
void QDBusMenuModel::start()
{
- QDbusObject::connect();
+ QDBusObject::connect();
}
void QDBusMenuModel::stop()
{
- QDbusObject::disconnect();
+ QDBusObject::disconnect();
}
void QDBusMenuModel::serviceVanish(GDBusConnection *)
@@ -36,6 +36,7 @@ void QDBusMenuModel::serviceAppear(GDBusConnection *connection)
}
}
+/*
void QDBusMenuModel::busTypeChanged(BusType)
{
busTypeChanged();
@@ -55,6 +56,7 @@ void QDBusMenuModel::statusChanged(ConnectionStatus status)
{
statusChanged();
}
+*/
void QDBusMenuModel::setIntBusType(int busType)
{
diff --git a/src/QMenuModel/qdbusmenumodel.h b/src/common/qdbusmenumodel.h
index 975e72d..4fc439d 100644
--- a/src/QMenuModel/qdbusmenumodel.h
+++ b/src/common/qdbusmenumodel.h
@@ -6,7 +6,7 @@
#include <gio/gio.h>
-class QDBusMenuModel : public QMenuModel, public QDbusObject
+class QDBusMenuModel : public QMenuModel, public QDBusObject
{
Q_OBJECT
Q_PROPERTY(int busType READ busType WRITE setIntBusType NOTIFY busTypeChanged)
@@ -19,10 +19,10 @@ public:
~QDBusMenuModel();
Q_SIGNALS:
- void busTypeChanged();
- void busNameChanged();
- void objectPathChanged();
- void statusChanged();
+ void busTypeChanged(BusType type);
+ void busNameChanged(const QString &busNameChanged);
+ void objectPathChanged(const QString &objectPath);
+ void statusChanged(QDBusObject::ConnectionStatus status);
public Q_SLOTS:
void start();
@@ -32,10 +32,12 @@ protected:
virtual void serviceAppear(GDBusConnection *connection);
virtual void serviceVanish(GDBusConnection *connection);
+ /*
virtual void busTypeChanged(BusType type);
virtual void busNameChanged(const QString &busNameChanged);
virtual void objectPathChanged(const QString &objectPath);
virtual void statusChanged(ConnectionStatus status);
+ */
private:
// workaround to support int as bustType
diff --git a/src/QMenuModel/qdbusobject.cpp b/src/common/qdbusobject.cpp
index 9f11813..6dec6d9 100644
--- a/src/QMenuModel/qdbusobject.cpp
+++ b/src/common/qdbusobject.cpp
@@ -2,14 +2,18 @@
#include <QDebug>
-QDbusObject::QDbusObject()
+
+
+QDBusObject::QDBusObject()
:m_watchId(0),
m_busType(None),
- m_status(QDbusObject::Disconnected)
+ m_status(QDBusObject::Disconnected)
{
+ qDebug() << "DBUS CREATED";
+ qRegisterMetaType<QDBusObject::ConnectionStatus>("QDBusObject::ConnectionStatus");
}
-QDbusObject::~QDbusObject()
+QDBusObject::~QDBusObject()
{
if (m_watchId != 0) {
g_bus_unwatch_name (m_watchId);
@@ -17,52 +21,52 @@ QDbusObject::~QDbusObject()
}
}
-QDbusObject::BusType QDbusObject::busType() const
+QDBusObject::BusType QDBusObject::busType() const
{
return m_busType;
}
-void QDbusObject::setBusType(QDbusObject::BusType type)
+void QDBusObject::setBusType(QDBusObject::BusType type)
{
if (m_busType != type) {
- if (m_status != QDbusObject::Disconnected)
+ if (m_status != QDBusObject::Disconnected)
disconnect();
m_busType = type;
busTypeChanged(m_busType);
}
}
-QString QDbusObject::busName() const
+QString QDBusObject::busName() const
{
return m_busName;
}
-void QDbusObject::setBusName(const QString &busName)
+void QDBusObject::setBusName(const QString &busName)
{
if (m_busName != busName) {
- if (m_status != QDbusObject::Disconnected)
+ if (m_status != QDBusObject::Disconnected)
disconnect();
m_busName = busName;
busNameChanged(m_busName);
}
}
-QString QDbusObject::objectPath() const
+QString QDBusObject::objectPath() const
{
return m_objectPath;
}
-void QDbusObject::setObjectPath(const QString &objectPath)
+void QDBusObject::setObjectPath(const QString &objectPath)
{
if (m_objectPath != objectPath) {
- if (m_status != QDbusObject::Disconnected)
+ if (m_status != QDBusObject::Disconnected)
disconnect();
m_objectPath = objectPath;
objectPathChanged(m_objectPath);
}
}
-void QDbusObject::setStatus(QDbusObject::ConnectionStatus status)
+void QDBusObject::setStatus(QDBusObject::ConnectionStatus status)
{
if (m_status != status) {
m_status = status;
@@ -70,52 +74,54 @@ void QDbusObject::setStatus(QDbusObject::ConnectionStatus status)
}
}
-QDbusObject::ConnectionStatus QDbusObject::status() const
+QDBusObject::ConnectionStatus QDBusObject::status() const
{
return m_status;
}
-void QDbusObject::connect()
+void QDBusObject::connect()
{
- if (m_status != QDbusObject::Disconnected) {
+ if (m_status != QDBusObject::Disconnected) {
return;
} else if ((m_busType > None) && !m_objectPath.isEmpty() && !m_busName.isEmpty()) {
GBusType type = m_busType == SessionBus ? G_BUS_TYPE_SESSION : G_BUS_TYPE_SYSTEM;
m_watchId = g_bus_watch_name (type,
m_busName.toLatin1(),
G_BUS_NAME_WATCHER_FLAGS_NONE,
- QDbusObject::onServiceAppeared,
- QDbusObject::onServiceFanished,
+ QDBusObject::onServiceAppeared,
+ QDBusObject::onServiceFanished,
this,
NULL);
- setStatus(QDbusObject::Connecting);
+ setStatus(QDBusObject::Connecting);
} else {
qWarning() << "Invalid dbus connection args";
}
}
-void QDbusObject::disconnect()
+void QDBusObject::disconnect()
{
- if (m_status != QDbusObject::Disconnected) {
+ if (m_status != QDBusObject::Disconnected) {
g_bus_unwatch_name (m_watchId);
m_watchId = 0;
- setStatus(QDbusObject::Disconnected);
+ setStatus(QDBusObject::Disconnected);
}
}
-void QDbusObject::onServiceAppeared(GDBusConnection *connection, const gchar *, const gchar *, gpointer data)
+void QDBusObject::onServiceAppeared(GDBusConnection *connection, const gchar *, const gchar *, gpointer data)
{
- QDbusObject *self = reinterpret_cast<QDbusObject*>(data);
+ QDBusObject *self = reinterpret_cast<QDBusObject*>(data);
+ qDebug() << "service appear";
- self->setStatus(QDbusObject::Connected);
+ self->setStatus(QDBusObject::Connected);
self->serviceAppear(connection);
}
-void QDbusObject::onServiceFanished(GDBusConnection *connection, const gchar *, gpointer data)
+void QDBusObject::onServiceFanished(GDBusConnection *connection, const gchar *, gpointer data)
{
- QDbusObject *self = reinterpret_cast<QDbusObject*>(data);
+ QDBusObject *self = reinterpret_cast<QDBusObject*>(data);
+ qDebug() << "service disappear";
- self->setStatus(QDbusObject::Connecting);
+ self->setStatus(QDBusObject::Connecting);
self->serviceVanish(connection);
}
diff --git a/src/QMenuModel/qdbusobject.h b/src/common/qdbusobject.h
index 0b17ae1..a2dc91c 100644
--- a/src/QMenuModel/qdbusobject.h
+++ b/src/common/qdbusobject.h
@@ -5,7 +5,7 @@
#include <gio/gio.h>
-class QDbusObject
+class QDBusObject
{
public:
enum BusType {
@@ -21,8 +21,8 @@ public:
Connected
};
- QDbusObject();
- ~QDbusObject();
+ QDBusObject();
+ ~QDBusObject();
BusType busType() const;
void setBusType(BusType type);
diff --git a/src/QMenuModel/qmenumodel.cpp b/src/common/qmenumodel.cpp
index c7ccf56..6e7483c 100644
--- a/src/QMenuModel/qmenumodel.cpp
+++ b/src/common/qmenumodel.cpp
@@ -1,4 +1,5 @@
#include "qmenumodel.h"
+
#include <QDebug>
QMenuModel::QMenuModel(GMenuModel *other, QObject *parent)
@@ -12,6 +13,7 @@ QMenuModel::QMenuModel(GMenuModel *other, QObject *parent)
rolesNames[Label] = "label";
rolesNames[LinkSection] = "linkSection";
rolesNames[LinkSubMenu] = "linkSubMenu";
+ rolesNames[Extra] = "extra";
}
setRoleNames(rolesNames);
setMenuModel(other);
@@ -36,17 +38,19 @@ void QMenuModel::setMenuModel(GMenuModel *other)
g_object_unref(m_menuModel);
}
- m_menuModel = other;
-
- endResetModel();
+ m_menuModel = other;
if (m_menuModel) {
- qDebug() << "Menu size:" << g_menu_model_get_n_items(m_menuModel);
+ // this will trigger the menu load
+ (void) g_menu_model_get_n_items(m_menuModel);
+ //qDebug() << "Menu size:" << g_menu_model_get_n_items(m_menuModel);
m_signalChangedId = g_signal_connect(m_menuModel,
"items-changed",
G_CALLBACK(QMenuModel::onItemsChanged),
this);
}
+
+ endResetModel();
}
GMenuModel *QMenuModel::menuModel() const
@@ -67,8 +71,7 @@ QVariant QMenuModel::data(const QModelIndex &index, int role) const
if ((rowCountValue > 0) && (index.row() >= 0) && (index.row() < rowCountValue)) {
if (m_menuModel) {
- switch (role)
- {
+ switch (role) {
case Action:
attribute = getStringAttribute(index, G_MENU_ATTRIBUTE_ACTION);
break;
@@ -81,6 +84,9 @@ QVariant QMenuModel::data(const QModelIndex &index, int role) const
case LinkSubMenu:
attribute = getLink(index, G_MENU_LINK_SUBMENU);
break;
+ case Extra:
+ attribute = getExtraProperties(index);
+ break;
default:
break;
}
@@ -135,6 +141,27 @@ QVariant QMenuModel::getLink(const QModelIndex &index,
return QVariant();
}
+QVariant QMenuModel::getExtraProperties(const QModelIndex &index) const
+{
+ GMenuAttributeIter *iter = g_menu_model_iterate_item_attributes(m_menuModel, index.row());
+ if (iter == NULL) {
+ return QVariant();
+ }
+
+ QObject *extra = new QObject(const_cast<QMenuModel*>(this));
+ const gchar *attrName = NULL;
+ GVariant *value = NULL;
+ while (g_menu_attribute_iter_get_next (iter, &attrName, &value)) {
+ qDebug() << "Set property:" << attrName;
+ if (strncmp("x-", attrName, 2) == 0) {
+ extra->setProperty(attrName, parseGVariant(value));
+ }
+ }
+
+ return QVariant::fromValue<QObject*>(extra);
+}
+
+
void QMenuModel::onItemsChanged(GMenuModel *,
gint position,
gint removed,
@@ -142,6 +169,7 @@ void QMenuModel::onItemsChanged(GMenuModel *,
gpointer data)
{
QMenuModel *self = reinterpret_cast<QMenuModel*>(data);
+ //qDebug() << "Item Changed" << position << removed << added;
if (removed > 0) {
self->beginRemoveRows(QModelIndex(), position, position + removed - 1);
@@ -154,3 +182,60 @@ void QMenuModel::onItemsChanged(GMenuModel *,
}
}
+QVariant QMenuModel::parseGVariant(GVariant *value)
+{
+ QVariant result;
+ if (value == NULL) {
+ return result;
+ }
+
+ const GVariantType *type = g_variant_get_type(value);
+ if (g_variant_type_equal(type, G_VARIANT_TYPE_BOOLEAN)) {
+ result.setValue((bool)g_variant_get_boolean(value));
+ } else if (g_variant_type_equal(type, G_VARIANT_TYPE_BYTE)) {
+ result.setValue(g_variant_get_byte(value));
+ } else if (g_variant_type_equal(type, G_VARIANT_TYPE_INT16)) {
+ result.setValue(g_variant_get_int16(value));
+ } else if (g_variant_type_equal(type, G_VARIANT_TYPE_UINT16)) {
+ result.setValue(g_variant_get_uint16(value));
+ } else if (g_variant_type_equal(type, G_VARIANT_TYPE_INT32)) {
+ result.setValue(g_variant_get_int32(value));
+ } else if (g_variant_type_equal(type, G_VARIANT_TYPE_UINT32)) {
+ result.setValue(g_variant_get_uint32(value));
+ } else if (g_variant_type_equal(type, G_VARIANT_TYPE_INT64)) {
+ result.setValue(g_variant_get_int64(value));
+ } else if (g_variant_type_equal(type, G_VARIANT_TYPE_UINT64)) {
+ result.setValue(g_variant_get_uint64(value));
+ } else if (g_variant_type_equal(type, G_VARIANT_TYPE_DOUBLE)) {
+ result.setValue(g_variant_get_double(value));
+ } else if (g_variant_type_equal(type, G_VARIANT_TYPE_STRING)) {
+ gsize size = 0;
+ const gchar *v = g_variant_get_string(value, &size);
+ result.setValue(QString::fromLatin1(v, size));
+ } else {
+ qWarning() << "Unsupported GVariant value";
+ }
+
+ /* TODO: implement convertions to others types
+ * G_VARIANT_TYPE_HANDLE
+ * G_VARIANT_TYPE_OBJECT_PATH
+ * G_VARIANT_TYPE_SIGNATURE
+ * G_VARIANT_TYPE_VARIANT
+ * G_VARIANT_TYPE_ANY
+ * G_VARIANT_TYPE_BASIC
+ * G_VARIANT_TYPE_MAYBE
+ * G_VARIANT_TYPE_ARRAY
+ * G_VARIANT_TYPE_TUPLE
+ * G_VARIANT_TYPE_UNIT
+ * G_VARIANT_TYPE_DICT_ENTRY
+ * G_VARIANT_TYPE_DICTIONARY
+ * G_VARIANT_TYPE_STRING_ARRAY
+ * G_VARIANT_TYPE_BYTESTRING
+ * G_VARIANT_TYPE_OBJECT_PATH_ARRAY
+ * G_VARIANT_TYPE_BYTESTRING_ARRAY
+ * G_VARIANT_TYPE_VARDICT
+ */
+
+ return result;
+}
+
diff --git a/src/QMenuModel/qmenumodel.h b/src/common/qmenumodel.h
index 18ad21c..f1e0b61 100644
--- a/src/QMenuModel/qmenumodel.h
+++ b/src/common/qmenumodel.h
@@ -4,7 +4,6 @@
#include <QAbstractListModel>
#include <gio/gio.h>
-
class QMenuModel : public QAbstractListModel
{
Q_OBJECT
@@ -14,7 +13,8 @@ public:
Action = 0,
Label,
LinkSection,
- LinkSubMenu
+ LinkSubMenu,
+ Extra
};
~QMenuModel();
@@ -36,9 +36,10 @@ private:
QVariant getStringAttribute(const QModelIndex &index, const QString &attribute) const;
QVariant getLink(const QModelIndex &index, const QString &linkName) const;
+ QVariant getExtraProperties(const QModelIndex &index) const;
static void onItemsChanged(GMenuModel *model, gint position, gint removed, gint added, gpointer data);
-
+ static QVariant parseGVariant(GVariant *value);
};
#endif
diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt
new file mode 100644
index 0000000..96c4bad
--- /dev/null
+++ b/tests/CMakeLists.txt
@@ -0,0 +1,2 @@
+add_subdirectory(script)
+add_subdirectory(client)
diff --git a/tests/client/CMakeLists.txt b/tests/client/CMakeLists.txt
new file mode 100644
index 0000000..bf77461
--- /dev/null
+++ b/tests/client/CMakeLists.txt
@@ -0,0 +1,46 @@
+macro(declare_test testname)
+ set(TEST_MOC_FILE ${CMAKE_CURRENT_BINARY_DIR}/${testname}.moc)
+ qt4_generate_moc(${testname}.cpp ${TEST_MOC_FILE})
+
+ add_executable(${testname} ${testname}.cpp ${TEST_MOC_FILE})
+ target_link_libraries(${testname}
+ qmenumodelcommon
+ dbusmenuscript
+ ${QT_QTTEST_LIBRARY}
+ ${QT_QTCORE_LIBRARY}
+ ${QT_QTGUI_LIBRARY}
+ ${QT_QTDBUS_LIBRARY}
+ ${GLIB_LDFLAGS}
+ ${GIO_LDFLAGS})
+
+ add_test(${testname}
+ ${DBUS_RUNNER}
+ --task ${CMAKE_CURRENT_BINARY_DIR}/${testname} --task-name Client
+ --task ${CMAKE_CURRENT_SOURCE_DIR}/script_${testname}.py --task-name Server
+ --ignore-return)
+ set_tests_properties(${testname} PROPERTIES
+ TIMEOUT ${CTEST_TESTING_TIMEOUT}
+ ENVIRONMENT "PYTHONPATH=${TEST_PYTHONPATH}")
+
+endmacro(declare_test testname)
+
+include_directories(${qmenumodelcommon_SOURCE_DIR}
+ ${dbusmenuscript_SOURCE_DIR}
+ ${CMAKE_CURRENT_BINARY_DIR}
+ ${QT_INCLUDE_DIR}
+ ${QT_QTTEST_INCLUDE_DIR}
+ ${QT_QTCORE_INCLUDE_DIR}
+ ${QT_QTGUI_INCLUDE_DIR}
+ ${QT_QTDBUS_INCLUDE_DIR}
+ ${GLIB_INCLUDE_DIRS}
+ ${GIO_INCLUDE_DIRS})
+
+add_definitions(-DTEST_SUITE)
+set(TEST_PYTHONPATH ${dbusmenuscript_SOURCE_DIR})
+if(NOT CTEST_TESTING_TIMEOUT)
+ set(CTEST_TESTING_TIMEOUT 60)
+endif()
+
+declare_test(servicetest)
+declare_test(menuchangestest)
+declare_test(modeltest)
diff --git a/tests/client/menuchangestest.cpp b/tests/client/menuchangestest.cpp
new file mode 100644
index 0000000..7e6379c
--- /dev/null
+++ b/tests/client/menuchangestest.cpp
@@ -0,0 +1,65 @@
+#include "qdbusmenumodel.h"
+#include "dbusmenuscript.h"
+
+#include <QObject>
+#include <QSignalSpy>
+#include <QtTestGui>
+#include <QDebug>
+
+class MenuChangesTest : public QObject
+{
+ Q_OBJECT
+private:
+ DBusMenuScript m_script;
+ QDBusMenuModel m_model;
+
+private Q_SLOTS:
+ void initTestCase()
+ {
+ Q_ASSERT(m_script.connect());
+ }
+
+ void cleanupTestCase()
+ {
+ m_script.quit();
+ }
+
+ void init()
+ {
+ m_model.stop();
+ m_model.setBusType(QDBusObject::SessionBus);
+ m_model.setBusName(MENU_SERVICE_NAME);
+ m_model.setObjectPath(MENU_OBJECT_PATH);
+ }
+
+ void cleanup()
+ {
+ m_script.unpublishMenu();
+ }
+
+ void testMenuItemAppend()
+ {
+ m_script.publishMenu();
+ m_model.start();
+
+ // Create first Item
+ m_script.walk();
+ QCOMPARE(m_model.rowCount(), 1);
+
+ // Create second item
+ m_script.walk();
+ QCOMPARE(m_model.rowCount(), 2);
+
+ // Remove item0
+ m_script.walk();
+ QCOMPARE(m_model.rowCount(), 1);
+
+ // Remove item1
+ m_script.walk();
+ QCOMPARE(m_model.rowCount(), 0);
+ }
+};
+
+QTEST_MAIN(MenuChangesTest)
+
+#include "menuchangestest.moc"
diff --git a/tests/client/modeltest.cpp b/tests/client/modeltest.cpp
new file mode 100644
index 0000000..1963327
--- /dev/null
+++ b/tests/client/modeltest.cpp
@@ -0,0 +1,185 @@
+#include "qdbusmenumodel.h"
+#include "dbusmenuscript.h"
+
+#include <QObject>
+#include <QSignalSpy>
+#include <QtTestGui>
+#include <QDebug>
+
+class ModelTest : public QObject
+{
+ Q_OBJECT
+private:
+ DBusMenuScript m_script;
+ QDBusMenuModel m_model;
+
+private Q_SLOTS:
+ void initTestCase()
+ {
+ Q_ASSERT(m_script.connect());
+ }
+
+ void cleanupTestCase()
+ {
+ m_script.quit();
+ }
+
+ void init()
+ {
+ m_model.stop();
+ m_model.setBusType(QDBusObject::SessionBus);
+ m_model.setBusName(MENU_SERVICE_NAME);
+ m_model.setObjectPath(MENU_OBJECT_PATH);
+ }
+
+ void cleanup()
+ {
+ m_script.unpublishMenu();
+ }
+
+ void testColumnCount()
+ {
+ QCOMPARE(m_model.columnCount(), 1);
+ }
+
+ void testParent()
+ {
+ QCOMPARE(m_model.parent(QModelIndex()), QModelIndex());
+ }
+
+ void testBusTypeProperty()
+ {
+ m_model.setProperty("busType", 1);
+ QCOMPARE(m_model.busType(), QDBusObject::SessionBus);
+
+ m_model.setProperty("busType", 2);
+ QCOMPARE(m_model.busType(), QDBusObject::SystemBus);
+
+ m_model.setProperty("busType", 0);
+ QCOMPARE(m_model.busType(), QDBusObject::SystemBus);
+
+ m_model.setProperty("busType", 10);
+ QCOMPARE(m_model.busType(), QDBusObject::SystemBus);
+
+ }
+
+ void testData()
+ {
+ // Make menu available
+ m_script.publishMenu();
+ m_script.run();
+
+ // start model
+ m_model.start();
+
+ // Wait for dbus sync
+ QTest::qWait(500);
+
+ QCOMPARE(m_model.status(), QDBusObject::Connected);
+ QCOMPARE(m_model.rowCount(), 4);
+
+ // Label (String)
+ QVariant label = m_model.data(m_model.index(0, 0), QMenuModel::Label);
+ QVERIFY(label.isValid());
+ QCOMPARE(label.type(), QVariant::String);
+ QCOMPARE(label.toString(), QString("Menu0"));
+
+ // Action (String)
+ QVariant action = m_model.data(m_model.index(1, 0), QMenuModel::Action);
+ QVERIFY(action.isValid());
+ QCOMPARE(action.type(), QVariant::String);
+ QCOMPARE(action.toString(), QString("Menu1Act"));
+
+ // Section (QObject)
+ QVariant vSection = m_model.data(m_model.index(2, 0), QMenuModel::LinkSection);
+ QVERIFY(vSection.isValid());
+ QMenuModel *section = qobject_cast<QMenuModel*>(vSection.value<QObject*>());
+ QVERIFY(section);
+ QCOMPARE(section->rowCount(), 2);
+
+ // SubMenu (QObject)
+ QVariant vSubMenu = m_model.data(m_model.index(3, 0), QMenuModel::LinkSubMenu);
+ QVERIFY(vSubMenu.isValid());
+ QMenuModel *submenu = qobject_cast<QMenuModel*>(vSubMenu.value<QObject*>());
+ QVERIFY(submenu);
+
+ // Wait for menu load (submenus are loaded async)
+ QTest::qWait(500);
+ QCOMPARE(submenu->rowCount(), 2);
+ }
+
+ void testExtraData()
+ {
+ // Make menu available
+ m_script.publishMenu();
+ m_script.run();
+
+ // start model
+ m_model.start();
+
+ // Wait for dbus sync
+ QTest::qWait(500);
+
+ QCOMPARE(m_model.status(), QDBusObject::Connected);
+ QCOMPARE(m_model.rowCount(), 4);
+
+ QVariant e = m_model.data(m_model.index(0, 0), QMenuModel::Extra);
+ QVERIFY(e.isValid());
+ QObject *extra = e.value<QObject*>();
+
+ // Boolean
+ QVariant v = extra->property("x-boolean");
+ QCOMPARE(v.type(), QVariant::Bool);
+ QCOMPARE(v.toBool(), true);
+
+ // Byte
+ v = extra->property("x-byte");
+ QCOMPARE(v.typeName(), "uchar");
+ QCOMPARE(v.value<uchar>(), (uchar)42);
+
+ // Int16
+ v = extra->property("x-int16");
+ QCOMPARE(v.typeName(), "short");
+ QCOMPARE(v.value<short>(), (short)-42);
+
+ // UInt16
+ v = extra->property("x-uint16");
+ QCOMPARE(v.typeName(), "ushort");
+ QCOMPARE(v.value<ushort>(), (ushort)42);
+
+ // Int32
+ v = extra->property("x-int32");
+ QCOMPARE(v.type(), QVariant::Int);
+ QCOMPARE(v.toInt(), -42);
+
+ // UInt32
+ v = extra->property("x-uint32");
+ QCOMPARE(v.type(), QVariant::UInt);
+ QCOMPARE(v.toUInt(), (uint) 42);
+
+ // Int64
+ v = extra->property("x-int64");
+ QCOMPARE(v.typeName(), "long");
+ QCOMPARE(v.value<long>(), (long) -42);
+
+ // UInt64
+ v = extra->property("x-uint64");
+ QCOMPARE(v.typeName(), "ulong");
+ QCOMPARE(v.value<ulong>(), (ulong) 42);
+
+ // Double
+ v = extra->property("x-double");
+ QCOMPARE(v.type(), QVariant::Double);
+ QCOMPARE(v.toDouble(), 42.42);
+
+ // String
+ v = extra->property("x-string");
+ QCOMPARE(v.type(), QVariant::String);
+ QCOMPARE(v.toString(), QString("42"));
+ }
+
+};
+
+QTEST_MAIN(ModelTest)
+
+#include "modeltest.moc"
diff --git a/tests/client/script_menuchangestest.py b/tests/client/script_menuchangestest.py
new file mode 100755
index 0000000..67f2ee0
--- /dev/null
+++ b/tests/client/script_menuchangestest.py
@@ -0,0 +1,14 @@
+#!/usr/bin/python2.7
+
+import time
+from menuscript import Script, ActionList, MENU_OBJECT_PATH
+
+al = ActionList(MENU_OBJECT_PATH)
+al.appendItem("Menu0", "Menu0")
+al.appendItem("Menu1", "Menu1")
+al.removeItem(0) # remove Menu0
+al.removeItem(0) # remove Menu1
+
+t = Script.create(al)
+t.run()
+
diff --git a/tests/client/script_modeltest.py b/tests/client/script_modeltest.py
new file mode 100755
index 0000000..db4e28b
--- /dev/null
+++ b/tests/client/script_modeltest.py
@@ -0,0 +1,31 @@
+#!/usr/bin/python2.7
+
+import time
+from gi.repository import GLib
+from menuscript import Script, ActionList, MENU_OBJECT_PATH
+
+al = ActionList(MENU_OBJECT_PATH)
+al.appendItem("Menu0", "Menu0Act", None, None, {'x-boolean' : GLib.Variant('b', True),
+ 'x-byte' : GLib.Variant('y', 42),
+ 'x-int16' : GLib.Variant('n', -42),
+ 'x-uint16' : GLib.Variant('q', 42),
+ 'x-int32' : GLib.Variant('i', -42),
+ 'x-uint32' : GLib.Variant('u', 42),
+ 'x-int64' : GLib.Variant('x', -42),
+ 'x-uint64' : GLib.Variant('t', 42),
+ 'x-double' : GLib.Variant('d', 42.42),
+ 'x-string' : GLib.Variant('s', u'42')
+ })
+al.appendItem("Menu1", "Menu1Act")
+
+al.appendItem("Menu2", "Menu2Act", "section")
+al.appendItem("Menu2.1", "Menu2.1Act", None, "2")
+al.appendItem("Menu2.2", "Menu2.2Act", None, "2")
+
+al.appendItem("Menu3", "Menu3Act", "submenu")
+al.appendItem("Menu3.1", "Menu3.1Act", None, "3")
+al.appendItem("Menu3.2", "Menu3.2Act", None, "3")
+
+t = Script.create(al)
+t.run()
+
diff --git a/tests/client/script_servicetest.py b/tests/client/script_servicetest.py
new file mode 100755
index 0000000..385256c
--- /dev/null
+++ b/tests/client/script_servicetest.py
@@ -0,0 +1,12 @@
+#!/usr/bin/python2.7
+
+import time
+from menuscript import Script, ActionList, MENU_OBJECT_PATH
+
+al = ActionList(MENU_OBJECT_PATH)
+al.appendItem("Menu0", "Menu0")
+al.appendItem("Menu1", "Menu1")
+
+t = Script.create(al)
+t.run()
+
diff --git a/tests/client/servicetest.cpp b/tests/client/servicetest.cpp
new file mode 100644
index 0000000..3faddd0
--- /dev/null
+++ b/tests/client/servicetest.cpp
@@ -0,0 +1,101 @@
+#include "qdbusmenumodel.h"
+#include "dbusmenuscript.h"
+
+#include <QObject>
+#include <QSignalSpy>
+#include <QtTestGui>
+#include <QDebug>
+
+class ServiceTest : public QObject
+{
+ Q_OBJECT
+private:
+ DBusMenuScript m_script;
+ QDBusMenuModel m_model;
+
+ void setupModel(QDBusMenuModel *model)
+ {
+ model->setBusType(QDBusObject::SessionBus);
+ model->setBusName(MENU_SERVICE_NAME);
+ model->setObjectPath(MENU_OBJECT_PATH);
+ }
+
+private Q_SLOTS:
+ void initTestCase()
+ {
+ Q_ASSERT(m_script.connect());
+ }
+
+ void cleanupTestCase()
+ {
+ m_script.quit();
+ }
+
+ void init()
+ {
+ qDebug() << "init>>>>>>>>>>>>>>>>>>>>>";
+ m_model.stop();
+ m_model.setBusType(QDBusObject::SessionBus);
+ m_model.setBusName(MENU_SERVICE_NAME);
+ m_model.setObjectPath(MENU_OBJECT_PATH);
+ }
+
+ void cleanup()
+ {
+ qDebug() << "cleanup>>>>>>>>>>>>>>>>>>";
+ m_script.unpublishMenu();
+ }
+
+ void testMenuStartStopWithNoService()
+ {
+ m_model.start();
+ QCOMPARE(m_model.status(), QDBusObject::Connecting);
+
+ m_model.stop();
+ QCOMPARE(m_model.status(), QDBusObject::Disconnected);
+ }
+
+ void testMenuStartStopWithService()
+ {
+ // Make menu available
+ m_script.publishMenu();
+
+ // start model
+ m_model.start();
+
+ // Wait for dbus sync
+ QTest::qWait(500);
+
+ QCOMPARE(m_model.status(), QDBusObject::Connected);
+
+ // Diconnect model
+ m_model.stop();
+ QCOMPARE(m_model.status(), QDBusObject::Disconnected);
+ }
+
+ void testMenuServiceAppearAndDissapear()
+ {
+ m_model.start();
+ QCOMPARE(m_model.status(), QDBusObject::Connecting);
+
+ QSignalSpy spy(&m_model, SIGNAL(statusChanged(QDBusObject::ConnectionStatus)));
+
+ // Make menu available
+ m_script.publishMenu();
+
+ // singal changed to connected
+ QCOMPARE(spy.count(), 1);
+ QCOMPARE(m_model.status(), QDBusObject::Connected);
+
+ // remove menu service
+ m_script.unpublishMenu();
+
+ // signal changed to connecting
+ QCOMPARE(spy.count(), 2);
+ QCOMPARE(m_model.status(), QDBusObject::Connecting);
+ }
+};
+
+QTEST_MAIN(ServiceTest)
+
+#include "servicetest.moc"
diff --git a/tests/script/CMakeLists.txt b/tests/script/CMakeLists.txt
new file mode 100644
index 0000000..11af44e
--- /dev/null
+++ b/tests/script/CMakeLists.txt
@@ -0,0 +1,34 @@
+project(dbusmenuscript)
+
+set(DBUSMENUSCRIPT_SRC
+ dbusmenuscript.cpp
+)
+
+set(DBUSMENUSCRIPT_HEADERS
+ dbusmenuscript.h
+)
+
+qt4_wrap_cpp(DBUSMENUSCRIPT_MOC
+ ${DBUSMENUSCRIPT_HEADERS}
+)
+
+add_library(dbusmenuscript STATIC
+ ${DBUSMENUSCRIPT_SRC}
+ ${DBUSMENUSCRIPT_MOC}
+)
+
+set_target_properties(dbusmenuscript PROPERTIES COMPILE_FLAGS -fPIC)
+
+include_directories(
+ ${CMAKE_CURRENT_SOURCE_DIR}
+ ${QT_INCLUDE_DIR}
+ ${QT_QTTEST_INCLUDE_DIR}
+ ${QT_QTCORE_INCLUDE_DIR}
+ ${QT_QTDBUS_INCLUDE_DIR}
+)
+
+target_link_libraries(dbusmenuscript
+ ${QT_QTCORE_LIBRARY}
+ ${QT_QTTEST_LIBRARY}
+ ${QT_QTDBUS_LIBRARY}
+)
diff --git a/tests/script/dbusmenuscript.cpp b/tests/script/dbusmenuscript.cpp
new file mode 100644
index 0000000..cc332f7
--- /dev/null
+++ b/tests/script/dbusmenuscript.cpp
@@ -0,0 +1,75 @@
+
+#include "dbusmenuscript.h"
+#include <QObject>
+#include <QtTestGui>
+#include <QDebug>
+
+#define WAIT_TIMEOUT 500
+
+DBusMenuScript::DBusMenuScript()
+ :m_script(0)
+{
+}
+
+DBusMenuScript::~DBusMenuScript()
+{
+ quit();
+}
+
+bool DBusMenuScript::connect()
+{
+ QTest::qWait(WAIT_TIMEOUT);
+ m_script = new QDBusInterface(SCRIPT_SERVICE_NAME,
+ SCRIPT_OBJECT_PATH,
+ SCRIPT_INTERFACE_NAME,
+ QDBusConnection::sessionBus(), 0);
+ if (m_script && m_script->isValid()) {
+ return true;
+ } else {
+ qWarning() << "DBUS ERROR:" << m_script->lastError().message();
+ return false;
+ }
+}
+
+void DBusMenuScript::publishMenu()
+{
+ if (m_script) {
+ m_script->call("publishMenu");
+ QTest::qWait(WAIT_TIMEOUT);
+ }
+}
+
+void DBusMenuScript::unpublishMenu()
+{
+ if (m_script) {
+ m_script->call("unpublishMenu");
+ QTest::qWait(WAIT_TIMEOUT);
+ }
+}
+
+
+void DBusMenuScript::quit()
+{
+ if (m_script) {
+ m_script->call("quit");
+ QTest::qWait(WAIT_TIMEOUT);
+ delete m_script;
+ m_script = 0;
+ }
+}
+
+void DBusMenuScript::walk(int steps)
+{
+ if (m_script) {
+ m_script->call("walk", steps);
+ QTest::qWait(WAIT_TIMEOUT);
+ }
+}
+
+void DBusMenuScript::run()
+{
+ if (m_script) {
+ m_script->call("walk", -1);
+ QTest::qWait(WAIT_TIMEOUT);
+ }
+}
diff --git a/tests/script/dbusmenuscript.h b/tests/script/dbusmenuscript.h
new file mode 100644
index 0000000..3d0d3c9
--- /dev/null
+++ b/tests/script/dbusmenuscript.h
@@ -0,0 +1,35 @@
+#ifndef DBUSMENUSCRIPT_H
+#define DBUSMENUSCRITP_H
+
+#include <QObject>
+#include <QDBusInterface>
+#include <QPair>
+
+#define SCRIPT_SERVICE_NAME "com.canonical.test"
+#define SCRIPT_OBJECT_PATH "/com/canonical/test/menuscript"
+#define SCRIPT_INTERFACE_NAME "com.canonical.test.menuscript"
+
+#define MENU_SERVICE_NAME SCRIPT_SERVICE_NAME ".menu"
+#define MENU_OBJECT_PATH SCRIPT_OBJECT_PATH "/menu"
+
+
+class DBusMenuScript
+{
+public:
+ DBusMenuScript();
+ ~DBusMenuScript();
+
+ bool connect();
+ void quit();
+
+ void walk(int steps = 1);
+ void run();
+
+ void publishMenu();
+ void unpublishMenu();
+
+private:
+ QDBusInterface *m_script;
+};
+
+#endif
diff --git a/tests/script/menuscript.py b/tests/script/menuscript.py
new file mode 100644
index 0000000..05e226c
--- /dev/null
+++ b/tests/script/menuscript.py
@@ -0,0 +1,172 @@
+import dbus
+import dbus.service
+from dbus import glib
+from dbus.mainloop.glib import DBusGMainLoop
+from gi.repository import GObject
+from gi.repository import GLib
+from gi.repository import Gio
+
+SERVICE_NAME = "com.canonical.test"
+INTERFACE_NAME = "com.canonical.test.menuscript"
+OBJECT_PATH = "/com/canonical/test/menuscript"
+MENU_SERVICE_NAME= SERVICE_NAME + ".menu"
+MENU_OBJECT_PATH = OBJECT_PATH + "/menu"
+bus = None
+
+class Script(dbus.service.Object):
+ def __init__(self, aList, session, object_path):
+ dbus.service.Object.__init__(self, session, object_path)
+ self._list = aList
+ self._session = session
+
+ def run(self):
+ self._loop = GObject.MainLoop()
+ self._loop.run()
+
+ @dbus.service.method(dbus_interface=INTERFACE_NAME,
+ in_signature='', out_signature='',
+ sender_keyword='sender')
+ def publishMenu(self, sender=None):
+ self._list.start()
+
+ @dbus.service.method(dbus_interface=INTERFACE_NAME,
+ in_signature='', out_signature='',
+ sender_keyword='sender')
+ def unpublishMenu(self, sender=None):
+ self._list.stop()
+
+ @dbus.service.method(dbus_interface=INTERFACE_NAME,
+ in_signature='', out_signature='',
+ sender_keyword='sender')
+ def quit(self, sender=None):
+ self.unpublishMenu();
+ self._loop.quit()
+
+ @dbus.service.method(dbus_interface=INTERFACE_NAME,
+ in_signature='i', out_signature='',
+ sender_keyword='sender')
+ def walk(self, steps, sender=None):
+ if steps == -1 or steps > self._list.size():
+ steps = self._list.size()
+
+ while(steps > 0):
+ self._list.walk()
+ steps -= 1
+
+ @staticmethod
+ def create(aList):
+ global bus
+
+ GObject.threads_init()
+ glib.threads_init()
+
+ dbus_loop = DBusGMainLoop()
+ bus = dbus.SessionBus(mainloop=dbus_loop)
+ bus_name = dbus.service.BusName(SERVICE_NAME, bus=bus)
+ return Script(aList, bus_name, OBJECT_PATH)
+
+class Action(object):
+ def __init__(self, aList, action, **kwargs):
+ self._list = aList
+ self._action = action
+ self._kargs = kwargs
+
+ def setProperties(self, menu, props):
+ if props:
+ for key in props:
+ menu.set_attribute_value(key, props[key])
+
+ def appendItem(self):
+ parentId = self._kargs['parentId']
+ if parentId and len(parentId):
+ parent = self._list.getMenu(parentId)[0]
+ else:
+ parent = self._list._root
+
+ if self._kargs['link'] == None:
+ item = Gio.MenuItem.new(self._kargs['label'], self._kargs['actionName'])
+ self.setProperties(item, self._kargs['properties'])
+ parent.append_item(item)
+ elif self._kargs['link'] == 'section':
+ section = Gio.Menu()
+ parent.append_section(self._kargs['label'], section)
+ elif self._kargs['link'] == 'submenu':
+ submenu = Gio.Menu()
+ parent.append_submenu(self._kargs['label'], submenu)
+
+ def removeItem(self):
+ menuId = self._kargs['menuId']
+ (menu, mId) = self._list.getMenu(menuId)
+ if mId != -1:
+ menu.remove(mId)
+ else:
+ print "Remove menu item"
+
+ def run(self):
+ if self._action == 'append':
+ self.appendItem()
+ elif self._action == 'remove':
+ self.removeItem()
+
+class ActionList(object):
+ def __init__(self, objectPath):
+ self._actions = []
+ self._objectPath = objectPath
+ self._bux = None
+ self._exportID = None
+ self._ownNameID = None
+ self._root = None
+
+ def appendItem(self, label, actionName, link=None, parentId=None, properties=None):
+ self._actions.append(Action(self, 'append',
+ parentId=parentId,
+ label=label,
+ actionName=actionName,
+ link=link,
+ properties=properties))
+
+ def removeItem(self, menuId):
+ self._actions.append(Action(self, 'remove',
+ menuId=menuId))
+
+ def _findMenu(self, root, ids):
+ if len(ids) == 0:
+ return (root, -1)
+
+ currentId = int(ids[0])
+ link = root.get_item_link(currentId, 'section')
+ if link == None:
+ link = root.get_item_link(currentId, 'submenu')
+
+ if link:
+ return self._findMenu(link, ids[1:])
+ else:
+ return (root, currentId)
+
+ def getMenu(self, menuId):
+ return self._findMenu(self._root, str(menuId).split('.'));
+
+ def walk(self):
+ item = self._actions.pop(0)
+ item.run()
+
+ def size(self):
+ return len(self._actions)
+
+ def _exportService(self, connection, name):
+ self._root = Gio.Menu()
+ self._bus = connection
+ self._exportID = connection.export_menu_model(MENU_OBJECT_PATH, self._root)
+
+ def start(self):
+ self._ownNameID = Gio.bus_own_name(2, MENU_SERVICE_NAME, 0, self._exportService, None, None)
+
+ def stop(self):
+ if self._exportID:
+ self._bus.unexport_menu_model(self._exportID)
+ self._exportID = None
+
+ if self._ownNameID:
+ Gio.bus_unown_name(self._ownNameID)
+ self._ownNameID = None
+