aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.bzrignore3
-rw-r--r--configure.ac25
-rw-r--r--libdbusmenu-glib/client.c9
-rw-r--r--libdbusmenu-glib/menuitem.c60
-rw-r--r--libdbusmenu-glib/menuitem.h2
-rw-r--r--libdbusmenu-glib/server.c1
-rw-r--r--libdbusmenu-gtk/Makefile.am9
-rw-r--r--libdbusmenu-gtk/menu.c304
-rw-r--r--libdbusmenu-gtk/menu.h74
-rw-r--r--libdbusmenu-gtk/test.c4
-rw-r--r--libdbusmenu-gtk/test.h2
-rw-r--r--tests/Makefile.am45
-rw-r--r--tests/test-glib-layout-server.c2
-rw-r--r--tests/test-glib-properties-server.c7
-rw-r--r--tests/test-gtk-label-client.c181
-rw-r--r--tests/test-gtk-label-server.c139
-rw-r--r--tests/test-gtk-label.json158
17 files changed, 1007 insertions, 18 deletions
diff --git a/.bzrignore b/.bzrignore
index 8c4e180..da9e9ce 100644
--- a/.bzrignore
+++ b/.bzrignore
@@ -33,3 +33,6 @@ menuitem-marshal.h
libdbusmenu_glib_la-menuitem-marshal.lo
test-glib-properties-client
test-glib-properties-server
+libdbusmenu_gtk_la-menu.lo
+test-gtk-label-client
+test-gtk-label-server
diff --git a/configure.ac b/configure.ac
index 96b23fc..d35e27b 100644
--- a/configure.ac
+++ b/configure.ac
@@ -35,6 +35,31 @@ AC_SUBST(DBUSMENUGLIB_CFLAGS)
AC_SUBST(DBUSMENUGLIB_LIBS)
###########################
+# Dependencies - GTK
+###########################
+
+GTK_REQUIRED_VERSION=2.16
+
+PKG_CHECK_MODULES(DBUSMENUGTK, gtk+-2.0 >= $GTK_REQUIRED_VERSION
+ glib-2.0 >= $GLIB_REQUIRED_VERSION
+ dbus-glib-1 >= $DBUS_REQUIRED_VERSION
+ libxml-2.0 >= $XML_REQUIRED_VERSION)
+
+AC_SUBST(DBUSMENUGTK_CFLAGS)
+AC_SUBST(DBUSMENUGTK_LIBS)
+
+###########################
+# Dependencies - Testing
+###########################
+
+JSON_GLIB_REQUIRED_VERSION=0.6.0
+
+PKG_CHECK_MODULES(DBUSMENUTESTS, json-glib-1.0 >= $JSON_GLIB_REQUIRED_VERSION)
+
+AC_SUBST(DBUSMENUTESTS_CFLAGS)
+AC_SUBST(DBUSMENUTESTS_LIBS)
+
+###########################
# Lib versioning
###########################
diff --git a/libdbusmenu-glib/client.c b/libdbusmenu-glib/client.c
index 6094eca..22f65fa 100644
--- a/libdbusmenu-glib/client.c
+++ b/libdbusmenu-glib/client.c
@@ -170,6 +170,11 @@ dbusmenu_client_dispose (GObject *object)
}
priv->session_bus = NULL;
+ if (priv->root != NULL) {
+ g_object_unref(G_OBJECT(priv->root));
+ priv->root = NULL;
+ }
+
G_OBJECT_CLASS (dbusmenu_client_parent_class)->dispose (object);
return;
}
@@ -388,6 +393,7 @@ parse_layout_xml(xmlNodePtr node, DbusmenuMenuitem * item, DbusmenuMenuitem * pa
dbusmenu_menuitem_child_delete(parent, item);
}
g_object_unref(G_OBJECT(item));
+ item = NULL;
}
if (id == 0) {
@@ -404,6 +410,7 @@ parse_layout_xml(xmlNodePtr node, DbusmenuMenuitem * item, DbusmenuMenuitem * pa
xmlNodePtr children;
guint position;
GList * oldchildren = dbusmenu_menuitem_take_children(item);
+ /* g_debug("Starting old children: %d", g_list_length(oldchildren)); */
for (children = node->children, position = 0; children != NULL; children = children->next, position++) {
/* g_debug("Looking at child: %d", position); */
@@ -424,9 +431,11 @@ parse_layout_xml(xmlNodePtr node, DbusmenuMenuitem * item, DbusmenuMenuitem * pa
dbusmenu_menuitem_child_add_position(item, childmi, position);
}
+ /* g_debug("Stopping old children: %d", g_list_length(oldchildren)); */
GList * oldchildleft = NULL;
for (oldchildleft = oldchildren; oldchildleft != NULL; oldchildleft = g_list_next(oldchildleft)) {
DbusmenuMenuitem * oldmi = DBUSMENU_MENUITEM(oldchildleft->data);
+ g_debug("Unref'ing menu item with layout update. ID: %d", dbusmenu_menuitem_get_id(oldmi));
g_object_unref(G_OBJECT(oldmi));
}
g_list_free(oldchildren);
diff --git a/libdbusmenu-glib/menuitem.c b/libdbusmenu-glib/menuitem.c
index 9506cad..95391a4 100644
--- a/libdbusmenu-glib/menuitem.c
+++ b/libdbusmenu-glib/menuitem.c
@@ -139,7 +139,7 @@ dbusmenu_menuitem_class_init (DbusmenuMenuitemClass *klass)
G_STRUCT_OFFSET(DbusmenuMenuitemClass, child_added),
NULL, NULL,
_dbusmenu_menuitem_marshal_VOID__OBJECT,
- G_TYPE_NONE, 2, G_TYPE_OBJECT);
+ G_TYPE_NONE, 1, G_TYPE_OBJECT);
/**
DbusmenuMenuitem::child-removed:
@arg0: The #DbusmenuMenuitem which was the parent.
@@ -156,7 +156,7 @@ dbusmenu_menuitem_class_init (DbusmenuMenuitemClass *klass)
G_STRUCT_OFFSET(DbusmenuMenuitemClass, child_removed),
NULL, NULL,
_dbusmenu_menuitem_marshal_VOID__OBJECT,
- G_TYPE_NONE, 2, G_TYPE_OBJECT);
+ G_TYPE_NONE, 1, G_TYPE_OBJECT);
g_object_class_install_property (object_class, PROP_ID,
g_param_spec_uint("id", "ID for the menu item",
@@ -185,6 +185,14 @@ dbusmenu_menuitem_init (DbusmenuMenuitem *self)
static void
dbusmenu_menuitem_dispose (GObject *object)
{
+ DbusmenuMenuitemPrivate * priv = DBUSMENU_MENUITEM_GET_PRIVATE(object);
+
+ GList * child = NULL;
+ for (child = priv->children; child != NULL; child = g_list_next(child)) {
+ g_object_unref(G_OBJECT(child->data));
+ }
+ g_list_free(priv->children);
+ priv->children = NULL;
G_OBJECT_CLASS (dbusmenu_menuitem_parent_class)->dispose (object);
return;
@@ -193,6 +201,7 @@ dbusmenu_menuitem_dispose (GObject *object)
static void
dbusmenu_menuitem_finalize (GObject *object)
{
+ g_debug("Menuitem dying");
DbusmenuMenuitemPrivate * priv = DBUSMENU_MENUITEM_GET_PRIVATE(object);
if (priv->properties != NULL) {
@@ -394,6 +403,28 @@ dbusmenu_menuitem_child_append (DbusmenuMenuitem * mi, DbusmenuMenuitem * child)
}
/**
+ dbusmenu_menuitem_child_prepend:
+ @mi: The #DbusmenuMenuitem which will become a new parent
+ @child: The #DbusmenMenuitem that will be a child
+
+ This function adds @child to the list of children on @mi at
+ the beginning of that list.
+
+ Return value: Whether the child has been added successfully.
+*/
+gboolean
+dbusmenu_menuitem_child_prepend (DbusmenuMenuitem * mi, DbusmenuMenuitem * child)
+{
+ g_return_val_if_fail(DBUSMENU_IS_MENUITEM(mi), FALSE);
+ g_return_val_if_fail(DBUSMENU_IS_MENUITEM(child), FALSE);
+
+ DbusmenuMenuitemPrivate * priv = DBUSMENU_MENUITEM_GET_PRIVATE(mi);
+ priv->children = g_list_prepend(priv->children, child);
+ g_signal_emit(G_OBJECT(mi), signals[CHILD_ADDED], 0, child, TRUE);
+ return TRUE;
+}
+
+/**
dbusmenu_menuitem_child_delete:
@mi: The #DbusmenuMenuitem which has @child as a child
@child: The child #DbusmenuMenuitem that you want to no longer
@@ -441,6 +472,31 @@ dbusmenu_menuitem_child_add_position (DbusmenuMenuitem * mi, DbusmenuMenuitem *
}
/**
+ dbusmenu_menuitem_child_reorder:
+ @base: The #DbusmenuMenuitem that has children needing realignment
+ @child: The #DbusmenuMenuitem that is a child needing to be moved
+ @position: The position in the list to place it in
+
+ This function moves a child on the list of children. It is
+ for a child that is already in the list, but simply needs a
+ new location.
+
+ Return value: Whether the move was successful.
+*/
+gboolean
+dbusmenu_menuitem_child_reorder(DbusmenuMenuitem * mi, DbusmenuMenuitem * child, guint position)
+{
+ g_return_val_if_fail(DBUSMENU_IS_MENUITEM(mi), FALSE);
+ g_return_val_if_fail(DBUSMENU_IS_MENUITEM(child), FALSE);
+
+ DbusmenuMenuitemPrivate * priv = DBUSMENU_MENUITEM_GET_PRIVATE(mi);
+ priv->children = g_list_remove(priv->children, child);
+ priv->children = g_list_insert(priv->children, child, position);
+
+ return TRUE;
+}
+
+/**
dbusmenu_menuitem_child_find:
@mi: The #DbusmenuMenuitem who's children to look on
@id: The ID of the child that we're looking for.
diff --git a/libdbusmenu-glib/menuitem.h b/libdbusmenu-glib/menuitem.h
index 3f3e97c..c4fcf73 100644
--- a/libdbusmenu-glib/menuitem.h
+++ b/libdbusmenu-glib/menuitem.h
@@ -107,8 +107,10 @@ GList * dbusmenu_menuitem_take_children (DbusmenuMenuitem * mi) G_GNUC_WARN_UNUS
guint dbusmenu_menuitem_get_position (DbusmenuMenuitem * mi, DbusmenuMenuitem * parent);
gboolean dbusmenu_menuitem_child_append (DbusmenuMenuitem * mi, DbusmenuMenuitem * child);
+gboolean dbusmenu_menuitem_child_prepend (DbusmenuMenuitem * mi, DbusmenuMenuitem * child);
gboolean dbusmenu_menuitem_child_delete (DbusmenuMenuitem * mi, DbusmenuMenuitem * child);
gboolean dbusmenu_menuitem_child_add_position (DbusmenuMenuitem * mi, DbusmenuMenuitem * child, guint position);
+gboolean dbusmenu_menuitem_child_reorder (DbusmenuMenuitem * mi, DbusmenuMenuitem * child, guint position);
DbusmenuMenuitem * dbusmenu_menuitem_child_find (DbusmenuMenuitem * mi, guint id);
DbusmenuMenuitem * dbusmenu_menuitem_find_id (DbusmenuMenuitem * mi, guint id);
diff --git a/libdbusmenu-glib/server.c b/libdbusmenu-glib/server.c
index 562b2d7..cf8ea85 100644
--- a/libdbusmenu-glib/server.c
+++ b/libdbusmenu-glib/server.c
@@ -490,6 +490,7 @@ dbusmenu_server_set_root (DbusmenuServer * self, DbusmenuMenuitem * root)
g_value_init(&rootvalue, G_TYPE_OBJECT);
g_value_set_object(&rootvalue, root);
g_object_set_property(G_OBJECT(self), DBUSMENU_SERVER_PROP_ROOT_NODE, &rootvalue);
+ g_object_unref(G_OBJECT(root));
return;
}
diff --git a/libdbusmenu-gtk/Makefile.am b/libdbusmenu-gtk/Makefile.am
index 1e36228..831719c 100644
--- a/libdbusmenu-gtk/Makefile.am
+++ b/libdbusmenu-gtk/Makefile.am
@@ -8,10 +8,10 @@ lib_LTLIBRARIES = \
libdbusmenu_gtkincludedir=$(includedir)/libdbusmenu-0.1/libdbusmenu-gtk/
libdbusmenu_gtkinclude_HEADERS = \
- test.h
+ menu.h
libdbusmenu_gtk_la_SOURCES = \
- test.c
+ menu.c
libdbusmenu_gtk_la_LDFLAGS = \
-version-info $(LIBDBUSMENU_CURRENT):$(LIBDBUSMENU_REVISION):$(LIBDBUSMENU_AGE) \
@@ -19,10 +19,11 @@ libdbusmenu_gtk_la_LDFLAGS = \
-export-symbols-regex "^[^_].*"
libdbusmenu_gtk_la_CFLAGS = \
- $(LIBDBUSMENU_GTK_CFLAGS)
+ $(DBUSMENUGTK_CFLAGS) -I$(srcdir)/.. -Wall -Werror
libdbusmenu_gtk_la_LIBADD = \
- $(LIBDBUSMENU_GTK_LIBS)
+ ../libdbusmenu-glib/libdbusmenu-glib.la \
+ $(DBUSMENUGTK_LIBS)
pkgconfig_DATA = dbusmenu-gtk.pc
pkgconfigdir = $(libdir)/pkgconfig
diff --git a/libdbusmenu-gtk/menu.c b/libdbusmenu-gtk/menu.c
new file mode 100644
index 0000000..dd2df37
--- /dev/null
+++ b/libdbusmenu-gtk/menu.c
@@ -0,0 +1,304 @@
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include <gtk/gtk.h>
+
+#include "menu.h"
+#include "libdbusmenu-glib/client.h"
+
+/* Properties */
+enum {
+ PROP_0,
+ PROP_DBUSOBJECT,
+ PROP_DBUSNAME
+};
+
+/* Private */
+typedef struct _DbusmenuGtkMenuPrivate DbusmenuGtkMenuPrivate;
+struct _DbusmenuGtkMenuPrivate {
+ DbusmenuClient * client;
+
+ gchar * dbus_object;
+ gchar * dbus_name;
+};
+
+#define DBUSMENU_GTKMENU_GET_PRIVATE(o) \
+(G_TYPE_INSTANCE_GET_PRIVATE ((o), DBUSMENU_GTKMENU_TYPE, DbusmenuGtkMenuPrivate))
+
+/* Prototypes */
+static void dbusmenu_gtkmenu_class_init (DbusmenuGtkMenuClass *klass);
+static void dbusmenu_gtkmenu_init (DbusmenuGtkMenu *self);
+static void dbusmenu_gtkmenu_dispose (GObject *object);
+static void dbusmenu_gtkmenu_finalize (GObject *object);
+static void set_property (GObject * obj, guint id, const GValue * value, GParamSpec * pspec);
+static void get_property (GObject * obj, guint id, GValue * value, GParamSpec * pspec);
+/* Internal */
+static void build_client (DbusmenuGtkMenu * self);
+
+/* GObject Stuff */
+G_DEFINE_TYPE (DbusmenuGtkMenu, dbusmenu_gtkmenu, GTK_TYPE_MENU);
+
+static void
+dbusmenu_gtkmenu_class_init (DbusmenuGtkMenuClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+ g_type_class_add_private (klass, sizeof (DbusmenuGtkMenuPrivate));
+
+ object_class->dispose = dbusmenu_gtkmenu_dispose;
+ object_class->finalize = dbusmenu_gtkmenu_finalize;
+ object_class->set_property = set_property;
+ object_class->get_property = get_property;
+
+ g_object_class_install_property (object_class, PROP_DBUSOBJECT,
+ g_param_spec_string(DBUSMENU_CLIENT_PROP_DBUS_OBJECT, "DBus Object we represent",
+ "The Object on the client that we're getting our data from.",
+ NULL,
+ G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS));
+ g_object_class_install_property (object_class, PROP_DBUSNAME,
+ g_param_spec_string(DBUSMENU_CLIENT_PROP_DBUS_NAME, "DBus Client we connect to",
+ "Name of the DBus client we're connecting to.",
+ NULL,
+ G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS));
+
+ return;
+}
+
+static void
+dbusmenu_gtkmenu_init (DbusmenuGtkMenu *self)
+{
+ DbusmenuGtkMenuPrivate * priv = DBUSMENU_GTKMENU_GET_PRIVATE(self);
+
+ priv->client = NULL;
+
+ priv->dbus_object = NULL;
+ priv->dbus_name = NULL;
+
+ return;
+}
+
+static void
+dbusmenu_gtkmenu_dispose (GObject *object)
+{
+ DbusmenuGtkMenuPrivate * priv = DBUSMENU_GTKMENU_GET_PRIVATE(object);
+
+ if (priv->client != NULL) {
+ g_object_unref(G_OBJECT(priv->client));
+ priv->client = NULL;
+ }
+
+ G_OBJECT_CLASS (dbusmenu_gtkmenu_parent_class)->dispose (object);
+ return;
+}
+
+static void
+dbusmenu_gtkmenu_finalize (GObject *object)
+{
+ DbusmenuGtkMenuPrivate * priv = DBUSMENU_GTKMENU_GET_PRIVATE(object);
+
+ g_free(priv->dbus_object);
+ priv->dbus_object = NULL;
+
+ g_free(priv->dbus_name);
+ priv->dbus_name = NULL;
+
+ G_OBJECT_CLASS (dbusmenu_gtkmenu_parent_class)->finalize (object);
+ return;
+}
+
+static void
+set_property (GObject * obj, guint id, const GValue * value, GParamSpec * pspec)
+{
+ DbusmenuGtkMenuPrivate * priv = DBUSMENU_GTKMENU_GET_PRIVATE(obj);
+
+ switch (id) {
+ case PROP_DBUSNAME:
+ priv->dbus_name = g_value_dup_string(value);
+ if (priv->dbus_name != NULL && priv->dbus_object != NULL) {
+ build_client(DBUSMENU_GTKMENU(obj));
+ }
+ break;
+ case PROP_DBUSOBJECT:
+ priv->dbus_object = g_value_dup_string(value);
+ if (priv->dbus_name != NULL && priv->dbus_object != NULL) {
+ build_client(DBUSMENU_GTKMENU(obj));
+ }
+ break;
+ default:
+ g_warning("Unknown property %d.", id);
+ return;
+ }
+
+ return;
+}
+
+static void
+get_property (GObject * obj, guint id, GValue * value, GParamSpec * pspec)
+{
+ DbusmenuGtkMenuPrivate * priv = DBUSMENU_GTKMENU_GET_PRIVATE(obj);
+
+ switch (id) {
+ case PROP_DBUSNAME:
+ g_value_set_string(value, priv->dbus_name);
+ break;
+ case PROP_DBUSOBJECT:
+ g_value_set_string(value, priv->dbus_object);
+ break;
+ default:
+ g_warning("Unknown property %d.", id);
+ return;
+ }
+
+ return;
+}
+
+/* Internal Functions */
+
+static const gchar * data_menuitem = "dbusmenugtk-data-gtkmenuitem";
+static const gchar * data_menu = "dbusmenugtk-data-gtkmenu";
+
+static gboolean
+menu_pressed_cb (GtkMenuItem * gmi, DbusmenuMenuitem * mi)
+{
+ dbusmenu_menuitem_activate(mi);
+ return TRUE;
+}
+
+static void
+menu_prop_change_cb (DbusmenuMenuitem * mi, gchar * prop, gchar * value, GtkMenuItem * gmi)
+{
+ if (!g_strcmp0(prop, "label")) {
+ gtk_menu_item_set_label(gmi, value);
+ gtk_widget_show(GTK_WIDGET(gmi));
+ }
+
+ return;
+}
+
+static void
+destoryed_dbusmenuitem_cb (gpointer udata, GObject * dbusmenuitem)
+{
+ /* g_debug("DbusmenuMenuitem was destroyed"); */
+ gtk_widget_destroy(GTK_WIDGET(udata));
+ return;
+}
+
+static void
+connect_menuitem (DbusmenuMenuitem * mi, GtkMenuItem * gmi)
+{
+ g_object_set_data(G_OBJECT(mi), data_menuitem, gmi);
+
+ g_signal_connect(G_OBJECT(mi), DBUSMENU_MENUITEM_SIGNAL_PROPERTY_CHANGED, G_CALLBACK(menu_prop_change_cb), gmi);
+ g_signal_connect(G_OBJECT(gmi), "activate", G_CALLBACK(menu_pressed_cb), mi);
+
+ g_object_weak_ref(G_OBJECT(mi), destoryed_dbusmenuitem_cb, gmi);
+
+ return;
+}
+
+static void
+process_dbusmenu_menuitem (DbusmenuMenuitem * mi, GtkMenu * parentmenu)
+{
+ gpointer unknown_menuitem = g_object_get_data(G_OBJECT(mi), data_menuitem);
+ if (unknown_menuitem == NULL) {
+ /* Oh, a virgin DbusmenuMenuitem, let's fix that. */
+ GtkWidget * menuitem = gtk_menu_item_new();
+ connect_menuitem(mi, GTK_MENU_ITEM(menuitem));
+ unknown_menuitem = menuitem;
+ gtk_menu_shell_append(GTK_MENU_SHELL(parentmenu), menuitem);
+ }
+
+ GList * children = dbusmenu_menuitem_get_children(mi);
+ if (children == NULL) {
+ /* If there are no children to process we are
+ done and we can move along */
+ return;
+ }
+
+ /* Phase 0: Make a submenu if we don't have one */
+ gpointer unknown_menu = g_object_get_data(G_OBJECT(mi), data_menu);
+ if (unknown_menu == NULL) {
+ GtkWidget * gtkmenu = gtk_menu_new();
+ g_object_ref(gtkmenu);
+ g_object_set_data_full(G_OBJECT(mi), data_menu, gtkmenu, g_object_unref);
+ unknown_menu = gtkmenu;
+ gtk_menu_item_set_submenu(GTK_MENU_ITEM(unknown_menuitem), gtkmenu);
+ gtk_widget_show(gtkmenu);
+ }
+
+ /* Phase 1: Add missing children */
+ GList * child = NULL;
+ for (child = children; child != NULL; child = g_list_next(child)) {
+ process_dbusmenu_menuitem(DBUSMENU_MENUITEM(child->data), GTK_MENU(unknown_menu));
+ }
+
+ /* Phase 2: Delete removed children */
+ /* Actually, we don't need to do this because of the weak
+ reference that we've added above. When the DbusmenuMenuitem
+ gets destroyed it takes its GtkMenuItem with it. Bye bye. */
+
+ /* Phase 3: Profit! */
+ return;
+}
+
+/* Processing the layout being updated and handling
+ that and making it into a menu */
+static void
+process_layout_change (DbusmenuClient * client, DbusmenuGtkMenu * gtkmenu)
+{
+ DbusmenuMenuitem * root = dbusmenu_client_get_root(client);
+
+ GList * children = dbusmenu_menuitem_get_children(root);
+ if (children == NULL) {
+ return;
+ }
+
+ GList * child = NULL;
+ for (child = children; child != NULL; child = g_list_next(child)) {
+ process_dbusmenu_menuitem(DBUSMENU_MENUITEM(child->data), GTK_MENU(gtkmenu));
+ }
+
+ return;
+}
+
+
+/* Builds the client and connects all of the signals
+ up for it so that it's happy-happy */
+static void
+build_client (DbusmenuGtkMenu * self)
+{
+ DbusmenuGtkMenuPrivate * priv = DBUSMENU_GTKMENU_GET_PRIVATE(self);
+
+ if (priv->client == NULL) {
+ priv->client = dbusmenu_client_new(priv->dbus_name, priv->dbus_object);
+
+ /* Register for layout changes, this should come after the
+ creation of the client pulls it from DBus */
+ g_signal_connect(G_OBJECT(priv->client), DBUSMENU_CLIENT_SIGNAL_LAYOUT_UPDATED, G_CALLBACK(process_layout_change), self);
+ }
+
+ return;
+}
+
+/* Public API */
+
+/**
+ dbusmenu_gtkmenu_new:
+ @dbus_name: Name of the #DbusmenuServer on DBus
+ @dbus_name: Name of the object on the #DbusmenuServer
+
+ Creates a new #DbusmenuGtkMenu object and creates a #DbusmenuClient
+ that connects across DBus to a #DbusmenuServer.
+
+ Return value: A new #DbusmenuGtkMenu sync'd with a server
+*/
+DbusmenuGtkMenu *
+dbusmenu_gtkmenu_new (gchar * dbus_name, gchar * dbus_object)
+{
+ return g_object_new(DBUSMENU_GTKMENU_TYPE,
+ DBUSMENU_CLIENT_PROP_DBUS_OBJECT, dbus_object,
+ DBUSMENU_CLIENT_PROP_DBUS_NAME, dbus_name,
+ NULL);
+}
+
diff --git a/libdbusmenu-gtk/menu.h b/libdbusmenu-gtk/menu.h
new file mode 100644
index 0000000..dd5bdd1
--- /dev/null
+++ b/libdbusmenu-gtk/menu.h
@@ -0,0 +1,74 @@
+#ifndef __DBUSMENU_GTKMENU_H__
+#define __DBUSMENU_GTKMENU_H__
+
+#include <glib.h>
+#include <glib-object.h>
+
+G_BEGIN_DECLS
+
+#define DBUSMENU_GTKMENU_TYPE (dbusmenu_gtkmenu_get_type ())
+#define DBUSMENU_GTKMENU(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), DBUSMENU_GTKMENU_TYPE, DbusmenuGtkMenu))
+#define DBUSMENU_GTKMENU_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), DBUSMENU_GTKMENU_TYPE, DbusmenuGtkMenuClass))
+#define DBUSMENU_IS_GTKMENU(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), DBUSMENU_GTKMENU_TYPE))
+#define DBUSMENU_IS_GTKMENU_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), DBUSMENU_GTKMENU_TYPE))
+#define DBUSMENU_GTKMENU_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), DBUSMENU_GTKMENU_TYPE, DbusmenuGtkMenuClass))
+
+/**
+ DbusmenuGtkMenuClass:
+ @parent_class: #GtkMenuClass
+ @reserved1: Reserved for future use.
+ @reserved2: Reserved for future use.
+ @reserved3: Reserved for future use.
+ @reserved4: Reserved for future use.
+*/
+typedef struct _DbusmenuGtkMenuClass DbusmenuGtkMenuClass;
+struct _DbusmenuGtkMenuClass {
+ GtkMenuClass parent_class;
+
+ /* Reserved */
+ void (*reserved1) (void);
+ void (*reserved2) (void);
+ void (*reserved3) (void);
+ void (*reserved4) (void);
+};
+
+/**
+ DbusmenuGtkMenu:
+ @parent: #GtkMenu
+*/
+typedef struct _DbusmenuGtkMenu DbusmenuGtkMenu;
+struct _DbusmenuGtkMenu {
+ GtkMenu parent;
+};
+
+GType dbusmenu_gtkmenu_get_type (void);
+DbusmenuGtkMenu * dbusmenu_gtkmenu_new (gchar * dbus_name, gchar * dbus_object);
+
+/**
+ SECTION:gtkmenu
+ @short_description: A GTK Menu Object that syncronizes over DBus
+ @stability: Unstable
+ @include: libdbusmenu-gtk/menu.h
+
+ In general, this is just a #GtkMenu, why else would you care? Oh,
+ because this menu is created by someone else on a server that exists
+ on the other side of DBus. You need a #DbusmenuServer to be able
+ push the data into this menu.
+
+ The first thing you need to know is how to find that #DbusmenuServer
+ on DBus. This involves both the DBus name and the DBus object that
+ the menu interface can be found on. Those two value should be set
+ when creating the object using dbusmenu_gtkmenu_new(). They are then
+ stored on two properties #DbusmenuGtkMenu:dbus-name and #DbusmenuGtkMenu:dbus-object.
+
+ After creation the #DbusmenuGtkMenu it will continue to keep in
+ synchronization with the #DbusmenuServer object across Dbus. If the
+ number of entries change, the menus change, if they change thier
+ properties change, they update in the items. All of this should
+ be handled transparently to the user of this object.
+
+ TODO: Document properties.
+*/
+G_END_DECLS
+
+#endif
diff --git a/libdbusmenu-gtk/test.c b/libdbusmenu-gtk/test.c
deleted file mode 100644
index 8ebb3f7..0000000
--- a/libdbusmenu-gtk/test.c
+++ /dev/null
@@ -1,4 +0,0 @@
-
-void mysymbol (void) {
- return;
-}
diff --git a/libdbusmenu-gtk/test.h b/libdbusmenu-gtk/test.h
deleted file mode 100644
index ad000af..0000000
--- a/libdbusmenu-gtk/test.h
+++ /dev/null
@@ -1,2 +0,0 @@
-
-void mysymbol (void);
diff --git a/tests/Makefile.am b/tests/Makefile.am
index 1f21141..2ec6ca7 100644
--- a/tests/Makefile.am
+++ b/tests/Makefile.am
@@ -2,14 +2,16 @@ check: tests
DBUS_RUNNER=dbus-test-runner --dbus-config /usr/share/dbus-test-runner/session.conf
-tests: test-glib-layout test-glib-properties
+tests: test-glib-layout test-glib-properties test-gtk-label
libexec_PROGRAMS = \
glib-server-nomenu \
test-glib-layout-client \
test-glib-layout-server \
test-glib-properties-client \
- test-glib-properties-server
+ test-glib-properties-server \
+ test-gtk-label-client \
+ test-gtk-label-server
glib_server_nomenu_SOURCES = \
glib-server-nomenu.c
@@ -82,9 +84,46 @@ test_glib_properties_client_LDADD = \
+test-gtk-label: test-gtk-label-client test-gtk-label-server test-gtk-label.json
+ $(DBUS_RUNNER) --task ./test-gtk-label-client --task-name Client --task ./test-gtk-label-server --parameter $(srcdir)/test-gtk-label.json --task-name Server --ignore-return
+
+test_gtk_label_server_SOURCES = \
+ test-gtk-label-server.c
+
+test_gtk_label_server_CFLAGS = \
+ -I $(srcdir)/.. \
+ $(DBUSMENUGTK_CFLAGS) \
+ $(DBUSMENUTESTS_CFLAGS) \
+ $(DBUSMENUGLIB_CFLAGS) -Wall -Werror
+
+test_gtk_label_server_LDADD = \
+ ../libdbusmenu-glib/libdbusmenu-glib.la \
+ ../libdbusmenu-gtk/libdbusmenu-gtk.la \
+ $(DBUSMENUGTK_LIBS) \
+ $(DBUSMENUTESTS_LIBS)
+
+test_gtk_label_client_SOURCES = \
+ test-gtk-label-client.c
+
+test_gtk_label_client_CFLAGS = \
+ -I $(srcdir)/.. \
+ $(DBUSMENUGTK_CFLAGS) \
+ $(DBUSMENUTESTS_CFLAGS) \
+ $(DBUSMENUGLIB_CFLAGS) -Wall -Werror
+
+test_gtk_label_client_LDADD = \
+ ../libdbusmenu-glib/libdbusmenu-glib.la \
+ ../libdbusmenu-gtk/libdbusmenu-gtk.la \
+ $(DBUSMENUGTK_LIBS) \
+ $(DBUSMENUTESTS_LIBS)
+
+
+
examplesdir = $(docdir)/examples/
examples_DATA = \
$(glib_server_nomenu_SOURCES)
-EXTRA_DIST = $(examples_DATA)
+EXTRA_DIST = \
+ $(examples_DATA) \
+ test-gtk-label.json
diff --git a/tests/test-glib-layout-server.c b/tests/test-glib-layout-server.c
index e69c6b2..cc9b8e7 100644
--- a/tests/test-glib-layout-server.c
+++ b/tests/test-glib-layout-server.c
@@ -48,7 +48,7 @@ layout2menuitem (layout_t * layout)
}
}
- g_debug("Layout to menu return: 0x%X", (unsigned int)local);
+ /* g_debug("Layout to menu return: 0x%X", (unsigned int)local); */
return local;
}
diff --git a/tests/test-glib-properties-server.c b/tests/test-glib-properties-server.c
index 477f951..a51ac0c 100644
--- a/tests/test-glib-properties-server.c
+++ b/tests/test-glib-properties-server.c
@@ -40,7 +40,7 @@ layout2menuitem (proplayout_t * layout)
}
}
- g_debug("Layout to menu return: 0x%X", (unsigned int)local);
+ /* g_debug("Layout to menu return: 0x%X", (unsigned int)local); */
return local;
}
@@ -57,7 +57,9 @@ timer_func (gpointer data)
}
g_debug("Updating to Layout %d", layouton);
- dbusmenu_server_set_root(server, layout2menuitem(&layouts[layouton]));
+ DbusmenuMenuitem * mi = layout2menuitem(&layouts[layouton]);
+ dbusmenu_server_set_root(server, mi);
+ g_object_unref(G_OBJECT(mi));
layouton++;
return TRUE;
@@ -78,6 +80,7 @@ main (int argc, char ** argv)
mainloop = g_main_loop_new(NULL, FALSE);
g_main_loop_run(mainloop);
+ g_object_unref(G_OBJECT(server));
g_debug("Quiting");
return 0;
diff --git a/tests/test-gtk-label-client.c b/tests/test-gtk-label-client.c
new file mode 100644
index 0000000..b691f84
--- /dev/null
+++ b/tests/test-gtk-label-client.c
@@ -0,0 +1,181 @@
+/*
+A test for libdbusmenu to ensure its quality.
+
+Copyright 2009 Canonical Ltd.
+
+Authors:
+ Ted Gould <ted@canonical.com>
+
+This program is free software: you can redistribute it and/or modify it
+under the terms of the GNU General Public License version 3, 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 GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License along
+with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include <gtk/gtk.h>
+#include <libdbusmenu-gtk/menu.h>
+
+static guint layouton = 0;
+static GMainLoop * mainloop = NULL;
+static gboolean passed = TRUE;
+static guint death_timer = 0;
+
+#if 0
+static gboolean
+verify_props (DbusmenuMenuitem * mi, gchar ** properties)
+{
+ if (properties == NULL) {
+ return TRUE;
+ }
+
+ /* Verify they're all there and correct */
+ guint i;
+ for (i = 0; properties[i] != NULL; i += 2) {
+ const gchar * value = dbusmenu_menuitem_property_get(mi, properties[i]);
+ if (g_strcmp0(value, properties[i + 1])) {
+ g_debug("\tFailed as property '%s' should be '%s' and is '%s'", properties[i], properties[i+1], value);
+ return FALSE;
+ }
+ }
+
+ /* Verify that we don't have any extras */
+ // GList * props = dbusmenu_menuitem_properties_list(mi);
+
+ return TRUE;
+}
+
+static gboolean
+verify_root_to_layout(DbusmenuMenuitem * mi, proplayout_t * layout)
+{
+ g_debug("Verifying ID: %d", layout->id);
+
+ if (layout->id != dbusmenu_menuitem_get_id(mi)) {
+ g_debug("\tFailed as ID %d is not equal to %d", layout->id, dbusmenu_menuitem_get_id(mi));
+ return FALSE;
+ }
+
+ if (!verify_props(mi, layout->properties)) {
+ g_debug("\tFailed as unable to verify properties.");
+ return FALSE;
+ }
+
+ GList * children = dbusmenu_menuitem_get_children(mi);
+
+ if (children == NULL && layout->submenu == NULL) {
+ g_debug("\tPassed: %d", layout->id);
+ return TRUE;
+ }
+ if (children == NULL || layout->submenu == NULL) {
+ if (children == NULL) {
+ g_debug("\tFailed as there are no children but we have submenus");
+ } else {
+ g_debug("\tFailed as we have children but no submenu");
+ }
+ return FALSE;
+ }
+
+ guint i = 0;
+ for (i = 0; children != NULL && layout->submenu[i].id != 0; children = g_list_next(children), i++) {
+ if (!verify_root_to_layout(DBUSMENU_MENUITEM(children->data), &layout->submenu[i])) {
+ return FALSE;
+ }
+ }
+
+ if (children == NULL && layout->submenu[i].id == 0) {
+ g_debug("\tPassed: %d", layout->id);
+ return TRUE;
+ }
+
+ if (children != NULL) {
+ g_debug("\tFailed as there are still children but no submenus. (ID: %d)", layout->id);
+ } else {
+ g_debug("\tFailed as there are still submenus but no children. (ID: %d)", layout->id);
+ }
+ return FALSE;
+}
+#endif
+
+static gboolean
+timer_func (gpointer data)
+{
+ g_debug("Death timer. Oops. Got to: %d", layouton);
+ passed = FALSE;
+ g_main_loop_quit(mainloop);
+ return FALSE;
+}
+
+#if 0
+static gboolean layout_verify_timer (gpointer data);
+
+static void
+layout_updated (DbusmenuClient * client, gpointer data)
+{
+ g_debug("Layout Updated");
+ g_timeout_add (250, layout_verify_timer, client);
+ return;
+}
+
+static gboolean
+layout_verify_timer (gpointer data)
+{
+ DbusmenuMenuitem * menuroot = dbusmenu_client_get_root(DBUSMENU_CLIENT(data));
+ proplayout_t * layout = &layouts[layouton];
+
+ if (!verify_root_to_layout(menuroot, layout)) {
+ g_debug("FAILED LAYOUT: %d", layouton);
+ passed = FALSE;
+ } else {
+ /* Extend our death */
+ g_source_remove(death_timer);
+ death_timer = g_timeout_add_seconds(10, timer_func, data);
+ }
+
+ layouton++;
+
+ if (layouts[layouton].id == 0) {
+ g_main_loop_quit(mainloop);
+ }
+
+ return FALSE;
+}
+#endif
+
+int
+main (int argc, char ** argv)
+{
+ gtk_init(&argc, &argv);
+
+ /* Make sure the server starts up and all that */
+ g_usleep(500000);
+
+ GtkWidget * window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
+ GtkWidget * menubar = gtk_menu_bar_new();
+ GtkWidget * menuitem = gtk_menu_item_new_with_label("Test");
+ gtk_menu_item_set_submenu(GTK_MENU_ITEM(menuitem), GTK_WIDGET(dbusmenu_gtkmenu_new ("glib.label.test", "/org/test")));
+ gtk_widget_show(menuitem);
+ gtk_menu_bar_append(menubar, menuitem);
+ gtk_widget_show(menubar);
+ gtk_container_add(GTK_CONTAINER(window), menubar);
+ gtk_window_set_title(GTK_WINDOW(window), "libdbusmenu-gtk test");
+ gtk_widget_show(window);
+
+ death_timer = g_timeout_add_seconds(60, timer_func, window);
+
+ mainloop = g_main_loop_new(NULL, FALSE);
+ g_main_loop_run(mainloop);
+
+ if (passed) {
+ g_debug("Quiting");
+ return 0;
+ } else {
+ g_debug("Quiting as we're a failure");
+ return 0;
+ }
+}
diff --git a/tests/test-gtk-label-server.c b/tests/test-gtk-label-server.c
new file mode 100644
index 0000000..a7534f2
--- /dev/null
+++ b/tests/test-gtk-label-server.c
@@ -0,0 +1,139 @@
+#include <glib.h>
+
+#include <dbus/dbus.h>
+#include <dbus/dbus-glib.h>
+#include <dbus/dbus-glib-lowlevel.h>
+#include <dbus/dbus-glib-bindings.h>
+
+#include <libdbusmenu-glib/menuitem.h>
+#include <libdbusmenu-glib/server.h>
+
+#include <json-glib/json-glib.h>
+
+static void
+set_props (DbusmenuMenuitem * mi, JsonObject * node)
+{
+ if (node == NULL) return;
+
+ GList * members = NULL;
+ for (members = json_object_get_members(node); members != NULL; members = g_list_next(members)) {
+ const gchar * member = members->data;
+
+ if (!g_strcmp0(member, "id")) { continue; }
+ if (!g_strcmp0(member, "submenu")) { continue; }
+
+ JsonNode * lnode = json_object_get_member(node, member);
+ if (JSON_NODE_TYPE(lnode) != JSON_NODE_VALUE) { continue; }
+
+ dbusmenu_menuitem_property_set(mi, member, json_node_get_string(lnode));
+ }
+
+ return;
+}
+
+static DbusmenuMenuitem *
+layout2menuitem (JsonNode * inlayout)
+{
+ if (inlayout == NULL) return NULL;
+ if (JSON_NODE_TYPE(inlayout) != JSON_NODE_OBJECT) return NULL;
+
+ JsonObject * layout = json_node_get_object(inlayout);
+
+ DbusmenuMenuitem * local = NULL;
+ if (json_object_has_member(layout, "id")) {
+ JsonNode * node = json_object_get_member(layout, "id");
+ g_return_val_if_fail(JSON_NODE_TYPE(node) == JSON_NODE_VALUE, NULL);
+ local = dbusmenu_menuitem_new_with_id(json_node_get_int(node));
+ } else {
+ local = dbusmenu_menuitem_new();
+ }
+
+ set_props(local, layout);
+
+ if (json_object_has_member(layout, "submenu")) {
+ JsonNode * node = json_object_get_member(layout, "submenu");
+ g_return_val_if_fail(JSON_NODE_TYPE(node) == JSON_NODE_ARRAY, local);
+ JsonArray * array = json_node_get_array(node);
+ guint count;
+ for (count = 0; count < json_array_get_length(array); count++) {
+ DbusmenuMenuitem * child = layout2menuitem(json_array_get_element(array, count));
+ if (child != NULL) {
+ dbusmenu_menuitem_child_append(local, child);
+ }
+ }
+ }
+
+ /* g_debug("Layout to menu return: 0x%X", (unsigned int)local); */
+ return local;
+}
+
+static JsonArray * root_array = NULL;
+static guint layouton = 0;
+static DbusmenuServer * server = NULL;
+static GMainLoop * mainloop = NULL;
+
+static gboolean
+timer_func (gpointer data)
+{
+ if (layouton == json_array_get_length(root_array)) {
+ g_debug("Completed %d layouts", layouton);
+ g_main_loop_quit(mainloop);
+ return FALSE;
+ }
+ g_debug("Updating to Layout %d", layouton);
+
+ dbusmenu_server_set_root(server, layout2menuitem(json_array_get_element(root_array, layouton)));
+ layouton++;
+
+ return TRUE;
+}
+
+int
+main (int argc, char ** argv)
+{
+ g_type_init();
+
+ JsonParser * parser = json_parser_new();
+ GError * error = NULL;
+ if (!json_parser_load_from_file(parser, argv[1], &error)) {
+ g_debug("Failed parsing file %s because: %s", argv[1], error->message);
+ return 1;
+ }
+ JsonNode * root_node = json_parser_get_root(parser);
+ if (JSON_NODE_TYPE(root_node) != JSON_NODE_ARRAY) {
+ g_debug("Root node is not an array, fail. It's an: %s", json_node_type_name(root_node));
+ return 1;
+ }
+
+ root_array = json_node_get_array(root_node);
+ g_debug("%d layouts in test description '%s'", json_array_get_length(root_array), argv[1]);
+
+ DBusGConnection * connection = dbus_g_bus_get(DBUS_BUS_SESSION, NULL);
+ g_debug("DBus ID: %s", dbus_connection_get_server_id(dbus_g_connection_get_connection(dbus_g_bus_get(DBUS_BUS_SESSION, NULL))));
+
+ DBusGProxy * bus_proxy = dbus_g_proxy_new_for_name(connection, DBUS_SERVICE_DBUS, DBUS_PATH_DBUS, DBUS_INTERFACE_DBUS);
+ guint nameret = 0;
+
+ if (!org_freedesktop_DBus_request_name(bus_proxy, "glib.label.test", 0, &nameret, &error)) {
+ g_error("Unable to call to request name");
+ return 1;
+ }
+
+ if (nameret != DBUS_REQUEST_NAME_REPLY_PRIMARY_OWNER) {
+ g_error("Unable to get name");
+ return 1;
+ }
+
+ server = dbusmenu_server_new("/org/test");
+
+ timer_func(NULL);
+ g_timeout_add_seconds(15, timer_func, NULL);
+
+ mainloop = g_main_loop_new(NULL, FALSE);
+ g_main_loop_run(mainloop);
+
+ g_debug("Quiting");
+
+ return 0;
+}
+
diff --git a/tests/test-gtk-label.json b/tests/test-gtk-label.json
new file mode 100644
index 0000000..64c1386
--- /dev/null
+++ b/tests/test-gtk-label.json
@@ -0,0 +1,158 @@
+[
+ {"id": 1,
+ "label": "value1",
+ "submenu": [
+ {"id": 30,
+ "label": "value30"},
+ {"id": 31,
+ "label": "value31"},
+ {"id": 32,
+ "label": "value32"},
+ {"id": 33,
+ "label": "value33"},
+ {"id": 34,
+ "label": "value34"},
+ {"id": 35,
+ "label": "value35"},
+ {"id": 36,
+ "label": "value36"},
+ {"id": 37,
+ "label": "value37"},
+ {"id": 38,
+ "label": "value38"},
+ {"id": 39,
+ "label": "value39"}
+ ]
+ },
+ {"id": 2,
+ "label": "value2",
+ "submenu": [
+ {"id": 20,
+ "label": "value20"},
+ {"id": 21,
+ "label": "value21"},
+ {"id": 22,
+ "label": "value22"},
+ {"id": 23,
+ "label": "value23"},
+ {"id": 24,
+ "label": "value24"},
+ {"id": 25,
+ "label": "value25"},
+ {"id": 26,
+ "label": "value26"},
+ {"id": 27,
+ "label": "value27"},
+ {"id": 28,
+ "label": "value28"},
+ {"id": 29,
+ "label": "value29"}
+ ]
+ },
+ {"id": 3,
+ "label": "a super long label that is really of unreasonable length but we should make sure it makes it across the bus",
+ "not.a.value": "A useless value",
+ "submenu": [
+ {"id": 10,
+ "label": "value10"},
+ {"id": 11,
+ "label": "value11"},
+ {"id": 12,
+ "label": "value12"},
+ {"id": 13,
+ "label": "value13"},
+ {"id": 14,
+ "label": "value14"},
+ {"id": 15,
+ "label": "value15"},
+ {"id": 16,
+ "label": "value16"},
+ {"id": 17,
+ "label": "value17"},
+ {"id": 18,
+ "label": "value18"},
+ {"id": 19,
+ "label": "value19"}
+ ]
+ },
+ {"id": 4,
+ "label": "value2",
+ "submenu": [
+ {"id": 5,
+ "label": "value5",
+ "submenu": [
+ {"id": 10,
+ "label": "value10"},
+ {"id": 11,
+ "label": "value11"},
+ {"id": 12,
+ "label": "value12"},
+ {"id": 13,
+ "label": "value13"},
+ {"id": 14,
+ "label": "value14"},
+ {"id": 15,
+ "label": "value15"},
+ {"id": 16,
+ "label": "value16"},
+ {"id": 17,
+ "label": "value17"},
+ {"id": 18,
+ "label": "value18"},
+ {"id": 19,
+ "label": "value19"}
+ ]
+ },
+ {"id": 6,
+ "label": "value6",
+ "submenu": [
+ {"id": 20,
+ "label": "value20"},
+ {"id": 21,
+ "label": "value21"},
+ {"id": 22,
+ "label": "value22"},
+ {"id": 23,
+ "label": "value23"},
+ {"id": 24,
+ "label": "value24"},
+ {"id": 25,
+ "label": "value25"},
+ {"id": 26,
+ "label": "value26"},
+ {"id": 27,
+ "label": "value27"},
+ {"id": 28,
+ "label": "value28"},
+ {"id": 29,
+ "label": "value29"}
+ ]
+ },
+ {"id": 7,
+ "label": "value7",
+ "submenu": [
+ {"id": 30,
+ "label": "value30"},
+ {"id": 31,
+ "label": "value31"},
+ {"id": 32,
+ "label": "value32"},
+ {"id": 33,
+ "label": "value33"},
+ {"id": 34,
+ "label": "value34"},
+ {"id": 35,
+ "label": "value35"},
+ {"id": 36,
+ "label": "value36"},
+ {"id": 37,
+ "label": "value37"},
+ {"id": 38,
+ "label": "value38"},
+ {"id": 39,
+ "label": "value39"}
+ ]
+ },
+ ]
+ }
+]