gnome-shell/src/st/st-focus-manager.c
Dan Winship ef6cce8988 popupMenu, panelMenu: split up panel and non-panel keynav
PopupMenuManager was pretending that it knew nothing about the menu's
sourceActors, while also trying to handle keynav between them. This
was a big mess, and resulted in bugs in navigation between panel menus
and the Activities button, and it totally gets in the way when trying
to add keynav to the dash (whose menu sources are arranged vertically
rather than horizontally).

Fix this up by moving the panel-specific parts to PanelMenuButton
instead.

https://bugzilla.gnome.org/show_bug.cgi?id=641253
2011-03-07 10:57:45 -05:00

225 lines
5.9 KiB
C

/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
/*
* st-focus-manager.c: Keyboard focus manager
*
* Copyright 2010 Red Hat, Inc.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as
* published by the Free Software Foundation, either version 2.1 of
* the License, or (at your option) any later version.
*
* This program is distributed in the hope it will be useful, but WITHOUT ANY
* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
* FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for
* more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
/**
* SECTION:st-focus-manager
* @short_description: Keyboard focus management
*
* #StFocusManager handles keyboard focus for all actors on the stage.
*/
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include <clutter/clutter.h>
#include "st-focus-manager.h"
#define ST_FOCUS_MANAGER_GET_PRIVATE(obj) (G_TYPE_INSTANCE_GET_PRIVATE ((obj), ST_TYPE_FOCUS_MANAGER, StFocusManagerPrivate))
struct _StFocusManagerPrivate
{
GHashTable *groups;
};
G_DEFINE_TYPE (StFocusManager, st_focus_manager, G_TYPE_OBJECT)
static void
st_focus_manager_dispose (GObject *object)
{
StFocusManager *manager = ST_FOCUS_MANAGER (object);
if (manager->priv->groups)
{
g_hash_table_destroy (manager->priv->groups);
manager->priv->groups = NULL;
}
G_OBJECT_CLASS (st_focus_manager_parent_class)->dispose (object);
}
static void
st_focus_manager_class_init (StFocusManagerClass *klass)
{
GObjectClass *object_class = G_OBJECT_CLASS (klass);
g_type_class_add_private (klass, sizeof (StFocusManagerPrivate));
object_class->dispose = st_focus_manager_dispose;
}
static void
st_focus_manager_init (StFocusManager *manager)
{
manager->priv = ST_FOCUS_MANAGER_GET_PRIVATE (manager);
manager->priv->groups = g_hash_table_new (NULL, NULL);
}
static gboolean
st_focus_manager_stage_event (ClutterActor *stage,
ClutterEvent *event,
gpointer user_data)
{
StFocusManager *manager = user_data;
GtkDirectionType direction;
gboolean wrap_around = FALSE;
ClutterActor *focused, *group;
if (event->type != CLUTTER_KEY_PRESS)
return FALSE;
switch (event->key.keyval)
{
case CLUTTER_KEY_Up:
direction = GTK_DIR_UP;
break;
case CLUTTER_KEY_Down:
direction = GTK_DIR_DOWN;
break;
case CLUTTER_KEY_Left:
direction = GTK_DIR_LEFT;
break;
case CLUTTER_KEY_Right:
direction = GTK_DIR_RIGHT;
break;
case CLUTTER_KEY_Tab:
if (event->key.modifier_state & CLUTTER_SHIFT_MASK)
direction = GTK_DIR_TAB_BACKWARD;
else
direction = GTK_DIR_TAB_FORWARD;
wrap_around = TRUE;
break;
case CLUTTER_KEY_ISO_Left_Tab:
direction = GTK_DIR_TAB_BACKWARD;
wrap_around = TRUE;
break;
default:
return FALSE;
}
focused = clutter_stage_get_key_focus (CLUTTER_STAGE (stage));
if (!focused)
return FALSE;
for (group = focused; group != stage; group = clutter_actor_get_parent (group))
{
if (g_hash_table_lookup (manager->priv->groups, group))
{
return st_widget_navigate_focus (ST_WIDGET (group), focused,
direction, wrap_around);
}
}
return FALSE;
}
/**
* st_focus_manager_get_for_stage:
* @stage: a #ClutterStage
*
* Gets the #StFocusManager for @stage, creating it if necessary.
*
* Return value: (transfer none): the focus manager for @stage
*/
StFocusManager *
st_focus_manager_get_for_stage (ClutterStage *stage)
{
StFocusManager *manager;
manager = g_object_get_data (G_OBJECT (stage), "st-focus-manager");
if (!manager)
{
manager = g_object_new (ST_TYPE_FOCUS_MANAGER, NULL);
g_object_set_data_full (G_OBJECT (stage), "st-focus-manager",
manager, g_object_unref);
g_signal_connect (stage, "event",
G_CALLBACK (st_focus_manager_stage_event), manager);
}
return manager;
}
static void
remove_destroyed_group (ClutterActor *actor,
gpointer user_data)
{
StFocusManager *manager = user_data;
st_focus_manager_remove_group (manager, ST_WIDGET (actor));
}
/**
* st_focus_manager_add_group:
* @manager: the #StFocusManager
* @root: the root container of the group
*
* Adds a new focus group to @manager. When the focus is in an actor
* that is a descendant of @root, @manager will handle moving focus
* from one actor to another within @root based on keyboard events.
*/
void
st_focus_manager_add_group (StFocusManager *manager,
StWidget *root)
{
g_signal_connect (root, "destroy",
G_CALLBACK (remove_destroyed_group),
manager);
g_hash_table_insert (manager->priv->groups, root, GINT_TO_POINTER (1));
}
/**
* st_focus_manager_remove_group:
* @manager: the #StFocusManager
* @root: the root container of the group
*
* Removes the group rooted at @root from @manager
*/
void
st_focus_manager_remove_group (StFocusManager *manager,
StWidget *root)
{
g_hash_table_remove (manager->priv->groups, root);
}
/**
* st_focus_manager_get_group:
* @manager: the #StFocusManager
* @widget: an #StWidget
*
* Checks if @widget is inside a focus group, and if so, returns
* the root of that group.
*
* Return value: (transfer none): the focus group root, or %NULL if
* @widget is not in a focus group
*/
StWidget *
st_focus_manager_get_group (StFocusManager *manager,
StWidget *widget)
{
ClutterActor *actor = CLUTTER_ACTOR (widget);
while (actor && !g_hash_table_lookup (manager->priv->groups, actor))
actor = clutter_actor_get_parent (actor);
return ST_WIDGET (actor);
}