gtkassistant.c 70.2 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
#include "gtkassistant.h"

65
#include "gtkaccessibleprivate.h"
66
#include "gtkbutton.h"
67
#include "gtkbox.h"
68
#include "gtkframe.h"
69
#include "gtknotebook.h"
70 71 72
#include "gtkimage.h"
#include "gtklabel.h"
#include "gtksizegroup.h"
73
#include "gtksizerequest.h"
74
#include "gtkstock.h"
75
#include "gtktypebuiltins.h"
76 77
#include "gtkintl.h"
#include "gtkprivate.h"
78
#include "gtkbuildable.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
static AtkObject *gtk_assistant_get_accessible   (GtkWidget         *widget);
153
static GType      gtk_assistant_accessible_factory_get_type  (void);
154

155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170
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);

171 172
static GList*     find_page                                  (GtkAssistant  *assistant,
                                                              GtkWidget     *page);
173

174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195
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 };


196 197 198
G_DEFINE_TYPE_WITH_CODE (GtkAssistant, gtk_assistant, GTK_TYPE_WINDOW,
                         G_IMPLEMENT_INTERFACE (GTK_TYPE_BUILDABLE,
                                                gtk_assistant_buildable_interface_init))
199 200 201 202 203 204 205 206 207 208 209 210 211


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;

212
  widget_class->destroy = gtk_assistant_destroy;
213 214 215
  widget_class->map = gtk_assistant_map;
  widget_class->unmap = gtk_assistant_unmap;
  widget_class->delete_event = gtk_assistant_delete_event;
216
  widget_class->get_accessible = gtk_assistant_get_accessible;
217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232

  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"),
233 234 235 236 237 238 239
                  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);

240 241 242 243 244
  /**
   * GtkAssistant::prepare:
   * @assistant: the #GtkAssistant
   * @page: the current page
   *
245 246 247 248 249
   * 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.
250 251 252 253 254
   *
   * Since: 2.10
   */
  signals[PREPARE] =
    g_signal_new (I_("prepare"),
255 256 257 258 259 260
                  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);
261 262 263

  /**
   * GtkAssistant::apply:
264
   * @assistant: the #GtkAssistant
265
   *
266 267 268 269
   * 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.
270
   *
271 272 273 274 275 276
   * 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.
277 278 279 280 281
   *
   * Since: 2.10
   */
  signals[APPLY] =
    g_signal_new (I_("apply"),
282 283 284 285 286 287
                  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);
288 289 290 291 292

  /**
   * GtkAssistant::close:
   * @assistant: the #GtkAssistant
   *
293 294
   * The ::close signal is emitted either when the close button of
   * a summary page is clicked, or when the apply button in the last
295
   * page in the flow (of type %GTK_ASSISTANT_PAGE_CONFIRM) is clicked.
296 297 298 299 300
   *
   * Since: 2.10
   */
  signals[CLOSE] =
    g_signal_new (I_("close"),
301 302 303 304 305 306
                  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);
307 308

  gtk_widget_class_install_style_property (widget_class,
309 310 311 312 313 314 315
                                           g_param_spec_int ("header-padding",
                                                             P_("Header Padding"),
                                                             P_("Number of pixels around the header."),
                                                             0,
                                                             G_MAXINT,
                                                             6,
                                                             GTK_PARAM_READABLE));
316
  gtk_widget_class_install_style_property (widget_class,
317 318 319 320 321 322 323
                                           g_param_spec_int ("content-padding",
                                                             P_("Content Padding"),
                                                             P_("Number of pixels around the content pages."),
                                                             0,
                                                             G_MAXINT,
                                                             1,
                                                             GTK_PARAM_READABLE));
324 325 326 327

  /**
   * GtkAssistant:page-type:
   *
328
   * The type of the assistant page.
329 330 331 332
   *
   * Since: 2.10
   */
  gtk_container_class_install_child_property (container_class,
333 334 335 336 337 338 339
                                              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));
340 341 342 343

  /**
   * GtkAssistant:title:
   *
344
   * The title of the page.
345 346 347 348
   *
   * Since: 2.10
   */
  gtk_container_class_install_child_property (container_class,
349 350 351 352 353 354
                                              CHILD_PROP_PAGE_TITLE,
                                              g_param_spec_string ("title",
                                                                   P_("Page title"),
                                                                   P_("The title of the assistant page"),
                                                                   NULL,
                                                                   GTK_PARAM_READWRITE));
355 356 357 358

  /**
   * GtkAssistant:header-image:
   *
359
   * This image used to be displayed in the page header.
360 361
   *
   * Since: 2.10
362 363 364
   *
   * Deprecated: 3.2: Since GTK+ 3.2, a header is no longer shown;
   *     add your header decoration to the page content instead.
365 366
   */
  gtk_container_class_install_child_property (container_class,
367 368 369 370 371 372
                                              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));
373 374

  /**
375
   * GtkAssistant:sidebar-image:
376
   *
377
   * This image used to be displayed in the 'sidebar'.
378 379
   *
   * Since: 2.10
380 381
   *
   * Deprecated: 3.2: Since GTK+ 3.2, the sidebar image is no longer shown.
382 383
   */
  gtk_container_class_install_child_property (container_class,
384 385 386 387 388 389
                                              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));
390

391 392 393
  /**
   * GtkAssistant:complete:
   *
394 395 396
   * 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.
397 398
   *
   * Since: 2.10
399
   */
400
  gtk_container_class_install_child_property (container_class,
401 402 403 404 405 406
                                              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));
407 408 409 410 411 412 413 414 415 416 417 418 419

  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);
420
  priv = assistant->priv;
421 422 423 424 425 426 427 428

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

  if (!page_node)
    return -1;

  page_info = (GtkAssistantPage *) page_node->data;

429
  while (page_node && !gtk_widget_get_visible (page_info->page))
430 431 432
    {
      page_node = page_node->next;
      current_page++;
433 434

      if (page_node)
435
        page_info = (GtkAssistantPage *) page_node->data;
436 437 438 439 440
    }

  return current_page;
}

441 442
static gboolean
last_button_visible (GtkAssistant *assistant, GtkAssistantPage *page)
443
{
444
  GtkAssistantPrivate *priv = assistant->priv;
445
  GtkAssistantPage *page_info;
446 447
  gint count, page_num, n_pages;

448 449 450 451 452 453
  if (page == NULL)
    return FALSE;

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

454
  count = 0;
455 456 457
  page_num = g_list_index (priv->pages, page);
  n_pages  = g_list_length (priv->pages);
  page_info = page;
458

459
  while (page_num >= 0 && page_num < n_pages &&
460 461 462
         page_info->type == GTK_ASSISTANT_PAGE_CONTENT &&
         (count == 0 || page_info->complete) &&
         count < n_pages)
463
    {
464
      page_num = (priv->forward_function) (page_num, priv->forward_function_data);
465
      page_info = g_list_nth_data (priv->pages, page_num);
466

467 468 469
      count++;
    }

470
  /* Make the last button visible if we can skip multiple
471
   * pages and end on a confirmation or summary page
472
   */
473
  if (count > 1 && page_info &&
474 475
      (page_info->type == GTK_ASSISTANT_PAGE_CONFIRM ||
       page_info->type == GTK_ASSISTANT_PAGE_SUMMARY))
476
    return TRUE;
477
  else
478
    return FALSE;
479 480
}

481
static void
482
update_actions_size (GtkAssistant *assistant)
483 484
{
  GtkAssistantPrivate *priv = assistant->priv;
485 486 487
  GList *l;
  GtkAssistantPage *page;
  gint buttons, page_buttons;
488

489 490
  if (!priv->current_page)
    return;
491

492 493 494 495 496 497 498 499
  /* 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;
500

501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517
      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);
518 519
}

520
static void
521
compute_last_button_state (GtkAssistant *assistant)
522
{
523
  GtkAssistantPrivate *priv = assistant->priv;
524

525 526 527 528 529
  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);
530 531 532
}

static void
533
compute_progress_state (GtkAssistant *assistant)
534
{
535
  GtkAssistantPrivate *priv = assistant->priv;
536 537 538 539
  gint page_num, n_pages;

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

541
  page_num = (priv->forward_function) (page_num, priv->forward_function_data);
542

543 544
  if (page_num >= 0 && page_num < n_pages)
    gtk_widget_show (priv->forward);
545
  else
546
    gtk_widget_hide (priv->forward);
547 548 549
}

static void
550
update_buttons_state (GtkAssistant *assistant)
551
{
552
  GtkAssistantPrivate *priv = assistant->priv;
553

554 555
  if (!priv->current_page)
    return;
556

557 558 559
  switch (priv->current_page->type)
    {
    case GTK_ASSISTANT_PAGE_INTRO:
560 561 562 563 564 565 566
      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);
567 568 569
      compute_last_button_state (assistant);
      break;
    case GTK_ASSISTANT_PAGE_CONFIRM:
570 571 572 573 574 575 576 577 578
      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);
579 580
      break;
    case GTK_ASSISTANT_PAGE_CONTENT:
581 582 583 584 585 586 587 588
      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);
589 590 591
      compute_last_button_state (assistant);
      break;
    case GTK_ASSISTANT_PAGE_SUMMARY:
592 593 594 595 596 597 598
      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);
599 600
      break;
    case GTK_ASSISTANT_PAGE_PROGRESS:
601 602 603 604 605 606 607 608
      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);
609
      compute_progress_state (assistant);
610
      break;
611 612 613 614 615 616 617 618
    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;
619 620 621 622
    default:
      g_assert_not_reached ();
    }

623
  if (priv->committed)
624
    gtk_widget_hide (priv->cancel);
625 626
  else if (priv->current_page->type == GTK_ASSISTANT_PAGE_SUMMARY ||
           priv->current_page->type == GTK_ASSISTANT_PAGE_CUSTOM)
627
    gtk_widget_hide (priv->cancel);
628
  else
629
    gtk_widget_show (priv->cancel);
630

631
  /* this is quite general, we don't want to
632 633
   * go back if it's the first page
   */
634
  if (!priv->visited_pages)
635
    gtk_widget_hide (priv->back);
636 637
}

638 639
static gboolean
update_page_title_state (GtkAssistant *assistant, GList *list)
640
{
641
  GtkAssistantPage *page, *other;
642
  GtkAssistantPrivate *priv = assistant->priv;
643 644
  gboolean visible;
  GList *l;
645

646
  page = list->data;
647

648 649 650 651
  if (page->title == NULL || page->title[0] == 0)
    visible = FALSE;
  else
    visible = gtk_widget_get_visible (page->page);
652

653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675
  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;
676

677 678 679 680 681 682
          if (other == priv->current_page)
            {
              visible = FALSE;
              break;
            }
        }
683

684 685
      gtk_widget_set_visible (page->regular_title, visible);
      gtk_widget_set_visible (page->current_title, FALSE);
686
    }
687

688 689 690 691 692 693 694 695 696 697 698 699
  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)
700
    {
701 702
      if (update_page_title_state (assistant, l))
        show_titles = TRUE;
703 704
    }

705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725
  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);

726 727 728 729 730 731
  if (!gtk_widget_child_focus (priv->current_page->page, GTK_DIR_TAB_FORWARD))
    {
      GtkWidget *button[6];
      gint i;

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

749 750 751 752 753 754
  gtk_widget_queue_resize (GTK_WIDGET (assistant));
}

static gint
compute_next_step (GtkAssistant *assistant)
{
755
  GtkAssistantPrivate *priv = assistant->priv;
756 757 758 759 760 761 762 763
  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,
764
                                        priv->forward_function_data);
765 766 767 768

  if (next_page >= 0 && next_page < n_pages)
    {
      priv->visited_pages = g_slist_prepend (priv->visited_pages, page_info);
769
      set_current_page (assistant, next_page);
770 771 772 773 774 775 776 777

      return TRUE;
    }

  return FALSE;
}

static void
778 779
on_assistant_close (GtkWidget    *widget,
                    GtkAssistant *assistant)
780 781 782 783 784
{
  g_signal_emit (assistant, signals [CLOSE], 0, NULL);
}

static void
785 786
on_assistant_apply (GtkWidget    *widget,
                    GtkAssistant *assistant)
787 788 789
{
  gboolean success;

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

792 793
  success = compute_next_step (assistant);

794 795 796 797
  /* 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
798
    g_signal_emit (assistant, signals [CLOSE], 0);
799 800 801
}

static void
802 803
on_assistant_forward (GtkWidget    *widget,
                      GtkAssistant *assistant)
804
{
805
  gtk_assistant_next_page (assistant);
806 807 808
}

static void
809 810
on_assistant_back (GtkWidget    *widget,
                   GtkAssistant *assistant)
811
{
812
  gtk_assistant_previous_page (assistant);
813 814 815
}

static void
816 817
on_assistant_cancel (GtkWidget    *widget,
                     GtkAssistant *assistant)
818 819 820 821 822
{
  g_signal_emit (assistant, signals [CANCEL], 0, NULL);
}

static void
823 824
on_assistant_last (GtkWidget    *widget,
                   GtkAssistant *assistant)
825
{
826
  GtkAssistantPrivate *priv = assistant->priv;
827 828

  while (priv->current_page->type == GTK_ASSISTANT_PAGE_CONTENT &&
829
         priv->current_page->complete)
830 831 832 833 834 835 836 837 838 839 840 841 842 843
    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,
844 845
                "gtk-alternative-button-order", &result,
                NULL);
846 847 848
  return result;
}

849 850 851 852 853 854 855 856 857 858 859 860 861 862 863 864 865
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;
}

866 867 868 869
static void
gtk_assistant_init (GtkAssistant *assistant)
{
  GtkAssistantPrivate *priv;
870
  GtkStyleContext *context;
871 872
  GtkWidget *main_box;
  GtkWidget *content_box;
873
  GtkWidget *sidebar_frame;
874

875 876 877 878
  assistant->priv = G_TYPE_INSTANCE_GET_PRIVATE (assistant,
                                                 GTK_TYPE_ASSISTANT,
                                                 GtkAssistantPrivate);
  priv = assistant->priv;
879

880 881
  /* use border on inner panes instead */
  gtk_container_set_border_width (GTK_CONTAINER (assistant), 0);
882

883 884
  gtk_widget_push_composite_child ();

885 886
  main_box = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 12);
  priv->sidebar = gtk_box_new (GTK_ORIENTATION_VERTICAL, 6);
887 888 889 890 891 892

  /* 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);
893 894
  gtk_style_context_add_class (context, GTK_STYLE_CLASS_SIDEBAR);

895 896 897
  g_signal_connect (sidebar_frame, "draw",
                    G_CALLBACK (assistant_sidebar_draw_cb), assistant);

898 899 900 901 902 903 904
  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);

905 906
  gtk_container_add (GTK_CONTAINER (sidebar_frame), priv->sidebar);
  gtk_box_pack_start (GTK_BOX (main_box), sidebar_frame, FALSE, FALSE, 0);
907
  gtk_box_pack_start (GTK_BOX (main_box), content_box, TRUE, TRUE, 0);
908
  gtk_box_pack_start (GTK_BOX (content_box), priv->content, TRUE, TRUE, 0);
909 910 911 912 913 914 915
  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);
916

917 918
  priv->close   = gtk_button_new_from_stock (GTK_STOCK_CLOSE);
  priv->apply   = gtk_button_new_from_stock (GTK_STOCK_APPLY);
919 920 921 922 923 924
  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));
925
  priv->cancel  = gtk_button_new_from_stock (GTK_STOCK_CANCEL);
926 927 928
  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));
929 930 931
  gtk_widget_set_can_default (priv->close, TRUE);
  gtk_widget_set_can_default (priv->apply, TRUE);
  gtk_widget_set_can_default (priv->forward, TRUE);
932

933 934 935 936 937 938 939
  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);
940 941 942

  if (!alternative_button_order (assistant))
    {
943 944 945 946 947 948
      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);
949 950 951
    }
  else
    {
952 953 954 955 956 957
      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);
958 959
    }

960 961 962
  gtk_widget_show (priv->forward);
  gtk_widget_show (priv->back);
  gtk_widget_show (priv->cancel);
963 964 965 966
  gtk_widget_show (priv->action_area);

  gtk_widget_pop_composite_child ();

967 968
  priv->title_size_group = gtk_size_group_new (GTK_SIZE_GROUP_HORIZONTAL);

969 970 971 972 973 974 975 976
  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;

977
  g_signal_connect (G_OBJECT (priv->close), "clicked",
978
                    G_CALLBACK (on_assistant_close), assistant);
979
  g_signal_connect (G_OBJECT (priv->apply), "clicked",
980
                    G_CALLBACK (on_assistant_apply), assistant);
981
  g_signal_connect (G_OBJECT (priv->forward), "clicked",
982
                    G_CALLBACK (on_assistant_forward), assistant);
983
  g_signal_connect (G_OBJECT (priv->back), "clicked",
984
                    G_CALLBACK (on_assistant_back), assistant);
985
  g_signal_connect (G_OBJECT (priv->cancel), "clicked",
986
                    G_CALLBACK (on_assistant_cancel), assistant);
987
  g_signal_connect (G_OBJECT (priv->last), "clicked",
988
                    G_CALLBACK (on_assistant_last), assistant);
989 990 991
}

static void
992 993 994 995 996
gtk_assistant_set_child_property (GtkContainer *container,
                                  GtkWidget    *child,
                                  guint         property_id,
                                  const GValue *value,
                                  GParamSpec   *pspec)
997 998 999 1000 1001
{
  switch (property_id)
    {
    case CHILD_PROP_PAGE_TYPE:
      gtk_assistant_set_page_type (GTK_ASSISTANT (container), child,
1002
                                   g_value_get_enum (value));
1003 1004 1005
      break;
    case CHILD_PROP_PAGE_TITLE:
      gtk_assistant_set_page_title (GTK_ASSISTANT (container), child,
1006
                                    g_value_get_string (value));
1007 1008 1009
      break;
    case CHILD_PROP_PAGE_HEADER_IMAGE:
      gtk_assistant_set_page_header_image (GTK_ASSISTANT (container), child,
1010
                                           g_value_get_object (value));
1011 1012 1013
      break;
    case CHILD_PROP_PAGE_SIDEBAR_IMAGE:
      gtk_assistant_set_page_side_image (GTK_ASSISTANT (container), child,
1014
                                         g_value_get_object (value));
1015 1016 1017
      break;
    case CHILD_PROP_PAGE_COMPLETE:
      gtk_assistant_set_page_complete (GTK_ASSISTANT (container), child,
1018
                                       g_value_get_boolean (value));
1019 1020 1021 1022 1023 1024 1025 1026 1027
      break;
    default:
      GTK_CONTAINER_WARN_INVALID_CHILD_PROPERTY_ID (container, property_id, pspec);
      break;
    }
}

static void
gtk_assistant_get_child_property (GtkContainer *container,
1028 1029 1030 1031
                                  GtkWidget    *child,
                                  guint         property_id,
                                  GValue       *value,
                                  GParamSpec   *pspec)
1032 1033 1034 1035 1036
{
  switch (property_id)
    {
    case CHILD_PROP_PAGE_TYPE:
      g_value_set_enum (value,
1037
                        gtk_assistant_get_page_type (GTK_ASSISTANT (container), child));
1038 1039 1040
      break;
    case CHILD_PROP_PAGE_TITLE:
      g_value_set_string (value,
1041
                          gtk_assistant_get_page_title (GTK_ASSISTANT (container), child));
1042 1043 1044
      break;
    case CHILD_PROP_PAGE_HEADER_IMAGE:
      g_value_set_object (value,
1045
                          gtk_assistant_get_page_header_image (GTK_ASSISTANT (container), child));
1046 1047 1048
      break;
    case CHILD_PROP_PAGE_SIDEBAR_IMAGE:
      g_value_set_object (value,
1049
                          gtk_assistant_get_page_side_image (GTK_ASSISTANT (container), child));
1050 1051 1052
      break;
    case CHILD_PROP_PAGE_COMPLETE:
      g_value_set_boolean (value,
1053
                           gtk_assistant_get_page_complete (GTK_ASSISTANT (container), child));
1054 1055 1056 1057 1058 1059 1060
      break;
    default:
      GTK_CONTAINER_WARN_INVALID_CHILD_PROPERTY_ID (container, property_id, pspec);
      break;
    }
}

1061 1062
static void
on_page_notify_visibility (GtkWidget  *widget,
1063 1064
                           GParamSpec *arg,
                           gpointer    data)
1065 1066 1067
{
  GtkAssistant *assistant = GTK_ASSISTANT (data);

1068
  if (gtk_widget_get_mapped (GTK_WIDGET (assistant)))
1069 1070 1071 1072
    {
      update_buttons_state (assistant);
      update_title_state (assistant);
    }
1073 1074
}

1075
static void
1076 1077
remove_page (GtkAssistant *assistant,
             GList        *element)
1078
{
1079
  GtkAssistantPrivate *priv = assistant->priv;
1080
  GtkAssistantPage *page_info;
1081
  GList *page_node;
1082 1083 1084

  page_info = element->data;

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

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

1099 1100 1101
          if (page_node == element)
            page_node = page_node->next;

1102 1103 1104 1105
          if (page_node)
            priv->current_page = page_node->data;
          else
            priv->current_page = NULL;
1106 1107
        }
    }
1108

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

1111 1112
  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);
1113

1114 1115 1116 1117 1118 1119 1120 1121
  gtk_container_remove (GTK_CONTAINER (priv->sidebar), page_info->regular_title);
  gtk_container_remove (GTK_CONTAINER (priv->sidebar), page_info->current_title);

  gtk_notebook_remove_page (GTK_NOTEBOOK (priv->content), gtk_notebook_page_num (GTK_NOTEBOOK (priv->content), page_info->page));
  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);
1122

Michael Natterer's avatar
Michael Natterer committed
1123
  g_slice_free (GtkAssistantPage, page_info);
1124
  g_list_free_1 (element);
1125 1126 1127 1128 1129 1130

  if (gtk_widget_get_mapped (GTK_WIDGET (assistant)))
    {
      update_buttons_state (assistant);
      update_actions_size (assistant);
    }
1131 1132 1133
}

static void
1134
gtk_assistant_destroy (GtkWidget *widget)
1135
{
1136
  GtkAssistant *assistant = GTK_ASSISTANT (widget);
1137
  GtkAssistantPrivate *priv = assistant->priv;
1138

1139 1140 1141 1142
  /* We set current to NULL so that the remove code doesn't try
   * to do anything funny
   */
  priv->current_page = NULL;
1143

1144 1145 1146 1147 1148 1149 1150 1151
  while (priv->pages)
    remove_page (assistant, priv->pages);

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

  if (priv->content)
    priv->content = NULL;
1152 1153

  if (priv->action_area)
1154 1155 1156
    priv->action_area = NULL;

  if (priv->button_size_group)
1157
    {
1158 1159
      g_object_unref (priv->button_size_group);
      priv->button_size_group = NULL;
1160 1161
    }

1162
  if (priv->title_size_group)
1163
    {
1164 1165
      g_object_unref (priv->title_size_group);
      priv->title_size_group = NULL;
1166 1167 1168 1169 1170
    }

  if (priv->forward_function)
    {
      if (priv->forward_function_data &&
1171 1172
          priv->forward_data_destroy)
        priv->forward_data_destroy (priv->forward_function_data);
1173 1174 1175 1176 1177 1178 1179 1180 1181 1182 1183 1184

      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;
    }

1185
  GTK_WIDGET_CLASS (gtk_assistant_parent_class)->destroy (widget);
1186 1187 1188 1189
}

static GList*
find_page (GtkAssistant  *assistant,
1190
           GtkWidget     *page)
1191
{
1192
  GtkAssistantPrivate *priv = assistant->priv;
1193
  GList *child = priv->pages;
1194

1195 1196 1197 1198
  while (child)
    {
      GtkAssistantPage *page_info = child->data;
      if (page_info->page == page)
1199
        return child;
1200 1201 1202

      child = child->next;
    }
1203

1204 1205 1206 1207 1208 1209
  return NULL;
}

static void
gtk_assistant_map (GtkWidget *widget)
{
1210 1211
  GtkAssistant *assistant = GTK_ASSISTANT (widget);
  GtkAssistantPrivate *priv = assistant->priv;
1212
  GList *page_node;
1213
  GtkAssistantPage *page;
1214
  gint page_num;
1215 1216

  /* if there's no default page, pick the first one */
1217
  page = NULL;
1218
  page_num = 0;
1219
  if (!priv->current_page)
1220 1221 1222
    {
      page_node = priv->pages;

1223
      while (page_node && !gtk_widget_get_visible (((GtkAssistantPage *) page_node->data)->page))
1224 1225 1226 1227
        {
          page_node = page_node->next;
          page_num++;
        }
1228 1229

      if (page_node)
1230
        page = page_node->data;
1231 1232
    }

1233 1234 1235 1236 1237 1238
  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);
1239 1240 1241 1242 1243 1244 1245

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

static void
gtk_assistant_unmap (GtkWidget *widget)
{
1246 1247
  GtkAssistant *assistant = GTK_ASSISTANT (widget);
  GtkAssistantPrivate *priv = assistant->priv;
1248 1249 1250 1251 1252 1253 1254 1255 1256 1257

  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,
1258
                            GdkEventAny *event)
1259
{
1260 1261
  GtkAssistant *assistant = GTK_ASSISTANT (widget);
  GtkAssistantPrivate *priv = assistant->priv;
1262 1263 1264 1265 1266 1267 1268 1269 1270 1271 1272 1273

  /* 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,
1274
                   GtkWidget    *page)
1275 1276 1277 1278 1279 1280
{
  gtk_assistant_append_page (GTK_ASSISTANT (container), page);
}

static void
gtk_assistant_remove (GtkContainer *container,
1281
                      GtkWidget    *page)
1282
{
1283
  GtkAssistant *assistant = (GtkAssistant*) container;
1284 1285 1286 1287 1288 1289 1290 1291 1292 1293 1294 1295 1296
  GList *element;

  element = find_page (assistant, page);

  if (element)
    {
      remove_page (assistant, element);
      gtk_widget_queue_resize ((GtkWidget *) container);
    }
}

/**
 * gtk_assistant_new:
1297
 *
1298 1299 1300 1301 1302
 * Creates a new #GtkAssistant.
 *
 * Return value: a newly created #GtkAssistant
 *
 * Since: 2.10
1303
 */
1304 1305 1306 1307 1308
GtkWidget*
gtk_assistant_new (void)
{
  GtkWidget *assistant;

1309 1310
  assistant = g_object_new (GTK_TYPE_ASSISTANT, NULL);

1311 1312 1313 1314 1315 1316 1317
  return assistant;
}

/**
 * gtk_assistant_get_current_page:
 * @assistant: a #GtkAssistant
 *
1318
 * Returns the page number of the current page.
1319
 *
1320
 * Return value: The index (starting from 0) of the current
1321 1322
 *     page in the @assistant, or -1 if the @assistant has no pages,
 *     or no current page.
1323 1324
 *
 * Since: 2.10
1325
 */
1326 1327 1328 1329 1330 1331 1332
gint
gtk_assistant_get_current_page (GtkAssistant *assistant)
{
  GtkAssistantPrivate *priv;

  g_return_val_if_fail (GTK_IS_ASSISTANT (assistant), -1);

1333
  priv = assistant->priv;
1334 1335 1336 1337 1338 1339 1340 1341 1342 1343 1344

  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.
1345 1346 1347
 *     If negative, the last page will be used. If greater
 *     than the number of pages in the @assistant, nothing
 *     will be done.
1348
 *
1349 1350 1351 1352
 * Switches the page to @page_num.
 *
 * Note that this will only be necessary in custom buttons,
 * as the @assistant flow can be set with
1353 1354 1355
 * gtk_assistant_set_forward_page_func().
 *
 * Since: 2.10
1356
 */
1357 1358
void
gtk_assistant_set_current_page (GtkAssistant *assistant,
1359
                                gint          page_num)
1360 1361 1362 1363 1364 1365
{
  GtkAssistantPrivate *priv;
  GtkAssistantPage *page;

  g_return_if_fail (GTK_IS_ASSISTANT (assistant));

1366
  priv = assistant->priv;
1367 1368 1369 1370

  if (page_num >= 0)
    page = (GtkAssistantPage *) g_list_nth_data (priv->pages, page_num);
  else
1371 1372 1373 1374
    {
      page = (GtkAssistantPage *) g_list_last (priv->pages)->data;
      page_num = g_list_length (priv->pages);
    }
1375 1376 1377 1378 1379 1380

  g_return_if_fail (page != NULL);

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

1381 1382 1383
  /* 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
1384
   */
1385
  if (gtk_widget_get_mapped (GTK_WIDGET (assistant)))
1386
    priv->visited_pages = g_slist_prepend (priv->visited_pages,
1387
                                           priv->current_page);
1388

1389
  set_current_page (assistant, page_num);
1390 1391
}

1392 1393 1394 1395
/**
 * gtk_assistant_next_page:
 * @assistant: a #GtkAssistant
 *
1396 1397 1398 1399
 * Navigate to the next page.
 *
 * It is a programming error to call this function when
 * there is no next page.
1400 1401
 *
 * This function is for use when creating pages of the
1402
 * #GTK_ASSISTANT_PAGE_CUSTOM type.
1403 1404
 *
 * Since: 3.0
1405
 */
1406 1407 1408 1409 1410 1411
void
gtk_assistant_next_page (GtkAssistant *assistant)
{
  g_return_if_fail (GTK_IS_ASSISTANT (assistant));

  if (!compute_next_step (assistant))
1412 1413 1414
    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");
1415 1416 1417 1418 1419 1420
}

/**
 * gtk_assistant_previous_page:
 * @assistant: a #GtkAssistant
 *
1421 1422 1423 1424
 * Navigate to the previous visited page.
 *
 * It is a programming error to call this function when
 * no previous page is available.
1425 1426
 *
 * This function is for use when creating pages of the
1427
 * #GTK_ASSISTANT_PAGE_CUSTOM type.
1428 1429
 *
 * Since: 3.0
1430
 */
1431 1432 1433 1434 1435 1436 1437 1438 1439 1440 1441 1442 1443 1444 1445 1446 1447 1448 1449 1450 1451 1452 1453
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 ||
1454
         !gtk_widget_get_visible (page_info->page));
1455

1456
  set_current_page (assistant, g_list_index (priv->pages, page_info));
1457 1458
}

1459 1460 1461 1462 1463 1464
/**
 * gtk_assistant_get_n_pages:
 * @assistant: a #GtkAssistant
 *
 * Returns the number of pages in the @assistant
 *
1465
 * Return value: the number of pages in the @assistant
1466 1467
 *
 * Since: 2.10
1468
 */