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