<!DOCTYPE article PUBLIC "-//OASIS//DTD DocBook XML V4.3//EN"
 "http://www.oasis-open.org/docbook/xml/4.3/docbookx.dtd"
  [
    <!ENTITY selfURL
        "http://www.nmt.edu/tcc/help/lang/python/examples/huey/ims/">
    <!ENTITY huey     "<application>huey</application>">
    <!ENTITY rgb-txt  "<filename>rgb.txt</filename>">
  ]
>
<article>
  <articleinfo>
    <title>&huey;: Internal maintenance specification</title>
    <authorgroup>
      <author>
        <firstname>John W.</firstname>
        <surname>Shipman</surname>
      </author>
    </authorgroup>
    <address><email>tcc-doc@nmt.edu</email>
    </address>
    <revhistory>
      <revision>
        <revnumber>$Revision: 1.39 $</revnumber>
        <date>$Date: 2008/01/09 04:57:43 $</date>
      </revision>
    </revhistory>
    <abstract>
      <para>
        Describes the implementation of &huey;, a graphical
        application for selecting colors and fonts, using the
        Python programming language and the Tkinter widget set.
      </para>
      <para>
        This publication is available in <ulink url="&selfURL;"
        >Web form</ulink > and also as a <ulink
        url="&selfURL;hueyims.pdf" >PDF document</ulink >.  Please
        forward any comments to <userinput
        >tcc-doc@nmt.edu</userinput >.
      </para>
    </abstract>
  </articleinfo>
  <section id='intro'>
    <title>Introduction</title>
    <para>
      This document describes the implementation of &huey;, a
      graphical user interface tool for displaying various colors
      and text fonts.  See the external specification, <ulink
      url='http://www.nmt.edu/tcc/help/lang/python/examples/huey/'
      ><citetitle >&huey;: a color and font selection
      tool</citetitle ></ulink >.
    </para>
    <para>
      For those interested in analyzing this program as an
      example of Python and Tkinter programming, the entire
      Python source for the program is presented here using a
      lightweight literate programming (LLP) style.  For more on
      LLP, see <ulink
      url='http://www.nmt.edu/~shipman/soft/litprog/' >the
      author's literate programming page</ulink >.
    </para>
    <section id='online-files'>
      <title>Files discussed in this publication</title>
      <para>
        Here are online links to all the files required by this
        program (other than Python and Tkinter installs).
      </para>
      <itemizedlist>
        <listitem>
          <para>
            <ulink url='&selfURL;huey' ><filename >huey</filename
            ></ulink >: The main script.
          </para>
        </listitem>
        <listitem>
          <para>
            <ulink url='&selfURL;scrolledlist.py' ><filename
            >scrolledlist.py</filename ></ulink >: Source for the
            <code >ScrolledList</code > compound widget.
          </para>
        </listitem>
        <listitem>
          <para>
            <ulink url='&selfURL;fontselect.py' ><filename
            >fontselect.py</filename ></ulink >: Source for the
            <code >FontSelect</code > compound widget.
          </para>
        </listitem>
        <listitem>
          <para>
            <ulink url='&selfURL;hueyims.xml' ><filename
            >hueyims.xml</filename ></ulink >: DocBook source for
            this document.
          </para>
        </listitem>
      </itemizedlist>
    </section> <!--online-files-->
  </section> <!--intro-->
  <section id='references'>
    <title>References</title>
    <para>
      The author has relied heavily on one book as a
      comprehensive reference on color theory as it relates to
      computer graphics.
    </para>
    <blockquote>
      <para>
        Foley, James D., Andries van Dam, Steven K. Feiner, and
        John F. Hughes.  Computer graphics: principles and
        practice.  Second edition.  Addison-Wesley, 1992. ISBN
        0-201-12110-7.
      </para>
    </blockquote>
  </section> <!--references-->
  <section id='design'>
    <title>Design notes</title>
    <para>
      Before proceeding, be sure you have a basic understanding
      of how Tkinter applications are built.  Strongly
      recommended is the author's <ulink
      url='http://www.nmt.edu/tcc/help/pubs/tkinter/' ><citetitle
      >Tkinter reference</citetitle ></ulink >.
    </para>
    <para>
      This program has a lot of widgets.  To keep the design
      conceptually simple, the architecture of the program uses
      boxes within boxes within boxes, grouping related widgets
      into frames, and related frames into larger frames, so that
      each grouping has a well-defined function, and connections
      within a grouping of widgets are hidden within a single
      Python class.
    </para>
    <para>
      One way to describe this technique is that we build
      <firstterm >compound widgets</firstterm > out of Tkinter's
      basic widgets.  A compound widget is a Python class that
      inherits from the <code >Frame</code > widget.
    </para>
    <para>
      This application uses the <code >.grid()</code > geometry
      manager to position widgets.  At the very highest level,
      the application is organized into two rows and three
      columns like this:
    </para>
    <informalfigure>
      <!--This diagram was created with xfig.  The source file is
       !  fig/topgrid.fig.  It is exported at these sizes to:
       !    fig/topgrid.png:  200% for HTML
       !    fig/topgrid.pdf:  100% for PDF
       !-->
      <mediaobject>
        <imageobject role="html">
          <imagedata fileref="fig/topgrid.png"/>
        </imageobject>
        <imageobject role="fo">
          <imagedata fileref="fig/topgrid.pdf"/>
        </imageobject>
      </mediaobject>
    </informalfigure>
    <para>
      The three columns of row 0 are spanned to make one wide,
      short grid cell that contains the general controls.  The name
      <code >.menuBar</code > has a dot in front of it because it
      is an attribute of the <code >Application</code > class,
      the <code >Frame</code > that contains all the
      application's widgets.  The compound widget that appears in
      this grid cell is an instance of class <code >MenuBar</code
      >; see <xref linkend='class-MenuBar' />.
    </para>
    <para>
      Similarly, three compound widgets fill out row 1.  Column 0
      contains a <code >NamePicker</code > compound widget, which
      is stored in attribute <code >.namePicker</code > of the
      <code >Application</code > class.  Column 1 is an <code
      >Adjuster</code > widget, and column 2 is a <code
      >Swatch</code > widget.
    </para>
    <para>
      Below this level, design of each of the four major areas proceeds
      by stepwise refinement.  Here is a diagram showing the layout of
      all the compound widgets in this program.
    </para>
    <informalfigure>
      <!--This artwork was creating using xfig; the source file is
       !  fig/nesting.fig.
       !    Export to PDF at 100%.
       !    Export to PNG at 200%.
       !-->
      <mediaobject>
        <imageobject role="html">
          <imagedata fileref="fig/nesting.png"/>
        </imageobject>
        <imageobject role="fo">
          <imagedata fileref="fig/nesting.pdf"/>
        </imageobject>
      </mediaobject>
    </informalfigure>
    <itemizedlist spacing="compact">
      <listitem>
        <para>
          <xref linkend='class-Application' /> is the entire
          application.
        </para>
      </listitem>
      <listitem>
        <para>
          <xref linkend='class-MenuBar' /> contains the controls along
          the top edge.
        </para>
      </listitem>
      <listitem>
        <para>
          <xref linkend='class-NamePicker' /> contains all controls for
          selecting colors by name.
        </para>
      </listitem>
      <listitem>
        <para>
          <xref linkend='class-PickList' /> is the list of standard
          colors.
        </para>
      </listitem>
      <listitem>
        <para>
          <xref linkend='class-Adjuster' /> contains controls for
          selecting text or background color, selecting the color model,
          and adjusting colors.
        </para>
      </listitem>
      <listitem>
        <para>
          <xref linkend='class-ColorReadout' /> shows the current text
          and background color names and allows the user to select which
          color is being adjusted.
        </para>
      </listitem>
      <listitem>
        <para>
          <xref linkend='class-ModelSelector' /> allows the user to
          select a color model.
        </para>
      </listitem>
      <listitem>
        <para>
          <xref linkend='class-ColorSliders' /> is a set of <code
          >Scale</code > widgets for making fine color adjustments.
        </para>
      </listitem>
      <listitem>
        <para>
          <xref linkend='class-ParamSlider' /> is one of those <code
          >Scale</code > widgets.
        </para>
      </listitem>
      <listitem>
        <para>
          <xref linkend='class-Swatch' /> displays the current font in
          the current text color against the current background color,
          and includes widgets for selecting the font.
        </para>
      </listitem>
    </itemizedlist>
    <para>
      The actual code for &huey; starts in <xref
      linkend='prologue' />.  The actual program logic starts
      with <xref linkend='main' />.
    </para>
  </section> <!--design-->
  <section id='prologue'>
    <title>Code prologue</title>
    <para>
      The actual code starts with the usual Unix
      &#x201c;pound-bang line&#x201d; that makes the script
      self-executing.  This is followed by a comment pointing
      the reader at this documentation.
    </para>
    <programlisting role='outFile:huey'
>#!/usr/local/bin/python
#================================================================
# huey:  A color and font selection tool
#   For documentation, see:
#     &selfURL;
#----------------------------------------------------------------

PROGRAM_NAME      =  "huey"
EXTERNAL_VERSION  =  "1.0"
</programlisting>
    <section id='imports'>
      <title>Imports</title>
      <para>
        Here are the module imports for this script.  First is
        the standard Python <code >math</code > module.
      </para>
      <programlisting role='outFile:huey'
>import math
</programlisting>
      <para>
        We'll need the <code >os.path</code > module to do file name
        manipulation.
      </para>
      <programlisting role='outFile:huey'
>import os
</programlisting>
      <para>
        All the <code >Tkinter</code > GUI package is added
        directly to the global namespace.  The <code
        >tkFont</code > module is necessary to select fonts.  The
        <code >Dialog</code > module makes it easy to create
        pop-up menus.
      </para>
      <programlisting role='outFile:huey'
>from Tkinter import *
import tkFont
from Dialog import Dialog
</programlisting>
      <para>
        <code >ScrolledList</code > is a compound widget, a
        combination of a <code >Listbox</code > and a vertical
        <code >Scrollbar</code >.  For its documentation, see
        <ulink
        url='http://www.nmt.edu/tcc/help/lang/python/examples/scrolledlist/'
        ><citetitle ><code >ScrolledList</code >: A Tkinter
        scrollable list widget</citetitle ></ulink >.
      </para>
      <programlisting role='outFile:huey'
>from scrolledlist import ScrolledList
</programlisting>
      <para>
        The other compound widget described elsewhere is <code
        >FontSelect</code >: a group of controls for selecting
        and displaying fonts.  For documentation, see <ulink
        url='http://www.nmt.edu/tcc/help/lang/python/examples/fontselect/'
        ><citetitle ><application >FontSelect</application >: A
        Tkinter widget to select fonts</citetitle ></ulink >.
      </para>
      <programlisting role='outFile:huey'
>from fontselect import FontSelect
</programlisting>
    </section> <!--imports-->
    <section id='constants'>
      <title>Manifest constants</title>
      <para>
        The constant <code >MAX_PARAM</code > defines the maximum value
        for a color parameter.  For example, black is represented as the
        triple (0,0,0), while white is represented as
        (65535,65535,65535).
      </para>
      <programlisting role='outFile:huey'
>#================================================================
# Manifest constants
#----------------------------------------------------------------

MAX_PARAM  =  65535
</programlisting>
      <para>
        The constant <code >N_PARAMS</code > is the number of parameters
        in a color model.  This will always be three, but we use a
        manifest constant so that readers will not have to look at a
        literal &#x201c;3&#x201d; and try to figure out three of what.
      </para>
      <programlisting role='outFile:huey'
>N_PARAMS  =  3
</programlisting>
      <para>
        The constant <code >MAX_BYTE</code > is the largest number
        that can be stored in a byte as an unsigned integer.
      </para>
      <programlisting role='outFile:huey'
>MAX_BYTE  =  255
</programlisting>
      <para>
        Also global are the names of the fonts used throughout
        the application.  <code >BUTTON_FONT</code > is used more or
        less everywhere; <code >MONO_FONT</code > is used where a
        monospaced font is required.
      </para>
      <programlisting role='outFile:huey'
>BUTTON_FONT  =  ('times', 12)
MONO_FONT  =  ('lucidatypewriter', 14, 'bold')
</programlisting>
    </section> <!--constants-->
  </section> <!--prologue-->
  <section id='main'>
    <title><code >main()</code >: The main program</title>
    <para>
      The graphical user interface as a whole is represented by
      the <code >Application</code > class.  Calling the
      constructor initializes the application.  See <xref
      linkend='class-Application' />.
    </para>
    <programlisting role='outFile:huey'
># - - - - -   m a i n

def main():
    """Main program.

      [ display a graphical application that allows the user to
        test various fonts in various colors displayed on various
        background colors ]
    """

    #-- 1 --
    # [ the screen  :=  the screen with a graphical application
    #   app  :=  the graphical application ]
    app  =  Application()
</programlisting>
    <para>
      Before actually running the application, we need to place
      the title of the application in the top-level window.  This
      title will be displayed in the decorations applied by the
      user's window manager.  The <code >.winfo_toplevel()</code
      > method returns the top-level window of the application;
      the <code >.title()</code > method sets the title in that
      window.  If we don't do this, the default window title will
      be just &#x201c;tk&#x201d;.
    </para>
      <programlisting role='outFile:huey'
>    #-- 2 --
    # [ app  :=  app with its window title set to the name of
    #            this application ]
    app.winfo_toplevel().title( "%s %s" %
                                (PROGRAM_NAME,  EXTERNAL_VERSION) )
</programlisting>
    <para>
      The <code >.mainloop()</code > method starts the
      application running.  It will respond to events until the
      user terminates it by clicking the <guibutton
      >Quit</guibutton > button.
    </para>
    <programlisting role='outFile:huey'
>    #-- 3 --
    # [ app  :=  app responding to user events ]
    app.mainloop()
</programlisting>
  </section> <!--main-->
  <section id='class-Color'>
    <title><code >class Color</code >: A specific color</title>
    <para>
      An instance of the <code >Color</code > class is used to represent
      a specific color.  There are two obvious ways to represent the
      red, green, and blue components: as floating point numbers or as
      integers.  The obvious way to use a float is to let 0.0 represent
      none and let 1.0 represent all of a component.  However, the
      &#x201c;<code >#RRGGBB</code >&#x201d; representation is also
      common, and in that form each component is an integer in the range
      [0, <code >MAX_PARAM</code >].  An internal precision of 16 bits
      per color is plenty.
    </para>
    <para>
      Here is the class interface:
    </para>
    <programlisting role='outFile:huey'>
# - - - - -   c l a s s   C o l o r

class Color:
    """Represents an arbitrary color.

      Exports:
        Color ( red, green, blue ):
          [ (red is the red value as a float in [0.0,1.0] or as an
            int in [0,MAX_PARAM]) and
            (green is the green value as a float in [0.0,1.0] or as an
            int in [0,MAX_PARAM]) and
            (blue is the blue value as a float in [0.0,1.0] or as an
            int in [0,MAX_PARAM]) ->
              return a new Color instance with those color values ]
</programlisting>
      <para>
        Internally, a color is represented in the integer form.
        The three components are exported as read-only
        attributes.
      </para>
      <programlisting role='outFile:huey'
>        .r:    [ the red component as an int in [0,MAX_PARAM] ]
        .g:    [ the green component as an int in [0,MAX_PARAM] ]
        .b:    [ the blue component as an int in [0,MAX_PARAM] ]
</programlisting>
      <para>
        The <code >.__str__()</code > method defines the behavior
        of the built-in Python <code >str()</code > function when
        applied to instances of this class.  See <xref
        linkend='Color-str' />.
      </para>
      <programlisting role='outFile:huey'
>        .__str__(self):
          [ return self as a string "#RRGGBB" ]
</programlisting>
      <para>
        The <code >.__cmp__()</code
        > function is used to compare two colors to see if they are the
        same color.  See <xref linkend='Color-cmp' />.
      </para>
      <programlisting role='outFile:huey'
>        .__cmp__(self, other):
          [ other is a Color instance ->
              if self's color name is less than other's ->
                return a negative number
              else if self's color name is greater than other's ->
                return a positive number
              else -> return 0 ]
    """
</programlisting>
    <section id='Color-init'>
      <title><code >Color.__init__()</code >: Constructor</title>
      <para>
        The constructor accepts each of the three color
        components as either a float or an integer.  Floats are
        in the range [0.0, 1.0], but integers are in the range
        [0,<code >MAX_PARAM</code >].  The <code
        >.__standardize()</code > method translates the float
        form into the integer form; see <xref
        linkend='Color-standardize' />.
      </para>
      <programlisting role='outFile:huey'
># - - -   C o l o r . _ _ i n i t _ _

    def __init__ ( self, red, green, blue ):
        """Constructor for Color
        """

        #-- 1 --
        # [ if red is a float in [0.0, 1.0] ->
        #     self.r  :=  red mapped onto [0, MAX_PARAM]
        #   if red is an int in [0,MAX_PARAM] ->
        #     self.r  :=  red ]
        self.r  =  self.__standardize ( red )

        #-- 2 --
        # [ simile ]
        self.g  =  self.__standardize ( green )

        #-- 3 --
        self.b  =  self.__standardize ( blue )
</programlisting>
    </section> <!--Color-init-->
    <section id='Color-standardize'>
      <title><code >Color.__standardize()</code >: Standardize
      color component representation</title>
      <para>
        This method takes care of translating float-type
        arguments for color components into the standard integer
        range.
      </para>
      <programlisting role='outFile:huey'
># - - -   C o l o r . _ _ s t a n d a r d i z e

    def __standardize ( self, rawValue ):
        """Standardize representation of a color component.

          [ if rawValue is a float in [0.0, 1.0] ->
              return int ( rawValue * MAX_PARAM )
            if rawValue is an int in [0, MAX_PARAM] ->
              return rawValue
            else -> raise ValueError ]
        """
        if  type(rawValue) is float:
            if  not 0.0 &lt;= rawValue &lt;= 1.0:
                raise ValueError, ( "Float color value %.4f "
                    "out of bounds." % rawValue )
            return  int ( rawValue * MAX_PARAM )
        elif type(rawValue) is int:
            if  not 0 &lt;= rawValue &lt;= MAX_PARAM:
                raise ValueError, ( "Int color value %d "
                    "out of bounds." % rawValue )
            return rawValue
        else:
            raise ValueError, ( "Color component %s "
                "not int or float." % rawValue )
</programlisting>
    </section> <!--Color-standardize-->
    <section id='Color-str'>
      <title><code >Color.__str__()</code >: Convert to a string</title>
      <para>
        Converting each 16-bit color component to its 8-bit
        equivalent is a simple matter of shifting out the
        low-order bits.
      </para>
      <programlisting role='outFile:huey'
># - - -   C o l o r . _ _ s t r _ _

    def __str__ ( self ):
        """Convert self to an X color name.
        """
        r8  =  self.r >> 8
        g8  =  self.g >> 8
        b8  =  self.b >> 8
        return "#%02X%02X%02X" % (r8, g8, b8)
</programlisting>
    </section> <!--Color-str-->
    <section id='Color-cmp'>
      <title><code >Color.__cmp__()</code >: Compare two colors</title>
      <para>
        This method implements the usual Python comparison function.  It
        can be used to test two colors for equality.  The definition of
        inequality is pretty arbitary, so we will just use their #RRGGBB
        color names to do the comparison.
      </para>
      <programlisting role='outFile:huey'
># - - -   C o l o r . _ _ c m p _ _

    def __cmp__ ( self, other ):
        """Compare two colors."""
        return  cmp ( str(self), str(other) )
</programlisting>
    </section> <!--Color-cmp-->
  </section> <!--class-Color-->
  <section id='class-ColorModel'>
    <title><code >class ColorModel</code >: Base class for color
    models</title>
    <para>
      The various color model we support will be concrete classes that
      share an interface defined in this base class.
    </para>
    <programlisting role='outFile:huey'>
# - - - - -   c l a s s   C o l o r M o d e l

class ColorModel:
    """Base class for color models.

      Exports:
        ColorModel ( modelName, labelList ):
          [ (modelName is the name of this model as a string) and
            (labelList is a sequence of three strings naming
            the parameters of this model) ->
              return a ColorModel object with those names ]
        .modelName:     [ as passed to constructor, read-only ]
        .labelList:     [ as passed to constructor, read-only ]
</programlisting>
    <para>
      Functionally, a color model translates between a <code
      >Color</code > object (which internally uses RGB color
      space) and a trio of parameters that describe that color in
      the given model.  The next two methods are the two
      conversions; see <xref linkend='ColorModel-paramsToColor'
      /> and <xref linkend='ColorModel-colorToParams' />.
    </para>
    <programlisting role='outFile:huey'
>        .paramsToColor ( params ):
          [ params is a sequence of three numbers in [0,MAX_PARAM] ->
              return that color in self's model as a Color instance ]
        .colorToParams ( color ):
          [ color is a Color instance ->
              return color's parameters in self's model as a
              sequence of three numbers in [0,MAX_PARAM] ]
</programlisting>
      <para>
        The class also contains two static methods used to
        convert between numbers in [0,MAX_PARAM] and floats
        in [0.0, 1.0].  See <xref linkend='ColorModel-normalize' />
        and <xref linkend='ColorModel-discretize' />.
      </para>
      <programlisting role='outFile:huey'
>        ColorModel.normalize(n):
           [ n is an int in [0,MAX_PARAM] ->
               return float(n)/MAX_PARAM ]
         ColorModel.discretize(n):
           [ n is a float in [0.0, 1.0] ->
               return int(n*MAX_PARAM) ]
    """
</programlisting>
    <section id='ColorModel-init'>
      <title><code >ColorModel.__init__()</code >: Constructor</title>
      <para>
        All the constructor does is to store the arguments in the
        instance's namespace.
      </para>
      <programlisting role='outFile:huey'
># - - -   C o l o r M o d e l . _ _ i n i t _ _

    def __init__ ( self, modelName, labelList ):
        """Constructor for ColorModel."""
        self.modelName  =  modelName
        self.labelList  =  labelList
</programlisting>
    </section> <!--ColorModel-init-->
    <section id='ColorModel-paramsToColor'>
      <title><code >ColorModel.paramsToColor()</code ></title>
      <para>
        The polite way to implement a virtual method in Python (like
        this one) is to raise the <code >NotImplementedError</code >
        exception.  For the interface, see <xref
        linkend='class-ColorModel' />.
      </para>
      <programlisting role='outFile:huey'
># - - -   C o l o r M o d e l . p a r a m s T o C o l o r

    def paramsToColor ( self, params ):
        raise NotImplementedError, "ColorModel.paramsToColor"
</programlisting>
    </section> <!--ColorModel-paramsToColor-->
    <section id='ColorModel-colorToParams'>
      <title><code >ColorModel.colorToParams()</code ></title>
      <para>
        A virtual method.  For the interface, see <xref
        linkend='class-ColorModel' />.
      </para>
      <programlisting role='outFile:huey'
># - - -   C o l o r M o d e l . c o l o r T o P a r a m s

    def colorToParams ( self, params ):
        raise NotImplementedError, "ColorModel.colorToParams"
</programlisting>
    </section> <!--ColorModel-paramsToColor-->
    <section id='ColorModel-normalize'>
      <title><code >ColorModel.normalize()</code >: Normalize an integer
      color parameter</title>
      <para>
        This static method converts a color parameter in [0,<code
        >MAX_PARAM</code >] to a normalized float in [0.0, 1.0].
        Values outside the range are forced back into the range.
      </para>
      <programlisting role='outFile:huey'
># - - -   C o l o r M o d e l . n o r m a l i z e

#   @staticmethod
    def normalize ( n ):
        result  =  float(n)/MAX_PARAM
        if  result &lt; 0.0:
           return 0.0
        elif result &gt; 1.0:
           return 1.0
        else:
           return result

    normalize  =  staticmethod ( normalize )
</programlisting>
    </section> <!--ColorModel-normalize-->
    <section id='ColorModel-discretize'>
      <title><code >ColorModel.discretize()</code >: Discretize an
      integer color parameter</title>
      <para>
        This static method converts a color parameter in [0.0, 1.0] to a
        discrete integer in [0, <code >MAX_PARAM</code >].  Values
        outside the range are forced back into the range; this prevents
        potential unpleasantnesses such as a color parameter being
        rounded to hex 100 when it must be no more than hex FF.
      </para>
      <programlisting role='outFile:huey'
># - - -   C o l o r M o d e l . d i s c r e t i z e

#   @staticmethod
    def discretize ( n ):
        result  =  int(n*MAX_PARAM)
        if  result &lt; 0:
           return 0
        elif result &gt; MAX_PARAM:
           return MAX_PARAM
        else:
           return result

    discretize  =  staticmethod ( discretize )
</programlisting>
    </section> <!--ColorModel-discretize-->
  </section> <!--class-ColorModel-->
  <section id='class-HSVModel'>
    <title><code >class HSVModel</code >: Hue-saturation-value color
    model</title>
    <para>
      The parameters of this color model are:
    </para>
    <itemizedlist>
      <listitem>
        <para>
          Hue: an angle around the &#x201c;color wheel&#x201d;; angles
          0&#x00b0; and 360&#x00b0; are red; 60&#x00b0; is yellow;
          120&#x00b0; is green; 180&#x00b0; is cyan; 240&#x00b0; is
          blue; and 300&#x00b0; is magenta.
        </para>
      </listitem>
      <listitem>
        <para>
          Saturation: 1.0 for a fully saturated, pure color; reducing it
          toward zero moves toward white.
        </para>
      </listitem>
      <listitem>
        <para>
          Value: 1.0 for a fully saturated, pure color; reducing it
          toward zero moves toward black.
        </para>
      </listitem>
    </itemizedlist>
    <para>
      Here is the class interface.
    </para>
    <programlisting role='outFile:huey'>
# - - - - -   c l a s s   H S V M o d e l

class HSVModel(ColorModel):
    """Represents the hue-saturation-value color model.
    """
</programlisting>
    <section id='HSVModel-init'>
      <title><code >HSVModel.__init__()</code >: Constructor</title>
      <para>
        This constructor has nothing to do but call its parent
        constructor and pass it the names of the model and its
        parameters.  Refer to <xref linkend='class-ColorModel' />.
      </para>
      <programlisting role='outFile:huey'
># - - -   H S V M o d e l . _ _ i n i t _ _

    def __init__ ( self ):
        ColorModel.__init__ ( self, "HSV",
            ("hue", "saturation", "value") )
</programlisting>
    </section> <!--HSVModel-init-->
    <section id='HSVModel-paramsToColor'>
      <title><code >HSVModel.paramsToColor()</code ></title>
      <para>
        This method converts a set of HSV parameters to a <code
        >Color</code > instance.  This algorithm comes from Foley &amp;
        van Dam's algorithm &#x201c;HSV_To_RGB&#x201d;; see <xref
        linkend='references' />.
      </para>
      <programlisting role='outFile:huey'
># - - -   H S V M o d e l . p a r a m s T o C o l o r

    def paramsToColor ( self, params ):
        """Convert the three HSV color parameters to a Color.
        """
</programlisting>
      <para>
        First we normalize the three color components to [0.0,
        1.0]; see <xref linkend='ColorModel-normalize' />.        
      </para>
      <programlisting role='outFile:huey'
>        h  =  ColorModel.normalize ( params[0] )
        s  =  ColorModel.normalize ( params[1] )
        v  =  ColorModel.normalize ( params[2] )
</programlisting>
      <para>
        Note that the <code >Color()</code > constructor will accept
        float values in [0.0, 1.0] as well as integer values in
        [0,<code >MAX_PARAM</code >].
      </para>
      <programlisting role='outFile:huey'
>        if  s == 0.0:
            return Color ( v, v, v )
</programlisting>
      <para>
        Treat a hue of 1.0 or more as a hue of 0.0 due to wraparound.
        Otherwise, normalize the value of <code >h</code > to the
        half-open interval [0,6).
      </para>
      <programlisting role='outFile:huey'
>        if  h >= 1.0:
            h = 0.0
        else:
            h = h * 6.0
</programlisting>
      <para>
        The <code >math.modf()</code > function returns a tuple <code
        >(<replaceable >f</replaceable >, <replaceable >i</replaceable
        >)</code > where <code ><replaceable >f</replaceable ></code >
        is the fractional part and <code ><replaceable >i</replaceable
        ></code > is the integral part.  The rest follows Foley &amp;
        van Dam's algorithm, using the same names.
      </para>
      <programlisting role='outFile:huey'
>        (f,i) = math.modf(h)
        i     = int ( i )
        p     = v * ( 1.0 - s )
        q     = v * ( 1.0 - s * f )
        t     = v * ( 1.0 - s * ( 1.0 - f ) )

        if  i == 0:
            return Color ( v, t, p )
        elif i == 1:
            return Color ( q, v, p )
        elif i == 2:
            return Color ( p, v, t )
        elif i == 3:
            return Color ( p, q, v )
        elif i == 4:
            return Color ( t, p, v )
        else:
            return Color ( v, p, q )
</programlisting>
    </section> <!--HSVModel-paramsToColor-->
    <section id='HSVModel-colorToParams'>
      <title><code >HSVModel.colorToParams()</code ></title>
      <para>
        This method converts a <code >Color</code > object into the
        three parameters of the HSV model, each in the range [0,<code
        >MAX_PARAM</code >].  The algorithm is Foley &amp; van Dam's
        &#x201c;<code >RGB_To_HSV</code >&#x201d;; see <xref
        linkend='references' />.
      </para>
      <programlisting role='outFile:huey'
># - - -   H S V M o d e l . c o l o r T o P a r a m s

    def colorToParams ( self, color ):
        """Convert a Color to the three HSV parameters.
        """
</programlisting>
      <para>
        First we normalize the three RGB parameters to [0.0, 1.0].
        See <xref linkend='ColorModel-normalize' />.
      </para>
      <programlisting role='outFile:huey'
>        rNorm  =  ColorModel.normalize(color.r)
        gNorm  =  ColorModel.normalize(color.g)
        bNorm  =  ColorModel.normalize(color.b)
</programlisting>
      <para>
        The value parameter <code >v</code > is the maximum of the three
        values.  We also calculate their minimum.
      </para>
      <programlisting role='outFile:huey'
>        v = maxColor = max ( rNorm, gNorm, bNorm )
        minColor = min ( rNorm, gNorm, bNorm )
</programlisting>
      <para>
        Next, calculate the saturation.  If all the colors are
        zero, the saturation is zero.
      </para>
      <programlisting role='outFile:huey'
>        if  maxColor == 0.0:
            s = 0.0
        else:
            s = ( maxColor - minColor ) / maxColor
</programlisting>
      <para>
        Next, find the hue.  If the saturation is zero, arbitrarily use
        a value of zero (red) for the hue.
      </para>
      <programlisting role='outFile:huey'
>        if  s == 0.0:
            h = 0.0     # Hue is undefined; use red arbitrarily
</programlisting>
      <para>
        These three cases are: colors between Y and M; colors between C
        and Y; and colors between M and C.  Negative values are then
        rotated to the range [0,6].  Finally, the hue is normalized to
        [0.0, 1.0].
      </para>
      <programlisting role='outFile:huey'
>        else:
            delta = maxColor - minColor

            if  rNorm == maxColor:        # Between Y and M
                h = ( gNorm - bNorm ) / delta
            elif gNorm == maxColor:       # Between C and Y
                h = 2.0 + ( bNorm - rNorm ) / delta
            else:                       # Between M and C
                h = 4.0 + ( rNorm - gNorm ) / delta

            if h &lt; 0.0:
                h = h + 6.0

            h  =  h / 6.0       # Normalize to [0,1]
</programlisting>
      <para>
        Finally, return the three parameters, discretized back to
        [0,<code >MAX_PARAM</code >].  See <xref
        linkend='ColorModel-discretize' />.
      </para>
      <programlisting role='outFile:huey'
>        return ( ColorModel.discretize ( h ),
                 ColorModel.discretize ( s ),
                 ColorModel.discretize ( v ) )
</programlisting>
    </section> <!--HSVModel-colorToParams-->
  </section> <!--class-HSVModel-->
  <section id='class-RGBModel'>
    <title><code >class RGBModel</code >: Red-green-blue color
    model</title>
    <para>
      This is a concrete class inheriting from the base <code
      >ColorModel</code > class.  It implements the red-green-blue color
      model.  It doesn't have much work to do, since the <code
      >Color</code > object uses the RGB model as its internal
      representation.
    </para>
    <programlisting role='outFile:huey'>
# - - - - -   c l a s s   R G B M o d e l

class RGBModel(ColorModel):
    """Represents the red-green-blue color model.
    """
</programlisting>
    <section id='RGBModel-init'>
      <title><code >RGBModel.__init_()</code >: Constructor</title>
      <para>
        All the constructor has to do is to call the parent class
        constructor, and supply the model's name and the names of
        its three parameters.  See <xref
        linkend='class-ColorModel' />.
      </para>
      <programlisting role='outFile:huey'
># - - -   R G B M o d e l . _ _ i n i t _ _

    def __init__ ( self ):
        """Constructor for RGBModel."""
        ColorModel.__init__ ( self, "RGB",
          ("red", "green", "blue") )
</programlisting>
    </section> <!--RGBModel-init-->
    <section id='RGBModel-paramsToColor'>
      <title><code >RGBModel.paramsToColor()</code ></title>
      <para>
        This method takes a tuple <code >(<replaceable >red</replaceable
        >, <replaceable >green</replaceable >, <replaceable
        >blue</replaceable >)</code > and returns a color as a <code
        >Color</code >instance.  The &#x201c;<code >*</code >&#x201d;
        used inside the call to the <code >Color()</code > constructor
        takes advantage of one of Python's features: that constructor
        expects three positional arguments, but since <code
        >params</code > is a three-element sequence, the &#x201c;<code
        >*</code >&#x201d; tells Python to allocate those three elements
        to the corresponding positional arguments.
      </para>
      <programlisting role='outFile:huey'
># - - -   R G B M o d e l . p a r a m s T o C o l o r

    def paramsToColor ( self, params ):
        """Convert three RGB parameters to a Color.
        """
        return  Color ( *params )
</programlisting>
    </section> <!--RGBModel-paramsToColor-->
    <section id='RGBModel-colorToParams'>
      <title><code >RGBModel.colorToParams()</code ></title>
      <para>
        This method takes a <code >Color</code > instance and returns
        the three parameters of that color in the RGB model.  No
        conversion is necessary, since the <code >Color</code > instance
        uses the RGB model internally.
      </para>
      <programlisting role='outFile:huey'
># - - -   R G B M o d e l . c o l o r T o P a r a m s

    def colorToParams ( self, color ):
        """Convert a Color to the three RGB parameters.
        """
        return ( ( color.r, color.g, color.b ) )
</programlisting>
    </section> <!--RGBModel-colorToParams-->
  </section> <!--class-RGBModel-->
  <section id='class-CMYModel'>
    <title><code >class CMYModel</code >: Cyan-magenta-yellow color
    model</title>
    <para>
      This class implements the CMY color model.  It is a concrete class
      that inherits from the <code >ColorModel</code > abstract class.
    </para>
    <para>
      There is little more logic here than in the <code >RGBModel</code
      > class, since each parameter is the complement of one of the RGB
      parameters used internally by the <code >Color</code > class.
    </para>
    <programlisting role='outFile:huey'>
# - - - - -   c l a s s   C M Y M o d e l

class CMYModel(ColorModel):
    """Represents the cyan-yellow-magenta color model.
    """
</programlisting>
    <section id='CMYModel-init'>
      <title><code >CMYModel.__init__()</code ></title>
      <para>
        This constructor calls the parent class constructor and
        supplies the name of the model and the names of its three
        parameters.  See <xref linkend='class-ColorModel' />.
      </para>
      <programlisting role='outFile:huey'
># - - -   C M Y M o d e l . _ _ i n i t _ _

    def __init__ ( self ):
        """Constructor for CMYModel."""
        ColorModel.__init__ ( self, "CMY",
          ("cyan", "yellow", "magenta") )
</programlisting>
    </section> <!--CMYModel-init-->
    <section id='CMYModel-paramsToColor'>
      <title><code >CMYModel.paramsToColor()</code ></title>
      <para>
        This method takes a triple <code >(<replaceable >c</replaceable
        >, <replaceable >m</replaceable >, <replaceable >y</replaceable
        >)</code > containing the three CMY parameters of a color, and
        returns the equivalent color as a <code >Color</code > instance.
      </para>
      <programlisting role='outFile:huey'
># - - -   C M Y M o d e l . p a r a m s T o C o l o r

    def paramsToColor ( self, params ):
        """Convert CMY parameters to a Color.
        """
</programlisting>
      <para>
        Each parameter is the complement of one of the RGB colors: cyan
        is opposite red, magenta is opposite green, and yellow is
        opposite blue.  Mathematically, we can convert a value by
        subtracting it from <code >MAX_PARAM</code >.
      </para>
      <programlisting role='outFile:huey'
>        cyan, magenta, yellow  =  params
        red    =  MAX_PARAM - cyan
        green  =  MAX_PARAM - magenta
        blue   =  MAX_PARAM - yellow
        return  Color ( red, green, blue )
</programlisting>
    </section> <!--CMYModel-paramsToColor-->
    <section id='CMYModel-colorToParams'>
      <title><code >CMYModel.colorToParams()</code ></title>
      <para>
        Converting a <code >Color</code > instance to cyan, magenta, and
        yellow values is straightforward, as each of the CMY colors is
        the complement of the corresponding RGB colors stored in
        attributes of the <code >Color</code > instance.  To complement
        a number in [0,<code >MAX_PARAM</code >], we subtract that
        number from <code >MAX_PARAM</code >.
      </para>
      <programlisting role='outFile:huey'
># - - -   C M Y M o d e l . c o l o r T o P a r a m s

    def colorToParams ( self, color ):
        """Convert a color to CMY parameters.
        """
        cyan     =  MAX_PARAM - color.r
        magenta  =  MAX_PARAM - color.g
        yellow   =  MAX_PARAM - color.b
        return (cyan, magenta, yellow)
</programlisting>
    </section> <!--CMYModel-colorToParams-->
  </section> <!--class-CMYModel-->
  <section id='class-Application'>
    <title><code >class Application</code >: The outermost frame</title>
    <para>
      This class encapsulates the entire graphical application.
      It inherits from Tkinter's <code >Frame</code > class,
      which makes <code >Application</code > for all intents and
      purposes a Tkinter widget.
    </para>
    <programlisting role='outFile:huey'>
# - - - - -   c l a s s   A p p l i c a t i o n

class Application(Frame):
    """Contains the entire application.

      Contained widgets:
        .menuBar:       [ a MenuBar widget ]
        .namePicker:    [ a NamePicker widget ]
        .adjuster:      [ an Adjuster widget ]
        .swatch:        [ a Swatch widget ]

      Grid plan:
         0             1           2
        +---------------+-------------+-----------+
      0 | .__menuBar                              |
        +---------------+-------------+-----------+
      1 | .__namePicker | .__adjuster | .__swatch |
        +---------------+-------------+-----------+
    """
</programlisting>
    <para>
      This class is a container for four compound widgets.  The
      <code >MenuBar</code > widget across the top edge
      contains general controls for the application.  The bulk
      of the space is taken up by the three major sections of
      the application: the <code >NamePicker</code > widget for
      selecting colors by name; the <code >Adjuster</code >
      widget for adjusting colors and selecting color models;
      and the <code >Swatch</code > widget displaying the
      selected colors and fonts, and including font selection
      controls.
    </para>
    <para>
      At this level, there are only two important linkages
      between the contained widgets:
    </para>
    <itemizedlist>
      <listitem>
        <para>
          Whenever the user selects a color in the <code
          >NamePicker</code > widget, that widget tells the
          <code >Adjuster</code > widget to display that color.
        </para>
      </listitem>
      <listitem>
        <para>
          Whenever the user adjusts the current color in the
          <code >Adjuster</code > widget, or when that widget
          is set by an outside call to change its color, it
          tells the <code >Swatch</code > widget to change to
          the new color as well.
        </para>
      </listitem>
    </itemizedlist>
    <para>
      The current text and background colors reside
      inside the <code >Adjuster</code > widget.
    </para>
    <section id='Application-init'>
      <title><code >Application.__init__()</code >: Constructor</title>
      <para>
        The constructor creates the application window and
        populates it with its four contained widgets.
      </para>
      <programlisting role='outFile:huey'
># - - -   A p p l i c a t i o n . _ _ i n i t _ _

    def __init__ ( self ):
        """Constructor for Application.
        """
</programlisting>
      <para>
        Because this class inherits from <code >Frame</code >,
        the first order of business is to call the parent class's
        constructor.  The first argument is <code >self</code >,
        the new instance's namespace.  The second argument is the
        parent window; passing <code >None</code > has the effect
        of creating a new root window.
      </para>
      <para>
        Calling the <code >.grid()</code > method on <code
        >self</code > is necessary to register the new window; if
        this isn't done, the application will not appear on the
        screen.
      </para>
      <programlisting role='outFile:huey'
>        #-- 1 --
        # [ self  :=  self as a new root window Frame ]
        Frame.__init__ ( self, None )
        self.grid()
</programlisting>
      <para>
        Widget creation is handled by <xref
        linkend='Application-createWidgets' />.
      </para>
      <programlisting role='outFile:huey'
>        #-- 2 --
        # [ self  :=  self with all widgets created and gridded ]
        self.__createWidgets()
</programlisting>
    </section> <!--Application-init-->
    <section id='Application-createWidgets'>
      <title><code >Application.__createWidgets()</code >: Set up
      the widgets</title>
      <para>
        This method creates and grids all the widgets for the
        <code >Application</code > class.
      </para>
      <programlisting role='outFile:huey'
># - - -   A p p l i c a t i o n . _ _ c r e a t e W i d g e t s

    def __createWidgets ( self ):
        """Create and grid all widgets.
        """
</programlisting>
      <para>
        The <code >MenuBar</code > compound widget occupies the
        full width of row 0.  The <code >sticky</code > parameter
        makes it adhere to the left (west) side of its grid cell.
        See <xref linkend='class-MenuBar' />.
      </para>
      <programlisting role='outFile:huey'
>        #-- 1 --
        # [ self  :=  self with a new MenuBar widget added
        #   self.__menuBar  :=  that widget ]
        self.__menuBar  =  MenuBar ( self )
        self.__menuBar.grid ( row=0, column=0, columnspan=3,
                              sticky=W )
</programlisting>
      <para>
        Next we add the <code >NamePicker</code > widget.  The
        first argument to a widget constructor is always the
        parent widget, <code >self</code > in this case.  The
        second argument is a callback function that is called
        whenever the user selects a color.  See <xref
        linkend='class-NamePicker' />.
      </para>
      <programlisting role='outFile:huey'
>        #-- 2 --
        # [ self  :=  self with a new NamePicker widget added that
        #             calls self.__nameHandler when a name is picked
        #   self.__namePicker  :=  that NamePicker ]
        self.__namePicker  =  NamePicker ( self, self.__nameHandler )
        self.__namePicker.grid ( row=1, column=0, sticky=NW )
</programlisting>
      <para>
        The <code >Adjuster</code > widget constructor also gets
        a second argument containing the name of a callback
        function, which will be called whenever the user adjusts
        the current color.  See <xref linkend='class-Adjuster'
        />.
      </para>
      <programlisting role='outFile:huey'
>        #-- 3 --
        # [ self  :=  self with a new Adjuster widget added that calls
        #             self.__adjustHandler when the color is adjusted
        #   self.__adjuster  :=  that Adjuster widget
        self.__adjuster  =  Adjuster ( self, self.__adjustHandler )
        self.__adjuster.grid ( row=1, column=1, sticky=N, padx=4 )
</programlisting>
      <para>
        Last to go in is the <code >Swatch</code > widget; see
        <xref linkend='class-Swatch' />.
      </para>
      <programlisting role='outFile:huey'
>        #-- 4 --
        # [ self  :=  self with a new Swatch widget added, using
        #       the background and text colors from self.__adjuster
        #   self.__swatch  :=  that widget ]
        self.__swatch  =  Swatch ( self, self.__adjuster.bgColor(),
                                   self.__adjuster.textColor() )
        self.__swatch.grid ( row=1, column=2, sticky=N )
</programlisting>
    </section> <!--Application-createWidgets-->
    <section id='Application-nameHandler'>
      <title><code >Application.__nameHandler()</code >:  Name
      picker handler</title>
      <para>
        This callback is called by the <code >NamePicker</code >
        widget whenever the user picks a color by name.  The
        argument is a <code >Color</code > object representing
        the selected color.  It passes the new color to the <code
        >.set()</code > method of the <code >Adjuster</code >
        widget, so the new color can be shown there.  See <xref
        linkend='Adjuster-set' />.
      </para>
      <programlisting role='outFile:huey'
># - - -   A p p l i c a t i o n . _ _ n a m e H a n d l e r

    def __nameHandler ( self, newColor ):
        """Handler for the NamePicker widget.

          [ newColor is a Color instance ->
              self  :=  self with its Adjuster displaying newColor ]
        """
        self.__adjuster.set ( newColor )
</programlisting>
    </section> <!--Application-nameHandler-->
    <section id='Application-adjustHandler'>
      <title><code >Application.__adjustHandler()</code >:
      Adjuster handler</title>
      <para>
        This method is called whenever the <code >Adjuster</code
        > widget changes the color that it shows.  It gets two
        arguments.  The first argument is 0 for background, 1 for
        text color.  The second argument is the new color.  It
        passes that new color on to the <code >Swatch</code >
        widget to be displayed; for these methods, see <xref
        linkend='class-Swatch' />.
      </para>
      <programlisting role='outFile:huey'
># - - -   A p p l i c a t i o n . _ _ a d j u s t H a n d l e r

    def __adjustHandler ( self, isText, newColor ):
        """Handler for the Adjuster widget.

          [ newColor is a Color instance ->
              if  isText ->
                self  :=  self with its Swatch displaying its
                          text in color (newColor)
              else ->
                self  :=  self with its Swatch displaying its
                          background in color (newColor) ]
        """
        if  isText:
            self.__swatch.setTextColor ( newColor )
        else:
            self.__swatch.setBgColor ( newColor )
</programlisting>
    </section> <!--Application-adjustHandler-->
  </section> <!--class-Application-->
  <section id='class-MenuBar'>
    <title><code >class MenuBar</code >: General controls</title>
    <para>
      This class is a compound widget containing all the general
      controls for the program:
    </para>
    <itemizedlist spacing='compact'>
      <listitem>
        <para>
          The <guibutton >Quit</guibutton > button.
        </para>
      </listitem>
      <listitem>
        <para>
          The <guibutton >Help</guibutton > button.
        </para>
      </listitem>
    </itemizedlist>
    <para>
      Because this widget is designed to take up a fairly thin
      slice across the top of the application, these controls are
      gridded in a horizontal row.  The class inherits from
      Tkinter's <code >Frame</code > class, so it is effectively
      a compound widget&#x2014;just a big widget with other
      widgets inside.
    </para>
    <programlisting role='outFile:huey'>
# - - - - -   c l a s s   M e n u B a r

class MenuBar(Frame):
    """Compound widget containing general program controls.

      Contained widgets:
        .__helpButton:  [ a Menubutton that displays help text ]
        .__quitButton:  [ a Button that terminates the application ]

      Grid plan:
          0               1
         +---------------+---------------+
       0 | .__helpButton | .__quitButton |
         +---------------+---------------+
    """
</programlisting>
    <section id='MenuBar-init'>
      <title><code >MenuBar.__init__()</code >:  Constructor</title>
      <para>
        The constructor starts by calling its parent class's
        constructor, which makes it a proper <code >Frame</code >
        widget.  It is not gridded in the parent; that is the
        caller's job.  Then the contained widgets are created and
        gridded within <code >self</code >.
      </para>
      <programlisting role='outFile:huey'
># - - -   M e n u B a r . _ _ i n i t _ _

    def __init__ ( self, parent ):
        """Constructor for MenuBar"""
        #-- 1 --
        # [ self  :=  self as a Frame, ungridded ]
        Frame.__init__ ( self, parent )
</programlisting>
      <para>
        The <guibutton >Help</guibutton > button is a menubutton,
        but there are many submenus.  This whole drop-down menu
        structure is built by <xref linkend='MenuBar-createHelp' />.
      </para>
      <programlisting role='outFile:huey'
>        #-- 2 --
        # [ self  :=  self with a new Menubutton added and
        #       gridded, leading to the help menu cascades ]
        self.__helpButton  =  self.__createHelp()
        self.__helpButton.grid ( row=0, column=0 )
</programlisting>
      <para>
        The <guibutton >Quit</guibutton > button is linked to the
        <code >.quit()</code > method that exists on all Tkinter
        widgets.
      </para>
      <programlisting role='outFile:huey'
>        #-- 3 --
        # [ self  :=  self with a Quit button added and gridded
        #             that terminates execution ]
        self.__quitButton  =  Button ( self, text='Quit',
            font=BUTTON_FONT, command=self.quit )
        self.__quitButton.grid ( row=0, column=1 )
</programlisting>
    </section> <!--MenuBar-init-->
    <section id='MenuBar-createHelp'>
      <title><code >MenuBar.__createHelp()</code >: Create the
      <guibutton >Help</guibutton > menu</title>
      <para>
        The visible <guibutton >Help</guibutton > button is a
        <code >Menubutton</code > widget that leads to a cascade
        of submenus.  When the user reaches an actual text item,
        that item appears in a pop-up <code >Dialog</code >.
      </para>
      <programlisting role='outFile:huey'
># - - -   M e n u B a r . _ _ c r e a t e H e l p

    def __createHelp ( self ):
        """Create the help menubutton and its cascade.

          [ self is a Frame ->
              self  :=  self with a new Menubutton added but not
                        gridded, leading to the help text
              return that new Menubutton ]
        """
</programlisting>
      <para>
        Here is an outline showing the structure of the help cascade,
        with links to the methods that set them up.
      </para>
      <itemizedlist spacing='compact'>
        <listitem>
          <para>
            Selecting a standard color: <xref
            linkend='MenuBar-cascadeNamePicker' />.
          </para>
          <itemizedlist spacing='compact'>
            <listitem>
              <para>
                Typing in a standard color name: <xref
                linkend='MenuBar-helpTyping' />.
              </para>
            </listitem>
            <listitem>
              <para>
                Picking a standard color name: <xref
                linkend='MenuBar-helpPicking' />.
              </para>
            </listitem>
          </itemizedlist>
        </listitem>
        <listitem>
          <para>
            Adjusting colors: <xref linkend='MenuBar-cascadeAdjuster'
            />.
          </para>
          <itemizedlist spacing='compact'>
            <listitem>
              <para>
                Selecting background or text color: <xref
                linkend='MenuBar-helpReadout' />.
              </para>
            </listitem>
            <listitem>
              <para>
                Color models and color space: <xref
                linkend='MenuBar-helpModelSelector' />.
              </para>
            </listitem>
            <listitem>
              <para>
                The HSV color model: <xref linkend='MenuBar-helpHSV' />.
              </para>
            </listitem>
            <listitem>
              <para>
                The RGB color model: <xref linkend='MenuBar-helpRGB' />.
              </para>
            </listitem>
            <listitem>
              <para>
                The CMY color model: <xref linkend='MenuBar-helpCMY' />.
              </para>
            </listitem>
            <listitem>
              <para>
                Using the color sliders: <xref
                linkend='MenuBar-helpSliders' />.
              </para>
            </listitem>
          </itemizedlist>
        </listitem>
        <listitem>
          <para>
            Viewing colors and fonts: <xref
            linkend='MenuBar-cascadeViewing' />.
          </para>
          <itemizedlist spacing='compact'>
            <listitem>
              <para>
                The color swatch: <xref linkend='MenuBar-helpSwatch' />.
              </para>
            </listitem>
            <listitem>
              <para>
                Selecting a font: <xref linkend='MenuBar-cascadeFonts'
                />.
              </para>
              <itemizedlist spacing='compact'>
                <listitem>
                  <para>
                    Selecting a font family: <xref
                    linkend='MenuBar-helpFontFamily' />.
                  </para>
                </listitem>
                <listitem>
                  <para>
                    Changing the font size: <xref
                    linkend='MenuBar-helpFontSize' />.
                  </para>
                </listitem>
                <listitem>
                  <para>
                    Changing font weight and slant: <xref
                    linkend='MenuBar-helpFontStyle' />.
                  </para>
                </listitem>
              </itemizedlist>
            </listitem>
          </itemizedlist>
        </listitem>
        <listitem>
          <para>
            Importing colors into other applications: <xref
            linkend='MenuBar-helpImporting' />.
          </para>
        </listitem>
        <listitem>
          <para>
            Who made this tool? <xref linkend='MenuBar-helpAuthor' />.
          </para>
        </listitem>
      </itemizedlist>
      <para>
        First we create the two interlinked widgets that make up a
        drop-down menu: the <code >Menubutton</code >, which appears all
        the time as the <guibutton >Help</guibutton > button, and the
        <code >Menu</code > widget, which contains the cascades that
        appear when the user clicks the <code >Menubutton</code >.
      </para>
      <programlisting role='outFile:huey'
>        #-- 1 --
        # [ mb  :=  a new Menubutton with self as its parent ]
        mb  =  Menubutton ( self, font=BUTTON_FONT,
                            relief=RAISED,
                            text="Help" )
</programlisting>
      <para>
        Creating the mutual linkage between the <code
        >Menubutton</code > and its first-level <code >Menu</code
        > is not completely obvious.  The upward linkage is
        established by making the <code >Menubutton</code > the
        parent of the <code >Menu</code >.  The downward linkage
        is established by setting the <code >Menu</code >'s <code
        >menu</code > attribute to the <code >Menubutton</code >.
      </para>
      <programlisting role='outFile:huey'
>        #-- 2 --
        # [ menu  :=  a new Menu with mb as its parent
        #   mb    :=  mb with its 'menu' attribute set to that
        #             new Menu ]
        menu  =  Menu ( mb )
        mb['menu']  =  menu
</programlisting>
      <para>
        There are two kinds of choices on a drop-down <code
        >Menu</code >:
      </para>
      <itemizedlist>
        <listitem>
          <para>
            A <firstterm >cascade</firstterm > is a choice that
            leads to another level of drop-down menu.  This type
            of choice is identified by a right-pointing arrowhead.
          </para>
        </listitem>
        <listitem>
          <para>
            Terminal (non-cascade) choices are identified by a
            simple text label without a right-pointing arrowhead.
          </para>
        </listitem>
      </itemizedlist>
      <programlisting role='outFile:huey'
>        #-- 3 --
        # [ menu  :=  menu with a cascade added about selecting
        #             colors by name ]
        self.__cascadeNamePicker ( menu )

        #-- 4 --
        # [ menu  :=  menu with a cascade added about adjusting
        #             colors ]
        self.__cascadeAdjuster ( menu )

        #-- 5 --
        # [ menu  :=  menu with a cascade added about viewing colors ]
        self.__cascadeViewing ( menu )

        #-- 6 --
        # [ menu  :=  menu with a pop-up added about importing
        #             colors into other applications
        menu.add_command ( command=self.__helpImporting,
            label="Importing colors into other applications" )

        #-- 7 --
        # [ menu  :=  menu with a pop-up added about the author ]
        menu.add_command ( command=self.__helpAuthor,
            label="Who made this tool?" )
</programlisting>
      <para>
        The finished <code >Menubutton</code > is returned to the
        caller for gridding.
      </para>
      <programlisting role='outFile:huey'
>        #-- 8 --
        return mb
</programlisting>
    </section> <!--MenuBar-createHelp-->
    <section id='MenuBar-cascadeNamePicker'>
      <title><code >MenuBar.__cascadeNamePicker()</code ></title>
      <para>
        This method adds one selection to the first-level help
        menu, along with its subcommand cascade.
      </para>
      <programlisting role='outFile:huey'
># - - -   M e n u B a r . _ _ c a s c a d e N a m e P i c k e r

    def __cascadeNamePicker ( self, menu ):
        """Add the help cascade for picking colors by name

          [ menu is a Menu widget ->
              menu  :=  menu with choices added for help topics
                        about picking colors by name ]
        """
</programlisting>
      <para>
        A &#x201c;cascade&#x201d; is a choice on a drop-down
        <code >Menu</code > that leads to another <code
        >Menu</code >.
      </para>
      <programlisting role='outFile:huey'
>        #-- 1 --
        # [ menu    :=  menu with a new cascade added
        #   select  :=  that cascade ]
        select  =  Menu ( menu )
        menu.add_cascade ( menu=select,
            label="Selecting a standard color" )
</programlisting>
      <para>
        Both choices here are terminal choices.  For a discussion
        of cascade and terminal choices, see <xref
        linkend='MenuBar-createHelp' />.  For the methods that
        create the pop-up menus, see <xref
        linkend='MenuBar-helpTyping' /> and <xref
        linkend='MenuBar-helpPicking' />.
      </para>
      <programlisting role='outFile:huey'
>        #-- 2 --
        # [ select  :=  select with a pop-up added about typing
        #       standard color names ]
        select.add_command ( command=self.__helpTyping,
            label="Typing in a standard color name" )

        #-- 3 --
        # [ select  :=  select with a pop-up added about picking
        #       standard color names ]
        select.add_command ( command=self.__helpPicking,
            label="Picking a standard color name" )
</programlisting>
    </section> <!--MenuBar-cascadeNamePicker-->
    <section id='MenuBar-helpTyping'>
      <title><code >MenuBar.__helpTyping()</code ></title>
      <para>
        This method creates a pop-up menu for help with typing
        standard color names.
      </para>
      <programlisting role='outFile:huey'
># - - -   M e n u B a r . _ _ h e l p T y p i n g

    def __helpTyping ( self ):
        """Pop-up help for typing standard color names
        """
</programlisting>
      <para>
        All our dialogs are stereotyped, so their creation is
        handled by <xref linkend='MenuBar-dialog' />.
      </para>
      <programlisting role='outFile:huey'
>
        self.__dialog ( "Help: Typing in a standard color name",
            "If you already know the name of the color you want, "
            "move the cursor into the field labeled 'Enter a color "
            "name:', type the color name, and press Enter."
            "\n\tYour color will be displayed."
            "\n\tIf the color name you type is not known to the "
            "system, you will get a popup dialog menu." )
</programlisting>
    </section> <!--MenuBar-helpTyping-->
    <section id='MenuBar-helpPicking'>
      <title><code >MenuBar.__helpPicking()</code ></title>
      <para>
        This <code >Dialog</code > popup explains the color picker.
      </para>
      <programlisting role='outFile:huey'
># - - -   M e n u B a r . _ _ h e l p P i c k i n g

    def __helpPicking ( self ):
        """Help for the color pick list
        """
        self.__dialog ( "Help: Picking a standard color name",
            "Under the label 'Or click on a name:' you will see a "
            "list of all the standard color names.  Click on the color "
            "name to select that color." )
</programlisting>
    </section> <!--MenuBar-helpPicking-->
    <section id='MenuBar-cascadeAdjuster'>
      <title><code >MenuBar.__cascadeAdjuster()</code ></title>
      <para>
        Adds to the help menu a subtopic &#x201c;Adjust a color&#x201d;
        along with its cascade.  This is structurally similar to
        <xref linkend='MenuBar-cascadeNamePicker' />.
      </para>
      <programlisting role='outFile:huey'
># - - -   M e n u B a r . _ _ c a s c a d e A d j u s t e r

    def __cascadeAdjuster ( self, menu ):
        """Add the 'adjust' choice and cascade to the menu.

          [ menu is a Menu ->
              menu  :=  menu with a cascade added about adjusting
                        colors ]
        """
        #-- 1 --
        # [ menu    :=  menu with a new cascade added
        #   select  :=  that cascade ]
        select  =  Menu ( menu )
        menu.add_cascade ( menu=select,
                           label="Adjusting a color" )
</programlisting>
      <para>
        For the methods that created the sub-cascades, see
        <xref linkend='MenuBar-helpReadout' />,
        <xref linkend='MenuBar-helpModelSelector' />,
        <xref linkend='MenuBar-helpHSV' />,
        <xref linkend='MenuBar-helpRGB' />,
        <xref linkend='MenuBar-helpCMY' />, and
        <xref linkend='MenuBar-helpSliders' />.
      </para>
      <programlisting role='outFile:huey'
>        #-- 2 --
        # [ select  :=  select with pop-ups added that relate to
        #               color adjustment ]
        select.add_command ( label='Selecting background or text color',
            command=self.__helpReadout )

        select.add_command ( label='Color models and color space',
            command=self.__helpModelSelector )

        select.add_command ( label='The HSV color model',
            command=self.__helpHSV )

        select.add_command ( label='The RGB color model',
            command=self.__helpRGB )

        select.add_command ( label='The CMY color model',
            command=self.__helpCMY )

        select.add_command ( label='Using the color sliders',
            command=self.__helpSliders )
</programlisting>
    </section> <!--MenuBar-cascadeAdjuster-->
    <section id='MenuBar-helpReadout'>
      <title><code >MenuBar.__helpReadout()</code ></title>
      <programlisting role='outFile:huey'
># - - -   M e n u B a r . _ _ h e l p R e a d o u t

    def __helpReadout ( self ):
        """Help for selecting bg/text color
        """
        self.__dialog ( 'Help: Selecting background or text color',
            "When the radiobuttons labeled 'Background color' is set, "
            "you can select the background color by name or by "
            "adjusting the three color sliders."
            "\n\t When 'Text color' is selected, you can select the "
            "color of the displayed text." )
</programlisting>
    </section> <!--MenuBar-helpReadout-->
    <section id='MenuBar-helpModelSelector'>
      <title><code >MenuBar.__helpModelSelector()</code ></title>
      <programlisting role='outFile:huey'
># - - -   M e n u B a r . _ _ h e l p M o d e l S e l e c t o r

    def __helpModelSelector ( self ):
        """Help for selecting a color model.
        """
        self.__dialog ( 'Help: Selecting a color model',
            "A 'color model' is a way of describing a specific color as "
            "a set of three values from 0 and 255. Use the radiobuttons "
            "under 'Select a color model' to pick one of the color "
            "models." )
</programlisting>
    </section> <!--MenuBar-helpModelSelector-->
    <section id='MenuBar-helpHSV'>
      <title><code >MenuBar.__helpHSV()</code ></title>
      <programlisting role='outFile:huey'
># - - -   M e n u B a r . _ _ h e l p H S V

    def __helpHSV ( self ):
        """Help for the HSV color model.
        """
        self.__dialog ( 'Help: The HSV color model',
            "In the HSV color model, color is specified by these three "
            "parameters:"
            "\n\t* Hue controls the base color.  As you drag the Hue "
            "slider, the color goes through red, yellow, green, cyan, "
            "blue, magenta, and then back to red again."
            "\n\t* Saturation controls color intensity. Drag the "
            "Saturation slider down for pale colors, drag it up for "
            "intense colors."
            "\n\t* Value controls brightness. Drag the Value slider "
            "down to darken a color, up to lighten it." )
</programlisting>
    </section> <!--MenuBar-helpHSV-->
    <section id='MenuBar-helpRGB'>
      <title><code >MenuBar.__helpRGB()</code ></title>
      <programlisting role='outFile:huey'
># - - -   M e n u B a r . _ _ h e l p R G B

    def __helpRGB ( self ):
        """Help for the RGB color model.
        """
        self.__dialog ( 'Help: The RGB color model',
            "In the RGB color model, color is specified by three "
            "parameters: red, green, and blue.  These are called the "
            "'additive primary colors;' if you drag all three "
            "sliders all the way up, you get white.  RGB color mixing "
            "is used in display screens and also in stage lighting." )
</programlisting>
    </section> <!--MenuBar-helpRGB-->
    <section id='MenuBar-helpCMY'>
      <title><code >MenuBar.__helpCMY()</code ></title>
      <programlisting role='outFile:huey'
># - - -   M e n u B a r . _ _ h e l p C M Y

    def __helpCMY ( self ):
        """Help for the CMY color model.
        """
        self.__dialog ( 'Help: The CMY color model',
            "In the CMY color model, color is specified by three "
            "parameters: cyan, magenta, and yellow.  These are called "
            "the 'subtractive primary colors;' if you drag all three "
            "sliders all the way up, you get black. CMY color mixing "
            "is used in printing and in color darkroom work." )
</programlisting>
    </section> <!--MenuBar-helpCMY-->
    <section id='MenuBar-helpSliders'>
      <title><code >MenuBar.__helpSliders()</code ></title>
      <programlisting role='outFile:huey'
># - - -   M e n u B a r . _ _ h e l p S l i d e r s

    def __helpSliders ( self ):
        """Help for the color sliders.
        """
        self.__dialog ( 'Help: The color sliders',
            "To adjust a color, drag any of the three sliders with the "
            "mouse.  Each one controls one parameter of the color; see "
            "Help -> Creating new colors -> Color models and color "
            "space for an explanation of these parameters."
            "\n\tYou can also click on the '+' or '-' buttons to make "
            "fine adjustments." )
</programlisting>
    </section> <!--MenuBar-helpSliders-->
    <section id='MenuBar-cascadeViewing'>
      <title><code >MenuBar.__cascadeViewing()</code ></title>
      <para>
        This cascade is slightly more involved because it has a
        second-level cascade in it.
      </para>
      <programlisting role='outFile:huey'
>    def __cascadeViewing ( self, menu ):
        """Add the help cascade for viewing colors

          [ menu is a Menu widget ->
              menu  :=  menu with choices added for help topics
                        about viewing colors ]
        """
</programlisting>
      <para>
        First we create a new cascade and add it to the parent <code
        >menu</code >.
      </para>
      <programlisting role='outFile:huey'
>        #-- 1 --
        # [ menu    :=  menu with a new cascade added
        #   select  :=  that cascade ]
        select  =  Menu ( menu )
        menu.add_cascade ( menu=select,
            label="Viewing colors and fonts" )
</programlisting>
      <para>
        There are only two subcomponents: help on the color
        swatch area, and the subcascade for font help.  See <xref
        linkend='MenuBar-helpSwatch' /> and <xref
        linkend='MenuBar-cascadeFonts' />.
      </para>
      <programlisting role='outFile:huey'
>        #-- 2 --
        # [ select  :=  select with a pop-up added about the
        #               color swatch ]
        select.add_command ( command=self.__helpSwatch,
            label="The color swatch" )

        #-- 3 --
        # [ select  :=  select with a cascade added for font help ]
        self.__cascadeFonts ( select )
</programlisting>
    </section> <!--MenuBar-cascadeViewing-->
    <section id='MenuBar-helpSwatch'>
      <title><code >MenuBar.__helpSwatch()</code ></title>
      <programlisting role='outFile:huey'
># - - -   M e n u B a r . _ _ h e l p S w a t c h

    def __helpSwatch ( self ):
        """Help for the color swatch.
        """
        self.__dialog ( "Help: The color swatch",
            "The top right corner of the window displays your "
            "currently selected background color, with some text in "
            "your currently selected font displayed using the "
            "current text (foreground) color." )
</programlisting>
    </section> <!--MenuBar-helpSwatch-->
    <section id='MenuBar-cascadeFonts'>
      <title><code >MenuBar.__cascadeFonts()</code ></title>
      <para>
        This cascade contains subtopics about font selection.
      </para>
      <programlisting role='outFile:huey'
># - - -   M e n u B a r . _ _ c a s c a d e F o n t s

    def __cascadeFonts ( self, menu ):
        """Add the font help cascade.

          [ menu is a Menu widget ->
              menu  :=  menu with help topics added on fonts ]
        """
        #-- 1 --
        # [ menu  :=  menu with a new cascade added
        #   select  :=  that cascade ]
        select  =  Menu ( menu )
        menu.add_cascade ( menu=select,
            label="Selecting a font" )
</programlisting>
      <para>
        For subtopics, see
        <xref linkend='MenuBar-helpFontFamily' />,
        <xref linkend='MenuBar-helpFontSize' />, and
        <xref linkend='MenuBar-helpFontStyle' />.
      </para>
      <programlisting role='outFile:huey'
>        #-- 2 --
        # [ select  :=  select with a pop-up added about font
        #               families ]
        select.add_command ( command=self.__helpFontFamily,
            label="Selecting a font family" )

        #-- 3 --
        # [ simile ]
        select.add_command ( command=self.__helpFontSize,
            label="Changing the font size" )

        #-- 4 --
        select.add_command ( command=self.__helpFontStyle,
            label="Changing font weight and slant" )
</programlisting>
    </section> <!--MenuBar-cascadeFonts-->
    <section id='MenuBar-helpFontFamily'>
      <title><code >MenuBar.__helpFontFamily()</code ></title>
      <programlisting role='outFile:huey'
># - - -   M e n u B a r . _ _ h e l p F o n t F a m i l y

    def __helpFontFamily ( self ):
        """Help for font family selection.
        """
        self.__dialog ( "Help: Selecting a font family",
            "Under 'Click to select font family:' is a scrollable "
            "list containing the names of all the locally installed "
            "font families.  Click on any of these names to select "
            "that family." )
</programlisting>
    </section> <!--MenuBar-helpFontFamily-->
    <section id='MenuBar-helpFontSize'>
      <title><code >MenuBar.__helpFontSize()</code ></title>
      <programlisting role='outFile:huey'
># - - -   M e n u B a r . _ _ h e l p F o n t S i z e

    def __helpFontSize ( self ):
        """Help for changing font size.
        """
        self.__dialog ( "Help: Changing the font size",
            "To change the size of the text, enter a number in the "
            "field labeled 'Size:'.  This number gives the font size "
            "in points.  You can also enter a negative number to "
            "specify a size in pixels.  After changing the size, "
            "either press the Enter key or click on the 'Set size' "
            "button. " )
</programlisting>
    </section> <!--MenuBar-helpFontSize-->
    <section id='MenuBar-helpFontStyle'>
      <title><code >MenuBar.__helpFontStyle()</code ></title>
      <programlisting role='outFile:huey'
># - - -   M e n u B a r . _ _ h e l p F o n t S t y l e

    def __helpFontStyle ( self ):
        """Popup for changing font weight and slant.
        """
        self.__dialog ( "Help: Changing the font weight and slant",
            "To change from normal to boldface type or back to normal, "
            "click the 'Bold' button."
            "\n\tTo change from normal to italic type or back to "
            "normal, click the 'Italic' button." )
</programlisting>
    </section> <!--MenuBar-helpFontStyle-->
    <section id='MenuBar-helpImporting'>
      <title><code >MenuBar.__helpImporting()</code ></title>
      <programlisting role='outFile:huey'
># - - -   M e n u B a r . _ _ h e l p I m p o r t i n g

    def __helpImporting ( self ):
        """Pop-up help for importing color names.
        """
        self.__dialog ( "Help: Using colors in other applications",
            "To use one of the standard color names in an application, "
            "you can just use the name as it appears in the list of "
            "standard colors."
            "\n\tYou can also use the hexadecimal form of the color "
            "name that appears under #RRGGBB for either the background "
            "or text (foreground) color." )
</programlisting>
    </section> <!--MenuBar-helpImporting-->
    <section id='MenuBar-helpAuthor'>
      <title><code >MenuBar.__helpAuthor()</code ></title>
      <programlisting role='outFile:huey'
># - - -   M e n u B a r . _ _ h e l p A u t h o r

    def __helpAuthor ( self ):
        """Pop-up for author credit.
        """
        self.__dialog ( "Help: Who made this tool?",
            "%s was written by John W. Shipman (john@nmt.edu) "
            "of the New Mexico Tech Computer Center, Socorro, NM "
            "87801."
            "\n\tThis is version %s."
            "\n\tFor documentation, see:\n%s" %
                (PROGRAM_NAME, EXTERNAL_VERSION, "&selfURL;") )
</programlisting>
    </section> <!--MenuBar-helpAuthor-->
    <section id='MenuBar-dialog'>
      <title><code >MenuBar.__dialog()</code >: Pop up a dialog
      widget</title>
      <para>
        This method creates a pop-up <code >Dialog</code > widget.  The
        structure is the same except for the frame title (the <code
        >title</code > argument) and the help text (the <code
        >text</code > argument).
      </para>
      <programlisting role='outFile:huey'
># - - -   M e n u B a r . _ _ d i a l o g

    def __dialog ( self, title, text ):
        """Pop up a Dialog widget.

          [ (title is the frame title as a string) and
            (text is the help text as a multiline string containing
            newlines and tab characters) ->
              desktop  :=  desktop with a Dialog widget displaying
                  title and text ]
        """
</programlisting>
      <para>
        The <code >Dialog</code > widget for pop-up menus comes
        from the <code >Dialog.py</code > module, part of
        Tkinter.  Unfortunately, this widget is poorly
        documented; the author learned to use it by reading
        examples and reading its source code.
      </para>
      <itemizedlist spacing="compact">
        <listitem>
          <para>
            The <code >title</code > argument appears as the
            window title.
          </para>
        </listitem>
        <listitem>
          <para>
            The <code >default</code > argument specifies which
            button is the default choice; in our case, it's the
            only button, button number zero.
          </para>
        </listitem>
        <listitem>
          <para>
            The <code >strings</code > argument is a tuple of button
            titles.  We need only one button, a <guibutton
            >Dismiss</guibutton > button that gets rid of the pop-up.
          </para>
        </listitem>
        <listitem>
          <para>
            The <code >bitmap</code > argument specifies which
            pictograph appears on the pop-up; the <code
            >"info"</code > bitmap is a big gray question mark.
          </para>
        </listitem>
        <listitem>
          <para>
            The <code >text</code > argument is the body text for
            the pop-up.  This text may include newlines (<code
            >"\n"</code >) and tabs (<code >"\t"</code >).  Long
            lines will be folded automatically.  Note that Python
            automatically merges adjacent string constants.
          </para>
        </listitem>
      </itemizedlist>
      <programlisting role='outFile:huey'
>        Dialog ( self, default=0, strings=('Dismiss',), bitmap='info',
                 title=title, text=text )
</programlisting>
      <para>
        One drawback of these prefabricated <code >Dialog</code >
        widgets is that you can't override the ugly font.  This
        is a candidate for replacement someday.  Note that the
        widget grabs the focus, so the user can't do anything
        back in the main window until they dismiss the pop-up.
      </para>
    </section> <!--MenuBar-dialog-->
  </section> <!--class-MenuBar-->
  <section id='class-NamePicker'>
    <title><code >class NamePicker</code >: Select colors by name</title>
    <para>
      This class is a compound widget that gives the user two
      different ways to select a color by its name.  They may
      type the name directly into an <code >Entry</code >
      widget.  Most of the vertical space below this <code
      >Entry</code > contains a <code >ScrolledList</code > that
      displays a prefabricated list of color names; the user may
      instead click on any of these.
    </para>
    <programlisting role='outFile:huey'>
# - - - - -   c l a s s   N a m e P i c k e r

class NamePicker(Frame):
    """A compound widget for selecting a color by its name.

      Exports:
        NamePicker ( parent, callback=None ):
          [ (parent is a Frame) and
            (callback is a function or None) ->
              parent  :=  parent with a new NamePicker widget
                  added but not gridded, that calls callback
                  whenever a color is selected
              return that NamePicker widget ]

      Contained widgets:
        .__entryLabel: [ a Label widget that labels self.__entry ]
        .__entry:      [ an Entry widget for entering a color name ]
        .__pickLabel:  [ a Label widget that labels self.__pickList ]
        .__pickList:   [ a ScrolledList widget with color names ]

      Grid plan:  Vertical stacking.
</programlisting>
    <para>
      Private attributes include <code >.__callback</code >, the
      callback function, and a <code >StringVar</code > control
      variable linked to the <code >.__entry</code > widget.
    </para>
    <programlisting role='outFile:huey'
>      State/Invariants:
        .__parent:     [ as passed to constructor ]
        .__callback:   [ as passed to constructor ]
        .__entryVar:
          [ a StringVar control variable for self.__entry ]
    """
</programlisting>
    <para>
      The width of the <code >Entry</code > field, a manifest
      constant, is a class variable.
    </para>
    <programlisting role='outFile:huey'
>    NAME_WIDTH  =  20
</programlisting>
    <section id='NamePicker-init'>
      <title><code >NamePicker.__init()</code >: Constructor</title>
      <para>
        The constructor initializes the parent <code >Frame</code
        >, stores its argument, sets up a control variable for
        the <code >Entry</code >, and then creates all the
        widgets.  See <xref linkend='NamePicker-createWidgets' />.
      </para>
      <programlisting role='outFile:huey'
># - - -   N a m e P i c k e r . _ _ i n i t _ _

    def __init__ ( self, parent, callback=None ):
        """Constructor for the NamePicker compound widget.
        """
        #-- 1 --
        # [ self  :=  self as a Frame ]
        Frame.__init__ ( self, parent )

        #-- 2 --
        self.__parent    =  parent
        self.__callback  =  None

        #-- 3 --
        # [ self  :=  self with all contained widgets created and
        #             gridded ]
        self.__createWidgets()

        #-- 4 --
        self.__callback  =  callback
</programlisting>
    </section> <!--NamePicker-init-->
    <section id='NamePicker-createWidgets'>
      <title><code >NamePicker.__createWidgets()</code >: Create
      all widgets</title>
      <para>
        For gridding information, see <xref
        linkend='class-NamePicker' />.
      </para>
      <programlisting role='outFile:huey'
># - - -   N a m e P i c k e r . _ _ c r e a t e W i d g e t s

    def __createWidgets ( self ):
        """Create all widgets."""

        #-- 1 --
        # [ self  :=  self with a new Label to label the color
        #             name entry field
        #   self.__entryLabel  :=  that Label ]
        self.__entryLabel  =  Label ( self,
            font=BUTTON_FONT, text="Type a color name:" )
        rowx  =  0
        self.__entryLabel.grid ( row=rowx, column=0, sticky=W )
</programlisting>
      <para>
        The <code >Entry</code > field has an important event
        binding: if the user presses the <keysym >Enter</keysym >
        key (which is called <code >Return</code > in Tkinter),
        that means they have selected a color, so the callback
        must be called.  The binding uses the handler method
        <xref linkend='NamePicker-entryHandler' />.
      </para>
      <programlisting role='outFile:huey'
>        #-- 2 --
        # [ self  :=  self with a new Entry whose control variable
        #       is self.__entryVar, and which calls self.__callback
        #       when the user presses the Enter key ]
        self.__entryVar  =  StringVar()
        self.__entry  =  Entry ( self, relief=SUNKEN,
            font=BUTTON_FONT,
            width=self.NAME_WIDTH,
            textvariable=self.__entryVar )
        rowx  +=  1
        self.__entry.grid ( row=rowx, column=0, sticky=W )
        self.__entry.bind ( "&lt;Key-Return&gt;", self.__entryHandler )
</programlisting>
      <para>
        Placing the label for the color pick list is
        straightforward.  However, creating the color pick list
        is pretty involved, so that logic is located in <xref
        linkend='class-PickList'/>.
      </para>
      <programlisting role='outFile:huey'
>        #-- 3 --
        # [ self  :=  self with a Label added for the color pick list
        #   self.__pickLabel  :=  that Label ]
        self.__pickLabel  =  Label ( self,
            font=BUTTON_FONT,
            text="Or click on a name:" )
        rowx  +=  1
        self.__pickLabel.grid ( row=rowx, column=0, sticky=W )

        #-- 4 --
        # [ self  :=  self with a PickList added containing
        #       the color pick list, with bindings that will call
        #       self.__callback when a color is clicked
        #   self.__pickList  :=  that ScrolledList ]
        self.__pickList  =  PickList ( self, self.__pickListHandler )
        rowx  +=  1
        self.__pickList.grid ( row=rowx, column=0, sticky=W )
</programlisting>
    </section> <!--NamePicker-createWidgets-->
    <section id='NamePicker-entryHandler'>
      <title><code >NamePicker.__entryHandler()</code >: Handler
      for the <keysym >Enter</keysym > key</title>
      <para>
        This method is called when the user presses the <keysym
        >Enter</keysym > key in the color name field.  Assuming
        the color name is valid, we then call the class's
        callback to notify observers that the color has changed.
      </para>
      <programlisting role='outFile:huey'
># - - -   N a m e P i c k e r . _ _ e n t r y H a n d l e r

    def __entryHandler ( self, event ):
        """Handle the Enter key in the color name field.

          [ event is an Event from self.__entry ->
              if  self.__entryVar is a valid color name ->
                call self.__callback with a Color representing
                that name
              else ->
                pop up a Dialog informing the user that the
                color name is not valid ]
        """
</programlisting>
      <para>
        The <code >.get()</code > method on <code
        >self.__entryVar</code > retrieves the text from the
        color name field.  We then pass the name on to
        <xref linkend='NamePicker-setByName' />.
      </para>
      <programlisting role='outFile:huey'
>        #-- 1 --
        # [ colorName  :=  text from self.__entryVar ]
        colorName  =  self.__entryVar.get()

        #-- 2 --
        # [ if  self.__entryVar is a valid color name ->
        #     call self.__callback with a Color representing
        #     that name
        #   else ->
        #     pop up a Dialog informing the user that the
        #     color name is not valid ]
        self.__setByName ( colorName )
</programlisting>
    </section> <!--NamePicker-entryHandler-->
    <section id='NamePicker-setByName'>
      <title><code >NamePicker.__setByName()</code >: Try to
      convert a color name to a color</title>
      <para>
        The purpose of this method is to convert a color name into a
        numeric value, and thence into a <code >Color</code > instance.
      </para>
      <programlisting role='outFile:huey'
># - - -   N a m e P i c k e r . _ _ s e t B y N a m e

    def __setByName ( self, colorName ):
        """Convert a color name to RGB values as a Color instance.

          [ colorName is a string ->
              if colorName is defined in either Tkinter or our
              color pick list ->
                call self.__callback and pass it that color as
                a Color instance
              else ->
                pop up a Dialog window complaining about the
                undefined color name ]
        """
</programlisting>
      <para>
        In theory, a properly installed X Window system will have a file
        named &rgb-txt; that lists all the standard
        color names.  In theory, any application should be able to use a
        color name from that file, and the window system will recognize
        it.  In practice, the presence of that file is not guaranteed,
        and even if does exist, we have no assurance that the set of
        color names accepted by the window system is the same as the
        contents of the file.
      </para>
      <para>
        In case the &rgb-txt; file is missing, this
        program contains a data structure which represents the contents
        of one version of that file.
      </para>
      <para>
        However, there is another source for translating color names to
        RGB values. Every Tkinter widget has a method named <code
        >.winfo_rgb()</code > which takes a color name and returns a
        triple <code >(<replaceable >R</replaceable >, <replaceable
        >G</replaceable >, <replaceable >B</replaceable >)</code > where
        each value is in the range [0,<code >MAX_PARAM</code >].
      </para>
      <para>
        So, our liberal policy will be to try the latter technique
        first.  If that fails, we can still use the list that appears in
        the pick list to convert names to RGB values.  That way, if the
        two sources differ, we implement the union of their values.
        There is a discussion of the <code >Dialog</code > pop-up in
        <xref linkend='MenuBar-helpTyping' />.  See also
        <xref linkend='PickList-lookupName' />.
      </para>
      <programlisting role='outFile:huey'
>        #-- 1 --
        # [ if (Tkinter recognizes (colorName) as a color) or
        #   (colorName) is defined in self.__pickList) ->
        #     rgb  :=  that color as a tuple (r, g, b) of values
        #              in [0,MAX_PARAM]
        #   else ->
        #     pop up a Dialog complaining about the undefined
        #     color name
        #     return ]
        try:
            rgb  =  self.winfo_rgb ( colorName )
        except:
            rgb  =  self.__pickList.lookupName ( colorName )
            if  not rgb:
                Dialog ( self, title="Message", bitmap="info",
                    default=0, strings=("Dismiss",),
                    text=("Color '%s' is undefined." % colorName) )
                return
</programlisting>
      <para>
        All that remains is to convert <code >rgb</code > to a <code
        >Color</code > and call the callback.
      </para>
      <programlisting role='outFile:huey'
>        #-- 2 --
        # [ if self.__callback is not None ->
        #     call self.__callback with a Color made from rgb
        #   else -> I ]
        if  self.__callback is not None:
            self.__callback ( Color ( *rgb ) )
</programlisting>
    </section> <!--NamePicker-setByName-->
    <section id='NamePicker-pickListHandler'>
      <title><code >NamePicker.__pickListHandler()</code >: Somebody
      clicked on a color</title>
      <para>
        This method is called when the user clicks on a color name in
        the <code >PickList</code >.  That object passes us that color
        as a <code >Color</code > instance, and we pass it on to our
        callback.
      </para>
      <programlisting role='outFile:huey'
># - - -   N a m e P i c k e r . _ _ p i c k L i s t H a n d l e r

    def __pickListHandler ( self, color ):
        """Handler for self.__pickList.
        """
        if  self.__callback is not None:
            self.__callback ( color )
</programlisting>
    </section> <!--NamePicker-pickListHandler-->
  </section> <!--class-NamePicker-->
  <section id='class-PickList'>
    <title><code >class PickList</code >: The color names pick
    list</title>
    <para>
      This class is a compound widget containing the list of standard
      color names.  It responds to user clicks by calling its callback
      function with a <code >Color</code > instance.  It also contains a
      translation table for converting its color names to color values.
      For an important discussion of the two methods of converting color
      names, see <xref linkend='NamePicker-setByName' />.
    </para>
    <para>
      In GUI terms, it is a frame containing only a single widget, a
      <code >ScrolledList</code >.  This widget is a compound widget
      from the author's widget library.  It is a combination of a
      Tkinter <code >Listbox</code > widget with a vertical <code
      >Scrollbar</code >.  For particulars of its call and
      implementation, see <xref linkend='imports' />.
    </para>
    <para>
      To populate this list with standard color names, there are two
      possible sources.  On most Unix boxen, there is a file named
      &rgb-txt; that defines the names and values.
      Depending on the age of the install, it may be in one of these
      paths; the first one reflects the more recent practice:
      <programlisting
>/usr/share/X11
/usr/lib/X11
</programlisting>
      However, just so this program isn't useless in the absence of the
      file, this class contains an internal version of &rgb-txt; as a
      fallback measure.
    </para>
    <para>
      Here is the class interface:
    </para>
    <programlisting role='outFile:huey'>
# - - - - -   c l a s s   P i c k L i s t

class PickList(Frame):
    """Compound widget for the color pick list.

      Exports:
        PickList ( parent, callback=None ):
          [ (parent is a Frame) and
            (callback is a function or None) ->
              parent  :=  parent with a new PickList widget added
                  but not gridded
              return that new widget ]
</programlisting>
      <para>
        This method allows the caller to translate a color name
        to a <code >Color</code > instance; see <xref
        linkend='PickList-lookupName' />.
      </para>
      <programlisting role='outFile:huey'
>        .lookupName ( colorName ):
          [ colorName is a string ->
              if colorName matches a color in self's list,
              case-insensitive ->
                return the corresponding value as a Color instance
              else -> return None ]

      Contained widgets:
        .__scrolledList:
          [ a scrolledlist.ScrolledList containing self's color
            names ]

      Grid plan:  Only one widget
</programlisting>
      <para>
        Here are the internal attributes.  First, we keep a copy of the
        callback procedure.
      </para>
      <programlisting role='outFile:huey'
>      State/Invariants:
        .__callback:    [ as passed to the constructor ]
</programlisting>
      <para>
        For internal data structures, we need to be able to look up
        colors both by name and by their position in the pick list.  The
        lists <code >.__colorList</code > and <code >.__nameList</code >
        are by position: the <varname>n</varname>th element in <code
        >self.__colorList</code > is the color displayed in the
        <varname>n</varname>th line of the pick list, and the
        <varname>n</varname>th element of <code >self.__nameList</code >
        is the name of the color displayed in that line.
      </para>
      <programlisting role='outFile:huey'
>        .__colorList:
          [ a list of Color instances such that self.__colorList[i]
            is the value of the color displayed in the (i)th line
            of self.__scrolledList, as a Color instance ]
        .__nameList:
          [ a list of color names such that self.__nameList[i]
            is the name of the color displayed in the (i)th line
            of self.__scrolledList ]
</programlisting>
      <para>
        The obvious data structure for looking up color names is a
        dictionary.  To make the lookup case-insensitive, the keys of
        this dictionary are the color names, uppercased.  Each value in
        the dictionary is a tuple <code >(<replaceable
        >index</replaceable >, <replaceable >color</replaceable >,
        <replaceable >name</replaceable >)</code >, where:
      </para>
      <itemizedlist>
        <listitem>
          <para>
            The <code ><replaceable >index</replaceable ></code > is an
            integer that sorts colors into the original order in their
            source, whether that source is the &rgb-txt; file or the
            built-in default color list.
          </para>
          <para>
            This index is important in the process of removing redundant
            colors from the list while retaining their original order;
            see <xref linkend='PickList-cleanColorMap' />.
          </para>
        </listitem>
        <listitem>
          <para>
            The <code ><replaceable >color</replaceable ></code > is the
            color value as an instance of the <code >Color</code >
            class.
          </para>
        </listitem>
        <listitem>
          <para>
            The <code ><replaceable >name</replaceable ></code > is the
            color name as a string.
          </para>
        </listitem>
      </itemizedlist>
      <programlisting role='outFile:huey'>
        .__colorMap:
          [ a dictionary whose keys are the color names in self,
            uppercased, and each value is a tuple (index, color,
            name) where index orders the colors in their original
            source order, color is a Color instance, and name is
            the color name as a string ]
    """
</programlisting>
    <section id='PickList-constants'>
      <title><code >PickList</code > class constants</title>
      <para>
        The class has a few manifest constants.  First is the name of the
        standard color names file.
      </para>
      <programlisting role='outFile:huey'
>    COLOR_NAMES_FILE  =  "&rgb-txt;"
</programlisting>
      <para>
        Next, a list of all the pathnames in which to look for that file.
      </para>
      <programlisting role='outFile:huey'
>    PATHS_LIST  =  [ '/usr/share/X11', '/usr/lib/X11' ]
</programlisting>
      <para>
        The physical width and height of the list box are given in
        characters and lines, respectively.
      </para>
      <programlisting role='outFile:huey'
>    NAME_WIDTH  =  20
    NAME_LINES  =  22
</programlisting>
    </section> <!--PickList-constants-->
    <section id='PickList-defaultColors'>
      <title><code >PickList</code >: Default color list</title>
      <para>
        The default color list is also a class variable.  It's rather
        long, but it must be declared here.
      </para>
      <para>
        This list was generated by reformatting a 2001-era version of
        the stock &rgb-txt; file.  Each line of that file is represented
        here as one string.  The first three bytes of the string are
        the red, green, and blue parameters, each in the range [0,255],
        and the balance of the string is the actual color name.
      </para>
      <programlisting role='outFile:huey'
>    DEFAULT_COLORS  =  [
      '\xFF\xFA\xFAsnow',               '\xF8\xF8\xFFGhostWhite',
      '\xF5\xF5\xF5WhiteSmoke',         '\xDC\xDC\xDCgainsboro',
      '\xFF\xFA\xF0FloralWhite',        '\xFD\xF5\xE6OldLace',
      '\xFA\xF0\xE6linen',              '\xFA\xEB\xD7AntiqueWhite',
      '\xFF\xEF\xD5PapayaWhip',         '\xFF\xEB\xCDBlanchedAlmond',
      '\xFF\xE4\xC4bisque',             '\xFF\xDA\xB9PeachPuff',
      '\xFF\xDE\xADNavajoWhite',        '\xFF\xE4\xB5moccasin',
      '\xFF\xF8\xDCcornsilk',           '\xFF\xFF\xF0ivory',
      '\xFF\xFA\xCDLemonChiffon',       '\xFF\xF5\xEEseashell',
      '\xF0\xFF\xF0honeydew',           '\xF5\xFF\xFAMintCream',
      '\xF0\xFF\xFFazure',              '\xF0\xF8\xFFAliceBlue',
      '\xE6\xE6\xFAlavender',           '\xFF\xF0\xF5LavenderBlush',
      '\xFF\xE4\xE1MistyRose',          '\xFF\xFF\xFFwhite',
      '\x00\x00\x00black',              '\x2F\x4F\x4FDarkSlateGray',
      '\x69\x69\x69DimGray',            '\x70\x80\x90SlateGray',
      '\x77\x88\x99LightSlateGray',     '\xBE\xBE\xBEgray',
      '\xD3\xD3\xD3LightGray',          '\x19\x19\x70MidnightBlue',
      '\x00\x00\x80navy',               '\x00\x00\x80NavyBlue',
      '\x64\x95\xEDCornflowerBlue',     '\x48\x3D\x8BDarkSlateBlue',
      '\x6A\x5A\xCDSlateBlue',          '\x7B\x68\xEEMediumSlateBlue',
      '\x84\x70\xFFLightSlateBlue',     '\x00\x00\xCDMediumBlue',
      '\x41\x69\xE1RoyalBlue',          '\x00\x00\xFFblue',
      '\x1E\x90\xFFDodgerBlue',         '\x00\xBF\xFFDeepSkyBlue',
      '\x87\xCE\xEBSkyBlue',            '\x87\xCE\xFALightSkyBlue',
      '\x46\x82\xB4SteelBlue',          '\xB0\xC4\xDELightSteelBlue',
      '\xAD\xD8\xE6LightBlue',          '\xB0\xE0\xE6PowderBlue',
      '\xAF\xEE\xEEPaleTurquoise',      '\x00\xCE\xD1DarkTurquoise',
      '\x48\xD1\xCCMediumTurquoise',    '\x40\xE0\xD0turquoise',
      '\x00\xFF\xFFcyan',               '\xE0\xFF\xFFLightCyan',
      '\x5F\x9E\xA0CadetBlue',          '\x66\xCD\xAAMediumAquamarine',
      '\x7F\xFF\xD4aquamarine',         '\x00\x64\x00DarkGreen',
      '\x55\x6B\x2FDarkOliveGreen',     '\x8F\xBC\x8FDarkSeaGreen',
      '\x2E\x8B\x57SeaGreen',           '\x3C\xB3\x71MediumSeaGreen',
      '\x20\xB2\xAALightSeaGreen',      '\x98\xFB\x98PaleGreen',
      '\x00\xFF\x7FSpringGreen',        '\x7C\xFC\x00LawnGreen',
      '\x00\xFF\x00green',              '\x7F\xFF\x00chartreuse',
      '\x00\xFA\x9AMediumSpringGreen',  '\xAD\xFF\x2FGreenYellow',
      '\x32\xCD\x32LimeGreen',          '\x9A\xCD\x32YellowGreen',
      '\x22\x8B\x22ForestGreen',        '\x6B\x8E\x23OliveDrab',
      '\xBD\xB7\x6BDarkKhaki',          '\xF0\xE6\x8Ckhaki',
      '\xEE\xE8\xAAPaleGoldenrod',
      '\xFA\xFA\xD2LightGoldenrodYellow',
      '\xFF\xFF\xE0LightYellow',        '\xFF\xFF\x00yellow',
      '\xFF\xD7\x00gold',               '\xEE\xDD\x82LightGoldenrod',
      '\xDA\xA5\x20goldenrod',          '\xB8\x86\x0BDarkGoldenrod',
      '\xBC\x8F\x8FRosyBrown',          '\xCD\x5C\x5CIndianRed',
      '\x8B\x45\x13SaddleBrown',        '\xA0\x52\x2Dsienna',
      '\xCD\x85\x3Fperu',               '\xDE\xB8\x87burlywood',
      '\xF5\xF5\xDCbeige',              '\xF5\xDE\xB3wheat',
      '\xF4\xA4\x60SandyBrown',         '\xD2\xB4\x8Ctan',
      '\xD2\x69\x1Echocolate',          '\xB2\x22\x22firebrick',
      '\xA5\x2A\x2Abrown',              '\xE9\x96\x7ADarkSalmon',
      '\xFA\x80\x72salmon',             '\xFF\xA0\x7ALightSalmon',
      '\xFF\xA5\x00orange',             '\xFF\x8C\x00DarkOrange',
      '\xFF\x7F\x50coral',              '\xF0\x80\x80LightCoral',
      '\xFF\x63\x47tomato',             '\xFF\x45\x00OrangeRed',
      '\xFF\x00\x00red',                '\xFF\x69\xB4HotPink',
      '\xFF\x14\x93DeepPink',           '\xFF\xC0\xCBpink',
      '\xFF\xB6\xC1LightPink',          '\xDB\x70\x93PaleVioletRed',
      '\xB0\x30\x60maroon',             '\xC7\x15\x85MediumVioletRed',
      '\xD0\x20\x90VioletRed',          '\xFF\x00\xFFmagenta',
      '\xEE\x82\xEEviolet',             '\xDD\xA0\xDDplum',
      '\xDA\x70\xD6orchid',             '\xBA\x55\xD3MediumOrchid',
      '\x99\x32\xCCDarkOrchid',         '\x94\x00\xD3DarkViolet',
      '\x8A\x2B\xE2BlueViolet',         '\xA0\x20\xF0purple',
      '\x93\x70\xDBMediumPurple',       '\xD8\xBF\xD8thistle',
      '\xFF\xFA\xFAsnow1',              '\xEE\xE9\xE9snow2',
      '\xCD\xC9\xC9snow3',              '\x8B\x89\x89snow4',
      '\xFF\xF5\xEEseashell1',          '\xEE\xE5\xDEseashell2',
      '\xCD\xC5\xBFseashell3',          '\x8B\x86\x82seashell4',
      '\xFF\xEF\xDBAntiqueWhite1',      '\xEE\xDF\xCCAntiqueWhite2',
      '\xCD\xC0\xB0AntiqueWhite3',      '\x8B\x83\x78AntiqueWhite4',
      '\xFF\xE4\xC4bisque1',            '\xEE\xD5\xB7bisque2',
      '\xCD\xB7\x9Ebisque3',            '\x8B\x7D\x6Bbisque4',
      '\xFF\xDA\xB9PeachPuff1',         '\xEE\xCB\xADPeachPuff2',
      '\xCD\xAF\x95PeachPuff3',         '\x8B\x77\x65PeachPuff4',
      '\xFF\xDE\xADNavajoWhite1',       '\xEE\xCF\xA1NavajoWhite2',
      '\xCD\xB3\x8BNavajoWhite3',       '\x8B\x79\x5ENavajoWhite4',
      '\xFF\xFA\xCDLemonChiffon1',      '\xEE\xE9\xBFLemonChiffon2',
      '\xCD\xC9\xA5LemonChiffon3',      '\x8B\x89\x70LemonChiffon4',
      '\xFF\xF8\xDCcornsilk1',          '\xEE\xE8\xCDcornsilk2',
      '\xCD\xC8\xB1cornsilk3',          '\x8B\x88\x78cornsilk4',
      '\xFF\xFF\xF0ivory1',             '\xEE\xEE\xE0ivory2',
      '\xCD\xCD\xC1ivory3',             '\x8B\x8B\x83ivory4',
      '\xF0\xFF\xF0honeydew1',          '\xE0\xEE\xE0honeydew2',
      '\xC1\xCD\xC1honeydew3',          '\x83\x8B\x83honeydew4',
      '\xFF\xF0\xF5LavenderBlush1',     '\xEE\xE0\xE5LavenderBlush2',
      '\xCD\xC1\xC5LavenderBlush3',     '\x8B\x83\x86LavenderBlush4',
      '\xFF\xE4\xE1MistyRose1',         '\xEE\xD5\xD2MistyRose2',
      '\xCD\xB7\xB5MistyRose3',         '\x8B\x7D\x7BMistyRose4',
      '\xF0\xFF\xFFazure1',             '\xE0\xEE\xEEazure2',
      '\xC1\xCD\xCDazure3',             '\x83\x8B\x8Bazure4',
      '\x83\x6F\xFFSlateBlue1',         '\x7A\x67\xEESlateBlue2',
      '\x69\x59\xCDSlateBlue3',         '\x47\x3C\x8BSlateBlue4',
      '\x48\x76\xFFRoyalBlue1',         '\x43\x6E\xEERoyalBlue2',
      '\x3A\x5F\xCDRoyalBlue3',         '\x27\x40\x8BRoyalBlue4',
      '\x00\x00\xFFblue1',              '\x00\x00\xEEblue2',
      '\x00\x00\xCDblue3',              '\x00\x00\x8Bblue4',
      '\x1E\x90\xFFDodgerBlue1',        '\x1C\x86\xEEDodgerBlue2',
      '\x18\x74\xCDDodgerBlue3',        '\x10\x4E\x8BDodgerBlue4',
      '\x63\xB8\xFFSteelBlue1',         '\x5C\xAC\xEESteelBlue2',
      '\x4F\x94\xCDSteelBlue3',         '\x36\x64\x8BSteelBlue4',
      '\x00\xBF\xFFDeepSkyBlue1',       '\x00\xB2\xEEDeepSkyBlue2',
      '\x00\x9A\xCDDeepSkyBlue3',       '\x00\x68\x8BDeepSkyBlue4',
      '\x87\xCE\xFFSkyBlue1',           '\x7E\xC0\xEESkyBlue2',
      '\x6C\xA6\xCDSkyBlue3',           '\x4A\x70\x8BSkyBlue4',
      '\xB0\xE2\xFFLightSkyBlue1',      '\xA4\xD3\xEELightSkyBlue2',
      '\x8D\xB6\xCDLightSkyBlue3',      '\x60\x7B\x8BLightSkyBlue4',
      '\xC6\xE2\xFFSlateGray1',         '\xB9\xD3\xEESlateGray2',
      '\x9F\xB6\xCDSlateGray3',         '\x6C\x7B\x8BSlateGray4',
      '\xCA\xE1\xFFLightSteelBlue1',    '\xBC\xD2\xEELightSteelBlue2',
      '\xA2\xB5\xCDLightSteelBlue3',    '\x6E\x7B\x8BLightSteelBlue4',
      '\xBF\xEF\xFFLightBlue1',         '\xB2\xDF\xEELightBlue2',
      '\x9A\xC0\xCDLightBlue3',         '\x68\x83\x8BLightBlue4',
      '\xE0\xFF\xFFLightCyan1',         '\xD1\xEE\xEELightCyan2',
      '\xB4\xCD\xCDLightCyan3',         '\x7A\x8B\x8BLightCyan4',
      '\xBB\xFF\xFFPaleTurquoise1',     '\xAE\xEE\xEEPaleTurquoise2',
      '\x96\xCD\xCDPaleTurquoise3',     '\x66\x8B\x8BPaleTurquoise4',
      '\x98\xF5\xFFCadetBlue1',         '\x8E\xE5\xEECadetBlue2',
      '\x7A\xC5\xCDCadetBlue3',         '\x53\x86\x8BCadetBlue4',
      '\x00\xF5\xFFturquoise1',         '\x00\xE5\xEEturquoise2',
      '\x00\xC5\xCDturquoise3',         '\x00\x86\x8Bturquoise4',
      '\x00\xFF\xFFcyan1',              '\x00\xEE\xEEcyan2',
      '\x00\xCD\xCDcyan3',              '\x00\x8B\x8Bcyan4',
      '\x97\xFF\xFFDarkSlateGray1',     '\x8D\xEE\xEEDarkSlateGray2',
      '\x79\xCD\xCDDarkSlateGray3',     '\x52\x8B\x8BDarkSlateGray4',
      '\x7F\xFF\xD4aquamarine1',        '\x76\xEE\xC6aquamarine2',
      '\x66\xCD\xAAaquamarine3',        '\x45\x8B\x74aquamarine4',
      '\xC1\xFF\xC1DarkSeaGreen1',      '\xB4\xEE\xB4DarkSeaGreen2',
      '\x9B\xCD\x9BDarkSeaGreen3',      '\x69\x8B\x69DarkSeaGreen4',
      '\x54\xFF\x9FSeaGreen1',          '\x4E\xEE\x94SeaGreen2',
      '\x43\xCD\x80SeaGreen3',          '\x2E\x8B\x57SeaGreen4',
      '\x9A\xFF\x9APaleGreen1',         '\x90\xEE\x90PaleGreen2',
      '\x7C\xCD\x7CPaleGreen3',         '\x54\x8B\x54PaleGreen4',
      '\x00\xFF\x7FSpringGreen1',       '\x00\xEE\x76SpringGreen2',
      '\x00\xCD\x66SpringGreen3',       '\x00\x8B\x45SpringGreen4',
      '\x00\xFF\x00green1',             '\x00\xEE\x00green2',
      '\x00\xCD\x00green3',             '\x00\x8B\x00green4',
      '\x7F\xFF\x00chartreuse1',        '\x76\xEE\x00chartreuse2',
      '\x66\xCD\x00chartreuse3',        '\x45\x8B\x00chartreuse4',
      '\xC0\xFF\x3EOliveDrab1',         '\xB3\xEE\x3AOliveDrab2',
      '\x9A\xCD\x32OliveDrab3',         '\x69\x8B\x22OliveDrab4',
      '\xCA\xFF\x70DarkOliveGreen1',    '\xBC\xEE\x68DarkOliveGreen2',
      '\xA2\xCD\x5ADarkOliveGreen3',    '\x6E\x8B\x3DDarkOliveGreen4',
      '\xFF\xF6\x8Fkhaki1',             '\xEE\xE6\x85khaki2',
      '\xCD\xC6\x73khaki3',             '\x8B\x86\x4Ekhaki4',
      '\xFF\xEC\x8BLightGoldenrod1',    '\xEE\xDC\x82LightGoldenrod2',
      '\xCD\xBE\x70LightGoldenrod3',    '\x8B\x81\x4CLightGoldenrod4',
      '\xFF\xFF\xE0LightYellow1',       '\xEE\xEE\xD1LightYellow2',
      '\xCD\xCD\xB4LightYellow3',       '\x8B\x8B\x7ALightYellow4',
      '\xFF\xFF\x00yellow1',            '\xEE\xEE\x00yellow2',
      '\xCD\xCD\x00yellow3',            '\x8B\x8B\x00yellow4',
      '\xFF\xD7\x00gold1',              '\xEE\xC9\x00gold2',
      '\xCD\xAD\x00gold3',              '\x8B\x75\x00gold4',
      '\xFF\xC1\x25goldenrod1',         '\xEE\xB4\x22goldenrod2',
      '\xCD\x9B\x1Dgoldenrod3',         '\x8B\x69\x14goldenrod4',
      '\xFF\xB9\x0FDarkGoldenrod1',     '\xEE\xAD\x0EDarkGoldenrod2',
      '\xCD\x95\x0CDarkGoldenrod3',     '\x8B\x65\x08DarkGoldenrod4',
      '\xFF\xC1\xC1RosyBrown1',         '\xEE\xB4\xB4RosyBrown2',
      '\xCD\x9B\x9BRosyBrown3',         '\x8B\x69\x69RosyBrown4',
      '\xFF\x6A\x6AIndianRed1',         '\xEE\x63\x63IndianRed2',
      '\xCD\x55\x55IndianRed3',         '\x8B\x3A\x3AIndianRed4',
      '\xFF\x82\x47sienna1',            '\xEE\x79\x42sienna2',
      '\xCD\x68\x39sienna3',            '\x8B\x47\x26sienna4',
      '\xFF\xD3\x9Bburlywood1',         '\xEE\xC5\x91burlywood2',
      '\xCD\xAA\x7Dburlywood3',         '\x8B\x73\x55burlywood4',
      '\xFF\xE7\xBAwheat1',             '\xEE\xD8\xAEwheat2',
      '\xCD\xBA\x96wheat3',             '\x8B\x7E\x66wheat4',
      '\xFF\xA5\x4Ftan1',               '\xEE\x9A\x49tan2',
      '\xCD\x85\x3Ftan3',               '\x8B\x5A\x2Btan4',
      '\xFF\x7F\x24chocolate1',         '\xEE\x76\x21chocolate2',
      '\xCD\x66\x1Dchocolate3',         '\x8B\x45\x13chocolate4',
      '\xFF\x30\x30firebrick1',         '\xEE\x2C\x2Cfirebrick2',
      '\xCD\x26\x26firebrick3',         '\x8B\x1A\x1Afirebrick4',
      '\xFF\x40\x40brown1',             '\xEE\x3B\x3Bbrown2',
      '\xCD\x33\x33brown3',             '\x8B\x23\x23brown4',
      '\xFF\x8C\x69salmon1',            '\xEE\x82\x62salmon2',
      '\xCD\x70\x54salmon3',            '\x8B\x4C\x39salmon4',
      '\xFF\xA0\x7ALightSalmon1',       '\xEE\x95\x72LightSalmon2',
      '\xCD\x81\x62LightSalmon3',       '\x8B\x57\x42LightSalmon4',
      '\xFF\xA5\x00orange1',            '\xEE\x9A\x00orange2',
      '\xCD\x85\x00orange3',            '\x8B\x5A\x00orange4',
      '\xFF\x7F\x00DarkOrange1',        '\xEE\x76\x00DarkOrange2',
      '\xCD\x66\x00DarkOrange3',        '\x8B\x45\x00DarkOrange4',
      '\xFF\x72\x56coral1',             '\xEE\x6A\x50coral2',
      '\xCD\x5B\x45coral3',             '\x8B\x3E\x2Fcoral4',
      '\xFF\x63\x47tomato1',            '\xEE\x5C\x42tomato2',
      '\xCD\x4F\x39tomato3',            '\x8B\x36\x26tomato4',
      '\xFF\x45\x00OrangeRed1',         '\xEE\x40\x00OrangeRed2',
      '\xCD\x37\x00OrangeRed3',         '\x8B\x25\x00OrangeRed4',
      '\xFF\x00\x00red1',               '\xEE\x00\x00red2',
      '\xCD\x00\x00red3',               '\x8B\x00\x00red4',
      '\xFF\x14\x93DeepPink1',          '\xEE\x12\x89DeepPink2',
      '\xCD\x10\x76DeepPink3',          '\x8B\x0A\x50DeepPink4',
      '\xFF\x6E\xB4HotPink1',           '\xEE\x6A\xA7HotPink2',
      '\xCD\x60\x90HotPink3',           '\x8B\x3A\x62HotPink4',
      '\xFF\xB5\xC5pink1',              '\xEE\xA9\xB8pink2',
      '\xCD\x91\x9Epink3',              '\x8B\x63\x6Cpink4',
      '\xFF\xAE\xB9LightPink1',         '\xEE\xA2\xADLightPink2',
      '\xCD\x8C\x95LightPink3',         '\x8B\x5F\x65LightPink4',
      '\xFF\x82\xABPaleVioletRed1',     '\xEE\x79\x9FPaleVioletRed2',
      '\xCD\x68\x89PaleVioletRed3',     '\x8B\x47\x5DPaleVioletRed4',
      '\xFF\x34\xB3maroon1',            '\xEE\x30\xA7maroon2',
      '\xCD\x29\x90maroon3',            '\x8B\x1C\x62maroon4',
      '\xFF\x3E\x96VioletRed1',         '\xEE\x3A\x8CVioletRed2',
      '\xCD\x32\x78VioletRed3',         '\x8B\x22\x52VioletRed4',
      '\xFF\x00\xFFmagenta1',           '\xEE\x00\xEEmagenta2',
      '\xCD\x00\xCDmagenta3',           '\x8B\x00\x8Bmagenta4',
      '\xFF\x83\xFAorchid1',            '\xEE\x7A\xE9orchid2',
      '\xCD\x69\xC9orchid3',            '\x8B\x47\x89orchid4',
      '\xFF\xBB\xFFplum1',              '\xEE\xAE\xEEplum2',
      '\xCD\x96\xCDplum3',              '\x8B\x66\x8Bplum4',
      '\xE0\x66\xFFMediumOrchid1',      '\xD1\x5F\xEEMediumOrchid2',
      '\xB4\x52\xCDMediumOrchid3',      '\x7A\x37\x8BMediumOrchid4',
      '\xBF\x3E\xFFDarkOrchid1',        '\xB2\x3A\xEEDarkOrchid2',
      '\x9A\x32\xCDDarkOrchid3',        '\x68\x22\x8BDarkOrchid4',
      '\x9B\x30\xFFpurple1',            '\x91\x2C\xEEpurple2',
      '\x7D\x26\xCDpurple3',            '\x55\x1A\x8Bpurple4',
      '\xAB\x82\xFFMediumPurple1',      '\x9F\x79\xEEMediumPurple2',
      '\x89\x68\xCDMediumPurple3',      '\x5D\x47\x8BMediumPurple4',
      '\xFF\xE1\xFFthistle1',           '\xEE\xD2\xEEthistle2',
      '\xCD\xB5\xCDthistle3',           '\x8B\x7B\x8Bthistle4',
      '\x00\x00\x00gray0',              '\x03\x03\x03gray1',
      '\x05\x05\x05gray2',              '\x08\x08\x08gray3',
      '\x0A\x0A\x0Agray4',              '\x0D\x0D\x0Dgray5',
      '\x0F\x0F\x0Fgray6',              '\x12\x12\x12gray7',
      '\x14\x14\x14gray8',              '\x17\x17\x17gray9',
      '\x1A\x1A\x1Agray10',             '\x1C\x1C\x1Cgray11',
      '\x1F\x1F\x1Fgray12',             '\x21\x21\x21gray13',
      '\x24\x24\x24gray14',             '\x26\x26\x26gray15',
      '\x29\x29\x29gray16',             '\x2B\x2B\x2Bgray17',
      '\x2E\x2E\x2Egray18',             '\x30\x30\x30gray19',
      '\x33\x33\x33gray20',             '\x36\x36\x36gray21',
      '\x38\x38\x38gray22',             '\x3B\x3B\x3Bgray23',
      '\x3D\x3D\x3Dgray24',             '\x40\x40\x40gray25',
      '\x42\x42\x42gray26',             '\x45\x45\x45gray27',
      '\x47\x47\x47gray28',             '\x4A\x4A\x4Agray29',
      '\x4D\x4D\x4Dgray30',             '\x4F\x4F\x4Fgray31',
      '\x52\x52\x52gray32',             '\x54\x54\x54gray33',
      '\x57\x57\x57gray34',             '\x59\x59\x59gray35',
      '\x5C\x5C\x5Cgray36',             '\x5E\x5E\x5Egray37',
      '\x61\x61\x61gray38',             '\x63\x63\x63gray39',
      '\x66\x66\x66gray40',             '\x69\x69\x69gray41',
      '\x6B\x6B\x6Bgray42',             '\x6E\x6E\x6Egray43',
      '\x70\x70\x70gray44',             '\x73\x73\x73gray45',
      '\x75\x75\x75gray46',             '\x78\x78\x78gray47',
      '\x7A\x7A\x7Agray48',             '\x7D\x7D\x7Dgray49',
      '\x7F\x7F\x7Fgray50',             '\x82\x82\x82gray51',
      '\x85\x85\x85gray52',             '\x87\x87\x87gray53',
      '\x8A\x8A\x8Agray54',             '\x8C\x8C\x8Cgray55',
      '\x8F\x8F\x8Fgray56',             '\x91\x91\x91gray57',
      '\x94\x94\x94gray58',             '\x96\x96\x96gray59',
      '\x99\x99\x99gray60',             '\x9C\x9C\x9Cgray61',
      '\x9E\x9E\x9Egray62',             '\xA1\xA1\xA1gray63',
      '\xA3\xA3\xA3gray64',             '\xA6\xA6\xA6gray65',
      '\xA8\xA8\xA8gray66',             '\xAB\xAB\xABgray67',
      '\xAD\xAD\xADgray68',             '\xB0\xB0\xB0gray69',
      '\xB3\xB3\xB3gray70',             '\xB5\xB5\xB5gray71',
      '\xB8\xB8\xB8gray72',             '\xBA\xBA\xBAgray73',
      '\xBD\xBD\xBDgray74',             '\xBF\xBF\xBFgray75',
      '\xC2\xC2\xC2gray76',             '\xC4\xC4\xC4gray77',
      '\xC7\xC7\xC7gray78',             '\xC9\xC9\xC9gray79',
      '\xCC\xCC\xCCgray80',             '\xCF\xCF\xCFgray81',
      '\xD1\xD1\xD1gray82',             '\xD4\xD4\xD4gray83',
      '\xD6\xD6\xD6gray84',             '\xD9\xD9\xD9gray85',
      '\xDB\xDB\xDBgray86',             '\xDE\xDE\xDEgray87',
      '\xE0\xE0\xE0gray88',             '\xE3\xE3\xE3gray89',
      '\xE5\xE5\xE5gray90',             '\xE8\xE8\xE8gray91',
      '\xEB\xEB\xEBgray92',             '\xED\xED\xEDgray93',
      '\xF0\xF0\xF0gray94',             '\xF2\xF2\xF2gray95',
      '\xF5\xF5\xF5gray96',             '\xF7\xF7\xF7gray97',
      '\xFA\xFA\xFAgray98',             '\xFC\xFC\xFCgray99',
      '\xFF\xFF\xFFgray100',            '\xA9\xA9\xA9DarkGray',
      '\x00\x00\x8BDarkBlue',           '\x00\x8B\x8BDarkCyan',
      '\x8B\x00\x8BDarkMagenta',        '\x8B\x00\x00DarkRed',
      '\x90\xEE\x90LightGreen' ]
</programlisting>
    </section> <!--PickList-defaultColors-->
    <section id='PickList-lookupName'>
      <title><code >PickList.lookupName()</code >: Convert a color name
      to a color value</title>
      <para>
        This method uses the internal <code >self.__colorMap</code >
        dictionary to lookup color names and, if found, return the
        corresponding color value as a <code >Color</code > instance.
      </para>
      <programlisting role='outFile:huey'
># - - -   P i c k L i s t . l o o k u p N a m e

    def lookupName ( self, colorName ):
        """Look up a color name.
        """
        #-- 1 --
        # [ colorKey  :=  colorName, uppercased ]
        colorKey  =  colorName.upper()

        #-- 2 --
        # [ if  colorKey is a key in self.__colorMap ->
        #     return the corresponding value
        #   else ->
        #     return None ]
        try:
            index, color, name  =  self.__colorMap[colorKey]
            return  color
        except KeyError:
            return None
</programlisting>
    </section> <!--PickList-lookupName-->
    <section id='PickList-init'>
      <title><code >PickList.__init__()</code >: Constructor</title>
      <para>
        First the constructor calls the parent constructor to make it
        into a <code >Frame</code >.  Then it creates and grids the
        <code >PickList</code > widget.
      </para>
      <programlisting role='outFile:huey'
># - - -   P i c k L i s t . _ _ i n i t _ _

    def __init__ ( self, parent, callback=None ):
        """Constructor for the color name PickList."""
        #-- 1 --
        # [ parent  :=  parent with a new Frame added
        #   self    :=  that Frame ]
        Frame.__init__ ( self, parent )
</programlisting>
      <para>
        Next we initialize the internal data structures.
      </para>
      <programlisting role='outFile:huey'
>        #-- 2 --
        self.__colorList  =  []
        self.__nameList   =  []
        self.__colorMap   =  {}
        self.__callback   =  None
</programlisting>
      <para>
        Now create and grid the sole widget.  See <xref
        linkend='imports' /> for documentation on the <code
        >ScrolledList</code > widget.
      </para>
      <programlisting role='outFile:huey'
>        #-- 3 --
        # [ self  :=  self with a new ScrolledList widget added
        #             and gridded
        #   self.__scrolledList  :=  that widget ]
        self.__scrolledList  =  ScrolledList ( self,
            width=self.NAME_WIDTH, height=self.NAME_LINES,
            callback=self.__pickHandler )
        self.__scrolledList.listbox["font"]  =  BUTTON_FONT
        self.__scrolledList.grid ( row=0, column=0 )
</programlisting>
      <para>
        The logic that populates the widget with color names, and sets
        up the corresponding internal tables, is <xref
        linkend='PickList-addColors' />.
      </para>
      <programlisting role='outFile:huey'
>        #-- 4 --
        # [ self.__scrolledList  :=  self.__scrolledList populated
        #       with non-redundant color names from the standard
        #       file if found, defaulting to an internal color list
        #   self.__colorMap   +:=  as invariant
        #   self.__colorList  +:=  as invariant
        #   self.__nameList   +:=  as invariant ]
        self.__addColors()
</programlisting>
      <para>
        Now that everything is set up, enable the callback mechanism.
      </para>
      <programlisting role='outFile:huey'
>        #-- 5 --
        self.__callback   =  callback
</programlisting>
    </section> <!--PickList-init-->
    <section id='PickList-addColors'>
      <title><code >PickList.__addColors()</code >: Populate the color
      list</title>
      <para>
        The purpose of this method is to place a standard set of color
        names into <code >self.__scrolledList</code > and the internal
        data structures <code >self.__colorMap</code >, <code
        >self.__colorMap</code > and <code >self.__colorList</code >,
        that describe that same set of colors.
      </para>
      <programlisting role='outFile:huey'
># - - -   P i c k L i s t . _ _ a d d C o l o r s

    def __addColors ( self ):
        """Populate the color set

          [ if a readable, valid self.COLOR_NAMES_FILE exists in
            one of the directories named in self.PATHS_LIST ->
              self.__scrolledList   :=  self.pickList with the color
                  names added from that file in the same order
              self.__colorMap   :=  as invariant from that file
              self.__colorList  :=  as invariant from that file
              self.__nameList   :=  as invariant from that file
            else ->
              self.__scrolledList   :=  self.pickList with the color
                  names added from the internal default list
              self.__colorMap   :=  as invariant from that list
              self.__colorList  :=  as invariant from that list
              self.__nameList   :=  as invariant from that file ]
        """
</programlisting>
      <para>
        We will try to find the file first; see <xref
        linkend='PickList-findColorsFile' />.
      </para>
      <programlisting role='outFile:huey'
>        #-- 1 --
        # [ if self.COLOR_NAMES_FILE names a readable, valid
        #   rgb.txt file in one of the directories named in
        #   self.PATHS_LIST ->
        #       self.__colorMap  :=  as invariant from that file
        #   else ->
        #       self.__colorMap  :=  an empty dictionary ]
        self.__findColorsFile()
</programlisting>
      <para>
        If that didn't work, set up <code >self.__colorList</code
        > from <code >self.DEFAULT_COLORS</code >; see <xref
        linkend='PickList-useDefaultColors' />.
      </para>
      <programlisting role='outFile:huey'
>        #-- 2 --
        # [ if self.__colorMap is empty ->
        #     self.__colorMap  +:=  as invariant from self.DEFAULT_COLORS
        #   else -> I ]
        if  len(self.__colorMap) == 0:
            self.__useDefaultColors()
</programlisting>
      <para>
        Because of historical differences in language and naming
        conventions, the color files usually have a large number
        of redundant colors.  For example, in our local
        &rgb-txt;, colors &#x201c;LightSlateGray&#x201d;,
        &#x201c;LightSlateGrey&#x201d;, &#x201c;light slate
        gray&#x201d;, and &#x201c;light slate grey&#x201d; all
        refer to exactly the same color.  So, to make it easier
        for the user to scroll through the list, we'll remove
        redundant colors from <code >self.__colorMap</code >; see
        <xref linkend='PickList-cleanColorMap' />.
      </para>
      <programlisting role='outFile:huey'
>        #-- 3 --
        # [ self.__colorMap  :=  self.__colorMap with redundant colors
        #       removed ]
        self.__cleanColorMap()
</programlisting>
      <para>
        Now that the color set is finalized, populate the <code
        >ScrolledList</code > widget and build the <code
        >self.__nameList</code > and <code >self.__colorList</code >
        lists.  Because the values in <code >self.__colorMap</code > are
        tuples starting the original color index, sorting these values
        will put them back in their original order.
      </para>
      <programlisting role='outFile:huey'
>        #-- 4 --
        # [ valueList  :=  values from self.__colorMap, sorted ]
        valueList  =  self.__colorMap.values()
        valueList.sort()

        #-- 5 --
        # [ valueList is a list of tuples (index, color, name) ->
        #     self.__scrolledList  +:=  names from valueList in the
        #                               same order
        #     self.__colorList  +:=  colors from valueList in the
        #                           same order ]
        for index, color, name in valueList:
            self.__scrolledList.append ( name )
            self.__nameList.append ( name )
            self.__colorList.append ( color )
</programlisting>
    </section> <!--PickList-addColors-->
    <section id='PickList-findColorsFile'>
      <title><code >PickList.__findColorsFile()</code >:  Read the
      &rgb-txt; file</title>
      <para>
        This method searches various directories in hopes that there is
        a standard colors file there.  If so, it sets up <code
        >self.__colorList</code > with the color values and <code
        >self.__nameList</code > with the corresponding names.
      </para>
      <programlisting role='outFile:huey'
># - - -   P i c k L i s t . _ _ f i n d C o l o r s F i l e

    def __findColorsFile ( self ):
        """Try to find and read the standard colors file.

          [ if self.COLOR_NAMES_FILE names a readable, valid
            rgb.txt file in one of the directories named in
            self.PATHS_LIST ->
                self.__colorMap  :=  as invariant from that file
            else ->
                self.__colorMap  :=  an empty dictionary ]
        """

        #-- 1 --
        inFile  =  None

        #-- 2 --
        # [ if self.PATHS_LIST names a directory that contains
        #   a readable file named self._COLOR_NAMES_FILE ->
        #     inFile  :=  that file opened for reading
        #   else -> I ]
        for  dir in self.PATHS_LIST:
            fileName  =  os.path.join ( dir, self.COLOR_NAMES_FILE )
            try:
                inFile  =  open ( fileName )
                break
            except IOError:
                pass
</programlisting>
      <para>
        If we couldn't find the file, we set <code
        >self.__colorMap</code > to an empty list to signal failure,
        and return to the caller.
      </para>
      <programlisting role='outFile:huey'
>        #-- 2 --
        if inFile is None:
            self.__colorMap  = {}
            return
</programlisting>
      <para>
        We're not guaranteed success yet&#x2014;the file might not be in
        the correct format.  See <xref linkend='PickList-readColorsFile'
        /> for details of the file format and the process of reading it.
      </para>
      <programlisting role='outFile:huey'
>        #-- 3 --
        # [ if inFile is a valid colors file ->
        #     self.__colorMap  :=  as invariant from that file
        #   else ->
        #     self.__colorMap  :=  an empty dictionary ]
        self.__readColorsFile ( inFile )
</programlisting>
    </section> <!--PickList-findColorsFile-->
    <section id='PickList-readColorsFile'>
      <title><code >PickList.__readColorsFile()</code >: Process the
      &rgb-txt; file</title>
      <para>
        The purpose of this method is to attempt to read the <filename
        >rgb.txt</filename > file and use it to set up the list of
        standard colors.
      </para>
      <programlisting role='outFile:huey'
># - - -   P i c k L i s t . _ _ r e a d C o l o r s F i l e

    def __readColorsFile ( self, inFile ):
        """Try to read the file of standard colors.

          [ inFile is a readable file ->
              if inFile is a valid colors file ->
                self.__colorMap  :=  as invariant from that file
              else ->
                self.__colorMap  :=  an dictionary ]
        """
</programlisting>
      <para>
        The format of the colors file is fairly simple:
      </para>
      <itemizedlist>
        <listitem>
          <para>
            Ignore comment lines beginning with a bang (!).
          </para>
        </listitem>
        <listitem>
          <para>
            Here is a typical line defining a color:
            <programlisting
>255 239 213		papaya whip
</programlisting>
            The first three fields are numbers in [0,255] describing the
            red, green, and blue color values, respectively.  The rest
            of the line contains the color name.
          </para>
        </listitem>
      </itemizedlist>
      <para>
        The color files aren't huge, so we use <code >.readlines()</code
        > to return the contents of the file as a list of strings.  This
        makes it easier to number the lines in the next step.
      </para>
      <programlisting role='outFile:huey'
>        #-- 1 --
        # [ lineList  :=  the lines from inFile as a list of strings ]
        lineList  =  inFile.readlines()
</programlisting>
      <para>
        Later steps need to know the original order of the
        colors, so we use the index in <code >lineList</code > as
        the induction variable of the loop to process the lines.
        If even so much as one line is not valid, we clear <code
        >self.__colorMap</code > and return.  See <xref
        linkend='PickList-readColorLine' />.
      </para>
      <programlisting role='outFile:huey'
>        #-- 2 --
        # [ if all the non-comment lines in inFile are valid ->
        #     self.__colorMap  :=  as invariant from those lines
        #   else ->
        #     self.__colorMap  :=  an empty list ]
        try:
            for  index in range ( len ( lineList ) ):
                #-- 2 body --
                # [ if lineList[index] is a comment -> I
                #   else if lineList[index] is a valid color line ->
                #     self.__colorMap  +:=  an entry mapping the
                #         uppercased color name |-> a tuple (index,
                #         the color from that line as a Color instance),
                #         the name from that line)
                #   else ->
                #     self.__colorMap  :=  {}
                #     return ]
                self.__readColorLine ( index, lineList[index] )
        except IOError:
            self.__colorMap  =  {}
</programlisting>
    </section> <!--PickList-readColorsFile-->
    <section id='PickList-readColorLine'>
      <title><code >PickList.__readColorLine()</code >:  Process one
      line from &rgb-txt;</title>
      <para>
        This method reads and processes one line from the <filename
        >rgb.txt</filename > file.  For a description of the format, see
        <xref linkend='PickList-readColorsFile' />.
      </para>
      <programlisting role='outFile:huey'
># - - -   P i c k L i s t . _ _ r e a d C o l o r L i n e

    def __readColorLine ( self, index, rawLine ):
        """Process one line from the rgb.txt file.

          [ (index is a nonnegative integer) and
            (rawLine is a string) ->
              if rawLine is a comment -> I
              else if rawLine is valid ->
                  self.__colorMap  +:=  an entry mapping the
                      uppercased color name |-> a tuple (index,
                      the color from that line as a Color instance),
                      the name from that line)
              else -> raise IOError ]
        """
</programlisting>
      <para>
        First we remove any trailing whitespace.  This will take care of
        any newline character.
      </para>
      <programlisting role='outFile:huey'
>        #-- 1 --
        # [ line  :=  rawLine with trailing whitespace removed ]
        line  =  rawLine.rstrip()
</programlisting>
      <para>
        If the line is a comment, return.
      </para>
      <programlisting role='outFile:huey'
>        #-- 2 --
        if  line.startswith('!'):
             return
</programlisting>
      <para>
        Python's string <code >.split()</code > method does a nice job
        of breaking the line into fields.  In this call, there are two
        arguments.  The first argument is the delimiter; passing <code
        >None</code > to this argument has the effect of using any
        contiguous string of whitespace characters as a delimiter.  The
        second argument specifies an upper limit to the number of fields
        removed from the front of the string.  By passing a value of 3
        to this argument, we never get more than four fields; this will
        avoid splitting up color names that contain embedded spaces.
      </para>
      <programlisting role='outFile:huey'
>        #-- 3 --
        # [ fieldList  :=  line split on whitespace regions, but never
        #                  more than the first four ]
        fieldList  =  line.split ( None, 3 )
</programlisting>
      <para>
        Now for some validity tests.  There should be exactly
        four fields.  Also, the first three must contain strings
        that can be converted to integers.  For conversion of the
        integer values, see <xref linkend='PickList-convert8' />.
      </para>
      <programlisting role='outFile:huey'
>        #-- 4 --
        if len(fieldList) &lt; 4:
            raise IOError, "rgb.txt lines must have four fields"
        else:
            colorName  =  fieldList[-1]

        #-- 5 --
        # [ if the first three elements of fieldList can be
        #   converted to integers in [0,255] ->
        #       red8    :=  int(fieldList[0])
        #       green8  :=  int(fieldList[1])
        #       blue8   :=  int(fieldList[2])
        #   else -> raise IOError ]
        red8    =  self.__convert8 ( fieldList[0] )
        green8  =  self.__convert8 ( fieldList[1] )
        blue8   =  self.__convert8 ( fieldList[2] )
</programlisting>
      <para>
        The <code >Color</code > class constructor expects values in the
        range [0,MAX_PARAM].  One possibility is just to multiply the
        values by 256, and that's probably sufficiently accurate.  A
        rejected possibility was to duplicate the value, e.g., <code
        >0x80</code > becomes <code >0x8080</code >, and <code
        >0xff</code > becomes <code >0xffff</code >.  If anyone ever
        convinces me that it makes a difference, I'll change it.
      </para>
      <programlisting role='outFile:huey'
>        #-- 6 --
        # [ color  :=  a new Color instance made from red8, green8,
        #              and blue8, each shifted left 8 bits ]
        color  =  Color ( red8&lt;&lt;8, green8&lt;&lt;8,
                          blue8&lt;&lt;8 )
</programlisting>
      <para>
        Finally, add a new entry to <code >self.__colorMap</code > for
        this color.
      </para>
      <programlisting role='outFile:huey'
>        #-- 7 --
        colorKey  =  colorName.upper()
        colorTuple  =  (index, color, colorName)
        self.__colorMap [ colorKey ]  =  colorTuple
</programlisting>
    </section> <!--PickList-readColorLine-->
    <section id='PickList-convert8'>
      <title><code >PickList.__convert8()</code >: Convert an 8-bit
      number</title>
      <para>
        This method is used to process one of the 8-bit numbers
        from the &rgb-txt; file.
      </para>
      <programlisting role='outFile:huey'
># - - -   P i c k L i s t . _ _ c o n v e r t 8

    def __convert8 ( self, s ):
        """Convert a string to an 8-bit number.

          [ if s is the string representation of a number in
            [0,255] ->
              return s as an integer
            else -> raise IOError ]
        """
        #-- 1 --
        # [ if s can be converted to an integer ->
        #     result  :=  s converted to an integer
        #   else -> raise IOError ]
        try:
            result  =  int ( s )
        except ValueError:
            raise IOError, "Bad color value: '%s'" % s

        #-- 2 --
        return result
</programlisting>
    </section> <!--PickList-convert8-->
    <section id='PickList-useDefaultColors'>
      <title><code >PickList.__useDefaultColors()</code >: Set up a
      default color list</title>
      <para>
        This method is called when the &rgb-txt; file is missing
        or invalid.  It sets up the class's color list from the
        default color set in <xref
        linkend='PickList-defaultColors' />.
      </para>
      <programlisting role='outFile:huey'
># - - -   P i c k L i s t . _ _ u s e D e f a u l t C o l o r s

    def __useDefaultColors ( self ):
        """Set up self's colors from the default list.

          [ self.__colorMap  +:=  as invariant from
                                  self.DEFAULT_COLORS ]
        """
</programlisting>
      <para>
        This method sets up the color pick list from our internal,
        default list of colors.
      </para>
      <para>
        The <code >self.DEFAULT_COLORS</code > list is a list of
        strings with the same values as lines from <filename
        >rgb.txt</filename >: 8-bit values for red, green, and blue,
        followed by the color name.
      </para>
      <para>
        As we set up <code >self.__colorMap</code >,
        the 8-bit values are shifted left
        eight bits to scale them to the [0,<code >MAX_PARAM</code >]
        range expected by the <code >Color()</code > constructor.
      </para>
      <programlisting role='outFile:huey'
>        #-- 1 --
        for  index in range ( len ( self.DEFAULT_COLORS ) ):
            # [ self.DEFAULT_COLORS[index] is a string with the
            #   8-bit red, green, and blue values followed by the
            #   color name ->
            #     self.__colorMap  +:=  an entry mapping the uppercased
            #         color name |-> a tuple (index, a Color instance
            #         made from those color values, the color name) ]
            #-- 1.1 --
            colorString  =  self.DEFAULT_COLORS [ index ]
            red    =  ord ( colorString[0] ) &lt;&lt;8
            green  =  ord ( colorString[1] ) &lt;&lt;8
            blue   =  ord ( colorString[2] ) &lt;&lt;8
            color  =  Color ( red, green, blue )
            colorName  =  colorString[3:]

            #-- 1.2 --
            colorKey  =  colorName.upper()
            colorTuple  =  (index, color, colorName)

            #-- 1.3 --
            self.__colorMap [ colorKey ]  =  colorTuple
</programlisting>
    </section> <!--PickList-useDefaultColors-->
    <section id='PickList-cleanColorMap'>
      <title><code >PickList.__cleanColorMap()</code >: Remove redundant
      colors</title>
      <para>
        This method removes duplicate color names.  There are two
        mechanisms that create these:
      </para>
      <itemizedlist>
        <listitem>
          <para>
            Spacing conventions: &#x201c;light slate gray&#x201d; is the
            same color as &#x201c;LightSlateGray&#x201d;.
           </para>
         </listitem>
         <listitem>
           <para>
             Spelling conventions: &#x201c;LightSlateGray&#x201d; is the
             same color as &#x201c;LightSlateGrey&#x201d;.
           </para>
         </listitem>
       </itemizedlist>
      <para>
        Because of these variations, there may be as many as four names
        for the same color: &#x201c;LightSlateGray&#x201d; is also the
        same color as &#x201c;light slate grey&#x201d;.
      </para>
      <para>
        We'll arbitrary prefer the intercapitalized forms such as
        &#x201c;LightSlateGray&#x201d; to the versions with embedded
        spaces such as &#x201c;light slate gray&#x201d;.  Equally
        arbitrarily, because the author happened to be born in the USA,
        we'll cast out the &#x201c;grey&#x201d; variants.
      </para>
      <programlisting role='outFile:huey'
># - - -   P i c k L i s t . _ _ c l e a n C o l o r M a p

    def __cleanColorMap ( self ):
        """Remove redundant colors from the color map.

          [ self.__colorMap  :=  self.__colorMap with redundant colors
                removed ]
        """

        #-- 1 --
        # [ nameList  :=  keys of self.__colorMap ]
        nameList  =  self.__colorMap.keys()
</programlisting>
      <para>
        The logic for removing less preferred names is in
        <xref linkend='PickList-nameCleaner' />.
      </para>
      <programlisting role='outFile:huey'
>        #-- 2 --
        # [ self.__colorMap  :=  self.__colorMap with names removed
        #      when those names are less preferred alternatives to
        #      names in nameList ]
        for  colorName in nameList:
            self.__nameCleaner ( colorName )
</programlisting>
    </section> <!--PickList-cleanColorMap-->
    <section id='PickList-nameCleaner'>
      <title><code >PickList.__nameCleaner()</code >:  Remove redundant
      color names</title>
      <para>
        This method takes as an argument one of the keys to <code
        >self.__colorMap</code >, an uppercased color name.  Its purpose
        is to find any other entries in <code >self.__colorMap</code >
        whose keys are less preferred names, and remove them.
      </para>
      <programlisting role='outFile:huey'
># - - -   P i c k L i s t . _ _ n a m e C l e a n e r

    def  __nameCleaner ( self, colorName ):
        """Remove any redundant names dominated by colorName.

          [ colorName is a key in self.__colorMap ->
              self.__colorMap  :=  self.__colorMap with any entries
                  removed whose keys are less-preferred alternatives
                  to colorName ]
        """
</programlisting>
      <para>
        For general remarks on the preference rules for color names, see
        <xref linkend='PickList-cleanColorMap' />.
      </para>
      <para>
        Because intercapitalized color names such as <code
        >"DodgerBlue"</code > are stored under their uppercase
        equivalents such as <code >"DODGERBLUE"</code >, we need
        to pull out the original color name, or we won't know
        where spaces are to be inserted.  If this lookup fails, that's
        okay, because the name may already have been purged.  For
        example, the name &#x201c;DarkGray&#x201d; might purge
        &#x201c;dark grey&#x201d;, and then later when &#x201c;dark
        gray&#x201d; comes along it will also try to purge &#x201c;dark
        grey&#x201d;, but find that it's already gone.
      </para>
      <programlisting role='outFile:huey'
>        #-- 1 --
        # [ if colorName is a key in self.__colorMap ->
        #     origName  :=  corresponding value's color
        #   else -> return ]
        try:
            origName  =  self.__colorMap [ colorName.upper() ] [ 2 ]
        except KeyError:
            return
</programlisting>
      <para>
        There are two transformations on <code >colorName</code > to
        convert to the key for a less-preferred version.  The <code
        >.__lowerize()</code > method converts intercapitalized names
        like &#x201c;DodgerBlue&#x201d; to their
        space-embedded equivalents such as <code >"dodger blue"</code >.
        The <code >.__anglicize()</code > method converts the American
        spelling &#x201c;gray&#x201d; to the English spelling
        &#x201c;grey&#x201d;.  However, we most also purge the list of
        names to which <emphasis >both</emphasis > transformations are
        applied.
      </para>
      <programlisting role='outFile:huey'
>        #-- 2 --
        # [ anglican  :=  colorName with 'gray' -> 'grey' ]
        anglican  =  self.__anglicize ( origName )

        #-- 2 --
        # [ if self.__colorMap has a key (anglican) ->
        #     self.__colorMap  :=  self.__colorMap with that key's
        #                          entry removed
        #   else -> I ]
        self.__purgeName ( origName, anglican )

        #-- 3 --
        # [ if self.__colorMap has a key self.__lowerize(colorName) ->
        #     self.__colorMap  :=  self.__colorMap with that key's
        #                          entry removed
        #   else -> I ]
        self.__purgeName ( origName, self.__lowerize ( origName ) )

        #-- 4 --
        # [ if self.__colorMap has a key self.__lowerize(anglican) ->
        #     self.__colorMap  :=  self.__colorMap with that key's
        #                          entry removed
        #   else -> I ]
        self.__purgeName ( origName, self.__lowerize ( anglican ) )
</programlisting>
    </section> <!--PickList-nameCleaner-->
    <section id='PickList-anglicize'>
      <title><code >PickList.__anglicize()</code >: Convert
      &#x201c;gray&#x201d; to &#x201c;grey&#x201d;</title>
      <para>
        This method returns its string argument with the first
        occurrence of <code >"gray"</code > replaced by <code
        >"grey"</code >, if there is one.
      </para>
      <programlisting role='outFile:huey'
># - - -   P i c k L i s t . _ _ a n g l i c i z e

    def  __anglicize ( self, name ):
        """Anglicize a name.

          [ name is a string ->
              if name contains "gray" ->
                  return name with the first occurrence of "gray"
                  replaced by "grey"
              else -> return name ]
        """
        where  =  name.find ( "gray" )

        if  where &gt;= 0:
            return name[:where] + "grey" + name[where+4:]
        else:
            where  =  name.find ( "Gray" )
            if  where &gt;= 0:
                return name[:where] + "Grey" + name[where+4:]
            else:
                return name
</programlisting>
    </section> <!--PickList-anglicize-->
    <section id='PickList-lowerize'>
      <title><code >PickList.__lowerize()</code >: Convert
      intercapitalized names to lowercase</title>
      <para>
        This method converts an intercapitalized name such as
        &#x201c;DarkSlateGray&#x201d; to its lowercased, space-embedded
        form such as &#x201c;dark slate gray&#x201d;.
      </para>
      <programlisting role='outFile:huey'
># - - -   P i c k L i s t . _ _ l o w e r i z e

    def __lowerize ( self, name ):
        """Convert an intercapitalized color name to lowercase form.

          [ name is a string ->
              return name with all letters lowercased, and single
              spaces inserted at each lowercase->uppercase transition ]
        """
</programlisting>
      <para>
        First we break the name up into a list of individual characters.
        Then we work through the gaps between characters, adding a
        space after that gap if there was a lowercase-uppercase
        transition there.  Finally, the <code >str.join()</code > method
        reassembles the pieces into a string.
      </para>
      <programlisting role='outFile:huey'
>        #-- 1 --
        # [ letters  :=  a list of the characters in name ]
        letters  =  list ( name )

        #-- 2 --
        # [ letters  :=  letters with a space prefixed to any
        #       uppercase letter preceded by a lowercase letter ]
        for  i in range ( 1, len ( letters ) ):
            if  ( letters[i-1].islower() and
                  letters[i].isupper() ):
                letters[i]  =  " " + letters[i]

        #-- 3 --
        return  "".join ( letters )
</programlisting>
    </section> <!--PickList-lowerize-->
    <section id='PickList-purgeName'>
      <title><code >PickList.__purgeName()</code >: Remove one redundant
      color name</title>
      <para>
        This method takes two arguments.  The <code >goodName</code >
        argument is a color name; <code >badName</code > is a
        less-preferred alternative.  
      </para>
      <programlisting role='outFile:huey'
># - - -   P i c k L i s t . _ _ p u r g e N a m e

    def __purgeName ( self, goodName, badName ):
        """If badName is redundant for goodName, remove it.

          [ (goodName is a color name in self.__colorMap) and
            (badName is a string) ->
              if  goodName == badName ->
                I
                else if  badName is a color name in self.__colorMap
                that is the same color as self.__colorMap[goodName] ->
                self.__colorMap  :=  self.__colorName with its
                    entry for badName removed
              else -> I ]              
        """
</programlisting>
      <para>
        First we test to see if <code >goodName</code > and <code
        >badName</code > are the same.  For example, the <code
        >.__lowerize()</code > transformation on <code >"red"</code >
        yields <code >"red"</code >.  In this case we return.
      </para>
      <programlisting role='outFile:huey'
>        #-- 1 --
        if  goodName == badName:
            return
</programlisting>
      <para>
        Next, test to see if <code >badName</code > is even in the color
        map.  If not, return.  If it is there, retrieve the colors for
        both names.
      </para>
      <programlisting role='outFile:huey'
>        #-- 2 --
        # [ if  badName is a key in self.__colorMap ->
        #     goodColor  :=  self's color for goodName
        #     badColor   :=  self's color for badName
        #   else ->
        #     return ]
        badColor  =  self.lookupName ( badName )
        if  badColor is None:
            return
        else:
            goodColor  =  self.lookupName ( goodName )
</programlisting>
      <para>
        If the colors are the same, we can safely remove the
        less preferred one.  If they don't match, just for safety,
        retain the bad color.
      </para>
      <programlisting role='outFile:huey'
>        #-- 3 --
        # [ if  badColor == goodColor ->
        #     self.__colorMap  :=  self.__colorMap with the entry
        #         for badColor removed
        #   else -> I ]
        if  badColor == goodColor:
            badKey  =  badName.upper()
            del  self.__colorMap [ badKey ]
</programlisting>
    </section> <!--PickList-purgeName-->
    <section id='PickList-pickHandler'>
      <title><code >PickList.__pickHandler()</code >: Someone clicked on
      a color name</title>
      <para>
        This method is called when the user clicks on a color name.  The
        argument is the line number within the pick list.  It passes the
        corresponding color through to the class's callback function.
      </para>
      <programlisting role='outFile:huey'
># - - -   P i c k L i s t . _ _ p i c k H a n d l e r   - -

    def __pickHandler ( self, linex ):
        """Handler for user click on a color name.
        """
        #-- 1 --
        color  =  self.__colorList[linex]

        #-- 2 --
        if  self.__callback is not None:
            self.__callback ( color )
</programlisting>
    </section> <!--PickList-pickHandler-->
  </section> <!--class-PickList-->
  <section id='class-Adjuster'>
    <title><code >class Adjuster</code >: Color adjustment and
    color model selection</title>
    <para>
      This class includes all the widgets down the center third of the
      application:
    </para>
    <itemizedlist>
      <listitem>
        <para>
          A <code >ColorReadout</code > widget shows the text and
          background colors in <code >#RRGGBB</code > form, and has a
          pair of radiobuttons for selecting whether the rest of this
          widget displays the text (foreground) color or the background
          color.
        </para>
      </listitem>
      <listitem>
        <para>
          A <code >ModelSelector</code > widget has radiobuttons that
          let the user select HSV, RGB, or CMY color space.
        </para>
      </listitem>
      <listitem>
        <para>
          A <code >ColorSliders</code > widget has three Tkinter <code
          >Scale</code > widgets the user can slide in order to make
          fine adjustments to a color.
        </para>
      </listitem>
    </itemizedlist>
    <para>
      This <code >Adjuster</code > widget class, then, is really the
      central &#x201c;brain&#x201d; of the application.  Inside this
      object are stored the current background and text colors.
    </para>
    <para>
      Here is the class interface.  As with all compound widgets, it
      inherits its widgetness from the <code >Frame</code > class.
    </para>
    <programlisting role='outFile:huey'>
# - - - - -   c l a s s   A d j u s t e r

class Adjuster(Frame):
    """Widgets to store and adjust the current colors.

      Exports:
        Adjuster ( parent, callback=None ):
          [ (parent is a Frame) and
            (callback is a function or None) ->
              parent  :=  parent with a new Adjuster widget added
                  but not gridded, which will call callback
                  whenever the displayed color is changed, where
                  the calling sequence is
                    callback(isText, newColor)
                  and isText is True to set the text color, False
                  to set the background color, and newColor is a
                  the new color as a Color instance
              return that new Adjuster widget ]
</programlisting>
      <para>
        The next two methods return the current colors; see
        <xref linkend='Adjuster-textColor' /> and
        <xref linkend='Adjuster-bgColor' />.
      </para>
      <programlisting role='outFile:huey'
>        .textColor():
           [ return the current text color as a Color instance ]
        .bgColor():
           [ return the current background color as a Color instance ]
</programlisting>
      <para>
        The <code >.set()</code > method changes the currently
        displayed color; see <xref linkend='Adjuster-set' />.
      </para>
      <programlisting role='outFile:huey'
>        .set ( color ):
          [ color is a Color instance ->
              if self is displaying the text color ->
                 self's text color  :=  color
              else ->
                 self's background color  :=  color ]
</programlisting>
      <para>
        The <code >.isText()</code > method tests whether the
        sliders are displaying the text color or the background
        color; see <xref linkend='Adjuster-isText' />.
      </para>
      <programlisting role='outFile:huey'
>        .isText():
           [ if self is displaying the text color ->
               return True
             else -> return False ]
</programlisting>
      <para>
        One might ask, instead of making <code >isText()</code > a
        method call, why not make it simply an exported read-only <code
        >Boolean</code >?  It is made a method strictly for the
        convenience of the implementer: this way, the actual value can
        be kept in the same Tkinter <code >IntVar</code > control
        variable that is linked to the two radiobuttons that control
        that switch.
      </para>
      <para>
        Here is the layout of the widgets inside.
      </para>
      <programlisting role='outFile:huey'
>      Contained widgets:
         .__colorReadout:
           [ a ColorReadout widget that displays self's text and
             background colors, and a pair of radiobuttons to
             switch between them ]
         .__modelSelector:
           [ a ModelSelector widget that lets the user select
             which color model to display ]
         .__colorSliders:
           [ a ColorSliders widget that lets the user adjust
             the color model's parameters ]

       Grid plan:  Vertically stacked in a single column.
    """
</programlisting>
    <section id='Adjuster-constants'>
      <title>Manifest constants</title>
      <para>
        Two class variables hold the initial colors, black text
        and red background.
      </para>
      <programlisting role='outFile:huey'
>    DEFAULT_TEXT_COLOR  =  Color ( 0, 0, 0 )
    DEFAULT_BG_COLOR  =  Color ( MAX_PARAM, 0, 0 )
</programlisting>
    </section> <!--Adjuster-constants-->
    <section id='Adjuster-set'>
      <title><code >Adjuster.set()</code >: Change the color</title>
      <para>
        This method changes the color to the given value.  The
        actual color is stored inside the <code
        >ColorReadout</code > widget.  See <xref
        linkend='ColorReadout-set' />.
      </para>
      <programlisting role='outFile:huey'
># - - -   A d j u s t e r . s e t

    def set ( self, color ):
        """Change the currently displayed color.
        """
        #-- 1 --
        # [ self.__colorReadout  :=  self.__colorReadout
        #       displaying color ]
        self.__colorReadout.set ( color )
</programlisting>
      <para>
        Also change the slider positions to reflect the new
        color; see <xref linkend='ColorSliders-setColor' />.
      </para>
      <programlisting role='outFile:huey'
>        #-- 2 --
        # [ self.__colorSliders  :=  self.__colorSliders
        #       displaying color ]
        self.__colorSliders.setColor ( color )
</programlisting>
    </section> <!--Adjuster-set-->
    <section id='Adjuster-textColor'>
      <title><code >Adjuster.textColor()</code >: Return the
      current text color</title>
      <para>
        This is a pass-through method that extracts the current
        text color from the internal <code >ColorReadout</code >
        widget.  See <xref linkend='ColorReadout-textColor' />.
      </para>
      <programlisting role='outFile:huey'
># - - -   A d j u s t e r . t e x t C o l o r

    def textColor ( self ):
        """Return self's current text color.
        """
        return  self.__colorReadout.textColor()
</programlisting>
    </section> <!--Adjuster-textColor-->
    <section id='Adjuster-bgColor'>
      <title><code >Adjuster.bgColor()</code >: Return the
      current text color</title>
      <para>
        This is another pass-through method.  The reference
        source for the current background color is in the <code
        >ColorReadout</code > widget.  See <xref
        linkend='ColorReadout-bgColor' />.
      </para>
      <programlisting role='outFile:huey'
># - - -   A d j u s t e r . b g C o l o r

    def bgColor ( self ):
        """Return self's current background color.
        """
        return  self.__colorReadout.bgColor()
</programlisting>
    </section> <!--Adjuster-bgColor-->
    <section id='Adjuster-isText'>
      <title><code >Adjuster.isText()</code >: Which color is being
      displayed?</title>
      <para>
        This method is a predicate that returns <code >True</code > if
        the text color is currently being displayed in the <code
        >ColorSliders</code > widget, <code >False</code > if the
        background color is being displayed.
      </para>
      <para>
        The actual state lives inside the <code
        >ColorReadout</code > widget, so this method interrogates
        that widget for the value.  See <xref
        linkend='ColorReadout-isText' />.
      </para>
      <programlisting role='outFile:huey'
># - - -   A d j u s t e r . i s T e x t

    def isText ( self ):
        """Is self displaying the text color?
        """
        return  self.__colorReadout.isText()
</programlisting>
    </section> <!--Adjuster-isText-->
    <section id='Adjuster-init'>
      <title><code >Adjuster.__init__()</code >: Constructor</title>
      <para>
        The constructor starts out by calling the parent's constructor
        to initialize this instance's frame-nature.  Then it stores the
        name of the callback, and calls <xref
        linkend='Adjuster-createWidgets' /> to set up the contained
        widgets.
      </para>
      <programlisting role='outFile:huey'
># - - -   A d j u s t e r . _ _ i n i t _ _

    def __init__ ( self, parent, callback ):
        """Constructor for the Adjuster compound widget.
        """
        #-- 1 --
        # [ parent  :=  parent with self added as a new Frame ]
        Frame.__init__ ( self, parent )
</programlisting>
      <para>
        To prevent callbacks during initialization, we temporarily set
        the callback pointer to <code >None</code >.
      </para>
      <programlisting role='outFile:huey'
>        #-- 2 --
        self.__callback  =  None
</programlisting>
      <para>
        <xref linkend='Adjuster-createWidgets' /> takes care not
        only of widget creation, but also sets up the initial
        colors.
      </para>
      <programlisting role='outFile:huey'
>        #-- 3 --
        # [ self  :=  self with all contained widgets created and
        #             gridded ]
        self.__createWidgets()
</programlisting>
      <para>
        One more initialization remains.  The <code >ModelSelector</code
        > widget, just created, selects one of the models initially.  We
        must now retrieve the current model from that widget and pass it
        down to the <code >ColorSliders</code > widget so that it can
        display the correct labels on the sliders.  See
        <xref linkend='ModelSelector-getModel' /> and
        <xref linkend='ColorSliders-setModel' />.
      </para>
      <programlisting role='outFile:huey'
>        #-- 4 --
        # [ self.__colorSliders  :=  self.__colorSliders displaying
        #       the color model currently selected in 
        #       self.__modelSelector ]
        model  =  self.__modelSelector.getModel()
        self.__colorSliders.setModel ( model )
</programlisting>
      <para>
        Now that everything is initialized, we can arm the callback
        mechanism.
      </para>
      <programlisting role='outFile:huey'
>        #-- 5 --
        self.__callback  =  callback
</programlisting>
    </section> <!--Adjuster-init-->
    <section id='Adjuster-createWidgets'>
      <title><code >Adjuster.__createWidgets()</code >: Create internal
      widgets</title>
      <para>
        There are three contained widgets, stacked vertically in column
        0 of the grid.
      </para>
      <programlisting role='outFile:huey'
># - - -   A d j u s t e r . _ _ c r e a t e W i d g e t s

    def __createWidgets ( self ):
        """Create and grid all internal widgets
        """
</programlisting>
      <para>
        First is the <code >ColorReadout</code > widget that
        displays the selected colors; see <xref
        linkend='class-ColorReadout' />.  The initial colors are
        defined as manifest class constants; see <xref
        linkend='Adjuster-constants' />.  The <code
        >ColorReadout</code > class expects a callback function;
        we pass it a reference to <xref
        linkend='Adjuster-readoutHandler' />.
      </para>
      <programlisting role='outFile:huey'
>        #-- 1 --
        # [ self  :=  self with a new ColorReadout added and gridded
        #       that will call self.__callback() when the user
        #       changes the text/background choice
        #   self.__colorReadout  :=  that new ColorReadout ]
        self.__colorReadout  =  ColorReadout ( self,
            self.DEFAULT_BG_COLOR, self.DEFAULT_TEXT_COLOR,
            self.__readoutHandler )
        rowx  =  0
        self.__colorReadout.grid ( row=rowx, column=0, sticky=E+W )
</programlisting>
      <para>
        Next is the <code >ModelSelector</code > widget.  It
        needs a callback function that will be called when the
        user changes the displayed color model.  See <xref
        linkend='class-ModelSelector' />.
      </para>
      <programlisting role='outFile:huey'
>        #-- 2 --
        # [ self  :=  self with a new ModelSelector added and gridded
        #       that will select the color model displayed in
        #       self.__colorSliders, and call self.__modelHandler
        #       when the color model is changed ]
        self.__modelSelector  =  ModelSelector ( self,
                                 self.__modelHandler )
        rowx  +=  1
        self.__modelSelector.grid ( row=rowx, column=0, sticky=E+W,
            pady=4 )
</programlisting>
      <para>
        Last is the <code >ColorSliders</code > widget that
        displays the current color model parameters.  This too
        has a callback function, called when the user changes any
        of the current color model parameters.  See <xref
        linkend='class-ColorSliders' />.
      </para>
      <programlisting role='outFile:huey'
>        #-- 3 --
        # [ self  :=  self with a new ColorSliders widget added
        #       that displays the current color model parameters
        #       and calls self.__modelHandler whenever the user
        #       changes a color model parameter
        #   self.__colorSliders  :=  that ColorSliders widget ]
        self.__colorSliders  =  ColorSliders ( self, self.bgColor(),
            self.__sliderHandler )
        rowx  +=  1
        self.__colorSliders.grid ( row=rowx, column=0, sticky=W )
</programlisting>
    </section> <!--Adjuster-createWidgets-->
    <section id='Adjuster-readoutHandler'>
      <title><code >Adjuster.__readoutHandler()</code >: Change between
      text and background colors</title>
      <para>
        This method is called by the <code >ColorReadout</code > widget
        when the user changes between displaying the text color and
        displaying the background color.  It has two functions:
      </para>
      <orderedlist>
        <listitem>
          <para>
            Internally, it tells the <code >ColorSliders</code >
            to display whichever color is now shown in the <code
            >ColorReadout</code > widget.
          </para>
        </listitem>
        <listitem>
          <para>
            Externally, it calls the <code >Adjuster</code > instance's
            callback function.
          </para>
        </listitem>
      </orderedlist>
      <programlisting role='outFile:huey'
># - - -   A d j u s t e r . _ _ r e a d o u t H a n d l e r

    def __readoutHandler ( self ):
        """Change the internal and external colors.

          [ if  self.isText() ->
              self  :=  self displaying the text color
              call self.__callback(True, self.textColor())
            else ->
              self  :=  self displaying the background color
              call self.__callback(False, self.bgColor()) ]
        """
</programlisting>
      <para>
        The first step is to determine which color the sliders display:
        the text color or the background color.  Then order the <code
        >ColorSliders</code > widget to display that color using the
        current color model.
      </para>
      <programlisting role='outFile:huey'
>        #-- 1 --
        if self.isText():
            sliderColor  =  self.textColor()
        else:
            sliderColor  =  self.bgColor()

        #-- 2 --
        # [ self.__colorSliders  :=  self.__colorSliders displaying
        #                            sliderColor ]
        self.__colorSliders.setColor ( sliderColor )
</programlisting>
      <para>
        We must also notify <code >self</code >'s callback.
      </para>
      <programlisting role='outFile:huey'
>        #-- 3 --
        if  self.__callback is not None:
            self.__callback ( self.isText(), sliderColor )
</programlisting>
    </section> <!--Adjuster-readoutHandler-->
    <section id='Adjuster-modelHandler'>
      <title><code >Adjuster.__modelHandler()</code >: Change the
      current color model</title>
      <para>
        This method is called when the user changes the currently
        selected color model.  The internal text and background colors
        do not change, but the <code >ColorSliders</code > widget must
        change the labels and values shown on the three sliders for the
        color model's three parameters.
      </para>
      <para>
        Python's ability to pass classes as objects really
        simplifies life here.  We define an abstract class in
        <xref linkend='class-ColorModel' />, with concrete
        subclasses for each color model.  Then, when the user
        selects a new color model, we can pass the actual Python
        <code >class</code > to the <code >ColorSliders</code >
        object, where it is needed to convert between parameter
        values and colors.  See also <xref
        linkend='ColorSliders-setModel' />.
      </para>
      <programlisting role='outFile:huey'
># - - -   A d j u s t e r . _ _ m o d e l H a n d l e r

    def __modelHandler ( self, model ):
        """Change the color model used in the color sliders.

          [ model is a concrete subclass of ColorModel ->
              self.__colorSliders  :=  self.__colorSliders
                  displaying its current color using (model) ]
        """
        self.__colorSliders.setModel ( model )
</programlisting>
    </section> <!--Adjuster-modelHandler-->
    <section id='Adjuster-sliderHandler'>
      <title><code >Adjuster.__sliderHandler()</code >: Handler for
      slider changes</title>
      <para>
        This method is called when the user changes the position of any
        of the color sliders in the <code >ColorSliders</code > widget.
        It updates the <code >ColorReadout</code > to show the new
        color.  Then it calls this class's callback to inform external
        observers that the displayed color has changed.  See
        <xref linkend='ColorReadout-internalSet' />.
      </para>
      <programlisting role='outFile:huey'
># - - -   A d j u s t e r . _ _ s l i d e r H a n d l e r

    def __sliderHandler ( self, color ):
        """Notify observers of a change in the color sliders.
        """
        #-- 1 --
        # [ self.__colorReadout  :=  self.__colorReadout displaying
        #       color
        self.__colorReadout.internalSet ( color )

        #-- 2 --
        if  self.__callback is not None:
            self.__callback ( self.isText(), color )
</programlisting>
    </section> <!--Adjuster-sliderHandler-->
  </section> <!--class-Adjuster-->
  <section id='class-ColorReadout'>
    <title><code >class ColorReadout</code >: Text and background color
    readouts</title>
    <para>
      This class is a compound widget that has two functions:
    </para>
    <itemizedlist>
      <listitem>
        <para>
          It displays the current text and background colors in <code
          >#RRGGBB</code > form.
        </para>
      </listitem>
      <listitem>
        <para>
          It has radiobuttons that allow the user to select whether the
          color sliders are displaying the text color or the background
          color.
        </para>
      </listitem>
    </itemizedlist>
    <para>
      Here is the class interface:
    </para>
    <programlisting role='outFile:huey'>
# - - - - -   c l a s s   C o l o r R e a d o u t

class ColorReadout(Frame):
    """Displays text and background colors, and switches between them.

      Exports:
        ColorReadout ( parent, bg, fg, callback=None ):
          [ (parent is a Frame) and
            (bg is a background color as a Color) and
            (fg is a foreground color as a Color) and
            (callback is a function or None) ->
              parent  :=  parent with a new ColorReadout widget
                  added but not gridded, initially displaying
                  the background color, which will call
                  callback() whenever the user changes
                  between text to bg color ]
</programlisting>
      <para>
        The next two methods return the current text and
        background colors, respectively; see
        <xref linkend='ColorReadout-textColor' /> and
        <xref linkend='ColorReadout-bgColor' />.
      </para>
      <programlisting role='outFile:huey'
>        .textColor():
          [ return self's current text color as a Color instance ]
        .bgColor:
          [ return self's current background color as a Color instance ]
</programlisting>
      <para>
        The <code >.set()</code > method changes the currently
        displayed color; see <xref linkend='ColorReadout-set' />.
      </para>
      <programlisting role='outFile:huey'
>        .set ( color ):
          [ if self is currently displaying the text color ->
              self's text color  :=  color
            else ->
              self's background color  :=  color
            In any case ->
              call self.__callback(color) ]
</programlisting>
      <para>
        To determine whether the user has selected text or
        background color, call this method; see <xref
        linkend='ColorReadout-isText' />.
      </para>
      <programlisting role='outFile:huey'
>        .isText():
          [ if self is currently displaying the text color ->
              return True
            else -> return False ]
</programlisting>
      <para>
        Here is the layout of the internal widgets, and
        the internal attributes that control them.
      </para>
      <programlisting role='outFile:huey'
>      Contained widgets:
        .__rgbLabel:      [ text label '#RRGGBB' ]
        .__textColorName:
          [ an Entry widget displaying self.__textColorVar ]
        .__bgColorName:
          [ an Entry widget displaying self.__bgColorVar ]
        .__textRadio:   [ a Radiobutton selecting text color ]
        .__bgRadio:     [ a Radiobutton selecting background color ]

      Grid plan:
           0              1
          +--------------+-------------------+
        0 |              | .__rgbLabel       |
          +--------------+-------------------+
        1 | .__bgRadio   | .__bgColorName    |
          +--------------+-------------------+
        2 | .__textRadio | .__textColorName  |
          +--------------+-------------------+

      State/Internals:
        .__callback:     [ as passed to constructor, read-only ]
        .__isTextVar:
          [ an IntVar that is 1 if displaying text color,
            0 if displaying background color ]
        .__bgColor:
          [ self's current background color as a Color instance ]
        .__bgColorVar:
          [ a StringVar displaying self.__bgColor as #RRGGBB ]
        .__textColor:
          [ self's current text color as a Color instance ]
        .__textColorVar:
          [ a StringVar displaying self.__textColor as #RRGGBB ]
    """
</programlisting>
    <section id='ColorReadout-isText'>
      <title><code >ColorReadout.isText()</code >: Which color are we
      displaying?</title>
      <para>
        This method returns <code >True</code > if the text color is
        selected, <code >False</code > otherwise.  The actual state item
        that contains this choice is <code >self.__isTextVar</code >, a
        Tkinter <code >IntVar</code > control variable that is also
        linked to the <code >Radiobutton</code >widgets.
      </para>
      <programlisting role='outFile:huey'
># - - -   C o l o r R e a d o u t . i s T e x t

    def isText ( self ):
        """Is self showing the text color?
        """
        return  self.__isTextVar.get()
</programlisting>
    </section> <!--ColorReadout-isText-->
    <section id='ColorReadout-set'>
      <title><code >ColorReadout.set()</code >: Change the displayed
      color</title>
      <para>
        When the user changes the current color, either by clicking on
        it in the color name pick list, or by adjusting one of the color
        parameter sliders, this method is called to display the
        new color's name.
      </para>
      <programlisting role='outFile:huey'
># - - -   C o l o r R e a d o u t . s e t

    def set ( self, color ):
        """Change the currently selected color.
        """
</programlisting>
      <para>
        Translating a color into <code >"#RRGGBB"</code > is done
        by the <code >Color.__str__()</code > method.  The
        widgets that display this string are linked to control
        variables, so a call to the control variable's <code
        >.set()</code > method changes the displayed label.  See
        <xref linkend='ColorReadout-internalSet' />.
      </para>
      <programlisting role='outFile:huey'
>        #-- 1 --
        # [ if self.isText() ->
        #     self's text color  :=  color
        #   else ->
        #     self's background color  :=  color ]
        self.internalSet ( color )
</programlisting>
      <para>
        External observers must also be notified that the current color
        has changed.
      </para>
      <programlisting role='outFile:huey'
>        #-- 2 --
        if  self.__callback is not None:
            self.__callback ( )
</programlisting>
    </section> <!--ColorReadout-set-->
    <section id='ColorReadout-internalSet'>
      <title><code >ColorReadout.internalSet()</code ></title>
      <para>
        This method actually displays the new color.  It does not call
        the user's callback; it is used when the color change happens
        internal to the <code >Adjuster</code > widget.  Originally,
        a change to the color sliders called the <code >.set()</code >
        method, but this lead to an infinite loop: the sliders called
        <code >ColorReadout.set()</code > which called its callback
        which told the sliders about the new color, and around and
        around we go until we reach the Python recursion limit.
      </para>
      <programlisting role='outFile:huey'
># - - -   C o l o r R e a d o u t . i n t e r n a l S e t

    def internalSet ( self, color ):
        """Change the currently selected color without calling callbacks.

          [ color is a Color instance ->
              if self.isText() ->
                self's text color  :=  color
              else ->
                self's background color  :=  color ]
        """
        if  self.isText():
            self.__textColor  =  color
            self.__textColorVar.set ( str ( color ) )
        else:
            self.__bgColor  =  color
            self.__bgColorVar.set ( str ( color ) )
</programlisting>
    </section> <!--ColorReadout-internalSet-->
    <section id='ColorReadout-textColor'>
      <title><code >ColorReadout.textColor()</code >:  Return the
      current text color</title>
      <para>
        This method returns the current text color as a <code
        >Color</code > instance.  The actual value is stored in
        <code >self.__textColor</code >, but this is a private
        attribute.
      </para>
      <programlisting role='outFile:huey'
># - - -   C o l o r R e a d o u t . t e x t C o l o r

    def textColor ( self ):
        """Return self's current text color.
        """
        return  self.__textColor
</programlisting>
    </section> <!--ColorReadout-textColor-->
    <section id='ColorReadout-bgColor'>
      <title><code >ColorReadout.bgColor()</code >: Return the
      current background color</title>
      <para>
        Another pass-through method that protects the internal
        background color in <code >self.__bgColor</code >.
      </para>
      <programlisting role='outFile:huey'
># - - -   C o l o r R e a d o u t . b g C o l o r

    def bgColor ( self ):
        """Return self's current background color.
        """
        return  self.__bgColor
</programlisting>
    </section> <!--ColorReadout-bgColor-->
    <section id='ColorReadout-init'>
      <title><code >ColorReadout.__init__()</code >: Constructor</title>
      <para>
        The constructor first calls it parent class constructor to make
        it into a <code >Frame</code >.  Then it creates the control
        variable <code >.__isTextVar</code >, and set it initially to
        indicate the background color.  Finally it calls <xref
        linkend='ColorReadout-createWidgets' /> to create and position
        its contained widgets.
      </para>
      <programlisting role='outFile:huey'
># - - -   C o l o r R e a d o u t . _ _ i n i t _ _

    def __init__ ( self, parent, bg, fg, callback=None ):
        """Constructor for ColorReadout.
        """

        #-- 1 --
        # [ parent  :=  parent with self added as a new Frame ]
        Frame.__init__ ( self, parent, relief=SUNKEN, bd=4 )

        #-- 2 --
        # [ self.__isTextVar  :=  a new IntVar control variable
        #                         initialized to 0
        #   self.__bgColor  :=  bg
        #   self.__bgColorVar  :=  a new StringVar control
        #       variable set to str(bg)
        #   self.__textColor  :=  fg
        #   self.__textColorVar  :=  a new StringVar control
        #       variable set to str(fg) ]
        self.__isTextVar  =  IntVar()
        self.__isTextVar.set(0)
        self.__bgColor  =  bg
        self.__bgColorVar  =  StringVar()
        self.__bgColorVar.set ( str ( bg ) )
        self.__textColor  =  fg
        self.__textColorVar  =  StringVar()
        self.__textColorVar.set ( str ( fg ) )

        #-- 3 --
        # [ self  :=  self with all internal widgets created and
        #             gridded ]
        self.__createWidgets()

        #-- 4 --
        self.__callback  =  callback
</programlisting>
    </section> <!--ColorReadout-init-->
    <section id='ColorReadout-createWidgets'>
      <title><code >ColorReadout.__createWidgets()</code ></title>
      <para>
        This methods creates and grids all the widgets.  For the grid
        plan, refer to <xref linkend='class-ColorReadout' />.
      </para>
      <programlisting role='outFile:huey'
># - - -   C o l o r R e a d o u t . _ _ c r e a t e W i d g e t s

    def __createWidgets ( self ):
        """Create and grid all internal widgets.
        """
</programlisting>
      <para>
        The first row contains only a label <code >"#RRGGBB"</code >
        that helps the user interpret the background and text colors
        that appear aligned below it.
      </para>
      <programlisting role='outFile:huey'
>        #-- 1 --
        # [ self.__rgbLabel  :=  a new Label added and gridded ]
        self.__rgbLabel  =  Label ( self, font=MONO_FONT,
            text="#RRGGBB" )
        rowx  =  0
        self.__rgbLabel.grid ( row=rowx, column=1, sticky=E+W )
</programlisting>
      <para>
        The second row contains the radiobutton and readout
        for the background color.
      </para>
      <programlisting role='outFile:huey'
>        #-- 2 --
        # [ self  :=  self with a new Radiobutton widget that sets
        #       self.__isTextVar to 0 and calls self.__radioHandler
        #       when the radiobutton is changed
        #   self.__bgRadio  :=  that Radiobutton widget ]
        self.__bgRadio  =  Radiobutton ( self,
            command=self.__radioHandler,
            font=BUTTON_FONT, text="Background color",
            value=0, variable=self.__isTextVar )
        rowx  +=  1
        self.__bgRadio.grid ( row=rowx, column=0, sticky=W )
</programlisting>
      <para>
        It might seem logical to use a <code >Label</code > widget to
        display color names, but they have one drawback: the user can't
        use the mouse to cut the color name for pasting elsewhere.
        Consequently, we use an <code >Entry</code > widget.  Here are
        some of the less obvious options, and why we use them:
      </para>
      <variablelist>
        <varlistentry>
          <term>
            <code >relief=RAISED, bd=4</code >
          </term>
          <listitem>
            <para>
              Make the labels look like they are sticking up.  Use a
              four-pixel border to give some space for this 3-d effect.
            </para>
          </listitem>
        </varlistentry>
        <varlistentry>
          <term>
            <code >exportselection=1</code >
          </term>
          <listitem>
            <para>
              Allow the user to cut text from this widget.
            </para>
          </listitem>
        </varlistentry>
        <varlistentry>
          <term>
            <code >takefocus=0</code >
          </term>
          <listitem>
            <para>
              We don't want the user to be able to change the text
              displayed here; this option prevents the <keysym
              >tab</keysym > key from moving the input focus through
              this widget.
            </para>
          </listitem>
        </varlistentry>
      </variablelist>
      <programlisting role='outFile:huey'
>        #-- 3 --
        # [ self.__bgColorName  :=  a new Entry whose text is
        #       linked to self.__bgColorVar ]
        self.__bgColorName  =  Entry ( self,
            exportselection=1, takefocus=0, width=7,
            relief=RAISED, bd=4, bg="white",
            font=MONO_FONT, textvariable=self.__bgColorVar )
        self.__bgColorName.grid ( row=rowx, column=1,
            padx=4, sticky=W )
</programlisting>
      <para>
        The next row is quite similar to the previous row, but
        it is for the text color.
      </para>
      <programlisting role='outFile:huey'
>        #-- 4 --
        # [ self  :=  self with a new Radiobutton widget that sets
        #       self.__isTextVar to 1 and calls self.__radioHandler
        #       when the radiobutton is changed
        #   self.__textRadio  :=  that Radiobutton widget ]
        self.__textRadio  =  Radiobutton ( self,
            command=self.__radioHandler,
            font=BUTTON_FONT, text="Text color",
            value=1, variable=self.__isTextVar )
        rowx  +=  1
        self.__textRadio.grid ( row=rowx, column=0, sticky=W )

        #-- 5 --
        # [ self.__textColorName  :=  a new Entry whose text is
        #       linked to self.__textColorVar ]
        self.__textColorName  =  Entry ( self,
            exportselection=1, takefocus=0, width=7,
            relief=RAISED, bd=4, bg="white",
            font=MONO_FONT, textvariable=self.__textColorVar )
        self.__textColorName.grid ( row=rowx, column=1,
            padx=4, sticky=W )
</programlisting>
      <para>
        There is a drawback to using an <code >Entry</code >
        widget to display the color values: the user can then
        edit the displayed values, which would mean they no
        longer reflect the actual color values.  One trick we
        tried is to set <code >state=DISABLED</code > in both
        <code >Entry</code > widgets.  This does a fine job of
        preventing the user from changing the value, and in an
        older Tkinter version, the fix worked.  However, in
        a newer Tkinter install, it is now impossible to use
        cut-and-paste on a disabled widget.
      </para>
      <para>
        The solution is to bind all keyboard events to a little
        handler called <xref linkend='ColorReadout-fixNames' />.
        This event handler rewrites the values of both the names
        to keep them consistent with the internal colors.  Here
        is the code that sets up that event binding.
      </para>
      <programlisting role='outFile:huey'
>        #-- 6 --
        # [ self.__bgColorName  :=  self.__bgColorName set up so
        #       that any user keypresses cause self.__bgColorVar
        #       to be set to str(self.__bgColor)
        #   self.__textColorName  :=  self.__textColorName set up so
        #       that any user keypresses cause self.__textColorVar
        #       to be set to str(self.__textColor) ]
        self.__bgColorName.bind ( "&lt;Any-KeyRelease&gt;",
                                  self.__fixNames )
        self.__textColorName.bind ( "&lt;Any-KeyRelease&gt;",
                                  self.__fixNames )
</programlisting>
    </section> <!--ColorReadout-createWidgets-->
    <section id='ColorReadout-radioHandler'>
      <title><code >ColorReadout.__radioHandler()</code >:  Radiobutton
      state change handler</title>
      <para>
        When the user changes the state of the radiobuttons, we need to
        call the external callback function.
      </para>
      <programlisting role='outFile:huey'
># - - -   C o l o r R e a d o u t . _ _ r a d i o H a n d l e r

    def __radioHandler ( self ):
        """Handler for a change between text and background color.
        """
        if  self.__callback is not None:
            self.__callback ( )
</programlisting>
    </section> <!--ColorReadout-radioHandler-->
    <section id='ColorReadout-fixNames'>
      <title><code >ColorReadout.__fixNames()</code >: Prevent
      user modification of the <code >Entry</code >
      widgets</title>
      <para>
        For an explanation of what this handler does and why
        it does it, see <xref
        linkend='ColorReadout-createWidgets' />.  It is called
        with the event handler protocol, so it expects an <code
        >Event</code > object as its argument.
      </para>
      <programlisting role='outFile:huey'
># - - -   C o l o r R e a d o u t . _ _ f i x N a m e s

    def __fixNames ( self, event ):
        """Prevent the user from modifying color name Entry widgets.
        """
</programlisting>
      <para>
        These lines re-assert the two class invariants: that the
        <code >self.__bgColorVar</code > control variable
        reflects the background color in <code
        >self.__bgColor</code >, and the same invariant for <code
        >self.__textColorVar</code > and <code
        >self.__textColor</code >.
      </para>
      <programlisting role='outFile:huey'
>        self.__bgColorVar.set ( str ( self.__bgColor ) )
        self.__textColorVar.set ( str ( self.__textColor ) )
</programlisting>
    </section> <!--ColorReadout-fixNames-->
  </section> <!--class-ColorReadout-->
  <section id='class-ModelSelector'>
    <title><code >class ModelSelector</code >: Widgets to select a color
    model</title>
    <para>
      This class is a compound widget that allows the user to select
      among different color models.  There are a number of different
      ways to map color space onto coordinate systems, but three numbers
      are required in any case.
    </para>
    <para>
      Although originally designed to support three models (HSV, RGB,
      and CMY), there is no reason why other models cannot be added
      later.
    </para>
    <programlisting role='outFile:huey'>
# - - - - -   c l a s s   M o d e l S e l e c t o r

class ModelSelector(Frame):
    """Compound widget for selecting color models.

      Exports:
        ModelSelector ( parent, callback=None ):
          [ (parent is a Frame) and
            (callback is a function or None) ->
              parent  :=  parent with a new ModelSelector widget
                  added but not gridded, that lets the user select
                  a color model (initially RGB), and calls
                  callback(model) when a selection is made, where
                  model is a concrete class that inherits from ColorModel
              return that new ModelSelector widget ]
</programlisting>
      <para>
        This method returns the currently selected model; see
        <xref linkend='ModelSelector-getModel' />.
      </para>
      <programlisting role='outFile:huey'
>        .getModel():
          [ returns self's model as a concrete class of ColorModel ]

      Internal widgets:
        .__topLabel:    [ a Label that labels the radiobuttons ]
        .__radioList:
          [ a list of Radiobutton widgets, one per model ]

      Grid plan:
           0                 1                 2...
          +-----------------+-----------------+-----+
        0 | .__topLabel                             |
          +-----------------+-----------------+-----+
        1 | .__radioList[0] | .__radioList[1] | ... |
          +-----------------+-----------------+-----+
</programlisting>
      <para>
        Here are some private attributes of the class.  First, <code
        >self.__callback</code > is the callback function.  The control
        variable that links the radiobuttons together into a group is
        <code >self.__radioVar</code >.
      </para>
      <programlisting role='outFile:huey'
>      State/Invariants:
        .__callback:     [ as passed to constructor, read-only ]
        .__radioVar:
          [ an IntVar control variable for the radiobuttons whose
            value is the model's index in self.__modelList ]
</programlisting>
      <para>
        There is also a class variable that enumerates the supported
        color models, as instances of concrete classes implementing the
        <code >ColorModel</code > abstract class.
      </para>
      <programlisting role='outFile:huey'
>        ModelSelector.__modelList:
          [ a list of instances of ColorModel concrete classes,
            in the same order as self.__radioList ]
    """
</programlisting>
    <section id='ModelSelector-constants'>
      <title><code >ModelSelector</code >: Constants</title>
      <para>
        The set of concrete classes of <code >ColorModel</code >,
        representing the different color models that the user can
        select, is a class attribute.  See
        <xref linkend='class-HSVModel' />,
        <xref linkend='class-RGBModel' />, and
        <xref linkend='class-CMYModel' />.
      </para>
      <programlisting role='outFile:huey'
>    __modelList  =  [ HSVModel(), RGBModel(), CMYModel() ]
</programlisting>
      <para>
        To add a new model, write its class inheriting from <code
        >ColorModel</code >, implement its virtual methods, and
        add a call to its constructor to the line above.
      </para>
    </section> <!--ModelSelector-constants-->
    <section id='ModelSelector-getModel'>
      <title><code >ModelSelector.getModel()</code >: Return the current
      model</title>
      <para>
        This method returns the currently selected color model as a
        concrete class implementing <code >ColorModel</code >.  The
        <code >self.__radioVar</code > control variable contains the
        index of the currently selected model.
      </para>
      <programlisting role='outFile:huey'
># - - -   M o d e l S e l e c t o r . g e t M o d e l

    def getModel ( self ):
        """Returns self's current color model.
        """

        #-- 1 --
        # [ modelx  :=  index in self.__radioList of the currently
        #               selected color model ]
        modelx  =  self.__radioVar.get()
        
        #-- 2--
        return self.__modelList[modelx] 
</programlisting>
    </section> <!--ModelSelector-getModel-->
    <section id='ModelSelector-init'>
      <title><code >ModelSelector.__init__()</code >: Constructor</title>
      <para>
        The constructor first calls the parent class constructor, which
        makes <code >self</code > a <code >Frame</code >.
      </para>
      <programlisting role='outFile:huey'
># - - -   M o d e l S e l e c t o r . _ _ i n i t _ _

    def __init__ ( self, parent, callback ):
        """Constructor for the ModelSelector compound widget.
        """

        #-- 1 --
        # [ parent  :=  parent with a new Frame widget added but
        #               not gridded
        #   self  :=  that new Frame widget ]
        Frame.__init__ ( self, parent, relief=SUNKEN, bd=4 )
</programlisting>
      <para>
        Next we store a reference to the callback function, and create
        the list that will contain the radiobuttons, and then create and
        initialize their associated control variable.  Finally, we
        create all the widgets&#x2014;see <xref
        linkend='ModelSelector-createWidgets' />.
      </para>
      <programlisting role='outFile:huey'
>        #-- 2 --
        self.__callback  =  None
        self.__radioList  =  []
        self.__radioVar  =  IntVar()
        self.__radioVar.set(0)

        #-- 3 --
        # [ self  :=  self with all internal widgets created and
        #             gridded ]
        self.__createWidgets()

        #-- 4 --
        self.__callback  =  callback
</programlisting>
    </section> <!--ModelSelector-init-->
    <section id='ModelSelector-createWidgets'>
      <title><code >ModelSelector.__createWidgets()</code ></title>
      <para>
        For the list of internal widgets and the grid plan, see <xref
        linkend='class-ModelSelector' />.
      </para>
      <programlisting role='outFile:huey'
># - - -   M o d e l S e l e c t o r . _ _ c r e a t e W i d g e t s

    def __createWidgets ( self ):
        """Create and grid all internal widgets.
        """

        #-- 1 --
        # [ self.__topLabel  :=  a Label labeling the radiobuttons ]
        self.__topLabel  =  Label ( self,
            font=BUTTON_FONT,
            text="Select a color model:" )
        rowx  =  0
        self.__topLabel.grid ( row=rowx, column=0, columnspan=3,
            sticky=W )
</programlisting>
      <para>
        The radiobuttons can be arranged either horizontally or
        vertically.  We chose horizontal under the assumption that space
        may be at a premium, and also because the labels for the color
        models are short (all are three letters).  First we create the
        associated control variable, then we populate <code
        >self.__radioList</code > with <code >Radiobutton</code >
        widgets, each displaying the name from the corresponding model
        as its label.
      </para>
      <programlisting role='outFile:huey'
>        #-- 2 --
        # [ self.__modelList is a list of ColorModel instances ->
        #     self  :=  self with one Radiobutton added and gridded for
        #         each element of self.__modelList, labeled with the
        #         model name of that element, and setting self.__radioVar
        #         to the position of that element in self.__modelList
        #     self.__radioList  +:=  those Radiobuttons in the
        #         same order ]
        rowx  +=  1
        colx  =   0
        for  radiox in range(len(self.__modelList)):
            #-- 2 body --
            # [ radiox is an index in self.__modelList ->
            #     self  :=  self with a new Radiobutton added and
            #         gridded, labeled with the model name from
            #         self.__modelList[radiox], and setting
            #         self.__radioVar to radiox ]
            model  =  self.__modelList[radiox]
            radio  =  Radiobutton ( self, font=BUTTON_FONT,
                command=self.__radioHandler,
                value=radiox, variable=self.__radioVar,
                text=model.modelName )
            radio.grid ( row=rowx, column=colx, sticky=W, padx=6 )
            colx  +=  1
            self.__radioList.append ( radio )
</programlisting>
    </section> <!--ModelSelector-createWidgets-->
    <section id='ModelSelector-radioHandler'>
      <title><code >ModelSelector.__radioHandler()</code >: Handler for
      radiobutton action</title>
      <para>
        This method is called when the user selects one of the color
        model radiobuttons.
      </para>
      <programlisting role='outFile:huey'
># - - -   M o d e l S e l e c t o r . _ _ r a d i o H a n d l e r

    def __radioHandler ( self ):
        """The user selected a different color model.
        """
</programlisting>
      <para>
        When the model is changed, we must call our callback function to
        notify outside observers of the new model.  The argument to the
        callback is a <code >ColorModel</code > instance.
      </para>
      <programlisting role='outFile:huey'
>        #-- 1 --
        if  self.__callback is not None:
            modelx  =  self.__radioVar.get()
            self.__callback ( self.__modelList[modelx] )
</programlisting>
    </section> <!--ModelSelector-radioHandler-->
  </section> <!--class-ModelSelector-->
  <section id='class-ColorSliders'>
    <title><code >class ColorSliders</code >: Color model parameter
    sliders</title>
    <para>
      This class is a compound widget containing three sliders (Tkinter
      <code >Scale</code > widgets) that the user can drag to change
      the value of one of the color model's parameters.
    </para>
    <programlisting role='outFile:huey'>
# - - - - -   c l a s s   C o l o r S l i d e r s

class ColorSliders(Frame):
    """Compound widget for displaying and adjusting color parameters

      Exports:
        ColorSliders ( parent, color, callback=None ):
          [ (parent is a Frame) and
            (color is the initial color as a Color instance) and
            (callback is a function or None) ->
              parent  :=  parent with a new ColorSliders widget
                  added but not gridded, using the HSV model,
                  initially displaying white, that will call
                  callback(color) whenever the user changes a
                  color parameter
              return that new ColorSliders widget ]
</programlisting>
      <para>
        The <code >.setModel()</code > changes the model used to
        compute the slider positions; see <xref
        linkend='ColorSliders-setModel' />.
      </para>
      <programlisting role='outFile:huey'
>        .setModel ( model ):
          [ model is a concrete subclass of ColorModel ->
              self  :=  self with its current color displayed
                        using model ]
</programlisting>
      <para>
        The <code >.setColor()</code > method changes the
        currently displayed color; see <xref
        linkend='ColorSliders-setColor' />.
       </para>
       <programlisting role='outFile:huey'
 >        .setColor ( color ):
          [ color is a Color instance ->
              self  :=  self displaying color using its current
                        model ]
</programlisting>
      <para>
        The label for this widget spans all the columns in row 0.
        The three <code >ParamSlider</code > widgets are gridded
        horizontally across row 1.
      </para>
      <programlisting role='outFile:huey'
>      Internal widgets:
        .__topLabel:    [ a Label that describes this widget ]
        .__sliderList:
          [ a list containing three ParamSlider widgets
            corresponding to the three parameters of self.__model
            in the same order ]

      Grid plan:
           0                  1                  2
          +------------------+------------------+------------------+
        0 | .__topLabel                                            |
          +------------------+------------------+------------------+
        1 | .__sliderList[0] | .__sliderList[1] | .__sliderList[2] |
          +------------------+------------------+------------------+
</programlisting>
      <para>
        Internal state includes the current color and the current color
        model.
      </para>
      <programlisting role='outFile:huey'
>      State/Invariants:
        .__callback:  [ as passed to the constructor ]
        .__color:     [ the currently displayed color as a Color ]
        .__model:
          [ the current color model as a concrete subclass of
            ColorModel ]
    """
</programlisting>
    <section id='ColorSliders-setModel'>
      <title><code >ColorSliders.setModel()</code >: Display a new color
      model</title>
      <para>
        This method is called to changed the color model displayed in
        these sliders.
      </para>
      <programlisting role='outFile:huey'
># - - -   C o l o r S l i d e r s . s e t M o d e l

    def setModel ( self, model ):
        """Change the displayed color model.
        """
</programlisting>
      <para>
        It is not necessary to display the name of the currently
        selected color model here.  The user can look at the <code
        >ModelSelector</code > widget to find that out, or they can look
        at the labels displayed on the parameter sliders.
      </para>
      <para>
        The primary work of this method, then, is to tell the
        <code >ParamSlider</code > widgets to change their labels
        to those of the parameters of the new models.  Once they
        are relabeled, we call <xref
        linkend='ColorSliders-setColor' /> with the current
        color, which will tell the parameter sliders to redisplay
        that color using the newly selected model.
      </para>
      <programlisting role='outFile:huey'
>        #-- 1 --
        self.__model  =  model

        #-- 2 --
        # [ self.__sliderList  :=  self.__sliderList with its
        #       contained widgets relabeled for model (model) ]
        for  paramx in range ( N_PARAMS ):
            self.__sliderList[paramx].setModel ( self.__model )

        #-- 3 --
        # [ self  :=  self displaying the parameters self.__color
        #             using model self.__model ]
        self.setColor ( self.__color )
</programlisting>
    </section> <!--ColorSliders-setModel-->
    <section id='ColorSliders-setColor'>
      <title><code >ColorSliders.setColor()</code >: Change the
      displayed color</title>
      <para>
        This method is called to change the color currently being
        displayed in this widget.
      </para>
      <programlisting role='outFile:huey'
># - - -   C o l o r S l i d e r s . s e t C o l o r

    def setColor ( self, color ):
        """Change the displayed color.
        """
</programlisting>
      <para>
        Because each <code >ParamSlider</code > widget knows
        which parameter of the model is displaying, all we have
        to do is tell each one the new color; see <xref
        linkend='ParamSlider-setColor' />.
      </para>
      <programlisting role='outFile:huey'
>        #-- 1 --
        self.__color  =  color

        #-- 2 --
        for  paramx in range ( N_PARAMS ):
            #-- 1 body --
            # [ self.__sliderList[paramx]  :=  self.__sliderList[paramx]
            #       displaying the (paramx)th parameter of (color)
            #       using its current color model ]
            self.__sliderList[paramx].setColor ( color )
</programlisting>
    </section> <!--ColorSliders-setColor-->
    <section id='ColorSliders-init'>
      <title><code >ColorSliders.__init__()</code >: Constructor</title>
      <para>
        The constructor starts by calling its parent class constructor.
        The calls to <code >.columnconfigure()</code > distribute the
        width of the included columns equally (<code >weight</code >)
        and assign a minimum column size (<code >minsize</code >).
      </para>
      <programlisting role='outFile:huey'
># - - -   C o l o r S l i d e r s . _ _ i n i t _ _

    def __init__ ( self, parent, color, callback ):
        """Constructor for the ColorSliders widget.
        """
        #-- 1 --
        # [ parent  :=  parent with a new Frame added but not
        #               gridded
        #   self  :=  that Frame ]
        Frame.__init__ ( self, parent )
        self.columnconfigure ( 0, weight=1, minsize="80" )
        self.columnconfigure ( 1, weight=1, minsize="80" )
        self.columnconfigure ( 2, weight=1, minsize="80" )
</programlisting>
      <para>
        Then it creates the initial color (red) and model (HSV), and
        calls <xref linkend='ColorSliders-createWidgets' /> to create
        its internal widgets.
      </para>
      <programlisting role='outFile:huey'
>        #-- 2 --
        # [ self.__color     :=  red
        #   self.__model     :=  the HSV color model
        #   self.__callback  :=  callback ]
        self.__color  =  color
        self.__model  =  HSVModel()
        self.__callback  =  None

        #-- 3 --
        # [ self  :=  self with all internal widgets created and
        #             gridded ]
        self.__createWidgets()

        #-- 4 --
        self.__callback  =  callback
</programlisting>
    </section> <!--ColorSliders-init-->
    <section id='ColorSliders-createWidgets'>
      <title><code >ColorSliders.__createWidgets()</code ></title>
      <para>
        For the widget layout, see <xref linkend='class-ColorSliders'
        />.
      </para>
      <programlisting role='outFile:huey'
># - - -   C o l o r S l i d e r s . _ _ c r e a t e W i d g e t s

    def __createWidgets ( self ):
        """Create and grid all internal widgets.
        """
        #-- 1 --
        # [ self  :=  self with a new Label created and gridded
        #   self.__topLabel  :=  that new Label ]
        # 
        self.__topLabel  =  Label ( self,
            font=BUTTON_FONT,
            text="Adjust colors here:" )
        rowx  =  0
        self.__topLabel.grid ( row=rowx, column=0, columnspan=3,
            sticky=E+W )
</programlisting>
      <para>
        Next we add a horizontal group of parameter slider
        widgets: see <xref linkend='class-ParamSlider' />.
      </para>
      <programlisting role='outFile:huey'
>        #-- 2 --
        self.__sliderList  =  []

        # [ self  :=  self with N_PARAMS new ParamSlider widgets
        #             added and gridded
        #   self.__sliderList  +:=  those widgets ]
        rowx  +=  1
        for  paramx in range ( N_PARAMS ):
            paramSlider  =  ParamSlider ( self, self.__model,
                self.__color, paramx, self.__sliderHandler )
            paramSlider.grid ( row=rowx, column=paramx, sticky=E+W )
            self.__sliderList.append ( paramSlider )
</programlisting>
    </section> <!--ColorSliders-createWidgets-->
    <section id='ColorSliders-sliderHandler'>
      <title><code >ColorSliders.__sliderHandler()</code >: Handler for
      a parameter change</title>
      <para>
        This method is called when the user changes the setting of any
        of the <code >ParamSlider</code > widgets.  This method takes no
        arguments; when any slider changes, we have to look at all three
        and convert those settings into the new color.
      </para>
      <programlisting role='outFile:huey'
># - - -   C o l o r S l i d e r s . _ _ s l i d e r H a n d l e r

    def __sliderHandler ( self ):
        """Read the ParamSliders and convert them to a new color
        """
</programlisting>
      <para>
        First we read all three sliders and build a list of their
        values, each value in [0,<code >MAX_PARAM</code >].
      </para>
      <programlisting role='outFile:huey'
>        #-- 1 --
        paramList  =  []

        #-- 2 --
        # [ paramList  +:=  parameter values of the ParamSlider
        #       widgets in self.__sliderList ]
        for  paramx in range(N_PARAMS):
            paramList.append (self.__sliderList[paramx].get() )
</programlisting>
      <para>
        In classes that inherit from <code >ColorModel</code >, the
        <code >.paramsToColor()</code > method converts the list of
        parameters into a <code >Color</code > object.  See <xref
        linkend='class-ColorModel' />.
      </para>
      <programlisting role='outFile:huey'
>        #-- 3 --
        # [ paramList  is a list of N_PARAMS numbers, each in
        #   [0,MAX_PARAM] ->
        #     self.__color  :=  paramList converted to a color using
        #                       the color model in self.__model ]
        self.__color  =  self.__model.paramsToColor ( paramList )
</programlisting>
      <para>
        We must call our callback now to notify external observers that
        the color has changed.
      </para>
      <programlisting role='outFile:huey'
>        #-- 4 --
        if  self.__callback is not None:
            self.__callback ( self.__color )
</programlisting>
    </section> <!--ColorSliders-sliderHandler-->
  </section> <!--class-ColorSliders-->
  <section id='class-ParamSlider'>
    <title><code >class ParamSlider</code >: Scale widget for a color
    model parameter</title>
    <para>
      This class is a compound widget containing the controls that let
      the user adjust one of the parameters of the current color.
    </para>
    <para>
      Since colors in external form show only 8 bits per pixel, the
      length of the vertical <code >Scale</code > widget for the
      parameter is exactly 256 pixels, covering the range [0,255].
      Because color model parameter values are in the range
      [0,65535], conversion from the scale value to the parameter
      value are a simple shift of 8 bits.
    </para>
    <programlisting role='outFile:huey'>
# - - - - -   c l a s s   P a r a m S l i d e r

class ParamSlider(Frame):
    """Compound widget with a Scale for adjusting one color parameter.

      Exports:
        ParamSlider ( parent, model, color, paramx, callback=None ):
          [ (parent is a Frame) and
            (model is a ColorModel) and
            (color is a Color) and
            (param is an int in [0,N_PARAMS)) and
            (callback is a function or None) ->
              parent  :=  parent with a new ParamSlider widget added
                  but not gridded, that displays the (paramx)th
                  parameter of (color) using (model), and calls
                  callback() whenever its scale is moved
              return that new ParamSlider widget ]
</programlisting>
      <para>
        The <code >.get()</code > method returns the 16-bit
        integer value of this parameter; see <xref
        linkend='ParamSlider-get' />.
      </para>
      <programlisting role='outFile:huey'
>        .get():  [ return self's slider value in [0,MAX_PARAM] ]
</programlisting>
      <para>
        The <code >.setModel()</code > and <code
        >.setColor()</code > methods change the current color
        model and color, respectively; see
        <xref linkend='ParamSlider-setModel' /> and
        <xref linkend='ParamSlider-setColor' />.
      </para>
      <programlisting role='outFile:huey'
>        .setModel ( model ):
          [ self  :=  self displaying labels for the (paramx)th
                      parameter of model ]
        .setColor ( color ):
          [ self  :=  self displaying the (paramx)th parameter of
                      self.model ]
</programlisting>
      <para>
        Each <code >ParamSlider</code > contains four widgets in a
        vertical row:
      </para>
      <programlisting role='outFile:huey'
>      Internal widgets:
        .__topLabel:    [ Label showing the parameter's name ]
        .__plusButton:  [ Button that increments the parameter ]
        .__scale:       [ Scale for adjusting the parameter ]
        .__minusButton: [ Button that decrements the parameter ]

      Grid plan:  One vertical column.
</programlisting>
      <para>
        Here are the private attributes.
      </para>
      <programlisting role='outFile:huey'
>      State/Invariants:
        .__paramx:      [ as passed to the constructor ]
        .__callback:    [ as passed to the constructor ]
        .__topLabelVar: [ StringVar for self.__topLabel ]
        .__scaleVar:    [ IntVar for self.__scale ]
        .__model:       [ current model as a ColorModel ]
        .__color:       [ current color as a Color ]
    """
</programlisting>
    <section id='ParamSlider-get'>
      <title><code >ParamSlider.get()</code >: Retrieve the current
      parameter value</title>
      <para>
        This method returns the parameter value in the range [0,<code
        >MAX_PARAM</code >.  The actual parameter value lives in the
        <code >self.__scaleVar</code > control variable, but it is an
        8-bit value, so we must convert it.
      </para>
      <programlisting role='outFile:huey'
># - - -   P a r a m S l i d e r . g e t

    def get ( self ):
        """Return self's parameter value in [0,MAX_PARAM].
        """
        return  self.__scaleVar.get() &lt;&lt; 8
</programlisting>
    </section> <!--ParamSlider-get-->
    <section id='ParamSlider-setModel'>
      <title><code >ParamSlider.setModel()</code >: Change the color
      model</title>
      <para>
        This method changes the color model being displayed.
      </para>
      <programlisting role='outFile:huey'
># - - -   P a r a m S l i d e r . s e t M o d e l

    def setModel ( self, model ):
        """Change the current color model."""
</programlisting>
      <para>
        First we store the new color model in <code
        >self.__model</code >.
      </para>
      <programlisting role='outFile:huey'
>        #-- 1 --
        self.__model  =  model
</programlisting>
      <para>
        The label <code >self.__topLabel</code > displays the parameter
        name; retrieve the parameter name from the color model and
        change the label's text to that name.
      </para>
      <programlisting role='outFile:huey'
>        #-- 2 --
        # [ self.__topLabelVar  :=  self.__topLabelVar with its
        #       value set to the (self.__paramx)th parameter name
        #       of self.__model ]
        paramLabel  =  self.__model.labelList[self.__paramx]
        self.__topLabelVar.set ( paramLabel )
</programlisting>
      <para>
        <emphasis>Note</emphasis>: Originally, there was logic here to
        change the displayed color.  However, this led to a nasty defect.
        In the original version, the caller first sets the first
        slider's model&#x2014;but then the callback to change the
        color would calculate a new color based on the positions
        of sliders 2 and 3, which still used the old model!
      </para>
      <para>
        The fix was to put the burden on the caller to redisplay
        the new color once all the sliders were moved to the new model.
      </para>
    </section> <!--ParamSlider-setModel-->
    <section id='ParamSlider-setColor'>
      <title><code >ParamSlider.setColor()</code >: Change the current
      color</title>
      <para>
        This method changes the color whose parameter is currently
        displayed.
      </para>
      <programlisting role='outFile:huey'
># - - -   P a r a m S l i d e r . s e t C o l o r

    def setColor ( self, color ):
        """Change the currently displayed color."""

        #-- 1 --
        self.__color  =  color
</programlisting>
      <para>
        We must readjust the position of the <code >Scale</code >
        widget.  First, we translate the new color into its three
        parameters using the current color model.  For this
        interface, see <xref linkend='class-ColorModel' />.
      </para>
      <programlisting role='outFile:huey'
>        #-- 2 --
        # [ paramList  :=  self.__color expressed as a list of
        #       N_PARAMS parameters using color model self.__model ]
        paramList  =  self.__model.colorToParams ( color )
</programlisting>
      <para>
        The parameter displayed is an element of <code >paramList</code
        >, with that element's position specified by <code
        >self.__paramx</code >.  This value must be shifted right by 8
        bits to fit into the range [0,<code >MAX_BYTE</code >].
        Then the <code >Scale</code > widget is repositioned by calling
        the <code >.set()</code > method on its control variable.
      </para>
      <programlisting role='outFile:huey'
>        #-- 3 --
        scaleValue  =  paramList[self.__paramx] &gt;&gt; 8

        #-- 4 --
        # [ self.__scale  :=  self.__scale repositioned to value
        #                     (scaleValue) ]
        self.__scaleVar.set ( scaleValue )
</programlisting>
      <para>
        Finally, we must notify the external observers that the color
        has changed.
      </para>
      <programlisting role='outFile:huey'
>        #-- 5 --
        if  self.__callback is not None:
            self.__callback()
</programlisting>
    </section> <!--ParamSlider-setColor-->
    <section id='ParamSlider-init'>
      <title><code >ParamSlider.__init__()</code >: Constructor</title>
      <para>
        For internal widgets and their placement, see <xref
        linkend='class-ParamSlider' />.
      </para>
      <programlisting role='outFile:huey'
># - - -   P a r a m S l i d e r . _ _ i n i t _ _

    def __init__ ( self, parent, model, color, paramx, callback=None ):
        """Constructor for ParamSlider.
        """
</programlisting>
      <para>
        First we call the parent constructor and store all the
        constructor's arguments.
      </para>
      <programlisting role='outFile:huey'
>        #-- 1 --
        # [ parent  :=  parent with a new Frame widget added but
        #               not gridded
        #   self  :=  that Frame widget ]
        Frame.__init__ ( self, parent )

        #-- 2 --
        self.__model     =  model
        self.__color     =  color
        self.__paramx    =  paramx
        self.__callback  =  None
</programlisting>
      <para>
        Create the two control variables.  <code >self.__scaleVar</code
        > is for the <code >Scale</code > widget, so its values vary
        in the range [0,<code >MAX_BYTE</code >].
      </para>
      <para>
        The second control variable, <code >self.__topLabelVar</code >,
        is for the <code >Label</code > widget that displays the
        parameter name.  We can't create this <code >Label</code > using
        the normal <code >text=</code > parameter, because it can change
        any time the model is changed.
      </para>
      <para>
        For example, if this is the third parameter of the RGB model,
        this <code >Label</code > would display &#x201c;B&#x201d;.  The
        <code >paramx</code > value in that case would be 2 (since
        indices count from 0).
      </para>
      <programlisting role='outFile:huey'
>        #-- 3 --
        # [ self.__topLabelVar  :=  a new StringVar control variable
        #       set to the (paramxth) parameter name of self.__model
        #   self.__scaleVar  :=  a new IntVar control variable ]
        paramLabel  =  self.__model.labelList[self.__paramx]
        self.__topLabelVar  =  StringVar()
        self.__topLabelVar.set ( paramLabel )
        self.__scaleVar  =  IntVar()
</programlisting>
      <para>
        Now we're ready to create the widgets; see <xref
        linkend='ParamSlider-createWidgets' />.
      </para>
      <programlisting role='outFile:huey'
>        #-- 4 --
        # [ self  :=  self with all internal widgets created and
        #             gridded ]
        self.__createWidgets()

        #-- 5 --
        self.__callback  =  callback
</programlisting>
    </section> <!--ParamSlider-init-->
    <section id='ParamSlider-createWidgets'>
      <title><code >ParamSlider.__createWidgets()</code ></title>
      <programlisting role='outFile:huey'
># - - -   P a r a m S l i d e r . _ _ c r e a t e W i d g e t s

    def __createWidgets ( self ):
        """Create and grid all widgets.
        """
</programlisting>
      <para>
        The first widget is the <code >Label</code > that displays
        the parameter name.
      </para>
      <programlisting role='outFile:huey'
>        #-- 1 --
        # [ self  :=  self with a new Label added and gridded,
        #             with control variable self.__topLabelVar
        #   self.topLabel  :=  that new Label ]
        self.__topLabel  =  Label ( self, font=BUTTON_FONT,
            textvariable=self.__topLabelVar )
        rowx  =  0
        self.__topLabel.grid ( row=rowx, column=0 )
</programlisting>
      <para>
        Next is a button with &#x201c;+&#x201d; on it that
        increments the parameter value by 256, which is
        equivalent to an increment of 1 in the <code >Scale</code
        > widget's control variable.  For the associated handler,
        see <xref linkend='ParamSlider-plusHandler' />.
      </para>
      <programlisting role='outFile:huey'
>        #-- 2 --
        # [ self  :=  self with a new Button widget added and gridded
        #       that adds 1 to self.__scaleVar but no higher than
        #       MAX_BYTE
        #   self.__plusButton  :=  that widget ]
        self.__plusButton  =  Button ( self, font=BUTTON_FONT,
            text="+", command=self.__plusHandler )
        rowx  +=  1
        self.__plusButton.grid ( row=rowx, column=0 )
</programlisting>
      <para>
        The <code >Scale</code > widget comes next.  Its handler,
        called when the user drags the slider, is <xref
        linkend='ParamSlider-scaleHandler' />.
      </para>
      <programlisting role='outFile:huey'
>        #-- 3 --
        # [ self  :=  self with a new Scale widget added and gridded,
        #       with control variable self.__scaleVar, and a length
        #       of (MAX_BYTE+1) pixels
        #   self.__scale  :=  that Scale widget ]
        self.__scale  =  Scale ( self, orient=VERTICAL,
            command=self.__scaleHandler,
            length=(MAX_BYTE+1), from_=MAX_BYTE, to=0,
            variable=self.__scaleVar )
        rowx  +=  1
        self.__scale.grid ( row=rowx, column=0 )
</programlisting>
      <para>
        The &#x201c;-&#x201d; button is pretty similar to the
        &#x201c;+&#x201d; button.  For its handler, see <xref
        linkend='ParamSlider-minusHandler' />.
      </para>
      <programlisting role='outFile:huey'
>        #-- 4 --
        # [ self  :=  self with a new Button widget added and gridded
        #       that subtracts 1 to self.__scaleVar but no lower than 0
        #   self.__minusButton  :=  that widget ]
        self.__minusButton  =  Button ( self, font=BUTTON_FONT,
            text="-", command=self.__minusHandler )
        rowx  +=  1
        self.__minusButton.grid ( row=rowx, column=0 )
</programlisting>
    </section> <!--ParamSlider-createWidgets-->
    <section id='ParamSlider-plusHandler'>
      <title><code >ParamSlider.__plusHandler()</code >: Increment the
      parameter</title>
      <para>
        This method is called when the user clicks the &#x201c;+&#x201d;
        button to increment the parameter.
      </para>
      <programlisting role='outFile:huey'
># - - -   P a r a m S l i d e r . _ _ p l u s H a n d l e r

    def __plusHandler ( self ):
        """Increment the parameter.
        """

        #-- 1 --
        # [ if self.__scaleVar has a value &lt; MAX_BYTE _&gt;
        #     self.__scaleVar  +=  1
        #   else -> I ]
        oldValue  =  self.__scaleVar.get()
        if  oldValue &lt; MAX_BYTE:
            self.__scaleVar.set ( oldValue + 1 )
            if  self.__callback is not None:
                self.__callback()
</programlisting>
    </section> <!--ParamSlider-plusHandler-->
    <section id='ParamSlider-minusHandler'>
      <title><code >ParamSlider.__minusHandler()</code >: Decrement the
      parameter</title>
      <para>
        This method is called when the user clicks the &#x201c;-&#x201d;
        button to decrement the parameter.
      </para>
      <programlisting role='outFile:huey'
># - - -   P a r a m S l i d e r . _ _ m i n u s H a n d l e r

    def __minusHandler ( self ):
        """Increment the parameter.
        """

        #-- 1 --
        # [ if self.__scaleVar has a value &gt; 0 ->
        #     self.__scaleVar  -=  1
        #   else -> I ]
        oldValue  =  self.__scaleVar.get()
        if  oldValue &gt; 0:
            self.__scaleVar.set ( oldValue - 1 )
            if  self.__callback is not None:
                self.__callback()
</programlisting>
    </section> <!--ParamSlider-minusHandler-->
    <section id='ParamSlider-scaleHandler'>
      <title><code >ParamSlider.__scaleHandler()</code >: Somebody
      dragged the scale</title>
      <para>
        This method is called whenever the slider in the <code
        >Scale</code > widget gets moved.
      </para>
      <programlisting role='outFile:huey'
># - - -   P a r a m S l i d e r . _ _ s c a l e H a n d l e r

    def __scaleHandler ( self, value ):
        """Handler for self.__scale
        """
</programlisting>
      <para>
        Whenever the scale changes, we must notify the external
        observers by calling the callback function in <code
        >self.__callback</code >.  The callback doesn't take
        any arguments.
      </para>
      <programlisting role='outFile:huey'
>        #-- 1 --
        if  self.__callback is not None:
            self.__callback()
</programlisting>
    </section> <!--ParamSlider-scaleHandler-->
  </section> <!--class-ParamSlider-->
  <section id='class-Swatch'>
    <title><code >class Swatch</code >: Font and color samples</title>
    <para>
      This class is a compound widget containing only two internal
      widgets.
    </para>
    <itemizedlist>
      <listitem>
        <para>
          On top is a large <code >Text</code > widget that displays
          some text in the currently selected font using the currently
          selected foreground color, and its background color is the
          currently selected background color.
        </para>
      </listitem>
      <listitem>
        <para>
          Below that is a <code >FontSelect</code > widget that
          allows the user to select different text fonts.  For a
          link to this compound widget's documentation, see <xref
          linkend='imports' />.
        </para>
      </listitem>
    </itemizedlist>
    <para>
      Here is the class interface.
    </para>
    <programlisting role='outFile:huey'>
# - - - - -   c l a s s   S w a t c h

class Swatch(Frame):
    """Compound widget for text/background display and font selection.

      Exports:
        Swatch ( parent, bg, fg ):
          [ (parent is a Frame) and
            (bg is the initial background color as a Color instance) and
            (fg is the initial foreground color as a Color instance) ->
              parent  :=  parent with a new Swatch widget added but not
                          gridded
              return that new Swatch widget ]
</programlisting>
      <para>
        These methods set either the foreground or background
        color; see <xref linkend='Swatch-setTextColor' /> and
        <xref linkend='Swatch-setBgColor' />.
      </para>
      <programlisting role='outFile:huey'
>        .setTextColor ( color ):
          [ color is a color as a Color instance ->
              self  :=  self with the text displayed in that color ]
        .setBgColor ( color ):
          [ color is a color as a Color instance ->
              self  :=  self with the background set to color ]

      Internal widgets:
        .__text:          [ a Text widget ]
        .__fontSelect:    [ a FontSelect widget ]

      Grid plan:  One vertical column.
</programlisting>
      <para>
        The internal state includes the current foreground and
        background colors, and also the current font.
      </para>
      <programlisting role='outFile:huey'
>      State/Invariants:
        .__textColor:   [ current text color as a Color ]
        .__bgColor:     [ current background color as a Color ]
        .__font:        [ current font as a tkFont.Font ]
    """
</programlisting>
    <section id='Swatch-constants'>
      <title>Class constants</title>
      <para>
        These class variables contain constants used to
        configure the widget.
      </para>
      <para>
        <code >SWATCH_TEXT</code > is the sample text that
        appears in the swatch initially.
      </para>
      <programlisting role='outFile:huey'
>    SWATCH_TEXT  =  (
        "The quick brown fox jumps over\n"
        "the lazy dog.\n"
        "ABCDEFGHIJKLMNOPQRSTUVWXYZ\n"
        "abcdefghijklmnopqrstuvwxyz\n"
        "0123456789 !\"#$%&amp;'()*+,-./\n"
        ":;&lt;=&gt;?@[\\]^_/{|}~" )
</programlisting>
      <para>
        These two constants specify the width in characters of the <code
        >Text</code > widget, and its height in lines.
      </para>
      <programlisting role='outFile:huey'
>    SWATCH_WIDE  =  40        # Width of the color swatch in characters
    SWATCH_HIGH  =  8         # Height of the color swatch in lines
</programlisting>
      <para>
        The next constant specifies the number of font family names that
        will be visible at once in the font selector widget.
      </para>
      <programlisting role='outFile:huey'
>    FONT_FAMILIES  =  15
</programlisting>
    </section> <!--Swatch-constants-->
    <section id='Swatch-setTextColor'>
      <title><code >Swatch.setTextColor()</code ></title>
      <para>
        This method sets the text (foreground) color in the <code
        >Text</code > widget.  The text color of a <code >Text</code >
        widget is its <code >"fg"</code > (foreground) attribute.
        The <code >str()</code > function on a <code >Color</code >
        instance returns the color's name in the <code >"#RRGGBB"</code
        > form.
      </para>
      <programlisting role='outFile:huey'
># - - -   S w a t c h . s e t T e x t C o l o r

    def setTextColor ( self, color ):
        """Sets the text color of self.__text.
        """
        self.__text["fg"]  =  str(color)
</programlisting>
    </section> <!--Swatch-setTextColor-->
    <section id='Swatch-setBgColor'>
      <title><code >Swatch.setBgColor()</code ></title>
      <para>
        Similar to <xref linkend='Swatch-setTextColor' />, but the
        background color is the <code >"bg"</code > attribute.
      </para>
      <programlisting role='outFile:huey'
># - - -   S w a t c h . s e t B g C o l o r

    def setBgColor ( self, color ):
        """Sets the background color of self.__text.
        """
        self.__text["bg"]  =  str(color)
</programlisting>
    </section> <!--Swatch-setBgColor-->
    <section id='Swatch-init'>
      <title><code >Swatch.__init__()</code >: Constructor</title>
      <para>
        Call the parent constructor, then create the widgets;
        see <xref linkend='Swatch-createWidgets' />.
      </para>
      <programlisting role='outFile:huey'
># - - -   S w a t c h . _ _ i n i t _ _

    def __init__ ( self, parent, bg, fg ):
        """Constructor for Swatch.
        """
        #-- 1 --
        # [ parent  :=  parent with a new Frame added
        #   self  :=  that Frame ]
        Frame.__init__ ( self, parent )

        #-- 2 --
        self.__textColor  =  fg
        self.__bgColor  =  bg

        #-- 3 --
        # [ self  :=  self with all internal widgets added and gridded ]
        self.__createWidgets()
</programlisting>
      <para>
        At this point the <code >FontSelect</code > widget contains an
        initial font, so we can set up the <code >Text</code > widget to
        use that font.  Then we place some sample text into the <code
        >Text</code > widget.
      </para>
      <programlisting role='outFile:huey'
>        #-- 4 --
        # [ self.__text  :=  self.__text using the font from
        #                    self.__fontSelect ]
        self.__text["font"]  =  self.__fontSelect.get()

        #-- 5 --
        # [ self.__text  :=  self.__text with some sample text added ]
        self.__text.insert ( END, self.SWATCH_TEXT )
</programlisting>
    </section> <!--Swatch-init-->
    <section id='Swatch-createWidgets'>
      <title><code >Swatch.__createWidgets()</code ></title>
      <para>
        For the widget layout, see <xref linkend='class-Swatch' />.
      </para>
      <programlisting role='outFile:huey'
># - - -   S w a t c h . _ _ c r e a t e W i d g e t s

    def __createWidgets ( self ):
        """Create and grid all internal widgets.
        """
</programlisting>
      <para>
        First comes the <code >Text</code > widget.
        The <code >wrap=NONE</code > operation causes the text to be
        cropped if it won't fit in the window.
      </para>
      <programlisting role='outFile:huey'
>        #-- 1 --
        # [ self  :=  self with a new Text widget added
        #   self.__text  :=  that widget ]
        self.__text  =  Text ( self,
            bg=self.__bgColor, fg=self.__textColor,
            width=self.SWATCH_WIDE,
            height=self.SWATCH_HIGH )
        rowx  =  0
        self.__text.grid ( row=rowx, column=0, sticky=W )
</programlisting>
      <para>
        Next is the <code >FontSelect</code > widget.  For the
        documentation on this widget, see <xref linkend='imports'
        />.  Its callback, <xref linkend='Swatch-fontHandler' />,
        is called whenever the user changes the font in any way.
      </para>
      <programlisting role='outFile:huey'
>        #-- 2 --
        # [ self  :=  self with a new FontSelect widget added that
        #       calls self.__fontHandler when the font is changed
        #   self.__fontSelect  :=  that widget ]
        self.__fontSelect  =  FontSelect ( self,
            font=BUTTON_FONT,
            listCount=self.FONT_FAMILIES,
            observer=self.__fontHandler )
        rowx  +=  1
        self.__fontSelect.grid ( row=rowx, column=0, sticky=W )
</programlisting>
    </section> <!--Swatch-createWidgets-->
    <section id='Swatch-fontHandler'>
      <title><code >Swatch.__fontHandler()</code >: Handle a font
      change</title>
      <para>
        This method is called when the user changes the font displayed
        in the <code >FontSelect</code > widget.  It changes the <code
        >Text</code > widget to use that font.
      </para>
      <programlisting role='outFile:huey'
># - - -   S w a t c h . _ _ f o n t H a n d l e r

    def __fontHandler ( self, font ):
        """Handler for font changes.
        """
        self.__text["font"]  =  font
</programlisting>
    </section> <!--Swatch-fontHandler-->
  </section> <!--class-Swatch-->
  <section id='epilogue'>
    <title>Code epilogue</title>
    <para>
      The last lines of the script will actually run the
      application&#x2014;provided it has been run as a script, in
      which case Python will set the built-in <code
      >__name__</code > variable to <code >"__main__"</code >.
      If another script wishes to import this script in order to
      reuse some of its pieces, <code >name</code > will not be
      set to <code >"__main__"</code > while the script is being
      read, and in that case <code >main()</code > will not be
      called.
    </para>
    <programlisting role='outFile:huey'
>#================================================================
# Epilogue
#----------------------------------------------------------------

# [ if this file is being run as a script ->
#     run an instance of the Application class
#   else -> I ]
if  __name__ == '__main__':
    main()
</programlisting>
  </section> <!--epilogue-->
</article>

