thread safety
[hildon] / hildon-widgets / hildon-banner.c
1 /*
2  * This file is part of hildon-libs
3  *
4  * Copyright (C) 2005 Nokia Corporation.
5  *
6  * Contact: Luc Pionchon <luc.pionchon@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; either 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 #include <gtk/gtkhbox.h>
26 #include <gtk/gtkimage.h>
27 #include <gtk/gtkicontheme.h>
28 #include <string.h>
29 #include <X11/X.h>
30 #include <X11/Xatom.h>
31
32 #include "hildon-defines.h"
33 #include "hildon-banner.h"
34
35 #define HILDON_BANNER_WINDOW_X            30
36 #define HILDON_BANNER_WINDOW_Y            73
37 #define HILDON_BANNER_WINDOW_FULLSCREEN_Y 20
38 #define HILDON_BANNER_PROGRESS_WIDTH      104
39 #define HILDON_BANNER_LABEL_MAX_TIMED     375
40 #define HILDON_BANNER_LABEL_MAX_PROGRESS  265
41 #define HILDON_BANNER_TIMEOUT             3000
42
43 #define HILDON_BANNER_DEFAULT_ICON               "qgn_note_infoprint"
44 #define HILDON_BANNER_DEFAULT_PROGRESS_ANIMATION "qgn_indi_pball_a"
45
46 enum {
47    PROP_PARENT_WINDOW = 1,
48    PROP_IS_TIMED
49 };
50
51 struct _HildonBannerPrivate
52 {
53    GtkWidget *main_item, *label, *layout;
54    guint timeout_id;
55    gboolean is_timed; 
56 };
57
58 static GtkWidget *global_timed_banner = NULL;
59
60 G_DEFINE_TYPE(HildonBanner, hildon_banner, GTK_TYPE_WINDOW)
61
62 /* copy/paste from old infoprint implementation: Use matchbox 
63    properties to find the topmost application window */
64 static Window get_current_app_window(void)
65 {
66     unsigned long n;
67     unsigned long extra;
68     int format;
69     int status;
70
71     Atom atom_current_app_window = gdk_x11_get_xatom_by_name ("_MB_CURRENT_APP_WINDOW");
72     Atom realType;
73     Window win_result = None;
74     guchar *data_return = NULL;
75
76     status = XGetWindowProperty(GDK_DISPLAY(), GDK_ROOT_WINDOW(), 
77                                 atom_current_app_window, 0L, 16L,
78                                 0, XA_WINDOW, &realType, &format,
79                                 &n, &extra, 
80                                 &data_return);
81
82     if ( status == Success && realType == XA_WINDOW 
83          && format == 32 && n == 1 && data_return != NULL )
84     {
85         win_result = ((Window*) data_return)[0];
86     } 
87         
88     if ( data_return ) 
89         XFree(data_return);    
90     
91     return win_result;
92 }
93
94 /* Checks if a window is in fullscreen state or not. This
95    information is needed when banners are positioned on screen.
96    copy/paste from old infoprint implementation.  */
97 static gboolean check_fullscreen_state( Window window )
98 {
99     unsigned long n;
100     unsigned long extra;
101     int format, status, i; 
102     guchar *data_return = NULL;
103     
104     Atom realType;
105     Atom atom_window_state = gdk_x11_get_xatom_by_name ("_NET_WM_STATE");
106     Atom atom_fullscreen   = gdk_x11_get_xatom_by_name ("_NET_WM_STATE_FULLSCREEN");
107     
108     if ( window == None )
109         return FALSE;
110
111     /* in some cases XGetWindowProperty seems to generate BadWindow,
112        so at the moment this function does not always work perfectly */
113     gdk_error_trap_push();
114     status = XGetWindowProperty(GDK_DISPLAY(), window,
115                                 atom_window_state, 0L, 1000000L,
116                                 0, XA_ATOM, &realType, &format,
117                                 &n, &extra, &data_return);
118     gdk_flush();
119     if (gdk_error_trap_pop())
120         return FALSE;
121
122     if (status == Success && realType == XA_ATOM && format == 32 && n > 0)
123     {
124         for(i=0; i < n; i++)
125             if ( ((Atom*)data_return)[i] && 
126                  ((Atom*)data_return)[i] == atom_fullscreen)
127             {
128                 if (data_return) XFree(data_return);
129                 return TRUE;
130             }
131     }
132
133     if (data_return) 
134         XFree(data_return);
135
136     return FALSE;
137 }
138
139 static GQuark hildon_banner_timed_quark(void)
140 {
141     static GQuark quark = 0;
142
143     if (G_UNLIKELY(quark == 0))
144         quark = g_quark_from_static_string("hildon-banner-timed");
145
146     return quark;
147 }
148
149 /* Set the label name to make the correct rc-style attached into it */
150 static void hildon_banner_bind_label_style(HildonBanner *self, 
151    const gchar *name)
152 {
153    GtkWidget *label = self->priv->label;
154
155    /* Too bad that we cannot really reset the widget name */
156    gtk_widget_set_name(label, name ? name : g_type_name(GTK_WIDGET_TYPE(label)));
157 }
158
159 /* In timeout function we automatically destroy timed banners */
160 static gboolean hildon_banner_timeout(gpointer data)
161 {
162    GtkWidget *widget;
163    GdkEvent *event;
164    gboolean continue_timeout = FALSE;
165
166    GDK_THREADS_ENTER ();
167    
168    g_assert(HILDON_IS_BANNER(data));
169
170    widget = GTK_WIDGET(data);
171    g_object_ref(widget);
172
173    /* If the banner is currently visible (it normally should), 
174       we simulate clicking the close button of the window.
175       This allows applications to reuse the banner by prevent
176       closing it etc */
177    if (GTK_WIDGET_DRAWABLE(widget))
178    {
179       event = gdk_event_new (GDK_DELETE);
180       event->any.window = g_object_ref(widget->window);
181       event->any.send_event = FALSE;
182       continue_timeout = gtk_widget_event (widget, event);
183       gdk_event_free(event);
184    }
185    
186    if (!continue_timeout)
187       gtk_widget_destroy (widget);
188
189    g_object_unref(widget);
190
191    GDK_THREADS_LEAVE ();
192    
193    return continue_timeout;
194 }
195
196 static gboolean hildon_banner_clear_timeout(HildonBanner *self)
197 {
198    g_assert(HILDON_IS_BANNER(self));
199
200    if (self->priv->timeout_id != 0) {
201       g_source_remove(self->priv->timeout_id);
202       self->priv->timeout_id = 0;
203       return TRUE;
204    }
205
206    return FALSE;
207 }
208
209 static void hildon_banner_ensure_timeout(HildonBanner *self)
210 {
211    g_assert(HILDON_IS_BANNER(self));
212
213    if (self->priv->timeout_id == 0 && self->priv->is_timed)
214       self->priv->timeout_id = g_timeout_add(HILDON_BANNER_TIMEOUT, 
215          hildon_banner_timeout, self);
216 }
217
218 static void hildon_banner_set_property(GObject      *object,
219                                        guint         prop_id,
220                                        const GValue *value,
221                                        GParamSpec   *pspec)
222 {
223    GtkWidget *window;
224    GdkGeometry geom;
225    HildonBannerPrivate *priv = HILDON_BANNER(object)->priv;
226
227    switch (prop_id) {
228       case PROP_IS_TIMED:
229          priv->is_timed = g_value_get_boolean(value);
230
231          /* Timed and progress notifications have different
232             pixel size values for text */
233          geom.max_width = priv->is_timed ? 
234             HILDON_BANNER_LABEL_MAX_TIMED : HILDON_BANNER_LABEL_MAX_PROGRESS;
235
236          geom.max_height = -1;
237          gtk_window_set_geometry_hints(GTK_WINDOW(object), 
238                                        priv->label, &geom, GDK_HINT_MAX_SIZE);
239          break;
240       case PROP_PARENT_WINDOW:
241          window = g_value_get_object(value);         
242
243          gtk_window_set_transient_for(GTK_WINDOW(object), (GtkWindow *) window);
244
245          if (window)
246             gtk_window_set_destroy_with_parent(GTK_WINDOW(object), TRUE);
247
248          break;
249       default:
250          G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
251          break;
252   }
253 }
254
255 static void hildon_banner_get_property(GObject *object,
256    guint prop_id, GValue *value, GParamSpec *pspec)
257 {
258    HildonBanner *self = HILDON_BANNER(object);
259
260    switch (prop_id)
261    {
262       case PROP_IS_TIMED:
263          g_value_set_boolean(value, self->priv->is_timed);
264          break;
265       case PROP_PARENT_WINDOW:
266          g_value_set_object(value, gtk_window_get_transient_for(GTK_WINDOW(object)));
267          break;
268       default:
269          G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
270          break;
271   }
272 }
273
274 static void hildon_banner_destroy(GtkObject *object)
275 {
276    HildonBanner *self;
277    GObject *parent_window;
278
279    g_assert(HILDON_IS_BANNER(object));
280    self = HILDON_BANNER(object);
281
282    /* Drop possible global pointer. That can hold reference to us */
283    if ((gpointer) object == (gpointer) global_timed_banner) {
284       global_timed_banner = NULL;
285       g_object_unref(object);
286    }
287
288    /* Remove the data from parent window for timed banners. Those hold reference */
289    if (self->priv->is_timed && 
290       (parent_window = (GObject *) gtk_window_get_transient_for(
291              GTK_WINDOW(object))) != NULL)
292       g_object_set_qdata(parent_window, hildon_banner_timed_quark(), NULL);
293
294    (void) hildon_banner_clear_timeout(self);
295
296    if (GTK_OBJECT_CLASS(hildon_banner_parent_class)->destroy)
297       GTK_OBJECT_CLASS(hildon_banner_parent_class)->destroy(object);
298 }
299
300 /* Search a previous banner instance */
301 static GObject *hildon_banner_real_get_instance(GObject *window, gboolean timed)
302 {
303    g_assert(window == NULL || GTK_IS_WINDOW(window));
304
305    if (timed) {
306       /* If we have a parent window, the previous instance is stored there */
307       if (window) 
308          return g_object_get_qdata(window, hildon_banner_timed_quark());
309
310       /* System notification instance is strored into global pointer */
311       return (GObject *) global_timed_banner;
312    }
313
314    /* Non-timed banners are normal (non-singleton) objects */
315    return NULL;
316 }
317
318 /* By overriding constructor we force timed banners to be
319    singletons for each window */
320 static GObject* hildon_banner_constructor (GType type,
321    guint n_construct_params, GObjectConstructParam *construct_params)
322 {
323    GObject *banner, *window = NULL;
324    gboolean timed = FALSE;
325    guint i;
326
327    /* Search banner type information from parameters in order
328       to locate the possible previous banner instance. */
329    for (i = 0; i < n_construct_params; i++)
330    {
331       if (strcmp(construct_params[i].pspec->name, "parent-window") == 0)
332          window = g_value_get_object(construct_params[i].value);       
333       else if (strcmp(construct_params[i].pspec->name, "is-timed") == 0)
334          timed = g_value_get_boolean(construct_params[i].value);
335    }
336
337    /* Try to get a previous instance if such exists */
338    banner = hildon_banner_real_get_instance(window, timed);
339    if (!banner)
340    {
341       /* We have to create a new banner */
342       banner = G_OBJECT_CLASS(hildon_banner_parent_class)->constructor(
343                   type, n_construct_params, construct_params);
344
345       /* Store the newly created singleton instance either into parent 
346          window data or into global variables. */
347       if (timed) {
348          if (window) {
349             g_object_set_qdata_full(G_OBJECT(window), hildon_banner_timed_quark(), 
350                g_object_ref(banner), g_object_unref); 
351          } else {
352             g_assert(global_timed_banner == NULL);
353             global_timed_banner = g_object_ref(banner);
354          }
355       }
356    }
357    else {
358       /* FIXME: This is a hack! We have to manually freeze
359          notifications. This is normally done by g_object_init, but we
360          are not going to call that. g_object_newv will otherwise give
361          a critical like this:
362
363             GLIB CRITICAL ** GLib-GObject - g_object_notify_queue_thaw: 
364             assertion `nqueue->freeze_count > 0' failed */
365
366       g_object_freeze_notify(banner);
367    }
368
369    /* We restart possible timeouts for each new timed banner request */
370    if (timed && hildon_banner_clear_timeout(HILDON_BANNER(banner)))
371       hildon_banner_ensure_timeout(HILDON_BANNER(banner));
372
373    return banner;
374 }
375
376 /* We start the timer for timed notifications after the window appears on screen */
377 static gboolean hildon_banner_map_event(GtkWidget *widget, GdkEventAny *event)
378 {
379    gboolean result = FALSE;
380
381    if (GTK_WIDGET_CLASS(hildon_banner_parent_class)->map_event)
382       result = GTK_WIDGET_CLASS(hildon_banner_parent_class)->map_event(widget, event);
383
384    hildon_banner_ensure_timeout(HILDON_BANNER(widget));
385
386    return result;
387 }  
388
389 static void hildon_banner_check_position(GtkWidget *widget)
390 {
391    gint x, y;
392    GtkRequisition req;
393
394    gtk_widget_size_request(widget, &req);
395    if (req.width == 0)
396    {
397      return;
398    }
399
400    x = gdk_screen_width() - HILDON_BANNER_WINDOW_X - req.width;
401    y = check_fullscreen_state(get_current_app_window()) ? 
402          HILDON_BANNER_WINDOW_FULLSCREEN_Y : HILDON_BANNER_WINDOW_Y;
403
404    gtk_window_move(GTK_WINDOW(widget), x, y);
405 }
406
407 static void hildon_banner_realize(GtkWidget *widget)
408 {
409    /* We let the parent to init widget->window before we need it */
410    if (GTK_WIDGET_CLASS(hildon_banner_parent_class)->realize)
411       GTK_WIDGET_CLASS(hildon_banner_parent_class)->realize(widget);
412
413    /* We use special hint to turn the banner into information notification. */
414    gdk_window_set_type_hint(widget->window, GDK_WINDOW_TYPE_HINT_MESSAGE);
415
416    hildon_banner_check_position(widget);
417 }
418
419 static void hildon_banner_class_init(HildonBannerClass *klass)
420 {
421    GObjectClass *object_class;
422    GtkWidgetClass *widget_class;
423
424    object_class = G_OBJECT_CLASS(klass);
425    widget_class = GTK_WIDGET_CLASS(klass);
426
427    /* Append private structure to class. This is more elegant than
428       on g_new based approach */
429    g_type_class_add_private(klass, sizeof(HildonBannerPrivate));
430
431    /* Override virtual methods */
432    object_class->constructor = hildon_banner_constructor;
433    object_class->set_property = hildon_banner_set_property;
434    object_class->get_property = hildon_banner_get_property;
435    GTK_OBJECT_CLASS(klass)->destroy = hildon_banner_destroy;
436    widget_class->map_event = hildon_banner_map_event;
437    widget_class->realize = hildon_banner_realize;
438
439    /* Install properties.
440       We need construct properties for singleton purposes */
441    g_object_class_install_property(object_class, PROP_PARENT_WINDOW,
442       g_param_spec_object("parent-window",
443                           "Parent window",
444                           "The window for which the banner will be singleton",
445                           GTK_TYPE_WINDOW, G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
446
447    g_object_class_install_property(object_class, PROP_IS_TIMED,
448       g_param_spec_boolean("is-timed",
449                            "Is timed",
450                            "Whether or not the notification goes away automatically "
451                            "after the specified time has passed",
452                            FALSE, G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
453 }
454
455 static void hildon_banner_init(HildonBanner *self)
456 {
457    HildonBannerPrivate *priv;
458
459    /* Make private data available through cached pointer */
460    self->priv = priv = G_TYPE_INSTANCE_GET_PRIVATE(self, 
461                                                    HILDON_TYPE_BANNER,
462                                                    HildonBannerPrivate);
463
464    /* Initialize the common layout inside banner */
465    self->priv->layout = gtk_hbox_new(FALSE, HILDON_MARGIN_DEFAULT);
466    priv->label = g_object_new(GTK_TYPE_LABEL, NULL);
467    gtk_label_set_line_wrap(GTK_LABEL(priv->label), TRUE);
468    gtk_container_set_border_width(GTK_CONTAINER(self->priv->layout), HILDON_MARGIN_DEFAULT);
469    gtk_container_add(GTK_CONTAINER(self), self->priv->layout);
470    gtk_box_pack_start(GTK_BOX(self->priv->layout), priv->label, TRUE, TRUE, 0);
471    gtk_window_set_accept_focus(GTK_WINDOW(self), FALSE);
472 }
473
474 /* Makes sure that icon/progress item contains the desired type
475    of item. If possible, tries to avoid creating a new widget but
476    reuses the existing one */
477 static void hildon_banner_ensure_child(HildonBanner *self, 
478                                        GtkWidget    *user_widget,
479                                        guint         pos,
480                                        GType         type,
481                                        const gchar  *first_property, ...)
482 {
483    GtkWidget *widget;
484    va_list args;
485
486    g_assert(HILDON_IS_BANNER(self));
487    g_assert(user_widget == NULL || GTK_IS_WIDGET(user_widget));
488
489    widget = self->priv->main_item;
490    va_start(args, first_property);
491
492    /* Reuse existing widget if possible */
493    if (!user_widget && G_TYPE_CHECK_INSTANCE_TYPE(widget, type))
494    {
495       g_object_set_valist(G_OBJECT(widget), first_property, args);
496    }
497    else
498    {
499       /* We have to abandon old content widget */
500       if (widget)
501          gtk_container_remove(GTK_CONTAINER(self->priv->layout), widget);
502
503       /* Use user provided widget or create a new one */
504       self->priv->main_item = widget = user_widget ? 
505          user_widget : GTK_WIDGET(g_object_new_valist(type, first_property, args));
506       gtk_box_pack_start(GTK_BOX(self->priv->layout), widget, FALSE, TRUE, 0);
507    }
508
509    /* We make sure that the widget exists in desired position. Different
510       banners place this child widget to different places */
511    gtk_box_reorder_child(GTK_BOX(self->priv->layout), widget, pos);
512    va_end(args);
513 }
514
515 /* Creates a new banner instance or uses an existing one */
516 static HildonBanner *hildon_banner_get_instance_for_widget(GtkWidget *widget, gboolean timed)
517 {
518    GtkWidget *window;
519
520    g_return_val_if_fail(widget == NULL || GTK_IS_WIDGET(widget), NULL);
521    window = widget ? gtk_widget_get_ancestor(widget, GTK_TYPE_WINDOW) : NULL;
522    return g_object_new(HILDON_TYPE_BANNER, "parent-window", window, "is-timed", timed, NULL);
523 }
524
525
526
527
528
529 /* Public functions **************************************************************/
530
531 /**
532  * hildon_banner_show_information:
533  * @widget: the #GtkWidget that wants to display banner
534  * @icon_name: the name of icon to use. Can be %NULL for default icon.
535  * @text: Text to display
536  *
537  * This function creates and displays an information banner that
538  * automatically goes away after certain time period. For each window
539  * in your application there can only be one timed banner, so if you
540  * spawn a new banner before the earlier one has timed out, the
541  * previous one will be replaced.
542  *
543  * Since: 0.12.2
544  */
545 void hildon_banner_show_information(GtkWidget *widget, 
546    const gchar *icon_name, const gchar *text)
547 {
548    HildonBanner *banner;
549
550    g_return_if_fail(widget == NULL || GTK_IS_WIDGET(widget));
551    g_return_if_fail(icon_name == NULL || icon_name[0] != 0);
552    g_return_if_fail(text != NULL);
553
554    /* Prepare banner */
555    banner = hildon_banner_get_instance_for_widget(widget, TRUE);
556    hildon_banner_ensure_child(banner, NULL, 0, GTK_TYPE_IMAGE, 
557       "pixel-size", HILDON_ICON_PIXEL_SIZE_NOTE, 
558       "icon-name", icon_name ? icon_name : HILDON_BANNER_DEFAULT_ICON,
559       NULL);
560    hildon_banner_set_text(banner, text);
561    hildon_banner_bind_label_style(banner, NULL);
562
563    /* Show the banner, since caller cannot do that */
564    gtk_widget_show_all(GTK_WIDGET(banner));
565 }
566
567 /**
568  * hildon_banner_show_information_with_markup:
569  * @widget: the #GtkWidget that wants to display banner
570  * @icon_name: the name of icon to use. Can be %NULL for default icon.
571  * @markup: a markup string to display (see <link linkend="PangoMarkupFormat">Pango markup format</link>)
572  *
573  * This function creates and displays an information banner that
574  * automatically goes away after certain time period. For each window
575  * in your application there can only be one timed banner, so if you
576  * spawn a new banner before the earlier one has timed out, the
577  * previous one will be replaced.
578  *
579  * Since: 0.12.8
580  */
581 void hildon_banner_show_information_with_markup(GtkWidget *widget, 
582    const gchar *icon_name, const gchar *markup)
583 {
584    HildonBanner *banner;
585
586    g_return_if_fail(widget == NULL || GTK_IS_WIDGET(widget));
587    g_return_if_fail(icon_name == NULL || icon_name[0] != 0);
588    g_return_if_fail(markup != NULL);
589
590    /* Prepare banner */
591    banner = hildon_banner_get_instance_for_widget(widget, TRUE);
592
593    hildon_banner_ensure_child(banner, NULL, 0, GTK_TYPE_IMAGE, 
594       "pixel-size", HILDON_ICON_PIXEL_SIZE_NOTE, 
595       "icon-name", icon_name ? icon_name : HILDON_BANNER_DEFAULT_ICON,
596       NULL);
597    hildon_banner_set_markup(banner, markup);
598    hildon_banner_bind_label_style(banner, NULL);
599
600    /* Show the banner, since caller cannot do that */
601    gtk_widget_show_all(GTK_WIDGET(banner));
602 }
603
604 /**
605  * hildon_banner_show_animation:
606  * @widget: the #GtkWidget that wants to display banner
607  * @animation_name: The progress animation to use. You usually can just
608  *                  pass %NULL for the default animation.
609  * @text: the text to display.
610  *
611  * Shows an animated progress notification. It's recommended not to try
612  * to show more than one progress notification at a time, since
613  * they will appear on top of each other. You can use progress
614  * notifications with timed banners. In this case the banners are
615  * located so that you can somehow see both.
616  *
617  * Please note that banners are destroyed automatically once the
618  * window they are attached to is closed. The pointer that you
619  * receive with this function do not contain additional references,
620  * so it can become invalid without warning (this is true for
621  * all toplevel windows in gtk). To make sure that the banner do not disapear
622  * automatically, you can separately ref the return value (this
623  * doesn't prevent the banner from disappearing, but the object it just
624  * not finalized). In this case you have to call both #gtk_widget_destroy 
625  * followed by #g_object_unref (in this order).
626  * 
627  * Returns: a #HildonBanner widget. You must call #gtk_widget_destroy
628  *          once you are ready with the banner.
629  *
630  * Since: 0.12.2
631  */
632 GtkWidget *hildon_banner_show_animation(GtkWidget *widget, 
633    const gchar *animation_name, const gchar *text)
634 {
635    HildonBanner *banner;
636    GtkIconTheme *theme; 
637    GtkIconInfo *info;
638    GtkWidget *image_widget;
639    const gchar *filename;
640
641    g_return_val_if_fail(widget == NULL || GTK_IS_WIDGET(widget), NULL);
642    g_return_val_if_fail(animation_name == NULL || animation_name[0] != 0, NULL);
643    g_return_val_if_fail(text != NULL, NULL);
644
645    /* Find out which animation to use */
646    theme = gtk_icon_theme_get_default();
647    info = gtk_icon_theme_lookup_icon(theme, animation_name ? 
648             animation_name : HILDON_BANNER_DEFAULT_PROGRESS_ANIMATION,
649             HILDON_ICON_SIZE_NOTE, 0);
650
651    /* Try to load animation. One could try to optimize this 
652       to avoid loading the default animation during each call */
653    if (info) {
654           filename = gtk_icon_info_get_filename(info);
655       image_widget = gtk_image_new_from_file(filename);
656       gtk_icon_info_free(info);
657    } else {
658       g_warning("icon theme lookup for icon failed!");
659       image_widget = NULL;
660    }
661
662    /* Prepare banner */
663    banner = hildon_banner_get_instance_for_widget(widget, FALSE);
664    hildon_banner_ensure_child(banner, image_widget, 0,
665       GTK_TYPE_IMAGE, NULL);
666    hildon_banner_set_text(banner, text);
667    hildon_banner_bind_label_style(banner, NULL);
668
669    /* And show it */
670    gtk_widget_show_all(GTK_WIDGET(banner));
671
672    return GTK_WIDGET(banner);
673 }
674
675 /**
676  * hildon_banner_show_progress:
677  * @widget: the #GtkWidget that wants to display banner
678  * @bar: Progressbar to use. You usually can just pass %NULL, unless
679  *       you want somehow customized progress bar.
680  * @text: text to display.
681  *
682  * Shows progress notification. See #hildon_banner_show_animation
683  * for more information.
684  * 
685  * Returns: a #HildonBanner widget. You must call #gtk_widget_destroy
686  *          once you are ready with the banner.
687  *
688  * Since: 0.12.2
689  */
690 GtkWidget *hildon_banner_show_progress(GtkWidget *widget, 
691    GtkProgressBar *bar, const gchar *text)
692 {
693    HildonBanner *banner;
694
695    g_return_val_if_fail(widget == NULL || GTK_IS_WIDGET(widget), NULL);
696    g_return_val_if_fail(bar == NULL || GTK_IS_PROGRESS_BAR(bar), NULL);
697    g_return_val_if_fail(text != NULL, NULL);
698
699    /* Prepare banner */
700    banner = hildon_banner_get_instance_for_widget(widget, FALSE);
701    hildon_banner_ensure_child(banner, (GtkWidget *) bar, -1, GTK_TYPE_PROGRESS_BAR, NULL);
702    gtk_widget_set_size_request(banner->priv->main_item,
703       HILDON_BANNER_PROGRESS_WIDTH, -1);
704    hildon_banner_set_text(banner, text);
705    hildon_banner_bind_label_style(banner, NULL);
706
707    /* Show the banner */
708    gtk_widget_show_all(GTK_WIDGET(banner));
709
710    return GTK_WIDGET(banner);   
711 }
712
713 /**
714  * hildon_banner_set_text:
715  * @self: a #HildonBanner widget
716  * @text: a new text to display in banner
717  *
718  * Sets the text that is displayed in the banner.
719  *
720  * Since: 0.12.2
721  */
722 void hildon_banner_set_text(HildonBanner *self, const gchar *text)
723 {
724    GtkLabel *label;
725    GtkRequisition req;
726
727    g_return_if_fail(HILDON_IS_BANNER(self));
728
729    label = GTK_LABEL(self->priv->label);
730    gtk_label_set_text(label, text);
731
732    /* A re-used window may be too large, shrink to the requisition size */
733    gtk_widget_size_request(GTK_WIDGET(self), &req);
734    if (req.width != 0)
735    {
736      gtk_window_resize(GTK_WINDOW(self), req.width, req.height);
737    }
738
739    hildon_banner_check_position(GTK_WIDGET(self));
740 }
741
742 /**
743  * hildon_banner_set_markup:
744  * @self: a #HildonBanner widget
745  * @markup: a new text with Pango markup to display in the banner
746  *
747  * Sets the text with markup that is displayed in the banner.
748  *
749  * Since: 0.12.8
750  */
751 void hildon_banner_set_markup(HildonBanner *self, const gchar *markup)
752 {
753    GtkLabel *label;
754    GtkRequisition req;
755
756    g_return_if_fail(HILDON_IS_BANNER(self));
757
758    label = GTK_LABEL(self->priv->label);
759    gtk_label_set_markup(label, markup);
760
761    /* A re-used window may be too large, shrink to the requisition size */
762    gtk_widget_size_request(GTK_WIDGET(self), &req);
763    if (req.width != 0)
764    {
765      gtk_window_resize(GTK_WINDOW(self), req.width, req.height);
766    }
767
768    hildon_banner_check_position(GTK_WIDGET(self));
769 }
770
771 /**
772  * hildon_banner_set_fraction:
773  * @self: a #HildonBanner widget
774  * @fraction: #gdouble
775  *
776  * The fraction is the completion of progressbar, 
777  * the scale is from 0.0 to 1.0.
778  * Sets the amount of fraction the progressbar has.
779  *
780  * Since: 0.12.2
781  */
782 void hildon_banner_set_fraction(HildonBanner *self, gdouble fraction)
783 {
784    g_return_if_fail(HILDON_IS_BANNER(self));
785    g_return_if_fail(GTK_IS_PROGRESS_BAR(self->priv->main_item));
786    gtk_progress_bar_set_fraction(GTK_PROGRESS_BAR(self->priv->main_item), fraction);
787 }
788
789
790
791
792
793 /**
794  * Deprecated: really, do NOT use.
795  */
796 void _hildon_gtk_label_set_text_n_lines(GtkLabel *label, const gchar *text, gint max_lines)
797 {
798 /* Forces the wrapping of text into several lines and ellipsizes the rest. 
799    Similar to combination of gtk_label_set_wrap and pango ellipzation. 
800    We cannot just use those directly, since ellipzation always wins wrapping.
801
802    This means that we have to:
803      * First wrap the text
804      * Insert forced linebreaks into text
805      * Truncate the result
806
807    NOTE! This will not work with pango markup!
808
809    FIXME: luc: DO NOT TRUNCATE the text. Use as many lines as needed.
810           Lenth of the text is under applications' responsibility.
811           Widget does not have to enforce this.
812 */
813    PangoLayout *layout;
814    PangoLayoutLine *line;
815    GtkRequisition req;
816    GString *wrapped_text;
817    gchar *line_data;
818    gint lines, i;
819
820    g_return_if_fail(GTK_IS_LABEL(label));
821    g_return_if_fail(max_lines >= 1);
822
823    /* Setup the label to contain the new data */
824    gtk_label_set_text(label, text);
825    gtk_label_set_line_wrap(label, TRUE);
826    gtk_label_set_ellipsize(label, PANGO_ELLIPSIZE_NONE);
827    
828    /* We really want to recalculate the size, not use some old values */
829    gtk_widget_size_request(GTK_WIDGET(label), &req);
830    layout = gtk_label_get_layout(label);
831    lines = pango_layout_get_line_count(layout);
832
833    /* Now collect the wrapped text. */
834    wrapped_text = g_string_new(NULL);
835
836    for (i = 0; i < lines; i++)
837    {
838       /* Append the next line into wrapping buffer, but 
839          avoid adding extra whitespaces at the end, since those
840          can cause other lines to be ellipsized as well. */
841       line = pango_layout_get_line(layout, i);
842       line_data = g_strndup(pango_layout_get_text(layout) + line->start_index, 
843          line->length);
844       g_strchomp(line_data);
845       g_string_append(wrapped_text, line_data);
846
847       /* Append forced linebreaks, until we have the desired
848          amount of lines. After that we put the rest to the
849          last line to make ellipzation to happen */
850       if (i < lines - 1)
851       {
852          if (i < max_lines - 1)
853             g_string_append_c(wrapped_text, '\n');
854          else
855             g_string_append_c(wrapped_text, ' ');
856       }
857
858       g_free(line_data);
859    }
860
861    /* Now update the label to use wrapped text. Use builtin
862       ellipzation as well. */
863    gtk_widget_set_size_request(GTK_WIDGET(label), req.width, -1);
864    gtk_label_set_text(label, wrapped_text->str);
865    gtk_label_set_ellipsize(label, PANGO_ELLIPSIZE_END);
866    gtk_label_set_line_wrap(label, FALSE);
867
868    g_string_free(wrapped_text, TRUE);
869 }