diff options
-rw-r--r-- | ChangeLog | 26 | ||||
-rw-r--r-- | ChangeLog.pre-2-10 | 26 | ||||
-rw-r--r-- | gtk/Makefile.am | 10 | ||||
-rw-r--r-- | gtk/gtk.h | 1 | ||||
-rw-r--r-- | gtk/gtk.symbols | 26 | ||||
-rwxr-xr-x | gtk/gtkstatusicon.c | 925 | ||||
-rwxr-xr-x | gtk/gtkstatusicon.h | 108 | ||||
-rwxr-xr-x | gtk/gtktrayicon-x11.c | 498 | ||||
-rwxr-xr-x | gtk/gtktrayicon.h | 75 | ||||
-rw-r--r-- | tests/Makefile.am | 6 | ||||
-rwxr-xr-x | tests/teststatusicon.c | 209 |
11 files changed, 1906 insertions, 4 deletions
@@ -1,8 +1,32 @@ +2005-08-29 Matthias Clasen <mclasen@redhat.com> + + Add a cross-platform "tray icon" API, by + porting EggStatusIcon/EggTrayIcon (#105101) + + * gtk/gtkstatusicon.h: A GtkStatusIcon is an object which + displays an icon in a notification area. + + * gtk/gtkstatusicon-x11.c: GtkStatusIcon implementation for + X11, using GtkTrayIcon. + + * gtk/gtktrayicon.h: + * gtk/gtktrayicon.c: An implementation of the freedesktop.org + system tray specification, not public API. + + * gtk/gtk.symbols: Add new exported functions. + + * gtk/gtk.h: Include gtkstatusicon.h. + + * gtk/Makefile.am: Add new files. + + * tests/Makefile.am: + * tests/teststatusicon.c: Test for GtkStatusIcon. + 2005-08-29 Christopher Aillon <caillon@redhat.com> * gtk/gtkstock.c: Add builtin GtkStockItems for GTK_STOCK_CONNECT and GTK_STOCK_DISCONNECT - + 2005-08-29 Matthias Clasen <mclasen@redhat.com> * gtk/gtksocket-x11.c (_gtk_socket_windowing_embed_notify): diff --git a/ChangeLog.pre-2-10 b/ChangeLog.pre-2-10 index 95d729d0a..ff20bae7c 100644 --- a/ChangeLog.pre-2-10 +++ b/ChangeLog.pre-2-10 @@ -1,8 +1,32 @@ +2005-08-29 Matthias Clasen <mclasen@redhat.com> + + Add a cross-platform "tray icon" API, by + porting EggStatusIcon/EggTrayIcon (#105101) + + * gtk/gtkstatusicon.h: A GtkStatusIcon is an object which + displays an icon in a notification area. + + * gtk/gtkstatusicon-x11.c: GtkStatusIcon implementation for + X11, using GtkTrayIcon. + + * gtk/gtktrayicon.h: + * gtk/gtktrayicon.c: An implementation of the freedesktop.org + system tray specification, not public API. + + * gtk/gtk.symbols: Add new exported functions. + + * gtk/gtk.h: Include gtkstatusicon.h. + + * gtk/Makefile.am: Add new files. + + * tests/Makefile.am: + * tests/teststatusicon.c: Test for GtkStatusIcon. + 2005-08-29 Christopher Aillon <caillon@redhat.com> * gtk/gtkstock.c: Add builtin GtkStockItems for GTK_STOCK_CONNECT and GTK_STOCK_DISCONNECT - + 2005-08-29 Matthias Clasen <mclasen@redhat.com> * gtk/gtksocket-x11.c (_gtk_socket_windowing_embed_notify): diff --git a/gtk/Makefile.am b/gtk/Makefile.am index 5f9433081..4ea8eda68 100644 --- a/gtk/Makefile.am +++ b/gtk/Makefile.am @@ -239,6 +239,7 @@ gtk_public_h_sources = \ gtksocket.h \ gtkspinbutton.h \ gtkstatusbar.h \ + gtkstatusicon.h \ gtkstock.h \ gtkstyle.h \ gtktable.h \ @@ -535,8 +536,13 @@ gtk_c_sources += gtkfilesystemwin32.c endif if USE_X11 -gtk_private_h_sources += gtkxembed.h -gtk_c_sources += gtkplug-x11.c gtksocket-x11.c gtkxembed.c +gtk_private_h_sources += gtkxembed.h gtktrayicon.h +gtk_c_sources += \ + gtkplug-x11.c \ + gtksocket-x11.c \ + gtkxembed.c \ + gtktrayicon.c \ + gtkstatusicon-x11.c else if USE_WIN32 gtk_private_h_sources += gtkwin32embed.h @@ -150,6 +150,7 @@ #include <gtk/gtksocket.h> #include <gtk/gtkspinbutton.h> #include <gtk/gtkstatusbar.h> +#include <gtk/gtkstatusicon.h> #include <gtk/gtkstock.h> #include <gtk/gtkstyle.h> #include <gtk/gtktable.h> diff --git a/gtk/gtk.symbols b/gtk/gtk.symbols index 916581397..f0ea5cbdb 100644 --- a/gtk/gtk.symbols +++ b/gtk/gtk.symbols @@ -973,6 +973,32 @@ gtk_drag_unhighlight #endif #endif +#if IN_HEADER(__GTK_STATUS_ICON_H__) +#if IN_FILE(__GTK_STATUS_ICON_C__) +gtk_status_icon_get_type G_GNUC_CONST +gtk_status_icon_new +gtk_status_icon_new_from_pixbuf +gtk_status_icon_new_from_file +gtk_status_icon_new_from_stock +gtk_status_icon_new_from_icon_name +gtk_status_icon_set_from_pixbuf +gtk_status_icon_set_from_file +gtk_status_icon_set_from_stock +gtk_status_icon_set_from_icon_name +gtk_status_icon_get_storage_type +gtk_status_icon_get_pixbuf +gtk_status_icon_get_stock +gtk_status_icon_get_icon_name +gtk_status_icon_get_size +gtk_status_icon_set_tooltip +gtk_status_icon_set_visible +gtk_status_icon_get_visible +gtk_status_icon_set_blinking +gtk_status_icon_get_blinking +gtk_status_icon_is_embedded +#endif +#endif + #if IN_HEADER(__GTK_STYLE_H__) #if IN_FILE(__GTK_STYLE_C__) #ifndef GTK_DISABLE_DEPRECATED diff --git a/gtk/gtkstatusicon.c b/gtk/gtkstatusicon.c new file mode 100755 index 000000000..490b5aa52 --- /dev/null +++ b/gtk/gtkstatusicon.c @@ -0,0 +1,925 @@ +/* gtkstatusicon-x11.c: + * + * Copyright (C) 2003 Sun Microsystems, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + * + * Authors: + * Mark McLoughlin <mark@skynet.ie> + */ + +#include <config.h> +#include <string.h> + +#include "gtkstatusicon.h" + +#include "gtkintl.h" +#include "gtkiconfactory.h" +#include "gtkmarshalers.h" +#include "gtktooltips.h" +#include "gtktrayicon.h" + +#include "gtkprivate.h" +#include "gtkwidget.h" + +#include "gtkalias.h" + +#define BLINK_TIMEOUT 500 + +enum +{ + PROP_0, + PROP_PIXBUF, + PROP_FILE, + PROP_STOCK, + PROP_ICON_NAME, + PROP_STORAGE_TYPE, + PROP_SIZE, + PROP_VISIBLE, + PROP_BLINKING +}; + +enum +{ + ACTIVATE_SIGNAL, + POPUP_MENU_SIGNAL, + SIZE_CHANGED_SIGNAL, + LAST_SIGNAL +}; + +struct _GtkStatusIconPrivate +{ + GtkWidget *tray_icon; + GtkWidget *image; + gint size; + + gint image_width; + gint image_height; + + GtkTooltips *tooltips; + + GtkImageType storage_type; + + union + { + GdkPixbuf *pixbuf; + gchar *stock_id; + gchar *icon_name; + } image_data; + + GdkPixbuf *blank_icon; + guint blinking_timeout; + + guint blinking : 1; + guint blink_off : 1; + guint visible : 1; +}; + +static void gtk_status_icon_finalize (GObject *object); +static void gtk_status_icon_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec); +static void gtk_status_icon_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec); + +static void gtk_status_icon_size_allocate (GtkStatusIcon *status_icon, + GtkAllocation *allocation); +static gboolean gtk_status_icon_button_press (GtkStatusIcon *status_icon, + GdkEventButton *event); +static void gtk_status_icon_disable_blinking (GtkStatusIcon *status_icon); +static void gtk_status_icon_reset_image_data (GtkStatusIcon *status_icon); + + +static guint status_icon_signals [LAST_SIGNAL] = { 0 }; + +G_DEFINE_TYPE (GtkStatusIcon, gtk_status_icon, G_TYPE_OBJECT); + +static void +gtk_status_icon_class_init (GtkStatusIconClass *class) +{ + GObjectClass *gobject_class = (GObjectClass *) class; + + gobject_class->finalize = gtk_status_icon_finalize; + gobject_class->set_property = gtk_status_icon_set_property; + gobject_class->get_property = gtk_status_icon_get_property; + + g_object_class_install_property (gobject_class, + PROP_PIXBUF, + g_param_spec_object ("pixbuf", + P_("Pixbuf"), + P_("A GdkPixbuf to display"), + GDK_TYPE_PIXBUF, + GTK_PARAM_READWRITE)); + + g_object_class_install_property (gobject_class, + PROP_FILE, + g_param_spec_string ("file", + P_("Filename"), + P_("Filename to load and display"), + NULL, + GTK_PARAM_WRITABLE)); + + g_object_class_install_property (gobject_class, + PROP_STOCK, + g_param_spec_string ("stock", + P_("Stock ID"), + P_("Stock ID for a stock image to display"), + NULL, + GTK_PARAM_READWRITE)); + + g_object_class_install_property (gobject_class, + PROP_ICON_NAME, + g_param_spec_string ("icon-name", + P_("Icon Name"), + P_("The name of the icon from the icon theme"), + NULL, + GTK_PARAM_READWRITE)); + + g_object_class_install_property (gobject_class, + PROP_STORAGE_TYPE, + g_param_spec_enum ("storage-type", + P_("Storage type"), + P_("The representation being used for image data"), + GTK_TYPE_IMAGE_TYPE, + GTK_IMAGE_EMPTY, + GTK_PARAM_READABLE)); + + g_object_class_install_property (gobject_class, + PROP_SIZE, + g_param_spec_int ("size", + P_("Size"), + P_("The size of the icon"), + 0, + G_MAXINT, + 0, + GTK_PARAM_READABLE)); + + g_object_class_install_property (gobject_class, + PROP_BLINKING, + g_param_spec_boolean ("blinking", + P_("Blinking"), + P_("Whether or not the status icon is blinking"), + FALSE, + GTK_PARAM_READWRITE)); + + g_object_class_install_property (gobject_class, + PROP_VISIBLE, + g_param_spec_boolean ("visible", + P_("Visible"), + P_("Whether or not the status icon is visible"), + TRUE, + GTK_PARAM_READWRITE)); + + + status_icon_signals [ACTIVATE_SIGNAL] = + g_signal_new ("activate", + G_TYPE_FROM_CLASS (gobject_class), + G_SIGNAL_RUN_FIRST | G_SIGNAL_ACTION, + G_STRUCT_OFFSET (GtkStatusIconClass, activate), + NULL, + NULL, + g_cclosure_marshal_VOID__VOID, + G_TYPE_NONE, + 0); + + status_icon_signals [POPUP_MENU_SIGNAL] = + g_signal_new ("popup-menu", + G_TYPE_FROM_CLASS (gobject_class), + G_SIGNAL_RUN_FIRST | G_SIGNAL_ACTION, + G_STRUCT_OFFSET (GtkStatusIconClass, popup_menu), + NULL, + NULL, + _gtk_marshal_VOID__UINT_UINT, + G_TYPE_NONE, + 2, + G_TYPE_UINT, + G_TYPE_UINT); + + status_icon_signals [SIZE_CHANGED_SIGNAL] = + g_signal_new ("size-changed", + G_TYPE_FROM_CLASS (gobject_class), + G_SIGNAL_RUN_FIRST, + G_STRUCT_OFFSET (GtkStatusIconClass, size_changed), + NULL, + NULL, + g_cclosure_marshal_VOID__INT, + G_TYPE_NONE, + 1, + G_TYPE_INT); + + g_type_class_add_private (class, sizeof (GtkStatusIconPrivate)); +} + +static void +gtk_status_icon_init (GtkStatusIcon *status_icon) +{ + status_icon->priv = G_TYPE_INSTANCE_GET_PRIVATE (status_icon, GTK_TYPE_STATUS_ICON, + GtkStatusIconPrivate); + + status_icon->priv->storage_type = GTK_IMAGE_EMPTY; + status_icon->priv->size = 0; + status_icon->priv->image_width = 0; + status_icon->priv->image_height = 0; + status_icon->priv->visible = TRUE; + + status_icon->priv->tray_icon = GTK_WIDGET (_gtk_tray_icon_new (NULL)); + + gtk_widget_add_events (GTK_WIDGET (status_icon->priv->tray_icon), + GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK); + + g_signal_connect_swapped (status_icon->priv->tray_icon, "button-press-event", + G_CALLBACK (gtk_status_icon_button_press), status_icon); + + status_icon->priv->image = gtk_image_new (); + gtk_container_add (GTK_CONTAINER (status_icon->priv->tray_icon), + status_icon->priv->image); + + g_signal_connect_swapped (status_icon->priv->image, "size-allocate", + G_CALLBACK (gtk_status_icon_size_allocate), status_icon); + + gtk_widget_show (status_icon->priv->image); + gtk_widget_show (status_icon->priv->tray_icon); + + status_icon->priv->tooltips = gtk_tooltips_new (); + g_object_ref (status_icon->priv->tooltips); + gtk_object_sink (GTK_OBJECT (status_icon->priv->tooltips)); +} + +static void +gtk_status_icon_finalize (GObject *object) +{ + GtkStatusIcon *status_icon = GTK_STATUS_ICON (object); + + gtk_status_icon_disable_blinking (status_icon); + + gtk_status_icon_reset_image_data (status_icon); + + if (status_icon->priv->blank_icon) + g_object_unref (status_icon->priv->blank_icon); + status_icon->priv->blank_icon = NULL; + + if (status_icon->priv->tooltips) + g_object_unref (status_icon->priv->tooltips); + status_icon->priv->tooltips = NULL; + + gtk_widget_destroy (status_icon->priv->tray_icon); + + G_OBJECT_CLASS (gtk_status_icon_parent_class)->finalize (object); +} + +static void +gtk_status_icon_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + GtkStatusIcon *status_icon = GTK_STATUS_ICON (object); + + switch (prop_id) + { + case PROP_PIXBUF: + gtk_status_icon_set_from_pixbuf (status_icon, g_value_get_object (value)); + break; + case PROP_FILE: + gtk_status_icon_set_from_file (status_icon, g_value_get_string (value)); + break; + case PROP_STOCK: + gtk_status_icon_set_from_stock (status_icon, g_value_get_string (value)); + break; + case PROP_ICON_NAME: + gtk_status_icon_set_from_icon_name (status_icon, g_value_get_string (value)); + break; + case PROP_BLINKING: + gtk_status_icon_set_blinking (status_icon, g_value_get_boolean (value)); + break; + case PROP_VISIBLE: + gtk_status_icon_set_visible (status_icon, g_value_get_boolean (value)); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +gtk_status_icon_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + GtkStatusIcon *status_icon = GTK_STATUS_ICON (object); + + switch (prop_id) + { + case PROP_PIXBUF: + g_value_set_object (value, gtk_status_icon_get_pixbuf (status_icon)); + break; + case PROP_STOCK: + g_value_set_string (value, gtk_status_icon_get_stock (status_icon)); + break; + case PROP_ICON_NAME: + g_value_set_string (value, gtk_status_icon_get_icon_name (status_icon)); + break; + case PROP_STORAGE_TYPE: + g_value_set_enum (value, gtk_status_icon_get_storage_type (status_icon)); + break; + case PROP_SIZE: + g_value_set_int (value, gtk_status_icon_get_size (status_icon)); + break; + case PROP_BLINKING: + g_value_set_boolean (value, gtk_status_icon_get_blinking (status_icon)); + break; + case PROP_VISIBLE: + g_value_set_boolean (value, gtk_status_icon_get_visible (status_icon)); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +GtkStatusIcon * +gtk_status_icon_new (void) +{ + return g_object_new (GTK_TYPE_STATUS_ICON, NULL); +} + +GtkStatusIcon * +gtk_status_icon_new_from_pixbuf (GdkPixbuf *pixbuf) +{ + return g_object_new (GTK_TYPE_STATUS_ICON, + "pixbuf", pixbuf, + NULL); +} + +GtkStatusIcon * +gtk_status_icon_new_from_file (const gchar *filename) +{ + return g_object_new (GTK_TYPE_STATUS_ICON, + "file", filename, + NULL); +} + +GtkStatusIcon * +gtk_status_icon_new_from_stock (const gchar *stock_id) +{ + return g_object_new (GTK_TYPE_STATUS_ICON, + "stock", stock_id, + NULL); +} + +GtkStatusIcon * +gtk_status_icon_new_from_icon_name (const gchar *icon_name) +{ + return g_object_new (GTK_TYPE_STATUS_ICON, + "icon-name", icon_name, + NULL); +} + +static void +emit_activate_signal (GtkStatusIcon *status_icon) +{ + g_signal_emit (status_icon, + status_icon_signals [ACTIVATE_SIGNAL], 0); +} + +static void +emit_popup_menu_signal (GtkStatusIcon *status_icon, + guint button, + guint32 activate_time) +{ + g_signal_emit (status_icon, + status_icon_signals [POPUP_MENU_SIGNAL], 0, + button, + activate_time); +} + +static gboolean +emit_size_changed_signal (GtkStatusIcon *status_icon, + gint size) +{ + gboolean handled = FALSE; + + g_signal_emit (status_icon, + status_icon_signals [SIZE_CHANGED_SIGNAL], 0, + size, + &handled); + + return handled; +} + +static GdkPixbuf * +gtk_status_icon_blank_icon (GtkStatusIcon *status_icon) +{ + if (status_icon->priv->blank_icon) + { + gint width, height; + + width = gdk_pixbuf_get_width (status_icon->priv->blank_icon); + height = gdk_pixbuf_get_height (status_icon->priv->blank_icon); + + + if (width == status_icon->priv->image_width && + height == status_icon->priv->image_height) + { + return status_icon->priv->blank_icon; + } + else + { + g_object_unref (status_icon->priv->blank_icon); + status_icon->priv->blank_icon = NULL; + } + } + + status_icon->priv->blank_icon = gdk_pixbuf_new (GDK_COLORSPACE_RGB, TRUE, 8, + status_icon->priv->image_width, + status_icon->priv->image_height); + if (status_icon->priv->blank_icon) + gdk_pixbuf_fill (status_icon->priv->blank_icon, 0); + + return status_icon->priv->blank_icon; +} + +static GtkIconSize +find_icon_size (GtkWidget *widget, + gint pixel_size) +{ + GdkScreen *screen; + GtkSettings *settings; + GtkIconSize s, size; + gint w, h, d, dist; + + screen = gtk_widget_get_screen (widget); + + if (!screen) + return GTK_ICON_SIZE_MENU; + + settings = gtk_settings_get_for_screen (screen); + + dist = G_MAXINT; + size = GTK_ICON_SIZE_MENU; + + for (s = GTK_ICON_SIZE_MENU; s < GTK_ICON_SIZE_DIALOG; s++) + { + if (gtk_icon_size_lookup_for_settings (settings, s, &w, &h) && + w <= pixel_size && h <= pixel_size) + { + d = MAX (pixel_size - w, pixel_size - h); + if (d < dist) + { + dist = d; + size = s; + } + } + } + + return size; +} + +static void +gtk_status_icon_update_image (GtkStatusIcon *status_icon) +{ + if (status_icon->priv->blink_off) + { + gtk_image_set_from_pixbuf (GTK_IMAGE (status_icon->priv->image), + gtk_status_icon_blank_icon (status_icon)); + return; + } + + switch (status_icon->priv->storage_type) + { + case GTK_IMAGE_PIXBUF: + { + GdkPixbuf *pixbuf; + + pixbuf = status_icon->priv->image_data.pixbuf; + + if (pixbuf) + { + GdkPixbuf *scaled; + gint size; + gint width; + gint height; + + size = status_icon->priv->size; + + width = gdk_pixbuf_get_width (pixbuf); + height = gdk_pixbuf_get_height (pixbuf); + + if (width > size || height > size) + { + scaled = gdk_pixbuf_scale_simple (pixbuf, + MIN (size, width), + MIN (size, height), + GDK_INTERP_BILINEAR); + } + else + { + scaled = g_object_ref (pixbuf); + } + + gtk_image_set_from_pixbuf (GTK_IMAGE (status_icon->priv->image), scaled); + + g_object_unref (scaled); + } + else + { + gtk_image_set_from_pixbuf (GTK_IMAGE (status_icon->priv->image), NULL); + } + } + break; + + case GTK_IMAGE_STOCK: + { + GtkIconSize size = find_icon_size (status_icon->priv->image, status_icon->priv->size); + gtk_image_set_from_stock (GTK_IMAGE (status_icon->priv->image), + status_icon->priv->image_data.stock_id, + size); + } + break; + + case GTK_IMAGE_ICON_NAME: + { + GtkIconSize size = find_icon_size (status_icon->priv->image, status_icon->priv->size); + gtk_image_set_from_icon_name (GTK_IMAGE (status_icon->priv->image), + status_icon->priv->image_data.icon_name, + size); + } + break; + + case GTK_IMAGE_EMPTY: + gtk_image_set_from_pixbuf (GTK_IMAGE (status_icon->priv->image), NULL); + break; + default: + g_assert_not_reached (); + break; + } +} + +static void +gtk_status_icon_size_allocate (GtkStatusIcon *status_icon, + GtkAllocation *allocation) +{ + GtkOrientation orientation; + gint size; + + orientation = _gtk_tray_icon_get_orientation (GTK_TRAY_ICON (status_icon->priv->tray_icon)); + + if (orientation == GTK_ORIENTATION_HORIZONTAL) + size = allocation->height; + else + size = allocation->width; + + status_icon->priv->image_width = allocation->width - GTK_MISC (status_icon->priv->image)->xpad * 2; + status_icon->priv->image_height = allocation->height - GTK_MISC (status_icon->priv->image)->ypad * 2; + + if (status_icon->priv->size != size) + { + status_icon->priv->size = size; + + g_object_notify (G_OBJECT (status_icon), "size"); + + if (!emit_size_changed_signal (status_icon, size)) + { + gtk_status_icon_update_image (status_icon); + } + } +} + +static gboolean +gtk_status_icon_button_press (GtkStatusIcon *status_icon, + GdkEventButton *event) +{ + if (event->button == 1 && event->type == GDK_2BUTTON_PRESS) + { + emit_activate_signal (status_icon); + return TRUE; + } + else if (event->button == 3 && event->type == GDK_BUTTON_PRESS) + { + emit_popup_menu_signal (status_icon, event->button, event->time); + return TRUE; + } + + return FALSE; +} + +static void +gtk_status_icon_reset_image_data (GtkStatusIcon *status_icon) +{ + status_icon->priv->storage_type = GTK_IMAGE_EMPTY; + g_object_notify (G_OBJECT (status_icon), "storage-type"); + + switch (status_icon->priv->storage_type) + { + case GTK_IMAGE_PIXBUF: + if (status_icon->priv->image_data.pixbuf) + g_object_unref (status_icon->priv->image_data.pixbuf); + status_icon->priv->image_data.pixbuf = NULL; + g_object_notify (G_OBJECT (status_icon), "pixbuf"); + break; + + case GTK_IMAGE_STOCK: + g_free (status_icon->priv->image_data.stock_id); + status_icon->priv->image_data.stock_id = NULL; + + g_object_notify (G_OBJECT (status_icon), "stock"); + break; + + case GTK_IMAGE_ICON_NAME: + g_free (status_icon->priv->image_data.icon_name); + status_icon->priv->image_data.icon_name = NULL; + + g_object_notify (G_OBJECT (status_icon), "icon-name"); + break; + + case GTK_IMAGE_EMPTY: + break; + default: + g_assert_not_reached (); + break; + } +} + +static void +gtk_status_icon_set_image (GtkStatusIcon *status_icon, + GtkImageType storage_type, + gpointer data) +{ + g_object_freeze_notify (G_OBJECT (status_icon)); + + gtk_status_icon_reset_image_data (status_icon); + + status_icon->priv->storage_type = storage_type; + g_object_notify (G_OBJECT (status_icon), "storage-type"); + + switch (storage_type) + { + case GTK_IMAGE_PIXBUF: + status_icon->priv->image_data.pixbuf = (GdkPixbuf *)data; + g_object_notify (G_OBJECT (status_icon), "pixbuf"); + break; + case GTK_IMAGE_STOCK: + status_icon->priv->image_data.stock_id = g_strdup ((const gchar *)data); + g_object_notify (G_OBJECT (status_icon), "stock"); + break; + case GTK_IMAGE_ICON_NAME: + status_icon->priv->image_data.icon_name = g_strdup ((const gchar *)data); + g_object_notify (G_OBJECT (status_icon), "icon-name"); + break; + default: + g_warning ("Image type %d not handled by GtkStatusIcon", storage_type); + } + + g_object_thaw_notify (G_OBJECT (status_icon)); + + gtk_status_icon_update_image (status_icon); +} + +void +gtk_status_icon_set_from_pixbuf (GtkStatusIcon *status_icon, + GdkPixbuf *pixbuf) +{ + g_return_if_fail (GTK_IS_STATUS_ICON (status_icon)); + g_return_if_fail (pixbuf == NULL || GDK_IS_PIXBUF (pixbuf)); + + if (pixbuf) + g_object_ref (pixbuf); + + gtk_status_icon_set_image (status_icon, GTK_IMAGE_PIXBUF, + (gpointer) pixbuf); +} + +void +gtk_status_icon_set_from_file (GtkStatusIcon *status_icon, + const gchar *filename) +{ + GdkPixbuf *pixbuf; + + g_return_if_fail (GTK_IS_STATUS_ICON (status_icon)); + g_return_if_fail (filename != NULL); + + pixbuf = gdk_pixbuf_new_from_file (filename, NULL); + + gtk_status_icon_set_from_pixbuf (status_icon, pixbuf); + + if (pixbuf) + g_object_unref (pixbuf); +} + +void +gtk_status_icon_set_from_stock (GtkStatusIcon *status_icon, + const gchar *stock_id) +{ + g_return_if_fail (GTK_IS_STATUS_ICON (status_icon)); + g_return_if_fail (stock_id != NULL); + + gtk_status_icon_set_image (status_icon, GTK_IMAGE_STOCK, + (gpointer) stock_id); +} + +void +gtk_status_icon_set_from_icon_name (GtkStatusIcon *status_icon, + const gchar *icon_name) +{ + g_return_if_fail (GTK_IS_STATUS_ICON (status_icon)); + g_return_if_fail (icon_name != NULL); + + gtk_status_icon_set_image (status_icon, GTK_IMAGE_ICON_NAME, + (gpointer) icon_name); +} + +GtkImageType +gtk_status_icon_get_storage_type (GtkStatusIcon *status_icon) +{ + g_return_val_if_fail (GTK_IS_STATUS_ICON (status_icon), GTK_IMAGE_EMPTY); + + return status_icon->priv->storage_type; +} + +GdkPixbuf * +gtk_status_icon_get_pixbuf (GtkStatusIcon *status_icon) +{ + g_return_val_if_fail (GTK_IS_STATUS_ICON (status_icon), NULL); + g_return_val_if_fail (status_icon->priv->storage_type == GTK_IMAGE_PIXBUF || + status_icon->priv->storage_type == GTK_IMAGE_EMPTY, NULL); + + if (status_icon->priv->storage_type == GTK_IMAGE_EMPTY) + status_icon->priv->image_data.pixbuf = NULL; + + return status_icon->priv->image_data.pixbuf; +} + +G_CONST_RETURN gchar * +gtk_status_icon_get_stock (GtkStatusIcon *status_icon) +{ + g_return_val_if_fail (GTK_IS_STATUS_ICON (status_icon), NULL); + g_return_val_if_fail (status_icon->priv->storage_type == GTK_IMAGE_STOCK || + status_icon->priv->storage_type == GTK_IMAGE_EMPTY, NULL); + + if (status_icon->priv->storage_type == GTK_IMAGE_EMPTY) + status_icon->priv->image_data.stock_id = NULL; + + return status_icon->priv->image_data.stock_id; +} + +G_CONST_RETURN gchar * +gtk_status_icon_get_icon_name (GtkStatusIcon *status_icon) +{ + g_return_val_if_fail (GTK_IS_STATUS_ICON (status_icon), NULL); + g_return_val_if_fail (status_icon->priv->storage_type == GTK_IMAGE_ICON_NAME || + status_icon->priv->storage_type == GTK_IMAGE_EMPTY, NULL); + + if (status_icon->priv->storage_type == GTK_IMAGE_EMPTY) + status_icon->priv->image_data.icon_name = NULL; + + return status_icon->priv->image_data.icon_name; +} + +gint +gtk_status_icon_get_size (GtkStatusIcon *status_icon) +{ + g_return_val_if_fail (GTK_IS_STATUS_ICON (status_icon), 0); + + return status_icon->priv->size; +} + +void +gtk_status_icon_set_tooltip (GtkStatusIcon *status_icon, + const gchar *tooltip_text) +{ + g_return_if_fail (GTK_IS_STATUS_ICON (status_icon)); + + gtk_tooltips_set_tip (status_icon->priv->tooltips, + status_icon->priv->tray_icon, + tooltip_text, NULL); +} + +static gboolean +gtk_status_icon_blinker (GtkStatusIcon *status_icon) +{ + status_icon->priv->blink_off = !status_icon->priv->blink_off; + + gtk_status_icon_update_image (status_icon); + + return TRUE; +} + +static void +gtk_status_icon_enable_blinking (GtkStatusIcon *status_icon) +{ + if (!status_icon->priv->blinking_timeout) + { + gtk_status_icon_blinker (status_icon); + + status_icon->priv->blinking_timeout = + g_timeout_add (BLINK_TIMEOUT, + (GSourceFunc) gtk_status_icon_blinker, + status_icon); + } +} + +static void +gtk_status_icon_disable_blinking (GtkStatusIcon *status_icon) +{ + if (status_icon->priv->blinking_timeout) + { + g_source_remove (status_icon->priv->blinking_timeout); + status_icon->priv->blinking_timeout = 0; + status_icon->priv->blink_off = FALSE; + + gtk_status_icon_update_image (status_icon); + } +} + +void +gtk_status_icon_set_visible (GtkStatusIcon *status_icon, + gboolean visible) +{ + g_return_if_fail (GTK_IS_STATUS_ICON (status_icon)); + + visible = visible != FALSE; + + if (status_icon->priv->visible != visible) + { + status_icon->priv->visible = visible; + + if (visible) + gtk_widget_show (status_icon->priv->tray_icon); + else + gtk_widget_hide (status_icon->priv->tray_icon); + + g_object_notify (G_OBJECT (status_icon), "visible"); + } +} + +gboolean +gtk_status_icon_get_visible (GtkStatusIcon *status_icon) +{ + g_return_val_if_fail (GTK_IS_STATUS_ICON (status_icon), FALSE); + + return status_icon->priv->visible; +} + +void +gtk_status_icon_set_blinking (GtkStatusIcon *status_icon, + gboolean blinking) +{ + g_return_if_fail (GTK_IS_STATUS_ICON (status_icon)); + + blinking = blinking != FALSE; + + if (status_icon->priv->blinking != blinking) + { + status_icon->priv->blinking = blinking; + + if (blinking) + gtk_status_icon_enable_blinking (status_icon); + else + gtk_status_icon_disable_blinking (status_icon); + + g_object_notify (G_OBJECT (status_icon), "blinking"); + } +} + +gboolean +gtk_status_icon_get_blinking (GtkStatusIcon *status_icon) +{ + g_return_val_if_fail (GTK_IS_STATUS_ICON (status_icon), FALSE); + + return status_icon->priv->blinking; +} + +gboolean +gtk_status_icon_is_embedded (GtkStatusIcon *status_icon) +{ + GtkPlug *plug; + + g_return_val_if_fail (GTK_IS_STATUS_ICON (status_icon), FALSE); + + plug = GTK_PLUG (status_icon->priv->tray_icon); + + if (plug->socket_window) + return TRUE; + else + return FALSE; +} + +#define __GTK_STATUS_ICON_C__ +#include "gtkaliasdef.c" diff --git a/gtk/gtkstatusicon.h b/gtk/gtkstatusicon.h new file mode 100755 index 000000000..f94f395d4 --- /dev/null +++ b/gtk/gtkstatusicon.h @@ -0,0 +1,108 @@ +/* gtkstatusicon.h: + * + * Copyright (C) 2003 Sun Microsystems, Inc. + * + * This library 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 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + * + * Authors: + * Mark McLoughlin <mark@skynet.ie> + */ + +#ifndef __GTK_STATUS_ICON_H__ +#define __GTK_STATUS_ICON_H__ + +#include <gtk/gtkimage.h> + +G_BEGIN_DECLS + +#define GTK_TYPE_STATUS_ICON (gtk_status_icon_get_type ()) +#define GTK_STATUS_ICON(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), GTK_TYPE_STATUS_ICON, GtkStatusIcon)) +#define GTK_STATUS_ICON_CLASS(k) (G_TYPE_CHECK_CLASS_CAST ((k), GTK_TYPE_STATUS_ICON, GtkStatusIconClass)) +#define GTK_IS_STATUS_ICON(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), GTK_TYPE_STATUS_ICON)) +#define GTK_IS_STATUS_ICON_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), GTK_TYPE_STATUS_ICON)) +#define GTK_STATUS_ICON_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), GTK_TYPE_STATUS_ICON, GtkStatusIconClass)) + +typedef struct _GtkStatusIcon GtkStatusIcon; +typedef struct _GtkStatusIconClass GtkStatusIconClass; +typedef struct _GtkStatusIconPrivate GtkStatusIconPrivate; + +struct _GtkStatusIcon +{ + GObject parent_instance; + + GtkStatusIconPrivate *priv; +}; + +struct _GtkStatusIconClass +{ + GObjectClass parent_class; + + void (* activate) (GtkStatusIcon *status_icon); + void (* popup_menu) (GtkStatusIcon *status_icon, + guint buttton, + guint32 activate_time); + gboolean (* size_changed) (GtkStatusIcon *status_icon, + gint size); + + void (*__gtk_reserved1); + void (*__gtk_reserved2); + void (*__gtk_reserved3); + void (*__gtk_reserved4); + void (*__gtk_reserved5); + void (*__gtk_reserved6); +}; + +GType gtk_status_icon_get_type (void) G_GNUC_CONST; + +GtkStatusIcon *gtk_status_icon_new (void); +GtkStatusIcon *gtk_status_icon_new_from_pixbuf (GdkPixbuf *pixbuf); +GtkStatusIcon *gtk_status_icon_new_from_file (const gchar *filename); +GtkStatusIcon *gtk_status_icon_new_from_stock (const gchar *stock_id); +GtkStatusIcon *gtk_status_icon_new_from_icon_name (const gchar *icon_name); + +void gtk_status_icon_set_from_pixbuf (GtkStatusIcon *status_icon, + GdkPixbuf *pixbuf); +void gtk_status_icon_set_from_file (GtkStatusIcon *status_icon, + const gchar *filename); +void gtk_status_icon_set_from_stock (GtkStatusIcon *status_icon, + const gchar *stock_id); +void gtk_status_icon_set_from_icon_name (GtkStatusIcon *status_icon, + const gchar *icon_name); + +GtkImageType gtk_status_icon_get_storage_type (GtkStatusIcon *status_icon); + +GdkPixbuf *gtk_status_icon_get_pixbuf (GtkStatusIcon *status_icon); +G_CONST_RETURN gchar *gtk_status_icon_get_stock (GtkStatusIcon *status_icon); +G_CONST_RETURN gchar *gtk_status_icon_get_icon_name (GtkStatusIcon *status_icon); + +gint gtk_status_icon_get_size (GtkStatusIcon *status_icon); + +void gtk_status_icon_set_tooltip (GtkStatusIcon *status_icon, + const gchar *tooltip_text); + +void gtk_status_icon_set_visible (GtkStatusIcon *status_icon, + gboolean visible); +gboolean gtk_status_icon_get_visible (GtkStatusIcon *status_icon); + +void gtk_status_icon_set_blinking (GtkStatusIcon *status_icon, + gboolean blinking); +gboolean gtk_status_icon_get_blinking (GtkStatusIcon *status_icon); + +gboolean gtk_status_icon_is_embedded (GtkStatusIcon *status_icon); + +G_END_DECLS + +#endif /* __GTK_STATUS_ICON_H__ */ diff --git a/gtk/gtktrayicon-x11.c b/gtk/gtktrayicon-x11.c new file mode 100755 index 000000000..8f42c9d39 --- /dev/null +++ b/gtk/gtktrayicon-x11.c @@ -0,0 +1,498 @@ +/* gtktrayicon.c + * Copyright (C) 2002 Anders Carlsson <andersca@gnu.org> + * + * This library 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 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#include <config.h> +#include <string.h> +#include <libintl.h> + +#include "gtkintl.h" +#include "gtkprivate.h" +#include "gtktrayicon.h" + +#include "gtkalias.h" + +#include "x11/gdkx.h" +#include <X11/Xatom.h> + +#define SYSTEM_TRAY_REQUEST_DOCK 0 +#define SYSTEM_TRAY_BEGIN_MESSAGE 1 +#define SYSTEM_TRAY_CANCEL_MESSAGE 2 + +#define SYSTEM_TRAY_ORIENTATION_HORZ 0 +#define SYSTEM_TRAY_ORIENTATION_VERT 1 + +enum { + PROP_0, + PROP_ORIENTATION +}; + +struct _GtkTrayIconPrivate +{ + guint stamp; + + Atom selection_atom; + Atom manager_atom; + Atom system_tray_opcode_atom; + Atom orientation_atom; + Window manager_window; + + GtkOrientation orientation; +}; + +static void gtk_tray_icon_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec); + +static void gtk_tray_icon_realize (GtkWidget *widget); +static void gtk_tray_icon_unrealize (GtkWidget *widget); +static gboolean gtk_tray_icon_delete (GtkWidget *widget, + GdkEventAny *event); + +static void gtk_tray_icon_update_manager_window (GtkTrayIcon *icon, + gboolean dock_if_realized); +static void gtk_tray_icon_manager_window_destroyed (GtkTrayIcon *icon); + +G_DEFINE_TYPE (GtkTrayIcon, gtk_tray_icon, GTK_TYPE_PLUG); + +static void +gtk_tray_icon_class_init (GtkTrayIconClass *class) +{ + GObjectClass *gobject_class = (GObjectClass *)class; + GtkWidgetClass *widget_class = (GtkWidgetClass *)class; + + gobject_class->get_property = gtk_tray_icon_get_property; + + widget_class->realize = gtk_tray_icon_realize; + widget_class->unrealize = gtk_tray_icon_unrealize; + widget_class->delete_event = gtk_tray_icon_delete; + + g_object_class_install_property (gobject_class, + PROP_ORIENTATION, + g_param_spec_enum ("orientation", + P_("Orientation"), + P_("The orientation of the tray"), + GTK_TYPE_ORIENTATION, + GTK_ORIENTATION_HORIZONTAL, + GTK_PARAM_READABLE)); + + g_type_class_add_private (class, sizeof (GtkTrayIconPrivate)); +} + +static void +gtk_tray_icon_init (GtkTrayIcon *icon) +{ + icon->priv = G_TYPE_INSTANCE_GET_PRIVATE (icon, GTK_TYPE_TRAY_ICON, + GtkTrayIconPrivate); + + icon->priv->stamp = 1; + icon->priv->orientation = GTK_ORIENTATION_HORIZONTAL; + + gtk_widget_add_events (GTK_WIDGET (icon), GDK_PROPERTY_CHANGE_MASK); +} + +static void +gtk_tray_icon_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + GtkTrayIcon *icon = GTK_TRAY_ICON (object); + + switch (prop_id) + { + case PROP_ORIENTATION: + g_value_set_enum (value, icon->priv->orientation); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +gtk_tray_icon_get_orientation_property (GtkTrayIcon *icon) +{ + Display *xdisplay; + Atom type; + int format; + union { + gulong *prop; + guchar *prop_ch; + } prop = { NULL }; + gulong nitems; + gulong bytes_after; + int error, result; + + g_assert (icon->priv->manager_window != None); + + xdisplay = GDK_DISPLAY_XDISPLAY (gtk_widget_get_display (GTK_WIDGET (icon))); + + gdk_error_trap_push (); + type = None; + result = XGetWindowProperty (xdisplay, + icon->priv->manager_window, + icon->priv->orientation_atom, + 0, G_MAXLONG, FALSE, + XA_CARDINAL, + &type, &format, &nitems, + &bytes_after, &(prop.prop_ch)); + error = gdk_error_trap_pop (); + + if (error || result != Success) + return; + + if (type == XA_CARDINAL) + { + GtkOrientation orientation; + + orientation = (prop.prop [0] == SYSTEM_TRAY_ORIENTATION_HORZ) ? + GTK_ORIENTATION_HORIZONTAL : + GTK_ORIENTATION_VERTICAL; + + if (icon->priv->orientation != orientation) + { + icon->priv->orientation = orientation; + + g_object_notify (G_OBJECT (icon), "orientation"); + } + } + + if (prop.prop) + XFree (prop.prop); +} + +static GdkFilterReturn +gtk_tray_icon_manager_filter (GdkXEvent *xevent, + GdkEvent *event, + gpointer user_data) +{ + GtkTrayIcon *icon = user_data; + XEvent *xev = (XEvent *)xevent; + + if (xev->xany.type == ClientMessage && + xev->xclient.message_type == icon->priv->manager_atom && + xev->xclient.data.l[1] == icon->priv->selection_atom) + { + gtk_tray_icon_update_manager_window (icon, TRUE); + } + else if (xev->xany.window == icon->priv->manager_window) + { + if (xev->xany.type == PropertyNotify && + xev->xproperty.atom == icon->priv->orientation_atom) + { + gtk_tray_icon_get_orientation_property (icon); + } + if (xev->xany.type == DestroyNotify) + { + gtk_tray_icon_manager_window_destroyed (icon); + } + } + + return GDK_FILTER_CONTINUE; +} + +static void +gtk_tray_icon_unrealize (GtkWidget *widget) +{ + GtkTrayIcon *icon = GTK_TRAY_ICON (widget); + GdkWindow *root_window; + + if (icon->priv->manager_window != None) + { + GdkWindow *gdkwin; + + gdkwin = gdk_window_lookup_for_display (gtk_widget_get_display (widget), + icon->priv->manager_window); + + gdk_window_remove_filter (gdkwin, gtk_tray_icon_manager_filter, icon); + } + + root_window = gdk_screen_get_root_window (gtk_widget_get_screen (widget)); + + gdk_window_remove_filter (root_window, gtk_tray_icon_manager_filter, icon); + + if (GTK_WIDGET_CLASS (gtk_tray_icon_parent_class)->unrealize) + (* GTK_WIDGET_CLASS (gtk_tray_icon_parent_class)->unrealize) (widget); +} + +static void +gtk_tray_icon_send_manager_message (GtkTrayIcon *icon, + long message, + Window window, + long data1, + long data2, + long data3) +{ + XClientMessageEvent ev; + Display *display; + + ev.type = ClientMessage; + ev.window = window; + ev.message_type = icon->priv->system_tray_opcode_atom; + ev.format = 32; + ev.data.l[0] = gdk_x11_get_server_time (GTK_WIDGET (icon)->window); + ev.data.l[1] = message; + ev.data.l[2] = data1; + ev.data.l[3] = data2; + ev.data.l[4] = data3; + + display = GDK_DISPLAY_XDISPLAY (gtk_widget_get_display (GTK_WIDGET (icon))); + + gdk_error_trap_push (); + XSendEvent (display, + icon->priv->manager_window, False, NoEventMask, (XEvent *)&ev); + XSync (display, False); + gdk_error_trap_pop (); +} + +static void +gtk_tray_icon_send_dock_request (GtkTrayIcon *icon) +{ + gtk_tray_icon_send_manager_message (icon, + SYSTEM_TRAY_REQUEST_DOCK, + icon->priv->manager_window, + gtk_plug_get_id (GTK_PLUG (icon)), + 0, 0); +} + +static void +gtk_tray_icon_update_manager_window (GtkTrayIcon *icon, + gboolean dock_if_realized) +{ + Display *xdisplay; + + if (icon->priv->manager_window != None) + return; + + xdisplay = GDK_DISPLAY_XDISPLAY (gtk_widget_get_display (GTK_WIDGET (icon))); + + XGrabServer (xdisplay); + + icon->priv->manager_window = XGetSelectionOwner (xdisplay, + icon->priv->selection_atom); + + if (icon->priv->manager_window != None) + XSelectInput (xdisplay, + icon->priv->manager_window, StructureNotifyMask|PropertyChangeMask); + + XUngrabServer (xdisplay); + XFlush (xdisplay); + + if (icon->priv->manager_window != None) + { + GdkWindow *gdkwin; + + gdkwin = gdk_window_lookup_for_display (gtk_widget_get_display (GTK_WIDGET (icon)), + icon->priv->manager_window); + + gdk_window_add_filter (gdkwin, gtk_tray_icon_manager_filter, icon); + + if (dock_if_realized && GTK_WIDGET_REALIZED (icon)) + gtk_tray_icon_send_dock_request (icon); + + gtk_tray_icon_get_orientation_property (icon); + } +} + +static void +gtk_tray_icon_manager_window_destroyed (GtkTrayIcon *icon) +{ + GdkWindow *gdkwin; + + g_return_if_fail (icon->priv->manager_window != None); + + gdkwin = gdk_window_lookup_for_display (gtk_widget_get_display (GTK_WIDGET (icon)), + icon->priv->manager_window); + + gdk_window_remove_filter (gdkwin, gtk_tray_icon_manager_filter, icon); + + icon->priv->manager_window = None; + + gtk_tray_icon_update_manager_window (icon, TRUE); +} + +static gboolean +gtk_tray_icon_delete (GtkWidget *widget, + GdkEventAny *event) +{ + GtkTrayIcon *icon = GTK_TRAY_ICON (widget); + GdkWindow *gdkwin; + + if (icon->priv->manager_window != None) + { + gdkwin = gdk_window_lookup_for_display (gtk_widget_get_display (GTK_WIDGET (icon)), + icon->priv->manager_window); + + gdk_window_remove_filter (gdkwin, gtk_tray_icon_manager_filter, icon); + + icon->priv->manager_window = None; + } + + gtk_tray_icon_update_manager_window (icon, TRUE); + + return TRUE; +} + +static void +gtk_tray_icon_realize (GtkWidget *widget) +{ + GtkTrayIcon *icon = GTK_TRAY_ICON (widget); + GdkScreen *screen; + GdkDisplay *display; + Display *xdisplay; + char buffer[256]; + GdkWindow *root_window; + + if (GTK_WIDGET_CLASS (gtk_tray_icon_parent_class)->realize) + GTK_WIDGET_CLASS (gtk_tray_icon_parent_class)->realize (widget); + + screen = gtk_widget_get_screen (widget); + display = gdk_screen_get_display (screen); + xdisplay = gdk_x11_display_get_xdisplay (display); + + /* Now see if there's a manager window around */ + g_snprintf (buffer, sizeof (buffer), + "_NET_SYSTEM_TRAY_S%d", + gdk_screen_get_number (screen)); + + icon->priv->selection_atom = XInternAtom (xdisplay, buffer, False); + + icon->priv->manager_atom = XInternAtom (xdisplay, "MANAGER", False); + + icon->priv->system_tray_opcode_atom = XInternAtom (xdisplay, + "_NET_SYSTEM_TRAY_OPCODE", + False); + + icon->priv->orientation_atom = XInternAtom (xdisplay, + "_NET_SYSTEM_TRAY_ORIENTATION", + False); + + gtk_tray_icon_update_manager_window (icon, FALSE); + gtk_tray_icon_send_dock_request (icon); + + root_window = gdk_screen_get_root_window (screen); + + /* Add a root window filter so that we get changes on MANAGER */ + gdk_window_add_filter (root_window, + gtk_tray_icon_manager_filter, icon); +} + +guint +_gtk_tray_icon_send_message (GtkTrayIcon *icon, + gint timeout, + const gchar *message, + gint len) +{ + guint stamp; + + g_return_val_if_fail (GTK_IS_TRAY_ICON (icon), 0); + g_return_val_if_fail (timeout >= 0, 0); + g_return_val_if_fail (message != NULL, 0); + + if (icon->priv->manager_window == None) + return 0; + + if (len < 0) + len = strlen (message); + + stamp = icon->priv->stamp++; + + /* Get ready to send the message */ + gtk_tray_icon_send_manager_message (icon, SYSTEM_TRAY_BEGIN_MESSAGE, + icon->priv->manager_window, + timeout, len, stamp); + + /* Now to send the actual message */ + gdk_error_trap_push (); + while (len > 0) + { + XClientMessageEvent ev; + Display *xdisplay; + + xdisplay = GDK_DISPLAY_XDISPLAY (gtk_widget_get_display (GTK_WIDGET (icon))); + + ev.type = ClientMessage; + ev.window = icon->priv->manager_window; + ev.format = 8; + ev.message_type = XInternAtom (xdisplay, + "_NET_SYSTEM_TRAY_MESSAGE_DATA", False); + if (len > 20) + { + memcpy (&ev.data, message, 20); + len -= 20; + message += 20; + } + else + { + memcpy (&ev.data, message, len); + len = 0; + } + + XSendEvent (xdisplay, + icon->priv->manager_window, False, + StructureNotifyMask, (XEvent *)&ev); + XSync (xdisplay, False); + } + + gdk_error_trap_pop (); + + return stamp; +} + +void +_gtk_tray_icon_cancel_message (GtkTrayIcon *icon, + guint id) +{ + g_return_if_fail (GTK_IS_TRAY_ICON (icon)); + g_return_if_fail (id > 0); + + gtk_tray_icon_send_manager_message (icon, SYSTEM_TRAY_CANCEL_MESSAGE, + icon->priv->manager_window, + id, 0, 0); +} + +GtkTrayIcon * +_gtk_tray_icon_new_for_screen (GdkScreen *screen, + const gchar *name) +{ + g_return_val_if_fail (GDK_IS_SCREEN (screen), NULL); + + return g_object_new (GTK_TYPE_TRAY_ICON, + "screen", screen, + "title", name, + NULL); +} + +GtkTrayIcon* +_gtk_tray_icon_new (const gchar *name) +{ + return g_object_new (GTK_TYPE_TRAY_ICON, + "title", name, + NULL); +} + +GtkOrientation +_gtk_tray_icon_get_orientation (GtkTrayIcon *icon) +{ + g_return_val_if_fail (GTK_IS_TRAY_ICON (icon), GTK_ORIENTATION_HORIZONTAL); + + return icon->priv->orientation; +} + diff --git a/gtk/gtktrayicon.h b/gtk/gtktrayicon.h new file mode 100755 index 000000000..4c1e18489 --- /dev/null +++ b/gtk/gtktrayicon.h @@ -0,0 +1,75 @@ +/* gtktrayicon.h + * Copyright (C) 2002 Anders Carlsson <andersca@gnu.org> + * + * This library 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 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#ifndef __GTK_TRAY_ICON_H__ +#define __GTK_TRAY_ICON_H__ + +#include "gtkplug.h" + +G_BEGIN_DECLS + +#define GTK_TYPE_TRAY_ICON (gtk_tray_icon_get_type ()) +#define GTK_TRAY_ICON(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GTK_TYPE_TRAY_ICON, GtkTrayIcon)) +#define GTK_TRAY_ICON_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GTK_TYPE_TRAY_ICON, GtkTrayIconClass)) +#define GTK_IS_TRAY_ICON(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GTK_TYPE_TRAY_ICON)) +#define GTK_IS_TRAY_ICON_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GTK_TYPE_TRAY_ICON)) +#define GTK_TRAY_ICON_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GTK_TYPE_TRAY_ICON, GtkTrayIconClass)) + +typedef struct _GtkTrayIcon GtkTrayIcon; +typedef struct _GtkTrayIconPrivate GtkTrayIconPrivate; +typedef struct _GtkTrayIconClass GtkTrayIconClass; + +struct _GtkTrayIcon +{ + GtkPlug parent_instance; + + GtkTrayIconPrivate *priv; +}; + +struct _GtkTrayIconClass +{ + GtkPlugClass parent_class; + + void (*__gtk_reserved1); + void (*__gtk_reserved2); + void (*__gtk_reserved3); + void (*__gtk_reserved4); + void (*__gtk_reserved5); + void (*__gtk_reserved6); +}; + +GType gtk_tray_icon_get_type (void) G_GNUC_CONST; + +GtkTrayIcon *_gtk_tray_icon_new_for_screen (GdkScreen *screen, + const gchar *name); + +GtkTrayIcon *_gtk_tray_icon_new (const gchar *name); + +guint _gtk_tray_icon_send_message (GtkTrayIcon *icon, + gint timeout, + const gchar *message, + gint len); +void _gtk_tray_icon_cancel_message (GtkTrayIcon *icon, + guint id); + +GtkOrientation _gtk_tray_icon_get_orientation (GtkTrayIcon *icon); + +G_END_DECLS + +#endif /* __GTK_TRAY_ICON_H__ */ diff --git a/tests/Makefile.am b/tests/Makefile.am index ea7a88551..c54663a99 100644 --- a/tests/Makefile.am +++ b/tests/Makefile.am @@ -50,6 +50,7 @@ noinst_PROGRAMS = \ testselection \ $(testsocket_programs) \ testspinbutton \ + teststatusicon \ testtext \ testtextbuffer \ testtoolbar \ @@ -95,6 +96,7 @@ testselection_DEPENDENCIES = $(TEST_DEPS) testsocket_DEPENDENCIES = $(DEPS) testsocket_child_DEPENDENCIES = $(DEPS) testspinbutton_DEPENDENCIES = $(TEST_DEPS) +teststatusicon_DEPENDENCIES = $(TEST_DEPS) testtext_DEPENDENCIES = $(TEST_DEPS) testtextbuffer_DEPENDENCIES = $(TEST_DEPS) testtreeedit_DEPENDENCIES = $(DEPS) @@ -133,6 +135,7 @@ testselection_LDADD = $(LDADDS) testsocket_LDADD = $(LDADDS) testsocket_child_LDADD = $(LDADDS) testspinbutton_LDADD = $(LDADDS) +teststatusicon_LDADD = $(LDADDS) testtextbuffer_LDADD = $(LDADDS) testtoolbar_LDADD = $(LDADDS) stresstest_toolbar_LDADD = $(LDADDS) @@ -195,6 +198,9 @@ testsocket_child_SOURCES = \ testspinbutton_SOURCES = \ testspinbutton.c +teststatusicon_SOURCES = \ + teststatusicon.c + testmerge_SOURCES = \ testmerge.c diff --git a/tests/teststatusicon.c b/tests/teststatusicon.c new file mode 100755 index 000000000..467baf5dd --- /dev/null +++ b/tests/teststatusicon.c @@ -0,0 +1,209 @@ +/* gtkstatusicon-x11.c: + * + * Copyright (C) 2003 Sun Microsystems, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + * + * Authors: + * Mark McLoughlin <mark@skynet.ie> + */ + +#include <gtk/gtk.h> + +typedef enum +{ + TEST_STATUS_FILE, + TEST_STATUS_DIRECTORY +} TestStatus; + +static TestStatus status = TEST_STATUS_FILE; +static gint timeout = 0; + +static void +update_icon (GtkStatusIcon *status_icon) +{ + gchar *icon_name; + gchar *tooltip; + + if (status == TEST_STATUS_FILE) + { + icon_name = "gnome-fs-regular"; + tooltip = "Regular File"; + } + else + { + icon_name = "gnome-fs-directory"; + tooltip = "Directory"; + } + + gtk_status_icon_set_from_icon_name (status_icon, icon_name); + gtk_status_icon_set_tooltip (status_icon, tooltip); +} + +static gboolean +timeout_handler (gpointer data) +{ + GtkStatusIcon *icon = GTK_STATUS_ICON (data); + + if (status == TEST_STATUS_FILE) + status = TEST_STATUS_DIRECTORY; + else + status = TEST_STATUS_FILE; + + update_icon (icon); + + return TRUE; +} + +static void +blink_toggle_toggled (GtkToggleButton *toggle, + GtkStatusIcon *icon) +{ + gtk_status_icon_set_blinking (icon, + gtk_toggle_button_get_active (toggle)); +} + +static void +visible_toggle_toggled (GtkToggleButton *toggle, + GtkStatusIcon *icon) +{ + gtk_status_icon_set_visible (icon, + gtk_toggle_button_get_active (toggle)); +} + +static void +timeout_toggle_toggled (GtkToggleButton *toggle, + GtkStatusIcon *icon) +{ + if (timeout) + { + g_source_remove (timeout); + timeout = 0; + } + else + { + timeout = g_timeout_add (2000, timeout_handler, icon); + } +} + +static void +icon_activated (GtkStatusIcon *icon) +{ + GtkWidget *dialog; + GtkWidget *toggle; + + dialog = g_object_get_data (G_OBJECT (icon), "test-status-icon-dialog"); + if (dialog == NULL) + { + dialog = gtk_message_dialog_new (NULL, 0, + GTK_MESSAGE_QUESTION, + GTK_BUTTONS_CLOSE, + "You wanna test the status icon ?"); + + g_object_set_data_full (G_OBJECT (icon), "test-status-icon-dialog", + dialog, (GDestroyNotify) gtk_widget_destroy); + + g_signal_connect (dialog, "response", + G_CALLBACK (gtk_widget_hide), NULL); + g_signal_connect (dialog, "delete_event", + G_CALLBACK (gtk_widget_hide_on_delete), NULL); + + toggle = gtk_toggle_button_new_with_mnemonic ("_Show the icon"); + gtk_box_pack_end (GTK_BOX (GTK_DIALOG (dialog)->vbox), toggle, TRUE, TRUE, 6); + gtk_widget_show (toggle); + + gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (toggle), + gtk_status_icon_get_visible (icon)); + g_signal_connect (toggle, "toggled", + G_CALLBACK (visible_toggle_toggled), icon); + + toggle = gtk_toggle_button_new_with_mnemonic ("_Blink the icon"); + gtk_box_pack_end (GTK_BOX (GTK_DIALOG (dialog)->vbox), toggle, TRUE, TRUE, 6); + gtk_widget_show (toggle); + + gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (toggle), + gtk_status_icon_get_blinking (icon)); + g_signal_connect (toggle, "toggled", + G_CALLBACK (blink_toggle_toggled), icon); + + toggle = gtk_toggle_button_new_with_mnemonic ("_Change images"); + gtk_box_pack_end (GTK_BOX (GTK_DIALOG (dialog)->vbox), toggle, TRUE, TRUE, 6); + gtk_widget_show (toggle); + + gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (toggle), + timeout != 0); + g_signal_connect (toggle, "toggled", + G_CALLBACK (timeout_toggle_toggled), icon); + } + + gtk_window_present (GTK_WINDOW (dialog)); +} + +static void +check_activated (GtkCheckMenuItem *item, + GtkStatusIcon *icon) +{ + gtk_status_icon_set_blinking (icon, + gtk_check_menu_item_get_active (item)); +} + +static void +popup_menu (GtkStatusIcon *icon, + guint button, + guint32 activate_time) +{ + GtkWidget *menu, *menuitem; + + menu = gtk_menu_new (); + + menuitem = gtk_check_menu_item_new_with_label ("Blink"); + gtk_check_menu_item_set_active (GTK_CHECK_MENU_ITEM (menuitem), + gtk_status_icon_get_blinking (icon)); + g_signal_connect (menuitem, "activate", G_CALLBACK (check_activated), icon); + + gtk_menu_shell_append (GTK_MENU_SHELL (menu), menuitem); + + gtk_widget_show (menuitem); + + gtk_menu_popup (GTK_MENU (menu), + NULL, NULL, NULL, NULL, + button, activate_time); +} + +int +main (int argc, char **argv) +{ + GtkStatusIcon *icon; + + gtk_init (&argc, &argv); + + icon = gtk_status_icon_new (); + update_icon (icon); + + gtk_status_icon_set_blinking (GTK_STATUS_ICON (icon), TRUE); + + g_signal_connect (icon, "activate", + G_CALLBACK (icon_activated), NULL); + + g_signal_connect (icon, "popup-menu", + G_CALLBACK (popup_menu), NULL); + + timeout = g_timeout_add (2000, timeout_handler, icon); + + gtk_main (); + + return 0; +} |