* configure.ac: 0.12.0
[hildon] / hildon-widgets / gtk-infoprint.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/gtk.h>
26 #include <gdk/gdkx.h>
27 #include <X11/X.h>
28 #include <X11/Xatom.h>
29 #include "gtk-infoprint.h"
30 #include "hildon-defines.h"
31 #include "hildon-app.h"
32
33 #define INFOPRINT_MIN_WIDTH    39
34 #define INFOPRINT_MAX_WIDTH   334
35 #define BANNER_MIN_WIDTH       35
36 #define BANNER_MAX_WIDTH      375
37 #define DEFAULT_WIDTH          20
38 #define DEFAULT_HEIGHT         28       /* 44-8-8 = 28 */
39 #define WINDOW_WIDTH          600
40 #define MESSAGE_TIMEOUT      2500
41 #define GTK_INFOPRINT_STOCK "gtk-infoprint"
42 #define GTK_INFOPRINT_ICON_THEME  "qgn_note_infoprint"
43
44 #define BANNER_PROGRESSBAR_MIN_WIDTH 83
45 #define INFOPRINT_MIN_SIZE           50
46 #define INFOPRINT_WINDOW_Y           73
47 #define INFOPRINT_WINDOW_FS_Y        20
48 #define INFOPRINT_WINDOW_X           15
49
50
51 #define DEFAULT_PROGRESS_ANIMATION "qgn_indi_pball_a"
52
53 static gboolean gtk_infoprint_temporal_wrap_disable_flag = FALSE;
54
55 typedef struct {
56   GtkWindow *parent;
57   GtkWidget *window;
58   gchar *text;
59   GtkWidget *main_item;
60   guint timeout;
61 } InfoprintState;
62
63 enum {
64     WIN_TYPE = 0,
65     WIN_TYPE_MESSAGE,
66     MAX_WIN_MESSAGES
67 };
68
69 static GtkWidget *global_banner = NULL;
70 static GtkWidget *global_infoprint = NULL;
71
72 static GQueue *cbanner_queue = NULL;
73 static InfoprintState *current_ibanner = NULL;
74 static InfoprintState *current_pbanner = NULL;
75 static guint pbanner_refs = 0;
76
77 static gboolean compare_icons(GtkImage *image1, GtkImage *image2);
78 static void queue_new_cbanner(GtkWindow *parent,
79                               const gchar *text,
80                               GtkWidget *image);
81 static void gtk_msg_window_init(GtkWindow * parent, GQuark type,
82                                 const gchar * text, GtkWidget * main_item);
83 static gchar *three_lines_truncate(GtkWindow * parent,
84                                    const gchar * str,
85                                    gint * max_width, gint * resulting_lines);
86
87 static gboolean infoprint_idle_before_timer (GtkWidget *widget, 
88                                              GdkEventExpose *event, 
89                                              gpointer data);
90 static gboolean infoprint_start_timer (gpointer data);
91
92 /* Getters/initializers for needed quarks */
93 static GQuark banner_quark(void)
94 {
95     static GQuark quark = 0;
96
97     if (quark == 0)
98         quark = g_quark_from_static_string("banner");
99
100     return quark;
101 }
102
103 static GQuark infoprint_quark(void)
104 {
105     static GQuark quark = 0;
106
107     if (quark == 0)
108         quark = g_quark_from_static_string("infoprint");
109
110     return quark;
111 }
112
113 static GQuark type_quark(void)
114 {
115     static GQuark quark = 0;
116
117     if (quark == 0)
118         quark = g_quark_from_static_string("Message window type");
119
120     return quark;
121 }
122
123 static GQuark label_quark(void)
124 {
125     static GQuark quark = 0;
126
127     if (quark == 0)
128         quark = g_quark_from_static_string("Message window text");
129
130     return quark;
131 }
132
133 static GQuark item_quark(void)
134 {
135     static GQuark quark = 0;
136
137     if (quark == 0)
138         quark =
139             g_quark_from_static_string("Message window graphical item");
140
141     return quark;
142 }
143
144 static GQuark confirmation_banner_quark(void)
145 {
146     static GQuark quark = 0;
147
148     if (quark == 0)
149         quark = g_quark_from_static_string("confirmation_banner");
150
151     return quark;
152 }
153
154 /* Returns the infoprint/banner linked to the specific window */
155 static GtkWindow *gtk_msg_window_get(GtkWindow * parent, GQuark quark)
156 {
157     if (quark == 0)
158         return NULL;
159     if (parent == NULL) {
160         if (quark == banner_quark()) {
161             return GTK_WINDOW(global_banner);
162         }
163         return GTK_WINDOW(global_infoprint);
164     }
165
166     return GTK_WINDOW(g_object_get_qdata(G_OBJECT(parent), quark));
167 }
168
169 /* Returns the given widget from banner type of message window. 
170    This is used when the banner data is updated in later stage. 
171 */
172 static GtkWidget *gtk_banner_get_widget(GtkWindow * parent,
173                                         GQuark widget_quark)
174 {
175     GtkWindow *window = gtk_msg_window_get(parent, banner_quark());
176
177     if (window)
178         return
179             GTK_WIDGET(g_object_get_qdata(G_OBJECT(window), widget_quark));
180
181     return NULL;
182 }
183
184 /* Timed destroy. Removing this callback is done in other place. */
185 static gboolean gtk_msg_window_destroy(gpointer pointer)
186 {
187     g_return_val_if_fail(GTK_IS_WINDOW(pointer), TRUE);
188     gtk_widget_destroy(GTK_WIDGET(pointer));
189     
190     return FALSE;
191 }
192
193 static gboolean gtk_msg_window_real_destroy(gpointer pointer)
194 {
195     GObject *parent;
196     GQuark quark;
197
198     g_return_val_if_fail(GTK_IS_WINDOW(pointer), TRUE);
199
200     parent = G_OBJECT(gtk_window_get_transient_for(GTK_WINDOW(pointer)));
201     quark = (GQuark) g_object_get_qdata((GObject *) pointer, type_quark());
202
203     if (quark == infoprint_quark() && current_ibanner) {
204       gtk_widget_unref(current_ibanner->main_item);
205       g_free(current_ibanner->text);
206       g_free(current_ibanner);
207       current_ibanner = NULL;
208     } else if (quark == banner_quark() && current_pbanner) {
209       /*
210        * If the destroy signal is not emited via gkt_banner_close() 
211        * (for example when the banner is being destroyed with the parent)
212        * the reference counter will have a value larger than 0 and the
213        * reference counter should be decremented.
214        */
215       
216       if (pbanner_refs > 0) 
217         --pbanner_refs;
218       
219       gtk_widget_unref(current_pbanner->main_item);
220       g_free(current_pbanner->text);
221       g_free(current_pbanner);
222       current_pbanner = NULL;
223     } else if (quark == confirmation_banner_quark() &&
224                !g_queue_is_empty(cbanner_queue)) {
225       InfoprintState *cbanner = g_queue_pop_head(cbanner_queue);
226
227       gtk_widget_unref(cbanner->main_item);
228       g_free(cbanner->text);
229       g_free(cbanner);
230
231       if (!g_queue_is_empty(cbanner_queue)) {
232         cbanner = g_queue_peek_head(cbanner_queue);
233         gtk_msg_window_init(cbanner->parent, confirmation_banner_quark(),
234                             cbanner->text, cbanner->main_item);
235       } else {
236         g_queue_free(cbanner_queue);
237         cbanner_queue = NULL;
238       }
239     }
240
241     if (parent) {
242         g_object_set_qdata(parent, quark, NULL);
243     } else {
244         if (quark == banner_quark()) {
245             global_banner = NULL;
246         } else {
247             global_infoprint = NULL;
248         }
249     }
250
251     return FALSE;
252 }
253
254
255 /* Get window ID of top window from _NET_ACTIVE_WINDOW property */
256 static Window get_active_window( void )
257 {
258     unsigned long n;
259     unsigned long extra;
260     int format;
261     int status;
262
263     Atom atom_net_active = gdk_x11_get_xatom_by_name ("_NET_ACTIVE_WINDOW");
264     Atom realType;
265     Window win_result = None;
266     guchar *data_return = NULL;
267
268     status = XGetWindowProperty(GDK_DISPLAY(), GDK_ROOT_WINDOW(), 
269                                 atom_net_active, 0L, 16L,
270                                 0, XA_WINDOW, &realType, &format,
271                                 &n, &extra, 
272                                 &data_return);
273
274     if ( status == Success && realType == XA_WINDOW 
275             && format == 32 && n == 1 && data_return != NULL )
276     {
277         win_result = ((Window*) data_return)[0];
278     /*    g_print("_NET_ACTIVE_WINDOW id %d\n", ((gint *)data_return)[0] );*/
279     } 
280         
281     if ( data_return ) 
282         XFree(data_return);    
283     
284     return win_result;
285 }
286
287 /* Checks if a window is in fullscreen state or not */
288 static gboolean check_fullscreen_state( Window window )
289 {
290     unsigned long n;
291     unsigned long extra;
292     int           format, status, i; 
293     guchar *data_return = NULL;
294     
295     Atom          realType;
296     Atom  atom_window_state = gdk_x11_get_xatom_by_name ("_NET_WM_STATE");
297     Atom  atom_fullscreen = gdk_x11_get_xatom_by_name ("_NET_WM_STATE_FULLSCREEN");
298     
299     if ( window == None )
300         return FALSE;
301
302     /* in some cases XGetWindowProperty seems to generate BadWindow,
303        so at the moment this function does not always work perfectly */
304     gdk_error_trap_push();
305     status = XGetWindowProperty(GDK_DISPLAY(), window,
306                                 atom_window_state, 0L, 1000000L,
307                                 0, XA_ATOM, &realType, &format,
308                                 &n, &extra, &data_return);
309     gdk_flush();
310     if (gdk_error_trap_pop())
311         return FALSE;
312
313     if (status == Success && realType == XA_ATOM && format == 32 && n > 0)
314     {
315         for(i=0; i < n; i++)
316             if ( ((Atom*)data_return)[i] && 
317                  ((Atom*)data_return)[i] == atom_fullscreen)
318             {
319                 if (data_return) XFree(data_return);
320                 return True;
321             }
322     }
323
324     if (data_return) 
325         XFree(data_return);
326
327     return False;
328 }
329
330
331 static gboolean
332 compare_icons(GtkImage *image1, GtkImage *image2)
333 {
334   GtkImageType type = gtk_image_get_storage_type(image1);
335   gchar *name1, *name2;
336   const gchar *icon_name1, *icon_name2;
337   GtkIconSize size1, size2;
338
339   if (gtk_image_get_storage_type(image2) != type)
340     return FALSE;
341
342   switch (type) {
343   case GTK_IMAGE_STOCK:
344     gtk_image_get_stock(image1, &name1, &size1);
345     gtk_image_get_stock(image2, &name2, &size2);
346     return ((g_utf8_collate(name1, name2) == 0) && (size1 == size2));
347   case GTK_IMAGE_ICON_NAME:
348     gtk_image_get_icon_name(image1, &icon_name1, &size1);
349     gtk_image_get_icon_name(image2, &icon_name2, &size2);
350     return ((g_utf8_collate(icon_name1, icon_name2) == 0) && (size1 == size2));
351   case GTK_IMAGE_ANIMATION:
352     /* there is only one possible animation */
353     return TRUE;
354   default:
355     /* other types of icons are actually not even supported */
356     return FALSE;
357   }
358 }
359
360 /* confirmation banners are queued so that all of them will
361    be shown eventually for the appropriate amount of time */
362 static void
363 queue_new_cbanner(GtkWindow *parent, const gchar *text, GtkWidget *image)
364 {
365   InfoprintState *cbanner;
366
367   if (cbanner_queue == NULL)
368     cbanner_queue = g_queue_new();
369
370   /* identical consecutive cbanners are collapsed to just one cbanner */
371   if ((cbanner = g_queue_peek_tail(cbanner_queue)) != NULL &&
372       g_utf8_collate(cbanner->text, text) == 0 &&
373       compare_icons(GTK_IMAGE(image), GTK_IMAGE(cbanner->main_item))) {
374     g_source_remove(cbanner->timeout);
375
376     cbanner->timeout = g_timeout_add(MESSAGE_TIMEOUT,
377                                      gtk_msg_window_destroy,
378                                      cbanner->window);
379     g_signal_connect_swapped(cbanner->window, "destroy",
380                              G_CALLBACK(g_source_remove),
381                              GUINT_TO_POINTER(cbanner->timeout));
382     gtk_object_sink(GTK_OBJECT(image));
383     return;
384   }
385
386   cbanner = g_new0(InfoprintState, 1);
387   cbanner->parent = parent;
388   cbanner->text = g_strdup(text);
389   cbanner->main_item = image;
390   gtk_widget_ref(cbanner->main_item);
391
392   g_queue_push_tail(cbanner_queue, cbanner);
393
394   if (g_queue_get_length(cbanner_queue) == 1)
395     gtk_msg_window_init(parent, confirmation_banner_quark(), text, image);
396 }
397
398
399 /* gtk_msg_window_init
400  *
401  * @parent -- The parent window
402  * @type   -- The enumerated type of message window
403  * @text   -- The displayed text
404  * @item   -- The item to be loaded, or NULL if default 
405  *             (used only in INFOPRINT_WITH_ICON)
406  */
407 static void
408 gtk_msg_window_init(GtkWindow * parent, GQuark type,
409                     const gchar * text, GtkWidget * main_item)
410 {
411     GtkWidget *window;
412     GtkWidget *hbox;
413     GtkWidget *label;
414
415     gchar *str = NULL;
416     gint max_width = 0;
417
418     g_return_if_fail((GTK_IS_WINDOW(parent) || parent == NULL));
419
420     if (type == banner_quark())
421       pbanner_refs++;
422
423     /* information banners: just reset the timeout if trying
424        to recreate the currently visible information banner */
425     if (type == infoprint_quark() && current_ibanner) {
426      
427       /* caller is trying to recreate current information banner */
428       if (g_utf8_collate(current_ibanner->text, text) == 0 &&
429           compare_icons(GTK_IMAGE(main_item),
430                         GTK_IMAGE(current_ibanner->main_item))) {
431         /* If previous timer has been created, replace it with a new one */
432         if (current_ibanner->timeout > 0) {
433           g_source_remove(current_ibanner->timeout);
434           current_ibanner->timeout = g_timeout_add(MESSAGE_TIMEOUT,
435                                                    gtk_msg_window_destroy,
436                                                    current_ibanner->window);
437
438           g_signal_connect_swapped(current_ibanner->window, "destroy",
439                                    G_CALLBACK(g_source_remove),
440                                    GUINT_TO_POINTER(current_ibanner->timeout));
441         }
442         gtk_object_sink(GTK_OBJECT(main_item));
443         return;
444       }
445      
446       /* If the timer has already been set -> remove it */
447       if (current_ibanner->timeout > 0)
448         g_source_remove(current_ibanner->timeout);
449
450       gtk_msg_window_destroy(current_ibanner->window);
451     }
452
453     if (type == banner_quark() && current_pbanner) {
454       if (g_utf8_collate(current_pbanner->text, text) == 0) {
455         if (GTK_IS_PROGRESS_BAR(main_item) &&
456             GTK_IS_PROGRESS_BAR(current_pbanner->main_item)) {
457           gtk_progress_bar_set_fraction(GTK_PROGRESS_BAR(current_pbanner->main_item), 0.0);
458           gtk_object_sink(GTK_OBJECT(main_item));
459           return;
460         } else if (GTK_IS_IMAGE(main_item) &&
461                    GTK_IS_IMAGE(current_pbanner->main_item) &&
462                    compare_icons(GTK_IMAGE(main_item),
463                                  GTK_IMAGE(current_pbanner->main_item))) {
464           gtk_object_sink(GTK_OBJECT(main_item));
465           return;
466         }
467       }
468     }
469
470     window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
471     gtk_window_set_accept_focus(GTK_WINDOW(window), FALSE);
472     g_signal_connect(window, "destroy", G_CALLBACK(gtk_msg_window_real_destroy), window);
473
474     hbox = gtk_hbox_new(FALSE, 5);
475
476     if (current_pbanner && type == banner_quark()) {
477       /*
478        * The destroy substracts one from the reference counter,
479        * so adding one to keep the counter working
480        */
481       ++pbanner_refs;
482       gtk_msg_window_destroy(current_pbanner->window);
483     }
484
485     if (parent) {
486         gtk_window_set_transient_for(GTK_WINDOW(window), parent);
487         gtk_window_set_destroy_with_parent(GTK_WINDOW(window), TRUE);
488
489         g_object_set_qdata(G_OBJECT(parent), type, (gpointer) window);
490     } else {
491         if (type == banner_quark()) {
492             global_banner = window;
493         } else {
494             global_infoprint = window;
495         }
496     }
497
498     gtk_widget_realize(window);
499
500     if ((type == confirmation_banner_quark()) || (type == banner_quark()))
501         gtk_infoprint_temporal_wrap_disable_flag = TRUE;
502
503     if (type == banner_quark()) {
504         max_width = BANNER_MAX_WIDTH;
505     } else {
506         max_width = INFOPRINT_MAX_WIDTH;
507     }
508
509     if (parent == NULL) {
510       gdk_window_set_transient_for(GDK_WINDOW(window->window),
511                                    GDK_WINDOW(gdk_get_default_root_window()));
512       str = three_lines_truncate(GTK_WINDOW(window), text, &max_width, NULL);
513     } else {
514       str = three_lines_truncate(parent, text, &max_width, NULL);
515     }
516
517     gtk_infoprint_temporal_wrap_disable_flag = FALSE;
518
519     label = gtk_label_new(str);
520     g_free(str);
521
522     if (max_width < INFOPRINT_MIN_WIDTH) {
523         gtk_widget_set_size_request(GTK_WIDGET(label),
524                                     (max_width < INFOPRINT_MIN_WIDTH) ?
525                                     INFOPRINT_MIN_WIDTH : -1,
526                                     -1);
527     }
528
529     if ((type == confirmation_banner_quark()) || (type == banner_quark()))
530         gtk_widget_set_name(label, "hildon-banner-label");
531
532     g_object_set_qdata(G_OBJECT(window), type_quark(), (gpointer) type);
533     g_object_set_qdata(G_OBJECT(window), label_quark(), (gpointer) label);
534     g_object_set_qdata(G_OBJECT(window), item_quark(),
535                        (gpointer) main_item);
536
537     gtk_container_add(GTK_CONTAINER(window), hbox);
538
539     if (type == banner_quark()) {
540         gtk_box_pack_start_defaults(GTK_BOX(hbox), label);
541         if (main_item) {
542             if (GTK_IS_PROGRESS_BAR(main_item)) {
543                 gtk_widget_set_size_request(GTK_WIDGET(main_item),
544                                             BANNER_PROGRESSBAR_MIN_WIDTH,
545                                             -1);
546             }
547             gtk_box_pack_start_defaults(GTK_BOX(hbox), main_item);
548         }
549     } else {
550         if (main_item) {
551             GtkAlignment *ali =
552                 GTK_ALIGNMENT(gtk_alignment_new(0, 0, 0, 0));
553             gtk_widget_set_size_request(GTK_WIDGET(main_item),
554                                         INFOPRINT_MIN_SIZE,
555                                         INFOPRINT_MIN_SIZE);
556             gtk_container_add(GTK_CONTAINER(ali), GTK_WIDGET(main_item));
557             gtk_box_pack_start_defaults(GTK_BOX(hbox), GTK_WIDGET(ali));
558         }
559         gtk_box_pack_start_defaults(GTK_BOX(hbox), label);
560     }
561
562     gtk_window_set_default_size(GTK_WINDOW(window),
563                                 DEFAULT_WIDTH, DEFAULT_HEIGHT);
564
565     /* Positioning of the infoprint */ 
566     { 
567         gint y = INFOPRINT_WINDOW_Y;
568         gint x = gdk_screen_width() + INFOPRINT_WINDOW_X;
569         
570         /* Check if the active application is in fullscreen */
571         if( check_fullscreen_state(get_active_window()) )
572             y =  INFOPRINT_WINDOW_FS_Y;  
573             /* this should be fixed to use theme dependant infoprint border size */
574
575         gtk_window_move(GTK_WINDOW(window), x, y);
576     }
577
578     gdk_window_set_type_hint(window->window, GDK_WINDOW_TYPE_HINT_MESSAGE);
579
580     gtk_widget_show_all(window);
581
582     if (type == infoprint_quark()) {
583       current_ibanner = g_new0(InfoprintState, 1);
584       current_ibanner->parent = parent;
585       current_ibanner->window = window;
586       current_ibanner->text = g_strdup(text);
587       current_ibanner->main_item = main_item;
588       gtk_widget_ref(current_ibanner->main_item);
589     }
590     else if (type == banner_quark()) {
591       current_pbanner = g_new0(InfoprintState, 1);
592       current_pbanner->parent = parent;
593       current_pbanner->window = window;
594       current_pbanner->text = g_strdup(text);
595       current_pbanner->main_item = main_item;
596       gtk_widget_ref(current_pbanner->main_item);
597     }
598
599     /* If the type is an infoprint we set the timer after the expose-event */
600     if (type == infoprint_quark()) {
601       g_signal_connect_after(window, "expose-event", 
602                              G_CALLBACK(infoprint_idle_before_timer), NULL);
603     }
604     else if (type == confirmation_banner_quark()) {
605       InfoprintState *current_cbanner = g_queue_peek_head(cbanner_queue);
606
607       current_cbanner->window = window;
608       current_cbanner->timeout = g_timeout_add(MESSAGE_TIMEOUT,
609                                                gtk_msg_window_destroy,
610                                                current_cbanner->window);
611       g_signal_connect_swapped(window, "destroy",
612                                G_CALLBACK(g_source_remove),
613                                GUINT_TO_POINTER(current_cbanner->timeout));
614     }
615 }
616
617 static gchar *three_lines_truncate(GtkWindow * parent, const gchar * str,
618                                    gint * max_width, gint * resulting_lines)
619 {
620     gchar *result = NULL;
621     PangoLayout *layout;
622     PangoContext *context;
623
624     if (!str)
625         return g_strdup("");
626
627     if (GTK_IS_WIDGET(parent)) {
628         context = gtk_widget_get_pango_context(GTK_WIDGET(parent));
629     } else {
630         if (gdk_screen_get_default() != NULL) {
631             context = gdk_pango_context_get_for_screen
632                 (gdk_screen_get_default());
633         } else {
634             g_print("GtkInfoprint : Could not get default screen.\n");
635             return NULL;
636         }
637     }
638
639     {
640         gchar *line1 = NULL;
641         gchar *line2 = NULL;
642         gchar *line3 = NULL;
643
644         layout = pango_layout_new(context);
645         pango_layout_set_text(layout, str, -1);
646         if (gtk_infoprint_temporal_wrap_disable_flag == FALSE) {
647             pango_layout_set_width(layout, *max_width * PANGO_SCALE);
648         } else {
649             pango_layout_set_width(layout, -1);
650         }
651         pango_layout_set_wrap(layout, PANGO_WRAP_WORD_CHAR);
652
653         if (pango_layout_get_line_count(layout) >= 2) {
654             PangoLayoutLine *line = pango_layout_get_line(layout, 0);
655
656             if (line != NULL)
657                 line1 = g_strndup(str, line->length);
658
659             line = pango_layout_get_line(layout, 1);
660
661             if (line != NULL)
662                 line2 = g_strndup((gchar *) ((gint) str + line->start_index),
663                                   line->length);
664
665             line = pango_layout_get_line(layout, 2);
666
667             if (line != NULL)
668                 line3 = g_strdup((gchar *) ((gint) str + line->start_index));
669
670             g_object_unref(layout);
671             layout = pango_layout_new(context);
672             pango_layout_set_text(layout, line3 ? line3 : "", -1);
673
674             {
675                 gint index = 0;
676
677                 if (pango_layout_get_line_count(layout) > 1) {
678                     gchar *templine = NULL;
679
680                     line = pango_layout_get_line(layout, 0);
681                     templine = g_strndup(line3, line->length);
682                     g_free(line3);
683                     line3 = g_strconcat(templine, "\342\200\246", NULL);
684                     g_free(templine);
685                 }
686
687                 if (pango_layout_xy_to_index(layout,
688                                              *max_width * PANGO_SCALE,
689                                              0, &index, NULL)
690                     == TRUE) {
691                     gint ellipsiswidth;
692                     gchar *tempresult;
693                     PangoLayout *ellipsis = pango_layout_new(context);
694
695                     pango_layout_set_text(ellipsis, "\342\200\246", -1);
696                     pango_layout_get_size(ellipsis, &ellipsiswidth, NULL);
697                     pango_layout_xy_to_index(layout,
698                                              *max_width * PANGO_SCALE -
699                                              ellipsiswidth,
700                                              0, &index, NULL);
701                     g_object_unref(G_OBJECT(ellipsis));
702                     tempresult = g_strndup(line3, index);
703                     g_free(line3);
704                     line3 = g_strconcat(tempresult, "\342\200\246", NULL);
705                     g_free(tempresult);
706                 }
707             }
708
709         } else
710             line1 = g_strdup(str);
711
712         {
713             PangoLayout *templayout = pango_layout_new(context);
714
715             pango_layout_set_text(templayout, line1, -1);
716             if (pango_layout_get_line_count(templayout) < 3
717                 && line3 != NULL) {
718                 result = g_strconcat(line1, "\n", line2, "\n", line3, NULL);
719             } else if (pango_layout_get_line_count(templayout) < 2
720                        && line2 != NULL) {
721                 result = g_strconcat(line1, "\n", line2, line3, NULL);
722             } else {
723                 result = g_strconcat(line1, line2, line3, NULL);
724             }
725             g_object_unref(templayout);
726         }
727
728         g_free(line1);
729         g_free(line2);
730         g_free(line3);
731
732         g_object_unref(layout);
733
734         if (gtk_infoprint_temporal_wrap_disable_flag == TRUE) {
735             gint index = 0;
736             PangoLayout *templayout = pango_layout_new(context);
737
738             pango_layout_set_text(templayout, result, -1);
739
740             if (pango_layout_get_line_count(templayout) >= 2) {
741                 PangoLayoutLine *line =
742                     pango_layout_get_line(templayout, 0);
743                 gchar *templine = g_strndup(result, line->length);
744
745                 g_free(result);
746                 result = g_strconcat(templine, "\342\200\246", NULL);
747                 g_free(templine);
748                 pango_layout_set_text(templayout, result, -1);
749             }
750
751             if (pango_layout_xy_to_index
752                 (templayout, *max_width * PANGO_SCALE, 0, &index,
753                  NULL) == TRUE) {
754                 gint ellipsiswidth;
755                 gchar *tempresult;
756                 PangoLayout *ellipsis = pango_layout_new(context);
757
758                 pango_layout_set_text(ellipsis, "\342\200\246", -1);
759                 pango_layout_get_size(ellipsis, &ellipsiswidth, NULL);
760                 pango_layout_xy_to_index(templayout,
761                                          *max_width * PANGO_SCALE -
762                                          ellipsiswidth, 0, &index, NULL);
763                 g_object_unref(G_OBJECT(ellipsis));
764                 tempresult = g_strndup(result, index);
765                 g_free(result);
766                 result = g_strconcat(tempresult, "\342\200\246", NULL);
767                 g_free(tempresult);
768             }
769             g_object_unref(templayout);
770         }
771     }
772
773     {
774         PangoLayout *templayout = pango_layout_new(context);
775
776         pango_layout_set_text(templayout, result, -1);
777         pango_layout_get_size(templayout, max_width, NULL);
778         if (resulting_lines != NULL)
779           *resulting_lines = pango_layout_get_line_count(templayout);
780         g_object_unref(templayout);
781     }
782
783     if (result == NULL)
784         result = g_strdup(str);
785
786     return result;
787 }
788
789 /**************************************************/
790 /** Public                                       **/
791 /**************************************************/
792
793 /**
794  * gtk_infoprint:
795  * @parent: The transient window for the infoprint.
796  * @text: The text in infoprint
797  *
798  * Opens a new infoprint with @text content.
799  * 
800  * If parent is %NULL, the infoprint is a system infoprint.
801  * Normally you should use your application window
802  * or dialog as a transient parent and avoid passing %NULL.
803  */
804 void gtk_infoprint(GtkWindow * parent, const gchar * text)
805 {
806     gtk_infoprint_with_icon_name(parent, text, NULL);
807 }
808
809 /**
810  * gtk_infoprint_with_icon_stock:
811  * @parent: The transient window for the infoprint.
812  * @text: The text in infoprint
813  * @stock_id: The stock id of the custom icon
814  *
815  * Opens a new infoprint with @text content.
816  * With this function you can also set a custom icon
817  * by giving a stock id as last parameter.
818  * 
819  * If parent is %NULL, the infoprint is a system infoprint.
820  * Normally you should use your application window
821  * or dialog as a transient parent and avoid passing %NULL.
822  */
823 void
824 gtk_infoprint_with_icon_stock(GtkWindow * parent,
825                               const gchar * text, const gchar * stock_id)
826 {
827     GtkWidget *image;
828
829     if (stock_id) {
830         image = gtk_image_new_from_stock(stock_id, HILDON_ICON_SIZE_NOTE);
831     } else {
832         image = gtk_image_new_from_stock(GTK_INFOPRINT_STOCK,
833                                          HILDON_ICON_SIZE_NOTE);
834     }
835
836     gtk_msg_window_init(parent, infoprint_quark(), text, image);
837 }
838
839 /**
840  * gtk_infoprint_with_icon_name:
841  * @parent: The transient window for the infoprint.
842  * @text: The text in infoprint
843  * @icon_name: The name of the icon
844  *
845  * Opens a new infoprint with @text content.
846  * With this function you can also set a custom icon
847  * by giving a icon name as last parameter.
848  * 
849  * If parent is %NULL, the infoprint is a system infoprint.
850  * Normally you should use your application window
851  * or dialog as a transient parent and avoid passing %NULL.
852  */
853 void
854 gtk_infoprint_with_icon_name(GtkWindow * parent,
855                               const gchar * text, const gchar * icon_name)
856 {
857     GtkWidget *image;
858
859     if (icon_name) {
860         image = gtk_image_new_from_icon_name(icon_name, HILDON_ICON_SIZE_NOTE);
861     } else {
862       image = gtk_image_new_from_icon_name(GTK_INFOPRINT_ICON_THEME, 
863                                            HILDON_ICON_SIZE_NOTE);
864     }
865
866     gtk_msg_window_init(parent, infoprint_quark(), text, image);
867 }                                                                        
868
869 /**
870  * gtk_infoprintf:
871  * @parent: The transient window for the infoprint.
872  * @format: Format of the text.
873  * @Varargs: List of parameters.
874  *
875  * Opens a new infoprint with @text printf-style formatting
876  * string and comma-separated list of parameters.
877  * 
878  * If parent is %NULL, the infoprint is a system infoprint.
879  * This version of infoprint allow you to make printf-like formatting
880  * easily.
881  */
882 void gtk_infoprintf(GtkWindow * parent, const gchar * format, ...)
883 {
884     gchar *message;
885     va_list args;
886
887     va_start(args, format);
888     message = g_strdup_vprintf(format, args);
889     va_end(args);
890
891     gtk_infoprint(parent, message);
892
893     g_free(message);
894 }
895
896 /**
897  * gtk_infoprint_temporarily_disable_wrap:
898  * 
899  * Will disable wrapping for the next shown infoprint. This only
900  * affects next infoprint shown in this application.
901  */
902 void gtk_infoprint_temporarily_disable_wrap(void)
903 {
904     gtk_infoprint_temporal_wrap_disable_flag = TRUE;
905 }
906
907 /**
908  * gtk_confirmation_banner:
909  * @parent: The transient window for the confirmation banner.
910  * @text: The text in confirmation banner
911  * @stock_id: The stock id of the custom icon
912  *
913  * Opens a new confirmation banner with @text content.
914  * With this function you can also set a custom icon
915  * by giving a stock id as last parameter.
916  *
917  * If parent is %NULL, the banner is a system banner.
918  * Normally you should use your application window
919  * or dialog as a transient parent and avoid passing %NULL.
920  * 
921  * This function is otherwise similar to
922  * gtk_infoprint_with_icon_stock except in always restricts
923  * the text to one line and the font is emphasized.
924  */
925 void
926 gtk_confirmation_banner(GtkWindow * parent, const gchar * text,
927                         const gchar * stock_id)
928 {
929     GtkWidget *image;
930
931     if (stock_id) {
932         image = gtk_image_new_from_stock(stock_id, HILDON_ICON_SIZE_NOTE);
933     } else {
934         image = gtk_image_new_from_stock(GTK_INFOPRINT_STOCK,
935                                          HILDON_ICON_SIZE_NOTE);
936     }
937
938     queue_new_cbanner(parent, text, image);
939 }
940
941 /**
942  * gtk_confirmation_banner_with_icon_name:
943  * @parent: The transient window for the confirmation banner.
944  * @text: The text in confirmation banner
945  * @icon_name: The name of the custom icon in icon theme
946  *
947  * Opens a new confirmation banner with @text content.
948  * With this function you can also set a custom icon
949  * by giving a icon theme's icon name as last parameter.
950  *
951  * If parent is %NULL, the banner is a system banner.
952  * Normally you should use your application window
953  * or dialog as a transient parent and avoid passing %NULL.
954  * 
955  * This function is otherwise similar to
956  * gtk_infoprint_with_icon_name except in always restricts
957  * the text to one line and the font is emphasized.
958  */
959 void
960 gtk_confirmation_banner_with_icon_name(GtkWindow * parent, const gchar * text,
961                         const gchar * icon_name)
962 {
963     GtkWidget *image;
964
965     if (icon_name) {
966         image = gtk_image_new_from_icon_name(icon_name, HILDON_ICON_SIZE_NOTE);
967     } else {
968         image = gtk_image_new_from_stock(GTK_INFOPRINT_STOCK,
969                                          HILDON_ICON_SIZE_NOTE);
970     }
971
972     queue_new_cbanner(parent, text, image);
973 }
974
975 /**
976  * gtk_banner_show_animation:
977  * @parent: #GtkWindow
978  * @text: #const gchar *
979  *
980  * The @text is the text shown in banner.
981  * Creates a new banner with the animation.
982  */
983 void gtk_banner_show_animation(GtkWindow * parent, const gchar * text)
984 {
985     GtkWidget *item;
986     GtkIconTheme *theme; 
987     GtkIconInfo *info;
988
989     theme = gtk_icon_theme_get_default();
990     
991     info = gtk_icon_theme_lookup_icon(theme, DEFAULT_PROGRESS_ANIMATION,
992                     HILDON_ICON_SIZE_NOTE, 0);
993     
994     if (info) {
995         const gchar *filename = gtk_icon_info_get_filename(info);
996         item = gtk_image_new_from_file(filename);
997     } else {
998         g_print("icon theme lookup for icon failed!\n");
999         item = gtk_image_new();
1000     }
1001     if (info)
1002         gtk_icon_info_free(info);
1003
1004     gtk_msg_window_init(parent, banner_quark(), text, item);
1005 }
1006
1007 /**
1008  * gtk_banner_show_bar
1009  * @parent: #GtkWindow
1010  * @text: #const gchar *
1011  *
1012  * The @text is the text shown in banner.
1013  * Creates a new banner with the progressbar.
1014  */
1015 void gtk_banner_show_bar(GtkWindow * parent, const gchar * text)
1016 {
1017     gtk_msg_window_init(parent, banner_quark(),
1018                         text, gtk_progress_bar_new());
1019 }
1020
1021 /**
1022  * gtk_banner_set_text
1023  * @parent: #GtkWindow
1024  * @text: #const gchar *
1025  *
1026  * The @text is the text shown in banner.
1027  * Sets the banner text.
1028  */
1029 void gtk_banner_set_text(GtkWindow * parent, const gchar * text)
1030 {
1031     GtkWidget *item;
1032
1033     g_return_if_fail(GTK_IS_WINDOW(parent) || parent == NULL);
1034
1035     item = gtk_banner_get_widget(parent, label_quark());
1036
1037     if (GTK_IS_LABEL(item))
1038         gtk_label_set_text(GTK_LABEL(item), text);
1039 }
1040
1041 /**
1042  * gtk_banner_set_fraction:
1043  * @parent: #GtkWindow
1044  * @fraction: #gdouble
1045  *
1046  * The fraction is the completion of progressbar, 
1047  * the scale is from 0.0 to 1.0.
1048  * Sets the amount of fraction the progressbar has.
1049  */
1050 void gtk_banner_set_fraction(GtkWindow * parent, gdouble fraction)
1051 {
1052     GtkWidget *item;
1053
1054     g_return_if_fail(GTK_IS_WINDOW(parent) || parent == NULL);
1055
1056     item = gtk_banner_get_widget(parent, item_quark());
1057
1058     if (GTK_IS_PROGRESS_BAR(item))
1059         gtk_progress_bar_set_fraction(GTK_PROGRESS_BAR(item), fraction);
1060 }
1061
1062 /**
1063  * gtk_banner_close:
1064  * @parent: #GtkWindow
1065  *
1066  * Destroys the banner
1067  */
1068 void gtk_banner_close(GtkWindow * parent)
1069 {
1070     g_return_if_fail(GTK_IS_WINDOW(parent) || parent == NULL);
1071
1072     if (pbanner_refs > 0) {
1073         --pbanner_refs;
1074         if (pbanner_refs == 0 && current_pbanner) {
1075             gtk_msg_window_destroy(current_pbanner->window);
1076         }
1077     }
1078 }
1079
1080 /**
1081  * gtk_banner_temporarily_disable_wrap
1082  * 
1083  * Will disable wrapping for the next shown banner. This only
1084  * affects next banner shown in this application.
1085  **/
1086 void gtk_banner_temporarily_disable_wrap(void)
1087 {
1088     /* The below variable name is intentional. There's no real need for
1089        having two different variables for this functionality. */
1090     gtk_infoprint_temporal_wrap_disable_flag = TRUE;
1091 }
1092
1093 /* We want the timer to be launched only after the infoprint is fully drawn.
1094  * As an approximation, we wait for an idle moment before starting
1095  * the timer. This method is not exact, since it does not guarantee that
1096  * the x-server has gotten around to drawing the window. The only way to be 
1097  * sure would require syncing with the x-server, but this should be close 
1098  * enough.
1099  */
1100 static gboolean infoprint_idle_before_timer (GtkWidget *widget,
1101                                              GdkEventExpose *event,
1102                                              gpointer data)
1103 {
1104     g_idle_add(infoprint_start_timer, widget);
1105     return FALSE;
1106 }
1107
1108 /* Start the actual timer for the infoprint */
1109 static gboolean infoprint_start_timer (gpointer data)
1110 {
1111     if (GTK_IS_WIDGET (data)) {
1112       current_ibanner->timeout = g_timeout_add(MESSAGE_TIMEOUT,
1113               gtk_msg_window_destroy,
1114               current_ibanner->window);
1115       g_signal_connect_swapped(GTK_WIDGET(data), "destroy",
1116               G_CALLBACK(g_source_remove),
1117               GUINT_TO_POINTER(current_ibanner->timeout));
1118     }
1119     return FALSE;
1120 }