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