diff options
-rw-r--r-- | .bzrignore | 5 | ||||
-rw-r--r-- | libdbusmenu-glib/Makefile.am | 3 | ||||
-rw-r--r-- | libdbusmenu-glib/client.c | 131 | ||||
-rw-r--r-- | libdbusmenu-glib/menuitem-proxy.c | 362 | ||||
-rw-r--r-- | libdbusmenu-glib/menuitem-proxy.h | 74 | ||||
-rw-r--r-- | libdbusmenu-glib/server.c | 19 | ||||
-rw-r--r-- | tests/Makefile.am | 54 | ||||
-rw-r--r-- | tests/test-glib-proxy-client.c | 171 | ||||
-rw-r--r-- | tests/test-glib-proxy-proxy.c | 80 | ||||
-rw-r--r-- | tests/test-glib-proxy-server.c | 126 | ||||
-rw-r--r-- | tests/test-glib-proxy.h | 142 |
11 files changed, 1166 insertions, 1 deletions
@@ -59,3 +59,8 @@ tests/test-glib-objects-test tests/test-glib-objects.xml tools/testapp/dbusmenu-testapp libdbusmenu-glib/libdbusmenu_glib_la-client-menuitem.lo +libdbusmenu-glib/libdbusmenu_glib_la-menuitem-proxy.lo +tests/test-glib-proxy-client +tests/test-glib-proxy-server +tests/test-glib-proxy-proxy +tests/test-glib-proxy diff --git a/libdbusmenu-glib/Makefile.am b/libdbusmenu-glib/Makefile.am index 998af50..65ebf4c 100644 --- a/libdbusmenu-glib/Makefile.am +++ b/libdbusmenu-glib/Makefile.am @@ -12,6 +12,7 @@ libdbusmenu_glibincludedir=$(includedir)/libdbusmenu-0.1/libdbusmenu-glib/ libdbusmenu_glibinclude_HEADERS = \ menuitem.h \ + menuitem-proxy.h \ server.h \ client.h @@ -23,6 +24,8 @@ libdbusmenu_glib_la_SOURCES = \ menuitem-marshal.h \ menuitem-marshal.c \ menuitem-private.h \ + menuitem-proxy.h \ + menuitem-proxy.c \ server.h \ server.c \ server-marshal.h \ diff --git a/libdbusmenu-glib/client.c b/libdbusmenu-glib/client.c index 071654e..4735794 100644 --- a/libdbusmenu-glib/client.c +++ b/libdbusmenu-glib/client.c @@ -75,6 +75,8 @@ struct _DbusmenuClientPrivate DBusGProxy * dbusproxy; GHashTable * type_handlers; + + GArray * delayed_properties; }; typedef struct _newItemPropData newItemPropData; @@ -85,6 +87,21 @@ struct _newItemPropData DbusmenuMenuitem * parent; }; +typedef struct _propertyDelay propertyDelay; +struct _propertyDelay +{ + guint revision; + GArray * entries; +}; + +typedef struct _propertyDelayValue propertyDelayValue; +struct _propertyDelayValue +{ + gint id; + gchar * name; + GValue value; +}; + #define DBUSMENU_CLIENT_GET_PRIVATE(o) \ (G_TYPE_INSTANCE_GET_PRIVATE ((o), DBUSMENU_TYPE_CLIENT, DbusmenuClientPrivate)) @@ -208,6 +225,8 @@ dbusmenu_client_init (DbusmenuClient *self) priv->type_handlers = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, NULL); + priv->delayed_properties = g_array_new(FALSE, TRUE, sizeof(propertyDelay)); + return; } @@ -255,6 +274,23 @@ dbusmenu_client_finalize (GObject *object) g_hash_table_destroy(priv->type_handlers); } + if (priv->delayed_properties) { + gint i; + for (i = 0; i < priv->delayed_properties->len; i++) { + propertyDelay * delay = &g_array_index(priv->delayed_properties, propertyDelay, i); + gint j; + for (j = 0; j < delay->entries->len; j++) { + propertyDelayValue * value = &g_array_index(delay->entries, propertyDelayValue, j); + g_free(value->name); + g_value_unset(&value->value); + } + g_array_free(delay->entries, TRUE); + delay->entries = NULL; + } + g_array_free(priv->delayed_properties, TRUE); + priv->delayed_properties = NULL; + } + G_OBJECT_CLASS (dbusmenu_client_parent_class)->finalize (object); return; } @@ -319,6 +355,49 @@ layout_update (DBusGProxy * proxy, guint revision, gint parent, DbusmenuClient * return; } +/* Add an entry to the set of entries that are delayed until the + layout has been updated to this revision */ +static void +delay_prop_update (guint revision, GArray * delayarray, gint id, gchar * prop, GValue * value) +{ + propertyDelay * delay = NULL; + gint i; + + /* First look for something with this revision number. This + array should be really short, probably not more than an entry or + two so there is no reason to optimize this. */ + for (i = 0; i < delayarray->len; i++) { + propertyDelay * localdelay = &g_array_index(delayarray, propertyDelay, i); + if (localdelay->revision == revision) { + delay = localdelay; + break; + } + } + + /* If we don't have any entires for this revision number then we + need to create a new one with it's own array of entires. */ + if (delay == NULL) { + propertyDelay localdelay = {0}; + localdelay.revision = revision; + localdelay.entries = g_array_new(FALSE, TRUE, sizeof(propertyDelayValue)); + + g_array_append_val(delayarray, localdelay); + delay = &g_array_index(delayarray, propertyDelay, delayarray->len - 1); + } + + /* Build the actual entry and tack it on the end of the array + of entries */ + propertyDelayValue delayvalue = {0}; + delayvalue.id = id; + delayvalue.name = g_strdup(prop); + + g_value_init(&delayvalue.value, G_VALUE_TYPE(value)); + g_value_copy(value, &delayvalue.value); + + g_array_append_val(delay->entries, delayvalue); + return; +} + /* Signal from the server that a property has changed on one of our menuitems */ static void @@ -333,7 +412,16 @@ id_prop_update (DBusGProxy * proxy, gint id, gchar * property, GValue * value, D #endif DbusmenuClientPrivate * priv = DBUSMENU_CLIENT_GET_PRIVATE(client); - g_return_if_fail(priv->root != NULL); + + /* If we're not on the right revision, we need to cache the property + changes as it could be that the menuitems don't exist yet. */ + if (priv->root == NULL || priv->my_revision != priv->current_revision) { + #ifdef MASSIVEDEBUGGING + g_debug("Delaying prop update until rev %d for id %d property %s", priv->current_revision, id, property); + #endif + delay_prop_update(priv->current_revision, priv->delayed_properties, id, property, value); + return; + } DbusmenuMenuitem * menuitem = dbusmenu_menuitem_find_id(priv->root, id); g_return_if_fail(menuitem != NULL); @@ -810,16 +898,19 @@ update_layout_cb (DBusGProxy * proxy, guint rev, gchar * xml, GError * error, vo DbusmenuClient * client = DBUSMENU_CLIENT(data); DbusmenuClientPrivate * priv = DBUSMENU_CLIENT_GET_PRIVATE(client); + /* Check to make sure this isn't an issue */ if (error != NULL) { g_warning("Getting layout failed on client %s object %s: %s", priv->dbus_name, priv->dbus_object, error->message); return; } + /* Try to take in the layout that we got */ if (!parse_layout(client, xml)) { g_warning("Unable to parse layout!"); return; } + /* Success, so we need to update our local variables */ priv->my_revision = rev; /* g_debug("Root is now: 0x%X", (unsigned int)priv->root); */ priv->layoutcall = NULL; @@ -828,6 +919,44 @@ update_layout_cb (DBusGProxy * proxy, guint rev, gchar * xml, GError * error, vo #endif g_signal_emit(G_OBJECT(client), signals[LAYOUT_UPDATED], 0, TRUE); + /* Apply the delayed properties that were queued up while + we were waiting on this layout update. */ + if (G_LIKELY(priv->delayed_properties != NULL)) { + gint i; + for (i = 0; i < priv->delayed_properties->len; i++) { + propertyDelay * delay = &g_array_index(priv->delayed_properties, propertyDelay, i); + if (delay->revision > priv->my_revision) { + /* Check to see if this is for future revisions, which + is possible if there is a ton of updates. */ + break; + } + + gint j; + for (j = 0; j < delay->entries->len; j++) { + propertyDelayValue * value = &g_array_index(delay->entries, propertyDelayValue, j); + DbusmenuMenuitem * mi = dbusmenu_menuitem_find_id(priv->root, value->id); + if (mi != NULL) { + #ifdef MASSIVEDEBUGGING + g_debug("Applying delayed property id %d property %s", value->id, value->name); + #endif + dbusmenu_menuitem_property_set_value(mi, value->name, &value->value); + } + g_free(value->name); + g_value_unset(&value->value); + } + g_array_free(delay->entries, TRUE); + + /* We're removing the entry and moving the index down one + to ensure that we adjust for the shift in the array. The + reality is that i is always 0. You understood this loop + until you got here, didn't you :) */ + g_array_remove_index(priv->delayed_properties, i); + i--; + } + } + + /* Check to see if we got another update in the time this + one was issued. */ if (priv->my_revision < priv->current_revision) { update_layout(client); } diff --git a/libdbusmenu-glib/menuitem-proxy.c b/libdbusmenu-glib/menuitem-proxy.c new file mode 100644 index 0000000..2dd5ada --- /dev/null +++ b/libdbusmenu-glib/menuitem-proxy.c @@ -0,0 +1,362 @@ +/* +An object to ferry over properties and signals between two different +dbusmenu instances. Useful for services. + +Copyright 2010 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 either or both of the following licenses: + +1) the GNU Lesser General Public License version 3, as published by the +Free Software Foundation; and/or +2) the GNU Lesser General Public License version 2.1, as published by +the Free Software Foundation. + +This program is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranties of +MERCHANTABILITY, SATISFACTORY QUALITY or FITNESS FOR A PARTICULAR +PURPOSE. See the applicable version of the GNU Lesser General Public +License for more details. + +You should have received a copy of both the GNU Lesser General Public +License version 3 and version 2.1 along with this program. If not, see +<http://www.gnu.org/licenses/> +*/ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "menuitem-proxy.h" + +typedef struct _DbusmenuMenuitemProxyPrivate DbusmenuMenuitemProxyPrivate; +struct _DbusmenuMenuitemProxyPrivate { + DbusmenuMenuitem * mi; + gulong sig_property_changed; + gulong sig_child_added; + gulong sig_child_removed; + gulong sig_child_moved; +}; + +/* Properties */ +enum { + PROP_0, + PROP_MENU_ITEM +}; + +#define PROP_MENU_ITEM_S "menu-item" + +#define DBUSMENU_MENUITEM_PROXY_GET_PRIVATE(o) \ +(G_TYPE_INSTANCE_GET_PRIVATE ((o), DBUSMENU_TYPE_MENUITEM_PROXY, DbusmenuMenuitemProxyPrivate)) + +static void dbusmenu_menuitem_proxy_class_init (DbusmenuMenuitemProxyClass *klass); +static void dbusmenu_menuitem_proxy_init (DbusmenuMenuitemProxy *self); +static void dbusmenu_menuitem_proxy_dispose (GObject *object); +static void dbusmenu_menuitem_proxy_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); +static void handle_event (DbusmenuMenuitem * mi, const gchar * name, const GValue * value, guint timestamp); +static void add_menuitem (DbusmenuMenuitemProxy * pmi, DbusmenuMenuitem * mi); +static void remove_menuitem (DbusmenuMenuitemProxy * pmi); + +G_DEFINE_TYPE (DbusmenuMenuitemProxy, dbusmenu_menuitem_proxy, DBUSMENU_TYPE_MENUITEM); + +static void +dbusmenu_menuitem_proxy_class_init (DbusmenuMenuitemProxyClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + g_type_class_add_private (klass, sizeof (DbusmenuMenuitemProxyPrivate)); + + object_class->dispose = dbusmenu_menuitem_proxy_dispose; + object_class->finalize = dbusmenu_menuitem_proxy_finalize; + object_class->set_property = set_property; + object_class->get_property = get_property; + + DbusmenuMenuitemClass * miclass = DBUSMENU_MENUITEM_CLASS(klass); + + miclass->handle_event = handle_event; + + g_object_class_install_property (object_class, PROP_MENU_ITEM, + g_param_spec_object(PROP_MENU_ITEM_S, "The Menuitem we're proxying", + "An instance of the DbusmenuMenuitem class that this menuitem will mimic.", + DBUSMENU_TYPE_MENUITEM, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS)); + + return; +} + +static void +dbusmenu_menuitem_proxy_init (DbusmenuMenuitemProxy *self) +{ + DbusmenuMenuitemProxyPrivate * priv = DBUSMENU_MENUITEM_PROXY_GET_PRIVATE(self); + + priv->mi = NULL; + + priv->sig_property_changed = 0; + priv->sig_child_added = 0; + priv->sig_child_removed = 0; + priv->sig_child_moved = 0; + + return; +} + +/* Remove references to objects */ +static void +dbusmenu_menuitem_proxy_dispose (GObject *object) +{ + remove_menuitem(DBUSMENU_MENUITEM_PROXY(object)); + + G_OBJECT_CLASS (dbusmenu_menuitem_proxy_parent_class)->dispose (object); + return; +} + +/* Free any memory that we've allocated */ +static void +dbusmenu_menuitem_proxy_finalize (GObject *object) +{ + + G_OBJECT_CLASS (dbusmenu_menuitem_proxy_parent_class)->finalize (object); + return; +} + +/* Set a property using the generic GObject interface */ +static void +set_property (GObject * obj, guint id, const GValue * value, GParamSpec * pspec) +{ + switch (id) { + case PROP_MENU_ITEM: { + GObject * lobj = g_value_get_object(value); + add_menuitem(DBUSMENU_MENUITEM_PROXY(obj), DBUSMENU_MENUITEM(lobj)); + break; + } + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID(obj, id, pspec); + break; + } + + return; +} + +/* Get a property using the generic GObject interface */ +static void +get_property (GObject * obj, guint id, GValue * value, GParamSpec * pspec) +{ + DbusmenuMenuitemProxyPrivate * priv = DBUSMENU_MENUITEM_PROXY_GET_PRIVATE(obj); + + switch (id) { + case PROP_MENU_ITEM: + g_value_set_object(value, priv->mi); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID(obj, id, pspec); + break; + } + + return; +} + +/* Takes the event and passes it along to the item that we're + playing proxy for. */ +static void +handle_event (DbusmenuMenuitem * mi, const gchar * name, const GValue * value, guint timestamp) +{ + g_return_if_fail(DBUSMENU_IS_MENUITEM_PROXY(mi)); + DbusmenuMenuitemProxyPrivate * priv = DBUSMENU_MENUITEM_PROXY_GET_PRIVATE(mi); + g_return_if_fail(priv->mi != NULL); + return dbusmenu_menuitem_handle_event(priv->mi, name, value, timestamp); +} + +/* Watches a property change and makes sure to put that value + into our property list. */ +static void +proxy_item_property_changed (DbusmenuMenuitem * mi, gchar * property, GValue * value, gpointer user_data) +{ + DbusmenuMenuitemProxy * pmi = DBUSMENU_MENUITEM_PROXY(user_data); + dbusmenu_menuitem_property_set_value(DBUSMENU_MENUITEM(pmi), property, value); + return; +} + +/* Looks for a child getting added and wraps it and places it + in our list of children. */ +static void +proxy_item_child_added (DbusmenuMenuitem * parent, DbusmenuMenuitem * child, guint position, gpointer user_data) +{ + DbusmenuMenuitemProxy * pmi = DBUSMENU_MENUITEM_PROXY(user_data); + DbusmenuMenuitemProxy * child_pmi = dbusmenu_menuitem_proxy_new(child); + dbusmenu_menuitem_child_add_position(DBUSMENU_MENUITEM(pmi), DBUSMENU_MENUITEM(child_pmi), position); + return; +} + +/* Find the wrapper for this child and remove it as well. */ +static void +proxy_item_child_removed (DbusmenuMenuitem * parent, DbusmenuMenuitem * child, gpointer user_data) +{ + DbusmenuMenuitemProxy * pmi = DBUSMENU_MENUITEM_PROXY(user_data); + GList * children = dbusmenu_menuitem_get_children(DBUSMENU_MENUITEM(pmi)); + DbusmenuMenuitemProxy * finalpmi = NULL; + GList * childitem; + + for (childitem = children; childitem != NULL; childitem = g_list_next(childitem)) { + DbusmenuMenuitemProxy * childpmi = (DbusmenuMenuitemProxy *)childitem->data; + DbusmenuMenuitem * childmi = dbusmenu_menuitem_proxy_get_wrapped(childpmi); + if (childmi == child) { + finalpmi = childpmi; + break; + } + } + + if (finalpmi != NULL) { + dbusmenu_menuitem_child_delete(DBUSMENU_MENUITEM(pmi), DBUSMENU_MENUITEM(finalpmi)); + } + + return; +} + +/* Find the wrapper for the item and move it in our child list */ +static void +proxy_item_child_moved (DbusmenuMenuitem * parent, DbusmenuMenuitem * child, guint newpos, guint oldpos, gpointer user_data) +{ + DbusmenuMenuitemProxy * pmi = DBUSMENU_MENUITEM_PROXY(user_data); + GList * children = dbusmenu_menuitem_get_children(DBUSMENU_MENUITEM(pmi)); + DbusmenuMenuitemProxy * finalpmi = NULL; + GList * childitem; + + for (childitem = children; childitem != NULL; childitem = g_list_next(childitem)) { + DbusmenuMenuitemProxy * childpmi = (DbusmenuMenuitemProxy *)childitem->data; + DbusmenuMenuitem * childmi = dbusmenu_menuitem_proxy_get_wrapped(childpmi); + if (childmi == child) { + finalpmi = childpmi; + break; + } + } + + if (finalpmi != NULL) { + dbusmenu_menuitem_child_reorder(DBUSMENU_MENUITEM(pmi), DBUSMENU_MENUITEM(finalpmi), newpos); + } + + return; +} + +/* Making g_object_unref into a GFunc */ +static void +func_g_object_unref (gpointer data, gpointer user_data) +{ + return g_object_unref(G_OBJECT(data)); +} + +/* References all of the things we need for talking to this menuitem + including signals and other data. If the menuitem already has + properties we need to signal that they've changed for us. */ +static void +add_menuitem (DbusmenuMenuitemProxy * pmi, DbusmenuMenuitem * mi) +{ + /* Put it in private */ + DbusmenuMenuitemProxyPrivate * priv = DBUSMENU_MENUITEM_PROXY_GET_PRIVATE(pmi); + if (priv->mi != NULL) { + remove_menuitem(pmi); + } + priv->mi = mi; + g_object_ref(G_OBJECT(priv->mi)); + + /* Attach signals */ + priv->sig_property_changed = g_signal_connect(G_OBJECT(priv->mi), DBUSMENU_MENUITEM_SIGNAL_PROPERTY_CHANGED, G_CALLBACK(proxy_item_property_changed), pmi); + priv->sig_child_added = g_signal_connect(G_OBJECT(priv->mi), DBUSMENU_MENUITEM_SIGNAL_CHILD_ADDED, G_CALLBACK(proxy_item_child_added), pmi); + priv->sig_child_removed = g_signal_connect(G_OBJECT(priv->mi), DBUSMENU_MENUITEM_SIGNAL_CHILD_REMOVED, G_CALLBACK(proxy_item_child_removed), pmi); + priv->sig_child_moved = g_signal_connect(G_OBJECT(priv->mi), DBUSMENU_MENUITEM_SIGNAL_CHILD_MOVED, G_CALLBACK(proxy_item_child_moved), pmi); + + /* Grab (cache) Properties */ + GList * props = dbusmenu_menuitem_properties_list(priv->mi); + GList * prop; + for (prop = props; prop != NULL; prop = g_list_next(prop)) { + gchar * prop_name = (gchar *)prop->data; + dbusmenu_menuitem_property_set_value(DBUSMENU_MENUITEM(pmi), prop_name, dbusmenu_menuitem_property_get_value(priv->mi, prop_name)); + } + g_list_free(props); + + /* Go through children and wrap them */ + GList * children = dbusmenu_menuitem_get_children(priv->mi); + GList * child; + for (child = children; child != NULL; child = g_list_next(child)) { + DbusmenuMenuitemProxy * child_pmi = dbusmenu_menuitem_proxy_new(DBUSMENU_MENUITEM(child->data)); + dbusmenu_menuitem_child_append(DBUSMENU_MENUITEM(pmi), DBUSMENU_MENUITEM(child_pmi)); + } + + return; +} + +/* Removes the menuitem from being our proxy. Typically this isn't + done until this object is destroyed, but who knows?!? */ +static void +remove_menuitem (DbusmenuMenuitemProxy * pmi) +{ + DbusmenuMenuitemProxyPrivate * priv = DBUSMENU_MENUITEM_PROXY_GET_PRIVATE(pmi); + if (priv->mi == NULL) { + return; + } + + /* Remove signals */ + if (priv->sig_property_changed != 0) { + g_signal_handler_disconnect(G_OBJECT(priv->mi), priv->sig_property_changed); + } + if (priv->sig_child_added != 0) { + g_signal_handler_disconnect(G_OBJECT(priv->mi), priv->sig_child_added); + } + if (priv->sig_child_removed != 0) { + g_signal_handler_disconnect(G_OBJECT(priv->mi), priv->sig_child_removed); + } + if (priv->sig_child_moved != 0) { + g_signal_handler_disconnect(G_OBJECT(priv->mi), priv->sig_child_moved); + } + + /* Unref */ + g_object_unref(G_OBJECT(priv->mi)); + priv->mi = NULL; + + /* Remove our own children */ + GList * children = dbusmenu_menuitem_take_children(DBUSMENU_MENUITEM(pmi)); + g_list_foreach(children, func_g_object_unref, NULL); + g_list_free(children); + + return; +} + +/** + dbusmenu_menuitem_proxy_new: + @mi: The #DbusmenuMenuitem to proxy + + Builds a new #DbusmenuMenuitemProxy object that proxies + all of the values for @mi. + + Return value: A new #DbusmenuMenuitemProxy object. +*/ +DbusmenuMenuitemProxy * +dbusmenu_menuitem_proxy_new (DbusmenuMenuitem * mi) +{ + DbusmenuMenuitemProxy * pmi = g_object_new(DBUSMENU_TYPE_MENUITEM_PROXY, + PROP_MENU_ITEM_S, mi, + NULL); + + return pmi; +} + +/** + dbusmenu_menuitem_proxy_get_wrapped: + @pmi: #DbusmenuMenuitemProxy to look into + + Accesses the private variable of which #DbusmenuMenuitem + we are doing the proxying for. + + Return value: A #DbusmenuMenuitem object or a #NULL if we + don't have one or there is an error. +*/ +DbusmenuMenuitem * +dbusmenu_menuitem_proxy_get_wrapped (DbusmenuMenuitemProxy * pmi) +{ + g_return_val_if_fail(DBUSMENU_MENUITEM_PROXY(pmi), NULL); + DbusmenuMenuitemProxyPrivate * priv = DBUSMENU_MENUITEM_PROXY_GET_PRIVATE(pmi); + return priv->mi; +} diff --git a/libdbusmenu-glib/menuitem-proxy.h b/libdbusmenu-glib/menuitem-proxy.h new file mode 100644 index 0000000..56c4941 --- /dev/null +++ b/libdbusmenu-glib/menuitem-proxy.h @@ -0,0 +1,74 @@ +/* +An object to ferry over properties and signals between two different +dbusmenu instances. Useful for services. + +Copyright 2010 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 either or both of the following licenses: + +1) the GNU Lesser General Public License version 3, as published by the +Free Software Foundation; and/or +2) the GNU Lesser General Public License version 2.1, as published by +the Free Software Foundation. + +This program is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranties of +MERCHANTABILITY, SATISFACTORY QUALITY or FITNESS FOR A PARTICULAR +PURPOSE. See the applicable version of the GNU Lesser General Public +License for more details. + +You should have received a copy of both the GNU Lesser General Public +License version 3 and version 2.1 along with this program. If not, see +<http://www.gnu.org/licenses/> +*/ + +#ifndef __DBUSMENU_MENUITEM_PROXY_H__ +#define __DBUSMENU_MENUITEM_PROXY_H__ + +#include <glib.h> +#include <glib-object.h> +#include "menuitem.h" + +G_BEGIN_DECLS + +#define DBUSMENU_TYPE_MENUITEM_PROXY (dbusmenu_menuitem_proxy_get_type ()) +#define DBUSMENU_MENUITEM_PROXY(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), DBUSMENU_TYPE_MENUITEM_PROXY, DbusmenuMenuitemProxy)) +#define DBUSMENU_MENUITEM_PROXY_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), DBUSMENU_TYPE_MENUITEM_PROXY, DbusmenuMenuitemProxyClass)) +#define DBUSMENU_IS_MENUITEM_PROXY(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), DBUSMENU_TYPE_MENUITEM_PROXY)) +#define DBUSMENU_IS_MENUITEM_PROXY_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), DBUSMENU_TYPE_MENUITEM_PROXY)) +#define DBUSMENU_MENUITEM_PROXY_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), DBUSMENU_TYPE_MENUITEM_PROXY, DbusmenuMenuitemProxyClass)) + +typedef struct _DbusmenuMenuitemProxy DbusmenuMenuitemProxy; +typedef struct _DbusmenuMenuitemProxyClass DbusmenuMenuitemProxyClass; + +/** + DbusmenuMenuitemProxyClass: + @parent_class: The Class of #DbusmeneMenuitem + + Functions and signal slots for #DbusmenuMenuitemProxy. +*/ +struct _DbusmenuMenuitemProxyClass { + DbusmenuMenuitemClass parent_class; +}; + +/** + DbusmeneMenuitemProxy: + @parent: The instance of #DbusmenuMenuitem + + Public instance data for a #DbusmenuMenuitemProxy. +*/ +struct _DbusmenuMenuitemProxy { + DbusmenuMenuitem parent; +}; + +GType dbusmenu_menuitem_proxy_get_type (void); +DbusmenuMenuitemProxy * dbusmenu_menuitem_proxy_new (DbusmenuMenuitem * mi); +DbusmenuMenuitem * dbusmenu_menuitem_proxy_get_wrapped (DbusmenuMenuitemProxy * pmi); + +G_END_DECLS + +#endif diff --git a/libdbusmenu-glib/server.c b/libdbusmenu-glib/server.c index 55d339f..f6dddf1 100644 --- a/libdbusmenu-glib/server.c +++ b/libdbusmenu-glib/server.c @@ -301,10 +301,29 @@ menuitem_property_changed (DbusmenuMenuitem * mi, gchar * property, GValue * val return; } +/* Adds the signals for this entry to the list and looks at + the children of this entry to add the signals we need + as well. We like signals. */ +static void +added_check_children (gpointer data, gpointer user_data) +{ + DbusmenuMenuitem * mi = (DbusmenuMenuitem *)data; + DbusmenuServer * server = (DbusmenuServer *)user_data; + + menuitem_signals_create(mi, server); + g_list_foreach(dbusmenu_menuitem_get_children(mi), added_check_children, server); + + return; +} + +/* Callback for when a child is added. We need to connect everything + up and signal that the layout has changed. */ static void menuitem_child_added (DbusmenuMenuitem * parent, DbusmenuMenuitem * child, guint pos, DbusmenuServer * server) { menuitem_signals_create(child, server); + g_list_foreach(dbusmenu_menuitem_get_children(child), added_check_children, server); + /* TODO: We probably need to group the layout update signals to make the number more reasonble. */ DbusmenuServerPrivate * priv = DBUSMENU_SERVER_GET_PRIVATE(server); priv->layout_revision++; diff --git a/tests/Makefile.am b/tests/Makefile.am index 746ac23..cfa1399 100644 --- a/tests/Makefile.am +++ b/tests/Makefile.am @@ -5,6 +5,7 @@ TESTS = \ test-glib-objects-test \ test-glib-layout \ test-glib-properties \ + test-glib-proxy \ test-glib-simple-items \ test-gtk-label \ test-gtk-reorder @@ -16,6 +17,9 @@ check_PROGRAMS = \ test-glib-layout-server \ test-glib-properties-client \ test-glib-properties-server \ + test-glib-proxy-client \ + test-glib-proxy-server \ + test-glib-proxy-proxy \ test-gtk-label-client \ test-gtk-label-server \ test-glib-simple-items \ @@ -127,6 +131,56 @@ test_glib_properties_client_LDADD = \ ../libdbusmenu-glib/libdbusmenu-glib.la \ $(DBUSMENUGLIB_LIBS) +###################### +# Test Glib Proxy +###################### + +test-glib-proxy: test-glib-proxy-client test-glib-proxy-server test-glib-proxy-proxy Makefile.am + @echo "#!/bin/bash" > $@ + @echo $(DBUS_RUNNER) --task ./test-glib-proxy-client --task-name Client --task ./test-glib-proxy-server --task-name Server --ignore-return \\ >> $@ + @echo --task ./test-glib-proxy-proxy --parameter test.proxy.first_proxy --parameter test.proxy.second_proxy --task-name Proxy01 --ignore-return \\ >> $@ + @echo --task ./test-glib-proxy-proxy --parameter test.proxy.second_proxy --parameter test.proxy.third_proxy --task-name Proxy02 --ignore-return \\ >> $@ + @echo --task ./test-glib-proxy-proxy --parameter test.proxy.third_proxy --parameter test.proxy.fourth_proxy --task-name Proxy03 --ignore-return \\ >> $@ + @echo --task ./test-glib-proxy-proxy --parameter test.proxy.fourth_proxy --parameter test.proxy.last_proxy --task-name Proxy04 --ignore-return \\ >> $@ + @echo --task ./test-glib-proxy-proxy --parameter test.proxy.last_proxy --parameter test.proxy.server --task-name Proxy05 --ignore-return >> $@ + @chmod +x $@ + +test_glib_proxy_server_SOURCES = \ + test-glib-proxy.h \ + test-glib-proxy-server.c + +test_glib_proxy_server_CFLAGS = \ + -I $(srcdir)/.. \ + $(DBUSMENUGLIB_CFLAGS) -Wall -Werror + +test_glib_proxy_server_LDADD = \ + ../libdbusmenu-glib/libdbusmenu-glib.la \ + $(DBUSMENUGLIB_LIBS) + +test_glib_proxy_client_SOURCES = \ + test-glib-proxy.h \ + test-glib-proxy-client.c + +test_glib_proxy_client_CFLAGS = \ + -I $(srcdir)/.. \ + $(DBUSMENUGLIB_CFLAGS) -Wall -Werror + +test_glib_proxy_client_LDADD = \ + ../libdbusmenu-glib/libdbusmenu-glib.la \ + $(DBUSMENUGLIB_LIBS) + +test_glib_proxy_proxy_SOURCES = \ + test-glib-proxy.h \ + test-glib-proxy-proxy.c + +test_glib_proxy_proxy_CFLAGS = \ + -I $(srcdir)/.. \ + $(DBUSMENUGLIB_CFLAGS) -Wall -Werror + +test_glib_proxy_proxy_LDADD = \ + ../libdbusmenu-glib/libdbusmenu-glib.la \ + $(DBUSMENUGLIB_LIBS) + ######################### # Test Glib Simple Items ######################### diff --git a/tests/test-glib-proxy-client.c b/tests/test-glib-proxy-client.c new file mode 100644 index 0000000..50ad5d3 --- /dev/null +++ b/tests/test-glib-proxy-client.c @@ -0,0 +1,171 @@ +/* +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 <glib.h> + +#include <libdbusmenu-glib/client.h> +#include <libdbusmenu-glib/menuitem.h> + +#include "test-glib-proxy.h" + +static guint layouton = -1; +static GMainLoop * mainloop = NULL; +static gboolean passed = TRUE; +static guint death_timer = 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 (!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 != -1; 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 == -1) { + 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; +} + +static gboolean +timer_func (gpointer data) +{ + g_debug("Death timer. Oops. Got to: %d", layouton); + passed = FALSE; + g_main_loop_quit(mainloop); + return FALSE; +} + +static gboolean layout_verify_timer (gpointer data); + +static void +layout_updated (DbusmenuClient * client, gpointer data) +{ + g_debug("Layout Updated"); + if (dbusmenu_client_get_root(client) == NULL) { + g_debug("\tIgnored, no root"); + return; + } + layouton++; + g_timeout_add (1500, layout_verify_timer, client); + return; +} + +static gboolean +layout_verify_timer (gpointer data) +{ + g_debug("Verifing Layout: %d", layouton); + 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); + } + + if (layouts[layouton+1].id == -1) { + g_main_loop_quit(mainloop); + } + + return FALSE; +} + +int +main (int argc, char ** argv) +{ + g_type_init(); + + DbusmenuClient * client = dbusmenu_client_new("test.proxy.first_proxy", "/org/test"); + g_signal_connect(G_OBJECT(client), DBUSMENU_CLIENT_SIGNAL_LAYOUT_UPDATED, G_CALLBACK(layout_updated), NULL); + + death_timer = g_timeout_add_seconds(10, timer_func, client); + + mainloop = g_main_loop_new(NULL, FALSE); + g_main_loop_run(mainloop); + + g_object_unref(G_OBJECT(client)); + + if (passed) { + g_debug("Quiting"); + return 0; + } else { + g_debug("Quiting as we're a failure"); + return 1; + } +} diff --git a/tests/test-glib-proxy-proxy.c b/tests/test-glib-proxy-proxy.c new file mode 100644 index 0000000..1059cfd --- /dev/null +++ b/tests/test-glib-proxy-proxy.c @@ -0,0 +1,80 @@ +#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/menuitem-proxy.h> +#include <libdbusmenu-glib/server.h> +#include <libdbusmenu-glib/client.h> + +#include "test-glib-proxy.h" + +static DbusmenuServer * server = NULL; +static DbusmenuClient * client = NULL; +static GMainLoop * mainloop = NULL; + +void +root_changed (DbusmenuClient * client, DbusmenuMenuitem * newroot, gpointer user_data) +{ + g_debug("New root: %X", (guint)newroot); + + if (newroot == NULL) { + g_debug("Root removed, exiting"); + g_main_loop_quit(mainloop); + return; + } + + DbusmenuMenuitemProxy * pmi = dbusmenu_menuitem_proxy_new(newroot); + dbusmenu_server_set_root(server, DBUSMENU_MENUITEM(pmi)); + return; +} + +int +main (int argc, char ** argv) +{ + g_type_init(); + + if (argc != 3) { + g_error ("Need two params"); + return 1; + } + + gchar * whoami = argv[1]; + gchar * myproxy = argv[2]; + + g_debug("I am '%s' and I'm proxying '%s'", whoami, myproxy); + + GError * error = NULL; + 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(connection))); + + 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, whoami, 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"); + client = dbusmenu_client_new(myproxy, "/org/test"); + + g_signal_connect(client, DBUSMENU_CLIENT_SIGNAL_ROOT_CHANGED, G_CALLBACK(root_changed), server); + + 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-glib-proxy-server.c b/tests/test-glib-proxy-server.c new file mode 100644 index 0000000..cba8ec7 --- /dev/null +++ b/tests/test-glib-proxy-server.c @@ -0,0 +1,126 @@ +/* +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 <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 "test-glib-proxy.h" + +static void +set_props (DbusmenuMenuitem * mi, gchar ** props) +{ + if (props == NULL) return; + + guint i; + for (i = 0; props[i] != NULL; i += 2) { + dbusmenu_menuitem_property_set(mi, props[i], props[i+1]); + } + + return; +} + +static DbusmenuMenuitem * +layout2menuitem (proplayout_t * layout) +{ + if (layout == NULL || layout->id == -1) return NULL; + + DbusmenuMenuitem * local = dbusmenu_menuitem_new(); + set_props(local, layout->properties); + + if (layout->submenu != NULL) { + guint count; + for (count = 0; layout->submenu[count].id != -1; count++) { + DbusmenuMenuitem * child = layout2menuitem(&layout->submenu[count]); + if (child != NULL) { + dbusmenu_menuitem_child_append(local, child); + } + } + } + + /* g_debug("Layout to menu return: 0x%X", (unsigned int)local); */ + return local; +} + +static guint layouton = 0; +static DbusmenuServer * server = NULL; +static GMainLoop * mainloop = NULL; + +static gboolean +timer_func (gpointer data) +{ + if (layouts[layouton].id == -1) { + g_main_loop_quit(mainloop); + return FALSE; + } + g_debug("Updating to Layout %d", layouton); + + DbusmenuMenuitem * mi = layout2menuitem(&layouts[layouton]); + dbusmenu_server_set_root(server, mi); + g_object_unref(G_OBJECT(mi)); + layouton++; + + return TRUE; +} + +int +main (int argc, char ** argv) +{ + g_type_init(); + + GError * error = NULL; + 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(connection))); + + 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, "test.proxy.server", 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(2500, timer_func, NULL); + + 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-glib-proxy.h b/tests/test-glib-proxy.h new file mode 100644 index 0000000..bc12df6 --- /dev/null +++ b/tests/test-glib-proxy.h @@ -0,0 +1,142 @@ +/* +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 <glib.h> + +typedef struct _proplayout_t proplayout_t; +struct _proplayout_t { + gint id; + gchar ** properties; + proplayout_t * submenu; +}; + +gchar * props1[] = {"property1", "value1", "property2", "value2", NULL}; +gchar * props2[] = {"property00", "value00", "property01", "value01", "property02", "value02", "property03", "value03", "property04", "value04", + "property05", "value05", "property06", "value06", "property07", "value07", "property08", "value08", "property09", "value09", + "property10", "value10", "property11", "value11", "property12", "value12", "property13", "value13", "property14", "value14", + "property15", "value15", "property16", "value16", "property17", "value17", "property18", "value18", "property19", "value19", + "property20", "value20", "property21", "value21", "property22", "value22", "property23", "value23", "property24", "value24", + "property25", "value25", "property26", "value26", "property27", "value27", "property28", "value28", "property29", "value29", + "property30", "value30", "property31", "value31", "property32", "value32", "property33", "value33", "property34", "value34", + "property35", "value35", "property36", "value36", "property37", "value37", "property38", "value38", "property39", "value39", + "property40", "value40", "property41", "value41", "property42", "value42", "property43", "value43", "property44", "value44", + "property45", "value45", "property46", "value46", "property47", "value47", "property48", "value48", "property49", "value49", + "property50", "value50", "property51", "value51", "property52", "value52", "property53", "value53", "property54", "value54", + "property55", "value55", "property56", "value56", "property57", "value57", "property58", "value58", "property59", "value59", + "property60", "value60", "property61", "value61", "property62", "value62", "property63", "value63", "property64", "value64", + "property65", "value65", "property66", "value66", "property67", "value67", "property68", "value68", "property69", "value69", + "property70", "value70", "property71", "value71", "property72", "value72", "property73", "value73", "property74", "value74", + "property75", "value75", "property76", "value76", "property77", "value77", "property78", "value78", "property79", "value79", + "property80", "value80", "property81", "value81", "property82", "value82", "property83", "value83", "property84", "value84", + "property85", "value85", "property86", "value86", "property87", "value87", "property88", "value88", "property89", "value89", + "property90", "value90", "property91", "value91", "property92", "value92", "property93", "value93", "property94", "value94", + "property95", "value95", "property96", "value96", "property97", "value97", "property98", "value98", "property99", "value99", + NULL}; +gchar * props3[] = {"property name that is really long and will ensure that we can really have long property names, which could be important at some point.", + "And a property name that is really long should have a value that is really long, because well, that's an important part of the yin and yang of software testing.", + NULL}; +gchar * props4[] = {"icon-name", "network-status", "label", "Look at network", "right-column", "10:32", NULL}; + + +proplayout_t submenu_4_1[] = { + {id: 10, properties: props2, submenu: NULL}, + {id: 11, properties: props2, submenu: NULL}, + {id: 12, properties: props2, submenu: NULL}, + {id: 13, properties: props2, submenu: NULL}, + {id: 14, properties: props2, submenu: NULL}, + {id: 15, properties: props2, submenu: NULL}, + {id: 16, properties: props2, submenu: NULL}, + {id: 17, properties: props2, submenu: NULL}, + {id: 18, properties: props2, submenu: NULL}, + {id: 19, properties: props2, submenu: NULL}, + {id: -1, properties: NULL, submenu: NULL} +}; + +proplayout_t submenu_4_2[] = { + {id: 20, properties: props2, submenu: NULL}, + {id: 21, properties: props2, submenu: NULL}, + {id: 22, properties: props2, submenu: NULL}, + {id: 23, properties: props2, submenu: NULL}, + {id: 24, properties: props2, submenu: NULL}, + {id: 25, properties: props2, submenu: NULL}, + {id: 26, properties: props2, submenu: NULL}, + {id: 27, properties: props2, submenu: NULL}, + {id: 28, properties: props2, submenu: NULL}, + {id: 29, properties: props2, submenu: NULL}, + {id: -1, properties: NULL, submenu: NULL} +}; + +proplayout_t submenu_4_3[] = { + {id: 30, properties: props2, submenu: NULL}, + {id: 31, properties: props2, submenu: NULL}, + {id: 32, properties: props2, submenu: NULL}, + {id: 33, properties: props2, submenu: NULL}, + {id: 34, properties: props2, submenu: NULL}, + {id: 35, properties: props2, submenu: NULL}, + {id: 36, properties: props2, submenu: NULL}, + {id: 37, properties: props2, submenu: NULL}, + {id: 38, properties: props2, submenu: NULL}, + {id: 39, properties: props2, submenu: NULL}, + {id: -1, properties: NULL, submenu: NULL} +}; + +proplayout_t submenu_4_0[] = { + {id: 1, properties: props2, submenu: submenu_4_1}, + {id: 2, properties: props2, submenu: submenu_4_2}, + {id: 3, properties: props2, submenu: submenu_4_3}, + {id: -1, properties: NULL, submenu: NULL} +}; + +proplayout_t submenu_5_5[] = { + {id: 205, properties: props3, submenu: NULL}, + {id: -1, properties: NULL, submenu: NULL} +}; + +proplayout_t submenu_5_4[] = { + {id: 204, properties: props3, submenu: submenu_5_5}, + {id: -1, properties: NULL, submenu: NULL} +}; + +proplayout_t submenu_5_3[] = { + {id: 203, properties: props3, submenu: submenu_5_4}, + {id: -1, properties: NULL, submenu: NULL} +}; + +proplayout_t submenu_5_2[] = { + {id: 202, properties: props3, submenu: submenu_5_3}, + {id: -1, properties: NULL, submenu: NULL} +}; + +proplayout_t submenu_5_1[] = { + {id: 201, properties: props3, submenu: submenu_5_2}, + {id: -1, properties: NULL, submenu: NULL} +}; + +proplayout_t layouts[] = { + {id: 1, properties: props1, submenu: NULL}, + {id: 10, properties: props2, submenu: submenu_4_1}, + {id: 20, properties: props3, submenu: submenu_4_2}, + {id: 100, properties: props2, submenu: submenu_4_0}, + {id: 200, properties: props3, submenu: submenu_5_1}, + {id: -1, properties: NULL, submenu: NULL} +}; + |