~ubuntu-branches/ubuntu/utopic/rhythmbox/utopic-proposed

« back to all changes in this revision

Viewing changes to widgets/eggnotificationbubble.c

Tags: upstream-0.9.2
ImportĀ upstreamĀ versionĀ 0.9.2

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
/* EggNotificationBubble
 
2
 * Copyright (C) 2005 Colin Walters <walters@verbum.org>
 
3
 *
 
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.
 
8
 *
 
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.
 
13
 *
 
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.
 
18
 */
 
19
 
 
20
#include <stdlib.h>
 
21
#include <string.h>
 
22
#include <stdio.h>
 
23
 
 
24
#include <gtk/gtk.h>
 
25
#include "eggnotificationbubble.h"
 
26
 
 
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
 
30
                                     */
 
31
#define STICKY_REVERT_DELAY 1000    /* Delay before sticky bubble revert
 
32
                                     * to normal
 
33
                                     */
 
34
 
 
35
#define BORDER_SIZE 15
 
36
 
 
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);
 
41
 
 
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);
 
47
 
 
48
static GtkObjectClass *parent_class;
 
49
 
 
50
enum
 
51
{
 
52
        NOTIFICATION_CLICKED,
 
53
        NOTIFICATION_TIMEOUT,
 
54
        LAST_SIGNAL
 
55
};
 
56
 
 
57
static guint egg_notification_bubble_signals[LAST_SIGNAL] = { 0 };
 
58
 
 
59
GType
 
60
egg_notification_bubble_get_type (void)
 
61
{
 
62
  static GType bubble_type = 0;
 
63
 
 
64
  if (!bubble_type)
 
65
    {
 
66
      static const GTypeInfo bubble_info =
 
67
      {
 
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,
 
77
      };
 
78
 
 
79
      bubble_type = g_type_register_static (GTK_TYPE_OBJECT, "EggNotificationBubble",
 
80
                                              &bubble_info, 0);
 
81
    }
 
82
 
 
83
  return bubble_type;
 
84
}
 
85
 
 
86
static void
 
87
egg_notification_bubble_class_init (EggNotificationBubbleClass *class)
 
88
{
 
89
  GtkObjectClass *object_class;
 
90
 
 
91
  object_class = (GtkObjectClass*) class;
 
92
 
 
93
  parent_class = g_type_class_peek_parent (class);
 
94
 
 
95
  object_class->destroy = egg_notification_bubble_destroy;
 
96
 
 
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,
 
105
                  0);
 
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);
 
114
}
 
115
 
 
116
static void
 
117
egg_notification_bubble_init (EggNotificationBubble *bubble)
 
118
{
 
119
  bubble->bubble_window = NULL;
 
120
}
 
121
 
 
122
static void
 
123
bubble_window_display_closed (GdkDisplay  *display,
 
124
                           gboolean     was_error,
 
125
                           EggNotificationBubble *bubble)
 
126
{
 
127
  egg_notification_bubble_unset_bubble_window (bubble);
 
128
}
 
129
 
 
130
static void
 
131
disconnect_bubble_window_display_closed (EggNotificationBubble *bubble)
 
132
{
 
133
  g_signal_handlers_disconnect_by_func (gtk_widget_get_display (bubble->bubble_window),
 
134
                                        (gpointer) bubble_window_display_closed,
 
135
                                        bubble);
 
136
}
 
137
 
 
138
static void
 
139
egg_notification_bubble_unset_bubble_window (EggNotificationBubble *bubble)
 
140
{
 
141
  if (bubble->bubble_window)
 
142
    {
 
143
      disconnect_bubble_window_display_closed (bubble);
 
144
      
 
145
      gtk_widget_destroy (bubble->bubble_window);
 
146
      bubble->bubble_window = NULL;
 
147
    }
 
148
}
 
149
 
 
150
static void
 
151
egg_notification_bubble_destroy (GtkObject *object)
 
152
{
 
153
  EggNotificationBubble *bubble = EGG_NOTIFICATION_BUBBLE (object);
 
154
 
 
155
  g_return_if_fail (bubble != NULL);
 
156
 
 
157
  if (bubble->timeout_id)
 
158
    {
 
159
      g_source_remove (bubble->timeout_id);
 
160
      bubble->timeout_id = 0;
 
161
    }
 
162
 
 
163
  egg_notification_bubble_detach (bubble);
 
164
 
 
165
  egg_notification_bubble_unset_bubble_window (bubble);
 
166
 
 
167
  GTK_OBJECT_CLASS (parent_class)->destroy (object);
 
168
}
 
169
 
 
170
static void
 
171
force_window (EggNotificationBubble *bubble)
 
172
{
 
173
  g_return_if_fail (EGG_IS_NOTIFICATION_BUBBLE (bubble));
 
174
 
 
175
  if (!bubble->bubble_window)
 
176
    {
 
177
      GtkWidget *vbox;
 
178
 
 
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);
 
185
 
 
186
      g_signal_connect_swapped (bubble->bubble_window,
 
187
                                "expose_event",
 
188
                                G_CALLBACK (egg_notification_bubble_paint_window), 
 
189
                                bubble);
 
190
 
 
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);
 
199
 
 
200
      bubble->main_hbox = gtk_hbox_new (FALSE, 10);
 
201
      gtk_container_add (GTK_CONTAINER (bubble->main_hbox), bubble->bubble_body_label);
 
202
      
 
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);
 
207
 
 
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);
 
215
    }
 
216
}
 
217
 
 
218
void
 
219
egg_notification_bubble_attach (EggNotificationBubble *bubble,
 
220
                                GtkWidget *widget)
 
221
{
 
222
  bubble->widget = widget;
 
223
 
 
224
  g_signal_connect_object (widget, "destroy",
 
225
                           G_CALLBACK (g_object_unref),
 
226
                           bubble, G_CONNECT_SWAPPED);
 
227
}
 
228
 
 
229
void
 
230
egg_notification_bubble_set (EggNotificationBubble *bubble,
 
231
                             const gchar *bubble_header_text,
 
232
                             GtkWidget   *icon,
 
233
                             const gchar *bubble_body_text)
 
234
{
 
235
  g_return_if_fail (EGG_IS_NOTIFICATION_BUBBLE (bubble));
 
236
 
 
237
  g_free (bubble->bubble_header_text);
 
238
  g_free (bubble->bubble_body_text);
 
239
  if (bubble->icon)
 
240
    {
 
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;
 
245
    }
 
246
  
 
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));
 
251
}
 
252
 
 
253
static gint
 
254
egg_notification_bubble_paint_window (EggNotificationBubble *bubble)
 
255
{
 
256
  GtkRequisition req;
 
257
 
 
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;
 
264
}
 
265
 
 
266
static void
 
267
subtract_rectangle (GdkRegion *region, GdkRectangle *rectangle)
 
268
{
 
269
  GdkRegion *temp_region;
 
270
 
 
271
  temp_region = gdk_region_rectangle (rectangle);
 
272
  gdk_region_subtract (region, temp_region);
 
273
  gdk_region_destroy (temp_region);
 
274
}
 
275
 
 
276
static GdkRegion *
 
277
add_bevels_to_rectangle (GdkRectangle *rectangle)
 
278
{
 
279
  GdkRectangle temp_rect;
 
280
  GdkRegion *region = gdk_region_rectangle (rectangle);
 
281
 
 
282
  temp_rect.width = 5;
 
283
  temp_rect.height = 1;
 
284
  
 
285
  /* Top left */
 
286
  temp_rect.x = rectangle->x;
 
287
  temp_rect.y = rectangle->y;
 
288
  subtract_rectangle (region, &temp_rect);
 
289
 
 
290
  temp_rect.y += 1;
 
291
  temp_rect.width -= 2;
 
292
  subtract_rectangle (region, &temp_rect);
 
293
 
 
294
  temp_rect.y += 1;
 
295
  temp_rect.width -= 1;
 
296
  subtract_rectangle (region, &temp_rect);
 
297
 
 
298
  temp_rect.y += 1;
 
299
  temp_rect.width -= 1;
 
300
  temp_rect.height = 2;
 
301
  subtract_rectangle (region, &temp_rect);
 
302
 
 
303
 
 
304
  /* Top right */
 
305
  temp_rect.width = 5;
 
306
  temp_rect.height = 1;
 
307
  
 
308
  temp_rect.x = (rectangle->x + rectangle->width) - temp_rect.width;
 
309
  temp_rect.y = rectangle->y;
 
310
  subtract_rectangle (region, &temp_rect);
 
311
 
 
312
  temp_rect.y += 1;
 
313
  temp_rect.x += 2;
 
314
  subtract_rectangle (region, &temp_rect);
 
315
 
 
316
  temp_rect.y += 1;
 
317
  temp_rect.x += 1;
 
318
  subtract_rectangle (region, &temp_rect);
 
319
 
 
320
  temp_rect.y += 1;
 
321
  temp_rect.x += 1;
 
322
  temp_rect.height = 2;
 
323
  subtract_rectangle (region, &temp_rect);
 
324
 
 
325
  /* Bottom right */
 
326
  temp_rect.width = 5;
 
327
  temp_rect.height = 1;
 
328
  
 
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);
 
332
 
 
333
  temp_rect.y -= 1;
 
334
  temp_rect.x += 2;
 
335
  subtract_rectangle (region, &temp_rect);
 
336
 
 
337
  temp_rect.y -= 1;
 
338
  temp_rect.x += 1;
 
339
  subtract_rectangle (region, &temp_rect);
 
340
 
 
341
  temp_rect.y -= 1;
 
342
  temp_rect.x += 1;
 
343
  temp_rect.height = 2;
 
344
  subtract_rectangle (region, &temp_rect);
 
345
 
 
346
  /* Bottom left */
 
347
  temp_rect.width = 5;
 
348
  temp_rect.height = 1;
 
349
  
 
350
  temp_rect.x = rectangle->x;
 
351
  temp_rect.y = rectangle->y + rectangle->height;
 
352
  subtract_rectangle (region, &temp_rect);
 
353
 
 
354
  temp_rect.y -= 1;
 
355
  temp_rect.width -= 2;
 
356
  subtract_rectangle (region, &temp_rect);
 
357
 
 
358
  temp_rect.y -= 1;
 
359
  temp_rect.width -= 1;
 
360
  subtract_rectangle (region, &temp_rect);
 
361
 
 
362
  temp_rect.y -= 1;
 
363
  temp_rect.width -= 1;
 
364
  temp_rect.height = 2;
 
365
  subtract_rectangle (region, &temp_rect);
 
366
 
 
367
  return region;
 
368
}
 
369
 
 
370
static gboolean
 
371
idle_notification_expired (gpointer data)
 
372
{
 
373
  EggNotificationBubble *bubble = data;
 
374
 
 
375
  GDK_THREADS_ENTER ();
 
376
 
 
377
  g_signal_emit (bubble, egg_notification_bubble_signals[NOTIFICATION_TIMEOUT], 0);
 
378
  egg_notification_bubble_hide (bubble);
 
379
 
 
380
  GDK_THREADS_LEAVE ();
 
381
  return FALSE;
 
382
}
 
383
 
 
384
static void
 
385
draw_bubble (EggNotificationBubble *bubble, guint timeout)
 
386
{
 
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;
 
406
 
 
407
  if (!bubble->bubble_window)
 
408
    force_window (bubble);
 
409
 
 
410
  gtk_widget_ensure_style (bubble->bubble_window);
 
411
  style = bubble->bubble_window->style;
 
412
  
 
413
  widget = bubble->widget;
 
414
 
 
415
  screen = gtk_widget_get_screen (widget);
 
416
 
 
417
  if (bubble->icon)
 
418
    {
 
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);
 
421
    }
 
422
 
 
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);
 
429
  
 
430
  gtk_widget_show_all (bubble->bubble_window);
 
431
 
 
432
  gtk_widget_size_request (bubble->bubble_window, &requisition);
 
433
  w = requisition.width;
 
434
  h = requisition.height;
 
435
 
 
436
  gdk_window_get_origin (widget->window, &x, &y);
 
437
  if (GTK_WIDGET_NO_WINDOW (widget))
 
438
    {
 
439
      x += widget->allocation.x;
 
440
      y += widget->allocation.y;
 
441
    }
 
442
 
 
443
  orient = ORIENT_BOTTOM;
 
444
 
 
445
  triangle_offset = 20;
 
446
 
 
447
  x -= triangle_offset;
 
448
    
 
449
  monitor_num = gdk_screen_get_monitor_at_window (screen, widget->window);
 
450
  gdk_screen_get_monitor_geometry (screen, monitor_num, &monitor);
 
451
 
 
452
  if ((x + w) > monitor.x + monitor.width) {
 
453
    gint offset = (x + w) - (monitor.x + monitor.width);
 
454
    triangle_offset += offset;
 
455
    x -= offset;
 
456
  } else if (x < monitor.x) {
 
457
    gint offset = monitor.x - x;
 
458
    triangle_offset -= offset;
 
459
    x += offset;
 
460
  }
 
461
 
 
462
  if ((y + h + widget->allocation.height + 4) > monitor.y + monitor.height)
 
463
    {
 
464
      y -= (h - 4);
 
465
      orient = ORIENT_TOP;
 
466
    }
 
467
  else
 
468
    y = y + widget->allocation.height + 4;
 
469
 
 
470
  /* Overlap the arrow with the object slightly */
 
471
  if (orient == ORIENT_BOTTOM)
 
472
    y -= 5;
 
473
  else
 
474
    y += 5;
 
475
 
 
476
  rectangle_border = BORDER_SIZE-2; 
 
477
 
 
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);
 
483
 
 
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;
 
490
 
 
491
  triangle_region = gdk_region_polygon (triangle_points, 3, GDK_WINDING_RULE);
 
492
 
 
493
  gdk_region_union (region, triangle_region);
 
494
  gdk_region_destroy (triangle_region);
 
495
 
 
496
  gdk_window_shape_combine_region (bubble->bubble_window->window, region, 0, 0);
 
497
 
 
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)
 
502
    {
 
503
      g_source_remove (bubble->timeout_id);
 
504
      bubble->timeout_id = 0;
 
505
    }
 
506
  if (timeout > 0)
 
507
    bubble->timeout_id = g_timeout_add (timeout, idle_notification_expired, bubble);
 
508
}
 
509
 
 
510
void
 
511
egg_notification_bubble_show (EggNotificationBubble *bubble, guint timeout)
 
512
{
 
513
  draw_bubble (bubble, timeout);
 
514
}
 
515
 
 
516
void
 
517
egg_notification_bubble_hide (EggNotificationBubble *bubble)
 
518
{
 
519
  if (bubble->bubble_window)
 
520
    gtk_widget_hide (bubble->bubble_window);
 
521
  if (bubble->timeout_id)
 
522
    {
 
523
      g_source_remove (bubble->timeout_id);
 
524
      bubble->timeout_id = 0;
 
525
    }
 
526
}
 
527
 
 
528
EggNotificationBubble*
 
529
egg_notification_bubble_new (void)
 
530
{
 
531
  return g_object_new (EGG_TYPE_NOTIFICATION_BUBBLE, NULL);
 
532
}
 
533
 
 
534
static void
 
535
egg_notification_bubble_event_handler (GtkWidget *widget,
 
536
                                       GdkEvent  *event,
 
537
                                       gpointer user_data)
 
538
{
 
539
  EggNotificationBubble *bubble;
 
540
 
 
541
  bubble = EGG_NOTIFICATION_BUBBLE (user_data);
 
542
 
 
543
  switch (event->type)
 
544
    {
 
545
    case GDK_BUTTON_PRESS:
 
546
      g_signal_emit (bubble, egg_notification_bubble_signals[NOTIFICATION_CLICKED], 0);
 
547
      break;
 
548
    default:
 
549
      break;
 
550
    }
 
551
}
 
552
 
 
553
static void
 
554
egg_notification_bubble_detach (EggNotificationBubble *bubble)
 
555
{
 
556
  g_return_if_fail (bubble->widget);
 
557
 
 
558
  g_object_unref (bubble->widget);
 
559
}