Dr. Dobb's is part of the Informa Tech Division of Informa PLC

This site is operated by a business or businesses owned by Informa PLC and all copyright resides with them. Informa PLC's registered office is 5 Howick Place, London SW1P 1WG. Registered in England and Wales. Number 8860726.


Channels ▼
RSS

Open Source

An Object-Oriented UI for Perl


Jun00: An Object-Oriented UI for Perl

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

Back to Article

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

Back to Article

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

Back to Article

Listing Four

 ($self -> window) -> SUPER::bind($self -> window, `<Configure>',
                                     [\&ws_save_size, Ev(`W'),
                                      Ev(`h'), Ev(`w'),
                                      Ev(`x'), Ev(`y')]);

Back to Article

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

Back to Article

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;

Back to Article

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

Back to Article

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







Back to Article


Related Reading


More Insights






Currently we allow the following HTML tags in comments:

Single tags

These tags can be used alone and don't need an ending tag.

<br> Defines a single line break

<hr> Defines a horizontal line

Matching tags

These require an ending tag - e.g. <i>italic text</i>

<a> Defines an anchor

<b> Defines bold text

<big> Defines big text

<blockquote> Defines a long quotation

<caption> Defines a table caption

<cite> Defines a citation

<code> Defines computer code text

<em> Defines emphasized text

<fieldset> Defines a border around elements in a form

<h1> This is heading 1

<h2> This is heading 2

<h3> This is heading 3

<h4> This is heading 4

<h5> This is heading 5

<h6> This is heading 6

<i> Defines italic text

<p> Defines a paragraph

<pre> Defines preformatted text

<q> Defines a short quotation

<samp> Defines sample computer code text

<small> Defines small text

<span> Defines a section in a document

<s> Defines strikethrough text

<strike> Defines strikethrough text

<strong> Defines strong text

<sub> Defines subscripted text

<sup> Defines superscripted text

<u> Defines underlined text

Dr. Dobb's encourages readers to engage in spirited, healthy debate, including taking us to task. However, Dr. Dobb's moderates all comments posted to our site, and reserves the right to modify or remove any content that it determines to be derogatory, offensive, inflammatory, vulgar, irrelevant/off-topic, racist or obvious marketing or spam. Dr. Dobb's further reserves the right to disable the profile of any commenter participating in said activities.

 
Disqus Tips To upload an avatar photo, first complete your Disqus profile. | View the list of supported HTML tags you can use to style comments. | Please read our commenting policy.