From 8781e176e8c3b032a2f03cc429946b541da58731 Mon Sep 17 00:00:00 2001 From: Jesse van den Kieboom Date: Mon, 20 Feb 2006 22:30:20 +0000 Subject: [PATCH] Initial import --- gnoemoe/widgets/eggnotificationbubble.c | 559 ++++++++++++++++++++++++ gnoemoe/widgets/eggnotificationbubble.h | 92 ++++ 2 files changed, 651 insertions(+) create mode 100644 gnoemoe/widgets/eggnotificationbubble.c create mode 100644 gnoemoe/widgets/eggnotificationbubble.h diff --git a/gnoemoe/widgets/eggnotificationbubble.c b/gnoemoe/widgets/eggnotificationbubble.c new file mode 100644 index 0000000..2452b99 --- /dev/null +++ b/gnoemoe/widgets/eggnotificationbubble.c @@ -0,0 +1,559 @@ +/* EggNotificationBubble + * Copyright (C) 2005 Colin Walters + * + * 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 +#include +#include + +#include +#include "eggnotificationbubble.h" + +#define DEFAULT_DELAY 500 /* Default delay in ms */ +#define STICKY_DELAY 0 /* Delay before popping up next bubble + * if we're sticky + */ +#define STICKY_REVERT_DELAY 1000 /* Delay before sticky bubble revert + * to normal + */ + +#define BORDER_SIZE 15 + +static void egg_notification_bubble_class_init (EggNotificationBubbleClass *klass); +static void egg_notification_bubble_init (EggNotificationBubble *bubble); +static void egg_notification_bubble_destroy (GtkObject *object); +static void egg_notification_bubble_detach (EggNotificationBubble *bubble); + +static void egg_notification_bubble_event_handler (GtkWidget *widget, + GdkEvent *event, + gpointer user_data); +static gint egg_notification_bubble_paint_window (EggNotificationBubble *bubble); +static void egg_notification_bubble_unset_bubble_window (EggNotificationBubble *bubble); + +static GtkObjectClass *parent_class; + +enum +{ + NOTIFICATION_CLICKED, + NOTIFICATION_TIMEOUT, + LAST_SIGNAL +}; + +static guint egg_notification_bubble_signals[LAST_SIGNAL] = { 0 }; + +GType +egg_notification_bubble_get_type (void) +{ + static GType bubble_type = 0; + + if (!bubble_type) + { + static const GTypeInfo bubble_info = + { + sizeof (EggNotificationBubbleClass), + NULL, /* base_init */ + NULL, /* base_finalize */ + (GClassInitFunc) egg_notification_bubble_class_init, + NULL, /* class_finalize */ + NULL, /* class_data */ + sizeof (EggNotificationBubble), + 0, /* n_preallocs */ + (GInstanceInitFunc) egg_notification_bubble_init, + }; + + bubble_type = g_type_register_static (GTK_TYPE_OBJECT, "EggNotificationBubble", + &bubble_info, 0); + } + + return bubble_type; +} + +static void +egg_notification_bubble_class_init (EggNotificationBubbleClass *class) +{ + GtkObjectClass *object_class; + + object_class = (GtkObjectClass*) class; + + parent_class = g_type_class_peek_parent (class); + + object_class->destroy = egg_notification_bubble_destroy; + + egg_notification_bubble_signals[NOTIFICATION_CLICKED] = + g_signal_new ("clicked", + EGG_TYPE_NOTIFICATION_BUBBLE, + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (EggNotificationBubbleClass, clicked), + NULL, NULL, + g_cclosure_marshal_VOID__VOID, + G_TYPE_NONE, + 0); + egg_notification_bubble_signals[NOTIFICATION_TIMEOUT] = + g_signal_new ("timeout", + EGG_TYPE_NOTIFICATION_BUBBLE, + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (EggNotificationBubbleClass, timeout), + NULL, NULL, + g_cclosure_marshal_VOID__VOID, + G_TYPE_NONE, 0); +} + +static void +egg_notification_bubble_init (EggNotificationBubble *bubble) +{ + bubble->bubble_window = NULL; +} + +static void +bubble_window_display_closed (GdkDisplay *display, + gboolean was_error, + EggNotificationBubble *bubble) +{ + egg_notification_bubble_unset_bubble_window (bubble); +} + +static void +disconnect_bubble_window_display_closed (EggNotificationBubble *bubble) +{ + g_signal_handlers_disconnect_by_func (gtk_widget_get_display (bubble->bubble_window), + (gpointer) bubble_window_display_closed, + bubble); +} + +static void +egg_notification_bubble_unset_bubble_window (EggNotificationBubble *bubble) +{ + if (bubble->bubble_window) + { + disconnect_bubble_window_display_closed (bubble); + + gtk_widget_destroy (bubble->bubble_window); + bubble->bubble_window = NULL; + } +} + +static void +egg_notification_bubble_destroy (GtkObject *object) +{ + EggNotificationBubble *bubble = EGG_NOTIFICATION_BUBBLE (object); + + g_return_if_fail (bubble != NULL); + + if (bubble->timeout_id) + { + g_source_remove (bubble->timeout_id); + bubble->timeout_id = 0; + } + + egg_notification_bubble_detach (bubble); + + egg_notification_bubble_unset_bubble_window (bubble); + + GTK_OBJECT_CLASS (parent_class)->destroy (object); +} + +static void +force_window (EggNotificationBubble *bubble) +{ + g_return_if_fail (EGG_IS_NOTIFICATION_BUBBLE (bubble)); + + if (!bubble->bubble_window) + { + GtkWidget *vbox; + + bubble->bubble_window = gtk_window_new (GTK_WINDOW_POPUP); + gtk_widget_add_events (bubble->bubble_window, GDK_BUTTON_PRESS_MASK); + gtk_widget_set_app_paintable (bubble->bubble_window, TRUE); + gtk_window_set_resizable (GTK_WINDOW (bubble->bubble_window), FALSE); + gtk_widget_set_name (bubble->bubble_window, "gtk-tooltips"); + gtk_container_set_border_width (GTK_CONTAINER (bubble->bubble_window), BORDER_SIZE + 5); + + g_signal_connect_swapped (bubble->bubble_window, + "expose_event", + G_CALLBACK (egg_notification_bubble_paint_window), + bubble); + + bubble->bubble_header_label = gtk_label_new (NULL); + bubble->bubble_body_label = gtk_label_new (NULL); + gtk_label_set_line_wrap (GTK_LABEL (bubble->bubble_header_label), TRUE); + gtk_label_set_line_wrap (GTK_LABEL (bubble->bubble_body_label), TRUE); + gtk_misc_set_alignment (GTK_MISC (bubble->bubble_header_label), 0.5, 0.5); + gtk_misc_set_alignment (GTK_MISC (bubble->bubble_body_label), 0.5, 0.5); + gtk_widget_show (bubble->bubble_header_label); + gtk_widget_show (bubble->bubble_body_label); + + bubble->main_hbox = gtk_hbox_new (FALSE, 10); + gtk_container_add (GTK_CONTAINER (bubble->main_hbox), bubble->bubble_body_label); + + vbox = gtk_vbox_new (FALSE, 5); + gtk_container_add (GTK_CONTAINER (vbox), bubble->bubble_header_label); + gtk_container_add (GTK_CONTAINER (vbox), bubble->main_hbox); + gtk_container_add (GTK_CONTAINER (bubble->bubble_window), vbox); + + g_signal_connect (bubble->bubble_window, + "destroy", + G_CALLBACK (gtk_widget_destroyed), + &bubble->bubble_window); + g_signal_connect_after (bubble->bubble_window, "event-after", + G_CALLBACK (egg_notification_bubble_event_handler), + bubble); + } +} + +void +egg_notification_bubble_attach (EggNotificationBubble *bubble, + GtkWidget *widget) +{ + bubble->widget = widget; + + g_signal_connect_object (widget, "destroy", + G_CALLBACK (g_object_unref), + bubble, G_CONNECT_SWAPPED); +} + +void +egg_notification_bubble_set (EggNotificationBubble *bubble, + const gchar *bubble_header_text, + GtkWidget *icon, + const gchar *bubble_body_text) +{ + g_return_if_fail (EGG_IS_NOTIFICATION_BUBBLE (bubble)); + + g_free (bubble->bubble_header_text); + g_free (bubble->bubble_body_text); + if (bubble->icon) + { + if (bubble->active) + gtk_container_remove (GTK_CONTAINER (bubble->main_hbox), bubble->icon); + g_object_unref (G_OBJECT (bubble->icon)); + bubble->icon = NULL; + } + + bubble->bubble_header_text = g_strdup (bubble_header_text); + bubble->bubble_body_text = g_strdup (bubble_body_text); + if (icon) + bubble->icon = g_object_ref (G_OBJECT (icon)); +} + +static gint +egg_notification_bubble_paint_window (EggNotificationBubble *bubble) +{ + GtkRequisition req; + + gtk_widget_size_request (bubble->bubble_window, &req); + gtk_paint_flat_box (bubble->bubble_window->style, bubble->bubble_window->window, + GTK_STATE_NORMAL, GTK_SHADOW_OUT, + NULL, GTK_WIDGET (bubble->bubble_window), "notification", + 0, 0, req.width, req.height); + return FALSE; +} + +static void +subtract_rectangle (GdkRegion *region, GdkRectangle *rectangle) +{ + GdkRegion *temp_region; + + temp_region = gdk_region_rectangle (rectangle); + gdk_region_subtract (region, temp_region); + gdk_region_destroy (temp_region); +} + +static GdkRegion * +add_bevels_to_rectangle (GdkRectangle *rectangle) +{ + GdkRectangle temp_rect; + GdkRegion *region = gdk_region_rectangle (rectangle); + + temp_rect.width = 5; + temp_rect.height = 1; + + /* Top left */ + temp_rect.x = rectangle->x; + temp_rect.y = rectangle->y; + subtract_rectangle (region, &temp_rect); + + temp_rect.y += 1; + temp_rect.width -= 2; + subtract_rectangle (region, &temp_rect); + + temp_rect.y += 1; + temp_rect.width -= 1; + subtract_rectangle (region, &temp_rect); + + temp_rect.y += 1; + temp_rect.width -= 1; + temp_rect.height = 2; + subtract_rectangle (region, &temp_rect); + + + /* Top right */ + temp_rect.width = 5; + temp_rect.height = 1; + + temp_rect.x = (rectangle->x + rectangle->width) - temp_rect.width; + temp_rect.y = rectangle->y; + subtract_rectangle (region, &temp_rect); + + temp_rect.y += 1; + temp_rect.x += 2; + subtract_rectangle (region, &temp_rect); + + temp_rect.y += 1; + temp_rect.x += 1; + subtract_rectangle (region, &temp_rect); + + temp_rect.y += 1; + temp_rect.x += 1; + temp_rect.height = 2; + subtract_rectangle (region, &temp_rect); + + /* Bottom right */ + temp_rect.width = 5; + temp_rect.height = 1; + + temp_rect.x = (rectangle->x + rectangle->width) - temp_rect.width; + temp_rect.y = (rectangle->y + rectangle->height) - temp_rect.height; + subtract_rectangle (region, &temp_rect); + + temp_rect.y -= 1; + temp_rect.x += 2; + subtract_rectangle (region, &temp_rect); + + temp_rect.y -= 1; + temp_rect.x += 1; + subtract_rectangle (region, &temp_rect); + + temp_rect.y -= 1; + temp_rect.x += 1; + temp_rect.height = 2; + subtract_rectangle (region, &temp_rect); + + /* Bottom left */ + temp_rect.width = 5; + temp_rect.height = 1; + + temp_rect.x = rectangle->x; + temp_rect.y = rectangle->y + rectangle->height; + subtract_rectangle (region, &temp_rect); + + temp_rect.y -= 1; + temp_rect.width -= 2; + subtract_rectangle (region, &temp_rect); + + temp_rect.y -= 1; + temp_rect.width -= 1; + subtract_rectangle (region, &temp_rect); + + temp_rect.y -= 1; + temp_rect.width -= 1; + temp_rect.height = 2; + subtract_rectangle (region, &temp_rect); + + return region; +} + +static gboolean +idle_notification_expired (gpointer data) +{ + EggNotificationBubble *bubble = data; + + GDK_THREADS_ENTER (); + + g_signal_emit (bubble, egg_notification_bubble_signals[NOTIFICATION_TIMEOUT], 0); + egg_notification_bubble_hide (bubble); + + GDK_THREADS_LEAVE (); + return FALSE; +} + +static void +draw_bubble (EggNotificationBubble *bubble, guint timeout) +{ + GtkRequisition requisition; + GtkWidget *widget; + GtkStyle *style; + gint x, y, w, h; + GdkScreen *screen; + gint monitor_num; + GdkRectangle monitor; + GdkPoint triangle_points[3]; + char *markuptext; + char *markupquoted; + GdkRectangle rectangle; + GdkRegion *region; + GdkRegion *triangle_region; + enum { + ORIENT_TOP = 0, + ORIENT_BOTTOM = 1 + } orient; + guint rectangle_border; + guint triangle_offset; + + if (!bubble->bubble_window) + force_window (bubble); + + gtk_widget_ensure_style (bubble->bubble_window); + style = bubble->bubble_window->style; + + widget = bubble->widget; + + screen = gtk_widget_get_screen (widget); + + if (bubble->icon) + { + gtk_box_pack_start_defaults (GTK_BOX (bubble->main_hbox), bubble->icon); + gtk_box_reorder_child (GTK_BOX (bubble->main_hbox), bubble->icon, 0); + } + + markupquoted = g_markup_escape_text (bubble->bubble_header_text, -1); + markuptext = g_strdup_printf ("%s", markupquoted); + gtk_label_set_markup (GTK_LABEL (bubble->bubble_header_label), markuptext); + g_free (markuptext); + g_free (markupquoted); + gtk_label_set_text (GTK_LABEL (bubble->bubble_body_label), bubble->bubble_body_text); + + gtk_window_move (GTK_WINDOW (bubble->bubble_window), 0, 2 * gdk_screen_get_height (screen)); + gtk_widget_show_all (bubble->bubble_window); + + gtk_widget_size_request (bubble->bubble_window, &requisition); + w = requisition.width; + h = requisition.height; + + gdk_window_get_origin (widget->window, &x, &y); + if (GTK_WIDGET_NO_WINDOW (widget)) + { + x += widget->allocation.x; + y += widget->allocation.y; + } + + orient = ORIENT_BOTTOM; + + triangle_offset = 20; + + x -= triangle_offset; + + monitor_num = gdk_screen_get_monitor_at_window (screen, widget->window); + gdk_screen_get_monitor_geometry (screen, monitor_num, &monitor); + + if ((x + w) > monitor.x + monitor.width) { + gint offset = (x + w) - (monitor.x + monitor.width); + triangle_offset += offset; + x -= offset; + } else if (x < monitor.x) { + gint offset = monitor.x - x; + triangle_offset -= offset; + x += offset; + } + + if ((y + h + widget->allocation.height + 4) > monitor.y + monitor.height) + { + y -= (h - 4); + orient = ORIENT_TOP; + } + else + y = y + widget->allocation.height + 4; + + /* Overlap the arrow with the object slightly */ + if (orient == ORIENT_BOTTOM) + y -= 5; + else + y += 5; + + rectangle_border = BORDER_SIZE-2; + + rectangle.x = rectangle_border; + rectangle.y = rectangle_border; + rectangle.width = w - (rectangle_border * 2); + rectangle.height = h - (rectangle_border * 2); + region = add_bevels_to_rectangle (&rectangle); + + triangle_points[0].x = triangle_offset; + triangle_points[0].y = orient == ORIENT_BOTTOM ? BORDER_SIZE : h - BORDER_SIZE; + triangle_points[1].x = triangle_points[0].x + 20; + triangle_points[1].y = triangle_points[0].y; + triangle_points[2].x = (triangle_points[1].x + triangle_points[0].x) /2; + triangle_points[2].y = orient == ORIENT_BOTTOM ? 0 : h; + + triangle_region = gdk_region_polygon (triangle_points, 3, GDK_WINDING_RULE); + + gdk_region_union (region, triangle_region); + gdk_region_destroy (triangle_region); + + gdk_window_shape_combine_region (bubble->bubble_window->window, region, 0, 0); + + gtk_window_move (GTK_WINDOW (bubble->bubble_window), x, y); + bubble->active = TRUE; + if (bubble->timeout_id) + { + g_source_remove (bubble->timeout_id); + bubble->timeout_id = 0; + } + if (timeout > 0) + bubble->timeout_id = g_timeout_add (timeout, idle_notification_expired, bubble); +} + +void +egg_notification_bubble_show (EggNotificationBubble *bubble, guint timeout) +{ + draw_bubble (bubble, timeout); +} + +void +egg_notification_bubble_hide (EggNotificationBubble *bubble) +{ + if (bubble->bubble_window) + gtk_widget_hide (bubble->bubble_window); + if (bubble->timeout_id) + { + g_source_remove (bubble->timeout_id); + bubble->timeout_id = 0; + } +} + +EggNotificationBubble* +egg_notification_bubble_new (void) +{ + return g_object_new (EGG_TYPE_NOTIFICATION_BUBBLE, NULL); +} + +static void +egg_notification_bubble_event_handler (GtkWidget *widget, + GdkEvent *event, + gpointer user_data) +{ + EggNotificationBubble *bubble; + + bubble = EGG_NOTIFICATION_BUBBLE (user_data); + + switch (event->type) + { + case GDK_BUTTON_PRESS: + g_signal_emit (bubble, egg_notification_bubble_signals[NOTIFICATION_CLICKED], 0); + break; + default: + break; + } +} + +static void +egg_notification_bubble_detach (EggNotificationBubble *bubble) +{ + g_return_if_fail (bubble->widget); + + g_object_unref (bubble->widget); +} diff --git a/gnoemoe/widgets/eggnotificationbubble.h b/gnoemoe/widgets/eggnotificationbubble.h new file mode 100644 index 0000000..a7fc44d --- /dev/null +++ b/gnoemoe/widgets/eggnotificationbubble.h @@ -0,0 +1,92 @@ +/* EggNotificationBubble + * Copyright (C) 2005 Colin Walters + * + * 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 __EGG_NOTIFICATION_BUBBLE_H__ +#define __EGG_NOTIFICATION_BUBBLE_H__ + +#include +#include + +G_BEGIN_DECLS + +#define EGG_TYPE_NOTIFICATION_BUBBLE (egg_notification_bubble_get_type ()) +#define EGG_NOTIFICATION_BUBBLE(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), EGG_TYPE_NOTIFICATION_BUBBLE, EggNotificationBubble)) +#define EGG_NOTIFICATION_BUBBLE_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), EGG_TYPE_NOTIFICATION_BUBBLE, EggNotificationBubbleClass)) +#define EGG_IS_NOTIFICATION_BUBBLE(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), EGG_TYPE_NOTIFICATION_BUBBLE)) +#define EGG_IS_NOTIFICATION_BUBBLE_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), EGG_TYPE_NOTIFICATION_BUBBLE)) +#define EGG_NOTIFICATION_BUBBLE_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), EGG_TYPE_NOTIFICATION_BUBBLE, EggNotificationBubbleClass)) + + +typedef struct _EggNotificationBubble EggNotificationBubble; +typedef struct _EggNotificationBubbleClass EggNotificationBubbleClass; +typedef struct _EggNotificationBubbleData EggNotificationBubbleData; + +struct _EggNotificationBubble +{ + GtkObject parent_instance; + + GtkWidget *widget; + + guint timeout_id; + + char *bubble_header_text; + char *bubble_body_text; + GtkWidget *icon; + + gboolean active; + GtkWidget *bubble_window; + GtkWidget *main_hbox; + GtkWidget *bubble_header_label; + GtkWidget *bubble_body_label; +}; + +struct _EggNotificationBubbleClass +{ + GtkObjectClass parent_class; + + void (*clicked) (void); + void (*timeout) (void); + + /* Padding for future expansion */ + void (*_gtk_reserved1) (void); + void (*_gtk_reserved2) (void); + void (*_gtk_reserved3) (void); + void (*_gtk_reserved4) (void); +}; + +GType egg_notification_bubble_get_type (void) G_GNUC_CONST; +EggNotificationBubble* egg_notification_bubble_new (void); + +void egg_notification_bubble_attach (EggNotificationBubble *bubble, GtkWidget *widget); + +void egg_notification_bubble_set (EggNotificationBubble *bubble, + const gchar *notification_header, + GtkWidget *icon, + const gchar *notification_body); + +void egg_notification_bubble_show (EggNotificationBubble *bubble, guint timeout); +void egg_notification_bubble_hide (EggNotificationBubble *bubble); + +gboolean egg_notification_bubble_get_info_from_tip_window (GtkWindow *window, + EggNotificationBubble **bubble, + GtkWidget **current_widget); + +G_END_DECLS + +#endif /* __EGG_NOTIFICATION_BUBBLE_H__ */