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