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