Robert is maintainer of the Linux FAQ on the Internet. He can be contacted at [email protected].
While much has been written about Perl's capabilities as a glue language for system programming, its semantic capabilities and interpreted nature make it an ideal candidate for object-oriented programming, too. Yet Perl's object-oriented features are often overlooked, partly because the language does not require their use, and also because the language's command-line user interface does not encourage programmers to view the language in terms of objects.
If you work with OO languages, you are likely accustomed to user-interface objects (class browsers and text workspaces) written in the language itself, which highlight the object-oriented features of the language's internal structures. However, neither browser nor workspace are part of the standard Perl distribution. In this article, I'll show one way that Perl's features can provide subclass inheritance and methods that access an object's data, and how they contribute to Perl's usefulness in object-oriented projects.
There are many features that a graphical workspace could have. The Workspace.pm module (available electronically; see "Resource Center," page 5) presented here implements only a few of them. Instead of relying on Perl's text-mode UI facilities, the Workspace.pm module relies on Perl/TK, a package of C and Perl libraries that provide an API for the Tk GUI library (http://www .cpan.org/).
Again, Perl does not enforce object-oriented programming techniques, and common Perl data types such as scalars and hashes do not belong to a class hierarchy. Formal class membership is declared with the Perl keyword ISA. In the case of a Workspace, I declare it to be a subclass of a Perl/Tk text widget with the declaration:
@ISA = qw(Tk::TextUndo);
Listing One is the object class hierarchy for a Workspace. Subclasses are separated by double colons. By convention, Perl classes conform to the hierarchy of their source files in a directory structure, but again, this is not strictly necessary.
Another keyword for specifying class membership is bless, which identifies an object as an instance of a class. Objects are typically blessed into a class in the constructor. Constructor functions in Perl are often named new, but, as with many other features of the language, Perl does not enforce this convention. However, when instantiating classes, PerlTk uses the Construct widget declaration, and looks for the Class::new function when a widget is instantiated. Listing Two shows the global class declarations and part of the Workspace constructor.
The bless function uses the class of the object that called the constructor to identify the instance. In Perl, shift returns the first argument that is passed to the function from the argument list and increments the head of the list to point to the next argument. The keyword self refers to the object itself. Almost universally, objects are Perl hash tables, because they lend themselves to random-access referencing.
Perl does not enforce the use of methods to access instance variable data, and in the constructor itself, you access the data directly. The statement:
$self -> {window} -> {parent} = $self
requires some explanation. Generally, the parent of a window object is used for sub-widgets. Here, however, you use the variable to refer to the complete object. The reference is useful when writing callback functions because often the window widget receives a user input event, such as a resize or move event from the window manager, but the Workspace object must process the event.
You can access the object's data directly within the Workspace.pm module itself, but other objects might need to use methods to access the data. This selection would depend on how difficult it would be to implement the reference, and how well behaved you would like the object to be. In any event, instance methods are not difficult to write. This is the method to set or retrieve the Workspace's Window object:
sub window {
my $self = shift;
if (@_) { $self -> {window} = shift }
return $self -> {window}
}
Using a method, any object can retrieve a Workspace's text, as in:
$text = $workspace -> window -> Get('1.0', end);
The Get method is used by many widgets, and the return value and arguments differ depending on the type of object; that is, a Listbox requires a numeric index, while a Text widget requires line and character-within-line information.
Perl/Tk
You can delay event binding nearly until run time, associating events like keystrokes with methods only if the run-time state of the object calls for it. Although the keystroke shortcuts for the menu functions are hard-coded, it would be a simple matter to include them in the text itself to be evaluated at run time, using Perl's eval function. This binding can be performed on all instances of the class, or on a per-object basis if desired.
Interfacing Perl and Tk
The interface that Perl/Tk presents to you relies on Perl's object-oriented programming features also. Each Tk widget is a subclass of the Tk class. Widget, MainWindow, Wm (window manager), and Image, have their own constructor, class, and instance methods. However, many of the classes are interdependent, and objects can be created to handle special events. The main user-interface widgets in the Perl/Tk widget hierarchy are shown in Listing Three. In this implementation, the class hierarchy corresponds to the subdirectory hierarchy where Perl/Tk is installed (/usr/lib/perl5/ site_perl/ by default).
The Tk class provides services to the widgets through the MainWindow object that include event handling and user input binding. Each application must have a MainWindow object, at least, because the class receives event messages from the system.
The Event Loop
A call to the MainLoop function begins the actual program execution. This function is the standard entry point to the Perl/Tk kernel. Perl/Tk also has methods that allow user-defined events, such as key bindings and window callbacks.
In addition to functions called when users select a menu item, virtual callbacks allow the environment to communicate with the workspace with relative ease. For example, the workspace can record the window size and store the window geometry for use the next time it is opened. First, it is necessary to create the event binding, as in Listing Four. The bind methods of the TextUndo and MainWindow classes are equivalent from the workspace's perspective -- all widgets use the same binding function of the PerlTk object superclass, but it is the MainWindow object that receives resizing events, called Configure events, from the X display server. Therefore, you want to bind the event to the display window widget. Perl/Tk provides a parameterized calling protocol between objects and the environment using the Ev() function call. Once you have bound the workspace method ws_save_ size to the event, the code for the method (Listing Five) is straightforward. The only caveat is that you need to distinguish a Configure event that is received by the Tk::MainWindow object as opposed to the Tk::TextUndo object, due to the text widget's ability to process Configure events also.
Implementing the Workspace Object
Creating a workspace object is also straightforward: A call to the Workspace:: create function writes a template file with the name of the Workspace. The template contains only the label for the Workspace text, and the function calls that are strictly necessary at run time. The Workspace .pm library module contains most of the code that need not be part of the Workspace object itself. Listing Six is the default Workspace object. I chose the dimensions to approximate an 80×24 character display, and to bless the Workspace as its own class, due to the design of the Perl/Tk interface, so that the Workspace would appear on the display under its name, instead of the generic name "Workspace."
The methods that access the Perl/Tk widgets use the same conventions as the Workspace.pm methods, because, again, the Perl objects that communicate with the Tk display library are implemented as hash table data.
Using the Filesystem as Persistent Memory
Several Perl library modules exist that store objects as persistent memory in the filesystem. Their intended applications are for well-defined database objects. The Workspace object is only an elementary implementation for storing text data -- much more work could be done to store text format codes, hyperlinks, display parameters, and version information, for example.
Again, it would be possible, in some future version, to provide fields within the data, specified in Perl, and bound at run time. For the present, the Workspaces can import and export plain ASCII text. Listing Seven shows the function used to export text, which can be called as a function from within the library code itself, or as a method from an external object. Another way the current Workspace implementation communicates with the environment is through the X clipboard. In this implementation, the standard Cut, Copy, and Paste functions are provided. It would be easy to extend the clipboard functions to provide yet another form of persistent memory, as appropriate for the X display server. Listing Eight is an abbreviated method that pastes text into the workspace from the clipboard. Much more work can be done with the X clipboard because the X protocol allows for multiple selections, as described in the X11 ICCCM specification.
Provided that you can delay the binding of events to Workspace functions as close as possible to run time, you can maintain much of the flexibility in the Workspace specification that is inherent in the Perl language.
DDJ
Listing One
Tk::Widget Tk::TextUndo Tk::Workspace
Listing Two
@ISA = qw(Tk::TextUndo); Construct Tk::Widget `Workspace'; sub new { my $proto = shift; my $class = ref( $proto ) || $proto; my $self = { window => new MainWindow, textspace => undef, name => ((@_)?@_:'Workspace'), textfont => $defaulttextfont, # default is approximate width and height of 80x24 char. text widget width => $w, height => $h, filemenu => undef, editmenu => undef, helpmenu => undef, menubar => undef, text => [] # The text itself. }; bless($self, $class); $self -> {window} -> {parent} = $self; ... etc. ... }
Listing Three
Tk Derived Debug DirList FloatEntry (Also derives from Entry) MenuBar (Also derives from Menu) OptionMenu (Also derives from Menubutton) DragDrop DropSite Widget Button RadioButton Canvas Checkbutton CmdLine (resource) ColorSelect Entry Frame Adjuster BrowseEntry LabFrame LabRadioButton Tiler Font HList Label ListBox MenuButton Message NBFrame Notebook Scale Scrollbar TList Table Text TextUndo *Workspace* TixGrid Toplevel Balloon Dialog msgBox DialogBox (also inherits from Frame) ErrorDialog FBox (File selection dialog) FileSelect IconList MainWindow Text ROText Image Bitmap Photo Animation Pixmap Wm (Window manager) Menu
Listing Four
($self -> window) -> SUPER::bind($self -> window, `<Configure>', [\&ws_save_size, Ev(`W'), Ev(`h'), Ev(`w'), Ev(`x'), Ev(`y')]);
Listing Five
sub ws_save_size { my $self = shift; my $widget = shift; my $height = shift; my $width = shift; # We need to do this because TextUndo subclasses MainWindow, # and both of them send Configure messages. if ( $self =~ /Tk::TextUndo/ ) {return;} # We have to ref the parent widget directly because # MainWindows don't define a parent widget value. $self -> {parent} -> ws_width($width); $self -> {parent} -> ws_height($height); }
Listing Six
#!/usr/bin/perl my $text=''; my $h='351'; my $w='565'; my $name=''; use Tk::Workspace; :Workspace); use strict; use Tk; use FileHandle; use Env qw(HOME); my $workspace = Tk::Workspace -> new; $workspace -> name($name); $workspace -> menubar -> pack (-anchor => 'w', -fill => 'x'); $workspace -> textspace -> insert ( 'end', $text ); $workspace -> textspace -> pack; bless($workspace,ref('Tk::Workspace')); $workspace -> bind; $workspace -> window -> geometry( $w . 'x' . $h ); MainLoop;
Listing Seven
sub ws_export { my $self = shift; my $filedialog; my $filename; my $filename; my $fh = new IO::File; $filedialog = ($self -> {window}) -> FileSelect ( -directory => `.' ); $filename = $filedialog -> Show; $fh -> open( "+> $filename" ) or &filenotfound( $self ); print $fh ($self -> {textspace}) -> get( `1.0', `end' ); close $fh; }
Listing Eight
sub ws_paste { my $self = shift; my $selection; my $point; $selection = ($self -> {textspace}) -> clipboardGet; $point = ($self -> {textspace}) -> index("insert"); ($self -> {textspace}) -> insert( $point, $selection); return $selection; }