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