]> Pileus Git - ~andy/gtk/blob - gtk/gtkassistant.c
GtkAssistant: Mention custom pages in the introduction
[~andy/gtk] / gtk / gtkassistant.c
1 /* 
2  * GTK - The GIMP Toolkit
3  * Copyright (C) 1999  Red Hat, Inc.
4  * Copyright (C) 2002  Anders Carlsson <andersca@gnu.org>
5  * Copyright (C) 2003  Matthias Clasen <mclasen@redhat.com>
6  * Copyright (C) 2005  Carlos Garnacho Parro <carlosg@gnome.org>
7  *
8  * All rights reserved.
9  *
10  * This library is free software; you can redistribute it and/or
11  * modify it under the terms of the GNU Lesser General Public
12  * License as published by the Free Software Foundation; either
13  * version 2 of the License, or (at your option) any later version.
14  *
15  * This library is distributed in the hope that it will be useful,
16  * but WITHOUT ANY WARRANTY; without even the implied warranty of
17  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
18  * Lesser General Public License for more details.
19  *
20  * You should have received a copy of the GNU Lesser General Public
21  * License along with this library; if not, write to the
22  * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
23  * Boston, MA 02111-1307, USA.
24  */
25
26 /**
27  * SECTION:gtkassistant
28  * @Short_description: A widget used to guide users through multi-step operations
29  * @Title: GtkAssistant
30  *
31  * A #GtkAssistant is a widget used to represent a generally complex
32  * operation splitted in several steps, guiding the user through its pages
33  * and controlling the page flow to collect the necessary data.
34  *
35  * The design of GtkAssistant is that it controls what buttons to show and
36  * to make sensitive, based on what it knows about the page sequence and
37  * the <link linkend="GtkAssistantPageType">type</link> of each page, in
38  * addition to state information like the page
39  * <link linkend="gtk-assistant-set-page-complete">completion</link> and
40  * <link linkend="gtk-assistant-commit">committed</link> status.
41  *
42  * If you have a case that doesn't quite fit in #GtkAssistants way of
43  * handling buttons, you can use the #GTK_ASSISTANT_PAGE_CUSTOM page type
44  * and handle buttons yourself.
45  *
46  * <refsect2 id="GtkAssistant-BUILDER-UI">
47  * <title>GtkAssistant as GtkBuildable</title>
48  * <para>
49  * The GtkAssistant implementation of the GtkBuildable interface exposes the
50  * @action_area as internal children with the name "action_area".
51  *
52  * To add pages to an assistant in GtkBuilder, simply add it as a
53  * &lt;child&gt; to the GtkAssistant object, and set its child properties
54  * as necessary.
55  * </para>
56  * </refsect2>
57  */
58
59 #include "config.h"
60
61 #include <atk/atk.h>
62
63 #include "gtkassistant.h"
64
65 #include "gtkaccessible.h"
66 #include "gtkbutton.h"
67 #include "gtkhbox.h"
68 #include "gtkhbbox.h"
69 #include "gtkimage.h"
70 #include "gtklabel.h"
71 #include "gtksizegroup.h"
72 #include "gtksizerequest.h"
73 #include "gtkstock.h"
74
75 #include "gtkintl.h"
76 #include "gtkprivate.h"
77 #include "gtkbuildable.h"
78
79
80 #define HEADER_SPACING 12
81 #define ACTION_AREA_SPACING 12
82
83 typedef struct _GtkAssistantPage GtkAssistantPage;
84
85 struct _GtkAssistantPage
86 {
87   GtkWidget *page;
88   GtkAssistantPageType type;
89   guint      complete : 1;
90   guint      complete_set : 1;
91
92   GtkWidget *title;
93   GdkPixbuf *header_image;
94   GdkPixbuf *sidebar_image;
95 };
96
97 struct _GtkAssistantPrivate
98 {
99   GtkWidget *cancel;
100   GtkWidget *forward;
101   GtkWidget *back;
102   GtkWidget *apply;
103   GtkWidget *close;
104   GtkWidget *last;
105
106   GtkWidget *header_image;
107   GtkWidget *sidebar_image;
108
109   GtkWidget *action_area;
110
111   GList     *pages;
112
113   GtkAssistantPage *current_page;
114
115   GSList    *visited_pages;
116
117   GtkSizeGroup *size_group;
118
119   GtkAssistantPageFunc forward_function;
120   gpointer forward_function_data;
121   GDestroyNotify forward_data_destroy;
122
123   guint committed : 1;
124 };
125
126 static void     gtk_assistant_class_init         (GtkAssistantClass *class);
127 static void     gtk_assistant_init               (GtkAssistant      *assistant);
128 static void     gtk_assistant_destroy            (GtkWidget         *widget);
129 static void     gtk_assistant_style_set          (GtkWidget         *widget,
130                                                   GtkStyle          *old_style);
131 static void     gtk_assistant_get_preferred_width  (GtkWidget        *widget,
132                                                     gint             *minimum,
133                                                     gint             *natural);
134 static void     gtk_assistant_get_preferred_height (GtkWidget        *widget,
135                                                     gint             *minimum,
136                                                     gint             *natural);
137 static void     gtk_assistant_size_allocate      (GtkWidget         *widget,
138                                                   GtkAllocation     *allocation);
139 static void     gtk_assistant_map                (GtkWidget         *widget);
140 static void     gtk_assistant_unmap              (GtkWidget         *widget);
141 static gboolean gtk_assistant_delete_event       (GtkWidget         *widget,
142                                                   GdkEventAny       *event);
143 static gboolean gtk_assistant_draw               (GtkWidget         *widget,
144                                                   cairo_t           *cr);
145 static gboolean gtk_assistant_focus              (GtkWidget         *widget,
146                                                   GtkDirectionType   direction);
147 static void     gtk_assistant_add                (GtkContainer      *container,
148                                                   GtkWidget         *page);
149 static void     gtk_assistant_remove             (GtkContainer      *container,
150                                                   GtkWidget         *page);
151 static void     gtk_assistant_forall             (GtkContainer      *container,
152                                                   gboolean           include_internals,
153                                                   GtkCallback        callback,
154                                                   gpointer           callback_data);
155 static void     gtk_assistant_set_child_property (GtkContainer      *container,
156                                                   GtkWidget         *child,
157                                                   guint              property_id,
158                                                   const GValue      *value,
159                                                   GParamSpec        *pspec);
160 static void     gtk_assistant_get_child_property (GtkContainer      *container,
161                                                   GtkWidget         *child,
162                                                   guint              property_id,
163                                                   GValue            *value,
164                                                   GParamSpec        *pspec);
165
166 static AtkObject *gtk_assistant_get_accessible   (GtkWidget         *widget);
167
168 static void       gtk_assistant_buildable_interface_init     (GtkBuildableIface *iface);
169 static GObject   *gtk_assistant_buildable_get_internal_child (GtkBuildable  *buildable,
170                                                               GtkBuilder    *builder,
171                                                               const gchar   *childname);
172 static gboolean   gtk_assistant_buildable_custom_tag_start   (GtkBuildable  *buildable,
173                                                               GtkBuilder    *builder,
174                                                               GObject       *child,
175                                                               const gchar   *tagname,
176                                                               GMarkupParser *parser,
177                                                               gpointer      *data);
178 static void       gtk_assistant_buildable_custom_finished    (GtkBuildable  *buildable,
179                                                               GtkBuilder    *builder,
180                                                               GObject       *child,
181                                                               const gchar   *tagname,
182                                                               gpointer       user_data);
183
184 static GList*     find_page                                  (GtkAssistant  *assistant,
185                                                               GtkWidget     *page);
186
187 enum
188 {
189   CHILD_PROP_0,
190   CHILD_PROP_PAGE_TYPE,
191   CHILD_PROP_PAGE_TITLE,
192   CHILD_PROP_PAGE_HEADER_IMAGE,
193   CHILD_PROP_PAGE_SIDEBAR_IMAGE,
194   CHILD_PROP_PAGE_COMPLETE
195 };
196
197 enum
198 {
199   CANCEL,
200   PREPARE,
201   APPLY,
202   CLOSE,
203   LAST_SIGNAL
204 };
205
206 static guint signals [LAST_SIGNAL] = { 0 };
207
208
209 G_DEFINE_TYPE_WITH_CODE (GtkAssistant, gtk_assistant, GTK_TYPE_WINDOW,
210                          G_IMPLEMENT_INTERFACE (GTK_TYPE_BUILDABLE,
211                                                 gtk_assistant_buildable_interface_init))
212
213
214 static void
215 gtk_assistant_class_init (GtkAssistantClass *class)
216 {
217   GObjectClass *gobject_class;
218   GtkWidgetClass *widget_class;
219   GtkContainerClass *container_class;
220
221   gobject_class   = (GObjectClass *) class;
222   widget_class    = (GtkWidgetClass *) class;
223   container_class = (GtkContainerClass *) class;
224
225   widget_class->destroy = gtk_assistant_destroy;
226   widget_class->style_set = gtk_assistant_style_set;
227   widget_class->get_preferred_width = gtk_assistant_get_preferred_width;
228   widget_class->get_preferred_height = gtk_assistant_get_preferred_height;
229   widget_class->size_allocate = gtk_assistant_size_allocate;
230   widget_class->map = gtk_assistant_map;
231   widget_class->unmap = gtk_assistant_unmap;
232   widget_class->delete_event = gtk_assistant_delete_event;
233   widget_class->draw = gtk_assistant_draw;
234   widget_class->focus = gtk_assistant_focus;
235   widget_class->get_accessible = gtk_assistant_get_accessible;
236
237   container_class->add = gtk_assistant_add;
238   container_class->remove = gtk_assistant_remove;
239   container_class->forall = gtk_assistant_forall;
240   container_class->set_child_property = gtk_assistant_set_child_property;
241   container_class->get_child_property = gtk_assistant_get_child_property;
242
243   /**
244    * GtkAssistant::cancel:
245    * @assistant: the #GtkAssistant
246    *
247    * The ::cancel signal is emitted when then the cancel button is clicked.
248    *
249    * Since: 2.10
250    */
251   signals[CANCEL] =
252     g_signal_new (I_("cancel"),
253                   G_TYPE_FROM_CLASS (gobject_class),
254                   G_SIGNAL_RUN_LAST,
255                   G_STRUCT_OFFSET (GtkAssistantClass, cancel),
256                   NULL, NULL,
257                   g_cclosure_marshal_VOID__VOID,
258                   G_TYPE_NONE, 0);
259     
260   /**
261    * GtkAssistant::prepare:
262    * @assistant: the #GtkAssistant
263    * @page: the current page
264    *
265    * The ::prepare signal is emitted when a new page is set as the assistant's
266    * current page, before making the new page visible. A handler for this signal
267    * can do any preparation which are necessary before showing @page.
268    *
269    * Since: 2.10
270    */
271   signals[PREPARE] =
272     g_signal_new (I_("prepare"),
273                   G_TYPE_FROM_CLASS (gobject_class),
274                   G_SIGNAL_RUN_LAST,
275                   G_STRUCT_OFFSET (GtkAssistantClass, prepare),
276                   NULL, NULL,
277                   g_cclosure_marshal_VOID__OBJECT,
278                   G_TYPE_NONE, 1, GTK_TYPE_WIDGET);
279
280   /**
281    * GtkAssistant::apply:
282    * @assistant: the #GtkAssistant
283    *
284    * The ::apply signal is emitted when the apply button is clicked. The default
285    * behavior of the #GtkAssistant is to switch to the page after the current
286    * page, unless the current page is the last one.
287    *
288    * A handler for the ::apply signal should carry out the actions for which
289    * the wizard has collected data. If the action takes a long time to complete,
290    * you might consider putting a page of type %GTK_ASSISTANT_PAGE_PROGRESS
291    * after the confirmation page and handle this operation within the
292    * #GtkAssistant::prepare signal of the progress page.
293    *
294    * Since: 2.10
295    */
296   signals[APPLY] =
297     g_signal_new (I_("apply"),
298                   G_TYPE_FROM_CLASS (gobject_class),
299                   G_SIGNAL_RUN_LAST,
300                   G_STRUCT_OFFSET (GtkAssistantClass, apply),
301                   NULL, NULL,
302                   g_cclosure_marshal_VOID__VOID,
303                   G_TYPE_NONE, 0);
304
305   /**
306    * GtkAssistant::close:
307    * @assistant: the #GtkAssistant
308    *
309    * The ::close signal is emitted either when the close button of
310    * a summary page is clicked, or when the apply button in the last
311    * page in the flow (of type %GTK_ASSISTANT_PAGE_CONFIRM) is clicked.
312    *
313    * Since: 2.10
314    */
315   signals[CLOSE] =
316     g_signal_new (I_("close"),
317                   G_TYPE_FROM_CLASS (gobject_class),
318                   G_SIGNAL_RUN_LAST,
319                   G_STRUCT_OFFSET (GtkAssistantClass, close),
320                   NULL, NULL,
321                   g_cclosure_marshal_VOID__VOID,
322                   G_TYPE_NONE, 0);
323
324   gtk_widget_class_install_style_property (widget_class,
325                                            g_param_spec_int ("header-padding",
326                                                              P_("Header Padding"),
327                                                              P_("Number of pixels around the header."),
328                                                              0,
329                                                              G_MAXINT,
330                                                              6,
331                                                              GTK_PARAM_READABLE));
332   gtk_widget_class_install_style_property (widget_class,
333                                            g_param_spec_int ("content-padding",
334                                                              P_("Content Padding"),
335                                                              P_("Number of pixels around the content pages."),
336                                                              0,
337                                                              G_MAXINT,
338                                                              1,
339                                                              GTK_PARAM_READABLE));
340
341   /**
342    * GtkAssistant:page-type:
343    *
344    * The type of the assistant page.
345    *
346    * Since: 2.10
347    */
348   gtk_container_class_install_child_property (container_class,
349                                               CHILD_PROP_PAGE_TYPE,
350                                               g_param_spec_enum ("page-type", 
351                                                                  P_("Page type"),
352                                                                  P_("The type of the assistant page"),
353                                                                  GTK_TYPE_ASSISTANT_PAGE_TYPE,
354                                                                  GTK_ASSISTANT_PAGE_CONTENT,
355                                                                  GTK_PARAM_READWRITE));
356
357   /**
358    * GtkAssistant:title:
359    *
360    * The title that is displayed in the page header.
361    *
362    * If title and header-image are both %NULL, no header is displayed.
363    *
364    * Since: 2.10
365    */
366   gtk_container_class_install_child_property (container_class,
367                                               CHILD_PROP_PAGE_TITLE,
368                                               g_param_spec_string ("title", 
369                                                                    P_("Page title"),
370                                                                    P_("The title of the assistant page"),
371                                                                    NULL,
372                                                                    GTK_PARAM_READWRITE));
373
374   /**
375    * GtkAssistant:header-image:
376    *
377    * The image that is displayed next to the title in the page header.
378    *
379    * If title and header-image are both %NULL, no header is displayed.
380    *
381    * Since: 2.10
382    */
383   gtk_container_class_install_child_property (container_class,
384                                               CHILD_PROP_PAGE_HEADER_IMAGE,
385                                               g_param_spec_object ("header-image", 
386                                                                    P_("Header image"),
387                                                                    P_("Header image for the assistant page"),
388                                                                    GDK_TYPE_PIXBUF,
389                                                                    GTK_PARAM_READWRITE));
390
391   /**
392    * GtkAssistant:header-image:
393    *
394    * The image that is displayed next to the page.
395    *
396    * Set this to %NULL to make the sidebar disappear.
397    *
398    * Since: 2.10
399    */
400   gtk_container_class_install_child_property (container_class,
401                                               CHILD_PROP_PAGE_SIDEBAR_IMAGE,
402                                               g_param_spec_object ("sidebar-image", 
403                                                                    P_("Sidebar image"),
404                                                                    P_("Sidebar image for the assistant page"),
405                                                                    GDK_TYPE_PIXBUF,
406                                                                    GTK_PARAM_READWRITE));
407   /**
408    * GtkAssistant:complete:
409    *
410    * Setting the "complete" child property to %TRUE marks a page as complete
411    * (i.e.: all the required fields are filled out). GTK+ uses this information
412    * to control the sensitivity of the navigation buttons.
413    *
414    * Since: 2.10
415    **/
416   gtk_container_class_install_child_property (container_class,
417                                               CHILD_PROP_PAGE_COMPLETE,
418                                               g_param_spec_boolean ("complete", 
419                                                                     P_("Page complete"),
420                                                                     P_("Whether all required fields on the page have been filled out"),
421                                                                     FALSE,
422                                                                     G_PARAM_READWRITE));
423
424   g_type_class_add_private (gobject_class, sizeof (GtkAssistantPrivate));
425 }
426
427 static gint
428 default_forward_function (gint current_page, gpointer data)
429 {
430   GtkAssistant *assistant;
431   GtkAssistantPrivate *priv;
432   GtkAssistantPage *page_info;
433   GList *page_node;
434
435   assistant = GTK_ASSISTANT (data);
436   priv = assistant->priv;
437
438   page_node = g_list_nth (priv->pages, ++current_page);
439
440   if (!page_node)
441     return -1;
442
443   page_info = (GtkAssistantPage *) page_node->data;
444
445   while (page_node && !gtk_widget_get_visible (page_info->page))
446     {
447       page_node = page_node->next;
448       current_page++;
449
450       if (page_node)
451         page_info = (GtkAssistantPage *) page_node->data;
452     }
453
454   return current_page;
455 }
456
457 static void
458 compute_last_button_state (GtkAssistant *assistant)
459 {
460   GtkAssistantPrivate *priv = assistant->priv;
461   GtkAssistantPage *page_info, *current_page_info;
462   gint count, page_num, n_pages;
463
464   count = 0;
465   page_num = gtk_assistant_get_current_page (assistant);
466   n_pages  = gtk_assistant_get_n_pages (assistant);
467   current_page_info = page_info = g_list_nth_data (priv->pages, page_num);
468
469   while (page_num >= 0 && page_num < n_pages &&
470          page_info->type == GTK_ASSISTANT_PAGE_CONTENT &&
471          (count == 0 || page_info->complete) &&
472          count < n_pages)
473     {
474       page_num = (priv->forward_function) (page_num, priv->forward_function_data);
475       page_info = g_list_nth_data (priv->pages, page_num);
476
477       count++;
478     }
479
480   /* make the last button visible if we can skip multiple
481    * pages and end on a confirmation or summary page
482    */
483   if (count > 1 && page_info &&
484       (page_info->type == GTK_ASSISTANT_PAGE_CONFIRM ||
485        page_info->type == GTK_ASSISTANT_PAGE_SUMMARY))
486     {
487       gtk_widget_show (priv->last);
488       gtk_widget_set_sensitive (priv->last,
489                                 current_page_info->complete);
490     }
491   else
492     gtk_widget_hide (priv->last);
493 }
494
495 static void
496 compute_progress_state (GtkAssistant *assistant)
497 {
498   GtkAssistantPrivate *priv = assistant->priv;
499   gint page_num, n_pages;
500
501   n_pages = gtk_assistant_get_n_pages (assistant);
502   page_num = gtk_assistant_get_current_page (assistant);
503
504   page_num = (priv->forward_function) (page_num, priv->forward_function_data);
505
506   if (page_num >= 0 && page_num < n_pages)
507     gtk_widget_show (priv->forward);
508   else
509     gtk_widget_hide (priv->forward);
510 }
511
512 static void
513 set_assistant_header_image (GtkAssistant *assistant)
514 {
515   GtkAssistantPrivate *priv = assistant->priv;
516
517   gtk_image_set_from_pixbuf (GTK_IMAGE (priv->header_image),
518                              priv->current_page->header_image);
519 }
520
521 static void
522 set_assistant_sidebar_image (GtkAssistant *assistant)
523 {
524   GtkAssistantPrivate *priv = assistant->priv;
525
526   gtk_image_set_from_pixbuf (GTK_IMAGE (priv->sidebar_image),
527                              priv->current_page->sidebar_image);
528
529   if (priv->current_page->sidebar_image)
530     gtk_widget_show (priv->sidebar_image);
531   else
532     gtk_widget_hide (priv->sidebar_image);
533 }
534
535 static void
536 set_assistant_buttons_state (GtkAssistant *assistant)
537 {
538   GtkAssistantPrivate *priv = assistant->priv;
539
540   if (!priv->current_page)
541     return;
542   
543   switch (priv->current_page->type)
544     {
545     case GTK_ASSISTANT_PAGE_INTRO:
546       gtk_widget_set_sensitive (priv->cancel, TRUE);
547       gtk_widget_set_sensitive (priv->forward, priv->current_page->complete);
548       gtk_widget_grab_default (priv->forward);
549       gtk_widget_show (priv->forward);
550       gtk_widget_hide (priv->back);
551       gtk_widget_hide (priv->apply);
552       gtk_widget_hide (priv->close);
553       compute_last_button_state (assistant);
554       break;
555     case GTK_ASSISTANT_PAGE_CONFIRM:
556       gtk_widget_set_sensitive (priv->cancel, TRUE);
557       gtk_widget_set_sensitive (priv->back, TRUE);
558       gtk_widget_set_sensitive (priv->apply, priv->current_page->complete);
559       gtk_widget_grab_default (priv->apply);
560       gtk_widget_show (priv->back);
561       gtk_widget_show (priv->apply);
562       gtk_widget_hide (priv->forward);
563       gtk_widget_hide (priv->close);
564       gtk_widget_hide (priv->last);
565       break;
566     case GTK_ASSISTANT_PAGE_CONTENT:
567       gtk_widget_set_sensitive (priv->cancel, TRUE);
568       gtk_widget_set_sensitive (priv->back, TRUE);
569       gtk_widget_set_sensitive (priv->forward, priv->current_page->complete);
570       gtk_widget_grab_default (priv->forward);
571       gtk_widget_show (priv->back);
572       gtk_widget_show (priv->forward);
573       gtk_widget_hide (priv->apply);
574       gtk_widget_hide (priv->close);
575       compute_last_button_state (assistant);
576       break;
577     case GTK_ASSISTANT_PAGE_SUMMARY:
578       gtk_widget_set_sensitive (priv->close, priv->current_page->complete);
579       gtk_widget_grab_default (priv->close);
580       gtk_widget_show (priv->close);
581       gtk_widget_hide (priv->back);
582       gtk_widget_hide (priv->forward);
583       gtk_widget_hide (priv->apply);
584       gtk_widget_hide (priv->last);
585       break;
586     case GTK_ASSISTANT_PAGE_PROGRESS:
587       gtk_widget_set_sensitive (priv->cancel, priv->current_page->complete);
588       gtk_widget_set_sensitive (priv->back, priv->current_page->complete);
589       gtk_widget_set_sensitive (priv->forward, priv->current_page->complete);
590       gtk_widget_grab_default (priv->forward);
591       gtk_widget_show (priv->back);
592       gtk_widget_hide (priv->apply);
593       gtk_widget_hide (priv->close);
594       gtk_widget_hide (priv->last);
595       compute_progress_state (assistant);
596       break;
597     case GTK_ASSISTANT_PAGE_CUSTOM:
598       gtk_widget_hide (priv->cancel);
599       gtk_widget_hide (priv->back);
600       gtk_widget_hide (priv->forward);
601       gtk_widget_hide (priv->apply);
602       gtk_widget_hide (priv->last);
603       gtk_widget_hide (priv->close);
604       break;
605     default:
606       g_assert_not_reached ();
607     }
608
609   if (priv->committed)
610     gtk_widget_hide (priv->cancel);
611   else if (priv->current_page->type == GTK_ASSISTANT_PAGE_SUMMARY ||
612            priv->current_page->type == GTK_ASSISTANT_PAGE_CUSTOM)
613     gtk_widget_hide (priv->cancel);
614   else
615     gtk_widget_show (priv->cancel);
616
617   /* this is quite general, we don't want to
618    * go back if it's the first page */
619   if (!priv->visited_pages)
620     gtk_widget_hide (priv->back);
621 }
622
623 static void
624 set_current_page (GtkAssistant     *assistant,
625                   GtkAssistantPage *page)
626 {
627   GtkAssistantPrivate *priv = assistant->priv;
628   GtkAssistantPage *old_page;
629
630   if (priv->current_page &&
631       gtk_widget_is_drawable (priv->current_page->page))
632     old_page = priv->current_page;
633   else
634     old_page = NULL;
635
636   priv->current_page = page;
637
638   set_assistant_buttons_state (assistant);
639   set_assistant_header_image (assistant);
640   set_assistant_sidebar_image (assistant);
641
642   g_signal_emit (assistant, signals [PREPARE], 0, priv->current_page->page);
643
644   if (gtk_widget_get_visible (priv->current_page->page) && gtk_widget_get_mapped (GTK_WIDGET (assistant)))
645     {
646       gtk_widget_set_child_visible (priv->current_page->page, TRUE);
647       gtk_widget_map (priv->current_page->page);
648       gtk_widget_map (priv->current_page->title);
649     }
650   
651   if (old_page && gtk_widget_get_mapped (old_page->page))
652     {
653       gtk_widget_set_child_visible (old_page->page, FALSE);
654       gtk_widget_unmap (old_page->page);
655       gtk_widget_unmap (old_page->title);
656     }
657
658   if (!gtk_widget_child_focus (priv->current_page->page, GTK_DIR_TAB_FORWARD))
659     {
660       GtkWidget *button[6];
661       gint i;
662
663       /* find the best button to focus */
664       button[0] = priv->apply;
665       button[1] = priv->close;
666       button[2] = priv->forward;
667       button[3] = priv->back;
668       button[4] = priv->cancel;
669       button[5] = priv->last;
670       for (i = 0; i < 6; i++)
671         {
672           if (gtk_widget_get_visible (button[i]) && gtk_widget_get_sensitive (button[i]))
673             {
674               gtk_widget_grab_focus (button[i]);
675               break;
676             }
677         }
678     }
679
680   gtk_widget_queue_resize (GTK_WIDGET (assistant));
681 }
682
683 static gint
684 compute_next_step (GtkAssistant *assistant)
685 {
686   GtkAssistantPrivate *priv = assistant->priv;
687   GtkAssistantPage *page_info;
688   gint current_page, n_pages, next_page;
689
690   current_page = gtk_assistant_get_current_page (assistant);
691   page_info = priv->current_page;
692   n_pages = gtk_assistant_get_n_pages (assistant);
693
694   next_page = (priv->forward_function) (current_page,
695                                         priv->forward_function_data);
696
697   if (next_page >= 0 && next_page < n_pages)
698     {
699       priv->visited_pages = g_slist_prepend (priv->visited_pages, page_info);
700       set_current_page (assistant, g_list_nth_data (priv->pages, next_page));
701
702       return TRUE;
703     }
704
705   return FALSE;
706 }
707
708 static void
709 on_assistant_close (GtkWidget    *widget,
710                     GtkAssistant *assistant)
711 {
712   g_signal_emit (assistant, signals [CLOSE], 0, NULL);
713 }
714
715 static void
716 on_assistant_apply (GtkWidget    *widget,
717                     GtkAssistant *assistant)
718 {
719   gboolean success;
720
721   g_signal_emit (assistant, signals [APPLY], 0);
722
723   success = compute_next_step (assistant);
724
725   /* if the assistant hasn't switched to another page, just emit
726    * the CLOSE signal, it't the last page in the assistant flow
727    */
728   if (!success)
729     g_signal_emit (assistant, signals [CLOSE], 0);
730 }
731
732 static void
733 on_assistant_forward (GtkWidget    *widget,
734                       GtkAssistant *assistant)
735 {
736   gtk_assistant_next_page (assistant);
737 }
738
739 static void
740 on_assistant_back (GtkWidget    *widget,
741                    GtkAssistant *assistant)
742 {
743   gtk_assistant_previous_page (assistant);
744 }
745
746 static void
747 on_assistant_cancel (GtkWidget    *widget,
748                      GtkAssistant *assistant)
749 {
750   g_signal_emit (assistant, signals [CANCEL], 0, NULL);
751 }
752
753 static void
754 on_assistant_last (GtkWidget    *widget,
755                    GtkAssistant *assistant)
756 {
757   GtkAssistantPrivate *priv = assistant->priv;
758
759   while (priv->current_page->type == GTK_ASSISTANT_PAGE_CONTENT &&
760          priv->current_page->complete)
761     compute_next_step (assistant);
762 }
763
764 static gboolean
765 alternative_button_order (GtkAssistant *assistant)
766 {
767   GtkSettings *settings;
768   GdkScreen *screen;
769   gboolean result;
770
771   screen   = gtk_widget_get_screen (GTK_WIDGET (assistant));
772   settings = gtk_settings_get_for_screen (screen);
773
774   g_object_get (settings,
775                 "gtk-alternative-button-order", &result,
776                 NULL);
777   return result;
778 }
779
780 static void
781 gtk_assistant_init (GtkAssistant *assistant)
782 {
783   GtkAssistantPrivate *priv;
784
785   assistant->priv = G_TYPE_INSTANCE_GET_PRIVATE (assistant,
786                                                  GTK_TYPE_ASSISTANT,
787                                                  GtkAssistantPrivate);
788   priv = assistant->priv;
789
790   gtk_container_set_reallocate_redraws (GTK_CONTAINER (assistant), TRUE);
791   gtk_container_set_border_width (GTK_CONTAINER (assistant), 12);
792
793   gtk_widget_push_composite_child ();
794
795   /* Header */
796   priv->header_image = gtk_image_new ();
797   gtk_misc_set_alignment (GTK_MISC (priv->header_image), 1., 0.5);
798   gtk_widget_set_parent (priv->header_image, GTK_WIDGET (assistant));
799   gtk_widget_show (priv->header_image);
800
801   /* Sidebar */
802   priv->sidebar_image = gtk_image_new ();
803   gtk_misc_set_alignment (GTK_MISC (priv->sidebar_image), 0., 0.);
804   gtk_widget_set_parent (priv->sidebar_image, GTK_WIDGET (assistant));
805   gtk_widget_show (priv->sidebar_image);
806
807   /* Action area  */
808   priv->action_area  = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 6);
809
810   priv->close   = gtk_button_new_from_stock (GTK_STOCK_CLOSE);
811   priv->apply   = gtk_button_new_from_stock (GTK_STOCK_APPLY);
812   priv->forward = gtk_button_new_from_stock (GTK_STOCK_GO_FORWARD);
813   priv->back    = gtk_button_new_from_stock (GTK_STOCK_GO_BACK);
814   priv->cancel  = gtk_button_new_from_stock (GTK_STOCK_CANCEL);
815   priv->last    = gtk_button_new_from_stock (GTK_STOCK_GOTO_LAST);
816   gtk_widget_set_can_default (priv->close, TRUE);
817   gtk_widget_set_can_default (priv->apply, TRUE);
818   gtk_widget_set_can_default (priv->forward, TRUE);
819
820   priv->size_group   = gtk_size_group_new (GTK_SIZE_GROUP_HORIZONTAL);
821   gtk_size_group_add_widget (priv->size_group, priv->close);
822   gtk_size_group_add_widget (priv->size_group, priv->apply);
823   gtk_size_group_add_widget (priv->size_group, priv->forward);
824   gtk_size_group_add_widget (priv->size_group, priv->back);
825   gtk_size_group_add_widget (priv->size_group, priv->cancel);
826   gtk_size_group_add_widget (priv->size_group, priv->last);
827
828   if (!alternative_button_order (assistant))
829     {
830       gtk_box_pack_end (GTK_BOX (priv->action_area), priv->apply, FALSE, FALSE, 0);
831       gtk_box_pack_end (GTK_BOX (priv->action_area), priv->forward, FALSE, FALSE, 0);
832       gtk_box_pack_end (GTK_BOX (priv->action_area), priv->back, FALSE, FALSE, 0);
833       gtk_box_pack_end (GTK_BOX (priv->action_area), priv->last, FALSE, FALSE, 0);
834       gtk_box_pack_end (GTK_BOX (priv->action_area), priv->cancel, FALSE, FALSE, 0);
835       gtk_box_pack_end (GTK_BOX (priv->action_area), priv->close, FALSE, FALSE, 0);
836     }
837   else
838     {
839       gtk_box_pack_end (GTK_BOX (priv->action_area), priv->close, FALSE, FALSE, 0);
840       gtk_box_pack_end (GTK_BOX (priv->action_area), priv->cancel, FALSE, FALSE, 0);
841       gtk_box_pack_end (GTK_BOX (priv->action_area), priv->apply, FALSE, FALSE, 0);
842       gtk_box_pack_end (GTK_BOX (priv->action_area), priv->forward, FALSE, FALSE, 0);
843       gtk_box_pack_end (GTK_BOX (priv->action_area), priv->back, FALSE, FALSE, 0);
844       gtk_box_pack_end (GTK_BOX (priv->action_area), priv->last, FALSE, FALSE, 0);
845     }
846
847   gtk_widget_set_parent (priv->action_area, GTK_WIDGET (assistant));
848   gtk_widget_show (priv->forward);
849   gtk_widget_show (priv->back);
850   gtk_widget_show (priv->cancel);
851   gtk_widget_show (priv->action_area);
852
853   gtk_widget_pop_composite_child ();
854
855   priv->pages = NULL;
856   priv->current_page = NULL;
857   priv->visited_pages = NULL;
858
859   priv->forward_function = default_forward_function;
860   priv->forward_function_data = assistant;
861   priv->forward_data_destroy = NULL;
862
863   g_signal_connect (G_OBJECT (priv->close), "clicked",
864                     G_CALLBACK (on_assistant_close), assistant);
865   g_signal_connect (G_OBJECT (priv->apply), "clicked",
866                     G_CALLBACK (on_assistant_apply), assistant);
867   g_signal_connect (G_OBJECT (priv->forward), "clicked",
868                     G_CALLBACK (on_assistant_forward), assistant);
869   g_signal_connect (G_OBJECT (priv->back), "clicked",
870                     G_CALLBACK (on_assistant_back), assistant);
871   g_signal_connect (G_OBJECT (priv->cancel), "clicked",
872                     G_CALLBACK (on_assistant_cancel), assistant);
873   g_signal_connect (G_OBJECT (priv->last), "clicked",
874                     G_CALLBACK (on_assistant_last), assistant);
875 }
876
877 static void
878 gtk_assistant_set_child_property (GtkContainer    *container,
879                                   GtkWidget       *child,
880                                   guint            property_id,
881                                   const GValue    *value,
882                                   GParamSpec      *pspec)
883 {
884   switch (property_id)
885     {
886     case CHILD_PROP_PAGE_TYPE:
887       gtk_assistant_set_page_type (GTK_ASSISTANT (container), child,
888                                    g_value_get_enum (value));
889       break;
890     case CHILD_PROP_PAGE_TITLE:
891       gtk_assistant_set_page_title (GTK_ASSISTANT (container), child,
892                                     g_value_get_string (value));
893       break;
894     case CHILD_PROP_PAGE_HEADER_IMAGE:
895       gtk_assistant_set_page_header_image (GTK_ASSISTANT (container), child,
896                                            g_value_get_object (value));
897       break;
898     case CHILD_PROP_PAGE_SIDEBAR_IMAGE:
899       gtk_assistant_set_page_side_image (GTK_ASSISTANT (container), child,
900                                          g_value_get_object (value));
901       break;
902     case CHILD_PROP_PAGE_COMPLETE:
903       gtk_assistant_set_page_complete (GTK_ASSISTANT (container), child,
904                                        g_value_get_boolean (value));
905       break;
906     default:
907       GTK_CONTAINER_WARN_INVALID_CHILD_PROPERTY_ID (container, property_id, pspec);
908       break;
909     }
910 }
911
912 static void
913 gtk_assistant_get_child_property (GtkContainer *container,
914                                   GtkWidget    *child,
915                                   guint         property_id,
916                                   GValue       *value,
917                                   GParamSpec   *pspec)
918 {
919   switch (property_id)
920     {
921     case CHILD_PROP_PAGE_TYPE:
922       g_value_set_enum (value,
923                         gtk_assistant_get_page_type (GTK_ASSISTANT (container), child));
924       break;
925     case CHILD_PROP_PAGE_TITLE:
926       g_value_set_string (value,
927                           gtk_assistant_get_page_title (GTK_ASSISTANT (container), child));
928       break;
929     case CHILD_PROP_PAGE_HEADER_IMAGE:
930       g_value_set_object (value,
931                           gtk_assistant_get_page_header_image (GTK_ASSISTANT (container), child));
932       break;
933     case CHILD_PROP_PAGE_SIDEBAR_IMAGE:
934       g_value_set_object (value,
935                           gtk_assistant_get_page_side_image (GTK_ASSISTANT (container), child));
936       break;
937     case CHILD_PROP_PAGE_COMPLETE:
938       g_value_set_boolean (value,
939                            gtk_assistant_get_page_complete (GTK_ASSISTANT (container), child));
940       break;
941     default:
942       GTK_CONTAINER_WARN_INVALID_CHILD_PROPERTY_ID (container, property_id, pspec);
943       break;
944     }
945 }
946
947 static void
948 on_page_notify_visibility (GtkWidget  *widget,
949                            GParamSpec *arg,
950                            gpointer    data)
951 {
952   GtkAssistant *assistant = GTK_ASSISTANT (data);
953
954   /* update buttons state, flow may have changed */
955   if (gtk_widget_get_mapped (GTK_WIDGET (assistant)))
956     set_assistant_buttons_state (assistant);
957 }
958
959 static void
960 remove_page (GtkAssistant *assistant, 
961              GList        *element)
962 {
963   GtkAssistantPrivate *priv = assistant->priv;
964   GtkAssistantPage *page_info;
965   GList *page_node;
966
967   page_info = element->data;
968
969   /* If this is the current page, we need to switch away. */
970   if (page_info == priv->current_page)
971     {
972       if (!compute_next_step (assistant))
973         {
974           /* The best we can do at this point is probably to pick the first
975            * visible page.
976            */
977           page_node = priv->pages;
978
979           while (page_node && !gtk_widget_get_visible (((GtkAssistantPage *) page_node->data)->page))
980             page_node = page_node->next;
981
982           if (page_node == element)
983             page_node = page_node->next;
984
985           if (page_node)
986             priv->current_page = page_node->data;
987           else
988             priv->current_page = NULL;
989         }
990     }
991
992   priv->pages = g_list_remove_link (priv->pages, element);
993   priv->visited_pages = g_slist_remove_all (priv->visited_pages, page_info);
994
995   g_signal_handlers_disconnect_by_func (page_info->page, on_page_notify_visibility, assistant);
996   gtk_widget_unparent (page_info->page);
997
998   if (page_info->header_image)
999     g_object_unref (page_info->header_image);
1000
1001   if (page_info->sidebar_image)
1002     g_object_unref (page_info->sidebar_image);
1003
1004   gtk_widget_destroy (page_info->title);
1005   g_slice_free (GtkAssistantPage, page_info);
1006   g_list_free_1 (element);
1007 }
1008
1009 static void
1010 gtk_assistant_destroy (GtkWidget *widget)
1011 {
1012   GtkAssistant *assistant = GTK_ASSISTANT (widget);
1013   GtkAssistantPrivate *priv = assistant->priv;
1014
1015   if (priv->header_image)
1016     {
1017       gtk_widget_destroy (priv->header_image);
1018       priv->header_image = NULL;
1019     }
1020
1021   if (priv->sidebar_image)
1022     {
1023       gtk_widget_destroy (priv->sidebar_image);
1024       priv->sidebar_image = NULL;
1025     }
1026
1027   if (priv->action_area)
1028     {
1029       gtk_widget_destroy (priv->action_area);
1030       priv->action_area = NULL;
1031     }
1032
1033   if (priv->size_group)
1034     {
1035       g_object_unref (priv->size_group);
1036       priv->size_group = NULL;
1037     }
1038
1039   if (priv->forward_function)
1040     {
1041       if (priv->forward_function_data &&
1042           priv->forward_data_destroy)
1043         priv->forward_data_destroy (priv->forward_function_data);
1044
1045       priv->forward_function = NULL;
1046       priv->forward_function_data = NULL;
1047       priv->forward_data_destroy = NULL;
1048     }
1049
1050   if (priv->visited_pages)
1051     {
1052       g_slist_free (priv->visited_pages);
1053       priv->visited_pages = NULL;
1054     }
1055
1056   /* We set current to NULL so that the remove code doesn't try
1057    * to do anything funny */
1058   priv->current_page = NULL;
1059
1060   while (priv->pages)
1061     remove_page (assistant, priv->pages);
1062
1063   GTK_WIDGET_CLASS (gtk_assistant_parent_class)->destroy (widget);
1064 }
1065
1066 static GList*
1067 find_page (GtkAssistant  *assistant,
1068            GtkWidget     *page)
1069 {
1070   GtkAssistantPrivate *priv = assistant->priv;
1071   GList *child = priv->pages;
1072   
1073   while (child)
1074     {
1075       GtkAssistantPage *page_info = child->data;
1076       if (page_info->page == page)
1077         return child;
1078
1079       child = child->next;
1080     }
1081   
1082   return NULL;
1083 }
1084
1085 static void
1086 set_title_colors (GtkWidget *assistant,
1087                   GtkWidget *title_label)
1088 {
1089   GtkStyle *style;
1090
1091   gtk_widget_ensure_style (assistant);
1092   style = gtk_widget_get_style (assistant);
1093
1094   /* change colors schema, for making the header text visible */
1095   gtk_widget_modify_bg (title_label, GTK_STATE_NORMAL, &style->bg[GTK_STATE_SELECTED]);
1096   gtk_widget_modify_fg (title_label, GTK_STATE_NORMAL, &style->fg[GTK_STATE_SELECTED]);
1097 }
1098
1099 static void
1100 set_title_font (GtkWidget *assistant,
1101                 GtkWidget *title_label)
1102 {
1103   PangoFontDescription *desc;
1104   gint size;
1105
1106   desc = pango_font_description_new ();
1107   size = pango_font_description_get_size (gtk_widget_get_style (assistant)->font_desc);
1108
1109   pango_font_description_set_weight (desc, PANGO_WEIGHT_ULTRABOLD);
1110   pango_font_description_set_size   (desc, size * PANGO_SCALE_XX_LARGE);
1111
1112   gtk_widget_modify_font (title_label, desc);
1113   pango_font_description_free (desc);
1114 }
1115
1116 static void
1117 gtk_assistant_style_set (GtkWidget *widget,
1118                          GtkStyle  *old_style)
1119 {
1120   GtkAssistant *assistant = GTK_ASSISTANT (widget);
1121   GtkAssistantPrivate *priv = assistant->priv;
1122   GList *list;
1123
1124   list = priv->pages;
1125
1126   while (list)
1127     {
1128       GtkAssistantPage *page = list->data;
1129
1130       set_title_colors (widget, page->title);
1131       set_title_font (widget, page->title);
1132
1133       list = list->next;
1134     }
1135 }
1136
1137 static void
1138 gtk_assistant_size_request (GtkWidget      *widget,
1139                             GtkRequisition *requisition)
1140 {
1141   GtkAssistant *assistant = GTK_ASSISTANT (widget);
1142   GtkAssistantPrivate *priv = assistant->priv;
1143   GtkRequisition child_requisition;
1144   gint header_padding, content_padding;
1145   gint width, height, header_width, header_height;
1146   guint border_width;
1147   GList *list;
1148
1149   gtk_widget_style_get (widget,
1150                         "header-padding", &header_padding,
1151                         "content-padding", &content_padding,
1152                         NULL);
1153   width = height = 0;
1154   header_width = header_height = 0;
1155   list  = priv->pages;
1156
1157   while (list)
1158     {
1159       GtkAssistantPage *page = list->data;
1160       gint w, h;
1161
1162       gtk_widget_get_preferred_size (page->page,
1163                                      &child_requisition, NULL);
1164       width  = MAX (width,  child_requisition.width);
1165       height = MAX (height, child_requisition.height);
1166
1167       gtk_widget_get_preferred_size (page->title,
1168                                      &child_requisition, NULL);
1169       w = child_requisition.width;
1170       h = child_requisition.height;
1171
1172       if (page->header_image)
1173         {
1174           w += gdk_pixbuf_get_width (page->header_image) + HEADER_SPACING;
1175           h  = MAX (h, gdk_pixbuf_get_height (page->header_image));
1176         }
1177
1178       header_width  = MAX (header_width, w);
1179       header_height = MAX (header_height, h);
1180
1181       list = list->next;
1182     }
1183
1184   gtk_widget_get_preferred_size (priv->sidebar_image,
1185                                  &child_requisition, NULL);
1186   width  += child_requisition.width;
1187   height  = MAX (height, child_requisition.height);
1188
1189   gtk_widget_set_size_request (priv->header_image, header_width, header_height);
1190   gtk_widget_get_preferred_size (priv->header_image,
1191                                  &child_requisition, NULL);
1192   width   = MAX (width, header_width) + 2 * header_padding;
1193   height += header_height + 2 * header_padding;
1194
1195   gtk_widget_get_preferred_size (priv->action_area,
1196                                  &child_requisition, NULL);
1197   width   = MAX (width, child_requisition.width);
1198   height += child_requisition.height + ACTION_AREA_SPACING;
1199
1200   border_width = gtk_container_get_border_width (GTK_CONTAINER (widget));
1201   width += border_width * 2 + content_padding * 2;
1202   height += border_width * 2 + content_padding * 2;
1203
1204   requisition->width = width;
1205   requisition->height = height;
1206 }
1207
1208 static void
1209 gtk_assistant_get_preferred_width (GtkWidget *widget,
1210                                    gint      *minimum,
1211                                    gint      *natural)
1212 {
1213   GtkRequisition requisition;
1214
1215   gtk_assistant_size_request (widget, &requisition);
1216
1217   *minimum = *natural = requisition.width;
1218 }
1219
1220 static void
1221 gtk_assistant_get_preferred_height (GtkWidget *widget,
1222                                     gint      *minimum,
1223                                     gint      *natural)
1224 {
1225   GtkRequisition requisition;
1226
1227   gtk_assistant_size_request (widget, &requisition);
1228
1229   *minimum = *natural = requisition.height;
1230 }
1231
1232 static void
1233 gtk_assistant_size_allocate (GtkWidget      *widget,
1234                              GtkAllocation  *allocation)
1235 {
1236   GtkAssistant *assistant = GTK_ASSISTANT (widget);
1237   GtkAssistantPrivate *priv = assistant->priv;
1238   GtkRequisition header_requisition, action_requisition, sidebar_requisition;
1239   GtkAllocation child_allocation, header_allocation;
1240   GtkAllocation action_area_allocation, header_image_allocation;
1241   gint header_padding, content_padding;
1242   guint border_width;
1243   gboolean rtl;
1244   GList *pages;
1245
1246   rtl   = (gtk_widget_get_direction (widget) == GTK_TEXT_DIR_RTL);
1247   pages = priv->pages;
1248
1249   gtk_widget_style_get (widget,
1250                         "header-padding", &header_padding,
1251                         "content-padding", &content_padding,
1252                         NULL);
1253
1254   gtk_widget_set_allocation (widget, allocation);
1255   border_width = gtk_container_get_border_width (GTK_CONTAINER (widget));
1256
1257   /* Header */
1258   gtk_widget_get_preferred_size (priv->header_image,
1259                                  &header_requisition, NULL);
1260
1261   header_allocation.x = border_width + header_padding;
1262   header_allocation.y = border_width + header_padding;
1263   header_allocation.width  = allocation->width - 2 * border_width - 2 * header_padding;
1264   header_allocation.height = header_requisition.height;
1265
1266   gtk_widget_size_allocate (priv->header_image, &header_allocation);
1267
1268   /* Action area */
1269   gtk_widget_get_preferred_size (priv->action_area,
1270                                  &action_requisition, NULL);
1271
1272   child_allocation.x = border_width;
1273   child_allocation.y = allocation->height - border_width - action_requisition.height;
1274   child_allocation.width  = allocation->width - 2 * border_width;
1275   child_allocation.height = action_requisition.height;
1276
1277   gtk_widget_size_allocate (priv->action_area, &child_allocation);
1278
1279   gtk_widget_get_allocation (priv->header_image, &header_image_allocation);
1280   gtk_widget_get_allocation (priv->action_area, &action_area_allocation);
1281
1282   /* Sidebar */
1283   gtk_widget_get_preferred_size (priv->sidebar_image,
1284                                  &sidebar_requisition, NULL);
1285
1286   if (rtl)
1287     child_allocation.x = allocation->width - border_width - sidebar_requisition.width;
1288   else
1289     child_allocation.x = border_width;
1290
1291   child_allocation.y = border_width + header_image_allocation.height + 2 * header_padding;
1292   child_allocation.width = sidebar_requisition.width;
1293   child_allocation.height = allocation->height - 2 * border_width -
1294     header_image_allocation.height - 2 * header_padding - action_area_allocation.height;
1295
1296   gtk_widget_size_allocate (priv->sidebar_image, &child_allocation);
1297
1298   /* Pages */
1299   child_allocation.x = border_width + content_padding;
1300   child_allocation.y = border_width +
1301     header_image_allocation.height + 2 * header_padding + content_padding;
1302   child_allocation.width  = allocation->width - 2 * border_width - 2 * content_padding;
1303   child_allocation.height = allocation->height - 2 * border_width -
1304     header_image_allocation.height - 2 * header_padding - ACTION_AREA_SPACING - action_area_allocation.height - 2 * content_padding;
1305
1306   if (gtk_widget_get_visible (priv->sidebar_image))
1307     {
1308       GtkAllocation sidebar_image_allocation;
1309
1310       gtk_widget_get_allocation (priv->sidebar_image, &sidebar_image_allocation);
1311
1312       if (!rtl)
1313         child_allocation.x += sidebar_image_allocation.width;
1314
1315       child_allocation.width -= sidebar_image_allocation.width;
1316     }
1317
1318   while (pages)
1319     {
1320       GtkAssistantPage *page = pages->data;
1321
1322       gtk_widget_size_allocate (page->page, &child_allocation);
1323       gtk_widget_size_allocate (page->title, &header_allocation);
1324       pages = pages->next;
1325     }
1326 }
1327
1328 static void
1329 gtk_assistant_map (GtkWidget *widget)
1330 {
1331   GtkAssistant *assistant = GTK_ASSISTANT (widget);
1332   GtkAssistantPrivate *priv = assistant->priv;
1333   GList *page_node;
1334   GtkAssistantPage *page;
1335
1336   gtk_widget_set_mapped (widget, TRUE);
1337
1338   gtk_widget_map (priv->header_image);
1339   gtk_widget_map (priv->action_area);
1340
1341   if (gtk_widget_get_visible (priv->sidebar_image) &&
1342       !gtk_widget_get_mapped (priv->sidebar_image))
1343     gtk_widget_map (priv->sidebar_image);
1344
1345   /* if there's no default page, pick the first one */
1346   page = NULL;
1347   if (!priv->current_page)
1348     {
1349       page_node = priv->pages;
1350
1351       while (page_node && !gtk_widget_get_visible (((GtkAssistantPage *) page_node->data)->page))
1352         page_node = page_node->next;
1353
1354       if (page_node)
1355         page = page_node->data;
1356     }
1357
1358   if (page &&
1359       gtk_widget_get_visible (page->page) &&
1360       !gtk_widget_get_mapped (page->page))
1361     set_current_page (assistant, page);
1362
1363   GTK_WIDGET_CLASS (gtk_assistant_parent_class)->map (widget);
1364 }
1365
1366 static void
1367 gtk_assistant_unmap (GtkWidget *widget)
1368 {
1369   GtkAssistant *assistant = GTK_ASSISTANT (widget);
1370   GtkAssistantPrivate *priv = assistant->priv;
1371
1372   gtk_widget_set_mapped (widget, FALSE);
1373
1374   gtk_widget_unmap (priv->header_image);
1375   gtk_widget_unmap (priv->action_area);
1376
1377   if (gtk_widget_is_drawable (priv->sidebar_image))
1378     gtk_widget_unmap (priv->sidebar_image);
1379
1380   if (priv->current_page &&
1381       gtk_widget_is_drawable (priv->current_page->page))
1382     gtk_widget_unmap (priv->current_page->page);
1383
1384   g_slist_free (priv->visited_pages);
1385   priv->visited_pages = NULL;
1386   priv->current_page  = NULL;
1387
1388   GTK_WIDGET_CLASS (gtk_assistant_parent_class)->unmap (widget);
1389 }
1390
1391 static gboolean
1392 gtk_assistant_delete_event (GtkWidget   *widget,
1393                             GdkEventAny *event)
1394 {
1395   GtkAssistant *assistant = GTK_ASSISTANT (widget);
1396   GtkAssistantPrivate *priv = assistant->priv;
1397
1398   /* Do not allow cancelling in the middle of a progress page */
1399   if (priv->current_page &&
1400       (priv->current_page->type != GTK_ASSISTANT_PAGE_PROGRESS ||
1401        priv->current_page->complete))
1402     g_signal_emit (widget, signals [CANCEL], 0, NULL);
1403
1404   return TRUE;
1405 }
1406
1407 static void
1408 assistant_paint_colored_box (GtkWidget *widget,
1409                              cairo_t   *cr)
1410 {
1411   GtkAssistant *assistant = GTK_ASSISTANT (widget);
1412   GtkAssistantPrivate *priv = assistant->priv;
1413   GtkAllocation allocation, action_area_allocation, header_image_allocation;
1414   GtkStyle *style;
1415   gint border_width, header_padding, content_padding;
1416   gint content_x, content_width;
1417   gboolean rtl;
1418
1419   rtl  = (gtk_widget_get_direction (widget) == GTK_TEXT_DIR_RTL);
1420   border_width = gtk_container_get_border_width (GTK_CONTAINER (widget));
1421
1422   gtk_widget_style_get (widget,
1423                         "header-padding",  &header_padding,
1424                         "content-padding", &content_padding,
1425                         NULL);
1426
1427   style = gtk_widget_get_style (widget);
1428   gtk_widget_get_allocation (widget, &allocation);
1429   gtk_widget_get_allocation (priv->action_area, &action_area_allocation);
1430   gtk_widget_get_allocation (priv->header_image, &header_image_allocation);
1431
1432   /* colored box */
1433   gdk_cairo_set_source_color (cr, &style->bg[GTK_STATE_SELECTED]);
1434   cairo_rectangle (cr,
1435                    border_width,
1436                    border_width,
1437                    allocation.width - 2 * border_width,
1438                    allocation.height - action_area_allocation.height - 2 * border_width - ACTION_AREA_SPACING);
1439   cairo_fill (cr);
1440
1441   /* content box */
1442   content_x = content_padding + border_width;
1443   content_width = allocation.width - 2 * content_padding - 2 * border_width;
1444
1445   if (gtk_widget_get_visible (priv->sidebar_image))
1446     {
1447       GtkAllocation sidebar_image_allocation;
1448
1449       gtk_widget_get_allocation (priv->sidebar_image, &sidebar_image_allocation);
1450
1451       if (!rtl)
1452         content_x += sidebar_image_allocation.width;
1453       content_width -= sidebar_image_allocation.width;
1454     }
1455
1456   gdk_cairo_set_source_color (cr, &style->bg[GTK_STATE_NORMAL]);
1457
1458   cairo_rectangle (cr,
1459                    content_x,
1460                    header_image_allocation.height + content_padding + 2 * header_padding + border_width,
1461                    content_width,
1462                    allocation.height - 2 * border_width - action_area_allocation.height -
1463                    header_image_allocation.height - 2 * content_padding - 2 * header_padding - ACTION_AREA_SPACING);
1464   cairo_fill (cr);
1465 }
1466
1467 static gboolean
1468 gtk_assistant_draw (GtkWidget *widget,
1469                     cairo_t   *cr)
1470 {
1471   GtkAssistant *assistant = GTK_ASSISTANT (widget);
1472   GtkAssistantPrivate *priv = assistant->priv;
1473   GtkContainer *container = GTK_CONTAINER (widget);
1474
1475   if (GTK_WIDGET_CLASS (gtk_assistant_parent_class)->draw)
1476     GTK_WIDGET_CLASS (gtk_assistant_parent_class)->draw (widget, cr);
1477
1478   assistant_paint_colored_box (widget, cr);
1479
1480   gtk_container_propagate_draw (container, priv->header_image, cr);
1481   gtk_container_propagate_draw (container, priv->sidebar_image, cr);
1482   gtk_container_propagate_draw (container, priv->action_area, cr);
1483
1484   if (priv->current_page)
1485     {
1486       gtk_container_propagate_draw (container, priv->current_page->page, cr);
1487       gtk_container_propagate_draw (container, priv->current_page->title, cr);
1488     }
1489
1490   return FALSE;
1491 }
1492
1493 static gboolean
1494 gtk_assistant_focus (GtkWidget        *widget,
1495                      GtkDirectionType  direction)
1496 {
1497   GtkAssistantPrivate *priv;
1498   GtkContainer *container;
1499
1500   container = GTK_CONTAINER (widget);
1501   priv = GTK_ASSISTANT (widget)->priv;
1502
1503   /* we only have to care about 2 widgets, action area and the current page */
1504   if (gtk_container_get_focus_child (container) == priv->action_area)
1505     {
1506       if (!gtk_widget_child_focus (priv->action_area, direction) &&
1507           (priv->current_page == NULL ||
1508            !gtk_widget_child_focus (priv->current_page->page, direction)))
1509         {
1510           /* if we're leaving the action area and the current page hasn't
1511              any focusable widget, clear focus and go back to the action area */
1512           gtk_container_set_focus_child (GTK_CONTAINER (priv->action_area), NULL);
1513           gtk_widget_child_focus (priv->action_area, direction);
1514         }
1515     }
1516   else
1517     {
1518       if ((priv->current_page ==  NULL ||
1519            !gtk_widget_child_focus (priv->current_page->page, direction)) &&
1520           !gtk_widget_child_focus (priv->action_area, direction))
1521         {
1522           /* if we're leaving the current page and there isn't nothing focusable
1523              in the action area, try to clear focus and go back to the page */
1524           gtk_window_set_focus (GTK_WINDOW (widget), NULL);
1525           if (priv->current_page != NULL)
1526             gtk_widget_child_focus (priv->current_page->page, direction);
1527         }
1528     }
1529
1530   return TRUE;
1531 }
1532
1533 static void
1534 gtk_assistant_add (GtkContainer *container,
1535                    GtkWidget    *page)
1536 {
1537   gtk_assistant_append_page (GTK_ASSISTANT (container), page);
1538 }
1539
1540 static void
1541 gtk_assistant_remove (GtkContainer *container,
1542                       GtkWidget    *page)
1543 {
1544   GtkAssistant *assistant = (GtkAssistant*) container;
1545   GList *element;
1546
1547   element = find_page (assistant, page);
1548
1549   if (element)
1550     {
1551       remove_page (assistant, element);
1552       gtk_widget_queue_resize ((GtkWidget *) container);
1553     }
1554 }
1555
1556 static void
1557 gtk_assistant_forall (GtkContainer *container,
1558                       gboolean      include_internals,
1559                       GtkCallback   callback,
1560                       gpointer      callback_data)
1561 {
1562   GtkAssistant *assistant = (GtkAssistant*) container;
1563   GtkAssistantPrivate *priv = assistant->priv;
1564   GList *pages;
1565
1566   if (include_internals)
1567     {
1568       (*callback) (priv->header_image, callback_data);
1569       (*callback) (priv->sidebar_image, callback_data);
1570       (*callback) (priv->action_area, callback_data);
1571     }
1572
1573   pages = priv->pages;
1574
1575   while (pages)
1576     {
1577       GtkAssistantPage *page = (GtkAssistantPage *) pages->data;
1578
1579       (*callback) (page->page, callback_data);
1580
1581       if (include_internals)
1582         (*callback) (page->title, callback_data);
1583
1584       pages = pages->next;
1585     }
1586 }
1587
1588 /**
1589  * gtk_assistant_new:
1590  * 
1591  * Creates a new #GtkAssistant.
1592  *
1593  * Return value: a newly created #GtkAssistant
1594  *
1595  * Since: 2.10
1596  **/
1597 GtkWidget*
1598 gtk_assistant_new (void)
1599 {
1600   GtkWidget *assistant;
1601
1602   assistant = g_object_new (GTK_TYPE_ASSISTANT, NULL);
1603
1604   return assistant;
1605 }
1606
1607 /**
1608  * gtk_assistant_get_current_page:
1609  * @assistant: a #GtkAssistant
1610  *
1611  * Returns the page number of the current page
1612  *
1613  * Return value: The index (starting from 0) of the current page in
1614  * the @assistant, if the @assistant has no pages, -1 will be returned
1615  *
1616  * Since: 2.10
1617  **/
1618 gint
1619 gtk_assistant_get_current_page (GtkAssistant *assistant)
1620 {
1621   GtkAssistantPrivate *priv;
1622
1623   g_return_val_if_fail (GTK_IS_ASSISTANT (assistant), -1);
1624
1625   priv = assistant->priv;
1626
1627   if (!priv->pages || !priv->current_page)
1628     return -1;
1629
1630   return g_list_index (priv->pages, priv->current_page);
1631 }
1632
1633 /**
1634  * gtk_assistant_set_current_page:
1635  * @assistant: a #GtkAssistant
1636  * @page_num: index of the page to switch to, starting from 0.
1637  *            If negative, the last page will be used. If greater
1638  *            than the number of pages in the @assistant, nothing
1639  *            will be done.
1640  *
1641  * Switches the page to @page_num. Note that this will only be necessary
1642  * in custom buttons, as the @assistant flow can be set with
1643  * gtk_assistant_set_forward_page_func().
1644  *
1645  * Since: 2.10
1646  **/
1647 void
1648 gtk_assistant_set_current_page (GtkAssistant *assistant,
1649                                 gint          page_num)
1650 {
1651   GtkAssistantPrivate *priv;
1652   GtkAssistantPage *page;
1653
1654   g_return_if_fail (GTK_IS_ASSISTANT (assistant));
1655
1656   priv = assistant->priv;
1657
1658   if (page_num >= 0)
1659     page = (GtkAssistantPage *) g_list_nth_data (priv->pages, page_num);
1660   else
1661     page = (GtkAssistantPage *) g_list_last (priv->pages)->data;
1662
1663   g_return_if_fail (page != NULL);
1664
1665   if (priv->current_page == page)
1666     return;
1667
1668   /* only add the page to the visited list if the
1669    * assistant is mapped, if not, just use it as an
1670    * initial page setting, for the cases where the
1671    * initial page is != to 0
1672    */
1673   if (gtk_widget_get_mapped (GTK_WIDGET (assistant)))
1674     priv->visited_pages = g_slist_prepend (priv->visited_pages,
1675                                            priv->current_page);
1676
1677   set_current_page (assistant, page);
1678 }
1679
1680 /**
1681  * gtk_assistant_next_page:
1682  * @assistant: a #GtkAssistant
1683  *
1684  * Navigate to the next page. It is a programming
1685  *  error to call this function if there is no next page.
1686  *
1687  * This function is for use when creating pages of the
1688  *  #GTK_ASSISTANT_PAGE_CUSTOM type.
1689  *
1690  * Since: 3.0
1691  **/
1692 void
1693 gtk_assistant_next_page (GtkAssistant *assistant)
1694 {
1695   GtkAssistantPrivate *priv;
1696
1697   g_return_if_fail (GTK_IS_ASSISTANT (assistant));
1698
1699   priv = assistant->priv;
1700
1701   if (!compute_next_step (assistant))
1702     g_critical ("Page flow is broken, you may want to end it with a page of "
1703                 "type GTK_ASSISTANT_PAGE_CONFIRM or GTK_ASSISTANT_PAGE_SUMMARY");
1704 }
1705
1706 /**
1707  * gtk_assistant_previous_page:
1708  * @assistant: a #GtkAssistant
1709  *
1710  * Navigate to the previous visited page. It is a programming
1711  *  error to call this function if no previous page is
1712  *  available.
1713  *
1714  * This function is for use when creating pages of the
1715  *  #GTK_ASSISTANT_PAGE_CUSTOM type.
1716  *
1717  * Since: 3.0
1718  **/
1719 void
1720 gtk_assistant_previous_page (GtkAssistant *assistant)
1721 {
1722   GtkAssistantPrivate *priv;
1723   GtkAssistantPage *page_info;
1724   GSList *page_node;
1725
1726   g_return_if_fail (GTK_IS_ASSISTANT (assistant));
1727
1728   priv = assistant->priv;
1729
1730   /* skip the progress pages when going back */
1731   do
1732     {
1733       page_node = priv->visited_pages;
1734
1735       g_return_if_fail (page_node != NULL);
1736
1737       priv->visited_pages = priv->visited_pages->next;
1738       page_info = (GtkAssistantPage *) page_node->data;
1739       g_slist_free_1 (page_node);
1740     }
1741   while (page_info->type == GTK_ASSISTANT_PAGE_PROGRESS ||
1742          !gtk_widget_get_visible (page_info->page));
1743
1744   set_current_page (assistant, page_info);
1745 }
1746
1747 /**
1748  * gtk_assistant_get_n_pages:
1749  * @assistant: a #GtkAssistant
1750  *
1751  * Returns the number of pages in the @assistant
1752  *
1753  * Return value: The number of pages in the @assistant.
1754  *
1755  * Since: 2.10
1756  **/
1757 gint
1758 gtk_assistant_get_n_pages (GtkAssistant *assistant)
1759 {
1760   GtkAssistantPrivate *priv;
1761
1762   g_return_val_if_fail (GTK_IS_ASSISTANT (assistant), 0);
1763
1764   priv = assistant->priv;
1765
1766   return g_list_length (priv->pages);
1767 }
1768
1769 /**
1770  * gtk_assistant_get_nth_page:
1771  * @assistant: a #GtkAssistant
1772  * @page_num: The index of a page in the @assistant, or -1 to get the last page;
1773  *
1774  * Returns the child widget contained in page number @page_num.
1775  *
1776  * Return value: (transfer none): The child widget, or %NULL
1777  *     if @page_num is out of bounds.
1778  *
1779  * Since: 2.10
1780  **/
1781 GtkWidget*
1782 gtk_assistant_get_nth_page (GtkAssistant *assistant,
1783                             gint          page_num)
1784 {
1785   GtkAssistantPrivate *priv;
1786   GtkAssistantPage *page;
1787   GList *elem;
1788
1789   g_return_val_if_fail (GTK_IS_ASSISTANT (assistant), NULL);
1790   g_return_val_if_fail (page_num >= -1, NULL);
1791
1792   priv = assistant->priv;
1793
1794   if (page_num == -1)
1795     elem = g_list_last (priv->pages);
1796   else
1797     elem = g_list_nth (priv->pages, page_num);
1798
1799   if (!elem)
1800     return NULL;
1801
1802   page = (GtkAssistantPage *) elem->data;
1803
1804   return page->page;
1805 }
1806
1807 /**
1808  * gtk_assistant_prepend_page:
1809  * @assistant: a #GtkAssistant
1810  * @page: a #GtkWidget
1811  *
1812  * Prepends a page to the @assistant.
1813  *
1814  * Return value: the index (starting at 0) of the inserted page
1815  *
1816  * Since: 2.10
1817  **/
1818 gint
1819 gtk_assistant_prepend_page (GtkAssistant *assistant,
1820                             GtkWidget    *page)
1821 {
1822   g_return_val_if_fail (GTK_IS_ASSISTANT (assistant), 0);
1823   g_return_val_if_fail (GTK_IS_WIDGET (page), 0);
1824
1825   return gtk_assistant_insert_page (assistant, page, 0);
1826 }
1827
1828 /**
1829  * gtk_assistant_append_page:
1830  * @assistant: a #GtkAssistant
1831  * @page: a #GtkWidget
1832  *
1833  * Appends a page to the @assistant.
1834  *
1835  * Return value: the index (starting at 0) of the inserted page
1836  *
1837  * Since: 2.10
1838  **/
1839 gint
1840 gtk_assistant_append_page (GtkAssistant *assistant,
1841                            GtkWidget    *page)
1842 {
1843   g_return_val_if_fail (GTK_IS_ASSISTANT (assistant), 0);
1844   g_return_val_if_fail (GTK_IS_WIDGET (page), 0);
1845
1846   return gtk_assistant_insert_page (assistant, page, -1);
1847 }
1848
1849 /**
1850  * gtk_assistant_insert_page:
1851  * @assistant: a #GtkAssistant
1852  * @page: a #GtkWidget
1853  * @position: the index (starting at 0) at which to insert the page,
1854  *            or -1 to append the page to the @assistant
1855  *
1856  * Inserts a page in the @assistant at a given position.
1857  *
1858  * Return value: the index (starting from 0) of the inserted page
1859  *
1860  * Since: 2.10
1861  **/
1862 gint
1863 gtk_assistant_insert_page (GtkAssistant *assistant,
1864                            GtkWidget    *page,
1865                            gint          position)
1866 {
1867   GtkAssistantPrivate *priv;
1868   GtkAssistantPage *page_info;
1869   gint n_pages;
1870
1871   g_return_val_if_fail (GTK_IS_ASSISTANT (assistant), 0);
1872   g_return_val_if_fail (GTK_IS_WIDGET (page), 0);
1873   g_return_val_if_fail (gtk_widget_get_parent (page) == NULL, 0);
1874   g_return_val_if_fail (!gtk_widget_is_toplevel (page), 0);
1875
1876   priv = assistant->priv;
1877
1878   page_info = g_slice_new0 (GtkAssistantPage);
1879   page_info->page  = page;
1880   page_info->title = gtk_label_new (NULL);
1881
1882   g_signal_connect (G_OBJECT (page), "notify::visible",
1883                     G_CALLBACK (on_page_notify_visibility), assistant);
1884
1885   gtk_misc_set_alignment (GTK_MISC (page_info->title), 0.,0.5);
1886   set_title_colors (GTK_WIDGET (assistant), page_info->title);
1887   set_title_font   (GTK_WIDGET (assistant), page_info->title);
1888   gtk_widget_show  (page_info->title);
1889
1890   n_pages = g_list_length (priv->pages);
1891
1892   if (position < 0 || position > n_pages)
1893     position = n_pages;
1894
1895   priv->pages = g_list_insert (priv->pages, page_info, position);
1896
1897   gtk_widget_set_child_visible (page_info->page, FALSE);
1898   gtk_widget_set_parent (page_info->page,  GTK_WIDGET (assistant));
1899   gtk_widget_set_parent (page_info->title, GTK_WIDGET (assistant));
1900
1901   if (gtk_widget_get_realized (GTK_WIDGET (assistant)))
1902     {
1903       gtk_widget_realize (page_info->page);
1904       gtk_widget_realize (page_info->title);
1905     }
1906
1907   gtk_widget_queue_resize (GTK_WIDGET (assistant));
1908
1909   return position;
1910 }
1911
1912 /**
1913  * gtk_assistant_set_forward_page_func:
1914  * @assistant: a #GtkAssistant
1915  * @page_func: (allow-none): the #GtkAssistantPageFunc, or %NULL to use the default one
1916  * @data: user data for @page_func
1917  * @destroy: destroy notifier for @data
1918  *
1919  * Sets the page forwarding function to be @page_func, this function will
1920  * be used to determine what will be the next page when the user presses
1921  * the forward button. Setting @page_func to %NULL will make the assistant
1922  * to use the default forward function, which just goes to the next visible 
1923  * page.
1924  *
1925  * Since: 2.10
1926  **/
1927 void
1928 gtk_assistant_set_forward_page_func (GtkAssistant         *assistant,
1929                                      GtkAssistantPageFunc  page_func,
1930                                      gpointer              data,
1931                                      GDestroyNotify        destroy)
1932 {
1933   GtkAssistantPrivate *priv;
1934
1935   g_return_if_fail (GTK_IS_ASSISTANT (assistant));
1936
1937   priv = assistant->priv;
1938
1939   if (priv->forward_data_destroy &&
1940       priv->forward_function_data)
1941     (*priv->forward_data_destroy) (priv->forward_function_data);
1942
1943   if (page_func)
1944     {
1945       priv->forward_function = page_func;
1946       priv->forward_function_data = data;
1947       priv->forward_data_destroy = destroy;
1948     }
1949   else
1950     {
1951       priv->forward_function = default_forward_function;
1952       priv->forward_function_data = assistant;
1953       priv->forward_data_destroy = NULL;
1954     }
1955
1956   /* Page flow has possibly changed, so the
1957      buttons state might need to change too */
1958   set_assistant_buttons_state (assistant);
1959 }
1960
1961 /**
1962  * gtk_assistant_add_action_widget:
1963  * @assistant: a #GtkAssistant
1964  * @child: a #GtkWidget
1965  * 
1966  * Adds a widget to the action area of a #GtkAssistant.
1967  *
1968  * Since: 2.10
1969  **/
1970 void
1971 gtk_assistant_add_action_widget (GtkAssistant *assistant,
1972                                  GtkWidget    *child)
1973 {
1974   GtkAssistantPrivate *priv;
1975
1976   g_return_if_fail (GTK_IS_ASSISTANT (assistant));
1977   g_return_if_fail (GTK_IS_WIDGET (child));
1978
1979   priv = assistant->priv;
1980
1981   if (GTK_IS_BUTTON (child))
1982     gtk_size_group_add_widget (priv->size_group, child);
1983
1984   gtk_box_pack_end (GTK_BOX (priv->action_area), child, FALSE, FALSE, 0);
1985 }
1986
1987 /**
1988  * gtk_assistant_remove_action_widget:
1989  * @assistant: a #GtkAssistant
1990  * @child: a #GtkWidget
1991  *
1992  * Removes a widget from the action area of a #GtkAssistant.
1993  *
1994  * Since: 2.10
1995  **/
1996 void
1997 gtk_assistant_remove_action_widget (GtkAssistant *assistant,
1998                                     GtkWidget    *child)
1999 {
2000   GtkAssistantPrivate *priv;
2001
2002   g_return_if_fail (GTK_IS_ASSISTANT (assistant));
2003   g_return_if_fail (GTK_IS_WIDGET (child));
2004
2005   priv = assistant->priv;
2006
2007   if (GTK_IS_BUTTON (child))
2008     gtk_size_group_remove_widget (priv->size_group, child);
2009
2010   gtk_container_remove (GTK_CONTAINER (priv->action_area), child);
2011 }
2012
2013 /**
2014  * gtk_assistant_set_page_title:
2015  * @assistant: a #GtkAssistant
2016  * @page: a page of @assistant
2017  * @title: the new title for @page
2018  * 
2019  * Sets a title for @page. The title is displayed in the header
2020  * area of the assistant when @page is the current page.
2021  *
2022  * Since: 2.10
2023  **/
2024 void
2025 gtk_assistant_set_page_title (GtkAssistant *assistant,
2026                               GtkWidget    *page,
2027                               const gchar  *title)
2028 {
2029   GtkAssistantPage *page_info;
2030   GList *child;
2031
2032   g_return_if_fail (GTK_IS_ASSISTANT (assistant));
2033   g_return_if_fail (GTK_IS_WIDGET (page));
2034
2035   child = find_page (assistant, page);
2036
2037   g_return_if_fail (child != NULL);
2038
2039   page_info = (GtkAssistantPage*) child->data;
2040
2041   gtk_label_set_text ((GtkLabel*) page_info->title, title);
2042   gtk_widget_queue_resize (GTK_WIDGET (assistant));
2043   gtk_widget_child_notify (page, "title");
2044 }
2045
2046 /**
2047  * gtk_assistant_get_page_title:
2048  * @assistant: a #GtkAssistant
2049  * @page: a page of @assistant
2050  * 
2051  * Gets the title for @page. 
2052  * 
2053  * Return value: the title for @page.
2054  *
2055  * Since: 2.10
2056  **/
2057 G_CONST_RETURN gchar*
2058 gtk_assistant_get_page_title (GtkAssistant *assistant,
2059                               GtkWidget    *page)
2060 {
2061   GtkAssistantPage *page_info;
2062   GList *child;
2063
2064   g_return_val_if_fail (GTK_IS_ASSISTANT (assistant), NULL);
2065   g_return_val_if_fail (GTK_IS_WIDGET (page), NULL);
2066
2067   child = find_page (assistant, page);
2068
2069   g_return_val_if_fail (child != NULL, NULL);
2070
2071   page_info = (GtkAssistantPage*) child->data;
2072
2073   return gtk_label_get_text ((GtkLabel*) page_info->title);
2074 }
2075
2076 /**
2077  * gtk_assistant_set_page_type:
2078  * @assistant: a #GtkAssistant
2079  * @page: a page of @assistant
2080  * @type: the new type for @page
2081  * 
2082  * Sets the page type for @page. The page type determines the page
2083  * behavior in the @assistant.
2084  *
2085  * Since: 2.10
2086  **/
2087 void
2088 gtk_assistant_set_page_type (GtkAssistant         *assistant,
2089                              GtkWidget            *page,
2090                              GtkAssistantPageType  type)
2091 {
2092   GtkAssistantPrivate *priv;
2093   GtkAssistantPage *page_info;
2094   GList *child;
2095
2096   g_return_if_fail (GTK_IS_ASSISTANT (assistant));
2097   g_return_if_fail (GTK_IS_WIDGET (page));
2098
2099   priv = assistant->priv;
2100   child = find_page (assistant, page);
2101
2102   g_return_if_fail (child != NULL);
2103
2104   page_info = (GtkAssistantPage*) child->data;
2105
2106   if (type != page_info->type)
2107     {
2108       page_info->type = type;
2109
2110       /* backwards compatibility to the era before fixing bug 604289 */
2111       if (type == GTK_ASSISTANT_PAGE_SUMMARY && !page_info->complete_set)
2112         {
2113           gtk_assistant_set_page_complete (assistant, page, TRUE);
2114           page_info->complete_set = FALSE;
2115         }
2116
2117       /* Always set buttons state, a change in a future page
2118          might change current page buttons */
2119       set_assistant_buttons_state (assistant);
2120
2121       gtk_widget_child_notify (page, "page-type");
2122     }
2123 }
2124
2125 /**
2126  * gtk_assistant_get_page_type:
2127  * @assistant: a #GtkAssistant
2128  * @page: a page of @assistant
2129  *
2130  * Gets the page type of @page.
2131  *
2132  * Return value: the page type of @page.
2133  *
2134  * Since: 2.10
2135  **/
2136 GtkAssistantPageType
2137 gtk_assistant_get_page_type (GtkAssistant *assistant,
2138                              GtkWidget    *page)
2139 {
2140   GtkAssistantPage *page_info;
2141   GList *child;
2142
2143   g_return_val_if_fail (GTK_IS_ASSISTANT (assistant), GTK_ASSISTANT_PAGE_CONTENT);
2144   g_return_val_if_fail (GTK_IS_WIDGET (page), GTK_ASSISTANT_PAGE_CONTENT);
2145
2146   child = find_page (assistant, page);
2147
2148   g_return_val_if_fail (child != NULL, GTK_ASSISTANT_PAGE_CONTENT);
2149
2150   page_info = (GtkAssistantPage*) child->data;
2151
2152   return page_info->type;
2153 }
2154
2155 /**
2156  * gtk_assistant_set_page_header_image:
2157  * @assistant: a #GtkAssistant
2158  * @page: a page of @assistant
2159  * @pixbuf: (allow-none): the new header image @page
2160  *
2161  * Sets a header image for @page. This image is displayed in the header
2162  * area of the assistant when @page is the current page.
2163  *
2164  * Since: 2.10
2165  **/
2166 void
2167 gtk_assistant_set_page_header_image (GtkAssistant *assistant,
2168                                      GtkWidget    *page,
2169                                      GdkPixbuf    *pixbuf)
2170 {
2171   GtkAssistantPrivate *priv;
2172   GtkAssistantPage *page_info;
2173   GList *child;
2174
2175   g_return_if_fail (GTK_IS_ASSISTANT (assistant));
2176   g_return_if_fail (GTK_IS_WIDGET (page));
2177   g_return_if_fail (pixbuf == NULL || GDK_IS_PIXBUF (pixbuf));
2178
2179   priv = assistant->priv;
2180   child = find_page (assistant, page);
2181
2182   g_return_if_fail (child != NULL);
2183
2184   page_info = (GtkAssistantPage*) child->data;
2185
2186   if (pixbuf != page_info->header_image)
2187     {
2188       if (page_info->header_image)
2189         {
2190           g_object_unref (page_info->header_image);
2191           page_info->header_image = NULL;
2192         }
2193
2194       if (pixbuf)
2195         page_info->header_image = g_object_ref (pixbuf);
2196
2197       if (page_info == priv->current_page)
2198         set_assistant_header_image (assistant);
2199
2200       gtk_widget_child_notify (page, "header-image");
2201     }
2202 }
2203
2204 /**
2205  * gtk_assistant_get_page_header_image:
2206  * @assistant: a #GtkAssistant
2207  * @page: a page of @assistant
2208  *
2209  * Gets the header image for @page.
2210  *
2211  * Return value: (transfer none): the header image for @page, or %NULL
2212  *     if there's no header image for the page.
2213  *
2214  * Since: 2.10
2215  **/
2216 GdkPixbuf*
2217 gtk_assistant_get_page_header_image (GtkAssistant *assistant,
2218                                      GtkWidget    *page)
2219 {
2220   GtkAssistantPage *page_info;
2221   GList *child;
2222
2223   g_return_val_if_fail (GTK_IS_ASSISTANT (assistant), NULL);
2224   g_return_val_if_fail (GTK_IS_WIDGET (page), NULL);
2225
2226   child = find_page (assistant, page);
2227
2228   g_return_val_if_fail (child != NULL, NULL);
2229
2230   page_info = (GtkAssistantPage*) child->data;
2231
2232   return page_info->header_image;
2233 }
2234
2235 /**
2236  * gtk_assistant_set_page_side_image:
2237  * @assistant: a #GtkAssistant
2238  * @page: a page of @assistant
2239  * @pixbuf: (allow-none): the new header image @page
2240  *
2241  * Sets a header image for @page. This image is displayed in the side
2242  * area of the assistant when @page is the current page.
2243  *
2244  * Since: 2.10
2245  **/
2246 void
2247 gtk_assistant_set_page_side_image (GtkAssistant *assistant,
2248                                    GtkWidget    *page,
2249                                    GdkPixbuf    *pixbuf)
2250 {
2251   GtkAssistantPrivate *priv;
2252   GtkAssistantPage *page_info;
2253   GList *child;
2254
2255   g_return_if_fail (GTK_IS_ASSISTANT (assistant));
2256   g_return_if_fail (GTK_IS_WIDGET (page));
2257   g_return_if_fail (pixbuf == NULL || GDK_IS_PIXBUF (pixbuf));
2258
2259   priv = assistant->priv;
2260   child = find_page (assistant, page);
2261
2262   g_return_if_fail (child != NULL);
2263
2264   page_info = (GtkAssistantPage*) child->data;
2265
2266   if (pixbuf != page_info->sidebar_image)
2267     {
2268       if (page_info->sidebar_image)
2269         {
2270           g_object_unref (page_info->sidebar_image);
2271           page_info->sidebar_image = NULL;
2272         }
2273
2274       if (pixbuf)
2275         page_info->sidebar_image = g_object_ref (pixbuf);
2276
2277       if (page_info == priv->current_page)
2278         set_assistant_sidebar_image (assistant);
2279
2280       gtk_widget_child_notify (page, "sidebar-image");
2281     }
2282 }
2283
2284 /**
2285  * gtk_assistant_get_page_side_image:
2286  * @assistant: a #GtkAssistant
2287  * @page: a page of @assistant
2288  *
2289  * Gets the header image for @page.
2290  *
2291  * Return value: (transfer none): the side image for @page, or %NULL
2292  *     if there's no side image for the page.
2293  *
2294  * Since: 2.10
2295  **/
2296 GdkPixbuf*
2297 gtk_assistant_get_page_side_image (GtkAssistant *assistant,
2298                                    GtkWidget    *page)
2299 {
2300   GtkAssistantPage *page_info;
2301   GList *child;
2302
2303   g_return_val_if_fail (GTK_IS_ASSISTANT (assistant), NULL);
2304   g_return_val_if_fail (GTK_IS_WIDGET (page), NULL);
2305
2306   child = find_page (assistant, page);
2307
2308   g_return_val_if_fail (child != NULL, NULL);
2309
2310   page_info = (GtkAssistantPage*) child->data;
2311
2312   return page_info->sidebar_image;
2313 }
2314
2315 /**
2316  * gtk_assistant_set_page_complete:
2317  * @assistant: a #GtkAssistant
2318  * @page: a page of @assistant
2319  * @complete: the completeness status of the page
2320  * 
2321  * Sets whether @page contents are complete. This will make
2322  * @assistant update the buttons state to be able to continue the task.
2323  *
2324  * Since: 2.10
2325  **/
2326 void
2327 gtk_assistant_set_page_complete (GtkAssistant *assistant,
2328                                  GtkWidget    *page,
2329                                  gboolean      complete)
2330 {
2331   GtkAssistantPrivate *priv;
2332   GtkAssistantPage *page_info;
2333   GList *child;
2334
2335   g_return_if_fail (GTK_IS_ASSISTANT (assistant));
2336   g_return_if_fail (GTK_IS_WIDGET (page));
2337
2338   priv = assistant->priv;
2339   child = find_page (assistant, page);
2340
2341   g_return_if_fail (child != NULL);
2342
2343   page_info = (GtkAssistantPage*) child->data;
2344
2345   if (complete != page_info->complete)
2346     {
2347       page_info->complete = complete;
2348       page_info->complete_set = TRUE;
2349
2350       /* Always set buttons state, a change in a future page
2351          might change current page buttons */
2352       set_assistant_buttons_state (assistant);
2353
2354       gtk_widget_child_notify (page, "complete");
2355     }
2356 }
2357
2358 /**
2359  * gtk_assistant_get_page_complete:
2360  * @assistant: a #GtkAssistant
2361  * @page: a page of @assistant
2362  * 
2363  * Gets whether @page is complete.
2364  * 
2365  * Return value: %TRUE if @page is complete.
2366  *
2367  * Since: 2.10
2368  **/
2369 gboolean
2370 gtk_assistant_get_page_complete (GtkAssistant *assistant,
2371                                  GtkWidget    *page)
2372 {
2373   GtkAssistantPage *page_info;
2374   GList *child;
2375
2376   g_return_val_if_fail (GTK_IS_ASSISTANT (assistant), FALSE);
2377   g_return_val_if_fail (GTK_IS_WIDGET (page), FALSE);
2378
2379   child = find_page (assistant, page);
2380
2381   g_return_val_if_fail (child != NULL, FALSE);
2382
2383   page_info = (GtkAssistantPage*) child->data;
2384
2385   return page_info->complete;
2386 }
2387
2388 /**
2389  * gtk_assistant_update_buttons_state:
2390  * @assistant: a #GtkAssistant
2391  * 
2392  * Forces @assistant to recompute the buttons state.
2393  * 
2394  * GTK+ automatically takes care of this in most situations, 
2395  * e.g. when the user goes to a different page, or when the
2396  * visibility or completeness of a page changes.
2397  *
2398  * One situation where it can be necessary to call this
2399  * function is when changing a value on the current page
2400  * affects the future page flow of the assistant.
2401  *
2402  * Since: 2.10
2403  **/
2404 void
2405 gtk_assistant_update_buttons_state (GtkAssistant *assistant)
2406 {
2407   g_return_if_fail (GTK_IS_ASSISTANT (assistant));
2408
2409   set_assistant_buttons_state (assistant);
2410 }
2411
2412 /**
2413  * gtk_assistant_commit:
2414  * @assistant: a #GtkAssistant
2415  *
2416  * Erases the visited page history so the back button is not
2417  * shown on the current page, and removes the cancel button
2418  * from subsequent pages.
2419  *
2420  * Use this when the information provided up to the current
2421  * page is hereafter deemed permanent and cannot be modified
2422  * or undone.  For example, showing a progress page to track
2423  * a long-running, unreversible operation after the user has
2424  * clicked apply on a confirmation page.
2425  *
2426  * Since: 2.22
2427  **/
2428 void
2429 gtk_assistant_commit (GtkAssistant *assistant)
2430 {
2431   g_return_if_fail (GTK_IS_ASSISTANT (assistant));
2432
2433   g_slist_free (assistant->priv->visited_pages);
2434   assistant->priv->visited_pages = NULL;
2435
2436   assistant->priv->committed = TRUE;
2437
2438   set_assistant_buttons_state (assistant);
2439 }
2440
2441
2442
2443 /* accessible implementation */
2444
2445 static gint
2446 gtk_assistant_accessible_get_n_children (AtkObject *accessible)
2447 {
2448   GtkAssistant *assistant;
2449   GtkWidget *widget;
2450
2451   widget = gtk_accessible_get_widget (GTK_ACCESSIBLE (accessible));
2452
2453   if (!widget)
2454     return 0;
2455
2456   assistant = GTK_ASSISTANT (widget);
2457    
2458   return g_list_length (assistant->priv->pages) + 1;
2459 }
2460
2461
2462 static AtkObject *
2463 gtk_assistant_accessible_ref_child (AtkObject *accessible,
2464                                     gint       index)
2465 {
2466   GtkAssistant *assistant;
2467   GtkAssistantPrivate *priv;
2468   GtkWidget *widget, *child;
2469   gint n_pages;
2470   AtkObject *obj;
2471   const gchar *title;
2472
2473   widget = gtk_accessible_get_widget (GTK_ACCESSIBLE (accessible));
2474   if (!widget)
2475     return NULL;
2476
2477   assistant = GTK_ASSISTANT (widget);
2478   priv = assistant->priv;
2479   n_pages = g_list_length (priv->pages);
2480
2481   if (index < 0)
2482     return NULL;
2483   else if (index < n_pages)
2484     {
2485       GtkAssistantPage *page = g_list_nth_data (priv->pages, index);
2486
2487       child = page->page;
2488       title = gtk_assistant_get_page_title (assistant, child);
2489     }
2490   else if (index == n_pages)
2491     {
2492       child = priv->action_area;
2493       title = NULL;
2494     }
2495   else
2496     return NULL;
2497   
2498   obj = gtk_widget_get_accessible (child);
2499
2500   if (title)
2501     atk_object_set_name (obj, title);
2502
2503   return g_object_ref (obj);
2504 }
2505
2506 static void
2507 gtk_assistant_accessible_class_init (AtkObjectClass *class)
2508 {
2509   class->get_n_children = gtk_assistant_accessible_get_n_children;
2510   class->ref_child = gtk_assistant_accessible_ref_child;
2511 }
2512
2513 static GType
2514 gtk_assistant_accessible_get_type (void)
2515 {
2516   static GType type = 0;
2517   
2518   if (!type)
2519     {
2520       /*
2521        * Figure out the size of the class and instance
2522        * we are deriving from
2523        */
2524       AtkObjectFactory *factory;
2525       GType derived_type;
2526       GTypeQuery query;
2527       GType derived_atk_type;
2528
2529       derived_type = g_type_parent (GTK_TYPE_ASSISTANT);
2530       factory = atk_registry_get_factory (atk_get_default_registry (),
2531                                           derived_type);
2532       derived_atk_type = atk_object_factory_get_accessible_type (factory);
2533       g_type_query (derived_atk_type, &query);
2534
2535       type = g_type_register_static_simple (derived_atk_type,
2536                                             I_("GtkAssistantAccessible"),
2537                                             query.class_size,
2538                                             (GClassInitFunc) gtk_assistant_accessible_class_init,
2539                                             query.instance_size,
2540                                             NULL, 0);
2541     }
2542
2543   return type;
2544 }
2545
2546 static AtkObject *
2547 gtk_assistant_accessible_new (GObject *obj)
2548 {
2549   AtkObject *accessible;
2550
2551   g_return_val_if_fail (GTK_IS_ASSISTANT (obj), NULL);
2552
2553   accessible = g_object_new (gtk_assistant_accessible_get_type (), NULL);
2554   atk_object_initialize (accessible, obj);
2555
2556   return accessible;
2557 }
2558
2559 static GType
2560 gtk_assistant_accessible_factory_get_accessible_type (void)
2561 {
2562   return gtk_assistant_accessible_get_type ();
2563 }
2564
2565 static AtkObject*
2566 gtk_assistant_accessible_factory_create_accessible (GObject *obj)
2567 {
2568   return gtk_assistant_accessible_new (obj);
2569 }
2570
2571 static void
2572 gtk_assistant_accessible_factory_class_init (AtkObjectFactoryClass *class)
2573 {
2574   class->create_accessible = gtk_assistant_accessible_factory_create_accessible;
2575   class->get_accessible_type = gtk_assistant_accessible_factory_get_accessible_type;
2576 }
2577
2578 static GType
2579 gtk_assistant_accessible_factory_get_type (void)
2580 {
2581   static GType type = 0;
2582
2583   if (!type)
2584     {
2585       type = g_type_register_static_simple (ATK_TYPE_OBJECT_FACTORY,
2586                                             I_("GtkAssistantAccessibleFactory"),
2587                                             sizeof (AtkObjectFactoryClass),
2588                                             (GClassInitFunc) gtk_assistant_accessible_factory_class_init,
2589                                             sizeof (AtkObjectFactory),
2590                                             NULL, 0);
2591     }
2592
2593   return type;
2594 }
2595
2596 static AtkObject *
2597 gtk_assistant_get_accessible (GtkWidget *widget)
2598 {
2599   static gboolean first_time = TRUE;
2600
2601   if (first_time)
2602     {
2603       AtkObjectFactory *factory;
2604       AtkRegistry *registry;
2605       GType derived_type;
2606       GType derived_atk_type;
2607
2608       /*
2609        * Figure out whether accessibility is enabled by looking at the
2610        * type of the accessible object which would be created for
2611        * the parent type of GtkAssistant.
2612        */
2613       derived_type = g_type_parent (GTK_TYPE_ASSISTANT);
2614
2615       registry = atk_get_default_registry ();
2616       factory = atk_registry_get_factory (registry,
2617                                           derived_type);
2618       derived_atk_type = atk_object_factory_get_accessible_type (factory);
2619       if (g_type_is_a (derived_atk_type, GTK_TYPE_ACCESSIBLE))
2620         {
2621           atk_registry_set_factory_type (registry,
2622                                          GTK_TYPE_ASSISTANT,
2623                                          gtk_assistant_accessible_factory_get_type ());
2624         }
2625       first_time = FALSE;
2626     }
2627
2628   return GTK_WIDGET_CLASS (gtk_assistant_parent_class)->get_accessible (widget);
2629 }
2630
2631
2632 static GtkBuildableIface *parent_buildable_iface;
2633
2634 static void
2635 gtk_assistant_buildable_interface_init (GtkBuildableIface *iface)
2636 {
2637   parent_buildable_iface = g_type_interface_peek_parent (iface);
2638   iface->get_internal_child = gtk_assistant_buildable_get_internal_child;
2639   iface->custom_tag_start = gtk_assistant_buildable_custom_tag_start;
2640   iface->custom_finished = gtk_assistant_buildable_custom_finished;
2641 }
2642
2643 static GObject *
2644 gtk_assistant_buildable_get_internal_child (GtkBuildable *buildable,
2645                                             GtkBuilder   *builder,
2646                                             const gchar  *childname)
2647 {
2648     if (strcmp (childname, "action_area") == 0)
2649       return G_OBJECT (GTK_ASSISTANT (buildable)->priv->action_area);
2650
2651     return parent_buildable_iface->get_internal_child (buildable,
2652                                                        builder,
2653                                                        childname);
2654 }
2655
2656 gboolean
2657 gtk_assistant_buildable_custom_tag_start (GtkBuildable  *buildable,
2658                                           GtkBuilder    *builder,
2659                                           GObject       *child,
2660                                           const gchar   *tagname,
2661                                           GMarkupParser *parser,
2662                                           gpointer      *data)
2663 {
2664   return parent_buildable_iface->custom_tag_start (buildable, builder, child,
2665                                                    tagname, parser, data);
2666 }
2667
2668 static void
2669 gtk_assistant_buildable_custom_finished (GtkBuildable *buildable,
2670                                          GtkBuilder   *builder,
2671                                          GObject      *child,
2672                                          const gchar  *tagname,
2673                                          gpointer      user_data)
2674 {
2675   parent_buildable_iface->custom_finished (buildable, builder, child,
2676                                            tagname, user_data);
2677 }