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