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