/*
* Copyright (C) 2010 Canonical Ltd
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 3 as
* published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*
* Authored by: Neil Jagdish Patel
* Rodrigo Moya
*/
#if HAVE_CONFIG_H
#include
#endif
#include "panel-service.h"
#include
#include
#include
G_DEFINE_TYPE (PanelService, panel_service, G_TYPE_OBJECT);
#define GET_PRIVATE(o) \
(G_TYPE_INSTANCE_GET_PRIVATE ((o), PANEL_TYPE_SERVICE, PanelServicePrivate))
#define NOTIFY_TIMEOUT 80
#define N_TIMEOUT_SLOTS 50
static PanelService *static_service = NULL;
struct _PanelServicePrivate
{
GSList *indicators;
GHashTable *id2entry_hash;
GHashTable *entry2indicator_hash;
guint initial_sync_id;
gint32 timeouts[N_TIMEOUT_SLOTS];
IndicatorObjectEntry *last_entry;
GtkMenu *last_menu;
guint32 last_menu_id;
guint32 last_menu_move_id;
gint32 last_x;
gint32 last_y;
guint32 last_menu_button;
gint last_menu_x;
gint last_menu_y;
};
/* Globals */
static gboolean suppress_signals = FALSE;
enum
{
ENTRY_ACTIVATED = 0,
RE_SYNC,
ACTIVE_MENU_POINTER_MOTION,
ENTRY_ACTIVATE_REQUEST,
LAST_SIGNAL
};
enum
{
SYNC_WAITING = -1,
SYNC_NEUTRAL = 0,
};
static guint32 _service_signals[LAST_SIGNAL] = { 0 };
static gchar * indicator_order[] = {
"libappmenu.so",
"libapplication.so",
"libsoundmenu.so",
"libnetwork.so",
"libnetworkmenu.so",
"libmessaging.so",
"libdatetime.so",
"libme.so",
"libsession.so",
NULL
};
/* Forwards */
static void load_indicator (PanelService *self,
IndicatorObject *object,
const gchar *_name);
static void load_indicators (PanelService *self);
static void sort_indicators (PanelService *self);
static GdkFilterReturn event_filter (GdkXEvent *ev,
GdkEvent *gev,
PanelService *self);
/*
* GObject stuff
*/
static void
panel_service_class_dispose (GObject *object)
{
PanelServicePrivate *priv = PANEL_SERVICE (object)->priv;
gint i;
g_hash_table_destroy (priv->id2entry_hash);
g_hash_table_destroy (priv->entry2indicator_hash);
gdk_window_remove_filter (NULL, (GdkFilterFunc)event_filter, object);
if (priv->initial_sync_id)
{
g_source_remove (priv->initial_sync_id);
priv->initial_sync_id = 0;
}
for (i = 0; i < N_TIMEOUT_SLOTS; i++)
{
if (priv->timeouts[i] > 0)
{
g_source_remove (priv->timeouts[i]);
priv->timeouts[i] = 0;
}
}
G_OBJECT_CLASS (panel_service_parent_class)->dispose (object);
}
static void
panel_service_class_init (PanelServiceClass *klass)
{
GObjectClass *obj_class = G_OBJECT_CLASS (klass);
obj_class->dispose = panel_service_class_dispose;
/* Signals */
_service_signals[ENTRY_ACTIVATED] =
g_signal_new ("entry-activated",
G_OBJECT_CLASS_TYPE (obj_class),
G_SIGNAL_RUN_LAST,
0,
NULL, NULL,
g_cclosure_marshal_VOID__STRING,
G_TYPE_NONE, 1, G_TYPE_STRING);
_service_signals[RE_SYNC] =
g_signal_new ("re-sync",
G_OBJECT_CLASS_TYPE (obj_class),
G_SIGNAL_RUN_LAST,
0,
NULL, NULL,
g_cclosure_marshal_VOID__STRING,
G_TYPE_NONE, 1, G_TYPE_STRING);
_service_signals[ACTIVE_MENU_POINTER_MOTION] =
g_signal_new ("active-menu-pointer-motion",
G_OBJECT_CLASS_TYPE (obj_class),
G_SIGNAL_RUN_LAST,
0,
NULL, NULL,
g_cclosure_marshal_VOID__VOID,
G_TYPE_NONE, 0);
_service_signals[ENTRY_ACTIVATE_REQUEST] =
g_signal_new ("entry-activate-request",
G_OBJECT_CLASS_TYPE (obj_class),
G_SIGNAL_RUN_LAST,
0,
NULL, NULL,
g_cclosure_marshal_VOID__STRING,
G_TYPE_NONE, 1, G_TYPE_STRING);
g_type_class_add_private (obj_class, sizeof (PanelServicePrivate));
}
static GdkFilterReturn
event_filter (GdkXEvent *ev, GdkEvent *gev, PanelService *self)
{
XEvent *e = (XEvent *)ev;
GdkFilterReturn ret = GDK_FILTER_CONTINUE;
if (!PANEL_IS_SERVICE (self))
return ret;
if (!GTK_IS_WIDGET (self->priv->last_menu))
return ret;
if (e->type == 5 && self->priv->last_menu_button != 0) //FocusChange
{
gint x=0, y=0, width=0, height=0, depth=0, x_root=0, y_root=0;
GdkWindow *window = gtk_widget_get_window (GTK_WIDGET (self->priv->last_menu));
if (window == NULL)
return GDK_FILTER_CONTINUE;
Window xwindow = gdk_x11_drawable_get_xid (GDK_DRAWABLE (window));
if (xwindow == 0)
return GDK_FILTER_CONTINUE;
Window root = 0, child = 0;
int win_x=0, win_y = 0;
guint32 mask_return = 0;
XQueryPointer (gdk_x11_display_get_xdisplay (gdk_display_get_default ()),
xwindow,
&root,
&child,
&x_root,
&y_root,
&win_x,
&win_y,
&mask_return);
gdk_window_get_geometry (window, &x, &y, &width, &height, &depth);
gdk_window_get_origin (window, &x, &y);
if (x_root > x
&& x_root < x + width
&& y_root > y
&& y_root < y + height)
{
ret = GDK_FILTER_CONTINUE;
}
else
{
ret = GDK_FILTER_REMOVE;
}
self->priv->last_menu_button = 0;
}
// FIXME: THIS IS HORRIBLE AND WILL BE CHANGED BEFORE RELEASE
// ITS A WORKAROUND SO I CAN TEST THE PANEL SCRUBBING
// DONT HATE ME
// --------------------------------------------------------------------------
else if (e->type == 6)
{
int x_root=0, y_root=0;
GdkWindow *window = gtk_widget_get_window (GTK_WIDGET (self->priv->last_menu));
Window xwindow = gdk_x11_drawable_get_xid (GDK_DRAWABLE (window));
Window root = 0, child = 0;
int win_x=0, win_y = 0;
guint32 mask_return = 0;
XQueryPointer (gdk_x11_display_get_xdisplay (gdk_display_get_default ()),
xwindow,
&root,
&child,
&x_root,
&y_root,
&win_x,
&win_y,
&mask_return);
self->priv->last_menu_x = x_root;
self->priv->last_menu_y = y_root;
if (y_root <= self->priv->last_y)
{
g_signal_emit (self, _service_signals[ACTIVE_MENU_POINTER_MOTION], 0);
}
}
// /DONT HATE ME
// /FIXME
// --------------------------------------------------------------------------
return ret;
}
static gboolean
initial_resync (PanelService *self)
{
if (PANEL_IS_SERVICE (self))
{
g_signal_emit (self, _service_signals[RE_SYNC], 0, "");
self->priv->initial_sync_id = 0;
}
return FALSE;
}
static void
panel_service_init (PanelService *self)
{
PanelServicePrivate *priv;
priv = self->priv = GET_PRIVATE (self);
gdk_window_add_filter (NULL, (GdkFilterFunc)event_filter, self);
priv->id2entry_hash = g_hash_table_new_full (g_str_hash, g_str_equal,
g_free, NULL);
priv->entry2indicator_hash = g_hash_table_new (g_direct_hash, g_direct_equal);
suppress_signals = TRUE;
load_indicators (self);
sort_indicators (self);
suppress_signals = FALSE;
priv->initial_sync_id = g_idle_add ((GSourceFunc)initial_resync, self);
}
PanelService *
panel_service_get_default ()
{
if (static_service == NULL || !PANEL_IS_SERVICE (static_service))
static_service = g_object_new (PANEL_TYPE_SERVICE, NULL);
return static_service;
}
PanelService *
panel_service_get_default_with_indicators (GList *indicators)
{
PanelService *service = panel_service_get_default ();
GList *i;
for (i = indicators; i; i = i->next)
{
IndicatorObject *object = i->data;
if (INDICATOR_IS_OBJECT (object))
load_indicator (service, object, NULL);
}
return service;
}
guint
panel_service_get_n_indicators (PanelService *self)
{
g_return_val_if_fail (PANEL_IS_SERVICE (self), 0);
return g_slist_length (self->priv->indicators);
}
IndicatorObject *
panel_service_get_indicator (PanelService *self, guint position)
{
g_return_val_if_fail (PANEL_IS_SERVICE (self), NULL);
return (IndicatorObject *) g_slist_nth_data (self->priv->indicators, position);
}
/*
* Private Methods
*/
static gboolean
actually_notify_object (IndicatorObject *object)
{
PanelService *self;
PanelServicePrivate *priv;
gint position;
if (!PANEL_IS_SERVICE (static_service))
return FALSE;
if (!INDICATOR_IS_OBJECT (object))
return FALSE;
self = panel_service_get_default ();
priv = self->priv;
position = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (object), "position"));
priv->timeouts[position] = SYNC_WAITING;
if (!suppress_signals)
g_signal_emit (self, _service_signals[RE_SYNC],
0, g_object_get_data (G_OBJECT (object), "id"));
return FALSE;
}
static void
notify_object (IndicatorObject *object)
{
PanelService *self;
PanelServicePrivate *priv;
gint position;
if (suppress_signals)
return;
self = panel_service_get_default ();
priv = self->priv;
position = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (object), "position"));
if (priv->timeouts[position] == SYNC_WAITING)
{
/* No need to ping again as we're waiting for the client to sync anyway */
return;
}
else if (priv->timeouts[position] != SYNC_NEUTRAL)
{
/* We were going to signal that a sync was needed, but since there's been another change let's
* hold off a little longer so we're not flooding the client
*/
g_source_remove (priv->timeouts[position]);
}
priv->timeouts[position] = g_timeout_add (NOTIFY_TIMEOUT,
(GSourceFunc)actually_notify_object,
object);
}
static void
on_entry_property_changed (GObject *o,
GParamSpec *pspec,
IndicatorObject *object)
{
notify_object (object);
}
static void
on_entry_changed (GObject *o,
IndicatorObject *object)
{
notify_object (object);
}
static void
on_entry_added (IndicatorObject *object,
IndicatorObjectEntry *entry,
PanelService *self)
{
PanelServicePrivate *priv;
gchar *id;
g_return_if_fail (PANEL_IS_SERVICE (self));
g_return_if_fail (entry != NULL);
priv = self->priv;
id = g_strdup_printf ("%p", entry);
g_hash_table_insert (priv->id2entry_hash, id, entry);
g_hash_table_insert (priv->entry2indicator_hash, entry, object);
if (GTK_IS_LABEL (entry->label))
{
g_signal_connect (entry->label, "notify::label",
G_CALLBACK (on_entry_property_changed), object);
g_signal_connect (entry->label, "notify::sensitive",
G_CALLBACK (on_entry_property_changed), object);
g_signal_connect (entry->label, "show",
G_CALLBACK (on_entry_changed), object);
g_signal_connect (entry->label, "hide",
G_CALLBACK (on_entry_changed), object);
}
if (GTK_IS_IMAGE (entry->image))
{
g_signal_connect (entry->image, "notify::storage-type",
G_CALLBACK (on_entry_property_changed), object);
g_signal_connect (entry->image, "notify::file",
G_CALLBACK (on_entry_property_changed), object);
g_signal_connect (entry->image, "notify::gicon",
G_CALLBACK (on_entry_property_changed), object);
g_signal_connect (entry->image, "notify::icon-name",
G_CALLBACK (on_entry_property_changed), object);
g_signal_connect (entry->image, "notify::pixbuf",
G_CALLBACK (on_entry_property_changed), object);
g_signal_connect (entry->image, "notify::stock",
G_CALLBACK (on_entry_property_changed), object);
g_signal_connect (entry->image, "notify::sensitive",
G_CALLBACK (on_entry_property_changed), object);
g_signal_connect (entry->image, "show",
G_CALLBACK (on_entry_changed), object);
g_signal_connect (entry->image, "hide",
G_CALLBACK (on_entry_changed), object);
}
notify_object (object);
}
static void
on_entry_removed (IndicatorObject *object,
IndicatorObjectEntry *entry,
PanelService *self)
{
PanelServicePrivate *priv;
gchar *id;
g_return_if_fail (PANEL_IS_SERVICE (self));
g_return_if_fail (entry != NULL);
priv = self->priv;
id = g_strdup_printf ("%p", entry);
g_hash_table_remove (priv->entry2indicator_hash, entry);
g_hash_table_remove (priv->id2entry_hash, id);
g_free (id);
notify_object (object);
}
static void
on_entry_moved (IndicatorObject *object,
IndicatorObjectEntry *entry,
PanelService *self)
{
notify_object (object);
}
static void
on_indicator_menu_show (IndicatorObject *object,
IndicatorObjectEntry *entry,
guint32 timestamp,
PanelService *self)
{
gchar *entry_id;
g_return_if_fail (PANEL_IS_SERVICE (self));
entry_id = g_strdup_printf ("%p", entry);
g_signal_emit (self, _service_signals[ENTRY_ACTIVATE_REQUEST], 0, entry_id);
g_free (entry_id);
}
static void
load_indicator (PanelService *self, IndicatorObject *object, const gchar *_name)
{
PanelServicePrivate *priv = self->priv;
gchar *name;
GList *entries, *entry;
if (_name != NULL)
name = g_strdup (_name);
else
name = g_strdup_printf ("%p", object);
priv->indicators = g_slist_append (priv->indicators, object);
g_object_set_data_full (G_OBJECT (object), "id", g_strdup (name), g_free);
g_signal_connect (object, INDICATOR_OBJECT_SIGNAL_ENTRY_ADDED,
G_CALLBACK (on_entry_added), self);
g_signal_connect (object, INDICATOR_OBJECT_SIGNAL_ENTRY_REMOVED,
G_CALLBACK (on_entry_removed), self);
g_signal_connect (object, INDICATOR_OBJECT_SIGNAL_ENTRY_MOVED,
G_CALLBACK (on_entry_moved), self);
g_signal_connect (object, INDICATOR_OBJECT_SIGNAL_MENU_SHOW,
G_CALLBACK (on_indicator_menu_show), self);
entries = indicator_object_get_entries (object);
for (entry = entries; entry != NULL; entry = entry->next)
{
on_entry_added (object, entry->data, self);
}
g_list_free (entries);
g_free (name);
}
static void
load_indicators (PanelService *self)
{
GDir *dir;
const gchar *name;
if (!g_file_test (INDICATORDIR,
G_FILE_TEST_EXISTS | G_FILE_TEST_IS_DIR))
{
g_warning ("%s does not exist, cannot read any indicators", INDICATORDIR);
gtk_main_quit ();
}
dir = g_dir_open (INDICATORDIR, 0, NULL);
while ((name = g_dir_read_name (dir)) != NULL)
{
IndicatorObject *object;
gchar *path;
if (!g_str_has_suffix (name, ".so"))
continue;
path = g_build_filename (INDICATORDIR, name, NULL);
g_debug ("Loading: %s", path);
object = indicator_object_new_from_file (path);
if (object == NULL)
{
g_warning ("Unable to load '%s'", path);
g_free (path);
continue;
}
load_indicator (self, object, name);
g_free (path);
}
g_dir_close (dir);
}
static gint
name2order (const gchar * name)
{
int i;
for (i = 0; indicator_order[i] != NULL; i++)
{
if (g_strcmp0(name, indicator_order[i]) == 0)
{
return i;
}
}
return -1;
}
static int
indicator_compare_func (IndicatorObject *o1, IndicatorObject *o2)
{
gchar *s1;
gchar *s2;
int i1;
int i2;
s1 = g_object_get_data (G_OBJECT (o1), "id");
s2 = g_object_get_data (G_OBJECT (o2), "id");
i1 = name2order (s1);
i2 = name2order (s2);
return i1 - i2;
}
static void
sort_indicators (PanelService *self)
{
GSList *i;
int k = 0;
self->priv->indicators = g_slist_sort (self->priv->indicators,
(GCompareFunc)indicator_compare_func);
for (i = self->priv->indicators; i; i = i->next)
{
g_object_set_data (G_OBJECT (i->data), "position", GINT_TO_POINTER (k));
self->priv->timeouts[k] = SYNC_NEUTRAL;
k++;
}
}
static gchar *
gtk_image_to_data (GtkImage *image)
{
GtkImageType type = gtk_image_get_storage_type (image);
gchar *ret = NULL;
if (type == GTK_IMAGE_PIXBUF)
{
GdkPixbuf *pixbuf;
gchar *buffer = NULL;
gsize buffer_size = 0;
GError *error = NULL;
pixbuf = gtk_image_get_pixbuf (image);
if (gdk_pixbuf_save_to_buffer (pixbuf, &buffer, &buffer_size, "png", &error, NULL))
{
ret = g_base64_encode ((const guchar *)buffer, buffer_size);
g_free (buffer);
}
else
{
g_warning ("Unable to convert pixbuf to png data: '%s'", error ? error->message : "unknown");
if (error)
g_error_free (error);
ret = g_strdup ("");
}
}
else if (type == GTK_IMAGE_STOCK)
{
g_object_get (G_OBJECT (image), "stock", &ret, NULL);
}
else if (type == GTK_IMAGE_ICON_NAME)
{
g_object_get (G_OBJECT (image), "icon-name", &ret, NULL);
}
else if (type == GTK_IMAGE_GICON)
{
GIcon *icon = NULL;
gtk_image_get_gicon (image, &icon, NULL);
if (G_IS_ICON (icon))
{
ret = g_icon_to_string (icon);
}
}
else
{
ret = g_strdup ("");
g_warning ("Unable to support GtkImageType: %d", type);
}
return ret;
}
static void
indicator_entry_to_variant (IndicatorObjectEntry *entry,
const gchar *id,
const gchar *indicator_id,
GVariantBuilder *b)
{
gboolean is_label = GTK_IS_LABEL (entry->label);
gboolean is_image = GTK_IS_IMAGE (entry->image);
gchar *image_data = NULL;
g_variant_builder_add (b, "(sssbbusbb)",
indicator_id,
id,
is_label ? gtk_label_get_label (entry->label) : "",
is_label ? gtk_widget_get_sensitive (GTK_WIDGET (entry->label)) : FALSE,
is_label ? gtk_widget_get_visible (GTK_WIDGET (entry->label)) : FALSE,
is_image ? (guint32)gtk_image_get_storage_type (entry->image) : (guint32) 0,
is_image ? (image_data = gtk_image_to_data (entry->image)) : "",
is_image ? gtk_widget_get_sensitive (GTK_WIDGET (entry->image)) : FALSE,
is_image ? gtk_widget_get_visible (GTK_WIDGET (entry->image)) : FALSE);
g_free (image_data);
}
static void
indicator_entry_null_to_variant (const gchar *indicator_id,
GVariantBuilder *b)
{
g_variant_builder_add (b, "(sssbbusbb)",
indicator_id,
"",
"",
FALSE,
FALSE,
(guint32) 0,
"",
FALSE,
FALSE);
}
static void
indicator_object_to_variant (IndicatorObject *object, const gchar *indicator_id, GVariantBuilder *b)
{
GList *entries, *e;
entries = indicator_object_get_entries (object);
if (entries)
{
for (e = entries; e; e = e->next)
{
IndicatorObjectEntry *entry = e->data;
gchar *id = g_strdup_printf ("%p", entry);
indicator_entry_to_variant (entry, id, indicator_id, b);
g_free (id);
}
}
else
{
/* Add a null entry to indicate that there is an indicator here, it's just empty */
indicator_entry_null_to_variant (indicator_id, b);
}
g_list_free (entries);
}
static void
positon_menu (GtkMenu *menu,
gint *x,
gint *y,
gboolean *push,
gpointer user_data)
{
PanelService *self = PANEL_SERVICE (user_data);
PanelServicePrivate *priv = self->priv;
*x = priv->last_x;
*y = priv->last_y;
*push = TRUE;
}
static void
on_active_menu_hidden (GtkMenu *menu, PanelService *self)
{
PanelServicePrivate *priv = self->priv;
priv->last_x = 0;
priv->last_y = 0;
priv->last_menu_button = 0;
g_signal_handler_disconnect (priv->last_menu, priv->last_menu_id);
g_signal_handler_disconnect (priv->last_menu, priv->last_menu_move_id);
priv->last_menu = NULL;
priv->last_menu_id = 0;
priv->last_menu_move_id = 0;
priv->last_entry = NULL;
g_signal_emit (self, _service_signals[ENTRY_ACTIVATED], 0, "");
}
/*
* Public Methods
*/
GVariant *
panel_service_sync (PanelService *self)
{
GVariantBuilder b;
GSList *i;
g_variant_builder_init (&b, G_VARIANT_TYPE ("(a(sssbbusbb))"));
g_variant_builder_open (&b, G_VARIANT_TYPE ("a(sssbbusbb)"));
for (i = self->priv->indicators; i; i = i->next)
{
const gchar *indicator_id = g_object_get_data (G_OBJECT (i->data), "id");
gint position;
/* Set the sync back to neutral */
position = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (i->data), "position"));
self->priv->timeouts[position] = SYNC_NEUTRAL;
indicator_object_to_variant (i->data, indicator_id, &b);
}
g_variant_builder_close (&b);
return g_variant_builder_end (&b);
}
GVariant *
panel_service_sync_one (PanelService *self, const gchar *indicator_id)
{
GVariantBuilder b;
GSList *i;
g_variant_builder_init (&b, G_VARIANT_TYPE ("(a(sssbbusbb))"));
g_variant_builder_open (&b, G_VARIANT_TYPE ("a(sssbbusbb)"));
for (i = self->priv->indicators; i; i = i->next)
{
if (g_strcmp0 (indicator_id,
g_object_get_data (G_OBJECT (i->data), "id")) == 0)
{
gint position;
/* Set the sync back to neutral */
position = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (i->data), "position"));
self->priv->timeouts[position] = SYNC_NEUTRAL;
indicator_object_to_variant (i->data, indicator_id, &b);
break;
}
}
g_variant_builder_close (&b);
return g_variant_builder_end (&b);
}
static void
activate_next_prev_menu (PanelService *self,
IndicatorObject *object,
IndicatorObjectEntry *entry,
GtkMenuDirectionType direction)
{
PanelServicePrivate *priv = self->priv;
GSList *indicators = priv->indicators;
GList *entries;
gint n_entries;
IndicatorObjectEntry *new_entry;
gchar *id;
entries = indicator_object_get_entries (object);
n_entries = g_list_length (entries);
if (n_entries == 1
|| (g_list_index (entries, entry) == 0 && direction == GTK_MENU_DIR_PARENT)
|| (g_list_index (entries, entry) == n_entries - 1 && direction == GTK_MENU_DIR_CHILD))
{
int n_indicators;
IndicatorObject *new_object;
GList *new_entries;
n_indicators = g_slist_length (priv->indicators);
if (g_slist_index (indicators, object) == 0 && direction == GTK_MENU_DIR_PARENT)
{
new_object = g_slist_nth_data (indicators, n_indicators - 1);
}
else if (g_slist_index (indicators, object) == n_indicators -1 && direction == GTK_MENU_DIR_CHILD)
{
new_object = g_slist_nth_data (indicators, 0);
}
else
{
gint cur_object_index = g_slist_index (indicators, object);
gint new_object_index = cur_object_index + (direction == GTK_MENU_DIR_CHILD ? 1 : -1);
new_object = g_slist_nth_data (indicators, new_object_index);
}
new_entries = indicator_object_get_entries (new_object);
new_entry = g_list_nth_data (new_entries, direction == GTK_MENU_DIR_PARENT ? g_list_length (new_entries) - 1 : 0);
g_list_free (new_entries);
}
else
{
new_entry = g_list_nth_data (entries, g_list_index (entries, entry) + (direction == GTK_MENU_DIR_CHILD ? 1 : -1));
}
id = g_strdup_printf ("%p", new_entry);
g_signal_emit (self, _service_signals[ENTRY_ACTIVATE_REQUEST], 0, id);
g_free (id);
g_list_free (entries);
}
static void
on_active_menu_move_current (GtkMenu *menu,
GtkMenuDirectionType direction,
PanelService *self)
{
PanelServicePrivate *priv;
IndicatorObject *object;
g_return_if_fail (PANEL_IS_SERVICE (self));
priv = self->priv;
/* Not interested in up or down */
if (direction == GTK_MENU_DIR_NEXT
|| direction == GTK_MENU_DIR_PREV)
return;
/* We don't want to distrupt going into submenus */
if (direction == GTK_MENU_DIR_CHILD)
{
GList *children, *c;
children = gtk_container_get_children (GTK_CONTAINER (menu));
for (c = children; c; c = c->next)
{
GtkWidget *item = (GtkWidget *)c->data;
if (GTK_IS_MENU_ITEM (item)
&& gtk_widget_get_state (item) == GTK_STATE_PRELIGHT
&& gtk_menu_item_get_submenu (GTK_MENU_ITEM (item)))
{
/* Skip direction due to there being a submenu,
* and we don't want to inhibit going into that */
return;
}
}
g_list_free (children);
}
/* Find the next/prev indicator */
object = g_hash_table_lookup (priv->entry2indicator_hash, priv->last_entry);
if (object == NULL)
{
g_warning ("Unable to find IndicatorObject for entry");
return;
}
activate_next_prev_menu (self, object, priv->last_entry, direction);
}
void
panel_service_show_entry (PanelService *self,
const gchar *entry_id,
guint32 timestamp,
gint32 x,
gint32 y,
gint32 button)
{
PanelServicePrivate *priv = self->priv;
IndicatorObjectEntry *entry = g_hash_table_lookup (priv->id2entry_hash, entry_id);
IndicatorObject *object = g_hash_table_lookup (priv->entry2indicator_hash, entry);
GtkWidget *last_menu;
if (priv->last_entry == entry)
return;
last_menu = GTK_WIDGET (priv->last_menu);
if (GTK_IS_MENU (priv->last_menu))
{
priv->last_x = 0;
priv->last_y = 0;
g_signal_handler_disconnect (priv->last_menu, priv->last_menu_id);
g_signal_handler_disconnect (priv->last_menu, priv->last_menu_move_id);
priv->last_entry = NULL;
priv->last_menu = NULL;
priv->last_menu_id = 0;
priv->last_menu_move_id = 0;
priv->last_menu_button = 0;
}
if (entry != NULL)
{
if (GTK_IS_MENU (entry->menu))
{
priv->last_menu = entry->menu;
}
else
{
/* For some reason, this entry doesn't have a menu. To simplify the
rest of the code and to keep scrubbing fluidly, we'll create a
stub menu for the duration of this scrub. */
priv->last_menu = GTK_MENU (gtk_menu_new ());
g_signal_connect (priv->last_menu, "deactivate",
G_CALLBACK (gtk_widget_destroy), NULL);
g_signal_connect (priv->last_menu, "destroy",
G_CALLBACK (gtk_widget_destroyed), &priv->last_menu);
}
priv->last_entry = entry;
priv->last_x = x;
priv->last_y = y;
priv->last_menu_button = button;
priv->last_menu_id = g_signal_connect (priv->last_menu, "hide",
G_CALLBACK (on_active_menu_hidden), self);
priv->last_menu_move_id = g_signal_connect_after (priv->last_menu, "move-current",
G_CALLBACK (on_active_menu_move_current), self);
indicator_object_entry_activate (object, entry, CurrentTime);
gtk_menu_popup (priv->last_menu, NULL, NULL, positon_menu, self, 0, CurrentTime);
g_signal_emit (self, _service_signals[ENTRY_ACTIVATED], 0, entry_id);
}
/* We popdown the old one last so we don't accidently send key focus back to the
* active application (which will make it change colour (as state changes), which
* then looks like flickering to the user.
*/
if (GTK_MENU (last_menu))
gtk_menu_popdown (GTK_MENU (last_menu));
}
void
panel_service_scroll_entry (PanelService *self,
const gchar *entry_id,
gint32 delta)
{
PanelServicePrivate *priv = self->priv;
IndicatorObjectEntry *entry = g_hash_table_lookup (priv->id2entry_hash, entry_id);
IndicatorObject *object = g_hash_table_lookup (priv->entry2indicator_hash, entry);
GdkScrollDirection direction = delta > 0 ? GDK_SCROLL_DOWN : GDK_SCROLL_UP;
g_signal_emit_by_name(object, "scroll", abs(delta/120), direction);
g_signal_emit_by_name(object, "scroll-entry", entry, abs(delta/120), direction);
}
void
panel_service_get_last_xy (PanelService *self,
gint *x,
gint *y)
{
*x = self->priv->last_menu_x;
*y = self->priv->last_menu_y;
}