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