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