% $Header:$

\section{Introduction}

The \module{wizard} module makes it easy to write typical desktop GUI-style
"wizards," where a user is guided through a series of forms and enters
configuration data.  By design, the \module{wizard} module tries to
shield the developer from all aspects of GUI development.  This extends
to keeping the \module{Tkinter} names out of the global namespace.

A \var{wizard} encapsulates a set of interactions with a user.  It
contains one or more \dfn{sheets}.  A sheet can either have text for the
user to read, or it can have one or more data entry \dfn{fields}.  These
fields can be text entry, file or directory selection, check-boxes or
multiple-choice lists.
When the user indicates that they are finished, the \var{wizard}
returns a dictionary containing the data the user entered.  Entries can
also be \dfn{validated} (see section \ref{validate-sect}) by calling
out to application-specific code.  Validation can be done when the user
tries to move to the next sheet, or at the end after all the data has
been entered.  The \module{wizard} module includes several validators.

\section{Input}
The \module{wizard} module defines a number of classes.
Only the \class{Wizard} class has methods beyond the constructor.

\begin{classdesc}{Wizard}{sheets, title\optional{, root=None\optional{, defaults=None}}\optional{, **keywords}}
The main class of this module.
Users create an instance of this class, and then \method{run} it, which creates
the display and solicits input from the user.

The \var{root} parameter is the root of the display into which the
wizard should be created, and is usually \code{None}.
(Advanced applications may wish to create their own popup or frame, for
example, but must be aware that the \module{wizard} module uses
\module{Tkinter}'s \code{grid} layout manager.)
The \var{sheets} parameter is a list of \class{LicenseSheet},
\class{DynamicSheet}, and
\class{Sheet} objects, each of which represents one ``page'' of
display.
The \var{title} parameter is a text string that is displayed at the
top of the wizard.
The \code{defaults} parameter is a dictionary that contains the default
(initial) values of all the entries.

The following keyword parameters can also be given:

\begin{tableiii}{l|c|p{30em}}{textrm}{Keyword}{Default}{Description}
\lineiii{\code{entrywidth}}{\code{40}}{The desired width of text entry
    fields, in characters.}
\lineiii{\code{whenvalidate}}{\code{atend}}{When to validate user input.
    Choose from \code{atend} or \code{perpage}.}
\lineiii{\code{center}}{\code{0}}{If non-zero, then the wizard is
    centered in the screen.}
\lineiii{\code{geometry}}{\code{None}}{A \code{geometry} string used to
    specify the on-screen placement. The default is for the
    window system to control the location.}
\lineiii{\code{help}}{\code{None}}{A \class{Help} object for
    context-sensitive help, see section \ref{help-sect}.}
\lineiii{\code{helpkey}}{\code{'<F1>'}}{If a \class{Help} object is given,
    then this parameter specifies the help key.
    For details, see the \module{Tkinter} documentation.}
\lineiii{\code{titlefont}}{16-point bold italic}{A font modifier to display the
    title font.}
\lineiii{\code{sheetfont}}{underlined}{The font modifier for
    the sub-titles on each sheet.}
\lineiii{\code{sheetchange}}{\code{None}}{A \class{SheetChange} object, see
    below.}
\end{tableiii}
The \code{center} and \code{geometry} parameters are ignored if \code{root}
is not \code{None}.
\end{classdesc}

\begin{methoddesc}{set_defaults}{defaults}
Sets the default initial values from the dictionary \code{defaults}.
\end{methoddesc}

\begin{methoddesc}{run}{self\optional{, start = 0}}
Start the display and collect the user's input.
The optional \code{start} parameter specifies the starting sheet.
This method returns \code{None} if the user clicked on the \code{Cancel}
button, or a dictionary containing the data the user provided.
Note that all values in the dictionary are text strings.

This method can be called multiple times.
Unless \code{set_defalts} is called, subsequent invocations will start with
the values from the previous \code{run}.
\end{methoddesc}

\begin{classdesc}{LicenseSheet}{title, text\optional{, **keywords}}
A \class{LicenseSheet} object contains text to be displayed to the user.
The text can be
specified inline, or come from an external file.

The \code{title} is the text to appear at the top of the sheet.
The \code{text} is the text to display. If the \code{file}
parameter is specified (see the following table), then \code{text} is
used only if the file cannot be read.

The following keyword parameters can also be given:

\begin{tableiii}{l|c|p{30em}}{textrm}{Keyword}{Default}{Description}
\lineiii{\code{file}}{\code{None}}{Name of the file containing text to display.
    It is not an error if the file cannot be open -- the
    value of the \var{text} will be used instead.}
\lineiii{\code{height}}{\code{10}}{Height of display area, in lines.}
\lineiii{\code{width}}{\code{40}}{Width of display area, in characters.}
\lineiii{\code{wrap}}{\code{Tkinter.WORD}}{Whether or not to do word-wrapping
    on lines. If set to \code{Tkinter.CHAR} then lines
    are broken without regard to word boundaries. To avoid
    wrapping, use \code{hscrollbar}.}
\lineiii{\code{font}}{\code{None}}{Font modifier; see below.}
\lineiii{\code{mustread}}{\code{0}}{If non-zero, then the \code{Next/Done}
    button is disabled until the end of the text appears on
    the screen.}
\lineiii{\code{hscrollbar}}{\code{0}}{If non-zero, then a horizontal
    scrollbar is attached to the bottom of the text box.}
\end{tableiii}
\end{classdesc}

\begin{classdesc}{DynamicSheet}{title, builder\optional{, **keywords}}
A \class{DynamicSheet} object is like a \class{LicenseSheet} except that the
content is generated dynamically each time the sheet is displayed.
This class can be used to generate ``confirmation'' pages that offer a last
chance to view the actions that the program is about to perform.

The \code{title} is the text to appear at the top of the sheet.
The \code{builder} is an object that must implement the following three
methods:
\begin{methoddesc}[builder.__class__]{open}{dict}
The \var{dict} parameter is a dictionary with all the current field
values.
The return value is ignored.
\end{methoddesc}
\begin{methoddesc}[builder.__class__]{readline}{}
This method returns text to be displayed.
It will be called repeatedly until it returns \code{None}.
\end{methoddesc}
\begin{methoddesc}[builder.__class__]{close}{}
This method will be called when \code{readline} is finished.
The return value is ignored.
\end{methoddesc}

The following \code{height}, \code{width}, \code{wrap}, \code{font}, and
\code{hscrollbar} keyword parameters defined in the \class{LicenseSheet}
class can also be used.

\end{classdesc}

\begin{classdesc}{Sheet}{title, fields\optional{, **keywords}}
A \class{Sheet} object contains entry fields for the user to fill-in.
The \var{title} parameter is a text string to be
displayed at the top of the sheet.
The \var{fields} parameter is a list of \class{``xxxField''} objects.

The following keyword parameters can also be given:

\begin{tableiii}{l|c|p{30em}}{textrm}{Keyword}{Default}{Description}
\lineiii{\code{longvalidate}}{\code{0}}{If non-zero, then validating
    the fields on this sheet could take
    a noticeable amount of time, so the \module{wizard}
    module will temporarily change the cursor to an hourglass
    or its equivalent.}
\end{tableiii}
\end{classdesc}

The field objects also accept the \code{longvalidate} parameter.

\begin{classdesc}{SpacerField}{\optional{**keywords}}
This object is used to leave one or more blank lines, usually used to
group related entries.

The following keyword parameters can also be given:

\begin{tableiii}{l|c|p{30em}}{textrm}{Keyword}{Default}{Description}
\lineiii{\code{lines}}{\code{1}}{Number of blanks lines desired.}
\end{tableiii}
\end{classdesc}

\begin{classdesc}{LabelField}{prompt}
This object is used to display a line of text on the sheet.
It is usually used to provide a heading for a group of related fields,
or to provide a label for subsequent a \class{RBField} (radiobutton) object.

The following keyword parameters can also be given:

\begin{tableiii}{l|c|p{30em}}{textrm}{Keyword}{Default}{Description}
\lineiii{\code{font}}{\code{None}}{Font modifier; see below.}
\lineiii{\code{align}}{\code{align_l}}{Specifies the alignment of the text; use
    \code{align_l}, \code{align_c}, or \code{align_r} for
    flush-left, center, or flush-right alignment.}
\end{tableiii}
\end{classdesc}

\begin{classdesc}{EntryField}{key, prompt\optional{, **keywords}}
This object is used to collect a line of text from the user.
The \var{key} parameter specifies the key in the dictionary of returned values
to use for this field.
The \var{prompt} parameter specifies the prompt text that will appear
to the left of the text-entry area.

The following keyword parameters can also be given:

\begin{tableiii}{l|c|p{30em}}{textrm}{Keyword}{Default}{Description}
\lineiii{\code{private}}{\code{0}}{If non-zero, the text will not be
    displayed.  This is useful for password fields.}
\lineiii{\code{validate}}{\code{None}}{A \class{Validation} object to be
    called when validating the field.
    See section \ref{validate-sect}.}
\lineiii{\code{startdisabled}}{\code{0}}{If non-zero, then the field is
    initially disabled.}
\lineiii{\code{entrywidth}}{\code{30}}{The desired input width, in charcters.}
\end{tableiii}
\end{classdesc}

\begin{classdesc}{FileField}{key, prompt\optional{, **keywords}}
This is the same as \class{EntryField}, except that a ``browse'' button
appears to the right of the entry area, allowing the user to browse the
filesystem and select an existing file.
\end{classdesc}

\begin{classdesc}{DirField}{key, prompt\optional{, **keywords}}
This is the same as \class{FileField}, except that the user must pick
a directory.
\versionadded{1.4}
\end{classdesc}

\begin{classdesc}{CBField}{key, prompt\optional{, **keywords}}
This object creates a checkbox on the sheet, with \code{prompt} as the
text.
The \var{key} and \var{prompt} parameters are as described in the
\class{Entryfield} class, above.
The returned value will be the text string \code{``0''} or \code{``1''}.

The following keyword parameters can also be given:

\begin{tableiii}{l|c|p{30em}}{textrm}{Keyword}{Default}{Description}
\lineiii{\code{enables}}{\code{None}}{A comma separated list of \code{keys}
    that identify the fields controlled by this button.  The fields
    in the list are enabled, or disabled, according to whether this
    field is checked or not.}
\end{tableiii}
\end{classdesc}

\begin{classdesc}{RBField}{key, choices\optional{, **keywords}}
The \var{key} parameter is as described in the
\class{Entryfield} class, above.
The \var{choices} parameter is an array of text strings.
They will be displayed one per line, with the user allowed to select one.
The returned value will be the numeric index of the value chosen, as a string.
\end{classdesc}

\begin{classdesc}{DDField}{key, prompt, choices\optional{, **keywords}}
This is like a \class{RBField}, except that it is presented with the
specified \var{prompt} on the left and a pull-down list of the \var{choices}
on the right.
The returned value will be the numeric index of the value chosen, as a string.
\versionadded{1.4}
\end{classdesc}

\subsection{Font Modifiers}

The \module{wizard} module uses Python dictionaries to control fonts,
using the \module{tkFont} module to create the fonts it needs.
The following table lists the dictionary entries that can be used to modify the
default fonts chosen by the \module{wizard} module.
Note that the keys are text strings.

\begin{tableii}{l|p{30em}}{textrm}{Key}{Description}
\lineii{family}{A text string naming the font family.}
\lineii{size}{An integer specifying the ont size in points;
    use a negative number to specify size in pixels.}
\lineii{weight}{The font thickness; use \code{tkFont.NORMAL} (generally
    the default) or \code{tkFont.BOLD}.}
\lineii{slant}{The font slant; use \code{tkFont.NORMAL} (the default) or
    \code{tkFont.ITALIC}.}
\lineii{underline}{If non-zero, text is underlined; the default is \code{0}
    except for the sheet titles.}
\lineii{overwrite}{If non-zero, text is overstruck; the default is \code{0}.}
\end{tableii}

\subsection{Sheet Changes}

When a \class{Wizard} object is created, a callback object can
be given that will be invoked every time the wizard displays a
different sheet.
This object must implement the following method:

\begin{methoddesc}[sheetchange.__class__]{sheetchange}{dict, sheetnum}
The \var{dict} parameter is a dictionary with all the current field
values.
The \var{sheetnum} parameter is the new current sheet number.
The return value is ignored.
In particlar, it is impossible to prevent moving to the new sheet ---
see the description of validators in section \ref{validate-sect}.
\end{methoddesc}

\subsection{Example}

The following example constructs a three-sheet wizard.
Note that it also uses validators which are described in section
\ref{validate-sect}

\begin{verbatim}
license='''This is your license agreement...
'''

MySheets = (
    LicenseSheet('License', license, font={'family':'fixed'}, mustread=1),
    Sheet('Account information', (
        EntryField('adminname', 'Administrator name',
            validate=Nonblank('administrator')),
        EntryField('adminpass1', 'Administrator password',
            private=1, validate=PassConfirm('administrator', 'adminpass2')),
        EntryField('adminpass2', 'Repeat password',
            private=1),
    )),
    Sheet('Network information', (
        EntryField('hostname', 'Name of the host',
            validate=Nonblank('hostname')),
        FileField('path', 'Hosts file'),
        SpacerField(),
        LabelField('Network protocol'),
        RBField('protocol', ('TCP/IP', 'DECnet') ),
        SpacerField(),
        CBField('localserver', 'Start local server', enables='portnum'),
        EntryField('portnum', 'Server port#',
            validate=InactivePort('port'))
    )),
)
\end{verbatim}

\section{Validation \label{validate-sect}}

Most fields accept a \code{validate} parameter, which takes an object
responsible for validating the user's input.
A validator must implement the \method{validate} function.
If the user's input is invalid, it should raise the
\exception{InvalidEntry} exception.

\begin{classdesc}{Validator}{foo}
The \class{Validator} class may be used as a base class for any
validators.
\end{classdesc}

\begin{methoddesc}{validate}{dict, field}
This method validates the user's input.
The \var{dict} parameter will be a dictionary containing all the current
values, and while the \var{field} is the key for the field being validated.
(Remember that values in the dictionary are text strings.)
If the user's input is not valid, this method should raise an
\exception{InvalidEntry} (or subtype) exception.
The return value is ignored.
\end{methoddesc}

\begin{excdesc}{InvalidEntry}
Exception raised when the input is invalid. The constructor takes a
text string (stored as the \code{text} attribute) which will
be displayed to the user.
\end{excdesc}

A number of utility validators are provided in the \module{wizard}
module:

\begin{classdesc}{PassConfirm}{what, confirm}
Validate that a field and its ``confirmation'' match.
The \var{what} parameter should be a very short description (usually one word)
displayed to the user.
The \var{confirm} is the dictionary key that has the confirming
(``other'') entry.
\end{classdesc}

\begin{classdesc}{Nonblank}{what}
Validate that a field is not blank.
The \var{what} parameter is described in the \class{PassConfirm}
class, above.
\end{classdesc}

\begin{classdesc}{PositiveNumber}{what}
Validate that a field is a positive number.
The \var{what} parameter is described in the \class{PassConfirm}
class, above.
\end{classdesc}

\begin{classdesc}{InactivePort}{what}
Validate that a field specifies a TCP port that is not used on the
local host.
This is a subtype of \class{PositiveNumber}.
\end{classdesc}

\subsection{Example}

Here is the source for the \class{PassConfirm} class, used in the previous
example:

\begin{verbatim}
class PassConfirm(Validator):
    def __init__(self, what, confirm):
        self.what = what
        self.confirm = confirm
    def validate(self, dict, field):
        if len(dict[field]) == 0:
            raise InvalidEntry, 'Must provide %s password.' % (self.what,)
        if dict[field] != dict[self.confirm]:
            raise InvalidEntry, 'Mismatched %s passwords.' % (self.what,)
\end{verbatim}

\section{Help \label{help-sect}}

A \class{wizard} object can provide context-sensitive help.
To do this, the object must be provided when the wizard is created, and
it must implement the following two methods:

\begin{methoddesc}{help}{root, key}
This method should display the help appropriate for the indicated \var{key}.
The \var{root} parameter is the display root provided when the wizard
was created.
It can (and often will) be \code{None}.
\end{methoddesc}

\begin{methoddesc}{sheethelp}{root, sheetnum}
This method is similar, but should display general help for sheet number
\code{sheetnum}.
\end{methoddesc}

The \module{wizard} module provides a utility class that implements
pop-up help, \class{HelpPopup}.

\begin{classdesc}{HelpPopup}{}
This class provides unformatted help text in a popup dialog text box
with a scrollbar and a \code{Dismiss} button.
It implements \method{sheethelp} as a call to \method{help} with
the key specified as \code{sheetN}, where \code{N} is the sheet
number.

To use this class, create your own class derived from \class{HelpPopup}
and implement the following methods:
\end{classdesc}

\begin{methoddesc}{start}{key}
This method should do any preparations necessary to retrieve the
help text.
The return value is ignored.
If it raises an exception, then a message saying no help is available
will be displayed.
\end{methoddesc}

\begin{methoddesc}{eof}{key}
This method should return zero as long as there is more text to
display.
This method is called \emph{before} each call to \method{get}.
\end{methoddesc}

\begin{methoddesc}{get}{key}
This method should return (portions of) the help text to display.
\end{methoddesc}

\subsection{Example}

Here is a class that stores each help in its own file.
\begin{verbatim}
import os
class MyHelp(HelpPopup):
    def __init__(self, helpdir):
        HelpPopup.__init__(self)
        self.filename = helpdir
    def start(self, key):
        # caller catches exception and prints default.
        self.infile = open(os.path.join(self.helpdir, key), "r")
    def get(key):
        # read all the text at once and return it.
        text = self.infile.read()
        self.infile.close()
        return text
    def eof(key):
        return self.infile.closed
\end{verbatim}

\section{More Examples}

This example uses the code and data from the previous examples, to
make a complete wizard:

\begin{verbatim}
from wizard import *

helpobj = MyHelp('/usr/local/share/helpdir')
w = Wizard(MySheets, 'Installation', None,
        whenvalidate=perpage, help=helpobj)
w.set_defaults({'adminname': 'root',
        'path': '/etc/hosts',
        'protocol': '0',
        'localserver': '1'})
answers = w.run()
if answers == None:
    print 'Cancelled.'
    sys.exit(1)
\end{verbatim}

This example puts a cycling series of images to the left of the wizard:

\begin{verbatim}
from wizard import *
from Tkinter import *

class Cycler:
    def __init__(self, sheets, label):
        self.label = label
        self.images = [ ]
        try:
            Idefault = PhotoImage('default.gif')
        except:
            Idefault = None
        for i in range(len(sheets)):
            try:
                self.images.append(PhotoImage(file="pic%d.gif" % (i,)))
            except:
                self.images.append(Idefault)
    def goto(self, dict, sheetnum):
        self.label['image'] = self.images[sheetnum]

# Create a display root, the left-side frame holds the images
# and the right-side frame holds the wizard.
title = 'Installation'
root = Tk()
root.title(title)
root.iconname(title)
fLeft = Frame(root)
imageholder = Label(fLeft)
imageholder.grid()
fLeft.grid(row=0, column=0)
fRight = Frame(root)
fRight.grid(row=0, column=1)

w = Wizard(MySheets, title, fRight,
    sheetchange=Cycler(MySheets, imageholder))
answers = w.run()
if answers == None:
    print 'Cancelled.'
    sys.exit(1)
\end{verbatim}
