6e31e59b57
When the user click+hold+release over the icon, the effect we want is for the menu to stick around. Also, allow the user to mouse over the actual windows and select them directly. If the user mouses over a window, reflect that in the menu.
299 lines
7.9 KiB
C
299 lines
7.9 KiB
C
/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
|
|
|
|
/**
|
|
* SECTION:shell-menu
|
|
* @short_description: A box which acts like a popup menu
|
|
*
|
|
* A #BigBox subclass which adds methods and signals useful for implementing
|
|
* popup-menu like actors.
|
|
*/
|
|
|
|
#include "shell-menu.h"
|
|
|
|
G_DEFINE_TYPE(ShellMenu, shell_menu, BIG_TYPE_BOX);
|
|
|
|
struct _ShellMenuPrivate {
|
|
gboolean have_grab;
|
|
|
|
gboolean released_on_source;
|
|
ClutterActor *source_actor;
|
|
|
|
ClutterActor *selected;
|
|
};
|
|
|
|
/* Signals */
|
|
enum
|
|
{
|
|
UNSELECTED,
|
|
SELECTED,
|
|
ACTIVATE,
|
|
POPDOWN,
|
|
LAST_SIGNAL
|
|
};
|
|
|
|
static guint shell_menu_signals [LAST_SIGNAL] = { 0 };
|
|
|
|
static gboolean
|
|
shell_menu_contains (ClutterContainer *container,
|
|
ClutterActor *actor)
|
|
{
|
|
while (actor != NULL && actor != (ClutterActor*)container)
|
|
{
|
|
actor = clutter_actor_get_parent (actor);
|
|
}
|
|
return actor != NULL;
|
|
}
|
|
|
|
static void
|
|
on_selected_destroy (ClutterActor *actor,
|
|
ShellMenu *box)
|
|
{
|
|
box->priv->selected = NULL;
|
|
}
|
|
|
|
static void
|
|
set_selected (ShellMenu *box,
|
|
ClutterActor *actor)
|
|
{
|
|
if (actor == box->priv->selected)
|
|
return;
|
|
if (box->priv->selected)
|
|
{
|
|
g_signal_handlers_disconnect_by_func (box->priv->selected, G_CALLBACK(on_selected_destroy), box);
|
|
g_signal_emit (G_OBJECT (box), shell_menu_signals[UNSELECTED], 0, box->priv->selected);
|
|
}
|
|
box->priv->selected = actor;
|
|
if (box->priv->selected)
|
|
{
|
|
g_signal_connect (box->priv->selected, "destroy", G_CALLBACK(on_selected_destroy), box);
|
|
g_signal_emit (G_OBJECT (box), shell_menu_signals[SELECTED], 0, box->priv->selected);
|
|
}
|
|
}
|
|
|
|
static gboolean
|
|
shell_menu_enter_event (ClutterActor *actor,
|
|
ClutterCrossingEvent *event)
|
|
{
|
|
ShellMenu *box = SHELL_MENU (actor);
|
|
|
|
if (!shell_menu_contains (CLUTTER_CONTAINER (box), event->source))
|
|
return TRUE;
|
|
|
|
if (event->source == (ClutterActor*)box)
|
|
return TRUE;
|
|
|
|
if (g_object_get_data (G_OBJECT (event->source), "shell-is-separator"))
|
|
return TRUE;
|
|
|
|
set_selected (box, event->source);
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static gboolean
|
|
shell_menu_leave_event (ClutterActor *actor,
|
|
ClutterCrossingEvent *event)
|
|
{
|
|
ShellMenu *box = SHELL_MENU (actor);
|
|
|
|
set_selected (box, NULL);
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static gboolean
|
|
shell_menu_button_release_event (ClutterActor *actor,
|
|
ClutterButtonEvent *event)
|
|
{
|
|
ShellMenu *box = SHELL_MENU (actor);
|
|
|
|
if (event->button != 1)
|
|
return FALSE;
|
|
|
|
if (box->priv->source_actor && !box->priv->released_on_source)
|
|
{
|
|
if (box->priv->source_actor == event->source ||
|
|
(CLUTTER_IS_CONTAINER (box->priv->source_actor) &&
|
|
shell_menu_contains (CLUTTER_CONTAINER (box->priv->source_actor), event->source)))
|
|
{
|
|
/* On the next release, we want to pop down the menu regardless */
|
|
box->priv->released_on_source = TRUE;
|
|
return TRUE;
|
|
}
|
|
}
|
|
|
|
shell_menu_popdown (box);
|
|
|
|
if (!shell_menu_contains (CLUTTER_CONTAINER (box), event->source))
|
|
return FALSE;
|
|
|
|
if (box->priv->selected == NULL)
|
|
return FALSE;
|
|
|
|
g_signal_emit (G_OBJECT (box), shell_menu_signals[ACTIVATE], 0, box->priv->selected);
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
void
|
|
shell_menu_popup (ShellMenu *box,
|
|
guint button,
|
|
guint32 activate_time)
|
|
{
|
|
box->priv->have_grab = TRUE;
|
|
box->priv->released_on_source = FALSE;
|
|
clutter_grab_pointer (CLUTTER_ACTOR (box));
|
|
}
|
|
|
|
void
|
|
shell_menu_popdown (ShellMenu *box)
|
|
{
|
|
if (box->priv->have_grab)
|
|
clutter_ungrab_pointer ();
|
|
clutter_actor_hide (CLUTTER_ACTOR (box));
|
|
g_signal_emit (G_OBJECT (box), shell_menu_signals[POPDOWN], 0);
|
|
}
|
|
|
|
static void
|
|
on_source_destroyed (ClutterActor *actor,
|
|
ShellMenu *box)
|
|
{
|
|
box->priv->source_actor = NULL;
|
|
}
|
|
|
|
/**
|
|
* shell_menu_set_persistent_source:
|
|
* @box:
|
|
* @source: Actor to use as menu origin
|
|
*
|
|
* This function changes the menu behavior on button release. Normally
|
|
* when the mouse is released anywhere, the menu "pops down"; when this
|
|
* function is called, if the mouse is released over the source actor,
|
|
* the menu stays.
|
|
*
|
|
* The given @source actor must be reactive for this function to work.
|
|
*/
|
|
void
|
|
shell_menu_set_persistent_source (ShellMenu *box,
|
|
ClutterActor *source)
|
|
{
|
|
if (box->priv->source_actor)
|
|
{
|
|
g_signal_handlers_disconnect_by_func (G_OBJECT (box->priv->source_actor),
|
|
G_CALLBACK (on_source_destroyed),
|
|
box);
|
|
}
|
|
box->priv->source_actor = source;
|
|
if (box->priv->source_actor)
|
|
{
|
|
g_signal_connect (G_OBJECT (box->priv->source_actor),
|
|
"destroy",
|
|
G_CALLBACK (on_source_destroyed),
|
|
box);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* shell_menu_append_separator:
|
|
* @box:
|
|
* @separator: An actor which functions as a menu separator
|
|
* @flags: Packing flags
|
|
*
|
|
* Actors added to the menu with default functions are treated like
|
|
* menu items; this function will add an actor that should instead
|
|
* be treated like a menu separator. The current practical effect
|
|
* is that the separators will not be selectable.
|
|
*/
|
|
void
|
|
shell_menu_append_separator (ShellMenu *box,
|
|
ClutterActor *separator,
|
|
BigBoxPackFlags flags)
|
|
{
|
|
g_object_set_data (G_OBJECT (separator), "shell-is-separator", GUINT_TO_POINTER(TRUE));
|
|
big_box_append (BIG_BOX (box), separator, flags);
|
|
}
|
|
|
|
static void
|
|
shell_menu_class_init (ShellMenuClass *klass)
|
|
{
|
|
GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
|
|
ClutterActorClass *actor_class = CLUTTER_ACTOR_CLASS (klass);
|
|
|
|
actor_class->enter_event = shell_menu_enter_event;
|
|
actor_class->leave_event = shell_menu_leave_event;
|
|
actor_class->button_release_event = shell_menu_button_release_event;
|
|
|
|
/**
|
|
* ShellMenu::unselected
|
|
* @box: The #ShellMenu
|
|
* @actor: The previously hovered-over menu item
|
|
*
|
|
* This signal is emitted when a menu item transitions to
|
|
* an unselected state.
|
|
*/
|
|
shell_menu_signals[UNSELECTED] =
|
|
g_signal_new ("unselected",
|
|
G_TYPE_FROM_CLASS (klass),
|
|
G_SIGNAL_RUN_LAST,
|
|
0,
|
|
NULL, NULL,
|
|
g_cclosure_marshal_VOID__OBJECT,
|
|
G_TYPE_NONE, 1, CLUTTER_TYPE_ACTOR);
|
|
|
|
/**
|
|
* ShellMenu::selected
|
|
* @box: The #ShellMenu
|
|
* @actor: The hovered-over menu item
|
|
*
|
|
* This signal is emitted when a menu item is in a selected state.
|
|
*/
|
|
shell_menu_signals[SELECTED] =
|
|
g_signal_new ("selected",
|
|
G_TYPE_FROM_CLASS (klass),
|
|
G_SIGNAL_RUN_LAST,
|
|
0,
|
|
NULL, NULL,
|
|
g_cclosure_marshal_VOID__OBJECT,
|
|
G_TYPE_NONE, 1, CLUTTER_TYPE_ACTOR);
|
|
|
|
/**
|
|
* ShellMenu::activate
|
|
* @box: The #ShellMenu
|
|
* @actor: The clicked menu item
|
|
*
|
|
* This signal is emitted when a menu item is selected.
|
|
*/
|
|
shell_menu_signals[ACTIVATE] =
|
|
g_signal_new ("activate",
|
|
G_TYPE_FROM_CLASS (klass),
|
|
G_SIGNAL_RUN_LAST,
|
|
0,
|
|
NULL, NULL,
|
|
g_cclosure_marshal_VOID__OBJECT,
|
|
G_TYPE_NONE, 1, CLUTTER_TYPE_ACTOR);
|
|
|
|
/**
|
|
* ShellMenu::popdown
|
|
* @box: The #ShellMenu
|
|
*
|
|
* This signal is emitted when the menu is removed from the display.
|
|
*/
|
|
shell_menu_signals[POPDOWN] =
|
|
g_signal_new ("popdown",
|
|
G_TYPE_FROM_CLASS (klass),
|
|
G_SIGNAL_RUN_LAST,
|
|
0,
|
|
NULL, NULL,
|
|
g_cclosure_marshal_VOID__OBJECT,
|
|
G_TYPE_NONE, 0);
|
|
|
|
g_type_class_add_private (gobject_class, sizeof (ShellMenuPrivate));
|
|
}
|
|
|
|
static void
|
|
shell_menu_init (ShellMenu *self)
|
|
{
|
|
self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self, SHELL_TYPE_MENU,
|
|
ShellMenuPrivate);
|
|
}
|