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