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_window_move (GTK_WINDOW (bubble
->bubble_window
), 0, 2 * gdk_screen_get_height (screen
));
431 gtk_widget_show_all (bubble
->bubble_window
);
433 gtk_widget_size_request (bubble
->bubble_window
, &requisition
);
434 w
= requisition
.width
;
435 h
= requisition
.height
;
437 gdk_window_get_origin (widget
->window
, &x
, &y
);
438 if (GTK_WIDGET_NO_WINDOW (widget
))
440 x
+= widget
->allocation
.x
;
441 y
+= widget
->allocation
.y
;
444 orient
= ORIENT_BOTTOM
;
446 triangle_offset
= 20;
448 x
-= triangle_offset
;
450 monitor_num
= gdk_screen_get_monitor_at_window (screen
, widget
->window
);
451 gdk_screen_get_monitor_geometry (screen
, monitor_num
, &monitor
);
453 if ((x
+ w
) > monitor
.x
+ monitor
.width
) {
454 gint offset
= (x
+ w
) - (monitor
.x
+ monitor
.width
);
455 triangle_offset
+= offset
;
457 } else if (x
< monitor
.x
) {
458 gint offset
= monitor
.x
- x
;
459 triangle_offset
-= offset
;
463 if ((y
+ h
+ widget
->allocation
.height
+ 4) > monitor
.y
+ monitor
.height
)
469 y
= y
+ widget
->allocation
.height
+ 4;
471 /* Overlap the arrow with the object slightly */
472 if (orient
== ORIENT_BOTTOM
)
477 rectangle_border
= BORDER_SIZE
-2;
479 rectangle
.x
= rectangle_border
;
480 rectangle
.y
= rectangle_border
;
481 rectangle
.width
= w
- (rectangle_border
* 2);
482 rectangle
.height
= h
- (rectangle_border
* 2);
483 region
= add_bevels_to_rectangle (&rectangle
);
485 triangle_points
[0].x
= triangle_offset
;
486 triangle_points
[0].y
= orient
== ORIENT_BOTTOM
? BORDER_SIZE
: h
- BORDER_SIZE
;
487 triangle_points
[1].x
= triangle_points
[0].x
+ 20;
488 triangle_points
[1].y
= triangle_points
[0].y
;
489 triangle_points
[2].x
= (triangle_points
[1].x
+ triangle_points
[0].x
) /2;
490 triangle_points
[2].y
= orient
== ORIENT_BOTTOM
? 0 : h
;
492 triangle_region
= gdk_region_polygon (triangle_points
, 3, GDK_WINDING_RULE
);
494 gdk_region_union (region
, triangle_region
);
495 gdk_region_destroy (triangle_region
);
497 gdk_window_shape_combine_region (bubble
->bubble_window
->window
, region
, 0, 0);
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
);