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