Updated Macedonian Translation <arangela@cvs.gnome.org>
[rhythmbox.git] / widgets / eggnotificationbubble.c
blob2452b99a0ede00ae41b6164ddf0dab9c218148a4
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.
20 #include <stdlib.h>
21 #include <string.h>
22 #include <stdio.h>
24 #include <gtk/gtk.h>
25 #include "eggnotificationbubble.h"
27 #define DEFAULT_DELAY 500 /* Default delay in ms */
28 #define STICKY_DELAY 0 /* Delay before popping up next bubble
29 * if we're sticky
31 #define STICKY_REVERT_DELAY 1000 /* Delay before sticky bubble revert
32 * to normal
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,
43 GdkEvent *event,
44 gpointer user_data);
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;
50 enum
52 NOTIFICATION_CLICKED,
53 NOTIFICATION_TIMEOUT,
54 LAST_SIGNAL
57 static guint egg_notification_bubble_signals[LAST_SIGNAL] = { 0 };
59 GType
60 egg_notification_bubble_get_type (void)
62 static GType bubble_type = 0;
64 if (!bubble_type)
66 static const GTypeInfo bubble_info =
68 sizeof (EggNotificationBubbleClass),
69 NULL, /* base_init */
70 NULL, /* base_finalize */
71 (GClassInitFunc) egg_notification_bubble_class_init,
72 NULL, /* class_finalize */
73 NULL, /* class_data */
74 sizeof (EggNotificationBubble),
75 0, /* n_preallocs */
76 (GInstanceInitFunc) egg_notification_bubble_init,
79 bubble_type = g_type_register_static (GTK_TYPE_OBJECT, "EggNotificationBubble",
80 &bubble_info, 0);
83 return bubble_type;
86 static void
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,
100 G_SIGNAL_RUN_LAST,
101 G_STRUCT_OFFSET (EggNotificationBubbleClass, clicked),
102 NULL, NULL,
103 g_cclosure_marshal_VOID__VOID,
104 G_TYPE_NONE,
106 egg_notification_bubble_signals[NOTIFICATION_TIMEOUT] =
107 g_signal_new ("timeout",
108 EGG_TYPE_NOTIFICATION_BUBBLE,
109 G_SIGNAL_RUN_LAST,
110 G_STRUCT_OFFSET (EggNotificationBubbleClass, timeout),
111 NULL, NULL,
112 g_cclosure_marshal_VOID__VOID,
113 G_TYPE_NONE, 0);
116 static void
117 egg_notification_bubble_init (EggNotificationBubble *bubble)
119 bubble->bubble_window = NULL;
122 static void
123 bubble_window_display_closed (GdkDisplay *display,
124 gboolean was_error,
125 EggNotificationBubble *bubble)
127 egg_notification_bubble_unset_bubble_window (bubble);
130 static void
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,
135 bubble);
138 static void
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;
150 static void
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);
170 static void
171 force_window (EggNotificationBubble *bubble)
173 g_return_if_fail (EGG_IS_NOTIFICATION_BUBBLE (bubble));
175 if (!bubble->bubble_window)
177 GtkWidget *vbox;
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,
187 "expose_event",
188 G_CALLBACK (egg_notification_bubble_paint_window),
189 bubble);
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,
209 "destroy",
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),
214 bubble);
218 void
219 egg_notification_bubble_attach (EggNotificationBubble *bubble,
220 GtkWidget *widget)
222 bubble->widget = widget;
224 g_signal_connect_object (widget, "destroy",
225 G_CALLBACK (g_object_unref),
226 bubble, G_CONNECT_SWAPPED);
229 void
230 egg_notification_bubble_set (EggNotificationBubble *bubble,
231 const gchar *bubble_header_text,
232 GtkWidget *icon,
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);
239 if (bubble->icon)
241 if (bubble->active)
242 gtk_container_remove (GTK_CONTAINER (bubble->main_hbox), bubble->icon);
243 g_object_unref (G_OBJECT (bubble->icon));
244 bubble->icon = NULL;
247 bubble->bubble_header_text = g_strdup (bubble_header_text);
248 bubble->bubble_body_text = g_strdup (bubble_body_text);
249 if (icon)
250 bubble->icon = g_object_ref (G_OBJECT (icon));
253 static gint
254 egg_notification_bubble_paint_window (EggNotificationBubble *bubble)
256 GtkRequisition req;
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);
263 return FALSE;
266 static void
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);
276 static GdkRegion *
277 add_bevels_to_rectangle (GdkRectangle *rectangle)
279 GdkRectangle temp_rect;
280 GdkRegion *region = gdk_region_rectangle (rectangle);
282 temp_rect.width = 5;
283 temp_rect.height = 1;
285 /* Top left */
286 temp_rect.x = rectangle->x;
287 temp_rect.y = rectangle->y;
288 subtract_rectangle (region, &temp_rect);
290 temp_rect.y += 1;
291 temp_rect.width -= 2;
292 subtract_rectangle (region, &temp_rect);
294 temp_rect.y += 1;
295 temp_rect.width -= 1;
296 subtract_rectangle (region, &temp_rect);
298 temp_rect.y += 1;
299 temp_rect.width -= 1;
300 temp_rect.height = 2;
301 subtract_rectangle (region, &temp_rect);
304 /* Top right */
305 temp_rect.width = 5;
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);
312 temp_rect.y += 1;
313 temp_rect.x += 2;
314 subtract_rectangle (region, &temp_rect);
316 temp_rect.y += 1;
317 temp_rect.x += 1;
318 subtract_rectangle (region, &temp_rect);
320 temp_rect.y += 1;
321 temp_rect.x += 1;
322 temp_rect.height = 2;
323 subtract_rectangle (region, &temp_rect);
325 /* Bottom right */
326 temp_rect.width = 5;
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);
333 temp_rect.y -= 1;
334 temp_rect.x += 2;
335 subtract_rectangle (region, &temp_rect);
337 temp_rect.y -= 1;
338 temp_rect.x += 1;
339 subtract_rectangle (region, &temp_rect);
341 temp_rect.y -= 1;
342 temp_rect.x += 1;
343 temp_rect.height = 2;
344 subtract_rectangle (region, &temp_rect);
346 /* Bottom left */
347 temp_rect.width = 5;
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);
354 temp_rect.y -= 1;
355 temp_rect.width -= 2;
356 subtract_rectangle (region, &temp_rect);
358 temp_rect.y -= 1;
359 temp_rect.width -= 1;
360 subtract_rectangle (region, &temp_rect);
362 temp_rect.y -= 1;
363 temp_rect.width -= 1;
364 temp_rect.height = 2;
365 subtract_rectangle (region, &temp_rect);
367 return region;
370 static gboolean
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 ();
381 return FALSE;
384 static void
385 draw_bubble (EggNotificationBubble *bubble, guint timeout)
387 GtkRequisition requisition;
388 GtkWidget *widget;
389 GtkStyle *style;
390 gint x, y, w, h;
391 GdkScreen *screen;
392 gint monitor_num;
393 GdkRectangle monitor;
394 GdkPoint triangle_points[3];
395 char *markuptext;
396 char *markupquoted;
397 GdkRectangle rectangle;
398 GdkRegion *region;
399 GdkRegion *triangle_region;
400 enum {
401 ORIENT_TOP = 0,
402 ORIENT_BOTTOM = 1
403 } orient;
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);
417 if (bubble->icon)
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);
426 g_free (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;
456 x -= offset;
457 } else if (x < monitor.x) {
458 gint offset = monitor.x - x;
459 triangle_offset -= offset;
460 x += offset;
463 if ((y + h + widget->allocation.height + 4) > monitor.y + monitor.height)
465 y -= (h - 4);
466 orient = ORIENT_TOP;
468 else
469 y = y + widget->allocation.height + 4;
471 /* Overlap the arrow with the object slightly */
472 if (orient == ORIENT_BOTTOM)
473 y -= 5;
474 else
475 y += 5;
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;
506 if (timeout > 0)
507 bubble->timeout_id = g_timeout_add (timeout, idle_notification_expired, bubble);
510 void
511 egg_notification_bubble_show (EggNotificationBubble *bubble, guint timeout)
513 draw_bubble (bubble, timeout);
516 void
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);
534 static void
535 egg_notification_bubble_event_handler (GtkWidget *widget,
536 GdkEvent *event,
537 gpointer user_data)
539 EggNotificationBubble *bubble;
541 bubble = EGG_NOTIFICATION_BUBBLE (user_data);
543 switch (event->type)
545 case GDK_BUTTON_PRESS:
546 g_signal_emit (bubble, egg_notification_bubble_signals[NOTIFICATION_CLICKED], 0);
547 break;
548 default:
549 break;
553 static void
554 egg_notification_bubble_detach (EggNotificationBubble *bubble)
556 g_return_if_fail (bubble->widget);
558 g_object_unref (bubble->widget);