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