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