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