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