gtkassistant.c 71.4 KB
Newer Older
1
/*
Cody Russell's avatar
Cody Russell committed
2
 * GTK - The GIMP Toolkit
3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
 * Copyright (C) 1999  Red Hat, Inc.
 * Copyright (C) 2002  Anders Carlsson <andersca@gnu.org>
 * Copyright (C) 2003  Matthias Clasen <mclasen@redhat.com>
 * Copyright (C) 2005  Carlos Garnacho Parro <carlosg@gnome.org>
 *
 * All rights reserved.
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the
 * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
 * Boston, MA 02111-1307, USA.
 */

26 27 28 29 30 31 32 33 34
/**
 * SECTION:gtkassistant
 * @Short_description: A widget used to guide users through multi-step operations
 * @Title: GtkAssistant
 *
 * A #GtkAssistant is a widget used to represent a generally complex
 * operation splitted in several steps, guiding the user through its pages
 * and controlling the page flow to collect the necessary data.
 *
35 36 37 38 39 40 41
 * The design of GtkAssistant is that it controls what buttons to show and
 * to make sensitive, based on what it knows about the page sequence and
 * the <link linkend="GtkAssistantPageType">type</link> of each page, in
 * addition to state information like the page
 * <link linkend="gtk-assistant-set-page-complete">completion</link> and
 * <link linkend="gtk-assistant-commit">committed</link> status.
 *
42 43 44 45
 * If you have a case that doesn't quite fit in #GtkAssistants way of
 * handling buttons, you can use the #GTK_ASSISTANT_PAGE_CUSTOM page type
 * and handle buttons yourself.
 *
46 47 48 49 50 51 52 53 54 55 56 57 58
 * <refsect2 id="GtkAssistant-BUILDER-UI">
 * <title>GtkAssistant as GtkBuildable</title>
 * <para>
 * The GtkAssistant implementation of the GtkBuildable interface exposes the
 * @action_area as internal children with the name "action_area".
 *
 * To add pages to an assistant in GtkBuilder, simply add it as a
 * &lt;child&gt; to the GtkAssistant object, and set its child properties
 * as necessary.
 * </para>
 * </refsect2>
 */

59
#include "config.h"
60

61 62
#include <atk/atk.h>

63 64 65
#include "gtkassistant.h"

#include "gtkbutton.h"
66
#include "gtkbox.h"
67
#include "gtkframe.h"
68
#include "gtknotebook.h"
69 70 71
#include "gtkimage.h"
#include "gtklabel.h"
#include "gtksizegroup.h"
72
#include "gtksizerequest.h"
73
#include "gtkstock.h"
74
#include "gtktypebuiltins.h"
75 76
#include "gtkintl.h"
#include "gtkprivate.h"
77
#include "gtkbuildable.h"
78
#include "a11y/gtkwindowaccessible.h"
79 80 81


#define HEADER_SPACING 12
82
#define ACTION_AREA_SPACING 12
83 84 85 86 87 88

typedef struct _GtkAssistantPage GtkAssistantPage;

struct _GtkAssistantPage
{
  GtkAssistantPageType type;
89
  guint      complete     : 1;
90
  guint      complete_set : 1;
91

92 93 94 95 96
  gchar *title;

  GtkWidget *page;
  GtkWidget *regular_title;
  GtkWidget *current_title;
97 98 99 100 101 102
  GdkPixbuf *header_image;
  GdkPixbuf *sidebar_image;
};

struct _GtkAssistantPrivate
{
103 104 105 106 107 108 109
  GtkWidget *cancel;
  GtkWidget *forward;
  GtkWidget *back;
  GtkWidget *apply;
  GtkWidget *close;
  GtkWidget *last;

110 111
  GtkWidget *sidebar;
  GtkWidget *content;
112 113 114 115
  GtkWidget *action_area;

  GList     *pages;
  GSList    *visited_pages;
116
  GtkAssistantPage *current_page;
117

118 119
  GtkSizeGroup *button_size_group;
  GtkSizeGroup *title_size_group;
120 121 122 123

  GtkAssistantPageFunc forward_function;
  gpointer forward_function_data;
  GDestroyNotify forward_data_destroy;
124

125 126
  gint extra_buttons;

127
  guint committed : 1;
128 129 130 131
};

static void     gtk_assistant_class_init         (GtkAssistantClass *class);
static void     gtk_assistant_init               (GtkAssistant      *assistant);
132
static void     gtk_assistant_destroy            (GtkWidget         *widget);
133 134 135
static void     gtk_assistant_map                (GtkWidget         *widget);
static void     gtk_assistant_unmap              (GtkWidget         *widget);
static gboolean gtk_assistant_delete_event       (GtkWidget         *widget,
136
                                                  GdkEventAny       *event);
137
static void     gtk_assistant_add                (GtkContainer      *container,
138
                                                  GtkWidget         *page);
139
static void     gtk_assistant_remove             (GtkContainer      *container,
140
                                                  GtkWidget         *page);
141
static void     gtk_assistant_set_child_property (GtkContainer      *container,
142 143 144 145
                                                  GtkWidget         *child,
                                                  guint              property_id,
                                                  const GValue      *value,
                                                  GParamSpec        *pspec);
146
static void     gtk_assistant_get_child_property (GtkContainer      *container,
147 148 149 150
                                                  GtkWidget         *child,
                                                  guint              property_id,
                                                  GValue            *value,
                                                  GParamSpec        *pspec);
151

152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167
static void       gtk_assistant_buildable_interface_init     (GtkBuildableIface *iface);
static GObject   *gtk_assistant_buildable_get_internal_child (GtkBuildable  *buildable,
                                                              GtkBuilder    *builder,
                                                              const gchar   *childname);
static gboolean   gtk_assistant_buildable_custom_tag_start   (GtkBuildable  *buildable,
                                                              GtkBuilder    *builder,
                                                              GObject       *child,
                                                              const gchar   *tagname,
                                                              GMarkupParser *parser,
                                                              gpointer      *data);
static void       gtk_assistant_buildable_custom_finished    (GtkBuildable  *buildable,
                                                              GtkBuilder    *builder,
                                                              GObject       *child,
                                                              const gchar   *tagname,
                                                              gpointer       user_data);

168 169
static GList*     find_page                                  (GtkAssistant  *assistant,
                                                              GtkWidget     *page);
170 171 172 173 174 175
static void       gtk_assistant_do_set_page_header_image     (GtkAssistant  *assistant,
                                                              GtkWidget     *page,
                                                              GdkPixbuf     *pixbuf);
static void       gtk_assistant_do_set_page_side_image       (GtkAssistant  *assistant,
                                                              GtkWidget     *page,
                                                              GdkPixbuf     *pixbuf);
176

177 178
GType             _gtk_assistant_accessible_get_type         (void);

179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200
enum
{
  CHILD_PROP_0,
  CHILD_PROP_PAGE_TYPE,
  CHILD_PROP_PAGE_TITLE,
  CHILD_PROP_PAGE_HEADER_IMAGE,
  CHILD_PROP_PAGE_SIDEBAR_IMAGE,
  CHILD_PROP_PAGE_COMPLETE
};

enum
{
  CANCEL,
  PREPARE,
  APPLY,
  CLOSE,
  LAST_SIGNAL
};

static guint signals [LAST_SIGNAL] = { 0 };


201 202 203
G_DEFINE_TYPE_WITH_CODE (GtkAssistant, gtk_assistant, GTK_TYPE_WINDOW,
                         G_IMPLEMENT_INTERFACE (GTK_TYPE_BUILDABLE,
                                                gtk_assistant_buildable_interface_init))
204 205 206 207 208 209 210 211 212 213 214 215 216


static void
gtk_assistant_class_init (GtkAssistantClass *class)
{
  GObjectClass *gobject_class;
  GtkWidgetClass *widget_class;
  GtkContainerClass *container_class;

  gobject_class   = (GObjectClass *) class;
  widget_class    = (GtkWidgetClass *) class;
  container_class = (GtkContainerClass *) class;

217
  widget_class->destroy = gtk_assistant_destroy;
218 219 220
  widget_class->map = gtk_assistant_map;
  widget_class->unmap = gtk_assistant_unmap;
  widget_class->delete_event = gtk_assistant_delete_event;
221 222

  gtk_widget_class_set_accessible_type (widget_class, _gtk_assistant_accessible_get_type ());
223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238

  container_class->add = gtk_assistant_add;
  container_class->remove = gtk_assistant_remove;
  container_class->set_child_property = gtk_assistant_set_child_property;
  container_class->get_child_property = gtk_assistant_get_child_property;

  /**
   * GtkAssistant::cancel:
   * @assistant: the #GtkAssistant
   *
   * The ::cancel signal is emitted when then the cancel button is clicked.
   *
   * Since: 2.10
   */
  signals[CANCEL] =
    g_signal_new (I_("cancel"),
239 240 241 242 243 244 245
                  G_TYPE_FROM_CLASS (gobject_class),
                  G_SIGNAL_RUN_LAST,
                  G_STRUCT_OFFSET (GtkAssistantClass, cancel),
                  NULL, NULL,
                  g_cclosure_marshal_VOID__VOID,
                  G_TYPE_NONE, 0);

246 247 248 249 250
  /**
   * GtkAssistant::prepare:
   * @assistant: the #GtkAssistant
   * @page: the current page
   *
251 252 253 254 255
   * The ::prepare signal is emitted when a new page is set as the
   * assistant's current page, before making the new page visible.
   *
   * A handler for this signal can do any preparations which are
   * necessary before showing @page.
256 257 258 259 260
   *
   * Since: 2.10
   */
  signals[PREPARE] =
    g_signal_new (I_("prepare"),
261 262 263 264 265 266
                  G_TYPE_FROM_CLASS (gobject_class),
                  G_SIGNAL_RUN_LAST,
                  G_STRUCT_OFFSET (GtkAssistantClass, prepare),
                  NULL, NULL,
                  g_cclosure_marshal_VOID__OBJECT,
                  G_TYPE_NONE, 1, GTK_TYPE_WIDGET);
267 268 269

  /**
   * GtkAssistant::apply:
270
   * @assistant: the #GtkAssistant
271
   *
272 273 274 275
   * The ::apply signal is emitted when the apply button is clicked.
   *
   * The default behavior of the #GtkAssistant is to switch to the page
   * after the current page, unless the current page is the last one.
276
   *
277 278 279 280 281 282
   * A handler for the ::apply signal should carry out the actions for
   * which the wizard has collected data. If the action takes a long time
   * to complete, you might consider putting a page of type
   * %GTK_ASSISTANT_PAGE_PROGRESS after the confirmation page and handle
   * this operation within the #GtkAssistant::prepare signal of the progress
   * page.
283 284 285 286 287
   *
   * Since: 2.10
   */
  signals[APPLY] =
    g_signal_new (I_("apply"),
288 289 290 291 292 293
                  G_TYPE_FROM_CLASS (gobject_class),
                  G_SIGNAL_RUN_LAST,
                  G_STRUCT_OFFSET (GtkAssistantClass, apply),
                  NULL, NULL,
                  g_cclosure_marshal_VOID__VOID,
                  G_TYPE_NONE, 0);
294 295 296 297 298

  /**
   * GtkAssistant::close:
   * @assistant: the #GtkAssistant
   *
299 300
   * The ::close signal is emitted either when the close button of
   * a summary page is clicked, or when the apply button in the last
301
   * page in the flow (of type %GTK_ASSISTANT_PAGE_CONFIRM) is clicked.
302 303 304 305 306
   *
   * Since: 2.10
   */
  signals[CLOSE] =
    g_signal_new (I_("close"),
307 308 309 310 311 312
                  G_TYPE_FROM_CLASS (gobject_class),
                  G_SIGNAL_RUN_LAST,
                  G_STRUCT_OFFSET (GtkAssistantClass, close),
                  NULL, NULL,
                  g_cclosure_marshal_VOID__VOID,
                  G_TYPE_NONE, 0);
313 314

  gtk_widget_class_install_style_property (widget_class,
315 316 317 318 319 320 321
                                           g_param_spec_int ("header-padding",
                                                             P_("Header Padding"),
                                                             P_("Number of pixels around the header."),
                                                             0,
                                                             G_MAXINT,
                                                             6,
                                                             GTK_PARAM_READABLE));
322
  gtk_widget_class_install_style_property (widget_class,
323 324 325 326 327 328 329
                                           g_param_spec_int ("content-padding",
                                                             P_("Content Padding"),
                                                             P_("Number of pixels around the content pages."),
                                                             0,
                                                             G_MAXINT,
                                                             1,
                                                             GTK_PARAM_READABLE));
330 331 332 333

  /**
   * GtkAssistant:page-type:
   *
334
   * The type of the assistant page.
335 336 337 338
   *
   * Since: 2.10
   */
  gtk_container_class_install_child_property (container_class,
339 340 341 342 343 344 345
                                              CHILD_PROP_PAGE_TYPE,
                                              g_param_spec_enum ("page-type",
                                                                 P_("Page type"),
                                                                 P_("The type of the assistant page"),
                                                                 GTK_TYPE_ASSISTANT_PAGE_TYPE,
                                                                 GTK_ASSISTANT_PAGE_CONTENT,
                                                                 GTK_PARAM_READWRITE));
346 347 348 349

  /**
   * GtkAssistant:title:
   *
350
   * The title of the page.
351 352 353 354
   *
   * Since: 2.10
   */
  gtk_container_class_install_child_property (container_class,
355 356 357 358 359 360
                                              CHILD_PROP_PAGE_TITLE,
                                              g_param_spec_string ("title",
                                                                   P_("Page title"),
                                                                   P_("The title of the assistant page"),
                                                                   NULL,
                                                                   GTK_PARAM_READWRITE));
361 362 363 364

  /**
   * GtkAssistant:header-image:
   *
365
   * This image used to be displayed in the page header.
366 367
   *
   * Since: 2.10
368 369 370
   *
   * Deprecated: 3.2: Since GTK+ 3.2, a header is no longer shown;
   *     add your header decoration to the page content instead.
371 372
   */
  gtk_container_class_install_child_property (container_class,
373 374 375 376 377 378
                                              CHILD_PROP_PAGE_HEADER_IMAGE,
                                              g_param_spec_object ("header-image",
                                                                   P_("Header image"),
                                                                   P_("Header image for the assistant page"),
                                                                   GDK_TYPE_PIXBUF,
                                                                   GTK_PARAM_READWRITE));
379 380

  /**
381
   * GtkAssistant:sidebar-image:
382
   *
383
   * This image used to be displayed in the 'sidebar'.
384 385
   *
   * Since: 2.10
386 387
   *
   * Deprecated: 3.2: Since GTK+ 3.2, the sidebar image is no longer shown.
388 389
   */
  gtk_container_class_install_child_property (container_class,
390 391 392 393 394 395
                                              CHILD_PROP_PAGE_SIDEBAR_IMAGE,
                                              g_param_spec_object ("sidebar-image",
                                                                   P_("Sidebar image"),
                                                                   P_("Sidebar image for the assistant page"),
                                                                   GDK_TYPE_PIXBUF,
                                                                   GTK_PARAM_READWRITE));
396

397 398 399
  /**
   * GtkAssistant:complete:
   *
400 401 402
   * Setting the "complete" child property to %TRUE marks a page as
   * complete (i.e.: all the required fields are filled out). GTK+ uses
   * this information to control the sensitivity of the navigation buttons.
403 404
   *
   * Since: 2.10
405
   */
406
  gtk_container_class_install_child_property (container_class,
407 408 409 410 411 412
                                              CHILD_PROP_PAGE_COMPLETE,
                                              g_param_spec_boolean ("complete",
                                                                    P_("Page complete"),
                                                                    P_("Whether all required fields on the page have been filled out"),
                                                                    FALSE,
                                                                    G_PARAM_READWRITE));
413 414 415 416 417 418 419 420 421 422 423 424 425

  g_type_class_add_private (gobject_class, sizeof (GtkAssistantPrivate));
}

static gint
default_forward_function (gint current_page, gpointer data)
{
  GtkAssistant *assistant;
  GtkAssistantPrivate *priv;
  GtkAssistantPage *page_info;
  GList *page_node;

  assistant = GTK_ASSISTANT (data);
426
  priv = assistant->priv;
427 428 429 430 431 432 433 434

  page_node = g_list_nth (priv->pages, ++current_page);

  if (!page_node)
    return -1;

  page_info = (GtkAssistantPage *) page_node->data;

435
  while (page_node && !gtk_widget_get_visible (page_info->page))
436 437 438
    {
      page_node = page_node->next;
      current_page++;
439 440

      if (page_node)
441
        page_info = (GtkAssistantPage *) page_node->data;
442 443 444 445 446
    }

  return current_page;
}

447 448
static gboolean
last_button_visible (GtkAssistant *assistant, GtkAssistantPage *page)
449
{
450
  GtkAssistantPrivate *priv = assistant->priv;
451
  GtkAssistantPage *page_info;
452 453
  gint count, page_num, n_pages;

454 455 456 457 458 459
  if (page == NULL)
    return FALSE;

  if (page->type != GTK_ASSISTANT_PAGE_CONTENT)
    return FALSE;

460
  count = 0;
461 462 463
  page_num = g_list_index (priv->pages, page);
  n_pages  = g_list_length (priv->pages);
  page_info = page;
464

465
  while (page_num >= 0 && page_num < n_pages &&
466 467 468
         page_info->type == GTK_ASSISTANT_PAGE_CONTENT &&
         (count == 0 || page_info->complete) &&
         count < n_pages)
469
    {
470
      page_num = (priv->forward_function) (page_num, priv->forward_function_data);
471
      page_info = g_list_nth_data (priv->pages, page_num);
472

473 474 475
      count++;
    }

476
  /* Make the last button visible if we can skip multiple
477
   * pages and end on a confirmation or summary page
478
   */
479
  if (count > 1 && page_info &&
480 481
      (page_info->type == GTK_ASSISTANT_PAGE_CONFIRM ||
       page_info->type == GTK_ASSISTANT_PAGE_SUMMARY))
482
    return TRUE;
483
  else
484
    return FALSE;
485 486
}

487
static void
488
update_actions_size (GtkAssistant *assistant)
489 490
{
  GtkAssistantPrivate *priv = assistant->priv;
491 492 493
  GList *l;
  GtkAssistantPage *page;
  gint buttons, page_buttons;
494

495 496
  if (!priv->current_page)
    return;
497

498 499 500 501 502 503 504 505
  /* Some heuristics to find out how many buttons we should
   * reserve space for. It is possible to trick this code
   * with page forward functions and invisible pages, etc.
   */
  buttons = 0;
  for (l = priv->pages; l; l = l->next)
    {
      page = l->data;
506

507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523
      if (!gtk_widget_get_visible (page->page))
        continue;

      page_buttons = 2; /* cancel, forward/apply/close */
      if (l != priv->pages)
        page_buttons += 1; /* back */
      if (last_button_visible (assistant, page))
        page_buttons += 1; /* last */

      buttons = MAX (buttons, page_buttons);
    }

  buttons += priv->extra_buttons;

  gtk_widget_set_size_request (priv->action_area,
                               buttons * gtk_widget_get_allocated_width (priv->cancel) + (buttons - 1) * 6,
                               -1);
524 525
}

526
static void
527
compute_last_button_state (GtkAssistant *assistant)
528
{
529
  GtkAssistantPrivate *priv = assistant->priv;
530

531 532 533 534 535
  gtk_widget_set_sensitive (priv->last, priv->current_page->complete);
  if (last_button_visible (assistant, priv->current_page))
    gtk_widget_show (priv->last);
  else
    gtk_widget_hide (priv->last);
536 537 538
}

static void
539
compute_progress_state (GtkAssistant *assistant)
540
{
541
  GtkAssistantPrivate *priv = assistant->priv;
542 543 544 545
  gint page_num, n_pages;

  n_pages = gtk_assistant_get_n_pages (assistant);
  page_num = gtk_assistant_get_current_page (assistant);
546

547
  page_num = (priv->forward_function) (page_num, priv->forward_function_data);
548

549 550
  if (page_num >= 0 && page_num < n_pages)
    gtk_widget_show (priv->forward);
551
  else
552
    gtk_widget_hide (priv->forward);
553 554 555
}

static void
556
update_buttons_state (GtkAssistant *assistant)
557
{
558
  GtkAssistantPrivate *priv = assistant->priv;
559

560 561
  if (!priv->current_page)
    return;
562

563 564 565
  switch (priv->current_page->type)
    {
    case GTK_ASSISTANT_PAGE_INTRO:
566 567 568 569 570 571 572
      gtk_widget_set_sensitive (priv->cancel, TRUE);
      gtk_widget_set_sensitive (priv->forward, priv->current_page->complete);
      gtk_widget_grab_default (priv->forward);
      gtk_widget_show (priv->forward);
      gtk_widget_hide (priv->back);
      gtk_widget_hide (priv->apply);
      gtk_widget_hide (priv->close);
573 574 575
      compute_last_button_state (assistant);
      break;
    case GTK_ASSISTANT_PAGE_CONFIRM:
576 577 578 579 580 581 582 583 584
      gtk_widget_set_sensitive (priv->cancel, TRUE);
      gtk_widget_set_sensitive (priv->back, TRUE);
      gtk_widget_set_sensitive (priv->apply, priv->current_page->complete);
      gtk_widget_grab_default (priv->apply);
      gtk_widget_show (priv->back);
      gtk_widget_show (priv->apply);
      gtk_widget_hide (priv->forward);
      gtk_widget_hide (priv->close);
      gtk_widget_hide (priv->last);
585 586
      break;
    case GTK_ASSISTANT_PAGE_CONTENT:
587 588 589 590 591 592 593 594
      gtk_widget_set_sensitive (priv->cancel, TRUE);
      gtk_widget_set_sensitive (priv->back, TRUE);
      gtk_widget_set_sensitive (priv->forward, priv->current_page->complete);
      gtk_widget_grab_default (priv->forward);
      gtk_widget_show (priv->back);
      gtk_widget_show (priv->forward);
      gtk_widget_hide (priv->apply);
      gtk_widget_hide (priv->close);
595 596 597
      compute_last_button_state (assistant);
      break;
    case GTK_ASSISTANT_PAGE_SUMMARY:
598 599 600 601 602 603 604
      gtk_widget_set_sensitive (priv->close, priv->current_page->complete);
      gtk_widget_grab_default (priv->close);
      gtk_widget_show (priv->close);
      gtk_widget_hide (priv->back);
      gtk_widget_hide (priv->forward);
      gtk_widget_hide (priv->apply);
      gtk_widget_hide (priv->last);
605 606
      break;
    case GTK_ASSISTANT_PAGE_PROGRESS:
607 608 609 610 611 612 613 614
      gtk_widget_set_sensitive (priv->cancel, priv->current_page->complete);
      gtk_widget_set_sensitive (priv->back, priv->current_page->complete);
      gtk_widget_set_sensitive (priv->forward, priv->current_page->complete);
      gtk_widget_grab_default (priv->forward);
      gtk_widget_show (priv->back);
      gtk_widget_hide (priv->apply);
      gtk_widget_hide (priv->close);
      gtk_widget_hide (priv->last);
615
      compute_progress_state (assistant);
616
      break;
617 618 619 620 621 622 623 624
    case GTK_ASSISTANT_PAGE_CUSTOM:
      gtk_widget_hide (priv->cancel);
      gtk_widget_hide (priv->back);
      gtk_widget_hide (priv->forward);
      gtk_widget_hide (priv->apply);
      gtk_widget_hide (priv->last);
      gtk_widget_hide (priv->close);
      break;
625 626 627 628
    default:
      g_assert_not_reached ();
    }

629
  if (priv->committed)
630
    gtk_widget_hide (priv->cancel);
631 632
  else if (priv->current_page->type == GTK_ASSISTANT_PAGE_SUMMARY ||
           priv->current_page->type == GTK_ASSISTANT_PAGE_CUSTOM)
633
    gtk_widget_hide (priv->cancel);
634
  else
635
    gtk_widget_show (priv->cancel);
636

637
  /* this is quite general, we don't want to
638 639
   * go back if it's the first page
   */
640
  if (!priv->visited_pages)
641
    gtk_widget_hide (priv->back);
642 643
}

644 645
static gboolean
update_page_title_state (GtkAssistant *assistant, GList *list)
646
{
647
  GtkAssistantPage *page, *other;
648
  GtkAssistantPrivate *priv = assistant->priv;
649 650
  gboolean visible;
  GList *l;
651

652
  page = list->data;
653

654 655 656 657
  if (page->title == NULL || page->title[0] == 0)
    visible = FALSE;
  else
    visible = gtk_widget_get_visible (page->page);
658

659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681
  if (page == priv->current_page)
    {
      gtk_widget_set_visible (page->regular_title, FALSE);
      gtk_widget_set_visible (page->current_title, visible);
    }
  else
    {
      /* If multiple consecutive pages have the same title,
       * we only show it once, since it would otherwise look
       * silly. We have to be a little careful, since we
       * _always_ show the title of the current page.
       */
      if (list->prev)
        {
          other = list->prev->data;
          if (g_strcmp0 (page->title, other->title) == 0)
            visible = FALSE;
        }
      for (l = list->next; l; l = l->next)
        {
          other = l->data;
          if (g_strcmp0 (page->title, other->title) != 0)
            break;
682

683 684 685 686 687 688
          if (other == priv->current_page)
            {
              visible = FALSE;
              break;
            }
        }
689

690 691
      gtk_widget_set_visible (page->regular_title, visible);
      gtk_widget_set_visible (page->current_title, FALSE);
692
    }
693

694 695 696 697 698 699 700 701 702 703 704 705
  return visible;
}

static void
update_title_state (GtkAssistant *assistant)
{
  GtkAssistantPrivate *priv = assistant->priv;
  GList *l;
  gboolean show_titles;

  show_titles = FALSE;
  for (l = priv->pages; l != NULL; l = l->next)
706
    {
707 708
      if (update_page_title_state (assistant, l))
        show_titles = TRUE;
709 710
    }

711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731
  gtk_widget_set_visible (priv->sidebar, show_titles);
}

static void
set_current_page (GtkAssistant *assistant,
                  gint          page_num)
{
  GtkAssistantPrivate *priv = assistant->priv;

  priv->current_page = (GtkAssistantPage *)g_list_nth_data (priv->pages, page_num);

  g_signal_emit (assistant, signals [PREPARE], 0, priv->current_page->page);

  update_title_state (assistant);

  gtk_notebook_set_current_page (GTK_NOTEBOOK (priv->content), page_num);

  /* update buttons state, flow may have changed */
  if (gtk_widget_get_mapped (GTK_WIDGET (assistant)))
    update_buttons_state (assistant);

732 733 734 735 736 737
  if (!gtk_widget_child_focus (priv->current_page->page, GTK_DIR_TAB_FORWARD))
    {
      GtkWidget *button[6];
      gint i;

      /* find the best button to focus */
738 739 740 741 742 743
      button[0] = priv->apply;
      button[1] = priv->close;
      button[2] = priv->forward;
      button[3] = priv->back;
      button[4] = priv->cancel;
      button[5] = priv->last;
744 745
      for (i = 0; i < 6; i++)
        {
746 747
          if (gtk_widget_get_visible (button[i]) &&
              gtk_widget_get_sensitive (button[i]))
748 749 750 751 752 753 754
            {
              gtk_widget_grab_focus (button[i]);
              break;
            }
        }
    }

755 756 757 758 759 760
  gtk_widget_queue_resize (GTK_WIDGET (assistant));
}

static gint
compute_next_step (GtkAssistant *assistant)
{
761
  GtkAssistantPrivate *priv = assistant->priv;
762 763 764 765 766 767 768 769
  GtkAssistantPage *page_info;
  gint current_page, n_pages, next_page;

  current_page = gtk_assistant_get_current_page (assistant);
  page_info = priv->current_page;
  n_pages = gtk_assistant_get_n_pages (assistant);

  next_page = (priv->forward_function) (current_page,
770
                                        priv->forward_function_data);
771 772 773 774

  if (next_page >= 0 && next_page < n_pages)
    {
      priv->visited_pages = g_slist_prepend (priv->visited_pages, page_info);
775
      set_current_page (assistant, next_page);
776 777 778 779 780 781 782 783

      return TRUE;
    }

  return FALSE;
}

static void
784 785
on_assistant_close (GtkWidget    *widget,
                    GtkAssistant *assistant)
786 787 788 789 790
{
  g_signal_emit (assistant, signals [CLOSE], 0, NULL);
}

static void
791 792
on_assistant_apply (GtkWidget    *widget,
                    GtkAssistant *assistant)
793 794 795
{
  gboolean success;

Matthias Clasen's avatar
Matthias Clasen committed
796
  g_signal_emit (assistant, signals [APPLY], 0);
797

798 799
  success = compute_next_step (assistant);

800 801 802 803
  /* if the assistant hasn't switched to another page, just emit
   * the CLOSE signal, it't the last page in the assistant flow
   */
  if (!success)
Matthias Clasen's avatar
Matthias Clasen committed
804
    g_signal_emit (assistant, signals [CLOSE], 0);
805 806 807
}

static void
808 809
on_assistant_forward (GtkWidget    *widget,
                      GtkAssistant *assistant)
810
{
811
  gtk_assistant_next_page (assistant);
812 813 814
}

static void
815 816
on_assistant_back (GtkWidget    *widget,
                   GtkAssistant *assistant)
817
{
818
  gtk_assistant_previous_page (assistant);
819 820 821
}

static void
822 823
on_assistant_cancel (GtkWidget    *widget,
                     GtkAssistant *assistant)
824 825 826 827 828
{
  g_signal_emit (assistant, signals [CANCEL], 0, NULL);
}

static void
829 830
on_assistant_last (GtkWidget    *widget,
                   GtkAssistant *assistant)
831
{
832
  GtkAssistantPrivate *priv = assistant->priv;
833 834

  while (priv->current_page->type == GTK_ASSISTANT_PAGE_CONTENT &&
835
         priv->current_page->complete)
836 837 838 839 840 841 842 843 844 845 846 847 848 849
    compute_next_step (assistant);
}

static gboolean
alternative_button_order (GtkAssistant *assistant)
{
  GtkSettings *settings;
  GdkScreen *screen;
  gboolean result;

  screen   = gtk_widget_get_screen (GTK_WIDGET (assistant));
  settings = gtk_settings_get_for_screen (screen);

  g_object_get (settings,
850 851
                "gtk-alternative-button-order", &result,
                NULL);
852 853 854
  return result;
}

855 856 857 858 859 860 861 862 863 864 865 866 867 868 869 870 871
static gboolean
assistant_sidebar_draw_cb (GtkWidget *widget,
                           cairo_t *cr,
                           gpointer user_data)
{
  gint width, height;
  GtkStyleContext *context;

  width = gtk_widget_get_allocated_width (widget);
  height = gtk_widget_get_allocated_height (widget);
  context = gtk_widget_get_style_context (widget);

  gtk_render_background (context, cr, 0, 0, width, height);

  return FALSE;
}

872 873 874 875 876 877 878 879 880 881 882 883 884 885 886 887 888 889 890 891 892 893 894 895 896 897 898 899 900 901 902 903 904 905 906 907 908 909 910 911 912 913 914 915 916 917 918 919 920 921 922 923 924 925 926 927 928 929 930 931 932 933 934 935 936 937 938 939 940 941 942 943 944 945 946 947 948
static void
on_page_notify_visibility (GtkWidget  *widget,
                           GParamSpec *arg,
                           gpointer    data)
{
  GtkAssistant *assistant = GTK_ASSISTANT (data);

  if (gtk_widget_get_mapped (GTK_WIDGET (assistant)))
    {
      update_buttons_state (assistant);
      update_title_state (assistant);
    }
}

static void
assistant_remove_page_cb (GtkNotebook *notebook,
                          GtkWidget *page,
                          GtkAssistant *assistant)
{
  GtkAssistantPrivate *priv = assistant->priv;
  GtkAssistantPage *page_info;
  GList *page_node;
  GList *element;

  element = find_page (assistant, page);
  if (!element)
    return;

  page_info = element->data;

  /* If this is the current page, we need to switch away. */
  if (page_info == priv->current_page)
    {
      if (!compute_next_step (assistant))
        {
          /* The best we can do at this point is probably to pick
           * the first visible page.
           */
          page_node = priv->pages;

          while (page_node &&
                 !gtk_widget_get_visible (((GtkAssistantPage *) page_node->data)->page))
            page_node = page_node->next;

          if (page_node == element)
            page_node = page_node->next;

          if (page_node)
            priv->current_page = page_node->data;
          else
            priv->current_page = NULL;
        }
    }

  g_signal_handlers_disconnect_by_func (page_info->page, on_page_notify_visibility, assistant);

  gtk_size_group_remove_widget (priv->title_size_group, page_info->regular_title);
  gtk_size_group_remove_widget (priv->title_size_group, page_info->current_title);

  gtk_container_remove (GTK_CONTAINER (priv->sidebar), page_info->regular_title);
  gtk_container_remove (GTK_CONTAINER (priv->sidebar), page_info->current_title);

  priv->pages = g_list_remove_link (priv->pages, element);
  priv->visited_pages = g_slist_remove_all (priv->visited_pages, page_info);

  g_free (page_info->title);

  g_slice_free (GtkAssistantPage, page_info);
  g_list_free_1 (element);

  if (gtk_widget_get_mapped (GTK_WIDGET (assistant)))
    {
      update_buttons_state (assistant);
      update_actions_size (assistant);
    }
}

949 950 951 952
static void
gtk_assistant_init (GtkAssistant *assistant)
{
  GtkAssistantPrivate *priv;
953
  GtkStyleContext *context;
954 955
  GtkWidget *main_box;
  GtkWidget *content_box;
956
  GtkWidget *sidebar_frame;
957

958 959 960 961
  assistant->priv = G_TYPE_INSTANCE_GET_PRIVATE (assistant,
                                                 GTK_TYPE_ASSISTANT,
                                                 GtkAssistantPrivate);
  priv = assistant->priv;
962

963 964
  /* use border on inner panes instead */
  gtk_container_set_border_width (GTK_CONTAINER (assistant), 0);
965

966 967
  gtk_widget_push_composite_child ();

968 969
  main_box = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 12);
  priv->sidebar = gtk_box_new (GTK_ORIENTATION_VERTICAL, 6);
970 971 972 973 974 975

  /* use a frame for the sidebar, and manually render a background
   * in it. GtkFrame also gives us padding support for free.
   */
  sidebar_frame = gtk_frame_new (NULL);
  context = gtk_widget_get_style_context (sidebar_frame);
976 977
  gtk_style_context_add_class (context, GTK_STYLE_CLASS_SIDEBAR);

978 979 980
  g_signal_connect (sidebar_frame, "draw",
                    G_CALLBACK (assistant_sidebar_draw_cb), assistant);

981 982 983 984 985 986 987
  content_box = gtk_box_new (GTK_ORIENTATION_VERTICAL, 12);
  gtk_container_set_border_width (GTK_CONTAINER (content_box), 12);
  priv->content = gtk_notebook_new ();
  gtk_notebook_set_show_border (GTK_NOTEBOOK (priv->content), FALSE);
  gtk_notebook_set_show_tabs (GTK_NOTEBOOK (priv->content), FALSE);
  priv->action_area = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 6);

988 989 990
  g_signal_connect (priv->content, "remove",
                    G_CALLBACK (assistant_remove_page_cb), assistant);

991 992
  gtk_container_add (GTK_CONTAINER (sidebar_frame), priv->sidebar);
  gtk_box_pack_start (GTK_BOX (main_box), sidebar_frame, FALSE, FALSE, 0);
993
  gtk_box_pack_start (GTK_BOX (main_box), content_box, TRUE, TRUE, 0);
994
  gtk_box_pack_start (GTK_BOX (content_box), priv->content, TRUE, TRUE, 0);
995 996 997 998 999 1000 1001
  gtk_box_pack_start (GTK_BOX (content_box), priv->action_area, FALSE, TRUE, 0);
  gtk_widget_set_halign (priv->action_area, GTK_ALIGN_END);

  gtk_widget_show_all (main_box);

  gtk_widget_set_parent (main_box, GTK_WIDGET (assistant));
  _gtk_bin_set_child (GTK_BIN (assistant), main_box);
1002

1003 1004
  priv->close   = gtk_button_new_from_stock (GTK_STOCK_CLOSE);
  priv->apply   = gtk_button_new_from_stock (GTK_STOCK_APPLY);
1005 1006 1007 1008 1009 1010
  priv->forward = gtk_button_new_with_mnemonic (_("C_ontinue"));
  gtk_button_set_image (GTK_BUTTON (priv->forward),
      gtk_image_new_from_stock (GTK_STOCK_GO_FORWARD, GTK_ICON_SIZE_BUTTON));
  priv->back    = gtk_button_new_with_mnemonic (_("Go _Back"));
  gtk_button_set_image (GTK_BUTTON (priv->forward),
      gtk_image_new_from_stock (GTK_STOCK_GO_BACK, GTK_ICON_SIZE_BUTTON));
1011
  priv->cancel  = gtk_button_new_from_stock (GTK_STOCK_CANCEL);
1012 1013 1014
  priv->last    = gtk_button_new_with_mnemonic (_("_Finish"));
  gtk_button_set_image (GTK_BUTTON (priv->forward),
      gtk_image_new_from_stock (GTK_STOCK_GOTO_LAST, GTK_ICON_SIZE_BUTTON));
1015 1016 1017
  gtk_widget_set_can_default (priv->close, TRUE);
  gtk_widget_set_can_default (priv->apply, TRUE);
  gtk_widget_set_can_default (priv->forward, TRUE);
1018

1019 1020 1021 1022 1023 1024 1025
  priv->button_size_group = gtk_size_group_new (GTK_SIZE_GROUP_HORIZONTAL);
  gtk_size_group_add_widget (priv->button_size_group, priv->close);
  gtk_size_group_add_widget (priv->button_size_group, priv->apply);
  gtk_size_group_add_widget (priv->button_size_group, priv->forward);
  gtk_size_group_add_widget (priv->button_size_group, priv->back);
  gtk_size_group_add_widget (priv->button_size_group, priv->cancel);
  gtk_size_group_add_widget (priv->button_size_group, priv->last);
1026

1027 1028 1029 1030 1031 1032 1033
  gtk_widget_set_no_show_all (priv->close, TRUE);
  gtk_widget_set_no_show_all (priv->apply, TRUE);
  gtk_widget_set_no_show_all (priv->forward, TRUE);
  gtk_widget_set_no_show_all (priv->back, TRUE);
  gtk_widget_set_no_show_all (priv->cancel, TRUE);
  gtk_widget_set_no_show_all (priv->last, TRUE);

1034 1035
  if (!alternative_button_order (assistant))
    {
1036 1037 1038 1039 1040 1041
      gtk_box_pack_end (GTK_BOX (priv->action_area), priv->apply, FALSE, FALSE, 0);
      gtk_box_pack_end (GTK_BOX (priv->action_area), priv->forward, FALSE, FALSE, 0);
      gtk_box_pack_end (GTK_BOX (priv->action_area), priv->back, FALSE, FALSE, 0);
      gtk_box_pack_end (GTK_BOX (priv->action_area), priv->last, FALSE, FALSE, 0);
      gtk_box_pack_end (GTK_BOX (priv->action_area), priv->cancel, FALSE, FALSE, 0);
      gtk_box_pack_end (GTK_BOX (priv->action_area), priv->close, FALSE, FALSE, 0);
1042 1043 1044
    }
  else
    {
1045 1046 1047 1048 1049 1050
      gtk_box_pack_end (GTK_BOX (priv->action_area), priv->close, FALSE, FALSE, 0);
      gtk_box_pack_end (GTK_BOX (priv->action_area), priv->cancel, FALSE, FALSE, 0);
      gtk_box_pack_end (GTK_BOX (priv->action_area), priv->apply, FALSE, FALSE, 0);
      gtk_box_pack_end (GTK_BOX (priv->action_area), priv->forward, FALSE, FALSE, 0);
      gtk_box_pack_end (GTK_BOX (priv->action_area), priv->back, FALSE, FALSE, 0);
      gtk_box_pack_end (GTK_BOX (priv->action_area), priv->last, FALSE, FALSE, 0);
1051 1052
    }

1053 1054 1055
  gtk_widget_show (priv->forward);
  gtk_widget_show (priv->back);
  gtk_widget_show (priv->cancel);
1056 1057 1058 1059
  gtk_widget_show (priv->action_area);

  gtk_widget_pop_composite_child ();

1060 1061
  priv->title_size_group = gtk_size_group_new (GTK_SIZE_GROUP_HORIZONTAL);

1062 1063 1064 1065 1066 1067 1068 1069
  priv->pages = NULL;
  priv->current_page = NULL;
  priv->visited_pages = NULL;

  priv->forward_function = default_forward_function;
  priv->forward_function_data = assistant;
  priv->forward_data_destroy = NULL;

1070
  g_signal_connect (G_OBJECT (priv->close), "clicked",
1071
                    G_CALLBACK (on_assistant_close), assistant);
1072
  g_signal_connect (G_OBJECT (priv->apply), "clicked",
1073
                    G_CALLBACK (on_assistant_apply), assistant);
1074
  g_signal_connect (G_OBJECT (priv->forward), "clicked",
1075
                    G_CALLBACK (on_assistant_forward), assistant);
1076
  g_signal_connect (G_OBJECT (priv->back), "clicked",
1077
                    G_CALLBACK (on_assistant_back), assistant);
1078
  g_signal_connect (G_OBJECT (priv->cancel), "clicked",
1079
                    G_CALLBACK (on_assistant_cancel), assistant);
1080
  g_signal_connect (G_OBJECT (priv->last), "clicked",
1081
                    G_CALLBACK (on_assistant_last), assistant);
1082 1083 1084
}

static void
1085 1086 1087 1088 1089
gtk_assistant_set_child_property (GtkContainer *container,
                                  GtkWidget    *child,
                                  guint         property_id,
                                  const GValue *value,
                                  GParamSpec   *pspec)
1090 1091 1092 1093 1094
{
  switch (property_id)
    {
    case CHILD_PROP_PAGE_TYPE:
      gtk_assistant_set_page_type (GTK_ASSISTANT (container), child,
1095
                                   g_value_get_enum (value));
1096 1097 1098
      break;
    case CHILD_PROP_PAGE_TITLE:
      gtk_assistant_set_page_title (GTK_ASSISTANT (container), child,
1099
                                    g_value_get_string (value));
1100 1101
      break;
    case CHILD_PROP_PAGE_HEADER_IMAGE:
1102 1103
      gtk_assistant_do_set_page_header_image (GTK_ASSISTANT (container), child,
                                              g_value_get_object (value));
1104 1105
      break;
    case CHILD_PROP_PAGE_SIDEBAR_IMAGE:
1106 1107
      gtk_assistant_do_set_page_side_image (GTK_ASSISTANT (container), child,
                                            g_value_get_object (value));
1108 1109 1110
      break;
    case CHILD_PROP_PAGE_COMPLETE:
      gtk_assistant_set_page_complete (GTK_ASSISTANT (container), child,
1111
                                       g_value_get_boolean (value));
1112 1113 1114 1115 1116 1117 1118 1119 1120
      break;
    default:
      GTK_CONTAINER_WARN_INVALID_CHILD_PROPERTY_ID (container, property_id, pspec);
      break;
    }
}

static void
gtk_assistant_get_child_property (GtkContainer *container,
1121 1122 1123 1124
                                  GtkWidget    *child,
                                  guint         property_id,
                                  GValue       *value,
                                  GParamSpec   *pspec)
1125
{
1126 1127
  GtkAssistant *assistant = GTK_ASSISTANT (container);

1128 1129 1130 1131
  switch (property_id)
    {
    case CHILD_PROP_PAGE_TYPE:
      g_value_set_enum (value,
1132
                        gtk_assistant_get_page_type (assistant, child));
1133 1134 1135
      break;
    case CHILD_PROP_PAGE_TITLE:
      g_value_set_string (value,
1136
                          gtk_assistant_get_page_title (assistant, child));
1137 1138 1139
      break;
    case CHILD_PROP_PAGE_HEADER_IMAGE:
      g_value_set_object (value,
1140
                          ((GtkAssistantPage*) find_page (assistant, child))->header_image);
1141 1142 1143
      break;
    case CHILD_PROP_PAGE_SIDEBAR_IMAGE:
      g_value_set_object (value,
1144
                          ((GtkAssistantPage*) find_page (assistant, child))->sidebar_image);
1145 1146 1147
      break;
    case CHILD_PROP_PAGE_COMPLETE:
      g_value_set_boolean (value,
1148
                           gtk_assistant_get_page_complete (assistant, child));
1149 1150 1151 1152 1153 1154 1155 1156
      break;
    default:
      GTK_CONTAINER_WARN_INVALID_CHILD_PROPERTY_ID (container, property_id, pspec);
      break;
    }
}

static void
1157
gtk_assistant_destroy (GtkWidget *widget)
1158
{
1159
  GtkAssistant *assistant = GTK_ASSISTANT (widget);
1160
  GtkAssistantPrivate *priv = assistant->priv;
1161

1162 1163 1164 1165
  /* We set current to NULL so that the remove code doesn't try
   * to do anything funny
   */
  priv->current_page = NULL;
1166

1167 1168 1169 1170 1171 1172 1173 1174 1175 1176 1177 1178 1179 1180 1181
  if (priv->content)
    {
      GtkNotebook *notebook;
      GtkWidget *page;

      /* Remove all pages from the content notebook. */
      notebook = (GtkNotebook *) priv->content;
      while ((page = gtk_notebook_get_nth_page (notebook, 0)) != NULL)
        gtk_container_remove ((GtkContainer *) notebook, page);

      /* Our GtkAssistantPage list should be empty now. */
      g_warn_if_fail (priv->pages == NULL);

      priv->content = NULL;
    }
1182 1183 1184 1185

  if (priv->sidebar)
    priv->sidebar = NULL;

1186
  if (priv->action_area)
1187 1188 1189
    priv->action_area = NULL;

  if (priv->button_size_group)
1190
    {
1191 1192
      g_object_unref (priv->button_size_group);
      priv->button_size_group = NULL;
1193 1194
    }

1195
  if (priv->title_size_group)
1196
    {
1197 1198
      g_object_unref (priv->title_size_group);
      priv->title_size_group = NULL;
1199 1200 1201 1202 1203
    }

  if (priv->forward_function)
    {
      if (priv->forward_function_data &&
1204 1205
          priv->forward_data_destroy)
        priv->forward_data_destroy (priv->forward_function_data);
1206 1207 1208 1209 1210 1211 1212 1213 1214 1215 1216 1217

      priv->forward_function = NULL;
      priv->forward_function_data = NULL;
      priv->forward_data_destroy = NULL;
    }

  if (priv->visited_pages)
    {
      g_slist_free (priv->visited_pages);
      priv->visited_pages = NULL;
    }

1218
  GTK_WIDGET_CLASS (gtk_assistant_parent_class)->destroy (widget);
1219 1220 1221 1222
}

static GList*
find_page (GtkAssistant  *assistant,
1223
           GtkWidget     *page)
1224
{
1225
  GtkAssistantPrivate *priv = assistant->priv;
1226
  GList *child = priv->pages;
1227

1228 1229 1230 1231
  while (child)
    {
      GtkAssistantPage *page_info = child->data;
      if (page_info->page == page)
1232
        return child;
1233 1234 1235

      child = child->next;
    }
1236

1237 1238 1239 1240 1241 1242
  return NULL;
}

static void
gtk_assistant_map (GtkWidget *widget)
{
1243 1244
  GtkAssistant *assistant = GTK_ASSISTANT (widget);
  GtkAssistantPrivate *priv = assistant->priv;
1245
  GList *page_node;
1246
  GtkAssistantPage *page;
1247
  gint page_num;
1248 1249

  /* if there's no default page, pick the first one */
1250
  page = NULL;
1251
  page_num = 0;
1252
  if (!priv->current_page)
1253 1254 1255
    {
      page_node = priv->pages;

1256
      while (page_node && !gtk_widget_get_visible (((GtkAssistantPage *) page_node->data)->page))
1257 1258 1259 1260
        {
          page_node = page_node->next;
          page_num++;
        }
1261 1262

      if (page_node)
1263
        page = page_node->data;
1264 1265
    }

1266 1267 1268 1269 1270 1271
  if (page && gtk_widget_get_visible (page->page))
    set_current_page (assistant, page_num);

  update_buttons_state (assistant);
  update_actions_size (assistant);
  update_title_state (assistant);
1272 1273 1274 1275 1276 1277 1278

  GTK_WIDGET_CLASS (gtk_assistant_parent_class)->map (widget);
}

static void
gtk_assistant_unmap (GtkWidget *widget)
{
1279 1280
  GtkAssistant *assistant = GTK_ASSISTANT (widget);
  GtkAssistantPrivate *priv = assistant->priv;
1281 1282 1283 1284 1285 1286 1287 1288 1289 1290

  g_slist_free (priv->visited_pages);
  priv->visited_pages = NULL;
  priv->current_page  = NULL;

  GTK_WIDGET_CLASS (gtk_assistant_parent_class)->unmap (widget);
}

static gboolean
gtk_assistant_delete_event (GtkWidget   *widget,
1291
                            GdkEventAny *event)
1292
{
1293 1294
  GtkAssistant *assistant = GTK_ASSISTANT (widget);
  GtkAssistantPrivate *priv = assistant->priv;
1295 1296 1297 1298 1299 1300 1301 1302 1303 1304 1305 1306

  /* Do not allow cancelling in the middle of a progress page */
  if (priv->current_page &&
      (priv->current_page->type != GTK_ASSISTANT_PAGE_PROGRESS ||
       priv->current_page->complete))
    g_signal_emit (widget, signals [CANCEL], 0, NULL);

  return TRUE;
}

static void
gtk_assistant_add (GtkContainer *container,
1307
                   GtkWidget    *page)
1308 1309 1310 1311 1312 1313
{
  gtk_assistant_append_page (GTK_ASSISTANT (container), page);
}

static void
gtk_assistant_remove (GtkContainer *container,
1314
                      GtkWidget    *page)
1315
{
1316
  GtkAssistant *assistant = (GtkAssistant*) container;
1317

1318 1319 1320 1321 1322 1323
  /* Forward this removal to the content notebook */
  if (gtk_widget_get_parent (page) == assistant->priv->content)
    {
      container = (GtkContainer *) assistant->priv->content;
      gtk_container_remove (container, page);
    }
1324 1325 1326 1327
}

/**
 * gtk_assistant_new:
1328
 *
1329 1330 1331 1332 1333
 * Creates a new #GtkAssistant.
 *
 * Return value: a newly created #GtkAssistant
 *
 * Since: 2.10
1334
 */
1335 1336 1337 1338 1339
GtkWidget*
gtk_assistant_new (void)
{
  GtkWidget *assistant;

1340 1341
  assistant = g_object_new (GTK_TYPE_ASSISTANT, NULL);

1342 1343 1344 1345 1346 1347 1348
  return assistant;
}

/**
 * gtk_assistant_get_current_page:
 * @assistant: a #GtkAssistant
 *
1349
 * Returns the page number of the current page.
1350
 *
1351
 * Return value: The index (starting from 0) of the current
1352 1353
 *     page in the @assistant, or -1 if the @assistant has no pages,
 *     or no current page.
1354 1355
 *
 * Since: 2.10
1356
 */
1357 1358 1359 1360 1361 1362 1363
gint
gtk_assistant_get_current_page (GtkAssistant *assistant)
{
  GtkAssistantPrivate *priv;

  g_return_val_if_fail (GTK_IS_ASSISTANT (assistant), -1);

1364
  priv = assistant->priv;
1365 1366 1367 1368 1369 1370 1371 1372 1373 1374 1375

  if (!priv->pages || !priv->current_page)
    return -1;

  return g_list_index (priv->pages, priv->current_page);
}

/**
 * gtk_assistant_set_current_page:
 * @assistant: a #GtkAssistant
 * @page_num: index of the page to switch to, starting from 0.
1376 1377 1378
 *     If negative, the last page will be used. If greater
 *     than the number of pages in the @assistant, nothing
 *     will be done.
1379
 *
1380 1381 1382 1383
 * Switches the page to @page_num.
 *
 * Note that this will only be necessary in custom buttons,
 * as the @assistant flow can be set with
1384 1385 1386
 * gtk_assistant_set_forward_page_func().
 *
 * Since: 2.10
1387
 */
1388 1389
void
gtk_assistant_set_current_page (GtkAssistant *assistant,
1390
                                gint          page_num)
1391 1392 1393 1394 1395 1396
{
  GtkAssistantPrivate *priv;
  GtkAssistantPage *page;

  g_return_if_fail (GTK_IS_ASSISTANT (assistant));

1397
  priv = assistant->priv;
1398 1399 1400 1401

  if (page_num >= 0)
    page = (GtkAssistantPage *) g_list_nth_data (priv->pages, page_num);
  else
1402 1403 1404 1405
    {
      page = (GtkAssistantPage *) g_list_last (priv->pages)->data;
      page_num = g_list_length (priv->pages);
    }
1406 1407 1408 1409 1410 1411

  g_return_if_fail (page != NULL);

  if (priv->current_page == page)
    return;

1412 1413 1414
  /* only add the page to the visited list if the assistant is mapped,
   * if not, just use it as an initial page setting, for the cases where
   * the initial page is != to 0
1415
   */
1416
  if (gtk_widget_get_mapped (GTK_WIDGET (assistant)))
1417
    priv->visited_pages = g_slist_prepend (priv->visited_pages,
1418
                                           priv->current_page);
1419

1420
  set_current_page (assistant, page_num);
1421 1422
}

1423 1424 1425 1426
/**
 * gtk_assistant_next_page:
 * @assistant: a #GtkAssistant
 *
1427 1428 1429 1430
 * Navigate to the next page.
 *
 * It is a programming error to call this function when
 * there is no next page.
1431 1432
 *
 * This function is for use when creating pages of the
1433
 * #GTK_ASSISTANT_PAGE_CUSTOM type.
1434 1435
 *
 * Since: 3.0
1436
 */
1437 1438 1439 1440 1441 1442
void
gtk_assistant_next_page (GtkAssistant *assistant)
{
  g_return_if_fail (GTK_IS_ASSISTANT (assistant));

  if (!compute_next_step (assistant))
1443 1444 1445
    g_critical ("Page flow is broken.\n"
                "You may want to end it with a page of type\n"
                "GTK_ASSISTANT_PAGE_CONFIRM or GTK_ASSISTANT_PAGE_SUMMARY");
1446 1447 1448 1449 1450 1451
}

/**
 * gtk_assistant_previous_page:
 * @assistant: a #GtkAssistant
 *
1452 1453 1454 1455
 * Navigate to the previous visited page.
 *
 * It is a programming error to call this function when
 * no previous page is available.
1456 1457
 *
 * This function is for use when creating pages of the
1458
 * #GTK_ASSISTANT_PAGE_CUSTOM type.
1459 1460
 *
 * Since: 3.0
1461
 */
1462 1463 1464 1465 1466 1467 1468 1469 1470 1471 1472 1473 1474 1475 1476 1477 1478 1479 1480 1481 1482 1483 1484
void
gtk_assistant_previous_page (GtkAssistant *assistant)
{
  GtkAssistantPrivate *priv;
  GtkAssistantPage *page_info;
  GSList *page_node;

  g_return_if_fail (GTK_IS_ASSISTANT (assistant));

  priv = assistant->priv;

  /* skip the progress pages when going back */
  do
    {
      page_node = priv->visited_pages;

      g_return_if_fail (page_node != NULL);

      priv->visited_pages = priv->visited_pages->next;
      page_info = (GtkAssistantPage *) page_node->data;
      g_slist_free_1 (page_node);
    }
  while (page_info->type == GTK_ASSISTANT_PAGE_PROGRESS ||
1485
         !gtk_widget_get_visible (page_info->page));
1486

1487
  set_current_page (assistant, g_list_index (priv->pages, page_info));
1488 1489
}

1490 1491 1492 1493 1494 1495
/**
 * gtk_assistant_get_n_pages:
 * @assistant: a #GtkAssistant
 *
 * Returns the number of pages in the @assistant
 *
1496
 * Return value: the number of pages in the @assistant
1497 1498
 *
 * Since: 2.10
1499
 */
1500 1501 1502 1503 1504 1505 1506
gint
gtk_assistant_get_n_pages (GtkAssistant *assistant)
{
  GtkAssistantPrivate *priv;

  g_return_val_if_fail (GTK_IS_ASSISTANT (assistant), 0);

1507
  priv = assistant->priv;
1508 1509 1510 1511 1512 1513 1514

  return g_list_length (priv->pages);
}

/**
 * gtk_assistant_get_nth_page:
 * @assistant: a #GtkAssistant
1515 1516
 * @page_num: the index of a page in the @assistant,
 *     or -1 to get the last page
1517 1518 1519
 *
 * Returns the child widget contained in page number @page_num.
 *
1520 1521
 * Return value: (transfer none): the child widget, or %NULL
 *     if @page_num is out of bounds
1522 1523
 *
 * Since: 2.10
1524
 */
1525 1526
GtkWidget*
gtk_assistant_get_nth_page (GtkAssistant *assistant,