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     if (gtk_widget_get_direction (widget) == GTK_TEXT_DIR_RTL)
637         x = HILDON_BANNER_WINDOW_X;
638     else
639         x = gdk_screen_width() - HILDON_BANNER_WINDOW_X - req.width;
640
641     y = check_fullscreen_state (get_current_app_window ()) ? 
642         HILDON_BANNER_WINDOW_FULLSCREEN_Y : HILDON_BANNER_WINDOW_Y;
643
644     gtk_window_move (GTK_WINDOW (widget), x, y);
645 }
646
647 static void
648 hildon_banner_realize                           (GtkWidget *widget)
649 {
650     HildonBannerPrivate *priv = HILDON_BANNER_GET_PRIVATE (widget);
651     g_assert (priv);
652
653     /* We let the parent to init widget->window before we need it */
654     if (GTK_WIDGET_CLASS (hildon_banner_parent_class)->realize)
655         GTK_WIDGET_CLASS (hildon_banner_parent_class)->realize (widget);
656
657     /* We use special hint to turn the banner into information notification. */
658     gdk_window_set_type_hint (widget->window, GDK_WINDOW_TYPE_HINT_NOTIFICATION);
659     gtk_window_set_transient_for (GTK_WINDOW (widget), (GtkWindow *) priv->parent);
660
661     hildon_banner_check_position (widget);
662 }
663
664 static void 
665 hildon_banner_class_init                        (HildonBannerClass *klass)
666 {
667     GObjectClass *object_class;
668     GtkWidgetClass *widget_class;
669
670     object_class = G_OBJECT_CLASS (klass);
671     widget_class = GTK_WIDGET_CLASS (klass);
672
673     /* Append private structure to class. This is more elegant than
674        on g_new based approach */
675     g_type_class_add_private (klass, sizeof (HildonBannerPrivate));
676
677     /* Override virtual methods */
678     object_class->constructor = hildon_banner_constructor;
679     object_class->finalize = hildon_banner_finalize;
680     object_class->set_property = hildon_banner_set_property;
681     object_class->get_property = hildon_banner_get_property;
682     GTK_OBJECT_CLASS (klass)->destroy = hildon_banner_destroy;
683     widget_class->map_event = hildon_banner_map_event;
684     widget_class->realize = hildon_banner_realize;
685 #if defined(MAEMO_GTK)
686     widget_class->client_event = hildon_banner_client_event;
687 #endif
688
689     /* Install properties.
690        We need construct properties for singleton purposes */
691
692     /**
693      * HildonBanner:parent-window:
694      *
695      * The window for which the banner will be singleton. 
696      *                      
697      */
698     g_object_class_install_property (object_class, PROP_PARENT_WINDOW,
699             g_param_spec_object ("parent-window",
700                 "Parent window",
701                 "The window for which the banner will be singleton",
702                 GTK_TYPE_WINDOW, G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
703
704     /**
705      * HildonBanner:is-timed:
706      *
707      * Whether the banner is timed and goes away automatically.
708      *                      
709      */
710     g_object_class_install_property (object_class, PROP_IS_TIMED,
711             g_param_spec_boolean ("is-timed",
712                 "Is timed",
713                 "Whether or not the notification goes away automatically "
714                 "after the specified time has passed",
715                 FALSE, G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
716
717     /**
718      * HildonBanner:timeout:
719      *
720      * The time before making the banner banner go away. This needs 
721      * to be adjusted before the banner is mapped to the screen.
722      *                      
723      */
724     g_object_class_install_property (object_class, PROP_TIMEOUT,
725             g_param_spec_uint ("timeout",
726                 "Timeout",
727                 "The time before making the banner banner go away",
728                 0,
729                 10000,
730                 HILDON_BANNER_DEFAULT_TIMEOUT,
731                 G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
732 }
733
734 static void 
735 hildon_banner_init                              (HildonBanner *self)
736 {
737     HildonBannerPrivate *priv = HILDON_BANNER_GET_PRIVATE (self);
738     g_assert (priv);
739
740     priv->parent = NULL;
741
742     /* Initialize the common layout inside banner */
743     priv->layout = gtk_hbox_new (FALSE, HILDON_MARGIN_DEFAULT);
744
745     priv->label = g_object_new (GTK_TYPE_LABEL, NULL);
746     gtk_label_set_line_wrap (GTK_LABEL (priv->label), TRUE);
747
748     gtk_container_set_border_width (GTK_CONTAINER (priv->layout), HILDON_MARGIN_DEFAULT);
749     gtk_container_add (GTK_CONTAINER (self), priv->layout);
750     gtk_box_pack_start (GTK_BOX (priv->layout), priv->label, TRUE, TRUE, 0);
751
752     gtk_window_set_accept_focus (GTK_WINDOW (self), FALSE);
753
754 #if defined(MAEMO_GTK)
755     gtk_window_set_is_temporary (GTK_WINDOW (self), TRUE);
756 #endif
757 }
758
759 /* Makes sure that icon/progress item contains the desired type
760    of item. If possible, tries to avoid creating a new widget but
761    reuses the existing one */
762 static void
763 hildon_banner_ensure_child                      (HildonBanner *self, 
764                                                  GtkWidget *user_widget,
765                                                  guint pos,
766                                                  GType type,
767                                                  const gchar *first_property, 
768                                                  ...)
769 {
770     GtkWidget *widget;
771     va_list args;
772     HildonBannerPrivate *priv = HILDON_BANNER_GET_PRIVATE (self);
773
774     g_assert (priv);
775
776     widget = priv->main_item;
777     va_start (args, first_property);
778
779     /* Reuse existing widget if possible */
780     if (! user_widget && G_TYPE_CHECK_INSTANCE_TYPE (widget, type))
781     {
782         g_object_set_valist (G_OBJECT (widget), first_property, args);
783     }
784     else
785     {
786         /* We have to abandon old content widget */
787         if (widget)
788             gtk_container_remove (GTK_CONTAINER (priv->layout), widget);
789         
790         /* Use user provided widget or create a new one */
791         priv->main_item = widget = user_widget ? 
792             user_widget : GTK_WIDGET (g_object_new_valist(type, first_property, args));
793         gtk_box_pack_start (GTK_BOX (priv->layout), widget, TRUE, TRUE, 0);
794     }
795
796     /* We make sure that the widget exists in desired position. Different
797        banners place this child widget to different places */
798     gtk_box_reorder_child (GTK_BOX (priv->layout), widget, pos);
799     va_end (args);
800 }
801
802 /* Creates a new banner instance or uses an existing one */
803 static HildonBanner*
804 hildon_banner_get_instance_for_widget           (GtkWidget *widget, 
805                                                  gboolean timed)
806 {
807     GtkWidget *window;
808
809     window = widget ? gtk_widget_get_ancestor (widget, GTK_TYPE_WINDOW) : NULL;
810     return g_object_new (HILDON_TYPE_BANNER, "parent-window", window, "is-timed", timed, NULL);
811 }
812
813 /**
814  * hildon_banner_show_information:
815  * @widget: the #GtkWidget that is the owner of the banner
816  * @icon_name: the name of icon to use. Can be %NULL for default icon
817  * @text: Text to display
818  *
819  * This function creates and displays an information banner that
820  * automatically goes away after certain time period. For each window
821  * in your application there can only be one timed banner, so if you
822  * spawn a new banner before the earlier one has timed out, the
823  * previous one will be replaced.
824  *
825  * Returns: The newly created banner
826  *
827  */
828 GtkWidget*
829 hildon_banner_show_information                  (GtkWidget *widget, 
830                                                  const gchar *icon_name,
831                                                  const gchar *text)
832 {
833     HildonBanner *banner;
834
835     g_return_val_if_fail (icon_name == NULL || icon_name[0] != 0, NULL);
836     g_return_val_if_fail (text != NULL, NULL);
837
838     /* Prepare banner */
839     banner = hildon_banner_get_instance_for_widget (widget, TRUE);
840     hildon_banner_ensure_child (banner, NULL, 0, GTK_TYPE_IMAGE, 
841             "pixel-size", HILDON_ICON_PIXEL_SIZE_NOTE, 
842             "icon-name", icon_name ? icon_name : HILDON_BANNER_DEFAULT_ICON,
843             "yalign", 0.0, 
844             NULL);
845
846     hildon_banner_set_text (banner, text);
847     hildon_banner_bind_label_style (banner, NULL);
848
849     /* Show the banner, since caller cannot do that */
850     gtk_widget_show_all (GTK_WIDGET (banner));
851     gtk_window_present (GTK_WINDOW (banner));
852
853     return (GtkWidget *) banner;
854 }
855
856 /**
857  * hildon_banner_show_informationf:
858  * @widget: the #GtkWidget that is the owner of the banner
859  * @icon_name: the name of icon to use. Can be %NULL for default icon
860  * @format: a printf-like format string
861  * @Varargs: arguments for the format string
862  *
863  * A helper function for #hildon_banner_show_information with
864  * string formatting.
865  *
866  * Returns: the newly created banner
867  */
868 GtkWidget* 
869 hildon_banner_show_informationf                 (GtkWidget *widget, 
870                                                  const gchar *icon_name,
871                                                  const gchar *format, 
872                                                  ...)
873 {
874     g_return_val_if_fail (format != NULL, NULL);
875
876     gchar *message;
877     va_list args;
878     GtkWidget *banner;
879
880     va_start (args, format);
881     message = g_strdup_vprintf (format, args);
882     va_end (args);
883
884     banner = hildon_banner_show_information (widget, icon_name, message);
885
886     g_free (message);
887
888     return banner;
889 }
890
891 /**
892  * hildon_banner_show_information_with_markup:
893  * @widget: the #GtkWidget that wants to display banner
894  * @icon_name: the name of icon to use. Can be %NULL for default icon.
895  * @markup: a markup string to display (see <link linkend="PangoMarkupFormat">Pango markup format</link>)
896  *
897  * This function creates and displays an information banner that
898  * automatically goes away after certain time period. For each window
899  * in your application there can only be one timed banner, so if you
900  * spawn a new banner before the earlier one has timed out, the
901  * previous one will be replaced.
902  *
903  * Returns: the newly created banner
904  *
905  */
906 GtkWidget*
907 hildon_banner_show_information_with_markup      (GtkWidget *widget, 
908                                                  const gchar *icon_name, 
909                                                  const gchar *markup)
910 {
911     HildonBanner *banner;
912
913     g_return_val_if_fail (icon_name == NULL || icon_name[0] != 0, NULL);
914     g_return_val_if_fail (markup != NULL, NULL);
915
916     /* Prepare banner */
917     banner = hildon_banner_get_instance_for_widget (widget, TRUE);
918
919     hildon_banner_ensure_child (banner, NULL, 0, GTK_TYPE_IMAGE, 
920             "pixel-size", HILDON_ICON_PIXEL_SIZE_NOTE, 
921             "icon-name", icon_name ? icon_name : HILDON_BANNER_DEFAULT_ICON,
922             "yalign", 0.0, 
923             NULL);
924
925     hildon_banner_set_markup (banner, markup);
926     hildon_banner_bind_label_style (banner, NULL);
927
928     /* Show the banner, since caller cannot do that */
929     gtk_widget_show_all (GTK_WIDGET (banner));
930     gtk_window_present (GTK_WINDOW (banner));
931
932     return (GtkWidget *) banner;
933 }
934
935 /**
936  * hildon_banner_show_animation:
937  * @widget: the #GtkWidget that wants to display banner
938  * @animation_name: The progress animation to use. You usually can just
939  *                  pass %NULL for the default animation.
940  * @text: the text to display.
941  *
942  * Shows an animated progress notification. It's recommended not to try
943  * to show more than one progress notification at a time, since
944  * they will appear on top of each other. You can use progress
945  * notifications with timed banners. In this case the banners are
946  * located so that you can somehow see both.
947  *
948  * Please note that banners are destroyed automatically once the
949  * window they are attached to is closed. The pointer that you
950  * receive with this function do not contain additional references,
951  * so it can become invalid without warning (this is true for
952  * all toplevel windows in gtk). To make sure that the banner do not disapear
953  * automatically, you can separately ref the return value (this
954  * doesn't prevent the banner from disappearing, but the object it just
955  * not finalized). In this case you have to call both #gtk_widget_destroy 
956  * followed by #g_object_unref (in this order).
957  * 
958  * Returns: a #HildonBanner widget. You must call #gtk_widget_destroy
959  *          once you are done with the banner.
960  *
961  */
962 GtkWidget*
963 hildon_banner_show_animation                    (GtkWidget *widget, 
964                                                  const gchar *animation_name, 
965                                                  const gchar *text)
966 {
967     HildonBanner *banner;
968     GtkIconTheme *theme; 
969     GtkIconInfo *info;
970     GtkWidget *image_widget;
971     const gchar *filename;
972
973     g_return_val_if_fail (animation_name == NULL || animation_name[0] != 0, NULL);
974     g_return_val_if_fail (text != NULL, NULL);
975
976     /* Find out which animation to use */
977     theme = gtk_icon_theme_get_default ();
978     info = gtk_icon_theme_lookup_icon (theme, animation_name ?   /* FIXME: consider using: gtk_icon_theme_load_icon() */
979             animation_name : HILDON_BANNER_DEFAULT_PROGRESS_ANIMATION,
980             HILDON_ICON_SIZE_NOTE, 0);
981
982     /* Try to load animation. One could try to optimize this 
983        to avoid loading the default animation during each call */
984     if (info) {
985         filename = gtk_icon_info_get_filename (info);
986         image_widget = gtk_image_new_from_file (filename);
987         gtk_icon_info_free (info);
988     } else {
989         g_warning ("Icon theme lookup for icon failed!");
990         image_widget = NULL;
991     }
992
993     /* Prepare banner */
994     banner = hildon_banner_get_instance_for_widget (widget, FALSE);
995     hildon_banner_ensure_child (banner, image_widget, 0,
996             GTK_TYPE_IMAGE, "yalign", 0.0, NULL);
997
998     hildon_banner_set_text (banner, text);
999     hildon_banner_bind_label_style (banner, NULL);
1000
1001     /* And show it */
1002     gtk_widget_show_all (GTK_WIDGET (banner));
1003     gtk_window_present (GTK_WINDOW (banner));
1004
1005     return (GtkWidget *) banner;
1006 }
1007
1008 /**
1009  * hildon_banner_show_progress:
1010  * @widget: the #GtkWidget that wants to display banner
1011  * @bar: Progressbar to use. You usually can just pass %NULL, unless
1012  *       you want somehow customized progress bar.
1013  * @text: text to display.
1014  *
1015  * Shows progress notification. See #hildon_banner_show_animation
1016  * for more information.
1017  * 
1018  * Returns: a #HildonBanner widget. You must call #gtk_widget_destroy
1019  *          once you are done with the banner.
1020  *
1021  */
1022 GtkWidget*
1023 hildon_banner_show_progress                     (GtkWidget *widget, 
1024                                                  GtkProgressBar *bar, 
1025                                                  const gchar *text)
1026 {
1027     HildonBanner *banner;
1028     HildonBannerPrivate *priv;
1029
1030     g_return_val_if_fail (bar == NULL || GTK_IS_PROGRESS_BAR (bar), NULL);
1031     g_return_val_if_fail (text != NULL, NULL);
1032
1033
1034     /* Prepare banner */
1035     banner = hildon_banner_get_instance_for_widget (widget, FALSE);
1036     priv = HILDON_BANNER_GET_PRIVATE (banner);
1037     g_assert (priv);
1038     hildon_banner_ensure_child (banner, (GtkWidget *) bar, -1, GTK_TYPE_PROGRESS_BAR, NULL);
1039
1040     gtk_widget_set_size_request (priv->main_item,
1041             HILDON_BANNER_PROGRESS_WIDTH, -1);
1042
1043     hildon_banner_set_text (banner, text);
1044     hildon_banner_bind_label_style (banner, NULL);
1045
1046     /* Show the banner */
1047     gtk_widget_show_all (GTK_WIDGET (banner));
1048     gtk_window_present (GTK_WINDOW (banner));
1049
1050     return GTK_WIDGET (banner);   
1051 }
1052
1053 /**
1054  * hildon_banner_set_text:
1055  * @self: a #HildonBanner widget
1056  * @text: a new text to display in banner
1057  *
1058  * Sets the text that is displayed in the banner.
1059  *
1060  */
1061 void 
1062 hildon_banner_set_text                          (HildonBanner *self, 
1063                                                  const gchar *text)
1064 {
1065     GtkLabel *label;
1066     HildonBannerPrivate *priv;
1067     const gchar *existing_text;
1068
1069     g_return_if_fail (HILDON_IS_BANNER (self));
1070
1071     priv = HILDON_BANNER_GET_PRIVATE (self);
1072     g_assert (priv);
1073
1074     label = GTK_LABEL (priv->label);
1075     existing_text = gtk_label_get_text (label);
1076
1077     if (existing_text != NULL && 
1078         text != NULL          &&
1079         strcmp (existing_text, text) != 0)
1080             gtk_label_set_text (label, text);
1081
1082     hildon_banner_check_position (GTK_WIDGET (self));
1083 }
1084
1085 /**
1086  * hildon_banner_set_markup:
1087  * @self: a #HildonBanner widget
1088  * @markup: a new text with Pango markup to display in the banner
1089  *
1090  * Sets the text with markup that is displayed in the banner.
1091  *
1092  */
1093 void 
1094 hildon_banner_set_markup                        (HildonBanner *self, 
1095                                                  const gchar *markup)
1096 {
1097     GtkLabel *label;
1098     HildonBannerPrivate *priv;
1099
1100     g_return_if_fail (HILDON_IS_BANNER (self));
1101
1102     priv = HILDON_BANNER_GET_PRIVATE (self);
1103     g_assert (priv);
1104
1105     label = GTK_LABEL (priv->label);
1106     gtk_label_set_markup (label, markup);
1107
1108     hildon_banner_check_position (GTK_WIDGET(self));
1109 }
1110
1111 /**
1112  * hildon_banner_set_fraction:
1113  * @self: a #HildonBanner widget
1114  * @fraction: #gdouble
1115  *
1116  * The fraction is the completion of progressbar, 
1117  * the scale is from 0.0 to 1.0.
1118  * Sets the amount of fraction the progressbar has.
1119  *
1120  */
1121 void 
1122 hildon_banner_set_fraction                      (HildonBanner *self, 
1123                                                  gdouble fraction)
1124 {
1125     HildonBannerPrivate *priv;
1126
1127     g_return_if_fail (HILDON_IS_BANNER (self));
1128     priv = HILDON_BANNER_GET_PRIVATE (self);
1129     g_assert (priv);
1130
1131     g_return_if_fail (GTK_IS_PROGRESS_BAR (priv->main_item));
1132     gtk_progress_bar_set_fraction (GTK_PROGRESS_BAR (priv->main_item), fraction);
1133 }
1134
1135 /**
1136  * hildon_banner_set_timeout:
1137  * @self: a #HildonBanner widget
1138  * @timeout: timeout to set in miliseconds.
1139  *
1140  * Sets the timeout on the banner. After the given amount of miliseconds
1141  * has elapsed the banner will go away. Note that settings this only makes
1142  * sense on the banners that are timed and that have not been yet displayed
1143  * on the screen.
1144  *
1145  */
1146 void
1147 hildon_banner_set_timeout                       (HildonBanner *self,
1148                                                  guint timeout)
1149 {
1150     HildonBannerPrivate *priv;
1151
1152     g_return_if_fail (HILDON_IS_BANNER (self));
1153     priv = HILDON_BANNER_GET_PRIVATE (self);
1154     g_assert (priv);
1155
1156     priv->timeout = timeout;
1157 }
1158
1159 /**
1160  * hildon_banner_set_icon:
1161  * @self: a #HildonBanner widget
1162  * @icon_name: the name of icon to use. Can be %NULL for default icon
1163  *
1164  * Sets the icon to be used in the banner.
1165  *
1166  */
1167 void 
1168 hildon_banner_set_icon                          (HildonBanner *self, 
1169                                                  const gchar  *icon_name)
1170 {
1171     HildonBannerPrivate *priv;
1172
1173     g_return_if_fail (HILDON_IS_BANNER (self));
1174     priv = HILDON_BANNER_GET_PRIVATE (self);
1175     g_assert (priv);
1176
1177     hildon_banner_ensure_child (self, NULL, 0, GTK_TYPE_IMAGE, 
1178             "pixel-size", HILDON_ICON_PIXEL_SIZE_NOTE, 
1179             "icon-name", icon_name ? icon_name : HILDON_BANNER_DEFAULT_ICON,
1180             "yalign", 0.0, 
1181             NULL);
1182 }
1183
1184 /**
1185  * hildon_banner_set_icon_from_file:
1186  * @self: a #HildonBanner widget
1187  * @icon_file: the filename of icon to use. Can be %NULL for default icon
1188  *
1189  * Sets the icon from its filename to be used in the banner.
1190  *
1191  */
1192 void 
1193 hildon_banner_set_icon_from_file                (HildonBanner *self, 
1194                                                  const gchar  *icon_file)
1195 {
1196     HildonBannerPrivate *priv;
1197
1198     g_return_if_fail (HILDON_IS_BANNER (self));
1199     priv = HILDON_BANNER_GET_PRIVATE (self);
1200     g_assert (priv);
1201
1202     if (icon_file != NULL) {
1203         hildon_banner_ensure_child (self, NULL, 0, GTK_TYPE_IMAGE, 
1204                 "pixel-size", HILDON_ICON_PIXEL_SIZE_NOTE, 
1205                 "file", icon_file,
1206                 "yalign", 0.0, 
1207                 NULL);
1208     } else {
1209         hildon_banner_ensure_child (self, NULL, 0, GTK_TYPE_IMAGE, 
1210                 "pixel-size", HILDON_ICON_PIXEL_SIZE_NOTE, 
1211                 "icon-name", HILDON_BANNER_DEFAULT_ICON,
1212                 "yalign", 0.0, 
1213                 NULL);
1214     }
1215 }