Don't close HildonAppMenu if there's an animation actor below it
[hildon] / hildon / hildon-banner.c
1 /*
2  * This file is a part of hildon
3  *
4  * Copyright (C) 2005, 2006, 2007 Nokia Corporation, all rights reserved.
5  *
6  * Contact: Rodrigo Novo <rodrigo.novo@nokia.com>
7  *
8  * This library is free software; you can redistribute it and/or
9  * modify it under the terms of the GNU Lesser General Public License
10  * as published by the Free Software Foundation; version 2.1 of
11  * the License, or (at your option) any later version.
12  *
13  * This library is distributed in the hope that it will be useful, but
14  * WITHOUT ANY WARRANTY; without even the implied warranty of
15  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16  * Lesser General Public License for more details.
17  *
18  * You should have received a copy of the GNU Lesser General Public
19  * License along with this library; if not, write to the Free Software
20  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
21  * 02110-1301 USA
22  *
23  */
24
25 /**
26  * SECTION:hildon-banner 
27  * @short_description: A widget used to display timed notifications. 
28  *
29  * #HildonBanner is a small, pop-up window that can be used to display
30  * a short, timed notification or information to the user. It can
31  * communicate that a task has been finished or that the application
32  * state has changed.
33  *
34  * Hildon provides convenient funtions to create and show banners. To
35  * create and show information banners you can use
36  * hildon_banner_show_information(), hildon_banner_show_informationf()
37  * or hildon_banner_show_information_with_markup().
38  *
39  * If the application window has set the _HILDON_DO_NOT_DISTURB flag (using
40  * hildon_gtk_window_set_do_not_disturb() for example), the banner will not
41  * be shown. If you need to override this flag for important information,
42  * you can use the method hildon_banner_show_information_override_dnd().
43  * Please, take into account that this is only for important information.
44  *
45  *
46  * Two more kinds of banners are maintained for backward compatibility
47  * but are no longer recommended in Hildon 2.2. These are the animated
48  * banner (created with hildon_banner_show_animation()) and the
49  * progress banner (created with hildon_banner_show_progress()). See
50  * hildon_gtk_window_set_progress_indicator() for the preferred way of
51  * showing progress notifications in Hildon 2.2.
52  *
53  * Information banners dissapear automatically after a certain
54  * period. This is stored in the #HildonBanner:timeout property (in
55  * miliseconds), and can be changed using hildon_banner_set_timeout().
56  *
57  * Note that #HildonBanner<!-- -->s should only be used to display
58  * non-critical pieces of information.
59  */
60
61 #ifdef                                          HAVE_CONFIG_H
62 #include                                        <config.h>
63 #endif
64
65 #include                                        <string.h>
66 #include                                        <X11/Xatom.h>
67 #include                                        <gdk/gdkx.h>
68
69 #undef                                          HILDON_DISABLE_DEPRECATED
70
71 #include                                        "hildon-banner.h"
72 #include                                        "hildon-banner-private.h"
73 #include                                        "hildon-defines.h"
74 #include                                        "hildon-gtk.h"
75
76 /* position relative to the screen */
77
78 #define                                         HILDON_BANNER_WINDOW_X 0
79
80 #define                                         HILDON_BANNER_WINDOW_Y HILDON_WINDOW_TITLEBAR_HEIGHT
81
82 /* max widths */
83
84 #define                                         HILDON_BANNER_LABEL_MAX_TIMED \
85                                                 (gdk_screen_width() - ((HILDON_MARGIN_TRIPLE) * 2))
86
87 #define                                         HILDON_BANNER_LABEL_MAX_PROGRESS 375 /*265*/
88
89 /* default timeout */
90
91 #define                                         HILDON_BANNER_DEFAULT_TIMEOUT 3000
92
93 /* default icons */
94
95 #define                                         HILDON_BANNER_DEFAULT_PROGRESS_ANIMATION "indicator_update"
96
97 /* animation related stuff */
98
99 #define                                         HILDON_BANNER_ANIMATION_FRAMERATE ((float)1000/150)
100
101 #define                                         HILDON_BANNER_ANIMATION_TMPL "indicator_update%d"
102
103 #define                                         HILDON_BANNER_ANIMATION_NFRAMES 8
104
105 enum 
106 {
107     PROP_0,
108     PROP_PARENT_WINDOW, 
109     PROP_IS_TIMED,
110     PROP_TIMEOUT
111 };
112
113 static GtkWidget*                               global_timed_banner = NULL;
114
115 static GQuark 
116 hildon_banner_timed_quark                       (void);
117
118 static void 
119 hildon_banner_bind_style                        (HildonBanner *self,
120                                                  const gchar *name);
121
122 static gboolean 
123 hildon_banner_timeout                           (gpointer data);
124
125 static gboolean 
126 hildon_banner_clear_timeout                     (HildonBanner *self);
127
128 static void 
129 hildon_banner_ensure_timeout                    (HildonBanner *self);
130
131 static void 
132 hildon_banner_set_property                      (GObject *object,
133                                                  guint prop_id,
134                                                  const GValue *value,
135                                                  GParamSpec *pspec);
136     
137 static void 
138 hildon_banner_get_property                      (GObject *object,
139                                                  guint prop_id,
140                                                  GValue *value,
141                                                  GParamSpec *pspec);
142
143 static void
144 hildon_banner_destroy                           (GtkObject *object);
145         
146 static GObject*
147 hildon_banner_real_get_instance                 (GObject *window, 
148                                                  gboolean timed);
149
150 static GObject* 
151 hildon_banner_constructor                       (GType type,
152                                                  guint n_construct_params,
153                                                  GObjectConstructParam *construct_params);
154
155 static void
156 hildon_banner_finalize                          (GObject *object);
157
158 static gboolean
159 hildon_banner_button_press_event                (GtkWidget* widget,
160                                                  GdkEventButton* event);
161
162 static gboolean 
163 hildon_banner_map_event                         (GtkWidget *widget, 
164                                                  GdkEventAny *event);
165 static void
166 hildon_banner_reset_wrap_state                  (HildonBanner *banner);
167
168 static void 
169 force_to_wrap_truncated                         (HildonBanner *banner);
170
171 static void
172 hildon_banner_check_position                    (GtkWidget *widget);
173
174 static void
175 hildon_banner_realize                           (GtkWidget *widget);
176
177 static void 
178 hildon_banner_class_init                        (HildonBannerClass *klass);
179
180 static void 
181 hildon_banner_init                              (HildonBanner *self);
182
183 static void
184 hildon_banner_ensure_child                      (HildonBanner *self, 
185                                                  GtkWidget *user_widget,
186                                                  guint pos,
187                                                  GType type,
188                                                  const gchar *first_property, 
189                                                  ...);
190
191 static HildonBanner*
192 hildon_banner_get_instance_for_widget           (GtkWidget *widget, 
193                                                  gboolean timed);
194
195 static void
196 hildon_banner_set_override_flag                 (HildonBanner *banner);
197
198 static GtkWidget*
199 hildon_banner_real_show_information             (GtkWidget *widget,
200                                                  const gchar *text,
201                                                  gboolean override_dnd);
202
203
204 G_DEFINE_TYPE (HildonBanner, hildon_banner, GTK_TYPE_WINDOW)
205
206 static GQuark 
207 hildon_banner_timed_quark                       (void)
208 {
209     static GQuark quark = 0;
210
211     if (G_UNLIKELY(quark == 0))
212         quark = g_quark_from_static_string ("hildon-banner-timed");
213
214     return quark;
215 }
216
217 /* Set the widget and label name to make the correct rc-style attached into them */
218 static void 
219 hildon_banner_bind_style                  (HildonBanner *self,
220                                            const gchar *name_sufix)
221 {
222     HildonBannerPrivate *priv = HILDON_BANNER_GET_PRIVATE (self);
223     gchar *name;
224
225     g_assert (priv);
226
227     name = g_strconcat ("HildonBannerLabel-", name_sufix, NULL);
228     gtk_widget_set_name (priv->label, name);
229     g_free (name);
230
231     name = g_strconcat ("HildonBanner-", name_sufix, NULL);
232     gtk_widget_set_name (GTK_WIDGET (self), name);
233     g_free (name);
234 }
235
236 /* In timeout function we automatically destroy timed banners */
237 static gboolean
238 simulate_close (GtkWidget* widget)
239 {
240     gboolean result = FALSE;
241
242     /* If the banner is currently visible (it normally should), 
243        we simulate clicking the close button of the window.
244        This allows applications to reuse the banner by prevent
245        closing it etc */
246     if (GTK_WIDGET_DRAWABLE (widget))
247     {
248         GdkEvent *event = gdk_event_new (GDK_DELETE);
249         event->any.window = g_object_ref (widget->window);
250         event->any.send_event = FALSE;
251         result = gtk_widget_event (widget, event);
252         gdk_event_free (event);
253     }
254
255     return result;
256 }
257
258 static gboolean 
259 hildon_banner_timeout                           (gpointer data)
260 {
261     GtkWidget *widget;
262     gboolean continue_timeout = FALSE;
263
264     GDK_THREADS_ENTER ();
265
266     g_assert (HILDON_IS_BANNER (data));
267
268     widget = GTK_WIDGET (data);
269     g_object_ref (widget);
270
271     continue_timeout = simulate_close (widget);
272
273     if (! continue_timeout) {
274         HildonBannerPrivate *priv = HILDON_BANNER_GET_PRIVATE (data);
275         priv->timeout_id = 0;
276         gtk_widget_destroy (widget);
277     }
278
279     g_object_unref (widget);
280
281     GDK_THREADS_LEAVE ();
282
283     return continue_timeout;
284 }
285
286 static gboolean 
287 hildon_banner_clear_timeout                     (HildonBanner *self)
288 {
289     HildonBannerPrivate *priv = HILDON_BANNER_GET_PRIVATE (self);
290     g_assert (priv);
291
292     if (priv->timeout_id != 0) {
293         g_source_remove (priv->timeout_id);
294         priv->timeout_id = 0;
295         return TRUE;
296     }
297
298     return FALSE;
299 }
300
301 static void 
302 hildon_banner_ensure_timeout                    (HildonBanner *self)
303 {
304     HildonBannerPrivate *priv = HILDON_BANNER_GET_PRIVATE (self);
305     g_assert (priv);
306
307     if (priv->timeout_id == 0 && priv->is_timed && priv->timeout > 0)
308         priv->timeout_id = g_timeout_add (priv->timeout, 
309                 hildon_banner_timeout, self);
310 }
311
312 static void 
313 hildon_banner_set_property                      (GObject *object,
314                                                  guint prop_id,
315                                                  const GValue *value,
316                                                  GParamSpec *pspec)
317 {
318     GtkWidget *window;
319     HildonBannerPrivate *priv = HILDON_BANNER_GET_PRIVATE (object);
320     g_assert (priv);
321
322     switch (prop_id) {
323
324         case PROP_TIMEOUT:
325              priv->timeout = g_value_get_uint (value);
326              break;
327  
328         case PROP_IS_TIMED:
329             priv->is_timed = g_value_get_boolean (value);
330             break;
331
332         case PROP_PARENT_WINDOW:
333             window = g_value_get_object (value);         
334             if (priv->parent) {
335                 g_object_remove_weak_pointer(G_OBJECT (priv->parent), (gpointer) &priv->parent);
336             }
337
338             gtk_window_set_transient_for (GTK_WINDOW (object), (GtkWindow *) window);
339             priv->parent = (GtkWindow *) window;
340
341             if (window) {
342                 gtk_window_set_destroy_with_parent (GTK_WINDOW (object), TRUE);
343                 g_object_add_weak_pointer(G_OBJECT (window), (gpointer) &priv->parent);
344             }
345
346             break;
347
348         default:
349             G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
350             break;
351     }
352 }
353
354 static void 
355 hildon_banner_get_property                      (GObject *object,
356                                                  guint prop_id,
357                                                  GValue *value,
358                                                  GParamSpec *pspec)
359 {
360     HildonBannerPrivate *priv = HILDON_BANNER_GET_PRIVATE (object);
361     g_assert (priv);
362
363     switch (prop_id)
364     {
365         case PROP_TIMEOUT:
366              g_value_set_uint (value, priv->timeout);
367              break;
368  
369         case PROP_IS_TIMED:
370             g_value_set_boolean (value, priv->is_timed);
371             break;
372
373         case PROP_PARENT_WINDOW:
374             g_value_set_object (value, gtk_window_get_transient_for (GTK_WINDOW (object)));
375             break;
376
377         default:
378             G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
379             break;
380     }
381 }
382
383 static void
384 hildon_banner_destroy                           (GtkObject *object)
385 {
386     HildonBannerPrivate *priv = HILDON_BANNER_GET_PRIVATE (object);
387     g_assert (priv);
388
389     HildonBanner *self;
390     GObject *parent_window = (GObject *) priv->parent;
391
392     g_assert (HILDON_IS_BANNER (object));
393     self = HILDON_BANNER (object);
394
395     /* Drop possible global pointer. That can hold reference to us */
396     if ((gpointer) object == (gpointer) global_timed_banner) {
397         global_timed_banner = NULL;
398         g_object_unref (object);
399     }
400
401     /* Remove the data from parent window for timed banners. Those hold reference */
402     if (priv->is_timed && parent_window != NULL) {
403         g_object_set_qdata (parent_window, hildon_banner_timed_quark (), NULL);
404     }
405
406     if (!priv->is_timed && priv->parent) {
407         hildon_gtk_window_set_progress_indicator (priv->parent, 0);
408     }
409
410     (void) hildon_banner_clear_timeout (self);
411
412     if (GTK_OBJECT_CLASS (hildon_banner_parent_class)->destroy)
413         GTK_OBJECT_CLASS (hildon_banner_parent_class)->destroy (object);
414 }
415
416 /* Search a previous banner instance */
417 static GObject*
418 hildon_banner_real_get_instance                 (GObject *window, 
419                                                  gboolean timed)
420 {
421     if (timed) {
422         /* If we have a parent window, the previous instance is stored there */
423         if (window) {
424             return g_object_get_qdata(window, hildon_banner_timed_quark ());
425         }
426
427         /* System notification instance is stored into global pointer */
428         return (GObject *) global_timed_banner;
429     }
430
431     /* Non-timed banners are normal (non-singleton) objects */
432     return NULL;
433 }
434
435 /* By overriding constructor we force timed banners to be
436    singletons for each window */
437 static GObject* 
438 hildon_banner_constructor                       (GType type,
439                                                  guint n_construct_params,
440                                                  GObjectConstructParam *construct_params)
441 {
442     GObject *banner, *window = NULL;
443     gboolean timed = FALSE;
444     guint i;
445
446     /* Search banner type information from parameters in order
447        to locate the possible previous banner instance. */
448     for (i = 0; i < n_construct_params; i++)
449     {
450         if (strcmp(construct_params[i].pspec->name, "parent-window") == 0)
451             window = g_value_get_object (construct_params[i].value);       
452         else if (strcmp(construct_params[i].pspec->name, "is-timed") == 0)
453             timed = g_value_get_boolean (construct_params[i].value);
454     }
455
456     /* Try to get a previous instance if such exists */
457     banner = hildon_banner_real_get_instance (window, timed);
458     if (! banner)
459     {
460         /* We have to create a new banner */
461         banner = G_OBJECT_CLASS (hildon_banner_parent_class)->constructor (type, n_construct_params, construct_params);
462
463         /* Store the newly created singleton instance either into parent 
464            window data or into global variables. */
465         if (timed) {
466             if (window) {
467                 g_object_set_qdata_full (G_OBJECT (window), hildon_banner_timed_quark (), 
468                         g_object_ref (banner), g_object_unref); 
469             } else {
470                 g_assert (global_timed_banner == NULL);
471                 global_timed_banner = g_object_ref (banner);
472             }
473         }
474     }
475     else {
476         /* FIXME: This is a hack! We have to manually freeze
477            notifications. This is normally done by g_object_init, but we
478            are not going to call that. g_object_newv will otherwise give
479            a critical like this:
480
481            GLIB CRITICAL ** GLib-GObject - g_object_notify_queue_thaw: 
482            assertion `nqueue->freeze_count > 0' failed */
483
484         g_object_freeze_notify (banner);
485         hildon_banner_reset_wrap_state (HILDON_BANNER (banner));
486     }
487
488     /* We restart possible timeouts for each new timed banner request */
489     if (timed && hildon_banner_clear_timeout (HILDON_BANNER (banner)))
490         hildon_banner_ensure_timeout (HILDON_BANNER(banner));
491
492     return banner;
493 }
494
495 static void
496 hildon_banner_finalize                          (GObject *object)
497 {
498     HildonBannerPrivate *priv = HILDON_BANNER_GET_PRIVATE (object);
499
500     if (priv->parent) {
501         g_object_remove_weak_pointer(G_OBJECT (priv->parent), (gpointer) &priv->parent);
502     }
503
504     G_OBJECT_CLASS (hildon_banner_parent_class)->finalize (object);
505 }
506
507 static gboolean
508 hildon_banner_button_press_event                (GtkWidget* widget,
509                                                  GdkEventButton* event)
510 {
511     gboolean result = simulate_close (widget);
512
513     if (!result) {
514         /* signal emission not stopped - basically behave like
515          * gtk_main_do_event() for a delete event, but just hide the
516          * banner instead of destroying it, as it is already meant to
517          * be destroyed by hildon_banner_timeout() (if it's timed) or
518          * the application (if it's not). */
519         gtk_widget_hide (widget);
520     }
521
522     return result;
523 }
524
525 #if defined(MAEMO_GTK)
526 static void
527 hildon_banner_map                               (GtkWidget *widget)
528 {
529     if (GTK_WIDGET_CLASS (hildon_banner_parent_class)->map) {
530         /* Make the banner temporary _before_ mapping it, to avoid closing
531          * other temporary windows */
532         gtk_window_set_is_temporary (GTK_WINDOW (widget), TRUE);
533
534         GTK_WIDGET_CLASS (hildon_banner_parent_class)->map (widget);
535
536         /* Make the banner non-temporary _after_ mapping it, to avoid
537          * being closed by other non-temporary windows */
538         gtk_window_set_is_temporary (GTK_WINDOW (widget), FALSE);
539
540         hildon_banner_check_position (widget);
541     }
542 }
543 #endif
544
545 /* We start the timer for timed notifications after the window appears on screen */
546 static gboolean 
547 hildon_banner_map_event                         (GtkWidget *widget, 
548                                                  GdkEventAny *event)
549 {
550     gboolean result = FALSE;
551
552     if (GTK_WIDGET_CLASS (hildon_banner_parent_class)->map_event)
553         result = GTK_WIDGET_CLASS (hildon_banner_parent_class)->map_event (widget, event);
554
555     hildon_banner_ensure_timeout (HILDON_BANNER(widget));
556
557     return result;
558 }  
559
560 static void
561 hildon_banner_reset_wrap_state (HildonBanner *banner)
562 {
563     PangoLayout *layout;
564     HildonBannerPrivate *priv;
565
566     priv = HILDON_BANNER_GET_PRIVATE (banner);
567     g_assert (priv);
568
569     layout = gtk_label_get_layout (GTK_LABEL (priv->label));
570
571     pango_layout_set_width (layout, -1);
572     priv->has_been_wrapped = FALSE;
573     priv->has_been_truncated = FALSE;
574
575     gtk_widget_set_size_request (priv->label, -1, -1);
576     gtk_widget_set_size_request (GTK_WIDGET (banner), -1, -1);
577 }
578
579 /* force to wrap truncated label by setting explicit size request
580  * see N#27000 and G#329646 */
581 static void 
582 force_to_wrap_truncated                         (HildonBanner *banner)
583 {
584     GtkLabel *label;
585     PangoLayout *layout;
586     int width_text, width_max;
587     int width = -1;
588     int height = -1;
589     PangoRectangle logical;
590     GtkRequisition requisition;
591     HildonBannerPrivate *priv = HILDON_BANNER_GET_PRIVATE (banner);
592
593     g_return_if_fail (priv);
594
595     label = GTK_LABEL (priv->label);
596
597     layout = gtk_label_get_layout (label);
598
599     pango_layout_get_extents (layout, NULL, &logical);
600     width_text = PANGO_PIXELS (logical.width);
601
602     width_max = priv->is_timed ? HILDON_BANNER_LABEL_MAX_TIMED
603         : HILDON_BANNER_LABEL_MAX_PROGRESS;
604
605     /* If the width of the label is going to exceed the maximum allowed
606      * width, enforce the maximum allowed width now.
607      */
608     if (priv->has_been_wrapped
609         || width_text >= width_max
610         || pango_layout_is_wrapped (layout)) {
611         /* Force wrapping by setting the maximum size */
612         width = width_max;
613
614         priv->has_been_wrapped = TRUE;
615     }
616
617     /* Make the label update its layout; and update our layout pointer
618      * because the layout will be cleared and refreshed.
619      */
620     gtk_widget_set_size_request (GTK_WIDGET (label), width, height);
621     gtk_widget_size_request (GTK_WIDGET (label), &requisition);
622
623     layout = gtk_label_get_layout (label);
624
625     /* If the layout has now been wrapped and exceeds 3 lines, we truncate
626      * the rest of the label according to spec.
627      */
628     if (priv->has_been_truncated
629         || (pango_layout_is_wrapped (layout)
630             && pango_layout_get_line_count (layout) > 3)) {
631         int lines;
632
633         pango_layout_get_extents (layout, NULL, &logical);
634         lines = pango_layout_get_line_count (layout);
635
636         /* This calculation assumes that the same font is used
637          * throughout the banner -- this is usually the case on maemo
638          *
639          * FIXME: Pango >= 1.20 has pango_layout_set_height().
640          */
641         height = (PANGO_PIXELS (logical.height) * 3) / lines + 1;
642         priv->has_been_truncated = TRUE;
643     }
644
645     /* Set the new width/height if applicable */
646     gtk_widget_set_size_request (GTK_WIDGET (label), width, height);
647 }
648
649
650 static void
651 hildon_banner_check_position                    (GtkWidget *widget)
652 {
653     gint x, y;
654     GtkRequisition req;
655
656     gtk_widget_set_size_request (widget, gdk_screen_width (), -1);
657
658     force_to_wrap_truncated (HILDON_BANNER(widget)); /* see N#27000 and G#329646 */
659
660     gtk_widget_size_request (widget, &req);
661
662     if (req.width == 0)
663     {
664         return;
665     }
666
667     x = HILDON_BANNER_WINDOW_X;
668
669     y = HILDON_BANNER_WINDOW_Y;
670
671     gtk_window_move (GTK_WINDOW (widget), x, y);
672 }
673
674 static void
675 screen_size_changed                            (GdkScreen *screen,
676                                                 GtkWindow *banner)
677
678 {
679     gtk_window_reshow_with_initial_size (banner);
680 }
681
682 static void
683 hildon_banner_realize                           (GtkWidget *widget)
684 {
685     GdkWindow *gdkwin;
686     GdkScreen *screen;
687     GdkAtom atom;
688     guint32 portrait = 1;
689     const gchar *notification_type = "_HILDON_NOTIFICATION_TYPE_BANNER";
690     HildonBannerPrivate *priv = HILDON_BANNER_GET_PRIVATE (widget);
691     g_assert (priv);
692
693     /* We let the parent to init widget->window before we need it */
694     if (GTK_WIDGET_CLASS (hildon_banner_parent_class)->realize)
695         GTK_WIDGET_CLASS (hildon_banner_parent_class)->realize (widget);
696
697     /* We use special hint to turn the banner into information notification. */
698     gdk_window_set_type_hint (widget->window, GDK_WINDOW_TYPE_HINT_NOTIFICATION);
699     gtk_window_set_transient_for (GTK_WINDOW (widget), (GtkWindow *) priv->parent);
700
701     hildon_banner_check_position (widget);
702
703     gdkwin = widget->window;
704
705     /* Set the _HILDON_NOTIFICATION_TYPE property so Matchbox places the window correctly */
706     atom = gdk_atom_intern ("_HILDON_NOTIFICATION_TYPE", FALSE);
707     gdk_property_change (gdkwin, atom, gdk_x11_xatom_to_atom (XA_STRING), 8, GDK_PROP_MODE_REPLACE,
708                          (gpointer) notification_type, strlen (notification_type));
709
710     /* HildonBanner supports portrait mode */
711     atom = gdk_atom_intern ("_HILDON_PORTRAIT_MODE_SUPPORT", FALSE);
712     gdk_property_change (gdkwin, atom, gdk_x11_xatom_to_atom (XA_CARDINAL), 32,
713                          GDK_PROP_MODE_REPLACE, (gpointer) &portrait, 1);
714
715     /* Manage override flag */
716     if ((priv->require_override_dnd)&&(!priv->overrides_dnd)) {
717       hildon_banner_set_override_flag (HILDON_BANNER (widget));
718         priv->overrides_dnd = TRUE;
719     }
720
721     screen = gtk_widget_get_screen (widget);
722     g_signal_connect (screen, "size-changed", G_CALLBACK (screen_size_changed), widget);
723 }
724
725 static void
726 hildon_banner_unrealize                         (GtkWidget *widget)
727 {
728     GdkScreen *screen = gtk_widget_get_screen (widget);
729     g_signal_handlers_disconnect_by_func (screen, G_CALLBACK (screen_size_changed), widget);
730
731     GTK_WIDGET_CLASS (hildon_banner_parent_class)->unrealize (widget);
732 }
733
734 static void 
735 hildon_banner_class_init                        (HildonBannerClass *klass)
736 {
737     GObjectClass *object_class;
738     GtkWidgetClass *widget_class;
739
740     object_class = G_OBJECT_CLASS (klass);
741     widget_class = GTK_WIDGET_CLASS (klass);
742
743     /* Append private structure to class. This is more elegant than
744        on g_new based approach */
745     g_type_class_add_private (klass, sizeof (HildonBannerPrivate));
746
747     /* Override virtual methods */
748     object_class->constructor = hildon_banner_constructor;
749     object_class->finalize = hildon_banner_finalize;
750     object_class->set_property = hildon_banner_set_property;
751     object_class->get_property = hildon_banner_get_property;
752     GTK_OBJECT_CLASS (klass)->destroy = hildon_banner_destroy;
753     widget_class->map_event = hildon_banner_map_event;
754     widget_class->realize = hildon_banner_realize;
755     widget_class->unrealize = hildon_banner_unrealize;
756     widget_class->button_press_event = hildon_banner_button_press_event;
757 #if defined(MAEMO_GTK)
758     widget_class->map = hildon_banner_map;
759 #endif
760
761     /* Install properties.
762        We need construct properties for singleton purposes */
763
764     /**
765      * HildonBanner:parent-window:
766      *
767      * The window for which the banner will be singleton. 
768      *                      
769      */
770     g_object_class_install_property (object_class, PROP_PARENT_WINDOW,
771             g_param_spec_object ("parent-window",
772                 "Parent window",
773                 "The window for which the banner will be singleton",
774                 GTK_TYPE_WINDOW, G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
775
776     /**
777      * HildonBanner:is-timed:
778      *
779      * Whether the banner is timed and goes away automatically.
780      *                      
781      */
782     g_object_class_install_property (object_class, PROP_IS_TIMED,
783             g_param_spec_boolean ("is-timed",
784                 "Is timed",
785                 "Whether or not the notification goes away automatically "
786                 "after the specified time has passed",
787                 FALSE, G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
788
789     /**
790      * HildonBanner:timeout:
791      *
792      * The time before making the banner banner go away. This needs 
793      * to be adjusted before the banner is mapped to the screen.
794      *                      
795      */
796     g_object_class_install_property (object_class, PROP_TIMEOUT,
797             g_param_spec_uint ("timeout",
798                 "Timeout",
799                 "The time before making the banner banner go away",
800                 0,
801                 10000,
802                 HILDON_BANNER_DEFAULT_TIMEOUT,
803                 G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
804 }
805
806 static void 
807 hildon_banner_init                              (HildonBanner *self)
808 {
809     HildonBannerPrivate *priv = HILDON_BANNER_GET_PRIVATE (self);
810     g_assert (priv);
811
812     priv->parent = NULL;
813     priv->overrides_dnd = FALSE;
814     priv->require_override_dnd = FALSE;
815
816     /* Initialize the common layout inside banner */
817     priv->layout = gtk_hbox_new (FALSE, HILDON_MARGIN_DEFAULT);
818
819     priv->label = g_object_new (GTK_TYPE_LABEL, NULL);
820     gtk_label_set_line_wrap (GTK_LABEL (priv->label), TRUE);
821     gtk_label_set_line_wrap_mode (GTK_LABEL (priv->label), PANGO_WRAP_WORD_CHAR);
822
823     gtk_container_set_border_width (GTK_CONTAINER (priv->layout), HILDON_MARGIN_DEFAULT);
824     gtk_container_add (GTK_CONTAINER (self), priv->layout);
825     gtk_box_pack_start (GTK_BOX (priv->layout), priv->label, TRUE, TRUE, 0);
826
827     gtk_window_set_accept_focus (GTK_WINDOW (self), FALSE);
828
829     hildon_banner_reset_wrap_state (self);
830
831     gtk_widget_add_events (GTK_WIDGET (self), GDK_BUTTON_PRESS_MASK);
832 }
833
834 /* Makes sure that icon/progress item contains the desired type
835    of item. If possible, tries to avoid creating a new widget but
836    reuses the existing one */
837 static void
838 hildon_banner_ensure_child                      (HildonBanner *self, 
839                                                  GtkWidget *user_widget,
840                                                  guint pos,
841                                                  GType type,
842                                                  const gchar *first_property, 
843                                                  ...)
844 {
845     GtkWidget *widget;
846     va_list args;
847     HildonBannerPrivate *priv = HILDON_BANNER_GET_PRIVATE (self);
848
849     g_assert (priv);
850
851     widget = priv->main_item;
852     va_start (args, first_property);
853
854     /* Reuse existing widget if possible */
855     if (! user_widget && G_TYPE_CHECK_INSTANCE_TYPE (widget, type))
856     {
857         g_object_set_valist (G_OBJECT (widget), first_property, args);
858     }
859     else
860     {
861         /* We have to abandon old content widget */
862         if (widget)
863             gtk_container_remove (GTK_CONTAINER (priv->layout), widget);
864         
865         /* Use user provided widget or create a new one */
866         priv->main_item = widget = user_widget ? 
867             user_widget : GTK_WIDGET (g_object_new_valist(type, first_property, args));
868         gtk_box_pack_start (GTK_BOX (priv->layout), widget, TRUE, TRUE, 0);
869     }
870
871     /* We make sure that the widget exists in desired position. Different
872        banners place this child widget to different places */
873     gtk_box_reorder_child (GTK_BOX (priv->layout), widget, pos);
874     va_end (args);
875 }
876
877 /* Creates a new banner instance or uses an existing one */
878 static HildonBanner*
879 hildon_banner_get_instance_for_widget           (GtkWidget *widget, 
880                                                  gboolean timed)
881 {
882     GtkWidget *window;
883
884     window = widget ? gtk_widget_get_ancestor (widget, GTK_TYPE_WINDOW) : NULL;
885     return g_object_new (HILDON_TYPE_BANNER, "parent-window", window, "is-timed", timed, NULL);
886 }
887
888 static GtkWidget *
889 hildon_banner_create_animation (void)
890 {
891     GtkWidget *image;
892     GdkPixbufSimpleAnim *anim;
893     GdkPixbuf *frame;
894     GtkIconTheme *theme;
895     GError *error = NULL;
896     gchar *icon_name;
897     gint i;
898
899     anim = gdk_pixbuf_simple_anim_new (HILDON_ICON_PIXEL_SIZE_STYLUS,
900                                        HILDON_ICON_PIXEL_SIZE_STYLUS,
901                                        HILDON_BANNER_ANIMATION_FRAMERATE);
902     gdk_pixbuf_simple_anim_set_loop (anim, TRUE);
903     theme = gtk_icon_theme_get_default ();
904
905     for (i = 1; i <= HILDON_BANNER_ANIMATION_NFRAMES; i++) {
906         icon_name = g_strdup_printf (HILDON_BANNER_ANIMATION_TMPL, i);
907         frame = gtk_icon_theme_load_icon (theme, icon_name, HILDON_ICON_PIXEL_SIZE_STYLUS,
908                                           0, &error);
909
910         if (error) {
911             g_warning ("Icon theme lookup for icon `%s' failed: %s",
912                        icon_name, error->message);
913             g_error_free (error);
914             error = NULL;
915         } else {
916                 gdk_pixbuf_simple_anim_add_frame (anim, frame);
917         }
918
919         g_object_unref (frame);
920         g_free (icon_name);
921     }
922
923     image = gtk_image_new_from_animation (GDK_PIXBUF_ANIMATION (anim));
924     g_object_unref (anim);
925
926     return image;
927 }
928
929 /**
930  * hildon_banner_show_information:
931  * @widget: the #GtkWidget that is the owner of the banner
932  * @icon_name: since Hildon 2.2 this parameter is not used anymore and
933  * any value that you pass will be ignored
934  * @text: Text to display
935  *
936  * This function creates and displays an information banner that
937  * automatically goes away after certain time period. For each window
938  * in your application there can only be one timed banner, so if you
939  * spawn a new banner before the earlier one has timed out, the
940  * previous one will be replaced.
941  *
942  * Returns: The newly created banner
943  *
944  */
945 GtkWidget*
946 hildon_banner_show_information                  (GtkWidget *widget, 
947                                                  const gchar *icon_name,
948                                                  const gchar *text)
949 {
950     return hildon_banner_real_show_information (widget, text, FALSE);
951 }
952
953 /**
954  * hildon_banner_show_information_override_dnd:
955  * @widget: the #GtkWidget that is the owner of the banner
956  * @text: Text to display
957  *
958  * Equivalent to hildon_banner_show_information() but it overrides the do not
959  * disturb flag, in the special cases that could be needed. It is required
960  * because this method calls internally gtk_widget_show before returns the banner,
961  * but the do not disturb flag is checked on the mapping of the widget
962  *
963  *
964  * Returns: The newly created banner
965  *
966  * Since: 2.2
967  *
968  */
969 GtkWidget*
970 hildon_banner_show_information_override_dnd     (GtkWidget *widget,
971                                                  const gchar *text)
972 {
973     return hildon_banner_real_show_information (widget, text, TRUE);
974 }
975
976 static void
977 hildon_banner_set_override_flag                 (HildonBanner *banner)
978 {
979     guint32 state = 1;
980
981     gdk_property_change (GTK_WIDGET (banner)->window,
982                          gdk_atom_intern_static_string ("_HILDON_DO_NOT_DISTURB_OVERRIDE"),
983                          gdk_x11_xatom_to_atom (XA_INTEGER),
984                          32,
985                          GDK_PROP_MODE_REPLACE,
986                          (const guchar*) &state,
987                          1);
988 }
989
990
991 static GtkWidget*
992 hildon_banner_real_show_information             (GtkWidget *widget,
993                                                  const gchar *text,
994                                                  gboolean override_dnd)
995 {
996     HildonBanner *banner;
997     HildonBannerPrivate *priv = NULL;
998
999     g_return_val_if_fail (text != NULL, NULL);
1000
1001     /* Prepare banner */
1002     banner = hildon_banner_get_instance_for_widget (widget, TRUE);
1003     priv = HILDON_BANNER_GET_PRIVATE (banner);
1004
1005     hildon_banner_set_text (banner, text);
1006     hildon_banner_bind_style (banner, "information");
1007
1008     if (override_dnd) {
1009       /* so on the realize it will set the property */
1010       priv->require_override_dnd = TRUE;
1011     }
1012
1013     /* Show the banner, since caller cannot do that */
1014     gtk_widget_show_all (GTK_WIDGET (banner));
1015
1016     return GTK_WIDGET (banner);
1017 }
1018
1019 /**
1020  * hildon_banner_show_informationf:
1021  * @widget: the #GtkWidget that is the owner of the banner
1022  * @icon_name: since Hildon 2.2 this parameter is not used anymore and
1023  * any value that you pass will be ignored
1024  * @format: a printf-like format string
1025  * @Varargs: arguments for the format string
1026  *
1027  * A helper function for #hildon_banner_show_information with
1028  * string formatting.
1029  *
1030  * Returns: the newly created banner
1031  */
1032 GtkWidget* 
1033 hildon_banner_show_informationf                 (GtkWidget *widget, 
1034                                                  const gchar *icon_name,
1035                                                  const gchar *format, 
1036                                                  ...)
1037 {
1038     g_return_val_if_fail (format != NULL, NULL);
1039
1040     gchar *message;
1041     va_list args;
1042     GtkWidget *banner;
1043
1044     va_start (args, format);
1045     message = g_strdup_vprintf (format, args);
1046     va_end (args);
1047
1048     banner = hildon_banner_show_information (widget, icon_name, message);
1049
1050     g_free (message);
1051
1052     return banner;
1053 }
1054
1055 /**
1056  * hildon_banner_show_information_with_markup:
1057  * @widget: the #GtkWidget that wants to display banner
1058  * @icon_name: since Hildon 2.2 this parameter is not used anymore and
1059  * any value that you pass will be ignored
1060  * @markup: a markup string to display (see <link linkend="PangoMarkupFormat">Pango markup format</link>)
1061  *
1062  * This function creates and displays an information banner that
1063  * automatically goes away after certain time period. For each window
1064  * in your application there can only be one timed banner, so if you
1065  * spawn a new banner before the earlier one has timed out, the
1066  * previous one will be replaced.
1067  *
1068  * Returns: the newly created banner
1069  *
1070  */
1071 GtkWidget*
1072 hildon_banner_show_information_with_markup      (GtkWidget *widget, 
1073                                                  const gchar *icon_name, 
1074                                                  const gchar *markup)
1075 {
1076     HildonBanner *banner;
1077
1078     g_return_val_if_fail (icon_name == NULL || icon_name[0] != 0, NULL);
1079     g_return_val_if_fail (markup != NULL, NULL);
1080
1081     /* Prepare banner */
1082     banner = hildon_banner_get_instance_for_widget (widget, TRUE);
1083
1084     hildon_banner_set_markup (banner, markup);
1085     hildon_banner_bind_style (banner, "information");
1086
1087     /* Show the banner, since caller cannot do that */
1088     gtk_widget_show_all (GTK_WIDGET (banner));
1089
1090     return (GtkWidget *) banner;
1091 }
1092
1093 /**
1094  * hildon_banner_show_animation:
1095  * @widget: the #GtkWidget that wants to display banner
1096  * @animation_name: since Hildon 2.2 this parameter is not used
1097  *                  anymore and any value that you pass will be
1098  *                  ignored
1099  * @text: the text to display.
1100  *
1101  * Shows an animated progress notification. It's recommended not to try
1102  * to show more than one progress notification at a time, since
1103  * they will appear on top of each other. You can use progress
1104  * notifications with timed banners. In this case the banners are
1105  * located so that you can somehow see both.
1106  *
1107  * Please note that banners are destroyed automatically once the
1108  * window they are attached to is closed. The pointer that you receive
1109  * with this function does not contain additional references, so it
1110  * can become invalid without warning (this is true for all toplevel
1111  * windows in gtk). To make sure that the banner does not disappear
1112  * automatically, you can separately ref the return value (this
1113  * doesn't prevent the banner from disappearing, just the object from
1114  * being finalized). In this case you have to call both
1115  * gtk_widget_destroy() followed by g_object_unref() (in this order).
1116  *
1117  * Returns: a #HildonBanner widget. You must call gtk_widget_destroy()
1118  *          once you are done with the banner.
1119  *
1120  * Deprecated: Hildon 2.2: use
1121  * hildon_gtk_window_set_progress_indicator() instead.
1122  */
1123 GtkWidget*
1124 hildon_banner_show_animation                    (GtkWidget *widget, 
1125                                                  const gchar *animation_name, 
1126                                                  const gchar *text)
1127 {
1128     HildonBanner *banner;
1129     GtkWidget *image_widget;
1130
1131     g_return_val_if_fail (text != NULL, NULL);
1132
1133     image_widget = hildon_banner_create_animation ();
1134
1135     /* Prepare banner */
1136     banner = hildon_banner_get_instance_for_widget (widget, FALSE);
1137     hildon_banner_ensure_child (banner, image_widget, 0,
1138             GTK_TYPE_IMAGE, "yalign", 0.0, NULL);
1139
1140     hildon_banner_set_text (banner, text);
1141     hildon_banner_bind_style (banner, "animation");
1142
1143     /* And show it */
1144     gtk_widget_show_all (GTK_WIDGET (banner));
1145
1146     return (GtkWidget *) banner;
1147 }
1148
1149 /**
1150  * hildon_banner_show_progress:
1151  * @widget: the #GtkWidget that wants to display banner
1152  * @bar: since Hildon 2.2 this parameter is not used anymore and
1153  * any value that you pass will be ignored
1154  * @text: text to display.
1155  *
1156  * Shows progress notification. See #hildon_banner_show_animation
1157  * for more information.
1158  * 
1159  * Returns: a #HildonBanner widget. You must call #gtk_widget_destroy
1160  *          once you are done with the banner.
1161  *
1162  * Deprecated: Hildon 2.2: use hildon_gtk_window_set_progress_indicator() instead.
1163  */
1164 GtkWidget*
1165 hildon_banner_show_progress                     (GtkWidget *widget, 
1166                                                  GtkProgressBar *bar, 
1167                                                  const gchar *text)
1168 {
1169     HildonBanner *banner;
1170     HildonBannerPrivate *priv;
1171
1172     g_return_val_if_fail (text != NULL, NULL);
1173
1174     /* Prepare banner */
1175     banner = hildon_banner_get_instance_for_widget (widget, FALSE);
1176     priv = HILDON_BANNER_GET_PRIVATE (banner);
1177     g_assert (priv);
1178
1179     hildon_banner_set_text (banner, text);
1180     hildon_banner_bind_style (banner, "progress");
1181
1182     if (priv->parent)
1183         hildon_gtk_window_set_progress_indicator (priv->parent, 1);
1184
1185     /* Show the banner */
1186     gtk_widget_show_all (GTK_WIDGET (banner));
1187
1188     return GTK_WIDGET (banner);   
1189 }
1190
1191 /**
1192  * hildon_banner_set_text:
1193  * @self: a #HildonBanner widget
1194  * @text: a new text to display in banner
1195  *
1196  * Sets the text that is displayed in the banner.
1197  *
1198  */
1199 void 
1200 hildon_banner_set_text                          (HildonBanner *self, 
1201                                                  const gchar *text)
1202 {
1203     GtkLabel *label;
1204     HildonBannerPrivate *priv;
1205     const gchar *existing_text;
1206
1207     g_return_if_fail (HILDON_IS_BANNER (self));
1208
1209     priv = HILDON_BANNER_GET_PRIVATE (self);
1210     g_assert (priv);
1211
1212     label = GTK_LABEL (priv->label);
1213     existing_text = gtk_label_get_text (label);
1214
1215     if (existing_text != NULL && 
1216         text != NULL          &&
1217         strcmp (existing_text, text) != 0) {
1218             gtk_label_set_text (label, text);
1219             hildon_banner_reset_wrap_state (self);
1220     }
1221
1222     hildon_banner_check_position (GTK_WIDGET (self));
1223 }
1224
1225 /**
1226  * hildon_banner_set_markup:
1227  * @self: a #HildonBanner widget
1228  * @markup: a new text with Pango markup to display in the banner
1229  *
1230  * Sets the text with markup that is displayed in the banner.
1231  *
1232  */
1233 void 
1234 hildon_banner_set_markup                        (HildonBanner *self, 
1235                                                  const gchar *markup)
1236 {
1237     GtkLabel *label;
1238     HildonBannerPrivate *priv;
1239
1240     g_return_if_fail (HILDON_IS_BANNER (self));
1241
1242     priv = HILDON_BANNER_GET_PRIVATE (self);
1243     g_assert (priv);
1244
1245     label = GTK_LABEL (priv->label);
1246     gtk_label_set_markup (label, markup);
1247
1248     hildon_banner_reset_wrap_state (self);
1249
1250     hildon_banner_check_position (GTK_WIDGET(self));
1251 }
1252
1253 /**
1254  * hildon_banner_set_fraction:
1255  * @self: a #HildonBanner widget
1256  * @fraction: #gdouble
1257  *
1258  * The fraction is the completion of progressbar, 
1259  * the scale is from 0.0 to 1.0.
1260  * Sets the amount of fraction the progressbar has.
1261  *
1262  * Note that this method only has effect if @self was created with
1263  * hildon_banner_show_progress()
1264  *
1265  * Deprecated: This function does nothing. As of Hildon 2.2, hildon
1266  * banners don't have progress bars.
1267  */
1268 void 
1269 hildon_banner_set_fraction                      (HildonBanner *self, 
1270                                                  gdouble fraction)
1271 {
1272 }
1273
1274 /**
1275  * hildon_banner_set_timeout:
1276  * @self: a #HildonBanner widget
1277  * @timeout: timeout to set in miliseconds.
1278  *
1279  * Sets the timeout on the banner. After the given amount of miliseconds
1280  * has elapsed the banner will go away. Note that settings this only makes
1281  * sense on the banners that are timed and that have not been yet displayed
1282  * on the screen.
1283  *
1284  * Note that this method only has effect if @self is an information
1285  * banner (created using hildon_banner_show_information() and
1286  * friends).
1287  */
1288 void
1289 hildon_banner_set_timeout                       (HildonBanner *self,
1290                                                  guint timeout)
1291 {
1292     HildonBannerPrivate *priv;
1293
1294     g_return_if_fail (HILDON_IS_BANNER (self));
1295     priv = HILDON_BANNER_GET_PRIVATE (self);
1296     g_assert (priv);
1297
1298     priv->timeout = timeout;
1299 }
1300
1301 /**
1302  * hildon_banner_set_icon:
1303  * @self: a #HildonBanner widget
1304  * @icon_name: the name of icon to use. Can be %NULL for default icon
1305  *
1306  * Sets the icon to be used in the banner.
1307  *
1308  * Deprecated: This function does nothing. As of hildon 2.2, hildon
1309  * banners don't allow changing their icons.
1310  */
1311 void 
1312 hildon_banner_set_icon                          (HildonBanner *self, 
1313                                                  const gchar  *icon_name)
1314 {
1315 }
1316
1317 /**
1318  * hildon_banner_set_icon_from_file:
1319  * @self: a #HildonBanner widget
1320  * @icon_file: the filename of icon to use. Can be %NULL for default icon
1321  *
1322  * Sets the icon from its filename to be used in the banner.
1323  *
1324  * Deprecated: This function does nothing. As of hildon 2.2, hildon
1325  * banners don't allow changing their icons.
1326  */
1327 void 
1328 hildon_banner_set_icon_from_file                (HildonBanner *self, 
1329                                                  const gchar  *icon_file)
1330 {
1331 }