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