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