1 /*
2 A library to communicate a menu object set accross DBus and
3 track updates and maintain consistency.
4 
5 Copyright 2009 Canonical Ltd.
6 
7 Authors:
8     Ted Gould <ted@canonical.com>
9 
10 This program is free software: you can redistribute it and/or modify it
11 under the terms of either or both of the following licenses:
12 
13 1) the GNU Lesser General Public License version 3, as published by the
14 Free Software Foundation; and/or
15 2) the GNU Lesser General Public License version 2.1, as published by
16 the Free Software Foundation.
17 
18 This program is distributed in the hope that it will be useful, but
19 WITHOUT ANY WARRANTY; without even the implied warranties of
20 MERCHANTABILITY, SATISFACTORY QUALITY or FITNESS FOR A PARTICULAR
21 PURPOSE.  See the applicable version of the GNU Lesser General Public
22 License for more details.
23 
24 You should have received a copy of both the GNU Lesser General Public
25 License version 3 and version 2.1 along with this program.  If not, see
26 <http://www.gnu.org/licenses/>
27 */
28 
29 #include <stdlib.h>
30 #ifdef HAVE_CONFIG_H
31 #include "config.h"
32 #endif
33 #include "menuitem.h"
34 #include "menuitem-marshal.h"
35 #include "menuitem-private.h"
36 #include "defaults.h"
37 
38 #ifdef MASSIVEDEBUGGING
39 #define LABEL(x)  dbusmenu_menuitem_property_get(DBUSMENU_MENUITEM(x), DBUSMENU_MENUITEM_PROP_LABEL)
40 #define ID(x)     dbusmenu_menuitem_get_id(DBUSMENU_MENUITEM(x))
41 #endif
42 
43 /* Private */
44 /**
45 	DbusmenuMenuitemPrivate:
46 	@id: The ID of this menu item
47 	@children: A list of #DbusmenuMenuitem objects that are
48 	      children to this one.
49 	@properties: All of the properties on this menu item.
50 	@root: Whether this node is the root node
51 
52 	These are the little secrets that we don't want getting
53 	out of data that we have.  They can still be gotten using
54 	accessor functions, but are protected appropriately.
55 */
56 struct _DbusmenuMenuitemPrivate
57 {
58 	gint id;
59 	GList * children;
60 	GHashTable * properties;
61 	gboolean root;
62 	gboolean realized;
63 	DbusmenuDefaults * defaults;
64 	gboolean exposed;
65 	DbusmenuMenuitem * parent;
66 };
67 
68 /* Signals */
69 enum {
70 	PROPERTY_CHANGED,
71 	ITEM_ACTIVATED,
72 	CHILD_ADDED,
73 	CHILD_REMOVED,
74 	CHILD_MOVED,
75 	REALIZED,
76 	SHOW_TO_USER,
77 	ABOUT_TO_SHOW,
78 	EVENT,
79 	LAST_SIGNAL
80 };
81 
82 static guint signals[LAST_SIGNAL] = { 0 };
83 
84 /* Properties */
85 enum {
86 	PROP_0,
87 	PROP_ID,
88 };
89 
90 #define PROP_ID_S  "id"
91 
92 #define DBUSMENU_MENUITEM_GET_PRIVATE(o)  (DBUSMENU_MENUITEM(o)->priv)
93 
94 /* Prototypes */
95 static void dbusmenu_menuitem_class_init (DbusmenuMenuitemClass *klass);
96 static void dbusmenu_menuitem_init       (DbusmenuMenuitem *self);
97 static void dbusmenu_menuitem_dispose    (GObject *object);
98 static void dbusmenu_menuitem_finalize   (GObject *object);
99 static void set_property (GObject * obj, guint id, const GValue * value, GParamSpec * pspec);
100 static void get_property (GObject * obj, guint id, GValue * value, GParamSpec * pspec);
101 static void g_value_transform_STRING_BOOLEAN (const GValue * in, GValue * out);
102 static void g_value_transform_STRING_INT (const GValue * in, GValue * out);
103 static void handle_event (DbusmenuMenuitem * mi, const gchar * name, GVariant * variant, guint timestamp);
104 static void send_about_to_show (DbusmenuMenuitem * mi, void (*cb) (DbusmenuMenuitem * mi, gpointer user_data), gpointer cb_data);
105 
106 /* GObject stuff */
107 G_DEFINE_TYPE (DbusmenuMenuitem, dbusmenu_menuitem, G_TYPE_OBJECT);
108 
109 static void
dbusmenu_menuitem_class_init(DbusmenuMenuitemClass * klass)110 dbusmenu_menuitem_class_init (DbusmenuMenuitemClass *klass)
111 {
112 	GObjectClass *object_class = G_OBJECT_CLASS (klass);
113 
114 	g_type_class_add_private (klass, sizeof (DbusmenuMenuitemPrivate));
115 
116 	object_class->dispose = dbusmenu_menuitem_dispose;
117 	object_class->finalize = dbusmenu_menuitem_finalize;
118 	object_class->set_property = set_property;
119 	object_class->get_property = get_property;
120 
121 	klass->handle_event = handle_event;
122 	klass->send_about_to_show = send_about_to_show;
123 
124 	/**
125 		DbusmenuMenuitem::property-changed:
126 		@arg0: The #DbusmenuMenuitem object.
127 		@arg1: The name of the property that changed
128 		@arg2: The new value of the property
129 
130 		Emitted everytime a property on a menuitem is either
131 		updated or added.
132 	*/
133 	signals[PROPERTY_CHANGED] =   g_signal_new(DBUSMENU_MENUITEM_SIGNAL_PROPERTY_CHANGED,
134 	                                           G_TYPE_FROM_CLASS(klass),
135 	                                           G_SIGNAL_RUN_LAST,
136 	                                           G_STRUCT_OFFSET(DbusmenuMenuitemClass, property_changed),
137 	                                           NULL, NULL,
138 	                                           _dbusmenu_menuitem_marshal_VOID__STRING_VARIANT,
139 	                                           G_TYPE_NONE, 2, G_TYPE_STRING, G_TYPE_VARIANT);
140 	/**
141 		DbusmenuMenuitem::item-activated:
142 		@arg0: The #DbusmenuMenuitem object.
143 		@arg1: The timestamp of when it was activated
144 
145 		Emitted on the objects on the server side when
146 		they are signaled on the client side.
147 	*/
148 	signals[ITEM_ACTIVATED] =   g_signal_new(DBUSMENU_MENUITEM_SIGNAL_ITEM_ACTIVATED,
149 	                                           G_TYPE_FROM_CLASS(klass),
150 	                                           G_SIGNAL_RUN_LAST,
151 	                                           G_STRUCT_OFFSET(DbusmenuMenuitemClass, item_activated),
152 	                                           NULL, NULL,
153 	                                           _dbusmenu_menuitem_marshal_VOID__UINT,
154 	                                           G_TYPE_NONE, 1, G_TYPE_UINT, G_TYPE_NONE);
155 	/**
156 		DbusmenuMenuitem::child-added:
157 		@arg0: The #DbusmenuMenuitem which is the parent.
158 		@arg1: The #DbusmenuMenuitem which is the child.
159 		@arg2: The position that the child is being added in.
160 
161 		Signaled when the child menuitem has been added to
162 		the parent.
163 	*/
164 	signals[CHILD_ADDED] =        g_signal_new(DBUSMENU_MENUITEM_SIGNAL_CHILD_ADDED,
165 	                                           G_TYPE_FROM_CLASS(klass),
166 	                                           G_SIGNAL_RUN_LAST,
167 	                                           G_STRUCT_OFFSET(DbusmenuMenuitemClass, child_added),
168 	                                           NULL, NULL,
169 	                                           _dbusmenu_menuitem_marshal_VOID__OBJECT_UINT,
170 	                                           G_TYPE_NONE, 2, G_TYPE_OBJECT, G_TYPE_UINT);
171 	/**
172 		DbusmenuMenuitem::child-removed:
173 		@arg0: The #DbusmenuMenuitem which was the parent.
174 		@arg1: The #DbusmenuMenuitem which was the child.
175 
176 		Signaled when the child menuitem has been requested to
177 		be removed from the parent.  This signal is called when
178 		it has been removed from the list but not yet had
179 		#g_object_unref called on it.
180 	*/
181 	signals[CHILD_REMOVED] =      g_signal_new(DBUSMENU_MENUITEM_SIGNAL_CHILD_REMOVED,
182 	                                           G_TYPE_FROM_CLASS(klass),
183 	                                           G_SIGNAL_RUN_LAST,
184 	                                           G_STRUCT_OFFSET(DbusmenuMenuitemClass, child_removed),
185 	                                           NULL, NULL,
186 	                                           _dbusmenu_menuitem_marshal_VOID__OBJECT,
187 	                                           G_TYPE_NONE, 1, G_TYPE_OBJECT);
188 	/**
189 		DbusmenuMenuitem::child-moved:
190 		@arg0: The #DbusmenuMenuitem which is the parent.
191 		@arg1: The #DbusmenuMenuitem which is the child.
192 		@arg2: The position that the child is being moved to.
193 		@arg3: The position that the child is was in.
194 
195 		Signaled when the child menuitem has had its location
196 		in the list change.
197 	*/
198 	signals[CHILD_MOVED] =        g_signal_new(DBUSMENU_MENUITEM_SIGNAL_CHILD_MOVED,
199 	                                           G_TYPE_FROM_CLASS(klass),
200 	                                           G_SIGNAL_RUN_LAST,
201 	                                           G_STRUCT_OFFSET(DbusmenuMenuitemClass, child_moved),
202 	                                           NULL, NULL,
203 	                                           _dbusmenu_menuitem_marshal_VOID__OBJECT_UINT_UINT,
204 	                                           G_TYPE_NONE, 3, G_TYPE_OBJECT, G_TYPE_UINT, G_TYPE_UINT);
205 	/**
206 		DbusmenuMenuitem::realized:
207 		@arg0: The #DbusmenuMenuitem object.
208 
209 		Emitted when the initial request for properties
210 		is complete on the item.  If there is a type
211 		handler configured for the "type" parameter
212 		that will be executed before this is signaled.
213 	*/
214 	signals[REALIZED] =           g_signal_new(DBUSMENU_MENUITEM_SIGNAL_REALIZED,
215 	                                           G_TYPE_FROM_CLASS(klass),
216 	                                           G_SIGNAL_RUN_LAST,
217 	                                           G_STRUCT_OFFSET(DbusmenuMenuitemClass, realized),
218 	                                           NULL, NULL,
219 	                                           _dbusmenu_menuitem_marshal_VOID__VOID,
220 	                                           G_TYPE_NONE, 0, G_TYPE_NONE);
221 	/**
222 		DbusmenuMenuitem::show-to-user:
223 		@arg0: The #DbusmenuMenuitem which should be shown.
224 		@arg1: Timestamp the event happened at
225 
226 		Signaled when the application would like the visualization
227 		of this menu item shown to the user.  This usually requires
228 		going over the bus to get it done.
229 	*/
230 	signals[SHOW_TO_USER] =      g_signal_new(DBUSMENU_MENUITEM_SIGNAL_SHOW_TO_USER,
231 	                                           G_TYPE_FROM_CLASS(klass),
232 	                                           G_SIGNAL_RUN_LAST,
233 	                                           G_STRUCT_OFFSET(DbusmenuMenuitemClass, show_to_user),
234 	                                           NULL, NULL,
235 	                                           g_cclosure_marshal_VOID__UINT,
236 	                                           G_TYPE_NONE, 1, G_TYPE_UINT, G_TYPE_NONE);
237 
238 	/**
239 		DbusmenuMenuitem::about-to-show:
240 		@arg0: The #DbusmenuMenuitem object.
241 
242 		Emitted when the submenu for this item
243 		is about to be shown
244 	*/
245 	signals[ABOUT_TO_SHOW] =     g_signal_new(DBUSMENU_MENUITEM_SIGNAL_ABOUT_TO_SHOW,
246 	                                          G_TYPE_FROM_CLASS(klass),
247 	                                          G_SIGNAL_RUN_LAST,
248 	                                          G_STRUCT_OFFSET(DbusmenuMenuitemClass, about_to_show),
249 	                                          NULL, NULL,
250 	                                          _dbusmenu_menuitem_marshal_VOID__VOID,
251 	                                          G_TYPE_BOOLEAN, 0, G_TYPE_NONE);
252 	/**
253 		DbusmenuMenuitem::event:
254 		@arg0: The #DbusmenuMenuitem object.
255 		@arg1: Name of the event
256 		@arg2: Information passed along with the event
257 		@arg3: X11 timestamp of when the event happened
258 
259 		Emitted when an event is passed through.  The event is signalled
260 		after handle_event is called.
261 	*/
262 	signals[EVENT] =             g_signal_new(DBUSMENU_MENUITEM_SIGNAL_EVENT,
263 	                                          G_TYPE_FROM_CLASS(klass),
264 	                                          G_SIGNAL_RUN_LAST | G_SIGNAL_DETAILED,
265 	                                          G_STRUCT_OFFSET(DbusmenuMenuitemClass, event),
266 	                                          g_signal_accumulator_true_handled, NULL,
267 	                                          _dbusmenu_menuitem_marshal_BOOLEAN__STRING_VARIANT_UINT,
268 	                                          G_TYPE_BOOLEAN, 3, G_TYPE_STRING, G_TYPE_VARIANT, G_TYPE_UINT);
269 
270 	g_object_class_install_property (object_class, PROP_ID,
271 	                                 g_param_spec_int(PROP_ID_S, "ID for the menu item",
272 	                                              "This is a unique indentifier for the menu item.",
273 												  -1, G_MAXINT, -1,
274 	                                              G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS));
275 
276 	/* Check transfer functions for GValue */
277 	if (!g_value_type_transformable(G_TYPE_STRING, G_TYPE_BOOLEAN)) {
278 		g_value_register_transform_func(G_TYPE_STRING, G_TYPE_BOOLEAN, g_value_transform_STRING_BOOLEAN);
279 	}
280 	if (!g_value_type_transformable(G_TYPE_STRING, G_TYPE_INT)) {
281 		g_value_register_transform_func(G_TYPE_STRING, G_TYPE_INT, g_value_transform_STRING_INT);
282 	}
283 
284 	return;
285 }
286 
287 /* A little helper function to translate a string into
288    a boolean value */
289 static void
g_value_transform_STRING_BOOLEAN(const GValue * in,GValue * out)290 g_value_transform_STRING_BOOLEAN (const GValue * in, GValue * out)
291 {
292 	const gchar * string = g_value_get_string(in);
293 	if (!g_strcmp0(string, "TRUE") || !g_strcmp0(string, "true") || !g_strcmp0(string, "True")) {
294 		g_value_set_boolean(out, TRUE);
295 	} else {
296 		g_value_set_boolean(out, FALSE);
297 	}
298 	return;
299 }
300 
301 /* A little helper function to translate a string into
302    a integer value */
303 static void
g_value_transform_STRING_INT(const GValue * in,GValue * out)304 g_value_transform_STRING_INT (const GValue * in, GValue * out)
305 {
306 	g_value_set_int(out, atoi(g_value_get_string(in)));
307 	return;
308 }
309 
310 static gint menuitem_next_id = 1;
311 
312 /* Make the unref function match the prototype need for the
313    hashtable destructor */
314 static void
_g_variant_unref(gpointer data)315 _g_variant_unref (gpointer data)
316 {
317 	g_variant_unref((GVariant *)data);
318 	return;
319 }
320 
321 /* Initialize the values of the in the object, and build the
322    properties hash table. */
323 static void
dbusmenu_menuitem_init(DbusmenuMenuitem * self)324 dbusmenu_menuitem_init (DbusmenuMenuitem *self)
325 {
326 	self->priv = G_TYPE_INSTANCE_GET_PRIVATE ((self), DBUSMENU_TYPE_MENUITEM, DbusmenuMenuitemPrivate);
327 
328 	DbusmenuMenuitemPrivate * priv = DBUSMENU_MENUITEM_GET_PRIVATE(self);
329 
330 	priv->id = -1;
331 	priv->children = NULL;
332 
333 	priv->properties = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, _g_variant_unref);
334 
335 	priv->root = FALSE;
336 	priv->realized = FALSE;
337 
338 	priv->defaults = dbusmenu_defaults_ref_default();
339 	priv->exposed = FALSE;
340 
341 	return;
342 }
343 
344 static void
dbusmenu_menuitem_dispose(GObject * object)345 dbusmenu_menuitem_dispose (GObject *object)
346 {
347 	DbusmenuMenuitemPrivate * priv = DBUSMENU_MENUITEM_GET_PRIVATE(object);
348 
349 	GList * child = NULL;
350 	for (child = priv->children; child != NULL; child = g_list_next(child)) {
351 		g_object_unref(G_OBJECT(child->data));
352 	}
353 	g_list_free(priv->children);
354 	priv->children = NULL;
355 
356 	if (priv->defaults != NULL) {
357 		g_object_unref(priv->defaults);
358 		priv->defaults = NULL;
359 	}
360 
361 	if (priv->parent) {
362 		g_object_remove_weak_pointer(G_OBJECT(priv->parent), (gpointer *)&priv->parent);
363 		priv->parent = NULL;
364 	}
365 
366 	G_OBJECT_CLASS (dbusmenu_menuitem_parent_class)->dispose (object);
367 	return;
368 }
369 
370 static void
dbusmenu_menuitem_finalize(GObject * object)371 dbusmenu_menuitem_finalize (GObject *object)
372 {
373 	/* g_debug("Menuitem dying"); */
374 	DbusmenuMenuitemPrivate * priv = DBUSMENU_MENUITEM_GET_PRIVATE(object);
375 
376 	if (priv->properties != NULL) {
377 		g_hash_table_destroy(priv->properties);
378 		priv->properties = NULL;
379 	}
380 
381 	G_OBJECT_CLASS (dbusmenu_menuitem_parent_class)->finalize (object);
382 	return;
383 }
384 
385 static void
set_property(GObject * obj,guint id,const GValue * value,GParamSpec * pspec)386 set_property (GObject * obj, guint id, const GValue * value, GParamSpec * pspec)
387 {
388 	DbusmenuMenuitemPrivate * priv = DBUSMENU_MENUITEM_GET_PRIVATE(obj);
389 
390 	switch (id) {
391 	case PROP_ID:
392 		priv->id = g_value_get_int(value);
393 		if (priv->id > menuitem_next_id) {
394 			if (priv->id == G_MAXINT) {
395 				menuitem_next_id = 1;
396 			} else {
397 				menuitem_next_id = priv->id + 1;
398 			}
399 		}
400 		break;
401 	default:
402 		G_OBJECT_WARN_INVALID_PROPERTY_ID(obj, id, pspec);
403 		break;
404 	}
405 
406 	return;
407 }
408 
409 static void
get_property(GObject * obj,guint id,GValue * value,GParamSpec * pspec)410 get_property (GObject * obj, guint id, GValue * value, GParamSpec * pspec)
411 {
412 	DbusmenuMenuitemPrivate * priv = DBUSMENU_MENUITEM_GET_PRIVATE(obj);
413 
414 	switch (id) {
415 	case PROP_ID:
416 		if (priv->id == -1) {
417 			priv->id = menuitem_next_id;
418 			if (menuitem_next_id == G_MAXINT) {
419 				menuitem_next_id = 1;
420 			} else {
421 				menuitem_next_id += 1;
422 			}
423 		}
424 		if (dbusmenu_menuitem_get_root(DBUSMENU_MENUITEM(obj))) {
425 			g_value_set_int(value, 0);
426 		} else {
427 			g_value_set_int(value, priv->id);
428 		}
429 		break;
430 	default:
431 		G_OBJECT_WARN_INVALID_PROPERTY_ID(obj, id, pspec);
432 		break;
433 	}
434 
435 	return;
436 }
437 
438 /* Handles the activate event if it is sent. */
439 static void
handle_event(DbusmenuMenuitem * mi,const gchar * name,GVariant * value,guint timestamp)440 handle_event (DbusmenuMenuitem * mi, const gchar * name, GVariant * value, guint timestamp)
441 {
442 	if (g_strcmp0(name, DBUSMENU_MENUITEM_EVENT_ACTIVATED) == 0) {
443 		g_signal_emit(G_OBJECT(mi), signals[ITEM_ACTIVATED], 0, timestamp, TRUE);
444 	}
445 
446 	return;
447 }
448 
449 /* Handles our about to show signal on items that submenus
450    exist.  This is sending just activate now, but we should
451    probably consider a special signal in the future if GTK
452    gets more sophisticated about this. */
453 static void
send_about_to_show(DbusmenuMenuitem * mi,void (* cb)(DbusmenuMenuitem * mi,gpointer user_data),gpointer cb_data)454 send_about_to_show (DbusmenuMenuitem * mi, void (*cb) (DbusmenuMenuitem * mi, gpointer user_data), gpointer cb_data)
455 {
456 	g_return_if_fail(DBUSMENU_IS_MENUITEM(mi));
457 
458 	if (dbusmenu_menuitem_get_children(mi) == NULL && g_strcmp0(DBUSMENU_MENUITEM_CHILD_DISPLAY_SUBMENU, dbusmenu_menuitem_property_get(mi, DBUSMENU_MENUITEM_PROP_CHILD_DISPLAY)) != 0) {
459 		g_warning("About to Show called on an item wihtout submenus.  We're ignoring it.");
460 	} else {
461 		gboolean dummy;
462 		g_signal_emit(G_OBJECT(mi), signals[ABOUT_TO_SHOW], 0, &dummy);
463 	}
464 
465 	if (cb != NULL) {
466 		cb(mi, cb_data);
467 	}
468 
469 	return;
470 }
471 
472 /* A helper function to get the type of the menuitem, this might
473    be a candidate for optimization in the future. */
474 static const gchar *
menuitem_get_type(const DbusmenuMenuitem * mi)475 menuitem_get_type (const DbusmenuMenuitem * mi)
476 {
477 	DbusmenuMenuitemPrivate * priv = DBUSMENU_MENUITEM_GET_PRIVATE(mi);
478 	GVariant * currentval = (GVariant *)g_hash_table_lookup(priv->properties, DBUSMENU_MENUITEM_PROP_TYPE);
479 	if (currentval != NULL) {
480 		return g_variant_get_string(currentval, NULL);
481 	}
482 	return NULL;
483 }
484 
485 /* Public interface */
486 
487 /**
488  * dbusmenu_menuitem_new:
489  *
490  * Create a new #DbusmenuMenuitem with all default values.
491  *
492  * Return value: A newly allocated #DbusmenuMenuitem.
493  */
494 DbusmenuMenuitem *
dbusmenu_menuitem_new(void)495 dbusmenu_menuitem_new (void)
496 {
497 	return g_object_new(DBUSMENU_TYPE_MENUITEM, NULL);
498 }
499 
500 /**
501  * dbusmenu_menuitem_new_with_id:
502  * @id: ID to use for this menuitem
503  *
504  * This creates a blank #DbusmenuMenuitem with a specific ID.
505  *
506  * Return value: A newly allocated #DbusmenuMenuitem.
507  */
508 DbusmenuMenuitem *
dbusmenu_menuitem_new_with_id(gint id)509 dbusmenu_menuitem_new_with_id (gint id)
510 {
511 	DbusmenuMenuitem * mi = g_object_new(DBUSMENU_TYPE_MENUITEM, PROP_ID_S, id, NULL);
512 	/* g_debug("New Menuitem id %d goal id %d", dbusmenu_menuitem_get_id(mi), id); */
513 	return mi;
514 }
515 
516 /**
517  * dbusmenu_menuitem_get_id:
518  * @mi: The #DbusmenuMenuitem to query.
519  *
520  * Gets the unique ID for @mi.
521  *
522  * Return value: The ID of the @mi.
523  */
524 gint
dbusmenu_menuitem_get_id(DbusmenuMenuitem * mi)525 dbusmenu_menuitem_get_id (DbusmenuMenuitem * mi)
526 {
527 	g_return_val_if_fail(DBUSMENU_IS_MENUITEM(mi), -1);
528 
529 	GValue retval = {0};
530 	g_value_init(&retval, G_TYPE_INT);
531 	g_object_get_property(G_OBJECT(mi), PROP_ID_S, &retval);
532 	gint ret = g_value_get_int(&retval);
533 	#ifdef MASSIVEDEBUGGING
534 	g_debug("Getting menuitem ID: %d", ret);
535 	#endif
536 	return ret;
537 }
538 
539 /**
540  * dbusmenu_menuitem_realized:
541  * @mi: #DbusmenuMenuitem to check on
542  *
543  * This function returns whether the menuitem has been realized or
544  * not.  This is significant mostly in client implementations that
545  * can use this additional state to see if the second layers of
546  * the implementation have been built yet.
547  *
548  * Return value: Returns whether or not the menu item has been realized
549  * 	yet or not.
550  */
551 gboolean
dbusmenu_menuitem_realized(DbusmenuMenuitem * mi)552 dbusmenu_menuitem_realized (DbusmenuMenuitem * mi)
553 {
554 	g_return_val_if_fail(DBUSMENU_IS_MENUITEM(mi), FALSE);
555 	DbusmenuMenuitemPrivate * priv = DBUSMENU_MENUITEM_GET_PRIVATE(mi);
556 	return priv->realized;
557 }
558 
559 /**
560  * dbusmenu_menuitem_set_realized:
561  * @mi: #DbusmenuMenuitem to realize
562  *
563  * Sets the internal variable tracking whether it's been realized and
564  * signals the DbusmenuMenuitem::realized event.
565  */
566 void
dbusmenu_menuitem_set_realized(DbusmenuMenuitem * mi)567 dbusmenu_menuitem_set_realized (DbusmenuMenuitem * mi)
568 {
569 	g_return_if_fail(DBUSMENU_IS_MENUITEM(mi));
570 	DbusmenuMenuitemPrivate * priv = DBUSMENU_MENUITEM_GET_PRIVATE(mi);
571 	if (priv->realized) {
572 		g_warning("Realized entry realized again?  ID: %d", dbusmenu_menuitem_get_id(mi));
573 	}
574 	priv->realized = TRUE;
575 	g_signal_emit(G_OBJECT(mi), signals[REALIZED], 0, TRUE);
576 	return;
577 }
578 
579 /**
580  * dbusmenu_menuitem_get_children:
581  * @mi: The #DbusmenuMenuitem to query.
582  *
583  * Returns simply the list of children that this menu item
584  * has.  The list is valid until another child related function
585  * is called, where it might be changed.
586  *
587  * Return value: (transfer none) (element-type Dbusmenu.Menuitem): A #GList of pointers to #DbusmenuMenuitem objects.
588  */
589 GList *
dbusmenu_menuitem_get_children(DbusmenuMenuitem * mi)590 dbusmenu_menuitem_get_children (DbusmenuMenuitem * mi)
591 {
592 	g_return_val_if_fail(DBUSMENU_IS_MENUITEM(mi), NULL);
593 
594 	DbusmenuMenuitemPrivate * priv = DBUSMENU_MENUITEM_GET_PRIVATE(mi);
595 	return priv->children;
596 }
597 
598 /* For all the taken children we need to signal
599    that they were removed */
600 static void
take_children_helper(gpointer data,gpointer user_data)601 take_children_helper (gpointer data, gpointer user_data)
602 {
603 	#ifdef MASSIVEDEBUGGING
604 	g_debug("Menuitem %d (%s) signalling child removed %d (%s)", ID(user_data), LABEL(user_data), ID(data), LABEL(data));
605 	#endif
606 	dbusmenu_menuitem_unparent(DBUSMENU_MENUITEM(data));
607 	g_signal_emit(G_OBJECT(user_data), signals[CHILD_REMOVED], 0, DBUSMENU_MENUITEM(data), TRUE);
608 	return;
609 }
610 
611 /**
612  * dbusmenu_menuitem_take_children:
613  * @mi: The #DbusmenMenuitem to take the children from.
614  *
615  * While the name sounds devious that's exactly what this function
616  * does.  It takes the list of children from the @mi and clears the
617  * internal list.  The calling function is now in charge of the ref's
618  * on the children it has taken.  A lot of responsibility involved
619  * in taking children.
620  *
621  * Return value: (transfer full) (element-type Dbusmenu.Menuitem):
622  *    A #GList of pointers to #DbusmenuMenuitem objects.
623  */
624 GList *
dbusmenu_menuitem_take_children(DbusmenuMenuitem * mi)625 dbusmenu_menuitem_take_children (DbusmenuMenuitem * mi)
626 {
627 	g_return_val_if_fail(DBUSMENU_IS_MENUITEM(mi), NULL);
628 
629 	DbusmenuMenuitemPrivate * priv = DBUSMENU_MENUITEM_GET_PRIVATE(mi);
630 	GList * children = priv->children;
631 	priv->children = NULL;
632 	g_list_foreach(children, take_children_helper, mi);
633 
634 	dbusmenu_menuitem_property_remove(mi, DBUSMENU_MENUITEM_PROP_CHILD_DISPLAY);
635 
636 	return children;
637 }
638 
639 /**
640  * dbusmenu_menuitem_get_position:
641  * @mi: The #DbusmenuMenuitem to find the position of
642  * @parent: The #DbusmenuMenuitem who's children contain @mi
643  *
644  * This function returns the position of the menu item @mi
645  * in the children of @parent.  It will return zero if the
646  * menu item can't be found.
647  *
648  * Return value: The position of @mi in the children of @parent.
649  */
650 guint
dbusmenu_menuitem_get_position(DbusmenuMenuitem * mi,DbusmenuMenuitem * parent)651 dbusmenu_menuitem_get_position (DbusmenuMenuitem * mi, DbusmenuMenuitem * parent)
652 {
653 	#ifdef MASSIVEDEBUGGING
654 	if (!DBUSMENU_IS_MENUITEM(mi))     g_warning("Getting position of %d (%s), it's at: %d (mi fail)", ID(mi), LABEL(mi), 0);
655 	if (!DBUSMENU_IS_MENUITEM(parent)) g_warning("Getting position of %d (%s), it's at: %d (parent fail)", ID(mi), LABEL(mi), 0);
656 	#endif
657 
658 	/* TODO: I'm not too happy returning zeros here.  But that's all I've got */
659 	g_return_val_if_fail(DBUSMENU_IS_MENUITEM(mi), 0);
660 	g_return_val_if_fail(DBUSMENU_IS_MENUITEM(parent), 0);
661 
662 	GList * childs = dbusmenu_menuitem_get_children(parent);
663 	if (childs == NULL) return 0;
664 	guint count = 0;
665 	for ( ; childs != NULL; childs = childs->next, count++) {
666 		if (childs->data == mi) break;
667 	}
668 
669 	if (childs == NULL) return 0;
670 
671 	#ifdef MASSIVEDEBUGGING
672 	g_debug("Getting position of %d (%s), it's at: %d", ID(mi), LABEL(mi), count);
673 	#endif
674 
675 	return count;
676 }
677 
678 /**
679  * dbusmenu_menuitem_get_position_realized:
680  * @mi: The #DbusmenuMenuitem to find the position of
681  * @parent: The #DbusmenuMenuitem who's children contain @mi
682  *
683  * This function is very similar to #dbusmenu_menuitem_get_position
684  * except that it only counts in the children that have been realized.
685  *
686  * Return value: The position of @mi in the realized children of @parent.
687  */
688 guint
dbusmenu_menuitem_get_position_realized(DbusmenuMenuitem * mi,DbusmenuMenuitem * parent)689 dbusmenu_menuitem_get_position_realized (DbusmenuMenuitem * mi, DbusmenuMenuitem * parent)
690 {
691 	#ifdef MASSIVEDEBUGGING
692 	if (!DBUSMENU_IS_MENUITEM(mi))     g_warning("Getting position of %d (%s), it's at: %d (mi fail)", ID(mi), LABEL(mi), 0);
693 	if (!DBUSMENU_IS_MENUITEM(parent)) g_warning("Getting position of %d (%s), it's at: %d (parent fail)", ID(mi), LABEL(mi), 0);
694 	#endif
695 
696 	/* TODO: I'm not too happy returning zeros here.  But that's all I've got */
697 	g_return_val_if_fail(DBUSMENU_IS_MENUITEM(mi), 0);
698 	g_return_val_if_fail(DBUSMENU_IS_MENUITEM(parent), 0);
699 
700 	GList * childs = dbusmenu_menuitem_get_children(parent);
701 	if (childs == NULL) return 0;
702 	guint count = 0;
703 	for ( ; childs != NULL; childs = childs->next, count++) {
704 		if (!dbusmenu_menuitem_realized(DBUSMENU_MENUITEM(childs->data))) {
705 			count--;
706 			continue;
707 		}
708 		if (childs->data == mi) {
709 			break;
710 		}
711 	}
712 
713 	if (childs == NULL) return 0;
714 
715 	#ifdef MASSIVEDEBUGGING
716 	g_debug("Getting position of %d (%s), it's at: %d", ID(mi), LABEL(mi), count);
717 	#endif
718 
719 	return count;
720 }
721 
722 /**
723  * dbusmenu_menuitem_child_append:
724  * @mi: The #DbusmenuMenuitem which will become a new parent
725  * @child: The #DbusmenMenuitem that will be a child
726  *
727  * This function adds @child to the list of children on @mi at
728  * the end of that list.
729  *
730  * Return value: Whether the child has been added successfully.
731  */
732 gboolean
dbusmenu_menuitem_child_append(DbusmenuMenuitem * mi,DbusmenuMenuitem * child)733 dbusmenu_menuitem_child_append (DbusmenuMenuitem * mi, DbusmenuMenuitem * child)
734 {
735 	g_return_val_if_fail(DBUSMENU_IS_MENUITEM(mi), FALSE);
736 	g_return_val_if_fail(DBUSMENU_IS_MENUITEM(child), FALSE);
737 
738 	DbusmenuMenuitemPrivate * priv = DBUSMENU_MENUITEM_GET_PRIVATE(mi);
739 	g_return_val_if_fail(g_list_find(priv->children, child) == NULL, FALSE);
740 
741 	if (!dbusmenu_menuitem_set_parent(child, mi)) {
742 		return FALSE;
743 	}
744 
745 	if (priv->children == NULL && !dbusmenu_menuitem_property_exist(mi, DBUSMENU_MENUITEM_PROP_CHILD_DISPLAY)) {
746 		dbusmenu_menuitem_property_set(mi, DBUSMENU_MENUITEM_PROP_CHILD_DISPLAY, DBUSMENU_MENUITEM_CHILD_DISPLAY_SUBMENU);
747 	}
748 
749 	priv->children = g_list_append(priv->children, child);
750 	#ifdef MASSIVEDEBUGGING
751 	g_debug("Menuitem %d (%s) signalling child added %d (%s) at %d", ID(mi), LABEL(mi), ID(child), LABEL(child), g_list_length(priv->children) - 1);
752 	#endif
753 	g_object_ref(G_OBJECT(child));
754 	g_signal_emit(G_OBJECT(mi), signals[CHILD_ADDED], 0, child, g_list_length(priv->children) - 1, TRUE);
755 	return TRUE;
756 }
757 
758 /**
759  * dbusmenu_menuitem_child_prepend:
760  * @mi: The #DbusmenuMenuitem which will become a new parent
761  * @child: The #DbusmenMenuitem that will be a child
762  *
763  * This function adds @child to the list of children on @mi at
764  * the beginning of that list.
765  *
766  * Return value: Whether the child has been added successfully.
767  */
768 gboolean
dbusmenu_menuitem_child_prepend(DbusmenuMenuitem * mi,DbusmenuMenuitem * child)769 dbusmenu_menuitem_child_prepend (DbusmenuMenuitem * mi, DbusmenuMenuitem * child)
770 {
771 	g_return_val_if_fail(DBUSMENU_IS_MENUITEM(mi), FALSE);
772 	g_return_val_if_fail(DBUSMENU_IS_MENUITEM(child), FALSE);
773 
774 	DbusmenuMenuitemPrivate * priv = DBUSMENU_MENUITEM_GET_PRIVATE(mi);
775 	g_return_val_if_fail(g_list_find(priv->children, child) == NULL, FALSE);
776 
777 	if (!dbusmenu_menuitem_set_parent(child, mi)) {
778 		return FALSE;
779 	}
780 
781 	if (priv->children == NULL && !dbusmenu_menuitem_property_exist(mi, DBUSMENU_MENUITEM_PROP_CHILD_DISPLAY)) {
782 		dbusmenu_menuitem_property_set(mi, DBUSMENU_MENUITEM_PROP_CHILD_DISPLAY, DBUSMENU_MENUITEM_CHILD_DISPLAY_SUBMENU);
783 	}
784 
785 	priv->children = g_list_prepend(priv->children, child);
786 	#ifdef MASSIVEDEBUGGING
787 	g_debug("Menuitem %d (%s) signalling child added %d (%s) at %d", ID(mi), LABEL(mi), ID(child), LABEL(child), 0);
788 	#endif
789 	g_object_ref(G_OBJECT(child));
790 	g_signal_emit(G_OBJECT(mi), signals[CHILD_ADDED], 0, child, 0, TRUE);
791 	return TRUE;
792 }
793 
794 /**
795  * dbusmenu_menuitem_child_delete:
796  * @mi: The #DbusmenuMenuitem which has @child as a child
797  * @child: The child #DbusmenuMenuitem that you want to no longer
798  *     be a child of @mi.
799  *
800  * This function removes @child from the children list of @mi.  It does
801  * not call #g_object_unref on @child.
802  *
803  * Return value: If we were able to delete @child.
804  */
805 gboolean
dbusmenu_menuitem_child_delete(DbusmenuMenuitem * mi,DbusmenuMenuitem * child)806 dbusmenu_menuitem_child_delete (DbusmenuMenuitem * mi, DbusmenuMenuitem * child)
807 {
808 	g_return_val_if_fail(DBUSMENU_IS_MENUITEM(mi), FALSE);
809 	g_return_val_if_fail(DBUSMENU_IS_MENUITEM(child), FALSE);
810 
811 	if (dbusmenu_menuitem_get_parent(child) != mi) {
812 		g_warning("Trying to remove a child that doesn't believe we're its parent.");
813 		return FALSE;
814 	}
815 
816 	DbusmenuMenuitemPrivate * priv = DBUSMENU_MENUITEM_GET_PRIVATE(mi);
817 	priv->children = g_list_remove(priv->children, child);
818 	dbusmenu_menuitem_unparent(child);
819 	#ifdef MASSIVEDEBUGGING
820 	g_debug("Menuitem %d (%s) signalling child removed %d (%s)", ID(mi), LABEL(mi), ID(child), LABEL(child));
821 	#endif
822 	g_signal_emit(G_OBJECT(mi), signals[CHILD_REMOVED], 0, child, TRUE);
823 	g_object_unref(G_OBJECT(child));
824 
825 	if (priv->children == NULL) {
826 		dbusmenu_menuitem_property_remove(mi, DBUSMENU_MENUITEM_PROP_CHILD_DISPLAY);
827 	}
828 
829 	return TRUE;
830 }
831 
832 /**
833  * dbusmenu_menuitem_child_add_position:
834  * @mi: The #DbusmenuMenuitem that we're adding the child @child to.
835  * @child: The #DbusmenuMenuitem to make a child of @mi.
836  * @position: Where in @mi object's list of chidren @child should be placed.
837  *
838  * Puts @child in the list of children for @mi at the location
839  * specified in @position.  If there is not enough entires available
840  * then @child will be placed at the end of the list.
841  *
842  * Return value: Whether @child was added successfully.
843  */
844 gboolean
dbusmenu_menuitem_child_add_position(DbusmenuMenuitem * mi,DbusmenuMenuitem * child,guint position)845 dbusmenu_menuitem_child_add_position (DbusmenuMenuitem * mi, DbusmenuMenuitem * child, guint position)
846 {
847 	g_return_val_if_fail(DBUSMENU_IS_MENUITEM(mi), FALSE);
848 	g_return_val_if_fail(DBUSMENU_IS_MENUITEM(child), FALSE);
849 
850 	DbusmenuMenuitemPrivate * priv = DBUSMENU_MENUITEM_GET_PRIVATE(mi);
851 	g_return_val_if_fail(g_list_find(priv->children, child) == NULL, FALSE);
852 
853 	if (!dbusmenu_menuitem_set_parent(child, mi)) {
854 		return FALSE;
855 	}
856 
857 	if (priv->children == NULL && !dbusmenu_menuitem_property_exist(mi, DBUSMENU_MENUITEM_PROP_CHILD_DISPLAY)) {
858 		dbusmenu_menuitem_property_set(mi, DBUSMENU_MENUITEM_PROP_CHILD_DISPLAY, DBUSMENU_MENUITEM_CHILD_DISPLAY_SUBMENU);
859 	}
860 
861 	priv->children = g_list_insert(priv->children, child, position);
862 	#ifdef MASSIVEDEBUGGING
863 	g_debug("Menuitem %d (%s) signalling child added %d (%s) at %d", ID(mi), LABEL(mi), ID(child), LABEL(child), position);
864 	#endif
865 	g_object_ref(G_OBJECT(child));
866 	g_signal_emit(G_OBJECT(mi), signals[CHILD_ADDED], 0, child, position, TRUE);
867 	return TRUE;
868 }
869 
870 /**
871  * dbusmenu_menuitem_child_reorder:
872  * @mi: The #DbusmenuMenuitem that has children needing realignment
873  * @child: The #DbusmenuMenuitem that is a child needing to be moved
874  * @position: The position in the list to place it in
875  *
876  * This function moves a child on the list of children.  It is
877  * for a child that is already in the list, but simply needs a
878  * new location.
879  *
880  * Return value: Whether the move was successful.
881  */
882 gboolean
dbusmenu_menuitem_child_reorder(DbusmenuMenuitem * mi,DbusmenuMenuitem * child,guint position)883 dbusmenu_menuitem_child_reorder(DbusmenuMenuitem * mi, DbusmenuMenuitem * child, guint position)
884 {
885 	g_return_val_if_fail(DBUSMENU_IS_MENUITEM(mi), FALSE);
886 	g_return_val_if_fail(DBUSMENU_IS_MENUITEM(child), FALSE);
887 
888 	DbusmenuMenuitemPrivate * priv = DBUSMENU_MENUITEM_GET_PRIVATE(mi);
889 	gint oldpos = g_list_index(priv->children, child);
890 
891 	if (oldpos == -1) {
892 		g_warning("Can not reorder child that isn't actually a child.");
893 		return FALSE;
894 	}
895 	if (oldpos == position) {
896 		return TRUE;
897 	}
898 
899 	priv->children = g_list_remove(priv->children, child);
900 	priv->children = g_list_insert(priv->children, child, position);
901 
902 	#ifdef MASSIVEDEBUGGING
903 	g_debug("Menuitem %d (%s) signalling child %d (%s) moved from %d to %d", ID(mi), LABEL(mi), ID(child), LABEL(child), oldpos, position);
904 	#endif
905 	g_signal_emit(G_OBJECT(mi), signals[CHILD_MOVED], 0, child, position, oldpos, TRUE);
906 
907 	return TRUE;
908 }
909 
910 /**
911  * dbusmenu_menuitem_child_find:
912  * @mi: The #DbusmenuMenuitem who's children to look on
913  * @id: The ID of the child that we're looking for.
914  *
915  * Search the children of @mi to find one with the ID of @id.
916  * If it doesn't exist then we return #NULL.
917  *
918  * Return value: (transfer none): The menu item with the ID @id or #NULL if it
919  *    can't be found.
920  */
921 DbusmenuMenuitem *
dbusmenu_menuitem_child_find(DbusmenuMenuitem * mi,gint id)922 dbusmenu_menuitem_child_find (DbusmenuMenuitem * mi, gint id)
923 {
924 	g_return_val_if_fail(DBUSMENU_IS_MENUITEM(mi), NULL);
925 
926 	GList * childs = dbusmenu_menuitem_get_children(mi);
927 	if (childs == NULL) return NULL;
928 
929 	for ( ; childs == NULL; childs = g_list_next(childs)) {
930 		DbusmenuMenuitem * lmi = DBUSMENU_MENUITEM(childs->data);
931 		if (id == dbusmenu_menuitem_get_id(lmi)) {
932 			return lmi;
933 		}
934 	}
935 
936 	return NULL;
937 }
938 
939 typedef struct {
940 	DbusmenuMenuitem * mi;
941 	gint id;
942 } find_id_t;
943 
944 /* Basically the heart of the find_id that matches the
945    API of GFunc.  Unfortunately, this goes through all the
946    children, but it rejects them quickly. */
947 static void
find_id_helper(gpointer in_mi,gpointer in_find_id)948 find_id_helper (gpointer in_mi, gpointer in_find_id)
949 {
950 	DbusmenuMenuitem * mi = (DbusmenuMenuitem *)in_mi;
951 	find_id_t * find_id = (find_id_t *)in_find_id;
952 
953 	if (find_id->mi != NULL) return;
954 	if (find_id->id == dbusmenu_menuitem_get_id(mi)) {
955 		find_id->mi = mi;
956 		return;
957 	}
958 
959 	g_list_foreach(dbusmenu_menuitem_get_children(mi), find_id_helper, in_find_id);
960 	return;
961 }
962 
963 /**
964  * dbusmenu_menuitem_find_id:
965  * @mi: #DbusmenuMenuitem at the top of the tree to search
966  * @id: ID of the #DbusmenuMenuitem to search for
967  *
968  * This function searchs the whole tree of children that
969  * are attached to @mi.  This could be quite a few nodes, all
970  * the way down the tree.  It is a depth first search.
971  *
972  * Return value: (transfer none): The #DbusmenuMenuitem with the ID of @id
973  * 	or #NULL if there isn't such a menu item in the tree
974  * 	represented by @mi.
975  */
976 DbusmenuMenuitem *
dbusmenu_menuitem_find_id(DbusmenuMenuitem * mi,gint id)977 dbusmenu_menuitem_find_id (DbusmenuMenuitem * mi, gint id)
978 {
979 	g_return_val_if_fail(DBUSMENU_IS_MENUITEM(mi), NULL);
980 	if (id == 0) {
981 		if (dbusmenu_menuitem_get_root(mi) == FALSE) {
982 			g_warning("Getting a menuitem with id zero, but it's not set as root.");
983 		}
984 		return mi;
985 	}
986 	find_id_t find_id = {.mi=NULL, .id=id};
987 	find_id_helper(mi, &find_id);
988 	return find_id.mi;
989 }
990 
991 /**
992  * dbusmenu_menuitem_set_parent:
993  * @mi: The #DbusmenuMenuitem for which to set the parent
994  * @parent: The new parent #DbusmenuMenuitem
995  *
996  * Sets the parent of @mi to @parent. If @mi already
997  * has a parent, then this call will fail. The parent will
998  * be set automatically when using the usual methods to add a
999  * child menuitem, so this function should not normally be
1000  * called directly
1001  *
1002  * Return value: Whether the parent was set successfully
1003  */
1004 gboolean
dbusmenu_menuitem_set_parent(DbusmenuMenuitem * mi,DbusmenuMenuitem * parent)1005 dbusmenu_menuitem_set_parent (DbusmenuMenuitem * mi, DbusmenuMenuitem * parent)
1006 {
1007 	g_return_val_if_fail(DBUSMENU_IS_MENUITEM(mi), FALSE);
1008 	g_return_val_if_fail(DBUSMENU_IS_MENUITEM(mi), FALSE);
1009 
1010 	DbusmenuMenuitemPrivate * priv = DBUSMENU_MENUITEM_GET_PRIVATE(mi);
1011 
1012 	if (priv->parent != NULL) {
1013 		g_warning ("Menu item already has a parent");
1014 		return FALSE;
1015 	}
1016 
1017 	priv->parent = parent;
1018 	g_object_add_weak_pointer(G_OBJECT(priv->parent), (gpointer *)&priv->parent);
1019 
1020 	return TRUE;
1021 }
1022 
1023 /**
1024  * dbusmenu_menuitem_unparent:
1025  * @mi: The #DbusmenuMenuitem to unparent
1026  *
1027  * Unparents the menu item @mi. If @mi doesn't have a
1028  * parent, then this call will fail. The menuitem will
1029  * be unparented automatically when using the usual methods
1030  * to delete a child menuitem, so this function should not
1031  * normally be called directly
1032  *
1033  * Return value: Whether the menu item was unparented successfully
1034  */
1035 gboolean
dbusmenu_menuitem_unparent(DbusmenuMenuitem * mi)1036 dbusmenu_menuitem_unparent (DbusmenuMenuitem * mi)
1037 {
1038 	g_return_val_if_fail(DBUSMENU_IS_MENUITEM(mi), FALSE);
1039 	DbusmenuMenuitemPrivate * priv = DBUSMENU_MENUITEM_GET_PRIVATE(mi);
1040 
1041 	if (priv->parent == NULL) {
1042 		g_warning("Menu item doesn't have a parent");
1043 		return FALSE;
1044 	}
1045 
1046 	g_object_remove_weak_pointer(G_OBJECT(priv->parent), (gpointer *)&priv->parent);
1047 	priv->parent = NULL;
1048 
1049 	return TRUE;
1050 }
1051 
1052 /**
1053  * dbusmenu_menuitem_get_parent:
1054  * @mi: The #DbusmenuMenuitem for which to inspect the parent
1055  *
1056  * This function looks up the parent of @mi
1057  *
1058  * Return value: (transfer none): The parent of this menu item
1059  */
1060 DbusmenuMenuitem *
dbusmenu_menuitem_get_parent(DbusmenuMenuitem * mi)1061 dbusmenu_menuitem_get_parent (DbusmenuMenuitem * mi)
1062 {
1063 	g_return_val_if_fail(DBUSMENU_IS_MENUITEM(mi), NULL);
1064 	DbusmenuMenuitemPrivate * priv = DBUSMENU_MENUITEM_GET_PRIVATE(mi);
1065 
1066 	return priv->parent;
1067 }
1068 
1069 /**
1070  * dbusmenu_menuitem_property_set:
1071  * @mi: The #DbusmenuMenuitem to set the property on.
1072  * @property: Name of the property to set.
1073  * @value: The value of the property.
1074  *
1075  * Takes the pair of @property and @value and places them as a
1076  * property on @mi.  If a property already exists by that name,
1077  * then the value is set to the new value.  If not, the property
1078  * is added.  If the value is changed or the property was previously
1079  * unset then the signal #DbusmenuMenuitem::prop-changed will be
1080  * emitted by this function.
1081  *
1082  * Return value:  A boolean representing if the property value was set.
1083  */
1084 gboolean
dbusmenu_menuitem_property_set(DbusmenuMenuitem * mi,const gchar * property,const gchar * value)1085 dbusmenu_menuitem_property_set (DbusmenuMenuitem * mi, const gchar * property, const gchar * value)
1086 {
1087 	GVariant * variant = NULL;
1088 	if (value != NULL) {
1089 		variant = g_variant_new_string(value);
1090 	}
1091 	return dbusmenu_menuitem_property_set_variant(mi, property, variant);
1092 }
1093 
1094 /**
1095  * dbusmenu_menuitem_property_set_bool:
1096  * @mi: The #DbusmenuMenuitem to set the property on.
1097  * @property: Name of the property to set.
1098  * @value: The value of the property.
1099  *
1100  * Takes a boolean @value and sets it on @property as a
1101  * property on @mi.  If a property already exists by that name,
1102  * then the value is set to the new value.  If not, the property
1103  * is added.  If the value is changed or the property was previously
1104  * unset then the signal #DbusmenuMenuitem::prop-changed will be
1105  * emitted by this function.
1106  *
1107  * Return value:  A boolean representing if the property value was set.
1108  */
1109 gboolean
dbusmenu_menuitem_property_set_bool(DbusmenuMenuitem * mi,const gchar * property,const gboolean value)1110 dbusmenu_menuitem_property_set_bool (DbusmenuMenuitem * mi, const gchar * property, const gboolean value)
1111 {
1112 	GVariant * variant = g_variant_new("b", value);
1113 	return dbusmenu_menuitem_property_set_variant(mi, property, variant);
1114 }
1115 
1116 /**
1117  * dbusmenu_menuitem_property_set_int:
1118  * @mi: The #DbusmenuMenuitem to set the property on.
1119  * @property: Name of the property to set.
1120  * @value: The value of the property.
1121  *
1122  * Takes a boolean @value and sets it on @property as a
1123  * property on @mi.  If a property already exists by that name,
1124  * then the value is set to the new value.  If not, the property
1125  * is added.  If the value is changed or the property was previously
1126  * unset then the signal #DbusmenuMenuitem::prop-changed will be
1127  * emitted by this function.
1128  *
1129  * Return value:  A boolean representing if the property value was set.
1130  */
1131 gboolean
dbusmenu_menuitem_property_set_int(DbusmenuMenuitem * mi,const gchar * property,const gint value)1132 dbusmenu_menuitem_property_set_int (DbusmenuMenuitem * mi, const gchar * property, const gint value)
1133 {
1134 	GVariant * variant = g_variant_new("i", value);
1135 	return dbusmenu_menuitem_property_set_variant(mi, property, variant);
1136 }
1137 
1138 /**
1139  * dbusmenu_menuitem_property_set_byte_array:
1140  * @mi: The #DbusmenuMenuitem to set the property on.
1141  * @property: Name of the property to set.
1142  * @value: The byte array.
1143  * @nelements: The number of elements in the byte array.
1144  *
1145  * Takes a byte array @value and sets it on @property as a
1146  * property on @mi.  If a property already exists by that name,
1147  * then the value is set to the new value.  If not, the property
1148  * is added.  If the value is changed or the property was previously
1149  * unset then the signal #DbusmenuMenuitem::prop-changed will be
1150  * emitted by this function.
1151  *
1152  * Return value:  A boolean representing if the property value was set.
1153  */
1154 gboolean
dbusmenu_menuitem_property_set_byte_array(DbusmenuMenuitem * mi,const gchar * property,const guchar * value,gsize nelements)1155 dbusmenu_menuitem_property_set_byte_array (DbusmenuMenuitem * mi, const gchar * property, const guchar * value, gsize nelements)
1156 {
1157 	GVariant * variant = NULL;
1158 	if (value != NULL) {
1159 		variant = g_variant_new_fixed_array(G_VARIANT_TYPE_BYTE, value, nelements, sizeof(guchar));
1160 	}
1161 	return dbusmenu_menuitem_property_set_variant(mi, property, variant);
1162 }
1163 
1164 /**
1165  * dbusmenu_menuitem_property_set_variant:
1166  * @mi: The #DbusmenuMenuitem to set the property on.
1167  * @property: Name of the property to set.
1168  * @value: The value of the property.
1169  *
1170  * Takes the pair of @property and @value and places them as a
1171  * property on @mi.  If a property already exists by that name,
1172  * then the value is set to the new value.  If not, the property
1173  * is added.  If the value is changed or the property was previously
1174  * unset then the signal #DbusmenuMenuitem::prop-changed will be
1175  * emitted by this function.
1176  *
1177  * Return value:  A boolean representing if the property value was set.
1178  */
1179 gboolean
dbusmenu_menuitem_property_set_variant(DbusmenuMenuitem * mi,const gchar * property,GVariant * value)1180 dbusmenu_menuitem_property_set_variant (DbusmenuMenuitem * mi, const gchar * property, GVariant * value)
1181 {
1182 	g_return_val_if_fail(DBUSMENU_IS_MENUITEM(mi), FALSE);
1183 	g_return_val_if_fail(property != NULL, FALSE);
1184 	g_return_val_if_fail(g_utf8_validate(property, -1, NULL), FALSE);
1185 
1186 	DbusmenuMenuitemPrivate * priv = DBUSMENU_MENUITEM_GET_PRIVATE(mi);
1187 	GVariant * default_value = NULL;
1188 
1189 	const gchar * type = menuitem_get_type(mi);
1190 
1191 	if (value != NULL) {
1192 		/* Check the expected type to see if we want to have a warning */
1193 		GVariantType * default_type = dbusmenu_defaults_default_get_type(priv->defaults, type, property);
1194 		if (default_type != NULL) {
1195 			/* If we have an expected type we should check to see if
1196 			   the value we've been given is of the same type and generate
1197 			   a warning if it isn't */
1198 			if (!g_variant_is_of_type(value, default_type)) {
1199 				g_warning("Setting menuitem property '%s' with value of type '%s' when expecting '%s'", property, g_variant_get_type_string(value), g_variant_type_peek_string(default_type));
1200 			}
1201 		}
1202 	}
1203 
1204 	/* Check the defaults database to see if we have a default
1205 	   for this property. */
1206 	default_value = dbusmenu_defaults_default_get(priv->defaults, type, property);
1207 	if (default_value != NULL && value != NULL) {
1208 		/* Now see if we're setting this to the same value as the
1209 		   default.  If we are then we just want to swallow this variant
1210 		   and make the function behave like we're clearing it. */
1211 		if (g_variant_equal(default_value, value)) {
1212 			g_variant_ref_sink(value);
1213 			g_variant_unref(value);
1214 			value = NULL;
1215 		}
1216 	}
1217 
1218 	gboolean replaced = FALSE;
1219 	gboolean remove = FALSE;
1220 	gchar * hash_key = NULL;
1221 	GVariant * hash_variant = NULL;
1222 	gboolean inhash = g_hash_table_lookup_extended(priv->properties, property, (gpointer *)&hash_key, (gpointer *)&hash_variant);
1223 
1224 	if (inhash && hash_variant == NULL) {
1225 		g_warning("The property '%s' is in the hash with a NULL variant", property);
1226 		inhash = FALSE;
1227 	}
1228 
1229 	if (value != NULL) {
1230 		/* NOTE: We're only marking this as replaced if this is true
1231 		   but we're actually replacing it no matter.  This is so that
1232 		   the variant passed in sticks around which the caller may
1233 		   expect.  They shouldn't, but it's low cost to remove bugs. */
1234 		if (!inhash || (hash_variant != NULL && !g_variant_equal(hash_variant, value))) {
1235 			replaced = TRUE;
1236 		}
1237 
1238 		gchar * lprop = g_strdup(property);
1239 		g_variant_ref_sink(value);
1240 
1241 		/* Really important that this is _insert as that means the value
1242 		   that we just created in the _strdup is free'd and not the one
1243 		   currently in the hashtable.  That could be the same as the one
1244 		   being passed in and then the signal emit would be done with a
1245 		   bad value */
1246 		g_hash_table_insert(priv->properties, lprop, value);
1247 	} else {
1248 		if (inhash) {
1249 		/* So the question you should be asking if you're paying attention
1250 		   is "Why not just do the remove here?"  It's a good question with
1251 		   an interesting answer.  Bascially it's the same reason as above,
1252 		   in a couple cases the passed in properties is the value in the hash
1253 		   table so we can avoid strdup'ing it by removing it (and thus free'ing
1254 		   it) after the signal emition */
1255 			remove = TRUE;
1256 			replaced = TRUE;
1257 			g_hash_table_steal(priv->properties, property);
1258 		}
1259 	}
1260 
1261 	/* NOTE: The actual value is invalid at this point
1262 	   becuse it has been unref'd when replaced in the hash
1263 	   table.  But the fact that there was a value is
1264 	   the imporant part. */
1265 	if (replaced) {
1266 		GVariant * signalval = value;
1267 
1268 		if (signalval == NULL) {
1269 			/* Might also be NULL, but if it is we're definitely
1270 			   clearing this thing. */
1271 			signalval = default_value;
1272 		}
1273 
1274 		g_signal_emit(G_OBJECT(mi), signals[PROPERTY_CHANGED], 0, property, signalval, TRUE);
1275 	}
1276 
1277 	if (remove) {
1278 		g_free(hash_key);
1279 		g_variant_unref(hash_variant);
1280 	}
1281 
1282 	return TRUE;
1283 }
1284 
1285 /**
1286  * dbusmenu_menuitem_property_get:
1287  * @mi: The #DbusmenuMenuitem to look for the property on.
1288  * @property: The property to grab.
1289  *
1290  * Look up a property on @mi and return the value of it if
1291  * it exits.  #NULL will be returned if the property doesn't
1292  * exist.
1293  *
1294  * Return value: (transfer none): A string with the value of the property
1295  * 	that shouldn't be free'd.  Or #NULL if the property
1296  * 	is not set or is not a string.
1297  */
1298 const gchar *
dbusmenu_menuitem_property_get(const DbusmenuMenuitem * mi,const gchar * property)1299 dbusmenu_menuitem_property_get (const DbusmenuMenuitem * mi, const gchar * property)
1300 {
1301 	GVariant * variant = dbusmenu_menuitem_property_get_variant(mi, property);
1302 	if (variant == NULL) return NULL;
1303 	if (!g_variant_type_equal(g_variant_get_type(variant), G_VARIANT_TYPE_STRING)) return NULL;
1304 	return g_variant_get_string(variant, NULL);
1305 }
1306 
1307 /**
1308  * dbusmenu_menuitem_property_get_variant:
1309  * @mi: The #DbusmenuMenuitem to look for the property on.
1310  * @property: The property to grab.
1311  *
1312  * Look up a property on @mi and return the value of it if
1313  * it exits.  #NULL will be returned if the property doesn't
1314  * exist.
1315  *
1316  * Return value: (transfer none): A GVariant for the property.
1317  */
1318 GVariant *
dbusmenu_menuitem_property_get_variant(const DbusmenuMenuitem * mi,const gchar * property)1319 dbusmenu_menuitem_property_get_variant (const DbusmenuMenuitem * mi, const gchar * property)
1320 {
1321 	g_return_val_if_fail(DBUSMENU_IS_MENUITEM(mi), NULL);
1322 	g_return_val_if_fail(property != NULL, NULL);
1323 
1324 	DbusmenuMenuitemPrivate * priv = DBUSMENU_MENUITEM_GET_PRIVATE(mi);
1325 
1326 	GVariant * currentval = (GVariant *)g_hash_table_lookup(priv->properties, property);
1327 
1328 	if (currentval == NULL) {
1329 		currentval = dbusmenu_defaults_default_get(priv->defaults, menuitem_get_type(mi), property);
1330 	}
1331 
1332 	return currentval;
1333 }
1334 
1335 /**
1336  * dbusmenu_menuitem_property_get_bool:
1337  * @mi: The #DbusmenuMenuitem to look for the property on.
1338  * @property: The property to grab.
1339  *
1340  * Look up a property on @mi and return the value of it if
1341  * it exits.  Returns #FALSE if the property doesn't exist.
1342  *
1343  * Return value: The value of the property or #FALSE.
1344  */
1345 gboolean
dbusmenu_menuitem_property_get_bool(const DbusmenuMenuitem * mi,const gchar * property)1346 dbusmenu_menuitem_property_get_bool (const DbusmenuMenuitem * mi, const gchar * property)
1347 {
1348 	GVariant * variant = dbusmenu_menuitem_property_get_variant(mi, property);
1349 	if (variant == NULL) return FALSE;
1350 
1351 	if (g_variant_type_equal(g_variant_get_type(variant), G_VARIANT_TYPE_BOOLEAN)) {
1352 		return g_variant_get_boolean(variant);
1353 	}
1354 
1355 	if (g_variant_type_equal(g_variant_get_type(variant), G_VARIANT_TYPE_STRING)) {
1356 		const gchar * string = g_variant_get_string(variant, NULL);
1357 
1358 		if (!g_strcmp0(string, "TRUE") || !g_strcmp0(string, "true") || !g_strcmp0(string, "True")) {
1359 			return TRUE;
1360 		} else {
1361 			return FALSE;
1362 		}
1363 	}
1364 
1365 	g_warning("Property '%s' has been requested as an boolean but is not one.", property);
1366 	return FALSE;
1367 }
1368 
1369 /**
1370  * dbusmenu_menuitem_property_get_int:
1371  * @mi: The #DbusmenuMenuitem to look for the property on.
1372  * @property: The property to grab.
1373  *
1374  * Look up a property on @mi and return the value of it if
1375  * it exits.  Returns zero if the property doesn't exist.
1376  *
1377  * Return value: The value of the property or zero.
1378  */
1379 gint
dbusmenu_menuitem_property_get_int(const DbusmenuMenuitem * mi,const gchar * property)1380 dbusmenu_menuitem_property_get_int (const DbusmenuMenuitem * mi, const gchar * property)
1381 {
1382 	GVariant * variant = dbusmenu_menuitem_property_get_variant(mi, property);
1383 	if (variant == NULL) return 0;
1384 
1385 	if (g_variant_type_equal(g_variant_get_type(variant), G_VARIANT_TYPE_INT32)) {
1386 		return g_variant_get_int32(variant);
1387 	}
1388 
1389 	if (g_variant_type_equal(g_variant_get_type(variant), G_VARIANT_TYPE_STRING)) {
1390 		const gchar * string = g_variant_get_string(variant, NULL);
1391 		return atoi(string);
1392 	}
1393 
1394 	g_warning("Property '%s' has been requested as an int but is not one.", property);
1395 	return 0;
1396 }
1397 
1398 /**
1399  * dbusmenu_menuitem_property_get_byte_array:
1400  * @mi: The #DbusmenuMenuitem to look for the property on.
1401  * @property: The property to grab.
1402  * @nelements: A pointer to the location to store the number of items (out)
1403  *
1404  * Look up a property on @mi and return the value of it if
1405  * it exits.  #NULL will be returned if the property doesn't
1406  * exist.
1407  *
1408  * Return value: (array length=nelements)(element-type guint8)(transfer none): A byte array with the
1409  * 	value of the property that shouldn't be free'd.  Or #NULL if the property
1410  * 	is not set or is not a byte array.
1411  */
1412 const guchar *
dbusmenu_menuitem_property_get_byte_array(const DbusmenuMenuitem * mi,const gchar * property,gsize * nelements)1413 dbusmenu_menuitem_property_get_byte_array (const DbusmenuMenuitem * mi, const gchar * property, gsize * nelements)
1414 {
1415 	GVariant * variant = dbusmenu_menuitem_property_get_variant(mi, property);
1416 	if (variant == NULL) {
1417 		*nelements = 0;
1418 		return NULL;
1419 	}
1420 	if (!g_variant_type_equal(g_variant_get_type(variant), G_VARIANT_TYPE("ay"))) return NULL;
1421 	return g_variant_get_fixed_array(variant, nelements, sizeof(guchar));
1422 }
1423 
1424 /**
1425  * dbusmenu_menuitem_property_exist:
1426  * @mi: The #DbusmenuMenuitem to look for the property on.
1427  * @property: The property to look for.
1428  *
1429  * Checkes to see if a particular property exists on @mi and
1430  * returns #TRUE if so.
1431  *
1432  * Return value: A boolean checking to see if the property is available
1433  */
1434 gboolean
dbusmenu_menuitem_property_exist(const DbusmenuMenuitem * mi,const gchar * property)1435 dbusmenu_menuitem_property_exist (const DbusmenuMenuitem * mi, const gchar * property)
1436 {
1437 	g_return_val_if_fail(DBUSMENU_IS_MENUITEM(mi), FALSE);
1438 	g_return_val_if_fail(property != NULL, FALSE);
1439 
1440 	DbusmenuMenuitemPrivate * priv = DBUSMENU_MENUITEM_GET_PRIVATE(mi);
1441 
1442 	gpointer value = g_hash_table_lookup(priv->properties, property);
1443 
1444 	return value != NULL;
1445 }
1446 
1447 /**
1448  * dbusmenu_menuitem_property_remove:
1449  * @mi: The #DbusmenuMenuitem to remove the property on.
1450  * @property: The property to look for.
1451  *
1452  * Removes a property from the menuitem.
1453  */
1454 void
dbusmenu_menuitem_property_remove(DbusmenuMenuitem * mi,const gchar * property)1455 dbusmenu_menuitem_property_remove (DbusmenuMenuitem * mi, const gchar * property)
1456 {
1457 	g_return_if_fail(DBUSMENU_IS_MENUITEM(mi));
1458 	g_return_if_fail(property != NULL);
1459 
1460 	dbusmenu_menuitem_property_set_variant(mi, property, NULL);
1461 
1462 	return;
1463 }
1464 
1465 /**
1466  * dbusmenu_menuitem_properties_list:
1467  * @mi: #DbusmenuMenuitem to list the properties on
1468  *
1469  * This functiong gets a list of the names of all the properties
1470  * that are set on this menu item.  This data on the list is owned
1471  * by the menuitem but the list is not and should be freed using
1472  * g_list_free() when the calling function is done with it.
1473  *
1474  * Return value: (transfer container) (element-type utf8): A list of
1475  * strings or NULL if there are none.
1476 */
1477 GList *
dbusmenu_menuitem_properties_list(DbusmenuMenuitem * mi)1478 dbusmenu_menuitem_properties_list (DbusmenuMenuitem * mi)
1479 {
1480 	g_return_val_if_fail(DBUSMENU_IS_MENUITEM(mi), NULL);
1481 
1482 	DbusmenuMenuitemPrivate * priv = DBUSMENU_MENUITEM_GET_PRIVATE(mi);
1483 	return g_hash_table_get_keys(priv->properties);
1484 }
1485 
1486 /* Copy the keys and make references to the variants that are
1487    in the new table.  They'll be free'd and unref'd when the
1488    Hashtable gets destroyed. */
1489 static void
copy_helper(gpointer in_key,gpointer in_value,gpointer in_data)1490 copy_helper (gpointer in_key, gpointer in_value, gpointer in_data)
1491 {
1492 	GHashTable * table = (GHashTable *)in_data;
1493 	gchar * key = (gchar *)in_key;
1494 	GVariant * value = (GVariant *)in_value;
1495 	g_variant_ref_sink(value);
1496 	g_hash_table_insert(table, g_strdup(key), value);
1497 	return;
1498 }
1499 
1500 /**
1501  * dbusmenu_menuitem_properties_copy:
1502  * @mi: #DbusmenuMenuitem that we're interested in the properties of
1503  *
1504  * This function takes the properties of a #DbusmenuMenuitem
1505  * and puts them into a #GHashTable that is referenced by the
1506  * key of a string and has the value of a string.  The hash
1507  * table may not have any entries if there aren't any or there
1508  * is an error in processing.  It is the caller's responsibility
1509  * to destroy the created #GHashTable.
1510  *
1511  * Return value: (transfer full): A brand new #GHashTable that contains all of
1512  *    theroperties that are on this #DbusmenuMenuitem @mi.
1513 */
1514 GHashTable *
dbusmenu_menuitem_properties_copy(DbusmenuMenuitem * mi)1515 dbusmenu_menuitem_properties_copy (DbusmenuMenuitem * mi)
1516 {
1517 	GHashTable * ret = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, _g_variant_unref);
1518 
1519 	g_return_val_if_fail(DBUSMENU_IS_MENUITEM(mi), ret);
1520 
1521 	DbusmenuMenuitemPrivate * priv = DBUSMENU_MENUITEM_GET_PRIVATE(mi);
1522 	g_hash_table_foreach(priv->properties, copy_helper, ret);
1523 
1524 	return ret;
1525 }
1526 
1527 /* Looks at each value in the hashtable and tries to convert it
1528    into a variant and add it to our variant builder */
1529 static void
variant_helper(gpointer in_key,gpointer in_value,gpointer user_data)1530 variant_helper (gpointer in_key, gpointer in_value, gpointer user_data)
1531 {
1532 	GVariant * value = g_variant_new_dict_entry(g_variant_new_string((gchar *)in_key),
1533 	                                            g_variant_new_variant((GVariant *)in_value));
1534 	g_variant_builder_add_value((GVariantBuilder *)user_data, value);
1535 	return;
1536 }
1537 
1538 /**
1539  * dbusmenu_menuitem_properties_variant:
1540  * @mi: #DbusmenuMenuitem to get properties from
1541  *
1542  * Grabs the properties of the menuitem as a GVariant with the
1543  * type "a{sv}".
1544  *
1545  * Return Value: (transfer full): A GVariant of type "a{sv}" or NULL on error.
1546  */
1547 GVariant *
dbusmenu_menuitem_properties_variant(DbusmenuMenuitem * mi,const gchar ** properties)1548 dbusmenu_menuitem_properties_variant (DbusmenuMenuitem * mi, const gchar ** properties)
1549 {
1550 	g_return_val_if_fail(DBUSMENU_IS_MENUITEM(mi), NULL);
1551 
1552 	DbusmenuMenuitemPrivate * priv = DBUSMENU_MENUITEM_GET_PRIVATE(mi);
1553 
1554 	GVariant * final_variant = NULL;
1555 
1556 	if ((properties == NULL || properties[0] == NULL) && g_hash_table_size(priv->properties) > 0) {
1557 		GVariantBuilder builder;
1558 		g_variant_builder_init(&builder, G_VARIANT_TYPE_ARRAY);
1559 
1560 		g_hash_table_foreach(priv->properties, variant_helper, &builder);
1561 
1562 		final_variant = g_variant_builder_end(&builder);
1563 	}
1564 
1565 	if (properties != NULL) {
1566 		GVariantBuilder builder;
1567 		gboolean builder_init = FALSE;
1568 		int i = 0; const gchar * prop;
1569 
1570 		for (prop = properties[i]; prop != NULL; prop = properties[++i]) {
1571 			GVariant * propvalue = dbusmenu_menuitem_property_get_variant(mi, prop);
1572 
1573 			if (propvalue == NULL) {
1574 				continue;
1575 			}
1576 
1577 			if (!builder_init) {
1578 				builder_init = TRUE;
1579 				g_variant_builder_init(&builder, G_VARIANT_TYPE_ARRAY);
1580 			}
1581 
1582 			GVariant * dict = g_variant_new_dict_entry(g_variant_new_string((gchar *)prop),
1583 			                                           g_variant_new_variant((GVariant *)propvalue));
1584 			g_variant_builder_add_value(&builder, dict);
1585 		}
1586 
1587 		if (builder_init) {
1588 			final_variant = g_variant_builder_end(&builder);
1589 		}
1590 	}
1591 
1592 	return final_variant;
1593 }
1594 
1595 /**
1596  * dbusmenu_menuitem_set_root:
1597  * @mi: #DbusmenuMenuitem to set whether it's root
1598  * @root: Whether @mi is a root node or not
1599  *
1600  * This function sets the internal value of whether this is a
1601  * root node or not.
1602  */
1603 void
dbusmenu_menuitem_set_root(DbusmenuMenuitem * mi,gboolean root)1604 dbusmenu_menuitem_set_root (DbusmenuMenuitem * mi, gboolean root)
1605 {
1606 	g_return_if_fail(DBUSMENU_IS_MENUITEM(mi));
1607 	DbusmenuMenuitemPrivate * priv = DBUSMENU_MENUITEM_GET_PRIVATE(mi);
1608 	priv->root = root;
1609 	return;
1610 }
1611 
1612 /**
1613  * dbusmenu_menuitem_get_root:
1614  * @mi: #DbusmenuMenuitem to see whether it's root
1615  *
1616  * This function returns the internal value of whether this is a
1617  * root node or not.
1618  *
1619  * Return value: #TRUE if this is a root node
1620  */
1621 gboolean
dbusmenu_menuitem_get_root(DbusmenuMenuitem * mi)1622 dbusmenu_menuitem_get_root (DbusmenuMenuitem * mi)
1623 {
1624 	g_return_val_if_fail(DBUSMENU_IS_MENUITEM(mi), FALSE);
1625 	DbusmenuMenuitemPrivate * priv = DBUSMENU_MENUITEM_GET_PRIVATE(mi);
1626 	return priv->root;
1627 }
1628 
1629 
1630 /**
1631  * dbusmenu_menuitem_buildvariant:
1632  * @mi: #DbusmenuMenuitem to represent in a variant
1633  * @properties: (element-type utf8): A list of string that will be put into
1634  *      a variant
1635  *
1636  * This function will put at least one entry if this menu item has no children.
1637  * If it has children it will put two for this entry, one representing the
1638  * start tag and one that is a closing tag.  It will allow its
1639  * children to place their own tags in the array in between those two.
1640  *
1641  * Return value: (transfer full): Variant representing @properties
1642 */
1643 GVariant *
dbusmenu_menuitem_build_variant(DbusmenuMenuitem * mi,const gchar ** properties,gint recurse)1644 dbusmenu_menuitem_build_variant (DbusmenuMenuitem * mi, const gchar ** properties, gint recurse)
1645 {
1646 	g_return_val_if_fail(DBUSMENU_IS_MENUITEM(mi), NULL);
1647 	DbusmenuMenuitemPrivate * priv = DBUSMENU_MENUITEM_GET_PRIVATE(mi);
1648 	priv->exposed = TRUE;
1649 
1650 	gint id = 0;
1651 	if (!dbusmenu_menuitem_get_root(mi)) {
1652 		id = dbusmenu_menuitem_get_id(mi);
1653 	}
1654 
1655 	/* This is the tuple that'll build up being a representation of
1656 	   this entry */
1657 	GVariantBuilder tupleb;
1658 	g_variant_builder_init(&tupleb, G_VARIANT_TYPE_TUPLE);
1659 
1660 	/* Add our ID */
1661 	g_variant_builder_add_value(&tupleb, g_variant_new_int32(id));
1662 
1663 	/* Figure out the properties */
1664 	GVariant * props = dbusmenu_menuitem_properties_variant(mi, properties);
1665 	if (props != NULL) {
1666 		g_variant_builder_add_value(&tupleb, props);
1667 	} else {
1668 		GVariant *empty_props = g_variant_parse(G_VARIANT_TYPE("a{sv}"), "[ ]", NULL, NULL, NULL);
1669 		g_variant_builder_add_value(&tupleb, empty_props);
1670 		g_variant_unref(empty_props);
1671 	}
1672 
1673 	/* Pillage the children */
1674 	GList * children = dbusmenu_menuitem_get_children(mi);
1675 	if (children == NULL || recurse == 0) {
1676 		g_variant_builder_add_value(&tupleb, g_variant_new_array(G_VARIANT_TYPE_VARIANT, NULL, 0));
1677 	} else {
1678 		GVariantBuilder childrenbuilder;
1679 		g_variant_builder_init(&childrenbuilder, G_VARIANT_TYPE_ARRAY);
1680 
1681 		for ( ; children != NULL; children = children->next) {
1682 			GVariant * child = dbusmenu_menuitem_build_variant(DBUSMENU_MENUITEM(children->data), properties, recurse - 1);
1683 
1684 			g_variant_builder_add_value(&childrenbuilder, g_variant_new_variant(child));
1685 		}
1686 
1687 		g_variant_builder_add_value(&tupleb, g_variant_builder_end(&childrenbuilder));
1688 	}
1689 
1690 	return g_variant_builder_end(&tupleb);
1691 }
1692 
1693 typedef struct {
1694 	void (*func) (DbusmenuMenuitem * mi, gpointer data);
1695 	gpointer data;
1696 } foreach_struct_t;
1697 
1698 static void
foreach_helper(gpointer data,gpointer user_data)1699 foreach_helper (gpointer data, gpointer user_data)
1700 {
1701 	dbusmenu_menuitem_foreach(DBUSMENU_MENUITEM(data), ((foreach_struct_t *)user_data)->func, ((foreach_struct_t *)user_data)->data);
1702 	return;
1703 }
1704 
1705 /**
1706  * dbusmenu_menuitem_foreach:
1707  * @mi: The #DbusmenItem to start from
1708  * @func: Function to call on every node in the tree
1709  * @data: (closure): User data to pass to the function
1710  *
1711  * This calls the function @func on this menu item and all
1712  * of the children of this item.  And their children.  And
1713  * their children.  And... you get the point.  It will get
1714  * called on the whole tree.
1715 */
1716 void
dbusmenu_menuitem_foreach(DbusmenuMenuitem * mi,void (* func)(DbusmenuMenuitem * mi,gpointer data),gpointer data)1717 dbusmenu_menuitem_foreach (DbusmenuMenuitem * mi, void (*func) (DbusmenuMenuitem * mi, gpointer data), gpointer data)
1718 {
1719 	g_return_if_fail(DBUSMENU_IS_MENUITEM(mi));
1720 	g_return_if_fail(func != NULL);
1721 
1722 	func(mi, data);
1723 	GList * children = dbusmenu_menuitem_get_children(mi);
1724 	foreach_struct_t foreach_data = {.func=func, .data=data};
1725 	g_list_foreach(children, foreach_helper, &foreach_data);
1726 	return;
1727 }
1728 
1729 /**
1730  * dbusmenu_menuitem_handle_event:
1731  * @mi: The #DbusmenuMenuitem to send the signal on.
1732  * @name: The name of the signal
1733  * @variant: A value that could be set for the event
1734  * @timestamp: The timestamp of when the event happened
1735  *
1736  * This function is called to create an event.  It is likely
1737  * to be overrided by subclasses.  The default menu item
1738  * will respond to the activate signal and do:
1739  *
1740  * Emits the #DbusmenuMenuitem::item-activate signal on this
1741  * menu item.  Called by server objects when they get the
1742  * appropriate DBus signals from the client.
1743  *
1744  * If you subclass this function you should really think
1745  * about calling the parent function unless you have a good
1746  * reason not to.
1747 */
1748 void
dbusmenu_menuitem_handle_event(DbusmenuMenuitem * mi,const gchar * name,GVariant * variant,guint timestamp)1749 dbusmenu_menuitem_handle_event (DbusmenuMenuitem * mi, const gchar * name, GVariant * variant, guint timestamp)
1750 {
1751 	g_return_if_fail(DBUSMENU_IS_MENUITEM(mi));
1752 	#ifdef MASSIVEDEBUGGING
1753 	g_debug("Menuitem %d (%s) is getting event '%s'", ID(mi), LABEL(mi), name);
1754 	#endif
1755 	DbusmenuMenuitemClass * class = DBUSMENU_MENUITEM_GET_CLASS(mi);
1756 
1757 	gboolean handled = FALSE;
1758 	if (variant == NULL) {
1759 		variant = g_variant_new_int32(0);
1760 	}
1761 
1762 	g_variant_ref_sink(variant);
1763 
1764 	g_signal_emit(G_OBJECT(mi), signals[EVENT], g_quark_from_string(name), name, variant, timestamp, &handled);
1765 
1766 	if (!handled && class->handle_event != NULL) {
1767 		class->handle_event(mi, name, variant, timestamp);
1768 	}
1769 
1770 	g_variant_unref(variant);
1771 }
1772 
1773 /**
1774  * dbusmenu_menuitem_send_about_to_show:
1775  * @mi: The #DbusmenuMenuitem to send the signal on.
1776  * @cb: Callback to call when the call has returned.
1777  * @cb_data: (closure): Data to pass to the callback.
1778  *
1779  * This function is used to send the even that the submenu
1780  * of this item is about to be shown.  Callers to this event
1781  * should delay showing the menu until their callback is
1782  * called if possible.
1783  */
1784 void
dbusmenu_menuitem_send_about_to_show(DbusmenuMenuitem * mi,void (* cb)(DbusmenuMenuitem * mi,gpointer user_data),gpointer cb_data)1785 dbusmenu_menuitem_send_about_to_show (DbusmenuMenuitem * mi, void (*cb) (DbusmenuMenuitem * mi, gpointer user_data), gpointer cb_data)
1786 {
1787 	g_return_if_fail(DBUSMENU_IS_MENUITEM(mi));
1788 	#ifdef MASSIVEDEBUGGING
1789 	g_debug("Submenu for menuitem %d (%s) is about to be shown", ID(mi), LABEL(mi));
1790 	#endif
1791 	DbusmenuMenuitemClass * class = DBUSMENU_MENUITEM_GET_CLASS(mi);
1792 
1793 	if (class != NULL && class->send_about_to_show != NULL) {
1794 		return class->send_about_to_show(mi, cb, cb_data);
1795 	} else if (cb != NULL) {
1796 		cb(mi, cb_data);
1797 	}
1798 
1799 	return;
1800 }
1801 
1802 /**
1803  * dbusmenu_menuitem_show_to_user:
1804  * @mi: #DbusmenuMenuitem to show
1805  * @timestamp: The time that the user requested it to be shown
1806  *
1807  * Signals that this menu item should be shown to the user.  If this is
1808  * server side the server will then take it and send it over the
1809  * bus.
1810  */
1811 void
dbusmenu_menuitem_show_to_user(DbusmenuMenuitem * mi,guint timestamp)1812 dbusmenu_menuitem_show_to_user (DbusmenuMenuitem * mi, guint timestamp)
1813 {
1814 	g_return_if_fail(DBUSMENU_IS_MENUITEM(mi));
1815 
1816 	g_signal_emit(G_OBJECT(mi), signals[SHOW_TO_USER], 0, timestamp, TRUE);
1817 
1818 	return;
1819 }
1820 
1821 /* Checks to see if the value of this property is unique or just the
1822    default value. */
1823 gboolean
dbusmenu_menuitem_property_is_default(DbusmenuMenuitem * mi,const gchar * property)1824 dbusmenu_menuitem_property_is_default (DbusmenuMenuitem * mi, const gchar * property)
1825 {
1826 	g_return_val_if_fail(DBUSMENU_IS_MENUITEM(mi), FALSE);
1827 	DbusmenuMenuitemPrivate * priv = DBUSMENU_MENUITEM_GET_PRIVATE(mi);
1828 
1829 	GVariant * currentval = (GVariant *)g_hash_table_lookup(priv->properties, property);
1830 	if (currentval != NULL) {
1831 		/* If we're storing it locally, then it shouldn't be a default */
1832 		return FALSE;
1833 	}
1834 
1835 	/* If we haven't stored it locally, then it's the default */
1836 	return TRUE;
1837 }
1838 
1839 /* Check to see if this menu item has been sent into the bus yet or
1840    not.  If no one cares we can give less info */
1841 gboolean
dbusmenu_menuitem_exposed(DbusmenuMenuitem * mi)1842 dbusmenu_menuitem_exposed (DbusmenuMenuitem * mi)
1843 {
1844 	g_return_val_if_fail(DBUSMENU_IS_MENUITEM(mi), FALSE);
1845 	DbusmenuMenuitemPrivate * priv = DBUSMENU_MENUITEM_GET_PRIVATE(mi);
1846 	return priv->exposed;
1847 }
1848