aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorRenato Araujo Oliveira Filho <renato.filho@canonical.com>2012-09-05 16:15:39 -0300
committerRenato Araujo Oliveira Filho <renato.filho@canonical.com>2012-09-05 16:15:39 -0300
commit0dfd3d4cd4214a732e4a5cda59fd4dfc082b8425 (patch)
treea4d1d4e876e31a1265581ee89c673eb66aaaae8d
downloadqmenumodel-0dfd3d4cd4214a732e4a5cda59fd4dfc082b8425.tar.gz
qmenumodel-0dfd3d4cd4214a732e4a5cda59fd4dfc082b8425.tar.bz2
qmenumodel-0dfd3d4cd4214a732e4a5cda59fd4dfc082b8425.zip
Initial import.
-rw-r--r--CMakeLists.txt35
-rw-r--r--examples/CMakeLists.txt4
-rw-r--r--examples/Menu.qml18
-rw-r--r--examples/MenuFactory.qml33
-rw-r--r--examples/MenuSection.qml29
-rw-r--r--examples/SubMenu.qml14
-rw-r--r--examples/main.qml44
-rwxr-xr-xexamples/run-example.sh.in1
-rw-r--r--examples/simple-client.py26
-rw-r--r--examples/simple.py42
-rw-r--r--src/CMakeLists.txt3
-rw-r--r--src/QMenuModel/CMakeLists.txt43
-rw-r--r--src/QMenuModel/plugin.cpp15
-rw-r--r--src/QMenuModel/plugin.h14
-rw-r--r--src/QMenuModel/qdbusmenumodel.cpp125
-rw-r--r--src/QMenuModel/qdbusmenumodel.h65
-rw-r--r--src/QMenuModel/qmenumodel.cpp163
-rw-r--r--src/QMenuModel/qmenumodel.h44
-rw-r--r--src/QMenuModel/qmldir1
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