diff options
-rw-r--r-- | CMakeLists.txt | 35 | ||||
-rw-r--r-- | examples/CMakeLists.txt | 4 | ||||
-rw-r--r-- | examples/Menu.qml | 18 | ||||
-rw-r--r-- | examples/MenuFactory.qml | 33 | ||||
-rw-r--r-- | examples/MenuSection.qml | 29 | ||||
-rw-r--r-- | examples/SubMenu.qml | 14 | ||||
-rw-r--r-- | examples/main.qml | 44 | ||||
-rwxr-xr-x | examples/run-example.sh.in | 1 | ||||
-rw-r--r-- | examples/simple-client.py | 26 | ||||
-rw-r--r-- | examples/simple.py | 42 | ||||
-rw-r--r-- | src/CMakeLists.txt | 3 | ||||
-rw-r--r-- | src/QMenuModel/CMakeLists.txt | 43 | ||||
-rw-r--r-- | src/QMenuModel/plugin.cpp | 15 | ||||
-rw-r--r-- | src/QMenuModel/plugin.h | 14 | ||||
-rw-r--r-- | src/QMenuModel/qdbusmenumodel.cpp | 125 | ||||
-rw-r--r-- | src/QMenuModel/qdbusmenumodel.h | 65 | ||||
-rw-r--r-- | src/QMenuModel/qmenumodel.cpp | 163 | ||||
-rw-r--r-- | src/QMenuModel/qmenumodel.h | 44 | ||||
-rw-r--r-- | src/QMenuModel/qmldir | 1 |
19 files changed, 719 insertions, 0 deletions
diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..d8dad33 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,35 @@ +project(qmenumodel) + +cmake_minimum_required(VERSION 2.8.0) + +find_package(Qt4 REQUIRED) + +include(FindPkgConfig) +pkg_check_modules(GLIB REQUIRED glib-2.0>=2.32) +add_definitions(-DQT_NO_KEYWORDS) + +find_program(DBUS_RUNNER dbus-test-runner) + +# Cooverage tools +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") + 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() + +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() + diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt new file mode 100644 index 0000000..fda5683 --- /dev/null +++ b/examples/CMakeLists.txt @@ -0,0 +1,4 @@ +project(examples) + +configure_file("${CMAKE_CURRENT_SOURCE_DIR}/run-example.sh.in" + "${CMAKE_CURRENT_BINARY_DIR}/run-example.sh" @ONLY) diff --git a/examples/Menu.qml b/examples/Menu.qml new file mode 100644 index 0000000..3060edb --- /dev/null +++ b/examples/Menu.qml @@ -0,0 +1,18 @@ +import QtQuick 1.1 +import Ubuntu.Components 0.1 +import Ubuntu.Components.ListItems 0.1 as ListItem + + +ListItem.Standard { + property variant menu + + width: parent.width + text: menu.label + + onClicked: { + if (menu.linkSubMenu) + loadMainMenu(menu.linkSubMenu) + else + loadMainMenu(menu.linkSection) + } +} diff --git a/examples/MenuFactory.qml b/examples/MenuFactory.qml new file mode 100644 index 0000000..2729268 --- /dev/null +++ b/examples/MenuFactory.qml @@ -0,0 +1,33 @@ +import QtQuick 1.1 +import Ubuntu.Components 0.1 +import Ubuntu.Components.ListItems 0.1 as ListItem + +Loader { + property variant menu + height: childrenRect.height + + onMenuChanged: { + if (menu) { + if (menu.linkSection) + source = "MenuSection.qml" + else if (menu.linkSubMenu) + source = "SubMenu.qml" + else + source = "Menu.qml"; + } + + console.debug("Load: " + source) + } + + onStatusChanged: { + if (status == Loader.Ready) { + if (menu.linkSection) + item.menu = menu + else if (menu.linkSubMenu) + item.menu = menu + else if (menu) + item.menu = menu + } + } +} + diff --git a/examples/MenuSection.qml b/examples/MenuSection.qml new file mode 100644 index 0000000..2a1b6c9 --- /dev/null +++ b/examples/MenuSection.qml @@ -0,0 +1,29 @@ +import QtQuick 1.1 +import Ubuntu.Components 0.1 +import Ubuntu.Components.ListItems 0.1 as ListItem + + +Item { + property variant menu + width: parent.width + height: contents.height + + Column { + id: contents + width: parent.width + + ListItem.Header { + text: menu.label + } + + Repeater { + model: menu ? menu.linkSection : undefined + + MenuFactory { + menu: model + } + } + + ListItem.Divider { } + } +} diff --git a/examples/SubMenu.qml b/examples/SubMenu.qml new file mode 100644 index 0000000..7049c3b --- /dev/null +++ b/examples/SubMenu.qml @@ -0,0 +1,14 @@ +import QtQuick 1.1 +import Ubuntu.Components 0.1 +import Ubuntu.Components.ListItems 0.1 as ListItem + + +ListItem.Standard { + property variant menu + + anchors.fill: parent + text: menu.label + progression: true + + onClicked: { loadMainMenu (menu.linkSubMenu) } +} diff --git a/examples/main.qml b/examples/main.qml new file mode 100644 index 0000000..857351c --- /dev/null +++ b/examples/main.qml @@ -0,0 +1,44 @@ +import QtQuick 1.1 +import QMenuModel 1.0 +import Ubuntu.Components 0.1 +import Ubuntu.Components.ListItems 0.1 as ListItem + +Rectangle { + id: main + + height: 800 + width: 480 + color: "#eeeeee" + + function loadMainMenu(menu) { + mainMenu.model = menu + } + + QDBusMenuModel { + id: menuModel + busType: 1 + busName: "com.ubuntu.networksettings" + objectPath: "/com/ubuntu/networksettings" + onConnected: { + console.log("Menu appears ") + } + } + + + ListView { + id: mainMenu + anchors.fill: parent + model: menuModel + + delegate: MenuFactory { + width: parent.width + menu: model + } + + Component.onCompleted: { + menuModel.connect() + } + } +} + + diff --git a/examples/run-example.sh.in b/examples/run-example.sh.in new file mode 100755 index 0000000..4843f49 --- /dev/null +++ b/examples/run-example.sh.in @@ -0,0 +1 @@ +gdb --args qmlviewer -I @src_BINARY_DIR@ $1 diff --git a/examples/simple-client.py b/examples/simple-client.py new file mode 100644 index 0000000..ff7b416 --- /dev/null +++ b/examples/simple-client.py @@ -0,0 +1,26 @@ +# creates this menu: +# +# Menu Item +# ---------------- +# One +# Two +# Three +# ---------------- +# Submenu > | One +# | Two +# | Three + +from gi.repository import GLib, Gio + +def on_items_changed (model, position, removed, added, data): + print 'items changed:', position, removed, added + +bus = Gio.bus_get_sync (Gio.BusType.SESSION, None) +print dir(bus) +menu = bus.get_menu_model(':1.473', '/menu') +#menu = Gio.dbus_menu_model_get(Gio.BusType.SESSION, ':1.473', '/menu') +menu.connect ('items-changed', on_items_changed) + +loop = GLib.MainLoop () +loop.run () + diff --git a/examples/simple.py b/examples/simple.py new file mode 100644 index 0000000..bc2d37c --- /dev/null +++ b/examples/simple.py @@ -0,0 +1,42 @@ +# creates this menu: +# +# Menu Item +# ---------------- +# One +# Two +# Three +# ---------------- +# Submenu > | One +# | Two +# | Three + +from gi.repository import GLib, Gio + +def action_activated (action, parameter): + print action.get_name () + +actions = Gio.SimpleActionGroup () +for i in ['one', 'two', 'three']: + action = Gio.SimpleAction.new (i, None) + action.connect ('activate', action_activated) + actions.insert (action) + +numbers = Gio.Menu () +numbers.append ('One', 'one') +numbers.append ('Two', 'two') +numbers.append ('Three', 'three') + +menu = Gio.Menu () +menu.append ('Menu item', 'one') +menu.append_section ('Numbers', numbers) +menu.append_submenu ('Submenu', numbers) + +# export the menu and action group on d-bus +bus = Gio.bus_get_sync (Gio.BusType.SESSION, None) +bus.export_menu_model ('/menu', menu) +bus.export_action_group ('/menu', actions) +print bus.get_unique_name () + +loop = GLib.MainLoop () +loop.run () + diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt new file mode 100644 index 0000000..e2c06cb --- /dev/null +++ b/src/CMakeLists.txt @@ -0,0 +1,3 @@ +project(src) + +add_subdirectory(QMenuModel) diff --git a/src/QMenuModel/CMakeLists.txt b/src/QMenuModel/CMakeLists.txt new file mode 100644 index 0000000..dae9ff4 --- /dev/null +++ b/src/QMenuModel/CMakeLists.txt @@ -0,0 +1,43 @@ +set(QMENUMODEL_SRC + qmenumodel.cpp + qdbusmenumodel.cpp + plugin.cpp +) + +set(QMENUMODEL_HEADERS + qmenumodel.h + qdbusmenumodel.h + plugin.h +) + +qt4_wrap_cpp(QMENUMODEL_MOC + ${QMENUMODEL_HEADERS} +) + +add_library(qmenumodel MODULE + ${QMENUMODEL_SRC} + ${QMENUMODEL_MOC} +) + +#set_target_properties(dbusmenuqmlcommon PROPERTIES COMPILE_FLAGS -fPIC) + +include_directories( + ${CMAKE_CURRENT_SOURCE_DIR} + ${QT_INCLUDE_DIR} + ${QT_QTCORE_INCLUDE_DIR} + ${QT_QTGUI_INCLUDE_DIR} + ${QT_QTDECLARATIVE_INCLUDE_DIR} + ${GLIB_INCLUDE_DIRS} + ${GIO_INCLUDE_DIRS} +) + +target_link_libraries(qmenumodel + ${QT_QTCORE_LIBRARY} + ${QT_QTGUI_LIBRARY} + ${QT_QTDCLARATIVE_LIBRARY} + ${GLIB_LDFLAGS} + ${GIO_LDFLAGS} +) + +execute_process(COMMAND ${CMAKE_COMMAND} -E copy_if_different "${CMAKE_CURRENT_SOURCE_DIR}/qmldir" + "${CMAKE_CURRENT_BINARY_DIR}/qmldir") diff --git a/src/QMenuModel/plugin.cpp b/src/QMenuModel/plugin.cpp new file mode 100644 index 0000000..e6a828b --- /dev/null +++ b/src/QMenuModel/plugin.cpp @@ -0,0 +1,15 @@ +#include "plugin.h" +#include "qmenumodel.h" +#include "qdbusmenumodel.h" + +#include <QtDeclarative> + + +void QMenuModelQmlPlugin::registerTypes(const char *uri) +{ + qmlRegisterUncreatableType<QMenuModel>(uri, 1, 0, "QMenuModel", + "QMenuModel is a interface"); + qmlRegisterType<QDBusMenuModel>(uri, 1, 0, "QDBusMenuModel"); +} + +Q_EXPORT_PLUGIN2(qmenumodel, QMenuModelQmlPlugin) diff --git a/src/QMenuModel/plugin.h b/src/QMenuModel/plugin.h new file mode 100644 index 0000000..9346d32 --- /dev/null +++ b/src/QMenuModel/plugin.h @@ -0,0 +1,14 @@ +#ifndef QMENUMODELQMLPLUGIN_H +#define QMENUMODELQMLPLUGIN_H + +#include <QDeclarativeExtensionPlugin> + + +class QMenuModelQmlPlugin : public QDeclarativeExtensionPlugin +{ + Q_OBJECT +public: + void registerTypes(const char *uri); +}; + +#endif diff --git a/src/QMenuModel/qdbusmenumodel.cpp b/src/QMenuModel/qdbusmenumodel.cpp new file mode 100644 index 0000000..96f936c --- /dev/null +++ b/src/QMenuModel/qdbusmenumodel.cpp @@ -0,0 +1,125 @@ +#include "qdbusmenumodel.h" +#include <QDebug> + +QDBusMenuModel::QDBusMenuModel(QObject *parent) + :QMenuModel(parent), + m_watchId(0), + m_busType(None) +{ +} + +QDBusMenuModel::~QDBusMenuModel() +{ + disconnect(); +} + +QDBusMenuModel::BusType QDBusMenuModel::busType() const +{ + return m_busType; +} + +void QDBusMenuModel::setBusType(QDBusMenuModel::BusType type) +{ + if (m_busType != type) { + if (isConnected()) + disconnect(); + m_busType = type; + Q_EMIT busTypeChanged(m_busType); + } +} + +QString QDBusMenuModel::busName() const +{ + return m_busName; +} + +void QDBusMenuModel::setBusName(const QString &busName) +{ + if (m_busName != busName) { + if (isConnected()) + disconnect(); + m_busName = busName; + Q_EMIT busNameChanged(m_busName); + } +} + +QString QDBusMenuModel::objectPath() const +{ + return m_objectPath; +} + +void QDBusMenuModel::setObjectPath(const QString &objectPath) +{ + if (m_objectPath != objectPath) { + if (isConnected()) + disconnect(); + m_objectPath = objectPath; + Q_EMIT objectPathChanged(m_objectPath); + } +} + +void QDBusMenuModel::connect() +{ + if (isConnected() || (m_watchId > 0)) { + return; + } else if ((m_busType > None) && !m_objectPath.isEmpty() && !m_busName.isEmpty()) { + qDebug() << "Wait for service"; + 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, + QDBusMenuModel::onServiceAppeared, + QDBusMenuModel::onServiceFanished, + this, + NULL); + } else { + Q_EMIT connectionError("Invalid menu model connection args"); + } +} + +void QDBusMenuModel::disconnect() +{ + if (isConnected()) { + g_bus_unwatch_name (m_watchId); + m_watchId = 0; + + setMenuModel(NULL); + Q_EMIT disconnected(); + } +} + +bool QDBusMenuModel::isConnected() const +{ + return (m_watchId != 0); +} + +void QDBusMenuModel::setIntBusType(int busType) +{ + if ((busType > None) && (busType < LastBusType)) { + setBusType(static_cast<BusType>(busType)); + } +} + +void QDBusMenuModel::onServiceAppeared(GDBusConnection *connection, const gchar *, const gchar *, gpointer data) +{ + qDebug() << "Service appears"; + QDBusMenuModel *self = reinterpret_cast<QDBusMenuModel*>(data); + GMenuModel *model = reinterpret_cast<GMenuModel*>(g_dbus_menu_model_get(connection, + self->m_busName.toLatin1(), + self->m_objectPath.toLatin1())); + self->setMenuModel(model); + if (model) { + Q_EMIT self->connected(); + } else { + Q_EMIT self->connectionError("Fail to retrieve menu model"); + self->disconnect(); + } +} + +void QDBusMenuModel::onServiceFanished(GDBusConnection *, const gchar *, gpointer data) +{ + qDebug() << "Service fanished"; + QDBusMenuModel *self = reinterpret_cast<QDBusMenuModel*>(data); + Q_EMIT self->connectionError("Menu model disapear"); + self->disconnect(); +} diff --git a/src/QMenuModel/qdbusmenumodel.h b/src/QMenuModel/qdbusmenumodel.h new file mode 100644 index 0000000..e9d2461 --- /dev/null +++ b/src/QMenuModel/qdbusmenumodel.h @@ -0,0 +1,65 @@ +#ifndef QDBUSMENUMODEL_H +#define QDBUSMENUMODEL_H + +#include "qmenumodel.h" + +#include <gio/gio.h> + +class QDBusMenuModel : public QMenuModel +{ + Q_OBJECT + Q_PROPERTY(int busType READ busType WRITE setIntBusType NOTIFY busTypeChanged) + Q_PROPERTY(QString busName READ busName WRITE setBusName NOTIFY busNameChanged) + Q_PROPERTY(QString objectPath READ objectPath WRITE setObjectPath NOTIFY objectPathChanged) + +public: + enum BusType { + None = 0, + SessionBus, + SystemBus, + LastBusType + }; + + QDBusMenuModel(QObject *parent=0); + ~QDBusMenuModel(); + + BusType busType() const; + void setBusType(BusType type); + + QString busName() const; + void setBusName(const QString &busName); + + QString objectPath() const; + void setObjectPath(const QString &busName); + + bool isConnected() const; + +public Q_SLOTS: + void connect(); + void disconnect(); + + +Q_SIGNALS: + void busTypeChanged(BusType type); + void busNameChanged(const QString &busNameChanged); + void objectPathChanged(const QString &objectPath); + + void connected(); + void disconnected(); + void connectionError(const QString &errorMessage); + +private: + guint m_watchId; + BusType m_busType; + QString m_busName; + QString m_objectPath; + + // workaround to support busType as int + void setIntBusType(int busType); + + // glib slots + static void onServiceAppeared(GDBusConnection *connection, const gchar *name, const gchar *name_owner, gpointer data); + static void onServiceFanished(GDBusConnection *connection, const gchar *name, gpointer data); +}; + +#endif diff --git a/src/QMenuModel/qmenumodel.cpp b/src/QMenuModel/qmenumodel.cpp new file mode 100644 index 0000000..173dbbe --- /dev/null +++ b/src/QMenuModel/qmenumodel.cpp @@ -0,0 +1,163 @@ +#include "qmenumodel.h" +#include <QDebug> + +QMenuModel::QMenuModel(QObject *parent, GMenuModel *other) + : QAbstractListModel(parent), + m_menuModel(0), + m_signalChangedId(0) +{ + static QHash<int, QByteArray> rolesNames; + if (rolesNames.empty()) { + rolesNames[Action] = "action"; + rolesNames[Label] = "label"; + rolesNames[LinkSection] = "linkSection"; + rolesNames[LinkSubMenu] = "linkSubMenu"; + } + setRoleNames(rolesNames); + setMenuModel(other); +} + +QMenuModel::~QMenuModel() +{ + setMenuModel(NULL); +} + +void QMenuModel::setMenuModel(GMenuModel *other) +{ + if (m_menuModel == other) { + return; + } + + beginResetModel(); + + if (m_menuModel) { + g_signal_handler_disconnect(m_menuModel, m_signalChangedId); + m_signalChangedId = 0; + g_object_unref(m_menuModel); + } + + m_menuModel = other; + + endResetModel(); + + if (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); + } +} + +GMenuModel *QMenuModel::menuModel() const +{ + return m_menuModel; +} + +/* QAbstractItemModel */ +int QMenuModel::columnCount(const QModelIndex &) const +{ + return 1; +} + +QVariant QMenuModel::data(const QModelIndex &index, int role) const +{ + QVariant attribute; + int rowCountValue = rowCount(); + + if ((rowCountValue > 0) && (index.row() >= 0) && (index.row() < rowCountValue)) { + qDebug() << "GetData: " << index.row() << role; + if (m_menuModel) { + switch (role) + { + case Action: + attribute = getStringAttribute(index, G_MENU_ATTRIBUTE_ACTION); + break; + case Label: + attribute = getStringAttribute(index, G_MENU_ATTRIBUTE_LABEL); + break; + case LinkSection: + attribute = getLink(index, G_MENU_LINK_SECTION); + break; + case LinkSubMenu: + attribute = getLink(index, G_MENU_LINK_SUBMENU); + break; + default: + break; + } + } + } + qDebug() << "GetData done" << attribute; + return attribute; +} + +QModelIndex QMenuModel::parent(const QModelIndex &index) const +{ + return QModelIndex(); +} + +int QMenuModel::rowCount(const QModelIndex &) const +{ + if (m_menuModel) { + return g_menu_model_get_n_items(m_menuModel); + } + return 0; +} + +QVariant QMenuModel::getStringAttribute(const QModelIndex &index, + const QString &attribute) const +{ + QVariant result; + gchar* value = NULL; + g_menu_model_get_item_attribute(m_menuModel, + index.row(), + attribute.toLatin1(), + "s", &value); + if (value) { + result = QVariant(QString::fromLatin1(value)); + g_free(value); + } + return result; +} + +QVariant QMenuModel::getLink(const QModelIndex &index, + const QString &linkName) const +{ + GMenuModel *link; + + link = g_menu_model_get_item_link(m_menuModel, + index.row(), + linkName.toLatin1()); + + if (link) { + qDebug() << "link: " << (void*)link; + QMenuModel *other = new QMenuModel(const_cast<QMenuModel*>(this), link); + qDebug() << "link created: " << (void*)link; + return QVariant::fromValue<QObject*>(other); + } + + return QVariant(); +} + +void QMenuModel::onItemsChanged(GMenuModel *, + gint position, + gint removed, + gint added, + gpointer data) +{ + QMenuModel *self = reinterpret_cast<QMenuModel*>(data); + + qDebug() << "model changed" << position << removed << added; + + if (removed > 0) { + self->beginRemoveRows(QModelIndex(), position, position + removed - 1); + self->endRemoveRows(); + } + + if (added > 0) { + self->beginInsertRows(QModelIndex(), position, position + added - 1); + self->endInsertRows(); + } + qDebug() << "model size: " << self->rowCount(); +} + diff --git a/src/QMenuModel/qmenumodel.h b/src/QMenuModel/qmenumodel.h new file mode 100644 index 0000000..91b0eb9 --- /dev/null +++ b/src/QMenuModel/qmenumodel.h @@ -0,0 +1,44 @@ +#ifndef QMENUMODEL_H +#define QMENUMODEL_H + +#include <QAbstractListModel> +#include <gio/gio.h> + + +class QMenuModel : public QAbstractListModel +{ + Q_OBJECT + +public: + enum MenuRoles { + Action = 0, + Label, + LinkSection, + LinkSubMenu + }; + + ~QMenuModel(); + + /* QAbstractItemModel */ + int columnCount(const QModelIndex &parent = QModelIndex()) const; + QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const; + QModelIndex parent (const QModelIndex &index) const; + int rowCount(const QModelIndex &parent = QModelIndex()) const; + +protected: + QMenuModel(QObject *parent=0, GMenuModel *other=0); + void setMenuModel(GMenuModel *model); + GMenuModel *menuModel() const; + +private: + GMenuModel *m_menuModel; + guint m_signalChangedId; + + QVariant getStringAttribute(const QModelIndex &index, const QString &attribute) const; + QVariant getLink(const QModelIndex &index, const QString &linkName) const; + + static void onItemsChanged(GMenuModel *model, gint position, gint removed, gint added, gpointer data); + +}; + +#endif diff --git a/src/QMenuModel/qmldir b/src/QMenuModel/qmldir new file mode 100644 index 0000000..c8f525f --- /dev/null +++ b/src/QMenuModel/qmldir @@ -0,0 +1 @@ +plugin qmenumodel |