~noskcaj/ubuntu/trusty/gnome-documents/3.10.2

« back to all changes in this revision

Viewing changes to libgd/libgd/gd-notification.c

  • Committer: Package Import Robot
  • Author(s): Robert Ancell
  • Date: 2012-08-22 10:01:18 UTC
  • mfrom: (1.1.8)
  • Revision ID: package-import@ubuntu.com-20120822100118-3837rqfy72e1op72
Tags: 3.5.90-0ubuntu1
New upstream release

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 
2
/*
 
3
 * gd-notification
 
4
 * Based on gtk-notification from gnome-contacts:
 
5
 * http://git.gnome.org/browse/gnome-contacts/tree/src/gtk-notification.c?id=3.3.91
 
6
 *
 
7
 * Copyright (C) Erick Pérez Castellanos 2011 <erick.red@gmail.com>
 
8
 * Copyright (C) 2012 Red Hat, Inc.
 
9
 *
 
10
 * This program is free software: you can redistribute it and/or modify it
 
11
 * under the terms of the GNU Lesser General Public License as published
 
12
 * by the Free Software Foundation, either version 3 of the License, or
 
13
 * (at your option) any later version.
 
14
 *
 
15
 * This program is distributed in the hope that it will be useful, but
 
16
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 
17
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
 
18
 * See the GNU Lesser General Public License for more details.
 
19
 *
 
20
 * You should have received a copy of the GNU Lesser General Public License
 
21
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.";
 
22
 */
 
23
 
 
24
#include "gd-notification.h"
 
25
 
 
26
/**
 
27
 * SECTION:gdnotification
 
28
 * @short_description: Report notification messages to the user
 
29
 * @include: gtk/gtk.h
 
30
 * @see_also: #GtkStatusbar, #GtkMessageDialog, #GtkInfoBar
 
31
 *
 
32
 * #GdNotification is a widget made for showing notifications to
 
33
 * the user, allowing them to close the notification or wait for it
 
34
 * to time out.
 
35
 *
 
36
 * #GdNotification provides one signal (#GdNotification::dismissed), for when the notification
 
37
 * times out or is closed.
 
38
 *
 
39
 */
 
40
 
 
41
#define GTK_PARAM_READWRITE G_PARAM_READWRITE|G_PARAM_STATIC_NAME|G_PARAM_STATIC_NICK|G_PARAM_STATIC_BLURB
 
42
#define SHADOW_OFFSET_X 2
 
43
#define SHADOW_OFFSET_Y 3
 
44
#define ANIMATION_TIME 200 /* msec */
 
45
#define ANIMATION_STEP 40 /* msec */
 
46
 
 
47
enum {
 
48
  PROP_0,
 
49
  PROP_TIMEOUT,
 
50
  PROP_SHOW_CLOSE_BUTTON
 
51
};
 
52
 
 
53
struct _GdNotificationPrivate {
 
54
  GtkWidget *close_button;
 
55
  gboolean show_close_button;
 
56
 
 
57
  GdkWindow *bin_window;
 
58
 
 
59
  int animate_y; /* from 0 to allocation.height */
 
60
  gboolean waiting_for_viewable;
 
61
  gboolean revealed;
 
62
  gboolean dismissed;
 
63
  gboolean sent_dismissed;
 
64
  guint animate_timeout;
 
65
 
 
66
  gint timeout;
 
67
  guint timeout_source_id;
 
68
};
 
69
 
 
70
enum {
 
71
  DISMISSED,
 
72
  LAST_SIGNAL
 
73
};
 
74
 
 
75
static guint notification_signals[LAST_SIGNAL] = { 0 };
 
76
 
 
77
static gboolean gd_notification_draw                           (GtkWidget       *widget,
 
78
                                                                 cairo_t         *cr);
 
79
static void     gd_notification_get_preferred_width            (GtkWidget       *widget,
 
80
                                                                 gint            *minimum_size,
 
81
                                                                 gint            *natural_size);
 
82
static void     gd_notification_get_preferred_height_for_width (GtkWidget       *widget,
 
83
                                                                 gint             width,
 
84
                                                                 gint            *minimum_height,
 
85
                                                                 gint            *natural_height);
 
86
static void     gd_notification_get_preferred_height           (GtkWidget       *widget,
 
87
                                                                 gint            *minimum_size,
 
88
                                                                 gint            *natural_size);
 
89
static void     gd_notification_get_preferred_width_for_height (GtkWidget       *widget,
 
90
                                                                 gint             height,
 
91
                                                                 gint            *minimum_width,
 
92
                                                                 gint            *natural_width);
 
93
static void     gd_notification_size_allocate                  (GtkWidget       *widget,
 
94
                                                                 GtkAllocation   *allocation);
 
95
static gboolean gd_notification_timeout_cb                     (gpointer         user_data);
 
96
static void     gd_notification_show                           (GtkWidget       *widget);
 
97
static void     gd_notification_add                            (GtkContainer    *container,
 
98
                                                                 GtkWidget       *child);
 
99
 
 
100
/* signals handlers */
 
101
static void     gd_notification_close_button_clicked_cb        (GtkWidget       *widget,
 
102
                                                                 gpointer         user_data);
 
103
 
 
104
G_DEFINE_TYPE(GdNotification, gd_notification, GTK_TYPE_BIN);
 
105
 
 
106
static void
 
107
gd_notification_init (GdNotification *notification)
 
108
{
 
109
  GtkWidget *close_button_image;
 
110
  GtkStyleContext *context;
 
111
  GdNotificationPrivate *priv;
 
112
 
 
113
  context = gtk_widget_get_style_context (GTK_WIDGET (notification));
 
114
  gtk_style_context_add_class (context, "app-notification");
 
115
 
 
116
  gtk_widget_set_halign (GTK_WIDGET (notification), GTK_ALIGN_CENTER);
 
117
  gtk_widget_set_valign (GTK_WIDGET (notification), GTK_ALIGN_START);
 
118
 
 
119
  gtk_widget_set_has_window (GTK_WIDGET (notification), TRUE);
 
120
 
 
121
  gtk_widget_push_composite_child ();
 
122
 
 
123
  priv = notification->priv =
 
124
    G_TYPE_INSTANCE_GET_PRIVATE (notification,
 
125
                                 GD_TYPE_NOTIFICATION,
 
126
                                 GdNotificationPrivate);
 
127
 
 
128
  priv->animate_y = 0;
 
129
  priv->close_button = gtk_button_new ();
 
130
  gtk_widget_set_parent (priv->close_button, GTK_WIDGET (notification));
 
131
  gtk_widget_show (priv->close_button);
 
132
  g_object_set (priv->close_button,
 
133
                "relief", GTK_RELIEF_NONE,
 
134
                "focus-on-click", FALSE,
 
135
                NULL);
 
136
  g_signal_connect (priv->close_button,
 
137
                    "clicked",
 
138
                    G_CALLBACK (gd_notification_close_button_clicked_cb),
 
139
                    notification);
 
140
  close_button_image = gtk_image_new_from_icon_name ("window-close-symbolic", GTK_ICON_SIZE_BUTTON);
 
141
  gtk_button_set_image (GTK_BUTTON (notification->priv->close_button), close_button_image);
 
142
 
 
143
  gtk_widget_pop_composite_child ();
 
144
 
 
145
  priv->timeout_source_id = 0;
 
146
}
 
147
 
 
148
static void
 
149
gd_notification_finalize (GObject *object)
 
150
{
 
151
  GdNotification *notification;
 
152
  GdNotificationPrivate *priv;
 
153
 
 
154
  g_return_if_fail (GTK_IS_NOTIFICATION (object));
 
155
 
 
156
  notification = GD_NOTIFICATION (object);
 
157
  priv = notification->priv;
 
158
 
 
159
  if (priv->animate_timeout != 0)
 
160
    g_source_remove (priv->animate_timeout);
 
161
 
 
162
  if (priv->timeout_source_id != 0)
 
163
    g_source_remove (priv->timeout_source_id);
 
164
 
 
165
  G_OBJECT_CLASS (gd_notification_parent_class)->finalize (object);
 
166
}
 
167
 
 
168
static void
 
169
gd_notification_destroy (GtkWidget *widget)
 
170
{
 
171
  GdNotification *notification = GD_NOTIFICATION (widget);
 
172
  GdNotificationPrivate *priv = notification->priv;
 
173
 
 
174
  if (!priv->sent_dismissed)
 
175
    {
 
176
      g_signal_emit (notification, notification_signals[DISMISSED], 0);
 
177
      priv->sent_dismissed = TRUE;
 
178
    }
 
179
 
 
180
  if (priv->close_button)
 
181
    {
 
182
      gtk_widget_unparent (priv->close_button);
 
183
      priv->close_button = NULL;
 
184
    }
 
185
 
 
186
  GTK_WIDGET_CLASS (gd_notification_parent_class)->destroy (widget);
 
187
}
 
188
 
 
189
static void
 
190
gd_notification_realize (GtkWidget *widget)
 
191
{
 
192
  GdNotification *notification = GD_NOTIFICATION (widget);
 
193
  GdNotificationPrivate *priv = notification->priv;
 
194
  GtkBin *bin = GTK_BIN (widget);
 
195
  GtkAllocation allocation;
 
196
  GtkWidget *child;
 
197
  GdkWindow *window;
 
198
  GdkWindowAttr attributes;
 
199
  gint attributes_mask;
 
200
 
 
201
  gtk_widget_set_realized (widget, TRUE);
 
202
 
 
203
  gtk_widget_get_allocation (widget, &allocation);
 
204
 
 
205
  attributes.x = allocation.x;
 
206
  attributes.y = allocation.y;
 
207
  attributes.width = allocation.width;
 
208
  attributes.height = allocation.height;
 
209
  attributes.window_type = GDK_WINDOW_CHILD;
 
210
  attributes.wclass = GDK_INPUT_OUTPUT;
 
211
  attributes.visual = gtk_widget_get_visual (widget);
 
212
 
 
213
  attributes.event_mask = GDK_VISIBILITY_NOTIFY_MASK | GDK_EXPOSURE_MASK;
 
214
 
 
215
  attributes_mask = GDK_WA_X | GDK_WA_Y | GDK_WA_VISUAL;
 
216
 
 
217
  window = gdk_window_new (gtk_widget_get_parent_window (widget),
 
218
                           &attributes, attributes_mask);
 
219
  gtk_widget_set_window (widget, window);
 
220
  gdk_window_set_user_data (window, notification);
 
221
 
 
222
  attributes.x = 0;
 
223
  attributes.y = attributes.height + priv->animate_y;
 
224
  attributes.event_mask = gtk_widget_get_events (widget) | GDK_EXPOSURE_MASK | GDK_VISIBILITY_NOTIFY_MASK;
 
225
 
 
226
  priv->bin_window = gdk_window_new (window, &attributes, attributes_mask);
 
227
  gdk_window_set_user_data (priv->bin_window, notification);
 
228
 
 
229
  child = gtk_bin_get_child (bin);
 
230
  if (child)
 
231
    gtk_widget_set_parent_window (child, priv->bin_window);
 
232
  gtk_widget_set_parent_window (priv->close_button, priv->bin_window);
 
233
 
 
234
  gdk_window_show (priv->bin_window);
 
235
}
 
236
 
 
237
static void
 
238
gd_notification_unrealize (GtkWidget *widget)
 
239
{
 
240
  GdNotification *notification = GD_NOTIFICATION (widget);
 
241
  GdNotificationPrivate *priv = notification->priv;
 
242
 
 
243
  gdk_window_set_user_data (priv->bin_window, NULL);
 
244
  gdk_window_destroy (priv->bin_window);
 
245
  priv->bin_window = NULL;
 
246
 
 
247
  GTK_WIDGET_CLASS (gd_notification_parent_class)->unrealize (widget);
 
248
}
 
249
 
 
250
static int
 
251
animation_target (GdNotification *notification)
 
252
{
 
253
  GdNotificationPrivate *priv = notification->priv;
 
254
  GtkAllocation allocation;
 
255
 
 
256
  if (priv->revealed) {
 
257
    gtk_widget_get_allocation (GTK_WIDGET (notification), &allocation);
 
258
    return allocation.height;
 
259
  } else {
 
260
    return 0;
 
261
  }
 
262
}
 
263
 
 
264
static gboolean
 
265
animation_timeout_cb (gpointer user_data)
 
266
{
 
267
  GdNotification *notification = GD_NOTIFICATION (user_data);
 
268
  GdNotificationPrivate *priv = notification->priv;
 
269
  GtkAllocation allocation;
 
270
  int target, delta;
 
271
 
 
272
  target = animation_target (notification);
 
273
 
 
274
  if (priv->animate_y != target) {
 
275
    gtk_widget_get_allocation (GTK_WIDGET (notification), &allocation);
 
276
 
 
277
    delta = allocation.height * ANIMATION_STEP / ANIMATION_TIME;
 
278
 
 
279
    if (priv->revealed)
 
280
      priv->animate_y += delta;
 
281
    else
 
282
      priv->animate_y -= delta;
 
283
 
 
284
    priv->animate_y = CLAMP (priv->animate_y, 0, allocation.height);
 
285
 
 
286
    if (priv->bin_window != NULL)
 
287
      gdk_window_move (priv->bin_window,
 
288
                       0,
 
289
                       -allocation.height + priv->animate_y);
 
290
    return TRUE;
 
291
  }
 
292
 
 
293
  if (priv->dismissed && priv->animate_y == 0)
 
294
    gtk_widget_destroy (GTK_WIDGET (notification));
 
295
 
 
296
  priv->animate_timeout = 0;
 
297
  return FALSE;
 
298
}
 
299
 
 
300
static void
 
301
start_animation (GdNotification *notification)
 
302
{
 
303
  GdNotificationPrivate *priv = notification->priv;
 
304
  int target;
 
305
 
 
306
  if (priv->animate_timeout != 0)
 
307
    return; /* Already running */
 
308
 
 
309
  target = animation_target (notification);
 
310
  if (priv->animate_y != target)
 
311
    notification->priv->animate_timeout =
 
312
      gdk_threads_add_timeout (ANIMATION_STEP,
 
313
                               animation_timeout_cb,
 
314
                               notification);
 
315
}
 
316
 
 
317
static void
 
318
gd_notification_show (GtkWidget *widget)
 
319
{
 
320
  GdNotification *notification = GD_NOTIFICATION (widget);
 
321
  GdNotificationPrivate *priv = notification->priv;
 
322
 
 
323
  GTK_WIDGET_CLASS (gd_notification_parent_class)->show (widget);
 
324
  priv->revealed = TRUE;
 
325
  priv->waiting_for_viewable = TRUE;
 
326
}
 
327
 
 
328
static void
 
329
gd_notification_hide (GtkWidget *widget)
 
330
{
 
331
  GdNotification *notification = GD_NOTIFICATION (widget);
 
332
  GdNotificationPrivate *priv = notification->priv;
 
333
 
 
334
  GTK_WIDGET_CLASS (gd_notification_parent_class)->hide (widget);
 
335
  priv->revealed = FALSE;
 
336
  priv->waiting_for_viewable = FALSE;
 
337
}
 
338
 
 
339
static void
 
340
gd_notification_set_property (GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec)
 
341
{
 
342
  GdNotification *notification = GD_NOTIFICATION (object);
 
343
 
 
344
  g_return_if_fail (GTK_IS_NOTIFICATION (object));
 
345
 
 
346
  switch (prop_id) {
 
347
  case PROP_TIMEOUT:
 
348
    gd_notification_set_timeout (notification,
 
349
                                 g_value_get_int (value));
 
350
    break;
 
351
  case PROP_SHOW_CLOSE_BUTTON:
 
352
    gd_notification_set_show_close_button (notification,
 
353
                                           g_value_get_boolean (value));
 
354
    break;
 
355
  default:
 
356
    G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
 
357
    break;
 
358
  }
 
359
}
 
360
 
 
361
static void
 
362
gd_notification_get_property (GObject *object, guint prop_id, GValue *value, GParamSpec *pspec)
 
363
{
 
364
  g_return_if_fail (GTK_IS_NOTIFICATION (object));
 
365
  GdNotification *notification = GD_NOTIFICATION (object);
 
366
 
 
367
  switch (prop_id) {
 
368
  case PROP_TIMEOUT:
 
369
    g_value_set_int (value, notification->priv->timeout);
 
370
    break;
 
371
  case PROP_SHOW_CLOSE_BUTTON:
 
372
    g_value_set_boolean (value,
 
373
                         notification->priv->show_close_button);
 
374
    break;
 
375
  default:
 
376
    G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
 
377
    break;
 
378
  }
 
379
}
 
380
 
 
381
static void
 
382
gd_notification_forall (GtkContainer *container,
 
383
                         gboolean      include_internals,
 
384
                         GtkCallback   callback,
 
385
                         gpointer      callback_data)
 
386
{
 
387
  GtkBin *bin = GTK_BIN (container);
 
388
  GdNotification *notification = GD_NOTIFICATION (container);
 
389
  GdNotificationPrivate *priv = notification->priv;
 
390
  GtkWidget *child;
 
391
 
 
392
  child = gtk_bin_get_child (bin);
 
393
  if (child)
 
394
    (* callback) (child, callback_data);
 
395
 
 
396
  if (include_internals)
 
397
    (* callback) (priv->close_button, callback_data);
 
398
}
 
399
 
 
400
static gboolean
 
401
gd_notification_visibility_notify_event (GtkWidget          *widget,
 
402
                                          GdkEventVisibility  *event)
 
403
{
 
404
  GdNotification *notification = GD_NOTIFICATION (widget);
 
405
  GdNotificationPrivate *priv = notification->priv;
 
406
 
 
407
  if (!gtk_widget_get_visible (widget))
 
408
    return FALSE;
 
409
 
 
410
  if (priv->waiting_for_viewable)
 
411
    {
 
412
      start_animation (notification);
 
413
      priv->waiting_for_viewable = FALSE;
 
414
    }
 
415
 
 
416
  if (notification->priv->timeout_source_id == 0 &&
 
417
      notification->priv->timeout != -1)
 
418
    notification->priv->timeout_source_id =
 
419
      gdk_threads_add_timeout (notification->priv->timeout * 1000,
 
420
                               gd_notification_timeout_cb,
 
421
                               widget);
 
422
 
 
423
  return FALSE;
 
424
}
 
425
 
 
426
static void
 
427
gd_notification_class_init (GdNotificationClass *klass)
 
428
{
 
429
  GObjectClass* object_class = G_OBJECT_CLASS (klass);
 
430
  GtkWidgetClass* widget_class = GTK_WIDGET_CLASS(klass);
 
431
  GtkContainerClass *container_class = GTK_CONTAINER_CLASS (klass);
 
432
 
 
433
  object_class->finalize = gd_notification_finalize;
 
434
  object_class->set_property = gd_notification_set_property;
 
435
  object_class->get_property = gd_notification_get_property;
 
436
 
 
437
  widget_class->show = gd_notification_show;
 
438
  widget_class->hide = gd_notification_hide;
 
439
  widget_class->destroy = gd_notification_destroy;
 
440
  widget_class->get_preferred_width = gd_notification_get_preferred_width;
 
441
  widget_class->get_preferred_height_for_width = gd_notification_get_preferred_height_for_width;
 
442
  widget_class->get_preferred_height = gd_notification_get_preferred_height;
 
443
  widget_class->get_preferred_width_for_height = gd_notification_get_preferred_width_for_height;
 
444
  widget_class->size_allocate = gd_notification_size_allocate;
 
445
  widget_class->draw = gd_notification_draw;
 
446
  widget_class->realize = gd_notification_realize;
 
447
  widget_class->unrealize = gd_notification_unrealize;
 
448
  widget_class->visibility_notify_event = gd_notification_visibility_notify_event;
 
449
 
 
450
  container_class->add = gd_notification_add;
 
451
  container_class->forall = gd_notification_forall;
 
452
  gtk_container_class_handle_border_width (container_class);
 
453
 
 
454
 
 
455
  /**
 
456
   * GdNotification:timeout:
 
457
   *
 
458
   * The time it takes to hide the widget, in seconds.
 
459
   *
 
460
   * Since: 0.1
 
461
   */
 
462
  g_object_class_install_property (object_class,
 
463
                                   PROP_TIMEOUT,
 
464
                                   g_param_spec_int("timeout", "timeout",
 
465
                                                    "The time it takes to hide the widget, in seconds",
 
466
                                                    -1, G_MAXINT, -1,
 
467
                                                    GTK_PARAM_READWRITE | G_PARAM_CONSTRUCT));
 
468
  g_object_class_install_property (object_class,
 
469
                                   PROP_SHOW_CLOSE_BUTTON,
 
470
                                   g_param_spec_boolean("show-close-button", "show-close-button",
 
471
                                                        "Whether to show a stock close button that dismisses the notification",
 
472
                                                        TRUE,
 
473
                                                        GTK_PARAM_READWRITE | G_PARAM_CONSTRUCT));
 
474
 
 
475
  notification_signals[DISMISSED] = g_signal_new ("dismissed",
 
476
                                                  G_OBJECT_CLASS_TYPE (klass),
 
477
                                                  G_SIGNAL_RUN_LAST,
 
478
                                                  G_STRUCT_OFFSET (GdNotificationClass, dismissed),
 
479
                                                  NULL,
 
480
                                                  NULL,
 
481
                                                  g_cclosure_marshal_VOID__VOID,
 
482
                                                  G_TYPE_NONE,
 
483
                                                  0);
 
484
 
 
485
  g_type_class_add_private (object_class, sizeof (GdNotificationPrivate));
 
486
}
 
487
 
 
488
static void
 
489
draw_shadow_box (cairo_t *cr, GdkRectangle rect, int left_border, int right_border,
 
490
                 int bottom_border, double inner_alpha)
 
491
{
 
492
  cairo_pattern_t *pattern;
 
493
  cairo_matrix_t matrix;
 
494
  double x0, x1, x2, x3;
 
495
  double y0, y2, y3;
 
496
 
 
497
  cairo_save (cr);
 
498
 
 
499
  x0 = rect.x;
 
500
  x1 = rect.x + left_border;
 
501
  x2 = rect.x + rect.width - right_border;
 
502
  x3 = rect.x + rect.width;
 
503
 
 
504
  y0 = rect.y;
 
505
  y2 = rect.y + rect.height - bottom_border;
 
506
  y3 = rect.y + rect.height;
 
507
 
 
508
  /* Bottom border */
 
509
 
 
510
  pattern = cairo_pattern_create_linear(0, y2, 0, y3);
 
511
 
 
512
  cairo_pattern_add_color_stop_rgba(pattern, 0.0, 0, 0, 0, inner_alpha);
 
513
  cairo_pattern_add_color_stop_rgba(pattern, 1.0, 0, 0, 0, 0.0);
 
514
 
 
515
  cairo_set_source(cr, pattern);
 
516
  cairo_pattern_destroy(pattern);
 
517
 
 
518
  cairo_rectangle(cr, x1, y2, x2 - x1, y3 - y2);
 
519
  cairo_fill(cr);
 
520
 
 
521
  /* Left border */
 
522
 
 
523
  pattern = cairo_pattern_create_linear(x0, 0, x1, 0);
 
524
 
 
525
  cairo_pattern_add_color_stop_rgba(pattern, 0.0, 0, 0, 0, 0.0);
 
526
  cairo_pattern_add_color_stop_rgba(pattern, 1.0, 0, 0, 0, inner_alpha);
 
527
 
 
528
  cairo_set_source(cr, pattern);
 
529
  cairo_pattern_destroy(pattern);
 
530
 
 
531
  cairo_rectangle(cr, x0, y0, x1 - x0, y2 - y0);
 
532
  cairo_fill(cr);
 
533
 
 
534
  /* Right border */
 
535
 
 
536
  pattern = cairo_pattern_create_linear(x2, 0, x3, 0);
 
537
 
 
538
  cairo_pattern_add_color_stop_rgba(pattern, 0.0, 0, 0, 0, inner_alpha);
 
539
  cairo_pattern_add_color_stop_rgba(pattern, 1.0, 0, 0, 0, 0.0);
 
540
 
 
541
  cairo_set_source(cr, pattern);
 
542
  cairo_pattern_destroy(pattern);
 
543
 
 
544
  cairo_rectangle(cr, x2, y0, x3 - x2, y2 - y0);
 
545
  cairo_fill(cr);
 
546
 
 
547
  /* SW corner */
 
548
 
 
549
  pattern = cairo_pattern_create_radial(0, 0, 0, 0.0, 0, 1.0);
 
550
  cairo_pattern_add_color_stop_rgba(pattern, 0.0, 0, 0, 0, inner_alpha);
 
551
  cairo_pattern_add_color_stop_rgba(pattern, 1.0, 0, 0, 0, 0.0);
 
552
 
 
553
  cairo_matrix_init_scale (&matrix, 1.0 / left_border, 1.0 / bottom_border);
 
554
  cairo_matrix_translate (&matrix, - x1, -y2);
 
555
  cairo_pattern_set_matrix (pattern, &matrix);
 
556
 
 
557
  cairo_set_source(cr, pattern);
 
558
  cairo_pattern_destroy(pattern);
 
559
 
 
560
  cairo_rectangle(cr, x0, y2, x1 - x0, y3 - y2);
 
561
  cairo_fill(cr);
 
562
 
 
563
  /* SE corner */
 
564
 
 
565
  pattern = cairo_pattern_create_radial(0, 0, 0, 0, 0, 1.0);
 
566
  cairo_pattern_add_color_stop_rgba(pattern, 0.0, 0.0, 0, 0, inner_alpha);
 
567
  cairo_pattern_add_color_stop_rgba(pattern, 1.0, 0.0, 0, 0, 0.0);
 
568
 
 
569
  cairo_matrix_init_scale (&matrix, 1.0 / left_border, 1.0 / bottom_border);
 
570
  cairo_matrix_translate (&matrix, - x2, -y2);
 
571
  cairo_pattern_set_matrix (pattern, &matrix);
 
572
 
 
573
  cairo_set_source(cr, pattern);
 
574
  cairo_pattern_destroy(pattern);
 
575
 
 
576
  cairo_rectangle(cr, x2, y2, x3 - x2, y3 - y2);
 
577
  cairo_fill(cr);
 
578
 
 
579
  cairo_restore (cr);
 
580
}
 
581
 
 
582
static void
 
583
get_padding_and_border (GdNotification *notification,
 
584
                        GtkBorder *border)
 
585
{
 
586
  GtkStyleContext *context;
 
587
  GtkStateFlags state;
 
588
  GtkBorder tmp;
 
589
 
 
590
  context = gtk_widget_get_style_context (GTK_WIDGET (notification));
 
591
  state = gtk_widget_get_state_flags (GTK_WIDGET (notification));
 
592
 
 
593
  gtk_style_context_get_padding (context, state, border);
 
594
 
 
595
  gtk_style_context_get_border (context, state, &tmp);
 
596
  border->top += tmp.top;
 
597
  border->right += tmp.right;
 
598
  border->bottom += tmp.bottom;
 
599
  border->left += tmp.left;
 
600
}
 
601
 
 
602
static gboolean
 
603
gd_notification_draw (GtkWidget *widget, cairo_t *cr)
 
604
{
 
605
  GdNotification *notification = GD_NOTIFICATION (widget);
 
606
  GdNotificationPrivate *priv = notification->priv;
 
607
  GtkStyleContext *context;
 
608
  GdkRectangle rect;
 
609
  int inner_radius;
 
610
 
 
611
  if (gtk_cairo_should_draw_window (cr, priv->bin_window))
 
612
    {
 
613
      gtk_widget_get_allocation (widget, &rect);
 
614
 
 
615
      context = gtk_widget_get_style_context(widget);
 
616
 
 
617
      inner_radius = 5;
 
618
      draw_shadow_box (cr, rect, SHADOW_OFFSET_X + inner_radius, SHADOW_OFFSET_X + inner_radius,
 
619
                       SHADOW_OFFSET_Y + inner_radius, 0.8);
 
620
 
 
621
      gtk_style_context_save (context);
 
622
      gtk_render_background (context,  cr,
 
623
                             SHADOW_OFFSET_X, 0,
 
624
                             gtk_widget_get_allocated_width (widget) - 2 *SHADOW_OFFSET_X,
 
625
                             gtk_widget_get_allocated_height (widget) - SHADOW_OFFSET_Y);
 
626
      gtk_render_frame (context,cr,
 
627
                        SHADOW_OFFSET_X, 0,
 
628
                        gtk_widget_get_allocated_width (widget) - 2 *SHADOW_OFFSET_X,
 
629
                        gtk_widget_get_allocated_height (widget) - SHADOW_OFFSET_Y);
 
630
 
 
631
      gtk_style_context_restore (context);
 
632
 
 
633
      if (GTK_WIDGET_CLASS (gd_notification_parent_class)->draw)
 
634
        GTK_WIDGET_CLASS (gd_notification_parent_class)->draw(widget, cr);
 
635
    }
 
636
 
 
637
  return FALSE;
 
638
}
 
639
 
 
640
static void
 
641
gd_notification_add (GtkContainer *container,
 
642
                      GtkWidget    *child)
 
643
{
 
644
  GtkBin *bin = GTK_BIN (container);
 
645
  GdNotification *notification = GD_NOTIFICATION (bin);
 
646
  GdNotificationPrivate *priv = notification->priv;
 
647
 
 
648
  g_return_if_fail (gtk_bin_get_child (bin) == NULL);
 
649
 
 
650
  gtk_widget_set_parent_window (child, priv->bin_window);
 
651
 
 
652
  GTK_CONTAINER_CLASS (gd_notification_parent_class)->add (container, child);
 
653
}
 
654
 
 
655
 
 
656
static void
 
657
gd_notification_get_preferred_width (GtkWidget *widget, gint *minimum_size, gint *natural_size)
 
658
{
 
659
  GdNotification *notification = GD_NOTIFICATION (widget);
 
660
  GdNotificationPrivate *priv = notification->priv;
 
661
  GtkBin *bin = GTK_BIN (widget);
 
662
  gint child_min, child_nat;
 
663
  GtkWidget *child;
 
664
  GtkBorder padding;
 
665
  gint minimum, natural;
 
666
 
 
667
  get_padding_and_border (notification, &padding);
 
668
 
 
669
  minimum = 0;
 
670
  natural = 0;
 
671
 
 
672
  child = gtk_bin_get_child (bin);
 
673
  if (child && gtk_widget_get_visible (child))
 
674
    {
 
675
      gtk_widget_get_preferred_width (child,
 
676
                                      &child_min, &child_nat);
 
677
      minimum += child_min;
 
678
      natural += child_nat;
 
679
    }
 
680
 
 
681
  if (priv->show_close_button)
 
682
    {
 
683
      gtk_widget_get_preferred_width (priv->close_button,
 
684
                                      &child_min, &child_nat);
 
685
      minimum += child_min;
 
686
      natural += child_nat;
 
687
    }
 
688
 
 
689
  minimum += padding.left + padding.right + 2 * SHADOW_OFFSET_X;
 
690
  natural += padding.left + padding.right + 2 * SHADOW_OFFSET_X;
 
691
 
 
692
 if (minimum_size)
 
693
    *minimum_size = minimum;
 
694
 
 
695
  if (natural_size)
 
696
    *natural_size = natural;
 
697
}
 
698
 
 
699
static void
 
700
gd_notification_get_preferred_width_for_height (GtkWidget *widget,
 
701
                                                 gint height,
 
702
                                                 gint *minimum_width,
 
703
                                                 gint *natural_width)
 
704
{
 
705
  GdNotification *notification = GD_NOTIFICATION (widget);
 
706
  GdNotificationPrivate *priv = notification->priv;
 
707
  GtkBin *bin = GTK_BIN (widget);
 
708
  gint child_min, child_nat, child_height;
 
709
  GtkWidget *child;
 
710
  GtkBorder padding;
 
711
  gint minimum, natural;
 
712
 
 
713
  get_padding_and_border (notification, &padding);
 
714
 
 
715
  minimum = 0;
 
716
  natural = 0;
 
717
 
 
718
  child_height = height - SHADOW_OFFSET_Y - padding.top - padding.bottom;
 
719
 
 
720
  child = gtk_bin_get_child (bin);
 
721
  if (child && gtk_widget_get_visible (child))
 
722
    {
 
723
      gtk_widget_get_preferred_width_for_height (child, child_height,
 
724
                                                 &child_min, &child_nat);
 
725
      minimum += child_min;
 
726
      natural += child_nat;
 
727
    }
 
728
 
 
729
  if (priv->show_close_button)
 
730
    {
 
731
      gtk_widget_get_preferred_width_for_height (priv->close_button, child_height,
 
732
                                                 &child_min, &child_nat);
 
733
      minimum += child_min;
 
734
      natural += child_nat;
 
735
    }
 
736
 
 
737
  minimum += padding.left + padding.right + 2 * SHADOW_OFFSET_X;
 
738
  natural += padding.left + padding.right + 2 * SHADOW_OFFSET_X;
 
739
 
 
740
 if (minimum_width)
 
741
    *minimum_width = minimum;
 
742
 
 
743
  if (natural_width)
 
744
    *natural_width = natural;
 
745
}
 
746
 
 
747
static void
 
748
gd_notification_get_preferred_height_for_width (GtkWidget *widget,
 
749
                                                 gint width,
 
750
                                                 gint *minimum_height,
 
751
                                                 gint *natural_height)
 
752
{
 
753
  GdNotification *notification = GD_NOTIFICATION (widget);
 
754
  GdNotificationPrivate *priv = notification->priv;
 
755
  GtkBin *bin = GTK_BIN (widget);
 
756
  gint child_min, child_nat, child_width, button_width = 0;
 
757
  GtkWidget *child;
 
758
  GtkBorder padding;
 
759
  gint minimum = 0, natural = 0;
 
760
 
 
761
  get_padding_and_border (notification, &padding);
 
762
 
 
763
  if (priv->show_close_button)
 
764
    {
 
765
      gtk_widget_get_preferred_height (priv->close_button,
 
766
                                       &minimum, &natural);
 
767
      gtk_widget_get_preferred_width (priv->close_button,
 
768
                                      NULL, &button_width);
 
769
    }
 
770
 
 
771
  child = gtk_bin_get_child (bin);
 
772
  if (child && gtk_widget_get_visible (child))
 
773
    {
 
774
      child_width = width - button_width -
 
775
        2 * SHADOW_OFFSET_X - padding.left - padding.right;
 
776
 
 
777
      gtk_widget_get_preferred_height_for_width (child, child_width,
 
778
                                                 &child_min, &child_nat);
 
779
      minimum = MAX (minimum, child_min);
 
780
      natural = MAX (natural, child_nat);
 
781
    }
 
782
 
 
783
  minimum += padding.top + padding.bottom + SHADOW_OFFSET_Y;
 
784
  natural += padding.top + padding.bottom + SHADOW_OFFSET_Y;
 
785
 
 
786
 if (minimum_height)
 
787
    *minimum_height = minimum;
 
788
 
 
789
  if (natural_height)
 
790
    *natural_height = natural;
 
791
}
 
792
 
 
793
static void
 
794
gd_notification_get_preferred_height (GtkWidget *widget, 
 
795
                                      gint *minimum_height, 
 
796
                                      gint *natural_height)
 
797
{
 
798
  gint width;
 
799
 
 
800
  gd_notification_get_preferred_width (widget, &width, NULL);
 
801
  gd_notification_get_preferred_height_for_width (widget, width,
 
802
                                                  minimum_height, natural_height);
 
803
}
 
804
 
 
805
static void
 
806
gd_notification_size_allocate (GtkWidget *widget,
 
807
                                GtkAllocation *allocation)
 
808
{
 
809
  GdNotification *notification = GD_NOTIFICATION (widget);
 
810
  GdNotificationPrivate *priv = notification->priv;
 
811
  GtkBin *bin = GTK_BIN (widget);
 
812
  GtkAllocation child_allocation;
 
813
  GtkBorder padding;
 
814
  GtkRequisition button_req;
 
815
  GtkWidget *child;
 
816
 
 
817
  gtk_widget_set_allocation (widget, allocation);
 
818
 
 
819
  /* If somehow the notification changes while not hidden
 
820
     and we're not animating, immediately follow the resize */
 
821
  if (priv->animate_y > 0 &&
 
822
      !priv->animate_timeout)
 
823
    priv->animate_y = allocation->height;
 
824
 
 
825
  get_padding_and_border (notification, &padding);
 
826
 
 
827
  if (gtk_widget_get_realized (widget))
 
828
    {
 
829
      gdk_window_move_resize (gtk_widget_get_window (widget),
 
830
                              allocation->x,
 
831
                              allocation->y,
 
832
                              allocation->width,
 
833
                              allocation->height);
 
834
      gdk_window_move_resize (priv->bin_window,
 
835
                              0,
 
836
                              -allocation->height + priv->animate_y,
 
837
                              allocation->width,
 
838
                              allocation->height);
 
839
    }
 
840
 
 
841
  child_allocation.x = SHADOW_OFFSET_X + padding.left;
 
842
  child_allocation.y = padding.top;
 
843
 
 
844
  if (priv->show_close_button)
 
845
    gtk_widget_get_preferred_size (priv->close_button, &button_req, NULL);
 
846
  else
 
847
    button_req.width = button_req.height = 0;
 
848
 
 
849
  child_allocation.height = MAX (1, allocation->height - SHADOW_OFFSET_Y - padding.top - padding.bottom);
 
850
  child_allocation.width = MAX (1, (allocation->width - button_req.width -
 
851
                                    2 * SHADOW_OFFSET_X - padding.left - padding.right));
 
852
 
 
853
  child = gtk_bin_get_child (bin);
 
854
  if (child && gtk_widget_get_visible (child))
 
855
    gtk_widget_size_allocate (child, &child_allocation);
 
856
 
 
857
  if (priv->show_close_button)
 
858
    {
 
859
      child_allocation.x += child_allocation.width;
 
860
      child_allocation.width = button_req.width;
 
861
      child_allocation.y += (child_allocation.height - button_req.height) / 2;
 
862
      child_allocation.height = button_req.height;
 
863
 
 
864
      gtk_widget_size_allocate (priv->close_button, &child_allocation);
 
865
    }
 
866
}
 
867
 
 
868
static gboolean
 
869
gd_notification_timeout_cb (gpointer user_data)
 
870
{
 
871
  GdNotification *notification = GD_NOTIFICATION (user_data);
 
872
 
 
873
  gd_notification_dismiss (notification);
 
874
 
 
875
  return FALSE;
 
876
}
 
877
 
 
878
void
 
879
gd_notification_set_timeout (GdNotification *notification,
 
880
                             gint            timeout_sec)
 
881
{
 
882
  GdNotificationPrivate *priv = notification->priv;
 
883
 
 
884
  priv->timeout = timeout_sec;
 
885
  g_object_notify (G_OBJECT (notification), "timeout");
 
886
}
 
887
 
 
888
void
 
889
gd_notification_set_show_close_button (GdNotification *notification,
 
890
                                       gboolean show_close_button)
 
891
{
 
892
  GdNotificationPrivate *priv = notification->priv;
 
893
 
 
894
  priv->show_close_button = show_close_button;
 
895
 
 
896
  gtk_widget_set_visible (priv->close_button, show_close_button);
 
897
  gtk_widget_queue_resize (GTK_WIDGET (notification));
 
898
}
 
899
 
 
900
void
 
901
gd_notification_dismiss (GdNotification *notification)
 
902
{
 
903
  GdNotificationPrivate *priv = notification->priv;
 
904
 
 
905
  if (notification->priv->timeout_source_id)
 
906
    {
 
907
      g_source_remove (notification->priv->timeout_source_id);
 
908
      notification->priv->timeout_source_id = 0;
 
909
    }
 
910
 
 
911
  priv->dismissed = TRUE;
 
912
  priv->revealed = FALSE;
 
913
  start_animation (notification);
 
914
}
 
915
 
 
916
static void
 
917
gd_notification_close_button_clicked_cb (GtkWidget *widget, gpointer user_data)
 
918
{
 
919
  GdNotification *notification = GD_NOTIFICATION(user_data);
 
920
 
 
921
  gd_notification_dismiss (notification);
 
922
}
 
923
 
 
924
GtkWidget *
 
925
gd_notification_new (void)
 
926
{
 
927
  return g_object_new (GD_TYPE_NOTIFICATION, NULL);
 
928
}