1 /*
2  * popup-menu: A pop-up menu with menu items performing an action when an menu
3  *             item was clicked
4  *
5  * Copyright 2012-2020 Stephan Haller <nomad@froevel.de>
6  *
7  * This program is free software; you can redistribute it and/or modify
8  * it under the terms of the GNU General Public License as published by
9  * the Free Software Foundation; either version 2 of the License, or
10  * (at your option) any later version.
11  *
12  * This program is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15  * GNU General Public License for more details.
16  *
17  * You should have received a copy of the GNU General Public License
18  * along with this program; if not, write to the Free Software
19  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
20  * MA 02110-1301, USA.
21  *
22  *
23  */
24 
25 /**
26  * SECTION:popup-menu
27  * @short_description: A pop-up menu showing items and perfoming an action
28                        when an item was clicked
29  * @include: xfdashboard/popup-menu.h
30  *
31  * A #XfdashboardPopupMenu implements a drop down menu consisting of a list of
32  * #ClutterActor objects as menu items which can be navigated and activated by
33  * the user to perform the associated action of the selected menu item.
34  *
35  * The following example shows how create and activate a #XfdashboardPopupMenu
36  * when an actor was clicked.
37  * application when clicked:
38  *
39  * |[<!-- language="C" -->
40  * ]|
41  */
42 
43 #ifdef HAVE_CONFIG_H
44 #include "config.h"
45 #endif
46 
47 #include <libxfdashboard/popup-menu.h>
48 
49 #include <glib/gi18n-lib.h>
50 #include <gtk/gtk.h>
51 #include <gdk/gdk.h>
52 #include <math.h>
53 
54 #include <libxfdashboard/box-layout.h>
55 #include <libxfdashboard/focusable.h>
56 #include <libxfdashboard/focus-manager.h>
57 #include <libxfdashboard/stylable.h>
58 #include <libxfdashboard/window-tracker.h>
59 #include <libxfdashboard/application.h>
60 #include <libxfdashboard/click-action.h>
61 #include <libxfdashboard/bindings-pool.h>
62 #include <libxfdashboard/button.h>
63 #include <libxfdashboard/enums.h>
64 #include <libxfdashboard/utils.h>
65 #include <libxfdashboard/compat.h>
66 #include <libxfdashboard/debug.h>
67 
68 
69 /* Define this class in GObject system */
70 static void _xfdashboard_popup_menu_focusable_iface_init(XfdashboardFocusableInterface *iface);
71 
72 struct _XfdashboardPopupMenuPrivate
73 {
74 	/* Properties related */
75 	gboolean						destroyOnCancel;
76 
77 	ClutterActor					*source;
78 
79 	gboolean						showTitle;
80 	gboolean						showTitleIcon;
81 
82 	/* Instance related */
83 	gboolean						isActive;
84 
85 	ClutterActor					*title;
86 	ClutterActor					*itemsContainer;
87 
88 	XfdashboardWindowTracker		*windowTracker;
89 
90 	XfdashboardFocusManager			*focusManager;
91 	gpointer						oldFocusable;
92 	gpointer						selectedItem;
93 
94 	XfdashboardStage				*stage;
95 	guint							capturedEventSignalID;
96 
97 	guint							sourceDestroySignalID;
98 
99 	guint							suspendSignalID;
100 };
101 
102 G_DEFINE_TYPE_WITH_CODE(XfdashboardPopupMenu,
103 						xfdashboard_popup_menu,
104 						XFDASHBOARD_TYPE_BACKGROUND,
105 						G_ADD_PRIVATE(XfdashboardPopupMenu)
106 						G_IMPLEMENT_INTERFACE(XFDASHBOARD_TYPE_FOCUSABLE, _xfdashboard_popup_menu_focusable_iface_init));
107 
108 /* Properties */
109 enum
110 {
111 	PROP_0,
112 
113 	PROP_DESTROY_ON_CANCEL,
114 
115 	PROP_SOURCE,
116 
117 	PROP_SHOW_TITLE,
118 	PROP_TITLE,
119 
120 	PROP_SHOW_TITLE_ICON,
121 	PROP_TITLE_ICON_NAME,
122 	PROP_TITLE_GICON,
123 
124 	PROP_LAST
125 };
126 
127 static GParamSpec* XfdashboardPopupMenuProperties[PROP_LAST]={ 0, };
128 
129 /* Signals */
130 enum
131 {
132 	SIGNAL_ACTIVATED,
133 	SIGNAL_CANCELLED,
134 
135 	SIGNAL_ITEM_ACTIVATED,
136 
137 	SIGNAL_ITEM_ADDED,
138 	SIGNAL_ITEM_REMOVED,
139 
140 	SIGNAL_LAST
141 };
142 
143 static guint XfdashboardPopupMenuSignals[SIGNAL_LAST]={ 0, };
144 
145 
146 /* IMPLEMENTATION: Private variables and methods */
147 
148 /* Suspension state of application changed */
_xfdashboard_popup_menu_on_application_suspended_changed(XfdashboardPopupMenu * self,GParamSpec * inSpec,gpointer inUserData)149 static void _xfdashboard_popup_menu_on_application_suspended_changed(XfdashboardPopupMenu *self,
150 																		GParamSpec *inSpec,
151 																		gpointer inUserData)
152 {
153 	XfdashboardPopupMenuPrivate		*priv;
154 	XfdashboardApplication			*application;
155 	gboolean						isSuspended;
156 
157 	g_return_if_fail(XFDASHBOARD_IS_POPUP_MENU(self));
158 	g_return_if_fail(XFDASHBOARD_IS_APPLICATION(inUserData));
159 
160 	priv=self->priv;
161 	application=XFDASHBOARD_APPLICATION(inUserData);
162 
163 	/* Get application suspend state */
164 	isSuspended=xfdashboard_application_is_suspended(application);
165 
166 	/* If application is suspended then cancel pop-up menu */
167 	if(isSuspended)
168 	{
169 		XFDASHBOARD_DEBUG(self, ACTOR,
170 							"Cancel active pop-up menu '%s' for source %s@%p because application was suspended",
171 							xfdashboard_popup_menu_get_title(self),
172 							priv->source ? G_OBJECT_TYPE_NAME(priv->source) : "<nil>",
173 							priv->source);
174 
175 		xfdashboard_popup_menu_cancel(self);
176 	}
177 }
178 
179 /* An event occured after a popup menu was activated so check if popup menu should
180  * be cancelled because a button was pressed and release outside the popup menu.
181  */
_xfdashboard_popup_menu_on_captured_event(XfdashboardPopupMenu * self,ClutterEvent * inEvent,gpointer inUserData)182 static gboolean _xfdashboard_popup_menu_on_captured_event(XfdashboardPopupMenu *self,
183 															ClutterEvent *inEvent,
184 															gpointer inUserData)
185 {
186 	XfdashboardPopupMenuPrivate		*priv;
187 
188 	g_return_val_if_fail(XFDASHBOARD_IS_POPUP_MENU(self), CLUTTER_EVENT_PROPAGATE);
189 	g_return_val_if_fail(XFDASHBOARD_IS_STAGE(inUserData), CLUTTER_EVENT_PROPAGATE);
190 
191 	priv=self->priv;
192 
193 	/* Check if popup menu should be cancelled depending on event */
194 	switch(clutter_event_type(inEvent))
195 	{
196 		case CLUTTER_BUTTON_RELEASE:
197 			/* If button was released outside popup menu cancel this popup menu */
198 			{
199 				gfloat							x, y, w, h;
200 
201 				clutter_actor_get_transformed_position(CLUTTER_ACTOR(self), &x, &y);
202 				clutter_actor_get_size(CLUTTER_ACTOR(self), &w, &h);
203 				if(inEvent->button.x<x ||
204 					inEvent->button.x>=(x+w) ||
205 					inEvent->button.y<y ||
206 					inEvent->button.y>=(y+h))
207 				{
208 					/* Cancel popup menu */
209 					xfdashboard_popup_menu_cancel(self);
210 
211 					/* Do not let this event be handled */
212 					return(CLUTTER_EVENT_STOP);
213 				}
214 			}
215 			break;
216 
217 		case CLUTTER_KEY_PRESS:
218 		case CLUTTER_KEY_RELEASE:
219 			/* If key press or key release is not a selection action for a focusable
220 			 * actor then cancel this popup menu.
221 			 */
222 			{
223 				GSList							*targetFocusables;
224 				const gchar						*action;
225 				gboolean						cancelPopupMenu;
226 
227 				/* Lookup action for event and emit action if a binding was found
228 				 * for this event.
229 				 */
230 				targetFocusables=NULL;
231 				action=NULL;
232 				cancelPopupMenu=FALSE;
233 
234 				if(xfdashboard_focus_manager_get_event_targets_and_action(priv->focusManager, inEvent, XFDASHBOARD_FOCUSABLE(self), &targetFocusables, &action))
235 				{
236 					if(!targetFocusables ||
237 						!targetFocusables->data ||
238 						!XFDASHBOARD_IS_POPUP_MENU(targetFocusables->data))
239 					{
240 						cancelPopupMenu=TRUE;
241 					}
242 
243 					/* Release allocated resources */
244 					g_slist_free_full(targetFocusables, g_object_unref);
245 				}
246 
247 				/* 'ESC' is a special key as it cannot be determined by focus
248 				 * manager but it has to be intercepted as this key release
249 				 * should only cancel popup-menu but not quit application.
250 				 */
251 				if(!cancelPopupMenu &&
252 					clutter_event_type(inEvent)==CLUTTER_KEY_RELEASE &&
253 					inEvent->key.keyval==CLUTTER_KEY_Escape)
254 				{
255 					cancelPopupMenu=TRUE;
256 				}
257 
258 				/* Cancel popup-menu if requested */
259 				if(cancelPopupMenu)
260 				{
261 					/* Cancel popup menu */
262 					xfdashboard_popup_menu_cancel(self);
263 
264 					/* Do not let this event be handled */
265 					return(CLUTTER_EVENT_STOP);
266 				}
267 			}
268 			break;
269 
270 		default:
271 			/* Let all other event pass through */
272 			break;
273 	}
274 
275 	/* If we get here then this event passed our filter and can be handled normally */
276 	return(CLUTTER_EVENT_PROPAGATE);
277 }
278 
279 /*  Check if menu item is really part of this pop-up menu */
_xfdashboard_popup_menu_contains_menu_item(XfdashboardPopupMenu * self,XfdashboardPopupMenuItem * inMenuItem)280 static gboolean _xfdashboard_popup_menu_contains_menu_item(XfdashboardPopupMenu *self,
281 															XfdashboardPopupMenuItem *inMenuItem)
282 {
283 	ClutterActor			*parent;
284 
285 	g_return_val_if_fail(XFDASHBOARD_IS_POPUP_MENU(self), FALSE);
286 	g_return_val_if_fail(XFDASHBOARD_IS_POPUP_MENU_ITEM(inMenuItem), FALSE);
287 
288 	/* Iterate through parents and for each XfdashboardPopupMenu found, check
289 	 * if it is this pop-up menu and return TRUE if it is.
290 	 */
291 	parent=clutter_actor_get_parent(CLUTTER_ACTOR(inMenuItem));
292 	while(parent)
293 	{
294 		/* Check if current iterated parent is a XfdashboardPopupMenu and if it
295 		 * is this pop-up menu.
296 		 */
297 		if(XFDASHBOARD_IS_POPUP_MENU(parent) &&
298 			XFDASHBOARD_POPUP_MENU(parent)==self)
299 		{
300 			/* This one is this pop-up menu, so return TRUE here */
301 			return(TRUE);
302 		}
303 
304 		/* Continue with next parent */
305 		parent=clutter_actor_get_parent(parent);
306 	}
307 
308 	/* If we get here the "menu item" actor is a menu item of this pop-up menu */
309 	return(FALSE);
310 }
311 
312 /* Menu item was activated */
_xfdashboard_popup_menu_on_menu_item_activated(XfdashboardPopupMenu * self,gpointer inUserData)313 static void _xfdashboard_popup_menu_on_menu_item_activated(XfdashboardPopupMenu *self,
314 															gpointer inUserData)
315 {
316 	XfdashboardPopupMenuItem		*menuItem;
317 
318 	g_return_if_fail(XFDASHBOARD_IS_POPUP_MENU(self));
319 	g_return_if_fail(XFDASHBOARD_IS_POPUP_MENU_ITEM(inUserData));
320 
321 	menuItem=XFDASHBOARD_POPUP_MENU_ITEM(inUserData);
322 
323 	/* Emit "item-activated" signal */
324 	g_signal_emit(self, XfdashboardPopupMenuSignals[SIGNAL_ITEM_ACTIVATED], 0, menuItem);
325 
326 	/* Cancel pop-up menu as menu item was activated and its callback function
327 	 * was called by its meta object.
328 	 */
329 	xfdashboard_popup_menu_cancel(self);
330 }
331 
332 /* Update visiblity of title actor depending on if title and/or icon of title
333  * should be shown or not.
334  */
_xfdashboard_popup_menu_update_title_actors_visibility(XfdashboardPopupMenu * self)335 static void _xfdashboard_popup_menu_update_title_actors_visibility(XfdashboardPopupMenu *self)
336 {
337 	XfdashboardPopupMenuPrivate		*priv;
338 	XfdashboardLabelStyle			oldStyle;
339 	XfdashboardLabelStyle			newStyle;
340 	gboolean						oldVisible;
341 	gboolean						newVisible;
342 
343 	g_return_if_fail(XFDASHBOARD_IS_POPUP_MENU(self));
344 
345 	priv=self->priv;
346 
347 	/* Get current visibility state */
348 	oldVisible=clutter_actor_is_visible(priv->title);
349 	oldStyle=xfdashboard_label_get_style(XFDASHBOARD_LABEL(priv->title));
350 
351 	/* Determine new visibility state depending on if title and/or icon of title
352 	 * should be shown or not.
353 	 */
354 	newStyle=0;
355 	newVisible=TRUE;
356 	if(priv->showTitle && priv->showTitleIcon) newStyle=XFDASHBOARD_LABEL_STYLE_BOTH;
357 		else if(priv->showTitle) newStyle=XFDASHBOARD_LABEL_STYLE_TEXT;
358 		else if(priv->showTitleIcon) newStyle=XFDASHBOARD_LABEL_STYLE_ICON;
359 		else newVisible=FALSE;
360 
361 	/* Set new visibility style if changed and re-layout title actor */
362 	if(newStyle!=oldStyle)
363 	{
364 		xfdashboard_label_set_style(XFDASHBOARD_LABEL(priv->title), newStyle);
365 		clutter_actor_queue_relayout(priv->title);
366 	}
367 
368 	/* Show or hide actor */
369 	if(newVisible!=oldVisible)
370 	{
371 		if(newVisible) clutter_actor_show(priv->title);
372 			else clutter_actor_hide(priv->title);
373 	}
374 }
375 
376 /* The source actor was destroyed so cancel this pop-up menu if active and
377  * destroy it if automatic destruction was turned on.
378  */
_xfdashboard_popup_menu_on_source_destroy(XfdashboardPopupMenu * self,gpointer inUserData)379 static void _xfdashboard_popup_menu_on_source_destroy(XfdashboardPopupMenu *self,
380 														gpointer inUserData)
381 {
382 	XfdashboardPopupMenuPrivate		*priv;
383 
384 	g_return_if_fail(XFDASHBOARD_IS_POPUP_MENU(self));
385 	g_return_if_fail(CLUTTER_IS_ACTOR(inUserData));
386 
387 	priv=self->priv;
388 
389 	/* Unset and clean-up source */
390 	if(priv->source)
391 	{
392 		gchar						*cssClass;
393 
394 		/* Disconnect signal handler */
395 		if(priv->sourceDestroySignalID)
396 		{
397 			g_signal_handler_disconnect(priv->source, priv->sourceDestroySignalID);
398 			priv->sourceDestroySignalID=0;
399 		}
400 
401 		/* Remove style */
402 		cssClass=g_strdup_printf("popup-menu-source-%s", G_OBJECT_TYPE_NAME(priv->source));
403 		xfdashboard_stylable_remove_class(XFDASHBOARD_STYLABLE(self), cssClass);
404 		g_free(cssClass);
405 
406 		/* Release source */
407 		g_object_remove_weak_pointer(G_OBJECT(priv->source), (gpointer*)&priv->source);
408 		priv->source=NULL;
409 	}
410 
411 	/* Enforce that pop-up menu is cancelled either by calling the cancel function
412 	 * if it is active or by checking and destructing it if automatic destruction
413 	 * flag is set.
414 	 */
415 	if(priv->isActive)
416 	{
417 		/* Pop-up menu is active so cancel it. The cancel function will also destroy
418 		 * it if destroy-on-cancel was enabled.
419 		 */
420 		xfdashboard_popup_menu_cancel(self);
421 	}
422 		else
423 		{
424 			/* Destroy this pop-up menu actor when destroy-on-cancel was enabled */
425 			if(priv->destroyOnCancel)
426 			{
427 				xfdashboard_actor_destroy(CLUTTER_ACTOR(self));
428 			}
429 		}
430 }
431 
432 
433 /* IMPLEMENTATION: ClutterActor */
434 
435 /* Allocate position and size of actor and its children */
_xfdashboard_popup_menu_allocate(ClutterActor * inActor,const ClutterActorBox * inBox,ClutterAllocationFlags inFlags)436 static void _xfdashboard_popup_menu_allocate(ClutterActor *inActor,
437 												const ClutterActorBox *inBox,
438 												ClutterAllocationFlags inFlags)
439 {
440 	ClutterAllocationFlags		flags;
441 
442 	/* Chain up to store the allocation of the actor */
443 	flags=inFlags | CLUTTER_DELEGATE_LAYOUT;
444 	CLUTTER_ACTOR_CLASS(xfdashboard_popup_menu_parent_class)->allocate(inActor, inBox, flags);
445 }
446 
447 
448 /* IMPLEMENTATION: Interface XfdashboardFocusable */
449 
450 /* Determine if actor can get the focus */
_xfdashboard_popup_menu_focusable_can_focus(XfdashboardFocusable * inFocusable)451 static gboolean _xfdashboard_popup_menu_focusable_can_focus(XfdashboardFocusable *inFocusable)
452 {
453 	XfdashboardPopupMenu			*self;
454 	XfdashboardPopupMenuPrivate		*priv;
455 	XfdashboardFocusableInterface	*selfIface;
456 	XfdashboardFocusableInterface	*parentIface;
457 
458 	g_return_val_if_fail(XFDASHBOARD_IS_FOCUSABLE(inFocusable), FALSE);
459 	g_return_val_if_fail(XFDASHBOARD_IS_POPUP_MENU(inFocusable), FALSE);
460 
461 	self=XFDASHBOARD_POPUP_MENU(inFocusable);
462 	priv=self->priv;
463 
464 	/* Call parent class interface function */
465 	selfIface=XFDASHBOARD_FOCUSABLE_GET_IFACE(inFocusable);
466 	parentIface=g_type_interface_peek_parent(selfIface);
467 
468 	if(parentIface && parentIface->can_focus)
469 	{
470 		if(!parentIface->can_focus(inFocusable))
471 		{
472 			return(FALSE);
473 		}
474 	}
475 
476 	/* Only active pop-up menus can be focused */
477 	if(!priv->isActive) return(FALSE);
478 
479 	/* If we get here this actor can be focused */
480 	return(TRUE);
481 }
482 
483 /* Actor lost focus */
_xfdashboard_popup_menu_focusable_unset_focus(XfdashboardFocusable * inFocusable)484 static void _xfdashboard_popup_menu_focusable_unset_focus(XfdashboardFocusable *inFocusable)
485 {
486 	XfdashboardPopupMenu			*self;
487 	XfdashboardPopupMenuPrivate		*priv;
488 	XfdashboardFocusableInterface	*selfIface;
489 	XfdashboardFocusableInterface	*parentIface;
490 
491 	g_return_if_fail(XFDASHBOARD_IS_FOCUSABLE(inFocusable));
492 	g_return_if_fail(XFDASHBOARD_IS_POPUP_MENU(inFocusable));
493 
494 	self=XFDASHBOARD_POPUP_MENU(inFocusable);
495 	priv=self->priv;
496 
497 	/* Call parent class interface function */
498 	selfIface=XFDASHBOARD_FOCUSABLE_GET_IFACE(inFocusable);
499 	parentIface=g_type_interface_peek_parent(selfIface);
500 
501 	if(parentIface && parentIface->unset_focus)
502 	{
503 		parentIface->unset_focus(inFocusable);
504 	}
505 
506 	/* If this pop-up menu is active (has flag set) then it was not cancelled and
507 	 * this actor lost its focus in any other way than expected. So do not refocus
508 	 * old remembered focusable as it may not be the one which has the focus before.
509 	 */
510 	if(priv->isActive &&
511 		priv->oldFocusable)
512 	{
513 		g_object_remove_weak_pointer(G_OBJECT(priv->oldFocusable), &priv->oldFocusable);
514 		priv->oldFocusable=NULL;
515 	}
516 
517 	/* This actor lost focus so ensure that this popup menu is cancelled */
518 	xfdashboard_popup_menu_cancel(self);
519 }
520 
521 /* Determine if this actor supports selection */
_xfdashboard_popup_menu_focusable_supports_selection(XfdashboardFocusable * inFocusable)522 static gboolean _xfdashboard_popup_menu_focusable_supports_selection(XfdashboardFocusable *inFocusable)
523 {
524 	g_return_val_if_fail(XFDASHBOARD_IS_FOCUSABLE(inFocusable), FALSE);
525 	g_return_val_if_fail(XFDASHBOARD_IS_POPUP_MENU(inFocusable), FALSE);
526 
527 	/* This actor supports selection */
528 	return(TRUE);
529 }
530 
531 /* Get current selection */
_xfdashboard_popup_menu_focusable_get_selection(XfdashboardFocusable * inFocusable)532 static ClutterActor* _xfdashboard_popup_menu_focusable_get_selection(XfdashboardFocusable *inFocusable)
533 {
534 	XfdashboardPopupMenu			*self;
535 	XfdashboardPopupMenuPrivate		*priv;
536 
537 	g_return_val_if_fail(XFDASHBOARD_IS_FOCUSABLE(inFocusable), NULL);
538 	g_return_val_if_fail(XFDASHBOARD_IS_POPUP_MENU(inFocusable), NULL);
539 
540 	self=XFDASHBOARD_POPUP_MENU(inFocusable);
541 	priv=self->priv;
542 
543 	/* Return current selection */
544 	return(priv->selectedItem);
545 }
546 
547 /* Set new selection */
_xfdashboard_popup_menu_focusable_set_selection(XfdashboardFocusable * inFocusable,ClutterActor * inSelection)548 static gboolean _xfdashboard_popup_menu_focusable_set_selection(XfdashboardFocusable *inFocusable,
549 																ClutterActor *inSelection)
550 {
551 	XfdashboardPopupMenu			*self;
552 	XfdashboardPopupMenuPrivate		*priv;
553 
554 	g_return_val_if_fail(XFDASHBOARD_IS_FOCUSABLE(inFocusable), FALSE);
555 	g_return_val_if_fail(XFDASHBOARD_IS_POPUP_MENU(inFocusable), FALSE);
556 	g_return_val_if_fail(!inSelection || CLUTTER_IS_ACTOR(inSelection), FALSE);
557 
558 	self=XFDASHBOARD_POPUP_MENU(inFocusable);
559 	priv=self->priv;
560 
561 	/* Check that selection is a child of this actor */
562 	if(inSelection &&
563 		!clutter_actor_contains(CLUTTER_ACTOR(self), inSelection))
564 	{
565 		g_warning("%s is not a child of %s and cannot be selected",
566 					G_OBJECT_TYPE_NAME(inSelection),
567 					G_OBJECT_TYPE_NAME(self));
568 
569 		return(FALSE);
570 	}
571 
572 	/* Remove weak reference at current selection */
573 	if(priv->selectedItem)
574 	{
575 		g_object_remove_weak_pointer(G_OBJECT(priv->selectedItem), &priv->selectedItem);
576 	}
577 
578 	/* Set new selection */
579 	priv->selectedItem=inSelection;
580 
581 	/* Add weak reference at new selection */
582 	if(priv->selectedItem)
583 	{
584 		g_object_add_weak_pointer(G_OBJECT(priv->selectedItem), &priv->selectedItem);
585 	}
586 
587 	/* New selection was set successfully */
588 	return(TRUE);
589 }
590 
591 /* Find requested selection target depending of current selection */
_xfdashboard_popup_menu_focusable_find_selection(XfdashboardFocusable * inFocusable,ClutterActor * inSelection,XfdashboardSelectionTarget inDirection)592 static ClutterActor* _xfdashboard_popup_menu_focusable_find_selection(XfdashboardFocusable *inFocusable,
593 																		ClutterActor *inSelection,
594 																		XfdashboardSelectionTarget inDirection)
595 {
596 	XfdashboardPopupMenu				*self;
597 	XfdashboardPopupMenuPrivate			*priv;
598 	ClutterActor						*selection;
599 	ClutterActor						*newSelection;
600 	gchar								*valueName;
601 
602 	g_return_val_if_fail(XFDASHBOARD_IS_FOCUSABLE(inFocusable), NULL);
603 	g_return_val_if_fail(XFDASHBOARD_IS_POPUP_MENU(inFocusable), NULL);
604 	g_return_val_if_fail(!inSelection || CLUTTER_IS_ACTOR(inSelection), NULL);
605 	g_return_val_if_fail(inDirection>=0 && inDirection<=XFDASHBOARD_SELECTION_TARGET_NEXT, NULL);
606 
607 	self=XFDASHBOARD_POPUP_MENU(inFocusable);
608 	priv=self->priv;
609 	selection=inSelection;
610 	newSelection=NULL;
611 
612 	/* If there is nothing selected, select first actor and return */
613 	if(!inSelection)
614 	{
615 		selection=clutter_actor_get_first_child(CLUTTER_ACTOR(priv->itemsContainer));
616 
617 		valueName=xfdashboard_get_enum_value_name(XFDASHBOARD_TYPE_SELECTION_TARGET, inDirection);
618 		XFDASHBOARD_DEBUG(self, ACTOR,
619 							"No selection at %s, so select first child %s for direction %s",
620 							G_OBJECT_TYPE_NAME(self),
621 							selection ? G_OBJECT_TYPE_NAME(selection) : "<nil>",
622 							valueName);
623 		g_free(valueName);
624 
625 		return(selection);
626 	}
627 
628 	/* Check that selection is a child of this actor otherwise return NULL */
629 	if(!clutter_actor_contains(CLUTTER_ACTOR(self), inSelection))
630 	{
631 		ClutterActor						*parent;
632 
633 		parent=clutter_actor_get_parent(inSelection);
634 		g_warning("Cannot lookup selection target at %s because %s is a child of %s",
635 					G_OBJECT_TYPE_NAME(self),
636 					G_OBJECT_TYPE_NAME(inSelection),
637 					parent ? G_OBJECT_TYPE_NAME(parent) : "<nil>");
638 
639 		return(NULL);
640 	}
641 
642 	/* Find target selection */
643 	switch(inDirection)
644 	{
645 		case XFDASHBOARD_SELECTION_TARGET_UP:
646 			newSelection=clutter_actor_get_previous_sibling(inSelection);
647 			break;
648 
649 		case XFDASHBOARD_SELECTION_TARGET_DOWN:
650 			newSelection=clutter_actor_get_next_sibling(inSelection);
651 			break;
652 
653 		case XFDASHBOARD_SELECTION_TARGET_FIRST:
654 		case XFDASHBOARD_SELECTION_TARGET_PAGE_UP:
655 			newSelection=clutter_actor_get_first_child(CLUTTER_ACTOR(priv->itemsContainer));
656 			break;
657 
658 		case XFDASHBOARD_SELECTION_TARGET_LAST:
659 		case XFDASHBOARD_SELECTION_TARGET_PAGE_DOWN:
660 			newSelection=clutter_actor_get_last_child(CLUTTER_ACTOR(priv->itemsContainer));
661 			break;
662 
663 		case XFDASHBOARD_SELECTION_TARGET_NEXT:
664 			newSelection=clutter_actor_get_next_sibling(inSelection);
665 			if(!newSelection) newSelection=clutter_actor_get_previous_sibling(inSelection);
666 			break;
667 
668 		default:
669 			{
670 				valueName=xfdashboard_get_enum_value_name(XFDASHBOARD_TYPE_SELECTION_TARGET, inDirection);
671 				g_critical("Focusable object %s does not handle selection direction of type %s.",
672 							G_OBJECT_TYPE_NAME(self),
673 							valueName);
674 				g_free(valueName);
675 			}
676 			break;
677 	}
678 
679 	/* If new selection could be found override current selection with it */
680 	if(newSelection) selection=newSelection;
681 
682 	/* Return new selection found */
683 	XFDASHBOARD_DEBUG(self, ACTOR,
684 						"Selecting %s at %s for current selection %s in direction %u",
685 						selection ? G_OBJECT_TYPE_NAME(selection) : "<nil>",
686 						G_OBJECT_TYPE_NAME(self),
687 						inSelection ? G_OBJECT_TYPE_NAME(inSelection) : "<nil>",
688 						inDirection);
689 
690 	return(selection);
691 }
692 
693 /* Activate selection */
_xfdashboard_popup_menu_focusable_activate_selection(XfdashboardFocusable * inFocusable,ClutterActor * inSelection)694 static gboolean _xfdashboard_popup_menu_focusable_activate_selection(XfdashboardFocusable *inFocusable,
695 																		ClutterActor *inSelection)
696 {
697 	XfdashboardPopupMenu				*self;
698 	XfdashboardPopupMenuItem			*menuItem;
699 
700 	g_return_val_if_fail(XFDASHBOARD_IS_FOCUSABLE(inFocusable), FALSE);
701 	g_return_val_if_fail(XFDASHBOARD_IS_POPUP_MENU(inFocusable), FALSE);
702 	g_return_val_if_fail(XFDASHBOARD_IS_POPUP_MENU_ITEM(inSelection), FALSE);
703 
704 	self=XFDASHBOARD_POPUP_MENU(inFocusable);
705 	menuItem=XFDASHBOARD_POPUP_MENU_ITEM(inSelection);
706 
707 	/* Check that selection is a child of this actor */
708 	if(!clutter_actor_contains(CLUTTER_ACTOR(self), inSelection))
709 	{
710 		ClutterActor						*parent;
711 
712 		parent=clutter_actor_get_parent(inSelection);
713 		g_warning("%s is a child of %s and cannot be activated at %s",
714 					G_OBJECT_TYPE_NAME(inSelection),
715 					parent ? G_OBJECT_TYPE_NAME(parent) : "<nil>",
716 					G_OBJECT_TYPE_NAME(self));
717 
718 		return(FALSE);
719 	}
720 
721 	/* Activate selection */
722 	xfdashboard_popup_menu_item_activate(menuItem);
723 
724 	/* If we get here activation of menu item was successful */
725 	return(TRUE);
726 }
727 
728 /* Interface initialization
729  * Set up default functions
730  */
_xfdashboard_popup_menu_focusable_iface_init(XfdashboardFocusableInterface * iface)731 void _xfdashboard_popup_menu_focusable_iface_init(XfdashboardFocusableInterface *iface)
732 {
733 	iface->can_focus=_xfdashboard_popup_menu_focusable_can_focus;
734 	iface->unset_focus=_xfdashboard_popup_menu_focusable_unset_focus;
735 
736 	iface->supports_selection=_xfdashboard_popup_menu_focusable_supports_selection;
737 	iface->get_selection=_xfdashboard_popup_menu_focusable_get_selection;
738 	iface->set_selection=_xfdashboard_popup_menu_focusable_set_selection;
739 	iface->find_selection=_xfdashboard_popup_menu_focusable_find_selection;
740 	iface->activate_selection=_xfdashboard_popup_menu_focusable_activate_selection;
741 }
742 
743 
744 /* IMPLEMENTATION: GObject */
745 
746 /* Dispose this object */
_xfdashboard_popup_menu_dispose(GObject * inObject)747 static void _xfdashboard_popup_menu_dispose(GObject *inObject)
748 {
749 	XfdashboardPopupMenu			*self=XFDASHBOARD_POPUP_MENU(inObject);
750 	XfdashboardPopupMenuPrivate		*priv=self->priv;
751 
752 	/* Cancel this pop-up menu if it is still active */
753 	xfdashboard_popup_menu_cancel(self);
754 
755 	/* Release our allocated variables */
756 	if(priv->suspendSignalID)
757 	{
758 		g_signal_handler_disconnect(xfdashboard_application_get_default(), priv->suspendSignalID);
759 		priv->suspendSignalID=0;
760 	}
761 
762 	if(priv->capturedEventSignalID)
763 	{
764 		g_signal_handler_disconnect(priv->stage, priv->capturedEventSignalID);
765 		priv->capturedEventSignalID=0;
766 	}
767 
768 	if(priv->source)
769 	{
770 		gchar						*cssClass;
771 
772 		/* Disconnect signal handler */
773 		if(priv->sourceDestroySignalID)
774 		{
775 			g_signal_handler_disconnect(priv->source, priv->sourceDestroySignalID);
776 			priv->sourceDestroySignalID=0;
777 		}
778 
779 		/* Remove style */
780 		cssClass=g_strdup_printf("popup-menu-source-%s", G_OBJECT_TYPE_NAME(priv->source));
781 		xfdashboard_stylable_remove_class(XFDASHBOARD_STYLABLE(self), cssClass);
782 		g_free(cssClass);
783 
784 		/* Release source */
785 		g_object_remove_weak_pointer(G_OBJECT(priv->source), (gpointer*)&priv->source);
786 		priv->source=NULL;
787 	}
788 
789 	if(priv->selectedItem)
790 	{
791 		g_object_remove_weak_pointer(G_OBJECT(priv->selectedItem), &priv->selectedItem);
792 		priv->selectedItem=NULL;
793 	}
794 
795 	if(priv->oldFocusable)
796 	{
797 		g_object_remove_weak_pointer(G_OBJECT(priv->oldFocusable), &priv->oldFocusable);
798 		priv->oldFocusable=NULL;
799 	}
800 
801 	if(priv->itemsContainer)
802 	{
803 		clutter_actor_destroy(priv->itemsContainer);
804 		priv->itemsContainer=NULL;
805 	}
806 
807 	if(priv->focusManager)
808 	{
809 		xfdashboard_focus_manager_unregister(priv->focusManager, XFDASHBOARD_FOCUSABLE(self));
810 		g_object_unref(priv->focusManager);
811 		priv->focusManager=NULL;
812 	}
813 
814 	if(priv->windowTracker)
815 	{
816 		g_object_unref(priv->windowTracker);
817 		priv->windowTracker=NULL;
818 	}
819 
820 	if(priv->stage)
821 	{
822 		priv->stage=NULL;
823 	}
824 
825 	/* Call parent's class dispose method */
826 	G_OBJECT_CLASS(xfdashboard_popup_menu_parent_class)->dispose(inObject);
827 }
828 
829 /* Set/get properties */
_xfdashboard_popup_menu_set_property(GObject * inObject,guint inPropID,const GValue * inValue,GParamSpec * inSpec)830 static void _xfdashboard_popup_menu_set_property(GObject *inObject,
831 													guint inPropID,
832 													const GValue *inValue,
833 													GParamSpec *inSpec)
834 {
835 	XfdashboardPopupMenu			*self=XFDASHBOARD_POPUP_MENU(inObject);
836 
837 	switch(inPropID)
838 	{
839 		case PROP_DESTROY_ON_CANCEL:
840 			xfdashboard_popup_menu_set_destroy_on_cancel(self, g_value_get_boolean(inValue));
841 			break;
842 
843 		case PROP_SOURCE:
844 			xfdashboard_popup_menu_set_source(self, CLUTTER_ACTOR(g_value_get_object(inValue)));
845 			break;
846 
847 		case PROP_SHOW_TITLE:
848 			xfdashboard_popup_menu_set_show_title(self, g_value_get_boolean(inValue));
849 			break;
850 
851 		case PROP_TITLE:
852 			xfdashboard_popup_menu_set_title(self, g_value_get_string(inValue));
853 			break;
854 
855 		case PROP_SHOW_TITLE_ICON:
856 			xfdashboard_popup_menu_set_show_title_icon(self, g_value_get_boolean(inValue));
857 			break;
858 
859 		case PROP_TITLE_ICON_NAME:
860 			xfdashboard_popup_menu_set_title_icon_name(self, g_value_get_string(inValue));
861 			break;
862 
863 		case PROP_TITLE_GICON:
864 			xfdashboard_popup_menu_set_title_gicon(self, G_ICON(g_value_get_object(inValue)));
865 			break;
866 
867 		default:
868 			G_OBJECT_WARN_INVALID_PROPERTY_ID(inObject, inPropID, inSpec);
869 			break;
870 	}
871 }
872 
_xfdashboard_popup_menu_get_property(GObject * inObject,guint inPropID,GValue * outValue,GParamSpec * inSpec)873 static void _xfdashboard_popup_menu_get_property(GObject *inObject,
874 													guint inPropID,
875 													GValue *outValue,
876 													GParamSpec *inSpec)
877 {
878 	XfdashboardPopupMenu			*self=XFDASHBOARD_POPUP_MENU(inObject);
879 	XfdashboardPopupMenuPrivate		*priv=self->priv;
880 
881 	switch(inPropID)
882 	{
883 		case PROP_DESTROY_ON_CANCEL:
884 			g_value_set_boolean(outValue, priv->destroyOnCancel);
885 			break;
886 
887 		case PROP_SOURCE:
888 			g_value_set_object(outValue, priv->source);
889 			break;
890 
891 		case PROP_SHOW_TITLE:
892 			g_value_set_boolean(outValue, priv->showTitle);
893 			break;
894 
895 		case PROP_TITLE:
896 			g_value_set_string(outValue, xfdashboard_popup_menu_get_title(self));
897 			break;
898 
899 		case PROP_SHOW_TITLE_ICON:
900 			g_value_set_boolean(outValue, priv->showTitleIcon);
901 			break;
902 
903 		case PROP_TITLE_ICON_NAME:
904 			g_value_set_string(outValue, xfdashboard_popup_menu_get_title_icon_name(self));
905 			break;
906 
907 		case PROP_TITLE_GICON:
908 			g_value_set_object(outValue, xfdashboard_popup_menu_get_title_gicon(self));
909 			break;
910 
911 		default:
912 			G_OBJECT_WARN_INVALID_PROPERTY_ID(inObject, inPropID, inSpec);
913 			break;
914 	}
915 }
916 
917 /* Class initialization
918  * Override functions in parent classes and define properties
919  * and signals
920  */
xfdashboard_popup_menu_class_init(XfdashboardPopupMenuClass * klass)921 static void xfdashboard_popup_menu_class_init(XfdashboardPopupMenuClass *klass)
922 {
923 	XfdashboardActorClass	*actorClass=XFDASHBOARD_ACTOR_CLASS(klass);
924 	ClutterActorClass		*clutterActorClass=CLUTTER_ACTOR_CLASS(klass);
925 	GObjectClass			*gobjectClass=G_OBJECT_CLASS(klass);
926 
927 	/* Override functions */
928 	gobjectClass->dispose=_xfdashboard_popup_menu_dispose;
929 	gobjectClass->set_property=_xfdashboard_popup_menu_set_property;
930 	gobjectClass->get_property=_xfdashboard_popup_menu_get_property;
931 
932 	clutterActorClass->allocate=_xfdashboard_popup_menu_allocate;
933 
934 	/* Define properties */
935 	/**
936 	 * XfdashboardPopupMenu:destroy-on-cancel:
937 	 *
938 	 * A flag indicating if this pop-up menu should be destroyed automatically
939 	 * when it is cancelled.
940 	 */
941 	XfdashboardPopupMenuProperties[PROP_DESTROY_ON_CANCEL]=
942 		g_param_spec_boolean("destroy-on-cancel",
943 								"Destroy on cancel",
944 								"Flag indicating this pop-up menu should be destroyed automatically when it is cancelled",
945 								FALSE,
946 								G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
947 
948 	/**
949 	 * XfdashboardPopupMenu:source:
950 	 *
951 	 * The #ClutterActor on which this pop-up menu depends on. If this actor is
952 	 * destroyed then this pop-up menu is cancelled when active.
953 	 */
954 	XfdashboardPopupMenuProperties[PROP_SOURCE]=
955 		g_param_spec_object("source",
956 							"Source",
957 							"The object on which this pop-up menu depends on",
958 							CLUTTER_TYPE_ACTOR,
959 							G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
960 
961 	/**
962 	 * XfdashboardPopupMenu:show-title:
963 	 *
964 	 * A flag indicating if the title of this pop-up menu should be shown.
965 	 */
966 	XfdashboardPopupMenuProperties[PROP_SHOW_TITLE]=
967 		g_param_spec_boolean("show-title",
968 								"Show title",
969 								"Flag indicating if the title of this pop-up menu should be shown",
970 								FALSE,
971 								G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
972 
973 	/**
974 	 * XfdashboardPopupMenu:title:
975 	 *
976 	 * A string containing the title of this pop-up menu.
977 	 */
978 	XfdashboardPopupMenuProperties[PROP_TITLE]=
979 		g_param_spec_string("title",
980 							"Title",
981 							"Title of pop-up menu",
982 							N_(""),
983 							G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
984 
985 	/**
986 	 * XfdashboardPopupMenu:show-title-icon:
987 	 *
988 	 * A flag indicating if the icon of the title of this pop-up menu should be shown.
989 	 */
990 	XfdashboardPopupMenuProperties[PROP_SHOW_TITLE_ICON]=
991 		g_param_spec_boolean("show-title-icon",
992 								"Show title icon",
993 								"Flag indicating if the icon of title of this pop-up menu should be shown",
994 								FALSE,
995 								G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
996 
997 	/**
998 	 * XfdashboardPopupMenu:title-icon-name:
999 	 *
1000 	 * A string containing the stock icon name or file name for the icon to use
1001 	 * at title of this pop-up menu.
1002 	 */
1003 	XfdashboardPopupMenuProperties[PROP_TITLE_ICON_NAME]=
1004 		g_param_spec_string("title-icon-name",
1005 							"Title icon name",
1006 							"Themed icon name or file name of icon used in title",
1007 							N_(""),
1008 							G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
1009 
1010 	/**
1011 	 * XfdashboardPopupMenu:title-gicon:
1012 	 *
1013 	 * A #GIcon containing the icon image to use at title of this pop-up menu.
1014 	 */
1015 	XfdashboardPopupMenuProperties[PROP_TITLE_GICON]=
1016 		g_param_spec_object("title-gicon",
1017 							"Title GIcon",
1018 							"The GIcon of icon used in title",
1019 							G_TYPE_ICON,
1020 							G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
1021 
1022 	g_object_class_install_properties(gobjectClass, PROP_LAST, XfdashboardPopupMenuProperties);
1023 
1024 	/* Define stylable properties */
1025 	xfdashboard_actor_install_stylable_property(actorClass, XfdashboardPopupMenuProperties[PROP_SHOW_TITLE]);
1026 	xfdashboard_actor_install_stylable_property(actorClass, XfdashboardPopupMenuProperties[PROP_SHOW_TITLE_ICON]);
1027 
1028 	/* Define signals */
1029 	/**
1030 	 * XfdashboardPopupMenu::activated:
1031 	 * @self: The pop-up menu which was activated
1032 	 *
1033 	 * The ::activated signal is emitted when the pop-up menu is shown and the
1034 	 * user can perform an action by selecting an item.
1035 	 */
1036 	XfdashboardPopupMenuSignals[SIGNAL_ACTIVATED]=
1037 		g_signal_new("activated",
1038 						G_TYPE_FROM_CLASS(klass),
1039 						G_SIGNAL_RUN_LAST,
1040 						G_STRUCT_OFFSET(XfdashboardPopupMenuClass, activated),
1041 						NULL,
1042 						NULL,
1043 						g_cclosure_marshal_VOID__VOID,
1044 						G_TYPE_NONE,
1045 						0);
1046 
1047 	/**
1048 	 * XfdashboardPopupMenu::cancelled:
1049 	 * @self: The pop-up menu which was cancelled
1050 	 *
1051 	 * The ::cancelled signal is emitted when the pop-up menu is hidden. This
1052 	 * signal is emitted regardless the user has chosen an item and perform the
1053 	 * associated action or not.
1054 	 *
1055 	 * Note: This signal does not indicate if a selection was made or not.
1056 	 */
1057 	XfdashboardPopupMenuSignals[SIGNAL_CANCELLED]=
1058 		g_signal_new("cancelled",
1059 						G_TYPE_FROM_CLASS(klass),
1060 						G_SIGNAL_RUN_LAST,
1061 						G_STRUCT_OFFSET(XfdashboardPopupMenuClass, cancelled),
1062 						NULL,
1063 						NULL,
1064 						g_cclosure_marshal_VOID__VOID,
1065 						G_TYPE_NONE,
1066 						0);
1067 
1068 	/**
1069 	 * XfdashboardPopupMenu::item-activated:
1070 	 * @self: The pop-up menu containing the activated menu item
1071 	 * @inMenuItem: The menu item which was activated
1072 	 *
1073 	 * The ::item-activated signal is emitted when a menu item at pop-up menu
1074 	 * was activated either by key-press or by clicking on it.
1075 	 */
1076 	XfdashboardPopupMenuSignals[SIGNAL_ITEM_ACTIVATED]=
1077 		g_signal_new("item-activated",
1078 						G_TYPE_FROM_CLASS(klass),
1079 						G_SIGNAL_RUN_LAST,
1080 						G_STRUCT_OFFSET(XfdashboardPopupMenuClass, item_activated),
1081 						NULL,
1082 						NULL,
1083 						g_cclosure_marshal_VOID__OBJECT,
1084 						G_TYPE_NONE,
1085 						1,
1086 						XFDASHBOARD_TYPE_POPUP_MENU_ITEM);
1087 
1088 	/**
1089 	 * XfdashboardPopupMenu::item-added:
1090 	 * @self: The pop-up menu containing the activated menu item
1091 	 * @inMenuItem: The menu item which was added
1092 	 *
1093 	 * The ::item-added signal is emitted when a menu item was added to pop-up menu.
1094 	 */
1095 	XfdashboardPopupMenuSignals[SIGNAL_ITEM_ADDED]=
1096 		g_signal_new("item-added",
1097 						G_TYPE_FROM_CLASS(klass),
1098 						G_SIGNAL_RUN_LAST,
1099 						G_STRUCT_OFFSET(XfdashboardPopupMenuClass, item_added),
1100 						NULL,
1101 						NULL,
1102 						g_cclosure_marshal_VOID__OBJECT,
1103 						G_TYPE_NONE,
1104 						1,
1105 						XFDASHBOARD_TYPE_POPUP_MENU_ITEM);
1106 
1107 	/**
1108 	 * XfdashboardPopupMenu::item-removed:
1109 	 * @self: The pop-up menu containing the activated menu item
1110 	 * @inMenuItem: The menu item which was added
1111 	 *
1112 	 * The ::item-added signal is emitted when a menu item was added to pop-up menu.
1113 	 */
1114 	XfdashboardPopupMenuSignals[SIGNAL_ITEM_REMOVED]=
1115 		g_signal_new("item-removed",
1116 						G_TYPE_FROM_CLASS(klass),
1117 						G_SIGNAL_RUN_LAST,
1118 						G_STRUCT_OFFSET(XfdashboardPopupMenuClass, item_removed),
1119 						NULL,
1120 						NULL,
1121 						g_cclosure_marshal_VOID__OBJECT,
1122 						G_TYPE_NONE,
1123 						1,
1124 						XFDASHBOARD_TYPE_POPUP_MENU_ITEM);
1125 }
1126 
1127 /* Object initialization
1128  * Create private structure and set up default values
1129  */
xfdashboard_popup_menu_init(XfdashboardPopupMenu * self)1130 static void xfdashboard_popup_menu_init(XfdashboardPopupMenu *self)
1131 {
1132 	XfdashboardPopupMenuPrivate		*priv;
1133 	ClutterLayoutManager			*layout;
1134 
1135 	priv=self->priv=xfdashboard_popup_menu_get_instance_private(self);
1136 
1137 	/* Set up default values */
1138 	priv->destroyOnCancel=FALSE;
1139 	priv->source=NULL;
1140 	priv->showTitle=FALSE;
1141 	priv->showTitleIcon=FALSE;
1142 	priv->isActive=FALSE;
1143 	priv->title=NULL;
1144 	priv->itemsContainer=NULL;
1145 	priv->windowTracker=xfdashboard_window_tracker_get_default();
1146 	priv->focusManager=xfdashboard_focus_manager_get_default();
1147 	priv->oldFocusable=NULL;
1148 	priv->selectedItem=NULL;
1149 	priv->stage=NULL;
1150 	priv->capturedEventSignalID=0;
1151 	priv->sourceDestroySignalID=0;
1152 	priv->suspendSignalID=0;
1153 
1154 	/* This actor is react on events */
1155 	clutter_actor_set_reactive(CLUTTER_ACTOR(self), TRUE);
1156 
1157 	/* Set up title actor */
1158 	priv->title=xfdashboard_button_new();
1159 	xfdashboard_label_set_style(XFDASHBOARD_LABEL(priv->title), XFDASHBOARD_LABEL_STYLE_TEXT);
1160 	xfdashboard_label_set_text(XFDASHBOARD_LABEL(priv->title), NULL);
1161 	clutter_actor_set_x_expand(priv->title, TRUE);
1162 	clutter_actor_set_y_expand(priv->title, TRUE);
1163 	clutter_actor_hide(priv->title);
1164 	xfdashboard_stylable_add_class(XFDASHBOARD_STYLABLE(priv->title), "popup-menu-title");
1165 
1166 	/* Set up items container which will hold all menu items */
1167 	layout=xfdashboard_box_layout_new();
1168 	clutter_box_layout_set_orientation(CLUTTER_BOX_LAYOUT(layout), CLUTTER_ORIENTATION_VERTICAL);
1169 
1170 	priv->itemsContainer=xfdashboard_actor_new();
1171 	clutter_actor_set_x_expand(priv->itemsContainer, TRUE);
1172 	clutter_actor_set_y_expand(priv->itemsContainer, TRUE);
1173 	clutter_actor_set_layout_manager(priv->itemsContainer, layout);
1174 
1175 	/* Set up this actor */
1176 	layout=xfdashboard_box_layout_new();
1177 	clutter_box_layout_set_orientation(CLUTTER_BOX_LAYOUT(layout), CLUTTER_ORIENTATION_VERTICAL);
1178 	clutter_actor_set_layout_manager(CLUTTER_ACTOR(self), layout);
1179 
1180 	clutter_actor_add_child(CLUTTER_ACTOR(self), priv->title);
1181 	clutter_actor_add_child(CLUTTER_ACTOR(self), priv->itemsContainer);
1182 	xfdashboard_stylable_add_class(XFDASHBOARD_STYLABLE(self), "popup-menu");
1183 
1184 	/* Register this actor at focus manager but ensure that this actor is
1185 	 * not focusable initially */
1186 	xfdashboard_actor_set_can_focus(XFDASHBOARD_ACTOR(self), FALSE);
1187 	xfdashboard_focus_manager_register(priv->focusManager, XFDASHBOARD_FOCUSABLE(self));
1188 
1189 	/* Add popup menu to stage */
1190 	priv->stage=xfdashboard_application_get_stage(xfdashboard_application_get_default());
1191 	clutter_actor_insert_child_above(CLUTTER_ACTOR(priv->stage), CLUTTER_ACTOR(self), NULL);
1192 
1193 	/* Connect signal to get notified when application suspends to cancel pop-up menu */
1194 	priv->suspendSignalID=g_signal_connect_swapped(xfdashboard_application_get_default(),
1195 													"notify::is-suspended",
1196 													G_CALLBACK(_xfdashboard_popup_menu_on_application_suspended_changed),
1197 													self);
1198 }
1199 
1200 /* IMPLEMENTATION: Public API */
1201 
1202 /**
1203  * xfdashboard_popup_menu_new:
1204  *
1205  * Creates a new #XfdashboardPopupMenu actor
1206  *
1207  * Return value: The newly created #XfdashboardPopupMenu
1208  */
xfdashboard_popup_menu_new(void)1209 ClutterActor* xfdashboard_popup_menu_new(void)
1210 {
1211 	return(g_object_new(XFDASHBOARD_TYPE_POPUP_MENU, NULL));
1212 }
1213 
1214 /**
1215  * xfdashboard_popup_menu_new_for_source:
1216  * @inSource: A #ClutterActor which this pop-up menu should depend on
1217  *
1218  * Creates a new #XfdashboardPopupMenu actor which depends on actor @inSource.
1219  * When the actor @inSource is destroyed and the pop-up menu is active then it
1220  * will be cancelled automatically.
1221  *
1222  * Return value: The newly created #XfdashboardPopupMenu
1223  */
xfdashboard_popup_menu_new_for_source(ClutterActor * inSource)1224 ClutterActor* xfdashboard_popup_menu_new_for_source(ClutterActor *inSource)
1225 {
1226 	g_return_val_if_fail(CLUTTER_IS_ACTOR(inSource), NULL);
1227 
1228 	return(g_object_new(XFDASHBOARD_TYPE_POPUP_MENU,
1229 						"source", inSource,
1230 						NULL));
1231 }
1232 
1233 /**
1234  * xfdashboard_popup_menu_get_destroy_on_cancel:
1235  * @self: A #XfdashboardPopupMenu
1236  *
1237  * Retrieves the automatic destruction mode of @self. If automatic destruction mode
1238  * is %TRUE then the pop-up menu will be destroy by calling xfdashboard_actor_destroy()
1239  * when it is cancelled, e.g. by calling xfdashboard_popup_menu_cancel().
1240  *
1241  * Return value: Returns %TRUE if automatic destruction mode is enabled, otherwise
1242  *   %FALSE.
1243  */
xfdashboard_popup_menu_get_destroy_on_cancel(XfdashboardPopupMenu * self)1244 gboolean xfdashboard_popup_menu_get_destroy_on_cancel(XfdashboardPopupMenu *self)
1245 {
1246 	g_return_val_if_fail(XFDASHBOARD_IS_POPUP_MENU(self), FALSE);
1247 
1248 	return(self->priv->destroyOnCancel);
1249 }
1250 
1251 /**
1252  * xfdashboard_popup_menu_set_destroy_on_cancel:
1253  * @self: A #XfdashboardPopupMenu
1254  * @inDestroyOnCancel: The automatic destruction mode to set at @self
1255  *
1256  * Sets the automatic destruction mode of @self. If @inDestroyOnCancel is set to
1257  * %TRUE then the pop-up menu will automatically destroyed by calling xfdashboard_actor_destroy()
1258  * when it is cancelled, e.g. by calling xfdashboard_popup_menu_cancel().
1259  */
xfdashboard_popup_menu_set_destroy_on_cancel(XfdashboardPopupMenu * self,gboolean inDestroyOnCancel)1260 void xfdashboard_popup_menu_set_destroy_on_cancel(XfdashboardPopupMenu *self, gboolean inDestroyOnCancel)
1261 {
1262 	XfdashboardPopupMenuPrivate			*priv;
1263 
1264 	g_return_if_fail(XFDASHBOARD_IS_POPUP_MENU(self));
1265 
1266 	priv=self->priv;
1267 
1268 	/* Set value if changed */
1269 	if(priv->destroyOnCancel!=inDestroyOnCancel)
1270 	{
1271 		/* Set value */
1272 		priv->destroyOnCancel=inDestroyOnCancel;
1273 
1274 		/* Notify about property change */
1275 		g_object_notify_by_pspec(G_OBJECT(self), XfdashboardPopupMenuProperties[PROP_DESTROY_ON_CANCEL]);
1276 	}
1277 }
1278 
1279 /**
1280  * xfdashboard_popup_menu_get_source:
1281  * @self: A #XfdashboardPopupMenu
1282  *
1283  * Retrieves the source actor to @inSource which the pop-up menu at @self depends on.
1284  *
1285  * Return value: (transfer none): Returns #ClutterActor which the pop-up menu or
1286  *   %NULL if no source actor is set.
1287  */
xfdashboard_popup_menu_get_source(XfdashboardPopupMenu * self)1288 ClutterActor* xfdashboard_popup_menu_get_source(XfdashboardPopupMenu *self)
1289 {
1290 	g_return_val_if_fail(XFDASHBOARD_IS_POPUP_MENU(self), NULL);
1291 
1292 	return(self->priv->source);
1293 }
1294 
1295 /**
1296  * xfdashboard_popup_menu_set_source:
1297  * @self: A #XfdashboardPopupMenu
1298  * @inSource: A #ClutterActor which this pop-up menu should depend on or %NULL
1299  *   if it should not depend on any actor
1300  *
1301  * Sets the source actor to @inSource which the pop-up menu at @self depends on.
1302  * When the actor @inSource is destroyed and the pop-up menu at @self is active
1303  * then it will be cancelled automatically.
1304  *
1305  * In addition the CSS class "popup-menu-source-SOURCE_CLASS_NAME" will be set
1306  * on pop-up menu at @self, e.g. if source is of type ClutterActor the CSS class
1307  * "popup-menu-source-ClutterActor" will be set.
1308  */
xfdashboard_popup_menu_set_source(XfdashboardPopupMenu * self,ClutterActor * inSource)1309 void xfdashboard_popup_menu_set_source(XfdashboardPopupMenu *self, ClutterActor *inSource)
1310 {
1311 	XfdashboardPopupMenuPrivate			*priv;
1312 	gchar								*cssClass;
1313 
1314 	g_return_if_fail(XFDASHBOARD_IS_POPUP_MENU(self));
1315 	g_return_if_fail(!inSource || CLUTTER_IS_ACTOR(inSource));
1316 
1317 	priv=self->priv;
1318 
1319 	/* Set value if changed */
1320 	if(priv->source!=inSource)
1321 	{
1322 		/* Release old source if set */
1323 		if(priv->source)
1324 		{
1325 			/* Disconnect signal handler */
1326 			g_signal_handler_disconnect(priv->source, priv->sourceDestroySignalID);
1327 			priv->sourceDestroySignalID=0;
1328 
1329 			/* Remove style */
1330 			cssClass=g_strdup_printf("popup-menu-source-%s", G_OBJECT_TYPE_NAME(priv->source));
1331 			xfdashboard_stylable_remove_class(XFDASHBOARD_STYLABLE(self), cssClass);
1332 			g_free(cssClass);
1333 
1334 			/* Release source */
1335 			g_object_remove_weak_pointer(G_OBJECT(priv->source), (gpointer*)&priv->source);
1336 			priv->source=NULL;
1337 		}
1338 
1339 		/* Set value */
1340 		if(inSource)
1341 		{
1342 			/* Set up source */
1343 			priv->source=inSource;
1344 			g_object_add_weak_pointer(G_OBJECT(priv->source), (gpointer*)&priv->source);
1345 
1346 			/* Add style */
1347 			cssClass=g_strdup_printf("popup-menu-source-%s", G_OBJECT_TYPE_NAME(priv->source));
1348 			xfdashboard_stylable_add_class(XFDASHBOARD_STYLABLE(self), cssClass);
1349 			g_free(cssClass);
1350 
1351 			/* Connect signal handler */
1352 			priv->sourceDestroySignalID=g_signal_connect_swapped(priv->source,
1353 																	"destroy",
1354 																	G_CALLBACK(_xfdashboard_popup_menu_on_source_destroy),
1355 																	self);
1356 		}
1357 
1358 		/* Notify about property change */
1359 		g_object_notify_by_pspec(G_OBJECT(self), XfdashboardPopupMenuProperties[PROP_SOURCE]);
1360 	}
1361 }
1362 
1363 /**
1364  * xfdashboard_popup_menu_get_show_title:
1365  * @self: A #XfdashboardPopupMenu
1366  *
1367  * Retrieves the state if the title of pop-up menu at @self should be shown or not.
1368  *
1369  * Return value: Returns %TRUE if title of pop-up menu should be shown and
1370  *   %FALSE if not.
1371  */
xfdashboard_popup_menu_get_show_title(XfdashboardPopupMenu * self)1372 gboolean xfdashboard_popup_menu_get_show_title(XfdashboardPopupMenu *self)
1373 {
1374 	g_return_val_if_fail(XFDASHBOARD_IS_POPUP_MENU(self), FALSE);
1375 
1376 	return(self->priv->showTitle);
1377 }
1378 
1379 /**
1380  * xfdashboard_popup_menu_set_show_title:
1381  * @self: A #XfdashboardPopupMenu
1382  * @inShowTitle: Flag indicating if the title of pop-up menu should be shown.
1383  *
1384  * If @inShowTitle is %TRUE then the title of the pop-up menu at @self will be
1385  * shown. If @inShowTitle is %FALSE it will be hidden.
1386  */
xfdashboard_popup_menu_set_show_title(XfdashboardPopupMenu * self,gboolean inShowTitle)1387 void xfdashboard_popup_menu_set_show_title(XfdashboardPopupMenu *self, gboolean inShowTitle)
1388 {
1389 	XfdashboardPopupMenuPrivate		*priv;
1390 
1391 	g_return_if_fail(XFDASHBOARD_IS_POPUP_MENU(self));
1392 
1393 	priv=self->priv;
1394 
1395 	/* Set value if changed */
1396 	if(priv->showTitle!=inShowTitle)
1397 	{
1398 		/* Set value */
1399 		priv->showTitle=inShowTitle;
1400 
1401 		/* Update visibility state of title actor */
1402 		_xfdashboard_popup_menu_update_title_actors_visibility(self);
1403 
1404 		/* Notify about property change */
1405 		g_object_notify_by_pspec(G_OBJECT(self), XfdashboardPopupMenuProperties[PROP_SHOW_TITLE]);
1406 	}
1407 }
1408 
1409 /**
1410  * xfdashboard_popup_menu_get_title:
1411  * @self: A #XfdashboardPopupMenu
1412  *
1413  * Retrieves the title of pop-up menu.
1414  *
1415  * Return value: Returns string with title of pop-up menu.
1416  */
xfdashboard_popup_menu_get_title(XfdashboardPopupMenu * self)1417 const gchar* xfdashboard_popup_menu_get_title(XfdashboardPopupMenu *self)
1418 {
1419 	g_return_val_if_fail(XFDASHBOARD_IS_POPUP_MENU(self), NULL);
1420 
1421 	return(xfdashboard_label_get_text(XFDASHBOARD_LABEL(self->priv->title)));
1422 }
1423 
1424 /**
1425  * xfdashboard_popup_menu_set_title:
1426  * @self: A #XfdashboardPopupMenu
1427  * @inMarkupTitle: The title to set
1428  *
1429  * Sets @inMarkupTitle as title of pop-up menu at @self. The title string can
1430  * contain markup.
1431  */
xfdashboard_popup_menu_set_title(XfdashboardPopupMenu * self,const gchar * inMarkupTitle)1432 void xfdashboard_popup_menu_set_title(XfdashboardPopupMenu *self, const gchar *inMarkupTitle)
1433 {
1434 	XfdashboardPopupMenuPrivate		*priv;
1435 
1436 	g_return_if_fail(XFDASHBOARD_IS_POPUP_MENU(self));
1437 	g_return_if_fail(inMarkupTitle);
1438 
1439 	priv=self->priv;
1440 
1441 	/* Set value if changed */
1442 	if(g_strcmp0(xfdashboard_label_get_text(XFDASHBOARD_LABEL(priv->title)), inMarkupTitle)!=0)
1443 	{
1444 		/* Set value */
1445 		xfdashboard_label_set_text(XFDASHBOARD_LABEL(priv->title), inMarkupTitle);
1446 
1447 		/* Notify about property change */
1448 		g_object_notify_by_pspec(G_OBJECT(self), XfdashboardPopupMenuProperties[PROP_TITLE]);
1449 	}
1450 }
1451 
1452 /**
1453  * xfdashboard_popup_menu_get_show_title_icon:
1454  * @self: A #XfdashboardPopupMenu
1455  *
1456  * Retrieves the state if the icon of title of pop-up menu at @self should be
1457  * shown or not.
1458  *
1459  * Return value: Returns %TRUE if icon of title of pop-up menu should be shown
1460  *   and %FALSE if not.
1461  */
xfdashboard_popup_menu_get_show_title_icon(XfdashboardPopupMenu * self)1462 gboolean xfdashboard_popup_menu_get_show_title_icon(XfdashboardPopupMenu *self)
1463 {
1464 	g_return_val_if_fail(XFDASHBOARD_IS_POPUP_MENU(self), FALSE);
1465 
1466 	return(self->priv->showTitleIcon);
1467 }
1468 
1469 /**
1470  * xfdashboard_popup_menu_set_show_title_icon:
1471  * @self: A #XfdashboardPopupMenu
1472  * @inShowTitle: Flag indicating if the icon of title of pop-up menu should be shown.
1473  *
1474  * If @inShowTitle is %TRUE then the icon of title of the pop-up menu at @self will be
1475  * shown. If @inShowTitle is %FALSE it will be hidden.
1476  */
xfdashboard_popup_menu_set_show_title_icon(XfdashboardPopupMenu * self,gboolean inShowTitleIcon)1477 void xfdashboard_popup_menu_set_show_title_icon(XfdashboardPopupMenu *self, gboolean inShowTitleIcon)
1478 {
1479 	XfdashboardPopupMenuPrivate		*priv;
1480 
1481 	g_return_if_fail(XFDASHBOARD_IS_POPUP_MENU(self));
1482 
1483 	priv=self->priv;
1484 
1485 	/* Set value if changed */
1486 	if(priv->showTitleIcon!=inShowTitleIcon)
1487 	{
1488 		/* Set value */
1489 		priv->showTitleIcon=inShowTitleIcon;
1490 
1491 		/* Update visibility state of title actor */
1492 		_xfdashboard_popup_menu_update_title_actors_visibility(self);
1493 
1494 		/* Notify about property change */
1495 		g_object_notify_by_pspec(G_OBJECT(self), XfdashboardPopupMenuProperties[PROP_SHOW_TITLE_ICON]);
1496 	}
1497 }
1498 
1499 /**
1500  * xfdashboard_popup_menu_get_title_icon_name:
1501  * @self: A #XfdashboardPopupMenu
1502  *
1503  * Retrieves the stock icon name or file name of title icon of pop-up menu at @self.
1504  *
1505  * Return value: Returns string with icon name or file name of pop-up menu's title.
1506  */
xfdashboard_popup_menu_get_title_icon_name(XfdashboardPopupMenu * self)1507 const gchar* xfdashboard_popup_menu_get_title_icon_name(XfdashboardPopupMenu *self)
1508 {
1509 	g_return_val_if_fail(XFDASHBOARD_IS_POPUP_MENU(self), NULL);
1510 
1511 	return(xfdashboard_label_get_icon_name(XFDASHBOARD_LABEL(self->priv->title)));
1512 }
1513 
1514 /**
1515  * xfdashboard_popup_menu_set_title_icon_name:
1516  * @self: A #XfdashboardPopupMenu
1517  * @inIconName: A string containing the stock icon name or file name for the icon
1518  *   to be place in the toogle button
1519  *
1520  * Sets the icon in title to icon at @inIconName of pop-up menu at @self. If set to
1521  * %NULL the title icon is hidden.
1522  */
xfdashboard_popup_menu_set_title_icon_name(XfdashboardPopupMenu * self,const gchar * inIconName)1523 void xfdashboard_popup_menu_set_title_icon_name(XfdashboardPopupMenu *self, const gchar *inIconName)
1524 {
1525 	XfdashboardPopupMenuPrivate		*priv;
1526 
1527 	g_return_if_fail(XFDASHBOARD_IS_POPUP_MENU(self));
1528 	g_return_if_fail(inIconName);
1529 
1530 	priv=self->priv;
1531 
1532 	/* Set value if changed */
1533 	if(g_strcmp0(xfdashboard_label_get_icon_name(XFDASHBOARD_LABEL(priv->title)), inIconName)!=0)
1534 	{
1535 		/* Set value */
1536 		xfdashboard_label_set_icon_name(XFDASHBOARD_LABEL(priv->title), inIconName);
1537 
1538 		/* Notify about property change */
1539 		g_object_notify_by_pspec(G_OBJECT(self), XfdashboardPopupMenuProperties[PROP_TITLE_ICON_NAME]);
1540 	}
1541 }
1542 
1543 /**
1544  * xfdashboard_popup_menu_get_title_gicon:
1545  * @self: A #XfdashboardPopupMenu
1546  *
1547  * Retrieves the title's icon of type #GIcon of pop-up menu at @self.
1548  *
1549  * Return value: Returns #GIcon of pop-up menu's title.
1550  */
xfdashboard_popup_menu_get_title_gicon(XfdashboardPopupMenu * self)1551 GIcon* xfdashboard_popup_menu_get_title_gicon(XfdashboardPopupMenu *self)
1552 {
1553 	g_return_val_if_fail(XFDASHBOARD_IS_POPUP_MENU(self), NULL);
1554 
1555 	return(xfdashboard_label_get_gicon(XFDASHBOARD_LABEL(self->priv->title)));
1556 }
1557 
1558 /**
1559  * xfdashboard_popup_menu_set_title_gicon:
1560  * @self: A #XfdashboardPopupMenu
1561  * @inIcon: A #GIcon containing the icon image
1562  *
1563  * Sets the icon in title to icon at @inIcon of pop-up menu at @self. If set to
1564  * %NULL the title icon is hidden.
1565  */
xfdashboard_popup_menu_set_title_gicon(XfdashboardPopupMenu * self,GIcon * inIcon)1566 void xfdashboard_popup_menu_set_title_gicon(XfdashboardPopupMenu *self, GIcon *inIcon)
1567 {
1568 	XfdashboardPopupMenuPrivate		*priv;
1569 	GIcon							*icon;
1570 
1571 	g_return_if_fail(XFDASHBOARD_IS_POPUP_MENU(self));
1572 	g_return_if_fail(G_IS_ICON(inIcon));
1573 
1574 	priv=self->priv;
1575 
1576 	/* Set value if changed */
1577 	icon=xfdashboard_label_get_gicon(XFDASHBOARD_LABEL(priv->title));
1578 	if(icon!=inIcon ||
1579 		(icon && inIcon && !g_icon_equal(icon, inIcon)))
1580 	{
1581 		/* Set value */
1582 		xfdashboard_label_set_gicon(XFDASHBOARD_LABEL(priv->title), inIcon);
1583 
1584 		/* Notify about property change */
1585 		g_object_notify_by_pspec(G_OBJECT(self), XfdashboardPopupMenuProperties[PROP_TITLE_GICON]);
1586 	}
1587 }
1588 
1589 /**
1590  * xfdashboard_popup_menu_add_item:
1591  * @self: A #XfdashboardPopupMenu
1592  * @inMenuItem: A #XfdashboardPopupMenuItem to add to pop-up menu
1593  *
1594  * Adds the actor @inMenuItem to end of pop-up menu.
1595  *
1596  * If menu item actor implements the #XfdashboardStylable interface the CSS class
1597  * popup-menu-item will be added.
1598  *
1599  * Return value: Returns index where item was inserted at or -1 if it failed.
1600  */
xfdashboard_popup_menu_add_item(XfdashboardPopupMenu * self,XfdashboardPopupMenuItem * inMenuItem)1601 gint xfdashboard_popup_menu_add_item(XfdashboardPopupMenu *self,
1602 										XfdashboardPopupMenuItem *inMenuItem)
1603 {
1604 	return(xfdashboard_popup_menu_insert_item(self, inMenuItem, -1));
1605 }
1606 
1607 /**
1608  * xfdashboard_popup_menu_insert_item:
1609  * @self: A #XfdashboardPopupMenu
1610  * @inMenuItem: A #XfdashboardPopupMenuItem to add to pop-up menu
1611  * @inIndex: The position where to insert this item at
1612  *
1613  * Inserts the actor @inMenuItem at position @inIndex into pop-up menu.
1614  *
1615  * If position @inIndex is greater than the number of menu items in @self or is
1616  * less than 0, then the menu item actor @inMenuItem is added to end to
1617  * pop-up menu.
1618  *
1619  * If menu item actor implements the #XfdashboardStylable interface the CSS class
1620  * popup-menu-item will be added.
1621  *
1622  * Return value: Returns index where item was inserted at or -1 if it failed.
1623  */
xfdashboard_popup_menu_insert_item(XfdashboardPopupMenu * self,XfdashboardPopupMenuItem * inMenuItem,gint inIndex)1624 gint xfdashboard_popup_menu_insert_item(XfdashboardPopupMenu *self,
1625 										XfdashboardPopupMenuItem *inMenuItem,
1626 										gint inIndex)
1627 {
1628 	XfdashboardPopupMenuPrivate		*priv;
1629 
1630 	g_return_val_if_fail(XFDASHBOARD_IS_POPUP_MENU(self), -1);
1631 	g_return_val_if_fail(XFDASHBOARD_IS_POPUP_MENU_ITEM(inMenuItem), -1);
1632 	g_return_val_if_fail(clutter_actor_get_parent(CLUTTER_ACTOR(inMenuItem))==NULL, -1);
1633 
1634 	priv=self->priv;
1635 
1636 	/* Insert menu item actor to container at requested position */
1637 	clutter_actor_insert_child_at_index(priv->itemsContainer, CLUTTER_ACTOR(inMenuItem), inIndex);
1638 
1639 	/* Add CSS class 'popup-menu-item' to newly added menu item */
1640 	if(XFDASHBOARD_IS_STYLABLE(inMenuItem))
1641 	{
1642 		xfdashboard_stylable_add_class(XFDASHBOARD_STYLABLE(inMenuItem), "popup-menu-item");
1643 	}
1644 
1645 	/* Connect signal to get notified when user made a selection to cancel pop-up
1646 	 * menu but ensure that it is called nearly at last because the pop-up menu
1647 	 * could be configured to get destroyed automatically when user selected an
1648 	 * item (or cancelled the menu). In this case other signal handler may not be
1649 	 * called if pop-up menu's signal handler is called before. By calling it at
1650 	 * last all other normally connected signal handlers will get be called.
1651 	 */
1652 	g_signal_connect_data(inMenuItem,
1653 							"activated",
1654 							G_CALLBACK(_xfdashboard_popup_menu_on_menu_item_activated),
1655 							self,
1656 							NULL,
1657 							G_CONNECT_AFTER | G_CONNECT_SWAPPED);
1658 
1659 	/* Emit signal */
1660 	g_signal_emit(self, XfdashboardPopupMenuSignals[SIGNAL_ITEM_ADDED], 0, inMenuItem);
1661 
1662 	/* Get index where menu item actor was inserted at */
1663 	return(xfdashboard_popup_menu_get_item_index(self, inMenuItem));
1664 }
1665 
1666 /**
1667  * xfdashboard_popup_menu_move_item:
1668  * @self: A #XfdashboardPopupMenu
1669  * @inMenuItem: A #XfdashboardPopupMenuItem menu item to move
1670  * @inIndex: The position where to insert this item at
1671  *
1672  * Moves the actor @inMenuItem to position @inIndex at pop-up menu @self. If position
1673  * @inIndex is greater than the number of menu items in @self or is less than 0,
1674  * then the menu item actor @inMenuItem is added to end to pop-up menu.
1675  *
1676  * Return value: Returns %TRUE if moving menu item was successful, otherwise %FALSE.
1677  */
xfdashboard_popup_menu_move_item(XfdashboardPopupMenu * self,XfdashboardPopupMenuItem * inMenuItem,gint inIndex)1678 gboolean xfdashboard_popup_menu_move_item(XfdashboardPopupMenu *self,
1679 											XfdashboardPopupMenuItem *inMenuItem,
1680 											gint inIndex)
1681 {
1682 	XfdashboardPopupMenuPrivate		*priv;
1683 
1684 	g_return_val_if_fail(XFDASHBOARD_IS_POPUP_MENU(self), FALSE);
1685 	g_return_val_if_fail(XFDASHBOARD_IS_POPUP_MENU_ITEM(inMenuItem), FALSE);
1686 
1687 	priv=self->priv;
1688 
1689 	/* Check if menu item is really part of this pop-up menu */
1690 	if(!_xfdashboard_popup_menu_contains_menu_item(self, inMenuItem))
1691 	{
1692 		g_warning("%s is not a child of %s and cannot be moved",
1693 					G_OBJECT_TYPE_NAME(inMenuItem),
1694 					G_OBJECT_TYPE_NAME(self));
1695 		return(FALSE);
1696 	}
1697 
1698 	/* Move menu item actor to new position */
1699 	g_object_ref(inMenuItem);
1700 	clutter_actor_remove_child(priv->itemsContainer, CLUTTER_ACTOR(inMenuItem));
1701 	clutter_actor_insert_child_at_index(priv->itemsContainer, CLUTTER_ACTOR(inMenuItem), inIndex);
1702 	g_object_unref(inMenuItem);
1703 
1704 	/* If we get here moving menu item actor was successful */
1705 	return(TRUE);
1706 }
1707 
1708 /**
1709  * xfdashboard_popup_menu_get_item:
1710  * @self: A #XfdashboardPopupMenu
1711  * @inIndex: The position whose menu item to get
1712  *
1713  * Returns the menu item actor at position @inIndex at pop-up menu @self.
1714  *
1715  * Return value: Returns #XfdashboardPopupMenuItem of the menu item at position @inIndex or
1716  *   %NULL in case of errors or if index is out of range that means it is greater
1717  *   than the number of menu items in @self or is less than 0.
1718  */
xfdashboard_popup_menu_get_item(XfdashboardPopupMenu * self,gint inIndex)1719 XfdashboardPopupMenuItem* xfdashboard_popup_menu_get_item(XfdashboardPopupMenu *self, gint inIndex)
1720 {
1721 	XfdashboardPopupMenuPrivate		*priv;
1722 	ClutterActor					*menuItem;
1723 
1724 	g_return_val_if_fail(XFDASHBOARD_IS_POPUP_MENU(self), NULL);
1725 	g_return_val_if_fail(inIndex>=0 && inIndex<clutter_actor_get_n_children(self->priv->itemsContainer), NULL);
1726 
1727 	priv=self->priv;
1728 
1729 	/* Get and return child at requested position at items container */
1730 	menuItem=clutter_actor_get_child_at_index(priv->itemsContainer, inIndex);
1731 	return(XFDASHBOARD_POPUP_MENU_ITEM(menuItem));
1732 }
1733 
1734 /**
1735  * xfdashboard_popup_menu_get_item_index:
1736  * @self: A #XfdashboardPopupMenu
1737  * @inMenuItem: The #XfdashboardPopupMenuItem menu item whose index to lookup
1738  *
1739  * Returns the position for menu item actor @inMenuItem of pop-up menu @self.
1740  *
1741  * Return value: Returns the position of the menu item or -1 in case of errors
1742  *   or if pop-up menu does not have the menu item
1743  */
xfdashboard_popup_menu_get_item_index(XfdashboardPopupMenu * self,XfdashboardPopupMenuItem * inMenuItem)1744 gint xfdashboard_popup_menu_get_item_index(XfdashboardPopupMenu *self, XfdashboardPopupMenuItem *inMenuItem)
1745 {
1746 	XfdashboardPopupMenuPrivate		*priv;
1747 	gint							index;
1748 	ClutterActorIter				iter;
1749 	ClutterActor					*child;
1750 
1751 	g_return_val_if_fail(XFDASHBOARD_IS_POPUP_MENU(self), -1);
1752 	g_return_val_if_fail(XFDASHBOARD_IS_POPUP_MENU_ITEM(inMenuItem), -1);
1753 
1754 	priv=self->priv;
1755 
1756 	/* Iterate through menu item and return the index if the requested one was found */
1757 	index=0;
1758 
1759 	clutter_actor_iter_init(&iter, priv->itemsContainer);
1760 	while(clutter_actor_iter_next(&iter, &child))
1761 	{
1762 		/* If this child is the one we are looking for return index now */
1763 		if(child==CLUTTER_ACTOR(inMenuItem)) return(index);
1764 
1765 		/* Increase index */
1766 		index++;
1767 	}
1768 
1769 	/* If we get here we did not find the requested menu item */
1770 	return(-1);
1771 }
1772 
1773 /**
1774  * xfdashboard_popup_menu_remove_item:
1775  * @self: A #XfdashboardPopupMenu
1776  * @inMenuItem: A #XfdashboardPopupMenuItem menu item to remove
1777  *
1778  * Removes the actor @inMenuItem from pop-up menu @self. When the pop-up menu holds
1779  * the last reference on that menu item actor then it will be destroyed otherwise
1780  * it will only be removed from pop-up menu.
1781  *
1782  * If the removed menu item actor implements the #XfdashboardStylable interface
1783  * the CSS class popup-menu-item will be removed also.
1784  *
1785  * Return value: Returns %TRUE if moving menu item was successful, otherwise %FALSE.
1786  */
xfdashboard_popup_menu_remove_item(XfdashboardPopupMenu * self,XfdashboardPopupMenuItem * inMenuItem)1787 gboolean xfdashboard_popup_menu_remove_item(XfdashboardPopupMenu *self, XfdashboardPopupMenuItem *inMenuItem)
1788 {
1789 	XfdashboardPopupMenuPrivate		*priv;
1790 
1791 	g_return_val_if_fail(XFDASHBOARD_IS_POPUP_MENU(self), FALSE);
1792 	g_return_val_if_fail(XFDASHBOARD_IS_POPUP_MENU_ITEM(inMenuItem), FALSE);
1793 
1794 	priv=self->priv;
1795 
1796 	/* Check if menu item is really part of this pop-up menu */
1797 	if(!_xfdashboard_popup_menu_contains_menu_item(self, inMenuItem))
1798 	{
1799 		g_warning("%s is not a child of %s and cannot be removed",
1800 					G_OBJECT_TYPE_NAME(inMenuItem),
1801 					G_OBJECT_TYPE_NAME(self));
1802 		return(FALSE);
1803 	}
1804 
1805 	/* Take extra reference on actor to remove to keep it alive while working with it */
1806 	g_object_ref(inMenuItem);
1807 
1808 	/* Remove CSS class 'popup-menu-item' from menu item going to be removed */
1809 	if(XFDASHBOARD_IS_STYLABLE(inMenuItem))
1810 	{
1811 		xfdashboard_stylable_remove_class(XFDASHBOARD_STYLABLE(inMenuItem), "popup-menu-item");
1812 	}
1813 
1814 	/* Remove menu item actor from pop-up menu */
1815 	clutter_actor_remove_child(priv->itemsContainer, CLUTTER_ACTOR(inMenuItem));
1816 
1817 	/* Disconnect signal handlers from removed menu item */
1818 	g_signal_handlers_disconnect_by_func(inMenuItem, G_CALLBACK(_xfdashboard_popup_menu_on_menu_item_activated), self);
1819 
1820 	/* Emit signal */
1821 	g_signal_emit(self, XfdashboardPopupMenuSignals[SIGNAL_ITEM_REMOVED], 0, inMenuItem);
1822 
1823 	/* Release extra reference on actor to took to keep it alive */
1824 	g_object_unref(inMenuItem);
1825 
1826 	/* If we get here we removed the menu item actor successfully */
1827 	return(TRUE);
1828 }
1829 
1830 /**
1831  * xfdashboard_popup_menu_activate:
1832  * @self: A #XfdashboardPopupMenu
1833  *
1834  * Displays the pop-up menu at @self and makes it available for selection.
1835  *
1836  * This actor will gain the focus automatically and will select the first menu item.
1837  */
xfdashboard_popup_menu_activate(XfdashboardPopupMenu * self)1838 void xfdashboard_popup_menu_activate(XfdashboardPopupMenu *self)
1839 {
1840 	XfdashboardPopupMenuPrivate			*priv;
1841 	GdkDisplay							*display;
1842 #if GTK_CHECK_VERSION(3, 20, 0)
1843 	GdkSeat								*seat;
1844 #else
1845 	GdkDeviceManager					*deviceManager;
1846 #endif
1847 	GdkDevice							*pointerDevice;
1848 	gint								pointerX, pointerY;
1849 	XfdashboardWindowTrackerMonitor		*monitor;
1850 	gint								monitorX, monitorY, monitorWidth, monitorHeight;
1851 	gfloat								x, y, w, h;
1852 
1853 	g_return_if_fail(XFDASHBOARD_IS_POPUP_MENU(self));
1854 
1855 	priv=self->priv;
1856 
1857 	/* If this actor is already active, then do nothing */
1858 	if(priv->isActive) return;
1859 
1860 	/* Move popup menu next to pointer similar to tooltips but keep it on current monitor */
1861 	display=gdk_display_get_default();
1862 #if GTK_CHECK_VERSION(3, 20, 0)
1863 	seat=gdk_display_get_default_seat(display);
1864 	pointerDevice=gdk_seat_get_pointer(seat);
1865 #else
1866 	deviceManager=gdk_display_get_device_manager(display);
1867 	pointerDevice=gdk_device_manager_get_client_pointer(deviceManager);
1868 #endif
1869 	gdk_device_get_position(pointerDevice, NULL, &pointerX, &pointerY);
1870 	XFDASHBOARD_DEBUG(self, ACTOR,
1871 						"Pointer is at position %d,%d",
1872 						pointerX, pointerY);
1873 
1874 	monitor=xfdashboard_window_tracker_get_monitor_by_position(priv->windowTracker, pointerX, pointerY);
1875 	if(!monitor)
1876 	{
1877 		/* Show error message */
1878 		g_critical("Could not find monitor at pointer position %d,%d",
1879 					pointerX,
1880 					pointerY);
1881 
1882 		return;
1883 	}
1884 
1885 	xfdashboard_window_tracker_monitor_get_geometry(monitor, &monitorX, &monitorY, &monitorWidth, &monitorHeight);
1886 	XFDASHBOARD_DEBUG(self, ACTOR,
1887 						"Pointer is on monitor %d with position at %d,%d and size of %dx%d",
1888 						xfdashboard_window_tracker_monitor_get_number(monitor),
1889 						monitorX, monitorY,
1890 						monitorWidth, monitorHeight);
1891 
1892 	x=pointerX;
1893 	y=pointerY;
1894 	clutter_actor_get_size(CLUTTER_ACTOR(self), &w, &h);
1895 	if(x<monitorX) x=monitorX;
1896 	if((x+w)>=(monitorX+monitorWidth)) x=(monitorX+monitorWidth)-w;
1897 	if(y<monitorY) y=monitorY;
1898 	if((y+h)>=(monitorY+monitorHeight)) y=(monitorY+monitorHeight)-h;
1899 	clutter_actor_set_position(CLUTTER_ACTOR(self), floor(x), floor(y));
1900 
1901 	/* Now start capturing event in "capture" phase to stop propagating event to
1902 	 * other actors except this one while popup menu is active.
1903 	 */
1904 	priv->capturedEventSignalID=
1905 		g_signal_connect_swapped(priv->stage,
1906 									"captured-event",
1907 									G_CALLBACK(_xfdashboard_popup_menu_on_captured_event),
1908 									self);
1909 
1910 	/* Show popup menu */
1911 	clutter_actor_show(CLUTTER_ACTOR(self));
1912 
1913 	/* Set flag that this pop-up menu is now active otherwise we cannot focus
1914 	 * this actor.
1915 	 */
1916 	priv->isActive=TRUE;
1917 
1918 	/* Make popup menu focusable as this also marks this actor to be active */
1919 	xfdashboard_actor_set_can_focus(XFDASHBOARD_ACTOR(self), TRUE);
1920 
1921 	/* Move focus to popup menu but remember the actor which has current focus */
1922 	priv->oldFocusable=xfdashboard_focus_manager_get_focus(priv->focusManager);
1923 	if(priv->oldFocusable) g_object_add_weak_pointer(G_OBJECT(priv->oldFocusable), &priv->oldFocusable);
1924 
1925 	xfdashboard_focus_manager_set_focus(priv->focusManager, XFDASHBOARD_FOCUSABLE(self));
1926 }
1927 
1928 /**
1929  * xfdashboard_popup_menu_cancel:
1930  * @self: A #XfdashboardPopupMenu
1931  *
1932  * Hides the pop-up menu if displayed and stops making it available for selection.
1933  *
1934  * The actor tries to refocus the actor which had the focus before this pop-up
1935  * menu was displayed. If that actor cannot be focused it move the focus to the
1936  * next focusable actor.
1937  */
xfdashboard_popup_menu_cancel(XfdashboardPopupMenu * self)1938 void xfdashboard_popup_menu_cancel(XfdashboardPopupMenu *self)
1939 {
1940 	XfdashboardPopupMenuPrivate			*priv;
1941 
1942 	g_return_if_fail(XFDASHBOARD_IS_POPUP_MENU(self));
1943 
1944 	priv=self->priv;
1945 
1946 	/* Do nothing if pop-up menu is not active */
1947 	if(!priv->isActive) return;
1948 
1949 	/* Unset flag that pop-up menu is active to prevent recursive calls on this
1950 	 * function, e.g. if pop-up menu is cancelled because the object instance
1951 	 * is disposed.
1952 	 */
1953 	priv->isActive=FALSE;
1954 
1955 	/* Stop capturing events in "capture" phase as this popup menu actor will not
1956 	 * be active anymore.
1957 	 */
1958 	if(priv->capturedEventSignalID)
1959 	{
1960 		g_signal_handler_disconnect(priv->stage, priv->capturedEventSignalID);
1961 		priv->capturedEventSignalID=0;
1962 	}
1963 
1964 	/* Move focus to actor which had the focus previously */
1965 	if(priv->oldFocusable)
1966 	{
1967 		/* Remove weak pointer from previously focused actor */
1968 		g_object_remove_weak_pointer(G_OBJECT(priv->oldFocusable), &priv->oldFocusable);
1969 
1970 		/* Move focus to previous focussed actor */
1971 		xfdashboard_focus_manager_set_focus(priv->focusManager, priv->oldFocusable);
1972 
1973 		/* Forget previous focussed actor */
1974 		priv->oldFocusable=NULL;
1975 	}
1976 
1977 	/* Hide popup menu */
1978 	clutter_actor_hide(CLUTTER_ACTOR(self));
1979 
1980 	/* Reset popup menu to be not focusable as this also marks this actor is
1981 	 * not active anymore.
1982 	 */
1983 	xfdashboard_actor_set_can_focus(XFDASHBOARD_ACTOR(self), FALSE);
1984 
1985 	/* Destroy this pop-up menu actor when destroy-on-cancel was enabled */
1986 	if(priv->destroyOnCancel)
1987 	{
1988 		xfdashboard_actor_destroy(CLUTTER_ACTOR(self));
1989 	}
1990 }
1991