



	   ################################################
	   #                                              #
	   # ##   ## ###### ####### ##    ## ## ##     ## #
	   # ##   ## ##  ## ##      ###   ## ##  ##   ##  #
	   # ##   ## ##     ##      ####  ## ##   ## ##   #
	   # ##   ## ###### ######  ## ## ## ##    ###    #
	   # ##   ##     ## ##      ##  #### ##   ## ##   #
	   # ##   ## ##  ## ##      ##   ### ##  ##   ##  #
	   # ####### ###### ####### ##    ## ## ##     ## #
	   #                                              #
	   ################################################






 
 
 
	 The following paper was originally presented at the

		     Third Annual Tcl/Tk Workshop
		 Toronto, Ontario, Canada, July 1995

	   sponsored by Unisys, Inc. and USENIX Association



	    It was published by USENIX Association in the
		  1995 Tcl/Tk Workshop Proceedings.
 
 
 
 
        For more information about USENIX Association contact:
 
                   1. Phone:    510 528-8649
                   2. FAX:      510 548-5738
                   3. Email:    office@usenix.org
                   4. WWW URL:  http://www.usenix.org
 
 
 
 
 
^L
                       The New [incr Tcl]:
            Objects, Mega-Widgets, Namespaces and More

                       Michael J. McLennan

                     AT&T Bell Laboratories
                    1247 S. Cedar Crest Blvd.
                      Allentown, PA 18103
                    michael.mclennan@att.com


Abstract

The allure of using Tcl/Tk is the way that applications come together
with relative ease.  A sticky note facility can be put together in an
hour.  A simple video game can be created in an afternoon.  But as
applications get larger, Tcl/Tk code becomes more and more difficult
to understand, maintain and extend.  [incr Tcl] provides the extra
language support needed to build large Tcl/Tk applications.  The
latest release offers better performance and a host of new features.
The global namespace of commands and variables can now be partitioned
into smaller namespaces acting as subsystems.  The class facility
extends the concept of namespaces to support unique instances of
objects with similar characteristics.  This technology can be used
to create mega-widgets and other high-level building blocks that
help applications come together even faster than before, with better
structure in the resulting code.


Introduction

Object-oriented programming is not an alternative to 
procedural programming, it is an evolution beyond it.  
Indeed, object-oriented programming is full of proce-
dures, but these procedures are organized around the 
data in the application, in a way that makes the system 
easier to maintain and extend.

[incr Tcl] is an object-oriented extension of the 
Tcl language.  It was conceived two years ago [1] to pro-
vide better support for building large Tcl/Tk applica-
tions.  Since then, it has been used to build support 
software for telephone communication and cellular 
phone networks. It is the backbone of a distributed com-
putational chemistry system.  It controls a particle accel-
erator.  It supports software on Wall Street. It even 
supports the flight software that will take the Pathfinder 
to Mars [2].

The latest release of [incr Tcl] extends the Tcl 
language even further. It has been redesigned and 
rewritten to provide better packaging facilities and a 
host of new features:

* NAMESPACES

[incr Tcl] contains a namespace facility 
patterned after the proposal by Howlett [3].  It can 
be used to package data, procedures, classes, and 
other namespaces into reusable building blocks that 
are easily integrated into other applications.

* MEGA-WIDGETS

[incr Tk] provides a framework for combining 
Tk widgets into a larger building block.  The 
resulting widget looks and acts like a regular Tk 
widget, but provides a higher level of functionality.

* PUBLIC / PROTECTED / PRIVATE

The accessibility of both procedures and data can 
be "public" (open to other classes/namespaces), 
"protected" (open only to friendly classes/
namespaces), or "private" (closed to all other 
classes/namespaces).

* SUPPORT FOR INTEGRATING C/C++ CODE

Procedures can be implemented with Tcl code, or 
with C/C++ code.

* DYNAMIC LOADING

C/C++ functions and extensions can be 
dynamically loaded on architectures that support 
shared objects and libraries.

* MORE DYNAMIC CLASSES

The bodies of class methods and procs can be 
redefined at any point, making it easier to do 
interactive debugging and development.

* BETTER PERFORMANCE

The performance of [incr Tcl] is now on par 
with Tcl, and memory consumption has been 
drastically reduced.

* NO MORE MULTIPLE INTERPRETERS

Previous versions of [incr Tcl] used a separate 
interpreter to maintain the namespace for each 
class.  The current version layers the class 
mechanism on top of the namespace facility.



[incr Tcl] does not change the fundamental character of Tcl.
It does not change the syntax or semantics of the language.
Rather, it adds the ability to package the usual variables
and procedures into a more understandable and reusable form.
In this paper, we show how a simple Tk "help" facility can
be transformed into a better building block by packaging it
first in a namespace, then as a mega-widget.  Finally, we 
extend its capabilities by introducing supporting classes.


Tk "Help" Facility

Suppose that we have an application that requires 
some on-line help. It is easy to build such a facility with 
Tcl/Tk.  We could combine a text widget and a few buttons
to create the rudimentary help window shown in Figure 1.
We will go one step further, and create some procedures to
access this facility.  The command:

  help_topic file

loads a file of help information into the viewer.  The 
help facility should keep track of the list of topics that 
have been loaded, so that at any point the command

  help_back

will take the user back to the previous help page.  We 
also create the command:

  help_show

that is used internally by help_topic, to make sure 
that the help viewer is visible whenever a new topic is 
loaded.  The code used to implement this facility is 
shown in Figure 2.

Most of this code is quite straightforward.  The procedure
help_show creates the help window if it does not already
exist, and makes it visible on the screen.  The procedure
help_topic reads text from the specified file and loads it
into the help viewer.  It also adds the file name to the
list of topics that have been loaded into the viewer.  This
list is maintained as a global variable HelpFiles, so that
it can be accessed by other help procedures.  For example,
the procedure help_back finds the previous file name near
the end of the list and loads this into the viewer.

Notice that we have added the prefix "help_" to each of
these commands. This makes it easier to recognize the
relationship between them, and also makes it less likely
that these command names will clash with others in the
application.  Also, we are careful to name global variables
like HelpFiles with a special "Help" prefix.  But although
this naming convention makes accidental name clashes less
likely, it cannot prevent them.  Moreover, we cannot
guarantee that other procedures in our application will
not sabotage our interface and access global variables
directly.  Suppose that we want to protect certain commands
like help_show that are used internally by the package, 
but that are not meant to be part of the public interface.  
With ordinary Tcl, the best we can hope to do is add a 
special prefix to the command name (e.g., "_help_show")
and discourage its use.


Using Namespaces

[incr Tcl] offers a way of packaging together 
procedures and global variables, and controlling access 
to them.  Our familiar help facility can be wrapped in 
the confines of a namespace, as shown in Figure 3. 
Members that are "public" are accessible from any other 
namespace, including the global namespace.  Members 
that are "protected" are accessible only if other 
namespaces request special access.  Members that are 
"private" are completely hidden from other namespaces.

Once a namespace is created, the public members 
within it can be accessed using the special scope "::" 
qualifier.  From the global namespace, a topic can be 
displayed in our help viewer as follows:

  help::topic file

It is not possible, however, to access the private 
HelpFiles variable from the global namespace, even 
if the scope qualifier is used:

  # this will fail:
  set help::HelpFiles ""

Within the confines of a namespace, procedures and 
global variables belonging to the namespace can be 
accessed directly.  Within the body of help::topic, 
for example, the command show requires no special 
"help::" qualifier.

Any commands that are created within the context of 
a namespace belong to that namespace.  For proc decla-
rations such as those shown in Figure 3, this would be 
expected.  But this also implies that any widget created 
within a namespace has an access command that 
belongs to the namespace.  For example, the toplevel 
window .help can only be accessed via the ".help" 
command in the help namespace:

  namespace help {
      .help configure -bg gray
  }

In effect, the access command is protected by the 
namespace as a detail of its implementation.

Of course, the window name ".help" is still recognized
everywhere within the application.  It is still possible
to query information about the widget from any context:

  set w [winfo reqwidth .help]
  set h [winfo reqheight .help]

For some applications it may be useful to know 
where a widget's access command resides.  This is 
referred to as the widget's "locality", and it can be
queried as follows:

  % locality widget .help
  ::help

Commands from one namespace can be integrated 
into another using the namespace "import" feature.  For 
example, we can integrate the help namespace into the 
global namespace by invoking the following command 
at the global scope:

  import add help

From this point on, commands like topic and 
back can be accessed without the special "help::" 
qualifier, as if they were defined in the global 
namespace.  But if a command like back is defined in 
the global namespace, it will override the command 
imported from the help namespace.  The scope quali-
fier can be used in cases like this, to access a command 
in a specific namespace.

Note that the import command only integrates the public
members within a namespace; protected and private members
remain hidden. But sometimes namespaces are designed to
work in cooperation.  It is useful to have members which
are available to some namespaces, but hidden from all others.
Such members are declared as "protected", and can be accessed
by any other namespace that imports in a "protected" mode.  
For example, if we import the help namespace in a 
"protected" mode at the global namespace:

  import add {help protected}

then we can access the protected command show from the
global scope. There is no way to import in a "private"
mode, so private members always remain hidden from other
namespaces.

Suppose that we create another namespace like "plotter"
which will provide plotting facilities based on the BLT
toolkit [4] extensions.  Suppose that we want to include
on-line help in this facility, but we want to make sure
that the "plotter" help is separate from any other "help"
included in the application.  We can include the help
namespace as a child within the plotter namespace:

  namespace plotter {
      namespace help {
          ...same as Figure 3...
      }
  }

Within the plotter namespace, help topics can be viewed
using "help::topic".  But at the global scope this same
command must be referenced as "plotter::help::topic"; this
distinguishes the plotter help facility from another help
facility that might be included in the same application.

By default, each namespace imports from its parent 
in the "public" mode. This is why a command like set, 
which is really defined at the global scope, can be used 
transparently within a child namespace like plotter 
or plotter::help.  Parents, on the other hand, do 
not automatically import from their children.  If they 
did, the global scope would revert to a hodgepodge of 
all (public) members in the application.

Without the import facility, namespaces would be little
more than a fancy naming convention, swapping "help::topic"
for "help_topic" in our original example.  The import
facility, however, allows namespaces to act like little
building blocks that can be glued together in different
ways to provide more functionality.


Creating Mega-Widgets

Suppose that we want to clone a help window, so that the
user can view two help pages side by side.  Our help
namespace was only designed to support one help window.
It would be helpful if we could use the namespace facility
like a cookie cutter, to create lots of similar but unique
objects, each one parameterized by its own bundle of data.
This is precisely what the [incr Tcl] class facility
provides. A class is a special kind of namespace shared by
a group of related objects.  Each object has its own bundle
of data, and is manipulated by special procedures called
"methods" defined within the class. One class can inherit
the characteristics of another, in much the same way that
one namespace can import the commands of another.

But simply defining a class of HelpWin objects with methods
like topic and back is not enough.  We would like to make
these objects look like ordinary Tk widgets, which have
configuration options like "-cursor" and "-background".
When we set an option like "-background", we would like
all of the component widgets within the help window to
change color.

[incr Tk] provides a framework for building composite
"mega-widgets" using [incr Tcl] classes.  It defines a
set of base classes that are specialized to create all
other widgets.  Among these, itk::Toplevel provides all
of the functionality needed by a toplevel window.  The
details of [incr Tk] are described elsewhere [5], so we
simply provide an example here.

A HelpWin class based on itk::Toplevel is presented in
Figure 4. The "constructor" is invoked whenever a new
widget is created.  It creates all of the internal
components within a specific help window and registers
their configuration options on a master list.  Each
component is created with a symbolic name; for example,
"hull" is the symbolic name of the toplevel window
representing the widget, which is created automatically
by the base class.  The window path name for any component
can be found by accessing the itk_component array defined
in the base class.  For example:

  $itk_component(hull)

is the window path name for the "hull" component.

Methods are defined within the class in much the 
same way that commands are defined within a 
namespace.  Variables like the private helpFiles list 
are also declared in a similar manner.  But unlike 
namespaces, the variables declared within a class are 
created for each object.  Each HelpWin object, there-
fore, has its own unique list of help files.

The real power of [incr Tk] comes from its automatic
management of components and configuration options.
Each HelpWin object acts like a high-level Tk widget
with a master list of configuration options:

  % HelpWin .help

  % .help configure
  {-activebackground activeBack-
  ground Foreground Black Black} {-
  activeforeground activeForeground 
  Background White White} {-back-
  ground background Background White 
  White} {-borderwidth borderWidth 
  BorderWidth 0 0} {-cursor cursor 
  Cursor {} {}} {-font font Font *-
  Courier-Medium-R-Normal-*-120-* *-
  Courier-Medium-R-Normal-*-120-*} 
  {-foreground foreground Foreground 
  Black Black} {-geometry geometry 
  Geometry {} {}} {-relief relief 
  Relief flat flat} {-textbg text-
  Background Background White White} 
  {-textfg textForeground Foreground 
  Black Black} {-width width Width 
  40 40}

When we change the "-foreground" and "-background"
options, the change is automatically propagated to
each of the internal components that declared those
options with a "keep" statement.  All of this
functionality comes for free from the base class 
itk::Toplevel via a simple "inherit" statement.


Not Everything is a Mega-Widget

Suppose that we want to extend our help facility to 
handle more than plain text files.  Suppose that we also 
want to support hypertext markup language (HTML) 
files, "man" pages, etc.  We could modify our topic 
method to contain a big "switch" statement, to handle 
each of the various formats allowed by our system.  The 
problem with such switch statements is that they tend to 
keep popping up at various places in a program, and 
they are hard to keep up to date.  For example, suppose 
we add a search method, to scan through the text of a 
help page and look for keywords.  We might need to 
search each of the page formats in a different manner, 
hence the need for another big switch statement.  Suppose
that later on we add another page format to our system.
We must be careful to track down each of the switch
statements and update it accordingly.

Instead, we can use object-oriented programming to 
provide the basis for an extensible system of page
formats.  We define a base class HelpPage that acts
as a common abstraction for all page formats, as shown
in Figure 5.  It maintains a variable text that contains
the raw text for the help page, and provides a method
convert that will create the commands needed to load 
information into a text widget.  We can create a generic 
help page as follows:

  HelpPage page1 "Some help text"

and load its contents into a text widget like this:

  eval [page1 convert .help.info]

We create a derived class HelpFile, also shown in 
Figure 5, to represent a plain page of help text loaded 
from a file.  This class inherits all of the text handling 
capability from HelpPage, and simply defines a constructor
that will load help information from a specified file.
We can create a file page as follows:

  HelpFile page2 info.txt

and load its contents into a text widget as before:

  eval [page2 convert .help.info]

In a similar manner, we could create many other 
page types, each inheriting its core abstraction from 
HelpPage.  More complicated page formats might 
override the default convert method with a specialized
version, for example, to parse HTML text and generate
the appropriate commands to load that information into
a text widget.

We could convert our HelpWin mega-widget to work with
HelpPage objects by updating the topic method as shown
in Figure 5.  This method first checks to see that page
contains the name of a valid HelpPage object, then pops
up the help viewer, and loads the internal "info"
component (i.e., the help viewer's text widget) with
the appropriate information.  Each HelpPage object
knows how to interpret its contents and communicate
with the text widget, so there is no need for a
switch statement to handle the various page formats.

Notice that the same code (e.g., load help text from a 
file) would appear in this program regardless of whether 
we use the "switch" approach or the object-oriented 
approach.  Using a switch statement organizes our code 
along procedural lines.  All of the code associated with a 
particular page format is scattered throughout the
program, buried in the switch statements associated with 
each operation.  In stark contrast, the object-oriented 
approach collects all of the code needed for each page 
format into a class definition for that format.  The
resulting code is easier to understand and maintain.

Not every problem lends itself to an object-oriented 
solution, but when a problem does, it is foolish to
overlook the object-oriented approach.


Conclusion

[incr Tcl] provides the language support needed to build
large Tcl/Tk applications.  The current release offers
better performance and a host of new features.  Namespaces
can be used to package procedures and global variables
as reusable building blocks.  Classes extend the namespace
concept to support unique instances of objects with similar
characteristics.  [incr Tk] provides the infrastructure
needed to construct mega-widgets, composite widgets with
high-level functionality that look and feel like the usual
Tk widgets.  Using all of this technology, applications can
be put together with high-level building blocks in a fraction 
of the normal development time, and the resulting code will
be easier to understand, maintain and extend.


References

[1]	M. J. McLennan, "[incr Tcl]:  Object-
Oriented Programming in Tcl," Proceedings of 
the Tcl/Tk Workshop, University of California at 
Berkeley, June 10-11, 1993.

[2]	D. E. Smyth, "Tcl and Concurrent Object-
Oriented Flight Software: Tcl on Mars," 
Proceedings of the Tcl/Tk 1994 Workshop, New 
Orleans, LA, June 23-25, 1994.

[3]	G. A. Howlett, "Packages:  Adding Namespaces 
to Tcl," Proceedings of the Tcl/Tk 1994 Workshop, 
New Orleans, LA, June 23-25, 1994.

[4]	G. A. Howlett, The BLT Toolkit, available by 
anonymous ftp from:
ftp.aud.alcatel.com:/tcl.

[5]	M. J. McLennan, "[incr Tk]:  Building 
Extensible Widgets with [incr Tcl]", 
Proceedings of the Tcl/Tk 1994 Workshop, New 
Orleans, LA, June 23-25, 1994. 


FIGURES:
Figure 1 - On-line help facility

------------------------------------------------------------------------
Figure 2 - Ordinary Tcl/Tk code used to implement the help facility

set HelpFiles {}

proc help_show {} {
    if {![winfo exists .help]} {
        toplevel .help
        text .help.info -wrap none -borderwidth 2 -relief sunken
        pack .help.info -side top -fill both -padx 5 -pady 5
        frame .help.cntls -borderwidth 1 -relief raised

        button .help.cntls.back -text "Back" -command help_back
        pack .help.cntls.back -side left -padx 20 -pady 8

        button .help.cntls.dismiss -text "Dismiss" \
            -command {wm withdraw .help}
        pack .help.cntls.dismiss -side right -padx 20 -pady 8
        pack .help.cntls -side bottom -fill x

        wm title .help "Help"
    }
    wm deiconify .help
}

proc help_topic {file} {
    global HelpFiles

    if {[catch "open $file r" fid] != 0} {
        error "can't open help file \"$file\""
    }
    set info [read $fid]
    close $fid

    help_show
    .help.info configure -state normal
    .help.info delete 1.0 end
    .help.info insert end $info
    .help.info configure -state disabled

    lappend HelpFiles $file
}

proc help_back {} {
    global HelpFiles

    set last [expr [llength $HelpFiles]-2]
    if {$last >= 0} {
        set file [lindex $HelpFiles $last]
        set HelpFiles [lrange $HelpFiles 0 [expr $last-1]]

        help_topic $file
    }
}
------------------------------------------------------------------------
Figure 3 -  Implementing help using the namespace facility in [incr Tcl]

namespace help {
    private variable HelpFiles {}

    protected proc show {} {
        if {![winfo exists .help]} {
            toplevel .help
            text .help.info -wrap none -borderwidth 2 -relief sunken
            pack .help.info -side top -fill both -padx 5 -pady 5
            frame .help.cntls -borderwidth 1 -relief raised

            button .help.cntls.back -text "Back" -command back
            pack .help.cntls.back -side left -padx 20 -pady 8

            button .help.cntls.dismiss -text "Dismiss" \
                -command {wm withdraw .help}
            pack .help.cntls.dismiss -side right -padx 20 -pady 8
            pack .help.cntls -side bottom -fill x

            wm title .help "Help"
        }
        wm deiconify .help
    }

    public proc topic {file} {
        global HelpFiles

        if {[catch "open $file r" fid] != 0} {
            error "can't open help file \"$file\""
        }
        set info [read $fid]
        close $fid

        show
        .help.info configure -state normal
        .help.info delete 1.0 end
        .help.info insert end $info
        .help.info configure -state disabled

        lappend HelpFiles $file
    }

    public proc back {} {
        global HelpFiles

        set last [expr [llength $HelpFiles]-2]
        if {$last >= 0} {
            set file [lindex $HelpFiles $last]
            set HelpFiles [lrange $HelpFiles 0 [expr $last-1]]

            topic $file
        }
    }
}
------------------------------------------------------------------------
Figure 4 -  Implementing help as an [incr Tk] mega-widget

class HelpWin {
    inherit itk::Toplevel

    constructor {args} {
        itk_component info {
            text $itk_component(hull).info -wrap none \
                -borderwidth 2 -relief sunken
        } {
            keep -cursor -font -width
            rename -background -textbg textBackground Background
            rename -foreground -textfg textForeground Foreground
        }
        pack $itk_component(hull).info -side top -fill both -padx 5 -pady 5

        itk_component cntls {
            frame $itk_component(hull).cntls -borderwidth 1 -relief raised
        } {
            keep -cursor -background
        }

        itk_component back {
            button $itk_component(hull).cntls.back -text "Back" -command "$this back"
        } {
            keep -cursor -background -foreground \
                -activebackground -activeforeground
        }
        pack $itk_component(hull).cntls.back -side left -padx 20 -pady 8

        itk_component dismiss {
            button $itk_component(hull).cntls.dismiss -text "Dismiss" \
                -command "wm withdraw $this"
        } {
            keep -cursor -background -foreground -activebackground -activeforeground
        }
        pack $itk_component(hull).cntls.dismiss -side right -padx 20 -pady 8

        pack $itk_component(hull).cntls -side bottom -fill x
        wm title $this "Help"

        eval configure $args
    }

    protected method show {} {
        wm deiconify $itk_component(hull)
    }

    public method topic {file} {
        ...similar to Figure 3...
    }

    public method back {} {
        ...similar to Figure 3...
    }

    private variable helpFiles {}
}
------------------------------------------------------------------------
Figure 5 -  Using HelpPage objects to represent various types of help pages

class HelpPage {
    constructor {{mesg ""}} {
        setText $mesg
    }

    method convert {widget} {
        set cmd {
            namespace [locality widget WIDGET] {
              WIDGET configure -state normal
              WIDGET delete 1.0 end
              WIDGET insert end {TEXT}
              WIDGET configure -state disabled
            }
        }
        regsub -all WIDGET $cmd $widget cmd
        regsub -all TEXT $cmd $text cmd
        return $cmd
    }

    protected method setText {mesg} {
        set text $mesg
    }

    private variable text {}
}

class HelpFile {
    inherit HelpPage

    constructor {file} {
        if {[catch "open $file r" fid] != 0} {
            error "can't open help file \"$file\""
        }
        setText [read $fid]
        close $fid
    }
}

class HelpWin {
    ...
    method topic {page} {
        if {[itcl_info objects $page -isa HelpPage] == ""} {
            error "not a help page: $page"
        }
        show
        eval [$page convert $itk_component(info)]
        lappend helpFiles $page
    }
    ...
}
------------------------------------------------------------------------
