1
/* EggNotificationBubble
2
* Copyright (C) 2005 Colin Walters <walters@verbum.org>
4
* This library is free software; you can redistribute it and/or
5
* modify it under the terms of the GNU Lesser General Public
6
* License as published by the Free Software Foundation; either
7
* version 2 of the License, or (at your option) any later version.
9
* This library is distributed in the hope that it will be useful,
10
* but WITHOUT ANY WARRANTY; without even the implied warranty of
11
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12
* Lesser General Public License for more details.
14
* You should have received a copy of the GNU Lesser General Public
15
* License along with this library; if not, write to the
16
* Free Software Foundation, Inc., 59 Temple Place - Suite 330,
17
* Boston, MA 02111-1307, USA.
25
#include "eggnotificationbubble.h"
27
#define DEFAULT_DELAY 500 /* Default delay in ms */
28
#define STICKY_DELAY 0 /* Delay before popping up next bubble
31
#define STICKY_REVERT_DELAY 1000 /* Delay before sticky bubble revert
35
#define BORDER_SIZE 15
37
static void egg_notification_bubble_class_init (EggNotificationBubbleClass *klass);
38
static void egg_notification_bubble_init (EggNotificationBubble *bubble);
39
static void egg_notification_bubble_destroy (GtkObject *object);
40
static void egg_notification_bubble_detach (EggNotificationBubble *bubble);
42
static void egg_notification_bubble_event_handler (GtkWidget *widget,
45
static gint egg_notification_bubble_paint_window (EggNotificationBubble *bubble);
46
static void egg_notification_bubble_unset_bubble_window (EggNotificationBubble *bubble);
48
static GtkObjectClass *parent_class;
57
static guint egg_notification_bubble_signals[LAST_SIGNAL] = { 0 };
60
egg_notification_bubble_get_type (void)
62
static GType bubble_type = 0;
66
static const GTypeInfo bubble_info =
68
sizeof (EggNotificationBubbleClass),
70
NULL, /* base_finalize */
71
(GClassInitFunc) egg_notification_bubble_class_init,
72
NULL, /* class_finalize */
73
NULL, /* class_data */
74
sizeof (EggNotificationBubble),
76
(GInstanceInitFunc) egg_notification_bubble_init,
79
bubble_type = g_type_register_static (GTK_TYPE_OBJECT, "EggNotificationBubble",
87
egg_notification_bubble_class_init (EggNotificationBubbleClass *class)
89
GtkObjectClass *object_class;
91
object_class = (GtkObjectClass*) class;
93
parent_class = g_type_class_peek_parent (class);
95
object_class->destroy = egg_notification_bubble_destroy;
97
egg_notification_bubble_signals[NOTIFICATION_CLICKED] =
98
g_signal_new ("clicked",
99
EGG_TYPE_NOTIFICATION_BUBBLE,
101
G_STRUCT_OFFSET (EggNotificationBubbleClass, clicked),
103
g_cclosure_marshal_VOID__VOID,
106
egg_notification_bubble_signals[NOTIFICATION_TIMEOUT] =
107
g_signal_new ("timeout",
108
EGG_TYPE_NOTIFICATION_BUBBLE,
110
G_STRUCT_OFFSET (EggNotificationBubbleClass, timeout),
112
g_cclosure_marshal_VOID__VOID,
117
egg_notification_bubble_init (EggNotificationBubble *bubble)
119
bubble->bubble_window = NULL;
123
bubble_window_display_closed (GdkDisplay *display,
125
EggNotificationBubble *bubble)
127
egg_notification_bubble_unset_bubble_window (bubble);
131
disconnect_bubble_window_display_closed (EggNotificationBubble *bubble)
133
g_signal_handlers_disconnect_by_func (gtk_widget_get_display (bubble->bubble_window),
134
(gpointer) bubble_window_display_closed,
139
egg_notification_bubble_unset_bubble_window (EggNotificationBubble *bubble)
141
if (bubble->bubble_window)
143
disconnect_bubble_window_display_closed (bubble);
145
gtk_widget_destroy (bubble->bubble_window);
146
bubble->bubble_window = NULL;
151
egg_notification_bubble_destroy (GtkObject *object)
153
EggNotificationBubble *bubble = EGG_NOTIFICATION_BUBBLE (object);
155
g_return_if_fail (bubble != NULL);
157
if (bubble->timeout_id)
159
g_source_remove (bubble->timeout_id);
160
bubble->timeout_id = 0;
163
egg_notification_bubble_detach (bubble);
165
egg_notification_bubble_unset_bubble_window (bubble);
167
GTK_OBJECT_CLASS (parent_class)->destroy (object);
171
force_window (EggNotificationBubble *bubble)
173
g_return_if_fail (EGG_IS_NOTIFICATION_BUBBLE (bubble));
175
if (!bubble->bubble_window)
179
bubble->bubble_window = gtk_window_new (GTK_WINDOW_POPUP);
180
gtk_widget_add_events (bubble->bubble_window, GDK_BUTTON_PRESS_MASK);
181
gtk_widget_set_app_paintable (bubble->bubble_window, TRUE);
182
gtk_window_set_resizable (GTK_WINDOW (bubble->bubble_window), FALSE);
183
gtk_widget_set_name (bubble->bubble_window, "gtk-tooltips");
184
gtk_container_set_border_width (GTK_CONTAINER (bubble->bubble_window), BORDER_SIZE + 5);
186
g_signal_connect_swapped (bubble->bubble_window,
188
G_CALLBACK (egg_notification_bubble_paint_window),
191
bubble->bubble_header_label = gtk_label_new (NULL);
192
bubble->bubble_body_label = gtk_label_new (NULL);
193
gtk_label_set_line_wrap (GTK_LABEL (bubble->bubble_header_label), TRUE);
194
gtk_label_set_line_wrap (GTK_LABEL (bubble->bubble_body_label), TRUE);
195
gtk_misc_set_alignment (GTK_MISC (bubble->bubble_header_label), 0.5, 0.5);
196
gtk_misc_set_alignment (GTK_MISC (bubble->bubble_body_label), 0.5, 0.5);
197
gtk_widget_show (bubble->bubble_header_label);
198
gtk_widget_show (bubble->bubble_body_label);
200
bubble->main_hbox = gtk_hbox_new (FALSE, 10);
201
gtk_container_add (GTK_CONTAINER (bubble->main_hbox), bubble->bubble_body_label);
203
vbox = gtk_vbox_new (FALSE, 5);
204
gtk_container_add (GTK_CONTAINER (vbox), bubble->bubble_header_label);
205
gtk_container_add (GTK_CONTAINER (vbox), bubble->main_hbox);
206
gtk_container_add (GTK_CONTAINER (bubble->bubble_window), vbox);
208
g_signal_connect (bubble->bubble_window,
210
G_CALLBACK (gtk_widget_destroyed),
211
&bubble->bubble_window);
212
g_signal_connect_after (bubble->bubble_window, "event-after",
213
G_CALLBACK (egg_notification_bubble_event_handler),
219
egg_notification_bubble_attach (EggNotificationBubble *bubble,
222
bubble->widget = widget;
224
g_signal_connect_object (widget, "destroy",
225
G_CALLBACK (g_object_unref),
226
bubble, G_CONNECT_SWAPPED);
230
egg_notification_bubble_set (EggNotificationBubble *bubble,
231
const gchar *bubble_header_text,
233
const gchar *bubble_body_text)
235
g_return_if_fail (EGG_IS_NOTIFICATION_BUBBLE (bubble));
237
g_free (bubble->bubble_header_text);
238
g_free (bubble->bubble_body_text);
242
gtk_container_remove (GTK_CONTAINER (bubble->main_hbox), bubble->icon);
243
g_object_unref (G_OBJECT (bubble->icon));
247
bubble->bubble_header_text = g_strdup (bubble_header_text);
248
bubble->bubble_body_text = g_strdup (bubble_body_text);
250
bubble->icon = g_object_ref (G_OBJECT (icon));
254
egg_notification_bubble_paint_window (EggNotificationBubble *bubble)
258
gtk_widget_size_request (bubble->bubble_window, &req);
259
gtk_paint_flat_box (bubble->bubble_window->style, bubble->bubble_window->window,
260
GTK_STATE_NORMAL, GTK_SHADOW_OUT,
261
NULL, GTK_WIDGET (bubble->bubble_window), "notification",
262
0, 0, req.width, req.height);
267
subtract_rectangle (GdkRegion *region, GdkRectangle *rectangle)
269
GdkRegion *temp_region;
271
temp_region = gdk_region_rectangle (rectangle);
272
gdk_region_subtract (region, temp_region);
273
gdk_region_destroy (temp_region);
277
add_bevels_to_rectangle (GdkRectangle *rectangle)
279
GdkRectangle temp_rect;
280
GdkRegion *region = gdk_region_rectangle (rectangle);
283
temp_rect.height = 1;
286
temp_rect.x = rectangle->x;
287
temp_rect.y = rectangle->y;
288
subtract_rectangle (region, &temp_rect);
291
temp_rect.width -= 2;
292
subtract_rectangle (region, &temp_rect);
295
temp_rect.width -= 1;
296
subtract_rectangle (region, &temp_rect);
299
temp_rect.width -= 1;
300
temp_rect.height = 2;
301
subtract_rectangle (region, &temp_rect);
306
temp_rect.height = 1;
308
temp_rect.x = (rectangle->x + rectangle->width) - temp_rect.width;
309
temp_rect.y = rectangle->y;
310
subtract_rectangle (region, &temp_rect);
314
subtract_rectangle (region, &temp_rect);
318
subtract_rectangle (region, &temp_rect);
322
temp_rect.height = 2;
323
subtract_rectangle (region, &temp_rect);
327
temp_rect.height = 1;
329
temp_rect.x = (rectangle->x + rectangle->width) - temp_rect.width;
330
temp_rect.y = (rectangle->y + rectangle->height) - temp_rect.height;
331
subtract_rectangle (region, &temp_rect);
335
subtract_rectangle (region, &temp_rect);
339
subtract_rectangle (region, &temp_rect);
343
temp_rect.height = 2;
344
subtract_rectangle (region, &temp_rect);
348
temp_rect.height = 1;
350
temp_rect.x = rectangle->x;
351
temp_rect.y = rectangle->y + rectangle->height;
352
subtract_rectangle (region, &temp_rect);
355
temp_rect.width -= 2;
356
subtract_rectangle (region, &temp_rect);
359
temp_rect.width -= 1;
360
subtract_rectangle (region, &temp_rect);
363
temp_rect.width -= 1;
364
temp_rect.height = 2;
365
subtract_rectangle (region, &temp_rect);
371
idle_notification_expired (gpointer data)
373
EggNotificationBubble *bubble = data;
375
GDK_THREADS_ENTER ();
377
g_signal_emit (bubble, egg_notification_bubble_signals[NOTIFICATION_TIMEOUT], 0);
378
egg_notification_bubble_hide (bubble);
380
GDK_THREADS_LEAVE ();
385
draw_bubble (EggNotificationBubble *bubble, guint timeout)
387
GtkRequisition requisition;
393
GdkRectangle monitor;
394
GdkPoint triangle_points[3];
397
GdkRectangle rectangle;
399
GdkRegion *triangle_region;
404
guint rectangle_border;
405
guint triangle_offset;
407
if (!bubble->bubble_window)
408
force_window (bubble);
410
gtk_widget_ensure_style (bubble->bubble_window);
411
style = bubble->bubble_window->style;
413
widget = bubble->widget;
415
screen = gtk_widget_get_screen (widget);
419
gtk_box_pack_start_defaults (GTK_BOX (bubble->main_hbox), bubble->icon);
420
gtk_box_reorder_child (GTK_BOX (bubble->main_hbox), bubble->icon, 0);
423
markupquoted = g_markup_escape_text (bubble->bubble_header_text, -1);
424
markuptext = g_strdup_printf ("<b>%s</b>", markupquoted);
425
gtk_label_set_markup (GTK_LABEL (bubble->bubble_header_label), markuptext);
427
g_free (markupquoted);
428
gtk_label_set_text (GTK_LABEL (bubble->bubble_body_label), bubble->bubble_body_text);
430
gtk_widget_show_all (bubble->bubble_window);
432
gtk_widget_size_request (bubble->bubble_window, &requisition);
433
w = requisition.width;
434
h = requisition.height;
436
gdk_window_get_origin (widget->window, &x, &y);
437
if (GTK_WIDGET_NO_WINDOW (widget))
439
x += widget->allocation.x;
440
y += widget->allocation.y;
443
orient = ORIENT_BOTTOM;
445
triangle_offset = 20;
447
x -= triangle_offset;
449
monitor_num = gdk_screen_get_monitor_at_window (screen, widget->window);
450
gdk_screen_get_monitor_geometry (screen, monitor_num, &monitor);
452
if ((x + w) > monitor.x + monitor.width) {
453
gint offset = (x + w) - (monitor.x + monitor.width);
454
triangle_offset += offset;
456
} else if (x < monitor.x) {
457
gint offset = monitor.x - x;
458
triangle_offset -= offset;
462
if ((y + h + widget->allocation.height + 4) > monitor.y + monitor.height)
468
y = y + widget->allocation.height + 4;
470
/* Overlap the arrow with the object slightly */
471
if (orient == ORIENT_BOTTOM)
476
rectangle_border = BORDER_SIZE-2;
478
rectangle.x = rectangle_border;
479
rectangle.y = rectangle_border;
480
rectangle.width = w - (rectangle_border * 2);
481
rectangle.height = h - (rectangle_border * 2);
482
region = add_bevels_to_rectangle (&rectangle);
484
triangle_points[0].x = triangle_offset;
485
triangle_points[0].y = orient == ORIENT_BOTTOM ? BORDER_SIZE : h - BORDER_SIZE;
486
triangle_points[1].x = triangle_points[0].x + 20;
487
triangle_points[1].y = triangle_points[0].y;
488
triangle_points[2].x = (triangle_points[1].x + triangle_points[0].x) /2;
489
triangle_points[2].y = orient == ORIENT_BOTTOM ? 0 : h;
491
triangle_region = gdk_region_polygon (triangle_points, 3, GDK_WINDING_RULE);
493
gdk_region_union (region, triangle_region);
494
gdk_region_destroy (triangle_region);
496
gdk_window_shape_combine_region (bubble->bubble_window->window, region, 0, 0);
498
gtk_widget_show_all (bubble->bubble_window);
499
gtk_window_move (GTK_WINDOW (bubble->bubble_window), x, y);
500
bubble->active = TRUE;
501
if (bubble->timeout_id)
503
g_source_remove (bubble->timeout_id);
504
bubble->timeout_id = 0;
507
bubble->timeout_id = g_timeout_add (timeout, idle_notification_expired, bubble);
511
egg_notification_bubble_show (EggNotificationBubble *bubble, guint timeout)
513
draw_bubble (bubble, timeout);
517
egg_notification_bubble_hide (EggNotificationBubble *bubble)
519
if (bubble->bubble_window)
520
gtk_widget_hide (bubble->bubble_window);
521
if (bubble->timeout_id)
523
g_source_remove (bubble->timeout_id);
524
bubble->timeout_id = 0;
528
EggNotificationBubble*
529
egg_notification_bubble_new (void)
531
return g_object_new (EGG_TYPE_NOTIFICATION_BUBBLE, NULL);
535
egg_notification_bubble_event_handler (GtkWidget *widget,
539
EggNotificationBubble *bubble;
541
bubble = EGG_NOTIFICATION_BUBBLE (user_data);
545
case GDK_BUTTON_PRESS:
546
g_signal_emit (bubble, egg_notification_bubble_signals[NOTIFICATION_CLICKED], 0);
554
egg_notification_bubble_detach (EggNotificationBubble *bubble)
556
g_return_if_fail (bubble->widget);
558
g_object_unref (bubble->widget);